From 6a027fd0efc6b9c280e56cce9b8eafd57bd880ac Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 07:25:54 +0700 Subject: [PATCH 01/18] kygy uhuhuh --- .../CheckPermutationTest.java | 14 ++++ .../ctci/arraysandstrings/IsUniqueTest.java | 13 ++++ .../ctci/arraysandstrings/OneAwayTest.java | 13 ++++ .../PalindromePermutationTest.java | 13 ++++ .../ctci/arraysandstrings/RotateMatrix.java | 72 +++++++++++++++++-- .../arraysandstrings/RotateMatrixTest.java | 14 ++++ .../StringCompressionTest.java | 14 ++++ .../arraysandstrings/StringRotationTest.java | 14 ++++ .../com/ctci/arraysandstrings/URLifyTest.java | 14 ++++ .../ctci/arraysandstrings/ZeroMatrixTest.java | 14 ++++ .../bitmanipulation/BinaryToStringTest.java | 14 ++++ .../ctci/bitmanipulation/ConversionTest.java | 14 ++++ .../ctci/bitmanipulation/DebuggerTest.java | 14 ++++ .../ctci/bitmanipulation/DrawLineTest.java | 14 ++++ .../bitmanipulation/FlipBitToWinTest.java | 14 ++++ .../ctci/bitmanipulation/InsertionTest.java | 14 ++++ .../ctci/bitmanipulation/NextNumberTest.java | 14 ++++ .../bitmanipulation/PairwiseSwapTest.java | 14 ++++ 18 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/OneAwayTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/StringRotationTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/URLifyTest.java create mode 100644 src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/BinaryToStringTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/ConversionTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/DebuggerTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/DrawLineTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/FlipBitToWinTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/InsertionTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/NextNumberTest.java create mode 100644 src/main/java/com/ctci/bitmanipulation/PairwiseSwapTest.java diff --git a/src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java b/src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java new file mode 100644 index 00000000..20ecaa7c --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java @@ -0,0 +1,14 @@ +package arraysandstrings; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class CheckPermutationTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(IIRFilter.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java b/src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java new file mode 100644 index 00000000..ce619e22 --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java @@ -0,0 +1,13 @@ +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class IsUniqueTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(IIRFilter.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/OneAwayTest.java b/src/main/java/com/ctci/arraysandstrings/OneAwayTest.java new file mode 100644 index 00000000..1b9e535d --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/OneAwayTest.java @@ -0,0 +1,13 @@ +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class OneAwayTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(IIRFilter.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java b/src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java new file mode 100644 index 00000000..eeb36daa --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java @@ -0,0 +1,13 @@ +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class PalindromePermutationTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(IIRFilter.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/RotateMatrix.java b/src/main/java/com/ctci/arraysandstrings/RotateMatrix.java index de43c56b..93adb5c8 100644 --- a/src/main/java/com/ctci/arraysandstrings/RotateMatrix.java +++ b/src/main/java/com/ctci/arraysandstrings/RotateMatrix.java @@ -1,16 +1,76 @@ package com.ctci.arraysandstrings; - +import java.util.ArrayList; +import java.util.List; + /** * @author rampatra * @since 2019-01-20 */ public class RotateMatrix { - - public static void rotateImage(int[][] pixels) { - + // Function to rotate the matrix in a clockwise + // direction + public static void + rotateMatrix(List > matrix) { + int n = matrix.size(); + + // Transpose the matrix + for (int i = 0; i < n; i++) { + for (int j = i; j < n; j++) { + int temp = matrix.get(i).get(j); + matrix.get(i).set(j, matrix.get(j).get(i)); + matrix.get(j).set(i, temp); + } + } + + // Reverse the columns + for (int i = 0; i < n; i++) { + for (int j = 0, k = n - 1; j < k; j++, k--) { + int temp = matrix.get(j).get(i); + matrix.get(j).set(i, matrix.get(k).get(i)); + matrix.get(k).set(i, temp); + } + } } - + + // Function to print the matrix + public static void + printMatrix(List > matrix) { + for (int i = 0; i < matrix.size(); i++) { + for (int j = 0; j < matrix.get(i).size(); j++) { + System.out.print(matrix.get(i).get(j) + + " "); + } + System.out.println(); + } + } + public static void main(String[] args) { - + List > matrix = new ArrayList<>(); + matrix.add(new ArrayList() { + { + add(1); + add(2); + add(3); + } + }); + matrix.add(new ArrayList() { + { + add(4); + add(5); + add(6); + } + }); + matrix.add(new ArrayList() { + { + add(7); + add(8); + add(9); + } + }); + System.out.println("Original matrix:"); + printMatrix(matrix); + rotateMatrix(matrix); + System.out.println("Rotated matrix:"); + printMatrix(matrix); } } diff --git a/src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java b/src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java new file mode 100644 index 00000000..9ec23e8b --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java @@ -0,0 +1,14 @@ +package arraysandstrings; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class RotateMatrixTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(IIRFilter.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java b/src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java new file mode 100644 index 00000000..882fea61 --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java @@ -0,0 +1,14 @@ +package arraysandstrings; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class StringCompressionTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(StringCompression.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/StringRotationTest.java b/src/main/java/com/ctci/arraysandstrings/StringRotationTest.java new file mode 100644 index 00000000..0c2c3245 --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/StringRotationTest.java @@ -0,0 +1,14 @@ +package arraysandstrings; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class StringRotationTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(StringRotation.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/URLifyTest.java b/src/main/java/com/ctci/arraysandstrings/URLifyTest.java new file mode 100644 index 00000000..8160df50 --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/URLifyTest.java @@ -0,0 +1,14 @@ +package arraysandstrings; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class URLifyTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(URLify.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java b/src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java new file mode 100644 index 00000000..6e8fbfc0 --- /dev/null +++ b/src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java @@ -0,0 +1,14 @@ +package arraysandstrings; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class ZeroMatrixTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(ZeroMatrix.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/BinaryToStringTest.java b/src/main/java/com/ctci/bitmanipulation/BinaryToStringTest.java new file mode 100644 index 00000000..610ff7dd --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/BinaryToStringTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class BinaryToStringTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(BinaryToString.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/ConversionTest.java b/src/main/java/com/ctci/bitmanipulation/ConversionTest.java new file mode 100644 index 00000000..9b157bf4 --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/ConversionTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class ConversionTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Conversion.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/DebuggerTest.java b/src/main/java/com/ctci/bitmanipulation/DebuggerTest.java new file mode 100644 index 00000000..5c17d7bd --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/DebuggerTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class DebuggerTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Debugger.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/DrawLineTest.java b/src/main/java/com/ctci/bitmanipulation/DrawLineTest.java new file mode 100644 index 00000000..b49a8b01 --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/DrawLineTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class DrawLineTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(DrawLine.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/FlipBitToWinTest.java b/src/main/java/com/ctci/bitmanipulation/FlipBitToWinTest.java new file mode 100644 index 00000000..4426cf78 --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/FlipBitToWinTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class FlipBitToWinTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(FlipBitToWin.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/InsertionTest.java b/src/main/java/com/ctci/bitmanipulation/InsertionTest.java new file mode 100644 index 00000000..58e4b7d2 --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/InsertionTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class InsertionTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Insertion.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/NextNumberTest.java b/src/main/java/com/ctci/bitmanipulation/NextNumberTest.java new file mode 100644 index 00000000..98ef01b4 --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/NextNumberTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class NextNumberTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Insertion.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/bitmanipulation/PairwiseSwapTest.java b/src/main/java/com/ctci/bitmanipulation/PairwiseSwapTest.java new file mode 100644 index 00000000..d82f2b28 --- /dev/null +++ b/src/main/java/com/ctci/bitmanipulation/PairwiseSwapTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class PairwiseSwapTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Insertion.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From 337e9a265884d24491c8713bbc4a72a1ced0fcfb Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 07:36:47 +0700 Subject: [PATCH 02/18] jh --- .../com/ctci/linkedlists/DeleteMiddleNodeTest.java | 14 ++++++++++++++ .../com/ctci/linkedlists/IntersectionTest.java | 14 ++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java create mode 100644 src/main/java/com/ctci/linkedlists/IntersectionTest.java diff --git a/src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java b/src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java new file mode 100644 index 00000000..b01b944c --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class DeleteMiddleNodeTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(DeleteMiddleNode.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/linkedlists/IntersectionTest.java b/src/main/java/com/ctci/linkedlists/IntersectionTest.java new file mode 100644 index 00000000..3d9485dd --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/IntersectionTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class IntersectionTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Intersection.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From db10353509322ce6d50f155774e9b673f3ebca51 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 08:09:16 +0700 Subject: [PATCH 03/18] Create KthToLastElementTest.java --- .../com/ctci/linkedlists/KthToLastElementTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/ctci/linkedlists/KthToLastElementTest.java diff --git a/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java b/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java new file mode 100644 index 00000000..f785b439 --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class KthToLastElementTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Intersection.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From ae6ea8b4ac335b2f9bb0c0cded00e9e2d0faf2e9 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 08:31:20 +0700 Subject: [PATCH 04/18] fvdf --- .../com/ctci/linkedlists/KthToLastElementTest.java | 2 +- .../com/ctci/linkedlists/LoopDetectionTest.java | 14 ++++++++++++++ src/main/java/com/ctci/linkedlists/NodeTest.java | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ctci/linkedlists/LoopDetectionTest.java create mode 100644 src/main/java/com/ctci/linkedlists/NodeTest.java diff --git a/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java b/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java index f785b439..44f60480 100644 --- a/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java +++ b/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java @@ -5,7 +5,7 @@ public class KthToLastElementTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(Intersection.class); + Result result = JUnitCore.runClasses(KthToLastElement.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/linkedlists/LoopDetectionTest.java b/src/main/java/com/ctci/linkedlists/LoopDetectionTest.java new file mode 100644 index 00000000..a839ff42 --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/LoopDetectionTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class LoopDetectionTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Intersection.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/linkedlists/NodeTest.java b/src/main/java/com/ctci/linkedlists/NodeTest.java new file mode 100644 index 00000000..4baf7daf --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/NodeTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class NodeTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Node.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From 71ae51606cc05ca48af479d1d8121e50612c3c28 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 08:45:16 +0700 Subject: [PATCH 05/18] t --- .../java/com/ctci/linkedlists/PalindromeTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/ctci/linkedlists/PalindromeTest.java diff --git a/src/main/java/com/ctci/linkedlists/PalindromeTest.java b/src/main/java/com/ctci/linkedlists/PalindromeTest.java new file mode 100644 index 00000000..0fbd997a --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/PalindromeTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class PalindromeTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(KthToLastElement.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From f662f5ea5ec1fe5ff7adea5dd55dae8c5d6d5be4 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 09:07:30 +0700 Subject: [PATCH 06/18] l --- .../java/com/ctci/linkedlists/PartitionTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/ctci/linkedlists/PartitionTest.java diff --git a/src/main/java/com/ctci/linkedlists/PartitionTest.java b/src/main/java/com/ctci/linkedlists/PartitionTest.java new file mode 100644 index 00000000..f643669c --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/PartitionTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class PartitionTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(KthToLastElement.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From 4db8f70028d755131e6447912590f1d898e2ee88 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 09:08:15 +0700 Subject: [PATCH 07/18] d --- .../com/ctci/linkedlists/RemoveDuplicatesTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java diff --git a/src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java b/src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java new file mode 100644 index 00000000..e48b5ffa --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java @@ -0,0 +1,14 @@ +package com.ctci.bitmanipulation; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class RemoveDuplicatesTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(KthToLastElement.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From 449f267a0c223f3dae283876e4b758d167ee0d40 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 09:47:05 +0700 Subject: [PATCH 08/18] l --- .../ctci/recursionanddp/FibonacciNumberTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java diff --git a/src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java b/src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java new file mode 100644 index 00000000..96fa2f10 --- /dev/null +++ b/src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java @@ -0,0 +1,14 @@ +package com.ctci.recursionanddp; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class FibonacciNumberTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(Insertion.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From da0ca0ffdaaee11b7b609c3f6cdc1b0fffded50d Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:15:26 +0700 Subject: [PATCH 09/18] g --- .../ctci/recursionanddp/FibonacciNumberTest.java | 2 +- .../ctci/stacksandqueues/QueueViaStacksTest.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java diff --git a/src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java b/src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java index 96fa2f10..9a1a0bf6 100644 --- a/src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java +++ b/src/main/java/com/ctci/recursionanddp/FibonacciNumberTest.java @@ -5,7 +5,7 @@ public class FibonacciNumberTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(Insertion.class); + Result result = JUnitCore.runClasses(FibonacciNumber.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java b/src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java new file mode 100644 index 00000000..86e785eb --- /dev/null +++ b/src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java @@ -0,0 +1,14 @@ +package com.ctci.recursionanddp; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class QueueViaStacksTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(QueueViaStacks.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From 84a5f8d61f5d9a1d60c395ab98cc1e2381e638ad Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:17:27 +0700 Subject: [PATCH 10/18] l m --- .../java/com/ctci/stacksandqueues/SortStack.java | 4 ++++ .../com/ctci/stacksandqueues/SortStackTest.java | 14 ++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 src/main/java/com/ctci/stacksandqueues/SortStackTest.java diff --git a/src/main/java/com/ctci/stacksandqueues/SortStack.java b/src/main/java/com/ctci/stacksandqueues/SortStack.java index b94c68fb..2f611c7d 100644 --- a/src/main/java/com/ctci/stacksandqueues/SortStack.java +++ b/src/main/java/com/ctci/stacksandqueues/SortStack.java @@ -45,4 +45,8 @@ public static void main(String[] args) { sortStack(unsortedStack); printStack(unsortedStack); } + public static void testAssertions() { + Scanner scanner = new Scanner(System.in); + int n = scanner.nextInt(); + } } diff --git a/src/main/java/com/ctci/stacksandqueues/SortStackTest.java b/src/main/java/com/ctci/stacksandqueues/SortStackTest.java new file mode 100644 index 00000000..8a1206a6 --- /dev/null +++ b/src/main/java/com/ctci/stacksandqueues/SortStackTest.java @@ -0,0 +1,14 @@ +package com.ctci.recursionanddp; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class SortStackTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(QueueViaStacks.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From b2b1e911a4c98cf9cb6dd50e5481b0f0ea027c14 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:47:27 +0700 Subject: [PATCH 11/18] yt --- .../java/com/ctci/stacksandqueues/QueueViaStacks.java | 11 ++++++++++- .../java/com/ctci/stacksandqueues/SortStackTest.java | 1 - 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java b/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java index 271fd8e4..84c331ed 100644 --- a/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java +++ b/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java @@ -2,7 +2,8 @@ import java.util.NoSuchElementException; import java.util.Stack; - +import static org.junit.Assert.*; +import org.junit.Test; /** * Implement a queue using two stacks. No other data structures to be used. * @@ -58,4 +59,12 @@ public static void main(String[] args) { queue.remove(); queue.print(); } + @Test + public static void testAssertions() { + Scanner scanner = new Scanner(System.in); + int n = scanner.nextInt(); + for (int i = 0; i < n; i++) { + + } + } } diff --git a/src/main/java/com/ctci/stacksandqueues/SortStackTest.java b/src/main/java/com/ctci/stacksandqueues/SortStackTest.java index 8a1206a6..69dabcb7 100644 --- a/src/main/java/com/ctci/stacksandqueues/SortStackTest.java +++ b/src/main/java/com/ctci/stacksandqueues/SortStackTest.java @@ -4,7 +4,6 @@ import org.junit.runner.notification.Failure; public class SortStackTest { public static void main(String[] args) { - //testAssertions(); Result result = JUnitCore.runClasses(QueueViaStacks.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); From d439a55357d9c0a6b66cda3fac6fb163f0c021ce Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Wed, 5 Jul 2023 22:30:41 +0700 Subject: [PATCH 12/18] lol --- src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java | 6 +----- src/main/java/com/ctci/stacksandqueues/SortStack.java | 7 ++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java b/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java index 84c331ed..4dacf154 100644 --- a/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java +++ b/src/main/java/com/ctci/stacksandqueues/QueueViaStacks.java @@ -61,10 +61,6 @@ public static void main(String[] args) { } @Test public static void testAssertions() { - Scanner scanner = new Scanner(System.in); - int n = scanner.nextInt(); - for (int i = 0; i < n; i++) { - - } + } } diff --git a/src/main/java/com/ctci/stacksandqueues/SortStack.java b/src/main/java/com/ctci/stacksandqueues/SortStack.java index 2f611c7d..e61c1c75 100644 --- a/src/main/java/com/ctci/stacksandqueues/SortStack.java +++ b/src/main/java/com/ctci/stacksandqueues/SortStack.java @@ -1,4 +1,4 @@ -package com.ctci.stacksandqueues; +package com.ctci.stackandqueues; import java.util.Arrays; import java.util.Stack; @@ -45,8 +45,5 @@ public static void main(String[] args) { sortStack(unsortedStack); printStack(unsortedStack); } - public static void testAssertions() { - Scanner scanner = new Scanner(System.in); - int n = scanner.nextInt(); - } + } From 581685d08779b82e7891379c1f9a21bbff380e6b Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:07:16 +0700 Subject: [PATCH 13/18] d --- .../java/com/ctci/stacksandqueues/StackMinTest.java | 11 +++++++++++ .../com/ctci/stacksandqueues/StackOfPlatesTest.java | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/main/java/com/ctci/stacksandqueues/StackMinTest.java create mode 100644 src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java diff --git a/src/main/java/com/ctci/stacksandqueues/StackMinTest.java b/src/main/java/com/ctci/stacksandqueues/StackMinTest.java new file mode 100644 index 00000000..4eefc16e --- /dev/null +++ b/src/main/java/com/ctci/stacksandqueues/StackMinTest.java @@ -0,0 +1,11 @@ +package main.java.com.ctci.stacksandqueues; + +public class StackMinTest { + public static void main(String[] args) { + Result result = JUnitCore.runClasses(QueueViaStacks.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java b/src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java new file mode 100644 index 00000000..659bb423 --- /dev/null +++ b/src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java @@ -0,0 +1,10 @@ +package main.java.com.ctci.stacksandqueues; +public class StackOfPlatesTest { + public static void main(String[] args) { + Result result = JUnitCore.runClasses(QueueViaStacks.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From caaca9e06333b84e2b8a64d7758dc65e938a9dbc Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Thu, 6 Jul 2023 20:04:37 +0700 Subject: [PATCH 14/18] ff --- .../java/com/ctci/stacksandqueues/StackMinTest.java | 4 +++- .../java/com/ctci/treesandgraphs/BuildOrder.java | 6 ++++-- .../com/ctci/treesandgraphs/BuildOrderTest.java | 13 +++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java diff --git a/src/main/java/com/ctci/stacksandqueues/StackMinTest.java b/src/main/java/com/ctci/stacksandqueues/StackMinTest.java index 4eefc16e..398eba31 100644 --- a/src/main/java/com/ctci/stacksandqueues/StackMinTest.java +++ b/src/main/java/com/ctci/stacksandqueues/StackMinTest.java @@ -1,8 +1,10 @@ package main.java.com.ctci.stacksandqueues; +import com.ctci.stacksandqueues.StackMin; + public class StackMinTest { public static void main(String[] args) { - Result result = JUnitCore.runClasses(QueueViaStacks.class); + Result result = JUnitCore.runClasses(StackMin.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/treesandgraphs/BuildOrder.java b/src/main/java/com/ctci/treesandgraphs/BuildOrder.java index 6d64e48a..f1b4922f 100644 --- a/src/main/java/com/ctci/treesandgraphs/BuildOrder.java +++ b/src/main/java/com/ctci/treesandgraphs/BuildOrder.java @@ -8,7 +8,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; - +import org.junit.Assert.*; /** * You are given a list of projects and a list of dependencies (which is a list of pairs of projects, where the second * project is dependent on the first project). All of a project's dependencies must be built before the project is. Find @@ -107,7 +107,7 @@ private void removeDependency(Set newlyBuiltProjects) { projects.forEach((n, p) -> p.dependencies.removeAll(newlyBuiltProjects)); } - + @Test public static void main(String[] args) { /* test case 1 @@ -139,6 +139,8 @@ public static void main(String[] args) { buildOrder.addDependency("b", "a"); buildOrder.addDependency("b", "e"); buildOrder.addDependency("a", "e"); + assertNull(buildOrder); System.out.println(buildOrder.getBuildOrder()); + } } \ No newline at end of file diff --git a/src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java b/src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java new file mode 100644 index 00000000..30e8bf46 --- /dev/null +++ b/src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java @@ -0,0 +1,13 @@ +package main.java.com.ctci.treesandgraphs; + +import com.ctci.treesandgraphs.BuildOrder; + +public class BuildOrderTest { + public static void main(String[] args) { + Result result = JUnitCore.runClasses(BuildOrder.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From c0f05208385956405d580542dd0b0fc91608de03 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Thu, 6 Jul 2023 20:24:26 +0700 Subject: [PATCH 15/18] l --- .../ctci/treesandgraphs/CheckBalanced.java | 2 + .../junit-team-junit5-da216b8/.codecov.yml | 8 + .../junit-team-junit5-da216b8/.gitattributes | 7 + .../.github/FUNDING.yml | 8 + .../.github/ISSUE_TEMPLATE.md | 23 + .../.github/ISSUE_TEMPLATE/bug_report.md | 20 + .../.github/ISSUE_TEMPLATE/feature_request.md | 11 + .../.github/ISSUE_TEMPLATE/question.md | 11 + .../.github/PULL_REQUEST_TEMPLATE.md | 19 + .../.github/actions/main-build/action.yml | 19 + .../.github/actions/run-gradle/action.yml | 25 + .../.github/actions/setup-test-jdk/action.yml | 11 + .../.github/dependabot.yml | 17 + .../.github/stale.yml | 69 + .../.github/workflows/codeql-analysis.yml | 41 + .../.github/workflows/cross-version.yml | 53 + .../workflows/gradle-wrapper-validation.yml | 14 + .../.github/workflows/issue-labels.yml | 14 + .../.github/workflows/main.yml | 110 + .../.github/workflows/reproducible-build.yml | 31 + .../com/junit-team-junit5-da216b8/.gitignore | 34 + .../CODE_OF_CONDUCT.md | 76 + .../junit-team-junit5-da216b8/CONTRIBUTING.md | 159 + .../java/com/junit-team-junit5-da216b8/KEYS | 70 + .../LICENSE-notice.md | 8 + .../com/junit-team-junit5-da216b8/LICENSE.md | 98 + .../com/junit-team-junit5-da216b8/README.md | 100 + .../com/junit-team-junit5-da216b8/SECURITY.md | 12 + .../build.gradle.kts | 65 + .../buildSrc/build.gradle.kts | 31 + .../buildSrc/settings.gradle.kts | 1 + .../src/main/kotlin/JavaLibraryExtension.kt | 7 + .../buildSrc/src/main/kotlin/License.kt | 4 + .../src/main/kotlin/ProjectExtensions.kt | 19 + .../src/main/kotlin/TaskExtensions.kt | 5 + .../main/kotlin/base-conventions.gradle.kts | 6 + .../src/main/kotlin/build-metadata.gradle.kts | 28 + .../kotlin/dependency-update-check.gradle.kts | 21 + .../jacoco-aggregation-conventions.gradle.kts | 17 + .../main/kotlin/jacoco-conventions.gradle.kts | 13 + .../kotlin/jacoco-java-conventions.gradle.kts | 31 + .../java-library-conventions.gradle.kts | 289 ++ .../java-multi-release-sources.gradle.kts | 43 + .../kotlin/java-repackage-jars.gradle.kts | 52 + .../java-toolchain-conventions.gradle.kts | 42 + .../kotlin/junit4-compatibility.gradle.kts | 29 + .../kotlin-library-conventions.gradle.kts | 26 + .../exec/ClasspathSystemPropertyProvider.kt | 9 + .../junit/gradle/exec/RunConsoleLauncher.kt | 86 + .../org/junit/gradle/java/ExecJarAction.kt | 27 + .../gradle/java/ModulePathArgumentProvider.kt | 41 + .../java/PatchModuleArgumentProvider.kt | 47 + .../junit/gradle/java/WriteArtifactsFile.kt | 36 + .../ModuleSpecificJavadocFileOption.kt | 26 + .../main/kotlin/osgi-conventions.gradle.kts | 114 + .../kotlin/publishing-conventions.gradle.kts | 115 + .../main/kotlin/shadow-conventions.gradle.kts | 69 + .../kotlin/spotless-conventions.gradle.kts | 49 + .../main/kotlin/temp-maven-repo.gradle.kts | 30 + .../kotlin/testing-conventions.gradle.kts | 84 + .../documentation/README.md | 34 + .../documentation/documentation.gradle.kts | 457 +++ .../asciidoc/docinfos/docinfo-footer.html | 33 + .../src/docs/asciidoc/docinfos/docinfo.html | 18 + .../src/docs/asciidoc/link-attributes.adoc | 190 + .../docs/asciidoc/release-notes/index.adoc | 31 + .../release-notes/release-notes-5.8.0.adoc | 21 + .../release-notes/release-notes-5.8.1.adoc | 62 + .../release-notes/release-notes-5.8.2.adoc | 45 + .../release-notes/release-notes-5.9.0.adoc | 148 + .../release-notes/release-notes-5.9.1.adoc | 53 + .../release-notes/release-notes-5.9.2.adoc | 61 + .../release-notes/release-notes-5.9.3.adoc | 59 + .../release-notes/release-notes-TEMPLATE.adoc | 73 + .../docs/asciidoc/resources/fonts/Symbola.ttf | Bin 0 -> 2440452 bytes .../resources/themes/junit-pdf-theme.yml | 6 + .../src/docs/asciidoc/tocbot-3.0.2/styles.css | 1 + .../src/docs/asciidoc/tocbot-3.0.2/tocbot.css | 1 + .../src/docs/asciidoc/tocbot-3.0.2/tocbot.js | 136 + .../docs/asciidoc/tocbot-3.0.2/tocbot.min.js | 1 + .../asciidoc/user-guide/advanced-topics.adoc | 12 + .../user-guide/advanced-topics/engines.adoc | 122 + .../junit-platform-reporting.adoc | 138 + .../junit-platform-suite-engine.adoc | 50 + .../advanced-topics/launcher-api.adoc | 243 ++ .../user-guide/advanced-topics/testkit.adoc | 164 + .../asciidoc/user-guide/api-evolution.adoc | 63 + .../docs/asciidoc/user-guide/appendix.adoc | 232 ++ .../asciidoc/user-guide/contributors.adoc | 4 + .../docs/asciidoc/user-guide/extensions.adoc | 951 +++++ ...nsions_BrokenLifecycleMethodConfigDemo.png | Bin 0 -> 60272 bytes ...nsions_BrokenLifecycleMethodConfigDemo.txt | 19 + .../images/extensions_DatabaseTestsDemo.png | Bin 0 -> 94331 bytes .../images/extensions_DatabaseTestsDemo.txt | 27 + .../images/extensions_lifecycle.png | Bin 0 -> 223256 bytes .../images/extensions_lifecycle_source.docx | Bin 0 -> 15930 bytes .../images/writing-tests_execution_mode.svg | 386 ++ .../images/writing-tests_nested_test_ide.png | Bin 0 -> 230224 bytes .../src/docs/asciidoc/user-guide/index.adoc | 44 + .../user-guide/migration-from-junit4.adoc | 155 + .../docs/asciidoc/user-guide/overview.adoc | 90 + .../asciidoc/user-guide/running-tests.adoc | 1073 +++++ .../asciidoc/user-guide/writing-tests.adoc | 2554 ++++++++++++ .../src/javadoc/junit-overview.html | 26 + .../src/javadoc/junit-stylesheet.css | 155 + .../src/main/java/example/domain/Person.java | 98 + .../java/example/domain/package-info.java | 5 + .../java/example/registration/WebClient.java | 19 + .../example/registration/WebResponse.java | 19 + .../registration/WebServerExtension.java | 43 + .../example/registration/package-info.java | 5 + .../main/java/example/util/Calculator.java | 31 + .../main/java/example/util/ListWriter.java | 31 + .../main/java/example/util/StringUtils.java | 25 + .../main/java/example/util/package-info.java | 5 + .../src/test/java/example/AssertionsDemo.java | 150 + .../test/java/example/AssumptionsDemo.java | 55 + .../example/ConditionalTestExecutionDemo.java | 218 + .../test/java/example/CustomTestEngine.java | 40 + .../test/java/example/DisabledClassDemo.java | 25 + .../test/java/example/DisabledTestsDemo.java | 29 + .../test/java/example/DisplayNameDemo.java | 36 + .../example/DisplayNameGeneratorDemo.java | 58 + .../java/example/DocumentationTestSuite.java | 36 + .../test/java/example/DynamicTestsDemo.java | 197 + .../test/java/example/ExampleTestCase.java | 59 + .../example/ExternalCustomConditionDemo.java | 34 + .../example/ExternalMethodSourceDemo.java | 34 + .../src/test/java/example/Fast.java | 26 + .../src/test/java/example/FastTest.java | 28 + .../java/example/HamcrestAssertionsDemo.java | 32 + .../test/java/example/IgnoredTestsDemo.java | 31 + .../src/test/java/example/JUnit4Tests.java | 22 + .../java/example/JUnitPlatformClassDemo.java | 39 + .../java/example/JUnitPlatformSuiteDemo.java | 27 + .../MethodSourceParameterResolutionDemo.java | 66 + .../example/MyFirstJUnitJupiterTests.java | 30 + .../example/OrderedNestedTestClassesDemo.java | 41 + .../test/java/example/OrderedTestsDemo.java | 41 + .../java/example/ParameterizedTestDemo.java | 470 +++ .../test/java/example/PollingTimeoutDemo.java | 33 + .../test/java/example/RepeatedTestsDemo.java | 68 + .../java/example/SharedResourcesDemo.java | 66 + .../src/test/java/example/SlowTests.java | 126 + .../src/test/java/example/StandardTests.java | 67 + .../src/test/java/example/SuiteDemo.java | 28 + .../src/test/java/example/TaggingDemo.java | 27 + .../java/example/TempDirCleanupModeDemo.java | 28 + .../test/java/example/TempDirectoryDemo.java | 76 + .../src/test/java/example/TestInfoDemo.java | 49 + .../test/java/example/TestReporterDemo.java | 45 + .../test/java/example/TestTemplateDemo.java | 88 + .../test/java/example/TestingAStackDemo.java | 98 + .../src/test/java/example/TimeoutDemo.java | 42 + .../java/example/UsingTheLauncherDemo.java | 156 + .../callbacks/AbstractDatabaseTests.java | 51 + .../BrokenLifecycleMethodConfigDemo.java | 60 + .../example/callbacks/DatabaseTestsDemo.java | 61 + .../java/example/callbacks/Extension1.java | 35 + .../java/example/callbacks/Extension2.java | 35 + .../test/java/example/callbacks/Logger.java | 54 + .../defaultmethods/ComparableContract.java | 44 + .../defaultmethods/EqualsContract.java | 45 + .../example/defaultmethods/StringTests.java | 32 + .../java/example/defaultmethods/Testable.java | 19 + .../exception/IgnoreIOExceptionExtension.java | 33 + .../exception/IgnoreIOExceptionTests.java | 33 + .../exception/MultipleHandlersTestCase.java | 62 + .../RecordStateOnErrorExtension.java | 55 + .../test/java/example/extensions/Random.java | 26 + .../example/extensions/RandomNumberDemo.java | 41 + .../extensions/RandomNumberExtension.java | 39 + .../interceptor/SwingEdtInterceptor.java | 48 + .../registration/DocumentationDemo.java | 58 + .../example/registration/WebServerDemo.java | 41 + .../session/GlobalSetupTeardownListener.java | 92 + .../test/java/example/session/HttpTests.java | 37 + .../testinterface/TestInterfaceDemo.java | 29 + .../TestInterfaceDynamicTestsDemo.java | 34 + .../testinterface/TestLifecycleLogger.java | 54 + .../testinterface/TimeExecutionLogger.java | 23 + .../testkit/EngineTestKitAllEventsDemo.java | 72 + .../EngineTestKitFailedMethodDemo.java | 44 + .../EngineTestKitSkippedMethodDemo.java | 48 + .../testkit/EngineTestKitStatisticsDemo.java | 48 + .../java/example/timing/TimingExtension.java | 59 + .../example/timing/TimingExtensionTests.java | 36 + .../test/java/extensions/ExpectToFail.java | 51 + .../api/tools/AbstractApiReportWriter.java | 104 + .../java/org/junit/api/tools/ApiReport.java | 40 + .../junit/api/tools/ApiReportGenerator.java | 117 + .../org/junit/api/tools/ApiReportWriter.java | 27 + .../api/tools/AsciidocApiReportWriter.java | 74 + .../junit/api/tools/HtmlApiReportWriter.java | 74 + .../api/tools/MarkdownApiReportWriter.java | 73 + .../kotlin/example/FibonacciCalculator.kt | 29 + .../kotlin/example/KotlinAssertionsDemo.kt | 101 + .../registration/KotlinWebServerDemo.kt | 35 + ....platform.launcher.LauncherSessionListener | 1 + .../test/resources/junit-platform.properties | 4 + .../src/test/resources/log4j2-test.xml | 19 + .../src/test/resources/two-column.csv | 5 + .../gradle.properties | 31 + .../gradle/libs.versions.toml | 56 + .../gradle/wrapper/gradle-wrapper.properties | 7 + .../com/junit-team-junit5-da216b8/gradlew | 244 ++ .../com/junit-team-junit5-da216b8/gradlew.bat | 92 + .../junit-bom/README.md | 8 + .../junit-bom/junit-bom.gradle.kts | 42 + .../junit-jupiter-api.gradle.kts | 32 + .../java/org/junit/jupiter/api/AfterAll.java | 100 + .../java/org/junit/jupiter/api/AfterEach.java | 91 + .../java/org/junit/jupiter/api/AssertAll.java | 86 + .../junit/jupiter/api/AssertArrayEquals.java | 449 ++ .../junit/jupiter/api/AssertDoesNotThrow.java | 91 + .../org/junit/jupiter/api/AssertEquals.java | 199 + .../org/junit/jupiter/api/AssertFalse.java | 66 + .../junit/jupiter/api/AssertInstanceOf.java | 54 + .../jupiter/api/AssertIterableEquals.java | 212 + .../junit/jupiter/api/AssertLinesMatch.java | 239 ++ .../junit/jupiter/api/AssertNotEquals.java | 280 ++ .../org/junit/jupiter/api/AssertNotNull.java | 51 + .../org/junit/jupiter/api/AssertNotSame.java | 52 + .../org/junit/jupiter/api/AssertNull.java | 53 + .../org/junit/jupiter/api/AssertSame.java | 53 + .../org/junit/jupiter/api/AssertThrows.java | 76 + .../jupiter/api/AssertThrowsExactly.java | 77 + .../org/junit/jupiter/api/AssertTimeout.java | 86 + .../api/AssertTimeoutPreemptively.java | 156 + .../org/junit/jupiter/api/AssertTrue.java | 66 + .../jupiter/api/AssertionFailureBuilder.java | 205 + .../org/junit/jupiter/api/AssertionUtils.java | 117 + .../org/junit/jupiter/api/Assertions.java | 3625 +++++++++++++++++ .../org/junit/jupiter/api/Assumptions.java | 319 ++ .../java/org/junit/jupiter/api/BeforeAll.java | 100 + .../org/junit/jupiter/api/BeforeEach.java | 91 + .../junit/jupiter/api/ClassDescriptor.java | 87 + .../org/junit/jupiter/api/ClassOrderer.java | 271 ++ .../jupiter/api/ClassOrdererContext.java | 56 + .../java/org/junit/jupiter/api/Disabled.java | 66 + .../org/junit/jupiter/api/DisplayName.java | 51 + .../jupiter/api/DisplayNameGeneration.java | 57 + .../jupiter/api/DisplayNameGenerator.java | 378 ++ .../junit/jupiter/api/DynamicContainer.java | 109 + .../org/junit/jupiter/api/DynamicNode.java | 71 + .../org/junit/jupiter/api/DynamicTest.java | 243 ++ .../api/IndicativeSentencesGeneration.java | 71 + .../junit/jupiter/api/MethodDescriptor.java | 91 + .../org/junit/jupiter/api/MethodOrderer.java | 334 ++ .../jupiter/api/MethodOrdererContext.java | 63 + .../java/org/junit/jupiter/api/Named.java | 94 + .../java/org/junit/jupiter/api/Nested.java | 54 + .../java/org/junit/jupiter/api/Order.java | 72 + .../org/junit/jupiter/api/RepeatedTest.java | 155 + .../org/junit/jupiter/api/RepetitionInfo.java | 53 + .../main/java/org/junit/jupiter/api/Tag.java | 75 + .../main/java/org/junit/jupiter/api/Tags.java | 47 + .../main/java/org/junit/jupiter/api/Test.java | 79 + .../org/junit/jupiter/api/TestClassOrder.java | 85 + .../org/junit/jupiter/api/TestFactory.java | 79 + .../java/org/junit/jupiter/api/TestInfo.java | 110 + .../org/junit/jupiter/api/TestInstance.java | 123 + .../junit/jupiter/api/TestMethodOrder.java | 89 + .../org/junit/jupiter/api/TestReporter.java | 80 + .../org/junit/jupiter/api/TestTemplate.java | 84 + .../java/org/junit/jupiter/api/Timeout.java | 388 ++ .../AbstractOsBasedExecutionCondition.java | 70 + ...AbstractRepeatableAnnotationCondition.java | 72 + .../condition/BooleanExecutionCondition.java | 54 + .../api/condition/DisabledForJreRange.java | 112 + .../DisabledForJreRangeCondition.java | 45 + .../jupiter/api/condition/DisabledIf.java | 97 + .../api/condition/DisabledIfCondition.java | 31 + .../DisabledIfEnvironmentVariable.java | 108 + ...isabledIfEnvironmentVariableCondition.java | 75 + .../DisabledIfEnvironmentVariables.java | 47 + .../condition/DisabledIfSystemProperties.java | 47 + .../condition/DisabledIfSystemProperty.java | 108 + .../DisabledIfSystemPropertyCondition.java | 64 + .../api/condition/DisabledInNativeImage.java | 76 + .../jupiter/api/condition/DisabledOnJre.java | 96 + .../api/condition/DisabledOnJreCondition.java | 40 + .../jupiter/api/condition/DisabledOnOs.java | 112 + .../api/condition/DisabledOnOsCondition.java | 61 + .../api/condition/EnabledForJreRange.java | 112 + .../EnabledForJreRangeCondition.java | 45 + .../jupiter/api/condition/EnabledIf.java | 97 + .../api/condition/EnabledIfCondition.java | 32 + .../EnabledIfEnvironmentVariable.java | 107 + ...EnabledIfEnvironmentVariableCondition.java | 74 + .../EnabledIfEnvironmentVariables.java | 47 + .../condition/EnabledIfSystemProperties.java | 47 + .../condition/EnabledIfSystemProperty.java | 107 + .../EnabledIfSystemPropertyCondition.java | 63 + .../api/condition/EnabledInNativeImage.java | 76 + .../jupiter/api/condition/EnabledOnJre.java | 95 + .../api/condition/EnabledOnJreCondition.java | 43 + .../jupiter/api/condition/EnabledOnOs.java | 112 + .../api/condition/EnabledOnOsCondition.java | 61 + .../org/junit/jupiter/api/condition/JRE.java | 244 ++ .../api/condition/MethodBasedCondition.java | 110 + .../org/junit/jupiter/api/condition/OS.java | 157 + .../jupiter/api/condition/package-info.java | 5 + .../api/extension/AfterAllCallback.java | 70 + .../api/extension/AfterEachCallback.java | 73 + .../extension/AfterTestExecutionCallback.java | 75 + .../api/extension/BeforeAllCallback.java | 70 + .../api/extension/BeforeEachCallback.java | 73 + .../BeforeTestExecutionCallback.java | 75 + .../extension/ConditionEvaluationResult.java | 102 + .../DynamicTestInvocationContext.java | 36 + .../api/extension/ExecutableInvoker.java | 72 + .../api/extension/ExecutionCondition.java | 71 + .../jupiter/api/extension/ExtendWith.java | 102 + .../jupiter/api/extension/Extension.java | 41 + .../ExtensionConfigurationException.java | 37 + .../api/extension/ExtensionContext.java | 713 ++++ .../extension/ExtensionContextException.java | 34 + .../jupiter/api/extension/Extensions.java | 48 + .../api/extension/InvocationInterceptor.java | 248 ++ ...ecycleMethodExecutionExceptionHandler.java | 123 + .../api/extension/ParameterContext.java | 151 + .../ParameterResolutionException.java | 38 + .../api/extension/ParameterResolver.java | 93 + .../ReflectiveInvocationContext.java | 74 + .../api/extension/RegisterExtension.java | 144 + .../TestExecutionExceptionHandler.java | 65 + .../api/extension/TestInstanceFactory.java | 76 + .../extension/TestInstanceFactoryContext.java | 50 + .../extension/TestInstancePostProcessor.java | 57 + .../TestInstancePreConstructCallback.java | 58 + .../TestInstancePreDestroyCallback.java | 122 + .../jupiter/api/extension/TestInstances.java | 69 + .../extension/TestInstantiationException.java | 37 + .../TestTemplateInvocationContext.java | 69 + ...TestTemplateInvocationContextProvider.java | 89 + .../jupiter/api/extension/TestWatcher.java | 103 + .../jupiter/api/extension/package-info.java | 5 + .../support/TypeBasedParameterResolver.java | 81 + .../api/extension/support/package-info.java | 5 + .../jupiter/api/function/Executable.java | 49 + .../api/function/ThrowingConsumer.java | 53 + .../api/function/ThrowingSupplier.java | 54 + .../jupiter/api/function/package-info.java | 5 + .../org/junit/jupiter/api/io/CleanupMode.java | 53 + .../org/junit/jupiter/api/io/TempDir.java | 139 + .../junit/jupiter/api/io/package-info.java | 5 + .../org/junit/jupiter/api/package-info.java | 5 + .../junit/jupiter/api/parallel/Execution.java | 101 + .../jupiter/api/parallel/ExecutionMode.java | 41 + .../junit/jupiter/api/parallel/Isolated.java | 51 + .../api/parallel/ResourceAccessMode.java | 36 + .../jupiter/api/parallel/ResourceLock.java | 72 + .../jupiter/api/parallel/ResourceLocks.java | 48 + .../junit/jupiter/api/parallel/Resources.java | 84 + .../jupiter/api/parallel/package-info.java | 5 + .../org/junit/jupiter/api/Assertions.kt | 289 ++ .../org.junit.jupiter.api/module-info.java | 27 + .../ExtensionContextParameterResolver.java | 26 + .../jupiter/api/fixtures/TrackLogRecords.java | 83 + .../junit-jupiter-engine.gradle.kts | 53 + .../org/junit/jupiter/engine/Constants.java | 366 ++ .../jupiter/engine/JupiterTestEngine.java | 102 + .../config/CachingJupiterConfiguration.java | 118 + .../config/DefaultJupiterConfiguration.java | 131 + .../EnumConfigurationParameterConverter.java | 73 + ...iatingConfigurationParameterConverter.java | 67 + .../engine/config/JupiterConfiguration.java | 69 + .../jupiter/engine/config/package-info.java | 5 + .../descriptor/AbstractExtensionContext.java | 158 + .../descriptor/ClassBasedTestDescriptor.java | 524 +++ .../descriptor/ClassExtensionContext.java | 105 + .../descriptor/ClassTestDescriptor.java | 82 + .../DefaultDynamicTestInvocationContext.java | 34 + .../DefaultTestInstanceFactoryContext.java | 53 + .../engine/descriptor/DisplayNameUtils.java | 130 + .../DynamicContainerTestDescriptor.java | 77 + .../descriptor/DynamicDescendantFilter.java | 94 + .../descriptor/DynamicExtensionContext.java | 73 + .../descriptor/DynamicNodeTestDescriptor.java | 62 + .../descriptor/DynamicTestTestDescriptor.java | 81 + .../engine/descriptor/ExtensionUtils.java | 192 + .../jupiter/engine/descriptor/Filterable.java | 30 + .../descriptor/JupiterEngineDescriptor.java | 73 + .../JupiterEngineExtensionContext.java | 76 + .../descriptor/JupiterTestDescriptor.java | 238 ++ .../descriptor/LifecycleMethodUtils.java | 105 + .../descriptor/MethodBasedTestDescriptor.java | 149 + .../descriptor/MethodExtensionContext.java | 87 + .../descriptor/MethodSourceSupport.java | 67 + .../descriptor/NestedClassTestDescriptor.java | 90 + .../descriptor/TestFactoryTestDescriptor.java | 210 + .../TestInstanceLifecycleUtils.java | 46 + .../descriptor/TestMethodTestDescriptor.java | 323 ++ .../TestTemplateExtensionContext.java | 80 + .../TestTemplateInvocationTestDescriptor.java | 78 + .../TestTemplateTestDescriptor.java | 155 + .../engine/descriptor/package-info.java | 5 + .../AbstractAnnotatedDescriptorWrapper.java | 65 + .../discovery/AbstractOrderingVisitor.java | 189 + .../discovery/ClassOrderingVisitor.java | 87 + .../discovery/ClassSelectorResolver.java | 162 + .../discovery/DefaultClassDescriptor.java | 39 + .../discovery/DefaultClassOrdererContext.java | 45 + .../discovery/DefaultMethodDescriptor.java | 41 + .../DefaultMethodOrdererContext.java | 60 + .../discovery/DiscoverySelectorResolver.java | 49 + .../engine/discovery/MethodFinder.java | 40 + .../discovery/MethodOrderingVisitor.java | 84 + .../discovery/MethodSelectorResolver.java | 251 ++ .../engine/discovery/package-info.java | 6 + .../discovery/predicates/IsInnerClass.java | 42 + .../predicates/IsNestedTestClass.java | 40 + .../predicates/IsPotentialTestContainer.java | 49 + .../predicates/IsTestClassWithTests.java | 57 + .../predicates/IsTestFactoryMethod.java | 33 + .../discovery/predicates/IsTestMethod.java | 30 + .../predicates/IsTestTemplateMethod.java | 30 + .../predicates/IsTestableMethod.java | 55 + .../discovery/predicates/package-info.java | 5 + .../execution/AfterEachMethodAdapter.java | 33 + .../execution/BeforeEachMethodAdapter.java | 33 + .../ConditionEvaluationException.java | 31 + .../engine/execution/ConditionEvaluator.java | 84 + .../execution/ConstructorInvocation.java | 59 + .../execution/DefaultExecutableInvoker.java | 58 + .../execution/DefaultParameterContext.java | 80 + .../execution/DefaultTestInstances.java | 72 + .../execution/ExtensionValuesStore.java | 245 ++ .../InterceptingExecutableInvoker.java | 116 + .../execution/InvocationInterceptorChain.java | 160 + .../JupiterEngineExecutionContext.java | 195 + .../engine/execution/MethodInvocation.java | 63 + .../engine/execution/NamespaceAwareStore.java | 83 + .../execution/ParameterResolutionUtils.java | 194 + .../execution/TestInstancesProvider.java | 37 + .../engine/execution/package-info.java | 5 + .../engine/extension/DisabledCondition.java | 53 + .../engine/extension/ExtensionRegistrar.java | 71 + .../engine/extension/ExtensionRegistry.java | 70 + .../extension/MutableExtensionRegistry.java | 209 + .../RepeatedTestDisplayNameFormatter.java | 41 + .../extension/RepeatedTestExtension.java | 68 + .../RepeatedTestInvocationContext.java | 49 + .../RepetitionInfoParameterResolver.java | 76 + .../SameThreadTimeoutInvocation.java | 84 + .../SeparateThreadTimeoutInvocation.java | 45 + .../engine/extension/TempDirectory.java | 393 ++ .../extension/TestInfoParameterResolver.java | 93 + .../TestReporterParameterResolver.java | 35 + .../extension/TimeoutConfiguration.java | 146 + .../engine/extension/TimeoutDuration.java | 98 + .../extension/TimeoutDurationParser.java | 66 + .../extension/TimeoutExceptionFactory.java | 39 + .../engine/extension/TimeoutExtension.java | 232 ++ .../extension/TimeoutInvocationFactory.java | 114 + .../engine/extension/package-info.java | 5 + .../junit/jupiter/engine/package-info.java | 5 + .../JupiterThrowableCollectorFactory.java | 37 + ...est4JAndJUnit4AwareThrowableCollector.java | 70 + .../jupiter/engine/support/package-info.java | 5 + .../org.junit.platform.engine.TestEngine | 1 + .../org.junit.jupiter.engine/module-info.java | 35 + .../api/GroovyAssertEqualsTests.groovy | 192 + .../api/GroovyAssertNotEqualsTests.groovy | 196 + .../api/PrimitiveAndWrapperTypeHelpers.groovy | 70 + .../src/test/java/DefaultPackageTestCase.java | 28 + .../src/test/java/example/B_TestCase.java | 37 + .../org/junit/jupiter/JupiterTestSuite.java | 39 + .../jupiter/api/AssertAllAssertionsTests.java | 224 + .../api/AssertArrayEqualsAssertionsTests.java | 1977 +++++++++ .../AssertDoesNotThrowAssertionsTests.java | 321 ++ .../api/AssertEqualsAssertionsTests.java | 771 ++++ .../api/AssertFalseAssertionsTests.java | 111 + .../api/AssertInstanceOfAssertionsTests.java | 109 + .../AssertIterableEqualsAssertionsTests.java | 478 +++ .../api/AssertLinesMatchAssertionsTests.java | 337 ++ .../api/AssertNotEqualsAssertionsTests.java | 768 ++++ .../api/AssertNotNullAssertionsTests.java | 65 + .../api/AssertNotSameAssertionsTests.java | 87 + .../api/AssertNullAssertionsTests.java | 152 + .../api/AssertSameAssertionsTests.java | 101 + .../api/AssertThrowsAssertionsTests.java | 295 ++ .../AssertThrowsExactlyAssertionsTests.java | 335 ++ .../api/AssertTimeoutAssertionsTests.java | 168 + ...ertTimeoutPreemptivelyAssertionsTests.java | 255 ++ .../api/AssertTrueAssertionsTests.java | 111 + .../junit/jupiter/api/AssertionTestUtils.java | 100 + .../junit/jupiter/api/AssumptionsTests.java | 245 ++ ...playNameGenerationInheritanceTestCase.java | 36 + .../api/DisplayNameGenerationTests.java | 441 ++ .../junit/jupiter/api/DynamicTestTests.java | 203 + .../junit/jupiter/api/EnigmaThrowable.java | 22 + .../jupiter/api/FailAssertionsTests.java | 153 + ...entencesGenerationInheritanceTestCase.java | 36 + .../IndicativeSentencesNestedTestCase.java | 33 + .../IndicativeSentencesTopLevelTestCase.java | 33 + .../junit/jupiter/api/IterableFactory.java | 28 + .../AbstractExecutionConditionTests.java | 112 + .../DisabledForJreRangeConditionTests.java | 133 + .../DisabledForJreRangeIntegrationTests.java | 89 + .../condition/DisabledIfConditionTests.java | 112 + ...edIfEnvironmentVariableConditionTests.java | 144 + ...IfEnvironmentVariableIntegrationTests.java | 93 + .../condition/DisabledIfIntegrationTests.java | 97 + ...isabledIfSystemPropertyConditionTests.java | 131 + ...abledIfSystemPropertyIntegrationTests.java | 107 + .../DisabledOnJreConditionTests.java | 231 ++ .../DisabledOnJreIntegrationTests.java | 165 + .../condition/DisabledOnOsConditionTests.java | 267 ++ .../DisabledOnOsIntegrationTests.java | 175 + .../EnabledForJreRangeConditionTests.java | 132 + .../EnabledForJreRangeIntegrationTests.java | 92 + .../condition/EnabledIfConditionTests.java | 112 + ...edIfEnvironmentVariableConditionTests.java | 164 + ...IfEnvironmentVariableIntegrationTests.java | 101 + .../condition/EnabledIfIntegrationTests.java | 97 + ...EnabledIfSystemPropertyConditionTests.java | 138 + ...abledIfSystemPropertyIntegrationTests.java | 113 + .../condition/EnabledOnJreConditionTests.java | 231 ++ .../EnabledOnJreIntegrationTests.java | 207 + .../condition/EnabledOnOsConditionTests.java | 267 ++ .../EnabledOnOsIntegrationTests.java | 207 + .../junit/jupiter/api/condition/JRETests.java | 65 + .../api/condition/StaticConditionMethods.java | 23 + .../CloseableResourceIntegrationTests.java | 83 + .../ExecutableInvokerIntegrationTests.java | 92 + .../ExtensionComposabilityTests.java | 122 + .../api/extension/KitchenSinkExtension.java | 180 + .../TypeBasedParameterResolverTests.java | 159 + .../subpackage/SubclassedAssertionsTests.java | 37 + .../SubclassedAssumptionsTests.java | 45 + .../AbstractJupiterTestEngineTests.java | 68 + .../engine/AtypicalJvmMethodNameTests.java | 48 + ...AllAndAfterAllComposedAnnotationTests.java | 71 + ...chAndAfterEachComposedAnnotationTests.java | 71 + .../engine/DefaultExecutionModeTests.java | 186 + .../jupiter/engine/DefaultMethodTests.java | 230 ++ .../junit/jupiter/engine/DisabledTests.java | 76 + .../engine/DynamicNodeGenerationTests.java | 533 +++ .../engine/ExceptionHandlingTests.java | 359 ++ .../engine/FailedAssumptionsTests.java | 69 + ...alidLifecycleMethodConfigurationTests.java | 131 + .../engine/JupiterTestEngineBasicTests.java | 51 + ...leMethodOverridingAndSupersedingTests.java | 194 + .../MultipleTestableAnnotationsTests.java | 55 + .../engine/NestedTestClassesTests.java | 369 ++ .../engine/NestedWithInheritanceTests.java | 65 + .../NestedWithSeparateInheritanceTests.java | 95 + ...NonVoidTestableMethodIntegrationTests.java | 42 + .../engine/OverloadedTestMethodTests.java | 79 + .../org/junit/jupiter/engine/RecordTests.java | 38 + .../junit/jupiter/engine/ReportingTests.java | 83 + .../jupiter/engine/SealedClassTests.java | 45 + .../engine/StandardTestClassTests.java | 252 ++ ...estedBeforeAllAndAfterAllMethodsTests.java | 95 + .../engine/TestClassInheritanceTests.java | 283 ++ ...stInstanceLifecycleConfigurationTests.java | 234 ++ .../TestInstanceLifecycleKotlinTests.java | 80 + .../engine/TestInstanceLifecycleTests.java | 1067 +++++ .../engine/TestTemplateInvocationTests.java | 792 ++++ .../bridge/AbstractNonGenericTests.java | 65 + .../engine/bridge/AbstractNumberTests.java | 32 + .../engine/bridge/BridgeMethodTests.java | 103 + .../engine/bridge/ChildWithBridgeMethods.java | 38 + .../bridge/ChildWithoutBridgeMethods.java | 38 + .../jupiter/engine/bridge/NumberResolver.java | 46 + .../engine/bridge/NumberTestGroup.java | 61 + .../engine/bridge/PackagePrivateParent.java | 43 + .../CachingJupiterConfigurationTests.java | 138 + .../DefaultJupiterConfigurationTests.java | 118 + ...gConfigurationParameterConverterTests.java | 153 + .../CustomDisplayNameGenerator.java | 33 + .../descriptor/DisplayNameUtilsTests.java | 191 + .../descriptor/ExtensionContextTests.java | 345 ++ .../JupiterTestDescriptorTests.java | 406 ++ .../descriptor/LifecycleMethodUtilsTests.java | 214 + .../TestFactoryTestDescriptorTests.java | 195 + .../TestInstanceLifecycleUtilsTests.java | 115 + ...TemplateInvocationTestDescriptorTests.java | 56 + .../TestTemplateTestDescriptorTests.java | 113 + .../subpackage/Class1WithTestCases.java | 24 + .../subpackage/Class2WithTestCases.java | 24 + .../ClassWithStaticInnerTestCases.java | 35 + .../subpackage/ClassWithoutTestCases.java | 17 + .../DiscoverySelectorResolverTests.java | 900 ++++ .../engine/discovery/DiscoveryTests.java | 230 ++ .../predicates/IsInnerClassTests.java | 54 + .../predicates/IsNestedTestClassTests.java | 58 + .../IsPotentialTestContainerTests.java | 71 + .../predicates/IsTestClassWithTestsTests.java | 214 + .../predicates/IsTestFactoryMethodTests.java | 86 + .../predicates/IsTestMethodTests.java | 146 + .../predicates/IsTestTemplateMethodTests.java | 57 + .../AbstractExecutableInvokerTests.java | 98 + .../DefaultExecutableInvokerTests.java | 36 + .../execution/DefaultTestInstancesTests.java | 48 + .../DynamicTestIntegrationTests.java | 46 + ...ExtensionContextStoreConcurrencyTests.java | 52 + .../execution/ExtensionContextStoreTests.java | 90 + .../execution/ExtensionValuesStoreTests.java | 379 ++ .../InterceptingExecutableInvokerTests.java | 46 + .../JupiterEngineExecutionContextTests.java | 111 + .../ParameterResolutionUtilsTests.java | 465 +++ ...singForArrayParameterIntegrationTests.java | 67 + .../injection/sample/CustomAnnotation.java | 24 + .../CustomAnnotationParameterResolver.java | 35 + .../injection/sample/CustomType.java | 27 + .../sample/CustomTypeParameterResolver.java | 32 + .../sample/DoubleParameterResolver.java | 35 + .../sample/LongParameterResolver.java | 63 + .../MapOfListsTypeBasedParameterResolver.java | 32 + .../sample/MapOfStringsParameterResolver.java | 49 + .../sample/NullIntegerParameterResolver.java | 36 + .../sample/NumberParameterResolver.java | 49 + .../PrimitiveArrayParameterResolver.java | 34 + .../PrimitiveIntegerParameterResolver.java | 34 + .../extension/BeforeAndAfterAllTests.java | 385 ++ .../extension/BeforeAndAfterEachTests.java | 524 +++ ...oreAndAfterTestExecutionCallbackTests.java | 465 +++ .../extension/CloseablePathCleanupTests.java | 114 + .../engine/extension/EnigmaException.java | 20 + .../EventuallyInterruptibleInvocation.java | 32 + .../extension/ExecutionConditionTests.java | 210 + .../ExtensionContextExecutionTests.java | 83 + ...gistrationViaParametersAndFieldsTests.java | 928 +++++ .../extension/ExtensionRegistryTests.java | 213 + .../extension/InvocationInterceptorTests.java | 355 ++ ...eMethodExecutionExceptionHandlerTests.java | 570 +++ .../engine/extension/OrderedClassTests.java | 270 ++ .../engine/extension/OrderedMethodTests.java | 745 ++++ ...rogrammaticExtensionRegistrationTests.java | 311 ++ .../extension/ParameterResolverTests.java | 501 +++ ...rogrammaticExtensionRegistrationTests.java | 756 ++++ .../extension/RandomlyOrderedTests.java | 95 + .../engine/extension/RepeatedTestTests.java | 176 + .../SameThreadTimeoutInvocationTests.java | 54 + .../SeparateThreadTimeoutInvocationTest.java | 80 + .../extension/ServiceLoaderExtension.java | 28 + .../extension/TempDirectoryCleanupTests.java | 369 ++ .../TempDirectoryPerContextTests.java | 1308 ++++++ .../TempDirectoryPerDeclarationTests.java | 1068 +++++ .../TempDirectoryPreconditionTests.java | 119 + .../TestExecutionExceptionHandlerTests.java | 216 + .../TestInfoParameterResolverTests.java | 86 + .../extension/TestInstanceFactoryTests.java | 727 ++++ ...stProcessorAndPreDestroyCallbackTests.java | 260 ++ .../TestInstancePostProcessorTests.java | 172 + ...TestInstancePreConstructCallbackTests.java | 432 ++ .../TestInstancePreDestroyCallbackTests.java | 215 + ...ePreDestroyCallbackUtilityMethodTests.java | 111 + .../TestReporterParameterResolverTests.java | 72 + .../engine/extension/TestWatcherTests.java | 308 ++ .../extension/TimeoutConfigurationTests.java | 156 + .../extension/TimeoutDurationParserTests.java | 77 + .../extension/TimeoutDurationTests.java | 43 + .../TimeoutExceptionFactoryTest.java | 73 + .../extension/TimeoutExtensionTests.java | 851 ++++ .../TimeoutInvocationFactoryTest.java | 89 + .../sub/AlwaysDisabledCondition.java | 35 + .../sub/AnotherAlwaysDisabledCondition.java | 25 + .../sub/SystemPropertyCondition.java | 66 + ...cycleMethodInDifferentPackageTestCase.java | 32 + ...AndJUnit4AwareThrowableCollectorTests.java | 134 + .../api/KotlinAssertTimeoutAssertionsTests.kt | 291 ++ .../jupiter/api/KotlinAssertionsTests.kt | 223 + .../jupiter/api/KotlinFailAssertionsTests.kt | 122 + .../kotlin/ArbitraryNamingKotlinTestCase.kt | 26 + .../kotlin/InstancePerClassKotlinTestCase.kt | 62 + .../kotlin/InstancePerMethodKotlinTestCase.kt | 61 + .../org.junit.jupiter.api.extension.Extension | 1 + .../src/test/resources/log4j2-test.xml | 18 + .../discovery/JupiterUniqueIdBuilder.java | 66 + .../junit-jupiter-migrationsupport/README.md | 19 + .../junit-jupiter-migrationsupport.gradle.kts | 37 + .../EnableJUnit4MigrationSupport.java | 57 + .../conditions/IgnoreCondition.java | 59 + .../conditions/package-info.java | 7 + .../migrationsupport/package-info.java | 5 + .../rules/EnableRuleMigrationSupport.java | 47 + .../rules/ExpectedExceptionSupport.java | 67 + .../rules/ExternalResourceSupport.java | 56 + .../rules/TestRuleSupport.java | 168 + .../rules/VerifierSupport.java | 50 + .../adapter/AbstractTestRuleAdapter.java | 52 + .../adapter/ExpectedExceptionAdapter.java | 41 + .../adapter/ExternalResourceAdapter.java | 39 + .../adapter/GenericBeforeAndAfterAdvice.java | 32 + .../rules/adapter/VerifierAdapter.java | 34 + .../rules/adapter/package-info.java | 5 + .../AbstractTestRuleAnnotatedMember.java | 31 + .../rules/member/TestRuleAnnotatedField.java | 41 + .../rules/member/TestRuleAnnotatedMember.java | 26 + .../rules/member/TestRuleAnnotatedMethod.java | 31 + .../rules/member/package-info.java | 5 + .../migrationsupport/rules/package-info.java | 5 + .../module-info.java | 27 + .../JupiterMigrationSupportTestSuite.java | 38 + .../IgnoreAnnotationIntegrationTests.java | 85 + .../conditions/IgnoreConditionTests.java | 150 + .../rules/AbstractTestRuleAdapterTests.java | 83 + ...igrationSupportWithBothRuleTypesTests.java | 87 + .../rules/ExpectedExceptionSupportTests.java | 125 + ...ifferentDeclaredReturnTypesRulesTests.java | 78 + ...pportForMixedMethodAndFieldRulesTests.java | 94 + ...urceSupportForMultipleFieldRulesTests.java | 71 + ...rceSupportForMultipleMethodRulesTests.java | 75 + ...ceSupportForTemporaryFolderFieldTests.java | 42 + ...alResourceSupportWithInheritanceTests.java | 16 + .../ExternalResourceWithoutAdapterTests.java | 40 + .../rules/FailAfterAllHelper.java | 23 + ...rBasedEnableRuleMigrationSupportTests.java | 114 + ...pportForMixedMethodAndFieldRulesTests.java | 62 + .../WrongExtendWithForVerifierFieldTests.java | 46 + ...WrongExtendWithForVerifierMethodTests.java | 50 + .../src/test/resources/log4j2-test.xml | 15 + .../LICENSE-univocity-parsers.md | 168 + .../junit-jupiter-params.gradle.kts | 51 + .../jupiter/params/ParameterizedTest.java | 246 ++ .../params/ParameterizedTestExtension.java | 160 + .../ParameterizedTestInvocationContext.java | 46 + .../ParameterizedTestMethodContext.java | 276 ++ .../ParameterizedTestNameFormatter.java | 122 + .../ParameterizedTestParameterResolver.java | 122 + .../params/aggregator/AggregateWith.java | 48 + .../aggregator/ArgumentAccessException.java | 39 + .../params/aggregator/ArgumentsAccessor.java | 191 + .../ArgumentsAggregationException.java | 39 + .../aggregator/ArgumentsAggregator.java | 65 + .../aggregator/DefaultArgumentsAccessor.java | 129 + .../params/aggregator/package-info.java | 7 + .../ArgumentConversionException.java | 39 + .../params/converter/ArgumentConverter.java | 65 + .../jupiter/params/converter/ConvertWith.java | 46 + .../converter/DefaultArgumentConverter.java | 302 ++ .../FallbackStringToObjectConverter.java | 174 + .../converter/JavaTimeArgumentConverter.java | 72 + .../converter/JavaTimeConversionPattern.java | 47 + .../converter/SimpleArgumentConverter.java | 50 + .../converter/TypedArgumentConverter.java | 80 + .../params/converter/package-info.java | 7 + .../junit/jupiter/params/package-info.java | 5 + .../jupiter/params/provider/Arguments.java | 96 + .../params/provider/ArgumentsProvider.java | 48 + .../params/provider/ArgumentsSource.java | 48 + .../params/provider/ArgumentsSources.java | 46 + .../params/provider/CsvArgumentsProvider.java | 164 + .../provider/CsvFileArgumentsProvider.java | 216 + .../params/provider/CsvFileSource.java | 227 ++ .../params/provider/CsvParserFactory.java | 90 + .../params/provider/CsvParsingException.java | 38 + .../jupiter/params/provider/CsvSource.java | 281 ++ .../provider/EmptyArgumentsProvider.java | 66 + .../jupiter/params/provider/EmptySource.java | 53 + .../provider/EnumArgumentsProvider.java | 73 + .../jupiter/params/provider/EnumSource.java | 183 + .../provider/MethodArgumentsProvider.java | 207 + .../jupiter/params/provider/MethodSource.java | 135 + .../params/provider/NullAndEmptySource.java | 44 + .../provider/NullArgumentsProvider.java | 39 + .../jupiter/params/provider/NullEnum.java | 26 + .../jupiter/params/provider/NullSource.java | 43 + .../provider/ValueArgumentsProvider.java | 66 + .../jupiter/params/provider/ValueSource.java | 109 + .../jupiter/params/provider/package-info.java | 8 + .../params/support/AnnotationConsumer.java | 34 + .../AnnotationConsumerInitializer.java | 69 + .../jupiter/params/support/package-info.java | 9 + .../params/aggregator/ArgumentsAccessor.kt | 29 + .../org.junit.jupiter.params/module-info.java | 30 + .../ParameterizedTestExtensionTests.java | 369 ++ .../ParameterizedTestIntegrationTests.java | 1483 +++++++ .../ParameterizedTestMethodContextTests.java | 80 + .../ParameterizedTestNameFormatterTests.java | 303 ++ .../params/ParameterizedTestSuite.java | 38 + .../AggregatorIntegrationTests.java | 408 ++ .../DefaultArgumentsAccessorTests.java | 166 + .../DefaultArgumentConverterTests.java | 242 ++ .../FallbackStringToObjectConverterTests.java | 243 ++ .../JavaTimeArgumentConverterTests.java | 122 + .../TypedArgumentConverterTests.java | 176 + .../params/provider/ArgumentsTests.java | 59 + .../provider/CsvArgumentsProviderTests.java | 389 ++ .../CsvFileArgumentsProviderTests.java | 536 +++ .../provider/EnumArgumentsProviderTests.java | 139 + .../params/provider/EnumSourceTests.java | 118 + .../MethodArgumentsProviderTests.java | 998 +++++ .../provider/MockCsvAnnotationBuilder.java | 210 + .../provider/ValueArgumentsProviderTests.java | 170 + ...erizedTestNameFormatterIntegrationTests.kt | 62 + .../ArgumentsAccessorKotlinTests.kt | 50 + .../params/aggregator/DisplayNameTests.kt | 35 + .../src/test/resources/broken.csv | 2 + .../src/test/resources/default-max-chars.csv | 1 + .../resources/exceeds-default-max-chars.csv | 1 + .../resources/leading-trailing-spaces.csv | 2 + .../src/test/resources/log4j2-test.xml | 15 + .../params/two-column-with-headers.csv | 13 + .../org/junit/jupiter/params/two-column.csv | 4 + .../src/test/resources/single-column.csv | 5 + .../junit-jupiter/junit-jupiter.gradle.kts | 16 + .../module/org.junit.jupiter/module-info.java | 20 + .../junit-platform-commons.gradle.kts | 40 + .../platform/commons/JUnitException.java | 36 + .../PreconditionViolationException.java | 36 + .../platform/commons/annotation/Testable.java | 81 + .../commons/annotation/package-info.java | 5 + .../junit/platform/commons/function/Try.java | 374 ++ .../commons/function/package-info.java | 5 + .../commons/logging/LogRecordListener.java | 149 + .../platform/commons/logging/Logger.java | 118 + .../commons/logging/LoggerFactory.java | 187 + .../commons/logging/package-info.java | 11 + .../junit/platform/commons/package-info.java | 11 + .../commons/support/AnnotationSupport.java | 488 +++ .../commons/support/ClassSupport.java | 74 + .../support/HierarchyTraversalMode.java | 38 + .../commons/support/ModifierSupport.java | 241 ++ .../commons/support/ReflectionSupport.java | 336 ++ .../commons/support/SearchOption.java | 43 + .../commons/support/package-info.java | 11 + .../commons/util/AnnotationUtils.java | 510 +++ .../commons/util/BlacklistedExceptions.java | 60 + .../commons/util/ClassFileVisitor.java | 76 + .../platform/commons/util/ClassFilter.java | 78 + .../commons/util/ClassLoaderUtils.java | 94 + .../util/ClassNamePatternFilterUtils.java | 90 + .../platform/commons/util/ClassUtils.java | 95 + .../commons/util/ClasspathScanner.java | 261 ++ .../platform/commons/util/CloseablePath.java | 121 + .../commons/util/CollectionUtils.java | 206 + .../platform/commons/util/ExceptionUtils.java | 83 + .../platform/commons/util/FunctionUtils.java | 52 + .../junit/platform/commons/util/LruCache.java | 56 + .../platform/commons/util/ModuleUtils.java | 100 + .../platform/commons/util/PackageUtils.java | 102 + .../util/PreconditionViolationException.java | 39 + .../platform/commons/util/Preconditions.java | 318 ++ .../commons/util/ReflectionUtils.java | 1757 ++++++++ .../platform/commons/util/RuntimeUtils.java | 74 + .../platform/commons/util/StringUtils.java | 259 ++ .../commons/util/ToStringBuilder.java | 68 + .../commons/util/UnrecoverableExceptions.java | 56 + .../platform/commons/util/package-info.java | 11 + .../platform/commons/util/ModuleUtils.java | 218 + .../module-info.java | 54 + .../commons/test/ConcurrencyTestingUtils.java | 69 + ...nit-platform-console-standalone.gradle.kts | 93 + .../junit-platform-console/LICENSE-picocli.md | 194 + .../junit-platform-console.gradle.kts | 75 + .../platform/console/ConsoleLauncher.java | 131 + .../ConsoleLauncherExecutionResult.java | 83 + .../console/options/AvailableOptions.java | 444 ++ .../options/ClasspathEntriesConverter.java | 27 + .../console/options/CommandLineOptions.java | 363 ++ .../options/CommandLineOptionsParser.java | 29 + .../console/options/ConsoleUtils.java | 40 + .../platform/console/options/Details.java | 57 + .../PicocliCommandLineOptionsParser.java | 61 + .../console/options/SelectorConverter.java | 149 + .../junit/platform/console/options/Theme.java | 140 + .../console/options/package-info.java | 5 + .../junit/platform/console/package-info.java | 5 + .../junit/platform/console/tasks/Color.java | 83 + .../platform/console/tasks/ColorPalette.java | 128 + .../console/tasks/ConsoleTestExecutor.java | 150 + .../CustomContextClassLoaderExecutor.java | 51 + .../tasks/DiscoveryRequestCreator.java | 145 + .../console/tasks/FlatPrintingListener.java | 109 + .../junit/platform/console/tasks/Style.java | 40 + .../platform/console/tasks/TreeNode.java | 92 + .../platform/console/tasks/TreePrinter.java | 172 + .../console/tasks/TreePrintingListener.java | 79 + .../tasks/VerboseTreePrintingListener.java | 189 + .../platform/console/tasks/package-info.java | 5 + .../console/options/ConsoleUtils.java | 42 + .../console/ConsoleLauncherToolProvider.java | 37 + .../services/java.util.spi.ToolProvider | 1 + .../module-info.java | 25 + .../junit-platform-engine.gradle.kts | 19 + .../platform/engine/CompositeFilter.java | 90 + .../engine/ConfigurationParameters.java | 152 + .../platform/engine/DiscoveryFilter.java | 30 + .../platform/engine/DiscoverySelector.java | 28 + .../engine/EngineDiscoveryListener.java | 56 + .../engine/EngineDiscoveryRequest.java | 88 + .../engine/EngineExecutionListener.java | 140 + .../platform/engine/ExecutionRequest.java | 92 + .../org/junit/platform/engine/Filter.java | 115 + .../junit/platform/engine/FilterResult.java | 113 + .../engine/SelectorResolutionResult.java | 125 + .../junit/platform/engine/TestDescriptor.java | 309 ++ .../org/junit/platform/engine/TestEngine.java | 195 + .../platform/engine/TestExecutionResult.java | 132 + .../org/junit/platform/engine/TestSource.java | 34 + .../org/junit/platform/engine/TestTag.java | 153 + .../org/junit/platform/engine/UniqueId.java | 363 ++ .../junit/platform/engine/UniqueIdFormat.java | 161 + .../discovery/AbstractClassNameFilter.java | 50 + .../engine/discovery/ClassNameFilter.java | 77 + .../engine/discovery/ClassSelector.java | 111 + .../discovery/ClasspathResourceSelector.java | 104 + .../discovery/ClasspathRootSelector.java | 85 + .../engine/discovery/DirectorySelector.java | 110 + .../engine/discovery/DiscoverySelectors.java | 716 ++++ .../discovery/ExcludeClassNameFilter.java | 62 + .../discovery/ExcludePackageNameFilter.java | 79 + .../engine/discovery/FilePosition.java | 183 + .../engine/discovery/FileSelector.java | 120 + .../discovery/IncludeClassNameFilter.java | 63 + .../discovery/IncludePackageNameFilter.java | 79 + .../engine/discovery/IterationSelector.java | 93 + .../engine/discovery/MethodSelector.java | 220 + .../engine/discovery/ModuleSelector.java | 76 + .../engine/discovery/NestedClassSelector.java | 127 + .../discovery/NestedMethodSelector.java | 188 + .../engine/discovery/PackageNameFilter.java | 103 + .../engine/discovery/PackageSelector.java | 76 + .../engine/discovery/UniqueIdSelector.java | 77 + .../engine/discovery/UriSelector.java | 80 + .../engine/discovery/package-info.java | 7 + .../junit/platform/engine/package-info.java | 5 + .../engine/reporting/ReportEntry.java | 109 + .../engine/reporting/package-info.java | 6 + .../PrefixedConfigurationParameters.java | 79 + .../engine/support/config/package-info.java | 6 + .../descriptor/AbstractTestDescriptor.java | 187 + .../support/descriptor/ClassSource.java | 223 + .../descriptor/ClasspathResourceSource.java | 174 + .../descriptor/CompositeTestSource.java | 93 + .../support/descriptor/DefaultUriSource.java | 61 + .../support/descriptor/DirectorySource.java | 99 + .../support/descriptor/EngineDescriptor.java | 53 + .../support/descriptor/FilePosition.java | 178 + .../engine/support/descriptor/FileSource.java | 130 + .../support/descriptor/FileSystemSource.java | 38 + .../support/descriptor/MethodSource.java | 254 ++ .../support/descriptor/PackageSource.java | 93 + .../support/descriptor/ResourceUtils.java | 48 + .../engine/support/descriptor/UriSource.java | 78 + .../support/descriptor/package-info.java | 7 + .../ClassContainerSelectorResolver.java | 63 + .../EngineDiscoveryRequestResolution.java | 263 ++ .../EngineDiscoveryRequestResolver.java | 293 ++ .../support/discovery/SelectorResolver.java | 676 +++ .../support/discovery/package-info.java | 9 + .../filter/ClasspathScanningSupport.java | 70 + .../engine/support/filter/package-info.java | 6 + .../support/hierarchical/CompositeLock.java | 83 + ...DefaultParallelExecutionConfiguration.java | 67 + ...arallelExecutionConfigurationStrategy.java | 177 + .../hierarchical/EngineExecutionContext.java | 26 + .../hierarchical/ExclusiveResource.java | 134 + ...inPoolHierarchicalTestExecutorService.java | 231 ++ .../hierarchical/HierarchicalTestEngine.java | 115 + .../HierarchicalTestExecutor.java | 60 + .../HierarchicalTestExecutorService.java | 104 + .../support/hierarchical/LockManager.java | 88 + .../engine/support/hierarchical/Node.java | 362 ++ .../hierarchical/NodeExecutionAdvisor.java | 51 + .../support/hierarchical/NodeTestTask.java | 265 ++ .../hierarchical/NodeTestTaskContext.java | 55 + .../support/hierarchical/NodeTreeWalker.java | 105 + .../support/hierarchical/NodeUtils.java | 33 + .../engine/support/hierarchical/NopLock.java | 35 + .../OpenTest4JAwareThrowableCollector.java | 32 + .../ParallelExecutionConfiguration.java | 76 + ...arallelExecutionConfigurationStrategy.java | 33 + .../support/hierarchical/ResourceLock.java | 46 + ...ThreadHierarchicalTestExecutorService.java | 49 + .../support/hierarchical/SingleLock.java | 61 + .../hierarchical/SingleTestExecutor.java | 82 + .../hierarchical/ThrowableCollector.java | 217 + .../support/hierarchical/package-info.java | 8 + .../module-info.java | 33 + .../DemoEngineExecutionContext.java | 26 + .../DemoHierarchicalContainerDescriptor.java | 56 + .../DemoHierarchicalEngineDescriptor.java | 53 + .../DemoHierarchicalTestDescriptor.java | 67 + .../DemoHierarchicalTestEngine.java | 98 + .../platform/fakes/TestDescriptorStub.java | 30 + .../junit/platform/fakes/TestEngineSpy.java | 47 + .../junit/platform/fakes/TestEngineStub.java | 48 + .../junit-platform-jfr.gradle.kts | 31 + .../jfr/FlightRecordingDiscoveryListener.java | 103 + .../jfr/FlightRecordingExecutionListener.java | 162 + .../java/org/junit/platform/jfr/UniqueId.java | 28 + .../org/junit/platform/jfr/package-info.java | 5 + ...latform.launcher.LauncherDiscoveryListener | 1 + ...it.platform.launcher.TestExecutionListener | 1 + .../org.junit.platform.jfr/module-info.java | 32 + .../junit-platform-launcher.gradle.kts | 28 + .../launcher/EngineDiscoveryResult.java | 110 + .../junit/platform/launcher/EngineFilter.java | 172 + .../org/junit/platform/launcher/Launcher.java | 131 + .../platform/launcher/LauncherConstants.java | 142 + .../launcher/LauncherDiscoveryListener.java | 91 + .../launcher/LauncherDiscoveryRequest.java | 100 + .../platform/launcher/LauncherSession.java | 50 + .../launcher/LauncherSessionListener.java | 67 + .../launcher/PostDiscoveryFilter.java | 36 + .../junit/platform/launcher/TagFilter.java | 185 + .../launcher/TestExecutionListener.java | 175 + .../platform/launcher/TestIdentifier.java | 347 ++ .../org/junit/platform/launcher/TestPlan.java | 294 ++ .../CompositeEngineExecutionListener.java | 81 + .../core/CompositeTestExecutionListener.java | 112 + .../core/DefaultDiscoveryRequest.java | 97 + .../launcher/core/DefaultLauncher.java | 117 + .../launcher/core/DefaultLauncherConfig.java | 112 + .../launcher/core/DefaultLauncherSession.java | 127 + .../DelegatingEngineExecutionListener.java | 54 + .../core/EngineDiscoveryErrorDescriptor.java | 51 + .../core/EngineDiscoveryOrchestrator.java | 234 ++ .../core/EngineDiscoveryResultValidator.java | 66 + .../core/EngineExecutionOrchestrator.java | 156 + .../launcher/core/EngineFilterer.java | 94 + .../launcher/core/EngineIdValidator.java | 80 + .../core/ExecutionListenerAdapter.java | 68 + .../launcher/core/InternalLauncher.java | 25 + .../launcher/core/InternalTestPlan.java | 121 + .../launcher/core/LauncherConfig.java | 365 ++ .../core/LauncherConfigurationParameters.java | 290 ++ .../core/LauncherDiscoveryRequestBuilder.java | 332 ++ .../core/LauncherDiscoveryResult.java | 78 + .../launcher/core/LauncherFactory.java | 195 + .../launcher/core/ListenerRegistry.java | 95 + ...utcomeDelayingEngineExecutionListener.java | 90 + .../launcher/core/ServiceLoaderRegistry.java | 36 + .../core/ServiceLoaderTestEngineRegistry.java | 41 + .../core/SessionPerRequestLauncher.java | 77 + ...reamInterceptingTestExecutionListener.java | 99 + .../launcher/core/StreamInterceptor.java | 118 + .../launcher/core/TestEngineFormatter.java | 49 + .../platform/launcher/core/package-info.java | 8 + .../listeners/LegacyReportingUtils.java | 86 + .../launcher/listeners/LoggingListener.java | 130 + .../MutableTestExecutionSummary.java | 308 ++ .../launcher/listeners/OutputDir.java | 110 + .../listeners/SummaryGeneratingListener.java | 138 + .../listeners/TestExecutionSummary.java | 184 + .../listeners/UniqueIdTrackingListener.java | 190 + ...ortOnFailureLauncherDiscoveryListener.java | 65 + .../CompositeLauncherDiscoveryListener.java | 60 + .../discovery/LauncherDiscoveryListeners.java | 121 + .../LoggingLauncherDiscoveryListener.java | 107 + .../listeners/discovery/package-info.java | 9 + .../launcher/listeners/package-info.java | 7 + .../CompositeLauncherSessionListener.java | 41 + .../session/LauncherSessionListeners.java | 45 + .../listeners/session/package-info.java | 9 + .../junit/platform/launcher/package-info.java | 7 + .../launcher/tagexpression/DequeStack.java | 48 + .../launcher/tagexpression/Operator.java | 138 + .../launcher/tagexpression/Operators.java | 41 + .../launcher/tagexpression/ParseResult.java | 61 + .../launcher/tagexpression/ParseResults.java | 42 + .../launcher/tagexpression/ParseStatus.java | 84 + .../launcher/tagexpression/Parser.java | 34 + .../launcher/tagexpression/ShuntingYard.java | 164 + .../launcher/tagexpression/Stack.java | 28 + .../launcher/tagexpression/TagExpression.java | 50 + .../tagexpression/TagExpressions.java | 107 + .../launcher/tagexpression/Token.java | 51 + .../launcher/tagexpression/TokenWith.java | 26 + .../launcher/tagexpression/Tokenizer.java | 41 + .../launcher/tagexpression/package-info.java | 5 + ...it.platform.launcher.TestExecutionListener | 1 + .../module-info.java | 41 + ...onfigurationParametersFactoryForTests.java | 28 + ...LauncherFactoryForTestingPurposesOnly.java | 36 + .../LICENSE-open-test-reporting.md | 194 + .../junit-platform-reporting.gradle.kts | 28 + .../legacy/LegacyReportingUtils.java | 56 + .../reporting/legacy/package-info.java | 5 + .../LegacyXmlReportGeneratingListener.java | 133 + .../reporting/legacy/xml/XmlReportData.java | 143 + .../reporting/legacy/xml/XmlReportWriter.java | 417 ++ .../reporting/legacy/xml/package-info.java | 7 + .../reporting/open/xml/JUnitFactory.java | 35 + .../open/xml/LegacyReportingName.java | 26 + .../xml/OpenTestReportGeneratingListener.java | 266 ++ .../platform/reporting/open/xml/Type.java | 27 + .../platform/reporting/open/xml/UniqueId.java | 26 + .../reporting/open/xml/package-info.java | 5 + .../platform/reporting/package-info.java | 5 + ...it.platform.launcher.TestExecutionListener | 1 + .../platform/reporting/open/xml/catalog.xml | 4 + .../platform/reporting/open/xml/junit.xsd | 30 + .../module-info.java | 30 + .../junit-platform-runner.gradle.kts | 36 + .../junit/platform/runner/JUnitPlatform.java | 201 + .../runner/JUnitPlatformRunnerListener.java | 96 + .../runner/JUnitPlatformTestTree.java | 167 + .../junit/platform/runner/package-info.java | 6 + .../module-info.java | 25 + .../junit-platform-suite-api.gradle.kts | 15 + .../suite/api/ConfigurationParameter.java | 55 + .../suite/api/ConfigurationParameters.java | 46 + .../DisableParentConfigurationParameters.java | 44 + .../suite/api/ExcludeClassNamePatterns.java | 50 + .../platform/suite/api/ExcludeEngines.java | 46 + .../platform/suite/api/ExcludePackages.java | 45 + .../junit/platform/suite/api/ExcludeTags.java | 77 + .../suite/api/IncludeClassNamePatterns.java | 58 + .../platform/suite/api/IncludeEngines.java | 46 + .../platform/suite/api/IncludePackages.java | 45 + .../junit/platform/suite/api/IncludeTags.java | 77 + .../platform/suite/api/SelectClasses.java | 44 + .../suite/api/SelectClasspathResource.java | 59 + .../suite/api/SelectClasspathResources.java | 47 + .../platform/suite/api/SelectDirectories.java | 44 + .../junit/platform/suite/api/SelectFile.java | 58 + .../junit/platform/suite/api/SelectFiles.java | 46 + .../platform/suite/api/SelectModules.java | 45 + .../platform/suite/api/SelectPackages.java | 45 + .../junit/platform/suite/api/SelectUris.java | 44 + .../org/junit/platform/suite/api/Suite.java | 85 + .../platform/suite/api/SuiteDisplayName.java | 57 + .../platform/suite/api/UseTechnicalNames.java | 56 + .../platform/suite/api/package-info.java | 13 + .../module-info.java | 21 + .../junit-platform-suite-commons.gradle.kts | 18 + .../commons/AdditionalDiscoverySelectors.java | 116 + .../SuiteLauncherDiscoveryRequestBuilder.java | 234 ++ .../platform/suite/commons/package-info.java | 11 + .../module-info.java | 26 + .../junit-platform-suite-engine.gradle.kts | 18 + .../suite/engine/ClassSelectorResolver.java | 128 + .../engine/DiscoverySelectorResolver.java | 46 + .../engine/IsPotentialTestContainer.java | 42 + .../platform/suite/engine/IsSuiteClass.java | 34 + .../engine/NoTestsDiscoveredException.java | 23 + .../suite/engine/SuiteEngineDescriptor.java | 32 + .../platform/suite/engine/SuiteLauncher.java | 67 + .../suite/engine/SuiteTestDescriptor.java | 147 + .../suite/engine/SuiteTestEngine.java | 78 + .../platform/suite/engine/package-info.java | 5 + .../org.junit.platform.engine.TestEngine | 1 + .../module-info.java | 28 + .../junit-platform-suite.gradle.kts | 14 + .../org.junit.platform.suite/module-info.java | 19 + .../junit-platform-testkit/LICENSE.md | 98 + .../junit-platform-testkit.gradle.kts | 17 + .../platform/testkit/engine/Assertions.java | 88 + .../engine/EngineExecutionResults.java | 106 + .../testkit/engine/EngineTestKit.java | 446 ++ .../junit/platform/testkit/engine/Event.java | 260 ++ .../testkit/engine/EventConditions.java | 473 +++ .../testkit/engine/EventStatistics.java | 142 + .../platform/testkit/engine/EventType.java | 65 + .../junit/platform/testkit/engine/Events.java | 473 +++ .../platform/testkit/engine/Execution.java | 156 + .../testkit/engine/ExecutionRecorder.java | 91 + .../platform/testkit/engine/Executions.java | 302 ++ .../testkit/engine/TerminationInfo.java | 147 + .../engine/TestExecutionResultConditions.java | 145 + .../platform/testkit/engine/package-info.java | 6 + .../junit/platform/testkit/package-info.java | 5 + .../module-info.java | 29 + .../junit-vintage-engine.gradle.kts | 85 + .../vintage/engine/JUnit4VersionCheck.java | 77 + .../vintage/engine/VintageTestEngine.java | 85 + .../engine/descriptor/DescriptionUtils.java | 37 + .../vintage/engine/descriptor/OrFilter.java | 42 + .../engine/descriptor/RunnerDecorator.java | 23 + .../engine/descriptor/RunnerRequest.java | 31 + .../descriptor/RunnerTestDescriptor.java | 185 + .../engine/descriptor/TestSourceProvider.java | 91 + .../descriptor/VintageEngineDescriptor.java | 36 + .../descriptor/VintageTestDescriptor.java | 141 + .../engine/descriptor/package-info.java | 5 + .../discovery/ClassSelectorResolver.java | 82 + ...fensiveAllDefaultPossibilitiesBuilder.java | 151 + .../FilterableIgnoringRunnerDecorator.java | 36 + .../discovery/IgnoringRunnerDecorator.java | 49 + .../discovery/IsPotentialJUnit4TestClass.java | 44 + .../IsPotentialJUnit4TestMethod.java | 30 + .../discovery/MethodSelectorResolver.java | 121 + .../RunnerTestDescriptorPostProcessor.java | 76 + .../engine/discovery/UniqueIdFilter.java | 87 + .../engine/discovery/VintageDiscoverer.java | 51 + .../engine/discovery/package-info.java | 5 + .../vintage/engine/execution/EventType.java | 18 + .../engine/execution/RunListenerAdapter.java | 251 ++ .../engine/execution/RunnerExecutor.java | 58 + .../vintage/engine/execution/TestRun.java | 276 ++ .../engine/execution/package-info.java | 5 + .../junit/vintage/engine/package-info.java | 5 + .../engine/support/UniqueIdReader.java | 56 + .../engine/support/UniqueIdStringifier.java | 62 + .../vintage/engine/support/package-info.java | 6 + .../org.junit.platform.engine.TestEngine | 1 + .../org.junit.vintage.engine/module-info.java | 26 + .../engine/JUnit4ParameterizedTests.java | 81 + .../engine/JUnit4VersionCheckTests.java | 87 + .../VintageLauncherIntegrationTests.java | 288 ++ .../engine/VintageTestEngineBasicTests.java | 42 + .../VintageTestEngineDiscoveryTests.java | 803 ++++ .../VintageTestEngineExecutionTests.java | 923 +++++ .../engine/VintageTestEngineTestSuite.java | 38 + .../engine/VintageUniqueIdBuilder.java | 69 + .../descriptor/DescriptionUtilsTests.java | 62 + .../engine/descriptor/OrFilterTests.java | 78 + .../descriptor/TestSourceProviderTests.java | 38 + .../VintageTestDescriptorTests.java | 51 + .../IsPotentialJUnit4TestClassTests.java | 61 + ...unnerTestDescriptorPostProcessorTests.java | 80 + .../discovery/VintageDiscovererTests.java | 144 + .../engine/execution/TestRunTests.java | 60 + .../engine/support/UniqueIdReaderTests.java | 57 + .../support/UniqueIdStringifierTests.java | 101 + .../src/test/resources/log4j2-test.xml | 16 + ...ithUnrolledAndRegularFeatureMethods.groovy | 20 + ...inOldJavaClassWithoutAnyTestsTestCase.java | 22 + .../junit3/AbstractJUnit3TestCase.java | 26 + .../JUnit3ParallelSuiteWithSubsuites.java | 39 + ...ingleTestCaseWithSingleTestWhichFails.java | 27 + .../junit3/JUnit3SuiteWithSubsuites.java | 38 + ...Unit3TestCaseWithSingleTestWhichFails.java | 26 + .../junit4/AbstractJUnit4TestCase.java | 21 + ...unit4TestCaseWithConstructorParameter.java | 24 + .../engine/samples/junit4/Categories.java | 32 + .../junit4/CompletelyDynamicTestCase.java | 22 + .../junit4/ConcreteJUnit4TestCase.java | 14 + .../samples/junit4/ConfigurableRunner.java | 69 + .../engine/samples/junit4/DynamicRunner.java | 36 + .../samples/junit4/EmptyIgnoredTestCase.java | 17 + .../junit4/EnclosedJUnit4TestCase.java | 42 + ...thParameterizedChildrenJUnit4TestCase.java | 59 + .../junit4/ExceptionThrowingRunner.java | 29 + .../samples/junit4/IgnoredJUnit4TestCase.java | 36 + ...JUnit4TestCaseWithNotFilterableRunner.java | 20 + .../junit4/IgnoredParameterizedTestCase.java | 42 + .../junit4/JUnit4ParameterizedTestCase.java | 56 + ...SuiteOfSuiteWithFilterableChildRunner.java | 23 + ...SuiteOfSuiteWithIgnoredJUnit4TestCase.java | 23 + ...aseWithAssumptionFailureInBeforeClass.java | 23 + ...hJUnit4TestCaseWithErrorInBeforeClass.java | 23 + ...Unit4SuiteWithExceptionThrowingRunner.java | 22 + .../JUnit4SuiteWithIgnoredJUnit4TestCase.java | 23 + ...uiteWithJUnit3SuiteWithSingleTestCase.java | 24 + ...aseWithAssumptionFailureInBeforeClass.java | 23 + ...hJUnit4TestCaseWithErrorInBeforeClass.java | 23 + ...escriptionThatIsNotReportedAsFinished.java | 21 + ...nerWithCustomUniqueIdsAndDisplayNames.java | 23 + ...4TestCaseWithSingleTestWhichIsIgnored.java | 23 + .../junit4/JUnit4SuiteWithTwoTestCases.java | 23 + ...aseWithAssumptionFailureInBeforeClass.java | 34 + ...seWithDistinguishableOverloadedMethod.java | 35 + ...ErrorCollectorStoringMultipleFailures.java | 35 + .../JUnit4TestCaseWithErrorInAfterClass.java | 41 + .../JUnit4TestCaseWithErrorInBeforeClass.java | 33 + ...t4TestCaseWithExceptionThrowingRunner.java | 22 + ...escriptionThatIsNotReportedAsFinished.java | 24 + ...WithIndistinguishableOverloadedMethod.java | 39 + ...JUnit4TestCaseWithNotFilterableRunner.java | 30 + ...nerWithCustomUniqueIdsAndDisplayNames.java | 39 + ...ithDuplicateChangingChildDescriptions.java | 55 + .../junit4/MalformedJUnit4TestCase.java | 28 + .../samples/junit4/NotFilterableRunner.java | 22 + .../samples/junit4/ParameterizedTestCase.java | 42 + .../junit4/ParameterizedTimingTestCase.java | 68 + ...eterizedWithAfterParamFailureTestCase.java | 49 + ...terizedWithBeforeParamFailureTestCase.java | 49 + ...lainJUnit4TestCaseWithFiveTestMethods.java | 64 + ...ainJUnit4TestCaseWithLifecycleMethods.java | 68 + ...CaseWithSingleInheritedTestWhichFails.java | 17 + ...Unit4TestCaseWithSingleTestWhichFails.java | 27 + ...4TestCaseWithSingleTestWhichIsIgnored.java | 28 + ...PlainJUnit4TestCaseWithTwoTestMethods.java | 38 + .../junit4/RunnerThatOnlyReportsFailures.java | 34 + ...nerWithCustomUniqueIdsAndDisplayNames.java | 81 + .../junit4/SingleFailingTheoryTestCase.java | 29 + .../TestCaseRunWithJUnitPlatformRunner.java | 24 + .../platform-tests/platform-tests.gradle.kts | 96 + .../jupiter/jmh/AssertionBenchmarks.java | 59 + .../src/test/java/DefaultPackageTestCase.java | 28 + .../junit/jupiter/api/condition/OSTests.java | 79 + .../junit/jupiter/extensions/Heavyweight.java | 93 + .../jupiter/extensions/HeavyweightTests.java | 110 + .../AbstractEqualsAndHashCodeTests.java | 42 + .../platform/JUnitPlatformTestSuite.java | 38 + .../org/junit/platform/TestEngineTests.java | 60 + .../annotation/TestableAnnotationTests.java | 48 + .../platform/commons/function/TryTests.java | 114 + .../support/AnnotationSupportTests.java | 325 ++ .../commons/support/ClassSupportTests.java | 43 + .../commons/support/ModifierSupportTests.java | 243 ++ .../support/PreconditionAssertions.java | 34 + .../support/ReflectionSupportTests.java | 303 ++ .../commons/util/AnnotationUtilsTests.java | 967 +++++ .../commons/util/ClassLoaderUtilsTests.java | 70 + .../ClassNamePatternFilterUtilsTests.java | 137 + .../commons/util/ClassUtilsTests.java | 44 + .../commons/util/ClasspathScannerTests.java | 411 ++ .../commons/util/CollectionUtilsTests.java | 280 ++ .../commons/util/ExceptionUtilsTests.java | 65 + .../commons/util/FunctionUtilsTests.java | 49 + .../platform/commons/util/LruCacheTests.java | 35 + .../commons/util/ModuleUtilsTests.java | 37 + .../commons/util/PackageUtilsTests.java | 98 + .../commons/util/PreconditionsTests.java | 268 ++ .../commons/util/ReflectionUtilsTests.java | 1906 +++++++++ ...nUtilsWithGenericTypeHierarchiesTests.java | 182 + .../commons/util/RuntimeUtilsTests.java | 33 + .../commons/util/SerializationUtils.java | 34 + .../commons/util/StringUtilsTests.java | 192 + .../commons/util/ToStringBuilderTests.java | 168 + .../classes/AExecutionConditionClass.java | 26 + .../classes/ATestExecutionListenerClass.java | 20 + .../commons/util/classes/AVanillaEmpty.java | 18 + .../classes/BExecutionConditionClass.java | 26 + .../classes/BTestExecutionListenerClass.java | 20 + .../commons/util/classes/BVanillaEmpty.java | 18 + .../platform/console/ConsoleDetailsTests.java | 251 ++ .../ConsoleLauncherExecutionResultTests.java | 87 + .../ConsoleLauncherIntegrationTests.java | 68 + .../console/ConsoleLauncherTests.java | 110 + .../console/ConsoleLauncherWrapper.java | 69 + .../console/ConsoleLauncherWrapperResult.java | 156 + .../console/options/ConsoleUtilsTests.java | 28 + .../PicocliCommandLineOptionsParserTests.java | 655 +++ .../platform/console/options/ThemeTests.java | 36 + .../subpackage/ContainerForInnerTest.java | 30 + .../subpackage/ContainerForInnerTests.java | 30 + .../ContainerForStaticNestedTest.java | 28 + .../ContainerForStaticNestedTests.java | 28 + .../console/subpackage/SecondTest.java | 25 + .../platform/console/subpackage/Test.java | 23 + .../platform/console/subpackage/Test1.java | 25 + .../platform/console/subpackage/Tests.java | 25 + .../console/subpackage/ThirdTests.java | 25 + .../console/tasks/ColorPaletteTests.java | 207 + .../tasks/ConsoleTestExecutorTests.java | 183 + ...CustomContextClassLoaderExecutorTests.java | 76 + .../tasks/DiscoveryRequestCreatorTests.java | 339 ++ .../tasks/FlatPrintingListenerTests.java | 175 + .../platform/console/tasks/TreeNodeTests.java | 97 + .../console/tasks/TreePrinterTests.java | 123 + .../tasks/VerboseTreeListenerTests.java | 96 + .../engine/FilterCompositionTests.java | 74 + .../platform/engine/TestDescriptorTests.java | 38 + .../junit/platform/engine/TestTagTests.java | 105 + .../platform/engine/UniqueIdFormatTests.java | 141 + .../junit/platform/engine/UniqueIdTests.java | 299 ++ .../discovery/ClassNameFilterTests.java | 151 + .../engine/discovery/ClassSelectorTests.java | 46 + .../ClasspathResourceSelectorTests.java | 42 + .../discovery/ClasspathRootSelectorTests.java | 35 + .../discovery/DirectorySelectorTests.java | 33 + .../discovery/DiscoverySelectorsTests.java | 852 ++++ .../engine/discovery/FilePositionTests.java | 118 + .../engine/discovery/FileSelectorTests.java | 42 + .../engine/discovery/MethodSelectorTests.java | 51 + .../engine/discovery/ModuleSelectorTests.java | 33 + .../discovery/NestedClassSelectorTests.java | 62 + .../discovery/NestedMethodSelectorTests.java | 75 + .../discovery/PackageNameFilterTests.java | 127 + .../discovery/PackageSelectorTests.java | 33 + .../discovery/UniqueIdSelectorTests.java | 35 + .../engine/discovery/UriSelectorTests.java | 35 + .../PrefixedConfigurationParametersTests.java | 90 + .../AbstractTestDescriptorTests.java | 162 + .../descriptor/AbstractTestSourceTests.java | 85 + .../support/descriptor/ClassSourceTests.java | 181 + .../ClasspathResourceSourceTests.java | 120 + .../descriptor/CompositeTestSourceTests.java | 75 + .../descriptor/DefaultUriSourceTests.java | 59 + .../descriptor/DemoClassTestDescriptor.java | 71 + .../descriptor/DemoMethodTestDescriptor.java | 89 + .../support/descriptor/FilePositionTests.java | 122 + .../descriptor/FileSystemSourceTests.java | 95 + .../support/descriptor/MethodSourceTests.java | 293 ++ .../descriptor/PackageSourceTests.java | 72 + .../hierarchical/CompositeLockTests.java | 93 + ...elExecutionConfigurationStrategyTests.java | 201 + ...lHierarchicalTestExecutorServiceTests.java | 44 + .../HierarchicalTestExecutorTests.java | 753 ++++ .../hierarchical/LockManagerTests.java | 124 + .../support/hierarchical/MemoryLeakTests.java | 78 + .../NodeTreeWalkerIntegrationTests.java | 257 ++ .../ParallelExecutionIntegrationTests.java | 845 ++++ .../hierarchical/ResourceLockSupport.java | 27 + .../SameThreadExecutionIntegrationTests.java | 82 + .../support/hierarchical/SingleLockTests.java | 45 + .../hierarchical/SingleTestExecutorTests.java | 62 + .../hierarchical/ThrowableCollectorTests.java | 70 + ...dingDiscoveryListenerIntegrationTests.java | 51 + ...dingExecutionListenerIntegrationTests.java | 77 + .../launcher/DiscoveryFilterStub.java | 32 + .../junit/platform/launcher/FilterStub.java | 46 + .../launcher/PostDiscoveryFilterStub.java | 32 + .../platform/launcher/TagFilterTests.java | 226 + .../launcher/TagIntegrationTests.java | 121 + .../launcher/TestIdentifierTests.java | 113 + .../TestLauncherDiscoveryListener.java | 30 + .../launcher/TestLauncherSessionListener.java | 30 + .../platform/launcher/TestPlanTests.java | 80 + .../launcher/TestPostDiscoveryTagFilter.java | 23 + ...CompositeEngineExecutionListenerTests.java | 161 + .../CompositeTestExecutionListenerTests.java | 246 ++ .../DefaultLauncherEngineFilterTests.java | 194 + .../launcher/core/DefaultLauncherTests.java | 623 +++ .../EngineDiscoveryResultValidatorTests.java | 69 + .../core/ExecutionListenerAdapterTests.java | 75 + .../launcher/core/LauncherConfigTests.java | 141 + .../LauncherConfigurationParametersTests.java | 236 ++ .../LauncherDiscoveryRequestBuilderTests.java | 382 ++ .../launcher/core/LauncherFactoryTests.java | 255 ++ .../launcher/core/LauncherSessionTests.java | 109 + .../launcher/core/ListenerRegistryTests.java | 48 + ...TestExecutionListenerIntegrationTests.java | 151 + .../launcher/core/StreamInterceptorTests.java | 103 + .../AnotherUnusedTestExecutionListener.java | 17 + .../listeners/NoopTestExecutionListener.java | 20 + .../launcher/listeners/OutputDirTests.java | 87 + .../listeners/SummaryGenerationTests.java | 277 ++ ...queIdTrackingListenerIntegrationTests.java | 343 ++ .../UnusedTestExecutionListener.java | 17 + ...FailureLauncherDiscoveryListenerTests.java | 96 + ...bstractLauncherDiscoveryListenerTests.java | 47 + ...LoggingLauncherDiscoveryListenerTests.java | 128 + .../tagexpression/ParserErrorTests.java | 107 + .../launcher/tagexpression/ParserTests.java | 82 + .../tagexpression/TagExpressionsTests.java | 109 + .../launcher/tagexpression/TokenTests.java | 50 + .../tagexpression/TokenizerTests.java | 96 + .../legacy/LegacyReportingUtilsTests.java | 93 + .../legacy/xml/IncrementingClock.java | 52 + ...egacyXmlReportGeneratingListenerTests.java | 444 ++ .../legacy/xml/XmlReportAssertions.java | 62 + .../legacy/xml/XmlReportDataTests.java | 79 + .../legacy/xml/XmlReportWriterTests.java | 280 ++ ...OpenTestReportGeneratingListenerTests.java | 136 + .../runner/JUnitPlatformRunnerTests.java | 901 ++++ ...eLauncherDiscoveryRequestBuilderTests.java | 458 +++ .../suite/engine/SuiteEngineTests.java | 395 ++ .../engine/SuiteTestDescriptorTests.java | 120 + .../testcases/DynamicTestsTestCase.java | 34 + .../testcases/EmptyDynamicTestsTestCase.java | 28 + .../engine/testcases/EmptyTestTestCase.java | 18 + .../engine/testcases/JUnit4TestsTestCase.java | 20 + .../testcases/MultipleTestsTestCase.java | 27 + .../engine/testcases/SingleTestTestCase.java | 23 + .../engine/testcases/TaggedTestTestCase.java | 22 + .../engine/testsuites/AbstractSuite.java | 23 + .../suite/engine/testsuites/CyclicSuite.java | 25 + .../suite/engine/testsuites/DynamicSuite.java | 23 + .../engine/testsuites/EmptyCyclicSuite.java | 24 + .../testsuites/EmptyDynamicTestSuite.java | 23 + ...DynamicTestWithFailIfNoTestFalseSuite.java | 23 + .../engine/testsuites/EmptyTestCaseSuite.java | 23 + ...ptyTestCaseWithFailIfNoTestFalseSuite.java | 23 + .../engine/testsuites/MultiEngineSuite.java | 22 + .../engine/testsuites/MultipleSuite.java | 23 + .../suite/engine/testsuites/NestedSuite.java | 33 + .../engine/testsuites/SelectClassesSuite.java | 23 + .../testsuites/SuiteDisplayNameSuite.java | 25 + .../suite/engine/testsuites/SuiteSuite.java | 24 + .../testsuites/ThreePartCyclicSuite.java | 43 + .../testkit/engine/EngineTestKitTests.java | 82 + .../platform/testkit/engine/EventsTests.java | 287 ++ .../engine/ExecutionsIntegrationTests.java | 120 + .../NestedContainerEventConditionTests.java | 148 + ...Basic-changeDisplayName-flat-ascii.out.txt | 22 + ...sic-changeDisplayName-flat-unicode.out.txt | 22 + ...Basic-changeDisplayName-none-ascii.out.txt | 1 + ...sic-changeDisplayName-none-unicode.out.txt | 1 + ...ic-changeDisplayName-summary-ascii.out.txt | 14 + ...-changeDisplayName-summary-unicode.out.txt | 14 + ...Basic-changeDisplayName-tree-ascii.out.txt | 18 + ...sic-changeDisplayName-tree-unicode.out.txt | 18 + ...ic-changeDisplayName-verbose-ascii.out.txt | 28 + ...-changeDisplayName-verbose-unicode.out.txt | 28 + .../basic/Basic-empty-flat-ascii.out.txt | 22 + .../basic/Basic-empty-flat-unicode.out.txt | 22 + .../basic/Basic-empty-none-ascii.out.txt | 1 + .../basic/Basic-empty-none-unicode.out.txt | 1 + .../basic/Basic-empty-summary-ascii.out.txt | 14 + .../basic/Basic-empty-summary-unicode.out.txt | 14 + .../basic/Basic-empty-tree-ascii.out.txt | 18 + .../basic/Basic-empty-tree-unicode.out.txt | 18 + .../basic/Basic-empty-verbose-ascii.out.txt | 28 + .../basic/Basic-empty-verbose-unicode.out.txt | 28 + ...ailWithMultiLineMessage-flat-ascii.out.txt | 27 + ...lWithMultiLineMessage-flat-unicode.out.txt | 27 + ...ailWithMultiLineMessage-none-ascii.out.txt | 23 + ...lWithMultiLineMessage-none-unicode.out.txt | 23 + ...WithMultiLineMessage-summary-ascii.out.txt | 23 + ...thMultiLineMessage-summary-unicode.out.txt | 23 + ...ailWithMultiLineMessage-tree-ascii.out.txt | 30 + ...lWithMultiLineMessage-tree-unicode.out.txt | 30 + ...WithMultiLineMessage-verbose-ascii.out.txt | 33 + ...thMultiLineMessage-verbose-unicode.out.txt | 33 + ...ilWithSingleLineMessage-flat-ascii.out.txt | 24 + ...WithSingleLineMessage-flat-unicode.out.txt | 24 + ...ilWithSingleLineMessage-none-ascii.out.txt | 20 + ...WithSingleLineMessage-none-unicode.out.txt | 20 + ...ithSingleLineMessage-summary-ascii.out.txt | 20 + ...hSingleLineMessage-summary-unicode.out.txt | 20 + ...ilWithSingleLineMessage-tree-ascii.out.txt | 24 + ...WithSingleLineMessage-tree-unicode.out.txt | 24 + ...ithSingleLineMessage-verbose-ascii.out.txt | 30 + ...hSingleLineMessage-verbose-unicode.out.txt | 30 + ...ntriesWithMultiMappings-flat-ascii.out.txt | 28 + ...riesWithMultiMappings-flat-unicode.out.txt | 28 + ...ntriesWithMultiMappings-none-ascii.out.txt | 1 + ...riesWithMultiMappings-none-unicode.out.txt | 1 + ...iesWithMultiMappings-summary-ascii.out.txt | 14 + ...sWithMultiMappings-summary-unicode.out.txt | 14 + ...ntriesWithMultiMappings-tree-ascii.out.txt | 26 + ...riesWithMultiMappings-tree-unicode.out.txt | 26 + ...iesWithMultiMappings-verbose-ascii.out.txt | 31 + ...sWithMultiMappings-verbose-unicode.out.txt | 31 + ...ntriesWithSingleMapping-flat-ascii.out.txt | 26 + ...riesWithSingleMapping-flat-unicode.out.txt | 26 + ...ntriesWithSingleMapping-none-ascii.out.txt | 1 + ...riesWithSingleMapping-none-unicode.out.txt | 1 + ...iesWithSingleMapping-summary-ascii.out.txt | 14 + ...sWithSingleMapping-summary-unicode.out.txt | 14 + ...ntriesWithSingleMapping-tree-ascii.out.txt | 20 + ...riesWithSingleMapping-tree-unicode.out.txt | 20 + ...iesWithSingleMapping-verbose-ascii.out.txt | 30 + ...sWithSingleMapping-verbose-unicode.out.txt | 30 + ...-reportMultipleMessages-flat-ascii.out.txt | 26 + ...eportMultipleMessages-flat-unicode.out.txt | 26 + ...-reportMultipleMessages-none-ascii.out.txt | 1 + ...eportMultipleMessages-none-unicode.out.txt | 1 + ...portMultipleMessages-summary-ascii.out.txt | 14 + ...rtMultipleMessages-summary-unicode.out.txt | 14 + ...-reportMultipleMessages-tree-ascii.out.txt | 20 + ...eportMultipleMessages-tree-unicode.out.txt | 20 + ...portMultipleMessages-verbose-ascii.out.txt | 30 + ...rtMultipleMessages-verbose-unicode.out.txt | 30 + ...eEntryWithSingleMapping-flat-ascii.out.txt | 24 + ...ntryWithSingleMapping-flat-unicode.out.txt | 24 + ...eEntryWithSingleMapping-none-ascii.out.txt | 1 + ...ntryWithSingleMapping-none-unicode.out.txt | 1 + ...tryWithSingleMapping-summary-ascii.out.txt | 14 + ...yWithSingleMapping-summary-unicode.out.txt | 14 + ...eEntryWithSingleMapping-tree-ascii.out.txt | 19 + ...ntryWithSingleMapping-tree-unicode.out.txt | 19 + ...tryWithSingleMapping-verbose-ascii.out.txt | 29 + ...yWithSingleMapping-verbose-unicode.out.txt | 29 + ...ort-reportSingleMessage-flat-ascii.out.txt | 24 + ...t-reportSingleMessage-flat-unicode.out.txt | 24 + ...ort-reportSingleMessage-none-ascii.out.txt | 1 + ...t-reportSingleMessage-none-unicode.out.txt | 1 + ...-reportSingleMessage-summary-ascii.out.txt | 14 + ...eportSingleMessage-summary-unicode.out.txt | 14 + ...ort-reportSingleMessage-tree-ascii.out.txt | 19 + ...t-reportSingleMessage-tree-unicode.out.txt | 19 + ...-reportSingleMessage-verbose-ascii.out.txt | 29 + ...eportSingleMessage-verbose-unicode.out.txt | 29 + ...kipWithMultiLineMessage-flat-ascii.out.txt | 25 + ...pWithMultiLineMessage-flat-unicode.out.txt | 25 + ...kipWithMultiLineMessage-none-ascii.out.txt | 1 + ...pWithMultiLineMessage-none-unicode.out.txt | 1 + ...WithMultiLineMessage-summary-ascii.out.txt | 14 + ...thMultiLineMessage-summary-unicode.out.txt | 14 + ...kipWithMultiLineMessage-tree-ascii.out.txt | 21 + ...pWithMultiLineMessage-tree-unicode.out.txt | 21 + ...WithMultiLineMessage-verbose-ascii.out.txt | 31 + ...thMultiLineMessage-verbose-unicode.out.txt | 31 + ...kipWithSingleLineReason-flat-ascii.out.txt | 22 + ...pWithSingleLineReason-flat-unicode.out.txt | 22 + ...kipWithSingleLineReason-none-ascii.out.txt | 1 + ...pWithSingleLineReason-none-unicode.out.txt | 1 + ...WithSingleLineReason-summary-ascii.out.txt | 14 + ...thSingleLineReason-summary-unicode.out.txt | 14 + ...kipWithSingleLineReason-tree-ascii.out.txt | 18 + ...pWithSingleLineReason-tree-unicode.out.txt | 18 + ...WithSingleLineReason-verbose-ascii.out.txt | 28 + ...thSingleLineReason-verbose-unicode.out.txt | 28 + .../src/test/resources/do_not_delete_me.txt | 3 + .../src/test/resources/jenkins-junit.xsd | 118 + .../uidtracking/gradle/groovy/build.gradle | 1 + .../groovy/sub-project/sub-project.gradle | 1 + .../gradle/kotlin/build.gradle.kts | 1 + .../kotlin/sub-project/sub-project.gradle.kts | 1 + .../listeners/uidtracking/maven/pom.xml | 1 + .../src/test/resources/log4j2-test.xml | 23 + .../modules-2500/foo.bar/FooBar.java | 3 + .../modules-2500/foo.bar/module-info.java | 3 + .../test/resources/modules-2500/foo/Foo.java | 3 + .../modules-2500/foo/module-info.java | 3 + .../test/resources/serialized-test-identifier | Bin 0 -> 1079 bytes .../resources/test-junit-platform.properties | 1 + ...latform.launcher.LauncherDiscoveryListener | 1 + ....platform.launcher.LauncherSessionListener | 1 + ...unit.platform.launcher.PostDiscoveryFilter | 1 + ...it.platform.launcher.TestExecutionListener | 3 + .../testservices/junit-platform.properties | 1 + .../platform-tooling-support-tests.gradle.kts | 174 + .../projects/ant-starter/build.xml | 71 + .../java/com/example/project/Calculator.java | 19 + .../com/example/project/CalculatorTests.java | 41 + .../projects/graalvm-starter/build.gradle.kts | 29 + .../graalvm-starter/gradle.properties | 1 + .../graalvm-starter/settings.gradle.kts | 11 + .../java/com/example/project/Calculator.java | 19 + .../com/example/project/CalculatorTests.java | 41 + .../gradle-kotlin-extensions/build.gradle.kts | 33 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../projects/gradle-kotlin-extensions/gradlew | 188 + .../gradle-kotlin-extensions/gradlew.bat | 100 + .../settings.gradle.kts | 1 + .../project/ExtensionFunctionsTests.kt | 59 + .../gradle-missing-engine/build.gradle.kts | 38 + .../gradle-missing-engine/settings.gradle.kts | 1 + .../src/test/java/FooTests.java | 22 + .../projects/gradle-starter/build.gradle.kts | 42 + .../gradle-starter/settings.gradle.kts | 1 + .../java/com/example/project/Calculator.java | 19 + .../com/example/project/CalculatorTests.java | 41 + .../junit-jupiter-api.expected.txt | 12 + .../junit-jupiter-engine.expected.txt | 11 + ...unit-jupiter-migrationsupport.expected.txt | 11 + .../junit-jupiter-params.expected.txt | 13 + .../junit-jupiter.expected.txt | 5 + .../junit-platform-commons.expected.txt | 11 + .../junit-platform-console.expected.txt | 13 + .../junit-platform-engine.expected.txt | 13 + .../junit-platform-jfr.expected.txt | 9 + .../junit-platform-launcher.expected.txt | 16 + .../junit-platform-reporting.expected.txt | 11 + .../junit-platform-runner.expected.txt | 8 + .../junit-platform-suite-api.expected.txt | 5 + .../junit-platform-suite-commons.expected.txt | 8 + .../junit-platform-suite-engine.expected.txt | 10 + .../junit-platform-suite.expected.txt | 4 + .../junit-platform-testkit.expected.txt | 10 + .../junit-vintage-engine.expected.txt | 7 + .../projects/java-versions/pom.xml | 63 + .../test/java/JUnitPlatformCommonsTests.java | 37 + .../projects/maven-starter/pom.xml | 85 + .../java/com/example/project/Calculator.java | 19 + .../com/example/project/CalculatorTests.java | 41 + .../maven-surefire-compatibility/pom.xml | 74 + .../java/com/example/project/DummyTests.java | 26 + .../multi-release-jar/default/pom.xml | 73 + .../integration/JupiterIntegrationTests.java | 54 + .../integration/ModuleUtilsTests.java | 64 + .../projects/standalone/expected-err.txt | 18 + .../projects/standalone/expected-out.txt | 19 + .../projects/standalone/logging.properties | 3 + .../src/standalone/JupiterIntegration.java | 38 + .../standalone/JupiterParamsIntegration.java | 25 + .../src/standalone/SuiteIntegration.java | 26 + .../src/standalone/VintageIntegration.java | 39 + .../projects/vintage/build.gradle.kts | 33 + .../projects/vintage/pom.xml | 64 + .../projects/vintage/settings.gradle.kts | 1 + .../java/com/example/vintage/VintageTest.java | 27 + .../java/platform/tooling/support/Helper.java | 158 + .../platform/tooling/support/MavenRepo.java | 60 + .../platform/tooling/support/Request.java | 183 + .../tooling/support/ThirdPartyJars.java | 33 + .../tooling/support/package-info.java | 7 + .../platform/tooling/support/HelperTests.java | 73 + .../support/tests/AntStarterTests.java | 63 + .../tooling/support/tests/ArchUnitTests.java | 104 + .../support/tests/GraalVmStarterTests.java | 58 + .../tests/GradleKotlinExtensionsTests.java | 49 + .../tests/GradleMissingEngineTests.java | 67 + .../support/tests/GradleModuleFileTests.java | 165 + .../support/tests/GradleStarterTests.java | 58 + .../tests/JarContainsManifestFirstTests.java | 43 + .../support/tests/JarDescribeModuleTests.java | 77 + .../support/tests/JavaVersionsTests.java | 67 + .../tooling/support/tests/ManifestTests.java | 91 + .../support/tests/MavenPomFileTests.java | 75 + .../support/tests/MavenStarterTests.java | 56 + .../MavenSurefireCompatibilityTests.java | 70 + .../support/tests/ModularUserGuideTests.java | 203 + .../support/tests/MultiReleaseJarTests.java | 98 + .../tests/PackageCyclesDetectionTests.java | 51 + .../support/tests/StandaloneTests.java | 239 ++ .../support/tests/ToolProviderTests.java | 144 + .../tests/VintageGradleIntegrationTests.java | 73 + .../tests/VintageMavenIntegrationTests.java | 72 + .../tooling/support/tests/XmlAssertions.java | 158 + .../src/test/resources/log4j2-test.xml | 15 + .../settings.gradle.kts | 134 + .../src/checkBuildReproducibility.sh | 23 + .../src/checkstyle/checkstyleMain.xml | 39 + .../src/checkstyle/checkstyleTest.xml | 25 + .../src/checkstyle/suppressions.xml | 7 + .../junit-eclipse-formatter-settings.xml | 295 ++ .../src/eclipse/junit-eclipse.importorder | 7 + ...ishDocumentationSnapshotOnlyIfNecessary.sh | 62 + .../spotless/eclipse-public-license-2.0.java | 9 + src/main/java/com/test.java | 7 + 1694 files changed, 176440 insertions(+) create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.codecov.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.gitattributes create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/.gitignore create mode 100644 src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/KEYS create mode 100644 src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/LICENSE.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/README.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/SECURITY.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/README.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/fonts/Symbola.ttf create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.png create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.png create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle.png create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle_source.docx create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_execution_mode.svg create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradle.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradlew create mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradlew.bat create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/serialized-test-identifier create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java create mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh create mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java create mode 100644 src/main/java/com/test.java diff --git a/src/main/java/com/ctci/treesandgraphs/CheckBalanced.java b/src/main/java/com/ctci/treesandgraphs/CheckBalanced.java index d7658cd8..60dab15b 100644 --- a/src/main/java/com/ctci/treesandgraphs/CheckBalanced.java +++ b/src/main/java/com/ctci/treesandgraphs/CheckBalanced.java @@ -1,5 +1,7 @@ package com.ctci.treesandgraphs; + + /** * Implement a function to check if a binary tree is balanced. For the purposes of this question, a balanced * tree is defined to be a tree such that the heights of the two subtrees of any node never differ by more than one. diff --git a/src/main/java/com/junit-team-junit5-da216b8/.codecov.yml b/src/main/java/com/junit-team-junit5-da216b8/.codecov.yml new file mode 100644 index 00000000..04dae764 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.codecov.yml @@ -0,0 +1,8 @@ +comment: false +coverage: + status: + project: + default: + threshold: 1 + informational: true + patch: off diff --git a/src/main/java/com/junit-team-junit5-da216b8/.gitattributes b/src/main/java/com/junit-team-junit5-da216b8/.gitattributes new file mode 100644 index 00000000..d836e426 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.gitattributes @@ -0,0 +1,7 @@ +* text eol=lf +*.bat text eol=crlf +*.png binary +*.key binary +*.jar binary +*.ttf binary +release-notes-* merge=union diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml new file mode 100644 index 00000000..0786ce3e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: https://junit.org/sponsoring diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..f1ce8b0c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,23 @@ +## Overview + +_Replace the following bullet points with your issue description, +after answering yourself: **"What kind of issue is this?"**_ + +- ( ) **Question.** This issue tracker is not the place for questions. +If you want to ask how to do something, or to understand why +something isn't working the way you expect it to, please first use Stack +Overflow or Gitter. +https://stackoverflow.com/questions/tagged/junit5 + +- ( ) **Bug report.** Please provide us the version of JUnit 5 you are +using and, if possible, a failing unit test with your bug report. Don't +forget to describe the rationale for this issue (e.g. expected vs. +actual behavior). + +- ( ) **Feature request.** Start by telling us what problem you’re trying +to solve. Often a solution already exists! Please, don’t send pull requests +to implement new features without first getting our support. + +## Deliverables + +- [ ] ... diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..8d847d25 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,20 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + + + +## Steps to reproduce + + + +## Context + + - Used versions (Jupiter/Vintage/Platform): + - Build Tool/IDE: + +## Deliverables + +- [ ] ... diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..24e26904 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,11 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + + + +## Deliverables + +- [ ] ... diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000..23c796b2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,11 @@ +--- +name: Question +about: Please first ask on Gitter or StackOverflow before creating an issue +--- + +This issue tracker is not the place for questions. +If you want to ask how to do something, or to understand why +something isn't working the way you expect it to, please use Gitter [1] or Stack Overflow [2]. + +[1] https://gitter.im/junit-team/junit5 +[2] https://stackoverflow.com/questions/tagged/junit5 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md b/src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..1bdc52cd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## Overview + + + +--- + +I hereby agree to the terms of the [JUnit Contributor License Agreement](https://github.com/junit-team/junit5/blob/002a0052926ddee57cf90580fa49bc37e5a72427/CONTRIBUTING.md#junit-contributor-license-agreement). + +--- + +### Definition of Done + +- [ ] There are no TODOs left in the code +- [ ] Method [preconditions](https://junit.org/junit5/docs/snapshot/api/org.junit.platform.commons/org/junit/platform/commons/util/Preconditions.html) are checked and documented in the method's Javadoc +- [ ] [Coding conventions](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#coding-conventions) (e.g. for logging) have been followed +- [ ] Change is covered by [automated tests](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#tests) including corner cases, errors, and exception handling +- [ ] Public API has [Javadoc](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#javadoc) and [`@API` annotations](https://apiguardian-team.github.io/apiguardian/docs/current/api/org/apiguardian/api/API.html) +- [ ] Change is documented in the [User Guide](https://junit.org/junit5/docs/snapshot/user-guide/) and [Release Notes](https://junit.org/junit5/docs/snapshot/user-guide/#release-notes) +- [ ] All [continuous integration builds](https://github.com/junit-team/junit5#continuous-integration-builds) pass diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml new file mode 100644 index 00000000..a7817cd3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml @@ -0,0 +1,19 @@ +name: Main build +description: Sets up JDKs and runs Gradle +inputs: + arguments: + required: true + description: Gradle arguments + default: build +runs: + using: "composite" + steps: + - uses: ./.github/actions/setup-test-jdk + - uses: ./.github/actions/run-gradle + with: + arguments: ${{ inputs.arguments }} + - uses: actions/upload-artifact@v3 + if: ${{ always() }} + with: + name: Test Distribution trace files (${{ github.job }}) + path: '**/build/test-results/*/trace.json' diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml new file mode 100644 index 00000000..50139359 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml @@ -0,0 +1,25 @@ +name: Run Gradle +description: Sets up Gradle JDKs and runs Gradle +inputs: + arguments: + required: true + description: Gradle arguments + default: build +runs: + using: "composite" + steps: + - uses: actions/setup-java@v3 + id: setup-gradle-jdk + with: + distribution: temurin + java-version: 17 + - uses: gradle/gradle-build-action@v2 + env: + JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} + with: + arguments: | + -Porg.gradle.java.installations.auto-download=false + -PenablePredictiveTestSelection=${{ github.event_name == 'pull_request' }} + "-Dscan.value.GitHub job=${{ github.job }}" + javaToolchains + ${{ inputs.arguments }} diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml new file mode 100644 index 00000000..4e8c9626 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml @@ -0,0 +1,11 @@ +name: Set up Test JDK +description: Sets up the JDK required to run platform-tooling-support-tests +runs: + using: "composite" + steps: + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 8 + - shell: bash + run: echo "JDK8=$JAVA_HOME" >> $GITHUB_ENV diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml new file mode 100644 index 00000000..29a11556 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +registries: + gradle-plugin-portal: + type: maven-repository + url: https://plugins.gradle.org/m2 + username: dummy # Required by dependabot + password: dummy # Required by dependabot +updates: + - package-ecosystem: "gradle" + directory: "/" + allow: + - dependency-name: "com.gradle*" + registries: + - gradle-plugin-portal + schedule: + interval: "weekly" + open-pull-requests-limit: 10 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml new file mode 100644 index 00000000..2ef85c0b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml @@ -0,0 +1,69 @@ +# Configuration for probot-stale - https://github.com/probot/stale +# Configuration options apply to both Issues and Pull Requests. +# We configure those individually to match our workflow (see `pulls:` and `issues:`) + +pulls: + # Number of days of inactivity before a Pull Request becomes stale + daysUntilStale: 60 + + # Number of days of inactivity before a Pull Request with the stale label is closed. + # Set to false to disable. If disabled, Pull Request still need to be closed manually, but will remain marked as stale. + daysUntilClose: 21 + + # Comment to post when marking as stale. Set to `false` to disable + markComment: > + This pull request has been automatically marked as stale because it has not had recent activity. + Given the limited bandwidth of the team, it will be closed if no further activity occurs. + If you intend to work on this pull request, please reopen the PR. + Thank you for your contributions. + # Comment to post when closing a stale Pull Request. + closeComment: > + This pull request has been automatically closed due to inactivity. + If you are still interested in contributing this, please ensure that + it is rebased against the latest branch (usually `main`), all review + comments have been addressed and the build is passing. +issues: + daysUntilStale: 365 + + # Number of days of inactivity before an Issue with the stale label is closed. + # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. + daysUntilClose: 21 + + # Comment to post when marking as stale. Set to `false` to disable + markComment: > + This issue has been automatically marked as stale because it has not had recent activity. + Given the limited bandwidth of the team, it will be automatically closed if no further + activity occurs. + Thank you for your contribution. + # Comment to post when closing a stale Issue. + closeComment: > + This issue has been automatically closed due to inactivity. If you have a good use case for this + feature, please feel free to reopen the issue. + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +#onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: [] + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: "status: stale" + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +# only: issues diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..222d54f2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [main, releases/**] + pull_request: + # The branches below must be a subset of the branches above + branches: [main, releases/**] + schedule: + - cron: '0 19 * * 3' + +env: + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + language: + - java + - javascript + steps: + - name: Check out repository + uses: actions/checkout@v3 + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + tools: latest + - name: Build + uses: ./.github/actions/run-gradle + with: + arguments: | + --no-build-cache + -Dscan.tag.CodeQL + allMainClasses + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml new file mode 100644 index 00000000..a6a6e0c3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml @@ -0,0 +1,53 @@ +name: Cross-Version + +on: + push: + branches: + - main + - 'releases/*' + pull_request: + branches: + - '*' + +env: + ORG_GRADLE_PROJECT_enableTestDistribution: true + ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }} + ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ secrets.BUILD_CACHE_PASSWORD }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + +jobs: + openjdk: + strategy: + fail-fast: false + matrix: + jdk: [19, 20, 21] + name: "OpenJDK ${{ matrix.jdk }}" + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Set up Test JDK + uses: ./.github/actions/setup-test-jdk + - name: 'Set up JDK ${{ matrix.jdk }}' + uses: oracle-actions/setup-java@v1 + with: + website: jdk.java.net + release: ${{ matrix.jdk }} + version: latest + - name: 'Prepare JDK${{ matrix.jdk }} env var' + shell: bash + run: echo "JDK${{ matrix.jdk }}=$JAVA_HOME" >> $GITHUB_ENV + - name: Build + uses: ./.github/actions/run-gradle + with: + arguments: | + -PjavaToolchainVersion=${{ matrix.jdk }} + -Dscan.tag.JDK_${{ matrix.jdk }} + build + - name: Upload Test Distribution trace files + uses: actions/upload-artifact@v3 + with: + name: "Test Distribution trace files (OpenJDK ${{ matrix.jdk }})" + path: '**/build/test-results/*/trace.json' diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 00000000..b4182954 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,14 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "Validation" + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml new file mode 100644 index 00000000..3dadaf6b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml @@ -0,0 +1,14 @@ +name: Label new issues +on: + issues: + types: ['opened'] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: Renato66/auto-label@69b3cbe79438b2079aed0a49474275d3e572cae5 # or v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + labels-synonyms: '{"3rd-party: Eclipse":["Eclipse"],"3rd-party: Gradle":["Gradle"],"3rd-party: IntelliJ IDEA":["IDEA","IntelliJ"],"3rd-party: Maven Surefire":["Failsafe","Maven","Surefire"],"3rd-party: Pioneer":["pioneer"],"component: Groovy":["Groovy"],"component: Test Kit":["Test Kit","TestKit"]}' + labels-not-allowed: '["dependencies","status: blocked","status: declined","status: duplicate","status: in progress","status: invalid","status: stale","status: superseded","status: team discussion","status: waiting-for-feedback","status: waiting-for-interest","status: works-as-designed","up-for-grabs"]' + default-labels: '["status: new"]' diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml new file mode 100644 index 00000000..b1e40cf1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml @@ -0,0 +1,110 @@ +name: CI + +on: + push: + branches: + - main + - 'releases/*' + pull_request: + branches: + - '*' + +env: + ORG_GRADLE_PROJECT_enableTestDistribution: true + ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }} + ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ secrets.BUILD_CACHE_PASSWORD }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + +jobs: + Linux: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Install Graphviz + run: | + sudo apt-get update + sudo apt-get install graphviz + - name: Install GraalVM + uses: graalvm/setup-graalvm@v1 + with: + version: 'latest' + java-version: '17' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Build + uses: ./.github/actions/main-build + with: + arguments: | + -PenableJaCoCo + build + jacocoRootReport + prepareDocsForUploadToGhPages + - name: Upload to Codecov.io + uses: codecov/codecov-action@v3 + + Windows: + runs-on: windows-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Build + uses: ./.github/actions/main-build + + macOS: + runs-on: macos-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Build + uses: ./.github/actions/main-build + + publish_artifacts: + name: Publish Snapshot Artifacts + needs: linux + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Publish + uses: ./.github/actions/run-gradle + env: + ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} + with: + arguments: publish -x check + + update_documentation: + name: Update Snapshot Documentation + concurrency: + group: github-pages + cancel-in-progress: true + needs: Linux + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && github.ref == 'refs/heads/main' + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Install Graphviz + run: | + sudo apt-get update + sudo apt-get install graphviz + - name: Restore Gradle cache and display toolchains + uses: ./.github/actions/run-gradle + with: + arguments: --quiet + - name: Upload Documentation + env: + GRGIT_USER: ${{ secrets.GH_TOKEN }} + run: ./src/publishDocumentationSnapshotOnlyIfNecessary.sh diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml new file mode 100644 index 00000000..3327cc7f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml @@ -0,0 +1,31 @@ +name: Reproducible build + +on: + push: + branches: + - main + - 'releases/*' + pull_request: + branches: + - '*' + +env: + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} + +jobs: + check_build_reproducibility: + name: 'Check build reproducibility' + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Restore Gradle cache and display toolchains + uses: ./.github/actions/run-gradle + with: + arguments: --quiet + - name: Build and compare checksums + shell: bash + run: | + ./src/checkBuildReproducibility.sh diff --git a/src/main/java/com/junit-team-junit5-da216b8/.gitignore b/src/main/java/com/junit-team-junit5-da216b8/.gitignore new file mode 100644 index 00000000..e5f0af67 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/.gitignore @@ -0,0 +1,34 @@ +# Gradle +.gradle +/build/ +/*/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Eclipse +.classpath +.settings/ +.project +/bin/ +/*/bin + +# IntelliJ +*.iml +*.ipr +*.iws +*.uml +**/.idea/* +!/.idea/icon.png +!/.idea/vcs.xml +/out/ +/*/out/ + +# Misc +*.log +*.graphml +coverage.db* +.metadata +/.sdkmanrc + +checksums* diff --git a/src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md b/src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..c8726293 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at team@junit.org. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md b/src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md new file mode 100644 index 00000000..e2a44b38 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md @@ -0,0 +1,159 @@ +# Contributing + +## JUnit Contributor License Agreement + +- You will only Submit Contributions where You have authored 100% of the content. +- You will only Submit Contributions to which You have the necessary rights. This means + that if You are employed You have received the necessary permissions from Your employer + to make the Contributions. +- Whatever content You Contribute will be provided under the Project License(s). + +### Project Licenses + +- All modules use [Eclipse Public License v2.0](LICENSE.md). + +## Commit Messages + +As a general rule, the style and formatting of commit messages should follow the guidelines in +[How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/). + +In addition, any commit that is related to an existing issue must reference the issue. +For example, if a commit in a pull request addresses issue \#999, it must contain the +following at the bottom of the commit message. + +``` +Issue: #999 +``` + +## Pull Requests + +Our [Definition of Done](https://github.com/junit-team/junit5/wiki/Definition-of-Done) +offers some guidelines on what we expect from a pull request. +Feel free to open a pull request that does not fulfill all criteria, e.g. to discuss +a certain change before polishing it, but please be aware that we will only merge it +in case the DoD is met. + +Please add the following lines to your pull request description: + +```markdown +--- + +I hereby agree to the terms of the JUnit Contributor License Agreement. +``` + +## Coding Conventions + +### Naming Conventions + +Whenever an acronym is included as part of a type name or method name, keep the first +letter of the acronym uppercase and use lowercase for the rest of the acronym. Otherwise, +it becomes _impossible_ to perform camel-cased searches in IDEs, and it becomes +potentially very difficult for mere humans to read or reason about the element without +reading documentation (if documentation even exists). + +Consider for example a use case needing to support an HTTP URL. Calling the method +`getHTTPURL()` is absolutely horrible in terms of usability; whereas, `getHttpUrl()` is +great in terms of usability. The same applies for types `HTTPURLProvider` vs +`HttpUrlProvider`, etc. + +Whenever an acronym is included as part of a field name or parameter name: + +- If the acronym comes at the start of the field or parameter name, use lowercase for the + entire acronym -- for example, `String url;`. +- Otherwise, keep the first letter of the acronym uppercase and use lowercase for the + rest of the acronym -- for example, `String defaultUrl;`. + +### Formatting + +#### Code + +Code formatting is enforced using the [Spotless](https://github.com/diffplug/spotless) +Gradle plugin. You can use `gradle spotlessApply` to format new code and add missing +license headers to source files. Formatter and import order settings for Eclipse are +available in the repository under +[src/eclipse/junit-eclipse-formatter-settings.xml](src/eclipse/junit-eclipse-formatter-settings.xml) +and [src/eclipse/junit-eclipse.importorder](src/eclipse/junit-eclipse.importorder), +respectively. For IntelliJ IDEA there's a +[plugin](https://plugins.jetbrains.com/plugin/6546) you can use in conjunction with the +Eclipse settings. + +It is forbidden to use _wildcard imports_ (e.g., `import static org.junit.jupiter.api.Assertions.*;`) +in Java code. + +#### Documentation + +Text in `*.adoc` and `*.md` files should be wrapped at 90 characters whenever technically +possible. + +In multi-line bullet point entries, subsequent lines should be indented. + +### Spelling + +Use American English spelling rules when writing documentation as well as for +code -- class names, method names, variable names, etc. + +### Javadoc + +- Javadoc comments should be wrapped after 80 characters whenever possible. +- This first paragraph must be a single, concise sentence that ends with a period ("."). +- Place `

` on the same line as the first line in a new paragraph and precede `

` with a blank line. +- Insert a blank line before at-clauses/tags. +- Favor `{@code foo}` over `foo`. +- Favor literals (e.g., `{@literal @}`) over HTML entities. +- New classes and methods should have `@since ...` annotation. +- Use `@since 5.0` instead of `@since 5.0.0`. +- Do not use `@author` tags. Instead, contributors are listed on [GitHub](https://github.com/junit-team/junit5/graphs/contributors). +- Do not use verbs in third person form (e.g. use "Discover tests..." instead of "Discovers tests...") + in the first sentence describing a method. + +#### Examples + +See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java) and +[`ParameterContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java) for example Javadoc. + + +### Tests + +#### Naming + +- All test classes must end with a `Tests` suffix. +- Example test classes that should not be picked up by the build must end with a `TestCase` suffix. + +#### Assertions + +- Use `org.junit.jupiter.api.Assertions` wherever possible. +- Use AssertJ when richer assertions are needed. +- Do not use `org.junit.Assert` or `junit.framework.Assert`. + +#### Mocking + +- Use either [Mockito](https://github.com/mockito/mockito) or hand-written test doubles. + +### Logging + +- In general, logging should be used sparingly. +- All logging must be performed via the internal `Logger` façade provided via the JUnit [LoggerFactory](https://github.com/junit-team/junit5/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java). +- Levels defined in JUnit's [Logger](https://github.com/junit-team/junit5/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java) façade, which delegates to Java Util Logging (JUL) for the actual logging. + - _error_ (JUL: `SEVERE`, Log4J: `ERROR`): extra information (in addition to an Exception) about errors that will halt execution + - _warn_ (JUL: `WARNING`, Log4J: `WARN`): potential usage or configuration errors that should not halt execution + - _info_ (JUL: `INFO`, Log4J: `INFO`): information the users might want to know but not by default + - _config_ (JUL: `CONFIG`, Log4J: `CONFIG`): information related to configuration of the system (Example: `ServiceLoaderTestEngineRegistry` logs IDs of discovered engines) + - _debug_ (JUL: `FINE`, Log4J: `DEBUG`) + - _trace_ (JUL: `FINER`, Log4J: `TRACE`) + +### Deprecation + +Publicly available interfaces, classes and methods have a defined lifecycle +which is described in detail in the [User Guide](https://junit.org/junit5/docs/current/user-guide/#api-evolution). +This process is using the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian). +It also describes the deprecation process followed for API items. + +To deprecate an item: +- Update the `@API.status` to `DEPRECATED`. +- Update `@API.since`. Please note `since` describes the version when the + status was changed and not the introduction of the element. +- Add the `@Deprecated` Java annotation on the item. +- Add the `@deprecated` JavaDoc tag to describe the deprecation, and refer to + an eventual replacement. +- If the item is used in existing code, add `@SuppressWarnings("deprecation")` + to make the build pass. diff --git a/src/main/java/com/junit-team-junit5-da216b8/KEYS b/src/main/java/com/junit-team-junit5-da216b8/KEYS new file mode 100644 index 00000000..0433e72f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/KEYS @@ -0,0 +1,70 @@ +This file contains the PGP key that is used to sign releases. + +Importing: `pgp < KEYS` or `gpg --import KEYS` + +Adding a key: +`pgp -kxa >> KEYS`, +or `(pgpk -ll && pgpk -xa ) >> KEYS`, +or `(gpg --list-sigs && gpg --armor --export ) >> KEYS` + +================================ + +pub rsa4096 2018-04-08 [SC] + FF6E2C001948C5F2F38B0CC385911F425EC61B51 +uid [ unknown] Open Source Development +sig 3 85911F425EC61B51 2018-04-08 Open Source Development +sub rsa4096 2018-04-08 [E] +sig 85911F425EC61B51 2018-04-08 Open Source Development + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +mdzS2PJko7mm +=l0XA +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md b/src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md new file mode 100644 index 00000000..520713de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md @@ -0,0 +1,8 @@ +Open Source Licenses +==================== + +This product may include a number of subcomponents with separate +copyright notices and license terms. Your use of the source code for +these subcomponents is subject to the terms and conditions of the +subcomponent's license, as noted in the LICENSE-.md +files. diff --git a/src/main/java/com/junit-team-junit5-da216b8/LICENSE.md b/src/main/java/com/junit-team-junit5-da216b8/LICENSE.md new file mode 100644 index 00000000..a32decd8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/LICENSE.md @@ -0,0 +1,98 @@ +Eclipse Public License - v 2.0 +============================== + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +### 1. Definitions + +“Contribution” means: +* **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and +* **b)** in the case of each subsequent Contributor: + * **i)** changes to the Program, and + * **ii)** additions to the Program; +where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. + +“Contributor” means any person or entity that Distributes the Program. + +“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +“Program” means the Contributions Distributed in accordance with this Agreement. + +“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. + +“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. + +“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. + +“Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy. + +“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. + +“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. + +### 2. Grant of Rights + +**a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. + +**b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. + +**c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. + +**d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. + +**e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). + +### 3. Requirements + +**3.1** If a Contributor Distributes the Program in any form, then: + +* **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and + +* **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: + * **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; + * **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; + * **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and + * **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. + +**3.2** When the Program is Distributed as Source Code: + +* **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and +* **b)** a copy of this Agreement must be included with each copy of the Program. + +**3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. + +### 4. Commercial Distribution + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +### 5. No Warranty + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +### 6. Disclaimer of Liability + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +### 7. General + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. + +#### Exhibit A - Form of Secondary Licenses Notice + +> “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” + +Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. + +If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. diff --git a/src/main/java/com/junit-team-junit5-da216b8/README.md b/src/main/java/com/junit-team-junit5-da216b8/README.md new file mode 100644 index 00000000..59318bd5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/README.md @@ -0,0 +1,100 @@ +# JUnit 5 + +This repository is the home of the next generation of JUnit, _JUnit 5_. + +[![Support JUnit](https://img.shields.io/badge/%F0%9F%92%9A-Support%20JUnit-brightgreen.svg)](https://junit.org/sponsoring) + +## Latest Releases + +- General Availability (GA): [JUnit 5.9.3](https://github.com/junit-team/junit5/releases/tag/r5.9.3) (April 26, 2023) +- Preview (Milestone/Release Candidate): n/a + +## Documentation + +- [User Guide] +- [Javadoc] +- [Release Notes] +- [Samples] + +## Contributing + +Contributions to JUnit 5 are both welcomed and appreciated. For specific guidelines +regarding contributions, please see [CONTRIBUTING.md] in the root directory of the +project. Those willing to use milestone or SNAPSHOT releases are encouraged +to file feature requests and bug reports using the project's +[issue tracker](https://github.com/junit-team/junit5/issues). Issues marked with an +`up-for-grabs` +label are specifically targeted for community contributions. + +## Getting Help + +Ask JUnit 5 related questions on [StackOverflow] or chat with the community on [Gitter]. + +## Continuous Integration Builds + +[![CI Status](https://github.com/junit-team/junit5/workflows/CI/badge.svg)](https://github.com/junit-team/junit5/actions) [![Cross-Version Status](https://github.com/junit-team/junit5/workflows/Cross-Version/badge.svg)](https://github.com/junit-team/junit5/actions) + +Official CI build server for JUnit 5. Used to perform quick checks on submitted pull +requests and for build matrices including the latest released OpenJDK and early access +builds of the next OpenJDK. + +## Code Coverage + +Code coverage using [JaCoCo] for the latest build is available on [Codecov]. + +A code coverage report can also be generated locally via the [Gradle Wrapper] by +executing `./gradlew -PenableJaCoCo clean jacocoRootReport`. The results will be available +in `build/reports/jacoco/jacocoRootReport/html/index.html`. + +## Gradle Enterprise + +[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) + +JUnit 5 utilizes [Gradle Enterprise](https://gradle.com/) for _Build Scans_, _Build Cache_, and _Test Distribution_. + +The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently, +only core team members can publish Build Scans and use Test Distribution on that server. +You can, however, publish a Build Scan to [scans.gradle.com](https://scans.gradle.com/) by +using the `--scan` parameter explicitly. + +The remote Build Cache is enabled by default for everyone so that local builds can reuse +task outputs from previous CI builds. + +## Building from Source + +You need [JDK 17] to build JUnit 5. [Gradle toolchains] are used to detect and +potentially download additional JDKs for compilation and test execution. + +All modules can be _built_ and _tested_ with the [Gradle Wrapper] using the following command. + +`./gradlew build` + +## Installing in Local Maven Repository + +All modules can be _installed_ with the [Gradle Wrapper] in a local Maven repository for +consumption in other projects via the following command. + +`./gradlew publishToMavenLocal` + +## Dependency Metadata + +Consult the [Dependency Metadata] section of the [User Guide] for a list of all artifacts +of the JUnit Platform, JUnit Jupiter, and JUnit Vintage. + +See also for releases and + for snapshots. + + +[Codecov]: https://codecov.io/gh/junit-team/junit5 +[CONTRIBUTING.md]: https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md +[Dependency Metadata]: https://junit.org/junit5/docs/current/user-guide/#dependency-metadata +[Gitter]: https://gitter.im/junit-team/junit5 +[Gradle toolchains]: https://docs.gradle.org/current/userguide/toolchains.html +[Gradle Wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper +[JaCoCo]: https://www.eclemma.org/jacoco/ +[Javadoc]: https://junit.org/junit5/docs/current/api/ +[JDK 17]: https://foojay.io/almanac/java-17/ +[Release Notes]: https://junit.org/junit5/docs/current/release-notes/ +[Samples]: https://github.com/junit-team/junit5-samples +[StackOverflow]: https://stackoverflow.com/questions/tagged/junit5 +[User Guide]: https://junit.org/junit5/docs/current/user-guide/ diff --git a/src/main/java/com/junit-team-junit5-da216b8/SECURITY.md b/src/main/java/com/junit-team-junit5-da216b8/SECURITY.md new file mode 100644 index 00000000..fca52da5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 5.9.x | :white_check_mark: | +| < 5.9 | :x: | + +## Reporting a Vulnerability + +To report a security vulnerability, please send an email to security@junit.org. diff --git a/src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts new file mode 100644 index 00000000..5379f61f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + id("io.spring.nohttp") + id("io.github.gradle-nexus.publish-plugin") + `base-conventions` + `build-metadata` + `dependency-update-check` + `jacoco-aggregation-conventions` + `temp-maven-repo` +} + +description = "JUnit 5" + +val license by extra(License( + name = "Eclipse Public License v2.0", + url = uri("https://www.eclipse.org/legal/epl-v20.html"), + headerFile = file("src/spotless/eclipse-public-license-2.0.java") +)) + +val platformProjects by extra(listOf( + projects.junitPlatformCommons, + projects.junitPlatformConsole, + projects.junitPlatformConsoleStandalone, + projects.junitPlatformEngine, + projects.junitPlatformJfr, + projects.junitPlatformLauncher, + projects.junitPlatformReporting, + projects.junitPlatformRunner, + projects.junitPlatformSuite, + projects.junitPlatformSuiteApi, + projects.junitPlatformSuiteCommons, + projects.junitPlatformSuiteEngine, + projects.junitPlatformTestkit +).map { it.dependencyProject }) + +val jupiterProjects by extra(listOf( + projects.junitJupiter, + projects.junitJupiterApi, + projects.junitJupiterEngine, + projects.junitJupiterMigrationsupport, + projects.junitJupiterParams +).map { it.dependencyProject }) + +val vintageProjects by extra(listOf( + projects.junitVintageEngine.dependencyProject +)) + +val mavenizedProjects by extra(platformProjects + jupiterProjects + vintageProjects) +val modularProjects by extra(mavenizedProjects - listOf(projects.junitPlatformConsoleStandalone.dependencyProject)) + +dependencies { + (modularProjects + listOf(projects.platformTests.dependencyProject)).forEach { + jacocoAggregation(project(it.path)) + } +} + +nexusPublishing { + packageGroup.set("org.junit") + repositories { + sonatype() + } +} + +nohttp { + source.exclude("buildSrc/build/generated-sources/**") +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts new file mode 100644 index 00000000..264ec067 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts @@ -0,0 +1,31 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` + id("com.github.ben-manes.versions") version "0.42.0" +} + +repositories { + gradlePluginPortal() +} + +dependencies { + implementation(kotlin("gradle-plugin")) + implementation("biz.aQute.bnd:biz.aQute.bnd.gradle:6.4.0") + implementation("com.diffplug.spotless:spotless-plugin-gradle:6.11.0") + implementation("com.github.ben-manes:gradle-versions-plugin:0.42.0") + implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2") + compileOnly("com.gradle:gradle-enterprise-gradle-plugin:3.12.1") // keep in sync with root settings.gradle.kts +} + +tasks { + withType().configureEach { + options.release.set(8) + } + withType().configureEach { + kotlinOptions { + jvmTarget = "1.8" + allWarningsAsErrors = true + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts new file mode 100644 index 00000000..29744ec1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "buildSrc" diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt new file mode 100644 index 00000000..834374bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt @@ -0,0 +1,7 @@ +import org.gradle.api.JavaVersion + +open class JavaLibraryExtension { + var mainJavaVersion: JavaVersion = JavaVersion.VERSION_1_8 + var testJavaVersion: JavaVersion = JavaVersion.VERSION_17 + var configureRelease: Boolean = true +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt new file mode 100644 index 00000000..4f4e78ea --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt @@ -0,0 +1,4 @@ +import java.io.File +import java.net.URI + +data class License(val name: String, val url: URI, val headerFile: File) diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt new file mode 100644 index 00000000..fc8a5159 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt @@ -0,0 +1,19 @@ +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.the + +val Project.javaModuleName: String + get() = "org." + this.name.replace('-', '.') + +fun Project.requiredVersionFromLibs(name: String) = + libsVersionCatalog.findVersion(name).get().requiredVersion + +fun Project.dependencyFromLibs(name: String) = + libsVersionCatalog.findLibrary(name).get() + +fun Project.bundleFromLibs(name: String) = + libsVersionCatalog.findBundle(name).get() + +private val Project.libsVersionCatalog: VersionCatalog + get() = the().named("libs") diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt new file mode 100644 index 00000000..7ad4e7ab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt @@ -0,0 +1,5 @@ +import org.gradle.api.Task +import org.gradle.internal.os.OperatingSystem + +fun Task.trackOperationSystemAsInput() = + inputs.property("os", OperatingSystem.current().familyName) diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts new file mode 100644 index 00000000..0c4c4c5f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts @@ -0,0 +1,6 @@ +plugins { + eclipse + idea + id("java-toolchain-conventions") + id("spotless-conventions") +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts new file mode 100644 index 00000000..ad58b489 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts @@ -0,0 +1,28 @@ +import java.time.Instant +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + +val buildTimeAndDate = + if (System.getenv().containsKey("SOURCE_DATE_EPOCH")) { + + // SOURCE_DATE_EPOCH is a UNIX timestamp for pinning build metadata against + // in order to achieve reproducible builds + // + // More details - https://reproducible-builds.org/docs/source-date-epoch/ + val sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH").toLong() + + Instant.ofEpochSecond(sourceDateEpoch).atOffset(ZoneOffset.UTC) + + } else { + OffsetDateTime.now() + } + +val buildDate: String by extra { DateTimeFormatter.ISO_LOCAL_DATE.format(buildTimeAndDate) } +val buildTime: String by extra { DateTimeFormatter.ofPattern("HH:mm:ss.SSSZ").format(buildTimeAndDate) } +val buildRevision: String by extra { + providers.exec { + commandLine("git", "rev-parse", "--verify", "HEAD") + }.standardOutput.asText.get() +} +val builtByValue by extra { project.findProperty("builtBy") ?: project.property("defaultBuiltBy") } diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts new file mode 100644 index 00000000..a62a03a1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts @@ -0,0 +1,21 @@ +import org.gradle.kotlin.dsl.resolutionStrategy + +plugins { + id("com.github.ben-manes.versions") +} + +tasks.dependencyUpdates { + checkConstraints = true + resolutionStrategy { + componentSelection { + all { + val rejected = listOf("alpha", "beta", "rc", "cr", "m", "preview", "b", "ea") + .map { qualifier -> Regex("(?i).*[.-]$qualifier[.\\d-+]*") } + .any { it.matches(candidate.version) } + if (rejected) { + reject("Release candidate") + } + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts new file mode 100644 index 00000000..cd32b2d6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts @@ -0,0 +1,17 @@ +import org.gradle.api.attributes.TestSuiteType +import org.gradle.kotlin.dsl.invoke +import org.gradle.kotlin.dsl.`jacoco-report-aggregation` +import org.gradle.testing.jacoco.plugins.JacocoCoverageReport + +plugins { + id("jacoco-conventions") + `jacoco-report-aggregation` +} + +reporting { + reports { + create("jacocoRootReport") { + testType.set(TestSuiteType.UNIT_TEST) + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts new file mode 100644 index 00000000..03e5c885 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts @@ -0,0 +1,13 @@ +plugins { + jacoco +} + +val enableJaCoCo = project.hasProperty("enableJaCoCo") + +jacoco { + toolVersion = requiredVersionFromLibs("jacoco") +} + +tasks.withType().configureEach { + enabled = enableJaCoCo +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts new file mode 100644 index 00000000..265ff893 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts @@ -0,0 +1,31 @@ +import org.gradle.api.attributes.LibraryElements.CLASSES +import org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE + +plugins { + java + id("jacoco-conventions") +} + +val mavenizedProjects: List by rootProject.extra +val enableJaCoCo = project.hasProperty("enableJaCoCo") + +tasks.withType().configureEach { + configure { + isEnabled = enableJaCoCo + } +} + +val codeCoverageClassesJar by tasks.registering(Jar::class) { + from(tasks.jar.map { zipTree(it.archiveFile) }) + archiveClassifier.set("jacoco") + enabled = project in mavenizedProjects + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + +configurations.create("codeCoverageReportClasses") { + isCanBeResolved = false + isCanBeConsumed = true + isTransitive = false + attributes.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class, CLASSES)) + outgoing.artifact(codeCoverageClassesJar) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts new file mode 100644 index 00000000..e2df86c8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts @@ -0,0 +1,289 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.junit.gradle.java.ModulePathArgumentProvider +import org.junit.gradle.java.PatchModuleArgumentProvider + +plugins { + `java-library` + eclipse + idea + checkstyle + id("base-conventions") + id("jacoco-java-conventions") +} + +val mavenizedProjects: List by rootProject.extra +val modularProjects: List by rootProject.extra +val buildDate: String by rootProject.extra +val buildTime: String by rootProject.extra +val buildRevision: Any by rootProject.extra +val builtByValue: String by rootProject.extra + +val extension = extensions.create("javaLibrary") + +val moduleSourceDir = file("src/module/$javaModuleName") +val moduleOutputDir = file("$buildDir/classes/java/module") +val javaVersion = JavaVersion.current() + +eclipse { + jdt { + file { + // Set properties for org.eclipse.jdt.core.prefs + withProperties { + // Configure Eclipse projects with -parameters compiler flag. + setProperty("org.eclipse.jdt.core.compiler.codegen.methodParameters", "generate") + } + } + } +} + +java { + modularity.inferModulePath.set(false) +} + +if (project in mavenizedProjects) { + + apply(plugin = "publishing-conventions") + apply(plugin = "osgi-conventions") + + java { + withJavadocJar() + withSourcesJar() + } + + tasks.javadoc { + options { + memberLevel = JavadocMemberLevel.PROTECTED + header = project.name + encoding = "UTF-8" + locale = "en" + (this as StandardJavadocDocletOptions).apply { + addBooleanOption("Xdoclint:all,-missing,-reference", true) + addBooleanOption("XD-Xlint:none", true) + addBooleanOption("html5", true) + addMultilineStringsOption("tag").value = listOf( + "apiNote:a:API Note:", + "implNote:a:Implementation Note:" + ) + use(true) + noTimestamp(true) + } + } + } + + tasks.named("javadocJar").configure { + from(tasks.javadoc.map { File(it.destinationDir, "element-list") }) { + // For compatibility with older tools, e.g. NetBeans 11 + rename { "package-list" } + } + } + + tasks.named("sourcesJar").configure { + from(moduleSourceDir) { + include("module-info.java") + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + + pluginManager.withPlugin("java-test-fixtures") { + val javaComponent = components["java"] as AdhocComponentWithVariants + javaComponent.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } + javaComponent.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } + } + + configure { + publications { + named("maven") { + from(components["java"]) + versionMapping { + allVariants { + fromResolutionResult() + } + } + pom { + description.set(provider { "Module \"${project.name}\" of JUnit 5." }) + } + } + } + } + +} else { + tasks { + jar { + enabled = false + } + javadoc { + enabled = false + } + } +} + +tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + dirMode = Integer.parseInt("0755", 8) + fileMode = Integer.parseInt("0644", 8) +} + +normalization { + runtimeClasspath { + metaInf { + // Ignore inconsequential JAR manifest attributes such as timestamps and the commit checksum. + // This is used when checking whether runtime classpaths, e.g. of test tasks, have changed and + // improves cacheability of such tasks. + ignoreAttribute("Built-By") + ignoreAttribute("Build-Date") + ignoreAttribute("Build-Time") + ignoreAttribute("Build-Revision") + ignoreAttribute("Created-By") + } + } +} + +val allMainClasses by tasks.registering { + dependsOn(tasks.classes) +} + +val compileModule by tasks.registering(JavaCompile::class) { + dependsOn(allMainClasses) + source = fileTree(moduleSourceDir) + destinationDirectory.set(moduleOutputDir) + sourceCompatibility = "9" + targetCompatibility = "9" + classpath = files() + options.release.set(9) + options.compilerArgs.addAll(listOf( + // Suppress warnings for automatic modules: org.apiguardian.api, org.opentest4j + "-Xlint:all,-requires-automatic,-requires-transitive-automatic", + "-Werror", // Terminates compilation when warnings occur. + "--module-version", "${project.version}", + )) + options.compilerArgumentProviders.add(objects.newInstance(ModulePathArgumentProvider::class, project, modularProjects)) + options.compilerArgumentProviders.addAll(modularProjects.map { objects.newInstance(PatchModuleArgumentProvider::class, project, it) }) + modularity.inferModulePath.set(false) +} + +tasks.withType().configureEach { + from(rootDir) { + include("LICENSE.md", "LICENSE-notice.md") + into("META-INF") + } + val suffix = archiveClassifier.getOrElse("") + if (suffix.isBlank() || this is ShadowJar) { + dependsOn(allMainClasses, compileModule) + from("$moduleOutputDir/$javaModuleName") { + include("module-info.class") + } + } +} + +tasks.jar { + manifest { + attributes( + "Created-By" to "${System.getProperty("java.version")} (${System.getProperty("java.vendor")} ${System.getProperty("java.vm.version")})", + "Built-By" to builtByValue, + "Build-Date" to buildDate, + "Build-Time" to buildTime, + "Build-Revision" to buildRevision, + "Specification-Title" to project.name, + "Specification-Version" to (project.version as String).substringBefore('-'), + "Specification-Vendor" to "junit.org", + "Implementation-Title" to project.name, + "Implementation-Version" to project.version, + "Implementation-Vendor" to "junit.org" + ) + } +} + +tasks.withType().configureEach { + outputs.doNotCacheIf("Shadow jar contains a Manifest with Build-Time") { true } +} + +tasks.withType().configureEach { + options.encoding = "UTF-8" +} + +tasks.compileJava { + // See: https://docs.oracle.com/en/java/javase/12/tools/javac.html + options.compilerArgs.addAll(listOf( + "-Xlint:all", // Enables all recommended warnings. + "-Werror" // Terminates compilation when warnings occur. + )) +} + +tasks.compileTestJava { + // See: https://docs.oracle.com/en/java/javase/12/tools/javac.html + options.compilerArgs.addAll(listOf( + "-Xlint", // Enables all recommended warnings. + "-Xlint:-overrides", // Disables "method overrides" warnings. + "-Werror", // Terminates compilation when warnings occur. + "-parameters" // Generates metadata for reflection on method parameters. + )) +} + +afterEvaluate { + configurations { + apiElements { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, extension.mainJavaVersion.majorVersion.toInt()) + } + } + runtimeElements { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, extension.mainJavaVersion.majorVersion.toInt()) + } + } + } + tasks { + compileJava { + if (extension.configureRelease) { + options.release.set(extension.mainJavaVersion.majorVersion.toInt()) + } else { + sourceCompatibility = extension.mainJavaVersion.majorVersion + targetCompatibility = extension.mainJavaVersion.majorVersion + } + } + compileTestJava { + if (extension.configureRelease) { + options.release.set(extension.testJavaVersion.majorVersion.toInt()) + } else { + sourceCompatibility = extension.testJavaVersion.majorVersion + targetCompatibility = extension.testJavaVersion.majorVersion + } + } + } + pluginManager.withPlugin("groovy") { + tasks.named("compileGroovy").configure { + // Groovy compiler does not support the --release flag. + sourceCompatibility = extension.mainJavaVersion.majorVersion + targetCompatibility = extension.mainJavaVersion.majorVersion + } + tasks.named("compileTestGroovy").configure { + // Groovy compiler does not support the --release flag. + sourceCompatibility = extension.testJavaVersion.majorVersion + targetCompatibility = extension.testJavaVersion.majorVersion + } + } +} + +checkstyle { + toolVersion = requiredVersionFromLibs("checkstyle") + configDirectory.set(rootProject.file("src/checkstyle")) +} + +tasks { + checkstyleMain { + configFile = rootProject.file("src/checkstyle/checkstyleMain.xml") + } + checkstyleTest { + configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") + } +} + +pluginManager.withPlugin("java-test-fixtures") { + tasks.named("checkstyleTestFixtures") { + configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") + } + tasks.named("compileTestFixturesJava") { + options.release.set(extension.testJavaVersion.majorVersion.toInt()) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts new file mode 100644 index 00000000..9b3155ab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts @@ -0,0 +1,43 @@ +plugins { + id("java-library-conventions") +} + +val mavenizedProjects: List by rootProject.extra + +listOf(9, 17).forEach { javaVersion -> + val sourceSet = sourceSets.register("mainRelease${javaVersion}") { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + java { + setSrcDirs(setOf("src/main/java${javaVersion}")) + } + } + + configurations.named(sourceSet.get().compileClasspathConfigurationName).configure { + extendsFrom(configurations.compileClasspath.get()) + } + + tasks { + + named("allMainClasses").configure { + dependsOn(sourceSet.get().classesTaskName) + } + + named(sourceSet.get().compileJavaTaskName).configure { + options.release.set(javaVersion) + } + + named("checkstyle${sourceSet.name.capitalize()}").configure { + configFile = rootProject.file("src/checkstyle/checkstyleMain.xml") + } + + if (project in mavenizedProjects) { + javadoc { + source(sourceSet.get().allJava) + } + named("sourcesJar").configure { + from(sourceSet.get().allSource) + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts new file mode 100644 index 00000000..a000989b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts @@ -0,0 +1,52 @@ +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream +import org.gradle.api.internal.file.archive.ZipCopyAction +import java.nio.file.Files + +// This registers a `doLast` action to rewrite the timestamps of the project's output JAR +afterEvaluate { + val jarTask = (tasks.findByName("shadowJar") ?: tasks["jar"]) as Jar + + jarTask.doLast { + + val newFile = Files.createTempFile("rewrite-timestamp", null).toFile() + val originalOutput = jarTask.archiveFile.get().asFile + + newFile.outputStream().use { os -> + + val newJarStream = JarOutputStream(os) + val oldJar = JarFile(originalOutput) + + fun sortAlwaysFirst(name: String): Comparator = + Comparator { a, b -> + when { + a.name == name -> -1 + b.name == name -> 1 + else -> 0 + } + } + + oldJar.entries() + .toList() + .distinctBy { it.name } + .sortedWith(sortAlwaysFirst("META-INF/") + .then(sortAlwaysFirst("META-INF/MANIFEST.MF")) + .thenBy { it.name }) + .forEach { entry -> + val jarEntry = JarEntry(entry.name) + + // Use the same constant as the fixed timestamps in normal copy actions + jarEntry.time = ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES + + newJarStream.putNextEntry(jarEntry) + + oldJar.getInputStream(entry).copyTo(newJarStream) + } + + newJarStream.finish() + } + + newFile.renameTo(originalOutput) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts new file mode 100644 index 00000000..87c334d2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts @@ -0,0 +1,42 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension + +project.pluginManager.withPlugin("java") { + val javaToolchainVersion: String? by project + val defaultLanguageVersion = JavaLanguageVersion.of(17) + val javaLanguageVersion = javaToolchainVersion?.let { JavaLanguageVersion.of(it) } ?: defaultLanguageVersion + + val extension = the() + val javaToolchainService = the() + + extension.toolchain.languageVersion.set(javaLanguageVersion) + + pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + configure { + jvmToolchain { + languageVersion.set(javaLanguageVersion) + } + } + } + + tasks.withType().configureEach { + javaLauncher.set(javaToolchainService.launcherFor(extension.toolchain)) + } + + tasks.withType().configureEach { + outputs.cacheIf { javaLanguageVersion == defaultLanguageVersion } + doFirst { + if (options.release.orNull == 8 && javaLanguageVersion.asInt() >= 20) { + options.compilerArgs.add( + "-Xlint:-options" // see https://github.com/junit-team/junit5/issues/3029 + ) + } + } + } + + tasks.withType().configureEach { + javaLauncher.set(javaToolchainService.launcherFor { + // Groovy does not yet support JDK 19, see https://issues.apache.org/jira/browse/GROOVY-10569 + languageVersion.set(defaultLanguageVersion) + }) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts new file mode 100644 index 00000000..89ca169b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts @@ -0,0 +1,29 @@ +plugins { + `java-library` +} + +val junit_4_12 by configurations.creating { + extendsFrom(configurations.testRuntimeClasspath.get()) +} + +dependencies { + junit_4_12("junit:junit") { + version { + strictly("4.12") + } + } + pluginManager.withPlugin("osgi-conventions") { + val junit4Osgi = requiredVersionFromLibs("junit4Osgi") + "osgiVerification"("org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit:${junit4Osgi}") + } +} + +tasks { + val test_4_12 by registering(Test::class) { + classpath -= configurations.testRuntimeClasspath.get() + classpath += junit_4_12 + } + check { + dependsOn(test_4_12) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts new file mode 100644 index 00000000..16319094 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts @@ -0,0 +1,26 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("java-library-conventions") + kotlin("jvm") +} + +tasks.withType().configureEach { + kotlinOptions { + apiVersion = "1.3" + languageVersion = "1.3" + allWarningsAsErrors = false + } +} + +afterEvaluate { + val extension = project.the() + tasks { + withType().configureEach { + kotlinOptions.jvmTarget = extension.mainJavaVersion.toString() + } + named("compileTestKotlin") { + kotlinOptions.jvmTarget = extension.testJavaVersion.toString() + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt new file mode 100644 index 00000000..9c6423dc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt @@ -0,0 +1,9 @@ +package org.junit.gradle.exec + +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.Classpath +import org.gradle.process.CommandLineArgumentProvider + +class ClasspathSystemPropertyProvider(private val propertyName: String, @get:Classpath val files: FileCollection) : CommandLineArgumentProvider { + override fun asArguments() = listOf("-D$propertyName=${files.asPath}") +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt new file mode 100644 index 00000000..88889bcf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt @@ -0,0 +1,86 @@ +package org.junit.gradle.exec + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.the +import org.gradle.process.CommandLineArgumentProvider +import org.gradle.process.ExecOperations +import trackOperationSystemAsInput +import java.io.ByteArrayOutputStream +import javax.inject.Inject + +abstract class RunConsoleLauncher @Inject constructor(private val execOperations: ExecOperations): DefaultTask() { + + @get:Classpath + abstract val runtimeClasspath: ConfigurableFileCollection + + @get:Input + abstract val args: ListProperty + + @get:OutputDirectory + abstract val reportsDir: DirectoryProperty + + @get:Internal + abstract val debugging: Property + + @get:Internal + abstract val hideOutput: Property + + init { + runtimeClasspath.from(project.the()["test"].runtimeClasspath) + reportsDir.convention(project.layout.buildDirectory.dir("test-results")) + + debugging.convention( + project.providers.gradleProperty("consoleLauncherTestDebug") + .map { it != "false" } + .orElse(false) + ) + outputs.cacheIf { !debugging.get() } + outputs.upToDateWhen { !debugging.get() } + + hideOutput.convention(debugging.map { !it }) + + trackOperationSystemAsInput() + } + + @TaskAction + fun execute() { + val output = ByteArrayOutputStream() + val result = execOperations.javaexec { + classpath = runtimeClasspath + mainClass.set("org.junit.platform.console.ConsoleLauncher") + args("--scan-classpath") + args("--config=junit.platform.reporting.open.xml.enabled=true") + args(this@RunConsoleLauncher.args.get()) + argumentProviders += CommandLineArgumentProvider { + listOf( + "--reports-dir=${reportsDir.get()}", + "--config=junit.platform.reporting.output.dir=${reportsDir.get()}" + + ) + } + systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") + debug = debugging.get() + if (hideOutput.get()) { + standardOutput = output + errorOutput = output + } + isIgnoreExitValue = true + } + if (result.exitValue != 0 && hideOutput.get()) { + System.out.write(output.toByteArray()) + System.out.flush() + } + result.rethrowFailure().assertNormalExitValue() + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt new file mode 100644 index 00000000..afc97f40 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt @@ -0,0 +1,27 @@ +package org.junit.gradle.java + +import org.gradle.api.Action +import org.gradle.api.Task +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.jvm.tasks.Jar +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.jvm.toolchain.JavaToolchainService +import org.gradle.jvm.toolchain.JavaToolchainSpec +import org.gradle.process.ExecOperations +import javax.inject.Inject + +abstract class ExecJarAction @Inject constructor(private val operations: ExecOperations): Action { + + abstract val javaLauncher: Property + + abstract val args: ListProperty + + override fun execute(t: Task) { + operations.exec { + executable = javaLauncher.get() + .metadata.installationPath.file("bin/jar").asFile.absolutePath + args = this@ExecJarAction.args.get() + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt new file mode 100644 index 00000000..adb478c4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt @@ -0,0 +1,41 @@ +package org.junit.gradle.java + +import org.gradle.api.Named +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.get +import org.gradle.process.CommandLineArgumentProvider +import javax.inject.Inject + +abstract class ModulePathArgumentProvider @Inject constructor(project: Project, modularProjects: List) : + CommandLineArgumentProvider, Named { + + @get:CompileClasspath + abstract val modulePath: ConfigurableFileCollection + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val moduleSourceDirs: ConfigurableFileCollection + + init { + modulePath.from(project.configurations.named("compileClasspath")) + modularProjects.forEach { + moduleSourceDirs.from(project.files("${it.projectDir}/src/module")) + } + } + + override fun asArguments() = listOf( + "--module-path", + modulePath.asPath, + "--module-source-path", + moduleSourceDirs.asPath + ) + + @Internal + override fun getName() = "module-path" +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt new file mode 100644 index 00000000..f13fd8b9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt @@ -0,0 +1,47 @@ +package org.junit.gradle.java + +import javaModuleName +import org.gradle.api.Named +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.the +import org.gradle.process.CommandLineArgumentProvider +import javax.inject.Inject + +abstract class PatchModuleArgumentProvider @Inject constructor(compiledProject: Project, patchModuleProject: Project) : + CommandLineArgumentProvider, Named { + + @get:Input + abstract val module: Property + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val patch: ConfigurableFileCollection + + init { + module.convention(patchModuleProject.javaModuleName) + patch.from(compiledProject.provider { + if (patchModuleProject == compiledProject) + compiledProject.files(compiledProject.the().matching { it.name.startsWith("main") } + .map { it.output }) + else + patchModuleProject.files(patchModuleProject.the()["main"].java.srcDirs) + }) + } + + override fun asArguments(): List { + val path = patch.filter { it.exists() }.asPath + if (path.isEmpty()) { + return emptyList() + } + return listOf("--patch-module", "${module.get()}=$path") + } + + @Internal + override fun getName() = "patch-module(${module.get()})" +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt new file mode 100644 index 00000000..12107fc8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt @@ -0,0 +1,36 @@ +package org.junit.gradle.java + +import org.gradle.api.DefaultTask +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.* + +abstract class WriteArtifactsFile : DefaultTask() { + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @get:Input + abstract val moduleVersions: SetProperty + + fun from(configuration: Provider) { + moduleVersions.addAll(configuration.map { + it.resolvedConfiguration.resolvedArtifacts.map { it.moduleVersion.id } + }) + } + + @TaskAction + fun writeFile() { + outputFile.get().asFile.printWriter().use { out -> + moduleVersions.get() + .map { "${it.group}:${it.name}:${it.version}" } + .sorted() + .forEach(out::println) + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt new file mode 100644 index 00000000..e9391b22 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt @@ -0,0 +1,26 @@ +package org.junit.gradle.javadoc + +import org.gradle.external.javadoc.JavadocOptionFileOption +import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext + +class ModuleSpecificJavadocFileOption(private val option: String, private var valuePerModule: Map) : JavadocOptionFileOption> { + + override fun getOption() = option + + override fun getValue() = valuePerModule + + override fun setValue(value: Map) { + this.valuePerModule = value + } + + override fun write(writerContext: JavadocOptionFileWriterContext) { + valuePerModule.forEach { (moduleName, value) -> + writerContext + .writeOptionHeader(option) + .write(moduleName) + .write("=") + .write(value) + .newLine() + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts new file mode 100644 index 00000000..3a2bf7c6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts @@ -0,0 +1,114 @@ +import aQute.bnd.gradle.BundleTaskExtension +import aQute.bnd.gradle.Resolve + +plugins { + `java-library` +} + +val importAPIGuardian = "org.apiguardian.*;resolution:=\"optional\"" + +// This task enhances `jar` and `shadowJar` tasks with the bnd +// `BundleTaskExtension` extension which allows for generating OSGi +// metadata into the jar +tasks.withType().matching { task: Jar -> + task.name == "jar" || task.name == "shadowJar" +}.configureEach { + extra["importAPIGuardian"] = importAPIGuardian + + extensions.create(BundleTaskExtension.NAME, this).apply { + properties.set(provider { + mapOf("project.description" to project.description) + }) + // These are bnd instructions necessary for generating OSGi metadata. + // We've generalized these so that they are widely applicable limiting + // module configurations to special cases. + setBnd( + """ + # Set the Bundle-SymbolicName to the archiveBaseName. + # We don't use the archiveClassifier which Bnd will use + # in the default Bundle-SymbolicName value. + Bundle-SymbolicName: ${'$'}{task.archiveBaseName} + + # Set the Bundle-Name from the project description + Bundle-Name: ${'$'}{project.description} + + # These are the general rules for package imports. + Import-Package: \ + ${importAPIGuardian},\ + org.junit.platform.commons.logging;status=INTERNAL,\ + kotlin.*;resolution:="optional",\ + * + + # This tells bnd not to complain if a module doesn't actually import + # the kotlin and apiguardian packages, but enough modules do to make it a default. + -fixupmessages.kotlin.import: "Unused Import-Package instructions: \\[kotlin.*\\]";is:=ignore + -fixupmessages.apiguardian.import: "Unused Import-Package instructions: \\[org.apiguardian.*\\]";is:=ignore + + # This tells bnd to ignore classes it finds in `META-INF/versions/` + # because bnd doesn't yet support multi-release jars. + -fixupmessages.wrong.dir: "Classes found in the wrong directory: \\{META-INF/versions/...";is:=ignore + + # Don't scan for Class.forName package imports. + # See https://bnd.bndtools.org/instructions/noclassforname.html + -noclassforname: true + + # Don't add all the extra headers bnd normally adds. + # See https://bnd.bndtools.org/instructions/noextraheaders.html + -noextraheaders: true + + # Don't add the Private-Package header. + # See https://bnd.bndtools.org/instructions/removeheaders.html + -removeheaders: Private-Package + + # Instruct the APIGuardianAnnotations how to operate. + # See https://bnd.bndtools.org/instructions/export-apiguardian.html + -export-apiguardian: *;version=${'$'}{versionmask;===;${'$'}{version_cleanup;${'$'}{task.archiveVersion}}} + """ + ) + + // Do the actual work putting OSGi stuff in the jar. + doLast(buildAction()) + } +} + +// Bnd's Resolve task uses a properties file for its configuration. This +// task writes out the properties necessary for it to verify the OSGi +// metadata. +val osgiProperties by tasks.registering(WriteProperties::class) { + setOutputFile(layout.buildDirectory.file("verifyOSGiProperties.bndrun")) + property("-standalone", true) + project.extensions.getByType(JavaLibraryExtension::class.java).let { javaLibrary -> + property("-runee", "JavaSE-${javaLibrary.mainJavaVersion}") + } + property("-runrequires", "osgi.identity;filter:='(osgi.identity=${project.name})'") + property("-runsystempackages", "jdk.internal.misc,jdk.jfr,sun.misc") + // API Guardian should be optional -> instruct resolver to ignore it + // during resolution. Resolve should still pass. + property("-runblacklist", "org.apiguardian.api") +} + +val osgiVerification by configurations.creating { + extendsFrom(configurations.runtimeClasspath.get()) +} + +// Bnd's Resolve task is what verifies that a jar can be used in OSGi and +// that its metadata is valid. If the metadata is invalid this task will +// fail. +val verifyOSGi by tasks.registering(Resolve::class) { + bndrun.fileProvider(osgiProperties.map { it.outputFile }) + outputBndrun.set(layout.buildDirectory.file("resolvedOSGiProperties.bndrun")) + isReportOptional = false + // By default bnd will use jars found in: + // 1. project.sourceSets.main.runtimeClasspath + // 2. project.configurations.archives.artifacts.files + // to validate the metadata. + // This adds jars defined in `osgiVerification` also so that bnd + // can use them to validate the metadata without causing those to + // end up in the dependencies of those projects. + bundles(osgiVerification) + properties.empty() +} + +tasks.check { + dependsOn(verifyOSGi) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts new file mode 100644 index 00000000..c4e28d5b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts @@ -0,0 +1,115 @@ +plugins { + `maven-publish` + signing + id("base-conventions") +} + +val isSnapshot = project.version.toString().contains("SNAPSHOT") +val isContinuousIntegrationEnvironment = System.getenv("CI")?.toBoolean() ?: false + +val jupiterProjects: List by rootProject +val platformProjects: List by rootProject +val vintageProjects: List by rootProject + +when (project) { + in jupiterProjects -> { + group = property("jupiterGroup")!! + } + in platformProjects -> { + group = property("platformGroup")!! + version = property("platformVersion")!! + } + in vintageProjects -> { + group = property("vintageGroup")!! + version = property("vintageVersion")!! + } +} + +// ensure project is built successfully before publishing it +tasks.withType().configureEach { + dependsOn(provider { + val tempRepoName: String by rootProject + if (repository.name != tempRepoName) { + listOf(tasks.build) + } else { + emptyList() + } + }) +} +tasks.withType().configureEach { + dependsOn(tasks.build) +} + +signing { + useGpgCmd() + sign(publishing.publications) + isRequired = !(isSnapshot || isContinuousIntegrationEnvironment) +} + +tasks.withType().configureEach { + onlyIf { + !isSnapshot // Gradle Module Metadata currently does not support signing snapshots + } +} + +publishing { + publications { + create("maven") { + pom { + name.set(provider { + project.description ?: "${project.group}:${project.name}" + }) + url.set("https://junit.org/junit5/") + scm { + connection.set("scm:git:git://github.com/junit-team/junit5.git") + developerConnection.set("scm:git:git://github.com/junit-team/junit5.git") + url.set("https://github.com/junit-team/junit5") + } + licenses { + license { + val license: License by rootProject.extra + name.set(license.name) + url.set(license.url.toString()) + } + } + developers { + developer { + id.set("bechte") + name.set("Stefan Bechtold") + email.set("stefan.bechtold@me.com") + } + developer { + id.set("jlink") + name.set("Johannes Link") + email.set("business@johanneslink.net") + } + developer { + id.set("marcphilipp") + name.set("Marc Philipp") + email.set("mail@marcphilipp.de") + } + developer { + id.set("mmerdes") + name.set("Matthias Merdes") + email.set("matthias.merdes@heidelpay.com") + } + developer { + id.set("sbrannen") + name.set("Sam Brannen") + email.set("sam@sambrannen.com") + } + developer { + id.set("sormuras") + name.set("Christian Stein") + email.set("sormuras@gmail.com") + } + developer { + id.set("juliette-derancourt") + name.set("Juliette de Rancourt") + email.set("derancourt.juliette@gmail.com") + } + } + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts new file mode 100644 index 00000000..19028422 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts @@ -0,0 +1,69 @@ +plugins { + id("java-library-conventions") + id("com.github.johnrengelman.shadow") +} + +val shadowed by configurations.creating + +configurations { + listOf(apiElements, runtimeElements).forEach { + it.configure { + outgoing { + artifacts.clear() + artifact(tasks.shadowJar) { + classifier = "" + } + } + } + } +} + +sourceSets { + main { + compileClasspath += shadowed + } + test { + runtimeClasspath += shadowed + } +} + +eclipse { + classpath { + plusConfigurations.add(shadowed) + } +} + +idea { + module { + scopes["PROVIDED"]!!["plus"]!!.add(shadowed) + } +} + +tasks { + javadoc { + classpath += shadowed + } + checkstyleMain { + classpath += shadowed + } + shadowJar { + configurations = listOf(shadowed) + exclude("META-INF/maven/**") + excludes.remove("module-info.class") + archiveClassifier.set("") + } + jar { + dependsOn(shadowJar) + enabled = false + } + named("codeCoverageClassesJar") { + from(shadowJar.map { zipTree(it.archiveFile) }) + exclude("**/shadow/**") + } + test { + dependsOn(shadowJar) + // in order to run the test against the shadowJar + classpath -= sourceSets.main.get().output + classpath += files(shadowJar.map { it.archiveFile }) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts new file mode 100644 index 00000000..9f82998c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts @@ -0,0 +1,49 @@ +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.provideDelegate + +plugins { + id("com.diffplug.spotless") +} + +val license: License by rootProject.extra + +spotless { + + format("misc") { + target("*.gradle.kts", "buildSrc/**/*.gradle.kts", "*.gitignore") + targetExclude("buildSrc/build/**") + indentWithTabs() + trimTrailingWhitespace() + endWithNewline() + } + + format("documentation") { + target("*.adoc", "*.md", "src/**/*.adoc", "src/**/*.md") + trimTrailingWhitespace() + endWithNewline() + } + + pluginManager.withPlugin("java") { + + val importOrderConfigFile = rootProject.file("src/eclipse/junit-eclipse.importorder") + val javaFormatterConfigFile = rootProject.file("src/eclipse/junit-eclipse-formatter-settings.xml") + + java { + licenseHeaderFile(license.headerFile, "(package|import|open|module) ") + importOrderFile(importOrderConfigFile) + eclipse().configFile(javaFormatterConfigFile) + trimTrailingWhitespace() + endWithNewline() + } + } + + pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + kotlin { + targetExclude("**/src/test/resources/**") + ktlint(requiredVersionFromLibs("ktlint")) + licenseHeaderFile(license.headerFile) + trimTrailingWhitespace() + endWithNewline() + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts new file mode 100644 index 00000000..d180a3a8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts @@ -0,0 +1,30 @@ +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository +import org.gradle.kotlin.dsl.* + +val tempRepoName by extra("temp") +val tempRepoDir by extra(file("$buildDir/repo")) + +val clearTempRepoDir by tasks.registering { + doFirst { + tempRepoDir.deleteRecursively() + } +} + +subprojects { + pluginManager.withPlugin("maven-publish") { + configure() { + repositories { + maven { + name = tempRepoName + url = uri(tempRepoDir) + } + } + } + tasks.withType().configureEach { + if (name.endsWith("To${tempRepoName.capitalize()}Repository")) { + dependsOn(clearTempRepoDir) + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts new file mode 100644 index 00000000..d5a105f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts @@ -0,0 +1,84 @@ +import com.gradle.enterprise.gradleplugin.testretry.retry +import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL +import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED +import org.gradle.internal.os.OperatingSystem + +plugins { + `java-library` +} + +tasks.withType().configureEach { + useJUnitPlatform { + includeEngines("junit-jupiter") + } + include("**/*Test.class", "**/*Tests.class") + testLogging { + events = setOf(FAILED) + exceptionFormat = FULL + } + val isCiServer = System.getenv("CI") != null + retry { + maxRetries.set(providers.gradleProperty("retries").map(String::toInt).orElse(if (isCiServer) 2 else 0)) + } + distribution { + enabled.convention(providers.gradleProperty("enableTestDistribution") + .map(String::toBoolean) + .map { enabled -> enabled && (!isCiServer || System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY").isNotBlank()) } + .orElse(false)) + maxLocalExecutors.set(providers.gradleProperty("testDistribution.maxLocalExecutors").map(String::toInt).orElse(1)) + maxRemoteExecutors.set(providers.gradleProperty("testDistribution.maxRemoteExecutors").map(String::toInt)) + if (isCiServer) { + when { + OperatingSystem.current().isLinux -> requirements.add("os=linux") + OperatingSystem.current().isWindows -> requirements.add("os=windows") + OperatingSystem.current().isMacOsX -> requirements.add("os=macos") + } + } + } + predictiveSelection { + enabled.set(providers.gradleProperty("enablePredictiveTestSelection").map(String::toBoolean).orElse(true)) + } + systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") + // Required until ASM officially supports the JDK 14 + systemProperty("net.bytebuddy.experimental", true) + if (project.hasProperty("enableJFR")) { + jvmArgs( + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+DebugNonSafepoints", + "-XX:StartFlightRecording=filename=${reports.junitXml.outputLocation.get()},dumponexit=true,settings=profile.jfc", + "-XX:FlightRecorderOptions=stackdepth=1024" + ) + } + + // Track OS as input so that tests are executed on all configured operating systems on CI + trackOperationSystemAsInput() + + // Avoid passing unnecessary environment variables to the JVM (from GitHub Actions) + if (isCiServer) { + environment.remove("RUNNER_TEMP") + environment.remove("GITHUB_ACTION") + } + + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}" + ) + } +} + +dependencies { + testImplementation(dependencyFromLibs("assertj")) + testImplementation(dependencyFromLibs("mockito")) + + if (!project.name.startsWith("junit-jupiter")) { + testImplementation(project(":junit-jupiter")) + } + testImplementation(testFixtures(project(":junit-jupiter-api"))) + + testRuntimeOnly(project(":junit-platform-engine")) + testRuntimeOnly(project(":junit-platform-jfr")) + testRuntimeOnly(project(":junit-platform-reporting")) + + testRuntimeOnly(bundleFromLibs("log4j")) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/README.md b/src/main/java/com/junit-team-junit5-da216b8/documentation/README.md new file mode 100644 index 00000000..f06f5b2f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/README.md @@ -0,0 +1,34 @@ +# JUnit 5 User Guide + +This subproject contains the AsciiDoc sources for the JUnit 5 User Guide. + +## Structure + +- `src/docs/asciidoc`: AsciiDoc files +- `src/test/java`: Java test source code that can be included in the AsciiDoc files +- `src/test/kotlin`: Kotlin test source code that can be included in the AsciiDoc files +- `src/test/resources`: Classpath resources that can be included in the AsciiDoc files or + used in tests + +## Usage + +### Generate AsciiDoc + +This following Gradle command generates the HTML version of the User Guide as +`build/docs/asciidoc/user-guide/index.html`. + +``` +gradlew asciidoctor +``` + +On Linux operating systems, the `graphviz` package providing `/usr/bin/dot` must be +installed in order to generate the User Guide. + +### Generate AsciiDocPdf + +This following Gradle command generates the PDF version of the User Guide to +`build/docs/asciidocPdf/user-guide/index.pdf`. + +``` +gradlew asciidoctorPdf +``` diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts new file mode 100644 index 00000000..bb832b1c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts @@ -0,0 +1,457 @@ +import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask +import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.junit.gradle.exec.ClasspathSystemPropertyProvider +import org.junit.gradle.exec.RunConsoleLauncher +import org.junit.gradle.javadoc.ModuleSpecificJavadocFileOption +import java.io.ByteArrayOutputStream +import java.nio.file.Files + +plugins { + id("org.asciidoctor.jvm.convert") + id("org.asciidoctor.jvm.pdf") + id("org.ajoberstar.git-publish") + `kotlin-library-conventions` +} + +val modularProjects: List by rootProject + +// Because we need to set up Javadoc aggregation +modularProjects.forEach { evaluationDependsOn(it.path) } + +javaLibrary { + mainJavaVersion = JavaVersion.VERSION_1_8 + testJavaVersion = JavaVersion.VERSION_1_8 +} + +val apiReport by configurations.creating +val standaloneConsoleLauncher by configurations.creating + +dependencies { + implementation(projects.junitJupiterApi) { + because("Jupiter API is used in src/main/java") + } + + // Pull in all "modular projects" to ensure that they are included + // in reports generated by the ApiReportGenerator. + modularProjects.forEach { apiReport(it) } + + testImplementation(projects.junitJupiter) + testImplementation(projects.junitJupiterMigrationsupport) + testImplementation(projects.junitPlatformConsole) + testImplementation(projects.junitPlatformRunner) + testImplementation(projects.junitPlatformSuite) + testImplementation(projects.junitPlatformTestkit) + testImplementation(kotlin("stdlib")) + + testImplementation(projects.junitVintageEngine) + testRuntimeOnly(libs.bundles.log4j) + testRuntimeOnly(libs.apiguardian) { + because("it's required to generate API tables") + } + testRuntimeOnly(libs.openTestReporting.events) { + because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting") + } + + testImplementation(libs.classgraph) { + because("ApiReportGenerator needs it") + } + + standaloneConsoleLauncher(projects.junitPlatformConsoleStandalone) +} + +asciidoctorj { + modules { + diagram.use() + pdf.version(libs.versions.asciidoctor.pdf) + } +} + +val snapshot = rootProject.version.toString().contains("SNAPSHOT") +val docsVersion = if (snapshot) "snapshot" else rootProject.version +val releaseBranch = if (snapshot) "HEAD" else "r${rootProject.version}" +val docsDir = file("$buildDir/ghpages-docs") +val replaceCurrentDocs = project.hasProperty("replaceCurrentDocs") +val uploadPdfs = !snapshot +val userGuidePdfFileName = "junit-user-guide-${rootProject.version}.pdf" +val ota4jDocVersion = if (libs.versions.opentest4j.get().contains("SNAPSHOT")) "snapshot" else libs.versions.opentest4j.get() +val apiGuardianDocVersion = if (libs.versions.apiguardian.get().contains("SNAPSHOT")) "snapshot" else libs.versions.apiguardian.get() + +gitPublish { + repoUri.set("https://github.com/junit-team/junit5.git") + branch.set("gh-pages") + sign.set(false) + + contents { + from(docsDir) + into("docs") + } + + preserve { + include("**/*") + exclude("docs/$docsVersion/**") + if (replaceCurrentDocs) { + exclude("docs/current/**") + } + } +} + +val generatedAsciiDocPath = layout.buildDirectory.dir("generated/asciidoc") +val consoleLauncherOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-options.txt") } +val experimentalApisTableFile = generatedAsciiDocPath.map { it.file("experimental-apis-table.adoc") } +val deprecatedApisTableFile = generatedAsciiDocPath.map { it.file("deprecated-apis-table.adoc") } +val standaloneConsoleLauncherShadowedArtifactsFile = generatedAsciiDocPath.map { it.file("console-launcher-standalone-shadowed-artifacts.adoc") } + +val jdkJavadocBaseUrl = "https://docs.oracle.com/en/java/javase/11/docs/api" +val elementListsDir = file("$buildDir/elementLists") +val externalModulesWithoutModularJavadoc = mapOf( + "org.apiguardian.api" to "https://apiguardian-team.github.io/apiguardian/docs/$apiGuardianDocVersion/api/", + "org.assertj.core" to "https://javadoc.io/doc/org.assertj/assertj-core/${libs.versions.assertj.get()}/", + "org.opentest4j" to "https://ota4j-team.github.io/opentest4j/docs/$ota4jDocVersion/api/" +) +require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) { + "all base URLs must end with a trailing slash: $externalModulesWithoutModularJavadoc" +} + +tasks { + + val consoleLauncherTest by registering(RunConsoleLauncher::class) { + args.addAll("--config", "enableHttpServer=true") + args.addAll("--include-classname", ".*Tests") + args.addAll("--include-classname", ".*Demo") + args.addAll("--exclude-tag", "exclude") + } + + register("consoleLauncher") { + hideOutput.set(false) + reportsDir.set(layout.buildDirectory.dir("console-launcher")) + outputs.upToDateWhen { false } + args.addAll("--config", "enableHttpServer=true") + args.addAll("--include-classname", ".*Tests") + args.addAll("--include-classname", ".*Demo") + args.addAll("--exclude-tag", "exclude") + } + + test { + dependsOn(consoleLauncherTest) + exclude("**/*") + } + + val generateConsoleLauncherOptions by registering(JavaExec::class) { + classpath = sourceSets["test"].runtimeClasspath + mainClass.set("org.junit.platform.console.ConsoleLauncher") + args("--help", "--disable-banner") + redirectOutput(consoleLauncherOptionsFile) + } + + val generateExperimentalApisTable by registering(JavaExec::class) { + classpath = sourceSets["test"].runtimeClasspath + mainClass.set("org.junit.api.tools.ApiReportGenerator") + jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport) + args("EXPERIMENTAL") + redirectOutput(experimentalApisTableFile) + } + + val generateDeprecatedApisTable by registering(JavaExec::class) { + classpath = sourceSets["test"].runtimeClasspath + mainClass.set("org.junit.api.tools.ApiReportGenerator") + jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport) + args("DEPRECATED") + redirectOutput(deprecatedApisTableFile) + } + + val generateStandaloneConsoleLauncherShadowedArtifactsFile by registering(Copy::class) { + from(zipTree(standaloneConsoleLauncher.elements.map { it.single().asFile })) { + include("META-INF/shadowed-artifacts") + includeEmptyDirs = false + eachFile { + relativePath = RelativePath(true, standaloneConsoleLauncherShadowedArtifactsFile.get().asFile.name) + } + filter { line -> "- `${line}`" } + } + into(standaloneConsoleLauncherShadowedArtifactsFile.map { it.asFile.parentFile }) + } + + withType().configureEach { + inputs.files( + generateConsoleLauncherOptions, + generateExperimentalApisTable, + generateDeprecatedApisTable, + generateStandaloneConsoleLauncherShadowedArtifactsFile + ) + + resources { + from(sourceDir) { + include("**/images/**/*.png") + include("**/images/**/*.svg") + } + } + + // Temporary workaround for https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/599 + inputs.dir(sourceDir).withPropertyName("sourceDir").withPathSensitivity(RELATIVE) + + attributes(mapOf( + "jupiter-version" to version, + "platform-version" to project.property("platformVersion"), + "vintage-version" to project.property("vintageVersion"), + "bom-version" to version, + "junit4-version" to libs.versions.junit4.get(), + "apiguardian-version" to libs.versions.apiguardian.get(), + "ota4j-version" to libs.versions.opentest4j.get(), + "surefire-version" to libs.versions.surefire.get(), + "release-branch" to releaseBranch, + "docs-version" to docsVersion, + "revnumber" to version, + "consoleLauncherOptionsFile" to consoleLauncherOptionsFile, + "experimentalApisTableFile" to experimentalApisTableFile, + "deprecatedApisTableFile" to deprecatedApisTableFile, + "standaloneConsoleLauncherShadowedArtifactsFile" to standaloneConsoleLauncherShadowedArtifactsFile, + "outdir" to outputDir.absolutePath, + "source-highlighter" to "rouge", + "tabsize" to "4", + "toc" to "left", + "icons" to "font", + "sectanchors" to true, + "idprefix" to "", + "idseparator" to "-", + "jdk-javadoc-base-url" to jdkJavadocBaseUrl + )) + + sourceSets["test"].apply { + attributes(mapOf( + "testDir" to java.srcDirs.first(), + "testResourcesDir" to resources.srcDirs.first() + )) + inputs.dir(java.srcDirs.first()) + inputs.dir(resources.srcDirs.first()) + withConvention(KotlinSourceSet::class) { + attributes(mapOf("kotlinTestDir" to kotlin.srcDirs.first())) + inputs.dir(kotlin.srcDirs.first()) + } + } + + forkOptions { + // To avoid warning, see https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597 + jvmArgs( + "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED" + ) + } + } + + asciidoctor { + sources { + include("**/index.adoc") + } + resources { + from(sourceDir) { + include("tocbot-*/**") + } + } + attributes(mapOf( + "linkToPdf" to uploadPdfs, + "userGuidePdfFileName" to userGuidePdfFileName, + "releaseNotesUrl" to "../release-notes/index.html#release-notes" + )) + } + + asciidoctorPdf { + sources { + include("user-guide/index.adoc") + } + copyAllResources() + attributes(mapOf("releaseNotesUrl" to "https://junit.org/junit5/docs/$docsVersion/release-notes/")) + } + + val downloadJavadocElementLists by registering { + outputs.cacheIf { true } + outputs.dir(elementListsDir).withPropertyName("elementListsDir") + inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc) + doFirst { + externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> + val resource = resources.text.fromUri("${baseUrl}element-list") + elementListsDir.resolve(moduleName).apply { + mkdir() + resolve("element-list").writeText("module:$moduleName\n${resource.asString()}") + } + } + } + } + + val aggregateJavadocs by registering(Javadoc::class) { + dependsOn(modularProjects.map { it.tasks.jar }) + dependsOn(downloadJavadocElementLists) + group = "Documentation" + description = "Generates aggregated Javadocs" + + title = "JUnit $version API" + + val additionalStylesheetFile = "src/javadoc/junit-stylesheet.css" + inputs.file(additionalStylesheetFile) + val overviewFile = "src/javadoc/junit-overview.html" + inputs.file(overviewFile) + + options { + + memberLevel = JavadocMemberLevel.PROTECTED + header = rootProject.description + encoding = "UTF-8" + locale = "en" + overview = overviewFile + jFlags("-Xmx1g") + + this as StandardJavadocDocletOptions + splitIndex(true) + addBooleanOption("Xdoclint:all,-missing", true) + addBooleanOption("html5", true) + addMultilineStringsOption("tag").value = listOf( + "apiNote:a:API Note:", + "implNote:a:Implementation Note:" + ) + + links(jdkJavadocBaseUrl) + links("https://junit.org/junit4/javadoc/${libs.versions.junit4.get()}/") + externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> + linksOffline(baseUrl, "$elementListsDir/$moduleName") + } + + groups = mapOf( + "Jupiter" to listOf("org.junit.jupiter*"), + "Vintage" to listOf("org.junit.vintage*"), + "Platform" to listOf("org.junit.platform*") + ) + addStringOption("-add-stylesheet", additionalStylesheetFile) + use(true) + noTimestamp(true) + + addStringsOption("-module", ",").value = modularProjects.map { it.javaModuleName } + val moduleSourcePathOption = addPathOption("-module-source-path") + moduleSourcePathOption.value = modularProjects.map { it.file("src/module") } + moduleSourcePathOption.value.forEach { inputs.dir(it) } + addOption(ModuleSpecificJavadocFileOption("-patch-module", modularProjects.associate { + it.javaModuleName to files(it.sourceSets.matching { it.name.startsWith("main") }.map { it.allJava.srcDirs }).asPath + })) + addStringOption("-add-modules", "info.picocli") + addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf( + "org.junit.platform.console" to "info.picocli", + "org.junit.platform.reporting" to "org.opentest4j.reporting.events", + "org.junit.jupiter.params" to "univocity.parsers" + ))) + } + + source(modularProjects.map { files(it.sourceSets.matching { it.name.startsWith("main") }.map { it.allJava }) }) + classpath = files(modularProjects.map { it.sourceSets.main.get().compileClasspath }) + + maxMemory = "1024m" + destinationDir = file("$buildDir/docs/javadoc") + + doFirst { + (options as CoreJavadocOptions).modulePath = classpath.files.toList() + } + } + + val fixJavadoc by registering(Copy::class) { + dependsOn(aggregateJavadocs) + group = "Documentation" + description = "Fix links to external API specs in the locally aggregated Javadoc HTML files" + + val inputDir = aggregateJavadocs.map { it.destinationDir!! } + inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc) + from(inputDir.map { File(it, "element-list") }) { + // For compatibility with pre JDK 10 versions of the Javadoc tool + rename { "package-list" } + } + from(inputDir) { + filesMatching("**/*.html") { + val favicon = "" + filter { line -> + var result = if (line.startsWith("")) line.replace("", "$favicon") else line + externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> + result = result.replace("${baseUrl}$moduleName/", baseUrl) + } + return@filter result + } + } + } + into("$buildDir/docs/fixedJavadoc") + } + + val prepareDocsForUploadToGhPages by registering(Copy::class) { + dependsOn(fixJavadoc, asciidoctor, asciidoctorPdf) + outputs.dir(docsDir) + + from("$buildDir/checksum") { + include("published-checksum.txt") + } + from(asciidoctor.map { it.outputDir }) { + include("user-guide/**") + include("release-notes/**") + include("tocbot-*/**") + } + if (uploadPdfs) { + from(asciidoctorPdf.map { it.outputDir }) { + include("**/*.pdf") + rename { userGuidePdfFileName } + } + } + from(fixJavadoc.map { it.destinationDir }) { + into("api") + } + into("$docsDir/$docsVersion") + includeEmptyDirs = false + } + + val createCurrentDocsFolder by registering(Copy::class) { + dependsOn(prepareDocsForUploadToGhPages) + outputs.dir("$docsDir/current") + onlyIf { replaceCurrentDocs } + + from("$docsDir/$docsVersion") + into("$docsDir/current") + } + + val configureGitAuthor by registering { + dependsOn(gitPublishReset) + doFirst { + File(gitPublish.repoDir.get().asFile, ".git/config").appendText(""" + [user] + name = JUnit Team + email = team@junit.org + """.trimIndent()) + } + } + + gitPublishCopy { + dependsOn(prepareDocsForUploadToGhPages, createCurrentDocsFolder) + } + + gitPublishCommit { + dependsOn(configureGitAuthor) + } +} + +fun JavaExec.redirectOutput(outputFile: Provider) { + outputs.file(outputFile) + val byteStream = ByteArrayOutputStream() + standardOutput = byteStream + doLast { + outputFile.get().asFile.apply { + Files.createDirectories(parentFile.toPath()) + Files.write(toPath(), byteStream.toByteArray()) + } + } +} + +eclipse { + classpath { + plusConfigurations.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) + plusConfigurations.add(projects.junitJupiterParams.dependencyProject.configurations["shadowed"]) + } +} + +idea { + module { + scopes["PROVIDED"]!!["plus"]!!.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) + scopes["PROVIDED"]!!["plus"]!!.add(projects.junitJupiterParams.dependencyProject.configurations["shadowed"]) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html new file mode 100644 index 00000000..22136651 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html @@ -0,0 +1,33 @@ + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html new file mode 100644 index 00000000..30e46e0a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html @@ -0,0 +1,18 @@ + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc new file mode 100644 index 00000000..025ab6fc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc @@ -0,0 +1,190 @@ +:javadoc-root: link:../api +ifdef::backend-pdf[] +:javadoc-root: https://junit.org/junit5/docs/{docs-version}/api +endif::[] +// Snapshot Repository +:snapshot-repo: https://oss.sonatype.org/content/repositories/snapshots +// Base Links +:junit-team: https://github.com/junit-team +:junit5-repo: {junit-team}/junit5 +:current-branch: {junit5-repo}/tree/{release-branch} +// Platform Commons +:junit-platform-support-package: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/package-summary.html[org.junit.platform.commons.support] +:AnnotationSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/AnnotationSupport.html[AnnotationSupport] +:ClassSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ClassSupport.html[ClassSupport] +:ModifierSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ModifierSupport.html[ModifierSupport] +:ReflectionSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ReflectionSupport.html[ReflectionSupport] +:Testable: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/annotation/Testable.html[@Testable] +// Platform Console Launcher +:junit-platform-console: {javadoc-root}/org.junit.platform.console/org/junit/platform/console/package-summary.html[junit-platform-console] +:ConsoleLauncher: {javadoc-root}/org.junit.platform.console/org/junit/platform/console/ConsoleLauncher.html[ConsoleLauncher] +// Platform Engine +:junit-platform-engine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/package-summary.html[junit-platform-engine] +:junit-platform-engine-support-discovery: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/package-summary.html[org.junit.platform.engine.support.discovery] +:DiscoverySelectors_selectMethod: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod-java.lang.String-[selectMethod(String) in DiscoverySelectors] +:HierarchicalTestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine] +:ParallelExecutionConfigurationStrategy: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy] +:TestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html[TestEngine] +// Platform Launcher API +:junit-platform-launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/package-summary.html[junit-platform-launcher] +:Launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/Launcher.html[Launcher] +:LauncherConfig: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherConfig.html[LauncherConfig] +:LauncherDiscoveryListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryListener.html[LauncherDiscoveryListener] +:LauncherDiscoveryRequest: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest] +:LauncherDiscoveryRequestBuilder: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder] +:LauncherFactory: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory] +:LauncherSession: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession] +:LauncherSessionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSessionListener.html[LauncherSessionListener] +:LoggingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/LoggingListener.html[LoggingListener] +:PostDiscoveryFilter: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/PostDiscoveryFilter.html[PostDiscoveryFilter] +:SummaryGeneratingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/SummaryGeneratingListener.html[SummaryGeneratingListener] +:TestExecutionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestExecutionListener.html[TestExecutionListener] +:TestPlan: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestPlan.html[TestPlan] +:UniqueIdTrackingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.html[UniqueIdTrackingListener] +// Platform Reporting +:LegacyXmlReportGeneratingListener: {javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.html[LegacyXmlReportGeneratingListener] +:OpenTestReportGeneratingListener: {javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.html[OpenTestReportGeneratingListener] +// Platform Runner +:JUnitPlatform-Runner: {javadoc-root}/org.junit.platform.runnner/org/junit/platform/runner/JUnitPlatform.html[JUnitPlatform] +// Platform Suite +:suite-api-package: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html[org.junit.platform.suite.api] +:junit-platform-suite-engine: {javadoc-root}/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html[junit-platform-suite-engine] +// Platform Test Kit +:testkit-engine-package: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/package-summary.html[org.junit.platform.testkit.engine] +:EngineExecutionResults: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineExecutionResults.html[EngineExecutionResults] +:EngineTestKit: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineTestKit.html[EngineTestKit] +:Event: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Event.html[Event] +:EventConditions: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventConditions.html[EventConditions] +:Events: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Events.html[Events] +:EventStatistics: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventStatistics.html[EventStatistics] +:EventType: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventType.html[EventType] +:Execution: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Execution.html[Execution] +:Executions: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Executions.html[Executions] +:TerminationInfo: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TerminationInfo.html[TerminationInfo] +:TestExecutionResultConditions: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TestExecutionResultConditions.html[TestExecutionResultConditions] +// Jupiter Core API +:api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/package-summary.html[org.junit.jupiter.api] +:Assertions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html[org.junit.jupiter.api.Assertions] +:Assumptions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assumptions.html[org.junit.jupiter.api.Assumptions] +:ClassOrderer_ClassName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.ClassName.html[ClassOrderer.ClassName] +:ClassOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.DisplayName.html[ClassOrderer.DisplayName] +:ClassOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation] +:ClassOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random] +:ClassOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer] +:Disabled: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled] +:MethodOrderer_Alphanumeric: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Alphanumeric.html[MethodOrderer.Alphanumeric] +:MethodOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName] +:MethodOrderer_MethodName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.MethodName.html[MethodOrderer.MethodName] +:MethodOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html[MethodOrderer.OrderAnnotation] +:MethodOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Random.html[MethodOrderer.Random] +:MethodOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.html[MethodOrderer] +:Named: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Named.html[Named] +:Order: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Order.html[@Order] +:RepetitionInfo: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/RepetitionInfo.html[RepetitionInfo] +:TestInfo: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestInfo.html[TestInfo] +:TestClassOrder: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestClassOrder.html[@TestClassOrder] +:TestMethodOrder: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestMethodOrder.html[@TestMethodOrder] +:TestReporter: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestReporter.html[TestReporter] +:TestTemplate: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestTemplate.html[@TestTemplate] +// Jupiter Parallel API +:Execution: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html[@Execution] +:Isolated: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Isolated.html[@Isolated] +:ResourceLock: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[@ResourceLock] +:Resources: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Resources.html[Resources] +// Jupiter Extension APIs +:extension-api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/package-summary.html[org.junit.jupiter.api.extension] +:AfterAllCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterAllCallback.html[AfterAllCallback] +:AfterEachCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterEachCallback.html[AfterEachCallback] +:AfterTestExecutionCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterTestExecutionCallback.html[AfterTestExecutionCallback] +:ParameterContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterContext.html[ParameterContext] +:BeforeAllCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback] +:BeforeEachCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback] +:BeforeTestExecutionCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback] +:ExecutableInvoker: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html[ExecutableInvoker] +:ExecutionCondition: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition] +:ExtendWith: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith] +:ExtensionContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.html[ExtensionContext] +:ExtensionContext_Store: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.Store.html[Store] +:InvocationInterceptor: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/InvocationInterceptor.html[InvocationInterceptor] +:LifecycleMethodExecutionExceptionHandler: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.html[LifecycleMethodExecutionExceptionHandler] +:ParameterResolver: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterResolver.html[ParameterResolver] +:RegisterExtension: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/RegisterExtension.html[@RegisterExtension] +:TestExecutionExceptionHandler: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html[TestExecutionExceptionHandler] +:TestInstanceFactory: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstanceFactory.html[TestInstanceFactory] +:TestInstancePostProcessor: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePostProcessor.html[TestInstancePostProcessor] +:TestInstancePreConstructCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.html[TestInstancePreConstructCallback] +:TestInstancePreDestroyCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.html[TestInstancePreDestroyCallback] +:TestTemplateInvocationContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext] +:TestTemplateInvocationContextProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider] +:TestWatcher: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestWatcher.html[TestWatcher] +// Jupiter Conditions +:DisabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledForJreRange.html[@DisabledForJreRange] +:DisabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf] +:DisabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.html[@DisabledIfEnvironmentVariable] +:DisabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfSystemProperty.html[@DisabledIfSystemProperty] +:DisabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledInNativeImage.html[@DisabledInNativeImage] +:DisabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnJre.html[@DisabledOnJre] +:DisabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnOs.html[@DisabledOnOs] +:EnabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledForJreRange.html[@EnabledForJreRange] +:EnabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf] +:EnabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.html[@EnabledIfEnvironmentVariable] +:EnabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty] +:EnabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledInNativeImage.html[@EnabledInNativeImage] +:EnabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre] +:EnabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs] +:JRE: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/JRE.html[JRE] +// Jupiter I/O +:TempDir: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDir.html[@TempDir] +// Jupiter Params +:params-provider-package: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html[org.junit.jupiter.params.provider] +:ArgumentsAccessor: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAccessor.html[ArgumentsAccessor] +:ArgumentsAggregator: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAggregator.html[ArgumentsAggregator] +:EmptySource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/EmptySource.html[@EmptySource] +:MethodSource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html[@MethodSource] +:NullAndEmptySource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullAndEmptySource.html[@NullAndEmptySource] +:NullSource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullSource.html[@NullSource] +:ParameterizedTest: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html[@ParameterizedTest] +// Jupiter Engine +:junit-jupiter-engine: {javadoc-root}/org.junit.jupiter.engine/org/junit/jupiter/engine/package-summary.html[junit-jupiter-engine] +// Jupiter Extension Implementations +:DisabledCondition: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition] +:RepetitionInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java[RepetitionInfoParameterResolver] +:TempDirectory: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java[TempDirectory] +:TestInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java[TestInfoParameterResolver] +:TestReporterParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java[TestReporterParameterResolver] +:TypeBasedParameterResolver: {current-branch}/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java[TypeBasedParameterResolver] +// Jupiter Examples +:CustomAnnotationParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java[CustomAnnotationParameterResolver] +:CustomTypeParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java[CustomTypeParameterResolver] +:MapOfListsTypeBasedParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java[MapOfListsTypeBasedParameterResolver] +// Jupiter Migration Support +:EnableJUnit4MigrationSupport: {javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.html[@EnableJUnit4MigrationSupport] +:EnableRuleMigrationSupport: {javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.html[@EnableRuleMigrationSupport] +// Vintage +:junit-vintage-engine: {javadoc-root}/org.junit.vintage.engine/org/junit/vintage/engine/package-summary.html[junit-vintage-engine] +// Samples Repository +:junit5-samples-repo: {junit-team}/junit5-samples +:junit5-jupiter-starter-ant: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-ant[junit5-jupiter-starter-ant] +:junit5-jupiter-starter-gradle-groovy: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-gradle-groovy[junit5-jupiter-starter-gradle-groovy] +:junit5-jupiter-starter-gradle-kotlin: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-gradle-kotlin[junit5-jupiter-starter-gradle-kotlin] +:junit5-jupiter-starter-gradle: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-gradle[junit5-jupiter-starter-gradle] +:junit5-jupiter-starter-maven: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-maven[junit5-jupiter-starter-maven] +:RandomParametersExtension: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-extensions/src/main/java/com/example/random/RandomParametersExtension.java[RandomParametersExtension] +// Third-party Links +:API: https://apiguardian-team.github.io/apiguardian/docs/current/api/[@API] +:API_Guardian: https://github.com/apiguardian-team/apiguardian[@API Guardian] +:AssertJ: https://joel-costigliola.github.io/assertj/[AssertJ] +:Gitter: https://gitter.im/junit-team/junit5[Gitter] +:Hamcrest: https://hamcrest.org/JavaHamcrest/[Hamcrest] +:Log4j: https://logging.apache.org/log4j/2.x/[Log4j] +:Log4j_JDK_Logging_Adapter: https://logging.apache.org/log4j/2.x/log4j-jul/index.html[Log4j JDK Logging Adapter] +:Logback: https://logback.qos.ch/[Logback] +:LogManager: https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html[LogManager] +:Maven_Central: https://search.maven.org/[Maven Central] +:MockitoExtension: https://github.com/mockito/mockito/blob/release/2.x/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java[MockitoExtension] +:ServiceLoader: {jdk-javadoc-base-url}/java.base/java/util/ServiceLoader.html[ServiceLoader] +:SpringExtension: https://github.com/spring-projects/spring-framework/tree/HEAD/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java[SpringExtension] +:StackOverflow: https://stackoverflow.com/questions/tagged/junit5[Stack Overflow] +:Truth: https://truth.dev/[Truth] +:OpenTestReporting: https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] +:OpenTestReportingCliTool: https://github.com/ota4j-team/open-test-reporting#cli-tool-for-validation-and-format-conversion[Open Test Reporting CLI Tool] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc new file mode 100644 index 00000000..6b41a364 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -0,0 +1,31 @@ +[[release-notes]] += JUnit 5 Release Notes +Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juliette de Rancourt; Christian Stein +// +:basedir: {includedir}/release-notes +:docinfodir: {includedir}/docinfos +:docinfo2: +:numbered!: +// + +This document contains the _change log_ for all JUnit 5 releases since 5.8 GA. + +Please refer to the <<../user-guide/index.adoc#user-guide,User Guide>> for comprehensive +reference documentation for programmers writing tests, extension authors, and engine +authors as well as build tool and IDE vendors. + +include::{includedir}/link-attributes.adoc[] + +include::{basedir}/release-notes-5.9.3.adoc[] + +include::{basedir}/release-notes-5.9.2.adoc[] + +include::{basedir}/release-notes-5.9.1.adoc[] + +include::{basedir}/release-notes-5.9.0.adoc[] + +include::{basedir}/release-notes-5.8.2.adoc[] + +include::{basedir}/release-notes-5.8.1.adoc[] + +include::{basedir}/release-notes-5.8.0.adoc[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc new file mode 100644 index 00000000..fc0763c6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc @@ -0,0 +1,21 @@ +[[release-notes-5.8.0]] +== 5.8.0 + +*Date of Release:* September 12, 2021 + +*Scope:* + +* Declarative test suites via `@Suite` classes +* `LauncherSession` and accompanying listener +* New `UniqueIdTrackingListener` +* More fine-grained Java Flight Recorder events +* Java Flight Recorder support on Java 8 Update 262 or higher +* Test class ordering +* `@TempDir` can be used to create multiple temporary directories +* Extension registration via `@ExtendWith` on fields and parameters +* Auto-close support for arguments in `@ParameterizedTest` methods +* Memory and performance optimizations +* Numerous bug fixes and minor improvements + +For complete details consult the +https://junit.org/junit5/docs/5.8.0/release-notes/index.html[5.8.0 Release Notes] online. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc new file mode 100644 index 00000000..8a54b4f5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc @@ -0,0 +1,62 @@ +[[release-notes-5.8.1]] +== 5.8.1 + +*Date of Release:* September 22, 2021 + +*Scope:* + +* Support for _text blocks_ in `@CsvSource` +* Java 18 support in the `JRE` enum +* Access to the `ExecutionMode` in the `ExtensionContext` +* Minor bug fixes and enhancements since 5.8.0 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/59?closed=1+[5.8.1] milestone page in the JUnit repository on +GitHub. + + +[[release-notes-5.8.1-junit-platform]] +=== JUnit Platform + +==== Deprecations and Breaking Changes + +* `@UseTechnicalNames` has been deprecated in favor of the new `@Suite` support which does + not require the use of technical names. See the warning in + <<../user-guide/index.adoc#running-tests-junit-platform-runner, Using JUnit 4 to run the + JUnit Platform>> for details. + +==== New Features and Improvements + +* `ReflectionSupport.findNestedClasses(..)` is now thread-safe with regard to cycle + detection. + + +[[release-notes-5.8.1-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* `assertLinesMatch()` in `Assertions` no longer fails with a `NoSuchElementException` if + a limited fast-forward followed by at least one more expected line exceeds the remaining + actual lines. +* `assertLinesMatch()` in `Assertions` now handles fast-forwards with leading and trailing + spaces correctly and no longer throws an `IndexOutOfBoundsException`. + +==== New Features and Improvements + +* `JAVA_18` has been added to the `JRE` enum for use with JRE-based execution conditions. +* CSV content in `@CsvSource` can now be supplied as a _text block_ instead of an array of + strings. See the + <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User + Guide>> for details and an example. +* The `ExecutionMode` for the current test or container is now accessible via the + `ExtensionContext`. + + +[[release-notes-5.8.1-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* Relaxed version constraint in published Gradle Module Metadata to allow downgrading the + `junit:junit` dependency from 4.13.2. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc new file mode 100644 index 00000000..831cc74a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc @@ -0,0 +1,45 @@ +[[release-notes-5.8.2]] +== 5.8.2 + +*Date of Release:* November 28, 2021 + +*Scope:* + +* Text blocks in `@CsvSource` are treated like CSV files +* CSV headers in display names for `@CsvSource` and `@CsvFileSource` +* Custom quote character support in `@CsvSource` and `@CsvFileSource` + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/60?closed=1+[5.8.2] milestone page in the JUnit repository on +GitHub. + + +[[release-notes-5.8.2-junit-platform]] +=== JUnit Platform + +No changes. + + +[[release-notes-5.8.2-junit-jupiter]] +=== JUnit Jupiter + +==== New Features and Improvements + +* Text blocks in `@CsvSource` are now treated like complete CSV files, including support + for comments beginning with a `+++#+++` symbol as well as support for new lines within + quoted strings. See the + <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User + Guide>> for details and examples. +* CSV headers can now be used in display names in parameterized tests. See + <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, + `@CsvSource`>> and + <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvFileSource, + `@CsvFileSource`>> in the User Guide for details and examples. +* The quote character for _quoted strings_ in `@CsvSource` and `@CsvFileSource` is now + configurable via a new `quoteCharacter` attribute in each annotation. + + +[[release-notes-5.8.2-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc new file mode 100644 index 00000000..aa292b95 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc @@ -0,0 +1,148 @@ +[[release-notes-5.9.0]] +== 5.9.0 + +*Date of Release:* July 26, 2022 + +*Scope:* + +* XML reports in new https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] +format +* Configurable cleanup mode for `@TempDir` +* Configurable thread mode for `@Timeout` +* Conditional execution based on OS architectures +* New `TestInstancePreConstructCallback` extension API +* Reusable parameter resolution for custom extension methods via `ExecutableInvoker` +* Parameter injection for `@MethodSource` methods +* New `IterationSelector` +* Various improvements to `ConsoleLauncher` + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/58?closed=1+[5.9 M1], +link:{junit5-repo}+/milestone/61?closed=1+[5.9 RC1], and +link:{junit5-repo}+/milestone/62?closed=1+[5.9 GA] milestone pages in the JUnit repository +on GitHub. + + +[[release-notes-5.9.0-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* Fixed handling of global post-discovery filters that apply to `@Suite` classes. +* Since the Turkish language has special characters such as 'ı' and 'İ', the uppercase + conversion in `DefaultParallelExecutionConfigurationStrategy#getStrategy` previously + caused all tests to finish with exit code -1. This has been fixed by using the root + locale instead of the default one. +* Absolute path entries are now supported in JUnit's Platform Console Launcher on Windows. +* Attempts to load a `Class` for an invalid class name representing an extremely large + multidimensional array now fail within a reasonable amount of time. +* Fix concurrency issue in classpath scanning. + +==== Deprecations and Breaking Changes + +* `ConfigurationParameters.size()` has been deprecated in favor of the new `keySet()` + method. + +==== New Features and Improvements + +* Support for the https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] + format which supports all features of the JUnit Platform such as hierarchical test + structures, display names, tags, etc. Please refer to the + <<../user-guide/index.adoc#junit-platform-reporting-open-test-reporting, User Guide>> + for instructions on how to enable such reports in your build. +* `ConfigurationParameters` has a new `keySet()` method which allows you to retrieve all + configuration parameter keys. +* New `IterationSelector` for selecting a subset of a test's or container's iterations. +* `ParallelExecutionConfiguration` allows configuring the `saturate` predicate of the + `ForkJoinPool` used for parallel test execution. +* JUnit OSGi bundles now contain `engine` and `launcher` requirements ensuring that at + resolution time a fully running set of dependencies is calculated, avoiding the need for + these to be manually specified. +* JUnit Platform Standalone Console JAR now also includes the JUnit Platform Suite Engine. +* New `failIfNoTests` attribute added to `@Suite`. This will fail the suite if no tests + are discovered. +* The output color for the `ConsoleLauncher` can now be customized. The option + `--single-color` will apply a built-in monochrome style, while `--color-palette` will + accept a properties file. See the + <<../user-guide/index.adoc#running-tests-console-launcher-color-customization, + User Guide>> for details. +* The default theme for the `ConsoleLauncher` is now determined by the charset reported by + the system console on Java 17 and later. +* New `--list-engines` option added to the `ConsoleLauncher` which displays all registered + test engine implementations. +* Configuring included `EngineFilters` that do not match any registered `TestEngine` + results in an error to avoid misconfiguration – for example, due to typos. +* New public factory method to instantiate an `ExecutionRequest`. +* Documentation for overriding the JUnit version used in Spring Boot applications. See the + <<../user-guide/index.adoc#running-tests-build-spring-boot, User Guide>> for details. + + +[[release-notes-5.9.0-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* When cleaning up a `@TempDir`, only one retry attempt will be made to delete directories. +* Since the Turkish language has special characters such as 'ı' and 'İ', the uppercase + conversion in `DefaultParallelExecutionConfigurationStrategy#getStrategy` previously + caused all tests to finish with exit code -1. This has been fixed by using the root + locale instead of the default one. + +==== Deprecations and Breaking Changes + +* `@TempDir` fields are no longer allowed to be declared as `final`. + - This improves diagnostics for failures resulting from a user-declared `static final` + `@TempDir` field by throwing an exception with an informative error message. +* Private lifecycle methods (annotated with `@BeforeAll`, `@AfterAll`, `@BeforeEach`, or + `@AfterEach`) now correctly lead to an exception. Although this is a bug fix, it is + technically also a breaking change since there might be existing user code with + `private` lifecycle methods which will now start to fail. + +==== New Features and Improvements + +* `@TempDir` now includes a `cleanup` mode attribute for preventing a temporary directory + from being deleted after a test. The default cleanup mode can be configured via a + configuration parameter. +* Support for FreeBSD and OpenBSD operating systems in `@EnabledOnOs` and `@DisabledOnOs`. +* New `MATCH_NONE` mode for `@EnumSource` that selects only those enum constants whose + names match none of the supplied patterns. +* The `@Order` annotation is now a `STABLE` API. +* New `TestInstancePreConstructCallback` extension API that is called prior to test + instance construction – symmetric to the existing `TestInstancePreDestroyCallback` + extension API. +* Extensions can now leverage registered `ParameterResolver` extensions when invoking + methods and constructors via the new `ExecutableInvoker` API available in the + `ExtensionContext`. +* A subset of the invocations of parameterized or dynamic tests can now be selected via + the new `IterationSelector` discovery selector when launching the JUnit Platform. +* `JAVA_19` and `JAVA_20` have been added to the `JRE` enum for use with JRE-based + execution conditions. +* `@MethodSource` factory methods can now accept arguments resolved by registered + `ParameterResolver` extensions. +* `AssertionFailureBuilder` allows reusing Jupiter's logic for creating failure messages + to assist in writing custom assertion methods. +* Three new `abort` methods have been added to the `Assumptions` class. These are + analogous to the `fail` methods in the `Assertions` class, but instead of failing they + abort the test or container. +* Support for enabling/disabling tests based on the system's hardware architecture via new + `architectures` attributes in `@EnabledOnOs` and `@DisabledOnOs`. +* Default `@MethodSource` factory methods can now accept arguments. A _default_ factory + method is a method declared in the test class with the same name as the + `@ParameterizedTest` method that is inferred as the factory method when no explicit + factory method is specified in the `@MethodSource` annotation. +* Thread mode can be set on `@Timeout` annotation. It allows to configure whether test + code is executed in the thread of the calling code or in a separate thread. The three + modes are: `INFERRED` (default) which resolves the thread mode configured via the + property `junit.jupiter.execution.timeout.thread.mode.default`, `SAME_THREAD` that + executes the test code in the same thread as the calling code, and `SEPARATE_THREAD` + which executes it in a separate thread. + + +[[release-notes-5.9.0-junit-vintage]] +=== JUnit Vintage + +==== New Features and Improvements + +* More accurate reporting of intermediate start/finish events, e.g. iterations of the + `Parameterized` runner and classes executed indirectly via the `Suite` runner, when + running with JUnit 4.13 or later. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc new file mode 100644 index 00000000..3c828153 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -0,0 +1,53 @@ +[[release-notes-5.9.1]] +== 5.9.1 + +*Date of Release:* September 20, 2022 + +*Scope:* + +* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for testing in + GraalVM native images. +* Minor bug fixes and enhancements since 5.9.0 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/63?closed=1+[5.9.1] milestone page in the JUnit repository +on GitHub. + + +[[release-notes-5.9.1-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* `ReflectionSupport.findMethods(...)` now returns a distinct set of methods. +* Execution in GraalVM native images no longer requires `--initialize-at-build-time` for + `OpenTestReportGeneratingListener`. + + +[[release-notes-5.9.1-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* Headers provided via the `value` attribute in `@CsvSource` for a `@ParameterizedTest` + are now properly parsed when the `useHeadersInDisplayName` attribute is set to `true`. +* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that + references a factory method inherited from multiple interfaces no longer fails with an + exception stating that multiple factory methods with the same name were found. +* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that + references a factory method whose name is the same as other non-factory methods in the + same class no longer fails with an exception stating that multiple factory methods with + the same name were found. +* Assertion failures thrown from methods with applied timeouts using `ThreadMode.SEPARATE` + are now properly reported. + +==== New Features and Improvements + +* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for enabling and + disabling tests within a GraalVM native image. + + +[[release-notes-5.9.1-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc new file mode 100644 index 00000000..249e805d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc @@ -0,0 +1,61 @@ +[[release-notes-5.9.2]] +== 5.9.2 + +*Date of Release:* January 10, 2023 + +*Scope:* Bug fixes and enhancements since 5.9.1 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestones/5.9.2+[5.9.2] milestone page in the JUnit repository on +GitHub. + + +[[release-notes-5.9.2-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* The Java 7 based constructor for `ForkJoinPool` is no longer accidentally used on Java 9 + or higher when invalid `ParallelExecutionConfiguration` is provided. Instead, an + exception is thrown for invalid configuration, thereby preventing invalid configuration + from being silently ignored. + +==== New Features and Improvements + +* New `TestPlan.getTestIdentifier(UniqueId)` and `TestPlan.getChildren(UniqueId)` methods + to avoid parsing unique IDs unnecessarily during test execution. +* Support for limiting the `max-pool-size` for parallel execution via a configuration + parameter. +* Suite discovery now ignores cycles encountered in a test suite and logs an informational + message at `CONFIG` level instead of throwing an exception. + + +[[release-notes-5.9.2-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* New `@MethodSource` syntax for explicitly selecting an overloaded local factory method + without specifying its fully qualified name. + +==== Deprecations and Breaking Changes + +* The `fixed` parallel execution strategy now allows the thread pool to be saturated by + default. + +==== New Features and Improvements + +* `JAVA_21` has been added to the `JRE` enum for use with JRE-based execution conditions. +* New `junit.jupiter.execution.parallel.config.fixed.max-pool-size` configuration + parameter to set the maximum pool size. +* New `junit.jupiter.execution.parallel.config.fixed.saturate` configuration parameter to + disable pool saturation. + + +[[release-notes-5.9.2-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* `Parameterized` tests are now properly reported when used in combination with the + `Enclosed` runner. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc new file mode 100644 index 00000000..09dc2a8e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc @@ -0,0 +1,59 @@ +[[release-notes-5.9.3]] +== 5.9.3 + +*Date of Release:* April 26, 2023 + +*Scope:* Bug fixes and enhancements since 5.9.2 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/67?closed=1+[5.9.3] milestone page in the +JUnit repository on GitHub. + + +[[release-notes-5.9.3-junit-platform]] +=== JUnit Platform + +No changes. + + +[[release-notes-5.9.3-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* Parameter types for _local_ `@MethodSource` factory method names are now validated. For + example, `@MethodSource("myFactory(example.NonexistentType)")` will now result in an + exception stating that `example.NonexistentType` cannot be resolved to a valid type. +* The syntax for parameter types in _local_ `@MethodSource` factory method names now + supports canonical array names -- for example, you may now specify `int[]` as in + `@MethodSource("myFactory(int[])"` instead of the _binary_ name `[I` as in + `@MethodSource("myFactory([I)"` (which was already supported) and + `@MethodSource("myFactory(java.lang.String[])` instead of + `@MethodSource("myFactory([Ljava.lang.String;)`. +* The search algorithm used to find `@MethodSource` factory methods now applies consistent + semantics for _local_ qualified method names and fully-qualified method names for + overloaded factory methods. +* The `+{displayName}+` placeholder for `@ParameterizedTest` invocation names is no longer + parsed using `java.text.MessageFormat`.Consequently, any text in the display name of + the `@ParameterizedTest` method will be included unmodified in the invocation display + name.For example, Kotlin method names and custom display names configured via + `@DisplayName` can now contain apostrophes (`'`) as well as text resembling + `MessageFormat` elements such as `+{0}+` or `+{data}+`. +* Exceptions thrown for files that cannot be deleted when cleaning up a temporary + directory created via `@TempDir` now include the root cause. +* Lifecycle methods are allowed to be declared as `private` again for backwards + compatibility; however, using `private` visibility for lifecycle methods is strongly + discouraged and will be disallowed in a future release. + +==== New Features and Improvements + +* The search algorithm used to find `@MethodSource` factory methods now falls back to + lenient search semantics when a factory method cannot be found by qualified name + (without a parameter list) and also provides better diagnostics when a unique factory + method cannot be found. + + +[[release-notes-5.9.3-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc new file mode 100644 index 00000000..ef56e8c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc @@ -0,0 +1,73 @@ +// TODO: +// +// 1) Make a copy of this template file, replacing TEMPLATE in the file name with the +// current version (for example, 5.10.0-M1, 5.10.0-RC1, 5.10.0). +// 2) Open the new file for editing. +// 3) Replace all occurrences of VERSION with the current version (for example, 5.10.0-M1, +// 5.10.0-RC1, 5.10.0). The same version must be used in the file name and within the +// file. +// 4) Replace MILESTONE_NUMBER with the appropriate milestone number. This is an integer +// which you can determine via https://github.com/junit-team/junit5/milestones/. If a +// GitHub milestone does not yet exist for the given VERSION, you will need to create +// a new GitHub milestone or ask a member of the JUnit team to create it for you. +// 5) 'include:' this new file in index.adoc. +// 6) Delete this entire comment block. +// +[[release-notes-VERSION]] +== VERSION + +*Date of Release:* ❓ + +*Scope:* ❓ + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/MILESTONE_NUMBER?closed=1+[VERSION] milestone page in the +JUnit repository on GitHub. + + +[[release-notes-VERSION-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ + + +[[release-notes-VERSION-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ + + += [[release-notes-VERSION-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/fonts/Symbola.ttf b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/fonts/Symbola.ttf new file mode 100644 index 0000000000000000000000000000000000000000..055f02558b9a1edd0036bb1f48f435b250d474bb GIT binary patch literal 2440452 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%0DX?)du0 zzy4=dLqv4deWMD{iY;faF}F32Yg)-92q$9i%LF86uYaFgc-2RVh z?c*08nKgPUe)sZ#BKPt=NHuoEX#6t_DbO47eZ5P4FWGQ{I6u9_Bf%$;O1)vuom3zZ z#u@qb*h>Z29Q;Y(PNL#gN~lIS0k(*+wop9s=m@-ACa+>9RHWex_6ms#{N*Y!?lk6T zx|V!7t0LeQU(|(4G&6TqjBESlYYle9T+l=p^IWgvPQ1vd){CfnCBjdSHZTX%eK_Jw zO7hb<9%YoqfZ=cy60F0YhoV}ut1r|?LHYtd3O)+F4qyimST8GB9?|q~mMt3hIXm5FT}R)c+aIK33;I@64f-~O_$FYNC1?3LK~O2|9%_+Q zSPW3(f2hzBRbSmZD6jT@;MA0S`nA=L+g1;XF4jI1|9EWWo_F8fv+@}7n@JTXwjtHX zk@#C&^TTcPpMQS-Z9kByCNjK~{TbvYj2P8oA}~>e<%@)=>;hWhfr^1nfm*3Xp`b)< z29t@h9Q=iyYkn@M-=AFHWf++fryQAh``VHYTd;9LqMpSsN@nLjCJv6RuJcI^k8gia zjBN5L;6r;{w;IkM_R_uYMEf!=K0^0KZKP~O1jX8E1_*6{o0Bx%721HF@g4Uc z!%)67Jw72=rwzc4hwgvmNu))(Cm*?lueNlIZN~pX%I3Dl){SLZ%-g>_{*=11;-Q4# zl*)vtRO9$5Emrl@kNtuq&s@6n^tWis>V){!cq$&YDKW9)=q6|%?2S(M;SC}FGKjcQ z=j@Q?CBQ1-n{CCxe)a3wknoJS_~3vb{n=|s^V~(GJ(*R%>)?pA%=$g|)L2pfz5kK7 zHFwMijL(TQCWeoj-dgPU>yb0Cx$F-IpRAeHQ~mf;4Re; zLSO^;7stc+?1HE*^Ma=ZcP<@0X65=g9YEj0mX#q>Lyk=u|G)Aiu{zKOHXNq!U??VE={LWQ(v-O4HGMv6fm|dWX-T^yz#}PvZjdeu=~$DCg@|h z9U`tR-5i-C?_=7NvY02b!ux03Haoc>B^8yS2V?XaU-T;)O;)YO+i)4FC07dXWVunq z4*~CV+h{ambNvfhR`c(~l98Re*E?P>;Vz5W*7OK-s`@ngW;VL4$#PV#?9Mz0@&2Gv zxdh#fMnz<#o6{mOpnE!3d@saGAv)xa`$1`7ViLQ^0*4V9zETp>Zb3|+*x2dD-kg6J*uLjNSsGp-n6I4j7` z{DDuoZR|q?a|*T%Fc3J_sOJ&@IBa?*e6~lRNIF2^-SXnL`Ex$nxra-FqRq*uBy(6u z5DqJ`=~EI=1}gdpe;F?P1QT5j zf>96xQ1uD!(P>3{Q=0Gx0hvXL%dq|1{+z%?d*06p&PmVwVgJs}W0S&O=I?H-9679| zyCFW(Bs!!BSIILHpQ_CY(4^!|OHAQjwxzZf<}mSp-{HXh+L$CRF){C(mg@4Zgpi;| zj{j#V%BhPM#l*)wdF0-cZ7=~z6|+0{Y`yc0J-{bE#yfV{ z-8%_wWWlPfH`!5SS6V@5L2q5k$|=m?@b~&k*_%o`p`-kp3Bw)TagR$rS)ZVr)b~hE z#=JK-tm$iv4LK*;RGwc|IJ~(uGCWkg-8W3-*Lb*eRJ^?{CW${cr==EcJ$TFc$A?Qy z@d*!PSQAT1*7Tie`zkw>4~q+5GjD$HPHR?jY%cmZRTdu<78vZ*~EYhkYuClSGXc&xDnmF6*BI`;5EQK`PAvS#b6OQswZ=_5C! z7u`C}8etOMBZq0wi{tM{JF*PHFKxYh+UUr5QBcSIz2g@w7&Y_q2w!1QEsCPTBP-6$YTqLcG0soQ=&X+p3l(ox!59U~L?O$N zu4?=A`8CBMalE=@?&$KSy4=PcsnUeNuz+C42$BPW2IgQB`zrLE#K08Ldjs(Yi(Rmo z8YaYaj|ADF@2~&~Q9rr=FU2jVjGi|@CDx0s7v&(vUhUV%l#;=$`=m73I*sHy0v$2G3S(*30*Zqs5QPpb?qX~T)wOgOT|?}) zny5_3L(DJ8CuCSr?YxOIGJ<$g`Oaq%XrPk7yfee3J$ZdsL-XhTWS>bm(gIcbY$Y;T^_T-RuNu2Hv= zq=*xmV-CZ3g2S1#mA2F=eS}8Zbo8WP9yuFHn`meDi@a?Q{WvMW`udeu{L~h$E`Wd8cYAl$%DDNc zeo>+1j6aGj8J3Yb`of6hz#wh2*56l#qhqh$QxjOIkF!cctExh!*0`|zAXA*T;Q-3{ z@SwF$UKTVxq9@+_Q1+wf@Oz6BWs)jhKW4;;JMnwBBn9%8u>5Sa6#wub?eiM$ z4T#xRktqA`^;w9^UDs#zbv-7;J=kNBn0rHWQKGOrvh51-MXk55T2rqHR*!pWUa61S z+fT2C5YOYjYg*gBM3FP{#rJAZaAsbzwfb;ztj1qmtycSb<2uWW>kEBs0ft1eGC5f( zPBa8s{X>ir?VL~c^+aUKQvE}XT#OM)gD=LHQR2O0qosTjr^(MqpK`gzq+~+_ZSjbI zVh;2b_=39~<~N+8U^-DSiNGLQ9VuYqrZd5b0De!<-2@C$cUc;D``c1!Pu+;I4cUHa z=;Y&5S}$x~*K^Cg3pZ|PdKp*!XpLAH9G+9t5Tw6_tDo_X_;is+>aHm+X2xF|WK#PRJHTLQJWK9!eSm0ROWQgMhJaa`cTVZ4Zy3XLGm z09)VR4!&B-8%pXb7NQvzSUv+gw7Fq~frk}1a?L1Tn@=o0W7w}X;FIylC)KxEqwx1< z{7vGuS`fsHis=a*QLhYO66I(53(bZD(WMETWN(Z_#0047s>2ddNtKV4%HV}mUf@P< zHH_2HHl_S_n^Ha6#I07Tm?B)$Fyb2Kn-@0kx_vGF=a4v#M}D?D zrssU^Z)HZ)O)h^o9~%Eb8Z_wTDYybHgScogkzy`}NprhCsXH2Cu4viNVl#Aqz3aDv zVcUca^{8LWl~ad0=v=${2g>XB`Uhqf$=iX^7tpkGE6*EyxkrX7v$F6Rzk_+%&K*5( z(%WYInI#hc&}4Rxz{!yIYV0xK0O&MnNRgCL-2vryzF<=o>zG!Ff zhgx=F!mf;zIy~ZikUy3JWqqCK%pJ=VqH-Q_KN9;Jzx($>f9%)6c6%@}&EILdAp%Hn z8C1jrUzJl%Ks#Jnw-mj4@P~(k3KqV;%jYfaJBg!P%IlAvxcN{;TgzK3cY>kdaui`DvpnXK!T}*p)G)_i;iH>%Vus)?58LnkX zoY>&@nz{0|z*R$?qfk?f7|#M=_zlWo&`~^0qP|lpEgsJNM(j8s$F5J0>#8fMDeE#B znYx=rkdap_&=1MCCy_>yvrSRG2iHeHIBN*MN7%sU$^8Ty7609nYDSGkh4{*p2~Miv z^AI|W!d|?{9KavE{<%op5{5W9&`f)Lgeyf*~ex-*wa z#ys_Q(5x#zM6%P>P*{qZG0HUX3W`RIow&ETxO#L;OPfmF? zL|v@aPnwz2)v8X=a%Z%O{$nO)&u!meUiwYdip7{doEgE;?7L$In!)d>t;pWi(5gPju zXdO!3xN@h^(L{HVY8L*Wjzp2dh*-m#||E#Nhz;Q?)9rSAqB<_4Sg`5lBFtMuUwc zpPz~Zvdk3JEo2|jg{fpmZSmI|mK|{vNHi*0uUaZqtuA^j*xM%ozudGaJ1#DJcv##E z=^9_veO$N9A{!Q*ibD5qJa+MZFdWr>smGT+j3yqKGzP*e%5Y8}t~8qbl0SLg;XSV@ zJ~unQR^!J=oS@|l*Fwn?f3(P!sg@|0JJ(4%J0I=r{O8d5#|AsE&@&3h_rJXkHv+w< zR|v~;$tXm5jskR?cp5}mz{iDdJ?x&^|AK!ZUkoxX{OzKw=VMK;g!t#_3{gofeXS#Z zpM7J`j6>;12#fltGtklYoU*CiEAXY?W!$%#CgbODSEbBN=lo&HzpujyG4l+c?UGxf z5PJSHJ^wU0Pv(8#pZrgv&ms59MbB=@Wg?7BV5=28*Hq(Ek>-p{IjwLz$!HN|8QDQd zN8`;FD%-ew=1*R! z38&`D$wC}y=bh`;-3iNWXYDxibxH(tkZ{qv!dL@Xbf*zQMsh5DP*tG8&7*m*;Y+$m|a*39)fLAwjtqM@WWxCR?3#)4^0x!DUom%I*2@%Qn7+g>Ut8B=3s45b$x-kC4-V=U^ zh_hKG-ipA48J%-Z zPtMM@MaxnwE!C-AMIE`Dplpm{i7ZlDYDLYij3Z>&l-p6%m0I0mNs&d{aZ;?ZOSdK9d-bQ-FHpb6vt(E>XA_y!X!@VT0^cJ7*<=H+hhoPXm4Y~ z)U=NLOohbMSFv}gQmeDrTGQ(8`k-i^1U;#jN4XP~aDilB(Fb?crM22DI<0c)-ikhx zM3I@_kv6rVaqmN6!n|-302hF~r*=DFWe)@ou4C$0CdNAXA=5?XhWyYt0;z5VLkE6v zSeyyie!^pip2TMl`vyo9wWTv3*fVMR&iO_6MEYd4)Q{agrheX$+h?dnBAo+p`vRpX!S`;eKkr zi@?frKA#`5{*+)Nz{F$`!T*GS_@Dkeyu&m99l&w+k*qfru|U<%;~{G{aS#77i~*5v zAj+tk1q4JE#yag4Cw)g!w$j8dBdS6_URxesOEJk z*j!P#C;CBRIP}Cn^hAHqpq3@_8EAC?<^X~+&Nb6+<`gs|X|Ib%scfbuHfXdV9dQ|5 zPLdrMLb6Z%{OE#ZV*iIK>XBbVd6G|$O4`=E<*)aE31%ycL-V3bfpDurQauTmF}yQ% z)}yE_)HjkReluiL8u3$iHrBp^j)Cz-P7yQf+J}^QX+I?KFb|CchYqnA-OAd9G!4RT zw>O;kx6#SD@jOMQlK7u>QjNbXC%|0j+g+wa;{$;u)!uml7W7gFIi*0OBb=uqyU#1Q+Rf{LTpO)*;6Pu{yDKk7OP6aqLi9^RSL@ zX#4-rR{<7E))7jy8m*&#>6U0+-7Zz3F^1A1-7Zz4#+aeqhEwgXyCPi_s+kKA@W1bV zzdDzILck~Z1(*I^0kSpHUH`=sr&HNVTs=4U0uZP!S}_RK{pGgtFH~j*B-`1oPP6xS z(0X`^+^4fMITv0-4Zx55kEwOJ#VFnekJxG-k0a^?8ojOfCd*9t(eR*&%-up++ zA=IV5Yu|fc9o&2AwUWv+V=K$sA3fJz&g{m2zkKD&WfaajzWr@~P$ZH+`RqGyLM@aF zn-V`h{>{}37@?L%Eb>e~wqSHleDEb3Bmd5rB8c?ZD zWn@dUQ9Cvk!O~v?zaNSmd@#5RTJYS6;LiNB1tXZi?-{F4{v*_+Bw^`IUEoE_rp=$JkBV7%-T%0QS z+DBn>6Ji#lpHuyO@Q3&v=oVlE607_b;wU1bU7??5IQ|OuH8NAA#KdTU*WHwJHDzfSaWw>W7$thRYrNCUwOgGIkT6Sjnbygp1<|E+t!>+ zZ`gCF)@HBYf21}MU3wmWHviU#S9YWOpBNjW^XC+bu#UnkADJ{@)J%MEUE`>ocb<8r zb=@5;m;TbeaW4ogBEujRg1tg>^`l5@Y0MY6IN54XafUUW%l3hL*c7m>jW7qmHgvTK zP$d*?u%@wBZs}a+IG)kUGJb2As70N(zB0aW_{$S(FPK$twDy`)>`nNeD)ZBAOZ#V4 z6yz`_PR2Z=0=p_Vqj1{h9BoDcYA^5f3&k&Y;~g~_+4yj%FDaV`CqosJy7@qa#GCg%Rsinqn0z0lTs%?+-h_3mjv2E#DOV&8bENplG)ykf z=OTZJKXXwnRihm8PG;aK{2liNywgs+mxCzJ?PsASIoy60>SZyxpb`udRjB@heinw5 zDED-;a9@m%_pzD-OnHv8d8Pn!rhiOx7h0){i#r~p9mzKNsFh;7+7KA0E<^YwPPuHE zLWE@NG*KudaZg`ngpX0o;_sB{^4e@wP>?Enj5=Cg_=-fW^%MIIN6Dzm$g?@HGrct2 z(UoN6U{W;xl2=d(bAPZTI7AkUpTa*IOL$Jsi8zK4`HG!1Jd@IJxpUk<5p}Znd)?`2 z+tH0S{|Eny0F+_bGYQjEdZ&I8rzeUQ)TAb%YZN{GE5Q3OeDDJ!>lERXXGEQr*y2Sc z1t>GOye%iRqjPl5N=CGRB zjiG)VTUs8K6BAglEh|rij4Fvn_@+jp!XL<-=nZV&7M~Y} zybXE23WaE-H_xc$O>uC>Cmd#7A!_pGdGB?INCLG~$xL(15V69_4t|5XYzvjjHbg5V-YeLtphiULYC|d0;-qag^Q>B`@)zrOu6F#|lxw!x%$8t;n)=?T zkoh?kMpg{x(!0C`+6;N%Jkk23#5sX#G;ReBcEYr9(q&o$NFW(BL)r?p)hvaE{*a=g z1VsQo?5oo$@B=%KDE#i*!AfsK-cfQ`36kX-j+BH8VN!S*MCQ%V9Ha$SOqWycb7BN83*-y<0gcbijs{%fq^m4;H_35w_M4O)AJS!^4!IBsa`{83)Xd5Y!k=mKOX_hmC%qjGKG(tl z%nK)xX_Vqd8OzFUBXkqT{M+FzmkO#Xj=2QG4pr_nVud4(oTc~|kK^hgH&I05QqFdE z(RRYh6=y-N%>A6D(>AA-HrnDaCrIEIglTuSBw%enzCUw$CTcnha zwzp42FFt(7-Ep19wTqL~NQ(cXPFlRy*co^C9S@({b~Nc6J&*r`r1bo`q@&yR&#-lu zcWiJR-@0^61v6qpM|rnx#>ZopLYK)H!1})_bPPrdbOLzx5YP!(dm=zF&_yIr2ngSu zTR@E~;DMnh$lmb+p5+d?Kx;p~P0H4nJT7^voYKBiOTEMQMe4ze1X7Pu!x96Gg z7hxbQm7>w!qW{C(djLdrWq-ijH}6eh2AIMO3}u+Y&^ru6?;t3mD4?Pg5qs~wccaD< zYZ4QUF~$;&n%+z_CefHpHf7T{n`F~>Q+Csoxqj!~_omUr~x z>hEkmx2tB2YM<@G;HmFL90Jhi?wVa zoCUQk@NX@X*B}F^Q7U`H#Lfnd34V$X)Hwi+q00c`kvV{G1H_e>CEms@sZ#0wISod3 z^L`N2baC$QzbUNZA$6Qa=~tvcei~S+Qc{lw7uK`zs8E48P)=&4w5|jV>h!KUILG;L zZj%72lCUAs;p_D(GlBWV{j*G^G|(wCVj?rG-ZBHTEqd6%AJ~{vw8Spax`yR}Dha>Q zLO&!8?y^x7Q3QMU+9-WjfV58TPaP&Id%2pBzHfR|e1Ha01%5j4l88MA(Jb~loS!K2 zozo)*IU^o1y$h+Da$n0ru#V)NsXNSGpWeR0@o*Lz1*09SciLjr*$K==;W|dXi>7aQ zU||zhTc2-U=Jtm#Mfeq@>_GfAj9<$iuyFWPmmds%`i&dh6L2EthJ^Mb|`f&(j(1@Av{Fc=v6(1<;a*gX^ zM^Dj5gon(DT7FpkR(18XQT2LhDD}h2mhJd({~q+0m$%(;Y6npIU!#qUhzb@22U+q zcJAzwMa-`^HHLmy&0BQzk);cd?@!BEU7nR$zHUuFzywIy9^w^5Ed$ylJ z(Jh7@bMHR9{Qd`4@c!0k>|i*bxjyCOBv9*=k0X+fHG-}t+~_2-MtH)<5j#f_&31^< zklzM0Lw0aU#(NnhTcq5lD8_HAt>gat z3lVMxgawtJB~Je=Tei#Tud)t|DcLi>Jg0yS_EkA@Bf+IaCs38a|B0tvHquOv;Ea-Z z3)I7kzVdj!!u9+zc@FoB4v%I>a#zW_iQLh0389Dr?r}K&w8X{9R(CZliMZqf0s~3v z!B`C1wV_Jq@z>aqGiG18ZEuWR6!PzF%VT6kL+@EUq(79@y%-nyDy@h;8612vK|dM> z2HYDQj9Z!5qwOy&UxL2)`P~mzH=4C##F`(u7iX70vgPra<5u7?o|Sc?zePk;4AUwY z0N?v2A_6aJnz3ag-6M_Zk-dw)qkF#~x=i=zJX?8Wk9^RS?8;peD^$etOr=wufY+px zR|pZO1rZxgE4pe5ljs{pPOGkdOMG~F)a(#Lgnmj}%sOLko$`ZK8s6T2kZBbDs3|WrWI{m-{<bgMPps_++_#dcy)fbH|z zqUjrtE*y11ZC%&IUryk!bn+W5$w}sfAjZpY>~vw#mXVJ`x+gCe#e1jDJeX7c z^x&0s-Y55u>Fuyj;@#i6gQieJyfx7D4u5f7QCqhPfokfjjld7|?5kZynND8~Y(dZK z3*-Y@QP_!aX2m{VQ~3b-X6vZj^YUiKtACdF$BbAdE*j6eR)R&=gCCJwI99QzL zC8fhWiN+(QJ_)3dD3dl2I!QnY(3hg;I>(JRZ_UQ*$DXXtDHsu*Q6HPvRBX&tTkvzb zl#uEL!wgpzTzk)d?6H9IQJo?~j%#RR$Mm05GWw59kE->J8kM*ru3qGq=`Ur* zzAlGEp;bwTdO9VFK$UC=b!S`J+FE+`NT~ITd}hG>hGmeaii8C#A?qNrFd|D7bW5tz zmC%c$w!_RMHH}8YJQk5jCtikECLBL18+$5l==s9+ZTbq zz^-%*b2!eSJMc63`e0N&8*M^wIKIMTAF4y*?@<7H7=QTeSD!sR^6w}M6{0c239U9k zpHLa-1>!gJ>!1a<0H4J#1h9l*A+RFC+H@UPhW~0@zeHNT+fZ6vX`X&;Tb_MVMq$V? z{ied~i6cH!iu5=dA6?V?OtNp51gG)9B5Oc_Vknx6Y=s>($P4zO&Jwg z6;fPItbC$KsTJ&D<^RQ^4jc8-N#n*9$r|d>C$HFOJo!>(M3@*^@C8rnm*%o1hanf- zPB`GY4_WBFgn3#j3+Z*>M_5n;$Fn=64+>T=7)ZJp8~}Q6N)EdlNxst}{fheZ^!gR} z3(|qtwSfd*x@jUgPj~)B6yV)uvL3frPQi^+Dsl8CuNITPBGXM1%R>JN)gzyAZ-HGq z&Wko;5!EDwI7BqzkdoKlmO}Kc8^4q~+*_tluqsAP9XQ~U=9OWgkvTb$p~J3lU-m0r z-I8x$IE?{?e!TU)eY?2#ZfX*L5V&*qRbq80^-im!0spQF9*p0p4N~DZMtGPV+csb9 z)kuDP#TvmTfpZHPXz^^QbI&53Mefc=kyu4DkXmBzLfDqzhAE>(G%Xp#hjMiu<3*4h z=``tT@oYL}ME~x6(xBzlg>M&DFAtLL`$U!=n?(kWtS(|=0xgjm#ujeW)i~Dt<(<2e znG{l3l9!T+-dcsXr-k{)kiw@J|FASPdOfykg51DEJj_s`zYH`NL88Rk>dd zlSh6&OcZD~cU(?m0MqHO05ShX)&0lGx`Fo6{{rpXeI|RayAQP2+`^Hi719Lf)GqKz z`rpocTA}me(@&0)n;ll>eH8L1n8_f=OFdVG3}l9pu`}KCTKop^dJM^(-4d_+LNS*W zsR6%$N6aE53(6F~@zfA17}Gfa_R{2o$))Z7-1z&r={pSt_2ZHr!oD{>!i??>wGpJ} z7)Vb%_TAcN=|KbVZ9Q9*U!W~rg^S7-_XF4@rR4BtR67&(?s6v2(LmPC9#Dk=jh{-M(8M%h=M zn7h*Gc*3m%n105Ub5E?akJ1Im#gf2gn2&Tg8J$gsLRV-BS%x>J;HI*&X7F$2?CN;E zR-p>_!?V#qJ+U2my|5YVp$ZaZbbPd56Lu-QUaa`@J3LLfHJZiT^FBGE5?PkcWRi7)&(jj_HYGFYV!k%>fs-zKJR?PrXf$#Q$gYj>0hB5h`3g=fW&Ty15%}!`u})pG25ReO`K<8VYajVd;6gw>fLip0Id0*tK5A=4X-%K}^MDfy_OJcw_oC_+d#DIU&myrCaQ0Pz+2C>)@Qs^*J zCSq3qX<iTg-NV1qA&jv=B8MS#iO* z)L4`ls|$|G__5#a3^QP;2AgW~-p!l1JHMnQVqpX_ym)tX&A{j=g>vn=F}uePn}1+h zTL|mttB+-5j48B!>4_;^UY=i_Dq0($5{;W}NDoU&Nnto{@x7Bb;AJNk-+{)Tt}hA5 z$|#93M=x$JJX#kP({}Hq(KSQWKi>5yg61QMuU|>~-O~$gCArmsQGrRnq}JDnk&hfE z@TMX3)3*iYkfbEB{CZ$P%@TaAP2V^LNCq=&{w5m69`%_{BvR-J2Zc@T+8)T4=0QH{ z8FWuQpr}ASvxK+*iH||+tb`(=VXMaM0tl~!Ttkp`M3a3%Y$$6EQ0eUP_azUVmtw09 z8X5H3*4g2?IpNXaw~!EF7^b=h*rHD2qVD#@W1=Vi)%;=BPr+_FQ4zks<~be19m-=GlLjx8SwY6|9S*;k%j zw?Bfpq*uj>NFRhKPNjF`MC@O87H(3qZxTP+EV{SLzNy9Uv2WfR%0~nhf&c2`zS+&; zzJcWa^BX_uustZ&H#wjr1OFqVBtWl|k5Nbtzu`x&L6tg~D6T+OoKrHB^F3XXfg&Ykpus*_e`#Qd*O5HujOJ`F-ZhaYg;6E~NDbq!t)r#C|Y)B9X`?R0H$K!=U@R zRQ#ny(FzhE5|1je4<@$wmM2r6N9F%<#`TP{{ zV~_Fpy;P}_*6{8=7N&&4&e`3A28MH--HHz=!c|KAj8v+cHf!iTMN7rMAbnn&txlBD zufh^k*YQ~hBO0icD)YfDGl-Rr<;pamfIO7|*h_K9MF>X2V~gEP>+eYzI04?Iue zpOY9YktJLuTMDt39H>nviw63@j6?lysdCU+R)YdpB^X$sk^X6&e%Nnv1lCOW>c`)hA z#e)vEj%^$M_p;jiTG|>LKNVRARAp#<%M?u=^SxrS?SS|F)i9Mm1uMzt90ztFOXlPQ z)|m612ACdt_ra?pGodVg(?{ptYt6FX2Be`iG*yzxAj5<DN zGWk}i($TMnQ9QR!3%s$1;XUAc-S89NE6#*IpK{neu$Ln^op4r3+NqG>ufwRVOsg{q z1wRWwWRM%66-ARPI~lqiDdH8qW+MQmG*W!;k&Ae37*iM%V=yscc*BcFk6c8H!x)nx zCZ>=HLknI!dKsT+pK6i8)CtS<(fBy3bH76M%>DT1b8l%j`AVZwg7i^R-%Xm=&mdnU z7ar<^QiO;2AL#hqN%B*P$tQi|cjIZe9$r0^n0x|W5&japw~14-#jrmi&(LLm#18AS zKavrSrUqFu1OFjIjld{_GXSN=kRMhq-HHFHX6#0j-oR+^mv<~*wiAVF7=zwqv@>d? z-?{wwwF*Fvl4x}jCivv!IoHYuN}(uPCuZ~~rp!TaJi2(Ba)?9{5vdG}kVu9EY+Zcr z(IwlIwNkNBqlz?&rM1efi!LDj_;6`#O}N2#ifYW1llUum8B-HxkWE&NfjA6*Pl$&| z`unIs^AY(4mPEqAj#iV(2YMhm37mAWswh{`wEZH#sjI*J zE>D{G-M6c!9()MZ;y4uDT#~B`o;WdBms`?|qTxyHLkGpJh41_rkMy~Ad#-7=dZk`! zl7=M=O)*DYKiBc($G`si@sk~B#4GR1;`NSN$JU8W?K9e^s+TTRPla!qCNi^_Q~G$> z`>&Am3-gcO<6ibrfd+=!5!e1Hvsj3b_PBk>eDG2Z1`uqYp3-gzK!_qtnwikt+=_2Z z-dJUgsIFT)9fcMpX5)396ktZcaEDKfQsXDiX*BwTrDM{fb-^ty!MfZH{zE=D&GX!U$1<2!LnhI|yqf=R;^);oIQ-6WgQm?MHzQ!qoPZhQW=|V5?#|&4 zx+d2!d`?GVhKbZ!Kmp$82uBvczZH@heaiH3gKV;7>+Q$;AHUt2EVJoFr%~lib3bBc zvpH<|tXadu%$t$X(Q?a~AaX`&E(r1%8j4BQ&H6Qc$Pls1ZGL*={EhuL&Nu4)VEO(Z z-y~(k?pzZQP1YVAu?E&YuCECsICo%g;7ZyLP6h(Noxz)&l-~5*(33~+#I9pk1fA;4 zOtAln2o)8%r1O&qs#P-RWSftJR+9NIwa)fb6>e(u86D=3iW9CV_F5M$oFzbZ|+!DsetN(%FytbnrBSO z6I!GNWp)uShe<%XHWni=||9|6d;-ad143_0YJ0G9Do3sSp@%x zCI)12t6fJ@A9lcf*T6q|Xzij2{xRW2d6QR0&e*#ua)#BK++T@b_fH5eEI~DkOq2I5 zF;AHq*k=9bIwv#jw;}PzN}6vi2y?qWV0ii zopR%GLBIHjoZ7WF?NWJDEz#5AhwXMuVdvji%RLFR`sk;@?m?FWjwEb&aedR+&)Ztd zf=m9=jzrm@mR~RY+mA+M`Bwy4D{_e0z4&YHE;vWPTUaWv3=}%~3)FbSUuUO8Wy-B< z+d~(gFGkrF(dqi zsGNe7qJ%hDKf`Xg@o(-pS%XLdQWH&f02x(Eu^^qg|A3bUR8L51%Lyu&RgqJ_A?Da8 z?0dDr_14JLs_>P&vg?cLU?I}q`#W|sN5tiz*DX-<+s|hNd4NbC^+ugWhafw=zVQr)L7d)cY7(=AH#*$U$P+m5G?)fYiPu8qSrBdF zknolav1%YNB=q084RP`g=Wp{>Wz_d{zEy`>LP2<2qS6hKsinDtrXj0R6_lK@z#U z!~HjrjC_jwGugMCe~JEj$6@jz!Uu}ZI@cilKEA*&Quy_26FmMi)Bvmt> zC<&J@A4bdYs$NQCL<1pzjsH=AuX)$Oq{ChFj%XizcC-*lc6E8fXTxYX_-w84*{*UI zck%Szy|*JH{=~bu+g&o9od5ju`FDF(uQ>Nh@PQ&pg(EpXk|2r5D^sGg5Z8f+oN;|P z$w;b9(RNKH`|7$ycdyIbgWr;<&sFB*C(|mc)QdpO)ekd|J+yKEt9uSTY4g`0zm1vu zei6N~d&+3PuVCm0PBSW_X*d-{1(?`SqcWB&Xr6TB;Oz>3o8!BGMc`inMxEw+M5iI2 z80o~Xfc3emLm({()pOmhk95F;O_eD+-8Ot)Th8n^S@~^6Nk3bQi~Sp*-BiAj(XO4e z@Z^+vo2}|oV{+ykXYL%=zr=r!;VgbrBlBoFzo3!n*n;Bzb0>_1=J&(<4UZ7Bgv7Dg zY2peRrxVcxEwPfj%jQA9J2CRuX)%sh(*pvf>d*Qo;Z^oZsRjlRsw4nS$Q(X4up~rW zfuDlCU;mp<VjKj20Ko7-uC6ouyK|Vt>tkR z9dCjo;<<5js*H)Fd#CmsiJw!9fm>dO2i5?37MFC!8`!(o3vmGNEXJRDf)1eIVl>%> zJ3#!U!R$jo4+w8tDRv2%hP)XsWN^AA3r`YSbte!I#%}?7$wQBw!C%gsH)C-8={G)^ z(>`wQN0~XDDMjhz#I96ova(Rv=|`W&U!Qm|FQ?gf>VfUwezoI4h2u$2>Up79`rB}e z^q%_#IzlV#J~&U{?^y#`aQOb%;=v#h?Ze;v?fmwX4f_t)R}H8?6dBc-wv@67E~p2q z_5JmmH=O_WPUFIS3R{DWZM64aU_KXDW$ZnX;{<1H1q!wN>U^bIgao=)xqNtfyVzemZZv* z!TB2W{saRG@(1{P@md~>g7S#uuKW`7E&g6RJx3E87WBUZqmT!NixlzDBuCCoBgOf2=2}U%qev-2jPQbR#i zaeVag&?y?7xoCgTp<9Lq<>hcS#o&O95y{09APtW_lMRs}WFDLDeb3#&a%@?+W3IV> zc6#BCMr+oHL3x3V7be^?O2?8R6>L#xZfRbO`xdWfCJV_A6UAt)v9qKCRA zh_e{6H!ur9p|JBj70Mq*vk-wl*sGf8JD7#*=o7hsy!#QzBaat637QAv$q=owz*l$H zTQ{Nso!jYrbp?bRNZ}3~9Rcj8GnTRo|Fy7mO2Jb%0e~G}S8%?aosr$zHwf7I&Fw*W zKHBDiliZDr1fW2CEq$NlmnFTR!IE4z^!R{`jO|PtsNCxdX`Dg=rbN1yDoTMiF4BP3 z5wceBV~Bb>VNZrtl`Om&>`8G}$%(;9ve@E20iRSY(%|1=*s>ers2h1f(x*-)o@n>W z=?MU(S9$TizFo(LxTIYK26{`tNBuv*3kLr{b*3T}HMkJ9=itqM4mDUbsNn8Ue~Sw# zdso4CHvk%*`7Vpg_jndFukc2nhNj~768raqE)E7;*F9MjKBJGNI5C_ zt^Uv;OS2dtJdOFAdj-p(vLZ3}1ydS=mO|)&dOcnspna1OiMb1=^r`rM6w}`WD0CA5 zh1LZ{|8eo4kd+=-qVmB0c*ISFgJ7n7#?1mf($tG=@F5DILr=g2ULuicgVYdjwRq|e zZdMoABx9jBbivc4IW#{Y_z#N$1^jaZ97v$s;$JtD1B$a;LQq_swufiru=@xBy@#D0 zq%^4l(FAXF5aB7gq3AdSHv;p|&baC=O;6#&s!q^bI`@FaQ5kjb%}nn*r=|e-dPgvY zXcjPUc{}4U)FHS0;k`R=kxrX;KJE_H<-#B9ofW{1I>El&GRRSDXDk?qfk_rZ{X6s_ zdc=k_-B}j5E(<2fLRSC@K}g&iTECC{cL$iRf8ee^hj}@D;CwEPJwV)vdj$AA^#5d! zPUbp!ZsHz+@JPl^f`o|xTI-KNSa|?u+TC^FeS@&dr9Q-9$#JfT)TZ_v(}Ke&)aLR^ zopQAELpa<}LJu7rW@jZmjdMwkcN6CV>}~mIo~t!^`e?@)@}LbK?6vkI58x%5$Q1+7 zwS1vc+*8==*^mIE1YmQZo{^w<&%J(|_k$>SDF!gCX#Y^$Orv1r?0&?(2mQ@Zl6;dG zSf8-E;Le7+qWAb{*!um4>-y7Z7-0JF>~!LHQFt-|zl%WV0Df0DA+a?|0dkUpdbwY? zLA~%#G%!YL-rnY}Ks{tK>7cbjHZfZ4o;wzSh#GYcadgdHCC)G{kMu0-3(DlKG+Z4z zZpKOc_4&shJ&%I*N*UBQj>6ZFtf_V0P<&?Wf~THeK4oOny_xn?{oc;1XkVNWVau8L z?%oSYNgws;d|dxCb`I2UB$EmbVePz+0_5b^E>=Oc8VDQlI4F%ftsPvLOmE zHqfV2q=65fME6`H1B2k`*v!pe#jAW-O)!4@%2KaLN=ax7cAPv;#%*@4lo2Lr9$T9+ z++?|o-{-tyC&{HDgCbsk6)DL4O}=7=0gScPB7*l8!$*Ee7MWVI0%6^i#!jkJ$3^50 z7tzp(ow_wJEO){`V2tdlb(a=4&m0|>-9Bh{Uf$6&uIaI}T)~l=yu85h<}p!4SZBMk zbL+)dcI?e8n$eyW*UZ(JoAE37wN^8kC+qk{2!y2d&&aTZrP+*jK3)y6Upk*6h3H=< z%9Zs1V;lx01qmS!&%`e1Ad68fg5GzoBvrQ-|CXc-^3@;)4Rg+Mz>{U+!Tyh{rQxd7 z8$PSFDUs}>qP0joU*;co{SCb?R>TiHDiVey<)dhoflZ0`lPUoQ+`nxG(_EJ9pBo!)Da8lZ zM#D#>=o0oNTIEpqwT~HkZ&GGVmgAgFrS>tnm-H z4^9rkYtS}4-8WzWGp;HVpSM>@)sRh7OYkhT1-F~?we8Eko>dh*!w4T4 zQXrPdXumQ&SOAkQOc{H51wN0z)Wlhm`p+$X=i&%dig%l#wRFalr~0iRU8J$*q}naP zpFpGOkLX@Yk#QtsB%B9_%k)K`;kK|qXcVP)^lJ1GW1+pIKyl8-caCb~g8{mdOb=Ox z=Q?h)`AHaIxCC!!KLz$zY3IGsNxVW^e41V3y)(X+&bcoUzb`FpnSE+(l040-Hy7R8 z`sJ&Nrjp^Q$M+Te8~-=hrm1Y+yt=6M{6YE1ev{N^_7}X2Y#|xyD&&Ik&SOIXlotP2 z3H@OC93Y;eTr|lf5dml)S0D#)7<| zlSf232s3G%s=a{?K3)(MnLLd#|pR{qDAg`d_B#iN#pT3 zcu0=x%URb*K162Jc#Wii_2)x=XByZ{1XE3rF@pJ~W!;1s7Dan%s;#wyQZU{r5`ZQ4orhIhv zLsiB;&eiD%^OzDu_6IpP_BS{iz3uN7@i+;W0_j2d&B>Vr8Pg*Vbu$uac!5|vHT4@huSs|h-ucLzA#p9Hqo z9q+o^o71c822FL^`f_n6Jk=M<*G=yoPj!NB$vo5k@aJb%L3qdU^We0M_NnLaDKgxO zoGswb?Z6-Cw(;f4EqR1`aVyO)DckZo@YVL|b51=MX4vUwlCZSf~= zH@`D7^#cSA_kHqn;0yH;C2`;*+6lH2m|VmTH1ch5)SVJjj}f})oZbN6UlUD?SiV&V z>Qp2Qn6jW6MUuGd4~<#!C5nAk9;{GD$Qrlrdm4ZB++(8ii+7}D4$U4oAgj?RUOw@m zn3In}R^pknPltXnYV4SiUxhN4)%cljQA(gy87WttY#aZ<8z@X~wKmUuu%WDfRaVx3 z0V>6vV``1p9z6Hj#n;j7^`ysbGu;Eh$6QE#Mba0-?*U|s7TUthX}a>x>(_CD97*6O zQ~LCwqdSGe`yk8#uO^k=^gi2l9+&g1N!!F^uJkEb2Jbr4N(0YYWcTy#lF~Y&GKskU z4=b36o=#8W&UQu|WKOwrFaO)wxSeKT|NhedSF>_S{VB}Yq4e$uRS z*IDGxy7Nf?%XvJV>a?$${=b>e1GSrgMp^FzwL!GA!!up4?OMzk&7)FuXS9&037v){ zcnr>DCO47HdybCFH};?R-m*2`QSa92Z%n-FCS?(>6efF>&Tm$ZyCZ!;PQso8-+2~% z{$u+CvOq=ewGcFW32KS@2_Lahkf#J&n(scL1sq`NgFg&rw5u45bPD%m*SL19!S7lh9EtpL5>O~(h-{ZzF|z-j=+3|fyAhw0MOG%%MP%mk9S5ID+T z3f$|?j*ct0HtN@-U*lE={t}<=V~C62NxImr!X7+*R?j`~jd31(Ai*5(NoRBx%?+PO zvn3<%r)~g-dz?li#_t9f7PJKgQV;GHX3~`53T`d<0pyIiXz7eL^8}9uf|G0R)^mdN z&~a<|eD1vI+ZQ&C%}-4(SlVyLnCxUnA9I4xd($3Y!LbaO{ny^#GJ0f1_1x6r!sHpu ztg%BUi{yRE2@B`yZ*-;&=~s6H-+FJBhob1yW}PR?nH;3MWpjn>ZPTzBO;xqFm}py_ ztq~x1ZgIeks1?C*td33XWrZo3by2A)k^N;kg_-3H(<^SJbIc9hFYUDg-a=<%cjxO* zcT|bPoTu&2_D^{nzdtxPot#554~^tRyE%t{zAK77$)C&I&;D!|l-F6k_1S1NPDW!b zB#z`dvn2HfBRuI07ZQGyfQqz5*{Fdk?j`sFXWKLK!rkD9_{uaZeAQa%jS;El^pb$= zvSH|i-v8zyGdQMXqVy+5RV2!f3m>^4aFAaS%7P)F3N}5g9R2fV6RBZKG)_ihErYzy zY1a*c{><>$V0YmcqPje{$Lba*XC|s3*l6sqKL-s|^Bbg0f$KEEi~GXwurvfDM^05Any7Zwl1=Oi48DZ|2PD=N%`_IN9LE2?6v!h@$2W8 z?^-<>XhSbI*V>)h5-w_(VJ4iVcW+^gnfut>(l9f?ErQq(qX2mlGo;y^&Y0#lQopZ& z#`k;j`~*J0Mxsq5pbtwYsV`s-Ai=}k=mI}wi#z*bsB-z|Gy5M(h8Yd|u(0TiIQuiv z1sfkqMEm@WMt}66wou=K(Z?Tl_MAdUs4_2nRb72m)nrq8Xb>rsU{d`mt#RbI!Ks2h>cfX=t-0#$}C}S&=_KvoNs{*T+Oy zPlaX3-VqlM*a|lB~%_D8rmZjMXttD5C~|P<8wcJf}5TJCqgzc$%(fC zJ%swsWczpB_3g>xrO-yGg7cM?8r*O2+xYj)0L__oaWFeDDsJr=O+Y46eb-nT(@8e2 z>o6p$qLfB9+@OjT7vPHbs~Z#n88|96VT0q>4GC$EpJ2NB^tukGY|Nyx!AFB`A{*|K z4U35^pN1GjLMTooYUsR=P|LKpE^S)BWYu%$CXBH;jukcTy);Up4Bf({6)%lVJ>=J! z*9Q%GPw(3C@(bJcES~+C{V(IUeH4!THn%Ma8i=p)*kW&Utq}JVBk+3Q%o1MG*}3Q* z0ecL7PI%647kK`ui{~>5&)FRU&);&sA7VRX{=;AR=i&(hRvC#8!23nt^UpcBMd9kBr>3P1)0ncdee|G zF%pv4WSJv({QJ%Yi^nG9=O?I@;p4|lI@a3s?Cl{aA9q|mBTv_fhM4{F=R1Zqq5HmA zJaxwG{nK+25^}jQ6(!@woVfipX%za;>8t1|qf&>9lg3Y{>uMSiy5%?$bLZP z!^F42KHs40zQm;o`yq2x6J2vLF0rj1?epm2nzKmYd-{JgurN8acC_t>8(zrGNjPjd0= zD*1et)C73wfE(X&|Dp8wkUqb}<{anXampU{JU|7EViHMs0_AEa~ed z^5)UkOKwxI%9>8jr)OiYQbSUiuBw{wFp1C7fjYoSgt#56wi3yL?leyh5bURVpBhgsmXAH`Vh4I|^ zp3}v7T1r+>T7J&cOziKZCHDlSB`eVKJAsx}l$J%_>v#1?yL2Vo@j}2sW%CM4_trLD zQ@5(Ie5gGxF1vqD)&acR)j8f%3-!^pt>a@->f*Ap;;I9~k$x_i06=lij%s2Pzej0K zaGyN*-*wG2%L3XbAM}n#64t$94-N!JR4{2*o7NT%T+E`PHGrw zi%ZIniOJTknuKTdrO*Xj5>lozbn2`PwIxP#Ms!YYj3qR9Qp-VRWS>i2sNAIEzq5L< zBS~$c7|KYx-xs12Ab2~mw?Y?IPfAt>XVsPVi?>8&n9QkMSq=WM*CH2DE3H(?t{yx( zJ1Hn6CL$p*BEU~=&!2>D>qDIj(Ia5%90T4!yo#R;&`sG9iWUmGgkm$W0y~!;p&*bv zTBgGtTmJR*$}UAmQalb##aGNdR31suZoCIgCgn$9;C5Gbq$opg_F8zPD06&3N{@gw z#5aNb?=av^p~t>Dl^nsQh(+VxRHv#V=Z@H6lTHmS@+dd@7k&lFKI*38=n|BK{f@+> z>WJm?yHp!x7o+%|%8hU_K0xY?1pAXEm>wpg-DAlV0&0%YxRNuD86^p% z$aDGPZ^h~WS*0#y$<)oSOrNoD0olv?`#bL?J#);M%yhYC)8*8p*E^;OyGqG2Y+_iX z)RaEBc4*b|I@sZXi7n_o&mDGTw&&#M=d?eap)oQ5){0+7JLzV*V7fQht9TdeHIdZW zB7R>)R|)#c2izj!$&x{T1R$K>h)~x^U0fQV=FL6vuFWd>n|r=#UjO9zH7ilji}y`V z*0-RHxovH8<*LnBtmZd5)&vjRwtqx%e*JAtBU;f}gC?VV>EZkC+qw0HOUSeqy*^y4 z5S0d9PZ&FI-q`vKjRBVY8+zUtxpGtcl^4fsDy;-rFS+ryq!4VWT39ia3nwpV1xgYE zjzF-yYeTqz@lHHF9><+lIdo!=q%e)?rwavsY@WKrnsssd*HIzso%roz^d9)_8(w^1 z+Y=P0J>A~cW+&`!C z>3Jr-S{tB-fJj`lhq&=YHgtz%gAkV{aRzt;`LdWjw+NYnARTOx+=Zl1o5M<$<$4e#i3Z+X<%nwr%RjfSiL-k@usKNGy+=)smB* zp3^eS6tmW#l5lQJ_2fVvQ{A4BGl4>=j~%gb<8reW##9!u!~`L{gZk)Uz&qk;!kWmJ z+hpTwRV=WNzVVMlB!reLYSyKh(?S1G*STHA6OF|rQhJ>ofr1Lz`G;Um-$mj z3co?x`6I*QpiC;j@ZnwO09(IjmIb#=KJY;pfJr!exYMAnEf;tNwNbtAsZSi^H$`jO za>;n?NGSe%L9PjaA%vWOj2|GM=wz7}G(OL=nU%FN)movbjSMX&k@P#p%thYZaw6ah z+$yYW-wsvdiXbwu816|pW1oV~BWJ9W#^cM_NTVQJjD1T@bv|RCy0zYIGnPZ`Lg~|? zazzq;wP8tKLPFlah=iB3w0`Ob*_nJa)=U4nRbF+&I?=;0ug&70`q@hk`GSVTg1p39 ztv@5ZfbwQ)Lh}nqG`7R$I$u-;QaL1fauN88HH2s6ZgJ@l8o6`rt`A8odcKGt^9M{U zD%PZu6E=DB@OBswoVH0NmRWu*YwtdJwe5C^Z=@pskkxVoj+ew2j#qA)Ew7bX0(EPF zlJI@&9oHHPAU0x+4nGG8e$BXCLA+T-QyXDy*Oka6s7(27^kOv!QF2A*e z7M~RBfw2P{)}e!aN^BL7v{*+{Ck_*P=rit3u!lx>!{0to!Wpgg%5Ax)b9dIMZ#r2% zW!TtTOmqq9vTe7kta6Y7^8+4IklzZO@<)2Eox9>pcL9E!RPC7NrERG^a0Gvz^e=eG zxRJ*ecjDk`03?A|#Bhx``>#9=7H%oM5tnX$7k~W9i=@Bj(gR~u{#r!vbIs|k6IV@W z##c~Q>$FKVt@6N)FQ=zoMIVz(aQ^1%`gi_{BAtyrSO(a+b63>VC+)>{oV71G&NljJ zhQ=hygc@Qxzh=NrICJbmSATkd-I=|t7K3mjml{(NigdeidVg@1a@DMCQYFM zGhx#3vH8z5DYL>w2g0+JEf+Fx-$o?$pqIYNENErcnQBi#2=Bs~8na`Zmn4$4koe*{ zkVWD<(o2K4neEO-KgwH`rzp~V1C7bUQ<%t1(+@)73jC?{c;a)1pWZm{DBkhN)}vn? zECz{;2v&&2n(12}STMBtuGXPN^@^zbOBe2&JpL{ez1BZU?jMATp)~UNgn8!U!UZjQ6Miei5*C_YF+8Va_Pz;4a*@ASY4F#dS#;;n(Iz_Y?beK8p@|ico+et%PQWkiShjOS_1y1opR)4A!W0lxQ;@G% z>{mN}$?)7#NCKpk21GAT9X@^UtpAJx7<+l>r!x7s{e~WE$kc_lq5u6(I?M8a9)0LG;2>g%cI zgnOO8l;=#{=2g%D*Lc64u5k{LoA}z-TUr~v$`?3gZ`U``3HT#gUjlynO=}iB7vIyp z&CVeS&@7E})SBOwFvbrlmv!~5Chlm7Y ziSw8BXljXi?T->{t{dX@a3@1V*A;u|6r-R`z?2p}G0;VePSFb*j1jMg&_(g+udeu! zPN`#ii@wMIHH`0;t!QqB{r_+Qvc|Jzf&0vPeiLDC4gw5`B}d)XO=hENIJ?X%h}?i8bf5 zq$^Z3DfB&jd$OMtq1*N>-+uy|lj2wHT|s#ZKGHGJtMxvupq(H;>WcMSNPwy*L`N`H z`1qD5z9uD%X9krRryX>RpglJw~#s9mh2Owx#$K8j3ndLRF#8MPK4Cp+d#) z(16;qf(BE>sRQ-(Q!|!kn+A+r^gw%^E72--CR(wQPqaRZezW;2@Za(KAO|8J^Nml% z>5x}0f-V%OF>~S%MH5p)OowEGXAgTPOovbs+=G-5fDkRU_$rlrr>PmhJ1?RR@7Y!0fy?<0AgK4!tdl-S{8Q~7)r zUd2rV`$h~?!D$Q;x3NRuOo&PK8K2*id|g5 zR~R@-Caub7x7ztW5(v#xQq#&=PGS|qKpbLAkuRROF7&g(4Qp=-jW>TEe#?kbpfF+- zkAD^wSuRF4BYt_>U*@(=Ptq&p3d8U*MzK2hWKXs4-Qb_Sb8p_E*|Vnz;AhJELyE(B zM~=iwmD$CkGpE$mA2=03XajZ^#XkeT)+tlBKpUqk7fI$;P}7SN2mS(ynFUEePk9iA z$ej!RtUE+20EzX+vtnm_V*aNtHGTlaKU3??+}Gys`mcDhFi;_rYIBpcGJj4RwydWT z@^0YIm{pUqr=*Uq;nB}DSpy`~vuCuLtP7$lvvb;FRC!tu86q=yu!O{=TYxTI%faEu z5X}p^6ml_MxiC*VsV5_^Ga|~j-nHQr`IkpTNy58h``x_ScwFCsQAn>M8{V5?$m65% zD8bZ56#K$i8bRd4yFy$bLa!v~$7xW1U>pi9!NS=mryL713uIGE-QT?nN!E~NHY+iUJ!Tw7)(|lFnMkP zGph(`B}xIRhsxJH?o+M^?sCtVHnmdtLU^}c;U9tsfrxlQDY=95pOW5l8k`r9lN2@n zEgr~8ih+`Lu2`m}$VuJ-c>yFzPpdMWs7Wr6B!Md}v8tUE9yKQ#qI^1vq}QFe-d6d=>l$)X92fPVz1)-f{9%rT6gjzrdZy*-cn z7o6R}UTdWHGG8{@mv&v@%BQ-?18RZ$m$T3Rw-m)ZwXA9Q&V37R)`7hLap)%Z>htIO zkPCO!*N%03(PI}f&RqoE)p_InAG^sX@l4w0RT7xKViF1QD=zSvb!or7LV%% z(fGGTQvKPpAu@(*X$iHlsYP!*RBcly%f+5ne%mC+TVa4&KkUff z(F_q1rcmhSqgNPhw9=1b3vx*g2N$51Jkf4(aj$r$iK1T;0+LfzWzXZ}%jx5!3X9ZN z%(A|EKVlm*H=f7e%M{Qn#e5hK5M^^Q->Y%WoA=K1;A}Z0 z%!gm*WvG*{!MRw^h&pjzrihL&U0POxa`Ic*LRNahM#umbXo}I8SeZ~9FNccFga{?TFrLPZcm@bljmGWUkV%VMU!BcdFt6L^z| z9f+zN-{2-D;v*4$Sn40?J|&_XZ$RC`uW%m>?jyaNMNA|#Mfh};9c=ozBX zY8%P~dRe>`8vAQIfwT&?6geJub{`EJ1e)7l%(M7e#4L}x1cq3MCbwA=8<06UZZ(QP zb$5ntYyVIs(Q$=vqo`$pf7=m{9#eq86`^EDdkV44Xw3uj*|Ydr7Qct;*M1}tE8UVs z{OoyRGx*tLlCj&S^md*=#CnV1yKV`WQ7kq)4K*hq)6L5uyi7C->2nh|nU+6*I5?Ft zIVZ$l{+@jc{N-+IaOk0M<$!whjotmDE|tl?MaosI!R8`>U{XpN6>yK=top&jhNgwh zH}!&P2O6-A6)`+inGZq_`J56Nm@7V@2B0oA3TDFTL!uvuhTdC`9}1gmK)X>@<+%S|!ZUfA zK^KK+6Ucyp{4TX5VBf?z)LIzoX4t76TZnX`O*w@N0t(|ag^b8NB%o+W|2qbpdvsok zMHwk$7cU+#L^i_y1U?d3TpocMv%lKE`H6qtsLWW&45GY&>Ug$P_#7pz!-LNO$+Ey; zY>|e1MmqhUKpk89;l{+8gD~MWI;pDQt)Yufo{o-G&YmA+6|-0IM?+U^ojhybir-P| z>t|6GQe6J@>8CZ2g8=@Y^^vg#E*t7CeGtK;p`ReKD3J!f3xcVqK@y@3>4xmAp%{NP zb6}#sUrr3ZRG%;=p~mpy;wiFa`d1647q^w>m9RE^B5VAB(%JXmUzsV;a2GIie^wG2 zwZ`9g%@Sz@F9&i2Cw#0T%!R{zQj3d%Vv;E(p>T-I`G_)rcW{PahxCI*xZlvFCFzZ` zrj}Lh9CEs`Ke}3u?`y4X**;=iZd;l)7QMP|-p07<+``FsezN51u-~u^Cm%-Z$8I?? z?XE!u`f#3aU--yGKXBiZML(oZn~XaWl271A?z(r%%tP{x zBgWS+`r?G>>vyBt|AXhEJ5b1$Y0qA`sz}ybs#fxUoA4@zvru^w`M_Lj5Cn~nCSA?O z_~PeAD2rR?Uyeo9w<#Jp3w{be?wEwKuR0z<vERXgciC0c~{KZXK+M=E^p13!L@N>MoT0@nQ61~mQ+ zWb|pS%tz;A2Ac;CLKG-pp2WaZ%3Phu6-tK`^wZ@ttcFG9Gb)Ce$A57&lo2aZ)tB~U zmsD1!mzsb4vFz{~)DRz+KB0U9%Eb@2p8sxT3N!eh2`QPGp&B1_{XDzTCq=CBk@@&& zBv#JKMa!6!Nc;=`6$v-2FY;wiSbm28v!L<#BFck3vmK||b2o~_t9;~uJYyl%38Fw) zCrAP?MHlVyy=$5-Jhu}VD_|A~N9OswxVSv>@9y?~<>SVc_iM-RIUjvnoRm;nl9*J? zh4AmmyQAOOaTVob$KdPE2T4UG35lg8@WCC8?9dy#xxd0ntYl)J03hom8<7(7PbUF$ zpBxE3dTu4AKUoZUISF#Y%O|(27-~yNv%zk5*Y~3hm`) zYt}?gGaxH}*|LJH{{6G^moClE>TiopTo_-ikFiE*V(JH`!AlSYM-}@FJ|eb3egNb) znhaCKM;!?7c4&lG>SxiTi zW800(H_Y_2d_F~)BSyB-Q2m(|HosT-@zlt=N>OyZ-H;%Z=>ti9Ut8I z`G$hjRfvexk_Z9stabA}Xm`pHo)cZam$x%=l|INnFu+t%7^&2&wOX}a8Ch6i3JCNM z(yQc5yT}k*%fD2qgEiKoBC94?t>S;BHr9YSCO)mg)<4K*3m#yz4G6Z`g8JJk(&CxX z$|!1SMcGPNl}wszd1no1E}LEY;rtt<0Htn`Ea6W zjpK=fO$RISL@U}u*U>T6@f7_z+;N-;E)I}nat;`iKa-7I;I|wV`3a+$$8Cds*SEo2=N{c?XnMjv61sjlcXUTH&oX z`D7pF+Uqx-L`Km+d=x%@4xc>oUh0Y|9pbY3ioW;H26AtrRg=wl5 zvnWcXv=bWuYUIH-Ahf3Eja(cePm)*ns@pj*oZPqBtC1T1b6(E&CPNsWx|{)Wesf@DAO(8Kah z8ml_36mLV{^z6by9R={C8S>}!47$5&c^xO*K*FU1ly9Co_uQGeQzp+lcXr<7vq{Bs z78S%L70p{z7=vDZV$1$Jw?6jN?gx(UzW?vTmMm}h=+jXvw~hqc&vs*t=yv#wIIpM( z<*+kGMfER#xA7h>SPHj?&iee_&1=_e{&4ZwB?Liz@Pnsn3rgzGtt>O>wOY9{e1B-l zm818(|M&feY{dd#`hAO^zqInflJJzcI68wCgpS?9{SA6SNtK9KoE6kmB&hvpSO`mO zT$;H|OJ}zL$lX>~dNH?tIR4Q-HzOnM@hQldKEAq9uB>6EKphKB8BUCSY9u&=3DdUF2t$*5lKKb(}`&SERt#Lkr1ho4R6rnm#I2;Gf8wB=wLl6iXG6k|Fgh zyp`if#vs9APl9O>^u;R)4!Qe1Z4PE=NTYz-6-9jYk)bx}mnd(B!e&*%WEH(YSF%vIhT(WSZm^8;y-lMP_?Ab;K4<~CR4}76a+T? zCOnUPCfFCnaIVT|6v3o4cxeRYo5iWz$h$unVn3MMQ^G$Juuot}O7!bquNN?TZPPEK(6=qZ7bD;_FNH8_@} zL?|{w402CCVr5p8GU=R5SUmHvnAJdK^6u1ZP8xxa8N-_K)}}BMz8s;bw4a=fii(W* zUZO8VLDb z$=&zS!4InZ{pw|F*|P-&Yf zFKc9CNJReA&yLDSO3Injzpekksp_!N+2ZW#SMeP|1L;12f1)pdf3a}8f?nalWZ zU}BbxnY-a|=!X+(&s)`Rjap&Nus7iE$iZEXKG0V#n^jeuPbPVx2EAO5Qq+LiP>@|R zbw-)~YULQ1Uw(BQ-a9xu4<9l3LB^iwbEv_-3N_dXWX*&JJo))f03b7MfTLzFZ>z!? zCFfQvx@rm&%b*3Iy88X^07&9kgufUu!D0Wt=cE0*p-*7PvPwlj2m{fSn3`uyE%*)m z(#X*14be84&UE{$cWirEXbvE{7-+{P0PS=@Jt`pyh% z3P9&)fbB#2HpscAaWCG{q_GFSIL-!Y2u?RCQ`CV)K$H@F$(v!KjM~zPO*utt+O|v_ zwm8)k9g!hR9(4I7-4I>FFzrz7CpTVgkSoK?)84&f#wOC+XUO@z!48Md?dN$@A*y0Y4#vwUq~gM7XI$ZCM7&Z;3@9k& zNU3l-JABd|LyLy%@U??C1@L@6xV&KK=Coxa@O3oAU9*H8F&{w}@L- z^8XO`9`J2d*8{NMl04+C;VD^`CGRQAvL#!V_uezH<0Q5dXE=L1dy-8Efk1#j3WU99 zS*2No1Zc|$P$*@&KpNWqNlR%GJ%8ui_ns^{hJXLx@B95eLLBSrzIV?(_uO;OzWQr1 zW&ekwW$4ELKb0#ZumXRzGk1YS&MnOlI8izmmL7y~C_o0)PZ6U^D{DU<8uGyHu6qJP zV~^acRNZ$wbtNQ*3Rn^oa2tv*9rSJ2{t~RCzw1V6rrq@weOOh*QK7Ed4hs1x#Fxgw znKHTF1%`KAIVsnSjoWwdBjNh4k8a-k$ZxRn`s*jks!A$OQE#2P@m46ie(I(pw;z0( z(hnHo2O&86-28-u**LEX@$5>34@8O}_7q?lNmzkB1RLmP+v{i4!Rx5rPXvZ#Wd===TCqx}4b zHy%1bc+1#-*g)n9up`7sBl}o#A+_IUqEg8Q-hRv3H~3L1_Vk-ndiIS|&;4%tfup^r z-tOH^y?-n>#`T&eO*h-lT5MUvEp@JHxPO4CvG3}IQSJ}kY0@P zMA&>VI}+>*GlZ>XMJWaDZ_1NX1PrJhzAygDkujL3`JeGW-gE_~YL&rU|1wCwRJPhx z<%Ai3y3l^pTQJ=v1N-eap64&Uv25TYm~<__pm76rb7MbD9p+8sMVW)>(>AnUids9s zFqw2Htf@x$M?QhDjDmnDb8Oym;%~b)fA#o`E(=}Fyii=)TLVI2XtXBv{=wx#gGGgJ zEZZpSz!DGlnCTBK9~8wwVGl4P%x6#s0<%Sx93BATEQE6T;Mr0sg9Qv~ z$g+y!E>VFJiB%3xj67z9|MBJd1G|Phrky=dG7@zlBv>A{qi@M&M|yCqB|kp)qB(YU zWkLCZ=C-*^+e0tUJ~Yx)V{^{DX?}g~j2YIv7+GL&sGmx0?{;J#J>0}HAk6t^CA*w0 z8By8s`6YRk)BBO$sP{?ztZ;zVfJ!Fr(OAkL3g67dXY$+cf(3huyB}tY5u!6PV*P(r>t%FJkXN)40Y}d z|26Ck|2J??yn=L(ykm@c6674cAd$*t?%StP!kI)YMCMDRXXeQjb2t3J2JCTW{L5HU z64V*MvH`nj)%JO79vfb{*BEy4OZtu_^%eYkAli_iLQ?kWWVoN2b(GYun9~FD@qGde zU$M;qLlK^;B6n9Kb0;^EO-Mr#cnvY^2uiG8Wg=V@z5wMgl!*7Bz_HDxxgY12?(^l2 zy+oB%tSd`RicxVve(DUH%Fj;`o;>^F-sO9#PxksTtlyO268?iQZN0x?>;iol3)mnVf^;;AKdZdKW7`mY!#~xmnJnBYcS+K%&v;V0Dj9-oYXT^ zSr~|%|yUoRnTGggbhRHy~$XVLaBK+L^s*OwS?;W%$Ex3&dvd(zG8nrd#pU!}g6nPCo&<{f)y zEjY8E8Vi%DSTTfxr+({DcDF92-p2D3_W|Ye`q(qwWMI+%!GB95e*0VM?$@h|4Vq!f zmXNY=U3GDK6jct^?B5?GEdi>5^`mbQRnk)Iv zqpupyo9>^H;jp0l&CVuCbTWf3{4Q?WT7AJ5LV%fFW#MA zV4HrlvY2|{Oj6QYQK40;xVs<(SE1T*Ce83R`HUgC0qSjMlJRq;T9*cE^WLlvuj*4< zJCH7rK4$@a#=!?j?ZYDlVrY;vg8Zq1O-e3G6eN0f)3``BY7kI*W)GN?BF^d?q zAXo}}6721s12S5QsX4Us)S78CI*qxxMvjZ#Q@!Q!!itg9yjUvdECLWl-<=Tn)2^3~ zJU(|uW7C56wD|aRTf?uGuV|kc5>Gv`d>;XY|CQ?ZFh9%KQl^l-NaDbim}*51>2Yfs zQG{@L(6R;so1kzCXE_lS-4aIOOM4<>;xf|GHm*)dot9VF-Zr(i_qI8MFYJnpGMLlS zHm&h$W)5vU;lETCpej0ZwoqjV$*-$#NjJ_J+`Zwqij`c@#AZN0vHNi#$AMHQ;tUgs z1d|_uYy8q6VhJPG13)C8mE$T)>K?qr|NCgQ-Pw>?kZ5sswU}niSbS*bx{>uateZ2( zSkvOnvshE=Z>_OpefVK|c15Q1k)nda&W;tcR?hC5GGoQuk?w9sUg0Asd=Z&qC9EIF zv!1mMfq_r1B6}8<MML);%yOU+DOcg|AxePFB%A^xzBJ|~up!5f z>s+u9p4r)ulMAyIYCbcUUTSM|u1_moe?hkFkk-+$LPu#8I&C18#0G{A5vAI9xocb8&u8!b8;u6G#IYEseo`RVUBQa-HG@XW6}S+91RRHw--n8qbjGAni0kSMGYU| z>}GxaRU|b7?y^QfL`=zm%R0%2@hhoadBJ`v3iB`}`Ar6KJR%<0Smqv5d!2~8AsiB{ z8}R~-DlG}|O$ z8iw(Uf=Gmf(#|*~Ba2O$$N4{5noH?&O9PdmzRMNdvkKNPJUgbml>TEFM~AKEkEhfH z`U~;^tqzO`u1})uX{kjD8Rr)gQI_1bseB-ou7zY2A+lEE7vy?A9l}Zt6=4DH^(J){ zKxdhl3jpgbSm&TlhdV3>&lS9Pgd9tl1&cP(;GO?|UE8#waHic&!a{=4v9t2w!j>ek0jqn3i3g0kp* zxq|FlsUiof@(XtrhWjZ6+NDjbh2vqKAa%L%QOer6FQBc@vyE1eHn5tz`rI(&w}mQ{cI_hNN@xvm%FVD#{C?|&AYyhgZKq^KNA~=Fmn)*H{QmK z$n?+_=h0TmJW0lYeL3EbZN@elJQiYG4$y=@=?7kAkA4IP99UY$+hi=6+c*xAXfqSc zegH96!>}o6AL(<3WLKZm^CJ|`0j2Bj}N?-oL1MDUy+^A z)>YfPucIg{n<`33D(%;m?e4Fgeq)ohX<%LZ95#1DMY4ZNWws?Vuuz{;-;$luYFk0Y z6wM1yn_^E+n>jZ=ts~PpyK0*(MTpY@7;~5w_AMVJk+kI&>G(uH2l1L)6Qz&`;h@c9 zTE;q7-#j$%;o*a&R&#NHc`U>+w9uKERy0tyl`ao!X`VH6`_3~11FmajG(|0?Qo~fO z-OjC>oL$|>D;UHJ+W>gV15Z&6>;bf$PjZTjpqTY2T!9<|pRdOdKBr0G^n{PH4aZj< zPXbI8RJ6Bu)l56yGPLW4`toe*F>XsnTIpP+pIR5yeB|!=jlUVFo_?S{uW=}lA-7a!Kaq8ViY zVRl1M<7dHWwh`C5MP~LB>W=D5oN-qCNbW~e61Un&( z!TQ0tk;Hf8wSLmzc-;K_A}(rm2AHq@>@wC;F=Y5_w@kK}gvRLyVJ(TZOt4l7W&q;g z6N@hpCz@O9rXP&yxbOCNr_Z{1PfJ4%UrVPu7aVXhzTw&pY(hpyr6u2w{!x>%<=n3A z14Hf28!|RHd-uu1o|$5+&9}`La6NHMJU2x`0h^LUC~CP>{DFRO#^B9+TI;i^=P4NO zreYk9fN>{(gfWs)nJqA0N7IJP4bC1I4_15gxi`A)HC8+D>w-1{BEormB2%YFqB|Z~(zW2&wz>85n=5Bj8#=1@?Fn&&)YXR+g&ckERvRM^*U|q5 z`bx>2xjZ|4@st#;T3g^)wQ5;#W@hk){QwWr*+e)ztZE=Q3f?6$d-o>~Y-rT<$UPzi zRSDNapwz`6l*6pr4S~(E8X(BZ2->iZU?h1le?jxA;S0MXDr}j!U7ie(AO2hMeq#gdC6pU|o~wQmEBxCRKBR+krj{k`Zu4 z5DrjQgoYdp)E9l8)=**w5tLpum1gPCb%lS-Olf^>#?FElrUvOki)VF4-xa6|^K%x< ztykXFW%T!FmCu{Cfo3ub%{hkXxMI4LW1Un)#2+j6>%v%IG%S3y(xauox}Bk}>Bk}> zG<1m!%Gy+R%PA(SskAgTC>Ut~VJ_oz?At=!TmmyLBpQSyl!>4tx_+shFiBFp2dF|7 zL!l3b)7`C&y*D<{92h>_TbO52=iD6^bszb>px17d1QaR__V3% z@Yn0~zmclDrLwq#|5rL>bdgC3Z57TA{-+qfaMsXJ2nPf~w-5Mll?9g>KygYQ!!pQ22`5E)~gxCUnWD10WTj$~n% z;1q3Tuo~lsryGa~gKN%gBjaO!fv24ueGqDfIg(tsc!kY)8}r_6x4zG8<967( zdkVI3JKsD9&zb;smf0rQ$AL|m#_i))m=*Qm?T0_4Z%eW-TV>NzN0<$}PMq8++g#K$ zXdi*O;=arRS>ykn$ zuJn!^oIUgQY}?EYRm#%oL&bS`>el}`(l4BIGK`ZBGtx(L{2001^(#PqaiQAkG=!KBGL4p5XE@tb>S_G&T-O|?ALfc2NIccWbUCXuAN&k_0SajY4w(L{ zRT~cF=ct0a$0B1IyK>XvNT+;?{c}Mi8=CE~?zrLz^aDcYW2>X%%I)pf2-&-?;;{~E zvzq#n34?qZ9M3?QIMF>kOs*cE!K}y#Fa1xk!k;P$p}wq+(UsfVT==$<5SoRTJK1;v zqL6ONCnBb)%bX6-P(DuoTo2v>XcXrM%Lz%XxP+oH&m`_Kk$Fh)np%HUtG!nXnUP`hr`fG}s+^nuVrPxGu zDgAPgCj;tGmS#SL{htLN1i1va1st-#NeSEiSvwOrgFh_DwQqRp)aruFTxVjoHZHZ$ zs@tMyT{d@m-^_WvfqP?f@>BFlS@F7f`l&oq{NBBB2D349i9v6grD&{duXGlcwO6+) z=9uE*mncDZgFOmaI`nh0WQaQf1w({LZGn6WgH*B#Ll?q5(z#5(XeI?F>`lKX)o#VJ2YcM5B&vI z0(o0&+IqgIYUxP{4GdAHgqN;({1ewZF=++mD8E4fAN??#>5#^SP@$YcBo?qp$bpZc z%B6u_YtEq`o+aNGT~U=YXG4}>#_IkCeb!8+rp&=^s(UQ(vs>p6FJ5tgdUPoK_19ZY zzPRa5oO2?`hWE*`K?3(WEr16QdyeE_BI5}}jYh2Ih3Pu3OXHuF21@>aK$5P-NHn zA%<{GN@?H1jCl{Pz9nSWV1Gq>BbZ|VJ}R480yOcnHQB4*hbZ2;Vc9(8$+cNv!{B4; zZ(Tl@&|VUMnL0@S3UZ2&i+MO=!V&A^F=OP20ImslCnSS}mR|sp+T3Fyx+7jf9Nhr2 znZb~gb#1dCQs}9zk__ki9Ar4*Jm!C(JE`?xLs;SCbL~m7?MtQ~+qeObwy&5vY9X`% ze;4~DxIUwwqd$f=$lVj$jIPh=hp7e726MtT+*8o+J!(7XmSDROt;y1!5=;hCJ{eXW zhh0ymtLDa?Us8+xc4ZtR*lqwkfG zsY~;-t4GUw(I%GY`hrmMaIqTO22{@D19XltA5e0jJ5 z?azM(4;!^=NP}KKmkqprJ=341rV9S1MeFfuv{NhgiC3e|89WN;1bhPQ>;#M2ec~k90Yaaj2_DxS0cI@rx?$=J)d1Q){dixFj%eykV*!6~V zRdIU3)Xh<$!o?N*OIhj4lJxvuN2&22dk)?Bn>YG*7nODLmFa0)b`OYi`2gl(BXQHh z2|xq_72G!{oCM^~jsIJ17kk`V9n+XxL5IwMNdliZ^S3;gb zL=X@x+bMd96@~JKH&P96laKtsSG-L0IX|G^m;EJ`fX?P=4U6yn z+sy9upnV|iLwv_}z+;sUN|AV~Ux!98I*D|FCji;Xme5I&21TMgF-5%I+q2Bg51dVj zerS9i3jX}c{h`N;>83eo4lP;NgM#hU)V_A z^Y`2&YG#KSt`pCa`R=T;dUJq$O-EWw>EkAQb7(-CV$f^S=};srx&EcX%v>fjDK9t4 zsigNVIy_^XPw#ewK>Hs*S{`yw~XzCJggLx7A_x}ge%0mg(&C@KY z|NVRjKVxDAem1LR-h2PcOy&|UM&3MDns9B#Eq_33p@Kk7RtQlJe%X=MFEeD&cr%lsSrE!LXxt2v1Dnmv zRAt%bZM)GrE)$r|&+o6X(-tb5|6P&CKC>1axQWW8{FXG&xN|$m%Hla0{Z=-&chOz^ z%5wf(inN>J9{Yxh56c$ms2zO!sBMWc9{{F%zlRg@ci)yOTBq^INMwjy!?c@R>83C$ zD>}ky4$)4jw&efiZx7lnewGOORZ|#08WmBTrHn~QtI5s(_dj1K$ql3q@Qby<5peVJ zwt||uCd%R{c!-)36BNO34cwkzmt#rbpKd964bQo;-?K8V6}(?nK5c|X1p6>CFQCvx z=-zFta#IGLPza&a;FfMxNgx7}uFtq=vo%i0o>#2o1hS!eF+eXd$P4(QBFV8X?UY>1^XUiH2EgHv` z{>F}-J9@hsW)-G8i_(JW_LdDhdb`U~3vOZTE%)wiu-of*E+{seox?9IawSr}o%YrI zPhB>$mrY>1b+W(l>3}&B?jse2f;%I{V;qeqQY$f1VF5!DOy|DvgQPZ2Y$k?hE910j z-cs5W${@CYqxlOox4;&0PGwVs2Z586qBv)oO>;KF78Dx97IHL|LbKTNrp6W=63p1a zn#c`95>?E{_w=!g(C~6tsXvpoDZPSLJDhA?z^|F8+2~G z)>yl6puJzY@?=Z*4GTJ|Bc@JSx_b3y6Yna?Nh&rSS@3*abJp~nw-%I_X4}YqG|J_& zNS|r$coJNGKqaAt>(6)!HYDypQD}$q5*~&*+^|q-N;(X}HB6|xPNZCp2mYkxA^8+z zC2UdH&@J|@wjIsm``DMdOln6yWcx}i9|^XapQ{^5e}3)bBHX?su+_C+=)Y;Wk#3YW zx$E)FWM<00h02l_sCYd;!Y`BROAK{;3A8Gi9Q&ovn)!!YOtqla;=)bgiMsxj+7ht%rJ0a_+Ov5+8@2`Bb$9V^g2fPL9 zq5lBm<$=efTEK-26}ideJr6KEWFHM+LMXf?&CoI(iL>$i?(Y z&!}8#j6~%Ak*9?CTp={~8sywh#9L~$E^_=$qM%AiKfgU4Q!IX{o?WdPwP`Af|7sHlF`fu}mEBk$@l zMACFC9ck#fE3&%dsROF%(fmiQ$Em#bqet8GAlLIG{~e_{32>g;*6!<@n>SRud2{Vh zUaqfi`?gb&2`}~@IM9o7GhfNJfqtBVc?g`K9|PM0aS2Z$WD-P87JT(Y--oZjX_*L| z%*|Q|VYGQ)M7-xTQTB;`f4xS(aL0_A;)&i)6njsNec^;_lZOJwwRZ6u%`2BsoqyR| zQ>T0kd?kGm^mK@zLK`0*+XQ!lo=#MfNfD3ub!+P5nmK*ZLo)|m+e&T1kWgjf7S(8Hmsb2FU0^ zo%Y0g9`{#oaQRvnzp`O!Uvl33!hO_|We@$9|IePCr+b^~lUg$AQdf3O(}IItb!E-F z=R7-g0?|;M+4XLYeR01fc{)`~-Sw*@6!-e6ZJVu@&J(VJ<^Qv|cjk`eM{ZfM=RRPG zcs2m7JAi&b9-o|g0O9CYW+v8eG|QOQFp;iS%SvgpiRxMYnK?uy zV~AUWnu*1f1Axt6bx~Y$Y7UK8rO1YNg~L_4o6FQuAcs(6{{cD+hWSB3tI6kwRA(V^ z5CW4VMo5634)%t=D%$vle}5SD)A}-=ngBKzMYS*PBRn(u**2I*A=*?Yp9!4ElZqO!f)f0AieO0!ph>rV<+?Lu zqL|?pF(z14Dkrdp*Y{s1Wq4jVG|`em`NZ>;37dW)%juPxf z3^Q&yL0gIUB0VSBO(r*$@W;VGG8v1e`*A%y%p}5oqyGUm2GAdWekEWvCNnwUL4=$d z{!VUcP^;*Fc!S=tYNE+uld?6p=Vy!!57+c8+%bK~^Pu0AInm<4MW)mZPikw>S9GHJ z7wW~YW{<)732QU?96_oh1ScLvD7Fyxf|CO=#^cCUh3eU3-cxe@L@Ye+Gu8VOeaL?T zX+8HT8hFax_HFj8_vdR6#VMRMbs+np#}o2!077C^m>^ogEfdDRwCxlYt~I ziOEB5QA$nN5C$+NnnTgDBN7sYRnAF~PN;aZt*v6}mi9(vNJVCv+;H2pP19F>u$7V* zIOiQ{$Z!^<_Cxub7Z&D(1ca-NO|}7{Is*KsOp#mV~Vh|sRfa-fBO1uwKZS;%85+BhRk$* zYf?HJ#5BCIXD4$-n%lLrPzhF`rDsKG4?nVE?Kl}hK*KFJzY)kMNRy%i z2{noQhS|w2fY=sd`4dnHkg3P_F%StV%gvdb8b)uGFIUg+RLu&-M6Ec&3D-{0G z^ie5L_(y`4y#|GSGN8cEf8G#)T}Cv8up+urC!#%h*cUi#UgHvAih_LZUf0v+^_`^WpZ+}Z7YlM7y5?KTJ z^wc`RJMWU?zvD|U;&T_sA|x6JQ(Y%p`Ux1s6ema-#?*ede1a@t7ytd~SzQuL0_a#H zwCUCzT@APGNK7|U`}mhPmpC8Kc$arT(e;B4aXHG z>XvZrA}TFL8OCo{%v+3fAm-0NY+SfIW-QU2wu8FPxbI0);+U2XZ@Z}sGoNz!j@5_G z-JVtm6{^aGBq(MRWEnQB)6U1wJ$7_3VM6v@ACn$KT ziKGS)&__vJ_Rj@v(?4Ql_6@s8&~|-Yen^74@a*X#H3V(zaDLPg`fiB7OY+EEkwIP5 zUkFWvG398X00{EvyZbt4#>H4N@{`SToepmx*4^9JUuY`Y6&g~znPIz}HnyfcvaU9v zyJKhqIQBm@M?B7;JvNojrAKAH5QzxU8$zCxgaK0`o)G9#!b=o{GJB9Rs}7atc_D=! zedosAL&Nii_V(3eFMo)W>jMwY-Jf%0=M!LO4m$YL&D7E<0|glw)>(u3Y0Sc{FLd?n z+%vPUar;0Q6CWb4tuh!w8m2Pw!SecN`A;2d@2a(L-2)N$U?85DUjy)v^B@O&Taz7X z1YSwtfFgS%!(B@r=!nrN<&5pt=Z->hPkkMv`Rv(O-H}i=qq2E^{oHN$&_B$~g+!l- zs4)4y{Tnd9XW6P%jsi&dNtv=Z9YWg?4#)?g%VedP&y4&DSR=uKhe=h6a{$1Q8+q7I z%UYUrfq|uoaj(4S45_Qk+7p#l7#;g$sj=@#z&>dFK)Q*0@_qm<7}0*_${ zW*k7Ia~yTH^!9;W(K-PJGr*8TU@&;7puow=py1$vJR3YJ1z%>KU{FNY1zon`mWBE0 z7KB6ri8TmGeHB7t57!Y$m=}-q!#L)eDuaebNJ^-4Niv8UrvMsC4xS}&D$o$O8%w_n z`%_CQl3)dCJfE?ifn<-B$Op8V3AR}JU4BJRVNFX*X?hD4(7EE!Tf=h?@2$1wZph7V znB%Z!7xdR|qL1)f4Jpla)pmuB@@wgQX4?%t-78k`^Nv;8936E7Gi$r&;ClGH4e|J| z;x01E@frj$IEZR3Sr|AsJXkQI2USWE9tf#I{r_n?FuMNkxXAnUf$71R&|;M<=H3$v zpB33!ho9xMQ0l|QpHPiPPRRR!=|8X!azQA6O@K;h3p2W+OTh+7`)DDn_fhT#kOfGL zH2?!R$OB##PP}Un*NS5c0k2+8I6NJ!;8#+O$GvWiUQ7~}gy7fVsHkBfi)nE*cb6Fd z>iT2k1tDb#6KnMEfX`*M6bQ>-hE!an6^wz7g?t5w0vFCHOlt*W7+m292@8lCaQMYS z62zQE8imF;_n)(>!hNYinr|#9UpS4jWh-FqL-~vYy@?Qbin$`C)Lr0HhiDner;Y`c z$+?U908y$6RK3HW?LJ10WXVT*iMV+#xmMgZn$ zwR32$EW;M@zNR23ESRa9qh)qQ{}G17qHzGpAEV{bC)~iCfD972bLs%xG4w21TRnQ) zC0iX?UD$(?k%hQ>&|GhpTd)EN5FBO6Pc-G{n-cRkZgiAD=21D7R1i@>$v0lRlw!>FC!q^_^Lcxh<~U?aF0FjY?R{B-nEX$VF3ouO-rsw2}r)XIbRn3jQOk zBlRs=N9K7UZ|v&R;yR99rDCbO;aw=qxS<#KuNa;s#6^e>2p9rH05f$rBu`aXA$iIa z8#|n$in%*b74BP%1BU2>9W?8iHdR4+=B!=Y0f}trFMxF|qaTBHMV~a(Qv@8j2?(~X zXY>V75J|ze(vKNjcLs+jxIzr87`Ev?in51>hHyX;;$7^zCz%H7)Ig8 z`0>W@+~oN%Cger3_rg99BNCFg0vg$)02HC=pKEvLlc5z;#ok*~G6ZO8 zYd2|QhL-{x`S0@LCeJhbzvRY!O6C>=`F^jGxe*?i2(0J-_naLnJ(M~)GBWaoUwD|1 zw8OHVNjm{)97UISxA{T_ASboChJ_p`+~|bjHJ=^VtS%q95rQMO<4!u=t;-4ndD@x9r=`h2D1>6hLN5W z02AR&A^<;6MV{aQVUeSfRg_sPhDwTlCIfLeMr&$sqwaJVVya80QGflV3`9QsNk2_8 zI7P_bf|#e*Fe8D2d=U^)5-H#g!odUHM34!4@<(^#DJGHezC7oRC(y`|UYHQ)cIN z_v8&Y7ub3RL8-=^<^qT}KM!ZTP4H#Qz)_0aNSqb=NsI8FKd3H5qNw3;!~F}zIf8)A zX(19L5+UTV{sex;;c6QuIV1axkU5`0)8RZ!d-mAp`G@34EdmzB+RLj!f&| zLQ5k5>v@*cObfGN?Mq=hwc)Ioin}xH<&kx#!naxRa|uN=;o7a?rGWV z8?S1vUN)mzXDkg==$cwV8yK!A(MHH<%dv;=o*Cp!s_v{B?sO(AW8xqhlU3jRP<33# zmVzpVnVp|wo>^{o&>BNkjae66Q8q}$=~R9vS5JW{hG=sere`OcOmPqCwVH^9{gxO- zkS4PT=HWv*=o29SBJcK)e}U3q@jYZffqMej0Og6+CY=K0UOf?V1tHj^M>#ZaT;jd< zE{eCgq5xdZqzlrnfIA(@<%EhJlbuzB>LyR_6b>uc>Tp=8G}q;MdL{Wh>ewYidv~5Z zxpVJOgIs1d)n?7Sp|Gc?@P?ULwI;KW%V?srNE{{jGbj`6AK|WKL6DRVY?wC@s&U_u z@IPT_=d1vxx_~-`4%d|F*hP*sy#V9 zGu8S1mvcH-ch^pntJ5kQ3iL4p?cx~#^@8evF5(a`hAaqiu;hAH(#o@j;|72W*+A8( z^UNQGywczpF|QO zWmjV17oIk!ZYJ-hVwqdv-9(>7z61t)bWcq>gc*voqDU(oV9c$S^!x=o_==wyv?vxB zlOj;p)I#kB7PE8Ec0CCErD~B2!9GKq$eRF5yd;&Tf(sK&B7*T@!ju8C#2SKgr{A33 zGRvG$m7kRT-Sxm6ysxjn$Y|T5sdSbz>{M%3?uy8ostK^X+Z=H$5rkpHiIb`Y`Seu@ zitG5AXdgq-P+S8!hurwF;W>w=L3(I=p|xj*BY${iv?+MDicN#OA8_*`yP%*W0j$Z=BD#98*jSUOF6jTLX2{`jmZ#xh7!UcoorU#%TdKY7y zu0LMs^HtLxe{5QHMekEjPOI3Nu1daI{P@f74xkdlJ@2h^zx&m%2VXlay_@|?d>K{Euga#5CQ0w6Ty3r| zc-vS7!I2z=4@<7_6IKUr8N!oQxBvjoectB(%%Vk$@=O-ra?3oU7XCBNWU2~jA*qSa}#P#@~r)9jmL4(<`35WowrIqVWBAPoB;&rY3K9g)>O zFaJVGxmEk0s-;{i+?kjCWq#{!cK_77!U34#KgmAMpPJS+cfE-1I}6HW6C$ z>Z3PWHTnM0n8$&P)6ms*AG@!)v_1*UJ-)@QyQ=oK!IM zsM|Drh`gfvzRR;8mk2L|e*6c2h1aG1 z_&I@l_|OQJeSo_Qr@8*j-#k`{{Eri40nBqHDtZoBIn-DPK@D~WwAb=C=j71N*7Wc$ zd1q3Tn$0?pkXQd>M!HNMF|a1Y%+NU$r9KPF9J?`JkajWSp>3hug0q?k?GYe%mg)iRsdchh-3okB-5fu`u?aIA#{N6Y^BwHV6?TIyPiL^%+VbMn;lt67 zhfy?|#L)aA%I^G4QBLcztkuKx^Ze-4+|0GQphg4ru#wkq z-P-MIV-as_*llC0xR=r5E?s|wE?MCDDp9mUp%d|F0UMh+XIF*2uA?M7C%<^b4MXWA z@AQ^rW#*M8>y3NjUgWe@sm5Zvy|}-Va;W2H-085{9h#`bUaExPuZ;O4{}}g;JM$fR zJgkq1LD;KeO3b)M!9O-@$~-pOH>a~n6P=n=oTW$(%bJ=qWoBz@ZZ?5Z8g&iz@80rJHd90WpM8aybWjqNGcRA+a$t55nkd5^MZfheWoid zr~bvM1KJ2pwSao>t?+UFT9P!dL1lNVNZ%qwCN z1_0LC_F*@$(=7JZj#YD%vAOxtVak}i{OC~rJ&t;?Ce6x4wl43GH377Z$q}+JH@pP} z>*|`~(lg>3oAsHwalUFOhotDUfyM{+N%0hE%lcCH=YT)9kwh?uV z8Pvd_zySS{h4G4%kPu=Qq+Ky=8~?s+FVycLk^*1}k3ax`_-n3PP5%82-QDZgO^uG8 z@_NX(E9&N`bhaXGxJqX)(uQ%NO^X&cHm{N|k2eP1J}|#lo0hJvsg27pgX;nAH8sFk zZ_ADVJxMlhToGX~axh-!CQ?ms6Y+0v=xQr2+tiU}inYvOWl@Uyg5I=g&4V2L+OThs zD#^CSMMY@~@^z71NYlbxYis+`7+ZQ49Tg}uC8)LjX=d{4{09;$JCpRajq%CJaX=(4 zCG#}@Dfd^P^k1lb1~&nct%$%9VnTucQG)kRJxW3rvsyG%<;ajEJ*j zJF}DJ6}};%z6-M|TTAO|N?U7UA`)|Riu2Pl^9o<9GKB|LCWZ$N9c+(Iw54Qcr4(?_ z8xuH=4bf-V5);$`igYT%P-%;gj@B0xXk(HSbO~noB06FJl$h|;DbY&)U$z9eA~`r% z3AE~AU~F>R>>F>KRgxF4 zh=^7OM)&DrruD_@J1B}yF(sxr>e-KEeVT}bRc)=SO{R#gn$)fU!|<>{kr~HQY&P2C zU2?XE|4#M{@em?SW6V0%LId;&mKf;{MdDllUM?!27_bh7PSs}qJDV{Y?x4<4`KOlV zgbpa8IGv77Jhn8Lj;yf6WR}^qN%WdH2it@4fUjb;p-D|sC>%smkL#)I0Dfbq!rzxN zv#gm(QpPYhC*fY)%J%XL04_X>30&@yY=V~q;Nur8l!p!m1S?pbmPxut& zY-4arkiwryr4H46XArR;7odt?HP;ZK1LfN zBjhLI86?ys0`?`~lw<(p5UU*oX+l6S-c1RHDlqEBo4-m4TC7M(iQ3{H8s@*Lyh|?Y zDp%3jTcZ3oZuV1e4^q-P3O)lqnQZPW{z@1Wl>NOji>9-b6vt3m$}zYZJ`A8_`0qgk z0GN1~{}0?(7|So=5?}%)8%4YE)C2?+RBX}63SU|I9|psNha!EG3UZy1iiBCAZIVuwaIdeSz>yFY5pOlsu+svP5?3|1uFTEl-@)Bd_?Vx6 zI^4HbZOF=!Xt|C59(bRMRFM~@yU8MDg#C*W2><<--r8(mzrYk2?w;JzsUt%X@%hG@ z0E5+Gh~lDJSL7;!L#Y42fH?s_C9ZC&jn7JpdL%F?JqzXp>!V;^;lGCUaUy*Hao{m3 z`CbGGo*b-Z5a!)0CnYt({(*%fHf@lY^Im}a{JrR~yqGsxQ-5XaZ7UltwC2Jqoxb!3-@|#PC$@V+t%xEq5(B1= zEM7XtNbNU3ks#BETg8zY8EELz={sV$q%gmXdn{#B9QL7@xV*}wfP}mPgAR9$Vc9tK zQCl@our((jIjP9r3_cFqbFg(Rz)W%P=$X^}?|?p#JGkisSQ}1W+D52gixJ=a?*z&q zq~P(Zn=1GuBm{jnp%EkXF(C^)#{su^j$@pL;Hr=(hJ$TaSaWdZBb`fUI3Z2`hkH`X zI!t+)l}wax;=Qpc*)fsGkK#BZ@S&_s!?{3;RYu(em8}E#&y!+}ahV5^C&8z;^&mV^ z*B%6&ts2g6h>2umL}SYY11`=Ck~fG?PAIVmCk2MN;VB+ZYA)(1!ZzS@j`c-H*dl+W z%C9rJ&^KHau*x@v)z)j4TeVS9<+4D(+!7tNxPWb|WAmF;S~GW?O5^`WqxoI+zR)Nt zK}qGZkhGCz&&hc(SnZ1+{AmAkFZi)h@D1z3y41i6{wO;HHkM%UCYDO80CGQB4G7mD zOCc@?Xq3dE=&2wJE(YM@5aJxu%HFNiQl3sz}C6!3ysIA*TjH@6nRe`oOr<-28C z&z9@9&p1HaTbD0yZCksx-g6HzAQQIoKZxVQ&K2m^17mJi*e?**PFq2N?JWMFa)8cy z47!O#<_5#{CQzFxzWHY<4M1?Uu}k^8xZe?58Hnj%$}#hI@y{VT^5vSw_9aW&8#P%GySux0M{sv9t8Z!(%cs7r8zW zjKEgr@cUOr-nYHNmA=Bi^*;Ra3fOY?u%C_1lHCE`0Xe>#*v=WSpTplfzIME}TKp>; z;#cXYqq!bG3GcbK$@LMlX)N9+5C(NS5E6a#abV+o<{#f(xbWRSp3zc|P#;hq@Lc+w zBD=k4PC5mZIl&(DqHE_^{@5|FF#zWh#7`42N&~WL*tOvVN1iQb=~Q!H<;SLKRh;bk z@(XEoO5JrW(+8Zj8`plfZXN%=^c#Q%I#?gS8~6y=swqRF#cGzT_}vH2o;`4Z?Rz2U zg;!XBXQk`Qv5~Pp=z|vGL<49dW{nZtF_uKLX2&b+E6|Z|hes|PIP3Znf1XW*CtrC1 zYAKexzTkFoTY)!%r4uSde#5>)>O)$f#^M!-lQ;R43+>Feq#Zu7yzDb*HM;S5bnw_VLlOflD=cXQVjDv$y^hj2g+Vd<^x`2 zXn&JE?w$|UIWYND3GEOWjI}4rc{{h5%tz=y>W0tGdiEb>fuEB8$GrM`&d0O=WvRdG zL9hPbO7C|)==J_@S@6J+^}FKL|L+ohU01yN|DA!k2jU{{h}F;Cn77m${_*0X-Zb?>h_d5jq8WSOUCE znx8}uXQk(XzMmoV08%T!@g}=dcn&uHMR_Sa2ht_BGv}xih>x)kxb?C<&<>H1(0;~E zchyi=DjnLPb_2GLzn|Mq-k&PI|3~Tl>|A*U^sf`!ms5}9`+?rJb2zThf0X@FoWGr& zOXySRKgt5D7T3q~{pTcjU|yc@KgUY%pX>R4pAn!2e2;~n>-GNcB=~th&;Im3*h^x6 zKd=7(U}unaEnh;PA@D=Q3gF=fW(3>EUV`?1$Q3eS<{4>Mlm383`Xxf2*gp~W={agQ ze18s}y8_Ss9=@L;I|$ER7Qf%-^DOy3ZtNv-{*MrTfI=qhb57~TuYQtC~g#M!}n7Z-({4uZomsvPpN&jPB{oVT+_}qH({!B9NC_7Jj zKdBR8!b%tb=O_~=z90G%dcyvRP~RW=!}n(dK07wX+vy591M|n{;Cp-yzh_dY3j%yz z<6Rc`Dm-_Lt|0UxjCYvU7(|}7hG59H6H!{F2U#HBYTbDH;asWl3OU@ zSN1aDv&~}rUg7=NpU}I||4py{n_wP7|2MhS()?ww5&j_bALRx~yM?!tnif+3QK>)h zGlAYQDFOGZ$9@92!-SPtN&5`x{UZGe{YRzq4%P|lLP-5bS)faCy@mZJ^uO%hf5*J~ zUzYjnad{K5B;A$1WPZ;|Wzh@8n zoJIJ4=J|8%JJh%EJ@WI5vR3!==ivN%7oI=OCX78f$@6DmeBnHJ$76gv4}|g0urlF1 zfcM<<3(ucqKl;VzPr~^{o(I147NIB9gK=Ks`HS@}2tBol?QZ_YzD4Ls=>I10F@djR ze_U^&KeP+$2l%;%^he(((uXLofTYBb{vAR)-1#7^x1GC((4WxX<9sKzDNL9<4)5{U zuke24|JWb*E66MG{TcR75iUE7gXaJ~=eb|^yXnhwyvrgzhV#d~#=Fcw9;kqC=20@A zJTlIk%n!miP}}SuWIli=oF{XeG#}^dA+^RoG9YW`zlFWF_+v`;UFVJWA*h z`zOMBbW$K$!uv)fdZPa!^N080bMQU$tvH^-2k8~)(d+ql06r&q{s;2B5#f|gbwB?O z19%|c@Avs5KF`}d=9dWL!u$w)g1koHcFz;P2e`@iBK*J)N(p`-H3#1d`#_XW>33!4 zVB9WoyxCN(FdkP*)?4WBvEKB%P+L>#KgvAsUN6u79`c-5|H~5ofggDG_mI!%s~+$F zJM$}Py+rrp5Mq{wi9~5{*&@2b}r#h_#A$JJ%93i{tR4N%fh;xgMUT-1kWRX5}rT9=8wIE z-@|$Pu0T)RE3%K_d$6QH`)TPs2YsWCTMO+|gm#Ia!g&swqPzbn=TGnibi-mT5UIbL zzmI(&|BCP*8+p$OS*=7b+}{ZQnJTn%5Y;WfoiwvWG`HOix*9sEzw zehO-mll=<(xt)6z+NTNa%xBVkStHr6LVro_h4m$MMx_3u%==>hV_y9)i+T{~lV1HV zGv`FS!MW}sS6>$4j03*q$v@69iwXaL^A+gilzScD67g&y^Tl>yzHf>956|N(;Aau_ zY9>-#N8q;10O}VcCJ6D9L7l)DXKd%b7BcFI3e1Tvv1=F$U zSOA-ZiW%@MKy3Ocp8v~)^6_iq?~=*~v8Tt24OiZh$$xsI{a3E5-UZ{IuChZ78=+nh zzzDu(0wd&A3qC5a525)3EP`s4p7_PEmMI#Vy}=~DG&^|nq`3Zxpi)QGYQBF0uuo_- zPfQB!p){yrdF`YZtc)Gn&WoS|tjNz)a2}Q-ti+ZD_AJ4DMjV==&jxQtg6K(U5}_nB ziBQ{KnDo(r^txgRoSfwULAY&RaVte5R(RN6bRH+RYFfc6r|o;ok-hJ+@+ zmuQkcFYO)DA8iHLKN0rkB`Od0=t)raV=KlA(v#)8j} zoy2h`dHw{w=VzZsS>U|vMQSbJgX|^QzU#(654`st<91X zJ@

^QQrRVLzO5?}sHa+z;;Oh5f+F-1|XJ_CpexA6X9=|CHOd2J1rhgYZ0A7yN!4 zfAmqZK4{wkxIw27&w7d{>vLIbrzQSIA0@Jl(0^309f57i4*eGZoS>t_dqx?x)SsJ4 zbe$Zbojpa`Q8u_pY>TMFBK^sP{zP|1ekk-m4?3%;D=-%cJVO8TXfsCoBK>jwu|KTu zBr&kFSqs{}TYOxISL}Pq4sW#PxX^=}UaS$9h74*#F}DJ=gPT-2Y;K zu)TP^ANIf4A9Nbw{lJf&=AL)=_s|vDBW$F*zk9yWU$6zC?u2xAiM$7O5l>xfRC+(q z$9A$mg#PD2N0R0{m(U~XNXXYRr1{FW5qvHY@HVa+alQnfOGSKwv?JihtN#f}_6Hf( zv;PT6mLK~7`XfGt_j|wt{SlwS`@P^H_!Rn|mTYn4{fJMY|7pqB0XDsDvV8aZJ@CW% zGVi(jyYU0(;U`2_6LbqV{6yA9ACDkwyWt1?e?s_&(0>%<0>lI2U(f}G{-dG{UQXVR z{6Oe`o~sn_Pu`E`htU80xb6bF4$AUE|I=KKM}Is&g#M>d_J^Ds8)2Jj8!pm|PzEbd{Cb zm{}8$7I3G^Um0wE?ERj~h^x<5W$=5Ft9(@uBN^`J7j}cWDves5T)f8pp=Pg~pR_{laWl{~WD~ zg>4HHLw$g?fDToGGB&CI52CdgWkOOiLWw#sq0OxfATEp`ag$gSs2%|&1uW{9ND1Um z!-S&9{C=;Gk7+cIkvhrLQu5DBYW9HUTCz|O880@X`X>aFk0Jx7HRfZW=nrvPG_DO4We-UduCRg(>YaZ*b$ZdvzN%xXQ+$<)v;SoJe^`4D z_@<8Jaa{X~O)~%aY zH?I%Qv+oCYoT)9mbL?8I$a8NtguecCZuoKh)bQrmI!8A-roMesH~F zL&MWmxkXb-N515y$FHlRJd5Yl`dylH_cNz4N9DPV=2b7H$NT$?PF)N!URU?5PYO@I z9-B~6+Fl+pigk>0vZ0Z{QM{}91mhCmLc_auKUQwgC8Um^{08LPA|BQxS-O@ZJB0xC zgbxjZ-$GUWaOs@B{1+tqt>BG)@Q=Zg-&c;btq*=t^2LzwD+A!i`trXup!|OLRS}*+ z<{|w)0Q$c;Z6%_ zL-h~0Onh#L{^4E3=SU4jjzil7w}a9K^ba2{J~u@F3`!f&KkQSqf9BA)okB3D75ZlY z{Fa5jfloa}`-kKQz9aG%Vgr0iO8Q4A=VOtN2yilf1o%bEc%%9I;a5bs1a$k=v>nkX zSg27yZ-KN~Vj5`7fEz2m1M4MIg){~@=Rm##X))q+AJR0+-36RNTDh2ZgQi*WoqN*y z@eS?byeQr}=~JgH{ACdQ7FuD!C+9`+CgktqJ0w5Fn*je<0<{qE4{(AX0e-Q+Uk1Rh z41g257V^KSYT^tl0iS~f7PzkjICCk1asQ5U>E zKbj*L2ElJy;FRB!@&!J9eE?tY zhu*H*eYv>&iF4bqdCNQ$??>9+=@OUz#n(3JrL zR97b@0On~KkjF}A*9$s3eTOjiNqWETt`~H7nlAL|;B;XWkvH_E52}MOmIl>fP#$4S zS?7rbTIknN^*{@|paDP&EUZit+8vZmb=AS?lorU?A+!KG>pkff-4*aaX#AdZLJPw4 zREPbGbgIkVlTK(sc%JICf00ggTdQ=^m)1HiV1V*Yz!Ccr)Rf?WpTPWUcIc3KDa;o%NdlX zk3RbHkpAzNfrLIlHWK`CpoEI_VVzE885&9?^BHbc80ULw| z-}8KbI+daKq+4VuV1dZYzeuMt)heCzy|rw$rVnDFw9tpOjJ2i@!l5kvYAtK6>4WI! zmOR!n*P1?vT({)0mao?Ik#gTW{q$j-$6DT6(+818mUj)z(@!6kJc2z2e8UCV4C|yW z^jQIJ?1P`@0tGmcJycc+yqn5$if_;UbQXL{h4Kc%HO*`>?TSZ(nKde1FS!(o|7@w6}mzxgn ziRtZS9N?T-DMRR3D5nH;SA|tM#Fa!Ws-&WD{O(Y_KD4bk(#Xe;PL1P@o+AU4a=BMx zMS2nxLCe^|yaqTTbpbmjv5#B!FpYPj*b0w_6jbHK;?6_GpAEut94Uj$pp7Vma z_dU-l$xb-ICaR*R3yatHl<68D6ckTBxXN-Rtr1>>M++$ycs!v?Dw8NJ2t1$aYot%D zWfprHXn~{;f?GXD^*QTwYOf(QM4tm%`H<3(AOkIMLPG$zdJb-+*+j1en-dvdGTC*I zAqDhVk|xXxgOQ+(-W-tj27NAc$med0X@j530h+Sf-vBf<0baw<{^$T+Ya{W1-jJ~Z z5y)I2w3M*@Tv@xPa9AQF;LT_7Gp#HxG9<-jrfF-bOlHrY)XP0}MG zos66o7iusj+a#CPQoJ+NwnO;RbkZZ>pAk#oz5k+c!j}a2c^2vbH04BfdRWI7b^7lv z!(&6hzZc;``Gh7&Ia1lnXN3F|PJDGm_$wkD_yhAW_3NPW2kIlN;|p-YX9W0p5e}uX zf1~{$HWY1e4r1B>+7SCI7UrNfLR12g23WFPPjDH7ke_a@Osw{_x*%P>&Tc|}g*GUu zw2fQsj4FZy@|y8Fqit*|#Ak}m4J_AyW1~Ppjh@_&;sQ~;#+|3?xlqs8;woUIESMnGn)Ho z;V_UvSV*X8db&Uhf<1)L0-a9*j~_+*R)7;)5a8#@jLTmls^c5tAE}H_8CGuLisOC-tVsur5yqOiU=pNrG?NA+eFGAAfsA{ zAH4{-$SBA($bK@A9}Tpv4CF^tw~)jPw4IB*{s{Ov*h^gLsI}(p7EWUw^pQDYVcEDx zptTYUfO|>g=`l{I97wJS?+^i2@3FDGN8~v76((uw)7nOLdck;0@m4}4F(4Q}>4auA z;iSQ01PGbR;OQU_Y%Sc9tlpvNn%}e$K1l)G6a_Mdg?SrlEHbBmj;)u`IX002)_O3N zIYf^g1gA2G!1XdZ2e(cqHX}+4^f}qf78?N7y9td5&r!J5bNAXJt@;>jk-{2CG=i4` zMGoxaCc-<|)}i>y0A1JmIU;ic;JF~Sm9{U5Nr2yhIPBvLXu9?{&6KRA*!Gd|1#W-9GxS8xmfv7(7)4(PC*UXT9JF?j|9)b78}SH}3TCkL0u zp49dHL$7CD`^$5Id^hgNcSF}R8S-r%kWY3`KG_Z39mprM%D0K};X4MG$9L$uAs^S5 zkC7b0`=`Aa~G}WlY}zKIzN$Su&yuc z8y6PdRcJjeZWR=#F@_~2h2;i#avhgxajVV>#Tm&f2pu8GV!RH#nBIZdHxl9OJ4=}s zwH39l_O@3#uH(I;j7iC1^O<+o`MB9QZGToJ^Gk#mKp6w-Mcb+;BuxW8SlSKsN;DcZ zh~rkNmG(XxyaIB(5a#&7)=LT;(SM^Gc;WVMfB)o>8!)qfnNDT;*YD2=3 zM0~%s$=l7w-GXn5pkZ%sAbmiPCWS6wBw5T|W*@~rt&>#`5FdI=z(ArG-luPveH(o2 zlu#Wc4G1CFgwDrnHbNEMNfiUa!V(h!=EZfqWBqDco03cS))49h{l|Pp`w#Z{4&8$T z>tm@W^BL{ewY+DPF*(^dAFuxFe!eug-|xZ4{d!*PqakR5*_U7p(=Z&j!rj%*d!v^& zCoB=KO$rOqGCa3RrJ!_@YegsLR=V4`d2hNz=p<|a?QD3*LOVzjgGaNEfPXNMlCZBJ z0*@%}9UWp~u7y?L99l}|3iKjA7zM|1+Impxf|(T4X{ z*V442pp3~4bLTcp&Il?ZP%{23bp;Pb9aK*r6oH6`4v8d0Mq)Y|SX1zAqA_v@LWin9 zjYR~d!8nArOWmwmHAL;jw)z+hK7ZNDd-F{kvS~ttTc*uQOq?~X<^CwRHy_|6Qep<* z=8c16e=uiGuU>tce3(4p_cY-Z2qIN}f!+tJGjMIh%hCII8v6JSxkUHZVJ2V? z`lFS;hR$b2L}X<}glA76KM`5k@HFJ*rqR66HP*SUueoVC#_;T{2xE@9y047z?CkJ} z%xt5jES8id^A^hj&m-#+?sqNo?k~Cj>YjHDeixEVL0M=EGj=e((3YB%lp69u9}Bglji-oYM@ZXng^G|#o|A;)0hq=yjh2`D1-CPYycE`k{`&X+3@Iyt1kNmPr z?P_2nl-l;mTE&4!%ypKl9(Tn1MCerR6pkJNckXlOZA{#{;g>0M@VG1SGdOzlRe7Up zRteAz554KrJ!k#4?5@Sx4{gSeq7nVqoa*oDWtm|*nUDnZVnc9azemK48hpfZVP9~I z1m?FEybS*R|AeQ3I2?>M`#lz1^<%C7>aX#~e`3K}KmPi!{u-12cZ~iOc9Fz~`3ij_ zWkh?QNL|E~qE?}AZ1Ex|xtH{JMM7x$F-u89Bqw!v@bo*MAFL2n;)`}==k*LFnXZ}4Je_`7)vU^6AW=T&&m!@_&0 z4ID(Zs68s;gG9>}=mXSi_hBggl~O3()4vW0?3$=JK^r$2k6wSU;+R(mIKK0a_^w1Sf1Ojk9Qm zlxte|SX}GxjxMB%*(fnMI1%~kOu1v{zFa=NscFWTWqZdh!hg>U3(F)QZsUTImkypb zMzHQOo8YLZU|T8Po|RNQXV%!YsHv&7votrkuzMKIoRI;v111#y21o1-EZo3Z5S*k3 zkjviyjRHyB;r#TIy_Lkaz)Lm$aBhOeKb}2#41a~cI+nZ~IUtAS-+cC2>|>7+I)wbZ zj6ErJrM3uJkj5X3-5P)PWb$(SPyEm2$;VIt3gD9=d+cX4Ka5v)C6`8Gnb|UQz_Kf` z9@&#Y%Y4VB^^}@_A_JFyi|v)D4vG^wmLO`55(~v z2+w;M@h2XmJ@DsIC>6gMg>)XR?noDf-(=p2#$UL%y5ldzlt8#afzkL3_K(7!4}4~{ z2YrTpr1~7*k4BxV-o2~lJQ{`fpRd`~^RyN4902#7uim|T2pHgOB^rU|G9?uM5cCwy zy>$zXxJBBFund36tmL1hG3ojwAsMU8N@@28_U^$_E&{&!m5#Q5E|dp5+DezoNk4Sp zgG@62GgukPJV_%bCm~t?Fv&T1E?SwBgWpC~IvqZNOmJzC6$Bf4t{MzIpFkN?p^PN4 z48bxYAMnE@;kh|E$b?Vmbf^ly4c9C2L4$z>I|&r%blfc{#oRE_f zLQh35)WShDNg7?1%!vr)gUtO8Iqo-(r8TYimY2tKx!zG*=-_B*+xy;_;XPju31%3} znC|)7a>Z?2RCp2h7Q{z2BKtE%bDP7;){bmyoix0*sc_2ule#2|)j;O;=Ks_n%5V7_&>jt(>L9e|KlYSvP>b)^ zbNr1V+tB?%e=}LRr4{|{`K#`#{tvvFoKZi9`H6i&;zD{xt0a6G*mAszOoEu7mQ0y_ zbx#GxmNf$X@G{=eK;le zshoxAqZg+wVh`gTOize(;-A~36JshNcm*T*3I@jq+f!Vis^vo}WB6=;h;2uHi)TQ7SSItUD7eg3 zVVOZHr`HcS+o=4ueOjJWk^z0fNZyjH;JUdPqHS19B#>62@mDe+g<^>8;=1DxaVc(mx=!cYSD}T5&PVN|AhXY|J*^WPNEqH5~G5}z#0?H3DyYK zoL4xx4S!a%DpV?SQ26E-O=?5>n%z2El{fR?)QXy)miqXFCbn19{`_JTDK`mpmNUH> zR0q)#!qyTUDwLR}1? zjzn=I-Ue_YnV%%_`Z+b;eP9c{}v(D2Y)6Kst*wZKvG{j}4C+Gtd zCUx*Ke^t73N}^k?Q^1-K_b8QrkSWyDT^|?fBR8dV`6)Bh3D?7-qr=e0ULkP--ilaH z-AcVvjyfhrk)hVF3{}TEYGCN4MEeA~M7W1^>696co&kaGZmDwpvXnS`KV5vfF%pI( zXc`qTSSHgksUAEktw*T!H8a`LwfMB<3eouhxnq76MIhcf_=4yGFt?R@^v%0rq3b`* z^9Rqo2j5FQ{)d_P|9xV9&omv{0=Q#O^x=+L5k$fmG}E%4V+@VSe*Bpi|2H_bOfCA5 zOy+)vpg!2DKFJnJ7Un_stA%xqtn;wgQj?+TG*pfI`u?8k_0`joLU=UP7j^Q(=v?() zc_0oZ)M=@O<(KXOuKHp8 z1L8}24v80*CiNV)R0sMZr*}Ws#GazEE<%z_A_ZH&O-Yn+P<~AoO`0rRf^u%1$NGBc zei^05*{~j&32OK21_OFkZ}#u2a32}9>6ApGGF?n0eX+oAi>`VIHHzL#DsZ?^pMdk4RG zsHy;c#xpwR8s7tYoiFJ&XrF3@uq;8oG_$dDKul#r+kPMmPS#cx+=gtOAeUSSh^JCil?djq3mwA$#%o+TWY zfo$wFXsOAL9I2EkH-8<1V15sNUN&$>?uX#RBeR(aU0X(Bh0xB+6gI05mIAQPDeQqh z*hogl+DYx<+1Y)t{(RsmE0)&}o7)Fl%tUgT{7rZke22t*i`gHzOfC^%^Q~YF06Pb; z1%0qwv=yC&*d;QXg?+GcfPFw=odOKxJ>ZjV<=&;|e^Crl4IWTP)CqDdjwR551#W%Z zd_3E_-_P@qzmw6)wY@PpKGrQ}zYp9yMmfsU>|*~_zUBLcOg6==tv{+?pO;>Z|1iQP zbvV|XU|WY4(nF}{&-(&Dql1Y7&=0=CTw@*w`tc_g#=oQ=m}w9Sda|)#00qqp^LX{c zDMvR?|NfKq69=c`Yfr)i`-E3~yw|?6^{c?R2ZSS8gl{uDm=i>gM}q=FK-jnFc*0sp zzJUdA&5@_XDh!KZhq7a84pCok6M%5Y6=?p+`J7f z<5B_|@CV3mw3EH76#uF6bVFhLH;zcnna7lCqw1#YEFQk7vNUu!zH4Xm0K7K^@b)?2 z4K!OiEzi-evdr%Dkk0kInhwIP)etOm3;B>utYlg6%`*F2>)B%!HjOI?`u zsd8;|eW+pG!i<`_hSsV{?X_bQW2?txgP=?$GmvXxo{X38MAOOw24|#kbW%$ zJY0tNq!8)>G+3}8)kf;c@eF-t6Vey-lqZTF(QZxfMoyFkR9t>{%bX`_bg{8IdyhH! z;}f)AA(dl_g6#Y}WX=mJGr~%iK zML|0BtcDMEaB*QgoqRi7eotv@B)G8jl@?|~18CgR8??wMjO1t#t-#@ z3J(e^9bTL%3-YbNKex9v9nPB&T~VGja#ea;O*r6gB)-DhF)xdA3OHs3v~Q@2i!v@= zL(Z;J#sH1LMBC2gN|C!$MfsVyc6_A9U7Z}C$Vgoep@qym4_Ew0^7R=Z?)-^J(VM2aR zLV85M5P~O=x>D{+i2#RTwp?+0eQW zm#M>3K)sm?n#yL-RJQ0&8*m5QY=WByL2YR#&dnPxT|$0~+*EeTH}PCN@2rB*50aEac^?Sn zS$c+04Fry%qg<_0pbrX*EJcx$A3S|&!{$ymH+#idv;wVsQ)#DiTZF#>nI4S4Wu4J- zu$@nqNP14v_5zGK3NUXnlNYGdiY&FlLC@j|yTT&Aj5+FEeQ4*0&0`*1TRtor7cs_+ z_DvbAjncQ13k}b>u-n@s0t{Z8+$l*2AonXK@W0nJ{nWLlH9$Z^d5`~^eP_51Bq*}DPd+;t+ zM{5@i^{O7;8PS>10QK^64_R{QLS1rUe6moF+iWZIE!1PWSP#$_@GUkAU}?}HghN@* z>4Y(BiAWR!_cN5;>{UEfUW86IShF*sPMG@KuQ~jBxUkr;n-e+Up31 z^k$^X;_rUw7v9<$nKN-rC&Nah#6)Yo{L|sUDNq^Q-T#)m!A}84U{MVsp7}nDK`29R zZa(1du4Xc&vMfHYPKMaX;aORsPR`J+6rZ=zKH82)tlDv#_d^RwG<&ffR5sjS?;+gy zFZCfca054Q1bO0lzkR+wkN7yoI4UPMro@?4(fj;857B#~CP|&f@ALJ7^k^qVntcqY zNlyn2-~Qb~eA}WH6+c8&rGFVYD^;oZq1=pWe8<~X$=#zq&7c2`U7#Jl0Xme>Uhv>u z2)f_Ye)=(g&6Wc!fSf8!B_ln~P3$x1F+pvHE)(RROyFun2O@}J%dhU3mnC%`w<<1v zh0Z3*FW4pbZsWWyV_SAcCAD*({#ZtzTuUOy!tsXmkULTneGC&7 z6ka`U+Nw!$VNCM(-?!$w+A_(Nr3pFhBT=V`9Z@g^-_62{H#LNf75GLhHuJvRc1bC; zoJ0vT$@}FEptBI92(iR? z-|U{J=AT)_&c<=kC^(Ec zanvG58{PXSGUNQl;2lX(s9v>V-5VclS~q2cs~FBGeOgW-^1q`?PVy!*H)H{ z&+Qz~%QX06@+iD$Z-YaSVo0OJjyX zyQYxSUM#YMvRtScFLVzCH4{jc>eYl%lNP`j?Qgk6>#q-z!%`V6E6`SojfxpHzH9tA zd=+KI#TwmPk)h;NN{kUk0nW=TklK)|j+&Og-C)QlL2b&ZgqOnf9iH z1$wJe&OPPr?K{cU)n|Rc=z21iOn5(>xiU)9ENPWYkVrI8ITA$>>MG2K>A)8`CRv+< zrhKNRKDZl^B;Y?NV@)zR3OG*s^7-K4cRc0!ZvJ~+ zTyU?0+lupH+IPPrOVsH7hr23E>{ZLo8?^6w%JWoyVID2WIOdaXP11biOS1efvw1i# z9X)F9%<_M1rG1{tA42pYM`b3u{TqX4;v+Edr4Y8y1s@17#$d zAIZw3)v6qr9Q8NfeAs0|FOqzCQ}wWePTpQlcwC_^ixNkg|26e}cge1s1A0&&1EhCdgt(0qZ;DIdX z^%^&R`8!X*pQnogFTf>lzpqlgf9W~+llvLlQH`$xWusYdzw71s4mRUw>u+De+x~En zp|$n+GS8tc__LI*6!T?&PbLA+UXKV(qxEPt0MT+E2bN3o1Rn|GS_rggF6rvRdrBGeHBk&^vT@DslS>GSMUZplSW%Ql%A0g!DLBz*G}~ z1*8tgXh+Mbhmc6}WEw~Q#W4azA)aw8j1yp9P%8OHM9ffT7UOU8%h<+k>sp2>RY_x) zZH!&`z(V}3Oz~_%Dn1omP^g#(u_+Z#G0XQZ-QtDD}hki_c-$ZIz9vlNuF0)o z3J+{_a59O^!Oot$r3})#`KBC|*Drmb zGR4*XUX+@ifyY!mQn*Vy?;s@liHn7s{fP7&E$|K`G?3M}`L>sX3Ht+OBJ z=cNea(yH1X4K>83rfP=3QytO(p0EwpSWv1D$ni_)jNH)`S8fD*0nAt+ZZbWiq7+tB zs^OfEM#(72Xuu<^Lc`dT#kh&STb3V_hHz#WT{I|aG+-C#OsOHWZZZ}LQbdrF8msga zgfwc6PA}uNN(4tB>fsJ70J3j)gpPUbgU}v)^Q@zXr^8u%_YFsH5BR-|d_!-4JxeP0 z=(^P~z3>Rc(%x7iSGWWOwUYb8{0y|I#V$QEb@%Z45qLA+9)jG=ccY`2>R)H$Ga+k3 zZ1MI`Y@fs^-gGukGA<7e9m^?Sj7){16@2+kj{7=lms3f^Av~Q)n2cbnM0;Emiq6;DXsHtPM+W!jzI%@e9l3!7d@yE%-(XzWv@-=c)f2RegYf|Ge3Bi_m0YX^?U2|GOr zZzlcZ4KZ3`VI;v?zo_GzVk3}7RKH*#5iLF)K|jUAhi;`%LKmsY2e?E$vPYn%%6RWz zrc5a~lI)K4skWwÓ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 ^ij~u(C>=z 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Õ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%0Anb1GgP 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�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 s&|F?&E&K`&RJ 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 H>B* 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

@$%UOnCy3 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 z zw@kY;8gkhAxtWD~t43rOEOj_iW1~t$O5J5a^TZ3Q*P}W3mpRr%-z-#@Ds}4Xpxk6|6ciBRHxA z(Qv^YbM{WJZ!9Q?sl4Yro!Eb|<6NARUR4WgA4N~zTAEC+CNB%0Ah9hxyYCbFukh@+ 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%0A6kkYOl<7(LHlBb1q-r2IpeKA#>gPM^@ZN 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
Y(BRji6-58t2q& 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

bSfupmslN-*%+92oPx9kKt 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 z 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ĵ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

D;lqR_ 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ðQP5*h_K=;T6=E&zRE!rUS#ztQUdrj5taGy~lwb@RUrh*r!+! zxZ47T{-;TY_2SI~m}<%^wy&-V4Q`#{O7g?+_fna?ySpRa`Z{l`HMC3K1#iH}Kn>fTuHG?xaklCy>Y>f&TV~ItF3rYz z3>j!Wrl-B}%;dp}(LdN%!(_W*-8-oUuu_Vvwd)47)j30XbDHH|Z~qqc-+G6S#&uku z$1$bl=B)<7LTU^%QW3!lqKi{1uQt?&l^(s0KI#Kjn0Od+@UX>$AJMM?Q3mU!!f|!% zXq@zI_^d&IakIj+?rbSuF^#&ubmFMNvtY9s<%o?YF^VV~q~+A;v7^+7w1-3b_|3}v z5DUlY$V7|f@)KDIDhY3=mcV_%zd?OSzE120>UAaf<2VG1U*pB7LuQ+Y3n*eKgG091 zaY(z08HR^d1-uH>zRpBX zv7R;Z1y#i%B84R&9#?SmoN!)re1AxjSB&~9hrlQN!c6LDLtY_u$|#01GMEN@)a&Gn z#M%E+tO6B$d8`6_?J$!WFh?3JjWNnI1!m|oesxS8*sIF;Utm�anKPEziV$6k^p% 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

Y-EX2 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 AoB#j- literal 0 HcmV?d00001 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml new file mode 100644 index 00000000..1cba8c42 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml @@ -0,0 +1,6 @@ +extends: default +font: + catalog: + merge: true + Symbola: Symbola.ttf + fallbacks: [Symbola] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css new file mode 100644 index 00000000..3fd131d8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css @@ -0,0 +1 @@ +.transition--300{transition:all 300ms ease-in-out}.toc{height:100%;width:280px;transform:translateX(0)}.content h1:first-child,.content h2:first-child{padding-top:0;margin-top:0}.title{font-size:3em}.content{margin-bottom:95vh}.content ul,.content ol{list-style:inherit}.content a{color:#0977c3;text-decoration:none;border-bottom:1px solid #EEE;transition:all 300ms ease}.content a.no-decoration{border-bottom:0}.content a:hover{border-bottom:1px solid #0977c3}.content a:hover.no-decoration{border-bottom:0}a.toc-link{text-decoration:none}.try-it-container{transform:translateY(84%)}.try-it-container.is-open{transform:translateY(0%)}.page-content{display:block !important}.hljs{display:block;background:white;padding:0.5em;color:#333333;overflow-x:auto}.hljs-comment,.hljs-meta{color:#969896}.hljs-string,.hljs-variable,.hljs-template-variable,.hljs-strong,.hljs-emphasis,.hljs-quote{color:#df5000}.hljs-keyword,.hljs-selector-tag,.hljs-type{color:#a71d5d}.hljs-literal,.hljs-symbol,.hljs-bullet,.hljs-attribute{color:#0086b3}.hljs-section,.hljs-name{color:#63a35c}.hljs-tag{color:#333333}.hljs-title,.hljs-attr,.hljs-selector-id,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo{color:#795da3}.hljs-addition{color:#55a532;background-color:#eaffea}.hljs-deletion{color:#bd2c00;background-color:#ffecec}.hljs-link{text-decoration:underline}.toc-icon{position:fixed;top:0;right:0}#toc:checked ~ .toc{box-shadow:0 0 5px #c8c8c8;transform:translateX(0)}.toc{background-color:rgba(255,255,255,0.9);transform:translateX(-100%)}.toc.toc-right{transform:translateX(100%);right:0}@media (min-width: 52em){.toc{transform:translateX(0)}.toc.toc-right{transform:translateX(0);right:calc((100% - 48rem - 4rem) / 2)}.toc-icon{display:none}.try-it-container{display:block}.content{margin-left:280px}.toc-right ~ .content{margin-left:0;margin-right:280px}}*{box-sizing:border-box}body{font-size:1.2rem;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif}h1,h2,h3,h4,h5,h6{padding-top:0.5em}p{margin-top:0.25rem}pre{display:block;background:#f7f7f7;border-radius:2px;border:1px solid #e0e0e0;padding:2px;line-height:1.2;margin-bottom:10px;overflow:auto;white-space:pre-wrap}code{display:inline;font-size:.8em;max-width:100%} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css new file mode 100644 index 00000000..6265223f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css @@ -0,0 +1 @@ +.toc{overflow-y:auto}.toc>ul{overflow:hidden;position:relative}.toc>ul li{list-style:none}.toc-list{margin:0;padding-left:10px}a.toc-link{color:currentColor;height:100%}.is-collapsible{max-height:1000px;overflow:hidden;transition:all 300ms ease-in-out}.is-collapsed{max-height:0}.is-position-fixed{position:fixed !important;top:0}.is-active-link{font-weight:700}.toc-link::before{background-color:#EEE;content:' ';display:inline-block;height:inherit;left:0;margin-top:-1px;position:absolute;width:2px}.is-active-link::before{background-color:#54BC4B} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js new file mode 100644 index 00000000..52fe1837 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js @@ -0,0 +1,136 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 5); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/* unknown exports provided */ +/* all exports used */ +/*!***********************************!*\ + !*** (webpack)/buildin/global.js ***! + \***********************************/ +/***/ (function(module, exports) { + +eval("var g;\r\n\r\n// This works in non-strict mode\r\ng = (function() {\r\n\treturn this;\r\n})();\r\n\r\ntry {\r\n\t// This works if eval is allowed (see CSP)\r\n\tg = g || Function(\"return this\")() || (1,eval)(\"this\");\r\n} catch(e) {\r\n\t// This works if the window reference is available\r\n\tif(typeof window === \"object\")\r\n\t\tg = window;\r\n}\r\n\r\n// g can still be undefined, but nothing to do about it...\r\n// We return undefined, instead of nothing here, so it's\r\n// easier to handle this case. if(!global) { ...}\r\n\r\nmodule.exports = g;\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8od2VicGFjaykvYnVpbGRpbi9nbG9iYWwuanM/MzY5OCJdLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgZztcclxuXHJcbi8vIFRoaXMgd29ya3MgaW4gbm9uLXN0cmljdCBtb2RlXHJcbmcgPSAoZnVuY3Rpb24oKSB7XHJcblx0cmV0dXJuIHRoaXM7XHJcbn0pKCk7XHJcblxyXG50cnkge1xyXG5cdC8vIFRoaXMgd29ya3MgaWYgZXZhbCBpcyBhbGxvd2VkIChzZWUgQ1NQKVxyXG5cdGcgPSBnIHx8IEZ1bmN0aW9uKFwicmV0dXJuIHRoaXNcIikoKSB8fCAoMSxldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2goZSkge1xyXG5cdC8vIFRoaXMgd29ya3MgaWYgdGhlIHdpbmRvdyByZWZlcmVuY2UgaXMgYXZhaWxhYmxlXHJcblx0aWYodHlwZW9mIHdpbmRvdyA9PT0gXCJvYmplY3RcIilcclxuXHRcdGcgPSB3aW5kb3c7XHJcbn1cclxuXHJcbi8vIGcgY2FuIHN0aWxsIGJlIHVuZGVmaW5lZCwgYnV0IG5vdGhpbmcgdG8gZG8gYWJvdXQgaXQuLi5cclxuLy8gV2UgcmV0dXJuIHVuZGVmaW5lZCwgaW5zdGVhZCBvZiBub3RoaW5nIGhlcmUsIHNvIGl0J3NcclxuLy8gZWFzaWVyIHRvIGhhbmRsZSB0aGlzIGNhc2UuIGlmKCFnbG9iYWwpIHsgLi4ufVxyXG5cclxubW9kdWxlLmV4cG9ydHMgPSBnO1xyXG5cblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAod2VicGFjaykvYnVpbGRpbi9nbG9iYWwuanNcbi8vIG1vZHVsZSBpZCA9IDBcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); + +/***/ }), +/* 1 */ +/* unknown exports provided */ +/* all exports used */ +/*!**********************************!*\ + !*** ./~/zenscroll/zenscroll.js ***! + \**********************************/ +/***/ (function(module, exports, __webpack_require__) { + +eval("var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**\n * Zenscroll 4.0.0\n * https://github.com/zengabor/zenscroll/\n *\n * Copyright 2015–2017 Gabor Lenard\n *\n * This is free and unencumbered software released into the public domain.\n * \n * Anyone is free to copy, modify, publish, use, compile, sell, or\n * distribute this software, either in source code form or as a compiled\n * binary, for any purpose, commercial or non-commercial, and by any\n * means.\n * \n * In jurisdictions that recognize copyright laws, the author or authors\n * of this software dedicate any and all copyright interest in the\n * software to the public domain. We make this dedication for the benefit\n * of the public at large and to the detriment of our heirs and\n * successors. We intend this dedication to be an overt act of\n * relinquishment in perpetuity of all present and future rights to this\n * software under copyright law.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n * \n * For more information, please refer to \n * \n */\n\n/*jshint devel:true, asi:true */\n\n/*global define, module */\n\n\n(function (root, factory) {\n\tif (true) {\n\t\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory()),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?\n\t\t\t\t(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))\n\t} else if (typeof module === \"object\" && module.exports) {\n\t\tmodule.exports = factory()\n\t} else {\n\t\t(function install() {\n\t\t\t// To make sure Zenscroll can be referenced from the header, before `body` is available\n\t\t\tif (document && document.body) {\n\t\t\t\troot.zenscroll = factory()\n\t\t\t} else {\n\t\t\t\t// retry 9ms later\n\t\t\t\tsetTimeout(install, 9)\n\t\t\t}\n\t\t})()\n\t}\n}(this, function () {\n\t\"use strict\"\n\n\n\t// Detect if the browser already supports native smooth scrolling (e.g., Firefox 36+ and Chrome 49+) and it is enabled:\n\tvar isNativeSmoothScrollEnabledOn = function (elem) {\n\t\treturn (\"getComputedStyle\" in window) &&\n\t\t\twindow.getComputedStyle(elem)[\"scroll-behavior\"] === \"smooth\"\n\t}\n\n\n\t// Exit if it’s not a browser environment:\n\tif (typeof window === \"undefined\" || !(\"document\" in window)) {\n\t\treturn {}\n\t}\n\n\n\tvar makeScroller = function (container, defaultDuration, edgeOffset) {\n\n\t\t// Use defaults if not provided\n\t\tdefaultDuration = defaultDuration || 999 //ms\n\t\tif (!edgeOffset && edgeOffset !== 0) {\n\t\t\t// When scrolling, this amount of distance is kept from the edges of the container:\n\t\t\tedgeOffset = 9 //px\n\t\t}\n\n\t\t// Handling the life-cycle of the scroller\n\t\tvar scrollTimeoutId\n\t\tvar setScrollTimeoutId = function (newValue) {\n\t\t\tscrollTimeoutId = newValue\n\t\t}\n\n\t\t/**\n\t\t * Stop the current smooth scroll operation immediately\n\t\t */\n\t\tvar stopScroll = function () {\n\t\t\tclearTimeout(scrollTimeoutId)\n\t\t\tsetScrollTimeoutId(0)\n\t\t}\n\n\t\tvar getTopWithEdgeOffset = function (elem) {\n\t\t\treturn Math.max(0, container.getTopOf(elem) - edgeOffset)\n\t\t}\n\n\t\t/**\n\t\t * Scrolls to a specific vertical position in the document.\n\t\t *\n\t\t * @param {targetY} The vertical position within the document.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * If not provided the default duration is used.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollToY = function (targetY, duration, onDone) {\n\t\t\tstopScroll()\n\t\t\tif (duration === 0 || (duration && duration < 0) || isNativeSmoothScrollEnabledOn(container.body)) {\n\t\t\t\tcontainer.toY(targetY)\n\t\t\t\tif (onDone) {\n\t\t\t\t\tonDone()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar startY = container.getY()\n\t\t\t\tvar distance = Math.max(0, targetY) - startY\n\t\t\t\tvar startTime = new Date().getTime()\n\t\t\t\tduration = duration || Math.min(Math.abs(distance), defaultDuration);\n\t\t\t\t(function loopScroll() {\n\t\t\t\t\tsetScrollTimeoutId(setTimeout(function () {\n\t\t\t\t\t\t// Calculate percentage:\n\t\t\t\t\t\tvar p = Math.min(1, (new Date().getTime() - startTime) / duration)\n\t\t\t\t\t\t// Calculate the absolute vertical position:\n\t\t\t\t\t\tvar y = Math.max(0, Math.floor(startY + distance*(p < 0.5 ? 2*p*p : p*(4 - p*2)-1)))\n\t\t\t\t\t\tcontainer.toY(y)\n\t\t\t\t\t\tif (p < 1 && (container.getHeight() + y) < container.body.scrollHeight) {\n\t\t\t\t\t\t\tloopScroll()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetTimeout(stopScroll, 99) // with cooldown time\n\t\t\t\t\t\t\tif (onDone) {\n\t\t\t\t\t\t\t\tonDone()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 9))\n\t\t\t\t})()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Scrolls to the top of a specific element.\n\t\t *\n\t\t * @param {elem} The element to scroll to.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollToElem = function (elem, duration, onDone) {\n\t\t\tscrollToY(getTopWithEdgeOffset(elem), duration, onDone)\n\t\t}\n\n\t\t/**\n\t\t * Scrolls an element into view if necessary.\n\t\t *\n\t\t * @param {elem} The element.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollIntoView = function (elem, duration, onDone) {\n\t\t\tvar elemHeight = elem.getBoundingClientRect().height\n\t\t\tvar elemBottom = container.getTopOf(elem) + elemHeight\n\t\t\tvar containerHeight = container.getHeight()\n\t\t\tvar y = container.getY()\n\t\t\tvar containerBottom = y + containerHeight\n\t\t\tif (getTopWithEdgeOffset(elem) < y || (elemHeight + edgeOffset) > containerHeight) {\n\t\t\t\t// Element is clipped at top or is higher than screen.\n\t\t\t\tscrollToElem(elem, duration, onDone)\n\t\t\t} else if ((elemBottom + edgeOffset) > containerBottom) {\n\t\t\t\t// Element is clipped at the bottom.\n\t\t\t\tscrollToY(elemBottom - containerHeight + edgeOffset, duration, onDone)\n\t\t\t} else if (onDone) {\n\t\t\t\tonDone()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Scrolls to the center of an element.\n\t\t *\n\t\t * @param {elem} The element.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * @param {offset} Optionally the offset of the top of the element from the center of the screen.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollToCenterOf = function (elem, duration, offset, onDone) {\n\t\t\tscrollToY(Math.max(0, container.getTopOf(elem) - container.getHeight()/2 + (offset || elem.getBoundingClientRect().height/2)), duration, onDone)\n\t\t}\n\n\t\t/**\n\t\t * Changes default settings for this scroller.\n\t\t *\n\t\t * @param {newDefaultDuration} Optionally a new value for default duration, used for each scroll method by default.\n\t\t * Ignored if null or undefined.\n\t\t * @param {newEdgeOffset} Optionally a new value for the edge offset, used by each scroll method by default. Ignored if null or undefined.\n\t\t * @returns An object with the current values.\n\t\t */\n\t\tvar setup = function (newDefaultDuration, newEdgeOffset) {\n\t\t\tif (newDefaultDuration === 0 || newDefaultDuration) {\n\t\t\t\tdefaultDuration = newDefaultDuration\n\t\t\t}\n\t\t\tif (newEdgeOffset === 0 || newEdgeOffset) {\n\t\t\t\tedgeOffset = newEdgeOffset\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tdefaultDuration: defaultDuration,\n\t\t\t\tedgeOffset: edgeOffset\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tsetup: setup,\n\t\t\tto: scrollToElem,\n\t\t\ttoY: scrollToY,\n\t\t\tintoView: scrollIntoView,\n\t\t\tcenter: scrollToCenterOf,\n\t\t\tstop: stopScroll,\n\t\t\tmoving: function () { return !!scrollTimeoutId },\n\t\t\tgetY: container.getY,\n\t\t\tgetTopOf: container.getTopOf\n\t\t}\n\n\t}\n\n\n\tvar docElem = document.documentElement\n\tvar getDocY = function () { return window.scrollY || docElem.scrollTop }\n\n\t// Create a scroller for the document:\n\tvar zenscroll = makeScroller({\n\t\tbody: document.scrollingElement || document.body,\n\t\ttoY: function (y) { window.scrollTo(0, y) },\n\t\tgetY: getDocY,\n\t\tgetHeight: function () { return window.innerHeight || docElem.clientHeight },\n\t\tgetTopOf: function (elem) { return elem.getBoundingClientRect().top + getDocY() - docElem.offsetTop }\n\t})\n\n\n\t/**\n\t * Creates a scroller from the provided container element (e.g., a DIV)\n\t *\n\t * @param {scrollContainer} The vertical position within the document.\n\t * @param {defaultDuration} Optionally a value for default duration, used for each scroll method by default.\n\t * Ignored if 0 or null or undefined.\n\t * @param {edgeOffset} Optionally a value for the edge offset, used by each scroll method by default. \n\t * Ignored if null or undefined.\n\t * @returns A scroller object, similar to `zenscroll` but controlling the provided element.\n\t */\n\tzenscroll.createScroller = function (scrollContainer, defaultDuration, edgeOffset) {\n\t\treturn makeScroller({\n\t\t\tbody: scrollContainer,\n\t\t\ttoY: function (y) { scrollContainer.scrollTop = y },\n\t\t\tgetY: function () { return scrollContainer.scrollTop },\n\t\t\tgetHeight: function () { return Math.min(scrollContainer.clientHeight, window.innerHeight || docElem.clientHeight) },\n\t\t\tgetTopOf: function (elem) { return elem.offsetTop }\n\t\t}, defaultDuration, edgeOffset)\n\t}\n\n\n\t// Automatic link-smoothing on achors\n\t// Exclude IE8- or when native is enabled or Zenscroll auto- is disabled\n\tif (\"addEventListener\" in window && !window.noZensmooth && !isNativeSmoothScrollEnabledOn(document.body)) {\n\n\n\t\tvar isScrollRestorationSupported = \"scrollRestoration\" in history\n\n\t\t// On first load & refresh make sure the browser restores the position first\n\t\tif (isScrollRestorationSupported) {\n\t\t\thistory.scrollRestoration = \"auto\"\n\t\t}\n\n\t\twindow.addEventListener(\"load\", function () {\n\n\t\t\tif (isScrollRestorationSupported) {\n\t\t\t\t// Set it to manual\n\t\t\t\tsetTimeout(function () { history.scrollRestoration = \"manual\" }, 9)\n\t\t\t\twindow.addEventListener(\"popstate\", function (event) {\n\t\t\t\t\tif (event.state && \"zenscrollY\" in event.state) {\n\t\t\t\t\t\tzenscroll.toY(event.state.zenscrollY)\n\t\t\t\t\t}\n\t\t\t\t}, false)\n\t\t\t}\n\n\t\t\t// Add edge offset on first load if necessary\n\t\t\t// This may not work on IE (or older computer?) as it requires more timeout, around 100 ms\n\t\t\tif (window.location.hash) {\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t// Adjustment is only needed if there is an edge offset:\n\t\t\t\t\tvar edgeOffset = zenscroll.setup().edgeOffset\n\t\t\t\t\tif (edgeOffset) {\n\t\t\t\t\t\tvar targetElem = document.getElementById(window.location.href.split(\"#\")[1])\n\t\t\t\t\t\tif (targetElem) {\n\t\t\t\t\t\t\tvar targetY = Math.max(0, zenscroll.getTopOf(targetElem) - edgeOffset)\n\t\t\t\t\t\t\tvar diff = zenscroll.getY() - targetY\n\t\t\t\t\t\t\t// Only do the adjustment if the browser is very close to the element:\n\t\t\t\t\t\t\tif (0 <= diff && diff < 9 ) {\n\t\t\t\t\t\t\t\twindow.scrollTo(0, targetY)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}, 9)\n\t\t\t}\n\n\t\t}, false)\n\n\t\t// Handling clicks on anchors\n\t\tvar RE_noZensmooth = new RegExp(\"(^|\\\\s)noZensmooth(\\\\s|$)\")\n\t\twindow.addEventListener(\"click\", function (event) {\n\t\t\tvar anchor = event.target\n\t\t\twhile (anchor && anchor.tagName !== \"A\") {\n\t\t\t\tanchor = anchor.parentNode\n\t\t\t}\n\t\t\t// Let the browser handle the click if it wasn't with the primary button, or with some modifier keys:\n\t\t\tif (!anchor || event.which !== 1 || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Save the current scrolling position so it can be used for scroll restoration:\n\t\t\tif (isScrollRestorationSupported) {\n\t\t\t\ttry {\n\t\t\t\t\thistory.replaceState({ zenscrollY: zenscroll.getY() }, \"\")\n\t\t\t\t} catch (e) {\n\t\t\t\t\t// Avoid the Chrome Security exception on file protocol, e.g., file://index.html\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Find the referenced ID:\n\t\t\tvar href = anchor.getAttribute(\"href\") || \"\"\n\t\t\tif (href.indexOf(\"#\") === 0 && !RE_noZensmooth.test(anchor.className)) {\n\t\t\t\tvar targetY = 0\n\t\t\t\tvar targetElem = document.getElementById(href.substring(1))\n\t\t\t\tif (href !== \"#\") {\n\t\t\t\t\tif (!targetElem) {\n\t\t\t\t\t\t// Let the browser handle the click if the target ID is not found.\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\ttargetY = zenscroll.getTopOf(targetElem)\n\t\t\t\t}\n\t\t\t\tevent.preventDefault()\n\t\t\t\t// By default trigger the browser's `hashchange` event...\n\t\t\t\tvar onDone = function () { window.location = href }\n\t\t\t\t// ...unless there is an edge offset specified\n\t\t\t\tvar edgeOffset = zenscroll.setup().edgeOffset\n\t\t\t\tif (edgeOffset) {\n\t\t\t\t\ttargetY = Math.max(0, targetY - edgeOffset)\n\t\t\t\t\tonDone = function () { history.pushState(null, \"\", href) }\n\t\t\t\t}\n\t\t\t\tzenscroll.toY(targetY, null, onDone)\n\t\t\t}\n\t\t}, false)\n\n\t}\n\n\n\treturn zenscroll\n\n\n}));\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL34vemVuc2Nyb2xsL3plbnNjcm9sbC5qcz8yNzMyIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogWmVuc2Nyb2xsIDQuMC4wXG4gKiBodHRwczovL2dpdGh1Yi5jb20vemVuZ2Fib3IvemVuc2Nyb2xsL1xuICpcbiAqIENvcHlyaWdodCAyMDE14oCTMjAxNyBHYWJvciBMZW5hcmRcbiAqXG4gKiBUaGlzIGlzIGZyZWUgYW5kIHVuZW5jdW1iZXJlZCBzb2Z0d2FyZSByZWxlYXNlZCBpbnRvIHRoZSBwdWJsaWMgZG9tYWluLlxuICogXG4gKiBBbnlvbmUgaXMgZnJlZSB0byBjb3B5LCBtb2RpZnksIHB1Ymxpc2gsIHVzZSwgY29tcGlsZSwgc2VsbCwgb3JcbiAqIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSwgZWl0aGVyIGluIHNvdXJjZSBjb2RlIGZvcm0gb3IgYXMgYSBjb21waWxlZFxuICogYmluYXJ5LCBmb3IgYW55IHB1cnBvc2UsIGNvbW1lcmNpYWwgb3Igbm9uLWNvbW1lcmNpYWwsIGFuZCBieSBhbnlcbiAqIG1lYW5zLlxuICogXG4gKiBJbiBqdXJpc2RpY3Rpb25zIHRoYXQgcmVjb2duaXplIGNvcHlyaWdodCBsYXdzLCB0aGUgYXV0aG9yIG9yIGF1dGhvcnNcbiAqIG9mIHRoaXMgc29mdHdhcmUgZGVkaWNhdGUgYW55IGFuZCBhbGwgY29weXJpZ2h0IGludGVyZXN0IGluIHRoZVxuICogc29mdHdhcmUgdG8gdGhlIHB1YmxpYyBkb21haW4uIFdlIG1ha2UgdGhpcyBkZWRpY2F0aW9uIGZvciB0aGUgYmVuZWZpdFxuICogb2YgdGhlIHB1YmxpYyBhdCBsYXJnZSBhbmQgdG8gdGhlIGRldHJpbWVudCBvZiBvdXIgaGVpcnMgYW5kXG4gKiBzdWNjZXNzb3JzLiBXZSBpbnRlbmQgdGhpcyBkZWRpY2F0aW9uIHRvIGJlIGFuIG92ZXJ0IGFjdCBvZlxuICogcmVsaW5xdWlzaG1lbnQgaW4gcGVycGV0dWl0eSBvZiBhbGwgcHJlc2VudCBhbmQgZnV0dXJlIHJpZ2h0cyB0byB0aGlzXG4gKiBzb2Z0d2FyZSB1bmRlciBjb3B5cmlnaHQgbGF3LlxuICogXG4gKiBUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELFxuICogRVhQUkVTUyBPUiBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GXG4gKiBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuXG4gKiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdFUyBPUlxuICogT1RIRVIgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsXG4gKiBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1JcbiAqIE9USEVSIERFQUxJTkdTIElOIFRIRSBTT0ZUV0FSRS5cbiAqIFxuICogRm9yIG1vcmUgaW5mb3JtYXRpb24sIHBsZWFzZSByZWZlciB0byA8aHR0cDovL3VubGljZW5zZS5vcmc+XG4gKiBcbiAqL1xuXG4vKmpzaGludCBkZXZlbDp0cnVlLCBhc2k6dHJ1ZSAqL1xuXG4vKmdsb2JhbCBkZWZpbmUsIG1vZHVsZSAqL1xuXG5cbihmdW5jdGlvbiAocm9vdCwgZmFjdG9yeSkge1xuXHRpZiAodHlwZW9mIGRlZmluZSA9PT0gXCJmdW5jdGlvblwiICYmIGRlZmluZS5hbWQpIHtcblx0XHRkZWZpbmUoW10sIGZhY3RvcnkoKSlcblx0fSBlbHNlIGlmICh0eXBlb2YgbW9kdWxlID09PSBcIm9iamVjdFwiICYmIG1vZHVsZS5leHBvcnRzKSB7XG5cdFx0bW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KClcblx0fSBlbHNlIHtcblx0XHQoZnVuY3Rpb24gaW5zdGFsbCgpIHtcblx0XHRcdC8vIFRvIG1ha2Ugc3VyZSBaZW5zY3JvbGwgY2FuIGJlIHJlZmVyZW5jZWQgZnJvbSB0aGUgaGVhZGVyLCBiZWZvcmUgYGJvZHlgIGlzIGF2YWlsYWJsZVxuXHRcdFx0aWYgKGRvY3VtZW50ICYmIGRvY3VtZW50LmJvZHkpIHtcblx0XHRcdFx0cm9vdC56ZW5zY3JvbGwgPSBmYWN0b3J5KClcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdC8vIHJldHJ5IDltcyBsYXRlclxuXHRcdFx0XHRzZXRUaW1lb3V0KGluc3RhbGwsIDkpXG5cdFx0XHR9XG5cdFx0fSkoKVxuXHR9XG59KHRoaXMsIGZ1bmN0aW9uICgpIHtcblx0XCJ1c2Ugc3RyaWN0XCJcblxuXG5cdC8vIERldGVjdCBpZiB0aGUgYnJvd3NlciBhbHJlYWR5IHN1cHBvcnRzIG5hdGl2ZSBzbW9vdGggc2Nyb2xsaW5nIChlLmcuLCBGaXJlZm94IDM2KyBhbmQgQ2hyb21lIDQ5KykgYW5kIGl0IGlzIGVuYWJsZWQ6XG5cdHZhciBpc05hdGl2ZVNtb290aFNjcm9sbEVuYWJsZWRPbiA9IGZ1bmN0aW9uIChlbGVtKSB7XG5cdFx0cmV0dXJuIChcImdldENvbXB1dGVkU3R5bGVcIiBpbiB3aW5kb3cpICYmXG5cdFx0XHR3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShlbGVtKVtcInNjcm9sbC1iZWhhdmlvclwiXSA9PT0gXCJzbW9vdGhcIlxuXHR9XG5cblxuXHQvLyBFeGl0IGlmIGl04oCZcyBub3QgYSBicm93c2VyIGVudmlyb25tZW50OlxuXHRpZiAodHlwZW9mIHdpbmRvdyA9PT0gXCJ1bmRlZmluZWRcIiB8fCAhKFwiZG9jdW1lbnRcIiBpbiB3aW5kb3cpKSB7XG5cdFx0cmV0dXJuIHt9XG5cdH1cblxuXG5cdHZhciBtYWtlU2Nyb2xsZXIgPSBmdW5jdGlvbiAoY29udGFpbmVyLCBkZWZhdWx0RHVyYXRpb24sIGVkZ2VPZmZzZXQpIHtcblxuXHRcdC8vIFVzZSBkZWZhdWx0cyBpZiBub3QgcHJvdmlkZWRcblx0XHRkZWZhdWx0RHVyYXRpb24gPSBkZWZhdWx0RHVyYXRpb24gfHwgOTk5IC8vbXNcblx0XHRpZiAoIWVkZ2VPZmZzZXQgJiYgZWRnZU9mZnNldCAhPT0gMCkge1xuXHRcdFx0Ly8gV2hlbiBzY3JvbGxpbmcsIHRoaXMgYW1vdW50IG9mIGRpc3RhbmNlIGlzIGtlcHQgZnJvbSB0aGUgZWRnZXMgb2YgdGhlIGNvbnRhaW5lcjpcblx0XHRcdGVkZ2VPZmZzZXQgPSA5IC8vcHhcblx0XHR9XG5cblx0XHQvLyBIYW5kbGluZyB0aGUgbGlmZS1jeWNsZSBvZiB0aGUgc2Nyb2xsZXJcblx0XHR2YXIgc2Nyb2xsVGltZW91dElkXG5cdFx0dmFyIHNldFNjcm9sbFRpbWVvdXRJZCA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuXHRcdFx0c2Nyb2xsVGltZW91dElkID0gbmV3VmFsdWVcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBTdG9wIHRoZSBjdXJyZW50IHNtb290aCBzY3JvbGwgb3BlcmF0aW9uIGltbWVkaWF0ZWx5XG5cdFx0ICovXG5cdFx0dmFyIHN0b3BTY3JvbGwgPSBmdW5jdGlvbiAoKSB7XG5cdFx0XHRjbGVhclRpbWVvdXQoc2Nyb2xsVGltZW91dElkKVxuXHRcdFx0c2V0U2Nyb2xsVGltZW91dElkKDApXG5cdFx0fVxuXG5cdFx0dmFyIGdldFRvcFdpdGhFZGdlT2Zmc2V0ID0gZnVuY3Rpb24gKGVsZW0pIHtcblx0XHRcdHJldHVybiBNYXRoLm1heCgwLCBjb250YWluZXIuZ2V0VG9wT2YoZWxlbSkgLSBlZGdlT2Zmc2V0KVxuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFNjcm9sbHMgdG8gYSBzcGVjaWZpYyB2ZXJ0aWNhbCBwb3NpdGlvbiBpbiB0aGUgZG9jdW1lbnQuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0ge3RhcmdldFl9IFRoZSB2ZXJ0aWNhbCBwb3NpdGlvbiB3aXRoaW4gdGhlIGRvY3VtZW50LlxuXHRcdCAqIEBwYXJhbSB7ZHVyYXRpb259IE9wdGlvbmFsbHkgdGhlIGR1cmF0aW9uIG9mIHRoZSBzY3JvbGwgb3BlcmF0aW9uLlxuXHRcdCAqICAgICAgICBJZiBub3QgcHJvdmlkZWQgdGhlIGRlZmF1bHQgZHVyYXRpb24gaXMgdXNlZC5cblx0XHQgKiBAcGFyYW0ge29uRG9uZX0gQW4gb3B0aW9uYWwgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgaW52b2tlZCBvbmNlIHRoZSBzY3JvbGwgZmluaXNoZWQuXG5cdFx0ICovXG5cdFx0dmFyIHNjcm9sbFRvWSA9IGZ1bmN0aW9uICh0YXJnZXRZLCBkdXJhdGlvbiwgb25Eb25lKSB7XG5cdFx0XHRzdG9wU2Nyb2xsKClcblx0XHRcdGlmIChkdXJhdGlvbiA9PT0gMCB8fCAoZHVyYXRpb24gJiYgZHVyYXRpb24gPCAwKSB8fCBpc05hdGl2ZVNtb290aFNjcm9sbEVuYWJsZWRPbihjb250YWluZXIuYm9keSkpIHtcblx0XHRcdFx0Y29udGFpbmVyLnRvWSh0YXJnZXRZKVxuXHRcdFx0XHRpZiAob25Eb25lKSB7XG5cdFx0XHRcdFx0b25Eb25lKClcblx0XHRcdFx0fVxuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0dmFyIHN0YXJ0WSA9IGNvbnRhaW5lci5nZXRZKClcblx0XHRcdFx0dmFyIGRpc3RhbmNlID0gTWF0aC5tYXgoMCwgdGFyZ2V0WSkgLSBzdGFydFlcblx0XHRcdFx0dmFyIHN0YXJ0VGltZSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpXG5cdFx0XHRcdGR1cmF0aW9uID0gZHVyYXRpb24gfHwgTWF0aC5taW4oTWF0aC5hYnMoZGlzdGFuY2UpLCBkZWZhdWx0RHVyYXRpb24pO1xuXHRcdFx0XHQoZnVuY3Rpb24gbG9vcFNjcm9sbCgpIHtcblx0XHRcdFx0XHRzZXRTY3JvbGxUaW1lb3V0SWQoc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG5cdFx0XHRcdFx0XHQvLyBDYWxjdWxhdGUgcGVyY2VudGFnZTpcblx0XHRcdFx0XHRcdHZhciBwID0gTWF0aC5taW4oMSwgKG5ldyBEYXRlKCkuZ2V0VGltZSgpIC0gc3RhcnRUaW1lKSAvIGR1cmF0aW9uKVxuXHRcdFx0XHRcdFx0Ly8gQ2FsY3VsYXRlIHRoZSBhYnNvbHV0ZSB2ZXJ0aWNhbCBwb3NpdGlvbjpcblx0XHRcdFx0XHRcdHZhciB5ID0gTWF0aC5tYXgoMCwgTWF0aC5mbG9vcihzdGFydFkgKyBkaXN0YW5jZSoocCA8IDAuNSA/IDIqcCpwIDogcCooNCAtIHAqMiktMSkpKVxuXHRcdFx0XHRcdFx0Y29udGFpbmVyLnRvWSh5KVxuXHRcdFx0XHRcdFx0aWYgKHAgPCAxICYmIChjb250YWluZXIuZ2V0SGVpZ2h0KCkgKyB5KSA8IGNvbnRhaW5lci5ib2R5LnNjcm9sbEhlaWdodCkge1xuXHRcdFx0XHRcdFx0XHRsb29wU2Nyb2xsKClcblx0XHRcdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0XHRcdHNldFRpbWVvdXQoc3RvcFNjcm9sbCwgOTkpIC8vIHdpdGggY29vbGRvd24gdGltZVxuXHRcdFx0XHRcdFx0XHRpZiAob25Eb25lKSB7XG5cdFx0XHRcdFx0XHRcdFx0b25Eb25lKClcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0sIDkpKVxuXHRcdFx0XHR9KSgpXG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2Nyb2xscyB0byB0aGUgdG9wIG9mIGEgc3BlY2lmaWMgZWxlbWVudC5cblx0XHQgKlxuXHRcdCAqIEBwYXJhbSB7ZWxlbX0gVGhlIGVsZW1lbnQgdG8gc2Nyb2xsIHRvLlxuXHRcdCAqIEBwYXJhbSB7ZHVyYXRpb259IE9wdGlvbmFsbHkgdGhlIGR1cmF0aW9uIG9mIHRoZSBzY3JvbGwgb3BlcmF0aW9uLlxuXHRcdCAqIEBwYXJhbSB7b25Eb25lfSBBbiBvcHRpb25hbCBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBpbnZva2VkIG9uY2UgdGhlIHNjcm9sbCBmaW5pc2hlZC5cblx0XHQgKi9cblx0XHR2YXIgc2Nyb2xsVG9FbGVtID0gZnVuY3Rpb24gKGVsZW0sIGR1cmF0aW9uLCBvbkRvbmUpIHtcblx0XHRcdHNjcm9sbFRvWShnZXRUb3BXaXRoRWRnZU9mZnNldChlbGVtKSwgZHVyYXRpb24sIG9uRG9uZSlcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBTY3JvbGxzIGFuIGVsZW1lbnQgaW50byB2aWV3IGlmIG5lY2Vzc2FyeS5cblx0XHQgKlxuXHRcdCAqIEBwYXJhbSB7ZWxlbX0gVGhlIGVsZW1lbnQuXG5cdFx0ICogQHBhcmFtIHtkdXJhdGlvbn0gT3B0aW9uYWxseSB0aGUgZHVyYXRpb24gb2YgdGhlIHNjcm9sbCBvcGVyYXRpb24uXG5cdFx0ICogQHBhcmFtIHtvbkRvbmV9IEFuIG9wdGlvbmFsIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGludm9rZWQgb25jZSB0aGUgc2Nyb2xsIGZpbmlzaGVkLlxuXHRcdCAqL1xuXHRcdHZhciBzY3JvbGxJbnRvVmlldyA9IGZ1bmN0aW9uIChlbGVtLCBkdXJhdGlvbiwgb25Eb25lKSB7XG5cdFx0XHR2YXIgZWxlbUhlaWdodCA9IGVsZW0uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkuaGVpZ2h0XG5cdFx0XHR2YXIgZWxlbUJvdHRvbSA9IGNvbnRhaW5lci5nZXRUb3BPZihlbGVtKSArIGVsZW1IZWlnaHRcblx0XHRcdHZhciBjb250YWluZXJIZWlnaHQgPSBjb250YWluZXIuZ2V0SGVpZ2h0KClcblx0XHRcdHZhciB5ID0gY29udGFpbmVyLmdldFkoKVxuXHRcdFx0dmFyIGNvbnRhaW5lckJvdHRvbSA9IHkgKyBjb250YWluZXJIZWlnaHRcblx0XHRcdGlmIChnZXRUb3BXaXRoRWRnZU9mZnNldChlbGVtKSA8IHkgfHwgKGVsZW1IZWlnaHQgKyBlZGdlT2Zmc2V0KSA+IGNvbnRhaW5lckhlaWdodCkge1xuXHRcdFx0XHQvLyBFbGVtZW50IGlzIGNsaXBwZWQgYXQgdG9wIG9yIGlzIGhpZ2hlciB0aGFuIHNjcmVlbi5cblx0XHRcdFx0c2Nyb2xsVG9FbGVtKGVsZW0sIGR1cmF0aW9uLCBvbkRvbmUpXG5cdFx0XHR9IGVsc2UgaWYgKChlbGVtQm90dG9tICsgZWRnZU9mZnNldCkgPiBjb250YWluZXJCb3R0b20pIHtcblx0XHRcdFx0Ly8gRWxlbWVudCBpcyBjbGlwcGVkIGF0IHRoZSBib3R0b20uXG5cdFx0XHRcdHNjcm9sbFRvWShlbGVtQm90dG9tIC0gY29udGFpbmVySGVpZ2h0ICsgZWRnZU9mZnNldCwgZHVyYXRpb24sIG9uRG9uZSlcblx0XHRcdH0gZWxzZSBpZiAob25Eb25lKSB7XG5cdFx0XHRcdG9uRG9uZSgpXG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2Nyb2xscyB0byB0aGUgY2VudGVyIG9mIGFuIGVsZW1lbnQuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0ge2VsZW19IFRoZSBlbGVtZW50LlxuXHRcdCAqIEBwYXJhbSB7ZHVyYXRpb259IE9wdGlvbmFsbHkgdGhlIGR1cmF0aW9uIG9mIHRoZSBzY3JvbGwgb3BlcmF0aW9uLlxuXHRcdCAqIEBwYXJhbSB7b2Zmc2V0fSBPcHRpb25hbGx5IHRoZSBvZmZzZXQgb2YgdGhlIHRvcCBvZiB0aGUgZWxlbWVudCBmcm9tIHRoZSBjZW50ZXIgb2YgdGhlIHNjcmVlbi5cblx0XHQgKiBAcGFyYW0ge29uRG9uZX0gQW4gb3B0aW9uYWwgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgaW52b2tlZCBvbmNlIHRoZSBzY3JvbGwgZmluaXNoZWQuXG5cdFx0ICovXG5cdFx0dmFyIHNjcm9sbFRvQ2VudGVyT2YgPSBmdW5jdGlvbiAoZWxlbSwgZHVyYXRpb24sIG9mZnNldCwgb25Eb25lKSB7XG5cdFx0XHRzY3JvbGxUb1koTWF0aC5tYXgoMCwgY29udGFpbmVyLmdldFRvcE9mKGVsZW0pIC0gY29udGFpbmVyLmdldEhlaWdodCgpLzIgKyAob2Zmc2V0IHx8IGVsZW0uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkuaGVpZ2h0LzIpKSwgZHVyYXRpb24sIG9uRG9uZSlcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBDaGFuZ2VzIGRlZmF1bHQgc2V0dGluZ3MgZm9yIHRoaXMgc2Nyb2xsZXIuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0ge25ld0RlZmF1bHREdXJhdGlvbn0gT3B0aW9uYWxseSBhIG5ldyB2YWx1ZSBmb3IgZGVmYXVsdCBkdXJhdGlvbiwgdXNlZCBmb3IgZWFjaCBzY3JvbGwgbWV0aG9kIGJ5IGRlZmF1bHQuXG5cdFx0ICogICAgICAgIElnbm9yZWQgaWYgbnVsbCBvciB1bmRlZmluZWQuXG5cdFx0ICogQHBhcmFtIHtuZXdFZGdlT2Zmc2V0fSBPcHRpb25hbGx5IGEgbmV3IHZhbHVlIGZvciB0aGUgZWRnZSBvZmZzZXQsIHVzZWQgYnkgZWFjaCBzY3JvbGwgbWV0aG9kIGJ5IGRlZmF1bHQuIElnbm9yZWQgaWYgbnVsbCBvciB1bmRlZmluZWQuXG5cdFx0ICogQHJldHVybnMgQW4gb2JqZWN0IHdpdGggdGhlIGN1cnJlbnQgdmFsdWVzLlxuXHRcdCAqL1xuXHRcdHZhciBzZXR1cCA9IGZ1bmN0aW9uIChuZXdEZWZhdWx0RHVyYXRpb24sIG5ld0VkZ2VPZmZzZXQpIHtcblx0XHRcdGlmIChuZXdEZWZhdWx0RHVyYXRpb24gPT09IDAgfHwgbmV3RGVmYXVsdER1cmF0aW9uKSB7XG5cdFx0XHRcdGRlZmF1bHREdXJhdGlvbiA9IG5ld0RlZmF1bHREdXJhdGlvblxuXHRcdFx0fVxuXHRcdFx0aWYgKG5ld0VkZ2VPZmZzZXQgPT09IDAgfHwgbmV3RWRnZU9mZnNldCkge1xuXHRcdFx0XHRlZGdlT2Zmc2V0ID0gbmV3RWRnZU9mZnNldFxuXHRcdFx0fVxuXHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0ZGVmYXVsdER1cmF0aW9uOiBkZWZhdWx0RHVyYXRpb24sXG5cdFx0XHRcdGVkZ2VPZmZzZXQ6IGVkZ2VPZmZzZXRcblx0XHRcdH1cblx0XHR9XG5cblx0XHRyZXR1cm4ge1xuXHRcdFx0c2V0dXA6IHNldHVwLFxuXHRcdFx0dG86IHNjcm9sbFRvRWxlbSxcblx0XHRcdHRvWTogc2Nyb2xsVG9ZLFxuXHRcdFx0aW50b1ZpZXc6IHNjcm9sbEludG9WaWV3LFxuXHRcdFx0Y2VudGVyOiBzY3JvbGxUb0NlbnRlck9mLFxuXHRcdFx0c3RvcDogc3RvcFNjcm9sbCxcblx0XHRcdG1vdmluZzogZnVuY3Rpb24gKCkgeyByZXR1cm4gISFzY3JvbGxUaW1lb3V0SWQgfSxcblx0XHRcdGdldFk6IGNvbnRhaW5lci5nZXRZLFxuXHRcdFx0Z2V0VG9wT2Y6IGNvbnRhaW5lci5nZXRUb3BPZlxuXHRcdH1cblxuXHR9XG5cblxuXHR2YXIgZG9jRWxlbSA9IGRvY3VtZW50LmRvY3VtZW50RWxlbWVudFxuXHR2YXIgZ2V0RG9jWSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIHdpbmRvdy5zY3JvbGxZIHx8IGRvY0VsZW0uc2Nyb2xsVG9wIH1cblxuXHQvLyBDcmVhdGUgYSBzY3JvbGxlciBmb3IgdGhlIGRvY3VtZW50OlxuXHR2YXIgemVuc2Nyb2xsID0gbWFrZVNjcm9sbGVyKHtcblx0XHRib2R5OiBkb2N1bWVudC5zY3JvbGxpbmdFbGVtZW50IHx8IGRvY3VtZW50LmJvZHksXG5cdFx0dG9ZOiBmdW5jdGlvbiAoeSkgeyB3aW5kb3cuc2Nyb2xsVG8oMCwgeSkgfSxcblx0XHRnZXRZOiBnZXREb2NZLFxuXHRcdGdldEhlaWdodDogZnVuY3Rpb24gKCkgeyByZXR1cm4gd2luZG93LmlubmVySGVpZ2h0IHx8IGRvY0VsZW0uY2xpZW50SGVpZ2h0IH0sXG5cdFx0Z2V0VG9wT2Y6IGZ1bmN0aW9uIChlbGVtKSB7IHJldHVybiBlbGVtLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcCArIGdldERvY1koKSAtIGRvY0VsZW0ub2Zmc2V0VG9wIH1cblx0fSlcblxuXG5cdC8qKlxuXHQgKiBDcmVhdGVzIGEgc2Nyb2xsZXIgZnJvbSB0aGUgcHJvdmlkZWQgY29udGFpbmVyIGVsZW1lbnQgKGUuZy4sIGEgRElWKVxuXHQgKlxuXHQgKiBAcGFyYW0ge3Njcm9sbENvbnRhaW5lcn0gVGhlIHZlcnRpY2FsIHBvc2l0aW9uIHdpdGhpbiB0aGUgZG9jdW1lbnQuXG5cdCAqIEBwYXJhbSB7ZGVmYXVsdER1cmF0aW9ufSBPcHRpb25hbGx5IGEgdmFsdWUgZm9yIGRlZmF1bHQgZHVyYXRpb24sIHVzZWQgZm9yIGVhY2ggc2Nyb2xsIG1ldGhvZCBieSBkZWZhdWx0LlxuXHQgKiAgICAgICAgSWdub3JlZCBpZiAwIG9yIG51bGwgb3IgdW5kZWZpbmVkLlxuXHQgKiBAcGFyYW0ge2VkZ2VPZmZzZXR9IE9wdGlvbmFsbHkgYSB2YWx1ZSBmb3IgdGhlIGVkZ2Ugb2Zmc2V0LCB1c2VkIGJ5IGVhY2ggc2Nyb2xsIG1ldGhvZCBieSBkZWZhdWx0LiBcblx0ICogICAgICAgIElnbm9yZWQgaWYgbnVsbCBvciB1bmRlZmluZWQuXG5cdCAqIEByZXR1cm5zIEEgc2Nyb2xsZXIgb2JqZWN0LCBzaW1pbGFyIHRvIGB6ZW5zY3JvbGxgIGJ1dCBjb250cm9sbGluZyB0aGUgcHJvdmlkZWQgZWxlbWVudC5cblx0ICovXG5cdHplbnNjcm9sbC5jcmVhdGVTY3JvbGxlciA9IGZ1bmN0aW9uIChzY3JvbGxDb250YWluZXIsIGRlZmF1bHREdXJhdGlvbiwgZWRnZU9mZnNldCkge1xuXHRcdHJldHVybiBtYWtlU2Nyb2xsZXIoe1xuXHRcdFx0Ym9keTogc2Nyb2xsQ29udGFpbmVyLFxuXHRcdFx0dG9ZOiBmdW5jdGlvbiAoeSkgeyBzY3JvbGxDb250YWluZXIuc2Nyb2xsVG9wID0geSB9LFxuXHRcdFx0Z2V0WTogZnVuY3Rpb24gKCkgeyByZXR1cm4gc2Nyb2xsQ29udGFpbmVyLnNjcm9sbFRvcCB9LFxuXHRcdFx0Z2V0SGVpZ2h0OiBmdW5jdGlvbiAoKSB7IHJldHVybiBNYXRoLm1pbihzY3JvbGxDb250YWluZXIuY2xpZW50SGVpZ2h0LCB3aW5kb3cuaW5uZXJIZWlnaHQgfHwgZG9jRWxlbS5jbGllbnRIZWlnaHQpIH0sXG5cdFx0XHRnZXRUb3BPZjogZnVuY3Rpb24gKGVsZW0pIHsgcmV0dXJuIGVsZW0ub2Zmc2V0VG9wIH1cblx0XHR9LCBkZWZhdWx0RHVyYXRpb24sIGVkZ2VPZmZzZXQpXG5cdH1cblxuXG5cdC8vIEF1dG9tYXRpYyBsaW5rLXNtb290aGluZyBvbiBhY2hvcnNcblx0Ly8gRXhjbHVkZSBJRTgtIG9yIHdoZW4gbmF0aXZlIGlzIGVuYWJsZWQgb3IgWmVuc2Nyb2xsIGF1dG8tIGlzIGRpc2FibGVkXG5cdGlmIChcImFkZEV2ZW50TGlzdGVuZXJcIiBpbiB3aW5kb3cgJiYgIXdpbmRvdy5ub1plbnNtb290aCAmJiAhaXNOYXRpdmVTbW9vdGhTY3JvbGxFbmFibGVkT24oZG9jdW1lbnQuYm9keSkpIHtcblxuXG5cdFx0dmFyIGlzU2Nyb2xsUmVzdG9yYXRpb25TdXBwb3J0ZWQgPSBcInNjcm9sbFJlc3RvcmF0aW9uXCIgaW4gaGlzdG9yeVxuXG5cdFx0Ly8gT24gZmlyc3QgbG9hZCAmIHJlZnJlc2ggbWFrZSBzdXJlIHRoZSBicm93c2VyIHJlc3RvcmVzIHRoZSBwb3NpdGlvbiBmaXJzdFxuXHRcdGlmIChpc1Njcm9sbFJlc3RvcmF0aW9uU3VwcG9ydGVkKSB7XG5cdFx0XHRoaXN0b3J5LnNjcm9sbFJlc3RvcmF0aW9uID0gXCJhdXRvXCJcblx0XHR9XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcImxvYWRcIiwgZnVuY3Rpb24gKCkge1xuXG5cdFx0XHRpZiAoaXNTY3JvbGxSZXN0b3JhdGlvblN1cHBvcnRlZCkge1xuXHRcdFx0XHQvLyBTZXQgaXQgdG8gbWFudWFsXG5cdFx0XHRcdHNldFRpbWVvdXQoZnVuY3Rpb24gKCkgeyBoaXN0b3J5LnNjcm9sbFJlc3RvcmF0aW9uID0gXCJtYW51YWxcIiB9LCA5KVxuXHRcdFx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcInBvcHN0YXRlXCIsIGZ1bmN0aW9uIChldmVudCkge1xuXHRcdFx0XHRcdGlmIChldmVudC5zdGF0ZSAmJiBcInplbnNjcm9sbFlcIiBpbiBldmVudC5zdGF0ZSkge1xuXHRcdFx0XHRcdFx0emVuc2Nyb2xsLnRvWShldmVudC5zdGF0ZS56ZW5zY3JvbGxZKVxuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSwgZmFsc2UpXG5cdFx0XHR9XG5cblx0XHRcdC8vIEFkZCBlZGdlIG9mZnNldCBvbiBmaXJzdCBsb2FkIGlmIG5lY2Vzc2FyeVxuXHRcdFx0Ly8gVGhpcyBtYXkgbm90IHdvcmsgb24gSUUgKG9yIG9sZGVyIGNvbXB1dGVyPykgYXMgaXQgcmVxdWlyZXMgbW9yZSB0aW1lb3V0LCBhcm91bmQgMTAwIG1zXG5cdFx0XHRpZiAod2luZG93LmxvY2F0aW9uLmhhc2gpIHtcblx0XHRcdFx0c2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG5cdFx0XHRcdFx0Ly8gQWRqdXN0bWVudCBpcyBvbmx5IG5lZWRlZCBpZiB0aGVyZSBpcyBhbiBlZGdlIG9mZnNldDpcblx0XHRcdFx0XHR2YXIgZWRnZU9mZnNldCA9IHplbnNjcm9sbC5zZXR1cCgpLmVkZ2VPZmZzZXRcblx0XHRcdFx0XHRpZiAoZWRnZU9mZnNldCkge1xuXHRcdFx0XHRcdFx0dmFyIHRhcmdldEVsZW0gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCh3aW5kb3cubG9jYXRpb24uaHJlZi5zcGxpdChcIiNcIilbMV0pXG5cdFx0XHRcdFx0XHRpZiAodGFyZ2V0RWxlbSkge1xuXHRcdFx0XHRcdFx0XHR2YXIgdGFyZ2V0WSA9IE1hdGgubWF4KDAsIHplbnNjcm9sbC5nZXRUb3BPZih0YXJnZXRFbGVtKSAtIGVkZ2VPZmZzZXQpXG5cdFx0XHRcdFx0XHRcdHZhciBkaWZmID0gemVuc2Nyb2xsLmdldFkoKSAtIHRhcmdldFlcblx0XHRcdFx0XHRcdFx0Ly8gT25seSBkbyB0aGUgYWRqdXN0bWVudCBpZiB0aGUgYnJvd3NlciBpcyB2ZXJ5IGNsb3NlIHRvIHRoZSBlbGVtZW50OlxuXHRcdFx0XHRcdFx0XHRpZiAoMCA8PSBkaWZmICYmIGRpZmYgPCA5ICkge1xuXHRcdFx0XHRcdFx0XHRcdHdpbmRvdy5zY3JvbGxUbygwLCB0YXJnZXRZKVxuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9LCA5KVxuXHRcdFx0fVxuXG5cdFx0fSwgZmFsc2UpXG5cblx0XHQvLyBIYW5kbGluZyBjbGlja3Mgb24gYW5jaG9yc1xuXHRcdHZhciBSRV9ub1plbnNtb290aCA9IG5ldyBSZWdFeHAoXCIoXnxcXFxccylub1plbnNtb290aChcXFxcc3wkKVwiKVxuXHRcdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgZnVuY3Rpb24gKGV2ZW50KSB7XG5cdFx0XHR2YXIgYW5jaG9yID0gZXZlbnQudGFyZ2V0XG5cdFx0XHR3aGlsZSAoYW5jaG9yICYmIGFuY2hvci50YWdOYW1lICE9PSBcIkFcIikge1xuXHRcdFx0XHRhbmNob3IgPSBhbmNob3IucGFyZW50Tm9kZVxuXHRcdFx0fVxuXHRcdFx0Ly8gTGV0IHRoZSBicm93c2VyIGhhbmRsZSB0aGUgY2xpY2sgaWYgaXQgd2Fzbid0IHdpdGggdGhlIHByaW1hcnkgYnV0dG9uLCBvciB3aXRoIHNvbWUgbW9kaWZpZXIga2V5czpcblx0XHRcdGlmICghYW5jaG9yIHx8IGV2ZW50LndoaWNoICE9PSAxIHx8IGV2ZW50LnNoaWZ0S2V5IHx8IGV2ZW50Lm1ldGFLZXkgfHwgZXZlbnQuY3RybEtleSB8fCBldmVudC5hbHRLZXkpIHtcblx0XHRcdFx0cmV0dXJuXG5cdFx0XHR9XG5cdFx0XHQvLyBTYXZlIHRoZSBjdXJyZW50IHNjcm9sbGluZyBwb3NpdGlvbiBzbyBpdCBjYW4gYmUgdXNlZCBmb3Igc2Nyb2xsIHJlc3RvcmF0aW9uOlxuXHRcdFx0aWYgKGlzU2Nyb2xsUmVzdG9yYXRpb25TdXBwb3J0ZWQpIHtcblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRoaXN0b3J5LnJlcGxhY2VTdGF0ZSh7IHplbnNjcm9sbFk6IHplbnNjcm9sbC5nZXRZKCkgfSwgXCJcIilcblx0XHRcdFx0fSBjYXRjaCAoZSkge1xuXHRcdFx0XHRcdC8vIEF2b2lkIHRoZSBDaHJvbWUgU2VjdXJpdHkgZXhjZXB0aW9uIG9uIGZpbGUgcHJvdG9jb2wsIGUuZy4sIGZpbGU6Ly9pbmRleC5odG1sXG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdC8vIEZpbmQgdGhlIHJlZmVyZW5jZWQgSUQ6XG5cdFx0XHR2YXIgaHJlZiA9IGFuY2hvci5nZXRBdHRyaWJ1dGUoXCJocmVmXCIpIHx8IFwiXCJcblx0XHRcdGlmIChocmVmLmluZGV4T2YoXCIjXCIpID09PSAwICYmICFSRV9ub1plbnNtb290aC50ZXN0KGFuY2hvci5jbGFzc05hbWUpKSB7XG5cdFx0XHRcdHZhciB0YXJnZXRZID0gMFxuXHRcdFx0XHR2YXIgdGFyZ2V0RWxlbSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGhyZWYuc3Vic3RyaW5nKDEpKVxuXHRcdFx0XHRpZiAoaHJlZiAhPT0gXCIjXCIpIHtcblx0XHRcdFx0XHRpZiAoIXRhcmdldEVsZW0pIHtcblx0XHRcdFx0XHRcdC8vIExldCB0aGUgYnJvd3NlciBoYW5kbGUgdGhlIGNsaWNrIGlmIHRoZSB0YXJnZXQgSUQgaXMgbm90IGZvdW5kLlxuXHRcdFx0XHRcdFx0cmV0dXJuXG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdHRhcmdldFkgPSB6ZW5zY3JvbGwuZ2V0VG9wT2YodGFyZ2V0RWxlbSlcblx0XHRcdFx0fVxuXHRcdFx0XHRldmVudC5wcmV2ZW50RGVmYXVsdCgpXG5cdFx0XHRcdC8vIEJ5IGRlZmF1bHQgdHJpZ2dlciB0aGUgYnJvd3NlcidzIGBoYXNoY2hhbmdlYCBldmVudC4uLlxuXHRcdFx0XHR2YXIgb25Eb25lID0gZnVuY3Rpb24gKCkgeyB3aW5kb3cubG9jYXRpb24gPSBocmVmIH1cblx0XHRcdFx0Ly8gLi4udW5sZXNzIHRoZXJlIGlzIGFuIGVkZ2Ugb2Zmc2V0IHNwZWNpZmllZFxuXHRcdFx0XHR2YXIgZWRnZU9mZnNldCA9IHplbnNjcm9sbC5zZXR1cCgpLmVkZ2VPZmZzZXRcblx0XHRcdFx0aWYgKGVkZ2VPZmZzZXQpIHtcblx0XHRcdFx0XHR0YXJnZXRZID0gTWF0aC5tYXgoMCwgdGFyZ2V0WSAtIGVkZ2VPZmZzZXQpXG5cdFx0XHRcdFx0b25Eb25lID0gZnVuY3Rpb24gKCkgeyBoaXN0b3J5LnB1c2hTdGF0ZShudWxsLCBcIlwiLCBocmVmKSB9XG5cdFx0XHRcdH1cblx0XHRcdFx0emVuc2Nyb2xsLnRvWSh0YXJnZXRZLCBudWxsLCBvbkRvbmUpXG5cdFx0XHR9XG5cdFx0fSwgZmFsc2UpXG5cblx0fVxuXG5cblx0cmV0dXJuIHplbnNjcm9sbFxuXG5cbn0pKTtcblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vfi96ZW5zY3JvbGwvemVuc2Nyb2xsLmpzXG4vLyBtb2R1bGUgaWQgPSAxXG4vLyBtb2R1bGUgY2h1bmtzID0gMCJdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); + +/***/ }), +/* 2 */ +/* unknown exports provided */ +/* all exports used */ +/*!******************************!*\ + !*** ./src/js/build-html.js ***! + \******************************/ +/***/ (function(module, exports) { + +eval("/**\n * This file is responsible for building the DOM and updating DOM state.\n *\n * @author Tim Scanlin\n */\n\nmodule.exports = function (options) {\n var forEach = [].forEach\n var some = [].some\n var body = document.body\n var currentlyHighlighting = true\n var SPACE_CHAR = ' '\n\n /**\n * Create link and list elements.\n * @param {Object} d\n * @param {HTMLElement} container\n * @return {HTMLElement}\n */\n function createEl (d, container) {\n var link = container.appendChild(createLink(d))\n if (d.children.length) {\n var list = createList(d.isCollapsed)\n d.children.forEach(function (child) {\n createEl(child, list)\n })\n link.appendChild(list)\n }\n }\n\n /**\n * Render nested heading array data into a given selector.\n * @param {String} selector\n * @param {Array} data\n * @return {HTMLElement}\n */\n function render (selector, data) {\n var collapsed = false\n var container = createList(collapsed)\n\n data.forEach(function (d) {\n createEl(d, container)\n })\n\n var parent = document.querySelector(selector)\n\n // Return if no parent is found.\n if (parent === null) {\n return\n }\n\n // Remove existing child if it exists.\n if (parent.firstChild) {\n parent.removeChild(parent.firstChild)\n }\n\n // Append the Elements that have been created;\n return parent.appendChild(container)\n }\n\n /**\n * Create link element.\n * @param {Object} data\n * @return {HTMLElement}\n */\n function createLink (data) {\n var item = document.createElement('li')\n var a = document.createElement('a')\n if (options.listItemClass) {\n item.setAttribute('class', options.listItemClass)\n }\n if (options.includeHtml && data.childNodes.length) {\n forEach.call(data.childNodes, function (node) {\n a.appendChild(node.cloneNode(true))\n })\n } else {\n // Default behavior.\n a.textContent = data.textContent\n }\n a.setAttribute('href', '#' + data.id)\n a.setAttribute('class', options.linkClass +\n SPACE_CHAR + 'node-name--' + data.nodeName +\n SPACE_CHAR + options.extraLinkClasses)\n item.appendChild(a)\n return item\n }\n\n /**\n * Create list element.\n * @param {Boolean} isCollapsed\n * @return {HTMLElement}\n */\n function createList (isCollapsed) {\n var list = document.createElement('ul')\n var classes = options.listClass +\n SPACE_CHAR + options.extraListClasses\n if (isCollapsed) {\n classes += SPACE_CHAR + options.collapsibleClass\n classes += SPACE_CHAR + options.isCollapsedClass\n }\n list.setAttribute('class', classes)\n return list\n }\n\n /**\n * Update fixed sidebar class.\n * @return {HTMLElement}\n */\n function updateFixedSidebarClass () {\n var top = document.documentElement.scrollTop || body.scrollTop\n var posFixedEl = document.querySelector(options.positionFixedSelector)\n\n if (options.fixedSidebarOffset === 'auto') {\n options.fixedSidebarOffset = document.querySelector(options.tocSelector).offsetTop\n }\n\n if (top > options.fixedSidebarOffset) {\n if (posFixedEl.className.indexOf(options.positionFixedClass) === -1) {\n posFixedEl.className += SPACE_CHAR + options.positionFixedClass\n }\n } else {\n posFixedEl.className = posFixedEl.className.split(SPACE_CHAR + options.positionFixedClass).join('')\n }\n }\n\n /**\n * Update TOC highlighting and collpased groupings.\n */\n function updateToc (headingsArray) {\n var top = document.documentElement.scrollTop || body.scrollTop\n\n // Add fixed class at offset;\n if (options.positionFixedSelector) {\n updateFixedSidebarClass()\n }\n\n // Get the top most heading currently visible on the page so we know what to highlight.\n var headings = headingsArray\n var topHeader\n // Using some instead of each so that we can escape early.\n if (currentlyHighlighting &&\n document.querySelector(options.tocSelector) !== null &&\n headings.length > 0) {\n some.call(headings, function (heading, i) {\n if (heading.offsetTop > top + options.headingsOffset + 10) {\n // Don't allow negative index value.\n var index = (i === 0) ? i : i - 1\n topHeader = headings[index]\n return true\n } else if (i === headings.length - 1) {\n // This allows scrolling for the last heading on the page.\n topHeader = headings[headings.length - 1]\n return true\n }\n })\n\n // Remove the active class from the other tocLinks.\n var tocLinks = document.querySelector(options.tocSelector)\n .querySelectorAll('.' + options.linkClass)\n forEach.call(tocLinks, function (tocLink) {\n tocLink.className = tocLink.className.split(SPACE_CHAR + options.activeLinkClass).join('')\n })\n\n // Add the active class to the active tocLink.\n var activeTocLink = document.querySelector(options.tocSelector)\n .querySelector('.' + options.linkClass +\n '.node-name--' + topHeader.nodeName +\n '[href=\"#' + topHeader.id + '\"]')\n activeTocLink.className += SPACE_CHAR + options.activeLinkClass\n\n var tocLists = document.querySelector(options.tocSelector)\n .querySelectorAll('.' + options.listClass + '.' + options.collapsibleClass)\n\n // Collapse the other collapsible lists.\n forEach.call(tocLists, function (list) {\n var collapsedClass = SPACE_CHAR + options.isCollapsedClass\n if (list.className.indexOf(collapsedClass) === -1) {\n list.className += SPACE_CHAR + options.isCollapsedClass\n }\n })\n\n // Expand the active link's collapsible list and its sibling if applicable.\n if (activeTocLink.nextSibling) {\n activeTocLink.nextSibling.className = activeTocLink.nextSibling.className.split(SPACE_CHAR + options.isCollapsedClass).join('')\n }\n removeCollapsedFromParents(activeTocLink.parentNode.parentNode)\n }\n }\n\n /**\n * Remove collpased class from parent elements.\n * @param {HTMLElement} element\n * @return {HTMLElement}\n */\n function removeCollapsedFromParents (element) {\n if (element.className.indexOf(options.collapsibleClass) !== -1) {\n element.className = element.className.split(SPACE_CHAR + options.isCollapsedClass).join('')\n return removeCollapsedFromParents(element.parentNode.parentNode)\n }\n return element\n }\n\n /**\n * Disable TOC Animation when a link is clicked.\n * @param {Event} event\n */\n function disableTocAnimation (event) {\n var target = event.target || event.srcElement\n if (typeof target.className !== 'string' || target.className.indexOf(options.linkClass) === -1) {\n return\n }\n // Bind to tocLink clicks to temporarily disable highlighting\n // while smoothScroll is animating.\n currentlyHighlighting = false\n }\n\n /**\n * Enable TOC Animation.\n */\n function enableTocAnimation () {\n currentlyHighlighting = true\n }\n\n return {\n enableTocAnimation: enableTocAnimation,\n disableTocAnimation: disableTocAnimation,\n render: render,\n updateToc: updateToc\n }\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9idWlsZC1odG1sLmpzPzdkMDEiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGlzIGZpbGUgaXMgcmVzcG9uc2libGUgZm9yIGJ1aWxkaW5nIHRoZSBET00gYW5kIHVwZGF0aW5nIERPTSBzdGF0ZS5cbiAqXG4gKiBAYXV0aG9yIFRpbSBTY2FubGluXG4gKi9cblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICB2YXIgZm9yRWFjaCA9IFtdLmZvckVhY2hcbiAgdmFyIHNvbWUgPSBbXS5zb21lXG4gIHZhciBib2R5ID0gZG9jdW1lbnQuYm9keVxuICB2YXIgY3VycmVudGx5SGlnaGxpZ2h0aW5nID0gdHJ1ZVxuICB2YXIgU1BBQ0VfQ0hBUiA9ICcgJ1xuXG4gIC8qKlxuICAgKiBDcmVhdGUgbGluayBhbmQgbGlzdCBlbGVtZW50cy5cbiAgICogQHBhcmFtIHtPYmplY3R9IGRcbiAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gY29udGFpbmVyXG4gICAqIEByZXR1cm4ge0hUTUxFbGVtZW50fVxuICAgKi9cbiAgZnVuY3Rpb24gY3JlYXRlRWwgKGQsIGNvbnRhaW5lcikge1xuICAgIHZhciBsaW5rID0gY29udGFpbmVyLmFwcGVuZENoaWxkKGNyZWF0ZUxpbmsoZCkpXG4gICAgaWYgKGQuY2hpbGRyZW4ubGVuZ3RoKSB7XG4gICAgICB2YXIgbGlzdCA9IGNyZWF0ZUxpc3QoZC5pc0NvbGxhcHNlZClcbiAgICAgIGQuY2hpbGRyZW4uZm9yRWFjaChmdW5jdGlvbiAoY2hpbGQpIHtcbiAgICAgICAgY3JlYXRlRWwoY2hpbGQsIGxpc3QpXG4gICAgICB9KVxuICAgICAgbGluay5hcHBlbmRDaGlsZChsaXN0KVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZW5kZXIgbmVzdGVkIGhlYWRpbmcgYXJyYXkgZGF0YSBpbnRvIGEgZ2l2ZW4gc2VsZWN0b3IuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzZWxlY3RvclxuICAgKiBAcGFyYW0ge0FycmF5fSBkYXRhXG4gICAqIEByZXR1cm4ge0hUTUxFbGVtZW50fVxuICAgKi9cbiAgZnVuY3Rpb24gcmVuZGVyIChzZWxlY3RvciwgZGF0YSkge1xuICAgIHZhciBjb2xsYXBzZWQgPSBmYWxzZVxuICAgIHZhciBjb250YWluZXIgPSBjcmVhdGVMaXN0KGNvbGxhcHNlZClcblxuICAgIGRhdGEuZm9yRWFjaChmdW5jdGlvbiAoZCkge1xuICAgICAgY3JlYXRlRWwoZCwgY29udGFpbmVyKVxuICAgIH0pXG5cbiAgICB2YXIgcGFyZW50ID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihzZWxlY3RvcilcblxuICAgIC8vIFJldHVybiBpZiBubyBwYXJlbnQgaXMgZm91bmQuXG4gICAgaWYgKHBhcmVudCA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGNoaWxkIGlmIGl0IGV4aXN0cy5cbiAgICBpZiAocGFyZW50LmZpcnN0Q2hpbGQpIHtcbiAgICAgIHBhcmVudC5yZW1vdmVDaGlsZChwYXJlbnQuZmlyc3RDaGlsZClcbiAgICB9XG5cbiAgICAvLyBBcHBlbmQgdGhlIEVsZW1lbnRzIHRoYXQgaGF2ZSBiZWVuIGNyZWF0ZWQ7XG4gICAgcmV0dXJuIHBhcmVudC5hcHBlbmRDaGlsZChjb250YWluZXIpXG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGxpbmsgZWxlbWVudC5cbiAgICogQHBhcmFtIHtPYmplY3R9IGRhdGFcbiAgICogQHJldHVybiB7SFRNTEVsZW1lbnR9XG4gICAqL1xuICBmdW5jdGlvbiBjcmVhdGVMaW5rIChkYXRhKSB7XG4gICAgdmFyIGl0ZW0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdsaScpXG4gICAgdmFyIGEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJylcbiAgICBpZiAob3B0aW9ucy5saXN0SXRlbUNsYXNzKSB7XG4gICAgICBpdGVtLnNldEF0dHJpYnV0ZSgnY2xhc3MnLCBvcHRpb25zLmxpc3RJdGVtQ2xhc3MpXG4gICAgfVxuICAgIGlmIChvcHRpb25zLmluY2x1ZGVIdG1sICYmIGRhdGEuY2hpbGROb2Rlcy5sZW5ndGgpIHtcbiAgICAgIGZvckVhY2guY2FsbChkYXRhLmNoaWxkTm9kZXMsIGZ1bmN0aW9uIChub2RlKSB7XG4gICAgICAgIGEuYXBwZW5kQ2hpbGQobm9kZS5jbG9uZU5vZGUodHJ1ZSkpXG4gICAgICB9KVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBEZWZhdWx0IGJlaGF2aW9yLlxuICAgICAgYS50ZXh0Q29udGVudCA9IGRhdGEudGV4dENvbnRlbnRcbiAgICB9XG4gICAgYS5zZXRBdHRyaWJ1dGUoJ2hyZWYnLCAnIycgKyBkYXRhLmlkKVxuICAgIGEuc2V0QXR0cmlidXRlKCdjbGFzcycsIG9wdGlvbnMubGlua0NsYXNzICtcbiAgICAgIFNQQUNFX0NIQVIgKyAnbm9kZS1uYW1lLS0nICsgZGF0YS5ub2RlTmFtZSArXG4gICAgICBTUEFDRV9DSEFSICsgb3B0aW9ucy5leHRyYUxpbmtDbGFzc2VzKVxuICAgIGl0ZW0uYXBwZW5kQ2hpbGQoYSlcbiAgICByZXR1cm4gaXRlbVxuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBsaXN0IGVsZW1lbnQuXG4gICAqIEBwYXJhbSB7Qm9vbGVhbn0gaXNDb2xsYXBzZWRcbiAgICogQHJldHVybiB7SFRNTEVsZW1lbnR9XG4gICAqL1xuICBmdW5jdGlvbiBjcmVhdGVMaXN0IChpc0NvbGxhcHNlZCkge1xuICAgIHZhciBsaXN0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndWwnKVxuICAgIHZhciBjbGFzc2VzID0gb3B0aW9ucy5saXN0Q2xhc3MgK1xuICAgICAgU1BBQ0VfQ0hBUiArIG9wdGlvbnMuZXh0cmFMaXN0Q2xhc3Nlc1xuICAgIGlmIChpc0NvbGxhcHNlZCkge1xuICAgICAgY2xhc3NlcyArPSBTUEFDRV9DSEFSICsgb3B0aW9ucy5jb2xsYXBzaWJsZUNsYXNzXG4gICAgICBjbGFzc2VzICs9IFNQQUNFX0NIQVIgKyBvcHRpb25zLmlzQ29sbGFwc2VkQ2xhc3NcbiAgICB9XG4gICAgbGlzdC5zZXRBdHRyaWJ1dGUoJ2NsYXNzJywgY2xhc3NlcylcbiAgICByZXR1cm4gbGlzdFxuICB9XG5cbiAgLyoqXG4gICAqIFVwZGF0ZSBmaXhlZCBzaWRlYmFyIGNsYXNzLlxuICAgKiBAcmV0dXJuIHtIVE1MRWxlbWVudH1cbiAgICovXG4gIGZ1bmN0aW9uIHVwZGF0ZUZpeGVkU2lkZWJhckNsYXNzICgpIHtcbiAgICB2YXIgdG9wID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcCB8fCBib2R5LnNjcm9sbFRvcFxuICAgIHZhciBwb3NGaXhlZEVsID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihvcHRpb25zLnBvc2l0aW9uRml4ZWRTZWxlY3RvcilcblxuICAgIGlmIChvcHRpb25zLmZpeGVkU2lkZWJhck9mZnNldCA9PT0gJ2F1dG8nKSB7XG4gICAgICBvcHRpb25zLmZpeGVkU2lkZWJhck9mZnNldCA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3Iob3B0aW9ucy50b2NTZWxlY3Rvcikub2Zmc2V0VG9wXG4gICAgfVxuXG4gICAgaWYgKHRvcCA+IG9wdGlvbnMuZml4ZWRTaWRlYmFyT2Zmc2V0KSB7XG4gICAgICBpZiAocG9zRml4ZWRFbC5jbGFzc05hbWUuaW5kZXhPZihvcHRpb25zLnBvc2l0aW9uRml4ZWRDbGFzcykgPT09IC0xKSB7XG4gICAgICAgIHBvc0ZpeGVkRWwuY2xhc3NOYW1lICs9IFNQQUNFX0NIQVIgKyBvcHRpb25zLnBvc2l0aW9uRml4ZWRDbGFzc1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBwb3NGaXhlZEVsLmNsYXNzTmFtZSA9IHBvc0ZpeGVkRWwuY2xhc3NOYW1lLnNwbGl0KFNQQUNFX0NIQVIgKyBvcHRpb25zLnBvc2l0aW9uRml4ZWRDbGFzcykuam9pbignJylcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVXBkYXRlIFRPQyBoaWdobGlnaHRpbmcgYW5kIGNvbGxwYXNlZCBncm91cGluZ3MuXG4gICAqL1xuICBmdW5jdGlvbiB1cGRhdGVUb2MgKGhlYWRpbmdzQXJyYXkpIHtcbiAgICB2YXIgdG9wID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcCB8fCBib2R5LnNjcm9sbFRvcFxuXG4gICAgLy8gQWRkIGZpeGVkIGNsYXNzIGF0IG9mZnNldDtcbiAgICBpZiAob3B0aW9ucy5wb3NpdGlvbkZpeGVkU2VsZWN0b3IpIHtcbiAgICAgIHVwZGF0ZUZpeGVkU2lkZWJhckNsYXNzKClcbiAgICB9XG5cbiAgICAvLyBHZXQgdGhlIHRvcCBtb3N0IGhlYWRpbmcgY3VycmVudGx5IHZpc2libGUgb24gdGhlIHBhZ2Ugc28gd2Uga25vdyB3aGF0IHRvIGhpZ2hsaWdodC5cbiAgICB2YXIgaGVhZGluZ3MgPSBoZWFkaW5nc0FycmF5XG4gICAgdmFyIHRvcEhlYWRlclxuICAgIC8vIFVzaW5nIHNvbWUgaW5zdGVhZCBvZiBlYWNoIHNvIHRoYXQgd2UgY2FuIGVzY2FwZSBlYXJseS5cbiAgICBpZiAoY3VycmVudGx5SGlnaGxpZ2h0aW5nICYmXG4gICAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKG9wdGlvbnMudG9jU2VsZWN0b3IpICE9PSBudWxsICYmXG4gICAgICBoZWFkaW5ncy5sZW5ndGggPiAwKSB7XG4gICAgICBzb21lLmNhbGwoaGVhZGluZ3MsIGZ1bmN0aW9uIChoZWFkaW5nLCBpKSB7XG4gICAgICAgIGlmIChoZWFkaW5nLm9mZnNldFRvcCA+IHRvcCArIG9wdGlvbnMuaGVhZGluZ3NPZmZzZXQgKyAxMCkge1xuICAgICAgICAgIC8vIERvbid0IGFsbG93IG5lZ2F0aXZlIGluZGV4IHZhbHVlLlxuICAgICAgICAgIHZhciBpbmRleCA9IChpID09PSAwKSA/IGkgOiBpIC0gMVxuICAgICAgICAgIHRvcEhlYWRlciA9IGhlYWRpbmdzW2luZGV4XVxuICAgICAgICAgIHJldHVybiB0cnVlXG4gICAgICAgIH0gZWxzZSBpZiAoaSA9PT0gaGVhZGluZ3MubGVuZ3RoIC0gMSkge1xuICAgICAgICAgIC8vIFRoaXMgYWxsb3dzIHNjcm9sbGluZyBmb3IgdGhlIGxhc3QgaGVhZGluZyBvbiB0aGUgcGFnZS5cbiAgICAgICAgICB0b3BIZWFkZXIgPSBoZWFkaW5nc1toZWFkaW5ncy5sZW5ndGggLSAxXVxuICAgICAgICAgIHJldHVybiB0cnVlXG4gICAgICAgIH1cbiAgICAgIH0pXG5cbiAgICAgIC8vIFJlbW92ZSB0aGUgYWN0aXZlIGNsYXNzIGZyb20gdGhlIG90aGVyIHRvY0xpbmtzLlxuICAgICAgdmFyIHRvY0xpbmtzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihvcHRpb25zLnRvY1NlbGVjdG9yKVxuICAgICAgICAucXVlcnlTZWxlY3RvckFsbCgnLicgKyBvcHRpb25zLmxpbmtDbGFzcylcbiAgICAgIGZvckVhY2guY2FsbCh0b2NMaW5rcywgZnVuY3Rpb24gKHRvY0xpbmspIHtcbiAgICAgICAgdG9jTGluay5jbGFzc05hbWUgPSB0b2NMaW5rLmNsYXNzTmFtZS5zcGxpdChTUEFDRV9DSEFSICsgb3B0aW9ucy5hY3RpdmVMaW5rQ2xhc3MpLmpvaW4oJycpXG4gICAgICB9KVxuXG4gICAgICAvLyBBZGQgdGhlIGFjdGl2ZSBjbGFzcyB0byB0aGUgYWN0aXZlIHRvY0xpbmsuXG4gICAgICB2YXIgYWN0aXZlVG9jTGluayA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3Iob3B0aW9ucy50b2NTZWxlY3RvcilcbiAgICAgICAgLnF1ZXJ5U2VsZWN0b3IoJy4nICsgb3B0aW9ucy5saW5rQ2xhc3MgK1xuICAgICAgICAgICcubm9kZS1uYW1lLS0nICsgdG9wSGVhZGVyLm5vZGVOYW1lICtcbiAgICAgICAgICAnW2hyZWY9XCIjJyArIHRvcEhlYWRlci5pZCArICdcIl0nKVxuICAgICAgYWN0aXZlVG9jTGluay5jbGFzc05hbWUgKz0gU1BBQ0VfQ0hBUiArIG9wdGlvbnMuYWN0aXZlTGlua0NsYXNzXG5cbiAgICAgIHZhciB0b2NMaXN0cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3Iob3B0aW9ucy50b2NTZWxlY3RvcilcbiAgICAgICAgLnF1ZXJ5U2VsZWN0b3JBbGwoJy4nICsgb3B0aW9ucy5saXN0Q2xhc3MgKyAnLicgKyBvcHRpb25zLmNvbGxhcHNpYmxlQ2xhc3MpXG5cbiAgICAgIC8vIENvbGxhcHNlIHRoZSBvdGhlciBjb2xsYXBzaWJsZSBsaXN0cy5cbiAgICAgIGZvckVhY2guY2FsbCh0b2NMaXN0cywgZnVuY3Rpb24gKGxpc3QpIHtcbiAgICAgICAgdmFyIGNvbGxhcHNlZENsYXNzID0gU1BBQ0VfQ0hBUiArIG9wdGlvbnMuaXNDb2xsYXBzZWRDbGFzc1xuICAgICAgICBpZiAobGlzdC5jbGFzc05hbWUuaW5kZXhPZihjb2xsYXBzZWRDbGFzcykgPT09IC0xKSB7XG4gICAgICAgICAgbGlzdC5jbGFzc05hbWUgKz0gU1BBQ0VfQ0hBUiArIG9wdGlvbnMuaXNDb2xsYXBzZWRDbGFzc1xuICAgICAgICB9XG4gICAgICB9KVxuXG4gICAgICAvLyBFeHBhbmQgdGhlIGFjdGl2ZSBsaW5rJ3MgY29sbGFwc2libGUgbGlzdCBhbmQgaXRzIHNpYmxpbmcgaWYgYXBwbGljYWJsZS5cbiAgICAgIGlmIChhY3RpdmVUb2NMaW5rLm5leHRTaWJsaW5nKSB7XG4gICAgICAgIGFjdGl2ZVRvY0xpbmsubmV4dFNpYmxpbmcuY2xhc3NOYW1lID0gYWN0aXZlVG9jTGluay5uZXh0U2libGluZy5jbGFzc05hbWUuc3BsaXQoU1BBQ0VfQ0hBUiArIG9wdGlvbnMuaXNDb2xsYXBzZWRDbGFzcykuam9pbignJylcbiAgICAgIH1cbiAgICAgIHJlbW92ZUNvbGxhcHNlZEZyb21QYXJlbnRzKGFjdGl2ZVRvY0xpbmsucGFyZW50Tm9kZS5wYXJlbnROb2RlKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZW1vdmUgY29sbHBhc2VkIGNsYXNzIGZyb20gcGFyZW50IGVsZW1lbnRzLlxuICAgKiBAcGFyYW0ge0hUTUxFbGVtZW50fSBlbGVtZW50XG4gICAqIEByZXR1cm4ge0hUTUxFbGVtZW50fVxuICAgKi9cbiAgZnVuY3Rpb24gcmVtb3ZlQ29sbGFwc2VkRnJvbVBhcmVudHMgKGVsZW1lbnQpIHtcbiAgICBpZiAoZWxlbWVudC5jbGFzc05hbWUuaW5kZXhPZihvcHRpb25zLmNvbGxhcHNpYmxlQ2xhc3MpICE9PSAtMSkge1xuICAgICAgZWxlbWVudC5jbGFzc05hbWUgPSBlbGVtZW50LmNsYXNzTmFtZS5zcGxpdChTUEFDRV9DSEFSICsgb3B0aW9ucy5pc0NvbGxhcHNlZENsYXNzKS5qb2luKCcnKVxuICAgICAgcmV0dXJuIHJlbW92ZUNvbGxhcHNlZEZyb21QYXJlbnRzKGVsZW1lbnQucGFyZW50Tm9kZS5wYXJlbnROb2RlKVxuICAgIH1cbiAgICByZXR1cm4gZWxlbWVudFxuICB9XG5cbiAgLyoqXG4gICAqIERpc2FibGUgVE9DIEFuaW1hdGlvbiB3aGVuIGEgbGluayBpcyBjbGlja2VkLlxuICAgKiBAcGFyYW0ge0V2ZW50fSBldmVudFxuICAgKi9cbiAgZnVuY3Rpb24gZGlzYWJsZVRvY0FuaW1hdGlvbiAoZXZlbnQpIHtcbiAgICB2YXIgdGFyZ2V0ID0gZXZlbnQudGFyZ2V0IHx8IGV2ZW50LnNyY0VsZW1lbnRcbiAgICBpZiAodHlwZW9mIHRhcmdldC5jbGFzc05hbWUgIT09ICdzdHJpbmcnIHx8IHRhcmdldC5jbGFzc05hbWUuaW5kZXhPZihvcHRpb25zLmxpbmtDbGFzcykgPT09IC0xKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG4gICAgLy8gQmluZCB0byB0b2NMaW5rIGNsaWNrcyB0byB0ZW1wb3JhcmlseSBkaXNhYmxlIGhpZ2hsaWdodGluZ1xuICAgIC8vIHdoaWxlIHNtb290aFNjcm9sbCBpcyBhbmltYXRpbmcuXG4gICAgY3VycmVudGx5SGlnaGxpZ2h0aW5nID0gZmFsc2VcbiAgfVxuXG4gIC8qKlxuICAgKiBFbmFibGUgVE9DIEFuaW1hdGlvbi5cbiAgICovXG4gIGZ1bmN0aW9uIGVuYWJsZVRvY0FuaW1hdGlvbiAoKSB7XG4gICAgY3VycmVudGx5SGlnaGxpZ2h0aW5nID0gdHJ1ZVxuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBlbmFibGVUb2NBbmltYXRpb246IGVuYWJsZVRvY0FuaW1hdGlvbixcbiAgICBkaXNhYmxlVG9jQW5pbWF0aW9uOiBkaXNhYmxlVG9jQW5pbWF0aW9uLFxuICAgIHJlbmRlcjogcmVuZGVyLFxuICAgIHVwZGF0ZVRvYzogdXBkYXRlVG9jXG4gIH1cbn1cblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vc3JjL2pzL2J1aWxkLWh0bWwuanNcbi8vIG1vZHVsZSBpZCA9IDJcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOyIsInNvdXJjZVJvb3QiOiIifQ=="); + +/***/ }), +/* 3 */ +/* unknown exports provided */ +/* all exports used */ +/*!***********************************!*\ + !*** ./src/js/default-options.js ***! + \***********************************/ +/***/ (function(module, exports) { + +eval("module.exports = {\n // Where to render the table of contents.\n tocSelector: '.js-toc',\n // Where to grab the headings to build the table of contents.\n contentSelector: '.js-toc-content',\n // Which headings to grab inside of the contentSelector element.\n headingSelector: 'h1, h2, h3',\n // Headings that match the ignoreSelector will be skipped.\n ignoreSelector: '.js-toc-ignore',\n // Main class to add to links.\n linkClass: 'toc-link',\n // Extra classes to add to links.\n extraLinkClasses: '',\n // Class to add to active links,\n // the link corresponding to the top most heading on the page.\n activeLinkClass: 'is-active-link',\n // Main class to add to lists.\n listClass: 'toc-list',\n // Extra classes to add to lists.\n extraListClasses: '',\n // Class that gets added when a list should be collapsed.\n isCollapsedClass: 'is-collapsed',\n // Class that gets added when a list should be able\n // to be collapsed but isn't necessarily collpased.\n collapsibleClass: 'is-collapsible',\n // Class to add to list items.\n listItemClass: 'toc-list-item',\n // How many heading levels should not be collpased.\n // For example, number 6 will show everything since\n // there are only 6 heading levels and number 0 will collpase them all.\n // The sections that are hidden will open\n // and close as you scroll to headings within them.\n collapseDepth: 0,\n // Smooth scrolling enabled.\n smoothScroll: true,\n // Smooth scroll duration.\n smoothScrollDuration: 420,\n // Callback for scroll end (requires: smoothScroll).\n scrollEndCallback: function (e) {},\n // Headings offset between the headings and the top of the document.\n headingsOffset: 0,\n // Timeout between events firing to make sure it's\n // not too rapid (for performance reasons).\n throttleTimeout: 50,\n // Element to add the positionFixedClass to.\n positionFixedSelector: null,\n // Fixed position class to add to make sidebar fixed after scrolling\n // down past the fixedSidebarOffset.\n positionFixedClass: 'is-position-fixed',\n // fixedSidebarOffset can be any number but by default is set\n // to auto which sets the fixedSidebarOffset to the sidebar\n // element's offsetTop from the top of the document on init.\n fixedSidebarOffset: 'auto',\n // includeHtml can be set to true to include the HTML markup from the\n // heading node instead of just including the textContent.\n includeHtml: false\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9kZWZhdWx0LW9wdGlvbnMuanM/MTg1MSJdLCJzb3VyY2VzQ29udGVudCI6WyJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgLy8gV2hlcmUgdG8gcmVuZGVyIHRoZSB0YWJsZSBvZiBjb250ZW50cy5cbiAgdG9jU2VsZWN0b3I6ICcuanMtdG9jJyxcbiAgLy8gV2hlcmUgdG8gZ3JhYiB0aGUgaGVhZGluZ3MgdG8gYnVpbGQgdGhlIHRhYmxlIG9mIGNvbnRlbnRzLlxuICBjb250ZW50U2VsZWN0b3I6ICcuanMtdG9jLWNvbnRlbnQnLFxuICAvLyBXaGljaCBoZWFkaW5ncyB0byBncmFiIGluc2lkZSBvZiB0aGUgY29udGVudFNlbGVjdG9yIGVsZW1lbnQuXG4gIGhlYWRpbmdTZWxlY3RvcjogJ2gxLCBoMiwgaDMnLFxuICAvLyBIZWFkaW5ncyB0aGF0IG1hdGNoIHRoZSBpZ25vcmVTZWxlY3RvciB3aWxsIGJlIHNraXBwZWQuXG4gIGlnbm9yZVNlbGVjdG9yOiAnLmpzLXRvYy1pZ25vcmUnLFxuICAvLyBNYWluIGNsYXNzIHRvIGFkZCB0byBsaW5rcy5cbiAgbGlua0NsYXNzOiAndG9jLWxpbmsnLFxuICAvLyBFeHRyYSBjbGFzc2VzIHRvIGFkZCB0byBsaW5rcy5cbiAgZXh0cmFMaW5rQ2xhc3NlczogJycsXG4gIC8vIENsYXNzIHRvIGFkZCB0byBhY3RpdmUgbGlua3MsXG4gIC8vIHRoZSBsaW5rIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHRvcCBtb3N0IGhlYWRpbmcgb24gdGhlIHBhZ2UuXG4gIGFjdGl2ZUxpbmtDbGFzczogJ2lzLWFjdGl2ZS1saW5rJyxcbiAgLy8gTWFpbiBjbGFzcyB0byBhZGQgdG8gbGlzdHMuXG4gIGxpc3RDbGFzczogJ3RvYy1saXN0JyxcbiAgLy8gRXh0cmEgY2xhc3NlcyB0byBhZGQgdG8gbGlzdHMuXG4gIGV4dHJhTGlzdENsYXNzZXM6ICcnLFxuICAvLyBDbGFzcyB0aGF0IGdldHMgYWRkZWQgd2hlbiBhIGxpc3Qgc2hvdWxkIGJlIGNvbGxhcHNlZC5cbiAgaXNDb2xsYXBzZWRDbGFzczogJ2lzLWNvbGxhcHNlZCcsXG4gIC8vIENsYXNzIHRoYXQgZ2V0cyBhZGRlZCB3aGVuIGEgbGlzdCBzaG91bGQgYmUgYWJsZVxuICAvLyB0byBiZSBjb2xsYXBzZWQgYnV0IGlzbid0IG5lY2Vzc2FyaWx5IGNvbGxwYXNlZC5cbiAgY29sbGFwc2libGVDbGFzczogJ2lzLWNvbGxhcHNpYmxlJyxcbiAgLy8gQ2xhc3MgdG8gYWRkIHRvIGxpc3QgaXRlbXMuXG4gIGxpc3RJdGVtQ2xhc3M6ICd0b2MtbGlzdC1pdGVtJyxcbiAgLy8gSG93IG1hbnkgaGVhZGluZyBsZXZlbHMgc2hvdWxkIG5vdCBiZSBjb2xscGFzZWQuXG4gIC8vIEZvciBleGFtcGxlLCBudW1iZXIgNiB3aWxsIHNob3cgZXZlcnl0aGluZyBzaW5jZVxuICAvLyB0aGVyZSBhcmUgb25seSA2IGhlYWRpbmcgbGV2ZWxzIGFuZCBudW1iZXIgMCB3aWxsIGNvbGxwYXNlIHRoZW0gYWxsLlxuICAvLyBUaGUgc2VjdGlvbnMgdGhhdCBhcmUgaGlkZGVuIHdpbGwgb3BlblxuICAvLyBhbmQgY2xvc2UgYXMgeW91IHNjcm9sbCB0byBoZWFkaW5ncyB3aXRoaW4gdGhlbS5cbiAgY29sbGFwc2VEZXB0aDogMCxcbiAgLy8gU21vb3RoIHNjcm9sbGluZyBlbmFibGVkLlxuICBzbW9vdGhTY3JvbGw6IHRydWUsXG4gIC8vIFNtb290aCBzY3JvbGwgZHVyYXRpb24uXG4gIHNtb290aFNjcm9sbER1cmF0aW9uOiA0MjAsXG4gIC8vIENhbGxiYWNrIGZvciBzY3JvbGwgZW5kIChyZXF1aXJlczogc21vb3RoU2Nyb2xsKS5cbiAgc2Nyb2xsRW5kQ2FsbGJhY2s6IGZ1bmN0aW9uIChlKSB7fSxcbiAgLy8gSGVhZGluZ3Mgb2Zmc2V0IGJldHdlZW4gdGhlIGhlYWRpbmdzIGFuZCB0aGUgdG9wIG9mIHRoZSBkb2N1bWVudC5cbiAgaGVhZGluZ3NPZmZzZXQ6IDAsXG4gIC8vIFRpbWVvdXQgYmV0d2VlbiBldmVudHMgZmlyaW5nIHRvIG1ha2Ugc3VyZSBpdCdzXG4gIC8vIG5vdCB0b28gcmFwaWQgKGZvciBwZXJmb3JtYW5jZSByZWFzb25zKS5cbiAgdGhyb3R0bGVUaW1lb3V0OiA1MCxcbiAgLy8gRWxlbWVudCB0byBhZGQgdGhlIHBvc2l0aW9uRml4ZWRDbGFzcyB0by5cbiAgcG9zaXRpb25GaXhlZFNlbGVjdG9yOiBudWxsLFxuICAvLyBGaXhlZCBwb3NpdGlvbiBjbGFzcyB0byBhZGQgdG8gbWFrZSBzaWRlYmFyIGZpeGVkIGFmdGVyIHNjcm9sbGluZ1xuICAvLyBkb3duIHBhc3QgdGhlIGZpeGVkU2lkZWJhck9mZnNldC5cbiAgcG9zaXRpb25GaXhlZENsYXNzOiAnaXMtcG9zaXRpb24tZml4ZWQnLFxuICAvLyBmaXhlZFNpZGViYXJPZmZzZXQgY2FuIGJlIGFueSBudW1iZXIgYnV0IGJ5IGRlZmF1bHQgaXMgc2V0XG4gIC8vIHRvIGF1dG8gd2hpY2ggc2V0cyB0aGUgZml4ZWRTaWRlYmFyT2Zmc2V0IHRvIHRoZSBzaWRlYmFyXG4gIC8vIGVsZW1lbnQncyBvZmZzZXRUb3AgZnJvbSB0aGUgdG9wIG9mIHRoZSBkb2N1bWVudCBvbiBpbml0LlxuICBmaXhlZFNpZGViYXJPZmZzZXQ6ICdhdXRvJyxcbiAgLy8gaW5jbHVkZUh0bWwgY2FuIGJlIHNldCB0byB0cnVlIHRvIGluY2x1ZGUgdGhlIEhUTUwgbWFya3VwIGZyb20gdGhlXG4gIC8vIGhlYWRpbmcgbm9kZSBpbnN0ZWFkIG9mIGp1c3QgaW5jbHVkaW5nIHRoZSB0ZXh0Q29udGVudC5cbiAgaW5jbHVkZUh0bWw6IGZhbHNlXG59XG5cblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL3NyYy9qcy9kZWZhdWx0LW9wdGlvbnMuanNcbi8vIG1vZHVsZSBpZCA9IDNcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); + +/***/ }), +/* 4 */ +/* unknown exports provided */ +/* all exports used */ +/*!*********************************!*\ + !*** ./src/js/parse-content.js ***! + \*********************************/ +/***/ (function(module, exports) { + +eval("/**\n * This file is responsible for parsing the content from the DOM and making\n * sure data is nested properly.\n *\n * @author Tim Scanlin\n */\n\nmodule.exports = function parseContent (options) {\n var reduce = [].reduce\n\n /**\n * Get the last item in an array and return a reference to it.\n * @param {Array} array\n * @return {Object}\n */\n function getLastItem (array) {\n return array[array.length - 1]\n }\n\n /**\n * Get heading level for a heading dom node.\n * @param {HTMLElement} heading\n * @return {Number}\n */\n function getHeadingLevel (heading) {\n return +heading.nodeName.split('H').join('')\n }\n\n /**\n * Get important properties from a heading element and store in a plain object.\n * @param {HTMLElement} heading\n * @return {Object}\n */\n function getHeadingObject (heading) {\n var obj = {\n id: heading.id,\n children: [],\n nodeName: heading.nodeName,\n headingLevel: getHeadingLevel(heading),\n textContent: heading.textContent.trim()\n }\n\n if (options.includeHtml) {\n obj.childNodes = heading.childNodes\n }\n\n return obj\n }\n\n /**\n * Add a node to the nested array.\n * @param {Object} node\n * @param {Array} nest\n * @return {Array}\n */\n function addNode (node, nest) {\n var obj = getHeadingObject(node)\n var level = getHeadingLevel(node)\n var array = nest\n var lastItem = getLastItem(array)\n var lastItemLevel = lastItem\n ? lastItem.headingLevel\n : 0\n var counter = level - lastItemLevel\n\n while (counter > 0) {\n lastItem = getLastItem(array)\n if (lastItem && lastItem.children !== undefined) {\n array = lastItem.children\n }\n counter--\n }\n\n if (level >= options.collapseDepth) {\n obj.isCollapsed = true\n }\n\n array.push(obj)\n return array\n }\n\n /**\n * Select headings in content area, exclude any selector in options.ignoreSelector\n * @param {String} contentSelector\n * @param {Array} headingSelector\n * @return {Array}\n */\n function selectHeadings (contentSelector, headingSelector) {\n var selectors = headingSelector\n if (options.ignoreSelector) {\n selectors = headingSelector.split(',')\n .map(function mapSelectors (selector) {\n return selector.trim() + ':not(' + options.ignoreSelector + ')'\n })\n }\n try {\n return document.querySelector(contentSelector)\n .querySelectorAll(selectors)\n } catch (e) {\n console.warn('Element not found: ' + contentSelector); // eslint-disable-line\n return null\n }\n }\n\n /**\n * Nest headings array into nested arrays with 'children' property.\n * @param {Array} headingsArray\n * @return {Object}\n */\n function nestHeadingsArray (headingsArray) {\n return reduce.call(headingsArray, function reducer (prev, curr) {\n var currentHeading = getHeadingObject(curr)\n\n addNode(currentHeading, prev.nest)\n return prev\n }, {\n nest: []\n })\n }\n\n return {\n nestHeadingsArray: nestHeadingsArray,\n selectHeadings: selectHeadings\n }\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9wYXJzZS1jb250ZW50LmpzPzg3ZjAiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGlzIGZpbGUgaXMgcmVzcG9uc2libGUgZm9yIHBhcnNpbmcgdGhlIGNvbnRlbnQgZnJvbSB0aGUgRE9NIGFuZCBtYWtpbmdcbiAqIHN1cmUgZGF0YSBpcyBuZXN0ZWQgcHJvcGVybHkuXG4gKlxuICogQGF1dGhvciBUaW0gU2NhbmxpblxuICovXG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gcGFyc2VDb250ZW50IChvcHRpb25zKSB7XG4gIHZhciByZWR1Y2UgPSBbXS5yZWR1Y2VcblxuICAvKipcbiAgICogR2V0IHRoZSBsYXN0IGl0ZW0gaW4gYW4gYXJyYXkgYW5kIHJldHVybiBhIHJlZmVyZW5jZSB0byBpdC5cbiAgICogQHBhcmFtIHtBcnJheX0gYXJyYXlcbiAgICogQHJldHVybiB7T2JqZWN0fVxuICAgKi9cbiAgZnVuY3Rpb24gZ2V0TGFzdEl0ZW0gKGFycmF5KSB7XG4gICAgcmV0dXJuIGFycmF5W2FycmF5Lmxlbmd0aCAtIDFdXG4gIH1cblxuICAvKipcbiAgICogR2V0IGhlYWRpbmcgbGV2ZWwgZm9yIGEgaGVhZGluZyBkb20gbm9kZS5cbiAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gaGVhZGluZ1xuICAgKiBAcmV0dXJuIHtOdW1iZXJ9XG4gICAqL1xuICBmdW5jdGlvbiBnZXRIZWFkaW5nTGV2ZWwgKGhlYWRpbmcpIHtcbiAgICByZXR1cm4gK2hlYWRpbmcubm9kZU5hbWUuc3BsaXQoJ0gnKS5qb2luKCcnKVxuICB9XG5cbiAgLyoqXG4gICAqIEdldCBpbXBvcnRhbnQgcHJvcGVydGllcyBmcm9tIGEgaGVhZGluZyBlbGVtZW50IGFuZCBzdG9yZSBpbiBhIHBsYWluIG9iamVjdC5cbiAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gaGVhZGluZ1xuICAgKiBAcmV0dXJuIHtPYmplY3R9XG4gICAqL1xuICBmdW5jdGlvbiBnZXRIZWFkaW5nT2JqZWN0IChoZWFkaW5nKSB7XG4gICAgdmFyIG9iaiA9IHtcbiAgICAgIGlkOiBoZWFkaW5nLmlkLFxuICAgICAgY2hpbGRyZW46IFtdLFxuICAgICAgbm9kZU5hbWU6IGhlYWRpbmcubm9kZU5hbWUsXG4gICAgICBoZWFkaW5nTGV2ZWw6IGdldEhlYWRpbmdMZXZlbChoZWFkaW5nKSxcbiAgICAgIHRleHRDb250ZW50OiBoZWFkaW5nLnRleHRDb250ZW50LnRyaW0oKVxuICAgIH1cblxuICAgIGlmIChvcHRpb25zLmluY2x1ZGVIdG1sKSB7XG4gICAgICBvYmouY2hpbGROb2RlcyA9IGhlYWRpbmcuY2hpbGROb2Rlc1xuICAgIH1cblxuICAgIHJldHVybiBvYmpcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGQgYSBub2RlIHRvIHRoZSBuZXN0ZWQgYXJyYXkuXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBub2RlXG4gICAqIEBwYXJhbSB7QXJyYXl9IG5lc3RcbiAgICogQHJldHVybiB7QXJyYXl9XG4gICAqL1xuICBmdW5jdGlvbiBhZGROb2RlIChub2RlLCBuZXN0KSB7XG4gICAgdmFyIG9iaiA9IGdldEhlYWRpbmdPYmplY3Qobm9kZSlcbiAgICB2YXIgbGV2ZWwgPSBnZXRIZWFkaW5nTGV2ZWwobm9kZSlcbiAgICB2YXIgYXJyYXkgPSBuZXN0XG4gICAgdmFyIGxhc3RJdGVtID0gZ2V0TGFzdEl0ZW0oYXJyYXkpXG4gICAgdmFyIGxhc3RJdGVtTGV2ZWwgPSBsYXN0SXRlbVxuICAgICAgPyBsYXN0SXRlbS5oZWFkaW5nTGV2ZWxcbiAgICAgIDogMFxuICAgIHZhciBjb3VudGVyID0gbGV2ZWwgLSBsYXN0SXRlbUxldmVsXG5cbiAgICB3aGlsZSAoY291bnRlciA+IDApIHtcbiAgICAgIGxhc3RJdGVtID0gZ2V0TGFzdEl0ZW0oYXJyYXkpXG4gICAgICBpZiAobGFzdEl0ZW0gJiYgbGFzdEl0ZW0uY2hpbGRyZW4gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBhcnJheSA9IGxhc3RJdGVtLmNoaWxkcmVuXG4gICAgICB9XG4gICAgICBjb3VudGVyLS1cbiAgICB9XG5cbiAgICBpZiAobGV2ZWwgPj0gb3B0aW9ucy5jb2xsYXBzZURlcHRoKSB7XG4gICAgICBvYmouaXNDb2xsYXBzZWQgPSB0cnVlXG4gICAgfVxuXG4gICAgYXJyYXkucHVzaChvYmopXG4gICAgcmV0dXJuIGFycmF5XG4gIH1cblxuICAvKipcbiAgICogU2VsZWN0IGhlYWRpbmdzIGluIGNvbnRlbnQgYXJlYSwgZXhjbHVkZSBhbnkgc2VsZWN0b3IgaW4gb3B0aW9ucy5pZ25vcmVTZWxlY3RvclxuICAgKiBAcGFyYW0ge1N0cmluZ30gY29udGVudFNlbGVjdG9yXG4gICAqIEBwYXJhbSB7QXJyYXl9IGhlYWRpbmdTZWxlY3RvclxuICAgKiBAcmV0dXJuIHtBcnJheX1cbiAgICovXG4gIGZ1bmN0aW9uIHNlbGVjdEhlYWRpbmdzIChjb250ZW50U2VsZWN0b3IsIGhlYWRpbmdTZWxlY3Rvcikge1xuICAgIHZhciBzZWxlY3RvcnMgPSBoZWFkaW5nU2VsZWN0b3JcbiAgICBpZiAob3B0aW9ucy5pZ25vcmVTZWxlY3Rvcikge1xuICAgICAgc2VsZWN0b3JzID0gaGVhZGluZ1NlbGVjdG9yLnNwbGl0KCcsJylcbiAgICAgICAgLm1hcChmdW5jdGlvbiBtYXBTZWxlY3RvcnMgKHNlbGVjdG9yKSB7XG4gICAgICAgICAgcmV0dXJuIHNlbGVjdG9yLnRyaW0oKSArICc6bm90KCcgKyBvcHRpb25zLmlnbm9yZVNlbGVjdG9yICsgJyknXG4gICAgICAgIH0pXG4gICAgfVxuICAgIHRyeSB7XG4gICAgICByZXR1cm4gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcihjb250ZW50U2VsZWN0b3IpXG4gICAgICAgIC5xdWVyeVNlbGVjdG9yQWxsKHNlbGVjdG9ycylcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBjb25zb2xlLndhcm4oJ0VsZW1lbnQgbm90IGZvdW5kOiAnICsgY29udGVudFNlbGVjdG9yKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZVxuICAgICAgcmV0dXJuIG51bGxcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogTmVzdCBoZWFkaW5ncyBhcnJheSBpbnRvIG5lc3RlZCBhcnJheXMgd2l0aCAnY2hpbGRyZW4nIHByb3BlcnR5LlxuICAgKiBAcGFyYW0ge0FycmF5fSBoZWFkaW5nc0FycmF5XG4gICAqIEByZXR1cm4ge09iamVjdH1cbiAgICovXG4gIGZ1bmN0aW9uIG5lc3RIZWFkaW5nc0FycmF5IChoZWFkaW5nc0FycmF5KSB7XG4gICAgcmV0dXJuIHJlZHVjZS5jYWxsKGhlYWRpbmdzQXJyYXksIGZ1bmN0aW9uIHJlZHVjZXIgKHByZXYsIGN1cnIpIHtcbiAgICAgIHZhciBjdXJyZW50SGVhZGluZyA9IGdldEhlYWRpbmdPYmplY3QoY3VycilcblxuICAgICAgYWRkTm9kZShjdXJyZW50SGVhZGluZywgcHJldi5uZXN0KVxuICAgICAgcmV0dXJuIHByZXZcbiAgICB9LCB7XG4gICAgICBuZXN0OiBbXVxuICAgIH0pXG4gIH1cblxuICByZXR1cm4ge1xuICAgIG5lc3RIZWFkaW5nc0FycmF5OiBuZXN0SGVhZGluZ3NBcnJheSxcbiAgICBzZWxlY3RIZWFkaW5nczogc2VsZWN0SGVhZGluZ3NcbiAgfVxufVxuXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9zcmMvanMvcGFyc2UtY29udGVudC5qc1xuLy8gbW9kdWxlIGlkID0gNFxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); + +/***/ }), +/* 5 */ +/* unknown exports provided */ +/* all exports used */ +/*!*************************!*\ + !*** ./src/js/index.js ***! + \*************************/ +/***/ (function(module, exports, __webpack_require__) { + +eval("/* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**\n * Tocbot\n * Tocbot creates a toble of contents based on HTML headings on a page,\n * this allows users to easily jump to different sections of the document.\n * Tocbot was inspired by tocify (https://gregfranko.com/jquery.tocify.js/).\n * The main differences are that it works natively without any need for jquery or jquery UI).\n *\n * @author Tim Scanlin\n */\n\n/* globals define */\n\n(function (root, factory) {\n if (true) {\n !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory(root)),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?\n\t\t\t\t(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))\n } else if (typeof exports === 'object') {\n module.exports = factory(root)\n } else {\n root.tocbot = factory(root)\n }\n})(typeof global !== 'undefined' ? global : this.window || this.global, function (root) {\n 'use strict'\n\n // Default options.\n var defaultOptions = __webpack_require__(/*! ./default-options.js */ 3)\n // Object to store current options.\n var options = {}\n // Object for public APIs.\n var tocbot = {}\n\n var BuildHtml = __webpack_require__(/*! ./build-html.js */ 2)\n var ParseContent = __webpack_require__(/*! ./parse-content.js */ 4)\n // Keep these variables at top scope once options are passed in.\n var buildHtml\n var parseContent\n\n // Just return if its not a browser.\n if (typeof window === 'undefined') {\n return\n }\n var supports = !!root.document.querySelector && !!root.addEventListener // Feature test\n var headingsArray\n\n // From: https://github.com/Raynos/xtend\n var hasOwnProperty = Object.prototype.hasOwnProperty\n function extend () {\n var target = {}\n for (var i = 0; i < arguments.length; i++) {\n var source = arguments[i]\n for (var key in source) {\n if (hasOwnProperty.call(source, key)) {\n target[key] = source[key]\n }\n }\n }\n return target\n }\n\n // From: https://remysharp.com/2010/07/21/throttling-function-calls\n function throttle (fn, threshhold, scope) {\n threshhold || (threshhold = 250)\n var last\n var deferTimer\n return function () {\n var context = scope || this\n var now = +new Date()\n var args = arguments\n if (last && now < last + threshhold) {\n // hold on to it\n clearTimeout(deferTimer)\n deferTimer = setTimeout(function () {\n last = now\n fn.apply(context, args)\n }, threshhold)\n } else {\n last = now\n fn.apply(context, args)\n }\n }\n }\n\n /**\n * Destroy tocbot.\n */\n tocbot.destroy = function () {\n // Clear HTML.\n try {\n document.querySelector(options.tocSelector).innerHTML = ''\n } catch (e) {\n console.warn('Element not found: ' + options.tocSelector); // eslint-disable-line\n }\n\n // Remove event listeners.\n document.removeEventListener('scroll', this._scrollListener, false)\n document.removeEventListener('resize', this._scrollListener, false)\n if (buildHtml) {\n document.removeEventListener('click', this._clickListener, false)\n }\n }\n\n /**\n * Initialize tocbot.\n * @param {object} customOptions\n */\n tocbot.init = function (customOptions) {\n // feature test\n if (!supports) {\n return\n }\n\n // Merge defaults with user options.\n // Set to options variable at the top.\n options = extend(defaultOptions, customOptions || {})\n this.options = options\n this.state = {}\n\n // Init smooth scroll if enabled (default).\n if (options.smoothScroll) {\n tocbot.zenscroll = __webpack_require__(/*! zenscroll */ 1)\n tocbot.zenscroll.setup(options.smoothScrollDuration)\n }\n\n // Pass options to these modules.\n buildHtml = BuildHtml(options)\n parseContent = ParseContent(options)\n\n // For testing purposes.\n this._buildHtml = buildHtml\n this._parseContent = parseContent\n\n // Destroy it if it exists first.\n tocbot.destroy()\n\n // Get headings array.\n headingsArray = parseContent.selectHeadings(options.contentSelector, options.headingSelector)\n // Return if no headings are found.\n if (headingsArray === null) {\n return\n }\n\n // Build nested headings array.\n var nestedHeadingsObj = parseContent.nestHeadingsArray(headingsArray)\n var nestedHeadings = nestedHeadingsObj.nest\n\n // Render.\n buildHtml.render(options.tocSelector, nestedHeadings)\n\n // Update Sidebar and bind listeners.\n this._scrollListener = throttle(function (e) {\n buildHtml.updateToc(headingsArray)\n var isTop = e && e.target && e.target.scrollingElement && e.target.scrollingElement.scrollTop === 0\n if ((e && e.eventPhase === 0) || isTop) {\n buildHtml.enableTocAnimation()\n buildHtml.updateToc(headingsArray)\n if (options.scrollEndCallback) {\n options.scrollEndCallback(e)\n }\n }\n }, options.throttleTimeout)\n this._scrollListener()\n document.addEventListener('scroll', this._scrollListener, false)\n document.addEventListener('resize', this._scrollListener, false)\n\n // Bind click listeners to disable animation.\n this._clickListener = throttle(function (event) {\n if (options.smoothScroll) {\n buildHtml.disableTocAnimation(event)\n }\n buildHtml.updateToc(headingsArray)\n }, options.throttleTimeout)\n document.addEventListener('click', this._clickListener, false)\n\n return this\n }\n\n /**\n * Refresh tocbot.\n */\n tocbot.refresh = function (customOptions) {\n tocbot.destroy()\n tocbot.init(customOptions || this.options)\n }\n\n // Make tocbot available globally.\n root.tocbot = tocbot\n\n return tocbot\n})\n\n/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(/*! ./../../~/webpack/buildin/global.js */ 0)))//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9pbmRleC5qcz9iYzY2Il0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVG9jYm90XG4gKiBUb2Nib3QgY3JlYXRlcyBhIHRvYmxlIG9mIGNvbnRlbnRzIGJhc2VkIG9uIEhUTUwgaGVhZGluZ3Mgb24gYSBwYWdlLFxuICogdGhpcyBhbGxvd3MgdXNlcnMgdG8gZWFzaWx5IGp1bXAgdG8gZGlmZmVyZW50IHNlY3Rpb25zIG9mIHRoZSBkb2N1bWVudC5cbiAqIFRvY2JvdCB3YXMgaW5zcGlyZWQgYnkgdG9jaWZ5IChodHRwOi8vZ3JlZ2ZyYW5rby5jb20vanF1ZXJ5LnRvY2lmeS5qcy8pLlxuICogVGhlIG1haW4gZGlmZmVyZW5jZXMgYXJlIHRoYXQgaXQgd29ya3MgbmF0aXZlbHkgd2l0aG91dCBhbnkgbmVlZCBmb3IganF1ZXJ5IG9yIGpxdWVyeSBVSSkuXG4gKlxuICogQGF1dGhvciBUaW0gU2NhbmxpblxuICovXG5cbi8qIGdsb2JhbHMgZGVmaW5lICovXG5cbihmdW5jdGlvbiAocm9vdCwgZmFjdG9yeSkge1xuICBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgZGVmaW5lKFtdLCBmYWN0b3J5KHJvb3QpKVxuICB9IGVsc2UgaWYgKHR5cGVvZiBleHBvcnRzID09PSAnb2JqZWN0Jykge1xuICAgIG1vZHVsZS5leHBvcnRzID0gZmFjdG9yeShyb290KVxuICB9IGVsc2Uge1xuICAgIHJvb3QudG9jYm90ID0gZmFjdG9yeShyb290KVxuICB9XG59KSh0eXBlb2YgZ2xvYmFsICE9PSAndW5kZWZpbmVkJyA/IGdsb2JhbCA6IHRoaXMud2luZG93IHx8IHRoaXMuZ2xvYmFsLCBmdW5jdGlvbiAocm9vdCkge1xuICAndXNlIHN0cmljdCdcblxuICAvLyBEZWZhdWx0IG9wdGlvbnMuXG4gIHZhciBkZWZhdWx0T3B0aW9ucyA9IHJlcXVpcmUoJy4vZGVmYXVsdC1vcHRpb25zLmpzJylcbiAgLy8gT2JqZWN0IHRvIHN0b3JlIGN1cnJlbnQgb3B0aW9ucy5cbiAgdmFyIG9wdGlvbnMgPSB7fVxuICAvLyBPYmplY3QgZm9yIHB1YmxpYyBBUElzLlxuICB2YXIgdG9jYm90ID0ge31cblxuICB2YXIgQnVpbGRIdG1sID0gcmVxdWlyZSgnLi9idWlsZC1odG1sLmpzJylcbiAgdmFyIFBhcnNlQ29udGVudCA9IHJlcXVpcmUoJy4vcGFyc2UtY29udGVudC5qcycpXG4gIC8vIEtlZXAgdGhlc2UgdmFyaWFibGVzIGF0IHRvcCBzY29wZSBvbmNlIG9wdGlvbnMgYXJlIHBhc3NlZCBpbi5cbiAgdmFyIGJ1aWxkSHRtbFxuICB2YXIgcGFyc2VDb250ZW50XG5cbiAgLy8gSnVzdCByZXR1cm4gaWYgaXRzIG5vdCBhIGJyb3dzZXIuXG4gIGlmICh0eXBlb2Ygd2luZG93ID09PSAndW5kZWZpbmVkJykge1xuICAgIHJldHVyblxuICB9XG4gIHZhciBzdXBwb3J0cyA9ICEhcm9vdC5kb2N1bWVudC5xdWVyeVNlbGVjdG9yICYmICEhcm9vdC5hZGRFdmVudExpc3RlbmVyIC8vIEZlYXR1cmUgdGVzdFxuICB2YXIgaGVhZGluZ3NBcnJheVxuXG4gIC8vIEZyb206IGh0dHBzOi8vZ2l0aHViLmNvbS9SYXlub3MveHRlbmRcbiAgdmFyIGhhc093blByb3BlcnR5ID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eVxuICBmdW5jdGlvbiBleHRlbmQgKCkge1xuICAgIHZhciB0YXJnZXQgPSB7fVxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYXJndW1lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgc291cmNlID0gYXJndW1lbnRzW2ldXG4gICAgICBmb3IgKHZhciBrZXkgaW4gc291cmNlKSB7XG4gICAgICAgIGlmIChoYXNPd25Qcm9wZXJ0eS5jYWxsKHNvdXJjZSwga2V5KSkge1xuICAgICAgICAgIHRhcmdldFtrZXldID0gc291cmNlW2tleV1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gdGFyZ2V0XG4gIH1cblxuICAvLyBGcm9tOiBodHRwczovL3JlbXlzaGFycC5jb20vMjAxMC8wNy8yMS90aHJvdHRsaW5nLWZ1bmN0aW9uLWNhbGxzXG4gIGZ1bmN0aW9uIHRocm90dGxlIChmbiwgdGhyZXNoaG9sZCwgc2NvcGUpIHtcbiAgICB0aHJlc2hob2xkIHx8ICh0aHJlc2hob2xkID0gMjUwKVxuICAgIHZhciBsYXN0XG4gICAgdmFyIGRlZmVyVGltZXJcbiAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgdmFyIGNvbnRleHQgPSBzY29wZSB8fCB0aGlzXG4gICAgICB2YXIgbm93ID0gK25ldyBEYXRlKClcbiAgICAgIHZhciBhcmdzID0gYXJndW1lbnRzXG4gICAgICBpZiAobGFzdCAmJiBub3cgPCBsYXN0ICsgdGhyZXNoaG9sZCkge1xuICAgICAgICAvLyBob2xkIG9uIHRvIGl0XG4gICAgICAgIGNsZWFyVGltZW91dChkZWZlclRpbWVyKVxuICAgICAgICBkZWZlclRpbWVyID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgbGFzdCA9IG5vd1xuICAgICAgICAgIGZuLmFwcGx5KGNvbnRleHQsIGFyZ3MpXG4gICAgICAgIH0sIHRocmVzaGhvbGQpXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBsYXN0ID0gbm93XG4gICAgICAgIGZuLmFwcGx5KGNvbnRleHQsIGFyZ3MpXG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIERlc3Ryb3kgdG9jYm90LlxuICAgKi9cbiAgdG9jYm90LmRlc3Ryb3kgPSBmdW5jdGlvbiAoKSB7XG4gICAgLy8gQ2xlYXIgSFRNTC5cbiAgICB0cnkge1xuICAgICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvcihvcHRpb25zLnRvY1NlbGVjdG9yKS5pbm5lckhUTUwgPSAnJ1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGNvbnNvbGUud2FybignRWxlbWVudCBub3QgZm91bmQ6ICcgKyBvcHRpb25zLnRvY1NlbGVjdG9yKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZVxuICAgIH1cblxuICAgIC8vIFJlbW92ZSBldmVudCBsaXN0ZW5lcnMuXG4gICAgZG9jdW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhpcy5fc2Nyb2xsTGlzdGVuZXIsIGZhbHNlKVxuICAgIGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsIHRoaXMuX3Njcm9sbExpc3RlbmVyLCBmYWxzZSlcbiAgICBpZiAoYnVpbGRIdG1sKSB7XG4gICAgICBkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCdjbGljaycsIHRoaXMuX2NsaWNrTGlzdGVuZXIsIGZhbHNlKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplIHRvY2JvdC5cbiAgICogQHBhcmFtIHtvYmplY3R9IGN1c3RvbU9wdGlvbnNcbiAgICovXG4gIHRvY2JvdC5pbml0ID0gZnVuY3Rpb24gKGN1c3RvbU9wdGlvbnMpIHtcbiAgICAvLyBmZWF0dXJlIHRlc3RcbiAgICBpZiAoIXN1cHBvcnRzKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBNZXJnZSBkZWZhdWx0cyB3aXRoIHVzZXIgb3B0aW9ucy5cbiAgICAvLyBTZXQgdG8gb3B0aW9ucyB2YXJpYWJsZSBhdCB0aGUgdG9wLlxuICAgIG9wdGlvbnMgPSBleHRlbmQoZGVmYXVsdE9wdGlvbnMsIGN1c3RvbU9wdGlvbnMgfHwge30pXG4gICAgdGhpcy5vcHRpb25zID0gb3B0aW9uc1xuICAgIHRoaXMuc3RhdGUgPSB7fVxuXG4gICAgLy8gSW5pdCBzbW9vdGggc2Nyb2xsIGlmIGVuYWJsZWQgKGRlZmF1bHQpLlxuICAgIGlmIChvcHRpb25zLnNtb290aFNjcm9sbCkge1xuICAgICAgdG9jYm90LnplbnNjcm9sbCA9IHJlcXVpcmUoJ3plbnNjcm9sbCcpXG4gICAgICB0b2Nib3QuemVuc2Nyb2xsLnNldHVwKG9wdGlvbnMuc21vb3RoU2Nyb2xsRHVyYXRpb24pXG4gICAgfVxuXG4gICAgLy8gUGFzcyBvcHRpb25zIHRvIHRoZXNlIG1vZHVsZXMuXG4gICAgYnVpbGRIdG1sID0gQnVpbGRIdG1sKG9wdGlvbnMpXG4gICAgcGFyc2VDb250ZW50ID0gUGFyc2VDb250ZW50KG9wdGlvbnMpXG5cbiAgICAvLyBGb3IgdGVzdGluZyBwdXJwb3Nlcy5cbiAgICB0aGlzLl9idWlsZEh0bWwgPSBidWlsZEh0bWxcbiAgICB0aGlzLl9wYXJzZUNvbnRlbnQgPSBwYXJzZUNvbnRlbnRcblxuICAgIC8vIERlc3Ryb3kgaXQgaWYgaXQgZXhpc3RzIGZpcnN0LlxuICAgIHRvY2JvdC5kZXN0cm95KClcblxuICAgIC8vIEdldCBoZWFkaW5ncyBhcnJheS5cbiAgICBoZWFkaW5nc0FycmF5ID0gcGFyc2VDb250ZW50LnNlbGVjdEhlYWRpbmdzKG9wdGlvbnMuY29udGVudFNlbGVjdG9yLCBvcHRpb25zLmhlYWRpbmdTZWxlY3RvcilcbiAgICAvLyBSZXR1cm4gaWYgbm8gaGVhZGluZ3MgYXJlIGZvdW5kLlxuICAgIGlmIChoZWFkaW5nc0FycmF5ID09PSBudWxsKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBCdWlsZCBuZXN0ZWQgaGVhZGluZ3MgYXJyYXkuXG4gICAgdmFyIG5lc3RlZEhlYWRpbmdzT2JqID0gcGFyc2VDb250ZW50Lm5lc3RIZWFkaW5nc0FycmF5KGhlYWRpbmdzQXJyYXkpXG4gICAgdmFyIG5lc3RlZEhlYWRpbmdzID0gbmVzdGVkSGVhZGluZ3NPYmoubmVzdFxuXG4gICAgLy8gUmVuZGVyLlxuICAgIGJ1aWxkSHRtbC5yZW5kZXIob3B0aW9ucy50b2NTZWxlY3RvciwgbmVzdGVkSGVhZGluZ3MpXG5cbiAgICAvLyBVcGRhdGUgU2lkZWJhciBhbmQgYmluZCBsaXN0ZW5lcnMuXG4gICAgdGhpcy5fc2Nyb2xsTGlzdGVuZXIgPSB0aHJvdHRsZShmdW5jdGlvbiAoZSkge1xuICAgICAgYnVpbGRIdG1sLnVwZGF0ZVRvYyhoZWFkaW5nc0FycmF5KVxuICAgICAgdmFyIGlzVG9wID0gZSAmJiBlLnRhcmdldCAmJiBlLnRhcmdldC5zY3JvbGxpbmdFbGVtZW50ICYmIGUudGFyZ2V0LnNjcm9sbGluZ0VsZW1lbnQuc2Nyb2xsVG9wID09PSAwXG4gICAgICBpZiAoKGUgJiYgZS5ldmVudFBoYXNlID09PSAwKSB8fCBpc1RvcCkge1xuICAgICAgICBidWlsZEh0bWwuZW5hYmxlVG9jQW5pbWF0aW9uKClcbiAgICAgICAgYnVpbGRIdG1sLnVwZGF0ZVRvYyhoZWFkaW5nc0FycmF5KVxuICAgICAgICBpZiAob3B0aW9ucy5zY3JvbGxFbmRDYWxsYmFjaykge1xuICAgICAgICAgIG9wdGlvbnMuc2Nyb2xsRW5kQ2FsbGJhY2soZSlcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sIG9wdGlvbnMudGhyb3R0bGVUaW1lb3V0KVxuICAgIHRoaXMuX3Njcm9sbExpc3RlbmVyKClcbiAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdzY3JvbGwnLCB0aGlzLl9zY3JvbGxMaXN0ZW5lciwgZmFsc2UpXG4gICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigncmVzaXplJywgdGhpcy5fc2Nyb2xsTGlzdGVuZXIsIGZhbHNlKVxuXG4gICAgLy8gQmluZCBjbGljayBsaXN0ZW5lcnMgdG8gZGlzYWJsZSBhbmltYXRpb24uXG4gICAgdGhpcy5fY2xpY2tMaXN0ZW5lciA9IHRocm90dGxlKGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgaWYgKG9wdGlvbnMuc21vb3RoU2Nyb2xsKSB7XG4gICAgICAgIGJ1aWxkSHRtbC5kaXNhYmxlVG9jQW5pbWF0aW9uKGV2ZW50KVxuICAgICAgfVxuICAgICAgYnVpbGRIdG1sLnVwZGF0ZVRvYyhoZWFkaW5nc0FycmF5KVxuICAgIH0sIG9wdGlvbnMudGhyb3R0bGVUaW1lb3V0KVxuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgdGhpcy5fY2xpY2tMaXN0ZW5lciwgZmFsc2UpXG5cbiAgICByZXR1cm4gdGhpc1xuICB9XG5cbiAgLyoqXG4gICAqIFJlZnJlc2ggdG9jYm90LlxuICAgKi9cbiAgdG9jYm90LnJlZnJlc2ggPSBmdW5jdGlvbiAoY3VzdG9tT3B0aW9ucykge1xuICAgIHRvY2JvdC5kZXN0cm95KClcbiAgICB0b2Nib3QuaW5pdChjdXN0b21PcHRpb25zIHx8IHRoaXMub3B0aW9ucylcbiAgfVxuXG4gIC8vIE1ha2UgdG9jYm90IGF2YWlsYWJsZSBnbG9iYWxseS5cbiAgcm9vdC50b2Nib3QgPSB0b2Nib3RcblxuICByZXR1cm4gdG9jYm90XG59KVxuXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9zcmMvanMvaW5kZXguanNcbi8vIG1vZHVsZSBpZCA9IDVcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBIiwic291cmNlUm9vdCI6IiJ9"); + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js new file mode 100644 index 00000000..4468ef4e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js @@ -0,0 +1 @@ +!function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=5)}([function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){var o,i,r;!function(n,l){i=[],o=l(),void 0!==(r="function"==typeof o?o.apply(t,i):o)&&(e.exports=r)}(0,function(){"use strict";var e=function(e){return"getComputedStyle"in window&&"smooth"===window.getComputedStyle(e)["scroll-behavior"]};if("undefined"==typeof window||!("document"in window))return{};var t=function(t,n,o){n=n||999,o||0===o||(o=9);var i,r=function(e){i=e},l=function(){clearTimeout(i),r(0)},s=function(e){return Math.max(0,t.getTopOf(e)-o)},c=function(o,i,s){if(l(),0===i||i&&i<0||e(t.body))t.toY(o),s&&s();else{var c=t.getY(),a=Math.max(0,o)-c,u=(new Date).getTime();i=i||Math.min(Math.abs(a),n),function e(){r(setTimeout(function(){var n=Math.min(1,((new Date).getTime()-u)/i),o=Math.max(0,Math.floor(c+a*(n<.5?2*n*n:n*(4-2*n)-1)));t.toY(o),n<1&&t.getHeight()+ou?a(e,n,i):l+o>f?c(l-u+o,n,i):i&&i()},d=function(e,n,o,i){c(Math.max(0,t.getTopOf(e)-t.getHeight()/2+(o||e.getBoundingClientRect().height/2)),n,i)};return{setup:function(e,t){return(0===e||e)&&(n=e),(0===t||t)&&(o=t),{defaultDuration:n,edgeOffset:o}},to:a,toY:c,intoView:u,center:d,stop:l,moving:function(){return!!i},getY:t.getY,getTopOf:t.getTopOf}},n=document.documentElement,o=function(){return window.scrollY||n.scrollTop},i=t({body:document.scrollingElement||document.body,toY:function(e){window.scrollTo(0,e)},getY:o,getHeight:function(){return window.innerHeight||n.clientHeight},getTopOf:function(e){return e.getBoundingClientRect().top+o()-n.offsetTop}});if(i.createScroller=function(e,o,i){return t({body:e,toY:function(t){e.scrollTop=t},getY:function(){return e.scrollTop},getHeight:function(){return Math.min(e.clientHeight,window.innerHeight||n.clientHeight)},getTopOf:function(e){return e.offsetTop}},o,i)},"addEventListener"in window&&!window.noZensmooth&&!e(document.body)){var r="scrollRestoration"in history;r&&(history.scrollRestoration="auto"),window.addEventListener("load",function(){r&&(setTimeout(function(){history.scrollRestoration="manual"},9),window.addEventListener("popstate",function(e){e.state&&"zenscrollY"in e.state&&i.toY(e.state.zenscrollY)},!1)),window.location.hash&&setTimeout(function(){var e=i.setup().edgeOffset;if(e){var t=document.getElementById(window.location.href.split("#")[1]);if(t){var n=Math.max(0,i.getTopOf(t)-e),o=i.getY()-n;0<=o&&o<9&&window.scrollTo(0,n)}}},9)},!1);var l=new RegExp("(^|\\s)noZensmooth(\\s|$)");window.addEventListener("click",function(e){for(var t=e.target;t&&"A"!==t.tagName;)t=t.parentNode;if(!(!t||1!==e.which||e.shiftKey||e.metaKey||e.ctrlKey||e.altKey)){if(r)try{history.replaceState({zenscrollY:i.getY()},"")}catch(e){}var n=t.getAttribute("href")||"";if(0===n.indexOf("#")&&!l.test(t.className)){var o=0,s=document.getElementById(n.substring(1));if("#"!==n){if(!s)return;o=i.getTopOf(s)}e.preventDefault();var c=function(){window.location=n},a=i.setup().edgeOffset;a&&(o=Math.max(0,o-a),c=function(){history.pushState(null,"",n)}),i.toY(o,null,c)}}},!1)}return i})},function(e,t){e.exports=function(e){function t(e,n){var r=n.appendChild(o(e));if(e.children.length){var l=i(e.isCollapsed);e.children.forEach(function(e){t(e,l)}),r.appendChild(l)}}function n(e,n){var o=i(!1);n.forEach(function(e){t(e,o)});var r=document.querySelector(e);if(null!==r)return r.firstChild&&r.removeChild(r.firstChild),r.appendChild(o)}function o(t){var n=document.createElement("li"),o=document.createElement("a");return e.listItemClass&&n.setAttribute("class",e.listItemClass),e.includeHtml&&t.childNodes.length?u.call(t.childNodes,function(e){o.appendChild(e.cloneNode(!0))}):o.textContent=t.textContent,o.setAttribute("href","#"+t.id),o.setAttribute("class",e.linkClass+p+"node-name--"+t.nodeName+p+e.extraLinkClasses),n.appendChild(o),n}function i(t){var n=document.createElement("ul"),o=e.listClass+p+e.extraListClasses;return t&&(o+=p+e.collapsibleClass,o+=p+e.isCollapsedClass),n.setAttribute("class",o),n}function r(){var t=document.documentElement.scrollTop||f.scrollTop,n=document.querySelector(e.positionFixedSelector);"auto"===e.fixedSidebarOffset&&(e.fixedSidebarOffset=document.querySelector(e.tocSelector).offsetTop),t>e.fixedSidebarOffset?-1===n.className.indexOf(e.positionFixedClass)&&(n.className+=p+e.positionFixedClass):n.className=n.className.split(p+e.positionFixedClass).join("")}function l(t){var n=document.documentElement.scrollTop||f.scrollTop;e.positionFixedSelector&&r();var o,i=t;if(m&&null!==document.querySelector(e.tocSelector)&&i.length>0){d.call(i,function(t,r){if(t.offsetTop>n+e.headingsOffset+10){return o=i[0===r?r:r-1],!0}if(r===i.length-1)return o=i[i.length-1],!0});var l=document.querySelector(e.tocSelector).querySelectorAll("."+e.linkClass);u.call(l,function(t){t.className=t.className.split(p+e.activeLinkClass).join("")});var c=document.querySelector(e.tocSelector).querySelector("."+e.linkClass+".node-name--"+o.nodeName+'[href="#'+o.id+'"]');c.className+=p+e.activeLinkClass;var a=document.querySelector(e.tocSelector).querySelectorAll("."+e.listClass+"."+e.collapsibleClass);u.call(a,function(t){var n=p+e.isCollapsedClass;-1===t.className.indexOf(n)&&(t.className+=p+e.isCollapsedClass)}),c.nextSibling&&(c.nextSibling.className=c.nextSibling.className.split(p+e.isCollapsedClass).join("")),s(c.parentNode.parentNode)}}function s(t){return-1!==t.className.indexOf(e.collapsibleClass)?(t.className=t.className.split(p+e.isCollapsedClass).join(""),s(t.parentNode.parentNode)):t}function c(t){var n=t.target||t.srcElement;"string"==typeof n.className&&-1!==n.className.indexOf(e.linkClass)&&(m=!1)}function a(){m=!0}var u=[].forEach,d=[].some,f=document.body,m=!0,p=" ";return{enableTocAnimation:a,disableTocAnimation:c,render:n,updateToc:l}}},function(e,t){e.exports={tocSelector:".js-toc",contentSelector:".js-toc-content",headingSelector:"h1, h2, h3",ignoreSelector:".js-toc-ignore",linkClass:"toc-link",extraLinkClasses:"",activeLinkClass:"is-active-link",listClass:"toc-list",extraListClasses:"",isCollapsedClass:"is-collapsed",collapsibleClass:"is-collapsible",listItemClass:"toc-list-item",collapseDepth:0,smoothScroll:!0,smoothScrollDuration:420,scrollEndCallback:function(e){},headingsOffset:0,throttleTimeout:50,positionFixedSelector:null,positionFixedClass:"is-position-fixed",fixedSidebarOffset:"auto",includeHtml:!1}},function(e,t){e.exports=function(e){function t(e){return e[e.length-1]}function n(e){return+e.nodeName.split("H").join("")}function o(t){var o={id:t.id,children:[],nodeName:t.nodeName,headingLevel:n(t),textContent:t.textContent.trim()};return e.includeHtml&&(o.childNodes=t.childNodes),o}function i(i,r){for(var l=o(i),s=n(i),c=r,a=t(c),u=a?a.headingLevel:0,d=s-u;d>0;)a=t(c),a&&void 0!==a.children&&(c=a.children),d--;return s>=e.collapseDepth&&(l.isCollapsed=!0),c.push(l),c}function r(t,n){var o=n;e.ignoreSelector&&(o=n.split(",").map(function(t){return t.trim()+":not("+e.ignoreSelector+")"}));try{return document.querySelector(t).querySelectorAll(o)}catch(e){return console.warn("Element not found: "+t),null}}function l(e){return s.call(e,function(e,t){return i(o(t),e.nest),e},{nest:[]})}var s=[].reduce;return{nestHeadingsArray:l,selectHeadings:r}}},function(e,t,n){(function(o){var i,r,l;!function(n,o){r=[],i=o(n),void 0!==(l="function"==typeof i?i.apply(t,r):i)&&(e.exports=l)}(void 0!==o?o:this.window||this.global,function(e){"use strict";function t(){for(var e={},t=0;t> and <>). + +[[test-engines-junit]] +==== JUnit Test Engines + +JUnit provides three `TestEngine` implementations. + +* `{junit-jupiter-engine}`: The core of JUnit Jupiter. +* `{junit-vintage-engine}`: A thin layer on top of JUnit 4 to allow running _vintage_ + tests (based on JUnit 3.8 and JUnit 4) with the JUnit Platform launcher infrastructure. +* `{junit-platform-suite-engine}`: Executes declarative suites of tests with the JUnit + Platform launcher infrastructure. + +[[test-engines-custom]] +==== Custom Test Engines + +You can contribute your own custom `{TestEngine}` by implementing the interfaces in the +{junit-platform-engine} module and _registering_ your engine. + +Every `TestEngine` must provide its own _unique ID_, _discover_ tests from an +`EngineDiscoveryRequest`, and _execute_ those tests according to an `ExecutionRequest`. + +[WARNING] +.The `junit-` unique ID prefix is reserved for TestEngines from the JUnit Team +==== +The JUnit Platform `Launcher` enforces that only `TestEngine` implementations published +by the JUnit Team may use the `junit-` prefix for their `TestEngine` IDs. + +* If any third-party `TestEngine` claims to be `junit-jupiter` or `junit-vintage`, an + exception will be thrown, immediately halting execution of the JUnit Platform. +* If any third-party `TestEngine` uses the `junit-` prefix for its ID, a warning message + will be logged. Later releases of the JUnit Platform will throw an exception for such + violations. +==== + +In order to facilitate test discovery within IDEs and tools prior to launching the JUnit +Platform, `TestEngine` implementations are encouraged to make use of the `@Testable` +annotation. For example, the `@Test` and `@TestFactory` annotations in JUnit Jupiter are +meta-annotated with `@Testable`. Consult the Javadoc for `{Testable}` for further details. + +If your custom `TestEngine` needs to be configured, consider allowing users to supply +configuration via <>. Please note, +however, that you are strongly encouraged to use a unique prefix for all configuration +parameters supported by your test engine. Doing so will ensure that there are no conflicts +between the names of your configuration parameters and those from other test engines. In +addition, since configuration parameters may be supplied as JVM system properties, it is +wise to avoid conflicts with the names of other system properties. For example, JUnit +Jupiter uses `junit.jupiter.` as a prefix of all of its supported configuration +parameters. Furthermore, as with the warning above regarding the `junit-` prefix for +`TestEngine` IDs, you should not use `junit.` as a prefix for the names of your own +configuration parameters. + +Although there is currently no official guide on how to implement a custom `TestEngine`, +you can consult the implementation of <> or the implementation of +third-party test engines listed in the +https://github.com/junit-team/junit5/wiki/Third-party-Extensions#junit-platform-test-engines[JUnit 5 wiki]. +You will also find various tutorials and blogs on the Internet that demonstrate how to +write a custom `TestEngine`. + +NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation of the +`TestEngine` SPI (used by the `{junit-jupiter-engine}`) that only requires implementors to +provide the logic for test discovery. It implements execution of `TestDescriptors` that +implement the `Node` interface, including support for parallel execution. + +[[test-engines-registration]] +==== Registering a TestEngine + +`TestEngine` registration is supported via Java's `{ServiceLoader}` mechanism. + +For example, the `junit-jupiter-engine` module registers its +`org.junit.jupiter.engine.JupiterTestEngine` in a file named +`org.junit.platform.engine.TestEngine` within the `/META-INF/services` folder in the +`junit-jupiter-engine` JAR. + +[[test-engines-requirements]] +==== Requirements + +NOTE: The words "must", "must not", "required", "shall", "shall not", "should", "should +not", "recommended", "may", and "optional" in this section are to be interpreted as +described in https://www.ietf.org/rfc/rfc2119.txt[RFC 2119.] + +[[test-engines-requirements-mandatory]] +===== Mandatory requirements + +For interoperability with build tools and IDEs, `TestEngine` implementations must adhere +to the following requirements: + +* The `TestDescriptor` returned from `TestEngine.discover()` _must_ be the root of a tree + of `TestDescriptor` instances. This implies that there _must not_ be any cycles between + a node and its descendants. +* A `TestEngine` _must_ be able to discover `UniqueIdSelectors` for any unique ID that it + previously generated and returned from `TestEngine.discover()`. This enables selecting a + subset of tests to execute or rerun. +* The `executionSkipped`, `executionStarted`, and `executionFinished` methods of the + `EngineExecutionListener` passed to `TestEngine.execute()` _must_ be called for every + `TestDescriptor` node in the tree returned from `TestEngine.discover()` at most + once. Parent nodes _must_ be reported as started before their children and as finished + after their children. If a node is reported as skipped, there _must not_ be any events + reported for its descendants. + +[[test-engines-requirements-enhanced-compatibility]] +===== Enhanced compatibility + +Adhering to the following requirements is optional but recommended for enhanced +compatibility with build tools and IDEs: + +* Unless to indicate an empty discovery result, the `TestDescriptor` returned from + `TestEngine.discover()` _should_ have children rather than being completely dynamic. + This allows tools to display the structure of the tests and to select a subset of tests + to execute. +* When resolving `UniqueIdSelectors`, a `TestEngine` _should_ only return `TestDescriptor` + instances with matching unique IDs including their ancestors but _may_ return additional + siblings or other nodes that are required for the execution of the selected tests. +* `TestEngines` _should_ support <> tests and containers so + that tag filters can be applied when discovering tests. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc new file mode 100644 index 00000000..655d6bc7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc @@ -0,0 +1,138 @@ +[[junit-platform-reporting]] +=== JUnit Platform Reporting + +The `junit-platform-reporting` artifact contains `{TestExecutionListener}` implementations +that generate XML test reports in two flavors: +<> and +<>. + +NOTE: The module also contains other `TestExecutionListener` implementations that can be +used to build custom reporting. See <> for details. + +[[junit-platform-reporting-legacy-xml]] +==== Legacy XML format + +`{LegacyXmlReportGeneratingListener}` generates a separate XML report for each root in the +`{TestPlan}`. Note that the generated XML format is compatible with the de facto standard +for JUnit 4 based test reports that was made popular by the Ant build system. + +The `LegacyXmlReportGeneratingListener` is used by the <> +as well. + +[[junit-platform-reporting-open-test-reporting]] +==== Open Test Reporting XML format + +`{OpenTestReportGeneratingListener}` writes an XML report for the entire execution in the +event-based format specified by {OpenTestReporting} which supports all features of the +JUnit Platform such as hierarchical test structures, display names, tags, etc. + +The listener is auto-registered and can be configured via the following +<>: + +`junit.platform.reporting.open.xml.enabled=true|false`:: + Enable/disable writing the report. +`junit.platform.reporting.output.dir=`:: + Configure the output directory for the reports. By default, `build` is used if a Gradle + build script is found, and `target` if a Maven POM is found; otherwise, the current + working directory is used. + +If enabled, the listener creates an XML report file named +`junit-platform-events-.xml` per test run in the configured output directory. + +TIP: The {OpenTestReportingCliTool} can be used to convert from the event-based format to +the hierarchical format which is more human-readable. + +===== Gradle + +For Gradle, writing Open Test Reporting compatible XML reports can be enabled and +configured via system properties. The following samples configure its output directory to +be the same directory Gradle uses for its own XML reports. A `CommandLineArgumentProvider` +is used to keep the tasks relocatable across different machines which is important when +using Gradle's Build Cache. + +[source,groovy,indent=0] +[subs=attributes+] +.Groovy DSL +---- +dependencies { + testRuntimeOnly("org.junit.platform:junit-platform-reporting:{platform-version}") +} +tasks.withType(Test).configureEach { + def outputDir = reports.junitXml.outputLocation + jvmArgumentProviders << ({ + [ + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ] + } as CommandLineArgumentProvider) +} +---- + +[source,kotlin,indent=0] +[subs=attributes+] +.Kotlin DSL +---- +dependencies { + testRuntimeOnly("org.junit.platform:junit-platform-reporting:{platform-version}") +} +tasks.withType().configureEach { + val outputDir = reports.junitXml.outputLocation + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ) + } +} +---- + +===== Maven + +For Maven Surefire/Failsafe, you can enable Open Test Reporting output and configure the +resulting XML files to be written to the same directory Surefire/Failsafe uses for its own +XML reports as follows: + +[source,xml,indent=0] +[subs=attributes+] +---- + + + + + org.junit.platform + junit-platform-reporting + {platform-version} + test + + + + + + maven-surefire-plugin + {surefire-version} + + + + junit.platform.reporting.open.xml.enabled = true + junit.platform.reporting.output.dir = target/surefire-reports + + + + + + + + +---- + +===== Console Launcher + +When using the <>, you can enable Open Test Reporting +output by setting the configuration parameters via `--config`: + +[source,console,subs=attributes+] +---- +$ java -jar junit-platform-console-standalone-{platform-version}.jar \ + --config=junit.platform.reporting.open.xml.enabled=true \ + --config=junit.platform.reporting.output.dir=reports +---- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc new file mode 100644 index 00000000..d38a312d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc @@ -0,0 +1,50 @@ +[[junit-platform-suite-engine]] +=== JUnit Platform Suite Engine + +The JUnit Platform supports the declarative definition and execution of suites of tests +from _any_ test engine using the JUnit Platform. + +[[junit-platform-suite-engine-setup]] +==== Setup + +In addition to the `junit-platform-suite-api` and `junit-platform-suite-engine` artifacts, +you need _at least one_ other test engine and its dependencies on the classpath. See +<> for details regarding group IDs, artifact IDs, and versions. + +[[junit-platform-suite-engine-setup-required-dependencies]] +===== Required Dependencies + +* `junit-platform-suite-api` in _test_ scope: artifact containing annotations needed to + configure a test suite +* `junit-platform-suite-engine` in _test runtime_ scope: implementation of the + `TestEngine` API for declarative test suites + +NOTE: Both of the required dependencies are aggregated in the `junit-platform-suite` +artifact which can be declared in _test_ scope instead of declaring explicit dependencies +on `junit-platform-suite-api` and `junit-platform-suite-engine`. + +[[junit-platform-suite-engine-setup-transitive-dependencies]] +===== Transitive Dependencies + +* `junit-platform-suite-commons` in _test_ scope +* `junit-platform-launcher` in _test_ scope +* `junit-platform-engine` in _test_ scope +* `junit-platform-commons` in _test_ scope +* `opentest4j` in _test_ scope + +[[junit-platform-suite-engine-example]] +==== @Suite Example + +By annotating a class with `@Suite` it is marked as a test suite on the JUnit Platform. +As seen in the following example, selector and filter annotations can then be used to +control the contents of the suite. + +[source,java,indent=0] +---- +include::{testDir}/example/SuiteDemo.java[tags=user_guide] +---- + +.Additional Configuration Options +NOTE: There are numerous configuration options for discovering and filtering tests in a +test suite. Please consult the Javadoc of the `{suite-api-package}` package for a full +list of supported annotations and further details. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc new file mode 100644 index 00000000..2c8b25e7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc @@ -0,0 +1,243 @@ +[[launcher-api]] +=== JUnit Platform Launcher API + +One of the prominent goals of JUnit 5 is to make the interface between JUnit and its +programmatic clients – build tools and IDEs – more powerful and stable. The purpose is to +decouple the internals of discovering and executing tests from all the filtering and +configuration that's necessary from the outside. + +JUnit 5 introduces the concept of a `Launcher` that can be used to discover, filter, and +execute tests. Moreover, third party test libraries – like Spock, Cucumber, and FitNesse +– can plug into the JUnit Platform's launching infrastructure by providing a custom +<>. + +The launcher API is in the `{junit-platform-launcher}` module. + +An example consumer of the launcher API is the `{ConsoleLauncher}` in the +`{junit-platform-console}` project. + +[[launcher-api-discovery]] +==== Discovering Tests + +Having _test discovery_ as a dedicated feature of the platform itself frees IDEs and build +tools from most of the difficulties they had to go through to identify test classes and +test methods in previous versions of JUnit. + +Usage Example: + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=imports] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=discovery] +---- + +You can select classes, methods, and all classes in a package or even search for all tests +in the class-path or module-path. Discovery takes place across all participating test +engines. + +The resulting `TestPlan` is a hierarchical (and read-only) description of all engines, +classes, and test methods that fit the `LauncherDiscoveryRequest`. The client can +traverse the tree, retrieve details about a node, and get a link to the original source +(like class, method, or file position). Every node in the test plan has a _unique ID_ +that can be used to invoke a particular test or group of tests. + +Clients can register one or more `{LauncherDiscoveryListener}` implementations via the +`{LauncherDiscoveryRequestBuilder}` to gain insight into events that occur during test +discovery. By default, the builder registers an "abort on failure" listener that aborts +test discovery after the first discovery failure is encountered. The default +`LauncherDiscoveryListener` can be changed via the +`junit.platform.discovery.listener.default` <>. + +[[launcher-api-execution]] +==== Executing Tests + +To execute tests, clients can use the same `LauncherDiscoveryRequest` as in the discovery +phase or create a new request. Test progress and reporting can be achieved by registering +one or more `{TestExecutionListener}` implementations with the `Launcher` as in the +following example. + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=execution] +---- + +There is no return value for the `execute()` method, but you can use a +`TestExecutionListener` to aggregate the results. For examples see the +`{SummaryGeneratingListener}`, `{LegacyXmlReportGeneratingListener}`, and +`{UniqueIdTrackingListener}`. + +[[launcher-api-engines-custom]] +==== Registering a TestEngine + +See the dedicated section on <> for +details. + +[[launcher-api-post-discovery-filters-custom]] +==== Registering a PostDiscoveryFilter + +In addition to specifying post-discovery filters as part of a `{LauncherDiscoveryRequest}` +passed to the `{Launcher}` API, `{PostDiscoveryFilter}` implementations will be discovered +at runtime via Java's `{ServiceLoader}` mechanism and automatically applied by the +`Launcher` in addition to those that are part of the request. + +For example, an `example.CustomTagFilter` class implementing `PostDiscoveryFilter` and +declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter` +file is loaded and applied automatically. + +[[launcher-api-launcher-session-listeners-custom]] +==== Registering a LauncherSessionListener + +Registered implementations of `{LauncherSessionListener}` are notified when a +`{LauncherSession}` is opened (before a `{Launcher}` first discovers and executes tests) +and closed (when no more tests will be discovered or executed). They can be registered +programmatically via the `{LauncherConfig}` that is passed to the `{LauncherFactory}`, or +they can be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically +registered with `LauncherSession` (unless automatic registration is disabled.) + +[[launcher-api-launcher-session-listeners-tool-support]] +===== Tool Support + +The following build tools and IDEs are known to provide full support for `LauncherSession`: + +* Gradle 4.6 and later +* Maven Surefire/Failsafe 3.0.0-M6 and later +* IntelliJ IDEA 2017.3 and later + +Other tools might also work but have not been tested explicitly. + +[[launcher-api-launcher-session-listeners-tool-example-usage]] +===== Example Usage + +A `LauncherSessionListener` is well suited for implementing once-per-JVM setup/teardown +behavior since it's called before the first and after the last test in a launcher session, +respectively. The scope of a launcher session depends on the used IDE or build tool but +usually corresponds to the lifecycle of the test JVM. A custom listener that starts an +HTTP server before executing the first test and stops it after the last test has been +executed, could look like this: + +[source,java] +.src/test/java/example/session/GlobalSetupTeardownListener.java +---- +package example.session; + +include::{testDir}/example/session/GlobalSetupTeardownListener.java[tags=user_guide] +---- +<1> Start the HTTP server +<2> Export its host address as a system property for consumption by tests +<3> Export its port as a system property for consumption by tests +<4> Stop the HTTP server + +This sample uses the HTTP server implementation from the jdk.httpserver module that comes +with the JDK but would work similarly with any other server or resource. In order for the +listener to be picked up by JUnit Platform, you need to register it as a service by adding +a resource file with the following name and contents to your test runtime classpath (e.g. +by adding the file to `src/test/resources`): + +[source] +.src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener +---- +include::{testResourcesDir}/META-INF/services/org.junit.platform.launcher.LauncherSessionListener[] +---- + +You can now use the resource from your test: + +[source,java] +.src/test/java/example/session/HttpTests.java +---- +package example.session; + +include::{testDir}/example/session/HttpTests.java[tags=user_guide] +---- +<1> Read the host address of the server from the system property set by the listener +<2> Read the port of the server from the system property set by the listener +<3> Send a request to the server +<4> Check the status code of the response + +[[launcher-api-launcher-discovery-listeners-custom]] +==== Registering a LauncherDiscoveryListener + +In addition to specifying discovery listeners as part of a `{LauncherDiscoveryRequest}` or +registering them programmatically via the `{Launcher}` API, custom +`LauncherDiscoveryListener` implementations can be discovered at runtime via Java's +`{ServiceLoader}` mechanism and automatically registered with the `Launcher` created via +the `{LauncherFactory}`. + +For example, an `example.CustomLauncherDiscoveryListener` class implementing +`LauncherDiscoveryListener` and declared within the +`/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener` file is loaded +and registered automatically. + +[[launcher-api-listeners-custom]] +==== Registering a TestExecutionListener + +In addition to the public `{Launcher}` API method for registering test execution listeners +programmatically, custom `{TestExecutionListener}` implementations will be discovered at +runtime via Java's `{ServiceLoader}` mechanism and automatically registered with the +`Launcher` created via the `{LauncherFactory}`. + +For example, an `example.CustomTestExecutionListener` class implementing +`TestExecutionListener` and declared within the +`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and +registered automatically. + +[[launcher-api-listeners-config]] +==== Configuring a TestExecutionListener + +When a `{TestExecutionListener}` is registered programmatically via the `{Launcher}` API, +the listener may provide programmatic ways for it to be configured -- for example, via its +constructor, setter methods, etc. However, when a `TestExecutionListener` is registered +automatically via Java's `ServiceLoader` mechanism (see +<>), there is no way for the user to directly configure the +listener. In such cases, the author of a `TestExecutionListener` may choose to make the +listener configurable via <>. The +listener can then access the configuration parameters via the `TestPlan` supplied to the +`testPlanExecutionStarted(TestPlan)` and `testPlanExecutionFinished(TestPlan)` callback +methods. See the `{UniqueIdTrackingListener}` for an example. + +[[launcher-api-listeners-custom-deactivation]] +==== Deactivating a TestExecutionListener + +Sometimes it can be useful to run a test suite _without_ certain execution listeners being +active. For example, you might have custom a `{TestExecutionListener}` that sends the test +results to an external system for reporting purposes, and while debugging you might not +want these _debug_ results to be reported. To do this, provide a pattern for the +`junit.platform.execution.listeners.deactivate` _configuration parameter_ to specify which +execution listeners should be deactivated (i.e. not registered) for the current test run. + +[NOTE] +==== +Only listeners registered via the `{ServiceLoader}` mechanism within the +`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file can be +deactivated. In other words, any `TestExecutionListener` registered explicitly via the +`{LauncherDiscoveryRequest}` cannot be deactivated via the +`junit.platform.execution.listeners.deactivate` _configuration parameter_. + +In addition, since execution listeners are registered before the test run starts, the +`junit.platform.execution.listeners.deactivate` _configuration parameter_ can only be +supplied as a JVM system property or via the JUnit Platform configuration file (see +<> for details). This _configuration parameter_ cannot be +supplied in the `LauncherDiscoveryRequest` that is passed to the `{Launcher}`. +==== + +[[launcher-api-listeners-custom-deactivation-pattern]] +===== Pattern Matching Syntax + +Refer to <> for details. + +[[launcher-api-launcher-config]] +==== Configuring the Launcher + +If you require fine-grained control over automatic detection and registration of test +engines and listeners, you may create an instance of `{LauncherConfig}` and supply that to +the `{LauncherFactory}`. Typically, an instance of `LauncherConfig` is created via the +built-in fluent _builder_ API, as demonstrated in the following example. + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=launcherConfig] +---- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc new file mode 100644 index 00000000..665c4dae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc @@ -0,0 +1,164 @@ +[[testkit]] +=== JUnit Platform Test Kit + +The `junit-platform-testkit` artifact provides support for executing a test plan on the +JUnit Platform and then verifying the expected results. As of JUnit Platform 1.4, this +support is limited to the execution of a single `TestEngine` (see <>). + +[[testkit-engine]] +==== Engine Test Kit + +The `{testkit-engine-package}` package provides support for executing a `{TestPlan}` for a +given `{TestEngine}` running on the JUnit Platform and then accessing the results via a +fluent API to verify the expected results. The key entry point into this API is the +`{EngineTestKit}` which provides static factory methods named `engine()` and `execute()`. +It is recommended that you select one of the `engine()` variants to benefit from the +fluent API for building a `LauncherDiscoveryRequest`. + +NOTE: If you prefer to use the `LauncherDiscoveryRequestBuilder` from the `Launcher` API +to build your `LauncherDiscoveryRequest`, you must use one of the `execute()` variants in +`EngineTestKit`. + +The following test class written using JUnit Jupiter will be used in subsequent examples. + +[[testkit-engine-ExampleTestCase]] +[source,java,indent=0] +---- +include::{testDir}/example/ExampleTestCase.java[tags=user_guide] +---- + +For the sake of brevity, the following sections demonstrate how to test JUnit's own +`JupiterTestEngine` whose unique engine ID is `"junit-jupiter"`. If you want to test your +own `TestEngine` implementation, you need to use its unique engine ID. Alternatively, you +may test your own `TestEngine` by supplying an instance of it to the +`EngineTestKit.engine(TestEngine)` static factory method. + +[[testkit-engine-statistics]] +==== Asserting Statistics + +One of the most common features of the Test Kit is the ability to assert statistics +against events fired during the execution of a `TestPlan`. The following tests demonstrate +how to assert statistics for _containers_ and _tests_ in the JUnit Jupiter `TestEngine`. +For details on what statistics are available, consult the Javadoc for `{EventStatistics}`. + +[source,java,indent=0] +---- +include::{testDir}/example/testkit/EngineTestKitStatisticsDemo.java[tags=user_guide] +---- +<1> Select the JUnit Jupiter `TestEngine`. +<2> Select the <> test class. +<3> Execute the `TestPlan`. +<4> Filter by _container_ events. +<5> Assert statistics for _container_ events. +<6> Filter by _test_ events. +<7> Assert statistics for _test_ events. + +NOTE: In the `verifyJupiterContainerStats()` test method, the counts for the `started` and +`succeeded` statistics are `2` since the `JupiterTestEngine` and the +<> class are both considered containers. + +[[testkit-engine-events]] +==== Asserting Events + +If you find that <> alone is insufficient +for verifying the expected behavior of test execution, you can work directly with the +recorded `{Event}` elements and perform assertions against them. + +For example, if you want to verify the reason that the `skippedTest()` method in +<> was skipped, you can do that as +follows. + +[TIP] +==== +The `assertThatEvents()` method in the following example is a shortcut for +`org.assertj.core.api.Assertions.assertThat(events.list())` from the {AssertJ} assertion +library. + +For details on what _conditions_ are available for use with AssertJ assertions against +events, consult the Javadoc for `{EventConditions}`. +==== + +[source,java,indent=0] +---- +include::{testDir}/example/testkit/EngineTestKitSkippedMethodDemo.java[tags=user_guide] +---- +<1> Select the JUnit Jupiter `TestEngine`. +<2> Select the `skippedTest()` method in the <> test class. +<3> Execute the `TestPlan`. +<4> Filter by _test_ events. +<5> Save the _test_ `Events` to a local variable. +<6> Optionally assert the expected statistics. +<7> Assert that the recorded _test_ events contain exactly one skipped test named + `skippedTest` with `"for demonstration purposes"` as the _reason_. + +If you want to verify the type of exception thrown from the `failingTest()` method in +<>, you can do that as follows. + +[TIP] +==== +For details on what _conditions_ are available for use with AssertJ assertions against +events and execution results, consult the Javadoc for `{EventConditions}` and +`{TestExecutionResultConditions}`, respectively. +==== + +[source,java,indent=0] +---- +include::{testDir}/example/testkit/EngineTestKitFailedMethodDemo.java[tags=user_guide] +---- +<1> Select the JUnit Jupiter `TestEngine`. +<2> Select the <> test class. +<3> Execute the `TestPlan`. +<4> Filter by _test_ events. +<5> Assert that the recorded _test_ events contain exactly one failing test named + `failingTest` with an exception of type `ArithmeticException` and an error message + equal to `"/ by zero"`. + +Although typically unnecessary, there are times when you need to verify **all** of the +events fired during the execution of a `TestPlan`. The following test demonstrates how to +achieve this via the `assertEventsMatchExactly()` method in the `EngineTestKit` API. + +[TIP] +==== +Since `assertEventsMatchExactly()` matches conditions exactly in the order in which the +events were fired, <> has been +annotated with `@TestMethodOrder(OrderAnnotation.class)` and each test method has been +annotated with `@Order(...)`. This allows us to enforce the order in which the test +methods are executed, which in turn allows our `verifyAllJupiterEvents()` test to be +reliable. +==== + +If you want to do a _partial_ match _with_ or _without_ ordering requirements, you can use +the methods `assertEventsMatchLooselyInOrder()` and `assertEventsMatchLoosely()`, +respectively. + +[source,java,indent=0] +---- +include::{testDir}/example/testkit/EngineTestKitAllEventsDemo.java[tags=user_guide] +---- +<1> Select the JUnit Jupiter `TestEngine`. +<2> Select the <> test class. +<3> Execute the `TestPlan`. +<4> Filter by _all_ events. +<5> Print all events to the supplied `writer` for debugging purposes. Debug information + can also be written to an `OutputStream` such as `System.out` or `System.err`. +<6> Assert _all_ events in exactly the order in which they were fired by the test engine. + +The `debug()` invocation from the preceding example results in output similar to the +following. + +[source,options="nowrap"] +---- +All Events: + Event [type = STARTED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.082280Z, payload = null] + Event [type = STARTED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.089339Z, payload = null] + Event [type = SKIPPED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:skippedTest()], timestamp = 2018-12-14T12:45:14.094314Z, payload = 'for demonstration purposes'] + Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.095182Z, payload = null] + Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.104922Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] + Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.106121Z, payload = null] + Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.109956Z, payload = TestExecutionResult [status = ABORTED, throwable = org.opentest4j.TestAbortedException: Assumption failed: abc does not contain Z]] + Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.110680Z, payload = null] + Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.111217Z, payload = TestExecutionResult [status = FAILED, throwable = java.lang.ArithmeticException: / by zero]] + Event [type = FINISHED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.113731Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] + Event [type = FINISHED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.113806Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] +---- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc new file mode 100644 index 00000000..bac0e8b0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc @@ -0,0 +1,63 @@ +[[api-evolution]] +== API Evolution + +One of the major goals of JUnit 5 is to improve maintainers' capabilities to evolve JUnit +despite its being used in many projects. With JUnit 4 a lot of stuff that was originally +added as an internal construct only got used by external extension writers and tool +builders. That made changing JUnit 4 especially difficult and sometimes impossible. + +That's why JUnit 5 introduces a defined lifecycle for all publicly available interfaces, +classes, and methods. + +[[api-evolution-version-and-status]] +=== API Version and Status + +Every published artifact has a version number `..`, and all publicly +available interfaces, classes, and methods are annotated with {API} from the +{API_Guardian} project. The annotation's `status` attribute can be assigned one of the +following values. + +[cols="20,80"] +|=== +| Status | Description + +| `INTERNAL` | Must not be used by any code other than JUnit itself. Might be removed without prior notice. +| `DEPRECATED` | Should no longer be used; might disappear in the next minor release. +| `EXPERIMENTAL` | Intended for new, experimental features where we are looking for feedback. + + Use this element with caution; it might be promoted to `MAINTAINED` or + `STABLE` in the future, but might also be removed without prior notice, even in a patch. +| `MAINTAINED` | Intended for features that will not be changed in a backwards- + incompatible way for *at least* the next minor release of the current + major version. If scheduled for removal, it will be demoted to `DEPRECATED` first. +| `STABLE` | Intended for features that will not be changed in a backwards- + incompatible way in the current major version (`5.*`). +|=== + +If the `@API` annotation is present on a type, it is considered to be applicable for all +public members of that type as well. A member is allowed to declare a different `status` +value of lower stability. + +[[api-evolution-experimental-apis]] +=== Experimental APIs + +The following table lists which APIs are currently designated as _experimental_ via +`@API(status = EXPERIMENTAL)`. Caution should be taken when relying on such APIs. + +include::{experimentalApisTableFile}[] + +[[api-evolution-deprecated-apis]] +=== Deprecated APIs + +The following table lists which APIs are currently designated as _deprecated_ via +`@API(status = DEPRECATED)`. You should avoid using deprecated APIs whenever possible, +since such APIs will likely be removed in an upcoming release. + +include::{deprecatedApisTableFile}[] + +[[api-evolution-tooling]] +=== @API Tooling Support + +The {API_Guardian} project plans to provide tooling support for publishers and consumers +of APIs annotated with {API}. For example, the tooling support will likely provide a +means to check if JUnit APIs are being used in accordance with `@API` annotation +declarations. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc new file mode 100644 index 00000000..d92194fd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc @@ -0,0 +1,232 @@ +[[appendix]] +== Appendix + +[[reproducible-builds]] +=== Reproducible Builds + +Starting with version 5.7, JUnit 5 aims for its non-javadoc JARs to be +https://reproducible-builds.org/[reproducible]. + +Under identical build conditions, such as Java version, repeated builds should provide the +same output byte-for-byte. + +This means that anyone can reproduce the build conditions of the artifacts on Maven +Central/Sonatype and produce the same output artifact locally, confirming that the +artifacts in the repositories were actually generated from this source code. + +[[dependency-metadata]] +=== Dependency Metadata + +Artifacts for final releases and milestones are deployed to {Maven_Central}, and snapshot +artifacts are deployed to Sonatype's {snapshot-repo}[snapshots repository] under +{snapshot-repo}/org/junit/[/org/junit]. + +[[dependency-metadata-junit-platform]] +==== JUnit Platform + +* *Group ID*: `org.junit.platform` +* *Version*: `{platform-version}` +* *Artifact IDs*: + `junit-platform-commons`:: + Common APIs and support utilities for the JUnit Platform. Any API annotated with + `@API(status = INTERNAL)` is intended solely for usage within the JUnit framework + itself. _Any usage of internal APIs by external parties is not supported!_ + `junit-platform-console`:: + Support for discovering and executing tests on the JUnit Platform from the console. + See <> for details. + `junit-platform-console-standalone`:: + An executable JAR with all dependencies included is provided in Maven Central under the + https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone] + directory. See <> for details. + `junit-platform-engine`:: + Public API for test engines. See <> for details. + `junit-platform-jfr`:: + Provides a `LauncherDiscoveryListener` and `TestExecutionListener` for Java Flight + Recorder events on the JUnit Platform. See <> + for details. + `junit-platform-launcher`:: + Public API for configuring and launching test plans -- typically used by IDEs and + build tools. See <> for details. + `junit-platform-reporting`:: + `TestExecutionListener` implementations that generate test reports -- typically used + by IDEs and build tools. See <> for details. + `junit-platform-runner`:: + Runner for executing tests and test suites on the JUnit Platform in a JUnit 4 + environment. See <> for details. + `junit-platform-suite`:: + JUnit Platform Suite artifact that transitively pulls in dependencies on + `junit-platform-suite-api` and `junit-platform-suite-engine` for simplified dependency + management in build tools such as Gradle and Maven. + `junit-platform-suite-api`:: + Annotations for configuring test suites on the JUnit Platform. Supported by the + <> and the + <>. + `junit-platform-suite-commons`:: + Common support utilities for executing test suites on the JUnit Platform. + `junit-platform-suite-engine`:: + Engine that executes test suites on the JUnit Platform; only required at runtime. See + <> for details. + `junit-platform-testkit`:: + Provides support for executing a test plan for a given `TestEngine` and then + accessing the results via a fluent API to verify the expected results. + +[[dependency-metadata-junit-jupiter]] +==== JUnit Jupiter + +* *Group ID*: `org.junit.jupiter` +* *Version*: `{jupiter-version}` +* *Artifact IDs*: + `junit-jupiter`:: + JUnit Jupiter aggregator artifact that transitively pulls in dependencies on + `junit-jupiter-api`, `junit-jupiter-params`, and `junit-jupiter-engine` for + simplified dependency management in build tools such as Gradle and Maven. + `junit-jupiter-api`:: + JUnit Jupiter API for <> and <>. + `junit-jupiter-engine`:: + JUnit Jupiter test engine implementation; only required at runtime. + `junit-jupiter-params`:: + Support for <> in JUnit Jupiter. + `junit-jupiter-migrationsupport`:: + Support for migrating from JUnit 4 to JUnit Jupiter; only required for support for + JUnit 4's `@Ignore` annotation and for running selected JUnit 4 rules. + +[[dependency-metadata-junit-vintage]] +==== JUnit Vintage + +* *Group ID*: `org.junit.vintage` +* *Version*: `{vintage-version}` +* *Artifact ID*: + `junit-vintage-engine`:: + JUnit Vintage test engine implementation that allows one to run _vintage_ JUnit tests + on the JUnit Platform. _Vintage_ tests include those written using JUnit 3 or JUnit 4 + APIs or tests written using testing frameworks built on those APIs. + +[[dependency-metadata-junit-bom]] +==== Bill of Materials (BOM) + +The _Bill of Materials_ POM provided under the following Maven coordinates can be used to +ease dependency management when referencing multiple of the above artifacts using +https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies[Maven] +or https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:bom_import[Gradle]. + +* *Group ID*: `org.junit` +* *Artifact ID*: `junit-bom` +* *Version*: `{bom-version}` + +[[dependency-metadata-dependencies]] +==== Dependencies + +Most of the above artifacts have a dependency in their published Maven POMs on the +following _@API Guardian_ JAR. + +* *Group ID*: `org.apiguardian` +* *Artifact ID*: `apiguardian-api` +* *Version*: `{apiguardian-version}` + +In addition, most of the above artifacts have a direct or transitive dependency on the +following _OpenTest4J_ JAR. + +* *Group ID*: `org.opentest4j` +* *Artifact ID*: `opentest4j` +* *Version*: `{ota4j-version}` + +[[dependency-diagram]] +=== Dependency Diagram + +[plantuml, component-diagram, svg] +---- +skinparam { + defaultFontName Open Sans +} + +package org.junit.jupiter { + [junit-jupiter] as jupiter + [junit-jupiter-api] as jupiter_api + [junit-jupiter-engine] as jupiter_engine + [junit-jupiter-params] as jupiter_params + [junit-jupiter-migrationsupport] as jupiter_migration_support +} + +package org.junit.vintage { + [junit-vintage-engine] as vintage_engine +} + +package org.junit.platform { + [junit-platform-commons] as commons + [junit-platform-console] as console + [junit-platform-engine] as engine + [junit-platform-jfr] as jfr + [junit-platform-launcher] as launcher + [junit-platform-reporting] as reporting + [junit-platform-runner] as runner + [junit-platform-suite] as suite + [junit-platform-suite-api] as suite_api + [junit-platform-suite-commons] as suite_commons + [junit-platform-suite-engine] as suite_engine + [junit-platform-testkit] as testkit +} + +package "JUnit 4" { + [junit:junit] as junit4 +} + +package org.opentest4j { + [opentest4j] +} + +package org.apiguardian { + [apiguardian-api] as apiguardian + note bottom of apiguardian #white + All artifacts except + opentest4j and junit:junit + have a dependency on this + artifact. The edges have + been omitted from this + diagram for the sake of + readability. + endnote +} + +jupiter ..> jupiter_api +jupiter ..> jupiter_params +jupiter ..> jupiter_engine + +jupiter_api ....> opentest4j +jupiter_api ...> commons + +jupiter_engine ...> engine +jupiter_engine ..> jupiter_api + +jupiter_params ..> jupiter_api +jupiter_migration_support ..> jupiter_api +jupiter_migration_support ...> junit4 + +console ..> launcher +console ..> reporting + +launcher ..> engine + +jfr ..> launcher + +engine ....> opentest4j +engine ..> commons + +reporting ..> launcher + +runner ..> suite_commons +runner ...> junit4 + +suite ..> suite_api +suite ..> suite_engine + +suite_engine ..> suite_commons + +suite_commons ..> launcher +suite_commons ..> suite_api + +testkit ....> opentest4j +testkit ..> launcher + +vintage_engine ...> engine +vintage_engine ..> junit4 +---- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc new file mode 100644 index 00000000..c005e9e8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc @@ -0,0 +1,4 @@ +[[contributors]] +== Contributors + +Browse the {junit5-repo}/graphs/contributors[current list of contributors] directly on GitHub. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc new file mode 100644 index 00000000..a1fee08b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -0,0 +1,951 @@ +[[extensions]] +== Extension Model + +[[extensions-overview]] +=== Overview + +In contrast to the competing `Runner`, `TestRule`, and `MethodRule` extension points in +JUnit 4, the JUnit Jupiter extension model consists of a single, coherent concept: the +`Extension` API. Note, however, that `Extension` itself is just a marker interface. + +[[extensions-registration]] +=== Registering Extensions + +Extensions can be registered _declaratively_ via +<>, _programmatically_ via +<>, or _automatically_ via +Java's <> mechanism. + +[[extensions-registration-declarative]] +==== Declarative Extension Registration + +Developers can register one or more extensions _declaratively_ by annotating a test +interface, test class, test method, or custom _<>_ with `@ExtendWith(...)` and supplying class references for the extensions to +register. As of JUnit Jupiter 5.8, `@ExtendWith` may also be declared on fields or on +parameters in test class constructors, in test methods, and in `@BeforeAll`, `@AfterAll`, +`@BeforeEach`, and `@AfterEach` lifecycle methods. + +For example, to register a `WebServerExtension` for a particular test method, you would +annotate the test method as follows. We assume the `WebServerExtension` starts a local web +server and injects the server's URL into parameters annotated with `@WebServerUrl`. + +[source,java,indent=0] +---- +@Test +@ExtendWith(WebServerExtension.class) +void getProductList(@WebServerUrl String serverUrl) { + WebClient webClient = new WebClient(); + // Use WebClient to connect to web server using serverUrl and verify response + assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); +} +---- + +To register the `WebServerExtension` for all tests in a particular class and its +subclasses, you would annotate the test class as follows. + +[source,java,indent=0] +---- +@ExtendWith(WebServerExtension.class) +class MyTests { + // ... +} +---- + +Multiple extensions can be registered together like this: + +[source,java,indent=0] +---- +@ExtendWith({ DatabaseExtension.class, WebServerExtension.class }) +class MyFirstTests { + // ... +} +---- + +As an alternative, multiple extensions can be registered separately like this: + +[source,java,indent=0] +---- +@ExtendWith(DatabaseExtension.class) +@ExtendWith(WebServerExtension.class) +class MySecondTests { + // ... +} +---- + +[TIP] +.Extension Registration Order +==== +Extensions registered declaratively via `@ExtendWith` at the class level, method level, or +parameter level will be executed in the order in which they are declared in the source +code. For example, the execution of tests in both `MyFirstTests` and `MySecondTests` will +be extended by the `DatabaseExtension` and `WebServerExtension`, **in exactly that order**. +==== + +If you wish to combine multiple extensions in a reusable way, you can define a custom +_<>_ and use `@ExtendWith` as a +_meta-annotation_ as in the following code listing. Then `@DatabaseAndWebServerExtension` +can be used in place of `@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })`. + +[source,java,indent=0] +---- +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith({ DatabaseExtension.class, WebServerExtension.class }) +public @interface DatabaseAndWebServerExtension { +} +---- + +The above examples demonstrate how `@ExtendWith` can be applied at the class level or at +the method level; however, for certain use cases it makes sense for an extension to be +registered declaratively at the field or parameter level. Consider a +`RandomNumberExtension` that generates random numbers that can be injected into a field or +via a parameter in a constructor, test method, or lifecycle method. If the extension +provides a `@Random` annotation that is meta-annotated with +`@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used +transparently as in the following `RandomNumberDemo` example. + +[source,java,indent=0] +---- +include::{testDir}/example/extensions/Random.java[tags=user_guide] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/extensions/RandomNumberDemo.java[tags=user_guide] +---- + +[TIP] +.Extension Registration Order for `@ExtendWith` on Fields +==== +Extensions registered declaratively via `@ExtendWith` on fields will be ordered relative +to `@RegisterExtension` fields and other `@ExtendWith` fields using an algorithm that is +deterministic but intentionally nonobvious. However, `@ExtendWith` fields can be ordered +using the `@Order` annotation. See the <> tip for `@RegisterExtension` fields for details. +==== + +NOTE: `@ExtendWith` fields may be either `static` or non-static. The documentation on +<> and +<> for +`@RegisterExtension` fields also applies to `@ExtendWith` fields. + +[[extensions-registration-programmatic]] +==== Programmatic Extension Registration + +Developers can register extensions _programmatically_ by annotating fields in test classes +with `{RegisterExtension}`. + +When an extension is registered _declaratively_ via +<>, it can typically only be configured +via annotations. In contrast, when an extension is registered via `@RegisterExtension`, it +can be configured _programmatically_ -- for example, in order to pass arguments to the +extension's constructor, a static factory method, or a builder API. + +[[extensions-registration-programmatic-order]] +[TIP] +.Extension Registration Order +==== +By default, extensions registered programmatically via `@RegisterExtension` or +declaratively via `@ExtendWith` on fields will be ordered using an algorithm that is +deterministic but intentionally nonobvious. This ensures that subsequent runs of a test +suite execute extensions in the same order, thereby allowing for repeatable builds. +However, there are times when extensions need to be registered in an explicit order. To +achieve that, annotate `@RegisterExtension` fields or `@ExtendWith` fields with `{Order}`. + +Any `@RegisterExtension` field or `@ExtendWith` field not annotated with `@Order` will be +ordered using the _default_ order which has a value of `Integer.MAX_VALUE / 2`. This +allows `@Order` annotated extension fields to be explicitly ordered before or after +non-annotated extension fields. Extensions with an explicit order value less than the +default order value will be registered before non-annotated extensions. Similarly, +extensions with an explicit order value greater than the default order value will be +registered after non-annotated extensions. For example, assigning an extension an explicit +order value that is greater than the default order value allows _before_ callback +extensions to be registered last and _after_ callback extensions to be registered first, +relative to other programmatically registered extensions. +==== + +NOTE: `@RegisterExtension` fields must not be `null` (at evaluation time) but may be +either `static` or non-static. + +[[extensions-registration-programmatic-static-fields]] +===== Static Fields + +If a `@RegisterExtension` field is `static`, the extension will be registered after +extensions that are registered at the class level via `@ExtendWith`. Such _static +extensions_ are not limited in which extension APIs they can implement. Extensions +registered via static fields may therefore implement class-level and instance-level +extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, +`TestInstancePostProcessor`, and `TestInstancePreDestroyCallback` as well as method-level +extension APIs such as `BeforeEachCallback`, etc. + +In the following example, the `server` field in the test class is initialized +programmatically by using a builder pattern supported by the `WebServerExtension`. The +configured `WebServerExtension` will be automatically registered as an extension at the +class level -- for example, in order to start the server before all tests in the class +and then stop the server after all tests in the class have completed. In addition, static +lifecycle methods annotated with `@BeforeAll` or `@AfterAll` as well as `@BeforeEach`, +`@AfterEach`, and `@Test` methods can access the instance of the extension via the +`server` field if necessary. + +[source,java,indent=0] +.Registering an extension via a static field in Java +---- +include::{testDir}/example/registration/WebServerDemo.java[tags=user_guide] +---- + +[[extensions-registration-programmatic-static-fields-kotlin]] +====== Static Fields in Kotlin + +The Kotlin programming language does not have the concept of a `static` field. However, +the compiler can be instructed to generate a `private static` field using the `@JvmStatic` +annotation in Kotlin. If you want the Kotlin compiler to generate a `public static` field, +you can use the `@JvmField` annotation instead. + +The following example is a version of the `WebServerDemo` from the previous section that +has been ported to Kotlin. + +[source,kotlin,indent=0] +.Registering an extension via a static field in Kotlin +---- +include::{kotlinTestDir}/example/registration/KotlinWebServerDemo.kt[tags=user_guide] +---- + +[[extensions-registration-programmatic-instance-fields]] +===== Instance Fields + +If a `@RegisterExtension` field is non-static (i.e., an instance field), the extension +will be registered after the test class has been instantiated and after each registered +`TestInstancePostProcessor` has been given a chance to post-process the test instance +(potentially injecting the instance of the extension to be used into the annotated +field). Thus, if such an _instance extension_ implements class-level or instance-level +extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, or +`TestInstancePostProcessor`, those APIs will not be honored. By default, an instance +extension will be registered _after_ extensions that are registered at the method level +via `@ExtendWith`; however, if the test class is configured with +`@TestInstance(Lifecycle.PER_CLASS)` semantics, an instance extension will be registered +_before_ extensions that are registered at the method level via `@ExtendWith`. + +In the following example, the `docs` field in the test class is initialized +programmatically by invoking a custom `lookUpDocsDir()` method and supplying the result +to the static `forPath()` factory method in the `DocumentationExtension`. The configured +`DocumentationExtension` will be automatically registered as an extension at the method +level. In addition, `@BeforeEach`, `@AfterEach`, and `@Test` methods can access the +instance of the extension via the `docs` field if necessary. + +[source,java,indent=0] +.An extension registered via an instance field +---- +include::{testDir}/example/registration/DocumentationDemo.java[tags=user_guide] +---- + +[[extensions-registration-automatic]] +==== Automatic Extension Registration + +In addition to <> +and <> support +using annotations, JUnit Jupiter also supports _global extension registration_ via Java's +`{ServiceLoader}` mechanism, allowing third-party extensions to be auto-detected and +automatically registered based on what is available in the classpath. + +Specifically, a custom extension can be registered by supplying its fully qualified class +name in a file named `org.junit.jupiter.api.extension.Extension` within the +`/META-INF/services` folder in its enclosing JAR file. + +[[extensions-registration-automatic-enabling]] +===== Enabling Automatic Extension Detection + +Auto-detection is an advanced feature and is therefore not enabled by default. To enable +it, set the `junit.jupiter.extensions.autodetection.enabled` _configuration parameter_ to +`true`. This can be supplied as a JVM system property, as a _configuration parameter_ in +the `LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform +configuration file (see <> for details). + +For example, to enable auto-detection of extensions, you can start your JVM with the +following system property. + +`-Djunit.jupiter.extensions.autodetection.enabled=true` + +When auto-detection is enabled, extensions discovered via the `{ServiceLoader}` mechanism +will be added to the extension registry after JUnit Jupiter's global extensions (e.g., +support for `TestInfo`, `TestReporter`, etc.). + +[[extensions-registration-inheritance]] +==== Extension Inheritance + +Registered extensions are inherited within test class hierarchies with top-down +semantics. Similarly, extensions registered at the class-level are inherited at the +method-level. Furthermore, a specific extension implementation can only be registered +once for a given extension context and its parent contexts. Consequently, any attempt to +register a duplicate extension implementation will be ignored. + +[[extensions-conditions]] +=== Conditional Test Execution + +`{ExecutionCondition}` defines the `Extension` API for programmatic, _conditional test +execution_. + +An `ExecutionCondition` is _evaluated_ for each container (e.g., a test class) to +determine if all the tests it contains should be executed based on the supplied +`ExtensionContext`. Similarly, an `ExecutionCondition` is _evaluated_ for each test to +determine if a given test method should be executed based on the supplied +`ExtensionContext`. + +When multiple `ExecutionCondition` extensions are registered, a container or test is +disabled as soon as one of the conditions returns _disabled_. Thus, there is no guarantee +that a condition is evaluated because another extension might have already caused a +container or test to be disabled. In other words, the evaluation works like the +short-circuiting boolean OR operator. + +See the source code of `{DisabledCondition}` and `{Disabled}` for concrete examples. + +[[extensions-conditions-deactivation]] +==== Deactivating Conditions + +Sometimes it can be useful to run a test suite _without_ certain conditions being active. +For example, you may wish to run tests even if they are annotated with `@Disabled` in +order to see if they are still _broken_. To do this, provide a pattern for the +`junit.jupiter.conditions.deactivate` _configuration parameter_ to specify which +conditions should be deactivated (i.e., not evaluated) for the current test run. The +pattern can be supplied as a JVM system property, as a _configuration parameter_ in the +`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform +configuration file (see <> for details). + +For example, to deactivate JUnit's `@Disabled` condition, you can start your JVM with the +following system property. + +`-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition` + +[[extensions-conditions-deactivation-patterns]] +===== Pattern Matching Syntax + +Refer to <> for details. + +[[extensions-test-instance-pre-construct-callback]] +=== Test Instance Pre-construct Callback + +`{TestInstancePreConstructCallback}` defines the API for `Extensions` that wish to be invoked +_prior_ to test instances being constructed (by a constructor call or via +`{TestInstanceFactory}`). + +This extension provides a symmetric call to `{TestInstancePreDestroyCallback}` and is useful +in combination with other extensions to prepare constructor parameters or keeping track of test +instances and their lifecycle. + +[[extensions-test-instance-factories]] +=== Test Instance Factories + +`{TestInstanceFactory}` defines the API for `Extensions` that wish to _create_ test class +instances. + +Common use cases include acquiring the test instance from a dependency injection +framework or invoking a static factory method to create the test class instance. + +If no `TestInstanceFactory` is registered, the framework will invoke the _sole_ +constructor for the test class to instantiate it, potentially resolving constructor +arguments via registered `ParameterResolver` extensions. + +Extensions that implement `TestInstanceFactory` can be registered on test interfaces, +top-level test classes, or `@Nested` test classes. + +[WARNING] +==== +Registering multiple extensions that implement `TestInstanceFactory` for any single class +will result in an exception being thrown for all tests in that class, in any subclass, +and in any nested class. Note that any `TestInstanceFactory` registered in a superclass +or _enclosing_ class (i.e., in the case of a `@Nested` test class) is _inherited_. It is +the user's responsibility to ensure that only a single `TestInstanceFactory` is +registered for any specific test class. +==== + +[[extensions-test-instance-post-processing]] +=== Test Instance Post-processing + +`{TestInstancePostProcessor}` defines the API for `Extensions` that wish to _post +process_ test instances. + +Common use cases include injecting dependencies into the test instance, invoking custom +initialization methods on the test instance, etc. + +For a concrete example, consult the source code for the `{MockitoExtension}` and the +`{SpringExtension}`. + +[[extensions-test-instance-pre-destroy-callback]] +=== Test Instance Pre-destroy Callback + +`{TestInstancePreDestroyCallback}` defines the API for `Extensions` that wish to process +test instances _after_ they have been used in tests and _before_ they are destroyed. + +Common use cases include cleaning dependencies that have been injected into the +test instance, invoking custom de-initialization methods on the test instance, etc. + +[[extensions-parameter-resolution]] +=== Parameter Resolution + +`{ParameterResolver}` defines the `Extension` API for dynamically resolving parameters at +runtime. + +If a _test class_ constructor, _test method_, or _lifecycle method_ (see +<>) declares a parameter, the parameter must be +_resolved_ at runtime by a `ParameterResolver`. A `ParameterResolver` can either be +built-in (see `{TestInfoParameterResolver}`) or <>. Generally speaking, parameters may be resolved by _name_, _type_, +_annotation_, or any combination thereof. + +If you wish to implement a custom `{ParameterResolver}` that resolves parameters based +solely on the type of the parameter, you may find it convenient to extend the +`{TypeBasedParameterResolver}` which serves as a generic adapter for such use cases. + +For concrete examples, consult the source code for `{CustomTypeParameterResolver}`, +`{CustomAnnotationParameterResolver}`, and `{MapOfListsTypeBasedParameterResolver}`. + +[WARNING] +==== +Due to a bug in the byte code generated by `javac` on JDK versions prior to JDK 9, +looking up annotations on parameters directly via the core `java.lang.reflect.Parameter` +API will always fail for _inner class_ constructors (e.g., a constructor in a `@Nested` +test class). + +The `{ParameterContext}` API supplied to `ParameterResolver` implementations therefore +includes the following convenience methods for correctly looking up annotations on +parameters. Extension authors are strongly encouraged to use these methods instead of +those provided in `java.lang.reflect.Parameter` in order to avoid this bug in the JDK. + +* `boolean isAnnotated(Class annotationType)` +* `Optional findAnnotation(Class annotationType)` +* `List findRepeatableAnnotations(Class annotationType)` +==== + +[NOTE] +==== +Other extensions can also leverage registered `ParameterResolvers` for method and +constructor invocations, using the `{ExecutableInvoker}` available via the +`getExecutableInvoker()` method in the `ExtensionContext`. +==== + +[[extensions-test-result-processing]] +=== Test Result Processing + +`{TestWatcher}` defines the API for extensions that wish to process the results of _test +method_ executions. Specifically, a `TestWatcher` will be invoked with contextual +information for the following events. + +* `testDisabled`: invoked after a disabled _test method_ has been skipped +* `testSuccessful`: invoked after a _test method_ has completed successfully +* `testAborted`: invoked after a _test method_ has been aborted +* `testFailed`: invoked after a _test method_ has failed + +NOTE: In contrast to the definition of "test method" presented in +<>, in this context _test method_ refers to any +`@Test` method or `@TestTemplate` method (for example, a `@RepeatedTest` or +`@ParameterizedTest`). + +Extensions implementing this interface can be registered at the method level or at the +class level. In the latter case they will be invoked for any contained _test method_ +including those in `@Nested` classes. + +[WARNING] +==== +Any instances of `ExtensionContext.Store.CloseableResource` stored in the `Store` of the +provided `{ExtensionContext}` will be closed _before_ methods in this API are invoked (see +<>). You can use the parent context's `Store` to work with such +resources. +==== + +[[extensions-lifecycle-callbacks]] +=== Test Lifecycle Callbacks + +The following interfaces define the APIs for extending tests at various points in the +test execution lifecycle. Consult the following sections for examples and the Javadoc for +each of these interfaces in the `{extension-api-package}` package for further details. + +* `{BeforeAllCallback}` +** `{BeforeEachCallback}` +*** `{BeforeTestExecutionCallback}` +*** `{AfterTestExecutionCallback}` +** `{AfterEachCallback}` +* `{AfterAllCallback}` + +.Implementing Multiple Extension APIs +NOTE: Extension developers may choose to implement any number of these interfaces +within a single extension. Consult the source code of the `{SpringExtension}` for a +concrete example. + +[[extensions-lifecycle-callbacks-before-after-execution]] +==== Before and After Test Execution Callbacks + +`{BeforeTestExecutionCallback}` and `{AfterTestExecutionCallback}` define the APIs for +`Extensions` that wish to add behavior that will be executed _immediately before_ and +_immediately after_ a test method is executed, respectively. As such, these callbacks are +well suited for timing, tracing, and similar use cases. If you need to implement +callbacks that are invoked _around_ `@BeforeEach` and `@AfterEach` methods, implement +`BeforeEachCallback` and `AfterEachCallback` instead. + +The following example shows how to use these callbacks to calculate and log the execution +time of a test method. `TimingExtension` implements both `BeforeTestExecutionCallback` +and `AfterTestExecutionCallback` in order to time and log the test execution. + +[[extensions-lifecycle-callbacks-timing-extension]] +[source,java,indent=0] +.An extension that times and logs the execution of test methods +---- +include::{testDir}/example/timing/TimingExtension.java[tags=user_guide] +---- + +Since the `TimingExtensionTests` class registers the `TimingExtension` via `@ExtendWith`, +its tests will have this timing applied when they execute. + +[source,java,indent=0] +.A test class that uses the example TimingExtension +---- +include::{testDir}/example/timing/TimingExtensionTests.java[tags=user_guide] +---- + +The following is an example of the logging produced when `TimingExtensionTests` is run. + +.... +INFO: Method [sleep20ms] took 24 ms. +INFO: Method [sleep50ms] took 53 ms. +.... + +[[extensions-exception-handling]] +=== Exception Handling + +Exceptions thrown during the test execution may be intercepted and handled accordingly +before propagating further, so that certain actions like error logging or resource releasing +may be defined in specialized `Extensions`. JUnit Jupiter offers API for `Extensions` that +wish to handle exceptions thrown during `@Test` methods via `{TestExecutionExceptionHandler}` +and for those thrown during one of test lifecycle methods (`@BeforeAll`, `@BeforeEach`, +`@AfterEach` and `@AfterAll`) via `{LifecycleMethodExecutionExceptionHandler}`. + +The following example shows an extension which will swallow all instances of `IOException` +but rethrow any other type of exception. + +[source,java,indent=0] +.An exception handling extension that filters IOExceptions in test execution +---- +include::{testDir}/example/exception/IgnoreIOExceptionExtension.java[tags=user_guide] +---- + +Another example shows how to record the state of an application under test exactly at +the point of unexpected exception being thrown during setup and cleanup. Note that unlike +relying on lifecycle callbacks, which may or may not be executed depending on the test +status, this solution guarantees execution immediately after failing `@BeforeAll`, +`@BeforeEach`, `@AfterEach` or `@AfterAll`. + +[source,java,indent=0] +.An exception handling extension that records application state on error +---- +include::{testDir}/example/exception/RecordStateOnErrorExtension.java[tags=user_guide] +---- + +Multiple execution exception handlers may be invoked for the same lifecycle method in +order of declaration. If one of the handlers swallows the handled exception, subsequent +ones will not be executed, and no failure will be propagated to JUnit engine, as if the +exception was never thrown. Handlers may also choose to rethrow the exception or throw +a different one, potentially wrapping the original. + +Extensions implementing `{LifecycleMethodExecutionExceptionHandler}` that wish to handle +exceptions thrown during `@BeforeAll` or `@AfterAll` need to be registered on a class level, +while handlers for `BeforeEach` and `AfterEach` may be also registered for individual +test methods. + +[source,java,indent=0] +.Registering multiple exception handling extensions +---- +include::{testDir}/example/exception/MultipleHandlersTestCase.java[tags=user_guide] +---- + +[[extensions-intercepting-invocations]] +=== Intercepting Invocations + +`{InvocationInterceptor}` defines the API for `Extensions` that wish to intercept calls to +test code. + +The following example shows an extension that executes all test methods in Swing's Event +Dispatch Thread. + +[source,java,indent=0] +.An extension that executes tests in a user-defined thread +---- +include::{testDir}/example/interceptor/SwingEdtInterceptor.java[tags=user_guide] +---- + +[[extensions-test-templates]] +=== Providing Invocation Contexts for Test Templates + +A `{TestTemplate}` method can only be executed when at least one +`{TestTemplateInvocationContextProvider}` is registered. Each such provider is responsible +for providing a `Stream` of `{TestTemplateInvocationContext}` instances. Each context may +specify a custom display name and a list of additional extensions that will only be used +for the next invocation of the `{TestTemplate}` method. + +The following example shows how to write a test template as well as how to register and +implement a `{TestTemplateInvocationContextProvider}`. + +[source,java,indent=0] +.A test template with accompanying extension +---- +include::{testDir}/example/TestTemplateDemo.java[tags=user_guide] +---- + +In this example, the test template will be invoked twice. The display names of the +invocations will be `apple` and `banana` as specified by the invocation context. Each +invocation registers a custom `{ParameterResolver}` which is used to resolve the method +parameter. The output when using the `ConsoleLauncher` is as follows. + +.... +└─ testTemplate(String) ✔ + ├─ apple ✔ + └─ banana ✔ +.... + +The `{TestTemplateInvocationContextProvider}` extension API is primarily intended for +implementing different kinds of tests that rely on repetitive invocation of a test-like +method albeit in different contexts — for example, with different parameters, by preparing +the test class instance differently, or multiple times without modifying the context. +Please refer to the implementations of <> or +<> which use this extension point to provide their +functionality. + + +[[extensions-keeping-state]] +=== Keeping State in Extensions + +Usually, an extension is instantiated only once. So the question becomes relevant: How do +you keep the state from one invocation of an extension to the next? The +`ExtensionContext` API provides a `Store` exactly for this purpose. Extensions may put +values into a store for later retrieval. See the +`<>` for an example of +using the `Store` with a method-level scope. It is important to remember that values +stored in an `ExtensionContext` during test execution will not be available in the +surrounding `ExtensionContext`. Since `ExtensionContexts` may be nested, the scope of +inner contexts may also be limited. Consult the corresponding Javadoc for details on the +methods available for storing and retrieving values via the `{ExtensionContext_Store}`. + +.`ExtensionContext.Store.CloseableResource` +NOTE: An extension context store is bound to its extension context lifecycle. When an +extension context lifecycle ends it closes its associated store. All stored values +that are instances of `CloseableResource` are notified by an invocation of their `close()` +method in the inverse order they were added in. + +[[extensions-supported-utilities]] +=== Supported Utilities in Extensions + +The `junit-platform-commons` artifact exposes a package named +`{junit-platform-support-package}` that contains _maintained_ utility methods for working +with annotations, classes, reflection, and classpath scanning tasks. `TestEngine` and +`Extension` authors are encouraged to use these supported methods in order to align with +the behavior of the JUnit Platform. + +[[extensions-supported-utilities-annotations]] +==== Annotation Support + +`AnnotationSupport` provides static utility methods that operate on annotated elements +(e.g., packages, annotations, classes, interfaces, constructors, methods, and fields). +These include methods to check whether an element is annotated or meta-annotated with a +particular annotation, to search for specific annotations, and to find annotated methods +and fields in a class or interface. Some of these methods search on implemented +interfaces and within class hierarchies to find annotations. Consult the Javadoc for +`{AnnotationSupport}` for further details. + +[[extensions-supported-utilities-classes]] +==== Class Support + +`ClassSupport` provides static utility methods for working with classes (i.e., instances +of `java.lang.Class`). Consult the Javadoc for `{ClassSupport}` for further details. + +[[extensions-supported-utilities-reflection]] +==== Reflection Support + +`ReflectionSupport` provides static utility methods that augment the standard JDK +reflection and class-loading mechanisms. These include methods to scan the classpath in +search of classes matching specified predicates, to load and create new instances of a +class, and to find and invoke methods. Some of these methods traverse class hierarchies +to locate matching methods. Consult the Javadoc for `{ReflectionSupport}` for further +details. + +[[extensions-supported-utilities-modifier]] +==== Modifier Support + +`ModifierSupport` provides static utility methods for working with member and class +modifiers -- for example, to determine if a member is declared as `public`, `private`, +`abstract`, `static`, etc. Consult the Javadoc for `{ModifierSupport}` for further +details. + + +[[extensions-execution-order]] +=== Relative Execution Order of User Code and Extensions + +When executing a test class that contains one or more test methods, a number of extension +callbacks are called in addition to the user-supplied test and lifecycle methods. + +NOTE: See also: <> + +[[extensions-execution-order-overview]] +==== User and Extension Code + +The following diagram illustrates the relative order of user-supplied code and extension +code. User-supplied test and lifecycle methods are shown in orange, with callback code +implemented by extensions shown in blue. The grey box denotes the execution of a single +test method and will be repeated for every test method in the test class. + +:figure-caption: User code and extension code + +[#extensions-execution-order-diagram,reftext='{figure-caption}'] +image::extensions_lifecycle.png[caption='',title='{figure-caption}'] + +The following table further explains the sixteen steps in the +<> diagram. + +[cols="5,15,80"] +|=== +| Step | Interface/Annotation | Description + +| 1 +| interface `org.junit.jupiter.api.extension.BeforeAllCallback` +| extension code executed before all tests of the container are executed + +| 2 +| annotation `org.junit.jupiter.api.BeforeAll` +| user code executed before all tests of the container are executed + +| 3 +| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler +#handleBeforeAllMethodExecutionException` +| extension code for handling exceptions thrown from `@BeforeAll` methods + +| 4 +| interface `org.junit.jupiter.api.extension.BeforeEachCallback` +| extension code executed before each test is executed + +| 5 +| annotation `org.junit.jupiter.api.BeforeEach` +| user code executed before each test is executed + +| 6 +| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler +#handleBeforeEachMethodExecutionException` +| extension code for handling exceptions thrown from `@BeforeEach` methods + +| 7 +| interface `org.junit.jupiter.api.extension.BeforeTestExecutionCallback` +| extension code executed immediately before a test is executed + +| 8 +| annotation `org.junit.jupiter.api.Test` +| user code of the actual test method + +| 9 +| interface `org.junit.jupiter.api.extension.TestExecutionExceptionHandler` +| extension code for handling exceptions thrown during a test + +| 10 +| interface `org.junit.jupiter.api.extension.AfterTestExecutionCallback` +| extension code executed immediately after test execution and its corresponding exception handlers + +| 11 +| annotation `org.junit.jupiter.api.AfterEach` +| user code executed after each test is executed + +| 12 +| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler +#handleAfterEachMethodExecutionException` +| extension code for handling exceptions thrown from `@AfterEach` methods + +| 13 +| interface `org.junit.jupiter.api.extension.AfterEachCallback` +| extension code executed after each test is executed + +| 14 +| annotation `org.junit.jupiter.api.AfterAll` +| user code executed after all tests of the container are executed + +| 15 +| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler +#handleAfterAllMethodExecutionException` +| extension code for handling exceptions thrown from `@AfterAll` methods + +| 16 +| interface `org.junit.jupiter.api.extension.AfterAllCallback` +| extension code executed after all tests of the container are executed + +|=== + +In the simplest case only the actual test method will be executed (step 8); all other +steps are optional depending on the presence of user code or extension support for the +corresponding lifecycle callback. For further details on the various lifecycle callbacks +please consult the respective Javadoc for each annotation and extension. + +All invocations of user code methods in the above table can additionally be intercepted +by implementing <>. + +[[extensions-execution-order-wrapping-behavior]] +==== Wrapping Behavior of Callbacks + +JUnit Jupiter always guarantees _wrapping_ behavior for multiple registered extensions +that implement lifecycle callbacks such as `BeforeAllCallback`, `AfterAllCallback`, +`BeforeEachCallback`, `AfterEachCallback`, `BeforeTestExecutionCallback`, and +`AfterTestExecutionCallback`. + +That means that, given two extensions `Extension1` and `Extension2` with `Extension1` +registered before `Extension2`, any "before" callbacks implemented by `Extension1` are +guaranteed to execute **before** any "before" callbacks implemented by `Extension2`. +Similarly, given the two same two extensions registered in the same order, any "after" +callbacks implemented by `Extension1` are guaranteed to execute **after** any "after" +callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_ +`Extension2`. + +JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies +for user-supplied _lifecycle methods_ (see <>). + +* `@BeforeAll` methods are inherited from superclasses as long as they are not _hidden_, + _overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@BeforeAll` methods from superclasses will be + executed **before** `@BeforeAll` methods in subclasses. +** Similarly, `@BeforeAll` methods declared in an interface are inherited as long as they + are not _hidden_ or _overridden_, and `@BeforeAll` methods from an interface will be + executed **before** `@BeforeAll` methods in the class that implements the interface. +* `@AfterAll` methods are inherited from superclasses as long as they are not _hidden_, + _overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@AfterAll` methods from superclasses will be + executed **after** `@AfterAll` methods in subclasses. +** Similarly, `@AfterAll` methods declared in an interface are inherited as long as they + are not _hidden_ or _overridden_, and `@AfterAll` methods from an interface will be + executed **after** `@AfterAll` methods in the class that implements the interface. +* `@BeforeEach` methods are inherited from superclasses as long as they are not + _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@BeforeEach` methods from superclasses will be + executed **before** `@BeforeEach` methods in subclasses. +** Similarly, `@BeforeEach` methods declared as interface default methods are inherited as + long as they are not _overridden_, and `@BeforeEach` default methods will be executed + **before** `@BeforeEach` methods in the class that implements the interface. +* `@AfterEach` methods are inherited from superclasses as long as they are not + _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of + Java's visibility rules). Furthermore, `@AfterEach` methods from superclasses will be + executed **after** `@AfterEach` methods in subclasses. +** Similarly, `@AfterEach` methods declared as interface default methods are inherited as + long as they are not _overridden_, and `@AfterEach` default methods will be executed + **after** `@AfterEach` methods in the class that implements the interface. + +The following examples demonstrate this behavior. Please note that the examples do not +actually do anything realistic. Instead, they mimic common scenarios for testing +interactions with the database. All methods imported statically from the `Logger` class +log contextual information in order to help us better understand the execution order of +user-supplied callback methods and callback methods in extensions. + +[source,java,indent=0] +.Extension1 +---- +include::{testDir}/example/callbacks/Extension1.java[tags=user_guide] +---- + +[source,java,indent=0] +.Extension2 +---- +include::{testDir}/example/callbacks/Extension2.java[tags=user_guide] +---- + +[source,java,indent=0] +.AbstractDatabaseTests +---- +include::{testDir}/example/callbacks/AbstractDatabaseTests.java[tags=user_guide] +---- + +[source,java,indent=0] +.DatabaseTestsDemo +---- +include::{testDir}/example/callbacks/DatabaseTestsDemo.java[tags=user_guide] +---- + +When the `DatabaseTestsDemo` test class is executed, the following is logged. + +---- +@BeforeAll AbstractDatabaseTests.createDatabase() +@BeforeAll DatabaseTestsDemo.beforeAll() + Extension1.beforeEach() + Extension2.beforeEach() + @BeforeEach AbstractDatabaseTests.connectToDatabase() + @BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase() + @Test DatabaseTestsDemo.testDatabaseFunctionality() + @AfterEach DatabaseTestsDemo.deleteTestDataFromDatabase() + @AfterEach AbstractDatabaseTests.disconnectFromDatabase() + Extension2.afterEach() + Extension1.afterEach() +@BeforeAll DatabaseTestsDemo.afterAll() +@AfterAll AbstractDatabaseTests.destroyDatabase() +---- + +The following sequence diagram helps to shed further light on what actually goes on within +the `JupiterTestEngine` when the `DatabaseTestsDemo` test class is executed. + +//// +PNG generated using ZenUML: https://app.zenuml.com + +See corresponding *.txt file in images folder for the source. +//// +image::extensions_DatabaseTestsDemo.png[caption='',title='DatabaseTestsDemo'] + +JUnit Jupiter does **not** guarantee the execution order of multiple lifecycle methods +that are declared within a _single_ test class or test interface. It may at times appear +that JUnit Jupiter invokes such methods in alphabetical order. However, that is not +precisely true. The ordering is analogous to the ordering for `@Test` methods within a +single test class. + +[NOTE] +==== +Lifecycle methods that are declared within a _single_ test class or test interface will be +ordered using an algorithm that is deterministic but intentionally non-obvious. This +ensures that subsequent runs of a test suite execute lifecycle methods in the same order, +thereby allowing for repeatable builds. +==== + +In addition, JUnit Jupiter does **not** support _wrapping_ behavior for multiple lifecycle +methods declared within a single test class or test interface. + +The following example demonstrates this behavior. Specifically, the lifecycle method +configuration is _broken_ due to the order in which the locally declared lifecycle methods +are executed. + +* Test data is inserted _before_ the database connection has been opened, which results in + a failure to connect to the database. +* The database connection is closed _before_ deleting the test data, which results in a + failure to connect to the database. + +[source,java,indent=0] +.BrokenLifecycleMethodConfigDemo +---- +include::{testDir}/example/callbacks/BrokenLifecycleMethodConfigDemo.java[tags=user_guide] +---- + +When the `BrokenLifecycleMethodConfigDemo` test class is executed, the following is logged. + +---- +Extension1.beforeEach() +Extension2.beforeEach() + @BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase() + @BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase() + @Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality() + @AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase() + @AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase() +Extension2.afterEach() +Extension1.afterEach() +---- + +The following sequence diagram helps to shed further light on what actually goes on within +the `JupiterTestEngine` when the `BrokenLifecycleMethodConfigDemo` test class is executed. + +//// +PNG generated using ZenUML: https://app.zenuml.com + +See corresponding *.txt file in images folder for the source. +//// +image::extensions_BrokenLifecycleMethodConfigDemo.png[caption='',title='BrokenLifecycleMethodConfigDemo'] + +[TIP] +==== +Due to the aforementioned behavior, the JUnit Team recommends that developers declare at +most one of each type of _lifecycle method_ (see <>) +per test class or test interface unless there are no dependencies between such lifecycle +methods. +==== diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.png b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.png new file mode 100644 index 0000000000000000000000000000000000000000..3f661c200d6031f9622e7987969643e3cf2894c0 GIT binary patch literal 60272 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 literal 0 HcmV?d00001 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt new file mode 100644 index 00000000..8da511fa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt @@ -0,0 +1,27 @@ +@Starter(Platform) +Result = JupiterEngine.execute(DatabaseTestsDemo) { + + // @BeforeAll (static invocation) + AbstractDatabaseTests.createDatabase() + // @BeforeAll (static invocation) + DatabaseTestsDemo.beforeAll() + Extension1.beforeEach() + Extension2.beforeEach() + // @BeforeEach inherited from AbstractDatabaseTests + DatabaseTestsDemo.connectToDatabase() + // @BeforeEach + DatabaseTestsDemo.insertTestDataIntoDatabase() + // @Test + DatabaseTestsDemo.testDatabaseFunctionality() + // @AfterEach + DatabaseTestsDemo.deleteTestDataFromDatabase() + // @AfterEach inherited from AbstractDatabaseTests + DatabaseTestsDemo.disconnectFromDatabase() + Extension2.afterEach() + Extension1.afterEach() + // @AfterAll (static invocation) + DatabaseTestsDemo.afterAll() + // @AfterAll (static invocation) + AbstractDatabaseTests.destroyDatabase() + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle.png b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..bf8671b33f14e5734cf783128583603326cff54e GIT binary patch literal 223256 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

8p%Mdq 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 F`+o(w=xqQ1 literal 0 HcmV?d00001 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle_source.docx b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle_source.docx new file mode 100644 index 0000000000000000000000000000000000000000..ed2d03f158c31a403164fe66fce94f2fcbb02f9a GIT binary patch literal 15930 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 y;LuO`d;kCK^7%XXujAz(PynEj2019-01-012019-01-012019-01-022019-01-022019-01-032019-01-032019-01-042019-01-042019-01-05A.test1() A.test2() B.test1() B.test2() A.test1() A.test2() B.test1() B.test2() A.test1() A.test2() B.test1() B.test2() A.test1() A.test2() B.test1() B.test2() (same_thread, same_thread)(same_thread, concurrent)(concurrent, same_thread)(concurrent, concurrent) \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png new file mode 100644 index 0000000000000000000000000000000000000000..8a5dd0a1d1587bdeceb95ba49d5daee71f1df8ed GIT binary patch literal 230224 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 z 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 h2EPt_KAIC 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

*Yu9?kYueZX07fPg7FKePXyy`SA1+k?Nae>Sre zen0>D-j#($vhVMok4iWEq<2mJn!L2~(<@i6#b1l>W`4bXvhLdbYk=_I`LE&GJ!dLUUHx3vIVCg!0CIfKCjbBd literal 0 HcmV?d00001 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc new file mode 100644 index 00000000..57e0f309 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc @@ -0,0 +1,44 @@ +[[user-guide]] += JUnit 5 User Guide +Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juliette de Rancourt; Christian Stein +:basedir: {includedir}/user-guide +:pdf-fontsdir: GEM_FONTS_DIR;{includedir}/resources/fonts +:pdf-theme: {includedir}/resources/themes/junit-pdf-theme.yml +:imagesdir: images +:imagesoutdir: {outdir}/user-guide/images +// +:docinfodir: {includedir}/docinfos +:docinfo2: +// +ifdef::backend-pdf[:imagesdir: {imagesoutdir}] +// +// Blank lines are not permitted in the doc-header: https://asciidoctor.org/docs/user-manual/#doc-header +// +:sectnums: +:toclevels: 4 +// + +include::{includedir}/link-attributes.adoc[] + +include::{basedir}/overview.adoc[] + +include::{basedir}/writing-tests.adoc[] + +include::{basedir}/migration-from-junit4.adoc[] + +include::{basedir}/running-tests.adoc[] + +include::{basedir}/extensions.adoc[] + +include::{basedir}/advanced-topics.adoc[] + +include::{basedir}/api-evolution.adoc[] + +include::{basedir}/contributors.adoc[] + +[[release-notes]] +== Release Notes + +The release notes are available link:{releaseNotesUrl}[here]. + +include::{basedir}/appendix.adoc[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc new file mode 100644 index 00000000..09d351fb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc @@ -0,0 +1,155 @@ +[[migrating-from-junit4]] +== Migrating from JUnit 4 + +Although the JUnit Jupiter programming model and extension model do not support JUnit 4 +features such as `Rules` and `Runners` natively, it is not expected that source code +maintainers will need to update all of their existing tests, test extensions, and custom +build test infrastructure to migrate to JUnit Jupiter. + +Instead, JUnit provides a gentle migration path via a _JUnit Vintage test engine_ which +allows existing tests based on JUnit 3 and JUnit 4 to be executed using the JUnit Platform +infrastructure. Since all classes and annotations specific to JUnit Jupiter reside under +the `org.junit.jupiter` base package, having both JUnit 4 and JUnit Jupiter in the +classpath does not lead to any conflicts. It is therefore safe to maintain existing JUnit +4 tests alongside JUnit Jupiter tests. Furthermore, since the JUnit team will continue to +provide maintenance and bug fix releases for the JUnit 4.x baseline, developers have +plenty of time to migrate to JUnit Jupiter on their own schedule. + + +[[migrating-from-junit4-running]] +=== Running JUnit 4 Tests on the JUnit Platform + +Make sure that the `junit-vintage-engine` artifact is in your test runtime path. In that +case JUnit 3 and JUnit 4 tests will automatically be picked up by the JUnit Platform +launcher. + +See the example projects in the {junit5-samples-repo}[`junit5-samples`] repository to +find out how this is done with Gradle and Maven. + +[[migrating-from-junit4-categories-support]] +==== Categories Support + +For test classes or methods that are annotated with `@Category`, the _JUnit Vintage test +engine_ exposes the category's fully qualified class name as a <> +for the corresponding test class or test method. For example, if a test method is +annotated with `@Category(Example.class)`, it will be tagged with `"com.acme.Example"`. +Similar to the `Categories` runner in JUnit 4, this information can be used to filter the +discovered tests before executing them (see <> for details). + + +[[migrating-from-junit4-tips]] +=== Migration Tips + +The following are topics that you should be aware of when migrating existing JUnit 4 +tests to JUnit Jupiter. + +* Annotations reside in the `org.junit.jupiter.api` package. +* Assertions reside in `org.junit.jupiter.api.Assertions`. + - Note that you may continue to use assertion methods from `org.junit.Assert` or any + other assertion library such as {AssertJ}, {Hamcrest}, {Truth}, etc. +* Assumptions reside in `org.junit.jupiter.api.Assumptions`. + - Note that JUnit Jupiter 5.4 and later versions support methods from JUnit 4's + `org.junit.Assume` class for assumptions. Specifically, JUnit Jupiter supports JUnit + 4's `AssumptionViolatedException` to signal that a test should be aborted instead of + marked as a failure. +* `@Before` and `@After` no longer exist; use `@BeforeEach` and `@AfterEach` instead. +* `@BeforeClass` and `@AfterClass` no longer exist; use `@BeforeAll` and `@AfterAll` + instead. +* `@Ignore` no longer exists: use `@Disabled` or one of the other built-in + <> instead + - See also <>. +* `@Category` no longer exists; use `@Tag` instead. +* `@RunWith` no longer exists; superseded by `@ExtendWith`. +* `@Rule` and `@ClassRule` no longer exist; superseded by `@ExtendWith` and + `@RegisterExtension`. + - See also <>. +* Assertions and assumptions in JUnit Jupiter accept the failure message as their last + argument instead of the first one. + - See <> for details. + + +[[migrating-from-junit4-rule-support]] +=== Limited JUnit 4 Rule Support + +As stated above, JUnit Jupiter does not and will not support JUnit 4 rules natively. The +JUnit team realizes, however, that many organizations, especially large ones, are likely +to have large JUnit 4 code bases that make use of custom rules. To serve these +organizations and enable a gradual migration path the JUnit team has decided to support a +selection of JUnit 4 rules verbatim within JUnit Jupiter. This support is based on +adapters and is limited to those rules that are semantically compatible to the JUnit +Jupiter extension model, i.e. those that do not completely change the overall execution +flow of the test. + +The `junit-jupiter-migrationsupport` module from JUnit Jupiter currently supports the +following three `Rule` types including subclasses of these types: + +* `org.junit.rules.ExternalResource` (including `org.junit.rules.TemporaryFolder`) +* `org.junit.rules.Verifier` (including `org.junit.rules.ErrorCollector`) +* `org.junit.rules.ExpectedException` + +As in JUnit 4, Rule-annotated fields as well as methods are supported. By using these +class-level extensions on a test class such `Rule` implementations in legacy code bases +can be _left unchanged_ including the JUnit 4 rule import statements. + +This limited form of `Rule` support can be switched on by the class-level annotation +`{EnableRuleMigrationSupport}`. This annotation is a _composed annotation_ which enables +all rule migration support extensions: `VerifierSupport`, `ExternalResourceSupport`, and +`ExpectedExceptionSupport`. You may alternatively choose to annotate your test class with +`@EnableJUnit4MigrationSupport` which registers migration support for rules _and_ JUnit +4's `@Ignore` annotation (see <>). + +However, if you intend to develop a new extension for JUnit Jupiter please use the new +extension model of JUnit Jupiter instead of the rule-based model of JUnit 4. + + +[[migrating-from-junit4-ignore-annotation-support]] +=== JUnit 4 @Ignore Support + +In order to provide a smooth migration path from JUnit 4 to JUnit Jupiter, the +`junit-jupiter-migrationsupport` module provides support for JUnit 4's `@Ignore` +annotation analogous to Jupiter's `{Disabled}` annotation. + +To use `@Ignore` with JUnit Jupiter based tests, configure a _test_ dependency on the +`junit-jupiter-migrationsupport` module in your build and then annotate your test class +with `@ExtendWith(IgnoreCondition.class)` or `{EnableJUnit4MigrationSupport}` (which +automatically registers the `IgnoreCondition` along with +<>). The `IgnoreCondition` is an +`{ExecutionCondition}` that disables test classes or test methods that are annotated with +`@Ignore`. + +[source,java,indent=0] +---- +include::{testDir}/example/IgnoredTestsDemo.java[tags=user_guide] +---- + + +[[migrating-from-junit4-failure-message-arguments]] +=== Failure Message Arguments + +The `Assumptions` and `Assertions` classes in JUnit Jupiter declare arguments in a +different order than in JUnit 4. In JUnit 4 assertion and assumption methods accept the +failure message as the first argument; whereas, in JUnit Jupiter assertion and assumption +methods accept the failure message as the last argument. + +For instance, the method `assertEquals` in JUnit 4 is declared as `assertEquals(String +message, Object expected, Object actual)`, but in JUnit Jupiter it is declared as +`assertEquals(Object expected, Object actual, String message)`. The rationale for this is +that a failure message is _optional_, and optional arguments should be declared after +required arguments in a method signature. + +The methods affected by this change are the following: + +- Assertions + * `assertTrue` + * `assertFalse` + * `assertNull` + * `assertNotNull` + * `assertEquals` + * `assertNotEquals` + * `assertArrayEquals` + * `assertSame` + * `assertNotSame` + * `assertThrows` +- Assumptions + * `assumeTrue` + * `assumeFalse` diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc new file mode 100644 index 00000000..d06a5eb5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc @@ -0,0 +1,90 @@ +[[overview]] +== Overview + +The goal of this document is to provide comprehensive reference documentation for +programmers writing tests, extension authors, and engine authors as well as build tool +and IDE vendors. + +ifdef::backend-html5[] +ifdef::linkToPdf[] +This document is also available as a link:{userGuidePdfFileName}[PDF download]. +endif::linkToPdf[] +endif::backend-html5[] + +[[overview-what-is-junit-5]] +=== What is JUnit 5? + +Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from +three different sub-projects. + +**JUnit 5 = _JUnit Platform_ + _JUnit Jupiter_ + _JUnit Vintage_** + +The **JUnit Platform** serves as a foundation for <> on the JVM. It also defines the `{TestEngine}` API for developing a testing +framework that runs on the platform. Furthermore, the platform provides a +<> to launch the platform from the +command line and the <> for running a custom test suite using +one or more test engines on the platform. First-class support for the JUnit Platform also +exists in popular IDEs (see <>, +<>, <>, and +<>) and build tools (see <>, +<>, and <>). + +**JUnit Jupiter** is the combination of the <> and +<> for writing tests and extensions in JUnit 5. The Jupiter +sub-project provides a `TestEngine` for running Jupiter based tests on the platform. + +**JUnit Vintage** provides a `TestEngine` for running JUnit 3 and JUnit 4 based tests on +the platform. It requires JUnit 4.12 or later to be present on the class path or module +path. + +[[overview-java-versions]] +=== Supported Java Versions + +JUnit 5 requires Java 8 (or higher) at runtime. However, you can still test code that +has been compiled with previous versions of the JDK. + +[[overview-getting-help]] +=== Getting Help + +Ask JUnit 5 related questions on {StackOverflow} or chat with the community on {Gitter}. + +[[overview-getting-started]] +=== Getting Started + +[[overview-getting-started-junit-artifacts]] +==== Downloading JUnit Artifacts + +To find out what artifacts are available for download and inclusion in your project, refer +to <>. To set up dependency management for your build, refer to +<> and the <>. + +[[overview-getting-started-features]] +==== JUnit 5 Features + +To find out what features are available in JUnit 5 and how to use them, read the +corresponding sections of this User Guide, organized by topic. + +* <> +* <> +* <> +* <> +* Advanced Topics + - <> + - <> + +[[overview-getting-started-example-projects]] +==== Example Projects + +To see complete, working examples of projects that you can copy and experiment with, the +{junit5-samples-repo}[`junit5-samples`] repository is a good place to start. The +`junit5-samples` repository hosts a collection of sample projects based on JUnit Jupiter, +JUnit Vintage, and other testing frameworks. You'll find appropriate build scripts (e.g., +`build.gradle`, `pom.xml`, etc.) in the example projects. The links below highlight some +of the combinations you can choose from. + +* For Gradle and Java, check out the `{junit5-jupiter-starter-gradle}` project. +* For Gradle and Kotlin, check out the `{junit5-jupiter-starter-gradle-kotlin}` project. +* For Gradle and Groovy, check out the `{junit5-jupiter-starter-gradle-groovy}` project. +* For Maven, check out the `{junit5-jupiter-starter-maven}` project. +* For Ant, check out the `{junit5-jupiter-starter-ant}` project. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc new file mode 100644 index 00000000..e88d0056 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc @@ -0,0 +1,1073 @@ +[[running-tests]] +== Running Tests + +[[running-tests-ide]] +=== IDE Support + +[[running-tests-ide-intellij-idea]] +==== IntelliJ IDEA + +IntelliJ IDEA supports running tests on the JUnit Platform since version 2016.2. For +details please see the +https://blog.jetbrains.com/idea/2016/08/using-junit-5-in-intellij-idea/[post on the +IntelliJ IDEA blog]. Note, however, that it is recommended to use IDEA 2017.3 or newer +since these newer versions of IDEA will download the following JARs automatically based +on the API version used in the project: `junit-platform-launcher`, +`junit-jupiter-engine`, and `junit-vintage-engine`. + +WARNING: IntelliJ IDEA releases prior to IDEA 2017.3 bundle specific versions of JUnit 5. +Thus, if you want to use a newer version of JUnit Jupiter, execution of tests within the +IDE might fail due to version conflicts. In such cases, please follow the instructions +below to use a newer version of JUnit 5 than the one bundled with IntelliJ IDEA. + +In order to use a different JUnit 5 version (e.g., {jupiter-version}), you may need to +include the corresponding versions of the `junit-platform-launcher`, +`junit-jupiter-engine`, and `junit-vintage-engine` JARs in the classpath. + +.Additional Gradle Dependencies +[source,groovy] +[subs=attributes+] +---- +testImplementation(platform("org.junit:junit-bom:{bom-version}")) +testRuntimeOnly("org.junit.platform:junit-platform-launcher") { + because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions") +} +testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") +testRuntimeOnly("org.junit.vintage:junit-vintage-engine") +---- + +.Additional Maven Dependencies +[source,xml] +[subs=attributes+] +---- + + + + + org.junit.platform + junit-platform-launcher + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.vintage + junit-vintage-engine + test + + + + + + org.junit + junit-bom + {bom-version} + pom + import + + + +---- + +[[running-tests-ide-eclipse]] +==== Eclipse + +Eclipse IDE offers support for the JUnit Platform since the Eclipse Oxygen.1a (4.7.1a) +release. + +For more information on using JUnit 5 in Eclipse consult the official _Eclipse support +for JUnit 5_ section of the +https://www.eclipse.org/eclipse/news/4.7.1a/#junit-5-support[Eclipse Project Oxygen.1a +(4.7.1a) - New and Noteworthy] documentation. + +[[running-tests-ide-netbeans]] +==== NetBeans + +NetBeans offers support for JUnit Jupiter and the JUnit Platform since the +https://netbeans.apache.org/download/nb100/nb100.html[Apache NetBeans 10.0 release]. + +For more information consult the JUnit 5 section of the +https://netbeans.apache.org/download/nb100/index.html#_junit_5[Apache NetBeans 10.0 +release notes]. + +[[running-tests-ide-vscode]] +==== Visual Studio Code + +https://code.visualstudio.com/[Visual Studio Code] supports JUnit Jupiter and the JUnit +Platform via the +https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test[Java Test +Runner] extension which is installed by default as part of the +https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack[Java +Extension Pack]. + +For more information consult the _Testing_ section of the +https://code.visualstudio.com/docs/languages/java#_testing[Java in Visual Studio Code] +documentation. + +[[running-tests-ide-other]] +==== Other IDEs + +If you are using an editor or IDE other than one of those listed in the previous sections, +the JUnit team provides two alternative solutions to assist you in using JUnit 5. You can +use the <> manually -- for example, from the command line +-- or execute tests with a <> if +your IDE has built-in support for JUnit 4. + +[[running-tests-build]] +=== Build Support + +[[running-tests-build-gradle]] +==== Gradle + +[WARNING] +.The JUnit Platform Gradle Plugin has been discontinued +==== +The `junit-platform-gradle-plugin` developed by the JUnit team was deprecated in JUnit +Platform 1.2 and discontinued in 1.3. Please switch to Gradle's standard `test` task. +==== + +Starting with https://docs.gradle.org/4.6/release-notes.html[version 4.6], Gradle provides +https://docs.gradle.org/current/userguide/java_testing.html#using_junit5[native support] +for executing tests on the JUnit Platform. To enable it, you need to specify +`useJUnitPlatform()` within a `test` task declaration in `build.gradle`: + +[source,groovy,indent=0] +[subs=attributes+] +---- +test { + useJUnitPlatform() +} +---- + +Filtering by <>, +<>, or engines is also supported: + +[source,groovy,indent=0] +[subs=attributes+] +---- +test { + useJUnitPlatform { + includeTags("fast", "smoke & feature-a") + // excludeTags("slow", "ci") + includeEngines("junit-jupiter") + // excludeEngines("junit-vintage") + } +} +---- + +Please refer to the +https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_test[official Gradle documentation] +for a comprehensive list of options. + +NOTE: See <> for details on how to override the version +of JUnit used in your Spring Boot application. + +[[running-tests-build-gradle-config-params]] +===== Configuration Parameters + +The standard Gradle `test` task currently does not provide a dedicated DSL to set JUnit +Platform <> to influence test +discovery and execution. However, you can provide configuration parameters within the +build script via system properties (as shown below) or via the +`junit-platform.properties` file. + +[source,groovy,indent=0] +---- +test { + // ... + systemProperty("junit.jupiter.conditions.deactivate", "*") + systemProperty("junit.jupiter.extensions.autodetection.enabled", true) + systemProperty("junit.jupiter.testinstance.lifecycle.default", "per_class") + // ... +} +---- + +[[running-tests-build-gradle-engines-configure]] +===== Configuring Test Engines + +In order to run any tests at all, a `TestEngine` implementation must be on the classpath. + +To configure support for JUnit Jupiter based tests, configure a `testImplementation` dependency +on the dependency-aggregating JUnit Jupiter artifact similar to the following. + +[source,groovy,indent=0] +[subs=attributes+] +---- +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter:{jupiter-version}") +} +---- + +The JUnit Platform can run JUnit 4 based tests as long as you configure a `testImplementation` +dependency on JUnit 4 and a `testRuntimeOnly` dependency on the JUnit Vintage `TestEngine` +implementation similar to the following. + +[source,groovy,indent=0] +[subs=attributes+] +---- +dependencies { + testImplementation("junit:junit:{junit4-version}") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:{vintage-version}") +} +---- + +[[running-tests-build-gradle-logging]] +===== Configuring Logging (optional) + +JUnit uses the Java Logging APIs in the `java.util.logging` package (a.k.a. _JUL_) to +emit warnings and debug information. Please refer to the official documentation of +`{LogManager}` for configuration options. + +Alternatively, it's possible to redirect log messages to other logging frameworks such as +{Log4j} or {Logback}. To use a logging framework that provides a custom implementation of +`{LogManager}`, set the `java.util.logging.manager` system property to the _fully +qualified class name_ of the `{LogManager}` implementation to use. The example below +demonstrates how to configure Log4j{nbsp}2.x (see {Log4j_JDK_Logging_Adapter} for +details). + +[source,groovy,indent=0] +[subs=attributes+] +---- +test { + systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") +} +---- + +Other logging frameworks provide different means to redirect messages logged using +`java.util.logging`. For example, for {Logback} you can use the +https://www.slf4j.org/legacy.html#jul-to-slf4j[JUL to SLF4J Bridge] by adding an +additional dependency to the runtime classpath. + +[[running-tests-build-maven]] +==== Maven + +Starting with https://issues.apache.org/jira/browse/SUREFIRE-1330[version 2.22.0], Maven +Surefire and Maven Failsafe provide +https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html[native support] +for executing tests on the JUnit Platform. The `pom.xml` file in the +`{junit5-jupiter-starter-maven}` project demonstrates how to use the Maven Surefire plugin +and can serve as a starting point for configuring your Maven build. + +[WARNING] +.Use Maven Surefire/Failsafe 3.0.0-M4 or later to avoid interoperability issues +==== +Maven Surefire/Failsafe 3.0.0-M4 +https://issues.apache.org/jira/browse/SUREFIRE-1585[introduced support] for aligning the +version of the JUnit Platform Launcher it uses with the JUnit Platform version found on +the test runtime classpath. Therefore, it is recommended to use version 3.0.0-M4 or later +to avoid interoperability issues. + +Alternatively, you can add a test dependency on the matching version of the JUnit Platform +Launcher to your Maven build as follows. + +[source,xml] +[subs=attributes+] +---- + + org.junit.platform + junit-platform-launcher + {platform-version} + test + +---- +==== + +TIP: See <> for details on how to override the version +of JUnit used in your Spring Boot application. + +[[running-tests-build-maven-engines-configure]] +===== Configuring Test Engines + +In order to have Maven Surefire or Maven Failsafe run any tests at all, at least one +`TestEngine` implementation must be added to the test classpath. + +To configure support for JUnit Jupiter based tests, configure `test` scoped dependencies +on the JUnit Jupiter API and the JUnit Jupiter `TestEngine` implementation similar to the +following. + +[source,xml,indent=0] +[subs=attributes+] +---- + + + + + org.junit.jupiter + junit-jupiter + {jupiter-version} + test + + + + + + + maven-surefire-plugin + {surefire-version} + + + maven-failsafe-plugin + {surefire-version} + + + + +---- + +Maven Surefire and Maven Failsafe can run JUnit 4 based tests alongside Jupiter tests as +long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintage +`TestEngine` implementation similar to the following. + +[source,xml,indent=0] +[subs=attributes+] +---- + + + + + junit + junit + {junit4-version} + test + + + org.junit.vintage + junit-vintage-engine + {vintage-version} + test + + + + + + + + maven-surefire-plugin + {surefire-version} + + + maven-failsafe-plugin + {surefire-version} + + + + +---- + +[[running-tests-build-maven-filter-test-class-names]] +===== Filtering by Test Class Names + +The Maven Surefire Plugin will scan for test classes whose fully qualified names match +the following patterns. + +- `+++**/Test*.java+++` +- `+++**/*Test.java+++` +- `+++**/*Tests.java+++` +- `+++**/*TestCase.java+++` + +Moreover, it will exclude all nested classes (including static member classes) by default. + +Note, however, that you can override this default behavior by configuring explicit +`include` and `exclude` rules in your `pom.xml` file. For example, to keep Maven Surefire +from excluding static member classes, you can override its exclude rules as follows. + +[source,xml,indent=0] +[subs=attributes+] +.Overriding exclude rules of Maven Surefire +---- + + + + + maven-surefire-plugin + {surefire-version} + + + + + + + + + +---- + +Please see the +https://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html[Inclusions and Exclusions of Tests] +documentation for Maven Surefire for details. + +[[running-tests-build-maven-filter-tags]] +===== Filtering by Tags + +You can filter tests by <> or +<> using the following configuration +properties. + +- to include _tags_ or _tag expressions_, use `groups`. +- to exclude _tags_ or _tag expressions_, use `excludedGroups`. + +[source,xml,indent=0] +[subs=attributes+] +---- + + + + + maven-surefire-plugin + {surefire-version} + + acceptance | !feature-a + integration, regression + + + + + +---- + +[[running-tests-build-maven-config-params]] +===== Configuration Parameters + +You can set JUnit Platform <> to +influence test discovery and execution by declaring the `configurationParameters` +property and providing key-value pairs using the Java `Properties` file syntax (as shown +below) or via the `junit-platform.properties` file. + +[source,xml,indent=0] +[subs=attributes+] +---- + + + + + maven-surefire-plugin + {surefire-version} + + + + junit.jupiter.conditions.deactivate = * + junit.jupiter.extensions.autodetection.enabled = true + junit.jupiter.testinstance.lifecycle.default = per_class + + + + + + + +---- + +[[running-tests-build-ant]] +==== Ant + +Starting with version `1.10.3`, link:https://ant.apache.org/[Ant] has a +link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher`] task that +provides native support for launching tests on the JUnit Platform. The `junitlauncher` +task is solely responsible for launching the JUnit Platform and passing it the selected +collection of tests. The JUnit Platform then delegates to registered test engines to +discover and execute the tests. + +The `junitlauncher` task attempts to align as closely as possible with native Ant +constructs such as +link:https://ant.apache.org/manual/Types/resources.html#collection[resource collections] +for allowing users to select the tests that they want executed by test engines. This gives +the task a consistent and natural feel when compared to many other core Ant tasks. + +Starting with version `1.10.6` of Ant, the `junitlauncher` task supports +link:https://ant.apache.org/manual/Tasks/junitlauncher.html#fork[forking the tests in a separate JVM]. + +The `build.xml` file in the `{junit5-jupiter-starter-ant}` project demonstrates how to use +the task and can serve as a starting point. + +===== Basic Usage + +The following example demonstrates how to configure the `junitlauncher` task to select a +single test class (i.e., `org.myapp.test.MyFirstJUnit5Test`). + +[source,xml,indent=0] +---- + + + + + + + + + + + +---- + +The `test` element allows you to specify a single test class that you want to be selected +and executed. The `classpath` element allows you to specify the classpath to be used to +launch the JUnit Platform. This classpath will also be used to locate test classes that +are part of the execution. + +The following example demonstrates how to configure the `junitlauncher` task to select +test classes from multiple locations. + +[source,xml,indent=0] +---- + + + + + + + + + + + + + + + + +---- + +In the above example, the `testclasses` element allows you to select multiple test +classes that reside in different locations. + +For further details on usage and configuration options please refer to the official Ant +documentation for the +link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher` task]. + +[[running-tests-build-spring-boot]] +==== Spring Boot + +link:https://spring.io/projects/spring-boot[Spring Boot] provides automatic support for +managing the version of JUnit used in your project. In addition, the +`spring-boot-starter-test` artifact automatically includes testing libraries such as JUnit +Jupiter, AssertJ, Mockito, etc. + +If your build relies on dependency management support from Spring Boot, you should not +import the <> in your build script since that +will result in duplicate (and potentially conflicting) management of JUnit dependencies. + +If you need to override the version of a dependency used in your Spring Boot application, +you have to override the exact name of the +link:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.dependency-versions.properties[version property] +defined in the BOM used by the Spring Boot plugin. For example, the name of the JUnit +Jupiter version property in Spring Boot is `junit-jupiter.version`. The mechanism for +changing a dependency version is documented for both +link:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#managing-dependencies.dependency-management-plugin.customizing[Gradle] +and +link:https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#using.parent-pom[Maven]. + +With Gradle you can override the JUnit Jupiter version by including the following in your +`build.gradle` file. + +[source,groovy,indent=0] +[subs=attributes+] +---- + ext['junit-jupiter.version'] = '{jupiter-version}' +---- + +With Maven you can override the JUnit Jupiter version by including the following in your +`pom.xml` file. + +[source,xml,indent=0] +[subs=attributes+] +---- + + {jupiter-version} + +---- + +[[running-tests-console-launcher]] +=== Console Launcher + +The `{ConsoleLauncher}` is a command-line Java application that lets you launch the JUnit +Platform from the console. For example, it can be used to run JUnit Vintage and JUnit +Jupiter tests and print test execution results to the console. + +An executable `junit-platform-console-standalone-{platform-version}.jar` with all +dependencies included is published in the {Maven_Central} repository under the +https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone] +directory. It includes the following dependencies: + +include::{standaloneConsoleLauncherShadowedArtifactsFile}[] + +You can https://docs.oracle.com/javase/tutorial/deployment/jar/run.html[run] the +standalone `ConsoleLauncher` as shown below. + +[source,console,subs=attributes+] +---- +$ java -jar junit-platform-console-standalone-{platform-version}.jar + +├─ JUnit Vintage +│ └─ example.JUnit4Tests +│ └─ standardJUnit4Test ✔ +└─ JUnit Jupiter + ├─ StandardTests + │ ├─ succeedingTest() ✔ + │ └─ skippedTest() ↷ for demonstration purposes + └─ A special test case + ├─ Custom test name containing spaces ✔ + ├─ ╯°□°)╯ ✔ + └─ 😱 ✔ + +Test run finished after 64 ms +[ 5 containers found ] +[ 0 containers skipped ] +[ 5 containers started ] +[ 0 containers aborted ] +[ 5 containers successful ] +[ 0 containers failed ] +[ 6 tests found ] +[ 1 tests skipped ] +[ 5 tests started ] +[ 0 tests aborted ] +[ 5 tests successful ] +[ 0 tests failed ] +---- + +You can also run the standalone `ConsoleLauncher` as shown below (for example, to include +all jars in a directory): + +[source,console,subs=attributes+] +---- +$ java -cp classes:testlib/* org.junit.platform.console.ConsoleLauncher +---- + +.Exit Code +NOTE: The `{ConsoleLauncher}` exits with a status code of `1` if any containers or tests +failed. If no tests are discovered and the `--fail-if-no-tests` command-line option is +supplied, the `ConsoleLauncher` exits with a status code of `2`. Otherwise the exit code +is `0`. + +[[running-tests-console-launcher-options]] +==== Options + +---- +include::{consoleLauncherOptionsFile}[] +---- + +[[running-tests-console-launcher-argument-files]] +==== Argument Files (@-files) + +On some platforms you may run into system limitations on the length of a command line +when creating a command line with lots of options or with long arguments. + +Since version 1.3, the `ConsoleLauncher` supports _argument files_, also known as +_@-files_. Argument files are files that themselves contain arguments to be passed to the +command. When the underlying https://github.com/remkop/picocli[picocli] command line +parser encounters an argument beginning with the character `@`, it expands the contents +of that file into the argument list. + +The arguments within a file can be separated by spaces or newlines. If an argument +contains embedded whitespace, the whole argument should be wrapped in double or single +quotes -- for example, `"-f=My Files/Stuff.java"`. + +If the argument file does not exist or cannot be read, the argument will be treated +literally and will not be removed. This will likely result in an "unmatched argument" +error message. You can troubleshoot such errors by executing the command with the +`picocli.trace` system property set to `DEBUG`. + +Multiple _@-files_ may be specified on the command line. The specified path may be +relative to the current directory or absolute. + +You can pass a real parameter with an initial `@` character by escaping it with an +additional `@` symbol. For example, `@@somearg` will become `@somearg` and will not be +subject to expansion. + +[[running-tests-console-launcher-color-customization]] +==== Color customization + +The colors used in the output of the `{ConsoleLauncher}` can be customized. +The option `--single-color` will apply a built-in monochrome style, while +`--color-palette` will accept a properties file to override the +https://en.wikipedia.org/wiki/ANSI_escape_code#Colors[ANSI SGR] color styling. +The properties file below demonstrates the default style: + +[source,properties,indent=0] +---- +SUCCESSFUL = 32 +ABORTED = 33 +FAILED = 31 +SKIPPED = 35 +CONTAINER = 35 +TEST = 34 +DYNAMIC = 35 +REPORTED = 37 +---- + + +[[running-tests-junit-platform-runner]] +=== Using JUnit 4 to run the JUnit Platform + +[WARNING] +.The `JUnitPlatform` runner has been deprecated +==== +The `JUnitPlatform` runner was developed by the JUnit team as an interim solution for +running test suites and tests on the JUnit Platform in a JUnit 4 environment. + +In recent years, all mainstream build tools and IDEs provide built-in support for running +tests directly on the JUnit Platform. + +In addition, the introduction of `@Suite` support provided by the +`junit-platform-suite-engine` module makes the `JUnitPlatform` runner obsolete. See +<> for details. + +The `JUnitPlatform` runner and `@UseTechnicalNames` annotation have therefore been +deprecated in JUnit Platform 1.8 and will be removed in JUnit Platform 2.0. + +If you are using the `JUnitPlatform` runner, please migrate to the `@Suite` support. +==== + +The `JUnitPlatform` runner is a JUnit 4 based `Runner` which enables you to run any test +whose programming model is supported on the JUnit Platform in a JUnit 4 environment -- +for example, a JUnit Jupiter test class. + +Annotating a class with `@RunWith(JUnitPlatform.class)` allows it to be run with IDEs and +build systems that support JUnit 4 but do not yet support the JUnit Platform directly. + +NOTE: Since the JUnit Platform has features that JUnit 4 does not have, the runner is +only able to support a subset of the JUnit Platform functionality, especially with regard +to reporting (see <>). + +[[running-tests-junit-platform-runner-setup]] +==== Setup + +You need the following artifacts and their dependencies on the classpath. See +<> for details regarding group IDs, artifact IDs, and versions. + +[[running-tests-junit-platform-runner-setup-explicit-dependencies]] +===== Explicit Dependencies + +* `junit-platform-runner` in _test_ scope: location of the `JUnitPlatform` runner +* `junit-{junit4-version}.jar` in _test_ scope: to run tests using JUnit 4 +* `junit-jupiter-api` in _test_ scope: API for writing tests using JUnit Jupiter, + including `@Test`, etc. +* `junit-jupiter-engine` in _test runtime_ scope: implementation of the `TestEngine` API + for JUnit Jupiter + +[[running-tests-junit-platform-runner-setup-transitive-dependencies]] +===== Transitive Dependencies + +* `junit-platform-suite-api` in _test_ scope +* `junit-platform-suite-commons` in _test_ scope +* `junit-platform-launcher` in _test_ scope +* `junit-platform-engine` in _test_ scope +* `junit-platform-commons` in _test_ scope +* `opentest4j` in _test_ scope + +[[running-tests-junit-platform-runner-technical-names]] +==== Display Names vs. Technical Names + +To define a custom _display name_ for the class run via `@RunWith(JUnitPlatform.class)` +annotate the class with `@SuiteDisplayName` and provide a custom value. + +By default, _display names_ will be used for test artifacts; however, when the +`JUnitPlatform` runner is used to execute tests with a build tool such as Gradle or +Maven, the generated test report often needs to include the _technical names_ of test +artifacts — for example, fully qualified class names — instead of shorter display names +like the simple name of a test class or a custom display name containing special +characters. To enable technical names for reporting purposes, declare the +`@UseTechnicalNames` annotation alongside `@RunWith(JUnitPlatform.class)`. + +Note that the presence of `@UseTechnicalNames` overrides any custom display name +configured via `@SuiteDisplayName`. + +[[running-tests-junit-platform-runner-single-test]] +==== Single Test Class + +One way to use the `JUnitPlatform` runner is to annotate a test class with +`@RunWith(JUnitPlatform.class)` directly. Please note that the test methods in the +following example are annotated with `org.junit.jupiter.api.Test` (JUnit Jupiter), not +`org.junit.Test` (JUnit 4). Moreover, in this case the test class must be `public`; +otherwise, some IDEs and build tools might not recognize it as a JUnit 4 test class. + +[source,java,indent=0] +---- +include::{testDir}/example/JUnitPlatformClassDemo.java[tags=user_guide] +---- + +[[running-tests-junit-platform-runner-test-suite]] +==== Test Suite + +If you have multiple test classes you can create a test suite as can be seen in the +following example. + +[source,java,indent=0] +---- +include::{testDir}/example/JUnitPlatformSuiteDemo.java[tags=user_guide] +---- + +The `JUnitPlatformSuiteDemo` will discover and run all tests in the `example` package and +its subpackages. By default, it will only include test classes whose names either begin +with `Test` or end with `Test` or `Tests`. + +.Additional Configuration Options +NOTE: There are more configuration options for discovering and filtering tests than just +`@SelectPackages`. Please consult the Javadoc of the `{suite-api-package}` package for +further details. + +WARNING: Test classes and suites annotated with `@RunWith(JUnitPlatform.class)` +**cannot** be executed directly on the JUnit Platform (or as a "JUnit 5" test as +documented in some IDEs). Such classes and suites can only be executed using JUnit 4 +infrastructure. + +[[running-tests-config-params]] +=== Configuration Parameters + +In addition to instructing the platform which test classes and test engines to include, +which packages to scan, etc., it is sometimes necessary to provide additional custom +configuration parameters that are specific to a particular test engine, listener, or +registered extension. For example, the JUnit Jupiter `TestEngine` supports _configuration +parameters_ for the following use cases. + +- <> +- <> +- <> +- <> + +_Configuration Parameters_ are text-based key-value pairs that can be supplied to test +engines running on the JUnit Platform via one of the following mechanisms. + +1. The `configurationParameter()` and `configurationParameters()` methods in the + `LauncherDiscoveryRequestBuilder` which is used to build a request supplied to the + <>. When running tests via one of the tools provided + by the JUnit Platform you can specify configuration parameters as follows: + * <>: use the `--config` + command-line option. + * <>: use the + `systemProperty` or `systemProperties` DSL. + * <>: use the + `configurationParameters` property. +2. JVM system properties. +3. The JUnit Platform configuration file: a file named `junit-platform.properties` in the + root of the class path that follows the syntax rules for a Java `Properties` file. + +NOTE: Configuration parameters are looked up in the exact order defined above. +Consequently, configuration parameters supplied directly to the `Launcher` take +precedence over those supplied via system properties and the configuration file. +Similarly, configuration parameters supplied via system properties take precedence over +those supplied via the configuration file. + +[[running-tests-config-params-deactivation-pattern]] +==== Pattern Matching Syntax + +This section describes the pattern matching syntax that is applied to the _configuration +parameters_ used for the following features. + +- <> +- <> + +If the value for the given _configuration parameter_ consists solely of an asterisk +(`+++*+++`), the pattern will match against all candidate classes. Otherwise, the value +will be treated as a comma-separated list of patterns where each pattern will be matched +against the fully qualified class name (_FQCN_) of each candidate class. Any dot (`.`) in +a pattern will match against a dot (`.`) or a dollar sign (`$`) in a FQCN. Any asterisk +(`+++*+++`) will match against one or more characters in a FQCN. All other characters in a +pattern will be matched one-to-one against a FQCN. + +Examples: + +- `+++*+++`: matches all candidate classes. +- `+++org.junit.*+++`: matches all candidate classes under the `org.junit` base package and + any of its subpackages. +- `+++*.MyCustomImpl+++`: matches every candidate class whose simple class name is exactly + `MyCustomImpl`. +- `+++*System*+++`: matches every candidate class whose FQCN contains `System`. +- `+++*System*+++, +++*Unit*+++`: matches every candidate class whose FQCN contains + `System` or `Unit`. +- `org.example.MyCustomImpl`: matches the candidate class whose FQCN is exactly + `org.example.MyCustomImpl`. +- `org.example.MyCustomImpl, org.example.TheirCustomImpl`: matches candidate classes whose + FQCN is exactly `org.example.MyCustomImpl` or `org.example.TheirCustomImpl`. + +[[running-tests-tags]] +=== Tags + +Tags are a JUnit Platform concept for marking and filtering tests. The programming model +for adding tags to containers and tests is defined by the testing framework. For example, +in JUnit Jupiter based tests, the `@Tag` annotation (see +<>) should be used. For JUnit 4 based tests, the +Vintage engine maps `@Category` annotations to tags (see +<>). Other testing frameworks may define their +own annotation or other means for users to specify tags. + +[[running-tests-tag-syntax-rules]] +==== Syntax Rules for Tags + +Regardless how a tag is specified, the JUnit Platform enforces the following rules: + +* A tag must not be `null` or _blank_. +* A _trimmed_ tag must not contain whitespace. +* A _trimmed_ tag must not contain ISO control characters. +* A _trimmed_ tag must not contain any of the following _reserved characters_. +- `,`: _comma_ +- `(`: _left parenthesis_ +- `)`: _right parenthesis_ +- `&`: _ampersand_ +- `|`: _vertical bar_ +- `!`: _exclamation point_ + +NOTE: In the above context, "trimmed" means that leading and trailing whitespace +characters have been removed. + +[[running-tests-tag-expressions]] +==== Tag Expressions + +Tag expressions are boolean expressions with the operators `!`, `&` and `|`. In addition, +`(` and `)` can be used to adjust for operator precedence. + +Two special expressions are supported, `any()` and `none()`, which select all tests _with_ +any tags at all, and all tests _without_ any tags, respectively. +These special expressions may be combined with other expressions just like normal tags. + +.Operators (in descending order of precedence) +|=== +| Operator | Meaning | Associativity + +| `!` | not | right +| `&` | and | left +| `\|` | or | left +|=== + +If you are tagging your tests across multiple dimensions, tag expressions help you to +select which tests to execute. When tagging by test type (e.g., _micro_, _integration_, +_end-to-end_) and feature (e.g., *product*, *catalog*, *shipping*), the following tag +expressions can be useful. + +[%header,cols="40,60"] +|=== +| Tag Expression +| Selection + +| `+++product+++` +| all tests for *product* + +| `+++catalog \| shipping+++` +| all tests for *catalog* plus all tests for *shipping* + +| `+++catalog & shipping+++` +| all tests for the intersection between *catalog* and *shipping* + +| `+++product & !end-to-end+++` +| all tests for *product*, but not the _end-to-end_ tests + +| `+++(micro \| integration) & (product \| shipping)+++` +| all _micro_ or _integration_ tests for *product* or *shipping* +|=== + +[[running-tests-capturing-output]] +=== Capturing Standard Output/Error + +Since version 1.3, the JUnit Platform provides opt-in support for capturing output +printed to `System.out` and `System.err`. To enable it, set the +`junit.platform.output.capture.stdout` and/or `junit.platform.output.capture.stderr` +<> to `true`. In addition, you may +configure the maximum number of buffered bytes to be used per executed test or container +using `junit.platform.output.capture.maxBuffer`. + +If enabled, the JUnit Platform captures the corresponding output and publishes it as a +report entry using the `stdout` or `stderr` keys to all registered +`{TestExecutionListener}` instances immediately before reporting the test or container as +finished. + +Please note that the captured output will only contain output emitted by the thread that +was used to execute a container or test. Any output by other threads will be omitted +because particularly when +<> it would be impossible +to attribute it to a specific test or container. + +[[running-tests-listeners]] +=== Using Listeners + +The JUnit Platform provides the following listener APIs that allow JUnit, third parties, +and custom user code to react to events fired at various points during the discovery and +execution of a `TestPlan`. + +* `{LauncherSessionListener}`: receives events when a `{LauncherSession}` is opened and + closed. +* `{LauncherDiscoveryListener}`: receives events that occur during test discovery. +* `{TestExecutionListener}`: receives events that occur during test execution. + +The `LauncherSessionListener` API is typically implemented by build tools or IDEs and +registered automatically for you in order to support some feature of the build tool or IDE. + +The `LauncherDiscoveryListener` and `TestExecutionListener` APIs are often implemented in +order to produce some form of report or to display a graphical representation of the test +plan in an IDE. Such listeners may be implemented and automatically registered by a build +tool or IDE, or they may be included in a third-party library – potentially registered +for you automatically. You can also implement and register your own listeners. + +For details on registering and configuring listeners, see the following sections of this +guide. + +* <> +* <> +* <> +* <> +* <> + +The JUnit Platform provides the following listeners which you may wish to use with your +test suite. + +<> :: + `{LegacyXmlReportGeneratingListener}` can be used via the + <> or registered manually to generate XML reports + compatible with the de facto standard for JUnit 4 based test reports. ++ +`{OpenTestReportGeneratingListener}` generates an XML report in the event-based format +specified by {OpenTestReporting}. It is auto-registered and can be enabled and +configured via <>. ++ +See <> for details. + +<> :: + `FlightRecordingExecutionListener` and `FlightRecordingDiscoveryListener` that generate + Java Flight Recorder events during test discovery and execution. + +`{LoggingListener}` :: + `TestExecutionListener` for logging informational messages for all events via a + `BiConsumer` that consumes `Throwable` and `Supplier`. + +`{SummaryGeneratingListener}` :: + `TestExecutionListener` that generates a summary of the test execution which can be + printed via a `PrintWriter`. + +`{UniqueIdTrackingListener}` :: + `TestExecutionListener` that that tracks the unique IDs of all tests that were skipped + or executed during the execution of the `TestPlan` and generates a file containing the + unique IDs once execution of the `TestPlan` has finished. + +[[running-tests-listeners-flight-recorder]] +==== Flight Recorder Support + +Since version 1.7, the JUnit Platform provides opt-in support for generating Flight +Recorder events. https://openjdk.java.net/jeps/328[JEP 328] describes the Java Flight +Recorder (JFR) as: + +NOTE: Flight Recorder records events originating from applications, the JVM and the OS. +Events are stored in a single file that can be attached to bug reports and examined by +support engineers, allowing after-the-fact analysis of issues in the period leading up +to a problem. + +In order to record Flight Recorder events generated while running tests, you need to: + +1. Ensure that you are using either Java 8 Update 262 or higher or Java 11 or later. +2. Provide the `org.junit.platform.jfr` module (`junit-platform-jfr-{platform-version}.jar`) + on the class-path or module-path at test runtime. +3. Start flight recording when launching a test run. Flight Recorder can be started via + java command line option: + + -XX:StartFlightRecording:filename=... + +Please consult the manual of your build tool for the appropriate commands. + +To analyze the recorded events, use the +https://docs.oracle.com/en/java/javase/14/docs/specs/man/jfr.html[jfr] +command line tool shipped with recent JDKs or open the recording file with +https://jdk.java.net/jmc/[JDK Mission Control]. + +WARNING: Flight Recorder support is currently an _experimental_ feature. You're invited to +give it a try and provide feedback to the JUnit team so they can improve and eventually +<> this feature. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc new file mode 100644 index 00000000..45bddb0d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -0,0 +1,2554 @@ +[[writing-tests]] +== Writing Tests + +The following example provides a glimpse at the minimum requirements for writing a test in +JUnit Jupiter. Subsequent sections of this chapter will provide further details on all +available features. + +[source,java,indent=0] +.A first test case +---- +include::{testDir}/example/MyFirstJUnitJupiterTests.java[tags=user_guide] +---- + +[[writing-tests-annotations]] +=== Annotations + +JUnit Jupiter supports the following annotations for configuring tests and extending the +framework. + +Unless otherwise stated, all core annotations are located in the `{api-package}` package +in the `junit-jupiter-api` module. + +[cols="20,80"] +|=== +| Annotation | Description + +| `@Test` | Denotes that a method is a test method. Unlike JUnit 4's `@Test` annotation, this annotation does not declare any attributes, since test extensions in JUnit Jupiter operate based on their own dedicated annotations. Such methods are _inherited_ unless they are _overridden_. +| `@ParameterizedTest` | Denotes that a method is a <>. Such methods are _inherited_ unless they are _overridden_. +| `@RepeatedTest` | Denotes that a method is a test template for a <>. Such methods are _inherited_ unless they are _overridden_. +| `@TestFactory` | Denotes that a method is a test factory for <>. Such methods are _inherited_ unless they are _overridden_. +| `@TestTemplate` | Denotes that a method is a <> designed to be invoked multiple times depending on the number of invocation contexts returned by the registered <>. Such methods are _inherited_ unless they are _overridden_. +| `@TestClassOrder` | Used to configure the <> for `@Nested` test classes in the annotated test class. Such annotations are _inherited_. +| `@TestMethodOrder` | Used to configure the <> for the annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are _inherited_. +| `@TestInstance` | Used to configure the <> for the annotated test class. Such annotations are _inherited_. +| `@DisplayName` | Declares a custom <> for the test class or test method. Such annotations are not _inherited_. +| `@DisplayNameGeneration` | Declares a custom <> for the test class. Such annotations are _inherited_. +| `@BeforeEach` | Denotes that the annotated method should be executed _before_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@Before`. Such methods are _inherited_ – unless they are _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of Java's visibility rules). +| `@AfterEach` | Denotes that the annotated method should be executed _after_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@After`. Such methods are _inherited_ – unless they are _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of Java's visibility rules). +| `@BeforeAll` | Denotes that the annotated method should be executed _before_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@BeforeClass`. Such methods are _inherited_ – unless they are _hidden_, _overridden_, or _superseded_, (i.e., replaced based on signature only, irrespective of Java's visibility rules) – and must be `static` unless the "per-class" <> is used. +| `@AfterAll` | Denotes that the annotated method should be executed _after_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@AfterClass`. Such methods are _inherited_ – unless they are _hidden_, _overridden_, or _superseded_, (i.e., replaced based on signature only, irrespective of Java's visibility rules) – and must be `static` unless the "per-class" <> is used. +| `@Nested` | Denotes that the annotated class is a non-static <>. On Java 8 through Java 15, `@BeforeAll` and `@AfterAll` methods cannot be used directly in a `@Nested` test class unless the "per-class" <> is used. Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as `static` in a `@Nested` test class with either test instance lifecycle mode. Such annotations are not _inherited_. +| `@Tag` | Used to declare <>, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are _inherited_ at the class level but not at the method level. +| `@Disabled` | Used to <> a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not _inherited_. +| `@Timeout` | Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are _inherited_. +| `@ExtendWith` | Used to <>. Such annotations are _inherited_. +| `@RegisterExtension` | Used to <> via fields. Such fields are _inherited_ unless they are _shadowed_. +| `@TempDir` | Used to supply a <> via field injection or parameter injection in a lifecycle method or test method; located in the `org.junit.jupiter.api.io` package. +|=== + +WARNING: Some annotations may currently be _experimental_. Consult the table in +<> for details. + +[[writing-tests-meta-annotations]] +==== Meta-Annotations and Composed Annotations + +JUnit Jupiter annotations can be used as _meta-annotations_. That means that you can +define your own _composed annotation_ that will automatically _inherit_ the semantics of +its meta-annotations. + +For example, instead of copying and pasting `@Tag("fast")` throughout your code base (see +<>), you can create a custom _composed annotation_ +named `@Fast` as follows. `@Fast` can then be used as a drop-in replacement for +`@Tag("fast")`. + +[source,java,indent=0] +---- +include::{testDir}/example/Fast.java[tags=user_guide] +---- + +The following `@Test` method demonstrates usage of the `@Fast` annotation. + +[source,java,indent=0] +---- +@Fast +@Test +void myFastTest() { + // ... +} +---- + +You can even take that one step further by introducing a custom `@FastTest` annotation +that can be used as a drop-in replacement for `@Tag("fast")` _and_ `@Test`. + +[source,java,indent=0] +---- +include::{testDir}/example/FastTest.java[tags=user_guide] +---- + +JUnit automatically recognizes the following as a `@Test` method that is tagged with +"fast". + +[source,java,indent=0] +---- +@FastTest +void myFastTest() { + // ... +} +---- + +[[writing-tests-definitions]] +=== Definitions + +.Platform Concepts +**** +Container:: +a node in the test tree that contains other containers or tests as its children (e.g. a _test class_). + +Test:: +a node in the test tree that verifies expected behavior when executed (e.g. a `@Test` method). +**** + +.Jupiter Concepts +**** +Lifecycle Method:: +any method that is directly annotated or meta-annotated with +`@BeforeAll`, `@AfterAll`, `@BeforeEach`, or `@AfterEach`. + +Test Class:: +any top-level class, `static` member class, or <> that contains at least one _test method_, i.e. a _container_. +Test classes must not be `abstract` and must have a single constructor. + +Test Method:: +any instance method that is directly annotated or meta-annotated with +`@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, or `@TestTemplate`. +With the exception of `@Test`, these create a _container_ in the test tree that groups +_tests_ or, potentially (for `@TestFactory`), other _containers_. +**** + +[[writing-tests-classes-and-methods]] +=== Test Classes and Methods + +Test methods and lifecycle methods may be declared locally within the current test class, +inherited from superclasses, or inherited from interfaces (see +<>). In addition, test methods and +lifecycle methods must not be `abstract` and must not return a value (except `@TestFactory` +methods which are required to return a value). + +[NOTE] +.Class and method visibility +==== +Test classes, test methods, and lifecycle methods are not required to be `public`, but +they must _not_ be `private`. + +It is generally recommended to omit the `public` modifier for test classes, test methods, +and lifecycle methods unless there is a technical reason for doing so – for example, when +a test class is extended by a test class in another package. Another technical reason for +making classes and methods `public` is to simplify testing on the module path when using +the Java Module System. +==== + +The following test class demonstrates the use of `@Test` methods and all supported +lifecycle methods. For further information on runtime semantics, see +<> and +<>. + +[source,java,indent=0] +.A standard test class +---- +include::{testDir}/example/StandardTests.java[tags=user_guide] +---- + +[[writing-tests-display-names]] +=== Display Names + +Test classes and test methods can declare custom display names via `@DisplayName` -- with +spaces, special characters, and even emojis -- that will be displayed in test reports and +by test runners and IDEs. + +[source,java,indent=0] +---- +include::{testDir}/example/DisplayNameDemo.java[tags=user_guide] +---- + +[[writing-tests-display-name-generator]] +==== Display Name Generators + +JUnit Jupiter supports custom display name generators that can be configured via the +`@DisplayNameGeneration` annotation. Values provided via `@DisplayName` annotations +always take precedence over display names generated by a `DisplayNameGenerator`. + +Generators can be created by implementing `DisplayNameGenerator`. Here are some default +ones available in Jupiter: + +[cols="20,80"] +|=== +| DisplayNameGenerator | Behavior + +| `Standard` | Matches the standard display name generation behavior in place since JUnit Jupiter 5.0 was released. +| `Simple` | Removes trailing parentheses for methods with no parameters. +| `ReplaceUnderscores` | Replaces underscores with spaces. +| `IndicativeSentences` | Generates complete sentences by concatenating the names of the test and the enclosing classes. +|=== + +Note that for `IndicativeSentences`, you can customize the separator and the +underlying generator by using `@IndicativeSentencesGeneration` as shown in the +following example. + +[source,java,indent=0] +---- +include::{testDir}/example/DisplayNameGeneratorDemo.java[tags=user_guide] +---- + +``` ++-- DisplayNameGeneratorDemo [OK] + +-- A year is not supported [OK] + | +-- A negative value for year is not supported by the leap year computation. [OK] + | | +-- For example, year -1 is not supported. [OK] + | | '-- For example, year -4 is not supported. [OK] + | '-- if it is zero() [OK] + '-- A year is a leap year [OK] + +-- A year is a leap year -> if it is divisible by 4 but not by 100. [OK] + '-- A year is a leap year -> if it is one of the following years. [OK] + +-- Year 2016 is a leap year. [OK] + +-- Year 2020 is a leap year. [OK] + '-- Year 2048 is a leap year. [OK] +``` + + +[[writing-tests-display-name-generator-default]] +==== Setting the Default Display Name Generator + +You can use the `junit.jupiter.displayname.generator.default` +<> to specify the fully qualified +class name of the `DisplayNameGenerator` you would like to use by default. Just like for +display name generators configured via the `@DisplayNameGeneration` annotation, the +supplied class has to implement the `DisplayNameGenerator` interface. The default display +name generator will be used for all tests unless the `@DisplayNameGeneration` annotation +is present on an enclosing test class or test interface. Values provided via +`@DisplayName` annotations always take precedence over display names generated by a +`DisplayNameGenerator`. + +For example, to use the `ReplaceUnderscores` display name generator by default, you should +set the configuration parameter to the corresponding fully qualified class name (e.g., in +`src/test/resources/junit-platform.properties`): + +[source,properties,indent=0] +---- +junit.jupiter.displayname.generator.default = \ + org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores +---- + +Similarly, you can specify the fully qualified name of any custom class that implements +`DisplayNameGenerator`. + +[[writing-tests-display-name-generator-precedence-rules]] +In summary, the display name for a test class or method is determined according to the +following precedence rules: + +1. value of the `@DisplayName` annotation, if present +2. by calling the `DisplayNameGenerator` specified in the `@DisplayNameGeneration` + annotation, if present +3. by calling the default `DisplayNameGenerator` configured via the configuration + parameter, if present +4. by calling `org.junit.jupiter.api.DisplayNameGenerator.Standard` + +[[writing-tests-assertions]] +=== Assertions + +JUnit Jupiter comes with many of the assertion methods that JUnit 4 has and adds a few +that lend themselves well to being used with Java 8 lambdas. All JUnit Jupiter assertions +are `static` methods in the `{Assertions}` class. + +[source,java,indent=0] +---- +include::{testDir}/example/AssertionsDemo.java[tags=user_guide] +---- + +[[writing-tests-assertions-preemptive-timeouts]] +[WARNING] +.Preemptive Timeouts with `assertTimeoutPreemptively()` +==== +The various `assertTimeoutPreemptively()` methods in the `Assertions` class execute +the provided `executable` or `supplier` in a different thread than that of the calling +code. This behavior can lead to undesirable side effects if the code that is executed +within the `executable` or `supplier` relies on `java.lang.ThreadLocal` storage. + +One common example of this is the transactional testing support in the Spring Framework. +Specifically, Spring's testing support binds transaction state to the current thread (via +a `ThreadLocal`) before a test method is invoked. Consequently, if an `executable` or +`supplier` provided to `assertTimeoutPreemptively()` invokes Spring-managed components +that participate in transactions, any actions taken by those components will not be rolled +back with the test-managed transaction. On the contrary, such actions will be committed to +the persistent store (e.g., relational database) even though the test-managed transaction +is rolled back. + +Similar side effects may be encountered with other frameworks that rely on +`ThreadLocal` storage. +==== + +[[writing-tests-assertions-kotlin]] +==== Kotlin Assertion Support + +JUnit Jupiter also comes with a few assertion methods that lend themselves well to being +used in https://kotlinlang.org/[Kotlin]. All JUnit Jupiter Kotlin assertions are top-level +functions in the `org.junit.jupiter.api` package. + +[source,kotlin,indent=0] +---- +include::{kotlinTestDir}/example/KotlinAssertionsDemo.kt[tags=user_guide] +---- + +[[writing-tests-assertions-third-party]] +==== Third-party Assertion Libraries + +Even though the assertion facilities provided by JUnit Jupiter are sufficient for many +testing scenarios, there are times when more power and additional functionality such as +_matchers_ are desired or required. In such cases, the JUnit team recommends the use of +third-party assertion libraries such as {AssertJ}, {Hamcrest}, {Truth}, etc. Developers +are therefore free to use the assertion library of their choice. + +For example, the combination of _matchers_ and a fluent API can be used to make +assertions more descriptive and readable. However, JUnit Jupiter's `{Assertions}` class +does not provide an +https://junit.org/junit4/javadoc/latest/org/junit/Assert.html#assertThat[`assertThat()`] +method like the one found in JUnit 4's `org.junit.Assert` class which accepts a Hamcrest +https://junit.org/junit4/javadoc/latest/org/hamcrest/Matcher.html[`Matcher`]. Instead, +developers are encouraged to use the built-in support for matchers provided by third-party +assertion libraries. + +The following example demonstrates how to use the `assertThat()` support from Hamcrest in +a JUnit Jupiter test. As long as the Hamcrest library has been added to the classpath, +you can statically import methods such as `assertThat()`, `is()`, and `equalTo()` and +then use them in tests like in the `assertWithHamcrestMatcher()` method below. + +[source,java,indent=0] +---- +include::{testDir}/example/HamcrestAssertionsDemo.java[tags=user_guide] +---- + +Naturally, legacy tests based on the JUnit 4 programming model can continue using +`org.junit.Assert#assertThat`. + +[[writing-tests-assumptions]] +=== Assumptions + +JUnit Jupiter comes with a subset of the assumption methods that JUnit 4 provides and +adds a few that lend themselves well to being used with Java 8 lambda expressions and +method references. All JUnit Jupiter assumptions are static methods in the +`{Assumptions}` class. + +[source,java,indent=0] +---- +include::{testDir}/example/AssumptionsDemo.java[tags=user_guide] +---- + +NOTE: As of JUnit Jupiter 5.4, it is also possible to use methods from JUnit 4's +`org.junit.Assume` class for assumptions. Specifically, JUnit Jupiter supports JUnit 4's +`AssumptionViolatedException` to signal that a test should be aborted instead of marked +as a failure. + +[[writing-tests-disabling]] +=== Disabling Tests + +Entire test classes or individual test methods may be _disabled_ via the `{Disabled}` +annotation, via one of the annotations discussed in +<>, or via a custom <>. + +Here's a `@Disabled` test class. + +[source,java,indent=0] +---- +include::{testDir}/example/DisabledClassDemo.java[tags=user_guide] +---- + +And here's a test class that contains a `@Disabled` test method. + +[source,java,indent=0] +---- +include::{testDir}/example/DisabledTestsDemo.java[tags=user_guide] +---- + +NOTE: `@Disabled` may be declared without providing a _reason_; however, the JUnit team +recommends that developers provide a short explanation for why a test class or test +method has been disabled. Consequently, the above examples both show the use of a reason +-- for example, `@Disabled("Disabled until bug #42 has been resolved")`. Some development +teams even require the presence of issue tracking numbers in the _reason_ for automated +traceability, etc. + +[[writing-tests-conditional-execution]] +=== Conditional Test Execution + +The <> extension API in JUnit Jupiter allows +developers to either _enable_ or _disable_ a container or test based on certain +conditions _programmatically_. The simplest example of such a condition is the built-in +`{DisabledCondition}` which supports the `{Disabled}` annotation (see +<>). In addition to `@Disabled`, JUnit Jupiter also supports +several other annotation-based conditions in the `org.junit.jupiter.api.condition` +package that allow developers to enable or disable containers and tests _declaratively_. +When multiple `ExecutionCondition` extensions are registered, a container or test is +disabled as soon as one of the conditions returns _disabled_. If you wish to provide +details about why they might be disabled, every annotation associated with these built-in +conditions has a `disabledReason` attribute available for that purpose. + +See <> and the following sections for +details. + +[TIP] +.Composed Annotations +==== +Note that any of the _conditional_ annotations listed in the following sections may also +be used as a meta-annotation in order to create a custom _composed annotation_. For +example, the `@TestOnMac` annotation in the +<> shows how you can +combine `@Test` and `@EnabledOnOs` in a single, reusable annotation. +==== + +[WARNING] +==== +Unless otherwise stated, each of the _conditional_ annotations listed in the following +sections can only be declared once on a given test interface, test class, or test method. +If a conditional annotation is directly present, indirectly present, or meta-present +multiple times on a given element, only the first such annotation discovered by JUnit will +be used; any additional declarations will be silently ignored. Note, however, that each +conditional annotation may be used in conjunction with other conditional annotations in +the `org.junit.jupiter.api.condition` package. +==== + +[[writing-tests-conditional-execution-os]] +==== Operating System and Architecture Conditions + +A container or test may be enabled or disabled on a particular operating system, +architecture, or combination of both via the `{EnabledOnOs}` and `{DisabledOnOs}` +annotations. + +[[writing-tests-conditional-execution-os-demo]] +[source,java,indent=0] +.Conditional execution based on operating system +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_os] +---- + +[[writing-tests-conditional-execution-architectures-demo]] +[source,java,indent=0] +.Conditional execution based on architecture +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_architecture] +---- + +[[writing-tests-conditional-execution-jre]] +==== Java Runtime Environment Conditions + +A container or test may be enabled or disabled on particular versions of the Java +Runtime Environment (JRE) via the `{EnabledOnJre}` and `{DisabledOnJre}` annotations +or on a particular range of versions of the JRE via the `{EnabledForJreRange}` and +`{DisabledForJreRange}` annotations. The range defaults to `{JRE}.JAVA_8` as the lower +border (`min`) and `{JRE}.OTHER` as the higher border (`max`), which allows usage of +half open ranges. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre] +---- + +[[writing-tests-conditional-execution-native]] +==== Native Image Conditions + +A container or test may be enabled or disabled within a +https://www.graalvm.org/reference-manual/native-image/[GraalVM native image] via the +`{EnabledInNativeImage}` and `{DisabledInNativeImage}` annotations. These annotations are +typically used when running tests within a native image using the Gradle and Maven +plug-ins from the GraalVM https://graalvm.github.io/native-build-tools/latest/[Native +Build Tools] project. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_native] +---- + +[[writing-tests-conditional-execution-system-properties]] +==== System Property Conditions + +A container or test may be enabled or disabled based on the value of the `named` JVM +system property via the `{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}` +annotations. The value supplied via the `matches` attribute will be interpreted as a +regular expression. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_system_property] +---- + +[TIP] +==== +As of JUnit Jupiter 5.6, `{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}` are +_repeatable annotations_. Consequently, these annotations may be declared multiple times +on a test interface, test class, or test method. Specifically, these annotations will be +found if they are directly present, indirectly present, or meta-present on a given element. +==== + +[[writing-tests-conditional-execution-environment-variables]] +==== Environment Variable Conditions + +A container or test may be enabled or disabled based on the value of the `named` +environment variable from the underlying operating system via the +`{EnabledIfEnvironmentVariable}` and `{DisabledIfEnvironmentVariable}` annotations. The +value supplied via the `matches` attribute will be interpreted as a regular expression. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_environment_variable] +---- + +[TIP] +==== +As of JUnit Jupiter 5.6, `{EnabledIfEnvironmentVariable}` and +`{DisabledIfEnvironmentVariable}` are _repeatable annotations_. Consequently, these +annotations may be declared multiple times on a test interface, test class, or test +method. Specifically, these annotations will be found if they are directly present, +indirectly present, or meta-present on a given element. +==== + +[[writing-tests-conditional-execution-custom]] +==== Custom Conditions + +As an alternative to implementing an <>, a +container or test may be enabled or disabled based on a _condition method_ configured via +the `{EnabledIf}` and `{DisabledIf}` annotations. A condition method must have a `boolean` +return type and may accept either no arguments or a single `ExtensionContext` argument. + +The following test class demonstrates how to configure a local method named +`customCondition` via `@EnabledIf` and `@DisabledIf`. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_custom] +---- + +Alternatively, the condition method can be located outside the test class. In this case, +it must be referenced by its _fully qualified name_ as demonstrated in the following +example. + +[source,java,indent=0] +---- +package example; + +include::{testDir}/example/ExternalCustomConditionDemo.java[tags=user_guide_external_custom_condition] +---- + +[NOTE] +==== +There are several cases where a condition method would need to be `static`: + +- when `@EnabledIf` or `@DisabledIf` is used at class level +- when `@EnabledIf` or `@DisabledIf` is used on a `@ParameterizedTest` or a + `@TestTemplate` method +- when the condition method is located in an external class + +In any other case, you can use either static methods or instance methods as condition +methods. +==== + +[TIP] +==== +It is often the case that you can use an existing static method in a utility class as a +custom condition. + +For example, `java.awt.GraphicsEnvironment` provides a `public static boolean isHeadless()` +method that can be used to determine if the current environment does not support a +graphical display. Thus, if you have a test that depends on graphical support you can +disable it when such support is unavailable as follows. + +[source,java,indent=0] +---- +@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless", + disabledReason = "headless environment") +---- +==== + +[[writing-tests-tagging-and-filtering]] +=== Tagging and Filtering + +Test classes and methods can be tagged via the `@Tag` annotation. Those tags can later be +used to filter <>. Please refer to the +<> section for more information about tag support in the JUnit +Platform. + +[source,java,indent=0] +---- +include::{testDir}/example/TaggingDemo.java[tags=user_guide] +---- + +TIP: See <> for examples demonstrating how to create +custom annotations for tags. + +[[writing-tests-test-execution-order]] +=== Test Execution Order + +By default, test classes and methods will be ordered using an algorithm that is +deterministic but intentionally nonobvious. This ensures that subsequent runs of a test +suite execute test classes and test methods in the same order, thereby allowing for +repeatable builds. + +NOTE: See <> for a definition of _test method_ and +_test class_. + +[[writing-tests-test-execution-order-methods]] +==== Method Order + +Although true _unit tests_ typically should not rely on the order in which they are +executed, there are times when it is necessary to enforce a specific test method execution +order -- for example, when writing _integration tests_ or _functional tests_ where the +sequence of the tests is important, especially in conjunction with +`@TestInstance(Lifecycle.PER_CLASS)`. + +To control the order in which test methods are executed, annotate your test class or test +interface with `{TestMethodOrder}` and specify the desired `{MethodOrderer}` +implementation. You can implement your own custom `MethodOrderer` or use one of the +following built-in `MethodOrderer` implementations. + +* `{MethodOrderer_DisplayName}`: sorts test methods _alphanumerically_ based on their + display names (see <>) +* `{MethodOrderer_MethodName}`: sorts test methods _alphanumerically_ based on their names + and formal parameter lists +* `{MethodOrderer_OrderAnnotation}`: sorts test methods _numerically_ based on values + specified via the `{Order}` annotation +* `{MethodOrderer_Random}`: orders test methods _pseudo-randomly_ and supports + configuration of a custom _seed_ +* `{MethodOrderer_Alphanumeric}`: sorts test methods _alphanumerically_ based on their + names and formal parameter lists; **deprecated in favor of `{MethodOrderer_MethodName}`, + to be removed in 6.0** + +NOTE: See also: <> + +The following example demonstrates how to guarantee that test methods are executed in the +order specified via the `@Order` annotation. + +[source,java,indent=0] +---- +include::{testDir}/example/OrderedTestsDemo.java[tags=user_guide] +---- + +[[writing-tests-test-execution-order-methods-default]] +===== Setting the Default Method Orderer + +You can use the `junit.jupiter.testmethod.order.default` <> to specify the fully qualified class name of the +`{MethodOrderer}` you would like to use by default. Just like for the orderer configured +via the `{TestMethodOrder}` annotation, the supplied class has to implement the +`MethodOrderer` interface. The default orderer will be used for all tests unless the +`@TestMethodOrder` annotation is present on an enclosing test class or test interface. + +For example, to use the `{MethodOrderer_OrderAnnotation}` method orderer by default, you +should set the configuration parameter to the corresponding fully qualified class name +(e.g., in `src/test/resources/junit-platform.properties`): + +[source,properties,indent=0] +---- +junit.jupiter.testmethod.order.default = \ + org.junit.jupiter.api.MethodOrderer$OrderAnnotation +---- + +Similarly, you can specify the fully qualified name of any custom class that implements +`MethodOrderer`. + +[[writing-tests-test-execution-order-classes]] +==== Class Order + +Although test classes typically should not rely on the order in which they are executed, +there are times when it is desirable to enforce a specific test class execution order. You +may wish to execute test classes in a random order to ensure there are no accidental +dependencies between test classes, or you may wish to order test classes to optimize build +time as outlined in the following scenarios. + +* Run previously failing tests and faster tests first: "fail fast" mode +* With parallel execution enabled, run longer tests first: "shortest test plan execution + duration" mode +* Various other use cases + +To configure test class execution order _globally_ for the entire test suite, use the +`junit.jupiter.testclass.order.default` <> to specify the fully qualified class name of the `{ClassOrderer}` you would +like to use. The supplied class must implement the `ClassOrderer` interface. + +You can implement your own custom `ClassOrderer` or use one of the following built-in +`ClassOrderer` implementations. + +* `{ClassOrderer_ClassName}`: sorts test classes _alphanumerically_ based on their fully + qualified class names +* `{ClassOrderer_DisplayName}`: sorts test classes _alphanumerically_ based on their + display names (see <>) +* `{ClassOrderer_OrderAnnotation}`: sorts test classes _numerically_ based on values + specified via the `{Order}` annotation +* `{ClassOrderer_Random}`: orders test classes _pseudo-randomly_ and supports + configuration of a custom _seed_ + +For example, for the `@Order` annotation to be honored on _test classes_, you should +configure the `{ClassOrderer_OrderAnnotation}` class orderer using the configuration +parameter with the corresponding fully qualified class name (e.g., in +`src/test/resources/junit-platform.properties`): + +[source,properties,indent=0] +---- +junit.jupiter.testclass.order.default = \ + org.junit.jupiter.api.ClassOrderer$OrderAnnotation +---- + +The configured `ClassOrderer` will be applied to all top-level test classes (including +`static` nested test classes) and `@Nested` test classes. + +NOTE: Top-level test classes will be ordered relative to each other; whereas, `@Nested` +test classes will be ordered relative to other `@Nested` test classes sharing the same +_enclosing class_. + +To configure test class execution order _locally_ for `@Nested` test classes, declare the +`{TestClassOrder}` annotation on the enclosing class for the `@Nested` test classes you +want to order, and supply a class reference to the `ClassOrderer` implementation you would +like to use directly in the `@TestClassOrder` annotation. The configured `ClassOrderer` +will be applied recursively to `@Nested` test classes and their `@Nested` test classes. +Note that a local `@TestClassOrder` declaration always overrides an inherited +`@TestClassOrder` declaration or a `ClassOrderer` configured globally via the +`junit.jupiter.testclass.order.default` configuration parameter. + +The following example demonstrates how to guarantee that `@Nested` test classes are +executed in the order specified via the `@Order` annotation. + +[source,java,indent=0] +---- +include::{testDir}/example/OrderedNestedTestClassesDemo.java[tags=user_guide] +---- + +[[writing-tests-test-instance-lifecycle]] +=== Test Instance Lifecycle + +In order to allow individual test methods to be executed in isolation and to avoid +unexpected side effects due to mutable test instance state, JUnit creates a new instance +of each test class before executing each _test method_ (see +<>). This "per-method" test instance lifecycle is the +default behavior in JUnit Jupiter and is analogous to all previous versions of JUnit. + +NOTE: Please note that the test class will still be instantiated if a given _test method_ +is _disabled_ via a <> (e.g., `@Disabled`, +`@DisabledOnOs`, etc.) even when the "per-method" test instance lifecycle mode is active. + +If you would prefer that JUnit Jupiter execute all test methods on the same test +instance, annotate your test class with `@TestInstance(Lifecycle.PER_CLASS)`. When using +this mode, a new test instance will be created once per test class. Thus, if your test +methods rely on state stored in instance variables, you may need to reset that state in +`@BeforeEach` or `@AfterEach` methods. + +The "per-class" mode has some additional benefits over the default "per-method" mode. +Specifically, with the "per-class" mode it becomes possible to declare `@BeforeAll` and +`@AfterAll` on non-static methods as well as on interface `default` methods. The +"per-class" mode therefore also makes it possible to use `@BeforeAll` and `@AfterAll` +methods in `@Nested` test classes. + +NOTE: Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as +`static` in `@Nested` test classes. + +If you are authoring tests using the Kotlin programming language, you may also find it +easier to implement `@BeforeAll` and `@AfterAll` methods by switching to the "per-class" +test instance lifecycle mode. + +[[writing-tests-test-instance-lifecycle-changing-default]] +==== Changing the Default Test Instance Lifecycle + +If a test class or test interface is not annotated with `@TestInstance`, JUnit Jupiter +will use a _default_ lifecycle mode. The standard _default_ mode is `PER_METHOD`; +however, it is possible to change the _default_ for the execution of an entire test plan. +To change the default test instance lifecycle mode, set the +`junit.jupiter.testinstance.lifecycle.default` _configuration parameter_ to the name of +an enum constant defined in `TestInstance.Lifecycle`, ignoring case. This can be supplied +as a JVM system property, as a _configuration parameter_ in the +`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform +configuration file (see <> for details). + +For example, to set the default test instance lifecycle mode to `Lifecycle.PER_CLASS`, +you can start your JVM with the following system property. + +`-Djunit.jupiter.testinstance.lifecycle.default=per_class` + +Note, however, that setting the default test instance lifecycle mode via the JUnit +Platform configuration file is a more robust solution since the configuration file can be +checked into a version control system along with your project and can therefore be used +within IDEs and your build software. + +To set the default test instance lifecycle mode to `Lifecycle.PER_CLASS` via the JUnit +Platform configuration file, create a file named `junit-platform.properties` in the root +of the class path (e.g., `src/test/resources`) with the following content. + +`junit.jupiter.testinstance.lifecycle.default = per_class` + +WARNING: Changing the _default_ test instance lifecycle mode can lead to unpredictable +results and fragile builds if not applied consistently. For example, if the build +configures "per-class" semantics as the default but tests in the IDE are executed using +"per-method" semantics, that can make it difficult to debug errors that occur on the +build server. It is therefore recommended to change the default in the JUnit Platform +configuration file instead of via a JVM system property. + +[[writing-tests-nested]] +=== Nested Tests + +`@Nested` tests give the test writer more capabilities to express the relationship among +several groups of tests. Such nested tests make use of Java's nested classes and +facilitate hierarchical thinking about the test structure. Here's an elaborate example, +both as source code and as a screenshot of the execution within an IDE. + +[source,java,indent=0] +.Nested test suite for testing a stack +---- +include::{testDir}/example/TestingAStackDemo.java[tags=user_guide] +---- + +When executing this example in an IDE, the test execution tree in the GUI will look +similar to the following image. + +image::writing-tests_nested_test_ide.png[caption='',title='Executing a nested test in an IDE'] + +In this example, preconditions from outer tests are used in inner tests by defining +hierarchical lifecycle methods for the setup code. For example, `createNewStack()` is a +`@BeforeEach` lifecycle method that is used in the test class in which it is defined and +in all levels in the nesting tree below the class in which it is defined. + +The fact that setup code from outer tests is run before inner tests are executed gives you +the ability to run all tests independently. You can even run inner tests alone without +running the outer tests, because the setup code from the outer tests is always executed. + +NOTE: _Only non-static nested classes_ (i.e. _inner classes_) can serve as `@Nested` test +classes. Nesting can be arbitrarily deep, and those inner classes are subject to full +lifecycle support with one exception: `@BeforeAll` and `@AfterAll` methods do not work _by +default_. The reason is that Java does not allow `static` members in inner classes prior +to Java 16. However, this restriction can be circumvented by annotating a `@Nested` test +class with `@TestInstance(Lifecycle.PER_CLASS)` (see +<>). If you are using Java 16 or higher, +`@BeforeAll` and `@AfterAll` methods can be declared as `static` in `@Nested` test +classes, and this restriction no longer applies. + +[[writing-tests-dependency-injection]] +=== Dependency Injection for Constructors and Methods + +In all prior JUnit versions, test constructors or methods were not allowed to have +parameters (at least not with the standard `Runner` implementations). As one of the major +changes in JUnit Jupiter, both test constructors and methods are now permitted to have +parameters. This allows for greater flexibility and enables _Dependency Injection_ for +constructors and methods. + +`{ParameterResolver}` defines the API for test extensions that wish to _dynamically_ +resolve parameters at runtime. If a _test class_ constructor, a _test method_, or a +_lifecycle method_ (see <>) accepts a parameter, the +parameter must be resolved at runtime by a registered `ParameterResolver`. + +There are currently three built-in resolvers that are registered automatically. + +* `{TestInfoParameterResolver}`: if a constructor or method parameter is of type + `{TestInfo}`, the `TestInfoParameterResolver` will supply an instance of `TestInfo` + corresponding to the current container or test as the value for the parameter. The + `TestInfo` can then be used to retrieve information about the current container or test + such as the display name, the test class, the test method, and associated tags. The + display name is either a technical name, such as the name of the test class or test + method, or a custom name configured via `@DisplayName`. ++ +`{TestInfo}` acts as a drop-in replacement for the `TestName` rule from JUnit 4. The +following demonstrates how to have `TestInfo` injected into a test constructor, +`@BeforeEach` method, and `@Test` method. + +[source,java,indent=0] +---- +include::{testDir}/example/TestInfoDemo.java[tags=user_guide] +---- + +* `{RepetitionInfoParameterResolver}`: if a method parameter in a `@RepeatedTest`, + `@BeforeEach`, or `@AfterEach` method is of type `{RepetitionInfo}`, the + `RepetitionInfoParameterResolver` will supply an instance of `RepetitionInfo`. + `RepetitionInfo` can then be used to retrieve information about the current repetition + and the total number of repetitions for the corresponding `@RepeatedTest`. Note, + however, that `RepetitionInfoParameterResolver` is not registered outside the context + of a `@RepeatedTest`. See <>. + +* `{TestReporterParameterResolver}`: if a constructor or method parameter is of type + `{TestReporter}`, the `TestReporterParameterResolver` will supply an instance of + `TestReporter`. The `TestReporter` can be used to publish additional data about the + current test run. The data can be consumed via the `reportingEntryPublished()` method in + a `{TestExecutionListener}`, allowing it to be viewed in IDEs or included in reports. ++ +In JUnit Jupiter you should use `TestReporter` where you used to print information to +`stdout` or `stderr` in JUnit 4. Using `@RunWith(JUnitPlatform.class)` will output all +reported entries to `stdout`. In addition, some IDEs print report entries to `stdout` or +display them in the user interface for test results. + +[source,java,indent=0] +---- +include::{testDir}/example/TestReporterDemo.java[tags=user_guide] +---- + +NOTE: Other parameter resolvers must be explicitly enabled by registering appropriate +<> via `@ExtendWith`. + +Check out the `{RandomParametersExtension}` for an example of a custom +`{ParameterResolver}`. While not intended to be production-ready, it demonstrates the +simplicity and expressiveness of both the extension model and the parameter resolution +process. `MyRandomParametersTest` demonstrates how to inject random values into `@Test` +methods. + +[source,java,indent=0] +---- +@ExtendWith(RandomParametersExtension.class) +class MyRandomParametersTest { + + @Test + void injectsInteger(@Random int i, @Random int j) { + assertNotEquals(i, j); + } + + @Test + void injectsDouble(@Random double d) { + assertEquals(0.0, d, 1.0); + } + +} +---- + +For real-world use cases, check out the source code for the `{MockitoExtension}` and the +`{SpringExtension}`. + +When the type of the parameter to inject is the only condition for your +`{ParameterResolver}`, you can use the generic `{TypeBasedParameterResolver}` base class. +The `supportsParameters` method is implemented behind the scenes and supports +parameterized types. + +[[writing-tests-test-interfaces-and-default-methods]] +=== Test Interfaces and Default Methods + +JUnit Jupiter allows `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, +`@TestTemplate`, `@BeforeEach`, and `@AfterEach` to be declared on interface `default` +methods. `@BeforeAll` and `@AfterAll` can either be declared on `static` methods in a +test interface or on interface `default` methods _if_ the test interface or test class is +annotated with `@TestInstance(Lifecycle.PER_CLASS)` (see +<>). Here are some examples. + +[source,java] +---- +include::{testDir}/example/testinterface/TestLifecycleLogger.java[tags=user_guide] +---- + +[source,java] +---- +include::{testDir}/example/testinterface/TestInterfaceDynamicTestsDemo.java[tags=user_guide] +---- + +`@ExtendWith` and `@Tag` can be declared on a test interface so that classes that +implement the interface automatically inherit its tags and extensions. See +<> for the source code of the +<>. + +[source,java] +---- +include::{testDir}/example/testinterface/TimeExecutionLogger.java[tags=user_guide] +---- + +In your test class you can then implement these test interfaces to have them applied. + +[source,java] +---- +include::{testDir}/example/testinterface/TestInterfaceDemo.java[tags=user_guide] +---- + +Running the `TestInterfaceDemo` results in output similar to the following: + +.... +INFO example.TestLifecycleLogger - Before all tests +INFO example.TestLifecycleLogger - About to execute [dynamicTestsForPalindromes()] +INFO example.TimingExtension - Method [dynamicTestsForPalindromes] took 19 ms. +INFO example.TestLifecycleLogger - Finished executing [dynamicTestsForPalindromes()] +INFO example.TestLifecycleLogger - About to execute [isEqualValue()] +INFO example.TimingExtension - Method [isEqualValue] took 1 ms. +INFO example.TestLifecycleLogger - Finished executing [isEqualValue()] +INFO example.TestLifecycleLogger - After all tests +.... + +Another possible application of this feature is to write tests for interface contracts. +For example, you can write tests for how implementations of `Object.equals` or +`Comparable.compareTo` should behave as follows. + +[source,java] +---- +include::{testDir}/example/defaultmethods/Testable.java[tags=user_guide] +---- + +[source,java] +---- +include::{testDir}/example/defaultmethods/EqualsContract.java[tags=user_guide] +---- + +[source,java] +---- +include::{testDir}/example/defaultmethods/ComparableContract.java[tags=user_guide] +---- + +In your test class you can then implement both contract interfaces thereby inheriting the +corresponding tests. Of course you'll have to implement the abstract methods. + +[source,java] +---- +include::{testDir}/example/defaultmethods/StringTests.java[tags=user_guide] +---- + +NOTE: The above tests are merely meant as examples and therefore not complete. + + +[[writing-tests-repeated-tests]] +=== Repeated Tests + +JUnit Jupiter provides the ability to repeat a test a specified number of times by +annotating a method with `@RepeatedTest` and specifying the total number of repetitions +desired. Each invocation of a repeated test behaves like the execution of a regular +`@Test` method with full support for the same lifecycle callbacks and extensions. + +The following example demonstrates how to declare a test named `repeatedTest()` that +will be automatically repeated 10 times. + +[source,java] +---- +@RepeatedTest(10) +void repeatedTest() { + // ... +} +---- + +In addition to specifying the number of repetitions, a custom display name can be +configured for each repetition via the `name` attribute of the `@RepeatedTest` +annotation. Furthermore, the display name can be a pattern composed of a combination of +static text and dynamic placeholders. The following placeholders are currently supported. + +- `{displayName}`: display name of the `@RepeatedTest` method +- `{currentRepetition}`: the current repetition count +- `{totalRepetitions}`: the total number of repetitions + +The default display name for a given repetition is generated based on the following +pattern: `"repetition {currentRepetition} of {totalRepetitions}"`. Thus, the display +names for individual repetitions of the previous `repeatedTest()` example would be: +`repetition 1 of 10`, `repetition 2 of 10`, etc. If you would like the display name of +the `@RepeatedTest` method included in the name of each repetition, you can define your +own custom pattern or use the predefined `RepeatedTest.LONG_DISPLAY_NAME` pattern. The +latter is equal to `"{displayName} :: repetition {currentRepetition} of +{totalRepetitions}"` which results in display names for individual repetitions like +`repeatedTest() :: repetition 1 of 10`, `repeatedTest() :: repetition 2 of 10`, etc. + +In order to retrieve information about the current repetition and the total number of +repetitions programmatically, a developer can choose to have an instance of +`RepetitionInfo` injected into a `@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method. + +[[writing-tests-repeated-tests-examples]] +==== Repeated Test Examples + +The `RepeatedTestsDemo` class at the end of this section demonstrates several examples of +repeated tests. + +The `repeatedTest()` method is identical to example from the previous section; whereas, +`repeatedTestWithRepetitionInfo()` demonstrates how to have an instance of +`RepetitionInfo` injected into a test to access the total number of repetitions for the +current repeated test. + +The next two methods demonstrate how to include a custom `@DisplayName` for the +`@RepeatedTest` method in the display name of each repetition. `customDisplayName()` +combines a custom display name with a custom pattern and then uses `TestInfo` to verify +the format of the generated display name. `Repeat!` is the `{displayName}` which comes +from the `@DisplayName` declaration, and `1/1` comes from +`{currentRepetition}/{totalRepetitions}`. In contrast, +`customDisplayNameWithLongPattern()` uses the aforementioned predefined +`RepeatedTest.LONG_DISPLAY_NAME` pattern. + +`repeatedTestInGerman()` demonstrates the ability to translate display names of repeated +tests into foreign languages -- in this case German, resulting in names for individual +repetitions such as: `Wiederholung 1 von 5`, `Wiederholung 2 von 5`, etc. + +Since the `beforeEach()` method is annotated with `@BeforeEach` it will get executed +before each repetition of each repeated test. By having the `TestInfo` and +`RepetitionInfo` injected into the method, we see that it's possible to obtain +information about the currently executing repeated test. Executing `RepeatedTestsDemo` +with the `INFO` log level enabled results in the following output. + +.... +INFO: About to execute repetition 1 of 10 for repeatedTest +INFO: About to execute repetition 2 of 10 for repeatedTest +INFO: About to execute repetition 3 of 10 for repeatedTest +INFO: About to execute repetition 4 of 10 for repeatedTest +INFO: About to execute repetition 5 of 10 for repeatedTest +INFO: About to execute repetition 6 of 10 for repeatedTest +INFO: About to execute repetition 7 of 10 for repeatedTest +INFO: About to execute repetition 8 of 10 for repeatedTest +INFO: About to execute repetition 9 of 10 for repeatedTest +INFO: About to execute repetition 10 of 10 for repeatedTest +INFO: About to execute repetition 1 of 5 for repeatedTestWithRepetitionInfo +INFO: About to execute repetition 2 of 5 for repeatedTestWithRepetitionInfo +INFO: About to execute repetition 3 of 5 for repeatedTestWithRepetitionInfo +INFO: About to execute repetition 4 of 5 for repeatedTestWithRepetitionInfo +INFO: About to execute repetition 5 of 5 for repeatedTestWithRepetitionInfo +INFO: About to execute repetition 1 of 1 for customDisplayName +INFO: About to execute repetition 1 of 1 for customDisplayNameWithLongPattern +INFO: About to execute repetition 1 of 5 for repeatedTestInGerman +INFO: About to execute repetition 2 of 5 for repeatedTestInGerman +INFO: About to execute repetition 3 of 5 for repeatedTestInGerman +INFO: About to execute repetition 4 of 5 for repeatedTestInGerman +INFO: About to execute repetition 5 of 5 for repeatedTestInGerman +.... + +[source,java] +---- +include::{testDir}/example/RepeatedTestsDemo.java[tags=user_guide] +---- + +When using the `ConsoleLauncher` with the unicode theme enabled, execution of +`RepeatedTestsDemo` results in the following output to the console. + +.... +├─ RepeatedTestsDemo ✔ +│ ├─ repeatedTest() ✔ +│ │ ├─ repetition 1 of 10 ✔ +│ │ ├─ repetition 2 of 10 ✔ +│ │ ├─ repetition 3 of 10 ✔ +│ │ ├─ repetition 4 of 10 ✔ +│ │ ├─ repetition 5 of 10 ✔ +│ │ ├─ repetition 6 of 10 ✔ +│ │ ├─ repetition 7 of 10 ✔ +│ │ ├─ repetition 8 of 10 ✔ +│ │ ├─ repetition 9 of 10 ✔ +│ │ └─ repetition 10 of 10 ✔ +│ ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔ +│ │ ├─ repetition 1 of 5 ✔ +│ │ ├─ repetition 2 of 5 ✔ +│ │ ├─ repetition 3 of 5 ✔ +│ │ ├─ repetition 4 of 5 ✔ +│ │ └─ repetition 5 of 5 ✔ +│ ├─ Repeat! ✔ +│ │ └─ Repeat! 1/1 ✔ +│ ├─ Details... ✔ +│ │ └─ Details... :: repetition 1 of 1 ✔ +│ └─ repeatedTestInGerman() ✔ +│ ├─ Wiederholung 1 von 5 ✔ +│ ├─ Wiederholung 2 von 5 ✔ +│ ├─ Wiederholung 3 von 5 ✔ +│ ├─ Wiederholung 4 von 5 ✔ +│ └─ Wiederholung 5 von 5 ✔ +.... + + +[[writing-tests-parameterized-tests]] +=== Parameterized Tests + +Parameterized tests make it possible to run a test multiple times with different +arguments. They are declared just like regular `@Test` methods but use the +`{ParameterizedTest}` annotation instead. In addition, you must declare at least one +_source_ that will provide the arguments for each invocation and then _consume_ the +arguments in the test method. + +The following example demonstrates a parameterized test that uses the `@ValueSource` +annotation to specify a `String` array as the source of arguments. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=first_example] +---- + +When executing the above parameterized test method, each invocation will be reported +separately. For instance, the `ConsoleLauncher` will print output similar to the +following. + +.... +palindromes(String) ✔ +├─ [1] candidate=racecar ✔ +├─ [2] candidate=radar ✔ +└─ [3] candidate=able was I ere I saw elba ✔ +.... + +[[writing-tests-parameterized-tests-setup]] +==== Required Setup + +In order to use parameterized tests you need to add a dependency on the +`junit-jupiter-params` artifact. Please refer to <> for details. + +[[writing-tests-parameterized-tests-consuming-arguments]] +==== Consuming Arguments + +Parameterized test methods typically _consume_ arguments directly from the configured +source (see <>) following a one-to-one +correlation between argument source index and method parameter index (see examples in +<>). However, a parameterized test +method may also choose to _aggregate_ arguments from the source into a single object +passed to the method (see <>). +Additional arguments may also be provided by a `ParameterResolver` (e.g., to obtain an +instance of `TestInfo`, `TestReporter`, etc.). Specifically, a parameterized test method +must declare formal parameters according to the following rules. + +* Zero or more _indexed arguments_ must be declared first. +* Zero or more _aggregators_ must be declared next. +* Zero or more arguments supplied by a `ParameterResolver` must be declared last. + +In this context, an _indexed argument_ is an argument for a given index in the +`Arguments` provided by an `ArgumentsProvider` that is passed as an argument to the +parameterized method at the same index in the method's formal parameter list. An +_aggregator_ is any parameter of type `ArgumentsAccessor` or any parameter annotated with +`@AggregateWith`. + +[NOTE] +.AutoCloseable arguments +==== +Arguments that implement `java.lang.AutoCloseable` (or `java.io.Closeable` which extends +`java.lang.AutoCloseable`) will be automatically closed after `@AfterEach` methods and +`AfterEachCallback` extensions have been called for the current parameterized test +invocation. + +To prevent this from happening, set the `autoCloseArguments` attribute in +`@ParameterizedTest` to `false`. Specifically, if an argument that implements +`AutoCloseable` is reused for multiple invocations of the same parameterized test method, +you must annotate the method with `@ParameterizedTest(autoCloseArguments = false)` to +ensure that the argument is not closed between invocations. +==== + +[[writing-tests-parameterized-tests-sources]] +==== Sources of Arguments + +Out of the box, JUnit Jupiter provides quite a few _source_ annotations. Each of the +following subsections provides a brief overview and an example for each of them. Please +refer to the Javadoc in the `{params-provider-package}` package for additional +information. + +[[writing-tests-parameterized-tests-sources-ValueSource]] +===== @ValueSource + +`@ValueSource` is one of the simplest possible sources. It lets you specify a single +array of literal values and can only be used for providing a single argument per +parameterized test invocation. + +The following types of literal values are supported by `@ValueSource`. + +- `short` +- `byte` +- `int` +- `long` +- `float` +- `double` +- `char` +- `boolean` +- `java.lang.String` +- `java.lang.Class` + +For example, the following `@ParameterizedTest` method will be invoked three times, with +the values `1`, `2`, and `3` respectively. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ValueSource_example] +---- + +[[writing-tests-parameterized-tests-sources-null-and-empty]] +===== Null and Empty Sources + +In order to check corner cases and verify proper behavior of our software when it is +supplied _bad input_, it can be useful to have `null` and _empty_ values supplied to our +parameterized tests. The following annotations serve as sources of `null` and empty values +for parameterized tests that accept a single argument. + +* `{NullSource}`: provides a single `null` argument to the annotated `@ParameterizedTest` + method. + - `@NullSource` cannot be used for a parameter that has a primitive type. +* `{EmptySource}`: provides a single _empty_ argument to the annotated `@ParameterizedTest` + method for parameters of the following types: `java.lang.String`, `java.util.List`, + `java.util.Set`, `java.util.Map`, primitive arrays (e.g., `int[]`, `char[][]`, etc.), + object arrays (e.g.,`String[]`, `Integer[][]`, etc.). + - Subtypes of the supported types are not supported. +* `{NullAndEmptySource}`: a _composed annotation_ that combines the functionality of + `@NullSource` and `@EmptySource`. + +If you need to supply multiple varying types of _blank_ strings to a parameterized test, +you can achieve that using <> -- +for example, `@ValueSource(strings = {"{nbsp}", "{nbsp}{nbsp}{nbsp}", "\t", "\n"})`. + +You can also combine `@NullSource`, `@EmptySource`, and `@ValueSource` to test a wider +range of `null`, _empty_, and _blank_ input. The following example demonstrates how to +achieve this for strings. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example1] +---- + +Making use of the composed `@NullAndEmptySource` annotation simplifies the above as +follows. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example2] +---- + +NOTE: Both variants of the `nullEmptyAndBlankStrings(String)` parameterized test method +result in six invocations: 1 for `null`, 1 for the empty string, and 4 for the explicit +blank strings supplied via `@ValueSource`. + +[[writing-tests-parameterized-tests-sources-EnumSource]] +===== @EnumSource + +`@EnumSource` provides a convenient way to use `Enum` constants. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_example] +---- + +The annotation's `value` attribute is optional. When omitted, the declared type of the +first method parameter is used. The test will fail if it does not reference an enum type. +Thus, the `value` attribute is required in the above example because the method parameter +is declared as `TemporalUnit`, i.e. the interface implemented by `ChronoUnit`, which isn't +an enum type. Changing the method parameter type to `ChronoUnit` allows you to omit the +explicit enum type from the annotation as follows. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_example_autodetection] +---- + +The annotation provides an optional `names` attribute that lets you specify which +constants shall be used, like in the following example. If omitted, all constants will be +used. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_include_example] +---- + +The `@EnumSource` annotation also provides an optional `mode` attribute that enables +fine-grained control over which constants are passed to the test method. For example, you +can exclude names from the enum constant pool or specify regular expressions as in the +following examples. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_exclude_example] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_regex_example] +---- + +[[writing-tests-parameterized-tests-sources-MethodSource]] +===== @MethodSource + +`{MethodSource}` allows you to refer to one or more _factory_ methods of the test class +or external classes. + +Factory methods within the test class must be `static` unless the test class is annotated +with `@TestInstance(Lifecycle.PER_CLASS)`; whereas, factory methods in external classes +must always be `static`. + +Each factory method must generate a _stream_ of _arguments_, and each set of arguments +within the stream will be provided as the physical arguments for individual invocations +of the annotated `@ParameterizedTest` method. Generally speaking this translates to a +`Stream` of `Arguments` (i.e., `Stream`); however, the actual concrete return +type can take on many forms. In this context, a "stream" is anything that JUnit can +reliably convert into a `Stream`, such as `Stream`, `DoubleStream`, `LongStream`, +`IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects, or an array of +primitives. The "arguments" within the stream can be supplied as an instance of +`Arguments`, an array of objects (e.g., `Object[]`), or a single value if the +parameterized test method accepts a single argument. + +If you only need a single parameter, you can return a `Stream` of instances of the +parameter type as demonstrated in the following example. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=simple_MethodSource_example] +---- + +If you do not explicitly provide a factory method name via `@MethodSource`, JUnit Jupiter +will search for a _factory_ method that has the same name as the current +`@ParameterizedTest` method by convention. This is demonstrated in the following example. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=simple_MethodSource_without_value_example] +---- + +Streams for primitive types (`DoubleStream`, `IntStream`, and `LongStream`) are also +supported as demonstrated by the following example. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=primitive_MethodSource_example] +---- + +If a parameterized test method declares multiple parameters, you need to return a +collection, stream, or array of `Arguments` instances or object arrays as shown below +(see the Javadoc for `{MethodSource}` for further details on supported return types). +Note that `arguments(Object...)` is a static factory method defined in the `Arguments` +interface. In addition, `Arguments.of(Object...)` may be used as an alternative to +`arguments(Object...)`. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=multi_arg_MethodSource_example] +---- + +An external, `static` _factory_ method can be referenced by providing its _fully qualified +method name_ as demonstrated in the following example. + +[source,java,indent=0] +---- +package example; + +include::{testDir}/example/ExternalMethodSourceDemo.java[tags=external_MethodSource_example] +---- + +Factory methods can declare parameters, which will be provided by registered +implementations of the `ParameterResolver` extension API. In the following example, the +factory method is referenced by its name since there is only one such method in the test +class. If there are several local methods with the same name, parameters can also be +provided to differentiate them – for example, `@MethodSource("factoryMethod()")` or +`@MethodSource("factoryMethod(java.lang.String)")`. Alternatively, the factory method +can be referenced by its fully qualified method name, e.g. +`@MethodSource("example.MyTests#factoryMethod(java.lang.String)")`. + +[source,java,indent=0] +---- +include::{testDir}/example/MethodSourceParameterResolutionDemo.java[tags=parameter_resolution_MethodSource_example] +---- + + +[[writing-tests-parameterized-tests-sources-CsvSource]] +===== @CsvSource + +`@CsvSource` allows you to express argument lists as comma-separated values (i.e., CSV +`String` literals). Each string provided via the `value` attribute in `@CsvSource` +represents a CSV record and results in one invocation of the parameterized test. The first +record may optionally be used to supply CSV headers (see the Javadoc for the +`useHeadersInDisplayName` attribute for details and an example). + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=CsvSource_example] +---- + +The default delimiter is a comma (`,`), but you can use another character by setting the +`delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a +`String` delimiter instead of a single character. However, both delimiter attributes +cannot be set simultaneously. + +By default, `@CsvSource` uses a single quote (`'`) as its quote character, but this can be +changed via the `quoteCharacter` attribute. See the `'lemon, lime'` value in the example +above and in the table below. An empty, quoted value (`''`) results in an empty `String` +unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is +interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value +can be interpreted as a `null` reference (see the `NIL` example in the table below). An +`ArgumentConversionException` is thrown if the target type of a `null` reference is a +primitive type. + +NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless +of any custom values configured via the `nullValues` attribute. + +Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed +by default. This behavior can be changed by setting the +`ignoreLeadingAndTrailingWhitespace` attribute to `true`. + +[cols="50,50"] +|=== +| Example Input | Resulting Argument List + +| `@CsvSource({ "apple, banana" })` | `"apple"`, `"banana"` +| `@CsvSource({ "apple, 'lemon, lime'" })` | `"apple"`, `"lemon, lime"` +| `@CsvSource({ "apple, ''" })` | `"apple"`, `""` +| `@CsvSource({ "apple, " })` | `"apple"`, `null` +| `@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")` | `"apple"`, `"banana"`, `null` +| `@CsvSource(value = { " apple , banana" }, ignoreLeadingAndTrailingWhitespace = false)` | `" apple "`, `" banana"` +|=== + +If the programming language you are using supports _text blocks_ -- for example, Java SE +15 or higher -- you can alternatively use the `textBlock` attribute of `@CsvSource`. Each +record within a text block represents a CSV record and results in one invocation of the +parameterized test. The first record may optionally be used to supply CSV headers by +setting the `useHeadersInDisplayName` attribute to `true` as in the example below. + +Using a text block, the previous example can be implemented as follows. + +[source,java,indent=0] +---- +@ParameterizedTest(name = "[{index}] {arguments}") +@CsvSource(useHeadersInDisplayName = true, textBlock = """ + FRUIT, RANK + apple, 1 + banana, 2 + 'lemon, lime', 0xF1 + strawberry, 700_000 + """) +void testWithCsvSource(String fruit, int rank) { + // ... +} +---- + +The generated display names for the previous example include the CSV header names. + +---- +[1] FRUIT = apple, RANK = 1 +[2] FRUIT = banana, RANK = 2 +[3] FRUIT = lemon, lime, RANK = 0xF1 +[4] FRUIT = strawberry, RANK = 700_000 +---- + +In contrast to CSV records supplied via the `value` attribute, a text block can contain +comments. Any line beginning with a `+++#+++` symbol will be treated as a comment and +ignored. Note, however, that the `+++#+++` symbol must be the first character on the line +without any leading whitespace. It is therefore recommended that the closing text block +delimiter (`"""`) be placed either at the end of the last line of input or on the +following line, left aligned with the rest of the input (as can be seen in the example +below which demonstrates formatting similar to a table). + +[source,java,indent=0] +---- +@ParameterizedTest +@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """ + #----------------------------- + # FRUIT | RANK + #----------------------------- + apple | 1 + #----------------------------- + banana | 2 + #----------------------------- + "lemon lime" | 0xF1 + #----------------------------- + strawberry | 700_000 + #----------------------------- + """) +void testWithCsvSource(String fruit, int rank) { + // ... +} +---- + +[NOTE] +==== +Java's https://docs.oracle.com/en/java/javase/15/text-blocks/index.html[text block] +feature automatically removes _incidental whitespace_ when the code is compiled. +However other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a +programming language other than Java and your text block contains comments or new lines +within quoted strings, you will need to ensure that there is no leading whitespace within +your text block. +==== + +[[writing-tests-parameterized-tests-sources-CsvFileSource]] +===== @CsvFileSource + +`@CsvFileSource` lets you use comma-separated value (CSV) files from the classpath or the +local file system. Each record from a CSV file results in one invocation of the +parameterized test. The first record may optionally be used to supply CSV headers. You can +instruct JUnit to ignore the headers via the `numLinesToSkip` attribute. If you would like +for the headers to be used in the display names, you can set the `useHeadersInDisplayName` +attribute to `true`. The examples below demonstrate the use of `numLinesToSkip` and +`useHeadersInDisplayName`. + +The default delimiter is a comma (`,`), but you can use another character by setting the +`delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a +`String` delimiter instead of a single character. However, both delimiter attributes +cannot be set simultaneously. + +.Comments in CSV files +NOTE: Any line beginning with a `+++#+++` symbol will be interpreted as a comment and will +be ignored. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=CsvFileSource_example] +---- + +[source,csv,indent=0] +.two-column.csv +---- +include::{testResourcesDir}/two-column.csv[] +---- + +The following listing shows the generated display names for the first two parameterized +test methods above. + +---- +[1] country=Sweden, reference=1 +[2] country=Poland, reference=2 +[3] country=United States of America, reference=3 +[4] country=France, reference=700_000 +---- + +The following listing shows the generated display names for the last parameterized test +method above that uses CSV header names. + +---- +[1] COUNTRY = Sweden, REFERENCE = 1 +[2] COUNTRY = Poland, REFERENCE = 2 +[3] COUNTRY = United States of America, REFERENCE = 3 +[4] COUNTRY = France, REFERENCE = 700_000 +---- + +In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double +quote (`+++"+++`) as the quote character by default, but this can be changed via the +`quoteCharacter` attribute. See the `"United States of America"` value in the example +above. An empty, quoted value (`+++""+++`) results in an empty `String` unless the +`emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a +`null` reference. By specifying one or more `nullValues`, a custom value can be +interpreted as a `null` reference. An `ArgumentConversionException` is thrown if the +target type of a `null` reference is a primitive type. + +NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless +of any custom values configured via the `nullValues` attribute. + +Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed +by default. This behavior can be changed by setting the +`ignoreLeadingAndTrailingWhitespace` attribute to `true`. + +[[writing-tests-parameterized-tests-sources-ArgumentsSource]] +===== @ArgumentsSource + +`@ArgumentsSource` can be used to specify a custom, reusable `ArgumentsProvider`. Note +that an implementation of `ArgumentsProvider` must be declared as either a top-level +class or as a `static` nested class. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsSource_example] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsProvider_example] +---- + + +[[writing-tests-parameterized-tests-argument-conversion]] +==== Argument Conversion + +[[writing-tests-parameterized-tests-argument-conversion-widening]] +===== Widening Conversion + +JUnit Jupiter supports +https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.2[Widening Primitive +Conversion] for arguments supplied to a `@ParameterizedTest`. For example, a +parameterized test annotated with `@ValueSource(ints = { 1, 2, 3 })` can be declared to +accept not only an argument of type `int` but also an argument of type `long`, `float`, +or `double`. + +[[writing-tests-parameterized-tests-argument-conversion-implicit]] +===== Implicit Conversion + +To support use cases like `@CsvSource`, JUnit Jupiter provides a number of built-in +implicit type converters. The conversion process depends on the declared type of each +method parameter. + +For example, if a `@ParameterizedTest` declares a parameter of type `TimeUnit` and the +actual type supplied by the declared source is a `String`, the string will be +automatically converted into the corresponding `TimeUnit` enum constant. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=implicit_conversion_example] +---- + +`String` instances are implicitly converted to the following target types. + +NOTE: Decimal, hexadecimal, and octal `String` literals will be converted to their +integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts. + +[[writing-tests-parameterized-tests-argument-conversion-implicit-table]] +[cols="10,90"] +|=== +| Target Type | Example + +| `boolean`/`Boolean` | `"true"` -> `true` +| `byte`/`Byte` | `"15"`, `"0xF"`, or `"017"` -> `(byte) 15` +| `char`/`Character` | `"o"` -> `'o'` +| `short`/`Short` | `"15"`, `"0xF"`, or `"017"` -> `(short) 15` +| `int`/`Integer` | `"15"`, `"0xF"`, or `"017"` -> `15` +| `long`/`Long` | `"15"`, `"0xF"`, or `"017"` -> `15L` +| `float`/`Float` | `"1.0"` -> `1.0f` +| `double`/`Double` | `"1.0"` -> `1.0d` +| `Enum` subclass | `"SECONDS"` -> `TimeUnit.SECONDS` +| `java.io.File` | `"/path/to/file"` -> `new File("/path/to/file")` +| `java.lang.Class` | `"java.lang.Integer"` -> `java.lang.Integer.class` _(use `$` for nested classes, e.g. `"java.lang.Thread$State"`)_ +| `java.lang.Class` | `"byte"` -> `byte.class` _(primitive types are supported)_ +| `java.lang.Class` | `"char[]"` -> `char[].class` _(array types are supported)_ +| `java.math.BigDecimal` | `"123.456e789"` -> `new BigDecimal("123.456e789")` +| `java.math.BigInteger` | `"1234567890123456789"` -> `new BigInteger("1234567890123456789")` +| `java.net.URI` | `"https://junit.org/"` -> `URI.create("https://junit.org/")` +| `java.net.URL` | `"https://junit.org/"` -> `URI.create("https://junit.org/").toURL()` +| `java.nio.charset.Charset` | `"UTF-8"` -> `Charset.forName("UTF-8")` +| `java.nio.file.Path` | `"/path/to/file"` -> `Paths.get("/path/to/file")` +| `java.time.Duration` | `"PT3S"` -> `Duration.ofSeconds(3)` +| `java.time.Instant` | `"1970-01-01T00:00:00Z"` -> `Instant.ofEpochMilli(0)` +| `java.time.LocalDateTime` | `"2017-03-14T12:34:56.789"` -> `LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)` +| `java.time.LocalDate` | `"2017-03-14"` -> `LocalDate.of(2017, 3, 14)` +| `java.time.LocalTime` | `"12:34:56.789"` -> `LocalTime.of(12, 34, 56, 789_000_000)` +| `java.time.MonthDay` | `"--03-14"` -> `MonthDay.of(3, 14)` +| `java.time.OffsetDateTime` | `"2017-03-14T12:34:56.789Z"` -> `OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)` +| `java.time.OffsetTime` | `"12:34:56.789Z"` -> `OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)` +| `java.time.Period` | `"P2M6D"` -> `Period.of(0, 2, 6)` +| `java.time.YearMonth` | `"2017-03"` -> `YearMonth.of(2017, 3)` +| `java.time.Year` | `"2017"` -> `Year.of(2017)` +| `java.time.ZonedDateTime` | `"2017-03-14T12:34:56.789Z"` -> `ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)` +| `java.time.ZoneId` | `"Europe/Berlin"` -> `ZoneId.of("Europe/Berlin")` +| `java.time.ZoneOffset` | `"+02:30"` -> `ZoneOffset.ofHoursMinutes(2, 30)` +| `java.util.Currency` | `"JPY"` -> `Currency.getInstance("JPY")` +| `java.util.Locale` | `"en"` -> `new Locale("en")` +| `java.util.UUID` | `"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"` -> `UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")` +|=== + +[[writing-tests-parameterized-tests-argument-conversion-implicit-fallback]] +====== Fallback String-to-Object Conversion + +In addition to implicit conversion from strings to the target types listed in the above +table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a +`String` to a given target type if the target type declares exactly one suitable _factory +method_ or a _factory constructor_ as defined below. + +- __factory method__: a non-private, `static` method declared in the target type that + accepts a single `String` argument and returns an instance of the target type. The name + of the method can be arbitrary and need not follow any particular convention. +- __factory constructor__: a non-private constructor in the target type that accepts a + single `String` argument. Note that the target type must be declared as either a + top-level class or as a `static` nested class. + +NOTE: If multiple _factory methods_ are discovered, they will be ignored. If a _factory +method_ and a _factory constructor_ are discovered, the factory method will be used +instead of the constructor. + +For example, in the following `@ParameterizedTest` method, the `Book` argument will be +created by invoking the `Book.fromTitle(String)` factory method and passing `"42 Cats"` +as the title of the book. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example_Book] +---- + +[[writing-tests-parameterized-tests-argument-conversion-explicit]] +===== Explicit Conversion + +Instead of relying on implicit argument conversion you may explicitly specify an +`ArgumentConverter` to use for a certain parameter using the `@ConvertWith` annotation +like in the following example. Note that an implementation of `ArgumentConverter` must be +declared as either a top-level class or as a `static` nested class. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_conversion_example] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_ToStringArgumentConverter] +---- + +If the converter is only meant to convert one type to another, you can extend +`TypedArgumentConverter` to avoid boilerplate type checks. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_TypedArgumentConverter] +---- + +Explicit argument converters are meant to be implemented by test and extension authors. +Thus, `junit-jupiter-params` only provides a single explicit argument converter that may +also serve as a reference implementation: `JavaTimeArgumentConverter`. It is used via the +composed annotation `JavaTimeConversionPattern`. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_java_time_converter] +---- + +[[writing-tests-parameterized-tests-argument-aggregation]] +==== Argument Aggregation + +By default, each _argument_ provided to a `@ParameterizedTest` method corresponds to a +single method parameter. Consequently, argument sources which are expected to supply a +large number of arguments can lead to large method signatures. + +In such cases, an `{ArgumentsAccessor}` can be used instead of multiple parameters. Using +this API, you can access the provided arguments through a single argument passed to your +test method. In addition, type conversion is supported as discussed in +<>. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAccessor_example] +---- + +_An instance of `ArgumentsAccessor` is automatically injected into any parameter of type +`ArgumentsAccessor`._ + +[[writing-tests-parameterized-tests-argument-aggregation-custom]] +===== Custom Aggregators + +Apart from direct access to a `@ParameterizedTest` method's arguments using an +`ArgumentsAccessor`, JUnit Jupiter also supports the usage of custom, reusable +_aggregators_. + +To use a custom aggregator, implement the `{ArgumentsAggregator}` interface and register +it via the `@AggregateWith` annotation on a compatible parameter in the +`@ParameterizedTest` method. The result of the aggregation will then be provided as an +argument for the corresponding parameter when the parameterized test is invoked. Note +that an implementation of `ArgumentsAggregator` must be declared as either a top-level +class or as a `static` nested class. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example_PersonAggregator] +---- + +If you find yourself repeatedly declaring `@AggregateWith(MyTypeAggregator.class)` for +multiple parameterized test methods across your codebase, you may wish to create a custom +_composed annotation_ such as `@CsvToMyType` that is meta-annotated with +`@AggregateWith(MyTypeAggregator.class)`. The following example demonstrates this in +action with a custom `@CsvToPerson` annotation. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example] +---- + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example_CsvToPerson] +---- + + +[[writing-tests-parameterized-tests-display-names]] +==== Customizing Display Names + +By default, the display name of a parameterized test invocation contains the invocation +index and the `String` representation of all arguments for that specific invocation. +Each of them is preceded by the parameter name (unless the argument is only available via +an `ArgumentsAccessor` or `ArgumentAggregator`), if present in the bytecode (for Java, +test code must be compiled with the `-parameters` compiler flag). + +However, you can customize invocation display names via the `name` attribute of the +`@ParameterizedTest` annotation like in the following example. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=custom_display_names] +---- + +When executing the above method using the `ConsoleLauncher` you will see output similar to +the following. + +.... +Display name of container ✔ +├─ 1 ==> the rank of 'apple' is 1 ✔ +├─ 2 ==> the rank of 'banana' is 2 ✔ +└─ 3 ==> the rank of 'lemon, lime' is 3 ✔ +.... + +Please note that `name` is a `MessageFormat` pattern. Thus, a single quote (`'`) needs to +be represented as a doubled single quote (`''`) in order to be displayed. + +The following placeholders are supported within custom display names. + +[cols="20,80"] +|=== +| Placeholder | Description + +| `{displayName}` | the display name of the method +| `{index}` | the current invocation index (1-based) +| `{arguments}` | the complete, comma-separated arguments list +| `{argumentsWithNames}` | the complete, comma-separated arguments list with parameter names +| `{0}`, `{1}`, ... | an individual argument +|=== + +NOTE: When including arguments in display names, their string representations are truncated +if they exceed the configured maximum length. The limit is configurable via the +`junit.jupiter.params.displayname.argument.maxlength` configuration parameter and defaults +to 512 characters. + +When using `@MethodSource` or `@ArgumentsSource`, you can provide custom names for +arguments using the `{Named}` API. A custom name will be used if the argument is included +in the invocation display name, like in the example below. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=named_arguments] +---- + +.... +A parameterized test with named arguments ✔ +├─ 1: An important file ✔ +└─ 2: Another file ✔ +.... + +If you'd like to set a default name pattern for all parameterized tests in your project, +you can declare the `junit.jupiter.params.displayname.default` configuration parameter in +the `junit-platform.properties` file as demonstrated in the following example (see +<> for other options). + +[source,properties,indent=0] +---- +junit.jupiter.params.displayname.default = {index} +---- + +The display name for a parameterized test is determined according to the following +precedence rules: + +1. `name` attribute in `@ParameterizedTest`, if present +2. value of the `junit.jupiter.params.displayname.default` configuration parameter, if present +3. `DEFAULT_DISPLAY_NAME` constant defined in `@ParameterizedTest` + +[[writing-tests-parameterized-tests-lifecycle-interop]] +==== Lifecycle and Interoperability + +Each invocation of a parameterized test has the same lifecycle as a regular `@Test` +method. For example, `@BeforeEach` methods will be executed before each invocation. +Similar to <>, invocations will appear one by one in the +test tree of an IDE. You may at will mix regular `@Test` methods and `@ParameterizedTest` +methods within the same test class. + +You may use `ParameterResolver` extensions with `@ParameterizedTest` methods. However, +method parameters that are resolved by argument sources need to come first in the +argument list. Since a test class may contain regular tests as well as parameterized +tests with different parameter lists, values from argument sources are not resolved for +lifecycle methods (e.g. `@BeforeEach`) and test class constructors. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ParameterResolver_example] +---- + + +[[writing-tests-test-templates]] +=== Test Templates + +A `{TestTemplate}` method is not a regular test case but rather a template for test +cases. As such, it is designed to be invoked multiple times depending on the number of +invocation contexts returned by the registered providers. Thus, it must be used in +conjunction with a registered `{TestTemplateInvocationContextProvider}` extension. Each +invocation of a test template method behaves like the execution of a regular `@Test` +method with full support for the same lifecycle callbacks and extensions. Please refer to +<> for usage examples. + +NOTE: <> and <> are +built-in specializations of test templates. + +[[writing-tests-dynamic-tests]] +=== Dynamic Tests + +The standard `@Test` annotation in JUnit Jupiter described in +<> is very similar to the `@Test` annotation in JUnit 4. Both +describe methods that implement test cases. These test cases are static in the sense that +they are fully specified at compile time, and their behavior cannot be changed by +anything happening at runtime. _Assumptions provide a basic form of dynamic behavior but +are intentionally rather limited in their expressiveness._ + +In addition to these standard tests a completely new kind of test programming model has +been introduced in JUnit Jupiter. This new kind of test is a _dynamic test_ which is +generated at runtime by a factory method that is annotated with `@TestFactory`. + +In contrast to `@Test` methods, a `@TestFactory` method is not itself a test case but +rather a factory for test cases. Thus, a dynamic test is the product of a factory. +Technically speaking, a `@TestFactory` method must return a single `DynamicNode` or a +`Stream`, `Collection`, `Iterable`, `Iterator`, or array of `DynamicNode` instances. +Instantiable subclasses of `DynamicNode` are `DynamicContainer` and `DynamicTest`. +`DynamicContainer` instances are composed of a _display name_ and a list of dynamic child +nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes. +`DynamicTest` instances will be executed lazily, enabling dynamic and even +non-deterministic generation of test cases. + +Any `Stream` returned by a `@TestFactory` will be properly closed by calling +`stream.close()`, making it safe to use a resource such as `Files.lines()`. + +As with `@Test` methods, `@TestFactory` methods must not be `private` or `static` and may +optionally declare parameters to be resolved by `ParameterResolvers`. + +A `DynamicTest` is a test case generated at runtime. It is composed of a _display name_ +and an `Executable`. `Executable` is a `@FunctionalInterface` which means that the +implementations of dynamic tests can be provided as _lambda expressions_ or _method +references_. + +.Dynamic Test Lifecycle +WARNING: The execution lifecycle of a dynamic test is quite different than it is for a +standard `@Test` case. Specifically, there are no lifecycle callbacks for individual +dynamic tests. This means that `@BeforeEach` and `@AfterEach` methods and their +corresponding extension callbacks are executed for the `@TestFactory` method but not for +each _dynamic test_. In other words, if you access fields from the test instance within a +lambda expression for a dynamic test, those fields will not be reset by callback methods +or extensions between the execution of individual dynamic tests generated by the same +`@TestFactory` method. + +As of JUnit Jupiter {jupiter-version}, dynamic tests must always be created by factory +methods; however, this might be complemented by a registration facility in a later +release. + +[[writing-tests-dynamic-tests-examples]] +==== Dynamic Test Examples + +The following `DynamicTestsDemo` class demonstrates several examples of test factories +and dynamic tests. + +The first method returns an invalid return type. Since an invalid return type cannot be +detected at compile time, a `JUnitException` is thrown when it is detected at runtime. + +The next six methods demonstrate the generation of a `Collection`, `Iterable`, `Iterator`, +array, or `Stream` of `DynamicTest` instances. Most of these examples do not really +exhibit dynamic behavior but merely demonstrate the supported return types in principle. +However, `dynamicTestsFromStream()` and `dynamicTestsFromIntStream()` demonstrate how to +generate dynamic tests for a given set of strings or a range of input numbers. + +The next method is truly dynamic in nature. `generateRandomNumberOfTests()` implements an +`Iterator` that generates random numbers, a display name generator, and a test executor +and then provides all three to `DynamicTest.stream()`. Although the non-deterministic +behavior of `generateRandomNumberOfTests()` is of course in conflict with test +repeatability and should thus be used with care, it serves to demonstrate the +expressiveness and power of dynamic tests. + +The next method is similar to `generateRandomNumberOfTests()` in terms of flexibility; +however, `dynamicTestsFromStreamFactoryMethod()` generates a stream of dynamic tests from +an existing `Stream` via the `DynamicTest.stream()` factory method. + +For demonstration purposes, the `dynamicNodeSingleTest()` method generates a single +`DynamicTest` instead of a stream, and the `dynamicNodeSingleContainer()` method generates +a nested hierarchy of dynamic tests utilizing `DynamicContainer`. + +[source,java] +---- +include::{testDir}/example/DynamicTestsDemo.java[tags=user_guide] +---- + +[[writing-tests-dynamic-tests-uri-test-source]] +==== URI Test Sources for Dynamic Tests + +The JUnit Platform provides `TestSource`, a representation of the source of a test or +container used to navigate to its location by IDEs and build tools. + +The `TestSource` for a dynamic test or dynamic container can be constructed from a +`java.net.URI` which can be supplied via the `DynamicTest.dynamicTest(String, URI, +Executable)` or `DynamicContainer.dynamicContainer(String, URI, Stream)` factory method, +respectively. The `URI` will be converted to one of the following `TestSource` +implementations. + +`ClasspathResourceSource` :: + If the `URI` contains the `classpath` scheme -- for example, + `classpath:/test/foo.xml?line=20,column=2`. + +`DirectorySource` :: + If the `URI` represents a directory present in the file system. + +`FileSource` :: + If the `URI` represents a file present in the file system. + +`MethodSource` :: + If the `URI` contains the `method` scheme and the fully qualified method name (FQMN) -- + for example, `method:org.junit.Foo#bar(java.lang.String, java.lang.String[])`. Please + refer to the Javadoc for `DiscoverySelectors.selectMethod(String)` for the supported + formats for a FQMN. + +`ClassSource` :: + If the `URI` contains the `class` scheme and the fully qualified class name -- + for example, `class:org.junit.Foo?line=42`. + +`UriSource` :: + If none of the above `TestSource` implementations are applicable. + + +[[writing-tests-declarative-timeouts]] +=== Timeouts + +The `@Timeout` annotation allows one to declare that a test, test factory, test template, +or lifecycle method should fail if its execution time exceeds a given duration. The time +unit for the duration defaults to seconds but is configurable. + +The following example shows how `@Timeout` is applied to lifecycle and test methods. + +[source,java] +---- +include::{testDir}/example/TimeoutDemo.java[tags=user_guide] +---- + +To apply the same timeout to all test methods within a test class and all of its `@Nested` +classes, you can declare the `@Timeout` annotation at the class level. It will then be +applied to all test, test factory, and test template methods within that class and its +`@Nested` classes unless overridden by a `@Timeout` annotation on a specific method or +`@Nested` class. Please note that `@Timeout` annotations declared at the class level are +not applied to lifecycle methods. + +Declaring `@Timeout` on a `@TestFactory` method checks that the factory method returns +within the specified duration but does not verify the execution time of each individual +`DynamicTest` generated by the factory. Please use +`assertTimeout()` or `assertTimeoutPreemptively()` for that purpose. + +If `@Timeout` is present on a `@TestTemplate` method — for example, a `@RepeatedTest` or +`@ParameterizedTest` — each invocation will have the given timeout applied to it. + +[[writing-tests-declarative-timeouts-thread-mode]] +==== Thread mode + +The timeout can be applied using one of the following three thread modes: `SAME_THREAD`, +`SEPARATE_THREAD`, or `INFERRED`. + +When `SAME_THREAD` is used, the execution of the annotated method proceeds in the main +thread of the test. If the timeout is exceeded, the main thread is interrupted from +another thread. This is done to ensure interoperability with frameworks such as Spring +that make use of mechanisms that are sensitive to the currently running thread — for +example, `ThreadLocal` transaction management. + +On the contrary when `SEPARATE_THREAD` is used, like the `assertTimeoutPreemptively()` +assertion, the execution of the annotated method proceeds in a separate thread, this +can lead to undesirable side effects, see <>. + +When `INFERRED` (default) thread mode is used, the thread mode is resolved via the +`junit.jupiter.execution.timeout.thread.mode.default` configuration parameter. If the +provided configuration parameter is invalid or not present then `SAME_THREAD` is used as +fallback. + +[[writing-tests-declarative-timeouts-default-timeouts]] +==== Default Timeouts + +The following <> can be used to +specify default timeouts for all methods of a certain category unless they or an enclosing +test class is annotated with `@Timeout`: + +`junit.jupiter.execution.timeout.default`:: + Default timeout for all testable and lifecycle methods +`junit.jupiter.execution.timeout.testable.method.default`:: + Default timeout for all testable methods +`junit.jupiter.execution.timeout.test.method.default`:: + Default timeout for `@Test` methods +`junit.jupiter.execution.timeout.testtemplate.method.default`:: + Default timeout for `@TestTemplate` methods +`junit.jupiter.execution.timeout.testfactory.method.default`:: + Default timeout for `@TestFactory` methods +`junit.jupiter.execution.timeout.lifecycle.method.default`:: + Default timeout for all lifecycle methods +`junit.jupiter.execution.timeout.beforeall.method.default`:: + Default timeout for `@BeforeAll` methods +`junit.jupiter.execution.timeout.beforeeach.method.default`:: + Default timeout for `@BeforeEach` methods +`junit.jupiter.execution.timeout.aftereach.method.default`:: + Default timeout for `@AfterEach` methods +`junit.jupiter.execution.timeout.afterall.method.default`:: + Default timeout for `@AfterAll` methods + +More specific configuration parameters override less specific ones. For example, +`junit.jupiter.execution.timeout.test.method.default` overrides +`junit.jupiter.execution.timeout.testable.method.default` which overrides +`junit.jupiter.execution.timeout.default`. + +The values of such configuration parameters must be in the following, case-insensitive +format: ` [ns|μs|ms|s|m|h|d]`. The space between the number and the unit may be +omitted. Specifying no unit is equivalent to using seconds. + +.Example timeout configuration parameter values +[cols="20,80"] +|=== +| Parameter value | Equivalent annotation + +| `42` | `@Timeout(42)` +| `42 ns` | `@Timeout(value = 42, unit = NANOSECONDS)` +| `42 μs` | `@Timeout(value = 42, unit = MICROSECONDS)` +| `42 ms` | `@Timeout(value = 42, unit = MILLISECONDS)` +| `42 s` | `@Timeout(value = 42, unit = SECONDS)` +| `42 m` | `@Timeout(value = 42, unit = MINUTES)` +| `42 h` | `@Timeout(value = 42, unit = HOURS)` +| `42 d` | `@Timeout(value = 42, unit = DAYS)` +|=== + + +[[writing-tests-declarative-timeouts-polling]] +==== Using @Timeout for Polling Tests + +When dealing with asynchronous code, it is common to write tests that poll while waiting +for something to happen before performing any assertions. In some cases you can rewrite +the logic to use a `CountDownLatch` or another synchronization mechanism, but sometimes +that is not possible — for example, if the subject under test sends a message to a channel +in an external message broker and assertions cannot be performed until the message has +been successfully sent through the channel. Asynchronous tests like these require some +form of timeout to ensure they don't hang the test suite by executing indefinitely, as +would be the case if an asynchronous message never gets successfully delivered. + +By configuring a timeout for an asynchronous test that polls, you can ensure that the test +does not execute indefinitely. The following example demonstrates how to achieve this with +JUnit Jupiter's `@Timeout` annotation. This technique can be used to implement "poll +until" logic very easily. + +[source,java] +---- +include::{testDir}/example/PollingTimeoutDemo.java[tags=user_guide,indent=0] +---- + +NOTE: If you need more control over polling intervals and greater flexibility with +asynchronous tests, consider using a dedicated library such as +link:https://github.com/awaitility/awaitility[Awaitility]. + + +[[writing-tests-declarative-timeouts-mode]] +==== Disable @Timeout Globally +When stepping through your code in a debug session, a fixed timeout limit may influence +the result of the test, e.g. mark the test as failed although all assertions were met. + +JUnit Jupiter supports the `junit.jupiter.execution.timeout.mode` configuration parameter +to configure when timeouts are applied. There are three modes: `enabled`, `disabled`, +and `disabled_on_debug`. The default mode is `enabled`. +A VM runtime is considered to run in debug mode when one of its input parameters starts +with `-agentlib:jdwp` or `-Xrunjdwp`. +This heuristic is queried by the `disabled_on_debug` mode. + + +[[writing-tests-parallel-execution]] +=== Parallel Execution + +.Parallel test execution is an experimental feature +WARNING: You're invited to give it a try and provide feedback to the JUnit team so they +can improve and eventually <> this feature. + +By default, JUnit Jupiter tests are run sequentially in a single thread. Running tests in +parallel -- for example, to speed up execution -- is available as an opt-in feature since +version 5.3. To enable parallel execution, set the +`junit.jupiter.execution.parallel.enabled` configuration parameter to `true` -- for +example, in `junit-platform.properties` (see <> for other +options). + +Please note that enabling this property is only the first step required to execute tests +in parallel. If enabled, test classes and methods will still be executed sequentially by +default. Whether or not a node in the test tree is executed concurrently is controlled by +its execution mode. The following two modes are available. + +`SAME_THREAD`:: + Force execution in the same thread used by the parent. For example, when used on a test + method, the test method will be executed in the same thread as any `@BeforeAll` or + `@AfterAll` methods of the containing test class. + +`CONCURRENT`:: + Execute concurrently unless a resource lock forces execution in the same thread. + +By default, nodes in the test tree use the `SAME_THREAD` execution mode. You can change +the default by setting the `junit.jupiter.execution.parallel.mode.default` configuration +parameter. Alternatively, you can use the `{Execution}` annotation to change the +execution mode for the annotated element and its subelements (if any) which allows you to +activate parallel execution for individual test classes, one by one. + +[source,properties] +.Configuration parameters to execute all tests in parallel +---- +junit.jupiter.execution.parallel.enabled = true +junit.jupiter.execution.parallel.mode.default = concurrent +---- + +The default execution mode is applied to all nodes of the test tree with a few notable +exceptions, namely test classes that use the `Lifecycle.PER_CLASS` mode or a +`{MethodOrderer}` (except for `{MethodOrderer_Random}`). In the former case, test authors +have to ensure that the test class is thread-safe; in the latter, concurrent execution +might conflict with the configured execution order. Thus, in both cases, test methods in +such test classes are only executed concurrently if the `@Execution(CONCURRENT)` +annotation is present on the test class or method. + +All nodes of the test tree that are configured with the `CONCURRENT` execution mode will +be executed fully in parallel according to the provided +<> while observing the +declarative <> +mechanism. Please note that <> needs to be enabled +separately. + +In addition, you can configure the default execution mode for top-level classes by setting +the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter. By +combining both configuration parameters, you can configure classes to run in parallel but +their methods in the same thread: + +[source,properties] +.Configuration parameters to execute top-level classes in parallel but methods in same thread +---- +junit.jupiter.execution.parallel.enabled = true +junit.jupiter.execution.parallel.mode.default = same_thread +junit.jupiter.execution.parallel.mode.classes.default = concurrent +---- + +The opposite combination will run all methods within one class in parallel, but top-level +classes will run sequentially: + +[source,properties] +.Configuration parameters to execute top-level classes sequentially but their methods in parallel +---- +junit.jupiter.execution.parallel.enabled = true +junit.jupiter.execution.parallel.mode.default = concurrent +junit.jupiter.execution.parallel.mode.classes.default = same_thread +---- + +The following diagram illustrates how the execution of two top-level test classes `A` and +`B` with two test methods per class behaves for all four combinations of +`junit.jupiter.execution.parallel.mode.default` and +`junit.jupiter.execution.parallel.mode.classes.default` (see labels in first column). + +//// +Source: https://mermaidjs.github.io/mermaid-live-editor/#/view/eyJjb2RlIjoiZ2FudHRcbiAgICBkYXRlRm9ybWF0ICBZWVlZLU1NLUREXG5cbiAgICBzZWN0aW9uIChzYW1lX3RocmVhZCwgc2FtZV90aHJlYWQpXG4gICAgQS50ZXN0MSgpIDphc3MxLCAyMDE5LTAxLTAxLCAxZFxuICAgIEEudGVzdDIoKSA6YXNzMiwgYWZ0ZXIgYXNzMSwgMWRcbiAgICBCLnRlc3QxKCkgOmJzczEsIGFmdGVyIGFzczIsIDFkXG4gICAgQi50ZXN0MigpIDpic3MyLCBhZnRlciBic3MxLCAxZFxuXG4gICAgc2VjdGlvbiAoc2FtZV90aHJlYWQsIGNvbmN1cnJlbnQpXG4gICAgQS50ZXN0MSgpIDphc2MxLCAyMDE5LTAxLTAxLCAxZFxuICAgIEEudGVzdDIoKSA6YXNjMiwgYWZ0ZXIgYXNjMSwgMWRcbiAgICBCLnRlc3QxKCkgOmJzYzEsIDIwMTktMDEtMDEsIDFkXG4gICAgQi50ZXN0MigpIDpic2MyLCBhZnRlciBic2MxLCAxZFxuXG4gICAgc2VjdGlvbiAoY29uY3VycmVudCwgc2FtZV90aHJlYWQpXG4gICAgQS50ZXN0MSgpIDphY3MxLCAyMDE5LTAxLTAxLCAxZFxuICAgIEEudGVzdDIoKSA6YWNzMiwgMjAxOS0wMS0wMSwgMWRcbiAgICBCLnRlc3QxKCkgOmJjczEsIGFmdGVyIGFjczIsIDFkXG4gICAgQi50ZXN0MigpIDpiY3MyLCBhZnRlciBhY3MyLCAxZFxuXG4gICAgc2VjdGlvbiAoY29uY3VycmVudCwgY29uY3VycmVudClcbiAgICBBLnRlc3QxKCkgOmFjYzEsIDIwMTktMDEtMDEsIDFkXG4gICAgQS50ZXN0MigpIDphY2MyLCAyMDE5LTAxLTAxLCAxZFxuICAgIEIudGVzdDEoKSA6YmNjMSwgMjAxOS0wMS0wMSwgMWRcbiAgICBCLnRlc3QyKCkgOmJjYzIsIDIwMTktMDEtMDEsIDFkXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoibmV1dHJhbCIsImdhbnR0Ijp7ImxlZnRQYWRkaW5nIjoyMjUsImJhckdhcCI6NSwiZ3JpZExpbmVTdGFydFBhZGRpbmciOjEwLCJiYXJIZWlnaHQiOjMwLCJmb250U2l6ZSI6MTV9LCJ0aGVtZUNTUyI6Ii50YXNrVGV4dCwgLnNlY3Rpb25UaXRsZSB7IGZvbnQtZmFtaWx5OiAnT3BlbiBTYW5zJzsgZm9udC1zaXplOjE1cHggfSAuZ3JpZCAudGljayB0ZXh0IHsgZGlzcGxheTpub25lIH0gLmdyaWQgLnRpY2s6bnRoLWNoaWxkKDJuKzEpIHsgZGlzcGxheTpub25lIH0ifX0 + +gantt + dateFormat YYYY-MM-DD + + section (same_thread, same_thread) + A.test1() :ass1, 2019-01-01, 1d + A.test2() :ass2, after ass1, 1d + B.test1() :bss1, after ass2, 1d + B.test2() :bss2, after bss1, 1d + + section (same_thread, concurrent) + A.test1() :asc1, 2019-01-01, 1d + A.test2() :asc2, after asc1, 1d + B.test1() :bsc1, 2019-01-01, 1d + B.test2() :bsc2, after bsc1, 1d + + section (concurrent, same_thread) + A.test1() :acs1, 2019-01-01, 1d + A.test2() :acs2, 2019-01-01, 1d + B.test1() :bcs1, after acs2, 1d + B.test2() :bcs2, after acs2, 1d + + section (concurrent, concurrent) + A.test1() :acc1, 2019-01-01, 1d + A.test2() :acc2, 2019-01-01, 1d + B.test1() :bcc1, 2019-01-01, 1d + B.test2() :bcc2, 2019-01-01, 1d + +//// +image::writing-tests_execution_mode.svg[caption='',title='Default execution mode configuration combinations'] + +If the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter is +not explicitly set, the value for `junit.jupiter.execution.parallel.mode.default` will be +used instead. + +[[writing-tests-parallel-execution-config]] +==== Configuration + +Properties such as the desired parallelism and the maximum pool size can be configured +using a `{ParallelExecutionConfigurationStrategy}`. The JUnit Platform provides two +implementations out of the box: `dynamic` and `fixed`. Alternatively, you may implement a +`custom` strategy. + +To select a strategy, set the `junit.jupiter.execution.parallel.config.strategy` +configuration parameter to one of the following options. + +`dynamic`:: + Computes the desired parallelism based on the number of available processors/cores + multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor` + configuration parameter (defaults to `1`). + +`fixed`:: + Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism` + configuration parameter as the desired parallelism. + The optional `junit.jupiter.execution.parallel.config.fixed.max-pool-size` + configuration parameter can be used to limit the maximum number of threads. + +`custom`:: + Allows you to specify a custom `{ParallelExecutionConfigurationStrategy}` + implementation via the mandatory `junit.jupiter.execution.parallel.config.custom.class` + configuration parameter to determine the desired configuration. + +If no configuration strategy is set, JUnit Jupiter uses the `dynamic` configuration +strategy with a factor of `1`. Consequently, the desired parallelism will be equal to the +number of available processors/cores. + +.Parallelism alone does not imply maximum number of concurrent threads +NOTE: By default JUnit Jupiter does not guarantee that the number of concurrently +executing tests will not exceed the configured parallelism. For example, when using one +of the synchronization mechanisms described in the next section, the `ForkJoinPool` that +is used behind the scenes may spawn additional threads to ensure execution continues with +sufficient parallelism. +If you require such guarantees, with Java 9+, it is possible to limit the maximum number +of concurrent threads by controlling the maximum pool size of the `fixed` and `custom` +strategies. + +[[writing-tests-parallel-execution-config-properties]] +===== Relevant properties + +The following table lists relevant properties for configuring parallel execution. See +<> for details on how to set such properties. + +[cols="d,d,a,d"] +|=== +|Property |Description |Supported Values |Default Value + +| ```junit.jupiter.execution.parallel.enabled``` +| Enable parallel test execution +| + * `true` + * `false` +| ```false``` + +| ```junit.jupiter.execution.parallel.mode.default``` +| Default execution mode of nodes in the test tree +| + * `concurrent` + * `same_thread` +| ```same_thread``` + +| ```junit.jupiter.execution.parallel.mode.classes.default``` +| Default execution mode of top-level classes +| + * `concurrent` + * `same_thread` +| ```same_thread``` + +| ```junit.jupiter.execution.parallel.config.strategy``` +| Execution strategy for desired parallelism and maximum pool size +| + * `dynamic` + * `fixed` + * `custom` +| ```dynamic``` + +| ```junit.jupiter.execution.parallel.config.dynamic.factor``` +| Factor to be multiplied by the number of available processors/cores to determine the + desired parallelism for the ```dynamic``` configuration strategy +| a positive decimal number +| ```1.0``` + +| ```junit.jupiter.execution.parallel.config.fixed.parallelism``` +| Desired parallelism for the ```fixed``` configuration strategy +| a positive integer +| no default value + +| ```junit.jupiter.execution.parallel.config.fixed.max-pool-size``` +| Desired maximum pool size of the underlying fork-join pool for the ```fixed``` + configuration strategy +| a positive integer, must greater than or equal to `junit.jupiter.execution.parallel.config.fixed.parallelism` +| 256 + the value of `junit.jupiter.execution.parallel.config.fixed.parallelism` + +| ```junit.jupiter.execution.parallel.config.fixed.saturate``` +| Disable saturation of the underlying fork-join pool for the ```fixed``` configuration + strategy +| + * `true` + * `false` +| ```true``` + +| ```junit.jupiter.execution.parallel.config.custom.class``` +| Fully qualified class name of the _ParallelExecutionConfigurationStrategy_ to be + used for the ```custom``` configuration strategy +| for example, _org.example.CustomStrategy_ +| no default value +|=== + +[[writing-tests-parallel-execution-synchronization]] +==== Synchronization + +In addition to controlling the execution mode using the `{Execution}` annotation, JUnit +Jupiter provides another annotation-based declarative synchronization mechanism. The +`{ResourceLock}` annotation allows you to declare that a test class or method uses a +specific shared resource that requires synchronized access to ensure reliable test +execution. The shared resource is identified by a unique name which is a `String`. The +name can be user-defined or one of the predefined constants in `{Resources}`: +`SYSTEM_PROPERTIES`, `SYSTEM_OUT`, `SYSTEM_ERR`, `LOCALE`, or `TIME_ZONE`. + +If the tests in the following example were run in parallel _without_ the use of +{ResourceLock}, they would be _flaky_. Sometimes they would pass, and at other times they +would fail due to the inherent race condition of writing and then reading the same JVM +System Property. + +When access to shared resources is declared using the `{ResourceLock}` annotation, the +JUnit Jupiter engine uses this information to ensure that no conflicting tests are run in +parallel. + +[NOTE] +.Running tests in isolation +==== +If most of your test classes can be run in parallel without any synchronization but you +have some test classes that need to run in isolation, you can mark the latter with the +`{Isolated}` annotation. Tests in such classes are executed sequentially without any other +tests running at the same time. +==== + +In addition to the `String` that uniquely identifies the shared resource, you may specify +an access mode. Two tests that require `READ` access to a shared resource may run in +parallel with each other but not while any other test that requires `READ_WRITE` access +to the same shared resource is running. + +[source,java] +---- +include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide] +---- + + +[[writing-tests-built-in-extensions]] +=== Built-in Extensions + +While the JUnit team encourages reusable extensions to be packaged and maintained in +separate libraries, the JUnit Jupiter API artifact includes a few user-facing extension +implementations that are considered so generally useful that users shouldn't have to add +another dependency. + +[[writing-tests-built-in-extensions-TempDirectory]] +==== The TempDirectory Extension + +.`@TempDir` is an experimental feature +WARNING: You're invited to give it a try and provide feedback to the JUnit team so they +can improve and eventually <> this feature. + +The built-in `{TempDirectory}` extension is used to create and clean up a temporary +directory for an individual test or all tests in a test class. It is registered by +default. To use it, annotate a non-final, unassigned field of type `java.nio.file.Path` or +`java.io.File` with `{TempDir}` or add a parameter of type `java.nio.file.Path` or +`java.io.File` annotated with `@TempDir` to a lifecycle method or test method. + +For example, the following test declares a parameter annotated with `@TempDir` for a +single test method, creates and writes to a file in the temporary directory, and checks +its content. + +[source,java,indent=0] +.A test method that requires a temporary directory +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_parameter_injection] +---- + +You can inject multiple temporary directories by specifying multiple annotated parameters. + +[source,java,indent=0] +.A test method that requires multiple temporary directories +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_multiple_directories] +---- + +WARNING: To revert to the old behavior of using a single temporary directory for the +entire test class or method (depending on which level the annotation is used), you can set +the `junit.jupiter.tempdir.scope` configuration parameter to `per_context`. However, +please note that this option is deprecated and will be removed in a future release. + +`@TempDir` is not supported on constructor parameters. If you wish to retain a single +reference to a temp directory across lifecycle methods and the current test method, please +use field injection by annotating an instance field with `@TempDir`. + +The following example stores a _shared_ temporary directory in a `static` field. This +allows the same `sharedTempDir` to be used in all lifecycle methods and test methods of +the test class. For better isolation, you should use an instance field so that each test +method uses a separate directory. + +[source,java,indent=0] +.A test class that shares a temporary directory across test methods +---- +include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_field_injection] +---- + +The `@TempDir` annotation has an optional `cleanup` attribute that can be set to either +`NEVER`, `ON_SUCCESS`, or `ALWAYS`. If the cleanup mode is set to `NEVER`, temporary +directories are not deleted after a test completes. If it is set to `ON_SUCCESS`, +temporary directories are deleted only after a test completed successfully. + +The default cleanup mode is `ALWAYS`. You can use the +`junit.jupiter.tempdir.cleanup.mode.default` +<> to override this default. + +[source,java,indent=0] +.A test class with a temporary directory that doesn't get cleaned up +---- +include::{testDir}/example/TempDirCleanupModeDemo.java[tags=user_guide] +---- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html new file mode 100644 index 00000000..2e79b9d5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html @@ -0,0 +1,26 @@ + + +

This document consists of three sections:

+ +
+
Platform
+
The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. + It also defines the TestEngine API for developing a testing framework that runs on the + platform. Furthermore, the platform provides a Console Launcher to launch the platform + from the command line and a JUnit 4 based Runner for running any TestEngine on the + platform in a JUnit 4 based environment. +
+
Jupiter
+
JUnit Jupiter is the combination of the new programming model and extension model for + writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine + for running Jupiter based tests on the platform. +
+
Vintage
+
JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the + platform. +
+
+ +

Already consulted the JUnit 5 User Guide?

+ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css new file mode 100644 index 00000000..19fe4f0c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css @@ -0,0 +1,155 @@ +/* + * CSS customizations for JUnit + */ + +@import url('https://fonts.googleapis.com/css?family=Fira+Mono:400,700|Open+Sans:300,300i,400,400i,700,700i'); + +body, .title { + color:#333333; +} + +body, div.block, .deprecationBlock, button { + font-family: 'Open Sans', Arial, Helvetica, sans-serif; +} + +.title { + font-weight: 300; + font-size: 32px; + margin-top: 0; +} + +a:link, a:visited { + text-decoration:none; + color:#dc524a; +} + +a[href]:hover, a[href]:focus { + text-decoration:none; + color:#b62b23; +} + +pre, code, tt, dt code, table tr td dt code { + font-family: "Fira Mono", monospace; +} + +.bar { + background-color:#25a162; +} + +.top-nav { + background-color:#25a162; +} + +.bottom-nav { + background-color:#25a162; +} + +.sub-nav { + background-color:#f5f5f5; +} + +.top-nav a:hover, .bottom-nav a:hover { + text-decoration:underline; + color:inherit; +} + +.nav-bar-cell1-rev { + background-color:#fff; + color:#dc524a; + border-radius: 6px; +} + +.index-nav { + background-color:#eee; +} + +body.class-declaration-page .summary h2, +body.class-declaration-page .details h2, +body.class-use-page h2, +body.module-declaration-page .block-list h2 { + font-style: italic; + padding:0; + margin:15px 0; +} +body.class-declaration-page .summary h3, +body.class-declaration-page .details h3, +body.class-declaration-page .summary .inherited-list h2, +div.details ul.block-list ul.block-list ul.block-list li.block-list h4, +ul.block-list ul.block-list ul.block-list li.block-list h3 { + background-color:#ddd; + border:1px solid #ddd; +} + +.constants-summary caption a:link, .constants-summary caption a:visited, +.use-summary caption a:link, .use-summary caption a:visited { + color:#fff; +} + +.overview-summary caption span, .member-summary caption span, .type-summary caption span, +.use-summary caption span, .constants-summary caption span, .deprecated-summary caption span, +.requires-summary caption span, .packages-summary caption span, .provides-summary caption span, +.uses-summary caption span, +.member-summary caption span.active-table-tab span, .packages-summary caption span.active-table-tab span, +.overview-summary caption span.active-table-tab span, .type-summary caption span.active-table-tab span, +div.table-tabs > button.active-table-tab +{ + background-color:#dc524a; + color: #fff; +} + +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + /* Overrides the color of selection used in jQuery UI */ + background: #dc524a !important; + color: #fff !important; +} + +main a[href*="://"]::after, +main a[href*="://"]:hover::after, +main a[href*="://"]:focus::after { + background-image:url('data:image/svg+xml; utf8, \ + \ + \ + '); +} + +.member-summary caption span.table-tab span, .packages-summary caption span.table-tab span, +.overview-summary caption span.table-tab span, .type-summary caption span.table-tab span, +.ui-autocomplete-category, +div.table-tabs > button.table-tab { + background-color:#aaa; + color: #fff; +} + +th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .constants-summary th, +.packages-summary th { + background:#eee; +} + +.table-sub-heading-color { + background-color:#eee; +} + +.alt-color, .alt-color th { + background-color:#fff; +} + +.row-color, .row-color th { + background-color:#eee; +} + +.block { + margin:0 10px 5px 0; +} + +th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .constants-summary th, +.packages-summary th, .overview-summary td, .member-summary td, .type-summary td, +.use-summary td, .constants-summary td, .deprecated-summary td, +.requires-summary td, .packages-summary td, .provides-summary td, .uses-summary td { + padding-left:7px; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java new file mode 100644 index 00000000..b628febd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.domain; + +import java.time.LocalDate; + +public class Person { + + public enum Gender { + F, M + } + + private String firstName; + private String lastName; + private Gender gender; + private LocalDate dateOfBirth; + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public Person(String firstName, String lastName, Gender gender, LocalDate dateOfBirth) { + this(firstName, lastName); + this.gender = gender; + this.dateOfBirth = dateOfBirth; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public Gender getGender() { + return gender; + } + + public LocalDate getDateOfBirth() { + return dateOfBirth; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); + result = prime * result + ((lastName == null) ? 0 : lastName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Person other = (Person) obj; + if (firstName == null) { + if (other.firstName != null) { + return false; + } + } + else if (!firstName.equals(other.firstName)) { + return false; + } + if (lastName == null) { + if (other.lastName != null) { + return false; + } + } + else if (!lastName.equals(other.lastName)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Person [firstName=" + firstName + ", lastName=" + lastName + "]"; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java new file mode 100644 index 00000000..ace33c48 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java @@ -0,0 +1,5 @@ +/** + * Demo domain model. + */ + +package example.domain; diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java new file mode 100644 index 00000000..b907c2c5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.registration; + +public class WebClient { + + public WebResponse get(String string) { + return new WebResponse(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java new file mode 100644 index 00000000..598239f4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.registration; + +public class WebResponse { + + public int getResponseStatus() { + return 200; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java new file mode 100644 index 00000000..80fefe78 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.registration; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class WebServerExtension implements BeforeAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + /* no-op for demo */ + } + + public String getServerUrl() { + return "https://example.org:8181"; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + public Builder enableSecurity(boolean b) { + return this; + } + + public WebServerExtension build() { + return new WebServerExtension(); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java new file mode 100644 index 00000000..2f71c663 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java @@ -0,0 +1,5 @@ +/** + * Demo code for a WebServer extension. + */ + +package example.registration; diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java new file mode 100644 index 00000000..98291f6a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.util; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + + public int subtract(int a, int b) { + return a - b; + } + + public int multiply(int a, int b) { + return a * b; + } + + public int divide(int a, int b) { + return a / b; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java new file mode 100644 index 00000000..88fb7313 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.util; + +import static java.util.Collections.singletonList; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ListWriter { + + private final Path file; + + public ListWriter(Path file) { + this.file = file; + } + + public void write(String... items) throws IOException { + Files.write(file, singletonList(String.join(",", items))); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java new file mode 100644 index 00000000..b622aa3e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.util; + +public class StringUtils { + + public static boolean isPalindrome(String candidate) { + int length = candidate.length(); + for (int i = 0; i < length / 2; i++) { + if (candidate.charAt(i) != candidate.charAt(length - (i + 1))) { + return false; + } + } + return true; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java new file mode 100644 index 00000000..d31921f2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java @@ -0,0 +1,5 @@ +/** + * Demo utilities. + */ + +package example.util; diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java new file mode 100644 index 00000000..a9cd383d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java @@ -0,0 +1,150 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// @formatter:off +// tag::user_guide[] +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofMinutes; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeout; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; + +import example.domain.Person; +import example.util.Calculator; + +import org.junit.jupiter.api.Test; + +class AssertionsDemo { + + private final Calculator calculator = new Calculator(); + + private final Person person = new Person("Jane", "Doe"); + + @Test + void standardAssertions() { + assertEquals(2, calculator.add(1, 1)); + assertEquals(4, calculator.multiply(2, 2), + "The optional failure message is now the last parameter"); + assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- " + + "to avoid constructing complex messages unnecessarily."); + } + + @Test + void groupedAssertions() { + // In a grouped assertion all assertions are executed, and all + // failures will be reported together. + assertAll("person", + () -> assertEquals("Jane", person.getFirstName()), + () -> assertEquals("Doe", person.getLastName()) + ); + } + + @Test + void dependentAssertions() { + // Within a code block, if an assertion fails the + // subsequent code in the same block will be skipped. + assertAll("properties", + () -> { + String firstName = person.getFirstName(); + assertNotNull(firstName); + + // Executed only if the previous assertion is valid. + assertAll("first name", + () -> assertTrue(firstName.startsWith("J")), + () -> assertTrue(firstName.endsWith("e")) + ); + }, + () -> { + // Grouped assertion, so processed independently + // of results of first name assertions. + String lastName = person.getLastName(); + assertNotNull(lastName); + + // Executed only if the previous assertion is valid. + assertAll("last name", + () -> assertTrue(lastName.startsWith("D")), + () -> assertTrue(lastName.endsWith("e")) + ); + } + ); + } + + @Test + void exceptionTesting() { + Exception exception = assertThrows(ArithmeticException.class, () -> + calculator.divide(1, 0)); + assertEquals("/ by zero", exception.getMessage()); + } + + @Test + void timeoutNotExceeded() { + // The following assertion succeeds. + assertTimeout(ofMinutes(2), () -> { + // Perform task that takes less than 2 minutes. + }); + } + + @Test + void timeoutNotExceededWithResult() { + // The following assertion succeeds, and returns the supplied object. + String actualResult = assertTimeout(ofMinutes(2), () -> { + return "a result"; + }); + assertEquals("a result", actualResult); + } + + @Test + void timeoutNotExceededWithMethod() { + // The following assertion invokes a method reference and returns an object. + String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting); + assertEquals("Hello, World!", actualGreeting); + } + + // end::user_guide[] + @extensions.ExpectToFail + // tag::user_guide[] + @Test + void timeoutExceeded() { + // The following assertion fails with an error message similar to: + // execution exceeded timeout of 10 ms by 91 ms + assertTimeout(ofMillis(10), () -> { + // Simulate task that takes more than 10 ms. + Thread.sleep(100); + }); + } + + // end::user_guide[] + @extensions.ExpectToFail + // tag::user_guide[] + @Test + void timeoutExceededWithPreemptiveTermination() { + // The following assertion fails with an error message similar to: + // execution timed out after 10 ms + assertTimeoutPreemptively(ofMillis(10), () -> { + // Simulate task that takes more than 10 ms. + new CountDownLatch(1).await(); + }); + } + + private static String greeting() { + return "Hello, World!"; + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java new file mode 100644 index 00000000..41438fea --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// @formatter:off +// tag::user_guide[] + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.Assumptions.assumingThat; + +import example.util.Calculator; + +import org.junit.jupiter.api.Test; + +class AssumptionsDemo { + + private final Calculator calculator = new Calculator(); + + @Test + void testOnlyOnCiServer() { + assumeTrue("CI".equals(System.getenv("ENV"))); + // remainder of test + } + + @Test + void testOnlyOnDeveloperWorkstation() { + assumeTrue("DEV".equals(System.getenv("ENV")), + () -> "Aborting test: not on developer workstation"); + // remainder of test + } + + @Test + void testInAllEnvironments() { + assumingThat("CI".equals(System.getenv("ENV")), + () -> { + // perform these assertions only on the CI server + assertEquals(2, calculator.divide(4, 2)); + }); + + // perform these assertions in all environments + assertEquals(42, calculator.multiply(6, 7)); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java new file mode 100644 index 00000000..146443c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java @@ -0,0 +1,218 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static org.junit.jupiter.api.condition.JRE.JAVA_10; +import static org.junit.jupiter.api.condition.JRE.JAVA_11; +import static org.junit.jupiter.api.condition.JRE.JAVA_8; +import static org.junit.jupiter.api.condition.JRE.JAVA_9; +import static org.junit.jupiter.api.condition.OS.LINUX; +import static org.junit.jupiter.api.condition.OS.MAC; +import static org.junit.jupiter.api.condition.OS.WINDOWS; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledInNativeImage; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.EnabledOnOs; + +class ConditionalTestExecutionDemo { + + // tag::user_guide_os[] + @Test + @EnabledOnOs(MAC) + void onlyOnMacOs() { + // ... + } + + @TestOnMac + void testOnMac() { + // ... + } + + @Test + @EnabledOnOs({ LINUX, MAC }) + void onLinuxOrMac() { + // ... + } + + @Test + @DisabledOnOs(WINDOWS) + void notOnWindows() { + // ... + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @Test + @EnabledOnOs(MAC) + @interface TestOnMac { + } + // end::user_guide_os[] + + // tag::user_guide_architecture[] + @Test + @EnabledOnOs(architectures = "aarch64") + void onAarch64() { + // ... + } + + @Test + @DisabledOnOs(architectures = "x86_64") + void notOnX86_64() { + // ... + } + + @Test + @EnabledOnOs(value = MAC, architectures = "aarch64") + void onNewMacs() { + // ... + } + + @Test + @DisabledOnOs(value = MAC, architectures = "aarch64") + void notOnNewMacs() { + // ... + } + // end::user_guide_architecture[] + + // tag::user_guide_jre[] + @Test + @EnabledOnJre(JAVA_8) + void onlyOnJava8() { + // ... + } + + @Test + @EnabledOnJre({ JAVA_9, JAVA_10 }) + void onJava9Or10() { + // ... + } + + @Test + @EnabledForJreRange(min = JAVA_9, max = JAVA_11) + void fromJava9to11() { + // ... + } + + @Test + @EnabledForJreRange(min = JAVA_9) + void fromJava9toCurrentJavaFeatureNumber() { + // ... + } + + @Test + @EnabledForJreRange(max = JAVA_11) + void fromJava8To11() { + // ... + } + + @Test + @DisabledOnJre(JAVA_9) + void notOnJava9() { + // ... + } + + @Test + @DisabledForJreRange(min = JAVA_9, max = JAVA_11) + void notFromJava9to11() { + // ... + } + + @Test + @DisabledForJreRange(min = JAVA_9) + void notFromJava9toCurrentJavaFeatureNumber() { + // ... + } + + @Test + @DisabledForJreRange(max = JAVA_11) + void notFromJava8to11() { + // ... + } + // end::user_guide_jre[] + + // tag::user_guide_native[] + @Test + @EnabledInNativeImage + void onlyWithinNativeImage() { + // ... + } + + @Test + @DisabledInNativeImage + void neverWithinNativeImage() { + // ... + } + // end::user_guide_native[] + + // tag::user_guide_system_property[] + @Test + @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") + void onlyOn64BitArchitectures() { + // ... + } + + @Test + @DisabledIfSystemProperty(named = "ci-server", matches = "true") + void notOnCiServer() { + // ... + } + // end::user_guide_system_property[] + + // tag::user_guide_environment_variable[] + @Test + @EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server") + void onlyOnStagingServer() { + // ... + } + + @Test + @DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*") + void notOnDeveloperWorkstation() { + // ... + } + // end::user_guide_environment_variable[] + + // tag::user_guide_custom[] + @Test + @EnabledIf("customCondition") + void enabled() { + // ... + } + + @Test + @DisabledIf("customCondition") + void disabled() { + // ... + } + + boolean customCondition() { + return true; + } + // end::user_guide_custom[] + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java new file mode 100644 index 00000000..a1d0eb31 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; + +/** + * This is a no-op {@link TestEngine} that is only + * used to make examples compile. + */ +class CustomTestEngine implements TestEngine { + + @Override + public String getId() { + return "custom-test-engine"; + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + return new EngineDescriptor(UniqueId.forEngine(getId()), "Custom Test Engine"); + } + + @Override + public void execute(ExecutionRequest request) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java new file mode 100644 index 00000000..a2453a2a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled("Disabled until bug #99 has been fixed") +class DisabledClassDemo { + + @Test + void testWillBeSkipped() { + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java new file mode 100644 index 00000000..e1a7f6c0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class DisabledTestsDemo { + + @Disabled("Disabled until bug #42 has been resolved") + @Test + void testWillBeSkipped() { + } + + @Test + void testWillBeExecuted() { + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java new file mode 100644 index 00000000..c9ee6fed --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("A special test case") +class DisplayNameDemo { + + @Test + @DisplayName("Custom test name containing spaces") + void testWithDisplayNameContainingSpaces() { + } + + @Test + @DisplayName("╯°□°)╯") + void testWithDisplayNameContainingSpecialCharacters() { + } + + @Test + @DisplayName("😱") + void testWithDisplayNameContainingEmoji() { + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java new file mode 100644 index 00000000..f1483d15 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.IndicativeSentencesGeneration; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DisplayNameGeneratorDemo { + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class A_year_is_not_supported { + + @Test + void if_it_is_zero() { + } + + @DisplayName("A negative value for year is not supported by the leap year computation.") + @ParameterizedTest(name = "For example, year {0} is not supported.") + @ValueSource(ints = { -1, -4 }) + void if_it_is_negative(int year) { + } + + } + + @Nested + @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) + class A_year_is_a_leap_year { + + @Test + void if_it_is_divisible_by_4_but_not_by_100() { + } + + @ParameterizedTest(name = "Year {0} is a leap year.") + @ValueSource(ints = { 2016, 2020, 2048 }) + void if_it_is_one_of_the_following_years(int year) { + } + + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java new file mode 100644 index 00000000..b3bc6dd0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import org.junit.platform.suite.api.ExcludeTags; +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @since 5.0 + */ +@Suite +@SelectPackages("example") +@IncludeClassNamePatterns(".+(Tests|Demo)$") +@ExcludeTags("exclude") +class DocumentationTestSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java new file mode 100644 index 00000000..8a8d9bef --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java @@ -0,0 +1,197 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] + +import static example.util.StringUtils.isPalindrome; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.Named.named; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import example.util.Calculator; + +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.function.ThrowingConsumer; + +// end::user_guide[] +// @formatter:off +// tag::user_guide[] +class DynamicTestsDemo { + + private final Calculator calculator = new Calculator(); + + // end::user_guide[] + @Tag("exclude") + // tag::user_guide[] + // This will result in a JUnitException! + @TestFactory + List dynamicTestsWithInvalidReturnType() { + return Arrays.asList("Hello"); + } + + @TestFactory + Collection dynamicTestsFromCollection() { + return Arrays.asList( + dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))), + dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) + ); + } + + @TestFactory + Iterable dynamicTestsFromIterable() { + return Arrays.asList( + dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))), + dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) + ); + } + + @TestFactory + Iterator dynamicTestsFromIterator() { + return Arrays.asList( + dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))), + dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) + ).iterator(); + } + + @TestFactory + DynamicTest[] dynamicTestsFromArray() { + return new DynamicTest[] { + dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))), + dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) + }; + } + + @TestFactory + Stream dynamicTestsFromStream() { + return Stream.of("racecar", "radar", "mom", "dad") + .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))); + } + + @TestFactory + Stream dynamicTestsFromIntStream() { + // Generates tests for the first 10 even integers. + return IntStream.iterate(0, n -> n + 2).limit(10) + .mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0))); + } + + @TestFactory + Stream generateRandomNumberOfTestsFromIterator() { + + // Generates random positive integers between 0 and 100 until + // a number evenly divisible by 7 is encountered. + Iterator inputGenerator = new Iterator() { + + Random random = new Random(); + // end::user_guide[] + { + // Use fixed seed to always produce the same number of tests for execution on the CI server + random = new Random(23); + } + // tag::user_guide[] + int current; + + @Override + public boolean hasNext() { + current = random.nextInt(100); + return current % 7 != 0; + } + + @Override + public Integer next() { + return current; + } + }; + + // Generates display names like: input:5, input:37, input:85, etc. + Function displayNameGenerator = (input) -> "input:" + input; + + // Executes tests based on the current input value. + ThrowingConsumer testExecutor = (input) -> assertTrue(input % 7 != 0); + + // Returns a stream of dynamic tests. + return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor); + } + + @TestFactory + Stream dynamicTestsFromStreamFactoryMethod() { + // Stream of palindromes to check + Stream inputStream = Stream.of("racecar", "radar", "mom", "dad"); + + // Generates display names like: racecar is a palindrome + Function displayNameGenerator = text -> text + " is a palindrome"; + + // Executes tests based on the current input value. + ThrowingConsumer testExecutor = text -> assertTrue(isPalindrome(text)); + + // Returns a stream of dynamic tests. + return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor); + } + + @TestFactory + Stream dynamicTestsFromStreamFactoryMethodWithNames() { + // Stream of palindromes to check + Stream> inputStream = Stream.of( + named("racecar is a palindrome", "racecar"), + named("radar is also a palindrome", "radar"), + named("mom also seems to be a palindrome", "mom"), + named("dad is yet another palindrome", "dad") + ); + + // Returns a stream of dynamic tests. + return DynamicTest.stream(inputStream, + text -> assertTrue(isPalindrome(text))); + } + + @TestFactory + Stream dynamicTestsWithContainers() { + return Stream.of("A", "B", "C") + .map(input -> dynamicContainer("Container " + input, Stream.of( + dynamicTest("not null", () -> assertNotNull(input)), + dynamicContainer("properties", Stream.of( + dynamicTest("length > 0", () -> assertTrue(input.length() > 0)), + dynamicTest("not empty", () -> assertFalse(input.isEmpty())) + )) + ))); + } + + @TestFactory + DynamicNode dynamicNodeSingleTest() { + return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop"))); + } + + @TestFactory + DynamicNode dynamicNodeSingleContainer() { + return dynamicContainer("palindromes", + Stream.of("racecar", "radar", "mom", "dad") + .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))) + )); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java new file mode 100644 index 00000000..2a898b33 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import example.util.Calculator; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +@TestMethodOrder(OrderAnnotation.class) +public class ExampleTestCase { + + private final Calculator calculator = new Calculator(); + + @Test + @Disabled("for demonstration purposes") + @Order(1) + void skippedTest() { + // skipped ... + } + + @Test + @Order(2) + void succeedingTest() { + assertEquals(42, calculator.multiply(6, 7)); + } + + @Test + @Order(3) + void abortedTest() { + assumeTrue("abc".contains("Z"), "abc does not contain Z"); + // aborted ... + } + + @Test + @Order(4) + void failingTest() { + // The following throws an ArithmeticException: "/ by zero" + calculator.divide(1, 0); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java new file mode 100644 index 00000000..024b07d2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide_external_custom_condition[] +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +class ExternalCustomConditionDemo { + + @Test + @EnabledIf("example.ExternalCondition#customCondition") + void enabled() { + // ... + } + +} + +class ExternalCondition { + + static boolean customCondition() { + return true; + } + +} +// end::user_guide_external_custom_condition[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java new file mode 100644 index 00000000..1d829936 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::external_MethodSource_example[] +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class ExternalMethodSourceDemo { + + @ParameterizedTest + @MethodSource("example.StringsProviders#tinyStrings") + void testWithExternalMethodSource(String tinyString) { + // test with tiny string + } +} + +class StringsProviders { + + static Stream tinyStrings() { + return Stream.of(".", "oo", "OOO"); + } +} +// end::external_MethodSource_example[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java new file mode 100644 index 00000000..1e7c78e6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Tag("fast") +public @interface Fast { +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java new file mode 100644 index 00000000..af31b49d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Tag("fast") +@Test +public @interface FastTest { +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java new file mode 100644 index 00000000..8fa21982 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import example.util.Calculator; + +import org.junit.jupiter.api.Test; + +class HamcrestAssertionsDemo { + + private final Calculator calculator = new Calculator(); + + @Test + void assertWithHamcrestMatcher() { + assertThat(calculator.subtract(4, 1), is(equalTo(3))); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java new file mode 100644 index 00000000..96fca01c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.Ignore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport; + +// @ExtendWith(IgnoreCondition.class) +@EnableJUnit4MigrationSupport +class IgnoredTestsDemo { + + @Ignore + @Test + void testWillBeIgnored() { + } + + @Test + void testWillBeExecuted() { + } +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java new file mode 100644 index 00000000..d229e639 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import org.junit.Test; + +public class JUnit4Tests { + + @Test + public void standardJUnit4Test() { + // perform assertions + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java new file mode 100644 index 00000000..0bf0aeb0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; + +//end::user_guide[] +@SuppressWarnings("deprecation") +//tag::user_guide[] +@RunWith(org.junit.platform.runner.JUnitPlatform.class) +public class JUnitPlatformClassDemo { + + @Test + void succeedingTest() { + /* no-op */ + } + + // end::user_guide[] + @extensions.ExpectToFail + // tag::user_guide[] + @Test + void failingTest() { + fail("Failing for failing's sake."); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java new file mode 100644 index 00000000..b563204f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +//tag::user_guide[] +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.SuiteDisplayName; +import org.junit.runner.RunWith; + +@RunWith(org.junit.platform.runner.JUnitPlatform.class) +@SuiteDisplayName("JUnit Platform Suite Demo") +@SelectPackages("example") +//end::user_guide[] +@SuppressWarnings("deprecation") +@org.junit.platform.suite.api.ExcludeTags("exclude") +//tag::user_guide[] +public class JUnitPlatformSuiteDemo { +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java new file mode 100644 index 00000000..45b35dbc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MethodSourceParameterResolutionDemo { + + // @formatter:off + // tag::parameter_resolution_MethodSource_example[] + @RegisterExtension + static final IntegerResolver integerResolver = new IntegerResolver(); + + @ParameterizedTest + @MethodSource("factoryMethodWithArguments") + void testWithFactoryMethodWithArguments(String argument) { + assertTrue(argument.startsWith("2")); + } + + static Stream factoryMethodWithArguments(int quantity) { + return Stream.of( + arguments(quantity + " apples"), + arguments(quantity + " lemons") + ); + } + + static class IntegerResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + + return parameterContext.getParameter().getType() == int.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + + return 2; + } + + } + // end::parameter_resolution_MethodSource_example[] + // @formatter:on + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java new file mode 100644 index 00000000..77632abc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; + +import example.util.Calculator; + +import org.junit.jupiter.api.Test; + +class MyFirstJUnitJupiterTests { + + private final Calculator calculator = new Calculator(); + + @Test + void addition() { + assertEquals(2, calculator.add(1, 1)); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java new file mode 100644 index 00000000..b5e987da --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; + +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +class OrderedNestedTestClassesDemo { + + @Nested + @Order(1) + class PrimaryTests { + + @Test + void test1() { + } + } + + @Nested + @Order(2) + class SecondaryTests { + + @Test + void test2() { + } + } +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java new file mode 100644 index 00000000..2ee01d33 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +@TestMethodOrder(OrderAnnotation.class) +class OrderedTestsDemo { + + @Test + @Order(1) + void nullValues() { + // perform assertions against null values + } + + @Test + @Order(2) + void emptyValues() { + // perform assertions against empty values + } + + @Test + @Order(3) + void validValues() { + // perform assertions against valid values + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java new file mode 100644 index 00000000..3afd8872 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -0,0 +1,470 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Named.named; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; +import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; + +import java.io.File; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import example.domain.Person; +import example.domain.Person.Gender; +import example.util.StringUtils; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.aggregator.AggregateWith; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.aggregator.ArgumentsAggregator; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.converter.JavaTimeConversionPattern; +import org.junit.jupiter.params.converter.SimpleArgumentConverter; +import org.junit.jupiter.params.converter.TypedArgumentConverter; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.CsvFileSource; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class ParameterizedTestDemo { + + // tag::first_example[] + @ParameterizedTest + @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) + void palindromes(String candidate) { + assertTrue(StringUtils.isPalindrome(candidate)); + } + // end::first_example[] + + // tag::ValueSource_example[] + @ParameterizedTest + @ValueSource(ints = { 1, 2, 3 }) + void testWithValueSource(int argument) { + assertTrue(argument > 0 && argument < 4); + } + // end::ValueSource_example[] + + @Nested + class NullAndEmptySource_1 { + + // tag::NullAndEmptySource_example1[] + @ParameterizedTest + @NullSource + @EmptySource + @ValueSource(strings = { " ", " ", "\t", "\n" }) + void nullEmptyAndBlankStrings(String text) { + assertTrue(text == null || text.trim().isEmpty()); + } + // end::NullAndEmptySource_example1[] + } + + @Nested + class NullAndEmptySource_2 { + + // tag::NullAndEmptySource_example2[] + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = { " ", " ", "\t", "\n" }) + void nullEmptyAndBlankStrings(String text) { + assertTrue(text == null || text.trim().isEmpty()); + } + // end::NullAndEmptySource_example2[] + } + + // tag::EnumSource_example[] + @ParameterizedTest + @EnumSource(ChronoUnit.class) + void testWithEnumSource(TemporalUnit unit) { + assertNotNull(unit); + } + // end::EnumSource_example[] + + // tag::EnumSource_example_autodetection[] + @ParameterizedTest + @EnumSource + void testWithEnumSourceWithAutoDetection(ChronoUnit unit) { + assertNotNull(unit); + } + // end::EnumSource_example_autodetection[] + + // tag::EnumSource_include_example[] + @ParameterizedTest + @EnumSource(names = { "DAYS", "HOURS" }) + void testWithEnumSourceInclude(ChronoUnit unit) { + assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit)); + } + // end::EnumSource_include_example[] + + // tag::EnumSource_exclude_example[] + @ParameterizedTest + @EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" }) + void testWithEnumSourceExclude(ChronoUnit unit) { + assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit)); + } + // end::EnumSource_exclude_example[] + + // tag::EnumSource_regex_example[] + @ParameterizedTest + @EnumSource(mode = MATCH_ALL, names = "^.*DAYS$") + void testWithEnumSourceRegex(ChronoUnit unit) { + assertTrue(unit.name().endsWith("DAYS")); + } + // end::EnumSource_regex_example[] + + // tag::simple_MethodSource_example[] + @ParameterizedTest + @MethodSource("stringProvider") + void testWithExplicitLocalMethodSource(String argument) { + assertNotNull(argument); + } + + static Stream stringProvider() { + return Stream.of("apple", "banana"); + } + // end::simple_MethodSource_example[] + + // tag::simple_MethodSource_without_value_example[] + @ParameterizedTest + @MethodSource + void testWithDefaultLocalMethodSource(String argument) { + assertNotNull(argument); + } + + static Stream testWithDefaultLocalMethodSource() { + return Stream.of("apple", "banana"); + } + // end::simple_MethodSource_without_value_example[] + + // tag::primitive_MethodSource_example[] + @ParameterizedTest + @MethodSource("range") + void testWithRangeMethodSource(int argument) { + assertNotEquals(9, argument); + } + + static IntStream range() { + return IntStream.range(0, 20).skip(10); + } + // end::primitive_MethodSource_example[] + + // @formatter:off + // tag::multi_arg_MethodSource_example[] + @ParameterizedTest + @MethodSource("stringIntAndListProvider") + void testWithMultiArgMethodSource(String str, int num, List list) { + assertEquals(5, str.length()); + assertTrue(num >=1 && num <=2); + assertEquals(2, list.size()); + } + + static Stream stringIntAndListProvider() { + return Stream.of( + arguments("apple", 1, Arrays.asList("a", "b")), + arguments("lemon", 2, Arrays.asList("x", "y")) + ); + } + // end::multi_arg_MethodSource_example[] + // @formatter:on + + // @formatter:off + // tag::CsvSource_example[] + @ParameterizedTest + @CsvSource({ + "apple, 1", + "banana, 2", + "'lemon, lime', 0xF1", + "strawberry, 700_000" + }) + void testWithCsvSource(String fruit, int rank) { + assertNotNull(fruit); + assertNotEquals(0, rank); + } + // end::CsvSource_example[] + // @formatter:on + + // tag::CsvFileSource_example[] + @ParameterizedTest + @CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1) + void testWithCsvFileSourceFromClasspath(String country, int reference) { + assertNotNull(country); + assertNotEquals(0, reference); + } + + @ParameterizedTest + @CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1) + void testWithCsvFileSourceFromFile(String country, int reference) { + assertNotNull(country); + assertNotEquals(0, reference); + } + + @ParameterizedTest(name = "[{index}] {arguments}") + @CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true) + void testWithCsvFileSourceAndHeaders(String country, int reference) { + assertNotNull(country); + assertNotEquals(0, reference); + } + // end::CsvFileSource_example[] + + // tag::ArgumentsSource_example[] + @ParameterizedTest + @ArgumentsSource(MyArgumentsProvider.class) + void testWithArgumentsSource(String argument) { + assertNotNull(argument); + } + + // end::ArgumentsSource_example[] + static + // tag::ArgumentsProvider_example[] + public class MyArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of("apple", "banana").map(Arguments::of); + } + } + // end::ArgumentsProvider_example[] + + // tag::ParameterResolver_example[] + @BeforeEach + void beforeEach(TestInfo testInfo) { + // ... + } + + @ParameterizedTest + @ValueSource(strings = "apple") + void testWithRegularParameterResolver(String argument, TestReporter testReporter) { + testReporter.publishEntry("argument", argument); + } + + @AfterEach + void afterEach(TestInfo testInfo) { + // ... + } + // end::ParameterResolver_example[] + + // tag::implicit_conversion_example[] + @ParameterizedTest + @ValueSource(strings = "SECONDS") + void testWithImplicitArgumentConversion(ChronoUnit argument) { + assertNotNull(argument.name()); + } + // end::implicit_conversion_example[] + + // tag::implicit_fallback_conversion_example[] + @ParameterizedTest + @ValueSource(strings = "42 Cats") + void testWithImplicitFallbackArgumentConversion(Book book) { + assertEquals("42 Cats", book.getTitle()); + } + + // end::implicit_fallback_conversion_example[] + static + // tag::implicit_fallback_conversion_example_Book[] + public class Book { + + private final String title; + + private Book(String title) { + this.title = title; + } + + public static Book fromTitle(String title) { + return new Book(title); + } + + public String getTitle() { + return this.title; + } + } + // end::implicit_fallback_conversion_example_Book[] + + // @formatter:off + // tag::explicit_conversion_example[] + @ParameterizedTest + @EnumSource(ChronoUnit.class) + void testWithExplicitArgumentConversion( + @ConvertWith(ToStringArgumentConverter.class) String argument) { + + assertNotNull(ChronoUnit.valueOf(argument)); + } + + // end::explicit_conversion_example[] + static + // tag::explicit_conversion_example_ToStringArgumentConverter[] + public class ToStringArgumentConverter extends SimpleArgumentConverter { + + @Override + protected Object convert(Object source, Class targetType) { + assertEquals(String.class, targetType, "Can only convert to String"); + if (source instanceof Enum) { + return ((Enum) source).name(); + } + return String.valueOf(source); + } + } + // end::explicit_conversion_example_ToStringArgumentConverter[] + + static + // tag::explicit_conversion_example_TypedArgumentConverter[] + public class ToLengthArgumentConverter extends TypedArgumentConverter { + + protected ToLengthArgumentConverter() { + super(String.class, Integer.class); + } + + @Override + protected Integer convert(String source) { + return (source != null ? source.length() : 0); + } + + } + // end::explicit_conversion_example_TypedArgumentConverter[] + + // tag::explicit_java_time_converter[] + @ParameterizedTest + @ValueSource(strings = { "01.01.2017", "31.12.2017" }) + void testWithExplicitJavaTimeConverter( + @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) { + + assertEquals(2017, argument.getYear()); + } + // end::explicit_java_time_converter[] + // @formatter:on + + // @formatter:off + // tag::ArgumentsAccessor_example[] + @ParameterizedTest + @CsvSource({ + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + }) + void testWithArgumentsAccessor(ArgumentsAccessor arguments) { + Person person = new Person(arguments.getString(0), + arguments.getString(1), + arguments.get(2, Gender.class), + arguments.get(3, LocalDate.class)); + + if (person.getFirstName().equals("Jane")) { + assertEquals(Gender.F, person.getGender()); + } + else { + assertEquals(Gender.M, person.getGender()); + } + assertEquals("Doe", person.getLastName()); + assertEquals(1990, person.getDateOfBirth().getYear()); + } + // end::ArgumentsAccessor_example[] + // @formatter:on + + // @formatter:off + // tag::ArgumentsAggregator_example[] + @ParameterizedTest + @CsvSource({ + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + }) + void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) { + // perform assertions against person + } + + // end::ArgumentsAggregator_example[] + static + // tag::ArgumentsAggregator_example_PersonAggregator[] + public class PersonAggregator implements ArgumentsAggregator { + @Override + public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { + return new Person(arguments.getString(0), + arguments.getString(1), + arguments.get(2, Gender.class), + arguments.get(3, LocalDate.class)); + } + } + // end::ArgumentsAggregator_example_PersonAggregator[] + // @formatter:on + + // @formatter:off + // tag::ArgumentsAggregator_with_custom_annotation_example[] + @ParameterizedTest + @CsvSource({ + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + }) + void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { + // perform assertions against person + } + // end::ArgumentsAggregator_with_custom_annotation_example[] + + // tag::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @AggregateWith(PersonAggregator.class) + public @interface CsvToPerson { + } + // end::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] + // @formatter:on + + // tag::custom_display_names[] + @DisplayName("Display name of container") + @ParameterizedTest(name = "{index} ==> the rank of ''{0}'' is {1}") + @CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" }) + void testWithCustomDisplayNames(String fruit, int rank) { + } + // end::custom_display_names[] + + // @formatter:off + // tag::named_arguments[] + @DisplayName("A parameterized test with named arguments") + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("namedArguments") + void testWithNamedArguments(File file) { + } + + static Stream namedArguments() { + return Stream.of( + arguments(named("An important file", new File("path1"))), + arguments(named("Another file", new File("path2"))) + ); + } + // end::named_arguments[] + // @formatter:on +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java new file mode 100644 index 00000000..a10b7cec --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +class PollingTimeoutDemo { + + // tag::user_guide[] + @Test + @Timeout(5) // Poll at most 5 seconds + void pollUntil() throws InterruptedException { + while (asynchronousResultNotAvailable()) { + Thread.sleep(250); // custom poll interval + } + // Obtain the asynchronous result and perform assertions + } + // end::user_guide[] + + private boolean asynchronousResultNotAvailable() { + return false; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java new file mode 100644 index 00000000..ccf46845 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.RepetitionInfo; +import org.junit.jupiter.api.TestInfo; + +class RepeatedTestsDemo { + + private Logger logger = // ... + // end::user_guide[] + Logger.getLogger(RepeatedTestsDemo.class.getName()); + // tag::user_guide[] + + @BeforeEach + void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { + int currentRepetition = repetitionInfo.getCurrentRepetition(); + int totalRepetitions = repetitionInfo.getTotalRepetitions(); + String methodName = testInfo.getTestMethod().get().getName(); + logger.info(String.format("About to execute repetition %d of %d for %s", // + currentRepetition, totalRepetitions, methodName)); + } + + @RepeatedTest(10) + void repeatedTest() { + // ... + } + + @RepeatedTest(5) + void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) { + assertEquals(5, repetitionInfo.getTotalRepetitions()); + } + + @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") + @DisplayName("Repeat!") + void customDisplayName(TestInfo testInfo) { + assertEquals("Repeat! 1/1", testInfo.getDisplayName()); + } + + @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) + @DisplayName("Details...") + void customDisplayNameWithLongPattern(TestInfo testInfo) { + assertEquals("Details... :: repetition 1 of 1", testInfo.getDisplayName()); + } + + @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}") + void repeatedTestInGerman() { + // ... + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java new file mode 100644 index 00000000..be3d12b3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; +import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; + +import java.util.Properties; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ResourceLock; + +// tag::user_guide[] +@Execution(CONCURRENT) +class SharedResourcesDemo { + + private Properties backup; + + @BeforeEach + void backup() { + backup = new Properties(); + backup.putAll(System.getProperties()); + } + + @AfterEach + void restore() { + System.setProperties(backup); + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ) + void customPropertyIsNotSetByDefault() { + assertNull(System.getProperty("my.prop")); + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) + void canSetCustomPropertyToApple() { + System.setProperty("my.prop", "apple"); + assertEquals("apple", System.getProperty("my.prop")); + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) + void canSetCustomPropertyToBanana() { + System.setProperty("my.prop", "banana"); + assertEquals("banana", System.getProperty("my.prop")); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java new file mode 100644 index 00000000..f3c711e7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; + +@Disabled +class SlowTests { + + @Execution(SAME_THREAD) + @Test + void a() { + foo(); + } + + @Test + void b() { + foo(); + } + + @Test + void c() { + foo(); + } + + @Test + void d() { + foo(); + } + + @Test + void e() { + foo(); + } + + @Test + void f() { + foo(); + } + + @Test + void g() { + foo(); + } + + @Test + void h() { + foo(); + } + + @Test + void i() { + foo(); + } + + @Test + void j() { + foo(); + } + + @Test + void k() { + foo(); + } + + @Test + void l() { + foo(); + } + + @Test + void m() { + foo(); + } + + @Test + void n() { + foo(); + } + + @Test + void o() { + foo(); + } + + @Test + void p() { + foo(); + } + + @Execution(SAME_THREAD) + @Test + void q() { + foo(); + } + + @Test + void r() { + foo(); + } + + @Test + void s() { + foo(); + } + + private void foo() { + IntStream.range(1, 100_000_000).mapToDouble(i -> Math.pow(i, i)).map(Math::sqrt).max(); + } +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java new file mode 100644 index 00000000..4a6a660e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class StandardTests { + + @BeforeAll + static void initAll() { + } + + @BeforeEach + void init() { + } + + @Test + void succeedingTest() { + } + + // end::user_guide[] + @extensions.ExpectToFail + // tag::user_guide[] + @Test + void failingTest() { + fail("a failing test"); + } + + @Test + @Disabled("for demonstration purposes") + void skippedTest() { + // not executed + } + + @Test + void abortedTest() { + assumeTrue("abc".contains("Z")); + fail("test should have been aborted"); + } + + @AfterEach + void tearDown() { + } + + @AfterAll + static void tearDownAll() { + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java new file mode 100644 index 00000000..617806b3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +//tag::user_guide[] +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; + +@Suite +@SuiteDisplayName("JUnit Platform Suite Demo") +@SelectPackages("example") +@IncludeClassNamePatterns(".*Tests") +//end::user_guide[] +@org.junit.platform.suite.api.ExcludeTags("exclude") +//tag::user_guide[] +class SuiteDemo { +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java new file mode 100644 index 00000000..ebe9594b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("fast") +@Tag("model") +class TaggingDemo { + + @Test + @Tag("taxes") + void testingTaxCalculation() { + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java new file mode 100644 index 00000000..84c05b4b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +// tag::user_guide[] +class TempDirCleanupModeDemo { + + @Test + void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { + // perform test + } +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java new file mode 100644 index 00000000..70ce5822 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import example.util.ListWriter; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class TempDirectoryDemo { + + // tag::user_guide_parameter_injection[] + @Test + void writeItemsToFile(@TempDir Path tempDir) throws IOException { + Path file = tempDir.resolve("test.txt"); + + new ListWriter(file).write("a", "b", "c"); + + assertEquals(singletonList("a,b,c"), Files.readAllLines(file)); + } + // end::user_guide_parameter_injection[] + + // tag::user_guide_multiple_directories[] + @Test + void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException { + Path sourceFile = source.resolve("test.txt"); + new ListWriter(sourceFile).write("a", "b", "c"); + + Path targetFile = Files.copy(sourceFile, target.resolve("test.txt")); + + assertNotEquals(sourceFile, targetFile); + assertEquals(singletonList("a,b,c"), Files.readAllLines(targetFile)); + } + // end::user_guide_multiple_directories[] + + static + // tag::user_guide_field_injection[] + class SharedTempDirectoryDemo { + + @TempDir + static Path sharedTempDir; + + @Test + void writeItemsToFile() throws IOException { + Path file = sharedTempDir.resolve("test.txt"); + + new ListWriter(file).write("a", "b", "c"); + + assertEquals(singletonList("a,b,c"), Files.readAllLines(file)); + } + + @Test + void anotherTestThatUsesTheSameTempDir() { + // use sharedTempDir + } + + } + // end::user_guide_field_injection[] + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java new file mode 100644 index 00000000..ac8f044e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +@DisplayName("TestInfo Demo") +class TestInfoDemo { + + TestInfoDemo(TestInfo testInfo) { + assertEquals("TestInfo Demo", testInfo.getDisplayName()); + } + + @BeforeEach + void init(TestInfo testInfo) { + String displayName = testInfo.getDisplayName(); + assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); + } + + @Test + @DisplayName("TEST 1") + @Tag("my-tag") + void test1(TestInfo testInfo) { + assertEquals("TEST 1", testInfo.getDisplayName()); + assertTrue(testInfo.getTags().contains("my-tag")); + } + + @Test + void test2() { + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java new file mode 100644 index 00000000..dbd78d94 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +@Execution(ExecutionMode.SAME_THREAD) +// tag::user_guide[] +class TestReporterDemo { + + @Test + void reportSingleValue(TestReporter testReporter) { + testReporter.publishEntry("a status message"); + } + + @Test + void reportKeyValuePair(TestReporter testReporter) { + testReporter.publishEntry("a key", "a value"); + } + + @Test + void reportMultipleKeyValuePairs(TestReporter testReporter) { + Map values = new HashMap<>(); + values.put("user name", "dk38"); + values.put("award year", "1974"); + + testReporter.publishEntry(values); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java new file mode 100644 index 00000000..5f0c56a2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; + +class TestTemplateDemo { + + // tag::user_guide[] + final List fruits = Arrays.asList("apple", "banana", "lemon"); + + @TestTemplate + @ExtendWith(MyTestTemplateInvocationContextProvider.class) + void testTemplate(String fruit) { + assertTrue(fruits.contains(fruit)); + } + + // end::user_guide[] + static + // @formatter:off + // tag::user_guide[] + public class MyTestTemplateInvocationContextProvider + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts( + ExtensionContext context) { + + return Stream.of(invocationContext("apple"), invocationContext("banana")); + } + + private TestTemplateInvocationContext invocationContext(String parameter) { + return new TestTemplateInvocationContext() { + @Override + public String getDisplayName(int invocationIndex) { + return parameter; + } + + @Override + public List getAdditionalExtensions() { + return Collections.singletonList(new ParameterResolver() { + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + return parameterContext.getParameter().getType().equals(String.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + return parameter; + } + }); + } + }; + } + } + // end::user_guide[] + // @formatter:on + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java new file mode 100644 index 00000000..ab7b8339 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EmptyStackException; +import java.util.Stack; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("A stack") +class TestingAStackDemo { + + Stack stack; + + @Test + @DisplayName("is instantiated with new Stack()") + void isInstantiatedWithNew() { + new Stack<>(); + } + + @Nested + @DisplayName("when new") + class WhenNew { + + @BeforeEach + void createNewStack() { + stack = new Stack<>(); + } + + @Test + @DisplayName("is empty") + void isEmpty() { + assertTrue(stack.isEmpty()); + } + + @Test + @DisplayName("throws EmptyStackException when popped") + void throwsExceptionWhenPopped() { + assertThrows(EmptyStackException.class, stack::pop); + } + + @Test + @DisplayName("throws EmptyStackException when peeked") + void throwsExceptionWhenPeeked() { + assertThrows(EmptyStackException.class, stack::peek); + } + + @Nested + @DisplayName("after pushing an element") + class AfterPushing { + + String anElement = "an element"; + + @BeforeEach + void pushAnElement() { + stack.push(anElement); + } + + @Test + @DisplayName("it is no longer empty") + void isNotEmpty() { + assertFalse(stack.isEmpty()); + } + + @Test + @DisplayName("returns the element when popped and is empty") + void returnElementWhenPopped() { + assertEquals(anElement, stack.pop()); + assertTrue(stack.isEmpty()); + } + + @Test + @DisplayName("returns the element when peeked but remains not empty") + void returnElementWhenPeeked() { + assertEquals(anElement, stack.peek()); + assertFalse(stack.isEmpty()); + } + } + } +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java new file mode 100644 index 00000000..8700f23e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.Timeout.ThreadMode; + +// tag::user_guide[] +class TimeoutDemo { + + @BeforeEach + @Timeout(5) + void setUp() { + // fails if execution time exceeds 5 seconds + } + + @Test + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) + void failsIfExecutionTimeExceeds500Milliseconds() { + // fails if execution time exceeds 500 milliseconds + } + + @Test + @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD) + void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() { + // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java new file mode 100644 index 00000000..403e9084 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::imports[] +import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; + +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherConfig; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; +// end::imports[] + +/** + * @since 5.0 + */ +class UsingTheLauncherDemo { + + @org.junit.jupiter.api.Test + @SuppressWarnings("unused") + void discovery() { + // @formatter:off + // tag::discovery[] + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + .selectors( + selectPackage("com.example.mytests"), + selectClass(MyTestClass.class) + ) + .filters( + includeClassNamePatterns(".*Tests") + ) + .build(); + + try (LauncherSession session = LauncherFactory.openSession()) { + TestPlan testPlan = session.getLauncher().discover(request); + + // ... discover additional test plans or execute tests + } + // end::discovery[] + // @formatter:on + } + + @org.junit.jupiter.api.Tag("exclude") + @org.junit.jupiter.api.Test + @SuppressWarnings("unused") + void execution() { + // @formatter:off + // tag::execution[] + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + .selectors( + selectPackage("com.example.mytests"), + selectClass(MyTestClass.class) + ) + .filters( + includeClassNamePatterns(".*Tests") + ) + .build(); + + SummaryGeneratingListener listener = new SummaryGeneratingListener(); + + try (LauncherSession session = LauncherFactory.openSession()) { + Launcher launcher = session.getLauncher(); + // Register a listener of your choice + launcher.registerTestExecutionListeners(listener); + // Discover tests and build a test plan + TestPlan testPlan = launcher.discover(request); + // Execute test plan + launcher.execute(testPlan); + // Alternatively, execute the request directly + launcher.execute(request); + } + + TestExecutionSummary summary = listener.getSummary(); + // Do something with the summary... + + // end::execution[] + // @formatter:on + } + + @org.junit.jupiter.api.Test + void launcherConfig() { + Path reportsDir = Paths.get("target", "xml-reports"); + PrintWriter out = new PrintWriter(System.out); + // @formatter:off + // tag::launcherConfig[] + LauncherConfig launcherConfig = LauncherConfig.builder() + .enableTestEngineAutoRegistration(false) + .enableLauncherSessionListenerAutoRegistration(false) + .enableLauncherDiscoveryListenerAutoRegistration(false) + .enablePostDiscoveryFilterAutoRegistration(false) + .enableTestExecutionListenerAutoRegistration(false) + .addTestEngines(new CustomTestEngine()) + .addLauncherSessionListeners(new CustomLauncherSessionListener()) + .addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener()) + .addPostDiscoveryFilters(new CustomPostDiscoveryFilter()) + .addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out)) + .addTestExecutionListeners(new CustomTestExecutionListener()) + .build(); + + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + .selectors(selectPackage("com.example.mytests")) + .build(); + + try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) { + session.getLauncher().execute(request); + } + // end::launcherConfig[] + // @formatter:on + } + +} + +class MyTestClass { +} + +class CustomTestExecutionListener implements TestExecutionListener { +} + +class CustomLauncherSessionListener implements LauncherSessionListener { +} + +class CustomLauncherDiscoveryListener implements LauncherDiscoveryListener { +} + +class CustomPostDiscoveryFilter implements PostDiscoveryFilter { + @Override + public FilterResult apply(TestDescriptor object) { + return FilterResult.included("includes everything"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java new file mode 100644 index 00000000..a6eef448 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.callbacks; + +// tag::user_guide[] + +import static example.callbacks.Logger.afterAllMethod; +import static example.callbacks.Logger.afterEachMethod; +import static example.callbacks.Logger.beforeAllMethod; +import static example.callbacks.Logger.beforeEachMethod; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +/** + * Abstract base class for tests that use the database. + */ +abstract class AbstractDatabaseTests { + + @BeforeAll + static void createDatabase() { + beforeAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".createDatabase()"); + } + + @BeforeEach + void connectToDatabase() { + beforeEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".connectToDatabase()"); + } + + @AfterEach + void disconnectFromDatabase() { + afterEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".disconnectFromDatabase()"); + } + + @AfterAll + static void destroyDatabase() { + afterAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".destroyDatabase()"); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java new file mode 100644 index 00000000..03da5ff8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.callbacks; + +// tag::user_guide[] + +import static example.callbacks.Logger.afterEachMethod; +import static example.callbacks.Logger.beforeEachMethod; +import static example.callbacks.Logger.testMethod; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Example of "broken" lifecycle method configuration. + * + *

Test data is inserted before the database connection has been opened. + * + *

Database connection is closed before deleting test data. + */ +@ExtendWith({ Extension1.class, Extension2.class }) +class BrokenLifecycleMethodConfigDemo { + + @BeforeEach + void connectToDatabase() { + beforeEachMethod(getClass().getSimpleName() + ".connectToDatabase()"); + } + + @BeforeEach + void insertTestDataIntoDatabase() { + beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()"); + } + + @Test + void testDatabaseFunctionality() { + testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()"); + } + + @AfterEach + void deleteTestDataFromDatabase() { + afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()"); + } + + @AfterEach + void disconnectFromDatabase() { + afterEachMethod(getClass().getSimpleName() + ".disconnectFromDatabase()"); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java new file mode 100644 index 00000000..792c7788 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.callbacks; + +// tag::user_guide[] + +import static example.callbacks.Logger.afterEachMethod; +import static example.callbacks.Logger.beforeAllMethod; +import static example.callbacks.Logger.beforeEachMethod; +import static example.callbacks.Logger.testMethod; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Extension of {@link AbstractDatabaseTests} that inserts test data + * into the database (after the database connection has been opened) + * and deletes test data (before the database connection is closed). + */ +@ExtendWith({ Extension1.class, Extension2.class }) +class DatabaseTestsDemo extends AbstractDatabaseTests { + + @BeforeAll + static void beforeAll() { + beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".beforeAll()"); + } + + @BeforeEach + void insertTestDataIntoDatabase() { + beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()"); + } + + @Test + void testDatabaseFunctionality() { + testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()"); + } + + @AfterEach + void deleteTestDataFromDatabase() { + afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()"); + } + + @AfterAll + static void afterAll() { + beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()"); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java new file mode 100644 index 00000000..f0f5e697 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.callbacks; + +// tag::user_guide[] + +import static example.callbacks.Logger.afterEachCallback; +import static example.callbacks.Logger.beforeEachCallback; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class Extension1 implements BeforeEachCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + beforeEachCallback(this); + } + + @Override + public void afterEach(ExtensionContext context) { + afterEachCallback(this); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java new file mode 100644 index 00000000..a7cc878f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.callbacks; + +// tag::user_guide[] + +import static example.callbacks.Logger.afterEachCallback; +import static example.callbacks.Logger.beforeEachCallback; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class Extension2 implements BeforeEachCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + beforeEachCallback(this); + } + + @Override + public void afterEach(ExtensionContext context) { + afterEachCallback(this); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java new file mode 100644 index 00000000..ab227125 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.callbacks; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.extension.Extension; + +class Logger { + + static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName()); + + static void beforeAllMethod(String text) { + log(() -> "@BeforeAll " + text); + } + + static void beforeEachCallback(Extension extension) { + log(() -> " " + extension.getClass().getSimpleName() + ".beforeEach()"); + } + + static void beforeEachMethod(String text) { + log(() -> " @BeforeEach " + text); + } + + static void testMethod(String text) { + log(() -> " @Test " + text); + } + + static void afterEachMethod(String text) { + log(() -> " @AfterEach " + text); + } + + static void afterEachCallback(Extension extension) { + log(() -> " " + extension.getClass().getSimpleName() + ".afterEach()"); + } + + static void afterAllMethod(String text) { + log(() -> "@AfterAll " + text); + } + + private static void log(Supplier supplier) { + // System.err.println(supplier.get()); + logger.info(supplier); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java new file mode 100644 index 00000000..f72d80f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.defaultmethods; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +// tag::user_guide[] +public interface ComparableContract> extends Testable { + + T createSmallerValue(); + + @Test + default void returnsZeroWhenComparedToItself() { + T value = createValue(); + assertEquals(0, value.compareTo(value)); + } + + @Test + default void returnsPositiveNumberWhenComparedToSmallerValue() { + T value = createValue(); + T smallerValue = createSmallerValue(); + assertTrue(value.compareTo(smallerValue) > 0); + } + + @Test + default void returnsNegativeNumberWhenComparedToLargerValue() { + T value = createValue(); + T smallerValue = createSmallerValue(); + assertTrue(smallerValue.compareTo(value) < 0); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java new file mode 100644 index 00000000..36e30258 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.defaultmethods; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +// tag::user_guide[] +public interface EqualsContract extends Testable { + + T createNotEqualValue(); + + @Test + default void valueEqualsItself() { + T value = createValue(); + assertEquals(value, value); + } + + @Test + default void valueDoesNotEqualNull() { + T value = createValue(); + assertFalse(value.equals(null)); + } + + @Test + default void valueDoesNotEqualDifferentValue() { + T value = createValue(); + T differentValue = createNotEqualValue(); + assertNotEquals(value, differentValue); + assertNotEquals(differentValue, value); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java new file mode 100644 index 00000000..1449f28f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.defaultmethods; + +// tag::user_guide[] +class StringTests implements ComparableContract, EqualsContract { + + @Override + public String createValue() { + return "banana"; + } + + @Override + public String createSmallerValue() { + return "apple"; // 'a' < 'b' in "banana" + } + + @Override + public String createNotEqualValue() { + return "cherry"; + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java new file mode 100644 index 00000000..84bf397b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.defaultmethods; + +// tag::user_guide[] +public interface Testable { + + T createValue(); + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java new file mode 100644 index 00000000..f2010d1b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.exception; + +import java.io.IOException; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +// @formatter:off +// tag::user_guide[] +public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + + if (throwable instanceof IOException) { + return; + } + throw throwable; + } +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java new file mode 100644 index 00000000..ca706586 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.exception; + +import java.io.IOException; + +import extensions.ExpectToFail; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(IgnoreIOExceptionExtension.class) +class IgnoreIOExceptionTests { + + @Test + void shouldSucceed() throws IOException { + throw new IOException("any"); + } + + @Test + @ExpectToFail + void shouldFail() { + throw new RuntimeException("any"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java new file mode 100644 index 00000000..3db997fe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.exception; + +import static example.exception.MultipleHandlersTestCase.ThirdExecutedHandler; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +// @formatter:off +// tag::user_guide[] +// Register handlers for @Test, @BeforeEach, @AfterEach as well as @BeforeAll and @AfterAll +@ExtendWith(ThirdExecutedHandler.class) +class MultipleHandlersTestCase { + + // Register handlers for @Test, @BeforeEach, @AfterEach only + @ExtendWith(SecondExecutedHandler.class) + @ExtendWith(FirstExecutedHandler.class) + @Test + void testMethod() { + } + + // end::user_guide[] + + static class FirstExecutedHandler implements TestExecutionExceptionHandler { + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + throw ex; + } + } + + static class SecondExecutedHandler implements LifecycleMethodExecutionExceptionHandler { + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + throw ex; + } + } + + static class ThirdExecutedHandler implements LifecycleMethodExecutionExceptionHandler { + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + throw ex; + } + } + // tag::user_guide[] +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java new file mode 100644 index 00000000..ed4c7697 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.exception; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; + +// @formatter:off +// tag::user_guide[] +class RecordStateOnErrorExtension implements LifecycleMethodExecutionExceptionHandler { + + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during class setup"); + throw ex; + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during test setup"); + throw ex; + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during test cleanup"); + throw ex; + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during class cleanup"); + throw ex; + } + // end::user_guide[] + + private void memoryDumpForFurtherInvestigation(String error) { + + } + // tag::user_guide[] +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java new file mode 100644 index 00000000..4b9a904d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +//tag::user_guide[] +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(RandomNumberExtension.class) +public @interface Random { +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java new file mode 100644 index 00000000..d52af0a6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled("RandomNumberExtension has not been implemented") +//tag::user_guide[] +class RandomNumberDemo { + + // use random number field in test methods and @BeforeEach + // or @AfterEach lifecycle methods + @Random + private int randomNumber1; + + RandomNumberDemo(@Random int randomNumber2) { + // use random number in constructor + } + + @BeforeEach + void beforeEach(@Random int randomNumber3) { + // use random number in @BeforeEach method + } + + @Test + void test(@Random int randomNumber4) { + // use random number in test method + } + +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java new file mode 100644 index 00000000..98f1d1ac --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +class RandomNumberExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return null; + } + + @Override + public void beforeAll(ExtensionContext context) { + } + + @Override + public void beforeEach(ExtensionContext context) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java new file mode 100644 index 00000000..b12094f3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.interceptor; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicReference; + +import javax.swing.SwingUtilities; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; + +// @formatter:off +// tag::user_guide[] +public class SwingEdtInterceptor implements InvocationInterceptor { + + @Override + public void interceptTestMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { + + AtomicReference throwable = new AtomicReference<>(); + + SwingUtilities.invokeAndWait(() -> { + try { + invocation.proceed(); + } + catch (Throwable t) { + throwable.set(t); + } + }); + Throwable t = throwable.get(); + if (t != null) { + throw t; + } + } +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java new file mode 100644 index 00000000..973b4f19 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.registration; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; + +//tag::user_guide[] +class DocumentationDemo { + + static Path lookUpDocsDir() { + // return path to docs dir + // end::user_guide[] + return null; + // tag::user_guide[] + } + + @RegisterExtension + DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir()); + + @Test + void generateDocumentation() { + // use this.docs ... + } +} +//end::user_guide[] + +class DocumentationExtension implements AfterEachCallback { + + @SuppressWarnings("unused") + private final Path path; + + private DocumentationExtension(Path path) { + this.path = path; + } + + static DocumentationExtension forPath(Path path) { + return new DocumentationExtension(path); + } + + @Override + public void afterEach(ExtensionContext context) { + /* no-op for demo */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java new file mode 100644 index 00000000..25a6b279 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.registration; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +// tag::user_guide[] +class WebServerDemo { + + // end::user_guide[] + // @formatter:off + // tag::user_guide[] + @RegisterExtension + static WebServerExtension server = WebServerExtension.builder() + .enableSecurity(false) + .build(); + // end::user_guide[] + // @formatter:on + // tag::user_guide[] + + @Test + void getProductList() { + WebClient webClient = new WebClient(); + String serverUrl = server.getServerUrl(); + // Use WebClient to connect to web server using serverUrl and verify response + assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java new file mode 100644 index 00000000..595be12b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.session; + +//tag::user_guide[] +import static java.net.InetAddress.getLoopbackAddress; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.sun.net.httpserver.HttpServer; + +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +public class GlobalSetupTeardownListener implements LauncherSessionListener { + + private Fixture fixture; + + @Override + public void launcherSessionOpened(LauncherSession session) { + // Avoid setup for test discovery by delaying it until tests are about to be executed + session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() { + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + //end::user_guide[] + if (!testPlan.getConfigurationParameters().getBoolean("enableHttpServer").orElse(false)) { + // avoid starting multiple HTTP servers unnecessarily from UsingTheLauncherDemo + return; + } + //tag::user_guide[] + if (fixture == null) { + fixture = new Fixture(); + fixture.setUp(); + } + } + }); + } + + @Override + public void launcherSessionClosed(LauncherSession session) { + if (fixture != null) { + fixture.tearDown(); + fixture = null; + } + } + + static class Fixture { + + private HttpServer server; + private ExecutorService executorService; + + void setUp() { + try { + server = HttpServer.create(new InetSocketAddress(getLoopbackAddress(), 0), 0); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to start HTTP server", e); + } + server.createContext("/test", exchange -> { + exchange.sendResponseHeaders(204, -1); + exchange.close(); + }); + executorService = Executors.newCachedThreadPool(); + server.setExecutor(executorService); + server.start(); // <1> + int port = server.getAddress().getPort(); + System.setProperty("http.server.host", getLoopbackAddress().getHostAddress()); // <2> + System.setProperty("http.server.port", String.valueOf(port)); // <3> + } + + void tearDown() { + server.stop(0); // <4> + executorService.shutdownNow(); + } + } + +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java new file mode 100644 index 00000000..cbdf5367 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.session; + +//tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; + +import org.junit.jupiter.api.Test; + +class HttpTests { + + @Test + void respondsWith204() throws Exception { + String host = System.getProperty("http.server.host"); // <1> + String port = System.getProperty("http.server.port"); // <2> + URL url = URI.create("http://" + host + ":" + port + "/test").toURL(); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + int responseCode = connection.getResponseCode(); // <3> + + assertEquals(204, responseCode); // <4> + } +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java new file mode 100644 index 00000000..b24ab341 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testinterface; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +// @formatter:off +// tag::user_guide[] +class TestInterfaceDemo implements TestLifecycleLogger, + TimeExecutionLogger, TestInterfaceDynamicTestsDemo { + + @Test + void isEqualValue() { + assertEquals(1, "a".length(), "is always equal"); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java new file mode 100644 index 00000000..694192db --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testinterface; + +import static example.util.StringUtils.isPalindrome; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +// @formatter:off +// tag::user_guide[] +interface TestInterfaceDynamicTestsDemo { + + @TestFactory + default Stream dynamicTestsForPalindromes() { + return Stream.of("racecar", "radar", "mom", "dad") + .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java new file mode 100644 index 00000000..b269bfb6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testinterface; + +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +// @formatter:off +// tag::user_guide[] +@TestInstance(Lifecycle.PER_CLASS) +interface TestLifecycleLogger { + + static final Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName()); + + @BeforeAll + default void beforeAllTests() { + logger.info("Before all tests"); + } + + @AfterAll + default void afterAllTests() { + logger.info("After all tests"); + } + + @BeforeEach + default void beforeEachTest(TestInfo testInfo) { + logger.info(() -> String.format("About to execute [%s]", + testInfo.getDisplayName())); + } + + @AfterEach + default void afterEachTest(TestInfo testInfo) { + logger.info(() -> String.format("Finished executing [%s]", + testInfo.getDisplayName())); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java new file mode 100644 index 00000000..b3a53fea --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testinterface; + +import example.timing.TimingExtension; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +//tag::user_guide[] +@Tag("timed") +@ExtendWith(TimingExtension.class) +interface TimeExecutionLogger { +} +//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java new file mode 100644 index 00000000..5df68019 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testkit; + +// @formatter:off +// tag::user_guide[] + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.io.StringWriter; +import java.io.Writer; + +import example.ExampleTestCase; + +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.opentest4j.TestAbortedException; + +class EngineTestKitAllEventsDemo { + + @Test + void verifyAllJupiterEvents() { + Writer writer = // create a java.io.Writer for debug output + // end::user_guide[] + // For the demo, we are swallowing the debug output. + new StringWriter(); + // tag::user_guide[] + + EngineTestKit.engine("junit-jupiter") // <1> + .selectors(selectClass(ExampleTestCase.class)) // <2> + .execute() // <3> + .allEvents() // <4> + .debug(writer) // <5> + .assertEventsMatchExactly( // <6> + event(engine(), started()), + event(container(ExampleTestCase.class), started()), + event(test("skippedTest"), skippedWithReason("for demonstration purposes")), + event(test("succeedingTest"), started()), + event(test("succeedingTest"), finishedSuccessfully()), + event(test("abortedTest"), started()), + event(test("abortedTest"), + abortedWithReason(instanceOf(TestAbortedException.class), + message(m -> m.contains("abc does not contain Z")))), + event(test("failingTest"), started()), + event(test("failingTest"), finishedWithFailure( + instanceOf(ArithmeticException.class), message("/ by zero"))), + event(container(ExampleTestCase.class), finishedSuccessfully()), + event(engine(), finishedSuccessfully())); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java new file mode 100644 index 00000000..40ea51ba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testkit; + +// @formatter:off +// tag::user_guide[] + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import example.ExampleTestCase; + +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineTestKit; + +class EngineTestKitFailedMethodDemo { + + @Test + void verifyJupiterMethodFailed() { + EngineTestKit.engine("junit-jupiter") // <1> + .selectors(selectClass(ExampleTestCase.class)) // <2> + .execute() // <3> + .testEvents() // <4> + .assertThatEvents().haveExactly(1, // <5> + event(test("failingTest"), + finishedWithFailure( + instanceOf(ArithmeticException.class), message("/ by zero")))); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java new file mode 100644 index 00000000..1d06770f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testkit; + +// @formatter:off +// tag::user_guide[] + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.test; + +import example.ExampleTestCase; + +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; + +class EngineTestKitSkippedMethodDemo { + + @Test + void verifyJupiterMethodWasSkipped() { + String methodName = "skippedTest"; + + Events testEvents = EngineTestKit // <5> + .engine("junit-jupiter") // <1> + .selectors(selectMethod(ExampleTestCase.class, methodName)) // <2> + .execute() // <3> + .testEvents(); // <4> + + testEvents.assertStatistics(stats -> stats.skipped(1)); // <6> + + testEvents.assertThatEvents() // <7> + .haveExactly(1, event(test(methodName), + skippedWithReason("for demonstration purposes"))); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java new file mode 100644 index 00000000..b9576352 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.testkit; + +// @formatter:off +// tag::user_guide[] + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import example.ExampleTestCase; + +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineTestKit; + +class EngineTestKitStatisticsDemo { + + @Test + void verifyJupiterContainerStats() { + EngineTestKit + .engine("junit-jupiter") // <1> + .selectors(selectClass(ExampleTestCase.class)) // <2> + .execute() // <3> + .containerEvents() // <4> + .assertStatistics(stats -> stats.started(2).succeeded(2)); // <5> + } + + @Test + void verifyJupiterTestStats() { + EngineTestKit + .engine("junit-jupiter") // <1> + .selectors(selectClass(ExampleTestCase.class)) // <2> + .execute() // <3> + .testEvents() // <6> + .assertStatistics(stats -> + stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); // <7> + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java new file mode 100644 index 00000000..b6b51d61 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.timing; + +// tag::user_guide[] +import java.lang.reflect.Method; +import java.util.logging.Logger; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +// end::user_guide[] +/** + * Simple extension that times the execution of test methods and + * logs the results at {@code INFO} level. + * + * @since 5.0 + */ +// @formatter:off +// tag::user_guide[] +public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + private static final Logger logger = Logger.getLogger(TimingExtension.class.getName()); + + private static final String START_TIME = "start time"; + + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + getStore(context).put(START_TIME, System.currentTimeMillis()); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + Method testMethod = context.getRequiredTestMethod(); + long startTime = getStore(context).remove(START_TIME, long.class); + long duration = System.currentTimeMillis() - startTime; + + logger.info(() -> + String.format("Method [%s] took %s ms.", testMethod.getName(), duration)); + } + + private Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); + } + +} +// end::user_guide[] +// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java new file mode 100644 index 00000000..ee84b196 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.timing; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Tests that demonstrate the example {@link TimingExtension}. + * + * @since 5.0 + */ +// tag::user_guide[] +@ExtendWith(TimingExtension.class) +class TimingExtensionTests { + + @Test + void sleep20ms() throws Exception { + Thread.sleep(20); + } + + @Test + void sleep50ms() throws Exception { + Thread.sleep(50); + } + +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java new file mode 100644 index 00000000..8dd21a71 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package extensions; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(ExpectToFail.Extension.class) +public @interface ExpectToFail { + + class Extension implements TestExecutionExceptionHandler, AfterEachCallback { + + private static final String KEY = "exception"; + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + getExceptionStore(context).put(KEY, throwable); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + assertNotNull(getExceptionStore(context).get(KEY), "Test should have failed"); + } + + private Store getExceptionStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java new file mode 100644 index 00000000..6b9a143c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.api.tools; + +import static java.lang.String.format; + +import java.io.PrintWriter; +import java.util.EnumSet; +import java.util.List; + +import org.apiguardian.api.API.Status; + +/** + * @since 1.0 + */ +abstract class AbstractApiReportWriter implements ApiReportWriter { + + private final ApiReport apiReport; + + AbstractApiReportWriter(ApiReport apiReport) { + this.apiReport = apiReport; + } + + @Override + public void printReportHeader(PrintWriter out) { + out.println(h1("@API Declarations")); + out.println(); + out.println(paragraph( + format("Discovered %d types with %s declarations.", this.apiReport.getTypes().size(), code("@API")))); + out.println(); + } + + @Override + public void printDeclarationInfo(PrintWriter out, EnumSet statuses) { + // @formatter:off + this.apiReport.getDeclarationsMap().entrySet().stream() + .filter(e -> statuses.contains(e.getKey())) + .forEach(e -> printDeclarationSection(statuses, e.getKey(), e.getValue(), out)); + // @formatter:on + } + + protected void printDeclarationSection(EnumSet statuses, Status status, List> types, + PrintWriter out) { + printDeclarationSectionHeader(statuses, status, types, out); + if (types.size() > 0) { + printDeclarationTableHeader(out); + types.forEach(type -> printDeclarationTableRow(type, out)); + printDeclarationTableFooter(out); + out.println(); + } + } + + protected void printDeclarationSectionHeader(EnumSet statuses, Status status, List> types, + PrintWriter out) { + if (statuses.size() < 2) { + // omit section header when only a single status is printed + return; + } + out.println(h2(format("@API(%s)", status))); + out.println(); + out.println(paragraph(format("Discovered %d " + code("@API(%s)") + " declarations.", types.size(), status))); + out.println(); + } + + protected abstract String h1(String header); + + protected abstract String h2(String header); + + protected abstract String code(String element); + + protected abstract String italic(String element); + + protected String paragraph(String element) { + return element; + } + + protected abstract void printDeclarationTableHeader(PrintWriter out); + + protected abstract void printDeclarationTableRow(Class type, PrintWriter out); + + protected abstract void printDeclarationTableFooter(PrintWriter out); + + protected String getKind(Class type) { + if (type.isAnnotation()) { + return "annotation"; + } + if (type.isEnum()) { + return "enum"; + } + if (type.isInterface()) { + return "interface"; + } + return "class"; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java new file mode 100644 index 00000000..b21e63e8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.api.tools; + +import java.util.List; +import java.util.Map; + +import org.apiguardian.api.API.Status; + +/** + * @since 1.0 + */ +class ApiReport { + + private final List> types; + + private final Map>> declarationsMap; + + ApiReport(List> types, Map>> declarationsMap) { + this.types = types; + this.declarationsMap = declarationsMap; + } + + List> getTypes() { + return this.types; + } + + Map>> getDeclarationsMap() { + return this.declarationsMap; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java new file mode 100644 index 00000000..7bdb6c21 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java @@ -0,0 +1,117 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.api.tools; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * @since 1.0 + */ +class ApiReportGenerator { + + public static void main(String... args) { + + // CAUTION: The output produced by this method is used to + // generate a table in the User Guide. + + PrintWriter writer = new PrintWriter(System.out, true); + ApiReportGenerator reportGenerator = new ApiReportGenerator(); + + // scan all types below "org.junit" package + ApiReport apiReport = reportGenerator.generateReport("org.junit"); + + // ApiReportWriter reportWriter = new MarkdownApiReportWriter(apiReport); + ApiReportWriter reportWriter = new AsciidocApiReportWriter(apiReport); + // ApiReportWriter reportWriter = new HtmlApiReportWriter(apiReport); + + // reportWriter.printReportHeader(writer); + + // Print report for all Usage enum constants + // reportWriter.printDeclarationInfo(writer, EnumSet.allOf(Usage.class)); + + // Print report only for a specific Status constant, defaults to EXPERIMENTAL + Status status = Status.EXPERIMENTAL; + if (args.length == 1) { + status = Status.valueOf(args[0]); + } + reportWriter.printDeclarationInfo(writer, EnumSet.of(status)); + } + + // ------------------------------------------------------------------------- + + ApiReport generateReport(String... packages) { + Logger logger = LoggerFactory.getLogger(ApiReportGenerator.class); + String EOL = System.lineSeparator(); + ClassGraph classGraph = new ClassGraph() // + .acceptPackages(packages) // + .disableNestedJarScanning() // + .enableAnnotationInfo(); // + String apiClasspath = System.getProperty("api.classpath"); + if (apiClasspath != null) { + classGraph = classGraph.overrideClasspath(apiClasspath); + } + + // Scan packages + try (ScanResult scanResult = classGraph.scan()) { + + // Collect names + ClassInfoList classesWithApiAnnotation = scanResult.getClassesWithAnnotation(API.class.getCanonicalName()); + List names = classesWithApiAnnotation.getNames(); + + logger.debug(() -> { + StringBuilder builder = new StringBuilder( + names.size() + " @API declarations (including meta) found in class-path:"); + builder.append(EOL); + scanResult.getClasspathURLs().forEach(e -> builder.append(e).append(EOL)); + return builder.toString(); + }); + + // Collect types + List> types = classesWithApiAnnotation.loadClasses(); + // only retain directly annotated types + types.removeIf(c -> !c.isAnnotationPresent(API.class)); + types.sort(Comparator.comparing(Class::getName)); + + logger.debug(() -> { + StringBuilder builder = new StringBuilder("Listing of all " + types.size() + " annotated types:"); + builder.append(EOL); + types.forEach(e -> builder.append(e).append(EOL)); + return builder.toString(); + }); + + // Build map + Map>> declarationsMap = new EnumMap<>(Status.class); + for (Status status : Status.values()) { + declarationsMap.put(status, new ArrayList<>()); + } + types.forEach(type -> declarationsMap.get(type.getAnnotation(API.class).status()).add(type)); + + // Create report + return new ApiReport(types, declarationsMap); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java new file mode 100644 index 00000000..c231d1b4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.api.tools; + +import java.io.PrintWriter; +import java.util.EnumSet; + +import org.apiguardian.api.API.Status; + +/** + * @since 1.0 + */ +interface ApiReportWriter { + + void printReportHeader(PrintWriter out); + + void printDeclarationInfo(PrintWriter out, EnumSet statuses); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java new file mode 100644 index 00000000..05748d36 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.api.tools; + +import java.io.PrintWriter; + +import org.apiguardian.api.API; + +/** + * @since 1.0 + */ +class AsciidocApiReportWriter extends AbstractApiReportWriter { + + private static final String ASCIIDOC_FORMAT = "| %-52s | %-42s | %-12s%n"; + + AsciidocApiReportWriter(ApiReport apiReport) { + super(apiReport); + } + + @Override + protected String h1(String header) { + return "= " + header; + } + + @Override + protected String h2(String header) { + return "== " + header; + } + + @Override + protected String code(String element) { + return "`" + element + "`"; + } + + @Override + protected String italic(String element) { + return "_" + element + "_"; + } + + @Override + protected void printDeclarationTableHeader(PrintWriter out) { + out.println("|==="); + out.printf(ASCIIDOC_FORMAT, "Package Name", "Type Name", "Since"); + out.println(); + } + + @Override + protected void printDeclarationTableRow(Class type, PrintWriter out) { + String packageName = type.getPackage().getName(); + String typeName = type.getCanonicalName(); + if (typeName.startsWith(packageName + '.')) { + typeName = typeName.substring(packageName.length() + 1); + } + out.printf(ASCIIDOC_FORMAT, // + code(packageName), // + code(typeName) + " " + italic("(" + getKind(type) + ")"), // + code(type.getAnnotation(API.class).since()) // + ); + } + + @Override + protected void printDeclarationTableFooter(PrintWriter out) { + out.println("|==="); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java new file mode 100644 index 00000000..48193368 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.api.tools; + +import java.io.PrintWriter; + +import org.apiguardian.api.API; + +/** + * @since 1.0 + */ +class HtmlApiReportWriter extends AbstractApiReportWriter { + + private static final String HTML_HEADER_FORMAT = "\t%s%s%s%n"; + private static final String HTML_ROW_FORMAT = "\t%s%s%s%n"; + + HtmlApiReportWriter(ApiReport apiReport) { + super(apiReport); + } + + @Override + protected String h1(String header) { + return "

" + header + "

"; + } + + @Override + protected String h2(String header) { + return "

" + header + "

"; + } + + @Override + protected String code(String element) { + return "" + element + ""; + } + + @Override + protected String italic(String element) { + return "" + element + ""; + } + + @Override + protected String paragraph(String element) { + return "

" + element + "

"; + } + + @Override + protected void printDeclarationTableHeader(PrintWriter out) { + out.println(""); + out.printf(HTML_HEADER_FORMAT, "Package Name", "Type Name", "Since"); + } + + @Override + protected void printDeclarationTableRow(Class type, PrintWriter out) { + out.printf(HTML_ROW_FORMAT, // + code(type.getPackage().getName()), // + code(type.getSimpleName()) + " " + italic("(" + getKind(type) + ")"), // + code(type.getAnnotation(API.class).since()) // + ); + } + + @Override + protected void printDeclarationTableFooter(PrintWriter out) { + out.println("
"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java new file mode 100644 index 00000000..6294ac58 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.api.tools; + +import java.io.PrintWriter; +import java.nio.CharBuffer; + +import org.apiguardian.api.API; + +/** + * @since 1.0 + */ +class MarkdownApiReportWriter extends AbstractApiReportWriter { + + private static final String MARKDOWN_FORMAT = "%-52s | %-42s | %-12s | %-27s%n"; + + MarkdownApiReportWriter(ApiReport apiReport) { + super(apiReport); + } + + @Override + protected String h1(String header) { + return "# " + header; + } + + @Override + protected String h2(String header) { + return "## " + header; + } + + @Override + protected String code(String element) { + return "`" + element + "`"; + } + + @Override + protected String italic(String element) { + return "_" + element + "_"; + } + + @Override + protected void printDeclarationTableHeader(PrintWriter out) { + out.printf(MARKDOWN_FORMAT, "Package Name", "Type Name", "Since"); + out.printf(MARKDOWN_FORMAT, dashes(52), dashes(42), dashes(12), dashes(27)); + } + + private String dashes(int length) { + return CharBuffer.allocate(length).toString().replace('\0', '-'); + } + + @Override + protected void printDeclarationTableRow(Class type, PrintWriter out) { + out.printf(MARKDOWN_FORMAT, // + code(type.getPackage().getName()), // + code(type.getSimpleName()) + " " + italic("(" + getKind(type) + ")"), // + code(type.getAnnotation(API.class).since()) // + ); + } + + @Override + protected void printDeclarationTableFooter(PrintWriter out) { + /* no-op */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt new file mode 100644 index 00000000..33ddbe08 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package example + +class FibonacciCalculator { + + private val fibonacci = sequence { + yield(0) // 0th Fibonacci number + yield(1) // first Fibonacci number + var cur = 1 + var next = 1 + while (true) { + yield(next) // next Fibonacci number + val tmp = cur + next + cur = next + next = tmp + } + } + + fun fib(fibonacciNumber: Int) = + fibonacci.elementAt(fibonacciNumber) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt new file mode 100644 index 00000000..2870ae95 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package example + +// tag::user_guide[] + +import example.domain.Person +import example.util.Calculator +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.assertTimeout +import org.junit.jupiter.api.assertTimeoutPreemptively +import java.time.Duration + +class KotlinAssertionsDemo { + + private val person = Person("Jane", "Doe") + private val people = setOf(person, Person("John", "Doe")) + + @Test + fun `exception absence testing`() { + val calculator = Calculator() + val result = assertDoesNotThrow("Should not throw an exception") { + calculator.divide(0, 1) + } + assertEquals(0, result) + } + + @Test + fun `expected exception testing`() { + val calculator = Calculator() + val exception = assertThrows ("Should throw an exception") { + calculator.divide(1, 0) + } + assertEquals("/ by zero", exception.message) + } + + @Test + fun `grouped assertions`() { + assertAll( + "Person properties", + { assertEquals("Jane", person.firstName) }, + { assertEquals("Doe", person.lastName) } + ) + } + + @Test + fun `grouped assertions from a stream`() { + assertAll( + "People with first name starting with J", + people + .stream() + .map { + // This mapping returns Stream<() -> Unit> + { assertTrue(it.firstName.startsWith("J")) } + } + ) + } + + @Test + fun `grouped assertions from a collection`() { + assertAll( + "People with last name of Doe", + people.map { { assertEquals("Doe", it.lastName) } } + ) + } + + @Test + fun `timeout not exceeded testing`() { + val fibonacciCalculator = FibonacciCalculator() + val result = assertTimeout(Duration.ofMillis(1000)) { + fibonacciCalculator.fib(14) + } + assertEquals(377, result) + } + + // end::user_guide[] + @extensions.ExpectToFail + // tag::user_guide[] + @Test + fun `timeout exceeded with preemptive termination`() { + // The following assertion fails with an error message similar to: + // execution timed out after 10 ms + assertTimeoutPreemptively(Duration.ofMillis(10)) { + // Simulate task that takes more than 10 ms. + Thread.sleep(100) + } + } +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt new file mode 100644 index 00000000..52a390fe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package example.registration + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +// tag::user_guide[] +class KotlinWebServerDemo { + + companion object { + @JvmStatic + @RegisterExtension + val server = WebServerExtension.builder() + .enableSecurity(false) + .build() + } + + @Test + fun getProductList() { + // Use WebClient to connect to web server using serverUrl and verify response + val webClient = WebClient() + val serverUrl = server.serverUrl + assertEquals(200, webClient.get("$serverUrl/products").responseStatus) + } +} +// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 00000000..f6d29762 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1 @@ +example.session.GlobalSetupTeardownListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..6f2ed6e7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties @@ -0,0 +1,4 @@ +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.config.strategy=fixed +junit.jupiter.execution.parallel.config.fixed.parallelism=6 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..9fb7a4a6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv new file mode 100644 index 00000000..7ebb4c54 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv @@ -0,0 +1,5 @@ +COUNTRY, REFERENCE +Sweden, 1 +Poland, 2 +"United States of America", 3 +France, 700_000 diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradle.properties b/src/main/java/com/junit-team-junit5-da216b8/gradle.properties new file mode 100644 index 00000000..646efede --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/gradle.properties @@ -0,0 +1,31 @@ +group = org.junit +version = 5.9.3 + +jupiterGroup = org.junit.jupiter + +platformGroup = org.junit.platform +platformVersion = 1.9.3 + +vintageGroup = org.junit.vintage +vintageVersion = 5.9.3 + +defaultBuiltBy = JUnit Team + +# We need more metaspace due to apparent memory leak in Asciidoctor/JRuby +# The exports are needed due to https://github.com/diffplug/spotless/issues/834 +org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21 + +# Test Distribution +gradle.internal.testdistribution.writeTraceFile=true + +# Omit automatic compile dependency on kotlin-stdlib +# https://kotlinlang.org/docs/gradle.html#dependency-on-the-standard-library +kotlin.stdlib.default.dependency=false diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml b/src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml new file mode 100644 index 00000000..3c1dc9df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml @@ -0,0 +1,56 @@ +[versions] +ant = "1.10.12" +apiguardian = "1.1.2" +asciidoctor-pdf = "1.5.3" +assertj = "3.23.1" +checkstyle = "9.0" +jacoco = "0.8.7" +jmh = "1.36" +junit4 = "4.13.2" +junit4Osgi = "4.13.2_1" +junit4Min = "4.12" +ktlint = "0.43.0" +log4j = "2.19.0" +opentest4j = "1.2.0" +openTestReporting = "0.1.0-M1" +surefire = "3.0.0-M7" +xmlunit = "2.9.0" + +[libraries] +ant = { module = "org.apache.ant:ant", version.ref = "ant" } +ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" } +ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" } +apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } +archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.0.1" } +assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } +bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" } +bnd = { module = "biz.aQute.bnd:biz.aQute.bndlib", version = "6.4.0" } +classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.153" } +commons-io = { module = "commons-io:commons-io", version = "2.11.0" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.7" } +groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.14" } +hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } +jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } +jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } +jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } +joox = { module = "org.jooq:joox", version = "2.0.0" } +junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.6.4" } +log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } +maven = { module = "org.apache.maven:apache-maven", version = "3.8.6" } +mockito = { module = "org.mockito:mockito-junit-jupiter", version = "4.11.0" } +opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } +openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } +openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" } +picocli = { module = "info.picocli:picocli", version = "4.7.0" } +slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.6" } +spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } +univocity-parsers = { module = "com.univocity:univocity-parsers", version = "2.9.1" } +xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } +xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } + +[bundles] +ant = ["ant", "ant-junit", "ant-junitlauncher"] +log4j = ["log4j-core", "log4j-jul"] +xmlunit = ["xmlunit-assertj", "xmlunit-placeholders"] diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties b/src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..adb6acbd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradlew b/src/main/java/com/junit-team-junit5-da216b8/gradlew new file mode 100644 index 00000000..65dcd68d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradlew.bat b/src/main/java/com/junit-team-junit5-da216b8/gradlew.bat new file mode 100644 index 00000000..93e3f59f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md b/src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md new file mode 100644 index 00000000..907a361c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md @@ -0,0 +1,8 @@ +# JUnit Bill of Materials (BOM) + +This module provides a Bill of Materials POM to ease dependency management using [Maven] +or [Gradle]. Please refer to the [User Guide] for details. + +[Maven]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies +[Gradle]: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:bom_import +[User Guide]: https://junit.org/junit5/docs/current/user-guide/#dependency-metadata-junit-bom diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts new file mode 100644 index 00000000..fae23ee3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts @@ -0,0 +1,42 @@ +plugins { + `java-platform` + `publishing-conventions` +} + +description = "${rootProject.description} (Bill of Materials)" + +dependencies { + constraints { + val mavenizedProjects: List by rootProject.extra + mavenizedProjects.sorted() + .filter { it.name != "junit-platform-console-standalone" } + .forEach { api("${it.group}:${it.name}:${it.version}") } + } +} + +publishing.publications.named("maven") { + from(components["javaPlatform"]) + pom { + description.set("This Bill of Materials POM can be used to ease dependency management " + + "when referencing multiple JUnit artifacts using Gradle or Maven.") + withXml { + val filteredContent = asString().replace("\\s*compile".toRegex(), "") + asString().clear().append(filteredContent) + } + } +} + +tasks.withType().configureEach { + doLast { + val xml = destination.readText() + require(xml.indexOf("") == xml.lastIndexOf("")) { + "BOM must contain exactly one element but contained multiple:\n$destination" + } + require(xml.contains("")) { + "BOM must contain a element:\n$destination" + } + require(!xml.contains("")) { + "BOM must not contain elements:\n$destination" + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts new file mode 100644 index 00000000..d28dec7b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -0,0 +1,32 @@ +plugins { + `kotlin-library-conventions` + `java-test-fixtures` +} + +description = "JUnit Jupiter API" + +dependencies { + api(platform(projects.junitBom)) + api(libs.opentest4j) + api(projects.junitPlatformCommons) + + compileOnlyApi(libs.apiguardian) + + compileOnly(kotlin("stdlib")) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + jar { + bundle { + bnd(""" + Require-Capability:\ + org.junit.platform.engine;\ + filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;${rootProject.property("version")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("version")!!}}})))';\ + effective:=active + """) + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java new file mode 100644 index 00000000..52743ebe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @AfterAll} is used to signal that the annotated method should be + * executed after all tests in the current test class. + * + *

In contrast to {@link AfterEach @AfterEach} methods, {@code @AfterAll} + * methods are only executed once for a given test class. + * + *

Method Signatures

+ * + *

{@code @AfterAll} methods must have a {@code void} return type and must be + * {@code static} by default. Consequently, {@code @AfterAll} methods are not + * supported in {@link Nested @Nested} test classes or as interface default + * methods unless the test class is annotated with + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * However, beginning with Java 16 {@code @AfterAll} methods may be declared as + * {@code static} in {@link Nested @Nested} test classes, and the + * {@code Lifecycle.PER_CLASS} restriction no longer applies. {@code @AfterAll} + * methods may optionally declare parameters to be resolved by + * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. + * + *

Using {@code private} visibility for {@code @BeforeAll} methods is + * strongly discouraged and will be disallowed in a future release. + * + *

Inheritance and Execution Order

+ * + *

{@code @AfterAll} methods are inherited from superclasses as long as + * they are not hidden (default mode with {@code static} modifier), + * overridden, or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @AfterAll} methods from superclasses will be executed before + * {@code @AfterAll} methods in subclasses. + * + *

Similarly, {@code @AfterAll} methods declared in an interface are + * inherited as long as they are not hidden or overridden, + * and {@code @AfterAll} methods from an interface will be executed after + * {@code @AfterAll} methods in the class that implements the interface. + * + *

JUnit Jupiter does not guarantee the execution order of multiple + * {@code @AfterAll} methods that are declared within a single test class or + * test interface. While it may at times appear that these methods are invoked + * in alphabetical order, they are in fact sorted using an algorithm that is + * deterministic but intentionally non-obvious. + * + *

In addition, {@code @AfterAll} methods are in no way linked to + * {@code @BeforeAll} methods. Consequently, there are no guarantees with regard + * to their wrapping behavior. For example, given two + * {@code @BeforeAll} methods {@code createA()} and {@code createB()} as well as + * two {@code @AfterAll} methods {@code destroyA()} and {@code destroyB()}, the + * order in which the {@code @BeforeAll} methods are executed (e.g. + * {@code createA()} before {@code createB()}) does not imply any order for the + * seemingly corresponding {@code @AfterAll} methods. In other words, + * {@code destroyA()} might be called before or after + * {@code destroyB()}. The JUnit Team therefore recommends that developers + * declare at most one {@code @BeforeAll} method and at most one + * {@code @AfterAll} method per test class or test interface unless there are no + * dependencies between the {@code @BeforeAll} methods or between the + * {@code @AfterAll} methods. + * + *

Composition

+ * + *

{@code @AfterAll} may be used as a meta-annotation in order to create + * a custom composed annotation that inherits the semantics of + * {@code @AfterAll}. + * + * @since 5.0 + * @see BeforeAll + * @see BeforeEach + * @see AfterEach + * @see Test + * @see TestFactory + * @see TestInstance + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +public @interface AfterAll { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java new file mode 100644 index 00000000..8dfd018f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @AfterEach} is used to signal that the annotated method should be + * executed after each {@code @Test}, + * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, + * and {@code @TestTemplate} method in the current test class. + * + *

Method Signatures

+ * + *

{@code @AfterEach} methods must have a {@code void} return type and must + * not be {@code static}. Using {@code private} visibility is strongly + * discouraged and will be disallowed in a future release. + * They may optionally declare parameters to be resolved by + * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. + * + *

Inheritance and Execution Order

+ * + *

{@code @AfterEach} methods are inherited from superclasses as long as they + * are not overridden or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @AfterEach} methods from superclasses will be executed after + * {@code @AfterEach} methods in subclasses. + * + *

Similarly, {@code @AfterEach} methods declared as interface default + * methods are inherited as long as they are not overridden, and + * {@code @AfterEach} default methods will be executed after {@code @AfterEach} + * methods in the class that implements the interface. + * + *

JUnit Jupiter does not guarantee the execution order of multiple + * {@code @AfterEach} methods that are declared within a single test class or + * test interface. While it may at times appear that these methods are invoked + * in alphabetical order, they are in fact sorted using an algorithm that is + * deterministic but intentionally non-obvious. + * + *

In addition, {@code @AfterEach} methods are in no way linked to + * {@code @BeforeEach} methods. Consequently, there are no guarantees with + * regard to their wrapping behavior. For example, given two + * {@code @BeforeEach} methods {@code createA()} and {@code createB()} as well + * as two {@code @AfterEach} methods {@code destroyA()} and {@code destroyB()}, + * the order in which the {@code @BeforeEach} methods are executed (e.g. + * {@code createA()} before {@code createB()}) does not imply any order for the + * seemingly corresponding {@code @AfterEach} methods. In other words, + * {@code destroyA()} might be called before or after + * {@code destroyB()}. The JUnit Team therefore recommends that developers + * declare at most one {@code @BeforeEach} method and at most one + * {@code @AfterEach} method per test class or test interface unless there are + * no dependencies between the {@code @BeforeEach} methods or between the + * {@code @AfterEach} methods. + * + *

Composition

+ * + *

{@code @AfterEach} may be used as a meta-annotation in order to create + * a custom composed annotation that inherits the semantics of + * {@code @AfterEach}. + * + * @since 5.0 + * @see BeforeEach + * @see BeforeAll + * @see AfterAll + * @see Test + * @see RepeatedTest + * @see TestFactory + * @see TestTemplate + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +public @interface AfterEach { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java new file mode 100644 index 00000000..601e9ecf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.opentest4j.MultipleFailuresError; + +/** + * {@code AssertAll} is a collection of utility methods that support asserting + * multiple conditions in tests at once. + * + * @since 5.0 + */ +class AssertAll { + + private AssertAll() { + /* no-op */ + } + + static void assertAll(Executable... executables) { + assertAll(null, executables); + } + + static void assertAll(String heading, Executable... executables) { + Preconditions.notEmpty(executables, "executables array must not be null or empty"); + Preconditions.containsNoNullElements(executables, "individual executables must not be null"); + assertAll(heading, Arrays.stream(executables)); + } + + static void assertAll(Collection executables) { + assertAll(null, executables); + } + + static void assertAll(String heading, Collection executables) { + Preconditions.notNull(executables, "executables collection must not be null"); + Preconditions.containsNoNullElements(executables, "individual executables must not be null"); + assertAll(heading, executables.stream()); + } + + static void assertAll(Stream executables) { + assertAll(null, executables); + } + + static void assertAll(String heading, Stream executables) { + Preconditions.notNull(executables, "executables stream must not be null"); + + List failures = executables // + .map(executable -> { + Preconditions.notNull(executable, "individual executables must not be null"); + try { + executable.execute(); + return null; + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + return t; + } + }) // + .filter(Objects::nonNull) // + .collect(Collectors.toList()); + + if (!failures.isEmpty()) { + MultipleFailuresError multipleFailuresError = new MultipleFailuresError(heading, failures); + failures.forEach(multipleFailuresError::addSuppressed); + throw multipleFailuresError; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java new file mode 100644 index 00000000..45d37399 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java @@ -0,0 +1,449 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.jupiter.api.AssertionUtils.formatIndexes; +import static org.junit.platform.commons.util.ReflectionUtils.isArray; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * {@code AssertArrayEquals} is a collection of utility methods that support asserting + * array equality in tests. + * + * @since 5.0 + */ +class AssertArrayEquals { + + private AssertArrayEquals() { + /* no-op */ + } + + static void assertArrayEquals(boolean[] expected, boolean[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(boolean[] expected, boolean[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(boolean[] expected, boolean[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(char[] expected, char[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(char[] expected, char[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(char[] expected, char[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(byte[] expected, byte[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(byte[] expected, byte[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(byte[] expected, byte[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(short[] expected, short[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(short[] expected, short[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(short[] expected, short[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(int[] expected, int[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(int[] expected, int[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(int[] expected, int[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(long[] expected, long[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(long[] expected, long[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(long[] expected, long[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(float[] expected, float[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(float[] expected, float[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(float[] expected, float[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(float[] expected, float[] actual, float delta) { + assertArrayEquals(expected, actual, delta, (String) null); + } + + static void assertArrayEquals(float[] expected, float[] actual, float delta, String message) { + assertArrayEquals(expected, actual, delta, null, message); + } + + static void assertArrayEquals(float[] expected, float[] actual, float delta, Supplier messageSupplier) { + assertArrayEquals(expected, actual, delta, null, messageSupplier); + } + + static void assertArrayEquals(double[] expected, double[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(double[] expected, double[] actual, String message) { + assertArrayEquals(expected, actual, null, message); + } + + static void assertArrayEquals(double[] expected, double[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, null, messageSupplier); + } + + static void assertArrayEquals(double[] expected, double[] actual, double delta) { + assertArrayEquals(expected, actual, delta, (String) null); + } + + static void assertArrayEquals(double[] expected, double[] actual, double delta, String message) { + assertArrayEquals(expected, actual, delta, null, message); + } + + static void assertArrayEquals(double[] expected, double[] actual, double delta, Supplier messageSupplier) { + assertArrayEquals(expected, actual, delta, null, messageSupplier); + } + + static void assertArrayEquals(Object[] expected, Object[] actual) { + assertArrayEquals(expected, actual, (String) null); + } + + static void assertArrayEquals(Object[] expected, Object[] actual, String message) { + assertArrayEquals(expected, actual, new ArrayDeque<>(), message); + } + + static void assertArrayEquals(Object[] expected, Object[] actual, Supplier messageSupplier) { + assertArrayEquals(expected, actual, new ArrayDeque<>(), messageSupplier); + } + + private static void assertArrayEquals(boolean[] expected, boolean[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(char[] expected, char[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(byte[] expected, byte[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(short[] expected, short[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(int[] expected, int[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(long[] expected, long[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(float[] expected, float[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (!AssertionUtils.floatsAreEqual(expected[i], actual[i])) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(float[] expected, float[] actual, float delta, Deque indexes, + Object messageOrSupplier) { + + AssertionUtils.assertValidDelta(delta); + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (!AssertionUtils.floatsAreEqual(expected[i], actual[i], delta)) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(double[] expected, double[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (!AssertionUtils.doublesAreEqual(expected[i], actual[i])) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(double[] expected, double[] actual, double delta, Deque indexes, + Object messageOrSupplier) { + + AssertionUtils.assertValidDelta(delta); + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + if (!AssertionUtils.doublesAreEqual(expected[i], actual[i], delta)) { + failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); + } + } + } + + private static void assertArrayEquals(Object[] expected, Object[] actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == actual) { + return; + } + assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); + + for (int i = 0; i < expected.length; i++) { + Object expectedElement = expected[i]; + Object actualElement = actual[i]; + + if (expectedElement == actualElement) { + continue; + } + + indexes.addLast(i); + assertArrayElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier); + indexes.removeLast(); + } + } + + private static void assertArrayElementsEqual(Object expected, Object actual, Deque indexes, + Object messageOrSupplier) { + + if (expected instanceof Object[] && actual instanceof Object[]) { + assertArrayEquals((Object[]) expected, (Object[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof byte[] && actual instanceof byte[]) { + assertArrayEquals((byte[]) expected, (byte[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof short[] && actual instanceof short[]) { + assertArrayEquals((short[]) expected, (short[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof int[] && actual instanceof int[]) { + assertArrayEquals((int[]) expected, (int[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof long[] && actual instanceof long[]) { + assertArrayEquals((long[]) expected, (long[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof char[] && actual instanceof char[]) { + assertArrayEquals((char[]) expected, (char[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof float[] && actual instanceof float[]) { + assertArrayEquals((float[]) expected, (float[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof double[] && actual instanceof double[]) { + assertArrayEquals((double[]) expected, (double[]) actual, indexes, messageOrSupplier); + } + else if (expected instanceof boolean[] && actual instanceof boolean[]) { + assertArrayEquals((boolean[]) expected, (boolean[]) actual, indexes, messageOrSupplier); + } + else if (!Objects.equals(expected, actual)) { + if (expected == null && isArray(actual)) { + failExpectedArrayIsNull(indexes, messageOrSupplier); + } + else if (isArray(expected) && actual == null) { + failActualArrayIsNull(indexes, messageOrSupplier); + } + else { + failArraysNotEqual(expected, actual, indexes, messageOrSupplier); + } + } + } + + private static void assertArraysNotNull(Object expected, Object actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == null) { + failExpectedArrayIsNull(indexes, messageOrSupplier); + } + if (actual == null) { + failActualArrayIsNull(indexes, messageOrSupplier); + } + } + + private static void failExpectedArrayIsNull(Deque indexes, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected array was " + formatIndexes(indexes)) // + .buildAndThrow(); + } + + private static void failActualArrayIsNull(Deque indexes, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("actual array was " + formatIndexes(indexes)) // + .buildAndThrow(); + } + + private static void assertArraysHaveSameLength(int expected, int actual, Deque indexes, + Object messageOrSupplier) { + + if (expected != actual) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("array lengths differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } + } + + private static void failArraysNotEqual(Object expected, Object actual, Deque indexes, + Object messageOrSupplier) { + + assertionFailure() // + .message(messageOrSupplier) // + .reason("array contents differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } + + private static Deque nullSafeIndexes(Deque indexes, int newIndex) { + Deque result = (indexes != null ? indexes : new ArrayDeque<>()); + result.addLast(newIndex); + return result; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java new file mode 100644 index 00000000..2d424aa7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.opentest4j.AssertionFailedError; + +/** + * {@code AssertDoesNotThrow} is a collection of utility methods that support + * explicitly asserting that a given code block does not throw an exception. + * + * @since 5.2 + */ +class AssertDoesNotThrow { + + private AssertDoesNotThrow() { + /* no-op */ + } + + static void assertDoesNotThrow(Executable executable) { + assertDoesNotThrow(executable, (Object) null); + } + + static void assertDoesNotThrow(Executable executable, String message) { + assertDoesNotThrow(executable, (Object) message); + } + + static void assertDoesNotThrow(Executable executable, Supplier messageSupplier) { + assertDoesNotThrow(executable, (Object) messageSupplier); + } + + private static void assertDoesNotThrow(Executable executable, Object messageOrSupplier) { + try { + executable.execute(); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + throw createAssertionFailedError(messageOrSupplier, t); + } + } + + static T assertDoesNotThrow(ThrowingSupplier supplier) { + return assertDoesNotThrow(supplier, (Object) null); + } + + static T assertDoesNotThrow(ThrowingSupplier supplier, String message) { + return assertDoesNotThrow(supplier, (Object) message); + } + + static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier messageSupplier) { + return assertDoesNotThrow(supplier, (Object) messageSupplier); + } + + private static T assertDoesNotThrow(ThrowingSupplier supplier, Object messageOrSupplier) { + try { + return supplier.get(); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + throw createAssertionFailedError(messageOrSupplier, t); + } + } + + private static AssertionFailedError createAssertionFailedError(Object messageOrSupplier, Throwable t) { + return assertionFailure() // + .message(messageOrSupplier) // + .reason("Unexpected exception thrown: " + t.getClass().getName() + buildSuffix(t.getMessage())) // + .cause(t) // + .build(); + } + + private static String buildSuffix(String message) { + return StringUtils.isNotBlank(message) ? ": " + message : ""; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java new file mode 100644 index 00000000..2a46cea3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java @@ -0,0 +1,199 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; +import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; +import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; + +import java.util.function.Supplier; + +/** + * {@code AssertEquals} is a collection of utility methods that support asserting + * equality on objects and primitives in tests. + * + * @since 5.0 + */ +class AssertEquals { + + private AssertEquals() { + /* no-op */ + } + + static void assertEquals(byte expected, byte actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(byte expected, byte actual, String message) { + if (expected != actual) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(byte expected, byte actual, Supplier messageSupplier) { + if (expected != actual) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(char expected, char actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(char expected, char actual, String message) { + if (expected != actual) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(char expected, char actual, Supplier messageSupplier) { + if (expected != actual) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(double expected, double actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(double expected, double actual, String message) { + if (!doublesAreEqual(expected, actual)) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(double expected, double actual, Supplier messageSupplier) { + if (!doublesAreEqual(expected, actual)) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(double expected, double actual, double delta) { + assertEquals(expected, actual, delta, (String) null); + } + + static void assertEquals(double expected, double actual, double delta, String message) { + if (!doublesAreEqual(expected, actual, delta)) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(double expected, double actual, double delta, Supplier messageSupplier) { + if (!doublesAreEqual(expected, actual, delta)) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(float expected, float actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(float expected, float actual, String message) { + if (!floatsAreEqual(expected, actual)) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(float expected, float actual, Supplier messageSupplier) { + if (!floatsAreEqual(expected, actual)) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(float expected, float actual, float delta) { + assertEquals(expected, actual, delta, (String) null); + } + + static void assertEquals(float expected, float actual, float delta, String message) { + if (!floatsAreEqual(expected, actual, delta)) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(float expected, float actual, float delta, Supplier messageSupplier) { + if (!floatsAreEqual(expected, actual, delta)) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(short expected, short actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(short expected, short actual, String message) { + if (expected != actual) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(short expected, short actual, Supplier messageSupplier) { + if (expected != actual) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(int expected, int actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(int expected, int actual, String message) { + if (expected != actual) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(int expected, int actual, Supplier messageSupplier) { + if (expected != actual) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(long expected, long actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(long expected, long actual, String message) { + if (expected != actual) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(long expected, long actual, Supplier messageSupplier) { + if (expected != actual) { + failNotEqual(expected, actual, messageSupplier); + } + } + + static void assertEquals(Object expected, Object actual) { + assertEquals(expected, actual, (String) null); + } + + static void assertEquals(Object expected, Object actual, String message) { + if (!objectsAreEqual(expected, actual)) { + failNotEqual(expected, actual, message); + } + } + + static void assertEquals(Object expected, Object actual, Supplier messageSupplier) { + if (!objectsAreEqual(expected, actual)) { + failNotEqual(expected, actual, messageSupplier); + } + } + + private static void failNotEqual(Object expected, Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java new file mode 100644 index 00000000..5291a6ac --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +/** + * {@code AssertFalse} is a collection of utility methods that support asserting + * {@code false} in tests. + * + * @since 5.0 + */ +class AssertFalse { + + private AssertFalse() { + /* no-op */ + } + + static void assertFalse(boolean condition) { + assertFalse(condition, (String) null); + } + + static void assertFalse(boolean condition, String message) { + if (condition) { + failNotFalse(message); + } + } + + static void assertFalse(boolean condition, Supplier messageSupplier) { + if (condition) { + failNotFalse(messageSupplier); + } + } + + static void assertFalse(BooleanSupplier booleanSupplier) { + assertFalse(booleanSupplier.getAsBoolean(), (String) null); + } + + static void assertFalse(BooleanSupplier booleanSupplier, String message) { + assertFalse(booleanSupplier.getAsBoolean(), message); + } + + static void assertFalse(BooleanSupplier booleanSupplier, Supplier messageSupplier) { + assertFalse(booleanSupplier.getAsBoolean(), messageSupplier); + } + + private static void failNotFalse(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(false) // + .actual(true) // + .buildAndThrow(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java new file mode 100644 index 00000000..2f265f3f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.Supplier; + +/** + * {@code AssertInstanceOf} is a collection of utility methods that support + * asserting that an object is of an expected type — in other words, if it + * can be assigned to the expected type. + * + * @since 5.8 + */ +class AssertInstanceOf { + + private AssertInstanceOf() { + /* no-op */ + } + + static T assertInstanceOf(Class expectedType, Object actualValue) { + return assertInstanceOf(expectedType, actualValue, (Object) null); + } + + static T assertInstanceOf(Class expectedType, Object actualValue, String message) { + return assertInstanceOf(expectedType, actualValue, (Object) message); + } + + static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { + return assertInstanceOf(expectedType, actualValue, (Object) messageSupplier); + } + + private static T assertInstanceOf(Class expectedType, Object actualValue, Object messageOrSupplier) { + if (!expectedType.isInstance(actualValue)) { + assertionFailure() // + .message(messageOrSupplier) // + .reason(actualValue == null ? "Unexpected null value" : "Unexpected type") // + .expected(expectedType) // + .actual(actualValue == null ? null : actualValue.getClass()) // + .buildAndThrow(); + } + return expectedType.cast(actualValue); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java new file mode 100644 index 00000000..b837c449 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java @@ -0,0 +1,212 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.jupiter.api.AssertionUtils.formatIndexes; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +/** + * {@code AssertIterable} is a collection of utility methods that support asserting + * Iterable equality in tests. + * + * @since 5.0 + */ +class AssertIterableEquals { + + private AssertIterableEquals() { + /* no-op */ + } + + static void assertIterableEquals(Iterable expected, Iterable actual) { + assertIterableEquals(expected, actual, (String) null); + } + + static void assertIterableEquals(Iterable expected, Iterable actual, String message) { + assertIterableEquals(expected, actual, new ArrayDeque<>(), message); + } + + static void assertIterableEquals(Iterable expected, Iterable actual, Supplier messageSupplier) { + assertIterableEquals(expected, actual, new ArrayDeque<>(), messageSupplier); + } + + private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, + Object messageOrSupplier) { + assertIterableEquals(expected, actual, indexes, messageOrSupplier, new LinkedHashMap<>()); + } + + private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, + Object messageOrSupplier, Map investigatedElements) { + + if (expected == actual) { + return; + } + assertIterablesNotNull(expected, actual, indexes, messageOrSupplier); + + Iterator expectedIterator = expected.iterator(); + Iterator actualIterator = actual.iterator(); + + int processed = 0; + while (expectedIterator.hasNext() && actualIterator.hasNext()) { + Object expectedElement = expectedIterator.next(); + Object actualElement = actualIterator.next(); + + indexes.addLast(processed); + + assertIterableElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier, + investigatedElements); + + indexes.removeLast(); + processed++; + } + + assertIteratorsAreEmpty(expectedIterator, actualIterator, processed, indexes, messageOrSupplier); + } + + private static void assertIterableElementsEqual(Object expected, Object actual, Deque indexes, + Object messageOrSupplier, Map investigatedElements) { + + // If both are equal, we don't need to check recursively. + if (Objects.equals(expected, actual)) { + return; + } + + // If both are iterables, we need to check whether they contain the same elements. + if (expected instanceof Iterable && actual instanceof Iterable) { + + Pair pair = new Pair(expected, actual); + + // Before comparing their elements, we check whether we have already checked this pair. + Status status = investigatedElements.get(pair); + + // If we've already determined that both contain the same elements, we don't need to check them again. + if (status == Status.CONTAIN_SAME_ELEMENTS) { + return; + } + + // If the pair is already under investigation, we fail in order to avoid infinite recursion. + if (status == Status.UNDER_INVESTIGATION) { + indexes.removeLast(); + failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); + } + + // Otherwise, we put the pair under investigation and recurse. + investigatedElements.put(pair, Status.UNDER_INVESTIGATION); + + assertIterableEquals((Iterable) expected, (Iterable) actual, indexes, messageOrSupplier, + investigatedElements); + + // If we reach this point, we've checked that the two iterables contain the same elements so we store this information + // in case we come across the same pair again. + investigatedElements.put(pair, Status.CONTAIN_SAME_ELEMENTS); + } + + // Otherwise, they are neither equal nor iterables, so we fail. + else { + assertIterablesNotNull(expected, actual, indexes, messageOrSupplier); + failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); + } + } + + private static void assertIterablesNotNull(Object expected, Object actual, Deque indexes, + Object messageOrSupplier) { + + if (expected == null) { + failExpectedIterableIsNull(indexes, messageOrSupplier); + } + if (actual == null) { + failActualIterableIsNull(indexes, messageOrSupplier); + } + } + + private static void failExpectedIterableIsNull(Deque indexes, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected iterable was " + formatIndexes(indexes)) // + .buildAndThrow(); + } + + private static void failActualIterableIsNull(Deque indexes, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("actual iterable was " + formatIndexes(indexes)) // + .buildAndThrow(); + } + + private static void assertIteratorsAreEmpty(Iterator expected, Iterator actual, int processed, + Deque indexes, Object messageOrSupplier) { + + if (expected.hasNext() || actual.hasNext()) { + AtomicInteger expectedCount = new AtomicInteger(processed); + expected.forEachRemaining(e -> expectedCount.incrementAndGet()); + + AtomicInteger actualCount = new AtomicInteger(processed); + actual.forEachRemaining(e -> actualCount.incrementAndGet()); + + assertionFailure() // + .message(messageOrSupplier) // + .reason("iterable lengths differ" + formatIndexes(indexes)) // + .expected(expectedCount.get()) // + .actual(actualCount.get()) // + .buildAndThrow(); + } + } + + private static void failIterablesNotEqual(Object expected, Object actual, Deque indexes, + Object messageOrSupplier) { + + assertionFailure() // + .message(messageOrSupplier) // + .reason("iterable contents differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } + + private final static class Pair { + private final Object left; + private final Object right; + + public Pair(Object left, Object right) { + this.left = left; + this.right = right; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Pair that = (Pair) o; + return Objects.equals(this.left, that.left) // + && Objects.equals(this.right, that.right); + } + + @Override + public int hashCode() { + return Objects.hash(left, right); + } + } + + private enum Status { + UNDER_INVESTIGATION, CONTAIN_SAME_ELEMENTS + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java new file mode 100644 index 00000000..03e79081 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java @@ -0,0 +1,239 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.lang.String.format; +import static java.lang.String.join; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.platform.commons.util.Preconditions.condition; +import static org.junit.platform.commons.util.Preconditions.notNull; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * {@code AssertLinesMatch} is a collection of utility methods that support asserting + * lines of {@link String} equality or {@link java.util.regex.Pattern}-match in tests. + * + * @since 5.0 + */ +class AssertLinesMatch { + + private AssertLinesMatch() { + /* no-op */ + } + + private static final int MAX_SNIPPET_LENGTH = 21; + + static void assertLinesMatch(List expectedLines, List actualLines) { + assertLinesMatch(expectedLines, actualLines, (Object) null); + } + + static void assertLinesMatch(List expectedLines, List actualLines, String message) { + assertLinesMatch(expectedLines, actualLines, (Object) message); + } + + static void assertLinesMatch(Stream expectedLines, Stream actualLines) { + assertLinesMatch(expectedLines, actualLines, (Object) null); + } + + static void assertLinesMatch(Stream expectedLines, Stream actualLines, String message) { + assertLinesMatch(expectedLines, actualLines, (Object) message); + } + + static void assertLinesMatch(Stream expectedLines, Stream actualLines, Object messageOrSupplier) { + notNull(expectedLines, "expectedLines must not be null"); + notNull(actualLines, "actualLines must not be null"); + + // trivial case: same stream instance + if (expectedLines == actualLines) { + return; + } + + List expectedListOfStrings = expectedLines.collect(Collectors.toList()); + List actualListOfStrings = actualLines.collect(Collectors.toList()); + assertLinesMatch(expectedListOfStrings, actualListOfStrings, messageOrSupplier); + } + + static void assertLinesMatch(List expectedLines, List actualLines, Object messageOrSupplier) { + notNull(expectedLines, "expectedLines must not be null"); + notNull(actualLines, "actualLines must not be null"); + + // trivial case: same list instance + if (expectedLines == actualLines) { + return; + } + + new LinesMatcher(expectedLines, actualLines, messageOrSupplier).assertLinesMatch(); + } + + private static class LinesMatcher { + + private final List expectedLines; + private final List actualLines; + private final Object messageOrSupplier; + + LinesMatcher(List expectedLines, List actualLines, Object messageOrSupplier) { + this.expectedLines = expectedLines; + this.actualLines = actualLines; + this.messageOrSupplier = messageOrSupplier; + } + + void assertLinesMatch() { + int expectedSize = expectedLines.size(); + int actualSize = actualLines.size(); + + // trivial case: when expecting more than actual lines available, something is wrong + if (expectedSize > actualSize) { + fail("expected %d lines, but only got %d", expectedSize, actualSize); + } + + // simple case: both list are equally sized, compare them line-by-line + if (expectedSize == actualSize) { + if (IntStream.range(0, expectedSize).allMatch(i -> matches(expectedLines.get(i), actualLines.get(i)))) { + return; + } + // else fall-through to "with fast-forward" matching + } + + assertLinesMatchWithFastForward(); + } + + void assertLinesMatchWithFastForward() { + Deque expectedDeque = new ArrayDeque<>(expectedLines); + Deque actualDeque = new ArrayDeque<>(actualLines); + + main: while (!expectedDeque.isEmpty()) { + String expectedLine = expectedDeque.pop(); + int expectedLineNumber = expectedLines.size() - expectedDeque.size(); // 1-based line number + // trivial case: no more actual lines available + if (actualDeque.isEmpty()) { + fail("expected line #%d:`%s` not found - actual lines depleted", expectedLineNumber, + snippet(expectedLine)); + } + + String actualLine = actualDeque.peek(); + // trivial case: take the fast path when they match + if (matches(expectedLine, actualLine)) { + actualDeque.pop(); + continue; // main + } + + // fast-forward marker found in expected line: fast-forward actual line... + if (isFastForwardLine(expectedLine)) { + int fastForwardLimit = parseFastForwardLimit(expectedLine); + int actualRemaining = actualDeque.size(); + + // trivial case: fast-forward marker was in last expected line + if (expectedDeque.isEmpty()) { + // no limit given or perfect match? we're done. + if (fastForwardLimit == Integer.MAX_VALUE || fastForwardLimit == actualRemaining) { + return; + } + fail("terminal fast-forward(%d) error: fast-forward(%d) expected", fastForwardLimit, + actualRemaining); + } + + // fast-forward limit was given: use it + if (fastForwardLimit != Integer.MAX_VALUE) { + if (actualRemaining < fastForwardLimit) { + fail("fast-forward(%d) error: not enough actual lines remaining (%s)", fastForwardLimit, + actualRemaining); + } + // fast-forward now: actualDeque.pop(fastForwardLimit) + for (int i = 0; i < fastForwardLimit; i++) { + actualDeque.pop(); + } + continue; // main + } + + // peek next expected line + expectedLine = expectedDeque.peek(); + // fast-forward "unlimited": until next match + while (true) { + if (actualDeque.isEmpty()) { + fail("fast-forward(∞) didn't find: `%s`", snippet(expectedLine)); + } + if (matches(expectedLine, actualDeque.peek())) { + continue main; + } + actualDeque.pop(); + } + } + + int actualLineNumber = actualLines.size() - actualDeque.size() + 1; // 1-based line number + fail("expected line #%d doesn't match actual line #%d%n" + "\texpected: `%s`%n" + "\t actual: `%s`", + expectedLineNumber, actualLineNumber, expectedLine, actualLine); + } + + // after math + if (!actualDeque.isEmpty()) { + fail("more actual lines than expected: %d", actualDeque.size()); + } + } + + String snippet(String line) { + if (line.length() <= MAX_SNIPPET_LENGTH) { + return line; + } + return line.substring(0, MAX_SNIPPET_LENGTH - 5) + "[...]"; + } + + void fail(String format, Object... args) { + String newLine = System.lineSeparator(); + assertionFailure() // + .message(messageOrSupplier) // + .reason(format(format, args)) // + .expected(join(newLine, expectedLines)) // + .actual(join(newLine, actualLines)) // + .includeValuesInMessage(false) // + .buildAndThrow(); + } + } + + static boolean isFastForwardLine(String line) { + line = line.trim(); + return line.length() >= 4 && line.startsWith(">>") && line.endsWith(">>"); + } + + static int parseFastForwardLimit(String fastForwardLine) { + fastForwardLine = fastForwardLine.trim(); + String text = fastForwardLine.substring(2, fastForwardLine.length() - 2).trim(); + try { + int limit = Integer.parseInt(text); + condition(limit > 0, () -> format("fast-forward(%d) limit must be greater than zero", limit)); + return limit; + } + catch (NumberFormatException e) { + return Integer.MAX_VALUE; + } + } + + static boolean matches(String expectedLine, String actualLine) { + notNull(expectedLine, "expected line must not be null"); + notNull(actualLine, "actual line must not be null"); + if (expectedLine.equals(actualLine)) { + return true; + } + try { + return actualLine.matches(expectedLine); + } + catch (PatternSyntaxException ignore) { + return false; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java new file mode 100644 index 00000000..5a37b059 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java @@ -0,0 +1,280 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; +import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; +import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; + +import java.util.function.Supplier; + +/** + * {@code AssertNotEquals} is a collection of utility methods that support asserting + * inequality in objects and primitive values in tests. + * + * @since 5.0 + */ +class AssertNotEquals { + + private AssertNotEquals() { + /* no-op */ + } + + /** + * @since 5.4 + */ + static void assertNotEquals(byte unexpected, byte actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(byte unexpected, byte actual, String message) { + if (unexpected == actual) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { + if (unexpected == actual) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(short unexpected, short actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(short unexpected, short actual, String message) { + if (unexpected == actual) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { + if (unexpected == actual) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(int unexpected, int actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(int unexpected, int actual, String message) { + if (unexpected == actual) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { + if (unexpected == actual) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(long unexpected, long actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(long unexpected, long actual, String message) { + if (unexpected == actual) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { + if (unexpected == actual) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(float unexpected, float actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(float unexpected, float actual, String message) { + if (floatsAreEqual(unexpected, actual)) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { + if (floatsAreEqual(unexpected, actual)) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(float unexpected, float actual, float delta) { + assertNotEquals(unexpected, actual, delta, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(float unexpected, float actual, float delta, String message) { + if (floatsAreEqual(unexpected, actual, delta)) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { + if (floatsAreEqual(unexpected, actual, delta)) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(double unexpected, double actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(double unexpected, double actual, String message) { + if (doublesAreEqual(unexpected, actual)) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { + if (doublesAreEqual(unexpected, actual)) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(double unexpected, double actual, double delta) { + assertNotEquals(unexpected, actual, delta, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(double unexpected, double actual, double delta, String message) { + if (doublesAreEqual(unexpected, actual, delta)) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(double unexpected, double actual, double delta, Supplier messageSupplier) { + if (doublesAreEqual(unexpected, actual, delta)) { + failEqual(actual, messageSupplier); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(char unexpected, char actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + /** + * @since 5.4 + */ + static void assertNotEquals(char unexpected, char actual, String message) { + if (unexpected == actual) { + failEqual(actual, message); + } + } + + /** + * @since 5.4 + */ + static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { + if (unexpected == actual) { + failEqual(actual, messageSupplier); + } + } + + static void assertNotEquals(Object unexpected, Object actual) { + assertNotEquals(unexpected, actual, (String) null); + } + + static void assertNotEquals(Object unexpected, Object actual, String message) { + if (objectsAreEqual(unexpected, actual)) { + failEqual(actual, message); + } + } + + static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { + if (objectsAreEqual(unexpected, actual)) { + failEqual(actual, messageSupplier); + } + } + + private static void failEqual(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not equal but was: <" + actual + ">") // + .buildAndThrow(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java new file mode 100644 index 00000000..43787ab8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.Supplier; + +/** + * {@code AssertNotNull} is a collection of utility methods that support asserting + * that there is an object. + * + * @since 5.0 + */ +class AssertNotNull { + + private AssertNotNull() { + /* no-op */ + } + + static void assertNotNull(Object actual) { + assertNotNull(actual, (String) null); + } + + static void assertNotNull(Object actual, String message) { + if (actual == null) { + failNull(message); + } + } + + static void assertNotNull(Object actual, Supplier messageSupplier) { + if (actual == null) { + failNull(messageSupplier); + } + } + + private static void failNull(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not ") // + .buildAndThrow(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java new file mode 100644 index 00000000..66f795c7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.Supplier; + +/** + * {@code AssertNotSame} is a collection of utility methods that support asserting + * two objects are not the same. + * + * @since 5.0 + */ +class AssertNotSame { + + private AssertNotSame() { + /* no-op */ + } + + static void assertNotSame(Object unexpected, Object actual) { + assertNotSame(unexpected, actual, (String) null); + } + + static void assertNotSame(Object unexpected, Object actual, String message) { + if (unexpected == actual) { + failSame(actual, message); + } + } + + static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { + if (unexpected == actual) { + failSame(actual, messageSupplier); + } + } + + private static void failSame(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not same but was: <" + actual + ">") // + .buildAndThrow(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java new file mode 100644 index 00000000..53ceb4ce --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.Supplier; + +/** + * {@code AssertNull} is a collection of utility methods that support asserting + * there is no object. + * + * @since 5.0 + */ +class AssertNull { + + private AssertNull() { + /* no-op */ + } + + static void assertNull(Object actual) { + assertNull(actual, (String) null); + } + + static void assertNull(Object actual, String message) { + if (actual != null) { + failNotNull(actual, message); + } + } + + static void assertNull(Object actual, Supplier messageSupplier) { + if (actual != null) { + failNotNull(actual, messageSupplier); + } + } + + private static void failNotNull(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(null) // + .actual(actual) // + .buildAndThrow(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java new file mode 100644 index 00000000..feafa362 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.Supplier; + +/** + * {@code AssertSame} is a collection of utility methods that support asserting + * two objects are the same. + * + * @since 5.0 + */ +class AssertSame { + + private AssertSame() { + /* no-op */ + } + + static void assertSame(Object expected, Object actual) { + assertSame(expected, actual, (String) null); + } + + static void assertSame(Object expected, Object actual, String message) { + if (expected != actual) { + failNotSame(expected, actual, message); + } + } + + static void assertSame(Object expected, Object actual, Supplier messageSupplier) { + if (expected != actual) { + failNotSame(expected, actual, messageSupplier); + } + } + + private static void failNotSame(Object expected, Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java new file mode 100644 index 00000000..b6157064 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.lang.String.format; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * {@code AssertThrows} is a collection of utility methods that support asserting + * an exception of an expected type is thrown. + * + * @since 5.0 + */ +class AssertThrows { + + private AssertThrows() { + /* no-op */ + } + + static T assertThrows(Class expectedType, Executable executable) { + return assertThrows(expectedType, executable, (Object) null); + } + + static T assertThrows(Class expectedType, Executable executable, String message) { + return assertThrows(expectedType, executable, (Object) message); + } + + static T assertThrows(Class expectedType, Executable executable, + Supplier messageSupplier) { + + return assertThrows(expectedType, executable, (Object) messageSupplier); + } + + @SuppressWarnings("unchecked") + private static T assertThrows(Class expectedType, Executable executable, + Object messageOrSupplier) { + + try { + executable.execute(); + } + catch (Throwable actualException) { + if (expectedType.isInstance(actualException)) { + return (T) actualException; + } + else { + UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); + throw assertionFailure() // + .message(messageOrSupplier) // + .expected(expectedType) // + .actual(actualException.getClass()) // + .reason("Unexpected exception type thrown") // + .cause(actualException) // + .build(); + } + } + throw assertionFailure() // + .message(messageOrSupplier) // + .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // + .build(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java new file mode 100644 index 00000000..8a669388 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.lang.String.format; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * {@code AssertThrowsExactly} is a collection of utility methods that support asserting + * an exception of an exact type is thrown. + * + * @since 5.8 + */ +class AssertThrowsExactly { + + private AssertThrowsExactly() { + /* no-op */ + } + + static T assertThrowsExactly(Class expectedType, Executable executable) { + return assertThrowsExactly(expectedType, executable, (Object) null); + } + + static T assertThrowsExactly(Class expectedType, Executable executable, String message) { + return assertThrowsExactly(expectedType, executable, (Object) message); + } + + static T assertThrowsExactly(Class expectedType, Executable executable, + Supplier messageSupplier) { + + return assertThrowsExactly(expectedType, executable, (Object) messageSupplier); + } + + @SuppressWarnings("unchecked") + private static T assertThrowsExactly(Class expectedType, Executable executable, + Object messageOrSupplier) { + + try { + executable.execute(); + } + catch (Throwable actualException) { + if (expectedType.equals(actualException.getClass())) { + return (T) actualException; + } + else { + UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); + throw assertionFailure() // + .message(messageOrSupplier) // + .expected(expectedType) // + .actual(actualException.getClass()) // + .reason("Unexpected exception type thrown") // + .cause(actualException) // + .build(); + } + } + + throw assertionFailure() // + .message(messageOrSupplier) // + .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // + .build(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java new file mode 100644 index 00000000..bfd1f668 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; + +import java.time.Duration; +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingSupplier; + +/** + * {@code AssertTimeout} is a collection of utility methods that support asserting + * the execution of the code under test did not take longer than the timeout duration. + * + * @since 5.0 + */ +class AssertTimeout { + + private AssertTimeout() { + /* no-op */ + } + + static void assertTimeout(Duration timeout, Executable executable) { + assertTimeout(timeout, executable, (String) null); + } + + static void assertTimeout(Duration timeout, Executable executable, String message) { + assertTimeout(timeout, () -> { + executable.execute(); + return null; + }, message); + } + + static void assertTimeout(Duration timeout, Executable executable, Supplier messageSupplier) { + assertTimeout(timeout, () -> { + executable.execute(); + return null; + }, messageSupplier); + } + + static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { + return assertTimeout(timeout, supplier, (Object) null); + } + + static T assertTimeout(Duration timeout, ThrowingSupplier supplier, String message) { + return assertTimeout(timeout, supplier, (Object) message); + } + + static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { + return assertTimeout(timeout, supplier, (Object) messageSupplier); + } + + private static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { + long timeoutInMillis = timeout.toMillis(); + long start = System.currentTimeMillis(); + T result = null; + try { + result = supplier.get(); + } + catch (Throwable ex) { + throwAsUncheckedException(ex); + } + + long timeElapsed = System.currentTimeMillis() - start; + if (timeElapsed > timeoutInMillis) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("execution exceeded timeout of " + timeoutInMillis + " ms by " + + (timeElapsed - timeoutInMillis) + " ms") // + .buildAndThrow(); + } + return result; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java new file mode 100644 index 00000000..4ff96b71 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; + +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.platform.commons.JUnitException; +import org.opentest4j.AssertionFailedError; + +/** + * {@code AssertTimeout} is a collection of utility methods that support asserting + * the execution of the code under test did not take longer than the timeout duration + * using a preemptive approach. + * + * @since 5.9.1 + */ +class AssertTimeoutPreemptively { + + static void assertTimeoutPreemptively(Duration timeout, Executable executable) { + assertTimeoutPreemptively(timeout, executable, (String) null); + } + + static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { + assertTimeoutPreemptively(timeout, () -> { + executable.execute(); + return null; + }, message); + } + + static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { + assertTimeoutPreemptively(timeout, () -> { + executable.execute(); + return null; + }, messageSupplier); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { + return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { + return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, + AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier) { + return assertTimeoutPreemptively(timeout, supplier, messageSupplier, + AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier, Assertions.TimeoutFailureFactory failureFactory) throws E { + AtomicReference threadReference = new AtomicReference<>(); + ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); + + try { + Future future = submitTask(supplier, threadReference, executorService); + return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, + failureFactory); + } + finally { + executorService.shutdownNow(); + } + } + + private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, + ExecutorService executorService) { + return executorService.submit(() -> { + try { + threadReference.set(Thread.currentThread()); + return supplier.get(); + } + catch (Throwable throwable) { + throw throwAsUncheckedException(throwable); + } + }); + } + + private static T resolveFutureAndHandleException(Future future, Duration timeout, + Supplier messageSupplier, Supplier threadSupplier, + Assertions.TimeoutFailureFactory failureFactory) throws E { + try { + return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } + catch (TimeoutException ex) { + Thread thread = threadSupplier.get(); + ExecutionTimeoutException cause = null; + if (thread != null) { + cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); + cause.setStackTrace(thread.getStackTrace()); + } + throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); + } + catch (ExecutionException ex) { + throw throwAsUncheckedException(ex.getCause()); + } + catch (Throwable ex) { + throw throwAsUncheckedException(ex); + } + } + + private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, + Throwable cause) { + return assertionFailure() // + .message(messageSupplier) // + .reason("execution timed out after " + timeout.toMillis() + " ms") // + .cause(cause) // + .build(); + } + + private static class ExecutionTimeoutException extends JUnitException { + + private static final long serialVersionUID = 1L; + + ExecutionTimeoutException(String message) { + super(message); + } + } + + /** + * The thread factory used for preemptive timeout. + *

+ * The factory creates threads with meaningful names, helpful for debugging purposes. + */ + private static class TimeoutThreadFactory implements ThreadFactory { + private static final AtomicInteger threadNumber = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java new file mode 100644 index 00000000..cf4f9427 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; + +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +/** + * {@code AssertTrue} is a collection of utility methods that support asserting + * {@code true} in tests. + * + * @since 5.0 + */ +class AssertTrue { + + private AssertTrue() { + /* no-op */ + } + + static void assertTrue(boolean condition) { + assertTrue(condition, (String) null); + } + + static void assertTrue(boolean condition, String message) { + if (!condition) { + failNotTrue(message); + } + } + + static void assertTrue(boolean condition, Supplier messageSupplier) { + if (!condition) { + failNotTrue(messageSupplier); + } + } + + static void assertTrue(BooleanSupplier booleanSupplier) { + assertTrue(booleanSupplier.getAsBoolean(), (String) null); + } + + static void assertTrue(BooleanSupplier booleanSupplier, String message) { + assertTrue(booleanSupplier.getAsBoolean(), message); + } + + static void assertTrue(BooleanSupplier booleanSupplier, Supplier messageSupplier) { + assertTrue(booleanSupplier.getAsBoolean(), messageSupplier); + } + + private static void failNotTrue(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(true) // + .actual(false) // + .buildAndThrow(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java new file mode 100644 index 00000000..8d0341d3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java @@ -0,0 +1,205 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; + +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.StringUtils; +import org.opentest4j.AssertionFailedError; + +/** + * Builder for {@link AssertionFailedError AssertionFailedErrors}. + *

+ * Using this builder ensures consistency in how failure message are formatted + * within JUnit Jupiter and for custom user-defined assertions. + * + * @since 5.9 + * @see AssertionFailedError + */ +@API(status = STABLE, since = "5.9") +public class AssertionFailureBuilder { + + private Object message; + private Throwable cause; + private boolean mismatch; + private Object expected; + private Object actual; + private String reason; + private boolean includeValuesInMessage = true; + + /** + * Create a new {@code AssertionFailureBuilder}. + */ + public static AssertionFailureBuilder assertionFailure() { + return new AssertionFailureBuilder(); + } + + private AssertionFailureBuilder() { + } + + /** + * Set the user-defined message of the assertion. + *

+ * The {@code message} may be passed as a {@link Supplier} or plain + * {@link String}. If any other type is passed, it is converted to + * {@code String} as per {@link StringUtils#nullSafeToString(Object)}. + * + * @param message the user-defined failure message; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder message(Object message) { + this.message = message; + return this; + } + + /** + * Set the reason why the assertion failed. + * + * @param reason the failure reason; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder reason(String reason) { + this.reason = reason; + return this; + } + + /** + * Set the cause of the assertion failure. + * + * @param cause the failure cause; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder cause(Throwable cause) { + this.cause = cause; + return this; + } + + /** + * Set the expected value of the assertion. + * + * @param expected the expected value; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder expected(Object expected) { + this.mismatch = true; + this.expected = expected; + return this; + } + + /** + * Set the actual value of the assertion. + * + * @param actual the actual value; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder actual(Object actual) { + this.mismatch = true; + this.actual = actual; + return this; + } + + /** + * Set whether to include the actual and expected values in the generated + * failure message. + * + * @param includeValuesInMessage whether to include the actual and expected + * values + * @return this builder for method chaining + */ + public AssertionFailureBuilder includeValuesInMessage(boolean includeValuesInMessage) { + this.includeValuesInMessage = includeValuesInMessage; + return this; + } + + /** + * Build the {@link AssertionFailedError AssertionFailedError} and throw it. + * + * @throws AssertionFailedError always + */ + public void buildAndThrow() throws AssertionFailedError { + throw build(); + } + + /** + * Build the {@link AssertionFailedError AssertionFailedError} without + * throwing it. + * + * @return the built assertion failure + */ + public AssertionFailedError build() { + String reason = nullSafeGet(this.reason); + if (mismatch && includeValuesInMessage) { + reason = (reason == null ? "" : reason + ", ") + formatValues(expected, actual); + } + String message = nullSafeGet(this.message); + if (reason != null) { + message = buildPrefix(message) + reason; + } + return mismatch // + ? new AssertionFailedError(message, expected, actual, cause) // + : new AssertionFailedError(message, cause); + } + + private static String nullSafeGet(Object messageOrSupplier) { + if (messageOrSupplier == null) { + return null; + } + if (messageOrSupplier instanceof Supplier) { + Object message = ((Supplier) messageOrSupplier).get(); + return StringUtils.nullSafeToString(message); + } + return StringUtils.nullSafeToString(messageOrSupplier); + } + + private static String buildPrefix(String message) { + return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); + } + + private static String formatValues(Object expected, Object actual) { + String expectedString = toString(expected); + String actualString = toString(actual); + if (expectedString.equals(actualString)) { + return String.format("expected: %s but was: %s", formatClassAndValue(expected, expectedString), + formatClassAndValue(actual, actualString)); + } + return String.format("expected: <%s> but was: <%s>", expectedString, actualString); + } + + private static String formatClassAndValue(Object value, String valueString) { + // If the value is null, return instead of null. + if (value == null) { + return ""; + } + String classAndHash = getClassName(value) + toHash(value); + // if it's a class, there's no need to repeat the class name contained in the valueString. + return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); + } + + private static String toString(Object obj) { + if (obj instanceof Class) { + return getCanonicalName((Class) obj); + } + return StringUtils.nullSafeToString(obj); + } + + private static String toHash(Object obj) { + return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); + } + + private static String getClassName(Object obj) { + return (obj == null ? "null" + : obj instanceof Class ? getCanonicalName((Class) obj) : obj.getClass().getName()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java new file mode 100644 index 00000000..48a74da2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.util.stream.Collectors.joining; + +import java.util.Deque; +import java.util.function.Supplier; + +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.opentest4j.AssertionFailedError; + +/** + * {@code AssertionUtils} is a collection of utility methods that are common to + * all assertion implementations. + * + * @since 5.0 + */ +class AssertionUtils { + + private AssertionUtils() { + /* no-op */ + } + + static void fail() { + throw new AssertionFailedError(); + } + + static void fail(String message) { + throw new AssertionFailedError(message); + } + + static void fail(String message, Throwable cause) { + throw new AssertionFailedError(message, cause); + } + + static void fail(Throwable cause) { + throw new AssertionFailedError(null, cause); + } + + static void fail(Supplier messageSupplier) { + throw new AssertionFailedError(nullSafeGet(messageSupplier)); + } + + static String nullSafeGet(Supplier messageSupplier) { + return (messageSupplier != null ? messageSupplier.get() : null); + } + + static String getCanonicalName(Class clazz) { + try { + String canonicalName = clazz.getCanonicalName(); + return (canonicalName != null ? canonicalName : clazz.getName()); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + return clazz.getName(); + } + } + + static String formatIndexes(Deque indexes) { + if (indexes == null || indexes.isEmpty()) { + return ""; + } + String indexesString = indexes.stream().map(Object::toString).collect(joining("][", "[", "]")); + return " at index " + indexesString; + } + + static boolean floatsAreEqual(float value1, float value2, float delta) { + assertValidDelta(delta); + return floatsAreEqual(value1, value2) || Math.abs(value1 - value2) <= delta; + } + + static void assertValidDelta(float delta) { + if (Float.isNaN(delta) || delta < 0.0) { + failIllegalDelta(String.valueOf(delta)); + } + } + + static void assertValidDelta(double delta) { + if (Double.isNaN(delta) || delta < 0.0) { + failIllegalDelta(String.valueOf(delta)); + } + } + + static boolean floatsAreEqual(float value1, float value2) { + return Float.floatToIntBits(value1) == Float.floatToIntBits(value2); + } + + static boolean doublesAreEqual(double value1, double value2, double delta) { + assertValidDelta(delta); + return doublesAreEqual(value1, value2) || Math.abs(value1 - value2) <= delta; + } + + static boolean doublesAreEqual(double value1, double value2) { + return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2); + } + + static boolean objectsAreEqual(Object obj1, Object obj2) { + if (obj1 == null) { + return (obj2 == null); + } + return obj1.equals(obj2); + } + + private static void failIllegalDelta(String delta) { + fail("positive delta expected but was: <" + delta + ">"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java new file mode 100644 index 00000000..1240caa5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java @@ -0,0 +1,3625 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.opentest4j.MultipleFailuresError; + +/** + * {@code Assertions} is a collection of utility methods that support asserting + * conditions in tests. + * + *

Unless otherwise noted, a failed assertion will throw an + * {@link org.opentest4j.AssertionFailedError} or a subclass thereof. + * + *

Object Equality

+ * + *

Assertion methods comparing two objects for equality, such as the + * {@code assertEquals(expected, actual)} and {@code assertNotEquals(unexpected, actual)} + * variants, are only intended to test equality for an (un-)expected value + * and an actual value. They are not designed for testing whether a class correctly + * implements {@link Object#equals(Object)}. For example, {@code assertEquals()} + * might immediately return {@code true} when provided the same object for the + * expected and actual values, without calling {@code equals(Object)} at all. + * Tests that aim to verify the {@code equals(Object)} implementation should instead + * be written to explicitly verify the {@link Object#equals(Object)} contract by + * using {@link #assertTrue(boolean) assertTrue()} or {@link #assertFalse(boolean) + * assertFalse()} — for example, {@code assertTrue(expected.equals(actual))}, + * {@code assertTrue(actual.equals(expected))}, {@code assertFalse(expected.equals(null))}, + * etc. + * + *

Kotlin Support

+ * + *

Additional Kotlin assertions can be + * found as top-level functions in the {@link org.junit.jupiter.api} + * package. + * + *

Preemptive Timeouts

+ * + *

The various {@code assertTimeoutPreemptively()} methods in this class + * execute the provided {@code executable} or {@code supplier} in a different + * thread than that of the calling code. This behavior can lead to undesirable + * side effects if the code that is executed within the {@code executable} or + * {@code supplier} relies on {@link ThreadLocal} storage. + * + *

One common example of this is the transactional testing support in the Spring + * Framework. Specifically, Spring's testing support binds transaction state to + * the current thread (via a {@code ThreadLocal}) before a test method is invoked. + * Consequently, if an {@code executable} or {@code supplier} provided to + * {@code assertTimeoutPreemptively()} invokes Spring-managed components that + * participate in transactions, any actions taken by those components will not be + * rolled back with the test-managed transaction. On the contrary, such actions + * will be committed to the persistent store (e.g., relational database) even + * though the test-managed transaction is rolled back. + * + *

Similar side effects may be encountered with other frameworks that rely on + * {@code ThreadLocal} storage. + * + *

Extensibility

+ * + *

Although it is technically possible to extend this class, extension is + * strongly discouraged. The JUnit Team highly recommends that the methods + * defined in this class be used via static imports. + * + * @since 5.0 + * @see org.opentest4j.AssertionFailedError + * @see Assumptions + */ +@API(status = STABLE, since = "5.0") +public class Assertions { + + /** + * Protected constructor allowing subclassing but not direct instantiation. + * + * @since 5.3 + */ + @API(status = STABLE, since = "5.3") + protected Assertions() { + /* no-op */ + } + + // --- fail ---------------------------------------------------------------- + + /** + * Fail the test without a failure message. + * + *

Although failing with an explicit failure message is recommended, + * this method may be useful when maintaining legacy code. + * + *

See Javadoc for {@link #fail(String)} for an explanation of this method's + * generic return type {@code V}. + */ + public static V fail() { + AssertionUtils.fail(); + return null; // appeasing the compiler: this line will never be executed. + } + + /** + * Fail the test with the given failure {@code message}. + * + *

The generic return type {@code V} allows this method to be used + * directly as a single-statement lambda expression, thereby avoiding the + * need to implement a code block with an explicit return value. Since this + * method throws an {@link org.opentest4j.AssertionFailedError} before its + * return statement, this method never actually returns a value to its caller. + * The following example demonstrates how this may be used in practice. + * + *

{@code
+	 * Stream.of().map(entry -> fail("should not be called"));
+	 * }
+ */ + public static V fail(String message) { + AssertionUtils.fail(message); + return null; // appeasing the compiler: this line will never be executed. + } + + /** + * Fail the test with the given failure {@code message} as well + * as the underlying {@code cause}. + * + *

See Javadoc for {@link #fail(String)} for an explanation of this method's + * generic return type {@code V}. + */ + public static V fail(String message, Throwable cause) { + AssertionUtils.fail(message, cause); + return null; // appeasing the compiler: this line will never be executed. + } + + /** + * Fail the test with the given underlying {@code cause}. + * + *

See Javadoc for {@link #fail(String)} for an explanation of this method's + * generic return type {@code V}. + */ + public static V fail(Throwable cause) { + AssertionUtils.fail(cause); + return null; // appeasing the compiler: this line will never be executed. + } + + /** + * Fail the test with the failure message retrieved from the + * given {@code messageSupplier}. + * + *

See Javadoc for {@link #fail(String)} for an explanation of this method's + * generic return type {@code V}. + */ + public static V fail(Supplier messageSupplier) { + AssertionUtils.fail(messageSupplier); + return null; // appeasing the compiler: this line will never be executed. + } + + // --- assertTrue ---------------------------------------------------------- + + /** + * Assert that the supplied {@code condition} is {@code true}. + */ + public static void assertTrue(boolean condition) { + AssertTrue.assertTrue(condition); + } + + /** + * Assert that the supplied {@code condition} is {@code true}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertTrue(boolean condition, Supplier messageSupplier) { + AssertTrue.assertTrue(condition, messageSupplier); + } + + /** + * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. + */ + public static void assertTrue(BooleanSupplier booleanSupplier) { + AssertTrue.assertTrue(booleanSupplier); + } + + /** + * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertTrue(BooleanSupplier booleanSupplier, String message) { + AssertTrue.assertTrue(booleanSupplier, message); + } + + /** + * Assert that the supplied {@code condition} is {@code true}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertTrue(boolean condition, String message) { + AssertTrue.assertTrue(condition, message); + } + + /** + * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertTrue(BooleanSupplier booleanSupplier, Supplier messageSupplier) { + AssertTrue.assertTrue(booleanSupplier, messageSupplier); + } + + // --- assertFalse --------------------------------------------------------- + + /** + * Assert that the supplied {@code condition} is {@code false}. + */ + public static void assertFalse(boolean condition) { + AssertFalse.assertFalse(condition); + } + + /** + * Assert that the supplied {@code condition} is {@code false}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertFalse(boolean condition, String message) { + AssertFalse.assertFalse(condition, message); + } + + /** + * Assert that the supplied {@code condition} is {@code false}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertFalse(boolean condition, Supplier messageSupplier) { + AssertFalse.assertFalse(condition, messageSupplier); + } + + /** + * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. + */ + public static void assertFalse(BooleanSupplier booleanSupplier) { + AssertFalse.assertFalse(booleanSupplier); + } + + /** + * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertFalse(BooleanSupplier booleanSupplier, String message) { + AssertFalse.assertFalse(booleanSupplier, message); + } + + /** + * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertFalse(BooleanSupplier booleanSupplier, Supplier messageSupplier) { + AssertFalse.assertFalse(booleanSupplier, messageSupplier); + } + + // --- assertNull ---------------------------------------------------------- + + /** + * Assert that {@code actual} is {@code null}. + */ + public static void assertNull(Object actual) { + AssertNull.assertNull(actual); + } + + /** + * Assert that {@code actual} is {@code null}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertNull(Object actual, String message) { + AssertNull.assertNull(actual, message); + } + + /** + * Assert that {@code actual} is {@code null}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertNull(Object actual, Supplier messageSupplier) { + AssertNull.assertNull(actual, messageSupplier); + } + + // --- assertNotNull ------------------------------------------------------- + + /** + * Assert that {@code actual} is not {@code null}. + */ + public static void assertNotNull(Object actual) { + AssertNotNull.assertNotNull(actual); + } + + /** + * Assert that {@code actual} is not {@code null}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertNotNull(Object actual, String message) { + AssertNotNull.assertNotNull(actual, message); + } + + /** + * Assert that {@code actual} is not {@code null}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertNotNull(Object actual, Supplier messageSupplier) { + AssertNotNull.assertNotNull(actual, messageSupplier); + } + + // --- assertEquals -------------------------------------------------------- + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(short expected, short actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(short expected, Short actual) { + AssertEquals.assertEquals((Short) expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(Short expected, short actual) { + AssertEquals.assertEquals(expected, (Short) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Short expected, Short actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(short expected, short actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(short expected, Short actual, String message) { + AssertEquals.assertEquals((Short) expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(Short expected, short actual, String message) { + AssertEquals.assertEquals(expected, (Short) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Short expected, Short actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(short expected, short actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(short expected, Short actual, Supplier messageSupplier) { + AssertEquals.assertEquals((Short) expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(Short expected, short actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, (Short) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Short expected, Short actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(byte expected, byte actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(byte expected, Byte actual) { + AssertEquals.assertEquals((Byte) expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(Byte expected, byte actual) { + AssertEquals.assertEquals(expected, (Byte) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Byte expected, Byte actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(byte expected, byte actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(byte expected, Byte actual, String message) { + AssertEquals.assertEquals((Byte) expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(Byte expected, byte actual, String message) { + AssertEquals.assertEquals(expected, (Byte) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Byte expected, Byte actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(byte expected, byte actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(byte expected, Byte actual, Supplier messageSupplier) { + AssertEquals.assertEquals((Byte) expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(Byte expected, byte actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, (Byte) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Byte expected, Byte actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(int expected, int actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(int expected, Integer actual) { + AssertEquals.assertEquals((Integer) expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(Integer expected, int actual) { + AssertEquals.assertEquals(expected, (Integer) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Integer expected, Integer actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(int expected, int actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(int expected, Integer actual, String message) { + AssertEquals.assertEquals((Integer) expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(Integer expected, int actual, String message) { + AssertEquals.assertEquals(expected, (Integer) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Integer expected, Integer actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(int expected, int actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(int expected, Integer actual, Supplier messageSupplier) { + AssertEquals.assertEquals((Integer) expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(Integer expected, int actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, (Integer) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Integer expected, Integer actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(long expected, long actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(long expected, Long actual) { + AssertEquals.assertEquals((Long) expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(Long expected, long actual) { + AssertEquals.assertEquals(expected, (Long) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Long expected, Long actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(long expected, long actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(long expected, Long actual, String message) { + AssertEquals.assertEquals((Long) expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(Long expected, long actual, String message) { + AssertEquals.assertEquals(expected, (Long) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Long expected, Long actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(long expected, long actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(long expected, Long actual, Supplier messageSupplier) { + AssertEquals.assertEquals((Long) expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(Long expected, long actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, (Long) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Long expected, Long actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + */ + public static void assertEquals(float expected, float actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + */ + public static void assertEquals(float expected, Float actual) { + AssertEquals.assertEquals((Float) expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + */ + public static void assertEquals(Float expected, float actual) { + AssertEquals.assertEquals(expected, (Float) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Float expected, Float actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(float expected, float actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(float expected, Float actual, String message) { + AssertEquals.assertEquals((Float) expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(Float expected, float actual, String message) { + AssertEquals.assertEquals(expected, (Float) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Float expected, Float actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(float expected, float actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(float expected, Float actual, Supplier messageSupplier) { + AssertEquals.assertEquals((Float) expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(Float expected, float actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, (Float) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Float expected, Float actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + */ + public static void assertEquals(float expected, float actual, float delta) { + AssertEquals.assertEquals(expected, actual, delta); + } + + /** + * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(float expected, float actual, float delta, String message) { + AssertEquals.assertEquals(expected, actual, delta, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(float expected, float actual, float delta, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, delta, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + */ + public static void assertEquals(double expected, double actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + */ + public static void assertEquals(double expected, Double actual) { + AssertEquals.assertEquals((Double) expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + */ + public static void assertEquals(Double expected, double actual) { + AssertEquals.assertEquals(expected, (Double) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Double expected, Double actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(double expected, double actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(double expected, Double actual, String message) { + AssertEquals.assertEquals((Double) expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(Double expected, double actual, String message) { + AssertEquals.assertEquals(expected, (Double) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Double expected, Double actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(double expected, double actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(double expected, Double actual, Supplier messageSupplier) { + AssertEquals.assertEquals((Double) expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(Double expected, double actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, (Double) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Double expected, Double actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + */ + public static void assertEquals(double expected, double actual, double delta) { + AssertEquals.assertEquals(expected, actual, delta); + } + + /** + * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(double expected, double actual, double delta, String message) { + AssertEquals.assertEquals(expected, actual, delta, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(double expected, double actual, double delta, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, delta, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(char expected, char actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(char expected, Character actual) { + AssertEquals.assertEquals((Character) expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + */ + public static void assertEquals(Character expected, char actual) { + AssertEquals.assertEquals(expected, (Character) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Character expected, Character actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(char expected, char actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(char expected, Character actual, String message) { + AssertEquals.assertEquals((Character) expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertEquals(Character expected, char actual, String message) { + AssertEquals.assertEquals(expected, (Character) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Character expected, Character actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(char expected, char actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(char expected, Character actual, Supplier messageSupplier) { + AssertEquals.assertEquals((Character) expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertEquals(Character expected, char actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, (Character) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertEquals(Character expected, Character actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If both are {@code null}, they are considered equal. + * + * @see Object#equals(Object) + */ + public static void assertEquals(Object expected, Object actual) { + AssertEquals.assertEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If both are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + * + * @see Object#equals(Object) + */ + public static void assertEquals(Object expected, Object actual, String message) { + AssertEquals.assertEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are equal. + *

If both are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @see Object#equals(Object) + */ + public static void assertEquals(Object expected, Object actual, Supplier messageSupplier) { + AssertEquals.assertEquals(expected, actual, messageSupplier); + } + + // --- assertArrayEquals --------------------------------------------------- + + /** + * Assert that {@code expected} and {@code actual} boolean arrays are equal. + *

If both are {@code null}, they are considered equal. + */ + public static void assertArrayEquals(boolean[] expected, boolean[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} boolean arrays are equal. + *

If both are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(boolean[] expected, boolean[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} boolean arrays are equal. + *

If both are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(boolean[] expected, boolean[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} char arrays are equal. + *

If both are {@code null}, they are considered equal. + */ + public static void assertArrayEquals(char[] expected, char[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} char arrays are equal. + *

If both are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(char[] expected, char[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} char arrays are equal. + *

If both are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(char[] expected, char[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} byte arrays are equal. + *

If both are {@code null}, they are considered equal. + */ + public static void assertArrayEquals(byte[] expected, byte[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} byte arrays are equal. + *

If both are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(byte[] expected, byte[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} byte arrays are equal. + *

If both are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(byte[] expected, byte[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} short arrays are equal. + *

If both are {@code null}, they are considered equal. + */ + public static void assertArrayEquals(short[] expected, short[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} short arrays are equal. + *

If both are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(short[] expected, short[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} short arrays are equal. + *

If both are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(short[] expected, short[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} int arrays are equal. + *

If both are {@code null}, they are considered equal. + */ + public static void assertArrayEquals(int[] expected, int[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} int arrays are equal. + *

If both are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(int[] expected, int[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} int arrays are equal. + *

If both are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(int[] expected, int[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} long arrays are equal. + *

If both are {@code null}, they are considered equal. + */ + public static void assertArrayEquals(long[] expected, long[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} long arrays are equal. + *

If both are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(long[] expected, long[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} long arrays are equal. + *

If both are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(long[] expected, long[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} float arrays are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + */ + public static void assertArrayEquals(float[] expected, float[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} float arrays are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(float[] expected, float[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} float arrays are equal. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(float[] expected, float[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + */ + public static void assertArrayEquals(float[] expected, float[] actual, float delta) { + AssertArrayEquals.assertArrayEquals(expected, actual, delta); + } + + /** + * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(float[] expected, float[] actual, float delta, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); + } + + /** + * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and + * {@link Float#compare(float, float)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(float[] expected, float[] actual, float delta, + Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} double arrays are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + */ + public static void assertArrayEquals(double[] expected, double[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} double arrays are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(double[] expected, double[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} double arrays are equal. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(double[] expected, double[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + */ + public static void assertArrayEquals(double[] expected, double[] actual, double delta) { + AssertArrayEquals.assertArrayEquals(expected, actual, delta); + } + + /** + * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

Fails with the supplied failure {@code message}. + */ + public static void assertArrayEquals(double[] expected, double[] actual, double delta, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); + } + + /** + * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. + *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and + * {@link Double#compare(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertArrayEquals(double[] expected, double[] actual, double delta, + Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} object arrays are deeply equal. + *

If both are {@code null}, they are considered equal. + *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. + *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. + * + * @see Objects#equals(Object, Object) + * @see Arrays#deepEquals(Object[], Object[]) + */ + public static void assertArrayEquals(Object[] expected, Object[] actual) { + AssertArrayEquals.assertArrayEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} object arrays are deeply equal. + *

If both are {@code null}, they are considered equal. + *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. + *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. + *

Fails with the supplied failure {@code message}. + * + * @see Objects#equals(Object, Object) + * @see Arrays#deepEquals(Object[], Object[]) + */ + public static void assertArrayEquals(Object[] expected, Object[] actual, String message) { + AssertArrayEquals.assertArrayEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} object arrays are deeply equal. + *

If both are {@code null}, they are considered equal. + *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. + *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @see Objects#equals(Object, Object) + * @see Arrays#deepEquals(Object[], Object[]) + */ + public static void assertArrayEquals(Object[] expected, Object[] actual, Supplier messageSupplier) { + AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); + } + + // --- assertIterableEquals -------------------------------------------- + + /** + * Assert that {@code expected} and {@code actual} iterables are deeply equal. + *

Similarly to the check for deep equality in {@link #assertArrayEquals(Object[], Object[])}, + * if two iterables are encountered (including {@code expected} and {@code actual}) then their + * iterators must return equal elements in the same order as each other. Note: + * this means that the iterables do not need to be of the same type. Example:

{@code
+	 * import static java.util.Arrays.asList;
+	 *  . . .
+	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
+	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
+	 * assertIterableEquals(i0, i1); // Passes
+	 * }
+ *

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. + * + * @see Objects#equals(Object, Object) + * @see Arrays#deepEquals(Object[], Object[]) + * @see #assertArrayEquals(Object[], Object[]) + */ + public static void assertIterableEquals(Iterable expected, Iterable actual) { + AssertIterableEquals.assertIterableEquals(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} iterables are deeply equal. + *

Similarly to the check for deep equality in + * {@link #assertArrayEquals(Object[], Object[], String)}, if two iterables are encountered + * (including {@code expected} and {@code actual}) then their iterators must return equal + * elements in the same order as each other. Note: this means that the iterables + * do not need to be of the same type. Example:

{@code
+	 * import static java.util.Arrays.asList;
+	 *  . . .
+	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
+	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
+	 * assertIterableEquals(i0, i1); // Passes
+	 * }
+ *

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. + *

Fails with the supplied failure {@code message}. + * + * @see Objects#equals(Object, Object) + * @see Arrays#deepEquals(Object[], Object[]) + * @see #assertArrayEquals(Object[], Object[], String) + */ + public static void assertIterableEquals(Iterable expected, Iterable actual, String message) { + AssertIterableEquals.assertIterableEquals(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} iterables are deeply equal. + *

Similarly to the check for deep equality in + * {@link #assertArrayEquals(Object[], Object[], Supplier)}, if two iterables are encountered + * (including {@code expected} and {@code actual}) then their iterators must return equal + * elements in the same order as each other. Note: this means that the iterables + * do not need to be of the same type. Example:

{@code
+	 * import static java.util.Arrays.asList;
+	 *  . . .
+	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
+	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
+	 * assertIterableEquals(i0, i1); // Passes
+	 * }
+ *

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + * + * @see Objects#equals(Object, Object) + * @see Arrays#deepEquals(Object[], Object[]) + * @see #assertArrayEquals(Object[], Object[], Supplier) + */ + public static void assertIterableEquals(Iterable expected, Iterable actual, + Supplier messageSupplier) { + AssertIterableEquals.assertIterableEquals(expected, actual, messageSupplier); + } + + // --- assertLinesMatch ---------------------------------------------------- + + /** + * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} + * list. + * + *

This method differs from other assertions that effectively only check {@link String#equals(Object)}, + * in that it uses the following staged matching algorithm: + * + *

For each pair of expected and actual lines do + *

    + *
  1. check if {@code expected.equals(actual)} - if yes, continue with next pair
  2. + *
  3. otherwise treat {@code expected} as a regular expression and check via + * {@link String#matches(String)} - if yes, continue with next pair
  4. + *
  5. otherwise check if {@code expected} line is a fast-forward marker, if yes apply + * fast-forward actual lines accordingly (see below) and goto 1.
  6. + *
+ * + *

A valid fast-forward marker is an expected line that starts and ends with the literal + * {@code >>} and contains at least 4 characters. Examples: + *

    + *
  • {@code >>>>}
    {@code >> stacktrace >>}
    {@code >> single line, non Integer.parse()-able comment >>} + *
    Skip arbitrary number of actual lines, until first matching subsequent expected line is found. Any + * character between the fast-forward literals are discarded.
  • + *
  • {@code ">> 21 >>"} + *
    Skip strictly 21 lines. If they can't be skipped for any reason, an assertion error is raised.
  • + *
+ * + *

Here is an example showing all three kinds of expected line formats: + *

{@code
+	 * ls -la /
+	 * total [\d]+
+	 * drwxr-xr-x  0 root root   512 Jan  1  1970 .
+	 * drwxr-xr-x  0 root root   512 Jan  1  1970 ..
+	 * drwxr-xr-x  0 root root   512 Apr  5 07:45 bin
+	 * >> 4 >>
+	 * -rwxr-xr-x  1 root root [\d]+ Jan  1  1970 init
+	 * >> M A N Y  M O R E  E N T R I E S >>
+	 * drwxr-xr-x  0 root root   512 Sep 22  2017 var
+	 * }
+ *

Fails with a generated failure message describing the difference. + */ + public static void assertLinesMatch(List expectedLines, List actualLines) { + AssertLinesMatch.assertLinesMatch(expectedLines, actualLines); + } + + /** + * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} + * list. + * + *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. + * + *

Fails with the supplied failure {@code message} and the generated message. + * + * @see #assertLinesMatch(List, List) + */ + public static void assertLinesMatch(List expectedLines, List actualLines, String message) { + AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); + } + + /** + * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} + * list. + * + *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. + * + *

If necessary, a custom failure message will be retrieved lazily from the supplied + * {@code messageSupplier}. Fails with the custom failure message prepended to + * a generated failure message describing the difference. + * + * @see #assertLinesMatch(List, List) + */ + public static void assertLinesMatch(List expectedLines, List actualLines, + Supplier messageSupplier) { + AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); + } + + /** + * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} + * stream. + * + *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. + * + *

Note: An implementation of this method may consume all lines of both streams eagerly and + * delegate the evaluation to {@link #assertLinesMatch(List, List)}. + * + * @since 5.7 + * @see #assertLinesMatch(List, List) + */ + public static void assertLinesMatch(Stream expectedLines, Stream actualLines) { + AssertLinesMatch.assertLinesMatch(expectedLines, actualLines); + } + + /** + * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} + * stream. + * + *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. + * + *

Fails with the supplied failure {@code message} and the generated message. + * + *

Note: An implementation of this method may consume all lines of both streams eagerly and + * delegate the evaluation to {@link #assertLinesMatch(List, List)}. + * + * @since 5.7 + * @see #assertLinesMatch(List, List) + */ + public static void assertLinesMatch(Stream expectedLines, Stream actualLines, String message) { + AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); + } + + /** + * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} + * stream. + * + *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. + * + *

If necessary, a custom failure message will be retrieved lazily from the supplied + * {@code messageSupplier}. Fails with the custom failure message prepended to + * a generated failure message describing the difference. + * + *

Note: An implementation of this method may consume all lines of both streams eagerly and + * delegate the evaluation to {@link #assertLinesMatch(List, List)}. + * + * @since 5.7 + * @see #assertLinesMatch(List, List) + */ + public static void assertLinesMatch(Stream expectedLines, Stream actualLines, + Supplier messageSupplier) { + AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); + } + + // --- assertNotEquals ----------------------------------------------------- + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(byte unexpected, byte actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(byte unexpected, Byte actual) { + AssertNotEquals.assertNotEquals((Byte) unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Byte unexpected, byte actual) { + AssertNotEquals.assertNotEquals(unexpected, (Byte) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Byte unexpected, Byte actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(byte unexpected, byte actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(byte unexpected, Byte actual, String message) { + AssertNotEquals.assertNotEquals((Byte) unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Byte unexpected, byte actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Byte unexpected, Byte actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(byte unexpected, Byte actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals((Byte) unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Byte unexpected, byte actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Byte unexpected, Byte actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(short unexpected, short actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(short unexpected, Short actual) { + AssertNotEquals.assertNotEquals((Short) unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Short unexpected, short actual) { + AssertNotEquals.assertNotEquals(unexpected, (Short) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Short unexpected, Short actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(short unexpected, short actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(short unexpected, Short actual, String message) { + AssertNotEquals.assertNotEquals((Short) unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Short unexpected, short actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, (Short) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Short unexpected, Short actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(short unexpected, Short actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals((Short) unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Short unexpected, short actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, (Short) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Short unexpected, Short actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(int unexpected, int actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(int unexpected, Integer actual) { + AssertNotEquals.assertNotEquals((Integer) unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Integer unexpected, int actual) { + AssertNotEquals.assertNotEquals(unexpected, (Integer) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Integer unexpected, Integer actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(int unexpected, int actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(int unexpected, Integer actual, String message) { + AssertNotEquals.assertNotEquals((Integer) unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Integer unexpected, int actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Integer unexpected, Integer actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(int unexpected, Integer actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals((Integer) unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Integer unexpected, int actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Integer unexpected, Integer actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(long unexpected, long actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(long unexpected, Long actual) { + AssertNotEquals.assertNotEquals((Long) unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Long unexpected, long actual) { + AssertNotEquals.assertNotEquals(unexpected, (Long) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Long unexpected, Long actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(long unexpected, long actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(long unexpected, Long actual, String message) { + AssertNotEquals.assertNotEquals((Long) unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Long unexpected, long actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, (Long) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Long unexpected, Long actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(long unexpected, Long actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals((Long) unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Long unexpected, long actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, (Long) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Long unexpected, Long actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, float actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, Float actual) { + AssertNotEquals.assertNotEquals((Float) unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Float unexpected, float actual) { + AssertNotEquals.assertNotEquals(unexpected, (Float) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Float unexpected, Float actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, float actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, Float actual, String message) { + AssertNotEquals.assertNotEquals((Float) unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Float unexpected, float actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, (Float) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Float unexpected, Float actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, Float actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals((Float) unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Float unexpected, float actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, (Float) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Float unexpected, Float actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal + * within the given {@code delta}. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, float actual, float delta) { + AssertNotEquals.assertNotEquals(unexpected, actual, delta); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal + * within the given {@code delta}. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, float actual, float delta, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal + * within the given {@code delta}. + * + *

Inequality imposed by this method is consistent with + * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, double actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, Double actual) { + AssertNotEquals.assertNotEquals((Double) unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Double unexpected, double actual) { + AssertNotEquals.assertNotEquals(unexpected, (Double) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Double unexpected, Double actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, double actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, Double actual, String message) { + AssertNotEquals.assertNotEquals((Double) unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Double unexpected, double actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, (Double) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Double unexpected, Double actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, Double actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals((Double) unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Double unexpected, double actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, (Double) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Double unexpected, Double actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal + * within the given {@code delta}. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, double actual, double delta) { + AssertNotEquals.assertNotEquals(unexpected, actual, delta); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal + * within the given {@code delta}. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, double actual, double delta, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal + * within the given {@code delta}. + * + *

Inequality imposed by this method is consistent with + * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(double unexpected, double actual, double delta, + Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(char unexpected, char actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(char unexpected, Character actual) { + AssertNotEquals.assertNotEquals((Character) unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Character unexpected, char actual) { + AssertNotEquals.assertNotEquals(unexpected, (Character) actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Character unexpected, Character actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(char unexpected, char actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(char unexpected, Character actual, String message) { + AssertNotEquals.assertNotEquals((Character) unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Character unexpected, char actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, (Character) actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Character unexpected, Character actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(char unexpected, Character actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals((Character) unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Character unexpected, char actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, (Character) actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + public static void assertNotEquals(Character unexpected, Character actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails if both are {@code null}. + * + * @see Object#equals(Object) + */ + public static void assertNotEquals(Object unexpected, Object actual) { + AssertNotEquals.assertNotEquals(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails if both are {@code null}. + * + *

Fails with the supplied failure {@code message}. + * + * @see Object#equals(Object) + */ + public static void assertNotEquals(Object unexpected, Object actual, String message) { + AssertNotEquals.assertNotEquals(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} are not equal. + * + *

Fails if both are {@code null}. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @see Object#equals(Object) + */ + public static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { + AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); + } + + // --- assertSame ---------------------------------------------------------- + + /** + * Assert that {@code expected} and {@code actual} refer to the same object. + */ + public static void assertSame(Object expected, Object actual) { + AssertSame.assertSame(expected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} refer to the same object. + *

Fails with the supplied failure {@code message}. + */ + public static void assertSame(Object expected, Object actual, String message) { + AssertSame.assertSame(expected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} refer to the same object. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertSame(Object expected, Object actual, Supplier messageSupplier) { + AssertSame.assertSame(expected, actual, messageSupplier); + } + + // --- assertNotSame ------------------------------------------------------- + + /** + * Assert that {@code expected} and {@code actual} do not refer to the same object. + */ + public static void assertNotSame(Object unexpected, Object actual) { + AssertNotSame.assertNotSame(unexpected, actual); + } + + /** + * Assert that {@code expected} and {@code actual} do not refer to the same object. + *

Fails with the supplied failure {@code message}. + */ + public static void assertNotSame(Object unexpected, Object actual, String message) { + AssertNotSame.assertNotSame(unexpected, actual, message); + } + + /** + * Assert that {@code expected} and {@code actual} do not refer to the same object. + *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. + */ + public static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { + AssertNotSame.assertNotSame(unexpected, actual, messageSupplier); + } + + // --- assertAll ----------------------------------------------------------- + + /** + * Assert that all supplied {@code executables} do not throw + * exceptions. + * + *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this + * method's exception handling semantics. + * + * @see #assertAll(String, Executable...) + * @see #assertAll(Collection) + * @see #assertAll(String, Collection) + * @see #assertAll(Stream) + * @see #assertAll(String, Stream) + */ + public static void assertAll(Executable... executables) throws MultipleFailuresError { + AssertAll.assertAll(executables); + } + + /** + * Assert that all supplied {@code executables} do not throw + * exceptions. + * + *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this + * method's exception handling semantics. + * + * @see #assertAll(Executable...) + * @see #assertAll(Collection) + * @see #assertAll(Stream) + * @see #assertAll(String, Collection) + * @see #assertAll(String, Stream) + */ + public static void assertAll(String heading, Executable... executables) throws MultipleFailuresError { + AssertAll.assertAll(heading, executables); + } + + /** + * Assert that all supplied {@code executables} do not throw + * exceptions. + * + *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this + * method's exception handling semantics. + * + * @see #assertAll(Executable...) + * @see #assertAll(String, Executable...) + * @see #assertAll(String, Collection) + * @see #assertAll(Stream) + * @see #assertAll(String, Stream) + */ + public static void assertAll(Collection executables) throws MultipleFailuresError { + AssertAll.assertAll(executables); + } + + /** + * Assert that all supplied {@code executables} do not throw + * exceptions. + * + *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this + * method's exception handling semantics. + * + * @see #assertAll(Executable...) + * @see #assertAll(String, Executable...) + * @see #assertAll(Collection) + * @see #assertAll(Stream) + * @see #assertAll(String, Stream) + */ + public static void assertAll(String heading, Collection executables) throws MultipleFailuresError { + AssertAll.assertAll(heading, executables); + } + + /** + * Assert that all supplied {@code executables} do not throw + * exceptions. + * + *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this + * method's exception handling semantics. + * + * @see #assertAll(Executable...) + * @see #assertAll(String, Executable...) + * @see #assertAll(Collection) + * @see #assertAll(String, Collection) + * @see #assertAll(String, Stream) + */ + public static void assertAll(Stream executables) throws MultipleFailuresError { + AssertAll.assertAll(executables); + } + + /** + * Assert that all supplied {@code executables} do not throw + * exceptions. + * + *

If any supplied {@link Executable} throws an exception (i.e., a {@link Throwable} + * or any subclass thereof), all remaining {@code executables} will still be executed, + * and all exceptions will be aggregated and reported in a {@link MultipleFailuresError}. + * In addition, all aggregated exceptions will be added as {@linkplain + * Throwable#addSuppressed(Throwable) suppressed exceptions} to the + * {@code MultipleFailuresError}. However, if an {@code executable} throws an + * unrecoverable exception — for example, an {@link OutOfMemoryError} + * — execution will halt immediately, and the unrecoverable exception will be + * rethrown as is but masked as an unchecked exception. + * + *

The supplied {@code heading} will be included in the message string for the + * {@code MultipleFailuresError}. + * + * @see #assertAll(Executable...) + * @see #assertAll(String, Executable...) + * @see #assertAll(Collection) + * @see #assertAll(String, Collection) + * @see #assertAll(Stream) + */ + public static void assertAll(String heading, Stream executables) throws MultipleFailuresError { + AssertAll.assertAll(heading, executables); + } + + // --- assert exceptions --------------------------------------------------- + + // --- executable --- + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of exactly the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static T assertThrowsExactly(Class expectedType, Executable executable) { + return AssertThrowsExactly.assertThrowsExactly(expectedType, executable); + } + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of exactly the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static T assertThrowsExactly(Class expectedType, Executable executable, + String message) { + return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, message); + } + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of exactly the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static T assertThrowsExactly(Class expectedType, Executable executable, + Supplier messageSupplier) { + return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, messageSupplier); + } + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + */ + public static T assertThrows(Class expectedType, Executable executable) { + return AssertThrows.assertThrows(expectedType, executable); + } + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + * + *

Fails with the supplied failure {@code message}. + */ + public static T assertThrows(Class expectedType, Executable executable, String message) { + return AssertThrows.assertThrows(expectedType, executable, message); + } + + /** + * Assert that execution of the supplied {@code executable} throws + * an exception of the {@code expectedType} and return the exception. + * + *

If no exception is thrown, or if an exception of a different type is + * thrown, this method will fail. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + *

If you do not want to perform additional checks on the exception instance, + * ignore the return value. + */ + public static T assertThrows(Class expectedType, Executable executable, + Supplier messageSupplier) { + return AssertThrows.assertThrows(expectedType, executable, messageSupplier); + } + + // --- executable --- + + /** + * Assert that execution of the supplied {@code executable} does + * not throw any kind of {@linkplain Throwable exception}. + * + *

Usage Note

+ *

Although any exception thrown from a test method will cause the test + * to fail, there are certain use cases where it can be beneficial + * to explicitly assert that an exception is not thrown for a given code + * block within a test method. + * + * @since 5.2 + */ + @API(status = STABLE, since = "5.2") + public static void assertDoesNotThrow(Executable executable) { + AssertDoesNotThrow.assertDoesNotThrow(executable); + } + + /** + * Assert that execution of the supplied {@code executable} does + * not throw any kind of {@linkplain Throwable exception}. + * + *

Usage Note

+ *

Although any exception thrown from a test method will cause the test + * to fail, there are certain use cases where it can be beneficial + * to explicitly assert that an exception is not thrown for a given code + * block within a test method. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.2 + */ + @API(status = STABLE, since = "5.2") + public static void assertDoesNotThrow(Executable executable, String message) { + AssertDoesNotThrow.assertDoesNotThrow(executable, message); + } + + /** + * Assert that execution of the supplied {@code executable} does + * not throw any kind of {@linkplain Throwable exception}. + * + *

Usage Note

+ *

Although any exception thrown from a test method will cause the test + * to fail, there are certain use cases where it can be beneficial + * to explicitly assert that an exception is not thrown for a given code + * block within a test method. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.2 + */ + @API(status = STABLE, since = "5.2") + public static void assertDoesNotThrow(Executable executable, Supplier messageSupplier) { + AssertDoesNotThrow.assertDoesNotThrow(executable, messageSupplier); + } + + // --- supplier --- + + /** + * Assert that execution of the supplied {@code supplier} does + * not throw any kind of {@linkplain Throwable exception}. + * + *

If the assertion passes, the {@code supplier}'s result will be returned. + * + *

Usage Note

+ *

Although any exception thrown from a test method will cause the test + * to fail, there are certain use cases where it can be beneficial + * to explicitly assert that an exception is not thrown for a given code + * block within a test method. + * + * @since 5.2 + */ + @API(status = STABLE, since = "5.2") + public static T assertDoesNotThrow(ThrowingSupplier supplier) { + return AssertDoesNotThrow.assertDoesNotThrow(supplier); + } + + /** + * Assert that execution of the supplied {@code supplier} does + * not throw any kind of {@linkplain Throwable exception}. + * + *

If the assertion passes, the {@code supplier}'s result will be returned. + * + *

Fails with the supplied failure {@code message}. + * + *

Usage Note

+ *

Although any exception thrown from a test method will cause the test + * to fail, there are certain use cases where it can be beneficial + * to explicitly assert that an exception is not thrown for a given code + * block within a test method. + * + * @since 5.2 + */ + @API(status = STABLE, since = "5.2") + public static T assertDoesNotThrow(ThrowingSupplier supplier, String message) { + return AssertDoesNotThrow.assertDoesNotThrow(supplier, message); + } + + /** + * Assert that execution of the supplied {@code supplier} does + * not throw any kind of {@linkplain Throwable exception}. + * + *

If the assertion passes, the {@code supplier}'s result will be returned. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + *

Usage Note

+ *

Although any exception thrown from a test method will cause the test + * to fail, there are certain use cases where it can be beneficial + * to explicitly assert that an exception is not thrown for a given code + * block within a test method. + * + * @since 5.2 + */ + @API(status = STABLE, since = "5.2") + public static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier messageSupplier) { + return AssertDoesNotThrow.assertDoesNotThrow(supplier, messageSupplier); + } + + // --- assertTimeout ------------------------------------------------------- + + // --- executable --- + + /** + * Assert that execution of the supplied {@code executable} + * completes before the given {@code timeout} is exceeded. + * + *

Note: the {@code executable} will be executed in the same thread as that + * of the calling code. Consequently, execution of the {@code executable} will + * not be preemptively aborted if the timeout is exceeded. + * + * @see #assertTimeout(Duration, Executable, String) + * @see #assertTimeout(Duration, Executable, Supplier) + * @see #assertTimeout(Duration, ThrowingSupplier) + * @see #assertTimeout(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeoutPreemptively(Duration, Executable) + */ + public static void assertTimeout(Duration timeout, Executable executable) { + AssertTimeout.assertTimeout(timeout, executable); + } + + /** + * Assert that execution of the supplied {@code executable} + * completes before the given {@code timeout} is exceeded. + * + *

Note: the {@code executable} will be executed in the same thread as that + * of the calling code. Consequently, execution of the {@code executable} will + * not be preemptively aborted if the timeout is exceeded. + * + *

Fails with the supplied failure {@code message}. + * + * @see #assertTimeout(Duration, Executable) + * @see #assertTimeout(Duration, Executable, Supplier) + * @see #assertTimeout(Duration, ThrowingSupplier) + * @see #assertTimeout(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + */ + public static void assertTimeout(Duration timeout, Executable executable, String message) { + AssertTimeout.assertTimeout(timeout, executable, message); + } + + /** + * Assert that execution of the supplied {@code executable} + * completes before the given {@code timeout} is exceeded. + * + *

Note: the {@code executable} will be executed in the same thread as that + * of the calling code. Consequently, execution of the {@code executable} will + * not be preemptively aborted if the timeout is exceeded. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @see #assertTimeout(Duration, Executable) + * @see #assertTimeout(Duration, Executable, String) + * @see #assertTimeout(Duration, ThrowingSupplier) + * @see #assertTimeout(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + */ + public static void assertTimeout(Duration timeout, Executable executable, Supplier messageSupplier) { + AssertTimeout.assertTimeout(timeout, executable, messageSupplier); + } + + // --- supplier --- + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

If the assertion passes then the {@code supplier}'s result is returned. + * + *

Note: the {@code supplier} will be executed in the same thread as that + * of the calling code. Consequently, execution of the {@code supplier} will + * not be preemptively aborted if the timeout is exceeded. + * + * @see #assertTimeout(Duration, Executable) + * @see #assertTimeout(Duration, Executable, String) + * @see #assertTimeout(Duration, Executable, Supplier) + * @see #assertTimeout(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeoutPreemptively(Duration, Executable) + */ + public static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { + return AssertTimeout.assertTimeout(timeout, supplier); + } + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

If the assertion passes then the {@code supplier}'s result is returned. + * + *

Note: the {@code supplier} will be executed in the same thread as that + * of the calling code. Consequently, execution of the {@code supplier} will + * not be preemptively aborted if the timeout is exceeded. + * + *

Fails with the supplied failure {@code message}. + * + * @see #assertTimeout(Duration, Executable) + * @see #assertTimeout(Duration, Executable, String) + * @see #assertTimeout(Duration, Executable, Supplier) + * @see #assertTimeout(Duration, ThrowingSupplier) + * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + */ + public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, String message) { + return AssertTimeout.assertTimeout(timeout, supplier, message); + } + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

If the assertion passes then the {@code supplier}'s result is returned. + * + *

Note: the {@code supplier} will be executed in the same thread as that + * of the calling code. Consequently, execution of the {@code supplier} will + * not be preemptively aborted if the timeout is exceeded. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @see #assertTimeout(Duration, Executable) + * @see #assertTimeout(Duration, Executable, String) + * @see #assertTimeout(Duration, Executable, Supplier) + * @see #assertTimeout(Duration, ThrowingSupplier) + * @see #assertTimeout(Duration, ThrowingSupplier, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + */ + public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier) { + return AssertTimeout.assertTimeout(timeout, supplier, messageSupplier); + } + + // --- executable - preemptively --- + + /** + * Assert that execution of the supplied {@code executable} + * completes before the given {@code timeout} is exceeded. + * + *

Note: the {@code executable} will be executed in a different thread than + * that of the calling code. Furthermore, execution of the {@code executable} will + * be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + * + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeout(Duration, Executable) + */ + public static void assertTimeoutPreemptively(Duration timeout, Executable executable) { + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable); + } + + /** + * Assert that execution of the supplied {@code executable} + * completes before the given {@code timeout} is exceeded. + * + *

Note: the {@code executable} will be executed in a different thread than + * that of the calling code. Furthermore, execution of the {@code executable} will + * be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + * + *

Fails with the supplied failure {@code message}. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeout(Duration, Executable, String) + */ + public static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, message); + } + + /** + * Assert that execution of the supplied {@code executable} + * completes before the given {@code timeout} is exceeded. + * + *

Note: the {@code executable} will be executed in a different thread than + * that of the calling code. Furthermore, execution of the {@code executable} will + * be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeout(Duration, Executable, Supplier) + */ + public static void assertTimeoutPreemptively(Duration timeout, Executable executable, + Supplier messageSupplier) { + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, messageSupplier); + } + + // --- supplier - preemptively --- + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

If the assertion passes then the {@code supplier}'s result is returned. + * + *

Note: the {@code supplier} will be executed in a different thread than + * that of the calling code. Furthermore, execution of the {@code supplier} will + * be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeout(Duration, Executable) + */ + public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier); + } + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

If the assertion passes then the {@code supplier}'s result is returned. + * + *

Note: the {@code supplier} will be executed in a different thread than + * that of the calling code. Furthermore, execution of the {@code supplier} will + * be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + * + *

Fails with the supplied failure {@code message}. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) + * @see #assertTimeout(Duration, Executable, String) + */ + public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, message); + } + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

If the assertion passes then the {@code supplier}'s result is returned. + * + *

Note: the {@code supplier} will be executed in a different thread than + * that of the calling code. Furthermore, execution of the {@code supplier} will + * be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, Executable, Supplier) + */ + public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier) { + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier); + } + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

If the assertion passes then the {@code supplier}'s result is returned. + * + *

In the case the assertion does not pass, the supplied + * {@link TimeoutFailureFactory} is invoked to create an exception which is + * then thrown. + * + *

Note: the {@code supplier} will be executed in a different thread than + * that of the calling code. Furthermore, execution of the {@code supplier} will + * be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, Executable, Supplier) + */ + @API(status = INTERNAL, since = "5.9.1") + public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); + } + + // --- assertInstanceOf ---------------------------------------------------- + + /** + * Assert that the supplied {@code actualValue} is an instance of the + * {@code expectedType}. + * + *

Like the {@code instanceof} operator a {@code null} value is not + * considered to be of the {@code expectedType} and does not pass the assertion. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static T assertInstanceOf(Class expectedType, Object actualValue) { + return AssertInstanceOf.assertInstanceOf(expectedType, actualValue); + } + + /** + * Assert that the supplied {@code actualValue} is an instance of the + * {@code expectedType}. + * + *

Like the {@code instanceof} operator a {@code null} value is not + * considered to be of the {@code expectedType} and does not pass the assertion. + * + *

Fails with the supplied failure {@code message}. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static T assertInstanceOf(Class expectedType, Object actualValue, String message) { + return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, message); + } + + /** + * Assert that the supplied {@code actualValue} is an instance of the + * {@code expectedType}. + * + *

Like the {@code instanceof} operator a {@code null} value is not + * considered to be of the {@code expectedType} and does not pass the assertion. + * + *

If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { + return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); + } + + /** + * Factory for timeout failures. + * + * @param The type of error or exception created + * @since 5.9.1 + * @see Assertions#assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) + */ + @API(status = INTERNAL, since = "5.9.1") + public interface TimeoutFailureFactory { + + /** + * Create a failure for the given timeout, message, and cause. + * + * @return timeout failure; never {@code null} + */ + T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java new file mode 100644 index 00000000..d0448a89 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java @@ -0,0 +1,319 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.StringUtils; +import org.opentest4j.TestAbortedException; + +/** + * {@code Assumptions} is a collection of utility methods that support + * conditional test execution based on assumptions. + * + *

In direct contrast to failed {@linkplain Assertions assertions}, + * failed assumptions do not result in a test failure; rather, + * a failed assumption results in a test being aborted. + * + *

Assumptions are typically used whenever it does not make sense to + * continue execution of a given test method — for example, if the + * test depends on something that does not exist in the current runtime + * environment. + * + *

Although it is technically possible to extend this class, extension is + * strongly discouraged. The JUnit Team highly recommends that the methods + * defined in this class be used via static imports. + * + * @since 5.0 + * @see TestAbortedException + * @see Assertions + */ +@API(status = STABLE, since = "5.0") +public class Assumptions { + + /** + * Protected constructor allowing subclassing but not direct instantiation. + * + * @since 5.3 + */ + protected Assumptions() { + /* no-op */ + } + + // --- assumeTrue ---------------------------------------------------------- + + /** + * Validate the given assumption. + * + * @param assumption the assumption to validate + * @throws TestAbortedException if the assumption is not {@code true} + */ + public static void assumeTrue(boolean assumption) throws TestAbortedException { + assumeTrue(assumption, "assumption is not true"); + } + + /** + * Validate the given assumption. + * + * @param assumptionSupplier the supplier of the assumption to validate + * @throws TestAbortedException if the assumption is not {@code true} + */ + public static void assumeTrue(BooleanSupplier assumptionSupplier) throws TestAbortedException { + assumeTrue(assumptionSupplier.getAsBoolean(), "assumption is not true"); + } + + /** + * Validate the given assumption. + * + * @param assumptionSupplier the supplier of the assumption to validate + * @param message the message to be included in the {@code TestAbortedException} + * if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code true} + */ + public static void assumeTrue(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException { + assumeTrue(assumptionSupplier.getAsBoolean(), message); + } + + /** + * Validate the given assumption. + * + * @param assumption the assumption to validate + * @param messageSupplier the supplier of the message to be included in + * the {@code TestAbortedException} if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code true} + */ + public static void assumeTrue(boolean assumption, Supplier messageSupplier) throws TestAbortedException { + if (!assumption) { + throwAssumptionFailed(messageSupplier.get()); + } + } + + /** + * Validate the given assumption. + * + * @param assumption the assumption to validate + * @param message the message to be included in the {@code TestAbortedException} + * if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code true} + */ + public static void assumeTrue(boolean assumption, String message) throws TestAbortedException { + if (!assumption) { + throwAssumptionFailed(message); + } + } + + /** + * Validate the given assumption. + * + * @param assumptionSupplier the supplier of the assumption to validate + * @param messageSupplier the supplier of the message to be included in + * the {@code TestAbortedException} if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code true} + */ + public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier messageSupplier) + throws TestAbortedException { + + assumeTrue(assumptionSupplier.getAsBoolean(), messageSupplier); + } + + // --- assumeFalse --------------------------------------------------------- + + /** + * Validate the given assumption. + * + * @param assumption the assumption to validate + * @throws TestAbortedException if the assumption is not {@code false} + */ + public static void assumeFalse(boolean assumption) throws TestAbortedException { + assumeFalse(assumption, "assumption is not false"); + } + + /** + * Validate the given assumption. + * + * @param assumptionSupplier the supplier of the assumption to validate + * @throws TestAbortedException if the assumption is not {@code false} + */ + public static void assumeFalse(BooleanSupplier assumptionSupplier) throws TestAbortedException { + assumeFalse(assumptionSupplier.getAsBoolean(), "assumption is not false"); + } + + /** + * Validate the given assumption. + * + * @param assumptionSupplier the supplier of the assumption to validate + * @param message the message to be included in the {@code TestAbortedException} + * if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code false} + */ + public static void assumeFalse(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException { + assumeFalse(assumptionSupplier.getAsBoolean(), message); + } + + /** + * Validate the given assumption. + * + * @param assumption the assumption to validate + * @param messageSupplier the supplier of the message to be included in + * the {@code TestAbortedException} if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code false} + */ + public static void assumeFalse(boolean assumption, Supplier messageSupplier) throws TestAbortedException { + if (assumption) { + throwAssumptionFailed(messageSupplier.get()); + } + } + + /** + * Validate the given assumption. + * + * @param assumption the assumption to validate + * @param message the message to be included in the {@code TestAbortedException} + * if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code false} + */ + public static void assumeFalse(boolean assumption, String message) throws TestAbortedException { + if (assumption) { + throwAssumptionFailed(message); + } + } + + /** + * Validate the given assumption. + * + * @param assumptionSupplier the supplier of the assumption to validate + * @param messageSupplier the supplier of the message to be included in + * the {@code TestAbortedException} if the assumption is invalid + * @throws TestAbortedException if the assumption is not {@code false} + */ + public static void assumeFalse(BooleanSupplier assumptionSupplier, Supplier messageSupplier) + throws TestAbortedException { + + assumeFalse(assumptionSupplier.getAsBoolean(), messageSupplier); + } + + // --- assumingThat -------------------------------------------------------- + + /** + * Execute the supplied {@link Executable}, but only if the supplied + * assumption is valid. + * + *

Unlike the other assumption methods, this method will not abort the test. + * If the assumption is invalid, this method does nothing. If the assumption is + * valid and the {@code executable} throws an exception, it will be treated like + * a regular test failure. That exception will be rethrown as is + * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked + * exception. + * + * @param assumptionSupplier the supplier of the assumption to validate + * @param executable the block of code to execute if the assumption is valid + * @see #assumingThat(boolean, Executable) + */ + public static void assumingThat(BooleanSupplier assumptionSupplier, Executable executable) { + assumingThat(assumptionSupplier.getAsBoolean(), executable); + } + + /** + * Execute the supplied {@link Executable}, but only if the supplied + * assumption is valid. + * + *

Unlike the other assumption methods, this method will not abort the test. + * If the assumption is invalid, this method does nothing. If the assumption is + * valid and the {@code executable} throws an exception, it will be treated like + * a regular test failure. That exception will be rethrown as is + * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked + * exception. + * + * @param assumption the assumption to validate + * @param executable the block of code to execute if the assumption is valid + * @see #assumingThat(BooleanSupplier, Executable) + */ + public static void assumingThat(boolean assumption, Executable executable) { + if (assumption) { + try { + executable.execute(); + } + catch (Throwable t) { + ExceptionUtils.throwAsUncheckedException(t); + } + } + } + + // --- abort --------------------------------------------------------------- + + /** + * Abort the test without a message. + * + *

Although aborting with an explicit message is recommended, this may be + * useful when maintaining legacy code. + * + *

See Javadoc for {@link #abort(String)} for an explanation of this + * method's generic return type {@code V}. + * + * @throws TestAbortedException always + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + public static V abort() { + throw new TestAbortedException(); + } + + /** + * Abort the test with the given {@code message}. + * + *

The generic return type {@code V} allows this method to be used + * directly as a single-statement lambda expression, thereby avoiding the + * need to implement a code block with an explicit return value. Since this + * method throws a {@link TestAbortedException} before its return statement, + * this method never actually returns a value to its caller. The following + * example demonstrates how this may be used in practice. + * + *

{@code
+	 * Stream.of().map(entry -> abort("assumption not met"));
+	 * }
+ * + * @param message the message to be included in the {@code TestAbortedException} + * @throws TestAbortedException always + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + public static V abort(String message) { + throw new TestAbortedException(message); + } + + /** + * Abort the test with the supplied message. + * + *

See Javadoc for {@link #abort(String)} for an explanation of this + * method's generic return type {@code V}. + * + * @param messageSupplier the supplier of the message to be included in the + * {@code TestAbortedException} + * @throws TestAbortedException always + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + public static V abort(Supplier messageSupplier) { + throw new TestAbortedException(messageSupplier.get()); + } + + private static void throwAssumptionFailed(String message) { + throw new TestAbortedException( + StringUtils.isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java new file mode 100644 index 00000000..30eba9b7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @BeforeAll} is used to signal that the annotated method should be + * executed before all tests in the current test class. + * + *

In contrast to {@link BeforeEach @BeforeEach} methods, {@code @BeforeAll} + * methods are only executed once for a given test class. + * + *

Method Signatures

+ * + *

{@code @BeforeAll} methods must have a {@code void} return type and must + * be {@code static} by default. Consequently, {@code @BeforeAll} methods are + * not supported in {@link Nested @Nested} test classes or as interface + * default methods unless the test class is annotated with + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * However, beginning with Java 16 {@code @BeforeAll} methods may be declared as + * {@code static} in {@link Nested @Nested} test classes, and the + * {@code Lifecycle.PER_CLASS} restriction no longer applies. {@code @BeforeAll} + * methods may optionally declare parameters to be resolved by + * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. + * + *

Using {@code private} visibility for {@code @BeforeAll} methods is + * strongly discouraged and will be disallowed in a future release. + * + *

Inheritance and Execution Order

+ * + *

{@code @BeforeAll} methods are inherited from superclasses as long as + * they are not hidden (default mode with {@code static} modifier), + * overridden, or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @BeforeAll} methods from superclasses will be executed before + * {@code @BeforeAll} methods in subclasses. + * + *

Similarly, {@code @BeforeAll} methods declared in an interface are + * inherited as long as they are not hidden or overridden, + * and {@code @BeforeAll} methods from an interface will be executed before + * {@code @BeforeAll} methods in the class that implements the interface. + * + *

JUnit Jupiter does not guarantee the execution order of multiple + * {@code @BeforeAll} methods that are declared within a single test class or + * test interface. While it may at times appear that these methods are invoked + * in alphabetical order, they are in fact sorted using an algorithm that is + * deterministic but intentionally non-obvious. + * + *

In addition, {@code @BeforeAll} methods are in no way linked to + * {@code @AfterAll} methods. Consequently, there are no guarantees with regard + * to their wrapping behavior. For example, given two + * {@code @BeforeAll} methods {@code createA()} and {@code createB()} as well as + * two {@code @AfterAll} methods {@code destroyA()} and {@code destroyB()}, the + * order in which the {@code @BeforeAll} methods are executed (e.g. + * {@code createA()} before {@code createB()}) does not imply any order for the + * seemingly corresponding {@code @AfterAll} methods. In other words, + * {@code destroyA()} might be called before or after + * {@code destroyB()}. The JUnit Team therefore recommends that developers + * declare at most one {@code @BeforeAll} method and at most one + * {@code @AfterAll} method per test class or test interface unless there are no + * dependencies between the {@code @BeforeAll} methods or between the + * {@code @AfterAll} methods. + * + *

Composition

+ * + *

{@code @BeforeAll} may be used as a meta-annotation in order to create + * a custom composed annotation that inherits the semantics of + * {@code @BeforeAll}. + * + * @since 5.0 + * @see AfterAll + * @see BeforeEach + * @see AfterEach + * @see Test + * @see TestFactory + * @see TestInstance + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +public @interface BeforeAll { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java new file mode 100644 index 00000000..37ca2498 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @BeforeEach} is used to signal that the annotated method should be + * executed before each {@code @Test}, + * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, + * and {@code @TestTemplate} method in the current test class. + * + *

Method Signatures

+ * + *

{@code @BeforeEach} methods must have a {@code void} return type and must + * not be {@code static}. Using {@code private} visibility is strongly + * discouraged and will be disallowed in a future release. + * They may optionally declare parameters to be resolved by + * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. + * + *

Inheritance and Execution Order

+ * + *

{@code @BeforeEach} methods are inherited from superclasses as long as they + * are not overridden or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @BeforeEach} methods from superclasses will be executed before + * {@code @BeforeEach} methods in subclasses. + * + *

Similarly, {@code @BeforeEach} methods declared as interface default + * methods are inherited as long as they are not overridden, and + * {@code @BeforeEach} default methods will be executed before {@code @BeforeEach} + * methods in the class that implements the interface. + * + *

JUnit Jupiter does not guarantee the execution order of multiple + * {@code @BeforeEach} methods that are declared within a single test class or + * test interface. While it may at times appear that these methods are invoked + * in alphabetical order, they are in fact sorted using an algorithm that is + * deterministic but intentionally non-obvious. + * + *

In addition, {@code @BeforeEach} methods are in no way linked to + * {@code @AfterEach} methods. Consequently, there are no guarantees with regard + * to their wrapping behavior. For example, given two + * {@code @BeforeEach} methods {@code createA()} and {@code createB()} as well + * as two {@code @AfterEach} methods {@code destroyA()} and {@code destroyB()}, + * the order in which the {@code @BeforeEach} methods are executed (e.g. + * {@code createA()} before {@code createB()}) does not imply any order for the + * seemingly corresponding {@code @AfterEach} methods. In other words, + * {@code destroyA()} might be called before or after + * {@code destroyB()}. The JUnit Team therefore recommends that developers + * declare at most one {@code @BeforeEach} method and at most one + * {@code @AfterEach} method per test class or test interface unless there are + * no dependencies between the {@code @BeforeEach} methods or between the + * {@code @AfterEach} methods. + * + *

Composition

+ * + *

{@code @BeforeEach} may be used as a meta-annotation in order to create + * a custom composed annotation that inherits the semantics of + * {@code @BeforeEach}. + * + * @since 5.0 + * @see AfterEach + * @see BeforeAll + * @see AfterAll + * @see Test + * @see RepeatedTest + * @see TestFactory + * @see TestTemplate + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +public @interface BeforeEach { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java new file mode 100644 index 00000000..5ad667d1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ClassDescriptor} encapsulates functionality for a given {@link Class}. + * + * @since 5.8 + * @see ClassOrdererContext + */ +@API(status = EXPERIMENTAL, since = "5.8") +public interface ClassDescriptor { + + /** + * Get the class for this descriptor. + * + * @return the class; never {@code null} + */ + Class getTestClass(); + + /** + * Get the display name for this descriptor's {@link #getTestClass() class}. + * + * @return the display name for this descriptor's class; never {@code null} + * or blank + */ + String getDisplayName(); + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the {@link Class} for + * this descriptor. + * + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @see #findAnnotation(Class) + * @see #findRepeatableAnnotations(Class) + */ + boolean isAnnotated(Class annotationType); + + /** + * Find the first annotation of {@code annotationType} that is either + * present or meta-present on the {@link Class} for + * this descriptor. + * + * @param the annotation type + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @see #isAnnotated(Class) + * @see #findRepeatableAnnotations(Class) + */ + Optional findAnnotation(Class annotationType); + + /** + * Find all repeatable {@linkplain Annotation annotations} of + * {@code annotationType} that are either present or + * meta-present on the {@link Class} for this descriptor. + * + * @param the annotation type + * @param annotationType the repeatable annotation type to search for; never + * {@code null} + * @return the list of all such annotations found; neither {@code null} nor + * mutable, but potentially empty + * @see #isAnnotated(Class) + * @see #findAnnotation(Class) + * @see java.lang.annotation.Repeatable + */ + List findRepeatableAnnotations(Class annotationType); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java new file mode 100644 index 00000000..04017c02 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java @@ -0,0 +1,271 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.util.Comparator.comparingInt; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * {@code ClassOrderer} defines the API for ordering top-level test classes and + * {@link Nested @Nested} test classes. + * + *

In this context, the term "test class" refers to any class containing methods + * annotated with {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, + * {@code @TestFactory}, or {@code @TestTemplate}. + * + *

Top-level test classes will be ordered relative to each other; whereas, + * {@code @Nested} test classes will be ordered relative to other {@code @Nested} + * test classes sharing the same {@linkplain Class#getEnclosingClass() enclosing + * class}. + * + *

A {@link ClassOrderer} can be configured globally for the entire + * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration + * parameter (see the User Guide for details) or locally for + * {@link Nested @Nested} test classes via the {@link TestClassOrder @TestClassOrder} + * annotation. + * + *

Built-in Implementations

+ * + *

JUnit Jupiter provides the following built-in {@code ClassOrderer} + * implementations. + * + *

    + *
  • {@link ClassOrderer.ClassName}
  • + *
  • {@link ClassOrderer.DisplayName}
  • + *
  • {@link ClassOrderer.OrderAnnotation}
  • + *
  • {@link ClassOrderer.Random}
  • + *
+ * + * @since 5.8 + * @see TestClassOrder + * @see ClassOrdererContext + * @see #orderClasses(ClassOrdererContext) + * @see MethodOrderer + */ +@API(status = EXPERIMENTAL, since = "5.8") +public interface ClassOrderer { + + /** + * Property name used to set the default class orderer class name: {@value} + * + *

Supported Values

+ * + *

Supported values include fully qualified class names for types that + * implement {@link org.junit.jupiter.api.ClassOrderer}. + * + *

If not specified, test classes are not ordered unless test classes are + * annotated with {@link TestClassOrder @TestClassOrder}. + * + * @since 5.8 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testclass.order.default"; + + /** + * Order the classes encapsulated in the supplied {@link ClassOrdererContext}. + * + *

The classes to order or sort are made indirectly available via + * {@link ClassOrdererContext#getClassDescriptors()}. Since this method + * has a {@code void} return type, the list of class descriptors must be + * modified directly. + * + *

For example, a simplified implementation of the {@link ClassOrderer.Random} + * {@code ClassOrderer} might look like the following. + * + *

+	 * public void orderClasses(ClassOrdererContext context) {
+	 *     Collections.shuffle(context.getClassDescriptors());
+	 * }
+	 * 
+ * + * @param context the {@code ClassOrdererContext} containing the + * {@linkplain ClassDescriptor class descriptors} to order; never {@code null} + */ + void orderClasses(ClassOrdererContext context); + + /** + * {@code ClassOrderer} that sorts classes alphanumerically based on their + * fully qualified names using {@link String#compareTo(String)}. + */ + class ClassName implements ClassOrderer { + + public ClassName() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} alphanumerically based on their fully + * qualified names. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator.comparing( + descriptor -> descriptor.getTestClass().getName()); + } + + /** + * {@code ClassOrderer} that sorts classes alphanumerically based on their + * display names using {@link String#compareTo(String)} + */ + class DisplayName implements ClassOrderer { + + public DisplayName() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} alphanumerically based on their display + * names. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator.comparing( + ClassDescriptor::getDisplayName); + } + + /** + * {@code ClassOrderer} that sorts classes based on the {@link Order @Order} + * annotation. + * + *

Any classes that are assigned the same order value will be sorted + * arbitrarily adjacent to each other. + * + *

Any classes not annotated with {@code @Order} will be assigned the + * {@linkplain Order#DEFAULT default order} value which will effectively cause them + * to appear at the end of the sorted list, unless certain classes are assigned + * an explicit order value greater than the default order value. Any classes + * assigned an explicit order value greater than the default order value will + * appear after non-annotated classes in the sorted list. + */ + class OrderAnnotation implements ClassOrderer { + + public OrderAnnotation() { + } + + /** + * Sort the classes encapsulated in the supplied + * {@link ClassOrdererContext} based on the {@link Order @Order} + * annotation. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(comparingInt(OrderAnnotation::getOrder)); + } + + private static int getOrder(ClassDescriptor descriptor) { + return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT); + } + } + + /** + * {@code ClassOrderer} that orders classes pseudo-randomly. + * + *

Custom Seed

+ * + *

By default, the random seed used for ordering classes is the + * value returned by {@link System#nanoTime()} during static initialization + * of this class. In order to support repeatable builds, the value of the + * default random seed is logged at {@code CONFIG} level. In addition, a + * custom seed (potentially the default seed from the previous test plan + * execution) may be specified via the {@value Random#RANDOM_SEED_PROPERTY_NAME} + * configuration parameter which can be supplied via the {@code Launcher} + * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit + * Platform configuration file (i.e., a file named {@code junit-platform.properties} + * in the root of the class path). Consult the User Guide for further information. + * + * @see Random#RANDOM_SEED_PROPERTY_NAME + * @see java.util.Random + */ + class Random implements ClassOrderer { + + private static final Logger logger = LoggerFactory.getLogger(Random.class); + + /** + * Default seed, which is generated during initialization of this class + * via {@link System#nanoTime()} for reproducibility of tests. + */ + private static final long DEFAULT_SEED; + + static { + DEFAULT_SEED = System.nanoTime(); + logger.config(() -> "ClassOrderer.Random default seed: " + DEFAULT_SEED); + } + + /** + * Property name used to set the random seed used by this + * {@code ClassOrderer}: {@value} + * + *

The same property is used by {@link MethodOrderer.Random} for + * consistency between the two random orderers. + * + *

Supported Values

+ * + *

Supported values include any string that can be converted to a + * {@link Long} via {@link Long#valueOf(String)}. + * + *

If not specified or if the specified value cannot be converted to + * a {@link Long}, the default random seed will be used (see the + * {@linkplain Random class-level Javadoc} for details). + * + * @see MethodOrderer.Random + */ + public static final String RANDOM_SEED_PROPERTY_NAME = MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME; + + public Random() { + } + + /** + * Order the classes encapsulated in the supplied + * {@link ClassOrdererContext} pseudo-randomly. + */ + @Override + public void orderClasses(ClassOrdererContext context) { + Collections.shuffle(context.getClassDescriptors(), + new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED))); + } + + private Optional getCustomSeed(ClassOrdererContext context) { + return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> { + Long seed = null; + try { + seed = Long.valueOf(configurationParameter); + logger.config( + () -> String.format("Using custom seed for configuration parameter [%s] with value [%s].", + RANDOM_SEED_PROPERTY_NAME, configurationParameter)); + } + catch (NumberFormatException ex) { + logger.warn(ex, + () -> String.format( + "Failed to convert configuration parameter [%s] with value [%s] to a long. " + + "Using default seed [%s] as fallback.", + RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); + } + return seed; + }); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java new file mode 100644 index 00000000..1ec2e9b8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ClassOrdererContext} encapsulates the context in which + * a {@link ClassOrderer} will be invoked. + * + * @since 5.8 + * @see ClassOrderer + * @see ClassDescriptor + */ +@API(status = EXPERIMENTAL, since = "5.8") +public interface ClassOrdererContext { + + /** + * Get the list of {@linkplain ClassDescriptor class descriptors} to + * order. + * + * @return the list of class descriptors; never {@code null} + */ + List getClassDescriptors(); + + /** + * Get the configuration parameter stored under the specified {@code key}. + * + *

If no such key is present in the {@code ConfigurationParameters} for + * the JUnit Platform, an attempt will be made to look up the value as a + * JVM system property. If no such system property exists, an attempt will + * be made to look up the value in the JUnit Platform properties file. + * + * @param key the key to look up; never {@code null} or blank + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @see System#getProperty(String) + * @see org.junit.platform.engine.ConfigurationParameters + */ + Optional getConfigurationParameter(String key); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java new file mode 100644 index 00000000..9a771705 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @Disabled} is used to signal that the annotated test class or + * test method is currently disabled and should not be executed. + * + *

{@code @Disabled} may optionally be declared with a {@linkplain #value + * reason} to document why the annotated test class or test method is disabled. + * + *

When applied at the class level, all test methods within that class + * are automatically disabled as well. + * + *

When applied at the method level, the presence of this annotation does not + * prevent the test class from being instantiated. Rather, it prevents the + * execution of the test method and method-level lifecycle callbacks such as + * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding + * extension APIs. + * + * @since 5.0 + * @see #value + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.extension.ExecutionCondition + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +public @interface Disabled { + + /** + * The reason this annotated test class or test method is disabled. + */ + String value() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java new file mode 100644 index 00000000..e99ee34c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @DisplayName} is used to declare a {@linkplain #value custom display + * name} for the annotated test class or test method. + * + *

Display names are typically used for test reporting in IDEs and build + * tools and may contain spaces, special characters, and even emoji. + * + * @since 5.0 + * @see Test + * @see Tag + * @see TestInfo + * @see DisplayNameGeneration + * @see DisplayNameGenerator + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +public @interface DisplayName { + + /** + * Custom display name for the annotated class or method. + * + * @return a custom display name; never blank or consisting solely of + * whitespace + */ + String value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java new file mode 100644 index 00000000..bb0e1bf9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @DisplayNameGeneration} is used to declare a custom display name + * generator for the annotated test class. + * + *

This annotation is inherited from superclasses and implemented + * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() + * enclosing classes} for {@link Nested @Nested} test classes. + * + *

As an alternative to {@code @DisplayNameGeneration}, a global + * {@link DisplayNameGenerator} can be configured for the entire test suite via + * the {@value DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME} configuration parameter. See + * the User Guide for details. Note, however, that a {@code @DisplayNameGeneration} + * declaration always overrides a global {@code DisplayNameGenerator}. + * + * @since 5.4 + * @see DisplayName + * @see DisplayNameGenerator + * @see IndicativeSentencesGeneration + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@API(status = STABLE, since = "5.7") +public @interface DisplayNameGeneration { + + /** + * Custom display name generator. + * + * @return custom display name generator class + */ + Class value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java new file mode 100644 index 00000000..7d62049a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java @@ -0,0 +1,378 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.support.ModifierSupport.isStatic; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code DisplayNameGenerator} defines the SPI for generating display names + * programmatically. + * + *

Display names are typically used for test reporting in IDEs and build + * tools and may contain spaces, special characters, and even emoji. + * + *

Concrete implementations must have a default constructor. + * + *

A {@link DisplayNameGenerator} can be configured globally for the + * entire test suite via the {@value #DEFAULT_GENERATOR_PROPERTY_NAME} + * configuration parameter (see the User Guide for details) or locally + * for a test class via the {@link DisplayNameGeneration @DisplayNameGeneration} + * annotation. + * + *

Built-in Implementations

+ *
    + *
  • {@link Standard}
  • + *
  • {@link Simple}
  • + *
  • {@link ReplaceUnderscores}
  • + *
  • {@link IndicativeSentences}
  • + *
+ * + * @since 5.4 + * @see DisplayName @DisplayName + * @see DisplayNameGeneration @DisplayNameGeneration + */ +@API(status = STABLE, since = "5.7") +public interface DisplayNameGenerator { + + /** + * Property name used to set the default display name generator class name: + * {@value} + * + *

Supported Values

+ * + *

Supported values include fully qualified class names for types that + * implement {@link DisplayNameGenerator}. + * + *

If not specified, the default is + * {@link DisplayNameGenerator.Standard}. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_GENERATOR_PROPERTY_NAME = "junit.jupiter.displayname.generator.default"; + + /** + * Generate a display name for the given top-level or {@code static} nested test class. + * + * @param testClass the class to generate a name for; never {@code null} + * @return the display name for the class; never {@code null} or blank + */ + String generateDisplayNameForClass(Class testClass); + + /** + * Generate a display name for the given {@link Nested @Nested} inner test class. + * + * @param nestedClass the class to generate a name for; never {@code null} + * @return the display name for the nested class; never {@code null} or blank + */ + String generateDisplayNameForNestedClass(Class nestedClass); + + /** + * Generate a display name for the given method. + * + * @implNote The class instance supplied as {@code testClass} may differ from + * the class returned by {@code testMethod.getDeclaringClass()} — for + * example, when a test method is inherited from a superclass. + * + * @param testClass the class the test method is invoked on; never {@code null} + * @param testMethod method to generate a display name for; never {@code null} + * @return the display name for the test; never {@code null} or blank + */ + String generateDisplayNameForMethod(Class testClass, Method testMethod); + + /** + * Generate a string representation of the formal parameters of the supplied + * method, consisting of the {@linkplain Class#getSimpleName() simple names} + * of the parameter types, separated by commas, and enclosed in parentheses. + * + * @param method the method from to extract the parameter types from; never + * {@code null} + * @return a string representation of all parameter types of the supplied + * method or {@code "()"} if the method declares no parameters + */ + static String parameterTypesAsString(Method method) { + Preconditions.notNull(method, "Method must not be null"); + return '(' + ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes()) + ')'; + } + + /** + * Standard {@code DisplayNameGenerator}. + * + *

This implementation matches the standard display name generation + * behavior in place since JUnit Jupiter 5.0 was released. + */ + class Standard implements DisplayNameGenerator { + + static final DisplayNameGenerator INSTANCE = new Standard(); + + public Standard() { + } + + @Override + public String generateDisplayNameForClass(Class testClass) { + String name = testClass.getName(); + int lastDot = name.lastIndexOf('.'); + return name.substring(lastDot + 1); + } + + @Override + public String generateDisplayNameForNestedClass(Class nestedClass) { + return nestedClass.getSimpleName(); + } + + @Override + public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + return testMethod.getName() + parameterTypesAsString(testMethod); + } + } + + /** + * Simple {@code DisplayNameGenerator} that removes trailing parentheses + * for methods with no parameters. + * + *

This generator extends the functionality of {@link Standard} by + * removing parentheses ({@code '()'}) found at the end of method names + * with no parameters. + */ + class Simple extends Standard { + + static final DisplayNameGenerator INSTANCE = new Simple(); + + public Simple() { + } + + @Override + public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + String displayName = testMethod.getName(); + if (hasParameters(testMethod)) { + displayName += ' ' + parameterTypesAsString(testMethod); + } + return displayName; + } + + private static boolean hasParameters(Method method) { + return method.getParameterCount() > 0; + } + + } + + /** + * {@code DisplayNameGenerator} that replaces underscores with spaces. + * + *

This generator extends the functionality of {@link Simple} by + * replacing all underscores ({@code '_'}) found in class and method names + * with spaces ({@code ' '}). + */ + class ReplaceUnderscores extends Simple { + + static final DisplayNameGenerator INSTANCE = new ReplaceUnderscores(); + + public ReplaceUnderscores() { + } + + @Override + public String generateDisplayNameForClass(Class testClass) { + return replaceUnderscores(super.generateDisplayNameForClass(testClass)); + } + + @Override + public String generateDisplayNameForNestedClass(Class nestedClass) { + return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass)); + } + + @Override + public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod)); + } + + private static String replaceUnderscores(String name) { + return name.replace('_', ' '); + } + + } + + /** + * {@code DisplayNameGenerator} that generates complete sentences. + * + *

This generator generates display names that build up complete sentences + * by concatenating the names of the test and the enclosing classes. The + * sentence fragments are concatenated using a separator. The separator and + * the display name generator for individual sentence fragments can be configured + * via the {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} + * annotation. + * + * @since 5.7 + */ + @API(status = EXPERIMENTAL, since = "5.7") + class IndicativeSentences implements DisplayNameGenerator { + + static final DisplayNameGenerator INSTANCE = new IndicativeSentences(); + + public IndicativeSentences() { + } + + @Override + public String generateDisplayNameForClass(Class testClass) { + return getGeneratorFor(testClass).generateDisplayNameForClass(testClass); + } + + @Override + public String generateDisplayNameForNestedClass(Class nestedClass) { + return getSentenceBeginning(nestedClass); + } + + @Override + public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + return getSentenceBeginning(testClass) + getFragmentSeparator(testClass) + + getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod); + } + + private String getSentenceBeginning(Class testClass) { + Class enclosingClass = testClass.getEnclosingClass(); + boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); + Optional displayName = findAnnotation(testClass, DisplayName.class)// + .map(DisplayName::value).map(String::trim); + + if (topLevelTestClass) { + if (displayName.isPresent()) { + return displayName.get(); + } + Class generatorClass = findDisplayNameGeneration(testClass)// + .map(DisplayNameGeneration::value)// + .filter(not(IndicativeSentences.class))// + .orElse(null); + if (generatorClass != null) { + return getDisplayNameGenerator(generatorClass).generateDisplayNameForClass(testClass); + } + return generateDisplayNameForClass(testClass); + } + + // Only build prefix based on the enclosing class if the enclosing + // class is also configured to use the IndicativeSentences generator. + boolean buildPrefix = findDisplayNameGeneration(enclosingClass)// + .map(DisplayNameGeneration::value)// + .filter(IndicativeSentences.class::equals)// + .isPresent(); + + String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : ""); + + return prefix + displayName.orElseGet( + () -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass)); + } + + /** + * Get the sentence fragment separator. + * + *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} + * is present (searching enclosing classes if not found locally), the + * configured {@link IndicativeSentencesGeneration#separator() separator} + * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_SEPARATOR} + * will be used. + * + * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} + * @return the sentence fragment separator + */ + private static String getFragmentSeparator(Class testClass) { + return findIndicativeSentencesGeneration(testClass)// + .map(IndicativeSentencesGeneration::separator)// + .orElse(IndicativeSentencesGeneration.DEFAULT_SEPARATOR); + } + + /** + * Get the display name generator to use for the supplied test class. + * + *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} + * is present (searching enclosing classes if not found locally), the + * configured {@link IndicativeSentencesGeneration#generator() generator} + * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_GENERATOR} + * will be used. + * + * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} + * @return the {@code DisplayNameGenerator} instance to use + */ + private static DisplayNameGenerator getGeneratorFor(Class testClass) { + return findIndicativeSentencesGeneration(testClass)// + .map(IndicativeSentencesGeneration::generator)// + .filter(not(IndicativeSentences.class))// + .map(DisplayNameGenerator::getDisplayNameGenerator)// + .orElseGet(() -> getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR)); + } + + /** + * Find the first {@code DisplayNameGeneration} annotation that is either + * directly present, meta-present, or indirectly present + * on the supplied {@code testClass} or on an enclosing class. + * + * @param testClass the test class on which to find the annotation; never {@code null} + * @return an {@code Optional} containing the annotation, potentially empty if not found + */ + private static Optional findDisplayNameGeneration(Class testClass) { + return findAnnotation(testClass, DisplayNameGeneration.class, true); + } + + /** + * Find the first {@code IndicativeSentencesGeneration} annotation that is either + * directly present, meta-present, or indirectly present + * on the supplied {@code testClass} or on an enclosing class. + * + * @param testClass the test class on which to find the annotation; never {@code null} + * @return an {@code Optional} containing the annotation, potentially empty if not found + */ + private static Optional findIndicativeSentencesGeneration(Class testClass) { + return findAnnotation(testClass, IndicativeSentencesGeneration.class, true); + } + + private static Predicate> not(Class clazz) { + return ((Predicate>) clazz::equals).negate(); + } + + } + + /** + * Return the {@code DisplayNameGenerator} instance corresponding to the + * given {@code Class}. + * + * @param generatorClass the generator's {@code Class}; never {@code null}, + * has to be a {@code DisplayNameGenerator} implementation + * @return a {@code DisplayNameGenerator} implementation instance + */ + static DisplayNameGenerator getDisplayNameGenerator(Class generatorClass) { + Preconditions.notNull(generatorClass, "Class must not be null"); + Preconditions.condition(DisplayNameGenerator.class.isAssignableFrom(generatorClass), + "Class must be a DisplayNameGenerator implementation"); + if (generatorClass == Standard.class) { + return Standard.INSTANCE; + } + if (generatorClass == Simple.class) { + return Simple.INSTANCE; + } + if (generatorClass == ReplaceUnderscores.class) { + return ReplaceUnderscores.INSTANCE; + } + if (generatorClass == IndicativeSentences.class) { + return IndicativeSentences.INSTANCE; + } + return (DisplayNameGenerator) ReflectionUtils.newInstance(generatorClass); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java new file mode 100644 index 00000000..dcc48186 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.net.URI; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +/** + * A {@code DynamicContainer} is a container generated at runtime. + * + *

It is composed of a {@linkplain DynamicNode#getDisplayName display name} + * and an {@link Iterable} or {@link Stream} of {@link DynamicNode DynamicNodes}. + * + *

Instances of {@code DynamicContainer} must be generated by factory methods + * annotated with {@link TestFactory @TestFactory}. + * + * @since 5.0 + * @see #dynamicContainer(String, Iterable) + * @see #dynamicContainer(String, Stream) + * @see TestFactory + * @see DynamicTest + */ +@API(status = MAINTAINED, since = "5.3") +public class DynamicContainer extends DynamicNode { + + /** + * Factory for creating a new {@code DynamicContainer} for the supplied display + * name and collection of dynamic nodes. + * + *

The collection of dynamic nodes must not contain {@code null} elements. + * + * @param displayName the display name for the dynamic container; never + * {@code null} or blank + * @param dynamicNodes collection of dynamic nodes to execute; + * never {@code null} + * @see #dynamicContainer(String, Stream) + */ + public static DynamicContainer dynamicContainer(String displayName, Iterable dynamicNodes) { + return dynamicContainer(displayName, null, StreamSupport.stream(dynamicNodes.spliterator(), false)); + } + + /** + * Factory for creating a new {@code DynamicContainer} for the supplied display + * name and stream of dynamic nodes. + * + *

The stream of dynamic nodes must not contain {@code null} elements. + * + * @param displayName the display name for the dynamic container; never + * {@code null} or blank + * @param dynamicNodes stream of dynamic nodes to execute; + * never {@code null} + * @see #dynamicContainer(String, Iterable) + */ + public static DynamicContainer dynamicContainer(String displayName, Stream dynamicNodes) { + return dynamicContainer(displayName, null, dynamicNodes); + } + + /** + * Factory for creating a new {@code DynamicContainer} for the supplied display + * name, custom test source {@link URI}, and stream of dynamic nodes. + * + *

The stream of dynamic nodes must not contain {@code null} elements. + * + * @param displayName the display name for the dynamic container; never + * {@code null} or blank + * @param testSourceUri a custom test source URI for the dynamic container; + * may be {@code null} if the framework should generate the test source based + * on the {@code @TestFactory} method + * @param dynamicNodes stream of dynamic nodes to execute; never {@code null} + * @since 5.3 + * @see #dynamicContainer(String, Iterable) + */ + public static DynamicContainer dynamicContainer(String displayName, URI testSourceUri, + Stream dynamicNodes) { + + return new DynamicContainer(displayName, testSourceUri, dynamicNodes); + } + + private final Stream children; + + private DynamicContainer(String displayName, URI testSourceUri, Stream children) { + super(displayName, testSourceUri); + Preconditions.notNull(children, "children must not be null"); + this.children = children; + } + + /** + * Get the {@link Stream} of {@link DynamicNode DynamicNodes} associated + * with this {@code DynamicContainer}. + */ + public Stream getChildren() { + return children; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java new file mode 100644 index 00000000..c1a15177 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.net.URI; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code DynamicNode} serves as the abstract base class for a container or a + * test case generated at runtime. + * + * @since 5.0 + * @see DynamicTest + * @see DynamicContainer + */ +@API(status = MAINTAINED, since = "5.3") +public abstract class DynamicNode { + + private final String displayName; + + /** Custom test source {@link URI} associated with this node; potentially {@code null}. */ + private final URI testSourceUri; + + DynamicNode(String displayName, URI testSourceUri) { + this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank"); + this.testSourceUri = testSourceUri; + } + + /** + * Get the display name of this {@code DynamicNode}. + * + * @return the display name + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * Get the custom test source {@link URI} of this {@code DynamicNode}. + * + * @return an {@code Optional} containing the custom test source {@link URI}; + * never {@code null} but potentially empty + * @since 5.3 + */ + public Optional getTestSourceUri() { + return Optional.ofNullable(testSourceUri); + } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("displayName", displayName) // + .append("testSourceUri", testSourceUri) // + .toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java new file mode 100644 index 00000000..93595166 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java @@ -0,0 +1,243 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.util.Spliterator.ORDERED; +import static java.util.Spliterators.spliteratorUnknownSize; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.net.URI; +import java.util.Iterator; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.commons.util.Preconditions; + +/** + * A {@code DynamicTest} is a test case generated at runtime. + * + *

It is composed of a {@linkplain DynamicNode#getDisplayName display name} + * and an {@link #getExecutable Executable}. + * + *

Instances of {@code DynamicTest} must be generated by factory methods + * annotated with {@link TestFactory @TestFactory}. + * + *

Note that dynamic tests are quite different from standard {@link Test @Test} + * cases since callbacks such as {@link BeforeEach @BeforeEach} and + * {@link AfterEach @AfterEach} methods are not executed for dynamic tests. + * + * @since 5.0 + * @see #dynamicTest(String, Executable) + * @see #stream(Iterator, Function, ThrowingConsumer) + * @see Test + * @see TestFactory + * @see DynamicContainer + * @see Executable + */ +@API(status = MAINTAINED, since = "5.3") +public class DynamicTest extends DynamicNode { + + /** + * Factory for creating a new {@code DynamicTest} for the supplied display + * name and executable code block. + * + * @param displayName the display name for the dynamic test; never + * {@code null} or blank + * @param executable the executable code block for the dynamic test; + * never {@code null} + * @see #stream(Iterator, Function, ThrowingConsumer) + */ + public static DynamicTest dynamicTest(String displayName, Executable executable) { + return new DynamicTest(displayName, null, executable); + } + + /** + * Factory for creating a new {@code DynamicTest} for the supplied display + * name, custom test source {@link URI}, and executable code block. + * + * @param displayName the display name for the dynamic test; never + * {@code null} or blank + * @param testSourceUri a custom test source URI for the dynamic test; may + * be {@code null} if the framework should generate the test source based on + * the {@code @TestFactory} method + * @param executable the executable code block for the dynamic test; + * never {@code null} + * @since 5.3 + * @see #stream(Iterator, Function, ThrowingConsumer) + */ + public static DynamicTest dynamicTest(String displayName, URI testSourceUri, Executable executable) { + return new DynamicTest(displayName, testSourceUri, executable); + } + + /** + * Generate a stream of dynamic tests based on the given generator and test + * executor. + * + *

Use this method when the set of dynamic tests is nondeterministic in + * nature or when the input comes from an existing {@link Iterator}. See + * {@link #stream(Stream, Function, ThrowingConsumer)} as an alternative. + * + *

The given {@code inputGenerator} is responsible for generating + * input values. A {@link DynamicTest} will be added to the resulting + * stream for each dynamically generated input value, using the given + * {@code displayNameGenerator} and {@code testExecutor}. + * + * @param inputGenerator an {@code Iterator} that serves as a dynamic + * input generator; never {@code null} + * @param displayNameGenerator a function that generates a display name + * based on an input value; never {@code null} + * @param testExecutor a consumer that executes a test based on an input + * value; never {@code null} + * @param the type of input generated by the {@code inputGenerator} + * and used by the {@code displayNameGenerator} and {@code testExecutor} + * @return a stream of dynamic tests based on the given generator and + * executor; never {@code null} + * @see #dynamicTest(String, Executable) + * @see #stream(Stream, Function, ThrowingConsumer) + */ + public static Stream stream(Iterator inputGenerator, + Function displayNameGenerator, ThrowingConsumer testExecutor) { + + Preconditions.notNull(inputGenerator, "inputGenerator must not be null"); + + return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), + displayNameGenerator, testExecutor); + } + + /** + * Generate a stream of dynamic tests based on the given input stream and + * test executor. + * + *

Use this method when the set of dynamic tests is nondeterministic in + * nature or when the input comes from an existing {@link Stream}. See + * {@link #stream(Iterator, Function, ThrowingConsumer)} as an alternative. + * + *

The given {@code inputStream} is responsible for supplying input values. + * A {@link DynamicTest} will be added to the resulting stream for each + * dynamically supplied input value, using the given {@code displayNameGenerator} + * and {@code testExecutor}. + * + * @param inputStream a {@code Stream} that supplies dynamic input values; + * never {@code null} + * @param displayNameGenerator a function that generates a display name + * based on an input value; never {@code null} + * @param testExecutor a consumer that executes a test based on an input + * value; never {@code null} + * @param the type of input supplied by the {@code inputStream} + * and used by the {@code displayNameGenerator} and {@code testExecutor} + * @return a stream of dynamic tests based on the given generator and + * executor; never {@code null} + * @since 5.7 + * @see #dynamicTest(String, Executable) + * @see #stream(Iterator, Function, ThrowingConsumer) + */ + @API(status = MAINTAINED, since = "5.7") + public static Stream stream(Stream inputStream, + Function displayNameGenerator, ThrowingConsumer testExecutor) { + + Preconditions.notNull(inputStream, "inputStream must not be null"); + Preconditions.notNull(displayNameGenerator, "displayNameGenerator must not be null"); + Preconditions.notNull(testExecutor, "testExecutor must not be null"); + + return inputStream // + .map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input))); + } + + /** + * Generate a stream of dynamic tests based on the given generator and test + * executor. + * + *

Use this method when the set of dynamic tests is nondeterministic in + * nature or when the input comes from an existing {@link Iterator}. See + * {@link #stream(Stream, ThrowingConsumer)} as an alternative. + * + *

The given {@code inputGenerator} is responsible for generating + * input values and display names. A {@link DynamicTest} will be added to + * the resulting stream for each dynamically generated input value, + * using the given {@code testExecutor}. + * + * @param inputGenerator an {@code Iterator} with {@code Named} values + * that serves as a dynamic input generator; never {@code null} + * @param testExecutor a consumer that executes a test based on an input + * value; never {@code null} + * @param the type of input generated by the {@code inputGenerator} + * and used by the {@code testExecutor} + * @return a stream of dynamic tests based on the given generator and + * executor; never {@code null} + * @since 5.8 + * + * @see #dynamicTest(String, Executable) + * @see #stream(Stream, ThrowingConsumer) + * @see Named + */ + @API(status = MAINTAINED, since = "5.8") + public static Stream stream(Iterator> inputGenerator, + ThrowingConsumer testExecutor) { + Preconditions.notNull(inputGenerator, "inputGenerator must not be null"); + + return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), testExecutor); + } + + /** + * Generate a stream of dynamic tests based on the given input stream and + * test executor. + * + *

Use this method when the set of dynamic tests is nondeterministic in + * nature or when the input comes from an existing {@link Stream}. See + * {@link #stream(Iterator, ThrowingConsumer)} as an alternative. + * + *

The given {@code inputStream} is responsible for supplying input values + * and display names. A {@link DynamicTest} will be added to the resulting stream for + * each dynamically supplied input value, using the given {@code testExecutor}. + * + * @param inputStream a {@code Stream} that supplies dynamic {@code Named} + * input values; never {@code null} + * @param testExecutor a consumer that executes a test based on an input + * value; never {@code null} + * @param the type of input supplied by the {@code inputStream} + * and used by the {@code displayNameGenerator} and {@code testExecutor} + * @return a stream of dynamic tests based on the given generator and + * executor; never {@code null} + * @since 5.8 + * + * @see #dynamicTest(String, Executable) + * @see #stream(Iterator, ThrowingConsumer) + * @see Named + */ + @API(status = MAINTAINED, since = "5.8") + public static Stream stream(Stream> inputStream, + ThrowingConsumer testExecutor) { + Preconditions.notNull(inputStream, "inputStream must not be null"); + Preconditions.notNull(testExecutor, "testExecutor must not be null"); + + return inputStream // + .map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload()))); + } + + private final Executable executable; + + private DynamicTest(String displayName, URI testSourceUri, Executable executable) { + super(displayName, testSourceUri); + this.executable = Preconditions.notNull(executable, "executable must not be null"); + } + + /** + * Get the {@code executable} code block associated with this {@code DynamicTest}. + */ + public Executable getExecutable() { + return this.executable; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java new file mode 100644 index 00000000..6925a2b6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; + +/** + * {@code @IndicativeSentencesGeneration} is used to register the + * {@link IndicativeSentences} display name generator and configure it. + * + *

The {@link #separator} for sentence fragments and the display name + * {@link #generator} for sentence fragments are configurable. If this annotation + * is declared without any attributes — for example, + * {@code @IndicativeSentencesGeneration} or {@code @IndicativeSentencesGeneration()} + * — the default configuration will be used. + * + *

This annotation is inherited from superclasses and implemented + * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() + * enclosing classes} for {@link Nested @Nested} test classes. + * + * @since 5.7 + * @see DisplayName + * @see DisplayNameGenerator + * @see DisplayNameGenerator.IndicativeSentences + * @see DisplayNameGeneration + */ +@DisplayNameGeneration(IndicativeSentences.class) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@API(status = EXPERIMENTAL, since = "5.7") +public @interface IndicativeSentencesGeneration { + + String DEFAULT_SEPARATOR = ", "; + + Class DEFAULT_GENERATOR = DisplayNameGenerator.Standard.class; + + /** + * Custom separator for sentence fragments. + * + *

Defaults to {@value #DEFAULT_SEPARATOR}. + */ + String separator() default DEFAULT_SEPARATOR; + + /** + * Custom display name generator to use for sentence fragments. + * + *

Defaults to {@link DisplayNameGenerator.Standard}. + */ + Class generator() default DisplayNameGenerator.Standard.class; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java new file mode 100644 index 00000000..20550832 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code MethodDescriptor} encapsulates functionality for a given {@link Method}. + * + * @since 5.4 + * @see MethodOrdererContext + */ +@API(status = STABLE, since = "5.7") +public interface MethodDescriptor { + + /** + * Get the method for this descriptor. + * + * @return the method; never {@code null} + */ + Method getMethod(); + + /** + * Get the display name for this descriptor's {@link #getMethod() method}. + * + * @return the display name for this descriptor's method; never {@code null} + * or blank + * @since 5.7 + */ + @API(status = EXPERIMENTAL, since = "5.7") + String getDisplayName(); + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the {@link Method} for + * this descriptor. + * + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @see #findAnnotation(Class) + * @see #findRepeatableAnnotations(Class) + */ + boolean isAnnotated(Class annotationType); + + /** + * Find the first annotation of {@code annotationType} that is either + * present or meta-present on the {@link Method} for + * this descriptor. + * + * @param the annotation type + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @see #isAnnotated(Class) + * @see #findRepeatableAnnotations(Class) + */ + Optional findAnnotation(Class annotationType); + + /** + * Find all repeatable {@linkplain Annotation annotations} of + * {@code annotationType} that are either present or + * meta-present on the {@link Method} for this descriptor. + * + * @param the annotation type + * @param annotationType the repeatable annotation type to search for; never + * {@code null} + * @return the list of all such annotations found; neither {@code null} nor + * mutable, but potentially empty + * @see #isAnnotated(Class) + * @see #findAnnotation(Class) + * @see java.lang.annotation.Repeatable + */ + List findRepeatableAnnotations(Class annotationType); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java new file mode 100644 index 00000000..7818020d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java @@ -0,0 +1,334 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.util.Comparator.comparingInt; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassUtils; + +/** + * {@code MethodOrderer} defines the API for ordering the test methods + * in a given test class. + * + *

In this context, the term "test method" refers to any method annotated with + * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, + * {@code @TestFactory}, or {@code @TestTemplate}. + * + *

A {@link MethodOrderer} can be configured globally for the entire + * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration + * parameter (see the User Guide for details) or locally for a test + * class via the {@link TestMethodOrder @TestMethodOrder} annotation. + * + *

Built-in Implementations

+ * + *

JUnit Jupiter provides the following built-in {@code MethodOrderer} + * implementations. + * + *

    + *
  • {@link MethodName}
  • + *
  • {@link OrderAnnotation}
  • + *
  • {@link Random}
  • + *
+ * + * @since 5.4 + * @see TestMethodOrder + * @see MethodOrdererContext + * @see #orderMethods(MethodOrdererContext) + * @see ClassOrderer + */ +@API(status = STABLE, since = "5.7") +public interface MethodOrderer { + + /** + * Property name used to set the default method orderer class name: {@value} + * + *

Supported Values

+ * + *

Supported values include fully qualified class names for types that + * implement {@link org.junit.jupiter.api.MethodOrderer}. + * + *

If not specified, test methods will be ordered using an algorithm that + * is deterministic but intentionally non-obvious. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testmethod.order.default"; + + /** + * Order the methods encapsulated in the supplied {@link MethodOrdererContext}. + * + *

The methods to order or sort are made indirectly available via + * {@link MethodOrdererContext#getMethodDescriptors()}. Since this method + * has a {@code void} return type, the list of method descriptors must be + * modified directly. + * + *

For example, a simplified implementation of the {@link Random} + * {@code MethodOrderer} might look like the following. + * + *

+	 * public void orderMethods(MethodOrdererContext context) {
+	 *     Collections.shuffle(context.getMethodDescriptors());
+	 * }
+	 * 
+ * + * @param context the {@code MethodOrdererContext} containing the + * {@linkplain MethodDescriptor method descriptors} to order; never {@code null} + * @see #getDefaultExecutionMode() + */ + void orderMethods(MethodOrdererContext context); + + /** + * Get the default {@link ExecutionMode} for the test class + * configured with this {@link MethodOrderer}. + * + *

This method is guaranteed to be invoked after + * {@link #orderMethods(MethodOrdererContext)} which allows implementations + * of this method to determine the appropriate return value programmatically, + * potentially based on actions that were taken in {@code orderMethods()}. + * + *

Defaults to {@link ExecutionMode#SAME_THREAD SAME_THREAD}, since + * ordered methods are typically sorted in a fashion that would conflict + * with concurrent execution. + * + *

In case the ordering does not conflict with concurrent execution, + * implementations should return an empty {@link Optional} to signal that + * the engine should decide which execution mode to use. + * + *

Can be overridden via an explicit + * {@link org.junit.jupiter.api.parallel.Execution @Execution} declaration + * on the test class or in concrete implementations of the + * {@code MethodOrderer} API. + * + * @return the default {@code ExecutionMode}; never {@code null} but + * potentially empty + * @see #orderMethods(MethodOrdererContext) + */ + default Optional getDefaultExecutionMode() { + return Optional.of(ExecutionMode.SAME_THREAD); + } + + /** + * {@code MethodOrderer} that sorts methods alphanumerically based on their + * names using {@link String#compareTo(String)}. + * + *

If two methods have the same name, {@code String} representations of + * their formal parameter lists will be used as a fallback for comparing the + * methods. + * + * @since 5.4 + * @deprecated as of JUnit Jupiter 5.7 in favor of {@link MethodOrderer.MethodName}; + * to be removed in 6.0 + */ + @API(status = DEPRECATED, since = "5.7") + @Deprecated + class Alphanumeric extends MethodName { + + public Alphanumeric() { + } + } + + /** + * {@code MethodOrderer} that sorts methods alphanumerically based on their + * names using {@link String#compareTo(String)}. + * + *

If two methods have the same name, {@code String} representations of + * their formal parameter lists will be used as a fallback for comparing the + * methods. + * + * @since 5.7 + */ + @API(status = EXPERIMENTAL, since = "5.7") + class MethodName implements MethodOrderer { + + public MethodName() { + } + + /** + * Sort the methods encapsulated in the supplied + * {@link MethodOrdererContext} alphanumerically based on their names + * and formal parameter lists. + */ + @Override + public void orderMethods(MethodOrdererContext context) { + context.getMethodDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator. // + comparing(descriptor -> descriptor.getMethod().getName())// + .thenComparing(descriptor -> parameterList(descriptor.getMethod())); + + private static String parameterList(Method method) { + return ClassUtils.nullSafeToString(method.getParameterTypes()); + } + } + + /** + * {@code MethodOrderer} that sorts methods alphanumerically based on their + * display names using {@link String#compareTo(String)} + * + * @since 5.7 + */ + @API(status = EXPERIMENTAL, since = "5.7") + class DisplayName implements MethodOrderer { + + public DisplayName() { + } + + /** + * Sort the methods encapsulated in the supplied + * {@link MethodOrdererContext} alphanumerically based on their display + * names. + */ + @Override + public void orderMethods(MethodOrdererContext context) { + context.getMethodDescriptors().sort(comparator); + } + + private static final Comparator comparator = Comparator.comparing( + MethodDescriptor::getDisplayName); + } + + /** + * {@code MethodOrderer} that sorts methods based on the {@link Order @Order} + * annotation. + * + *

Any methods that are assigned the same order value will be sorted + * arbitrarily adjacent to each other. + * + *

Any methods not annotated with {@code @Order} will be assigned the + * {@linkplain Order#DEFAULT default order} value which will effectively cause them + * to appear at the end of the sorted list, unless certain methods are assigned + * an explicit order value greater than the default order value. Any methods + * assigned an explicit order value greater than the default order value will + * appear after non-annotated methods in the sorted list. + */ + class OrderAnnotation implements MethodOrderer { + + public OrderAnnotation() { + } + + /** + * Sort the methods encapsulated in the supplied + * {@link MethodOrdererContext} based on the {@link Order @Order} + * annotation. + */ + @Override + public void orderMethods(MethodOrdererContext context) { + context.getMethodDescriptors().sort(comparingInt(OrderAnnotation::getOrder)); + } + + private static int getOrder(MethodDescriptor descriptor) { + return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT); + } + } + + /** + * {@code MethodOrderer} that orders methods pseudo-randomly. + * + *

Custom Seed

+ * + *

By default, the random seed used for ordering methods is the + * value returned by {@link System#nanoTime()} during static initialization + * of this class. In order to support repeatable builds, the value of the + * default random seed is logged at {@code CONFIG} level. In addition, a + * custom seed (potentially the default seed from the previous test plan + * execution) may be specified via the {@value ClassOrderer.Random#RANDOM_SEED_PROPERTY_NAME} + * configuration parameter which can be supplied via the {@code Launcher} + * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit + * Platform configuration file (i.e., a file named {@code junit-platform.properties} + * in the root of the class path). Consult the User Guide for further information. + * + * @see Random#RANDOM_SEED_PROPERTY_NAME + * @see java.util.Random + */ + class Random implements MethodOrderer { + + private static final Logger logger = LoggerFactory.getLogger(Random.class); + + /** + * Default seed, which is generated during initialization of this class + * via {@link System#nanoTime()} for reproducibility of tests. + */ + private static final long DEFAULT_SEED; + + static { + DEFAULT_SEED = System.nanoTime(); + logger.config(() -> "MethodOrderer.Random default seed: " + DEFAULT_SEED); + } + + /** + * Property name used to set the random seed used by this + * {@code MethodOrderer}: {@value} + * + *

The same property is used by {@link ClassOrderer.Random} for + * consistency between the two random orderers. + * + *

Supported Values

+ * + *

Supported values include any string that can be converted to a + * {@link Long} via {@link Long#valueOf(String)}. + * + *

If not specified or if the specified value cannot be converted to + * a {@link Long}, the default random seed will be used (see the + * {@linkplain Random class-level Javadoc} for details). + * + * @see ClassOrderer.Random + */ + public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed"; + + public Random() { + } + + /** + * Order the methods encapsulated in the supplied + * {@link MethodOrdererContext} pseudo-randomly. + */ + @Override + public void orderMethods(MethodOrdererContext context) { + Collections.shuffle(context.getMethodDescriptors(), + new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED))); + } + + private Optional getCustomSeed(MethodOrdererContext context) { + return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> { + Long seed = null; + try { + seed = Long.valueOf(configurationParameter); + logger.config( + () -> String.format("Using custom seed for configuration parameter [%s] with value [%s].", + RANDOM_SEED_PROPERTY_NAME, configurationParameter)); + } + catch (NumberFormatException ex) { + logger.warn(ex, + () -> String.format( + "Failed to convert configuration parameter [%s] with value [%s] to a long. " + + "Using default seed [%s] as fallback.", + RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); + } + return seed; + }); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java new file mode 100644 index 00000000..cb585b70 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code MethodOrdererContext} encapsulates the context in which + * a {@link MethodOrderer} will be invoked. + * + * @since 5.4 + * @see MethodOrderer + * @see MethodDescriptor + */ +@API(status = STABLE, since = "5.7") +public interface MethodOrdererContext { + + /** + * Get the test class for this context. + * + * @return the test class; never {@code null} + */ + Class getTestClass(); + + /** + * Get the list of {@linkplain MethodDescriptor method descriptors} to + * order. + * + * @return the list of method descriptors; never {@code null} + */ + List getMethodDescriptors(); + + /** + * Get the configuration parameter stored under the specified {@code key}. + * + *

If no such key is present in the {@code ConfigurationParameters} for + * the JUnit Platform, an attempt will be made to look up the value as a + * JVM system property. If no such system property exists, an attempt will + * be made to look up the value in the JUnit Platform properties file. + * + * @param key the key to look up; never {@code null} or blank + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @see System#getProperty(String) + * @see org.junit.platform.engine.ConfigurationParameters + */ + Optional getConfigurationParameter(String key); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java new file mode 100644 index 00000000..6dee03e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code Named} is a container that associates a name with a given payload. + * + * @param the type of the payload + * + * @since 5.8 + */ +@API(status = STABLE, since = "5.8") +public interface Named { + + /** + * Factory method for creating an instance of {@code Named} based on a + * {@code name} and a {@code payload}. + * + * @param name the name associated with the payload; never {@code null} or + * blank + * @param payload the object that serves as the payload; may be {@code null} + * depending on the use case + * @param the type of the payload + * @return an instance of {@code Named}; never {@code null} + * @see #named(String, java.lang.Object) + */ + static Named of(String name, T payload) { + Preconditions.notBlank(name, "name must not be null or blank"); + + return new Named() { + @Override + public String getName() { + return name; + } + + @Override + public T getPayload() { + return payload; + } + + @Override + public String toString() { + return name; + } + }; + } + + /** + * Factory method for creating an instance of {@code Named} based on a + * {@code name} and a {@code payload}. + * + *

This method is an alias for {@link Named#of} and is + * intended to be used when statically imported — for example, via: + * {@code import static org.junit.jupiter.api.Named.named;} + * + * @param name the name associated with the payload; never {@code null} or + * blank + * @param payload the object that serves as the payload; may be {@code null} + * depending on the use case + * @param the type of the payload + * @return an instance of {@code Named}; never {@code null} + */ + static Named named(String name, T payload) { + return of(name, payload); + } + + /** + * Get the name of the payload. + * + * @return the name of the payload; never {@code null} or blank + */ + String getName(); + + /** + * Get the payload. + * + * @return the payload; may be {@code null} depending on the use case + */ + T getPayload(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java new file mode 100644 index 00000000..1627010a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +/** + * {@code @Nested} is used to signal that the annotated class is a nested, + * non-static test class (i.e., an inner class) that can share + * setup and state with an instance of its {@linkplain Class#getEnclosingClass() + * enclosing class}. The enclosing class may be a top-level test class or + * another {@code @Nested} test class, and nesting can be arbitrarily deep. + * + *

{@code @Nested} test classes may be ordered via + * {@link TestClassOrder @TestClassOrder} or a global {@link ClassOrderer}. + * + *

Test Instance Lifecycle

+ * + *
    + *
  • A {@code @Nested} test class can be configured with its own + * {@link Lifecycle} mode which may differ from that of an enclosing test + * class.
  • + *
  • A {@code @Nested} test class cannot change the {@link Lifecycle} + * mode of an enclosing test class.
  • + *
+ * + * @since 5.0 + * @see Test + * @see TestInstance + * @see TestClassOrder + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +public @interface Nested { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java new file mode 100644 index 00000000..5b48253f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @Order} is an annotation that is used to configure the + * {@linkplain #value order} in which the annotated element (i.e., field, + * method, or class) should be evaluated or executed relative to other elements + * of the same category. + * + *

When used with + * {@link org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension} or + * {@link org.junit.jupiter.api.extension.ExtendWith @ExtendWith}, + * the category applies to extension fields. When used with + * {@link MethodOrderer.OrderAnnotation}, the category applies to test methods. + * When used with {@link ClassOrderer.OrderAnnotation}, the category applies to + * test classes. + * + *

If {@code @Order} is not explicitly declared on an element, the + * {@link #DEFAULT} order value will be assigned to the element. + * + * @since 5.4 + * @see MethodOrderer.OrderAnnotation + * @see ClassOrderer.OrderAnnotation + * @see org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension + * @see org.junit.jupiter.api.extension.ExtendWith @ExtendWith + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.9") +public @interface Order { + + /** + * Default order value for elements not explicitly annotated with {@code @Order}, + * equal to the value of {@code Integer.MAX_VALUE / 2}. + * + * @since 5.6 + * @see Order#value + */ + int DEFAULT = Integer.MAX_VALUE / 2; + + /** + * The order value for the annotated element (i.e., field, method, or class). + * + *

Elements are ordered based on priority where a lower value has greater + * priority than a higher value. For example, {@link Integer#MAX_VALUE} has + * the lowest priority. + * + * @see #DEFAULT + */ + int value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java new file mode 100644 index 00000000..298de922 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java @@ -0,0 +1,155 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @RepeatedTest} is used to signal that the annotated method is a + * test template method that should be repeated a {@linkplain #value + * specified number of times} with a configurable {@linkplain #name display + * name}. + * + *

Each invocation of the repeated test behaves like the execution of a + * regular {@link Test @Test} method with full support for the same lifecycle + * callbacks and extensions. In addition, the current repetition and total + * number of repetitions can be accessed by having the {@link RepetitionInfo} + * injected. + * + *

{@code @RepeatedTest} methods must not be {@code private} or {@code static} + * and must return {@code void}. + * + *

{@code @RepeatedTest} methods may optionally declare parameters to be + * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver + * ParameterResolvers}. + * + *

{@code @RepeatedTest} may also be used as a meta-annotation in order to + * create a custom composed annotation that inherits the semantics + * of {@code @RepeatedTest}. + * + *

Test Execution Order

+ * + *

By default, test methods will be ordered using an algorithm that is + * deterministic but intentionally nonobvious. This ensures that subsequent runs + * of a test suite execute test methods in the same order, thereby allowing for + * repeatable builds. In this context, a test method is any instance + * method that is directly annotated or meta-annotated with {@code @Test}, + * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or + * {@code @TestTemplate}. + * + *

Although true unit tests typically should not rely on the order + * in which they are executed, there are times when it is necessary to enforce + * a specific test method execution order — for example, when writing + * integration tests or functional tests where the sequence of + * the tests is important, especially in conjunction with + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * + *

To control the order in which test methods are executed, annotate your + * test class or test interface with {@link TestMethodOrder @TestMethodOrder} + * and specify the desired {@link MethodOrderer} implementation. + * + * @since 5.0 + * @see DisplayName + * @see RepetitionInfo + * @see TestTemplate + * @see TestInfo + * @see Test + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +@TestTemplate +public @interface RepeatedTest { + + /** + * Placeholder for the {@linkplain TestInfo#getDisplayName display name} of + * a {@code @RepeatedTest} method: {displayName} + */ + String DISPLAY_NAME_PLACEHOLDER = "{displayName}"; + + /** + * Placeholder for the current repetition count of a {@code @RepeatedTest} + * method: {currentRepetition} + */ + String CURRENT_REPETITION_PLACEHOLDER = "{currentRepetition}"; + + /** + * Placeholder for the total number of repetitions of a {@code @RepeatedTest} + * method: {totalRepetitions} + */ + String TOTAL_REPETITIONS_PLACEHOLDER = "{totalRepetitions}"; + + /** + * Short display name pattern for a repeated test: {@value} + * + * @see #CURRENT_REPETITION_PLACEHOLDER + * @see #TOTAL_REPETITIONS_PLACEHOLDER + * @see #LONG_DISPLAY_NAME + */ + String SHORT_DISPLAY_NAME = "repetition " + CURRENT_REPETITION_PLACEHOLDER + " of " + TOTAL_REPETITIONS_PLACEHOLDER; + + /** + * Long display name pattern for a repeated test: {@value} + * + * @see #DISPLAY_NAME_PLACEHOLDER + * @see #SHORT_DISPLAY_NAME + */ + String LONG_DISPLAY_NAME = DISPLAY_NAME_PLACEHOLDER + " :: " + SHORT_DISPLAY_NAME; + + /** + * The number of repetitions. + * + * @return the number of repetitions; must be greater than zero + */ + int value(); + + /** + * The display name for each repetition of the repeated test. + * + *

Supported placeholders

+ *
    + *
  • {@link #DISPLAY_NAME_PLACEHOLDER}
  • + *
  • {@link #CURRENT_REPETITION_PLACEHOLDER}
  • + *
  • {@link #TOTAL_REPETITIONS_PLACEHOLDER}
  • + *
+ * + *

Defaults to {@link #SHORT_DISPLAY_NAME}, resulting in + * names such as {@code "repetition 1 of 2"}, {@code "repetition 2 of 2"}, + * etc. + * + *

Can be set to {@link #LONG_DISPLAY_NAME}, resulting in + * names such as {@code "myRepeatedTest() :: repetition 1 of 2"}, + * {@code "myRepeatedTest() :: repetition 2 of 2"}, etc. + * + *

Alternatively, you can provide a custom display name, optionally + * using the aforementioned placeholders. + * + * @return a custom display name; never blank or consisting solely of + * whitespace + * @see #SHORT_DISPLAY_NAME + * @see #LONG_DISPLAY_NAME + * @see #DISPLAY_NAME_PLACEHOLDER + * @see #CURRENT_REPETITION_PLACEHOLDER + * @see #TOTAL_REPETITIONS_PLACEHOLDER + * @see TestInfo#getDisplayName() + */ + String name() default SHORT_DISPLAY_NAME; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java new file mode 100644 index 00000000..89f5d1f0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code RepetitionInfo} is used to inject information about the current + * repetition of a repeated test into {@code @RepeatedTest}, {@code @BeforeEach}, + * and {@code @AfterEach} methods. + * + *

If a method parameter is of type {@code RepetitionInfo}, JUnit will + * supply an instance of {@code RepetitionInfo} corresponding to the current + * repeated test as the value for the parameter. + * + *

WARNING: {@code RepetitionInfo} cannot be injected into + * a {@code @BeforeEach} or {@code @AfterEach} method if the corresponding test + * method is not a {@code @RepeatedTest}. Any attempt to do so will result in a + * {@link org.junit.jupiter.api.extension.ParameterResolutionException + * ParameterResolutionException}. + * + * @since 5.0 + * @see RepeatedTest + * @see TestInfo + */ +@API(status = STABLE, since = "5.0") +public interface RepetitionInfo { + + /** + * Get the current repetition of the corresponding + * {@link RepeatedTest @RepeatedTest} method. + */ + int getCurrentRepetition(); + + /** + * Get the total number of repetitions of the corresponding + * {@link RepeatedTest @RepeatedTest} method. + * + * @see RepeatedTest#value + */ + int getTotalRepetitions(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java new file mode 100644 index 00000000..0def1ed3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @Tag} is a {@linkplain Repeatable repeatable} annotation that is + * used to declare a tag for the annotated test class or test method. + * + *

Tags are used to filter which tests are executed for a given test + * plan. For example, a development team may tag tests with values such as + * {@code "fast"}, {@code "slow"}, {@code "ci-server"}, etc. and then supply a + * list of tags to be included in or excluded from the current test plan, + * potentially dependent on the current environment. + * + *

Syntax Rules for Tags

+ *
    + *
  • A tag must not be blank.
  • + *
  • A trimmed tag must not contain whitespace.
  • + *
  • A trimmed tag must not contain ISO control characters.
  • + *
  • A trimmed tag must not contain any of the following + * reserved characters. + *
      + *
    • {@code ,}: comma
    • + *
    • {@code (}: left parenthesis
    • + *
    • {@code )}: right parenthesis
    • + *
    • {@code &}: ampersand
    • + *
    • {@code |}: vertical bar
    • + *
    • {@code !}: exclamation point
    • + *
    + *
  • + *
+ * + * @since 5.0 + * @see Tags + * @see Test + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Repeatable(Tags.class) +@API(status = STABLE, since = "5.0") +public @interface Tag { + + /** + * The tag. + * + *

Note: the tag will first be {@linkplain String#trim() trimmed}. If the + * supplied tag is syntactically invalid after trimming, the error will be + * logged as a warning, and the invalid tag will be effectively ignored. See + * {@linkplain Tag Syntax Rules for Tags}. + */ + String value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java new file mode 100644 index 00000000..0d6526ec --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @Tags} is a container for one or more {@link Tag @Tag} declarations. + * + *

Note, however, that use of the {@code @Tags} container is completely + * optional since {@code @Tag} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.0 + * @see Tag + * @see java.lang.annotation.Repeatable + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@API(status = STABLE, since = "5.0") +public @interface Tags { + + /** + * An array of one or more {@link Tag Tags}. + */ + Tag[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java new file mode 100644 index 00000000..53c520bd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.platform.commons.annotation.Testable; + +/** + * {@code @Test} is used to signal that the annotated method is a + * test method. + * + *

{@code @Test} methods must not be {@code private} or {@code static} + * and must not return a value. + * + *

{@code @Test} methods may optionally declare parameters to be + * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver + * ParameterResolvers}. + * + *

{@code @Test} may also be used as a meta-annotation in order to create + * a custom composed annotation that inherits the semantics of + * {@code @Test}. + * + *

Test Execution Order

+ * + *

By default, test methods will be ordered using an algorithm that is + * deterministic but intentionally nonobvious. This ensures that subsequent runs + * of a test suite execute test methods in the same order, thereby allowing for + * repeatable builds. In this context, a test method is any instance + * method that is directly annotated or meta-annotated with {@code @Test}, + * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or + * {@code @TestTemplate}. + * + *

Although true unit tests typically should not rely on the order + * in which they are executed, there are times when it is necessary to enforce + * a specific test method execution order — for example, when writing + * integration tests or functional tests where the sequence of + * the tests is important, especially in conjunction with + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * + *

To control the order in which test methods are executed, annotate your + * test class or test interface with {@link TestMethodOrder @TestMethodOrder} + * and specify the desired {@link MethodOrderer} implementation. + * + * @since 5.0 + * @see RepeatedTest + * @see org.junit.jupiter.params.ParameterizedTest + * @see TestTemplate + * @see TestFactory + * @see TestInfo + * @see DisplayName + * @see Tag + * @see BeforeAll + * @see AfterAll + * @see BeforeEach + * @see AfterEach + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +@Testable +public @interface Test { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java new file mode 100644 index 00000000..15eedf56 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @TestClassOrder} is a type-level annotation that is used to configure + * a {@link #value ClassOrderer} for the {@link Nested @Nested} test classes of + * the annotated test class. + * + *

If {@code @TestClassOrder} is not explicitly declared on a test class, + * inherited from a parent class, declared on a test interface implemented by + * a test class, or inherited from an {@linkplain Class#getEnclosingClass() enclosing + * class}, {@code @Nested} test classes will be executed in arbitrary order. + * + *

As an alternative to {@code @TestClassOrder}, a global {@link ClassOrderer} + * can be configured for the entire test suite via the + * {@value ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME} configuration parameter. See + * the User Guide for details. Note, however, that a {@code @TestClassOrder} + * declaration always overrides a global {@code ClassOrderer}. + * + *

Example Usage

+ * + *

The following demonstrates how to guarantee that {@code @Nested} test classes + * are executed in the order specified via the {@link Order @Order} annotation. + * + *

+ * {@literal @}TestClassOrder(ClassOrderer.OrderAnnotation.class)
+ * class OrderedNestedTests {
+ *
+ *     {@literal @}Nested
+ *     {@literal @}Order(1)
+ *     class PrimaryTests {
+ *         // {@literal @}Test methods ...
+ *     }
+ *
+ *     {@literal @}Nested
+ *     {@literal @}Order(2)
+ *     class SecondaryTests {
+ *         // {@literal @}Test methods ...
+ *     }
+ * }
+ * 
+ * + * @since 5.8 + * @see ClassOrderer + * @see TestMethodOrder + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@API(status = EXPERIMENTAL, since = "5.8") +public @interface TestClassOrder { + + /** + * The {@link ClassOrderer} to use. + * + * @see ClassOrderer + * @see ClassOrderer.ClassName + * @see ClassOrderer.DisplayName + * @see ClassOrderer.OrderAnnotation + * @see ClassOrderer.Random + */ + Class value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java new file mode 100644 index 00000000..96b1f524 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.platform.commons.annotation.Testable; + +/** + * {@code @TestFactory} is used to signal that the annotated method is a + * test factory method. + * + *

In contrast to {@link Test @Test} methods, a test factory is not itself + * a test case but rather a factory for test cases. + * + *

{@code @TestFactory} methods must not be {@code private} or {@code static} + * and must return a {@code Stream}, {@code Collection}, {@code Iterable}, + * {@code Iterator}, or array of {@link DynamicNode} instances. Supported + * subclasses of {@code DynamicNode} include {@link DynamicContainer} and + * {@link DynamicTest}. Dynamic tests will be executed lazily, + * enabling dynamic and even non-deterministic generation of test cases. + * + *

Any {@code Stream} returned by a {@code @TestFactory} will be properly + * closed by calling {@code stream.close()}, making it safe to use a resource + * such as {@code Files.lines()} as the initial source of the stream. + * + *

{@code @TestFactory} methods may optionally declare parameters to be + * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver + * ParameterResolvers}. + * + *

Test Execution Order

+ * + *

By default, test methods will be ordered using an algorithm that is + * deterministic but intentionally nonobvious. This ensures that subsequent runs + * of a test suite execute test methods in the same order, thereby allowing for + * repeatable builds. In this context, a test method is any instance + * method that is directly annotated or meta-annotated with {@code @Test}, + * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or + * {@code @TestTemplate}. + * + *

Although true unit tests typically should not rely on the order + * in which they are executed, there are times when it is necessary to enforce + * a specific test method execution order — for example, when writing + * integration tests or functional tests where the sequence of + * the tests is important, especially in conjunction with + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * + *

To control the order in which test methods are executed, annotate your + * test class or test interface with {@link TestMethodOrder @TestMethodOrder} + * and specify the desired {@link MethodOrderer} implementation. + * + * @since 5.0 + * @see Test + * @see DynamicNode + * @see DynamicTest + * @see DynamicContainer + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = MAINTAINED, since = "5.3") +@Testable +public @interface TestFactory { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java new file mode 100644 index 00000000..c7949d1c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; + +/** + * {@code TestInfo} is used to inject information about the current test or + * container into to {@code @Test}, {@code @RepeatedTest}, + * {@code @ParameterizedTest}, {@code @TestFactory}, {@code @BeforeEach}, + * {@code @AfterEach}, {@code @BeforeAll}, and {@code @AfterAll} methods. + * + *

If a method parameter is of type {@link TestInfo}, JUnit will supply + * an instance of {@code TestInfo} corresponding to the current test or + * container as the value for the parameter. + * + * @since 5.0 + * @see Test + * @see RepeatedTest + * @see TestFactory + * @see BeforeEach + * @see AfterEach + * @see BeforeAll + * @see AfterAll + * @see DisplayName + * @see Tag + */ +@API(status = STABLE, since = "5.0") +public interface TestInfo { + + /** + * Get the display name of the current test or container. + * + *

The display name is either a default name or a custom name configured + * via {@link DisplayName @DisplayName}. + * + *

Default Display Names

+ * + *

If the context in which {@code TestInfo} is used is at the container + * level, the default display name is generated based on the name of the + * test class. For top-level and {@link Nested @Nested} test classes, the + * default display name is the {@linkplain Class#getSimpleName simple name} + * of the class. For {@code static} nested test classes, the default display + * name is the default display name for the enclosing class concatenated with + * the {@linkplain Class#getSimpleName simple name} of the {@code static} + * nested class, separated by a dollar sign ({@code $}). For example, the + * default display names for the following test classes are + * {@code TopLevelTests}, {@code NestedTests}, and {@code TopLevelTests$StaticTests}. + * + *

+	 *   class TopLevelTests {
+	 *
+	 *      {@literal @}Nested
+	 *      class NestedTests {}
+	 *
+	 *      static class StaticTests {}
+	 *   }
+	 * 
+ * + *

If the context in which {@code TestInfo} is used is at the test level, + * the default display name is the name of the test method concatenated with + * a comma-separated list of {@linkplain Class#getSimpleName simple names} + * of the parameter types in parentheses. For example, the default display + * name for the following test method is {@code testUser(TestInfo, User)}. + * + *

+	 *   {@literal @}Test
+	 *   void testUser(TestInfo testInfo, {@literal @}Mock User user) {}
+	 * 
+ * + *

Note that display names are typically used for test reporting in IDEs + * and build tools and may contain spaces, special characters, and even emoji. + * + * @return the display name of the test or container; never {@code null} or blank + */ + String getDisplayName(); + + /** + * Get the set of all tags for the current test or container. + * + *

Tags may be declared directly on the test element or inherited + * from an outer context. + */ + Set getTags(); + + /** + * Get the {@link Class} associated with the current test or container, if available. + */ + Optional> getTestClass(); + + /** + * Get the {@link Method} associated with the current test or container, if available. + */ + Optional getTestMethod(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java new file mode 100644 index 00000000..0e6ae375 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @TestInstance} is a type-level annotation that is used to configure + * the {@linkplain Lifecycle lifecycle} of test instances for the annotated + * test class or test interface. + * + *

If {@code @TestInstance} is not explicitly declared on a test class or + * on a test interface implemented by a test class, the lifecycle mode will + * implicitly default to {@link Lifecycle#PER_METHOD PER_METHOD}. Note, however, + * that an explicit lifecycle mode is inherited within a test class + * hierarchy. In addition, the default lifecycle mode may be overridden + * via the {@value Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME} configuration + * parameter which can be supplied via the {@code Launcher} API, build tools + * (e.g., Gradle and Maven), a JVM system property, or the JUnit Platform + * configuration file (i.e., a file named {@code junit-platform.properties} in + * the root of the class path). Consult the User Guide for further information. + * + *

Use Cases

+ *

Setting the test instance lifecycle mode to {@link Lifecycle#PER_CLASS + * PER_CLASS} enables the following features. + *

    + *
  • Shared test instance state between test methods in a given test class + * as well as between non-static {@link BeforeAll @BeforeAll} and + * {@link AfterAll @AfterAll} methods in the test class.
  • + *
  • Declaration of non-static {@code @BeforeAll} and {@code @AfterAll} methods + * in {@link Nested @Nested} test classes. Beginning with Java 16, {@code @BeforeAll} + * and {@code @AfterAll} methods may be declared as {@code static} in + * {@link Nested @Nested} test classes with either lifecycle mode.
  • + *
  • Declaration of {@code @BeforeAll} and {@code @AfterAll} on interface + * {@code default} methods.
  • + *
  • Simplified declaration of non-static {@code @BeforeAll} and {@code @AfterAll} + * methods in test classes implemented with the Kotlin programming language.
  • + *
+ * + *

{@code @TestInstance} may also be used as a meta-annotation in order to + * create a custom composed annotation that inherits the semantics + * of {@code @TestInstance}. + * + * @since 5.0 + * @see Nested @Nested + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@API(status = STABLE, since = "5.0") +public @interface TestInstance { + + /** + * Enumeration of test instance lifecycle modes. + * + * @see #PER_METHOD + * @see #PER_CLASS + */ + enum Lifecycle { + + /** + * When using this mode, a new test instance will be created once per + * test class. + * + * @see #PER_METHOD + */ + PER_CLASS, + + /** + * When using this mode, a new test instance will be created for each + * test method, test factory method, or test template method. + * + *

This mode is analogous to the behavior found in JUnit versions 1 + * through 4. + * + * @see #PER_CLASS + */ + PER_METHOD; + + /** + * Property name used to set the default test instance lifecycle mode: + * {@value} + * + *

Supported Values

+ * + *

Supported values include names of enum constants defined in + * {@link org.junit.jupiter.api.TestInstance.Lifecycle}, ignoring case. + * + *

If not specified, the default is "per_method" which corresponds to + * {@code @TestInstance(Lifecycle.PER_METHOD)}. + * + * @since 5.0 + * @see org.junit.jupiter.api.TestInstance + */ + @API(status = STABLE, since = "5.9") + public static final String DEFAULT_LIFECYCLE_PROPERTY_NAME = "junit.jupiter.testinstance.lifecycle.default"; + + } + + /** + * The test instance lifecycle mode to use. + */ + Lifecycle value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java new file mode 100644 index 00000000..2e6a571e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @TestMethodOrder} is a type-level annotation that is used to configure + * a {@link #value MethodOrderer} for the test methods of the annotated + * test class or test interface. + * + *

In this context, the term "test method" refers to any method annotated with + * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, + * {@code @TestFactory}, or {@code @TestTemplate}. + * + *

If {@code @TestMethodOrder} is not explicitly declared on a test class, + * inherited from a parent class, or declared on a test interface implemented by + * a test class, test methods will be ordered using a default algorithm that is + * deterministic but intentionally nonobvious. + * + *

As an alternative to {@code @TestMethodOrder}, a global {@link MethodOrderer} + * can be configured for the entire test suite via the + * {@value MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME} configuration parameter. See + * the User Guide for details. Note, however, that a {@code @TestClassOrder} + * declaration always overrides a global {@code ClassOrderer}. + * + *

Example Usage

+ * + *

The following demonstrates how to guarantee that test methods are executed + * in the order specified via the {@link Order @Order} annotation. + * + *

+ * {@literal @}TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+ * class OrderedTests {
+ *
+ *     {@literal @}Test
+ *     {@literal @}Order(1)
+ *     void nullValues() {}
+ *
+ *     {@literal @}Test
+ *     {@literal @}Order(2)
+ *     void emptyValues() {}
+ *
+ *     {@literal @}Test
+ *     {@literal @}Order(3)
+ *     void validValues() {}
+ * }
+ * 
+ * + * @since 5.4 + * @see MethodOrderer + * @see TestClassOrder + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@API(status = STABLE, since = "5.7") +public @interface TestMethodOrder { + + /** + * The {@link MethodOrderer} to use. + * + * @see MethodOrderer + * @see MethodOrderer.MethodName + * @see MethodOrderer.DisplayName + * @see MethodOrderer.OrderAnnotation + * @see MethodOrderer.Random + */ + Class value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java new file mode 100644 index 00000000..6b5b349b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collections; +import java.util.Map; + +import org.apiguardian.api.API; + +/** + * Parameters of type {@code TestReporter} can be injected into + * {@link BeforeEach @BeforeEach} and {@link AfterEach @AfterEach} lifecycle + * methods as well as methods annotated with {@link Test @Test}, + * {@link RepeatedTest @RepeatedTest}, + * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}, + * {@link TestFactory @TestFactory}, etc. + * + *

Within such methods the injected {@code TestReporter} can be used to + * publish report entries for the current container or test to the + * reporting infrastructure. + * + * @since 5.0 + * @see #publishEntry(Map) + * @see #publishEntry(String, String) + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface TestReporter { + + /** + * Publish the supplied map of key-value pairs as a report entry. + * + * @param map the key-value pairs to be published; never {@code null}; + * keys and values within entries in the map also must not be + * {@code null} or blank + * @see #publishEntry(String, String) + * @see #publishEntry(String) + */ + void publishEntry(Map map); + + /** + * Publish the supplied key-value pair as a report entry. + * + * @param key the key of the entry to publish; never {@code null} or blank + * @param value the value of the entry to publish; never {@code null} or blank + * @see #publishEntry(Map) + * @see #publishEntry(String) + */ + default void publishEntry(String key, String value) { + this.publishEntry(Collections.singletonMap(key, value)); + } + + /** + * Publish the supplied value as a report entry. + * + *

This method delegates to {@link #publishEntry(String, String)}, + * supplying {@code "value"} as the key and the supplied {@code value} + * argument as the value. + * + * @param value the value to be published; never {@code null} or blank + * @since 5.3 + * @see #publishEntry(Map) + * @see #publishEntry(String, String) + */ + @API(status = STABLE, since = "5.3") + default void publishEntry(String value) { + this.publishEntry("value", value); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java new file mode 100644 index 00000000..24f8208a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.platform.commons.annotation.Testable; + +/** + * {@code @TestTemplate} is used to signal that the annotated method is a + * test template method. + * + *

In contrast to {@link Test @Test} methods, a test template is not itself + * a test case but rather a template for test cases. As such, it is designed to + * be invoked multiple times depending on the number of {@linkplain + * org.junit.jupiter.api.extension.TestTemplateInvocationContext invocation + * contexts} returned by the registered {@linkplain + * org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider + * providers}. Must be used together with at least one provider. Otherwise, + * execution will fail. + * + *

Each invocation of a test template method behaves like the execution of + * a regular {@link Test @Test} method with full support for the same lifecycle + * callbacks and extensions. + * + *

{@code @TestTemplate} methods must not be {@code private} or {@code static} + * and must return {@code void}. + * + *

{@code @TestTemplate} methods may optionally declare parameters to be + * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver + * ParameterResolvers}. + * + *

{@code @TestTemplate} may also be used as a meta-annotation in order to + * create a custom composed annotation that inherits the semantics + * of {@code @TestTemplate}. + * + *

Test Execution Order

+ * + *

By default, test methods will be ordered using an algorithm that is + * deterministic but intentionally nonobvious. This ensures that subsequent runs + * of a test suite execute test methods in the same order, thereby allowing for + * repeatable builds. In this context, a test method is any instance + * method that is directly annotated or meta-annotated with {@code @Test}, + * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or + * {@code @TestTemplate}. + * + *

Although true unit tests typically should not rely on the order + * in which they are executed, there are times when it is necessary to enforce + * a specific test method execution order — for example, when writing + * integration tests or functional tests where the sequence of + * the tests is important, especially in conjunction with + * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * + *

To control the order in which test methods are executed, annotate your + * test class or test interface with {@link TestMethodOrder @TestMethodOrder} + * and specify the desired {@link MethodOrderer} implementation. + * + * @since 5.0 + * @see Test + * @see org.junit.jupiter.api.extension.TestTemplateInvocationContext + * @see org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.0") +@Testable +public @interface TestTemplate { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java new file mode 100644 index 00000000..18f0d34c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java @@ -0,0 +1,388 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +import org.apiguardian.api.API; + +/** + * {@code @Timeout} is used to define a timeout for a method or all testable + * methods within one class and its {@link Nested @Nested} classes. + * + *

This annotation may also be used on lifecycle methods annotated with + * {@link BeforeAll @BeforeAll}, {@link BeforeEach @BeforeEach}, + * {@link AfterEach @AfterEach}, or {@link AfterAll @AfterAll}. + * + *

Applying this annotation to a test class has the same effect as applying + * it to all testable methods, i.e. all methods annotated or meta-annotated with + * {@link Test @Test}, {@link TestFactory @TestFactory}, or + * {@link TestTemplate @TestTemplate}, but not to its lifecycle methods. + * + *

Default Timeouts

+ * + *

If this annotation is not present, no timeout will be used unless a + * default timeout is defined via one of the following configuration parameters: + * + *

+ *
{@value #DEFAULT_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for all testable and lifecycle methods
+ *
{@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for all testable methods
+ *
{@value #DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for {@link Test @Test} methods
+ *
{@value #DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for {@link TestTemplate @TestTemplate} methods
+ *
{@value DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for {@link TestFactory @TestFactory} methods
+ *
{@value DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for all lifecycle methods
+ *
{@value #DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for {@link BeforeAll @BeforeAll} methods
+ *
{@value #DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for {@link BeforeEach @BeforeEach} methods
+ *
{@value #DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for {@link AfterEach @AfterEach} methods
+ *
{@value #DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME}
+ *
Default timeout for {@link AfterAll @AfterAll} methods
+ *
+ * + *

More specific configuration parameters override less specific ones. For + * example, {@value #DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME} + * overrides {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} + * which overrides {@value #DEFAULT_TIMEOUT_PROPERTY_NAME}. + * + *

Supported Values

+ * + *

Values for timeouts must be in the following, case-insensitive format: + * {@code [ns|μs|ms|s|m|h|d]}. The space between the number and the + * unit may be omitted. Specifying no unit is equivalent to using seconds. + * + * + * + * + * + * + * + * + * + * + * + * + *
Timeout configuration via configuration parameter vs. annotation
Value Equivalent annotation
{@code 42} {@code @Timeout(42)}
{@code 42 ns} {@code @Timeout(value = 42, unit = NANOSECONDS)}
{@code 42 μs} {@code @Timeout(value = 42, unit = MICROSECONDS)}
{@code 42 ms} {@code @Timeout(value = 42, unit = MILLISECONDS)}
{@code 42 s} {@code @Timeout(value = 42, unit = SECONDS)}
{@code 42 m} {@code @Timeout(value = 42, unit = MINUTES)}
{@code 42 h} {@code @Timeout(value = 42, unit = HOURS)}
{@code 42 d} {@code @Timeout(value = 42, unit = DAYS)}
+ * + *

Disabling Timeouts

+ * + *

You may use the {@value #TIMEOUT_MODE_PROPERTY_NAME} configuration + * parameter to explicitly enable or disable timeouts. + * + *

Supported values: + *

    + *
  • {@code enabled}: enables timeouts + *
  • {@code disabled}: disables timeouts + *
  • {@code disabled_on_debug}: disables timeouts while debugging + *
+ * + * @since 5.5 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@API(status = STABLE, since = "5.7") +public @interface Timeout { + + /** + * Property name used to set the default timeout for all testable and + * lifecycle methods: {@value}. + * + *

The value of this property will be used unless overridden by a more + * specific property or a {@link Timeout @Timeout} + * annotation present on the method or on an enclosing test class (for + * testable methods). + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.default"; + + /** + * Property name used to set the default timeout for all testable methods: + * {@value}. + * + *

The value of this property will be used unless overridden by a more + * specific property or a {@link Timeout @Timeout} + * annotation present on the testable method or on an enclosing test class. + * + *

This property overrides the {@value #DEFAULT_TIMEOUT_PROPERTY_NAME} + * property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testable.method.default"; + + /** + * Property name used to set the default timeout for all {@link Test @Test} + * methods: {@value}. + * + *

The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the {@link Test @Test} + * method or on an enclosing test class. + * + *

This property overrides the + * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.test.method.default"; + + /** + * Property name used to set the default timeout for all + * {@link TestTemplate @TestTemplate} methods: {@value}. + * + *

The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the + * {@link TestTemplate @TestTemplate} method or on an enclosing test class. + * + *

This property overrides the + * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testtemplate.method.default"; + + /** + * Property name used to set the default timeout for all + * {@link TestFactory @TestFactory} methods: {@value}. + * + *

The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the + * {@link TestFactory @TestFactory} method or on an enclosing test class. + * + *

This property overrides the + * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testfactory.method.default"; + + /** + * Property name used to set the default timeout for all lifecycle methods: + * {@value}. + * + *

The value of this property will be used unless overridden by a more + * specific property or a {@link Timeout @Timeout} annotation present on the + * lifecycle method. + * + *

This property overrides the {@value #DEFAULT_TIMEOUT_PROPERTY_NAME} + * property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.lifecycle.method.default"; + + /** + * Property name used to set the default timeout for all + * {@link BeforeAll @BeforeAll} methods: {@value}. + * + *

The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the + * {@link BeforeAll @BeforeAll} method. + * + *

This property overrides the + * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.beforeall.method.default"; + + /** + * Property name used to set the default timeout for all + * {@link BeforeEach @BeforeEach} methods: {@value}. + * + *

The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the + * {@link BeforeEach @BeforeEach} method. + * + *

This property overrides the + * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.beforeeach.method.default"; + + /** + * Property name used to set the default timeout for all + * {@link AfterEach @AfterEach} methods: {@value}. + * + *

The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the + * {@link AfterEach @AfterEach} method. + * + *

This property overrides the + * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.aftereach.method.default"; + + /** + * Property name used to set the default timeout for all + * {@link AfterAll @AfterAll} methods: {@value}. + * + *

The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the + * {@link AfterAll @AfterAll} method. + * + *

This property overrides the + * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. + * + *

Please refer to the class + * description for the definition of supported values. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.9") + String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.afterall.method.default"; + + /** + * Property used to determine if timeouts are applied to tests: {@value}. + * + *

The value of this property will be used to toggle whether + * {@link Timeout @Timeout} is applied to tests.

+ * + *

Supported timeout mode values:

+ *
    + *
  • {@code enabled}: enables timeouts + *
  • {@code disabled}: disables timeouts + *
  • {@code disabled_on_debug}: disables timeouts while debugging + *
+ * + *

If not specified, the default is {@code enabled}. + * + * @since 5.6 + */ + @API(status = STABLE, since = "5.9") + String TIMEOUT_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.mode"; + + /** + * Property name used to set the default thread mode for all testable and lifecycle + * methods: "junit.jupiter.execution.timeout.thread.mode.default". + * + *

The value of this property will be used unless overridden by a {@link Timeout @Timeout} + * annotation present on the method or on an enclosing test class (for testable methods). + * + *

The supported values are {@code SAME_THREAD} or {@code SEPARATE_THREAD}, if none is provided + * {@code SAME_THREAD} is used as default. + * + * @since 5.9 + */ + @API(status = EXPERIMENTAL, since = "5.9") + String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.thread.mode.default"; + + /** + * The duration of this timeout. + * + * @return timeout duration; must be a positive number + */ + long value(); + + /** + * The time unit of this timeout. + * + * @return time unit + * @see TimeUnit + */ + TimeUnit unit() default TimeUnit.SECONDS; + + /** + * The thread mode of this timeout. + * + * @return thread mode + * @since 5.9 + * @see ThreadMode + */ + @API(status = EXPERIMENTAL, since = "5.9") + ThreadMode threadMode() default ThreadMode.INFERRED; + + /** + * {@code ThreadMode} is use to define whether the test code should be executed in the thread + * of the calling code or in a separated thread. + * + * @since 5.9 + */ + @API(status = EXPERIMENTAL, since = "5.9") + enum ThreadMode { + /** + * The thread mode is determined using the parameter configured in property + * {@value Timeout#DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME}. + */ + INFERRED, + + /** + * The test code is executed in the thread of the calling code. + */ + SAME_THREAD, + + /** + * The test code is executed in a different thread than that of the calling code. Furthermore, + * execution of the test code will be preemptively aborted if the timeout is exceeded. See the + * {@linkplain Assertions Preemptive Timeouts} section of the class-level + * Javadoc for a discussion of possible undesirable side effects. + */ + SEPARATE_THREAD, + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java new file mode 100644 index 00000000..0a82b367 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.annotation.Annotation; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Base class for OS-based {@link ExecutionCondition} implementations. + * + * @since 5.9 + */ +abstract class AbstractOsBasedExecutionCondition implements ExecutionCondition { + + static final String CURRENT_ARCHITECTURE = System.getProperty("os.arch"); + static final String CURRENT_OS = System.getProperty("os.name"); + + private final Class annotationType; + + AbstractOsBasedExecutionCondition(Class annotationType) { + this.annotationType = annotationType; + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return findAnnotation(context.getElement(), this.annotationType) // + .map(this::evaluateExecutionCondition) // + .orElseGet(this::enabledByDefault); + } + + abstract ConditionEvaluationResult evaluateExecutionCondition(A annotation); + + String createReason(boolean enabled, boolean osSpecified, boolean archSpecified) { + StringBuilder reason = new StringBuilder() // + .append(enabled ? "Enabled" : "Disabled") // + .append(osSpecified ? " on operating system: " : " on architecture: "); + + if (osSpecified && archSpecified) { + reason.append(String.format("%s (%s)", CURRENT_OS, CURRENT_ARCHITECTURE)); + } + else if (osSpecified) { + reason.append(CURRENT_OS); + } + else { + reason.append(CURRENT_ARCHITECTURE); + } + + return reason.toString(); + } + + private ConditionEvaluationResult enabledByDefault() { + String reason = String.format("@%s is not present", this.annotationType.getSimpleName()); + return enabled(reason); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java new file mode 100644 index 00000000..5463f23c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static java.lang.String.format; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.AnnotatedElement; +import java.util.Optional; + +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.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * Abstract base class for {@link ExecutionCondition} implementations that support + * {@linkplain Repeatable repeatable} annotations. + * + * @param the type of repeatable annotation supported by this {@code ExecutionCondition} + * @since 5.6 + */ +abstract class AbstractRepeatableAnnotationCondition implements ExecutionCondition { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final Class annotationType; + + AbstractRepeatableAnnotationCondition(Class annotationType) { + this.annotationType = annotationType; + } + + @Override + public final ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + Optional optionalElement = context.getElement(); + if (optionalElement.isPresent()) { + AnnotatedElement annotatedElement = optionalElement.get(); + // @formatter:off + return findRepeatableAnnotations(annotatedElement, this.annotationType).stream() + .map(annotation -> { + ConditionEvaluationResult result = evaluate(annotation); + logResult(annotation, annotatedElement, result); + return result; + }) + .filter(ConditionEvaluationResult::isDisabled) + .findFirst() + .orElse(getNoDisabledConditionsEncounteredResult()); + // @formatter:on + } + return getNoDisabledConditionsEncounteredResult(); + } + + protected abstract ConditionEvaluationResult evaluate(A annotation); + + protected abstract ConditionEvaluationResult getNoDisabledConditionsEncounteredResult(); + + private void logResult(A annotation, AnnotatedElement annotatedElement, ConditionEvaluationResult result) { + logger.trace(() -> format("Evaluation of %s on [%s] resulted in: %s", annotation, annotatedElement, result)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java new file mode 100644 index 00000000..a4f7ac18 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.annotation.Annotation; +import java.util.function.Function; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +abstract class BooleanExecutionCondition implements ExecutionCondition { + + private final Class annotationType; + private final String enabledReason; + private final String disabledReason; + private final Function customDisabledReason; + + BooleanExecutionCondition(Class annotationType, String enabledReason, String disabledReason, + Function customDisabledReason) { + this.annotationType = annotationType; + this.enabledReason = enabledReason; + this.disabledReason = disabledReason; + this.customDisabledReason = customDisabledReason; + } + + abstract boolean isEnabled(A annotation); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return findAnnotation(context.getElement(), annotationType) // + .map(annotation -> isEnabled(annotation) ? enabled(enabledReason) + : disabled(disabledReason, customDisabledReason.apply(annotation))) // + .orElseGet(this::enabledByDefault); + } + + private ConditionEvaluationResult enabledByDefault() { + String reason = String.format("@%s is not present", annotationType.getSimpleName()); + return enabled(reason); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java new file mode 100644 index 00000000..57341bfa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @DisabledForJreRange} is used to signal that the annotated test class or + * test method is only disabled for a specific range of Java Runtime + * Environment (JRE) versions from {@link #min} to {@link #max}. + * + *

When applied at the class level, all test methods within that class will + * be disabled on the same specified JRE versions. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + *

This annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.6 + * @see JRE + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(DisabledForJreRangeCondition.class) +@API(status = STABLE, since = "5.6") +public @interface DisabledForJreRange { + + /** + * Java Runtime Environment version which is used as the lower boundary + * for the version range that determines if the annotated class or method + * should be disabled. + * + *

Defaults to {@link JRE#JAVA_8 JAVA_8}, as this is the lowest + * supported JRE version. + * + * @see JRE + */ + JRE min() default JRE.JAVA_8; + + /** + * Java Runtime Environment version which is used as the upper boundary + * for the version range that determines if the annotated class or method + * should be disabled. + * + *

Defaults to {@link JRE#OTHER OTHER}, as this will always be the highest + * possible version. + * + * @see JRE + */ + JRE max() default JRE.OTHER; + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java new file mode 100644 index 00000000..90e157d1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; +import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; + +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link DisabledForJreRange @DisabledForJreRange}. + * + * @since 5.6 + * @see DisabledForJreRange + */ +class DisabledForJreRangeCondition extends BooleanExecutionCondition { + + DisabledForJreRangeCondition() { + super(DisabledForJreRange.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, + DisabledForJreRange::disabledReason); + } + + @Override + boolean isEnabled(DisabledForJreRange annotation) { + JRE min = annotation.min(); + JRE max = annotation.max(); + + Preconditions.condition((min != JRE.JAVA_8 || max != JRE.OTHER), + "You must declare a non-default value for min or max in @DisabledForJreRange"); + Preconditions.condition(max.compareTo(min) >= 0, + "@DisabledForJreRange.min must be less than or equal to @DisabledForJreRange.max"); + + return !JRE.isCurrentVersionWithinRange(min, max); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java new file mode 100644 index 00000000..8ab454fb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @DisabledIf} is used to signal that the annotated test class or test + * method is disabled only if the provided + * {@linkplain #value() condition} evaluates to {@code true}. + * + *

When applied at the class level, all test methods within that class will + * be disabled on the same condition. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + * This annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.7 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(DisabledIfCondition.class) +@API(status = STABLE, since = "5.7") +public @interface DisabledIf { + + /** + * The name of a method within the test class or in an external class to use + * as a condition for the test's or container's execution. + * + *

Condition methods must be static if located outside the test class or + * if {@code @DisabledIf} is used at the class level. + * + *

A condition method in an external class must be referenced by its + * fully qualified method name — for example, + * {@code com.example.Conditions#isEncryptionSupported}. + */ + String value(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + */ + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java new file mode 100644 index 00000000..1998f884 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import org.junit.jupiter.api.extension.ExecutionCondition; + +/** + * {@link ExecutionCondition} for {@link DisabledIf @DisabledIf}. + * + * @since 5.7 + * @see DisabledIf + */ +class DisabledIfCondition extends MethodBasedCondition { + + DisabledIfCondition() { + super(DisabledIf.class, DisabledIf::value, DisabledIf::disabledReason); + } + + @Override + protected boolean isEnabled(boolean methodResult) { + return !methodResult; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java new file mode 100644 index 00000000..a98c31bb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @DisabledIfEnvironmentVariable} is used to signal that the annotated test + * class or test method is disabled if the value of the specified + * {@linkplain #named environment variable} matches the specified + * {@linkplain #matches regular expression}. + * + *

When declared at the class level, the result will apply to all test methods + * within that class as well. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

If the specified environment variable is undefined, the presence of this + * annotation will have no effect on whether or not the class or method + * is disabled. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable + * repeatable} annotation. Consequently, this annotation may be declared multiple + * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., + * test interface, test class, or test method). Specifically, this annotation will + * be found if it is directly present, indirectly present, or meta-present on a + * given element. + * + * @since 5.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Repeatable(DisabledIfEnvironmentVariables.class) +@ExtendWith(DisabledIfEnvironmentVariableCondition.class) +@API(status = STABLE, since = "5.1") +public @interface DisabledIfEnvironmentVariable { + + /** + * The name of the environment variable to retrieve. + * + * @return the environment variable name; never blank + * @see System#getenv(String) + */ + String named(); + + /** + * A regular expression that will be used to match against the retrieved + * value of the {@link #named} environment variable. + * + * @return the regular expression; never blank + * @see String#matches(String) + * @see java.util.regex.Pattern + */ + String matches(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java new file mode 100644 index 00000000..de9a8515 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static java.lang.String.format; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable}. + * + * @since 5.1 + * @see DisabledIfEnvironmentVariable + */ +class DisabledIfEnvironmentVariableCondition + extends AbstractRepeatableAnnotationCondition { + + private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( + "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + + DisabledIfEnvironmentVariableCondition() { + super(DisabledIfEnvironmentVariable.class); + } + + @Override + protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { + return ENABLED; + } + + @Override + protected ConditionEvaluationResult evaluate(DisabledIfEnvironmentVariable annotation) { + String name = annotation.named().trim(); + String regex = annotation.matches(); + Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); + Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); + String actual = getEnvironmentVariable(name); + + // Nothing to match against? + if (actual == null) { + return enabled(format("Environment variable [%s] does not exist", name)); + } + + if (actual.matches(regex)) { + return disabled(format("Environment variable [%s] with value [%s] matches regular expression [%s]", name, + actual, regex), annotation.disabledReason()); + } + // else + return enabled(format("Environment variable [%s] with value [%s] does not match regular expression [%s]", name, + actual, regex)); + } + + /** + * Get the value of the named environment variable. + * + *

The default implementation delegates to + * {@link System#getenv(String)}. Can be overridden in a subclass for + * testing purposes. + */ + protected String getEnvironmentVariable(String name) { + return System.getenv(name); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java new file mode 100644 index 00000000..deef2a4a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @DisabledIfEnvironmentVariables} is a container for one or more + * {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable} declarations. + * + *

Note, however, that use of the {@code @DisabledIfEnvironmentVariables} container + * is completely optional since {@code @DisabledIfEnvironmentVariable} is a {@linkplain + * java.lang.annotation.Repeatable repeatable} annotation. + * + * @since 5.6 + * @see DisabledIfEnvironmentVariable + * @see java.lang.annotation.Repeatable + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.6") +public @interface DisabledIfEnvironmentVariables { + + /** + * An array of one or more {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable} + * declarations. + */ + DisabledIfEnvironmentVariable[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java new file mode 100644 index 00000000..ff6877bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @DisabledIfSystemProperties} is a container for one or more + * {@link DisabledIfSystemProperty @DisabledIfSystemProperty} declarations. + * + *

Note, however, that use of the {@code @DisabledIfSystemProperties} container + * is completely optional since {@code @DisabledIfSystemProperty} is a {@linkplain + * java.lang.annotation.Repeatable repeatable} annotation. + * + * @since 5.6 + * @see DisabledIfSystemProperty + * @see java.lang.annotation.Repeatable + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.6") +public @interface DisabledIfSystemProperties { + + /** + * An array of one or more {@link DisabledIfSystemProperty @DisabledIfSystemProperty} + * declarations. + */ + DisabledIfSystemProperty[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java new file mode 100644 index 00000000..c8bfe4fc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @DisabledIfSystemProperty} is used to signal that the annotated test + * class or test method is disabled if the value of the specified + * {@linkplain #named system property} matches the specified + * {@linkplain #matches regular expression}. + * + *

When declared at the class level, the result will apply to all test methods + * within that class as well. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

If the specified system property is undefined, the presence of this + * annotation will have no effect on whether or not the class or method + * is disabled. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable + * repeatable} annotation. Consequently, this annotation may be declared multiple + * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., + * test interface, test class, or test method). Specifically, this annotation will + * be found if it is directly present, indirectly present, or meta-present on a + * given element. + * + * @since 5.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Repeatable(DisabledIfSystemProperties.class) +@ExtendWith(DisabledIfSystemPropertyCondition.class) +@API(status = STABLE, since = "5.1") +public @interface DisabledIfSystemProperty { + + /** + * The name of the JVM system property to retrieve. + * + * @return the system property name; never blank + * @see System#getProperty(String) + */ + String named(); + + /** + * A regular expression that will be used to match against the retrieved + * value of the {@link #named} JVM system property. + * + * @return the regular expression; never blank + * @see String#matches(String) + * @see java.util.regex.Pattern + */ + String matches(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java new file mode 100644 index 00000000..c2e17250 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static java.lang.String.format; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link DisabledIfSystemProperty @DisabledIfSystemProperty}. + * + * @since 5.1 + * @see DisabledIfSystemProperty + */ +class DisabledIfSystemPropertyCondition extends AbstractRepeatableAnnotationCondition { + + private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( + "No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + + DisabledIfSystemPropertyCondition() { + super(DisabledIfSystemProperty.class); + } + + @Override + protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { + return ENABLED; + } + + @Override + protected ConditionEvaluationResult evaluate(DisabledIfSystemProperty annotation) { + String name = annotation.named().trim(); + String regex = annotation.matches(); + Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); + Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); + String actual = System.getProperty(name); + + // Nothing to match against? + if (actual == null) { + return enabled(format("System property [%s] does not exist", name)); + } + + if (actual.matches(regex)) { + return disabled( + format("System property [%s] with value [%s] matches regular expression [%s]", name, actual, regex), + annotation.disabledReason()); + } + // else + return enabled( + format("System property [%s] with value [%s] does not match regular expression [%s]", name, actual, regex)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java new file mode 100644 index 00000000..a96399e7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @DisabledInNativeImage} is used to signal that the annotated test class + * or test method is only disabled when executing within a GraalVM native + * image. + * + *

When applied at the class level, all test methods within that class will + * be disabled within a native image. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Technical Details

+ * + *

JUnit detects whether tests are executing within a GraalVM native image by + * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} + * system property (see + * org.graalvm.nativeimage.ImageInfo + * for details). The GraalVM compiler sets the property to {@code buildtime} while + * compiling a native image; the property is set to {@code runtime} while a native + * image is executing; and the Gradle and Maven plug-ins in the GraalVM + * Native Build Tools + * project set the property to {@code agent} while executing tests with the GraalVM + * tracing agent. + * + * @since 5.9.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DisabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // + disabledReason = "Currently executing within a GraalVM native image") +@API(status = STABLE, since = "5.9.1") +public @interface DisabledInNativeImage { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java new file mode 100644 index 00000000..ef1b5d8a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @DisabledOnJre} is used to signal that the annotated test class or + * test method is disabled on one or more specified Java + * Runtime Environment (JRE) {@linkplain #value versions}. + * + *

When applied at the class level, all test methods within that class + * will be disabled on the same specified JRE versions. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.1 + * @see JRE + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(DisabledOnJreCondition.class) +@API(status = STABLE, since = "5.1") +public @interface DisabledOnJre { + + /** + * Java Runtime Environment versions on which the annotated class or + * method should be disabled. + * + * @see JRE + */ + JRE[] value(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java new file mode 100644 index 00000000..a2bacd66 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; +import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link DisabledOnJre @DisabledOnJre}. + * + * @since 5.1 + * @see DisabledOnJre + */ +class DisabledOnJreCondition extends BooleanExecutionCondition { + + DisabledOnJreCondition() { + super(DisabledOnJre.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, DisabledOnJre::disabledReason); + } + + @Override + boolean isEnabled(DisabledOnJre annotation) { + JRE[] versions = annotation.value(); + Preconditions.condition(versions.length > 0, "You must declare at least one JRE in @DisabledOnJre"); + return Arrays.stream(versions).noneMatch(JRE::isCurrentVersion); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java new file mode 100644 index 00000000..5f1e08d7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @DisabledOnOs} is used to signal that the annotated test class or + * test method is disabled on one or more specified + * {@linkplain #value operating systems} or on one or more specified + * {@linkplain #architectures architectures} + * + *

If operating systems and architectures are specified, the annotated + * test class or test method is disabled if both conditions apply. + * + *

When applied at the class level, all test methods within that class + * will be disabled on the same specified operating systems, architectures, or + * the specified combinations of both. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.1 + * @see OS + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(DisabledOnOsCondition.class) +@API(status = STABLE, since = "5.1") +public @interface DisabledOnOs { + + /** + * Operating systems on which the annotated class or method should be + * disabled. + * + * @see OS + */ + OS[] value() default {}; + + /** + * Architectures on which the annotated class or method should be disabled. + * + *

Each architecture will be compared to the value returned from + * {@code System.getProperty("os.arch")}, ignoring case. + * + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + String[] architectures() default {}; + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java new file mode 100644 index 00000000..90ec2089 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link DisabledOnOs @DisabledOnOs}. + * + * @since 5.1 + * @see DisabledOnOs + */ +class DisabledOnOsCondition extends AbstractOsBasedExecutionCondition { + + DisabledOnOsCondition() { + super(DisabledOnOs.class); + } + + @Override + ConditionEvaluationResult evaluateExecutionCondition(DisabledOnOs annotation) { + boolean osSpecified = annotation.value().length > 0; + boolean archSpecified = annotation.architectures().length > 0; + Preconditions.condition(osSpecified || archSpecified, + "You must declare at least one OS or architecture in @DisabledOnOs"); + + boolean enabled = isEnabledBasedOnOs(annotation) || isEnabledBasedOnArchitecture(annotation); + String reason = createReason(enabled, osSpecified, archSpecified); + + return enabled ? ConditionEvaluationResult.enabled(reason) + : ConditionEvaluationResult.disabled(reason, annotation.disabledReason()); + } + + private boolean isEnabledBasedOnOs(DisabledOnOs annotation) { + OS[] operatingSystems = annotation.value(); + if (operatingSystems.length == 0) { + return false; + } + return Arrays.stream(operatingSystems).noneMatch(OS::isCurrentOs); + } + + private boolean isEnabledBasedOnArchitecture(DisabledOnOs annotation) { + String[] architectures = annotation.architectures(); + if (architectures.length == 0) { + return false; + } + return Arrays.stream(architectures).noneMatch(CURRENT_ARCHITECTURE::equalsIgnoreCase); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java new file mode 100644 index 00000000..9e3154d8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledForJreRange} is used to signal that the annotated test class or + * test method is only enabled for a specific range of Java Runtime + * Environment (JRE) versions from {@link #min} to {@link #max}. + * + *

When applied at the class level, all test methods within that class will + * be enabled on the same specified JRE versions. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + *

This annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.6 + * @see JRE + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(EnabledForJreRangeCondition.class) +@API(status = STABLE, since = "5.6") +public @interface EnabledForJreRange { + + /** + * Java Runtime Environment version which should be used as the lower boundary + * for the version range that determines if the annotated class or method + * should be enabled. + * + *

Defaults to {@link JRE#JAVA_8 JAVA_8}, as this is the lowest + * supported JRE version. + * + * @see JRE + */ + JRE min() default JRE.JAVA_8; + + /** + * Java Runtime Environment version which should be used as the upper boundary + * for the version range that determines if the annotated class or method + * should be enabled. + * + *

Defaults to {@link JRE#OTHER OTHER}, as this will always be the highest + * possible version. + * + * @see JRE + */ + JRE max() default JRE.OTHER; + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java new file mode 100644 index 00000000..5a7c9e54 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; +import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; + +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link EnabledForJreRange @EnabledForJreRange}. + * + * @since 5.6 + * @see EnabledForJreRange + */ +class EnabledForJreRangeCondition extends BooleanExecutionCondition { + + EnabledForJreRangeCondition() { + super(EnabledForJreRange.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, + EnabledForJreRange::disabledReason); + } + + @Override + boolean isEnabled(EnabledForJreRange annotation) { + JRE min = annotation.min(); + JRE max = annotation.max(); + + Preconditions.condition((min != JRE.JAVA_8 || max != JRE.OTHER), + "You must declare a non-default value for min or max in @EnabledForJreRange"); + Preconditions.condition(max.compareTo(min) >= 0, + "@EnabledForJreRange.min must be less than or equal to @EnabledForJreRange.max"); + + return JRE.isCurrentVersionWithinRange(min, max); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java new file mode 100644 index 00000000..18e8a839 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledIf} is used to signal that the annotated test class or test + * method is enabled only if the provided + * {@linkplain #value() condition} evaluates to {@code true}. + * + *

When applied at the class level, all test methods within that class will + * be enabled on the same condition. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + * This annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.7 + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(EnabledIfCondition.class) +@API(status = STABLE, since = "5.7") +public @interface EnabledIf { + + /** + * The name of a method within the test class or in an external class to use + * as a condition for the test's or container's execution. + * + *

Condition methods must be static if located outside the test class or + * if {@code @EnabledIf} is used at the class level. + * + *

A condition method in an external class must be referenced by its + * fully qualified method name — for example, + * {@code com.example.Conditions#isEncryptionSupported}. + */ + String value(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + */ + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java new file mode 100644 index 00000000..3ebaa594 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import org.junit.jupiter.api.extension.ExecutionCondition; + +/** + * {@link ExecutionCondition} for {@link EnabledIf @EnabledIf}. + * + * @since 5.7 + * @see EnabledIf + */ +class EnabledIfCondition extends MethodBasedCondition { + + EnabledIfCondition() { + super(EnabledIf.class, EnabledIf::value, EnabledIf::disabledReason); + } + + @Override + protected boolean isEnabled(boolean methodResult) { + return methodResult; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java new file mode 100644 index 00000000..d7cab6c3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledIfEnvironmentVariable} is used to signal that the annotated test + * class or test method is only enabled if the value of the specified + * {@linkplain #named environment variable} matches the specified + * {@linkplain #matches regular expression}. + * + *

When declared at the class level, the result will apply to all test methods + * within that class as well. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

If the specified environment variable is undefined, the annotated class or + * method will be disabled. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable + * repeatable} annotation. Consequently, this annotation may be declared multiple + * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., + * test interface, test class, or test method). Specifically, this annotation will + * be found if it is directly present, indirectly present, or meta-present on a + * given element. + * + * @since 5.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Repeatable(EnabledIfEnvironmentVariables.class) +@ExtendWith(EnabledIfEnvironmentVariableCondition.class) +@API(status = STABLE, since = "5.1") +public @interface EnabledIfEnvironmentVariable { + + /** + * The name of the environment variable to retrieve. + * + * @return the environment variable name; never blank + * @see System#getenv(String) + */ + String named(); + + /** + * A regular expression that will be used to match against the retrieved + * value of the {@link #named} environment variable. + * + * @return the regular expression; never blank + * @see String#matches(String) + * @see java.util.regex.Pattern + */ + String matches(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java new file mode 100644 index 00000000..13848432 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static java.lang.String.format; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable}. + * + * @since 5.1 + * @see EnabledIfEnvironmentVariable + */ +class EnabledIfEnvironmentVariableCondition + extends AbstractRepeatableAnnotationCondition { + + private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( + "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + + EnabledIfEnvironmentVariableCondition() { + super(EnabledIfEnvironmentVariable.class); + } + + @Override + protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { + return ENABLED; + } + + @Override + protected ConditionEvaluationResult evaluate(EnabledIfEnvironmentVariable annotation) { + + String name = annotation.named().trim(); + String regex = annotation.matches(); + Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); + Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); + String actual = getEnvironmentVariable(name); + + // Nothing to match against? + if (actual == null) { + return disabled(format("Environment variable [%s] does not exist", name), annotation.disabledReason()); + } + if (actual.matches(regex)) { + return enabled(format("Environment variable [%s] with value [%s] matches regular expression [%s]", name, + actual, regex)); + } + return disabled(format("Environment variable [%s] with value [%s] does not match regular expression [%s]", name, + actual, regex), annotation.disabledReason()); + } + + /** + * Get the value of the named environment variable. + * + *

The default implementation delegates to + * {@link System#getenv(String)}. Can be overridden in a subclass for + * testing purposes. + */ + protected String getEnvironmentVariable(String name) { + return System.getenv(name); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java new file mode 100644 index 00000000..3589ed58 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @EnabledIfEnvironmentVariables} is a container for one or more + * {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable} declarations. + * + *

Note, however, that use of the {@code @EnabledIfEnvironmentVariables} container + * is completely optional since {@code @EnabledIfEnvironmentVariable} is a {@linkplain + * java.lang.annotation.Repeatable repeatable} annotation. + * + * @since 5.6 + * @see EnabledIfEnvironmentVariable + * @see java.lang.annotation.Repeatable + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.6") +public @interface EnabledIfEnvironmentVariables { + + /** + * An array of one or more {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable} + * declarations. + */ + EnabledIfEnvironmentVariable[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java new file mode 100644 index 00000000..f33bdfae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @EnabledIfSystemProperties} is a container for one or more + * {@link EnabledIfSystemProperty @EnabledIfSystemProperty} declarations. + * + *

Note, however, that use of the {@code @EnabledIfSystemProperties} container + * is completely optional since {@code @EnabledIfSystemProperty} is a {@linkplain + * java.lang.annotation.Repeatable repeatable} annotation. + * + * @since 5.6 + * @see EnabledIfSystemProperty + * @see java.lang.annotation.Repeatable + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.6") +public @interface EnabledIfSystemProperties { + + /** + * An array of one or more {@link EnabledIfSystemProperty @EnabledIfSystemProperty} + * declarations. + */ + EnabledIfSystemProperty[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java new file mode 100644 index 00000000..cd658e4e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledIfSystemProperty} is used to signal that the annotated test + * class or test method is only enabled if the value of the specified + * {@linkplain #named system property} matches the specified + * {@linkplain #matches regular expression}. + * + *

When declared at the class level, the result will apply to all test methods + * within that class as well. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

If the specified system property is undefined, the annotated class or + * method will be disabled. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable + * repeatable} annotation. Consequently, this annotation may be declared multiple + * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., + * test interface, test class, or test method). Specifically, this annotation will + * be found if it is directly present, indirectly present, or meta-present on a + * given element. + * + * @since 5.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Repeatable(EnabledIfSystemProperties.class) +@ExtendWith(EnabledIfSystemPropertyCondition.class) +@API(status = STABLE, since = "5.1") +public @interface EnabledIfSystemProperty { + + /** + * The name of the JVM system property to retrieve. + * + * @return the system property name; never blank + * @see System#getProperty(String) + */ + String named(); + + /** + * A regular expression that will be used to match against the retrieved + * value of the {@link #named} JVM system property. + * + * @return the regular expression; never blank + * @see String#matches(String) + * @see java.util.regex.Pattern + */ + String matches(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java new file mode 100644 index 00000000..af8c7cb9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static java.lang.String.format; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link EnabledIfSystemProperty @EnabledIfSystemProperty}. + * + * @since 5.1 + * @see EnabledIfSystemProperty + */ +class EnabledIfSystemPropertyCondition extends AbstractRepeatableAnnotationCondition { + + private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( + "No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + + EnabledIfSystemPropertyCondition() { + super(EnabledIfSystemProperty.class); + } + + @Override + protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { + return ENABLED; + } + + @Override + protected ConditionEvaluationResult evaluate(EnabledIfSystemProperty annotation) { + + String name = annotation.named().trim(); + String regex = annotation.matches(); + Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); + Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); + String actual = System.getProperty(name); + + // Nothing to match against? + if (actual == null) { + return disabled(format("System property [%s] does not exist", name), annotation.disabledReason()); + } + if (actual.matches(regex)) { + return enabled( + format("System property [%s] with value [%s] matches regular expression [%s]", name, actual, regex)); + } + return disabled( + format("System property [%s] with value [%s] does not match regular expression [%s]", name, actual, regex), + annotation.disabledReason()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java new file mode 100644 index 00000000..33641509 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @EnabledInNativeImage} is used to signal that the annotated test class + * or test method is only enabled when executing within a GraalVM native + * image. + * + *

When applied at the class level, all test methods within that class will + * be enabled within a native image. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Technical Details

+ * + *

JUnit detects whether tests are executing within a GraalVM native image by + * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} + * system property (see + * org.graalvm.nativeimage.ImageInfo + * for details). The GraalVM compiler sets the property to {@code buildtime} while + * compiling a native image; the property is set to {@code runtime} while a native + * image is executing; and the Gradle and Maven plug-ins in the GraalVM + * Native Build Tools + * project set the property to {@code agent} while executing tests with the GraalVM + * tracing agent. + * + * @since 5.9.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@EnabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // + disabledReason = "Not currently executing within a GraalVM native image") +@API(status = STABLE, since = "5.9.1") +public @interface EnabledInNativeImage { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java new file mode 100644 index 00000000..c5ca85ea --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledOnJre} is used to signal that the annotated test class or + * test method is only enabled on one or more specified Java + * Runtime Environment (JRE) {@linkplain #value versions}. + * + *

When applied at the class level, all test methods within that class + * will be enabled on the same specified JRE versions. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.1 + * @see JRE + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(EnabledOnJreCondition.class) +@API(status = STABLE, since = "5.1") +public @interface EnabledOnJre { + + /** + * Java Runtime Environment versions on which the annotated class or + * method should be enabled. + * + * @see JRE + */ + JRE[] value(); + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java new file mode 100644 index 00000000..2bd86535 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link EnabledOnJre @EnabledOnJre}. + * + * @since 5.1 + * @see EnabledOnJre + */ +class EnabledOnJreCondition extends BooleanExecutionCondition { + + static final String ENABLED_ON_CURRENT_JRE = // + "Enabled on JRE version: " + System.getProperty("java.version"); + + static final String DISABLED_ON_CURRENT_JRE = // + "Disabled on JRE version: " + System.getProperty("java.version"); + + EnabledOnJreCondition() { + super(EnabledOnJre.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, EnabledOnJre::disabledReason); + } + + @Override + boolean isEnabled(EnabledOnJre annotation) { + JRE[] versions = annotation.value(); + Preconditions.condition(versions.length > 0, "You must declare at least one JRE in @EnabledOnJre"); + return Arrays.stream(versions).anyMatch(JRE::isCurrentVersion); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java new file mode 100644 index 00000000..2805fc40 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledOnOs} is used to signal that the annotated test class or + * test method is only enabled on one or more specified + * {@linkplain #value operating systems} or one or more specified + * {@linkplain #architectures architectures}. + * + *

If operating systems and architectures are specified, the annotated + * test class or test method is enabled if both conditions apply. + * + *

When applied at the class level, all test methods within that class + * will be enabled on the same specified operating systems, architectures, or + * the specified combinations of both. + * + *

If a test method is disabled via this annotation, that does not prevent + * the test class from being instantiated. Rather, it prevents the execution of + * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Warning

+ * + *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an + * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test + * interface, test class, or test method). If this annotation is directly + * present, indirectly present, or meta-present multiple times on a given + * element, only the first such annotation discovered by JUnit will be used; + * any additional declarations will be silently ignored. Note, however, that + * this annotation may be used in conjunction with other {@code @Enabled*} or + * {@code @Disabled*} annotations in this package. + * + * @since 5.1 + * @see OS + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(EnabledOnOsCondition.class) +@API(status = STABLE, since = "5.1") +public @interface EnabledOnOs { + + /** + * Operating systems on which the annotated class or method should be + * enabled. + * + * @see OS + */ + OS[] value() default {}; + + /** + * Architectures on which the annotated class or method should be enabled. + * + *

Each architecture will be compared to the value returned from + * {@code System.getProperty("os.arch")}, ignoring case. + * + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + String[] architectures() default {}; + + /** + * Custom reason to provide if the test or container is disabled. + * + *

If a custom reason is supplied, it will be combined with the default + * reason for this annotation. If a custom reason is not supplied, the default + * reason will be used. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + String disabledReason() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java new file mode 100644 index 00000000..092342f7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ExecutionCondition} for {@link EnabledOnOs @EnabledOnOs}. + * + * @since 5.1 + * @see EnabledOnOs + */ +class EnabledOnOsCondition extends AbstractOsBasedExecutionCondition { + + EnabledOnOsCondition() { + super(EnabledOnOs.class); + } + + @Override + ConditionEvaluationResult evaluateExecutionCondition(EnabledOnOs annotation) { + boolean osSpecified = annotation.value().length > 0; + boolean archSpecified = annotation.architectures().length > 0; + Preconditions.condition(osSpecified || archSpecified, + "You must declare at least one OS or architecture in @EnabledOnOs"); + + boolean enabled = isEnabledBasedOnOs(annotation) && isEnabledBasedOnArchitecture(annotation); + String reason = createReason(enabled, osSpecified, archSpecified); + + return enabled ? ConditionEvaluationResult.enabled(reason) + : ConditionEvaluationResult.disabled(reason, annotation.disabledReason()); + } + + private boolean isEnabledBasedOnOs(EnabledOnOs annotation) { + OS[] operatingSystems = annotation.value(); + if (operatingSystems.length == 0) { + return true; + } + return Arrays.stream(operatingSystems).anyMatch(OS::isCurrentOs); + } + + private boolean isEnabledBasedOnArchitecture(EnabledOnOs annotation) { + String[] architectures = annotation.architectures(); + if (architectures.length == 0) { + return true; + } + return Arrays.stream(architectures).anyMatch(CURRENT_ARCHITECTURE::equalsIgnoreCase); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java new file mode 100644 index 00000000..b42bc90e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java @@ -0,0 +1,244 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.reflect.Method; +import java.util.EnumSet; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; + +/** + * Enumeration of Java Runtime Environment (JRE) versions. + * + *

If the current JRE version cannot be detected — for example, if the + * {@code java.version} JVM system property is undefined — then none of + * the constants defined in this enum will be considered to be the + * {@linkplain #isCurrentVersion current JRE version}. + * + * @since 5.1 + * @see #JAVA_8 + * @see #JAVA_9 + * @see #JAVA_10 + * @see #JAVA_11 + * @see #JAVA_12 + * @see #JAVA_13 + * @see #JAVA_14 + * @see #OTHER + * @see EnabledOnJre + * @see DisabledOnJre + * @see EnabledForJreRange + * @see DisabledForJreRange + */ +@API(status = STABLE, since = "5.1") +public enum JRE { + + /** + * Java 8. + */ + JAVA_8, + + /** + * Java 9. + */ + JAVA_9, + + /** + * Java 10. + */ + JAVA_10, + + /** + * Java 11. + */ + JAVA_11, + + /** + * Java 12. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + JAVA_12, + + /** + * Java 13. + * + * @since 5.4 + */ + @API(status = STABLE, since = "5.4") + JAVA_13, + + /** + * Java 14. + * + * @since 5.5 + */ + @API(status = STABLE, since = "5.5") + JAVA_14, + + /** + * Java 15. + * + * @since 5.6 + */ + @API(status = STABLE, since = "5.6") + JAVA_15, + + /** + * Java 16. + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + JAVA_16, + + /** + * Java 17. + * + * @since 5.7.1 + */ + @API(status = STABLE, since = "5.7.1") + JAVA_17, + + /** + * Java 18. + * + * @since 5.8.1 + */ + @API(status = STABLE, since = "5.8.1") + JAVA_18, + + /** + * Java 19. + * + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + JAVA_19, + + /** + * Java 20. + * + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + JAVA_20, + + /** + * Java 20. + * + * @since 5.9.2 + */ + @API(status = STABLE, since = "5.9.2") + JAVA_21, + + /** + * A JRE version other than {@link #JAVA_8}, {@link #JAVA_9}, + * {@link #JAVA_10}, {@link #JAVA_11}, {@link #JAVA_12}, + * {@link #JAVA_13}, {@link #JAVA_14}, {@link #JAVA_15}, + * {@link #JAVA_16}, {@link #JAVA_17}, {@link #JAVA_18}, + * {@link #JAVA_19}, {@link #JAVA_20}, or {@link #JAVA_21}. + */ + OTHER; + + private static final Logger logger = LoggerFactory.getLogger(JRE.class); + + private static final JRE CURRENT_VERSION = determineCurrentVersion(); + + private static JRE determineCurrentVersion() { + String javaVersion = System.getProperty("java.version"); + boolean javaVersionIsBlank = StringUtils.isBlank(javaVersion); + + if (javaVersionIsBlank) { + logger.debug( + () -> "JVM system property 'java.version' is undefined. It is therefore not possible to detect Java 8."); + } + + if (!javaVersionIsBlank && javaVersion.startsWith("1.8")) { + return JAVA_8; + } + + try { + // java.lang.Runtime.version() is a static method available on Java 9+ + // that returns an instance of java.lang.Runtime.Version which has the + // following method: public int major() + Method versionMethod = Runtime.class.getMethod("version"); + Object version = ReflectionUtils.invokeMethod(versionMethod, null); + Method majorMethod = version.getClass().getMethod("major"); + int major = (int) ReflectionUtils.invokeMethod(majorMethod, version); + switch (major) { + case 9: + return JAVA_9; + case 10: + return JAVA_10; + case 11: + return JAVA_11; + case 12: + return JAVA_12; + case 13: + return JAVA_13; + case 14: + return JAVA_14; + case 15: + return JAVA_15; + case 16: + return JAVA_16; + case 17: + return JAVA_17; + case 18: + return JAVA_18; + case 19: + return JAVA_19; + case 20: + return JAVA_20; + case 21: + return JAVA_21; + default: + return OTHER; + } + } + catch (Exception ex) { + logger.debug(ex, () -> "Failed to determine the current JRE version via java.lang.Runtime.Version."); + } + + // null signals that the current JRE version is "unknown" + return null; + } + + /** + * @return {@code true} if this {@code JRE} is known to be the + * Java Runtime Environment version for the currently executing JVM + */ + public boolean isCurrentVersion() { + return this == CURRENT_VERSION; + } + + /** + * @return the {@link JRE} for the currently executing JVM + * + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + public static JRE currentVersion() { + return CURRENT_VERSION; + } + + static boolean isCurrentVersionWithinRange(JRE min, JRE max) { + return EnumSet.range(min, max).contains(CURRENT_VERSION); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java new file mode 100644 index 00000000..40330479 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static java.lang.String.format; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; + +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.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; + +/** + * @since 5.7 + */ +abstract class MethodBasedCondition implements ExecutionCondition { + + private final Class annotationType; + private final Function methodName; + private final Function customDisabledReason; + + MethodBasedCondition(Class annotationType, Function methodName, + Function customDisabledReason) { + this.annotationType = annotationType; + this.methodName = methodName; + this.customDisabledReason = customDisabledReason; + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + Optional annotation = findAnnotation(context.getElement(), this.annotationType); + return annotation // + .map(this.methodName) // + .map(methodName -> getConditionMethod(methodName, context)) // + .map(method -> invokeConditionMethod(method, context)) // + .map(methodResult -> buildConditionEvaluationResult(methodResult, annotation.get())) // + .orElseGet(this::enabledByDefault); + } + + private Method getConditionMethod(String fullyQualifiedMethodName, ExtensionContext context) { + if (!fullyQualifiedMethodName.contains("#")) { + return findMethod(context.getRequiredTestClass(), fullyQualifiedMethodName); + } + String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); + String className = methodParts[0]; + String methodName = methodParts[1]; + Class clazz = ReflectionUtils.tryToLoadClass(className).getOrThrow( + cause -> new JUnitException(format("Could not load class [%s]", className), cause)); + return findMethod(clazz, methodName); + } + + private Method findMethod(Class clazz, String methodName) { + return ReflectionUtils.findMethod(clazz, methodName) // + .orElseGet(() -> ReflectionUtils.getRequiredMethod(clazz, methodName, ExtensionContext.class)); + } + + private boolean invokeConditionMethod(Method method, ExtensionContext context) { + Preconditions.condition(method.getReturnType() == boolean.class, + () -> format("Method [%s] must return a boolean", method)); + Preconditions.condition(acceptsExtensionContextOrNoArguments(method), + () -> format("Method [%s] must accept either an ExtensionContext or no arguments", method)); + + Object testInstance = context.getTestInstance().orElse(null); + if (method.getParameterCount() == 0) { + return (boolean) ReflectionUtils.invokeMethod(method, testInstance); + } + return (boolean) ReflectionUtils.invokeMethod(method, testInstance, context); + } + + private boolean acceptsExtensionContextOrNoArguments(Method method) { + int parameterCount = method.getParameterCount(); + return parameterCount == 0 || (parameterCount == 1 && method.getParameterTypes()[0] == ExtensionContext.class); + } + + private ConditionEvaluationResult buildConditionEvaluationResult(boolean methodResult, A annotation) { + Supplier defaultReason = () -> format("@%s(\"%s\") evaluated to %s", + this.annotationType.getSimpleName(), this.methodName.apply(annotation), methodResult); + if (isEnabled(methodResult)) { + return enabled(defaultReason.get()); + } + String customReason = this.customDisabledReason.apply(annotation); + return StringUtils.isNotBlank(customReason) ? disabled(customReason) : disabled(defaultReason.get()); + } + + protected abstract boolean isEnabled(boolean methodResult); + + private ConditionEvaluationResult enabledByDefault() { + return enabled(format("@%s is not present", this.annotationType.getSimpleName())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java new file mode 100644 index 00000000..5acb383e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java @@ -0,0 +1,157 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Locale; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.StringUtils; + +/** + * Enumeration of common operating systems used for testing Java applications. + * + *

If the current operating system cannot be detected — for example, + * if the {@code os.name} JVM system property is undefined — then none + * of the constants defined in this enum will be considered to be the + * {@linkplain #isCurrentOs current operating system}. + * + * @since 5.1 + * @see #AIX + * @see #FREEBSD + * @see #LINUX + * @see #MAC + * @see #OPENBSD + * @see #SOLARIS + * @see #WINDOWS + * @see #OTHER + * @see EnabledOnOs + * @see DisabledOnOs + */ +@API(status = STABLE, since = "5.1") +public enum OS { + + /** + * IBM AIX operating system. + * + * @since 5.3 + */ + @API(status = STABLE, since = "5.3") + AIX, + + /** + * FreeBSD operating system. + * + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + FREEBSD, + + /** + * Linux-based operating system. + */ + LINUX, + + /** + * Apple Macintosh operating system (e.g., macOS). + */ + MAC, + + /** + * OpenBSD operating system. + * + * @since 5.9 + */ + @API(status = STABLE, since = "5.9") + OPENBSD, + + /** + * Oracle Solaris operating system. + */ + SOLARIS, + + /** + * Microsoft Windows operating system. + */ + WINDOWS, + + /** + * An operating system other than {@link #AIX}, {@link #FREEBSD}, {@link #LINUX}, + * {@link #MAC}, {@link #OPENBSD}, {@link #SOLARIS}, or {@link #WINDOWS}. + */ + OTHER; + + private static final Logger logger = LoggerFactory.getLogger(OS.class); + + private static final OS CURRENT_OS = determineCurrentOs(); + + /** + * Get the current operating system. + * + * @since 5.9 + */ + @API(status = EXPERIMENTAL, since = "5.9") + public static OS current() { + return CURRENT_OS; + } + + private static OS determineCurrentOs() { + return parse(System.getProperty("os.name")); + } + + static OS parse(String osName) { + if (StringUtils.isBlank(osName)) { + logger.debug( + () -> "JVM system property 'os.name' is undefined. It is therefore not possible to detect the current OS."); + + // null signals that the current OS is "unknown" + return null; + } + + osName = osName.toLowerCase(Locale.ENGLISH); + + if (osName.contains("aix")) { + return AIX; + } + if (osName.contains("freebsd")) { + return FREEBSD; + } + if (osName.contains("linux")) { + return LINUX; + } + if (osName.contains("mac")) { + return MAC; + } + if (osName.contains("openbsd")) { + return OPENBSD; + } + if (osName.contains("sunos") || osName.contains("solaris")) { + return SOLARIS; + } + if (osName.contains("win")) { + return WINDOWS; + } + return OTHER; + } + + /** + * @return {@code true} if this {@code OS} is known to be the + * operating system on which the current JVM is executing + */ + public boolean isCurrentOs() { + return this == CURRENT_OS; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java new file mode 100644 index 00000000..14d1f7fb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java @@ -0,0 +1,5 @@ +/** + * Annotation-based conditions for enabling or disabling tests in JUnit Jupiter. + */ + +package org.junit.jupiter.api.condition; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java new file mode 100644 index 00000000..cdf4c267 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code AfterAllCallback} defines the API for {@link Extension Extensions} + * that wish to provide additional behavior to test containers once + * after all tests in the container have been executed. + * + *

Concrete implementations often implement {@link BeforeAllCallback} as well. + * + *

Extensions that implement {@code AfterAllCallback} must be registered at + * the class level. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + *

Wrapping Behavior

+ * + *

JUnit Jupiter guarantees wrapping behavior for multiple + * registered extensions that implement lifecycle callbacks such as + * {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link BeforeEachCallback}, {@link AfterEachCallback}, + * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. + * + *

That means that, given two extensions {@code Extension1} and + * {@code Extension2} with {@code Extension1} registered before + * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} + * are guaranteed to execute before any "before" callbacks implemented by + * {@code Extension2}. Similarly, given the two same two extensions registered + * in the same order, any "after" callbacks implemented by {@code Extension1} + * are guaranteed to execute after any "after" callbacks implemented by + * {@code Extension2}. {@code Extension1} is therefore said to wrap + * {@code Extension2}. + * + * @since 5.0 + * @see org.junit.jupiter.api.AfterAll + * @see BeforeAllCallback + * @see BeforeEachCallback + * @see AfterEachCallback + * @see BeforeTestExecutionCallback + * @see AfterTestExecutionCallback + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface AfterAllCallback extends Extension { + + /** + * Callback that is invoked once after all tests in the current + * container. + * + * @param context the current extension context; never {@code null} + */ + void afterAll(ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java new file mode 100644 index 00000000..6c26cc11 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code AfterEachCallback} defines the API for {@link Extension Extensions} + * that wish to provide additional behavior to tests after an individual test + * and any user-defined teardown methods (e.g., + * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods) for that test + * have been executed. + * + *

Concrete implementations often implement {@link BeforeEachCallback} as well. + * If you do not wish to have your callbacks wrapped around user-defined + * setup and teardown methods, implement {@link BeforeTestExecutionCallback} and + * {@link AfterTestExecutionCallback} instead of {@link BeforeEachCallback} and + * {@link AfterEachCallback}. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + *

Wrapping Behavior

+ * + *

JUnit Jupiter guarantees wrapping behavior for multiple + * registered extensions that implement lifecycle callbacks such as + * {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link BeforeEachCallback}, {@link AfterEachCallback}, + * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. + * + *

That means that, given two extensions {@code Extension1} and + * {@code Extension2} with {@code Extension1} registered before + * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} + * are guaranteed to execute before any "before" callbacks implemented by + * {@code Extension2}. Similarly, given the two same two extensions registered + * in the same order, any "after" callbacks implemented by {@code Extension1} + * are guaranteed to execute after any "after" callbacks implemented by + * {@code Extension2}. {@code Extension1} is therefore said to wrap + * {@code Extension2}. + * + * @since 5.0 + * @see org.junit.jupiter.api.AfterEach + * @see BeforeEachCallback + * @see BeforeTestExecutionCallback + * @see AfterTestExecutionCallback + * @see BeforeAllCallback + * @see AfterAllCallback + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface AfterEachCallback extends Extension { + + /** + * Callback that is invoked after an individual test and any + * user-defined teardown methods for that test have been executed. + * + * @param context the current extension context; never {@code null} + */ + void afterEach(ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java new file mode 100644 index 00000000..817744a1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code AfterTestExecutionCallback} defines the API for {@link Extension + * Extensions} that wish to provide additional behavior to tests + * immediately after an individual test has been executed but + * before any user-defined teardown methods (e.g., + * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods) have been executed + * for that test. + * + *

Concrete implementations often implement {@link BeforeTestExecutionCallback} + * as well. If you wish to have your callbacks wrapped around user-defined + * setup and teardown methods, implement {@link BeforeEachCallback} and + * {@link AfterEachCallback} instead of {@link BeforeTestExecutionCallback} and + * {@link AfterTestExecutionCallback}. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + *

Wrapping Behavior

+ * + *

JUnit Jupiter guarantees wrapping behavior for multiple + * registered extensions that implement lifecycle callbacks such as + * {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link BeforeEachCallback}, {@link AfterEachCallback}, + * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. + * + *

That means that, given two extensions {@code Extension1} and + * {@code Extension2} with {@code Extension1} registered before + * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} + * are guaranteed to execute before any "before" callbacks implemented by + * {@code Extension2}. Similarly, given the two same two extensions registered + * in the same order, any "after" callbacks implemented by {@code Extension1} + * are guaranteed to execute after any "after" callbacks implemented by + * {@code Extension2}. {@code Extension1} is therefore said to wrap + * {@code Extension2}. + * + * @since 5.0 + * @see org.junit.jupiter.api.Test + * @see BeforeTestExecutionCallback + * @see BeforeEachCallback + * @see AfterEachCallback + * @see BeforeAllCallback + * @see AfterAllCallback + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface AfterTestExecutionCallback extends Extension { + + /** + * Callback that is invoked immediately after an individual test has + * been executed but before any user-defined teardown methods have been + * executed for that test. + * + * @param context the current extension context; never {@code null} + */ + void afterTestExecution(ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java new file mode 100644 index 00000000..ae78aa2d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code BeforeAllCallback} defines the API for {@link Extension Extensions} + * that wish to provide additional behavior to test containers once + * before all tests in the container have been executed. + * + *

Concrete implementations often implement {@link AfterAllCallback} as well. + * + *

Extensions that implement {@code BeforeAllCallback} must be registered at + * the class level. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + *

Wrapping Behavior

+ * + *

JUnit Jupiter guarantees wrapping behavior for multiple + * registered extensions that implement lifecycle callbacks such as + * {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link BeforeEachCallback}, {@link AfterEachCallback}, + * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. + * + *

That means that, given two extensions {@code Extension1} and + * {@code Extension2} with {@code Extension1} registered before + * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} + * are guaranteed to execute before any "before" callbacks implemented by + * {@code Extension2}. Similarly, given the two same two extensions registered + * in the same order, any "after" callbacks implemented by {@code Extension1} + * are guaranteed to execute after any "after" callbacks implemented by + * {@code Extension2}. {@code Extension1} is therefore said to wrap + * {@code Extension2}. + * + * @since 5.0 + * @see org.junit.jupiter.api.BeforeAll + * @see AfterAllCallback + * @see BeforeEachCallback + * @see AfterEachCallback + * @see BeforeTestExecutionCallback + * @see AfterTestExecutionCallback + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface BeforeAllCallback extends Extension { + + /** + * Callback that is invoked once before all tests in the current + * container. + * + * @param context the current extension context; never {@code null} + */ + void beforeAll(ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java new file mode 100644 index 00000000..2ba5b378 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code BeforeEachCallback} defines the API for {@link Extension Extensions} + * that wish to provide additional behavior to tests before an individual test + * and any user-defined setup methods (e.g., + * {@link org.junit.jupiter.api.BeforeEach @BeforeEach} methods) for that test + * have been executed. + * + *

Concrete implementations often implement {@link AfterEachCallback} as well. + * If you do not wish to have your callbacks wrapped around user-defined + * setup and teardown methods, implement {@link BeforeTestExecutionCallback} and + * {@link AfterTestExecutionCallback} instead of {@link BeforeEachCallback} and + * {@link AfterEachCallback}. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + *

Wrapping Behavior

+ * + *

JUnit Jupiter guarantees wrapping behavior for multiple + * registered extensions that implement lifecycle callbacks such as + * {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link BeforeEachCallback}, {@link AfterEachCallback}, + * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. + * + *

That means that, given two extensions {@code Extension1} and + * {@code Extension2} with {@code Extension1} registered before + * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} + * are guaranteed to execute before any "before" callbacks implemented by + * {@code Extension2}. Similarly, given the two same two extensions registered + * in the same order, any "after" callbacks implemented by {@code Extension1} + * are guaranteed to execute after any "after" callbacks implemented by + * {@code Extension2}. {@code Extension1} is therefore said to wrap + * {@code Extension2}. + * + * @since 5.0 + * @see org.junit.jupiter.api.BeforeEach + * @see AfterEachCallback + * @see BeforeTestExecutionCallback + * @see AfterTestExecutionCallback + * @see BeforeAllCallback + * @see AfterAllCallback + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface BeforeEachCallback extends Extension { + + /** + * Callback that is invoked before an individual test and any + * user-defined setup methods for that test have been executed. + * + * @param context the current extension context; never {@code null} + */ + void beforeEach(ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java new file mode 100644 index 00000000..9a250881 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code BeforeTestExecutionCallback} defines the API for {@link Extension + * Extensions} that wish to provide additional behavior to tests + * immediately before an individual test is executed but after + * any user-defined setup methods (e.g., + * {@link org.junit.jupiter.api.BeforeEach @BeforeEach} methods) have been + * executed for that test. + * + *

Concrete implementations often implement {@link AfterTestExecutionCallback} + * as well. If you wish to have your callbacks wrapped around user-defined + * setup and teardown methods, implement {@link BeforeEachCallback} and + * {@link AfterEachCallback} instead of {@link BeforeTestExecutionCallback} and + * {@link AfterTestExecutionCallback}. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + *

Wrapping Behavior

+ * + *

JUnit Jupiter guarantees wrapping behavior for multiple + * registered extensions that implement lifecycle callbacks such as + * {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link BeforeEachCallback}, {@link AfterEachCallback}, + * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. + * + *

That means that, given two extensions {@code Extension1} and + * {@code Extension2} with {@code Extension1} registered before + * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} + * are guaranteed to execute before any "before" callbacks implemented by + * {@code Extension2}. Similarly, given the two same two extensions registered + * in the same order, any "after" callbacks implemented by {@code Extension1} + * are guaranteed to execute after any "after" callbacks implemented by + * {@code Extension2}. {@code Extension1} is therefore said to wrap + * {@code Extension2}. + * + * @since 5.0 + * @see org.junit.jupiter.api.Test + * @see AfterTestExecutionCallback + * @see BeforeEachCallback + * @see AfterEachCallback + * @see BeforeAllCallback + * @see AfterAllCallback + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface BeforeTestExecutionCallback extends Extension { + + /** + * Callback that is invoked immediately before an individual test is + * executed but after any user-defined setup methods have been executed + * for that test. + * + * @param context the current extension context; never {@code null} + */ + void beforeTestExecution(ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java new file mode 100644 index 00000000..e01c5ac6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -0,0 +1,102 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * The result of evaluating an {@link ExecutionCondition}. + * + * @since 5.0 + */ +@API(status = STABLE, since = "5.0") +public class ConditionEvaluationResult { + + /** + * Factory for creating enabled results. + * + * @param reason the reason why the container or test should be enabled + * @return an enabled {@code ConditionEvaluationResult} with the given reason + */ + public static ConditionEvaluationResult enabled(String reason) { + return new ConditionEvaluationResult(true, reason); + } + + /** + * Factory for creating disabled results. + * + * @param reason the reason why the container or test should be disabled + * @return a disabled {@code ConditionEvaluationResult} with the given reason + */ + public static ConditionEvaluationResult disabled(String reason) { + return new ConditionEvaluationResult(false, reason); + } + + /** + * Factory for creating disabled results with custom reasons + * added by the user. + * + * @param reason the default reason why the container or test should be disabled + * @param customReason the custom reason why the container or test should be disabled + * @return a disabled {@code ConditionEvaluationResult} with the given reasons + * @since 5.7 + */ + @API(status = STABLE, since = "5.7") + public static ConditionEvaluationResult disabled(String reason, String customReason) { + if (StringUtils.isBlank(customReason)) { + return disabled(reason); + } + return disabled(String.format("%s ==> %s", reason, customReason)); + } + + private final boolean enabled; + + private final Optional reason; + + private ConditionEvaluationResult(boolean enabled, String reason) { + this.enabled = enabled; + this.reason = Optional.ofNullable(reason); + } + + /** + * Whether the container or test should be disabled. + * + * @return {@code true} if the container or test should be disabled + */ + public boolean isDisabled() { + return !this.enabled; + } + + /** + * Get the reason why the container or test should be enabled or disabled, + * if available. + */ + public Optional getReason() { + return this.reason; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("enabled", this.enabled) + .append("reason", this.reason.orElse("")) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java new file mode 100644 index 00000000..a040ae80 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.function.Executable; + +/** + * {@code DynamicTestInvocationContext} represents the context of a + * single invocation of a {@linkplain org.junit.jupiter.api.DynamicTest + * dynamic test}. + * + * @since 5.8 + * @see org.junit.jupiter.api.DynamicTest + */ +@API(status = EXPERIMENTAL, since = "5.8") +public interface DynamicTestInvocationContext { + + /** + * Get the {@code Executable} of this dynamic test invocation context. + * + * @return the executable of the dynamic test invocation, never {@code null} + */ + Executable getExecutable(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java new file mode 100644 index 00000000..7cfad263 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.apiguardian.api.API; + +/** + * {@code ExecutableInvoker} allows invoking methods and constructors + * with support for dynamic resolution of parameters via + * {@link ParameterResolver ParameterResolvers}. + * + * @since 5.9 + */ +@API(status = EXPERIMENTAL, since = "5.9") +public interface ExecutableInvoker { + + /** + * Invoke the supplied {@code static} method with dynamic parameter resolution. + * + * @param method the method to invoke and resolve parameters for + * @see #invoke(Method, Object) + */ + default Object invoke(Method method) { + return invoke(method, null); + } + + /** + * Invoke the supplied method with dynamic parameter resolution. + * + * @param method the method to invoke and resolve parameters for + * @param target the target on which the executable will be invoked; + * can be {@code null} for {@code static} methods + */ + Object invoke(Method method, Object target); + + /** + * Invoke the supplied top-level constructor with dynamic parameter resolution. + * + * @param constructor the constructor to invoke and resolve parameters for + * @see #invoke(Constructor, Object) + */ + default T invoke(Constructor constructor) { + return invoke(constructor, null); + } + + /** + * Invoke the supplied constructor with the supplied outer instance and + * dynamic parameter resolution. + * + *

Use this method when invoking the constructor for an inner class. + * + * @param constructor the constructor to invoke and resolve parameters for + * @param outerInstance the outer instance to supply as the first argument + * to the constructor; must be {@code null} for top-level classes + * or {@code static} nested classes + */ + T invoke(Constructor constructor, Object outerInstance); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java new file mode 100644 index 00000000..9747a1e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code ExecutionCondition} defines the {@link Extension} API for + * programmatic, conditional test execution. + * + *

An {@code ExecutionCondition} is + * {@linkplain #evaluateExecutionCondition(ExtensionContext) evaluated} + * to determine if a given container or test should be executed based on the + * supplied {@link ExtensionContext}. + * + *

If an {@code ExecutionCondition} {@linkplain ConditionEvaluationResult#disabled + * disables} a test method, that does not prevent the test class from being + * instantiated. Rather, it prevents the execution of the test method and + * method-level lifecycle callbacks such as {@code @BeforeEach} methods, + * {@code @AfterEach} methods, and corresponding extension APIs. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + * @since 5.0 + * @see org.junit.jupiter.api.Disabled + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface ExecutionCondition extends Extension { + + /** + * Evaluate this condition for the supplied {@link ExtensionContext}. + * + *

An {@linkplain ConditionEvaluationResult#enabled enabled} result + * indicates that the container or test should be executed; whereas, a + * {@linkplain ConditionEvaluationResult#disabled disabled} result + * indicates that the container or test should not be executed. + * + * @param context the current extension context; never {@code null} + * @return the result of evaluating this condition; never {@code null} + */ + ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java new file mode 100644 index 00000000..dbee47c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java @@ -0,0 +1,102 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ExtendWith} is a {@linkplain Repeatable repeatable} annotation + * that is used to register {@linkplain Extension extensions} for the annotated + * test class, test interface, test method, parameter, or field. + * + *

Annotated parameters are supported in test class constructors, in test + * methods, and in {@code @BeforeAll}, {@code @AfterAll}, {@code @BeforeEach}, + * and {@code @AfterEach} lifecycle methods. + * + *

{@code @ExtendWith} fields may be either {@code static} or non-static. + * + *

Inheritance

+ * + *

{@code @ExtendWith} fields are inherited from superclasses as long as they + * are not hidden or overridden. Furthermore, {@code @ExtendWith} + * fields from superclasses will be registered before {@code @ExtendWith} fields + * in subclasses. + * + *

Registration Order

+ * + *

When {@code @ExtendWith} is present on a test class, test interface, or + * test method or on a parameter in a test method or lifecycle method, the + * corresponding extensions will be registered in the order in which the + * {@code @ExtendWith} annotations are discovered. For example, if a test class + * is annotated with {@code @ExtendWith(A.class)} and then with + * {@code @ExtendWith(B.class)}, extension {@code A} will be registered before + * extension {@code B}. + * + *

By default, if multiple extensions are registered on fields via + * {@code @ExtendWith}, they will be ordered using an algorithm that is + * deterministic but intentionally nonobvious. This ensures that subsequent runs + * of a test suite execute extensions in the same order, thereby allowing for + * repeatable builds. However, there are times when extensions need to be + * registered in an explicit order. To achieve that, you can annotate + * {@code @ExtendWith} fields with {@link org.junit.jupiter.api.Order @Order}. + * Any {@code @ExtendWith} field not annotated with {@code @Order} will be + * ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order + * value. Note that {@code @RegisterExtension} fields can also be ordered with + * {@code @Order}, relative to {@code @ExtendWith} fields and other + * {@code @RegisterExtension} fields. + * + *

Supported Extension APIs

+ * + *
    + *
  • {@link ExecutionCondition}
  • + *
  • {@link InvocationInterceptor}
  • + *
  • {@link BeforeAllCallback}
  • + *
  • {@link AfterAllCallback}
  • + *
  • {@link BeforeEachCallback}
  • + *
  • {@link AfterEachCallback}
  • + *
  • {@link BeforeTestExecutionCallback}
  • + *
  • {@link AfterTestExecutionCallback}
  • + *
  • {@link TestInstanceFactory}
  • + *
  • {@link TestInstancePostProcessor}
  • + *
  • {@link TestInstancePreDestroyCallback}
  • + *
  • {@link ParameterResolver}
  • + *
  • {@link TestExecutionExceptionHandler}
  • + *
  • {@link TestTemplateInvocationContextProvider}
  • + *
+ * + * @since 5.0 + * @see RegisterExtension + * @see Extension + */ +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Repeatable(Extensions.class) +@API(status = STABLE, since = "5.0") +public @interface ExtendWith { + + /** + * An array of one or more {@link Extension} classes to register. + */ + Class[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java new file mode 100644 index 00000000..fa1d255e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * Marker interface for all extensions. + * + *

An {@code Extension} can be registered declaratively via + * {@link ExtendWith @ExtendWith}, programmatically via + * {@link RegisterExtension @RegisterExtension}, or automatically via + * the {@link java.util.ServiceLoader} mechanism. For details on the latter, + * consult the User Guide. + * + *

Constructor Requirements

+ * + *

Extension implementations must have a default constructor if + * registered via {@code @ExtendWith} or the {@code ServiceLoader}. When + * registered via {@code @ExtendWith} the default constructor is not required + * to be {@code public}. When registered via the {@code ServiceLoader} the + * default constructor must be {@code public}. When registered via + * {@code @RegisterExtension} the extension's constructors typically must be + * {@code public} unless the extension provides {@code static} factory methods + * or a builder API as an alternative to constructors. + * + * @since 5.0 + */ +@API(status = STABLE, since = "5.0") +public interface Extension { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java new file mode 100644 index 00000000..f4204b49 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * Thrown if an error is encountered regarding the configuration of an + * extension. + * + * @since 5.0 + */ +@API(status = STABLE, since = "5.0") +public class ExtensionConfigurationException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ExtensionConfigurationException(String message) { + super(message); + } + + public ExtensionConfigurationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java new file mode 100644 index 00000000..e58f585f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java @@ -0,0 +1,713 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code ExtensionContext} encapsulates the context in which the + * current test or container is being executed. + * + *

{@link Extension Extensions} are provided an instance of + * {@code ExtensionContext} to perform their work. + * + * @since 5.0 + * @see Store + * @see Namespace + */ +@API(status = STABLE, since = "5.0") +public interface ExtensionContext { + + /** + * Get the parent extension context, if available. + * + * @return an {@code Optional} containing the parent; never {@code null} but + * potentially empty + * @see #getRoot() + */ + Optional getParent(); + + /** + * Get the root {@code ExtensionContext}. + * + * @return the root extension context; never {@code null} but potentially + * this {@code ExtensionContext} + * @see #getParent() + */ + ExtensionContext getRoot(); + + /** + * Get the unique ID of the current test or container. + * + * @return the unique ID of the test or container; never {@code null} or blank + */ + String getUniqueId(); + + /** + * Get the display name for the current test or container. + * + *

The display name is either a default name or a custom name configured + * via {@link org.junit.jupiter.api.DisplayName @DisplayName}. + * + *

For details on default display names consult the Javadoc for + * {@link org.junit.jupiter.api.TestInfo#getDisplayName()}. + * + *

Note that display names are typically used for test reporting in IDEs + * and build tools and may contain spaces, special characters, and even emoji. + * + * @return the display name of the test or container; never {@code null} or blank + */ + String getDisplayName(); + + /** + * Get the set of all tags for the current test or container. + * + *

Tags may be declared directly on the test element or inherited + * from an outer context. + * + * @return the set of tags for the test or container; never {@code null} but + * potentially empty + */ + Set getTags(); + + /** + * Get the {@link AnnotatedElement} corresponding to the current extension + * context, if available. + * + *

For example, if the current extension context encapsulates a test + * class, test method, test factory method, or test template method, the + * annotated element will be the corresponding {@link Class} or {@link Method} + * reference. + * + *

Favor this method over more specific methods whenever the + * {@code AnnotatedElement} API suits the task at hand — for example, + * when looking up annotations regardless of concrete element type. + * + * @return an {@code Optional} containing the {@code AnnotatedElement}; + * never {@code null} but potentially empty + * @see #getTestClass() + * @see #getTestMethod() + */ + Optional getElement(); + + /** + * Get the {@link Class} associated with the current test or container, + * if available. + * + * @return an {@code Optional} containing the class; never {@code null} but + * potentially empty + * @see #getRequiredTestClass() + */ + Optional> getTestClass(); + + /** + * Get the required {@link Class} associated with the current test + * or container. + * + *

Use this method as an alternative to {@link #getTestClass()} for use + * cases in which the test class is required to be present. + * + * @return the test class; never {@code null} + * @throws PreconditionViolationException if the test class is not present + * in this {@code ExtensionContext} + */ + default Class getRequiredTestClass() { + return Preconditions.notNull(getTestClass().orElse(null), + "Illegal state: required test class is not present in the current ExtensionContext"); + } + + /** + * Get the {@link Lifecycle} of the {@linkplain #getTestInstance() test + * instance} associated with the current test or container, if available. + * + * @return an {@code Optional} containing the test instance {@code Lifecycle}; + * never {@code null} but potentially empty + * @since 5.1 + * @see org.junit.jupiter.api.TestInstance {@code @TestInstance} + */ + @API(status = STABLE, since = "5.1") + Optional getTestInstanceLifecycle(); + + /** + * Get the test instance associated with the current test or container, + * if available. + * + * @return an {@code Optional} containing the test instance; never + * {@code null} but potentially empty + * @see #getRequiredTestInstance() + * @see #getTestInstances() + */ + Optional getTestInstance(); + + /** + * Get the required test instance associated with the current test + * or container. + * + *

Use this method as an alternative to {@link #getTestInstance()} for use + * cases in which the test instance is required to be present. + * + * @return the test instance; never {@code null} + * @throws PreconditionViolationException if the test instance is not present + * in this {@code ExtensionContext} + * + * @see #getRequiredTestInstances() + */ + default Object getRequiredTestInstance() { + return Preconditions.notNull(getTestInstance().orElse(null), + "Illegal state: required test instance is not present in the current ExtensionContext"); + } + + /** + * Get the test instances associated with the current test or container, + * if available. + * + *

While top-level tests only have a single test instance, nested tests + * have one additional instance for each enclosing test class. + * + * @return an {@code Optional} containing the test instances; never + * {@code null} but potentially empty + * @since 5.4 + * @see #getRequiredTestInstances() + */ + @API(status = STABLE, since = "5.7") + Optional getTestInstances(); + + /** + * Get the required test instances associated with the current test + * or container. + * + *

Use this method as an alternative to {@link #getTestInstances()} for use + * cases in which the test instances are required to be present. + * + * @return the test instances; never {@code null} + * @throws PreconditionViolationException if the test instances are not present + * in this {@code ExtensionContext} + * @since 5.4 + */ + @API(status = STABLE, since = "5.7") + default TestInstances getRequiredTestInstances() { + return Preconditions.notNull(getTestInstances().orElse(null), + "Illegal state: required test instances are not present in the current ExtensionContext"); + } + + /** + * Get the {@link Method} associated with the current test, if available. + * + * @return an {@code Optional} containing the method; never {@code null} but + * potentially empty + * @see #getRequiredTestMethod() + */ + Optional getTestMethod(); + + /** + * Get the required {@link Method} associated with the current test + * or container. + * + *

Use this method as an alternative to {@link #getTestMethod()} for use + * cases in which the test method is required to be present. + * + * @return the test method; never {@code null} + * @throws PreconditionViolationException if the test method is not present + * in this {@code ExtensionContext} + */ + default Method getRequiredTestMethod() { + return Preconditions.notNull(getTestMethod().orElse(null), + "Illegal state: required test method is not present in the current ExtensionContext"); + } + + /** + * Get the exception that was thrown during execution of the test or container + * associated with this {@code ExtensionContext}, if available. + * + *

This method is typically used for logging and tracing purposes. If you + * wish to actually handle an exception thrown during test execution, + * implement the {@link TestExecutionExceptionHandler} API. + * + *

Unlike the exception passed to a {@code TestExecutionExceptionHandler}, + * an execution exception returned by this method can be any + * exception thrown during the invocation of a {@code @Test} method, its + * surrounding {@code @BeforeEach} and {@code @AfterEach} methods, or a + * test-level {@link Extension}. Similarly, if this {@code ExtensionContext} + * represents a test class, the execution exception returned by + * this method can be any exception thrown in a {@code @BeforeAll} or + * {@code AfterAll} method or a class-level {@link Extension}. + * + *

Note, however, that this method will never return an exception + * swallowed by a {@code TestExecutionExceptionHandler}. Furthermore, if + * multiple exceptions have been thrown during test execution, the exception + * returned by this method will be the first such exception with all + * additional exceptions {@linkplain Throwable#addSuppressed(Throwable) + * suppressed} in the first one. + * + * @return an {@code Optional} containing the exception thrown; never + * {@code null} but potentially empty if test execution has not (yet) + * resulted in an exception + */ + Optional getExecutionException(); + + /** + * Get the configuration parameter stored under the specified {@code key}. + * + *

If no such key is present in the {@code ConfigurationParameters} for + * the JUnit Platform, an attempt will be made to look up the value as a + * JVM system property. If no such system property exists, an attempt will + * be made to look up the value in the JUnit Platform properties file. + * + * @param key the key to look up; never {@code null} or blank + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @since 5.1 + * @see System#getProperty(String) + * @see org.junit.platform.engine.ConfigurationParameters + */ + @API(status = STABLE, since = "5.1") + Optional getConfigurationParameter(String key); + + /** + * Get and transform the configuration parameter stored under the specified + * {@code key} using the specified {@code transformer}. + * + *

If no such key is present in the {@code ConfigurationParameters} for + * the JUnit Platform, an attempt will be made to look up the value as a + * JVM system property. If no such system property exists, an attempt will + * be made to look up the value in the JUnit Platform properties file. + * + *

In case the transformer throws an exception, it will be wrapped in a + * {@link org.junit.platform.commons.JUnitException} with a helpful message. + * + * @param key the key to look up; never {@code null} or blank + * @param transformer the transformer to apply in case a value is found; + * never {@code null} + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @since 5.7 + * @see System#getProperty(String) + * @see org.junit.platform.engine.ConfigurationParameters + */ + @API(status = EXPERIMENTAL, since = "5.7") + Optional getConfigurationParameter(String key, Function transformer); + + /** + * Publish a map of key-value pairs to be consumed by an + * {@code org.junit.platform.engine.EngineExecutionListener} in order to + * supply additional information to the reporting infrastructure. + * + * @param map the key-value pairs to be published; never {@code null}; + * keys and values within entries in the map also must not be + * {@code null} or blank + * @see #publishReportEntry(String, String) + * @see #publishReportEntry(String) + * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished + */ + void publishReportEntry(Map map); + + /** + * Publish the specified key-value pair to be consumed by an + * {@code org.junit.platform.engine.EngineExecutionListener} in order to + * supply additional information to the reporting infrastructure. + * + * @param key the key of the published pair; never {@code null} or blank + * @param value the value of the published pair; never {@code null} or blank + * @see #publishReportEntry(Map) + * @see #publishReportEntry(String) + * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished + */ + default void publishReportEntry(String key, String value) { + this.publishReportEntry(Collections.singletonMap(key, value)); + } + + /** + * Publish the specified value to be consumed by an + * {@code org.junit.platform.engine.EngineExecutionListener} in order to + * supply additional information to the reporting infrastructure. + * + *

This method delegates to {@link #publishReportEntry(String, String)}, + * supplying {@code "value"} as the key and the supplied {@code value} + * argument as the value. + * + * @param value the value to be published; never {@code null} or blank + * @since 5.3 + * @see #publishReportEntry(Map) + * @see #publishReportEntry(String, String) + * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished + */ + @API(status = STABLE, since = "5.3") + default void publishReportEntry(String value) { + this.publishReportEntry("value", value); + } + + /** + * Get the {@link Store} for the supplied {@link Namespace}. + * + *

Use {@code getStore(Namespace.GLOBAL)} to get the default, global {@link Namespace}. + * + *

A store is bound to its extension context lifecycle. When an extension + * context lifecycle ends it closes its associated store. All stored values + * that are instances of {@link ExtensionContext.Store.CloseableResource} are + * notified by invoking their {@code close()} methods. + * + * @param namespace the {@code Namespace} to get the store for; never {@code null} + * @return the store in which to put and get objects for other invocations + * working in the same namespace; never {@code null} + * @see Namespace#GLOBAL + */ + Store getStore(Namespace namespace); + + /** + * Get the {@link ExecutionMode} associated with the current test or container. + * + * @return the {@code ExecutionMode} of the test; never {@code null} + * + * @since 5.8.1 + * @see org.junit.jupiter.api.parallel.ExecutionMode {@code @ExecutionMode} + */ + @API(status = STABLE, since = "5.8.1") + ExecutionMode getExecutionMode(); + + /** + * Get an {@link ExecutableInvoker} to invoke methods and constructors + * with support for dynamic resolution of parameters. + * + * @since 5.9 + */ + @API(status = EXPERIMENTAL, since = "5.9") + ExecutableInvoker getExecutableInvoker(); + + /** + * {@code Store} provides methods for extensions to save and retrieve data. + */ + interface Store { + + /** + * Classes implementing this interface indicate that they want to {@link #close} + * some underlying resource or resources when the enclosing {@link Store Store} + * is closed. + * + *

Note that the {@code CloseableResource} API is only honored for + * objects stored within an extension context {@link Store Store}. + * + *

The resources stored in a {@link Store Store} are closed in the + * inverse order they were added in. + * + * @since 5.1 + */ + @API(status = STABLE, since = "5.1") + interface CloseableResource { + + /** + * Close underlying resources. + * + * @throws Throwable any throwable will be caught and rethrown + */ + void close() throws Throwable; + + } + + /** + * Get the value that is stored under the supplied {@code key}. + * + *

If no value is stored in the current {@link ExtensionContext} + * for the supplied {@code key}, ancestors of the context will be queried + * for a value with the same {@code key} in the {@code Namespace} used + * to create this store. + * + *

For greater type safety, consider using {@link #get(Object, Class)} + * instead. + * + * @param key the key; never {@code null} + * @return the value; potentially {@code null} + * @see #get(Object, Class) + * @see #getOrDefault(Object, Class, Object) + */ + Object get(Object key); + + /** + * Get the value of the specified required type that is stored under + * the supplied {@code key}. + * + *

If no value is stored in the current {@link ExtensionContext} + * for the supplied {@code key}, ancestors of the context will be queried + * for a value with the same {@code key} in the {@code Namespace} used + * to create this store. + * + * @param key the key; never {@code null} + * @param requiredType the required type of the value; never {@code null} + * @param the value type + * @return the value; potentially {@code null} + * @see #get(Object) + * @see #getOrDefault(Object, Class, Object) + */ + V get(Object key, Class requiredType); + + /** + * Get the value of the specified required type that is stored under + * the supplied {@code key}, or the supplied {@code defaultValue} if no + * value is found for the supplied {@code key} in this store or in an + * ancestor. + * + *

If no value is stored in the current {@link ExtensionContext} + * for the supplied {@code key}, ancestors of the context will be queried + * for a value with the same {@code key} in the {@code Namespace} used + * to create this store. + * + * @param key the key; never {@code null} + * @param requiredType the required type of the value; never {@code null} + * @param defaultValue the default value + * @param the value type + * @return the value; potentially {@code null} + * @since 5.5 + * @see #get(Object, Class) + */ + @API(status = STABLE, since = "5.5") + default V getOrDefault(Object key, Class requiredType, V defaultValue) { + V value = get(key, requiredType); + return (value != null ? value : defaultValue); + } + + /** + * Get the object of type {@code type} that is present in this + * {@code Store} (keyed by {@code type}); and otherwise invoke + * the default constructor for {@code type} to generate the object, + * store it, and return it. + * + *

This method is a shortcut for the following, where {@code X} is + * the type of object we wish to retrieve from the store. + * + *

+		 * X x = store.getOrComputeIfAbsent(X.class, key -> new X(), X.class);
+		 * // Equivalent to:
+		 * // X x = store.getOrComputeIfAbsent(X.class);
+		 * 
+ * + *

See {@link #getOrComputeIfAbsent(Object, Function, Class)} for + * further details. + * + *

If {@code type} implements {@link ExtensionContext.Store.CloseableResource} + * the {@code close()} method will be invoked on the stored object when + * the store is closed. + * + * @param type the type of object to retrieve; never {@code null} + * @param the key and value type + * @return the object; never {@code null} + * @since 5.1 + * @see #getOrComputeIfAbsent(Object, Function) + * @see #getOrComputeIfAbsent(Object, Function, Class) + * @see CloseableResource + */ + @API(status = STABLE, since = "5.1") + default V getOrComputeIfAbsent(Class type) { + return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); + } + + /** + * Get the value that is stored under the supplied {@code key}. + * + *

If no value is stored in the current {@link ExtensionContext} + * for the supplied {@code key}, ancestors of the context will be queried + * for a value with the same {@code key} in the {@code Namespace} used + * to create this store. If no value is found for the supplied {@code key}, + * a new value will be computed by the {@code defaultCreator} (given + * the {@code key} as input), stored, and returned. + * + *

For greater type safety, consider using + * {@link #getOrComputeIfAbsent(Object, Function, Class)} instead. + * + *

If the created value is an instance of {@link ExtensionContext.Store.CloseableResource} + * the {@code close()} method will be invoked on the stored object when + * the store is closed. + * + * @param key the key; never {@code null} + * @param defaultCreator the function called with the supplied {@code key} + * to create a new value; never {@code null} + * @param the key type + * @param the value type + * @return the value; potentially {@code null} + * @see #getOrComputeIfAbsent(Class) + * @see #getOrComputeIfAbsent(Object, Function, Class) + * @see CloseableResource + */ + Object getOrComputeIfAbsent(K key, Function defaultCreator); + + /** + * Get the value of the specified required type that is stored under the + * supplied {@code key}. + * + *

If no value is stored in the current {@link ExtensionContext} + * for the supplied {@code key}, ancestors of the context will be queried + * for a value with the same {@code key} in the {@code Namespace} used + * to create this store. If no value is found for the supplied {@code key}, + * a new value will be computed by the {@code defaultCreator} (given + * the {@code key} as input), stored, and returned. + * + *

If {@code requiredType} implements {@link ExtensionContext.Store.CloseableResource} + * the {@code close()} method will be invoked on the stored object when + * the store is closed. + * + * @param key the key; never {@code null} + * @param defaultCreator the function called with the supplied {@code key} + * to create a new value; never {@code null} + * @param requiredType the required type of the value; never {@code null} + * @param the key type + * @param the value type + * @return the value; potentially {@code null} + * @see #getOrComputeIfAbsent(Class) + * @see #getOrComputeIfAbsent(Object, Function) + * @see CloseableResource + */ + V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); + + /** + * Store a {@code value} for later retrieval under the supplied {@code key}. + * + *

A stored {@code value} is visible in child {@link ExtensionContext + * ExtensionContexts} for the store's {@code Namespace} unless they + * overwrite it. + * + *

If the {@code value} is an instance of {@link ExtensionContext.Store.CloseableResource} + * the {@code close()} method will be invoked on the stored object when + * the store is closed. + * + * @param key the key under which the value should be stored; never + * {@code null} + * @param value the value to store; may be {@code null} + * @see CloseableResource + */ + void put(Object key, Object value); + + /** + * Remove the value that was previously stored under the supplied {@code key}. + * + *

The value will only be removed in the current {@link ExtensionContext}, + * not in ancestors. In addition, the {@link CloseableResource} API will not + * be honored for values that are manually removed via this method. + * + *

For greater type safety, consider using {@link #remove(Object, Class)} + * instead. + * + * @param key the key; never {@code null} + * @return the previous value or {@code null} if no value was present + * for the specified key + * @see #remove(Object, Class) + */ + Object remove(Object key); + + /** + * Remove the value of the specified required type that was previously stored + * under the supplied {@code key}. + * + *

The value will only be removed in the current {@link ExtensionContext}, + * not in ancestors. In addition, the {@link CloseableResource} API will not + * be honored for values that are manually removed via this method. + * + * @param key the key; never {@code null} + * @param requiredType the required type of the value; never {@code null} + * @param the value type + * @return the previous value or {@code null} if no value was present + * for the specified key + * @see #remove(Object) + */ + V remove(Object key, Class requiredType); + + } + + /** + * A {@code Namespace} is used to provide a scope for data saved by + * extensions within a {@link Store}. + * + *

Storing data in custom namespaces allows extensions to avoid accidentally + * mixing data between extensions or across different invocations within the + * lifecycle of a single extension. + */ + class Namespace { + + /** + * The default, global namespace which allows access to stored data from + * all extensions. + */ + public static final Namespace GLOBAL = Namespace.create(new Object()); + + /** + * Create a namespace which restricts access to data to all extensions + * which use the same sequence of {@code parts} for creating a namespace. + * + *

The order of the {@code parts} is significant. + * + *

Internally the {@code parts} are compared using {@link Object#equals(Object)}. + */ + public static Namespace create(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + return new Namespace(new ArrayList<>(Arrays.asList(parts))); + } + + private final List parts; + + private Namespace(List parts) { + this.parts = parts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Namespace that = (Namespace) o; + return this.parts.equals(that.parts); + } + + @Override + public int hashCode() { + return this.parts.hashCode(); + } + + /** + * Create a new namespace by appending the supplied {@code parts} to the + * existing sequence of parts in this namespace. + * + * @return new namespace; never {@code null} + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + public Namespace append(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); + newParts.addAll(this.parts); + Collections.addAll(newParts, parts); + return new Namespace(newParts); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java new file mode 100644 index 00000000..957c0ed6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.platform.commons.JUnitException; + +/** + * Thrown if an error is encountered regarding the use of an + * {@link ExtensionContext} or {@link Store}. + * + * @since 5.0 + */ +@API(status = STABLE, since = "5.0") +public class ExtensionContextException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ExtensionContextException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java new file mode 100644 index 00000000..a683dea9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @Extensions} is a container for one or more {@code @ExtendWith} + * declarations. + * + *

Note, however, that use of the {@code @Extensions} container is completely + * optional since {@code @ExtendWith} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.0 + * @see ExtendWith + * @see java.lang.annotation.Repeatable + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@API(status = STABLE, since = "5.0") +public @interface Extensions { + + /** + * An array of one or more {@link ExtendWith @ExtendWith} declarations. + */ + ExtendWith[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java new file mode 100644 index 00000000..5d4bbdb2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java @@ -0,0 +1,248 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestTemplate; + +/** + * {@code InvocationInterceptor} defines the API for {@link Extension + * Extensions} that wish to intercept calls to test code. + * + *

Invocation Contract

+ * + *

Each method in this class must call {@link Invocation#proceed()} or {@link + * Invocation#skip()} exactly once on the supplied invocation. Otherwise, the + * enclosing test or container will be reported as failed. + * + *

The default implementation calls {@link Invocation#proceed() + * proceed()} on the supplied {@linkplain Invocation invocation}. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + * @since 5.5 + * @see Invocation + * @see ReflectiveInvocationContext + * @see ExtensionContext + */ +@API(status = EXPERIMENTAL, since = "5.5") +public interface InvocationInterceptor extends Extension { + + /** + * Intercept the invocation of a test class constructor. + * + *

Note that the test class may not have been initialized + * (static initialization) when this method is invoked. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @param the result type + * @return the result of the invocation; never {@code null} + * @throws Throwable in case of failure + */ + default T interceptTestClassConstructor(Invocation invocation, + ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) + throws Throwable { + return invocation.proceed(); + } + + /** + * Intercept the invocation of a {@link BeforeAll @BeforeAll} method. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + */ + default void interceptBeforeAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + invocation.proceed(); + } + + /** + * Intercept the invocation of a {@link BeforeEach @BeforeEach} method. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + */ + default void interceptBeforeEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + invocation.proceed(); + } + + /** + * Intercept the invocation of a {@link Test @Test} method. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + */ + default void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { + invocation.proceed(); + } + + /** + * Intercept the invocation of a {@link TestFactory @TestFactory} method, + * such as a {@link org.junit.jupiter.api.RepeatedTest @RepeatedTest} or + * {@code @ParameterizedTest} method. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @param the result type + * @return the result of the invocation; potentially {@code null} + * @throws Throwable in case of failures + */ + default T interceptTestFactoryMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + return invocation.proceed(); + } + + /** + * Intercept the invocation of a {@link TestTemplate @TestTemplate} method. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + */ + default void interceptTestTemplateMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + invocation.proceed(); + } + + /** + * Intercept the invocation of a {@link DynamicTest}. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + * @deprecated use {@link #interceptDynamicTest(Invocation, DynamicTestInvocationContext, ExtensionContext)} instead + */ + @Deprecated + @API(status = DEPRECATED, since = "5.8") + default void interceptDynamicTest(Invocation invocation, ExtensionContext extensionContext) throws Throwable { + invocation.proceed(); + } + + /** + * Intercept the invocation of a {@link DynamicTest}. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + */ + @API(status = EXPERIMENTAL, since = "5.8") + default void interceptDynamicTest(Invocation invocation, DynamicTestInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { + // by default call the old interceptDynamicTest(Invocation, ExtensionContext) method so that existing extensions still work + interceptDynamicTest(invocation, extensionContext); + } + + /** + * Intercept the invocation of an {@link AfterEach @AfterEach} method. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + */ + default void interceptAfterEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + invocation.proceed(); + } + + /** + * Intercept the invocation of an {@link AfterAll @AfterAll} method. + * + * @param invocation the invocation that is being intercepted; never + * {@code null} + * @param invocationContext the context of the invocation that is being + * intercepted; never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @throws Throwable in case of failures + */ + default void interceptAfterAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + invocation.proceed(); + } + + /** + * An invocation that returns a result and may throw a {@link Throwable}. + * + *

This interface is not intended to be implemented by clients. + * + * @param the result type + * @since 5.5 + */ + @API(status = EXPERIMENTAL, since = "5.5") + interface Invocation { + + /** + * Proceed with this invocation. + * + * @return the result of this invocation; potentially {@code null}. + * @throws Throwable in case the invocation failed + */ + T proceed() throws Throwable; + + /** + * Explicitly skip this invocation. + * + *

This allows to bypass the check that {@link #proceed()} must be + * called at least once. The default implementation does nothing. + */ + @API(status = EXPERIMENTAL, since = "5.6") + default void skip() { + // do nothing + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java new file mode 100644 index 00000000..7b7eb210 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * {@code LifecycleMethodExecutionExceptionHandler} defines the API for + * {@link Extension Extensions} that wish to handle exceptions thrown during + * the execution of {@code @BeforeAll}, {@code @BeforeEach}, {@code @AfterEach}, + * and {@code @AfterAll} lifecycle methods. + * + *

Common use cases include swallowing an exception if it's anticipated, + * logging errors, or rolling back a transaction in certain error scenarios. + * + *

Implementations of this extension API must be registered at the class level + * if exceptions thrown from {@code @BeforeAll} or {@code @AfterAll} methods are + * to be handled. When registered at the test level, only exceptions thrown from + * {@code @BeforeEach} or {@code @AfterEach} methods will be handled. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on constructor + * requirements. + * + *

Implementation Guidelines

+ * + *

An implementation of an exception handler method defined in this API must + * perform one of the following. + * + *

    + *
  1. Rethrow the supplied {@code Throwable} as is, which is the default implementation.
  2. + *
  3. Swallow the supplied {@code Throwable}, thereby preventing propagation.
  4. + *
  5. Throw a new exception, potentially wrapping the supplied {@code Throwable}.
  6. + *
+ * + *

If the supplied {@code Throwable} is swallowed by a handler method, subsequent + * handler methods for the same lifecycle will not be invoked; otherwise, the + * corresponding handler method of the next registered + * {@code LifecycleMethodExecutionExceptionHandler} (if there is one) will be + * invoked with any {@link Throwable} thrown by the previous handler. + * + * @since 5.5 + * @see TestExecutionExceptionHandler + */ +@API(status = EXPERIMENTAL, since = "5.5") +public interface LifecycleMethodExecutionExceptionHandler extends Extension { + + /** + * Handle the supplied {@link Throwable} that was thrown during execution of + * a {@code @BeforeAll} lifecycle method. + * + *

Please refer to the class-level Javadoc for + * Implementation Guidelines. + * + * @param context the current extension context; never {@code null} + * @param throwable the {@code Throwable} to handle; never {@code null} + */ + default void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + + throw throwable; + } + + /** + * Handle the supplied {@link Throwable} that was thrown during execution of + * a {@code @BeforeEach} lifecycle method. + * + *

Please refer to the class-level Javadoc for + * Implementation Guidelines. + * + * @param context the current extension context; never {@code null} + * @param throwable the {@code Throwable} to handle; never {@code null} + */ + default void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + + throw throwable; + } + + /** + * Handle the supplied {@link Throwable} that was thrown during execution of + * a {@code @AfterEach} lifecycle method. + * + *

Please refer to the class-level Javadoc for + * Implementation Guidelines. + * + * @param context the current extension context; never {@code null} + * @param throwable the {@code Throwable} to handle; never {@code null} + */ + default void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + + throw throwable; + } + + /** + * Handle the supplied {@link Throwable} that was thrown during execution of + * a {@code @AfterAll} lifecycle method. + * + *

Please refer to the class-level Javadoc for + * Implementation Guidelines. + * + * @param context the current extension context; never {@code null} + * @param throwable the {@code Throwable} to handle; never {@code null} + */ + default void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + + throw throwable; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java new file mode 100644 index 00000000..1bce9ffb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java @@ -0,0 +1,151 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Executable; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ParameterContext} encapsulates the context in which an + * {@link #getDeclaringExecutable Executable} will be invoked for a given + * {@link #getParameter Parameter}. + * + *

A {@code ParameterContext} is used to support parameter resolution via + * a {@link ParameterResolver}. + * + * @since 5.0 + * @see ParameterResolver + * @see java.lang.reflect.Parameter + * @see java.lang.reflect.Executable + * @see java.lang.reflect.Method + * @see java.lang.reflect.Constructor + */ +@API(status = STABLE, since = "5.0") +public interface ParameterContext { + + /** + * Get the {@link Parameter} for this context. + * + *

WARNING

+ *

When searching for annotations on the parameter in this context, + * favor {@link #isAnnotated(Class)}, {@link #findAnnotation(Class)}, and + * {@link #findRepeatableAnnotations(Class)} over methods in the + * {@link Parameter} API due to a bug in {@code javac} on JDK versions prior + * to JDK 9. + * + * @return the parameter; never {@code null} + * @see #getIndex() + */ + Parameter getParameter(); + + /** + * Get the index of the {@link Parameter} for this context within the + * parameter list of the {@link #getDeclaringExecutable Executable} that + * declares the parameter. + * + * @return the index of the parameter + * @see #getParameter() + * @see Executable#getParameters() + */ + int getIndex(); + + /** + * Get the {@link Executable} (i.e., the {@link java.lang.reflect.Method} or + * {@link java.lang.reflect.Constructor}) that declares the {@code Parameter} + * for this context. + * + * @return the declaring {@code Executable}; never {@code null} + * @see Parameter#getDeclaringExecutable() + */ + default Executable getDeclaringExecutable() { + return getParameter().getDeclaringExecutable(); + } + + /** + * Get the target on which the {@link #getDeclaringExecutable Executable} + * that declares the {@link #getParameter Parameter} for this context will + * be invoked, if available. + * + * @return an {@link Optional} containing the target on which the + * {@code Executable} will be invoked; never {@code null} but will be + * empty if the {@code Executable} is a constructor or a + * {@code static} method. + */ + Optional getTarget(); + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the {@link Parameter} for + * this context. + * + *

WARNING

+ *

Favor the use of this method over directly invoking + * {@link Parameter#isAnnotationPresent(Class)} due to a bug in {@code javac} + * on JDK versions prior to JDK 9. + * + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @since 5.1.1 + * @see #findAnnotation(Class) + * @see #findRepeatableAnnotations(Class) + */ + boolean isAnnotated(Class annotationType); + + /** + * Find the first annotation of {@code annotationType} that is either + * present or meta-present on the {@link Parameter} for + * this context. + * + *

WARNING

+ *

Favor the use of this method over directly invoking annotation lookup + * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK + * versions prior to JDK 9. + * + * @param the annotation type + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @since 5.1.1 + * @see #isAnnotated(Class) + * @see #findRepeatableAnnotations(Class) + */ + Optional findAnnotation(Class annotationType); + + /** + * Find all repeatable {@linkplain Annotation annotations} of + * {@code annotationType} that are either present or + * meta-present on the {@link Parameter} for this context. + * + *

WARNING

+ *

Favor the use of this method over directly invoking annotation lookup + * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK + * versions prior to JDK 9. + * + * @param the annotation type + * @param annotationType the repeatable annotation type to search for; never + * {@code null} + * @return the list of all such annotations found; neither {@code null} nor + * mutable, but potentially empty + * @since 5.1.1 + * @see #isAnnotated(Class) + * @see #findAnnotation(Class) + * @see java.lang.annotation.Repeatable + */ + List findRepeatableAnnotations(Class annotationType); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java new file mode 100644 index 00000000..5e34e823 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * Thrown if an error is encountered in the configuration or execution of a + * {@link ParameterResolver}. + * + * @since 5.0 + * @see ParameterResolver + */ +@API(status = STABLE, since = "5.0") +public class ParameterResolutionException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ParameterResolutionException(String message) { + super(message); + } + + public ParameterResolutionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java new file mode 100644 index 00000000..3ce4fdb4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.reflect.Parameter; + +import org.apiguardian.api.API; + +/** + * {@code ParameterResolver} defines the API for {@link Extension Extensions} + * that wish to dynamically resolve arguments for {@linkplain Parameter parameters} + * at runtime. + * + *

If a constructor for a test class or a + * {@link org.junit.jupiter.api.Test @Test}, + * {@link org.junit.jupiter.api.BeforeEach @BeforeEach}, + * {@link org.junit.jupiter.api.AfterEach @AfterEach}, + * {@link org.junit.jupiter.api.BeforeAll @BeforeAll}, or + * {@link org.junit.jupiter.api.AfterAll @AfterAll} method declares a parameter, + * an argument for the parameter must be resolved at runtime by a + * {@code ParameterResolver}. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + * @since 5.0 + * @see #supportsParameter(ParameterContext, ExtensionContext) + * @see #resolveParameter(ParameterContext, ExtensionContext) + * @see ParameterContext + * @see TestInstanceFactory + * @see TestInstancePostProcessor + * @see TestInstancePreDestroyCallback + */ +@API(status = STABLE, since = "5.0") +public interface ParameterResolver extends Extension { + + /** + * Determine if this resolver supports resolution of an argument for the + * {@link Parameter} in the supplied {@link ParameterContext} for the supplied + * {@link ExtensionContext}. + * + *

The {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} + * in which the parameter is declared can be retrieved via + * {@link ParameterContext#getDeclaringExecutable()}. + * + * @param parameterContext the context for the parameter for which an argument should + * be resolved; never {@code null} + * @param extensionContext the extension context for the {@code Executable} + * about to be invoked; never {@code null} + * @return {@code true} if this resolver can resolve an argument for the parameter + * @see #resolveParameter + * @see ParameterContext + */ + boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException; + + /** + * Resolve an argument for the {@link Parameter} in the supplied {@link ParameterContext} + * for the supplied {@link ExtensionContext}. + * + *

This method is only called by the framework if {@link #supportsParameter} + * previously returned {@code true} for the same {@link ParameterContext} + * and {@link ExtensionContext}. + * + *

The {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} + * in which the parameter is declared can be retrieved via + * {@link ParameterContext#getDeclaringExecutable()}. + * + * @param parameterContext the context for the parameter for which an argument should + * be resolved; never {@code null} + * @param extensionContext the extension context for the {@code Executable} + * about to be invoked; never {@code null} + * @return the resolved argument for the parameter; may only be {@code null} if the + * parameter type is not a primitive + * @see #supportsParameter + * @see ParameterContext + */ + Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java new file mode 100644 index 00000000..90666658 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.reflect.Executable; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code ReflectiveInvocationContext} encapsulates the context of + * a reflective invocation of an executable (method or constructor). + * + *

This interface is not intended to be implemented by clients. + * + * @since 5.5 + */ +@API(status = EXPERIMENTAL, since = "5.5") +public interface ReflectiveInvocationContext { + + /** + * Get the target class of this invocation context. + * + *

If this invocation context represents an instance method, this + * method returns the class of the object the method will be invoked on, + * not the class it is declared in. Otherwise, if this invocation + * represents a static method or constructor, this method returns the + * class the method or constructor is declared in. + * + * @return the target class of this invocation context; never + * {@code null} + */ + Class getTargetClass(); + + /** + * Get the method or constructor of this invocation context. + * + * @return the executable of this invocation context; never {@code null} + */ + T getExecutable(); + + /** + * Get the arguments of the executable in this invocation context. + * + * @return the arguments of the executable in this invocation context; + * immutable and never {@code null} + */ + List getArguments(); + + /** + * Get the target object of this invocation context, if available. + * + *

If this invocation context represents an instance method, this + * method returns the object the method will be invoked on. Otherwise, + * if this invocation context represents a static method or + * constructor, this method returns {@link Optional#empty() empty()}. + * + * @return the target of the executable of this invocation context; never + * {@code null} but potentially empty + */ + Optional getTarget(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java new file mode 100644 index 00000000..c3f89728 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @RegisterExtension} is used to register an {@link Extension} via a + * field in a test class. + * + *

In contrast to {@link ExtendWith @ExtendWith} which is used to register + * extensions declaratively, {@code @RegisterExtension} can be used to + * register an extension programmatically — for example, in order + * to pass arguments to the extension's constructor, {@code static} factory + * method, or builder API. + * + *

{@code @RegisterExtension} fields must not be {@code null} (when evaluated) + * but may be either {@code static} or non-static. + * + *

Static Fields

+ * + *

If a {@code @RegisterExtension} field is {@code static}, the extension + * will be registered after extensions that are registered at the class level + * via {@code @ExtendWith}. Such static extensions are not limited in + * which extension APIs they can implement. Extensions registered via static + * fields may therefore implement class-level and instance-level extension APIs + * such as {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link TestInstanceFactory}, {@link TestInstancePostProcessor} and + * {@link TestInstancePreDestroyCallback} as well as method-level extension APIs + * such as {@link BeforeEachCallback}, etc. + * + *

Instance Fields

+ * + *

If a {@code @RegisterExtension} field is non-static (i.e., an instance + * field), the extension will be registered after the test class has been + * instantiated and after all {@link TestInstancePostProcessor + * TestInstancePostProcessors} have been given a chance to post-process the + * test instance (potentially injecting the instance of the extension to be + * used into the annotated field). Thus, if such an instance extension + * implements class-level or instance-level extension APIs such as + * {@link BeforeAllCallback}, {@link AfterAllCallback}, + * {@link TestInstanceFactory}, or {@link TestInstancePostProcessor} those APIs + * will not be honored. By default, an instance extension will be registered + * after extensions that are registered at the method level via + * {@code @ExtendWith}; however, if the test class is configured with + * {@link org.junit.jupiter.api.TestInstance.Lifecycle @TestInstance(Lifecycle.PER_CLASS)} + * semantics, an instance extension will be registered before extensions + * that are registered at the method level via {@code @ExtendWith}. + * + *

Inheritance

+ * + *

{@code @RegisterExtension} fields are inherited from superclasses as long + * as they are not hidden or overridden. Furthermore, + * {@code @RegisterExtension} fields from superclasses will be registered before + * {@code @RegisterExtension} fields in subclasses. + * + *

Registration Order

+ * + *

By default, if multiple extensions are registered via + * {@code @RegisterExtension}, they will be ordered using an algorithm that is + * deterministic but intentionally nonobvious. This ensures that subsequent runs + * of a test suite execute extensions in the same order, thereby allowing for + * repeatable builds. However, there are times when extensions need to be + * registered in an explicit order. To achieve that, you can annotate + * {@code @RegisterExtension} fields with {@link org.junit.jupiter.api.Order @Order}. + * Any {@code @RegisterExtension} field not annotated with {@code @Order} will be + * ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order + * value. Note that {@code @ExtendWith} fields can also be ordered with + * {@code @Order}, relative to {@code @RegisterExtension} fields and other + * {@code @ExtendWith} fields. + * + *

Example Usage

+ * + *

In the following example, the {@code docs} field in the test class is + * initialized programmatically by supplying a custom {@code lookUpDocsDir()} + * method to a {@code static} factory method in the {@code DocumentationExtension}. + * The configured {@code DocumentationExtension} will be automatically registered + * as an extension. In addition, test methods can access the instance of the + * extension via the {@code docs} field if necessary. + * + *

+ * class DocumentationTests {
+ *
+ *     static Path lookUpDocsDir() {
+ *         // return path to docs dir
+ *     }
+ *
+ *     {@literal @}RegisterExtension
+ *     DocumentationExtension docs =
+ *         DocumentationExtension.forPath(lookUpDocsDir());
+ *
+ *     {@literal @}Test
+ *     void generateDocumentation() {
+ *         // use docs ...
+ *     }
+ * }
+ * + *

Supported Extension APIs

+ * + *
    + *
  • {@link ExecutionCondition}
  • + *
  • {@link InvocationInterceptor}
  • + *
  • {@link BeforeAllCallback}
  • + *
  • {@link AfterAllCallback}
  • + *
  • {@link BeforeEachCallback}
  • + *
  • {@link AfterEachCallback}
  • + *
  • {@link BeforeTestExecutionCallback}
  • + *
  • {@link AfterTestExecutionCallback}
  • + *
  • {@link TestInstanceFactory}
  • + *
  • {@link TestInstancePostProcessor}
  • + *
  • {@link TestInstancePreDestroyCallback}
  • + *
  • {@link ParameterResolver}
  • + *
  • {@link TestExecutionExceptionHandler}
  • + *
  • {@link TestTemplateInvocationContextProvider}
  • + *
  • {@link TestWatcher}
  • + *
+ * + * @since 5.1 + * @see ExtendWith @ExtendWith + * @see Extension + * @see org.junit.jupiter.api.Order @Order + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.1") +public @interface RegisterExtension { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java new file mode 100644 index 00000000..cfd03f74 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code TestExecutionExceptionHandler} defines the API for {@link Extension + * Extensions} that wish to handle exceptions thrown during test execution. + * + *

In this context, test execution refers to the physical + * invocation of a {@code @Test} method and not to any test-level extensions + * or callbacks. + * + *

Common use cases include swallowing an exception if it's anticipated + * or rolling back a transaction in certain error scenarios. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + * @since 5.0 + * @see LifecycleMethodExecutionExceptionHandler + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface TestExecutionExceptionHandler extends Extension { + + /** + * Handle the supplied {@link Throwable throwable}. + * + *

Implementors must perform one of the following. + *

    + *
  1. Swallow the supplied {@code throwable}, thereby preventing propagation.
  2. + *
  3. Rethrow the supplied {@code throwable} as is.
  4. + *
  5. Throw a new exception, potentially wrapping the supplied {@code throwable}.
  6. + *
+ * + *

If the supplied {@code throwable} is swallowed, subsequent + * {@code TestExecutionExceptionHandlers} will not be invoked; otherwise, + * the next registered {@code TestExecutionExceptionHandler} (if there is + * one) will be invoked with any {@link Throwable} thrown by this handler. + * + *

Note that the {@link ExtensionContext#getExecutionException() execution + * exception} in the supplied {@code ExtensionContext} will not + * contain the {@code Throwable} thrown during invocation of the corresponding + * {@code @Test} method. + * + * @param context the current extension context; never {@code null} + * @param throwable the {@code Throwable} to handle; never {@code null} + */ + void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java new file mode 100644 index 00000000..beb98895 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code TestInstanceFactory} defines the API for {@link Extension + * Extensions} that wish to {@linkplain #createTestInstance create} test instances. + * + *

Common use cases include creating test instances with special construction + * requirements or acquiring the test instance from a dependency injection + * framework. + * + *

Extensions that implement {@code TestInstanceFactory} must be registered + * at the class level. + * + *

Warning

+ * + *

Only one {@code TestInstanceFactory} is allowed to be registered for any + * given test class. Registering multiple factories for any single test class + * will result in an exception being thrown for all tests in that class, in any + * subclass, and in any nested class. Note that any {@code TestInstanceFactory} + * registered in a {@linkplain Class#getSuperclass() superclass} or + * {@linkplain Class#getEnclosingClass() enclosing} class (i.e., in the case of + * a {@code @Nested} test class) is inherited. It is therefore the + * user's responsibility to ensure that only a single {@code TestInstanceFactory} + * is registered for any specific test class. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + * @since 5.3 + * @see #createTestInstance(TestInstanceFactoryContext, ExtensionContext) + * @see TestInstanceFactoryContext + * @see TestInstancePostProcessor + * @see TestInstancePreDestroyCallback + * @see ParameterResolver + */ +@FunctionalInterface +@API(status = STABLE, since = "5.7") +public interface TestInstanceFactory extends Extension { + + /** + * Callback for creating a test instance for the supplied context. + * + *

Note: the {@code ExtensionContext} supplied to a + * {@code TestInstanceFactory} will always return an empty + * {@link java.util.Optional} value from + * {@link ExtensionContext#getTestInstance() getTestInstance()} since the + * test instance cannot exist before it has been created by a + * {@code TestInstanceFactory} or the framework itself. + * + * @param factoryContext the context for the test class to be instantiated; + * never {@code null} + * @param extensionContext the current extension context; never {@code null} + * @return the test instance; never {@code null} + * @throws TestInstantiationException if an error occurs while creating the + * test instance + */ + Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) + throws TestInstantiationException; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java new file mode 100644 index 00000000..99af882d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code TestInstanceFactoryContext} encapsulates the context in which + * a {@linkplain #getTestClass test class} is to be instantiated by a + * {@link TestInstanceFactory}. + * + * @since 5.3 + * @see TestInstanceFactory + */ +@API(status = STABLE, since = "5.7") +public interface TestInstanceFactoryContext { + + /** + * Get the test class for this context. + * + * @return the test class to be instantiated; never {@code null} + */ + Class getTestClass(); + + /** + * Get the instance of the outer class, if available. + * + *

The returned {@link Optional} will be empty unless the + * current {@linkplain #getTestClass() test class} is a + * {@link org.junit.jupiter.api.Nested @Nested} test class. + * + * @return an {@code Optional} containing the outer test instance; never + * {@code null} but potentially empty + * @see org.junit.jupiter.api.Nested + */ + Optional getOuterInstance(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java new file mode 100644 index 00000000..a2ec718e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code TestInstancePostProcessor} defines the API for {@link Extension + * Extensions} that wish to post-process test instances. + * + *

Common use cases include injecting dependencies into the test + * instance, invoking custom initialization methods on the test instance, + * etc. + * + *

Extensions that implement {@code TestInstancePostProcessor} must be + * registered at the class level. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + * @since 5.0 + * @see #postProcessTestInstance(Object, ExtensionContext) + * @see TestInstancePreDestroyCallback + * @see TestInstanceFactory + * @see ParameterResolver + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface TestInstancePostProcessor extends Extension { + + /** + * Callback for post-processing the supplied test instance. + * + *

Note: the {@code ExtensionContext} supplied to a + * {@code TestInstancePostProcessor} will always return an empty + * {@link java.util.Optional} value from {@link ExtensionContext#getTestInstance() + * getTestInstance()}. A {@code TestInstancePostProcessor} should therefore + * only attempt to process the supplied {@code testInstance}. + * + * @param testInstance the instance to post-process; never {@code null} + * @param context the current extension context; never {@code null} + */ + void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java new file mode 100644 index 00000000..0bad10b8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +/** + * {@code TestInstancePreConstructCallback} defines the API for {@link Extension + * Extensions} that wish to be invoked prior to creation of test instances. + * + *

This extension is a symmetric counterpart to {@link TestInstancePreDestroyCallback}. + * The use cases for this extension may include preparing context-sensitive arguments + * that are injected into the instance's constructor. + * + *

Extensions that implement {@code TestInstancePreConstructCallback} must be + * registered at the class level if the test class is configured with + * {@link Lifecycle @TestInstance(Lifecycle.PER_CLASS)} semantics. If the test + * class is configured with + * {@link Lifecycle @TestInstance(Lifecycle.PER_METHOD)} semantics, + * {@code TestInstancePreConstructCallback} extensions may be registered at the + * class level or at the method level. In the latter case, the extension will + * only be applied to the test method for which it is registered. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on constructor + * requirements. + * + * @since 5.9 + * @see TestInstancePreDestroyCallback + * @see TestInstanceFactory + * @see ParameterResolver + */ +@FunctionalInterface +@API(status = EXPERIMENTAL, since = "5.9") +public interface TestInstancePreConstructCallback extends Extension { + + /** + * Callback invoked prior to test instances being constructed. + * + * @param factoryContext the context for the test instance about to be instantiated; + * never {@code null} + * @param context the current extension context; never {@code null} + */ + void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java new file mode 100644 index 00000000..1feab185 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +/** + * {@code TestInstancePreDestroyCallback} defines the API for {@link Extension + * Extensions} that wish to process test instances after they have been + * used in tests but before they are destroyed. + * + *

Common use cases include releasing resources that have been created for + * the test instance, invoking custom clean-up methods on the test instance, etc. + * + *

Extensions that implement {@code TestInstancePreDestroyCallback} must be + * registered at the class level if the test class is configured with + * {@link Lifecycle @TestInstance(Lifecycle.PER_CLASS)} + * semantics. If the test class is configured with + * {@link Lifecycle @TestInstance(Lifecycle.PER_METHOD)} + * semantics, {@code TestInstancePreDestroyCallback} extensions may be registered + * at the class level or at the method level. In the latter case, the + * {@code TestInstancePreDestroyCallback} extension will only be applied to the + * test method for which it is registered. + * + *

A symmetric {@link TestInstancePreConstructCallback} extension defines a callback + * hook that is invoked prior to any test class instances being constructed. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on constructor + * requirements. + * + * @since 5.6 + * @see #preDestroyTestInstance(ExtensionContext) + * @see TestInstancePostProcessor + * @see TestInstancePreConstructCallback + * @see TestInstanceFactory + * @see ParameterResolver + */ +@FunctionalInterface +@API(status = STABLE, since = "5.7") +public interface TestInstancePreDestroyCallback extends Extension { + + /** + * Callback for processing test instances before they are destroyed. + * + *

Contrary to {@link TestInstancePostProcessor#postProcessTestInstance} + * this method is only called once for each {@link ExtensionContext} even if + * there are multiple test instances about to be destroyed in case of + * {@link Nested @Nested} tests. Please use the provided + * {@link #preDestroyTestInstances(ExtensionContext, Consumer)} utility + * method to ensure that all test instances are handled. + * + * @param context the current extension context; never {@code null} + * @see ExtensionContext#getTestInstance() + * @see ExtensionContext#getRequiredTestInstance() + * @see ExtensionContext#getTestInstances() + * @see ExtensionContext#getRequiredTestInstances() + * @see #preDestroyTestInstances(ExtensionContext, Consumer) + */ + void preDestroyTestInstance(ExtensionContext context) throws Exception; + + /** + * Utility method for processing all test instances of an + * {@link ExtensionContext} that are not present in any of its parent + * contexts. + * + *

This method should be called in order to implement this interface + * correctly since it ensures that the right test instances are processed + * regardless of the used {@linkplain Lifecycle lifecycle}. The supplied + * callback is called once per test instance that is about to be destroyed + * starting with the innermost one. + * + *

This method is intended to be called from an implementation of + * {@link #preDestroyTestInstance(ExtensionContext)} like this: + * + *

{@code class MyExtension implements TestInstancePreDestroyCallback {
+	 *    @Override
+	 *    public void preDestroyTestInstance(ExtensionContext context) {
+	 *        TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> {
+	 *            // custom logic that processes testInstance
+	 *        });
+	 *    }
+	 *}}
+ * + * @param context the current extension context; never {@code null} + * @param callback the callback to be invoked for every test instance of the + * current extension context that is about to be destroyed; never + * {@code null} + * @since 5.7.1 + */ + @API(status = EXPERIMENTAL, since = "5.7.1") + static void preDestroyTestInstances(ExtensionContext context, Consumer callback) { + List destroyedInstances = new ArrayList<>(context.getRequiredTestInstances().getAllInstances()); + for (Optional current = context.getParent(); current.isPresent(); current = current.get().getParent()) { + current.get().getTestInstances().map(TestInstances::getAllInstances).ifPresent( + destroyedInstances::removeAll); + } + Collections.reverse(destroyedInstances); + destroyedInstances.forEach(callback); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java new file mode 100644 index 00000000..d2bab806 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code TestInstances} encapsulates the test instances of a test. + * + *

While top-level tests only have a single test instance, nested tests + * have one additional instance for each enclosing test class. + * + * @since 5.4 + * @see ExtensionContext#getTestInstances() + * @see ExtensionContext#getRequiredTestInstances() + */ +@API(status = STABLE, since = "5.7") +public interface TestInstances { + + /** + * Get the innermost test instance. + * + *

The innermost instance is the one closest to the test method. + * + * @return the innermost test instance; never {@code null} + */ + Object getInnermostInstance(); + + /** + * Get the enclosing test instances, excluding the innermost test instance, + * ordered from outermost to innermost. + * + * @return the enclosing test instances; never {@code null} or containing + * {@code null}, but potentially empty + */ + List getEnclosingInstances(); + + /** + * Get all test instances, ordered from outermost to innermost. + * + * @return all test instances; never {@code null}, containing {@code null}, + * or empty + */ + List getAllInstances(); + + /** + * Find the first test instance that is an instance of the supplied required + * type, checking from innermost to outermost. + * + * @param requiredType the type to search for + * @return the first test instance of the required type; never {@code null} + * but potentially empty + */ + Optional findInstance(Class requiredType); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java new file mode 100644 index 00000000..e8c5920b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * Thrown if an error is encountered during the execution of + * a {@link TestInstanceFactory}. + * + * @since 5.3 + */ +@API(status = EXPERIMENTAL, since = "5.3") +public class TestInstantiationException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public TestInstantiationException(String message) { + super(message); + } + + public TestInstantiationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java new file mode 100644 index 00000000..268b5c47 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static java.util.Collections.emptyList; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; + +import org.apiguardian.api.API; + +/** + * {@code TestTemplateInvocationContext} represents the context of a + * single invocation of a {@linkplain org.junit.jupiter.api.TestTemplate test + * template}. + * + *

Each context is provided by a {@link TestTemplateInvocationContextProvider}. + * + * @since 5.0 + * @see org.junit.jupiter.api.TestTemplate + * @see TestTemplateInvocationContextProvider + */ +@API(status = STABLE, since = "5.0") +public interface TestTemplateInvocationContext { + + /** + * Get the display name for this invocation. + * + *

The supplied {@code invocationIndex} is incremented by the framework + * with each test invocation. Thus, in the case of multiple active + * {@linkplain TestTemplateInvocationContextProvider providers}, only the + * first active provider receives indices starting with {@code 1}. + * + *

The default implementation returns the supplied {@code invocationIndex} + * wrapped in brackets — for example, {@code [1]}, {@code [42]}, etc. + * + * @param invocationIndex the index of this invocation (1-based). + * @return the display name for this invocation; never {@code null} or blank + */ + default String getDisplayName(int invocationIndex) { + return "[" + invocationIndex + "]"; + } + + /** + * Get the additional {@linkplain Extension extensions} for this invocation. + * + *

The extensions provided by this method will only be used for this + * invocation of the test template. Thus, it does not make sense to return + * an extension that acts solely on the container level (e.g. + * {@link BeforeAllCallback}). + * + *

The default implementation returns an empty list. + * + * @return the additional extensions for this invocation; never {@code null} + * or containing {@code null} elements, but potentially empty + */ + default List getAdditionalExtensions() { + return emptyList(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java new file mode 100644 index 00000000..f7e0d61b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.stream.Stream; + +import org.apiguardian.api.API; + +/** + * {@code TestTemplateInvocationContextProvider} defines the API for + * {@link Extension Extensions} that wish to provide one or multiple contexts + * for the invocation of a + * {@link org.junit.jupiter.api.TestTemplate @TestTemplate} method. + * + *

This extension point makes it possible to execute a test template in + * different contexts — for example, with different parameters, by + * preparing the test class instance differently, or multiple times without + * modifying the context. + * + *

This interface defines two methods: {@link #supportsTestTemplate} and + * {@link #provideTestTemplateInvocationContexts}. The former is called by the + * framework to determine whether this extension wants to act on a test template + * that is about to be executed. If so, the latter is called and must return a + * {@link Stream} of {@link TestTemplateInvocationContext} instances. Otherwise, + * this provider is ignored for the execution of the current test template. + * + *

A provider that has returned {@code true} from its {@link #supportsTestTemplate} + * method is called active. When multiple providers are active for a + * test template method, the {@code Streams} returned by their + * {@link #provideTestTemplateInvocationContexts} methods will be chained, and + * the test template method will be invoked using the contexts of all active + * providers. + * + *

Constructor Requirements

+ * + *

Consult the documentation in {@link Extension} for details on + * constructor requirements. + * + * @since 5.0 + * @see org.junit.jupiter.api.TestTemplate + * @see TestTemplateInvocationContext + */ +@API(status = STABLE, since = "5.0") +public interface TestTemplateInvocationContextProvider extends Extension { + + /** + * Determine if this provider supports providing invocation contexts for the + * test template method represented by the supplied {@code context}. + * + * @param context the extension context for the test template method about + * to be invoked; never {@code null} + * @return {@code true} if this provider can provide invocation contexts + * @see #provideTestTemplateInvocationContexts + * @see ExtensionContext + */ + boolean supportsTestTemplate(ExtensionContext context); + + /** + * Provide {@linkplain TestTemplateInvocationContext invocation contexts} + * for the test template method represented by the supplied {@code context}. + * + *

This method is only called by the framework if {@link #supportsTestTemplate} + * previously returned {@code true} for the same {@link ExtensionContext}; + * this method is allowed to return an empty {@code Stream} but not {@code null}. + * + *

The returned {@code Stream} will be properly closed by calling + * {@link Stream#close()}, making it safe to use a resource such as + * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. + * + * @param context the extension context for the test template method about + * to be invoked; never {@code null} + * @return a {@code Stream} of {@code TestTemplateInvocationContext} + * instances for the invocation of the test template method; never {@code null} + * @see #supportsTestTemplate + * @see ExtensionContext + */ + Stream provideTestTemplateInvocationContexts(ExtensionContext context); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java new file mode 100644 index 00000000..a720af0e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java @@ -0,0 +1,103 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code TestWatcher} defines the API for {@link Extension Extensions} that + * wish to process test results. + * + *

The methods in this API are called after a test has been skipped or + * executed. Any {@link ExtensionContext.Store.CloseableResource CloseableResource} + * objects stored in the {@link ExtensionContext.Store Store} of the supplied + * {@link ExtensionContext} will have already been closed before + * methods in this API are invoked. + * + *

Please note that this API is currently only used to report the results of + * {@link org.junit.jupiter.api.Test @Test} methods and + * {@link org.junit.jupiter.api.TestTemplate @TestTemplate} methods (e.g., + * {@code @RepeatedTest} and {@code @ParameterizedTest}). Moreover, if there is a + * failure at the class level — for example, an exception thrown by a + * {@code @BeforeAll} method — no test results will be reported. + * + *

Extensions implementing this API can be registered at any level. + * + *

Exception Handling

+ * + *

In contrast to other {@link Extension} APIs, a {@code TestWatcher} is not + * permitted to adversely influence the execution of tests. Consequently, any + * exception thrown by a method in the {@code TestWatcher} API will be logged at + * {@code WARNING} level and will not be allowed to propagate or fail test + * execution. + * + * @since 5.4 + */ +@API(status = STABLE, since = "5.7") +public interface TestWatcher extends Extension { + + /** + * Invoked after a disabled test has been skipped. + * + *

The default implementation does nothing. Concrete implementations can + * override this method as appropriate. + * + * @param context the current extension context; never {@code null} + * @param reason the reason the test is disabled; never {@code null} but + * potentially empty + */ + default void testDisabled(ExtensionContext context, Optional reason) { + /* no-op */ + } + + /** + * Invoked after a test has completed successfully. + * + *

The default implementation does nothing. Concrete implementations can + * override this method as appropriate. + * + * @param context the current extension context; never {@code null} + */ + default void testSuccessful(ExtensionContext context) { + /* no-op */ + } + + /** + * Invoked after a test has been aborted. + * + *

The default implementation does nothing. Concrete implementations can + * override this method as appropriate. + * + * @param context the current extension context; never {@code null} + * @param cause the throwable responsible for the test being aborted; may be {@code null} + */ + default void testAborted(ExtensionContext context, Throwable cause) { + /* no-op */ + } + + /** + * Invoked after a test has failed. + * + *

The default implementation does nothing. Concrete implementations can + * override this method as appropriate. + * + * @param context the current extension context; never {@code null} + * @param cause the throwable that caused test failure; may be {@code null} + */ + default void testFailed(ExtensionContext context, Throwable cause) { + /* no-op */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java new file mode 100644 index 00000000..68f070a2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java @@ -0,0 +1,5 @@ +/** + * JUnit Jupiter API for writing extensions. + */ + +package org.junit.jupiter.api.extension; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java new file mode 100644 index 00000000..0346fc0b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension.support; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@link ParameterResolver} adapter which resolves a parameter based on its exact type. + * + * @param the type of the parameter supported by this {@code ParameterResolver} + * @since 5.6 + */ +@API(status = EXPERIMENTAL, since = "5.6") +public abstract class TypeBasedParameterResolver implements ParameterResolver { + + private final Type supportedParameterType; + + public TypeBasedParameterResolver() { + this.supportedParameterType = enclosedTypeOfParameterResolver(); + } + + @Override + public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return this.supportedParameterType.equals(getParameterType(parameterContext)); + } + + @Override + public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException; + + private Type getParameterType(ParameterContext parameterContext) { + return parameterContext.getParameter().getParameterizedType(); + } + + private Type enclosedTypeOfParameterResolver() { + ParameterizedType typeBasedParameterResolverSuperclass = findTypeBasedParameterResolverSuperclass(getClass()); + Preconditions.notNull(typeBasedParameterResolverSuperclass, + () -> String.format( + "Failed to discover parameter type supported by %s; " + + "potentially caused by lacking parameterized type in class declaration.", + getClass().getName())); + return typeBasedParameterResolverSuperclass.getActualTypeArguments()[0]; + } + + private ParameterizedType findTypeBasedParameterResolverSuperclass(Class clazz) { + Class superclass = clazz.getSuperclass(); + + // Abort? + if (superclass == null || superclass == Object.class) { + return null; + } + + Type genericSuperclass = clazz.getGenericSuperclass(); + if (genericSuperclass instanceof ParameterizedType) { + Type rawType = ((ParameterizedType) genericSuperclass).getRawType(); + if (rawType == TypeBasedParameterResolver.class) { + return (ParameterizedType) genericSuperclass; + } + } + return findTypeBasedParameterResolverSuperclass(superclass); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java new file mode 100644 index 00000000..3d4156a3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java @@ -0,0 +1,5 @@ +/** + * JUnit Jupiter API support for writing extensions. + */ + +package org.junit.jupiter.api.extension.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java new file mode 100644 index 00000000..c6309581 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.function; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code Executable} is a functional interface that can be used to + * implement any generic block of code that potentially throws a + * {@link Throwable}. + * + *

The {@code Executable} interface is similar to {@link java.lang.Runnable}, + * except that an {@code Executable} can throw any kind of exception. + * + *

Rationale for throwing {@code Throwable} instead of {@code Exception}

+ * + *

Although Java applications typically throw exceptions that are instances + * of {@link java.lang.Exception}, {@link java.lang.RuntimeException}, + * {@link java.lang.Error}, or {@link java.lang.AssertionError} (in testing + * scenarios), there may be use cases where an {@code Executable} needs to + * explicitly throw a {@code Throwable}. In order to support such specialized + * use cases, {@link #execute()} is declared to throw {@code Throwable}. + * + * @since 5.0 + * @see org.junit.jupiter.api.Assertions#assertAll(Executable...) + * @see org.junit.jupiter.api.Assertions#assertAll(String, Executable...) + * @see org.junit.jupiter.api.Assertions#assertThrows(Class, Executable) + * @see org.junit.jupiter.api.Assumptions#assumingThat(java.util.function.BooleanSupplier, Executable) + * @see org.junit.jupiter.api.DynamicTest#dynamicTest(String, Executable) + * @see ThrowingConsumer + * @see ThrowingSupplier + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface Executable { + + void execute() throws Throwable; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java new file mode 100644 index 00000000..a5f74507 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.function; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code ThrowingConsumer} is a functional interface that can be used to + * implement any generic block of code that consumes an argument and + * potentially throws a {@link Throwable}. + * + *

The {@code ThrowingConsumer} interface is similar to + * {@link java.util.function.Consumer}, except that a {@code ThrowingConsumer} + * can throw any kind of exception, including checked exceptions. + * + *

Rationale for throwing {@code Throwable} instead of {@code Exception}

+ * + *

Although Java applications typically throw exceptions that are instances + * of {@link java.lang.Exception}, {@link java.lang.RuntimeException}, + * {@link java.lang.Error}, or {@link java.lang.AssertionError} (in testing + * scenarios), there may be use cases where a {@code ThrowingConsumer} needs to + * explicitly throw a {@code Throwable}. In order to support such specialized + * use cases, {@link #accept} is declared to throw {@code Throwable}. + * + * @param the type of argument consumed + * @since 5.0 + * @see java.util.function.Consumer + * @see org.junit.jupiter.api.DynamicTest#stream + * @see Executable + * @see ThrowingSupplier + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface ThrowingConsumer { + + /** + * Consume the supplied argument, potentially throwing an exception. + * + * @param t the argument to consume + */ + void accept(T t) throws Throwable; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java new file mode 100644 index 00000000..573dffe7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.function; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * {@code ThrowingSupplier} is a functional interface that can be used to + * implement any generic block of code that returns an object and + * potentially throws a {@link Throwable}. + * + *

The {@code ThrowingSupplier} interface is similar to + * {@link java.util.function.Supplier}, except that a {@code ThrowingSupplier} + * can throw any kind of exception, including checked exceptions. + * + *

Rationale for throwing {@code Throwable} instead of {@code Exception}

+ * + *

Although Java applications typically throw exceptions that are instances + * of {@link Exception}, {@link RuntimeException}, + * {@link Error}, or {@link AssertionError} (in testing + * scenarios), there may be use cases where a {@code ThrowingSupplier} needs to + * explicitly throw a {@code Throwable}. In order to support such specialized + * use cases, {@link #get} is declared to throw {@code Throwable}. + * + * @param the type of argument supplied + * @since 5.0 + * @see java.util.function.Supplier + * @see org.junit.jupiter.api.Assertions#assertTimeout(java.time.Duration, ThrowingSupplier) + * @see org.junit.jupiter.api.Assertions#assertTimeoutPreemptively(java.time.Duration, ThrowingSupplier) + * @see Executable + * @see ThrowingConsumer + */ +@FunctionalInterface +@API(status = STABLE, since = "5.0") +public interface ThrowingSupplier { + + /** + * Get a result, potentially throwing an exception. + * + * @return a result + */ + T get() throws Throwable; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java new file mode 100644 index 00000000..aaebdbdb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java @@ -0,0 +1,5 @@ +/** + * Functional interfaces used within JUnit Jupiter. + */ + +package org.junit.jupiter.api.function; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java new file mode 100644 index 00000000..59472e28 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.io; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * Enumeration of cleanup modes for {@link TempDir @TempDir}. + * + *

When a test with a temporary directory completes, it might be useful in + * some cases to be able to view the contents of the temporary directory used by + * the test. {@code CleanupMode} allows you to control how a {@code TempDir} + * is cleaned up. + * + * @since 5.9 + * @see TempDir + */ +@API(status = EXPERIMENTAL, since = "5.9") +public enum CleanupMode { + + /** + * Use the default cleanup mode. + * + * @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME + */ + DEFAULT, + + /** + * Always clean up a temporary directory after the test has completed. + */ + ALWAYS, + + /** + * Only clean up a temporary directory if the test completed successfully. + */ + ON_SUCCESS, + + /** + * Never clean up a temporary directory after the test has completed. + */ + NEVER; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java new file mode 100644 index 00000000..8d0e6dad --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java @@ -0,0 +1,139 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.io; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.nio.file.Path; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ParameterResolutionException; + +/** + * {@code @TempDir} can be used to annotate a field in a test class or a + * parameter in a lifecycle method or test method of type {@link Path} or + * {@link File} that should be resolved into a temporary directory. + * + *

Please note that {@code @TempDir} is not supported on constructor + * parameters. Please use field injection instead by annotating an instance + * field with {@code @TempDir}. + * + *

Creation

+ * + *

The temporary directory is only created if a field in a test class or a + * parameter in a lifecycle method or test method is annotated with + * {@code @TempDir}. If the field type or parameter type is neither {@link Path} + * nor {@link File}, if a field is declared as {@code final}, or if the temporary + * directory cannot be created, an {@link ExtensionConfigurationException} or a + * {@link ParameterResolutionException} will be thrown as appropriate. In + * addition, a {@code ParameterResolutionException} will be thrown for a + * constructor parameter annotated with {@code @TempDir}. + * + *

Scope

+ * + *

By default, a separate temporary directory is created for every + * declaration of the {@code @TempDir} annotation. If you want to share a + * temporary directory across all tests in a test class, you should declare the + * annotation on a {@code static} field or on a parameter of a + * {@link org.junit.jupiter.api.BeforeAll @BeforeAll} method. + * + *

Old behavior

+ * + *

You can revert to the old behavior of using a single temporary directory + * by setting the {@value #SCOPE_PROPERTY_NAME} configuration parameter to + * {@code per_context}. In that case, the scope of the temporary directory + * depends on where the first {@code @TempDir} annotation is encountered when + * executing a test class. The temporary directory will be shared by all tests + * in a class when the annotation is present on a {@code static} field or on a + * parameter of a {@link org.junit.jupiter.api.BeforeAll @BeforeAll} method. + * Otherwise — for example, when {@code @TempDir} is only used on instance + * fields or on parameters in test, + * {@link org.junit.jupiter.api.BeforeEach @BeforeEach}, or + * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods — each test + * will use its own temporary directory. + * + *

Clean Up

+ * + *

By default, when the end of the scope of a temporary directory is reached, + * — when the test method or class has finished execution — JUnit will + * attempt to clean up the temporary directory by recursively deleting all files + * and directories in the temporary directory and, finally, the temporary directory + * itself. In case deletion of a file or directory fails, an {@link IOException} + * will be thrown that will cause the test or test class to fail. + * + *

The {@link #cleanup} attribute allows you to configure the {@link CleanupMode}. + * If the cleanup mode is set to {@link CleanupMode#NEVER NEVER}, the temporary + * directory will not be cleaned up after the test completes. If the cleanup mode is + * set to {@link CleanupMode#ON_SUCCESS ON_SUCCESS}, the temporary directory will + * only be cleaned up if the test completes successfully. By default, the + * {@link CleanupMode#ALWAYS ALWAYS} clean up mode will be used, but this can be + * configured globally by setting the {@value #DEFAULT_CLEANUP_MODE_PROPERTY_NAME} + * configuration parameter. + * + * @since 5.4 + */ +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = EXPERIMENTAL, since = "5.4") +public @interface TempDir { + + /** + * Property name used to set the scope of temporary directories created via + * {@link org.junit.jupiter.api.io.TempDir @TempDir} annotation: {@value} + * + *

Supported Values

+ *
    + *
  • {@code per_context}: creates a single temporary directory for the + * entire test class or method, depending on where it's first declared + *
  • {@code per_declaration}: creates separate temporary directories for + * each declaration site of the {@code @TempDir} annotation. + *
+ * + *

If not specified, the default is {@code per_declaration}. + * + * @since 5.8 + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + @API(status = DEPRECATED, since = "5.9") + String SCOPE_PROPERTY_NAME = "junit.jupiter.tempdir.scope"; + + /** + * The name of the configuration parameter that is used to configure the + * default {@link CleanupMode}. + * + *

If this configuration parameter is not set, {@link CleanupMode#ALWAYS} + * will be used as the default. + * + * @since 5.9 + */ + @API(status = EXPERIMENTAL, since = "5.9") + String DEFAULT_CLEANUP_MODE_PROPERTY_NAME = "junit.jupiter.tempdir.cleanup.mode.default"; + + /** + * How the temporary directory gets cleaned up after the test completes. + * + * @since 5.9 + */ + @API(status = EXPERIMENTAL, since = "5.9") + CleanupMode cleanup() default CleanupMode.DEFAULT; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java new file mode 100644 index 00000000..7e893893 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java @@ -0,0 +1,5 @@ +/** + * IO-related support in JUnit Jupiter. + */ + +package org.junit.jupiter.api.io; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java new file mode 100644 index 00000000..38e623dd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java @@ -0,0 +1,5 @@ +/** + * JUnit Jupiter API for writing tests. + */ + +package org.junit.jupiter.api; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java new file mode 100644 index 00000000..86d051c6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @Execution} is used to configure the parallel execution + * {@linkplain #value mode} of a test class or test method. + * + *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} + * within class hierarchies. + * + *

Default Execution Mode

+ * + *

If this annotation is not present, {@link ExecutionMode#SAME_THREAD} is + * used unless a default execution mode is defined via one of the following + * configuration parameters: + * + *

+ *
{@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME}
+ *
Default execution mode for all classes and tests
+ *
{@value #DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME}
+ *
Default execution mode for top-level classes
+ *
+ * + *

{@value #DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME} + * overrides {@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME} for top-level + * classes + * + * @see Isolated + * @see ResourceLock + * @since 5.3 + */ +@API(status = EXPERIMENTAL, since = "5.3") +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Inherited +public @interface Execution { + + /** + * Property name used to set the default test execution mode: {@value} + * + *

This setting is only effective if parallel execution is enabled. + * + *

Supported Values

+ * + *

Supported values include names of enum constants defined in + * {@link ExecutionMode}, ignoring case. + * + *

If not specified, the default is "same_thread" which corresponds to + * {@code @Execution(ExecutionMode.SAME_THREAD)}. + * + * @since 5.4 + */ + @API(status = EXPERIMENTAL, since = "5.9") + String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = "junit.jupiter.execution.parallel.mode.default"; + + /** + * Property name used to set the default test execution mode for top-level + * classes: {@value} + * + *

This setting is only effective if parallel execution is enabled. + * + *

Supported Values

+ * + *

Supported values include names of enum constants defined in + * {@link ExecutionMode}, ignoring case. + * + *

If not specified, it will be resolved into the same value as + * {@link #DEFAULT_EXECUTION_MODE_PROPERTY_NAME}. + * + * @since 5.4 + */ + @API(status = EXPERIMENTAL, since = "5.9") + String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = "junit.jupiter.execution.parallel.mode.classes.default"; + + /** + * The required/preferred execution mode. + * + * @see ExecutionMode + */ + ExecutionMode value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java new file mode 100644 index 00000000..b0b7752a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * Supported execution modes for parallel test execution. + * + * @since 5.3 + * @see #SAME_THREAD + * @see #CONCURRENT + */ +@API(status = EXPERIMENTAL, since = "5.3") +public enum ExecutionMode { + + /** + * Force execution in same thread as the parent node. + * + * @see #CONCURRENT + */ + SAME_THREAD, + + /** + * Allow concurrent execution with any other node. + * + * @see #SAME_THREAD + */ + CONCURRENT + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java new file mode 100644 index 00000000..5dc97602 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @Isolated} is used to declare that the annotated test class should be + * executed in isolation from other test classes. + * + *

When a test class is run in isolation, no other test class is executed + * concurrently. This can be used to enable parallel test execution for the + * entire test suite while running some tests in isolation (e.g. if they modify + * some global resource). + * + * @since 5.7 + * @see ExecutionMode + * @see ResourceLock + */ +@API(status = EXPERIMENTAL, since = "5.7") +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@ResourceLock(Resources.GLOBAL) +public @interface Isolated { + + /** + * The reason this test class needs to run in isolation. + * + *

The supplied string is currently not reported in any way but can be + * used for documentation purposes. + */ + String value() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java new file mode 100644 index 00000000..30378f53 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * The access mode required by a test class or method for a given resource. + * + * @since 5.3 + * @see ResourceLock + */ +@API(status = EXPERIMENTAL, since = "5.3") +public enum ResourceAccessMode { + + /** + * Require read and write access to the resource. + */ + READ_WRITE, + + /** + * Require only read access to the resource. + */ + READ + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java new file mode 100644 index 00000000..4f7f97d4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ResourceLock} is used to declare that the annotated test class or test + * method requires access to a shared resource identified by a key. + * + *

The resource key is specified via {@link #value}. In addition, + * {@link #mode} allows you to specify whether the annotated test class or test + * method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE} or only + * {@link ResourceAccessMode#READ READ} access to the resource. In the former case, + * execution of the annotated element will occur while no other test class or + * test method that uses the shared resource is being executed. In the latter case, + * the annotated element may be executed concurrently with other test classes or + * methods that also require {@code READ} access but not at the same time as any + * other test that requires {@code READ_WRITE} access. + * + *

This annotation can be repeated to declare the use of multiple shared resources. + * + *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} + * within class hierarchies. + * + * @see Isolated + * @see Resources + * @see ResourceAccessMode + * @see ResourceLocks + * @since 5.3 + */ +@API(status = EXPERIMENTAL, since = "5.3") +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Inherited +@Repeatable(ResourceLocks.class) +public @interface ResourceLock { + + /** + * The resource key. + * + * @see Resources + */ + String value(); + + /** + * The resource access mode. + * + *

Defaults to {@link ResourceAccessMode#READ_WRITE READ_WRITE}. + * + * @see ResourceAccessMode + */ + ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java new file mode 100644 index 00000000..f15bfbc3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ResourceLocks} is a container for one or more + * {@link ResourceLock @ResourceLock} declarations. + * + *

Note, however, that use of the {@code @ResourceLocks} container is + * completely optional since {@code @ResourceLock} is a + * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. + * + *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} + * within class hierarchies. + * + * @see ResourceLock + * @since 5.3 + */ +@API(status = EXPERIMENTAL, since = "5.3") +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Inherited +public @interface ResourceLocks { + + /** + * An array of one or more {@linkplain ResourceLock @ResourceLock} declarations. + */ + ResourceLock[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java new file mode 100644 index 00000000..ccdbaefe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * Common resource names for synchronizing test execution. + * + * @since 5.3 + * @see ResourceLock + */ +@API(status = EXPERIMENTAL, since = "5.3") +public class Resources { + + /** + * Represents Java's system properties: {@value} + * + * @see System#getProperties() + * @see System#setProperties(java.util.Properties) + */ + public static final String SYSTEM_PROPERTIES = "java.lang.System.properties"; + + /** + * Represents the standard output stream of the current process: {@value} + * + * @see System#out + * @see System#setOut(java.io.PrintStream) + */ + public static final String SYSTEM_OUT = "java.lang.System.out"; + + /** + * Represents the standard error stream of the current process: {@value} + * + * @see System#err + * @see System#setErr(java.io.PrintStream) + */ + public static final String SYSTEM_ERR = "java.lang.System.err"; + + /** + * Represents the default locale for the current instance of the JVM: + * {@value} + * + * @since 5.4 + * @see java.util.Locale#setDefault(java.util.Locale) + */ + @API(status = EXPERIMENTAL, since = "5.4") + public static final String LOCALE = "java.util.Locale.default"; + + /** + * Represents the default time zone for the current instance of the JVM: + * {@value} + * + * @since 5.4 + * @see java.util.TimeZone#setDefault(java.util.TimeZone) + */ + @API(status = EXPERIMENTAL, since = "5.4") + public static final String TIME_ZONE = "java.util.TimeZone.default"; + + /** + * Represents the global resource lock: {@value} + * + * @since 5.8 + * @see Isolated + * @see org.junit.platform.engine.support.hierarchical.ExclusiveResource + */ + @API(status = EXPERIMENTAL, since = "5.8") + public static final String GLOBAL = "org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY"; + + private Resources() { + /* no-op */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java new file mode 100644 index 00000000..16b34edf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java @@ -0,0 +1,5 @@ +/** + * JUnit Jupiter API for influencing parallel test execution. + */ + +package org.junit.jupiter.api.parallel; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt new file mode 100644 index 00000000..d0f5af1e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -0,0 +1,289 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +@file:API(status = STABLE, since = "5.7") + +package org.junit.jupiter.api + +import org.apiguardian.api.API +import org.apiguardian.api.API.Status.EXPERIMENTAL +import org.apiguardian.api.API.Status.STABLE +import org.junit.jupiter.api.function.Executable +import org.junit.jupiter.api.function.ThrowingSupplier +import java.time.Duration +import java.util.function.Supplier +import java.util.stream.Stream + +/** + * @see Assertions.fail + */ +fun fail(message: String?, throwable: Throwable? = null): Nothing = + Assertions.fail(message, throwable) + +/** + * @see Assertions.fail + */ +fun fail(message: (() -> String)?): Nothing = + Assertions.fail(message) + +/** + * @see Assertions.fail + */ +fun fail(throwable: Throwable?): Nothing = + Assertions.fail(throwable) + +/** + * [Stream] of functions to be executed. + */ +private typealias ExecutableStream = Stream<() -> Unit> +private fun ExecutableStream.convert() = map { Executable(it) } + +/** + * @see Assertions.assertAll + */ +fun assertAll(executables: ExecutableStream) = + Assertions.assertAll(executables.convert()) + +/** + * @see Assertions.assertAll + */ +fun assertAll(heading: String?, executables: ExecutableStream) = + Assertions.assertAll(heading, executables.convert()) + +/** + * [Collection] of functions to be executed. + */ +private typealias ExecutableCollection = Collection<() -> Unit> +private fun ExecutableCollection.convert() = map { Executable(it) } + +/** + * @see Assertions.assertAll + */ +fun assertAll(executables: ExecutableCollection) = + Assertions.assertAll(executables.convert()) + +/** + * @see Assertions.assertAll + */ +fun assertAll(heading: String?, executables: ExecutableCollection) = + Assertions.assertAll(heading, executables.convert()) + +/** + * @see Assertions.assertAll + */ +fun assertAll(vararg executables: () -> Unit) = + assertAll(executables.toList().stream()) + +/** + * @see Assertions.assertAll + */ +fun assertAll(heading: String?, vararg executables: () -> Unit) = + assertAll(heading, executables.toList().stream()) + +/** + * Example usage: + * ```kotlin + * val exception = assertThrows { + * throw IllegalArgumentException("Talk to a duck") + * } + * assertEquals("Talk to a duck", exception.message) + * ``` + * @see Assertions.assertThrows + */ +inline fun assertThrows(executable: () -> Unit): T { + val throwable: Throwable? = try { + executable() + } catch (caught: Throwable) { + caught + } as? Throwable + + return Assertions.assertThrows(T::class.java) { + if (throwable != null) { + throw throwable + } + } +} + +/** + * Example usage: + * ```kotlin + * val exception = assertThrows("Should throw an Exception") { + * throw IllegalArgumentException("Talk to a duck") + * } + * assertEquals("Talk to a duck", exception.message) + * ``` + * @see Assertions.assertThrows + */ +inline fun assertThrows(message: String, executable: () -> Unit): T = + assertThrows({ message }, executable) + +/** + * Example usage: + * ```kotlin + * val exception = assertThrows({ "Should throw an Exception" }) { + * throw IllegalArgumentException("Talk to a duck") + * } + * assertEquals("Talk to a duck", exception.message) + * ``` + * @see Assertions.assertThrows + */ +inline fun assertThrows(noinline message: () -> String, executable: () -> Unit): T { + val throwable: Throwable? = try { + executable() + } catch (caught: Throwable) { + caught + } as? Throwable + + return Assertions.assertThrows( + T::class.java, + Executable { + if (throwable != null) { + throw throwable + } + }, + Supplier(message) + ) +} + +/** + * Example usage: + * ```kotlin + * val result = assertDoesNotThrow { + * // Code block that is expected to not throw an exception + * } + * ``` + * @see Assertions.assertDoesNotThrow + * @param R the result type of the [executable] + */ +@API(status = EXPERIMENTAL, since = "5.5") +inline fun assertDoesNotThrow(executable: () -> R): R = + Assertions.assertDoesNotThrow(evaluateAndWrap(executable)) + +/** + * Example usage: + * ```kotlin + * val result = assertDoesNotThrow("Should not throw an exception") { + * // Code block that is expected to not throw an exception + * } + * ``` + * @see Assertions.assertDoesNotThrow + * @param R the result type of the [executable] + */ +@API(status = EXPERIMENTAL, since = "5.5") +inline fun assertDoesNotThrow(message: String, executable: () -> R): R = + assertDoesNotThrow({ message }, executable) + +/** + * Example usage: + * ```kotlin + * val result = assertDoesNotThrow({ "Should not throw an exception" }) { + * // Code block that is expected to not throw an exception + * } + * ``` + * @see Assertions.assertDoesNotThrow + * @param R the result type of the [executable] + */ +@API(status = EXPERIMENTAL, since = "5.5") +inline fun assertDoesNotThrow(noinline message: () -> String, executable: () -> R): R = + Assertions.assertDoesNotThrow( + evaluateAndWrap(executable), + Supplier(message) + ) + +@PublishedApi +internal inline fun evaluateAndWrap(executable: () -> R): ThrowingSupplier = try { + val result = executable() + ThrowingSupplier { result } +} catch (throwable: Throwable) { + ThrowingSupplier { throw throwable } +} + +/** + * Example usage: + * ```kotlin + * val result = assertTimeout(Duration.seconds(1)) { + * // Code block that is being timed. + * } + * ``` + * @see Assertions.assertTimeout + * @paramR the result of the [executable]. + */ +@API(status = EXPERIMENTAL, since = "5.5") +fun assertTimeout(timeout: Duration, executable: () -> R): R = + Assertions.assertTimeout(timeout, executable) + +/** + * Example usage: + * ```kotlin + * val result = assertTimeout(Duration.seconds(1), "Should only take one second") { + * // Code block that is being timed. + * } + * ``` + * @see Assertions.assertTimeout + * @paramR the result of the [executable]. + */ +@API(status = EXPERIMENTAL, since = "5.5") +fun assertTimeout(timeout: Duration, message: String, executable: () -> R): R = + Assertions.assertTimeout(timeout, executable, message) + +/** + * Example usage: + * ```kotlin + * val result = assertTimeout(Duration.seconds(1), { "Should only take one second" }) { + * // Code block that is being timed. + * } + * ``` + * @see Assertions.assertTimeout + * @paramR the result of the [executable]. + */ +@API(status = EXPERIMENTAL, since = "5.5") +fun assertTimeout(timeout: Duration, message: () -> String, executable: () -> R): R = + Assertions.assertTimeout(timeout, executable, message) + +/** + * Example usage: + * ```kotlin + * val result = assertTimeoutPreemptively(Duration.seconds(1)) { + * // Code block that is being timed. + * } + * ``` + * @see Assertions.assertTimeoutPreemptively + * @paramR the result of the [executable]. + */ +@API(status = EXPERIMENTAL, since = "5.5") +fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R = + Assertions.assertTimeoutPreemptively(timeout, executable) + +/** + * Example usage: + * ```kotlin + * val result = assertTimeoutPreemptively(Duration.seconds(1), "Should only take one second") { + * // Code block that is being timed. + * } + * ``` + * @see Assertions.assertTimeoutPreemptively + * @paramR the result of the [executable]. + */ +@API(status = EXPERIMENTAL, since = "5.5") +fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R = + Assertions.assertTimeoutPreemptively(timeout, executable, message) + +/** + * Example usage: + * ```kotlin + * val result = assertTimeoutPreemptively(Duration.seconds(1), { "Should only take one second" }) { + * // Code block that is being timed. + * } + * ``` + * @see Assertions.assertTimeoutPreemptively + * @paramR the result of the [executable]. + */ +@API(status = EXPERIMENTAL, since = "5.5") +fun assertTimeoutPreemptively(timeout: Duration, message: () -> String, executable: () -> R): R = + Assertions.assertTimeoutPreemptively(timeout, executable, message) diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java new file mode 100644 index 00000000..b6856c78 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Defines JUnit Jupiter API for writing tests. + */ +module org.junit.jupiter.api { + requires static transitive org.apiguardian.api; + requires transitive org.junit.platform.commons; + requires transitive org.opentest4j; + + exports org.junit.jupiter.api; + exports org.junit.jupiter.api.condition; + exports org.junit.jupiter.api.extension; + exports org.junit.jupiter.api.function; + exports org.junit.jupiter.api.io; + exports org.junit.jupiter.api.parallel; + + opens org.junit.jupiter.api.condition to org.junit.platform.commons; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java new file mode 100644 index 00000000..836aabab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +public class ExtensionContextParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return ExtensionContext.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return extensionContext; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java new file mode 100644 index 00000000..ef33de69 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.fixtures; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * {@code @TrackLogRecords} registers an extension that tracks log records + * logged via JUnit's logging facade for JUL. + * + *

Log records are tracked on a per-method basis (e.g., for a single + * test method). + * + *

Test methods can gain access to the {@link LogRecordListener} managed by + * the extension by having an instance of {@code LogRecordListener} injected as + * a method parameter. + * + * @since 5.1 + * @see LoggerFactory + * @see LogRecordListener + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(TrackLogRecords.Extension.class) +public @interface TrackLogRecords { + + class Extension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + + @Override + public void beforeEach(ExtensionContext context) { + LoggerFactory.addListener(getListener(context)); + } + + @Override + public void afterEach(ExtensionContext context) { + LoggerFactory.removeListener(getListener(context)); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + boolean isTestMethodLevel = extensionContext.getTestMethod().isPresent(); + boolean isListener = parameterContext.getParameter().getType() == LogRecordListener.class; + return isTestMethodLevel && isListener; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return getListener(extensionContext); + } + + private LogRecordListener getListener(ExtensionContext context) { + return getStore(context).getOrComputeIfAbsent(LogRecordListener.class); + } + + private Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts new file mode 100644 index 00000000..e15a42bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts @@ -0,0 +1,53 @@ +import org.gradle.api.tasks.PathSensitivity.RELATIVE + +plugins { + `kotlin-library-conventions` + `testing-conventions` + groovy + `java-test-fixtures` +} + +description = "JUnit Jupiter Engine" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformEngine) + api(projects.junitJupiterApi) + + compileOnlyApi(libs.apiguardian) + + testImplementation(projects.junitPlatformLauncher) + testImplementation(projects.junitPlatformSuiteEngine) + testImplementation(projects.junitPlatformTestkit) + testImplementation(testFixtures(projects.junitPlatformCommons)) + testImplementation(kotlin("stdlib")) + testImplementation(libs.junit4) + testImplementation(libs.kotlinx.coroutines) + testImplementation(libs.groovy4) + + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + test { + inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) + systemProperty("developmentVersion", version) + } +} + +tasks { + jar { + bundle { + bnd(""" + Provide-Capability:\ + org.junit.platform.engine;\ + org.junit.platform.engine='junit-jupiter';\ + version:Version="${'$'}{version_cleanup;${project.version}}" + Require-Capability:\ + org.junit.platform.launcher;\ + filter:='(&(org.junit.platform.launcher=junit-platform-launcher)(version>=${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}}})))';\ + effective:=active + """) + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java new file mode 100644 index 00000000..f99d61d8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java @@ -0,0 +1,366 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME; +import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME; +import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME; +import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; +import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_PROPERTY_NAME; +import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.util.ClassNamePatternFilterUtils; +import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy; + +/** + * Collection of constants related to the {@link JupiterTestEngine}. + * + * @since 5.0 + * @see org.junit.platform.engine.ConfigurationParameters + */ +@API(status = STABLE, since = "5.0") +public final class Constants { + + /** + * Property name used to provide patterns for deactivating conditions: {@value} + * + *

Pattern Matching Syntax

+ * + *

If the property value consists solely of an asterisk ({@code *}), all + * conditions will be deactivated. Otherwise, the property value will be treated + * as a comma-separated list of patterns where each individual pattern will be + * matched against the fully qualified class name (FQCN) of each registered + * condition. Any dot ({@code .}) in a pattern will match against a dot ({@code .}) + * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match + * against one or more characters in a FQCN. All other characters in a pattern + * will be matched one-to-one against a FQCN. + * + *

Examples

+ * + *
    + *
  • {@code *}: deactivates all conditions. + *
  • {@code org.junit.*}: deactivates every condition under the {@code org.junit} + * base package and any of its subpackages. + *
  • {@code *.MyCondition}: deactivates every condition whose simple class name is + * exactly {@code MyCondition}. + *
  • {@code *System*}: deactivates every condition whose FQCN contains + * {@code System}. + *
  • {@code *System*, *Dev*}: deactivates every condition whose FQCN contains + * {@code System} or {@code Dev}. + *
  • {@code org.example.MyCondition, org.example.TheirCondition}: deactivates + * conditions whose FQCN is exactly {@code org.example.MyCondition} or + * {@code org.example.TheirCondition}. + *
+ * + * @see #DEACTIVATE_ALL_CONDITIONS_PATTERN + * @see org.junit.jupiter.api.extension.ExecutionCondition + */ + public static final String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = JupiterConfiguration.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; + + /** + * Wildcard pattern which signals that all conditions should be deactivated: {@value} + * + * @see #DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME + * @see org.junit.jupiter.api.extension.ExecutionCondition + */ + public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; + + /** + * Property name used to set the default display name generator class name: {@value} + * + * @see DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME + */ + public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; + + /** + * Property name used to enable auto-detection and registration of extensions via + * Java's {@link java.util.ServiceLoader} mechanism: {@value} + * + *

The default behavior is not to perform auto-detection. + */ + public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; + + /** + * Property name used to set the default test instance lifecycle mode: {@value} + * + * @see TestInstance.Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME + */ + public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; + + /** + * Property name used to enable parallel test execution: {@value} + * + *

By default, tests are executed sequentially in a single thread. + * + * @since 5.3 + */ + @API(status = EXPERIMENTAL, since = "5.3") + public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; + + /** + * Property name used to set the default test execution mode: {@value} + * + * @see Execution#DEFAULT_EXECUTION_MODE_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.4") + public static final String DEFAULT_PARALLEL_EXECUTION_MODE = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; + + /** + * Property name used to set the default test execution mode for top-level + * classes: {@value} + * + * @see Execution#DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; + + static final String PARALLEL_CONFIG_PREFIX = "junit.jupiter.execution.parallel.config."; + + /** + * Property name used to select the + * {@link ParallelExecutionConfigurationStrategy}: {@value} + * + *

Potential values: {@code dynamic} (default), {@code fixed}, or + * {@code custom}. + * + * @since 5.3 + */ + @API(status = EXPERIMENTAL, since = "5.3") + public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + CONFIG_STRATEGY_PROPERTY_NAME; + + /** + * Property name used to set the desired parallelism for the {@code fixed} + * configuration strategy: {@value} + * + *

No default value; must be a positive integer. + * + * @since 5.3 + */ + @API(status = EXPERIMENTAL, since = "5.3") + public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; + + /** + * Property name used to configure the maximum pool size of the underlying + * fork-join pool for the {@code fixed} configuration strategy: {@value} + * + *

Value must be an integer and greater than or equal to + * {@value #PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME}; defaults to + * {@code 256 + fixed.parallelism}. + * + *

Note: This property only takes affect on Java 9+. + * + * @since 5.10 + */ + @API(status = EXPERIMENTAL, since = "5.10") + public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME; + + /** + * Property name used to disable saturation of the underlying fork-join pool + * for the {@code fixed} configuration strategy: {@value} + * + *

When set to {@code false} the underlying fork-join pool will reject + * additional tasks if all available workers are busy and the maximum + * pool-size would be exceeded. + + *

Value must either {@code true} or {@code false}; defaults to {@code true}. + * + *

Note: This property only takes affect on Java 9+. + * + * @since 5.10 + */ + @API(status = EXPERIMENTAL, since = "5.10") + public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + CONFIG_FIXED_SATURATE_PROPERTY_NAME; + + /** + * Property name used to set the factor to be multiplied with the number of + * available processors/cores to determine the desired parallelism for the + * {@code dynamic} configuration strategy: {@value} + * + *

Value must be a positive decimal number; defaults to {@code 1}. + * + * @since 5.3 + */ + @API(status = EXPERIMENTAL, since = "5.3") + public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME; + + /** + * Property name used to specify the fully qualified class name of the + * {@link ParallelExecutionConfigurationStrategy} to be used for the + * {@code custom} configuration strategy: {@value} + * + * @since 5.3 + */ + @API(status = EXPERIMENTAL, since = "5.3") + public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + + CONFIG_CUSTOM_CLASS_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all testable and + * lifecycle methods: {@value}. + * + * @see Timeout#DEFAULT_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all testable methods: {@value}. + * + * @see Timeout#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all + * {@link Test @Test} methods: {@value}. + * + * @see Timeout#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all + * {@link TestTemplate @TestTemplate} methods: {@value}. + * + * @see Timeout#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all + * {@link TestFactory @TestFactory} methods: {@value}. + * + * @see Timeout#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all lifecycle methods: {@value}. + * + * @see Timeout#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all + * {@link BeforeAll @BeforeAll} methods: {@value}. + * + * @see Timeout#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all + * {@link BeforeEach @BeforeEach} methods: {@value}. + * + * @see Timeout#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all + * {@link AfterEach @AfterEach} methods: {@value}. + * + * @see Timeout#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property name used to set the default timeout for all + * {@link AfterAll @AfterAll} methods: {@value}. + * + * @see Timeout#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.5") + public static final String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; + + /** + * Property used to determine if timeouts are applied to tests: {@value}. + * + * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "5.6") + public static final String TIMEOUT_MODE_PROPERTY_NAME = Timeout.TIMEOUT_MODE_PROPERTY_NAME; + + /** + * Property name used to set the default method orderer class name: {@value} + * + * @see MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME + */ + @API(status = STABLE, since = "5.9") + public static final String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; + + /** + * Property name used to set the default class orderer class name: {@value} + * + * @see ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME + */ + @API(status = STABLE, since = "5.9") + public static final String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; + + /** + * Property name used to set the scope of temporary directories created via + * {@link TempDir @TempDir} annotation: {@value} + * + * @see TempDir#SCOPE_PROPERTY_NAME + */ + @Deprecated + @API(status = DEPRECATED, since = "5.8") + @SuppressWarnings("deprecation") + public static final String TEMP_DIR_SCOPE_PROPERTY_NAME = TempDir.SCOPE_PROPERTY_NAME; + + /** + * Property name used to set the default timeout thread mode. + * + * @since 5.9 + * @see Timeout + * @see Timeout.ThreadMode + */ + @API(status = EXPERIMENTAL, since = "5.9") + public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; + + private Constants() { + /* no-op */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java new file mode 100644 index 00000000..dbd799b7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java @@ -0,0 +1,102 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.jupiter.engine.config.CachingJupiterConfiguration; +import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.config.PrefixedConfigurationParameters; +import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService; +import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine; +import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * The JUnit Jupiter {@link org.junit.platform.engine.TestEngine TestEngine}. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public final class JupiterTestEngine extends HierarchicalTestEngine { + + @Override + public String getId() { + return JupiterEngineDescriptor.ENGINE_ID; + } + + /** + * Returns {@code org.junit.jupiter} as the group ID. + */ + @Override + public Optional getGroupId() { + return Optional.of("org.junit.jupiter"); + } + + /** + * Returns {@code junit-jupiter-engine} as the artifact ID. + */ + @Override + public Optional getArtifactId() { + return Optional.of("junit-jupiter-engine"); + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + JupiterConfiguration configuration = new CachingJupiterConfiguration( + new DefaultJupiterConfiguration(discoveryRequest.getConfigurationParameters())); + JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration); + new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); + return engineDescriptor; + } + + @Override + protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) { + JupiterConfiguration configuration = getJupiterConfiguration(request); + if (configuration.isParallelExecutionEnabled()) { + return new ForkJoinPoolHierarchicalTestExecutorService(new PrefixedConfigurationParameters( + request.getConfigurationParameters(), Constants.PARALLEL_CONFIG_PREFIX)); + } + return super.createExecutorService(request); + } + + @Override + protected JupiterEngineExecutionContext createExecutionContext(ExecutionRequest request) { + return new JupiterEngineExecutionContext(request.getEngineExecutionListener(), + getJupiterConfiguration(request)); + } + + /** + * @since 5.4 + */ + @Override + protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) { + return JupiterThrowableCollectorFactory::createThrowableCollector; + } + + private JupiterConfiguration getJupiterConfiguration(ExecutionRequest request) { + JupiterEngineDescriptor engineDescriptor = (JupiterEngineDescriptor) request.getRootTestDescriptor(); + return engineDescriptor.getConfiguration(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java new file mode 100644 index 00000000..ebf54734 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.api.io.TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.parallel.ExecutionMode; + +/** + * Caching implementation of the {@link JupiterConfiguration} API. + * + * @since 5.4 + */ +@API(status = INTERNAL, since = "5.4") +public class CachingJupiterConfiguration implements JupiterConfiguration { + + private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + private final JupiterConfiguration delegate; + + public CachingJupiterConfiguration(JupiterConfiguration delegate) { + this.delegate = delegate; + } + + @Override + public Optional getRawConfigurationParameter(String key) { + return delegate.getRawConfigurationParameter(key); + } + + @Override + public Optional getRawConfigurationParameter(String key, Function transformer) { + return delegate.getRawConfigurationParameter(key, transformer); + } + + @Override + public boolean isParallelExecutionEnabled() { + return (boolean) cache.computeIfAbsent(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, + key -> delegate.isParallelExecutionEnabled()); + } + + @Override + public boolean isExtensionAutoDetectionEnabled() { + return (boolean) cache.computeIfAbsent(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME, + key -> delegate.isExtensionAutoDetectionEnabled()); + } + + @Override + public ExecutionMode getDefaultExecutionMode() { + return (ExecutionMode) cache.computeIfAbsent(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, + key -> delegate.getDefaultExecutionMode()); + } + + @Override + public ExecutionMode getDefaultClassesExecutionMode() { + return (ExecutionMode) cache.computeIfAbsent(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, + key -> delegate.getDefaultClassesExecutionMode()); + } + + @Override + public TestInstance.Lifecycle getDefaultTestInstanceLifecycle() { + return (TestInstance.Lifecycle) cache.computeIfAbsent(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, + key -> delegate.getDefaultTestInstanceLifecycle()); + } + + @SuppressWarnings("unchecked") + @Override + public Predicate getExecutionConditionFilter() { + return (Predicate) cache.computeIfAbsent(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, + key -> delegate.getExecutionConditionFilter()); + } + + @Override + public DisplayNameGenerator getDefaultDisplayNameGenerator() { + return (DisplayNameGenerator) cache.computeIfAbsent(DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME, + key -> delegate.getDefaultDisplayNameGenerator()); + } + + @SuppressWarnings("unchecked") + @Override + public Optional getDefaultTestMethodOrderer() { + return (Optional) cache.computeIfAbsent(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, + key -> delegate.getDefaultTestMethodOrderer()); + } + + @SuppressWarnings("unchecked") + @Override + public Optional getDefaultTestClassOrderer() { + return (Optional) cache.computeIfAbsent(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, + key -> delegate.getDefaultTestClassOrderer()); + } + + @Override + public CleanupMode getDefaultTempDirCleanupMode() { + return (CleanupMode) cache.computeIfAbsent(DEFAULT_CLEANUP_MODE_PROPERTY_NAME, + key -> delegate.getDefaultTempDirCleanupMode()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java new file mode 100644 index 00000000..ae8a4944 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; +import static org.junit.jupiter.api.io.TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.platform.commons.util.ClassNamePatternFilterUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * Default implementation of the {@link JupiterConfiguration} API. + * + * @since 5.4 + */ +@API(status = INTERNAL, since = "5.4") +public class DefaultJupiterConfiguration implements JupiterConfiguration { + + private static final EnumConfigurationParameterConverter executionModeConverter = // + new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode"); + + private static final EnumConfigurationParameterConverter lifecycleConverter = // + new EnumConfigurationParameterConverter<>(Lifecycle.class, "test instance lifecycle mode"); + + private static final InstantiatingConfigurationParameterConverter displayNameGeneratorConverter = // + new InstantiatingConfigurationParameterConverter<>(DisplayNameGenerator.class, "display name generator"); + + private static final InstantiatingConfigurationParameterConverter methodOrdererConverter = // + new InstantiatingConfigurationParameterConverter<>(MethodOrderer.class, "method orderer"); + + private static final InstantiatingConfigurationParameterConverter classOrdererConverter = // + new InstantiatingConfigurationParameterConverter<>(ClassOrderer.class, "class orderer"); + + private static final EnumConfigurationParameterConverter cleanupModeConverter = // + new EnumConfigurationParameterConverter<>(CleanupMode.class, "cleanup mode"); + + private final ConfigurationParameters configurationParameters; + + public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) { + this.configurationParameters = Preconditions.notNull(configurationParameters, + "ConfigurationParameters must not be null"); + } + + @Override + public Optional getRawConfigurationParameter(String key) { + return configurationParameters.get(key); + } + + @Override + public Optional getRawConfigurationParameter(String key, Function transformer) { + return configurationParameters.get(key, transformer); + } + + @Override + public boolean isParallelExecutionEnabled() { + return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false); + } + + @Override + public boolean isExtensionAutoDetectionEnabled() { + return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false); + } + + @Override + public ExecutionMode getDefaultExecutionMode() { + return executionModeConverter.get(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, + ExecutionMode.SAME_THREAD); + } + + @Override + public ExecutionMode getDefaultClassesExecutionMode() { + return executionModeConverter.get(configurationParameters, DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, + getDefaultExecutionMode()); + } + + @Override + public Lifecycle getDefaultTestInstanceLifecycle() { + return lifecycleConverter.get(configurationParameters, DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, + Lifecycle.PER_METHOD); + } + + @Override + public Predicate getExecutionConditionFilter() { + return ClassNamePatternFilterUtils.excludeMatchingClasses( + configurationParameters.get(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME).orElse(null)); + } + + @Override + public DisplayNameGenerator getDefaultDisplayNameGenerator() { + return displayNameGeneratorConverter.get(configurationParameters, DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME) // + .orElseGet(() -> DisplayNameGenerator.getDisplayNameGenerator(DisplayNameGenerator.Standard.class)); + } + + @Override + public Optional getDefaultTestMethodOrderer() { + return methodOrdererConverter.get(configurationParameters, DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME); + } + + @Override + public Optional getDefaultTestClassOrderer() { + return classOrdererConverter.get(configurationParameters, DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME); + } + + @Override + public CleanupMode getDefaultTempDirCleanupMode() { + return cleanupModeConverter.get(configurationParameters, DEFAULT_CLEANUP_MODE_PROPERTY_NAME, ALWAYS); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java new file mode 100644 index 00000000..75fd3f99 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Locale; +import java.util.Optional; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * @since 5.4 + */ +@API(status = INTERNAL, since = "5.8") +public class EnumConfigurationParameterConverter> { + + private static final Logger logger = LoggerFactory.getLogger(EnumConfigurationParameterConverter.class); + + private final Class enumType; + private final String enumDisplayName; + + public EnumConfigurationParameterConverter(Class enumType, String enumDisplayName) { + this.enumType = enumType; + this.enumDisplayName = enumDisplayName; + } + + E get(ConfigurationParameters configParams, String key, E defaultValue) { + Preconditions.notNull(configParams, "ConfigurationParameters must not be null"); + + return get(key, configParams::get, defaultValue); + } + + public E get(String key, Function> lookup, E defaultValue) { + + Optional value = lookup.apply(key); + + if (value.isPresent()) { + String constantName = null; + try { + constantName = value.get().trim().toUpperCase(Locale.ROOT); + E result = Enum.valueOf(enumType, constantName); + logger.config(() -> String.format("Using %s '%s' set via the '%s' configuration parameter.", + enumDisplayName, result, key)); + return result; + } + catch (Exception ex) { + // local copy necessary for use in lambda expression + String constant = constantName; + logger.warn(() -> String.format( + "Invalid %s '%s' set via the '%s' configuration parameter. " + + "Falling back to the %s default value.", + enumDisplayName, constant, key, defaultValue.name())); + } + } + + return defaultValue; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java new file mode 100644 index 00000000..7f77d471 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import java.util.Optional; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * @since 5.5 + */ +class InstantiatingConfigurationParameterConverter { + + private static final Logger logger = LoggerFactory.getLogger(InstantiatingConfigurationParameterConverter.class); + + private final Class clazz; + private final String name; + + public InstantiatingConfigurationParameterConverter(Class clazz, String name) { + this.clazz = clazz; + this.name = name; + } + + Optional get(ConfigurationParameters configurationParameters, String key) { + // @formatter:off + return configurationParameters.get(key) + .map(String::trim) + .filter(className -> !className.isEmpty()) + .flatMap(className -> newInstance(className, key)); + // @formatter:on + } + + private Optional newInstance(String className, String key) { + // @formatter:off + return ReflectionUtils.tryToLoadClass(className) + .andThenTry(ReflectionUtils::newInstance) + .andThenTry(this.clazz::cast) + .ifSuccess(generator -> logSuccessMessage(className, key)) + .ifFailure(cause -> logFailureMessage(className, key, cause)) + .toOptional(); + // @formatter:on + } + + private void logFailureMessage(String className, String key, Exception cause) { + logger.warn(cause, + () -> String.format("Failed to load default %s class '%s' set via the '%s' configuration parameter." + + " Falling back to default behavior.", + this.name, className, key)); + } + + private void logSuccessMessage(String className, String key) { + logger.config(() -> String.format("Using default %s '%s' set via the '%s' configuration parameter.", this.name, + className, key)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java new file mode 100644 index 00000000..d0027c15 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +/** + * @since 5.4 + */ +@API(status = INTERNAL, since = "5.4") +public interface JupiterConfiguration { + + String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate"; + String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; + String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; + String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; + String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; + String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; + String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; + String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; + String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; + + Optional getRawConfigurationParameter(String key); + + Optional getRawConfigurationParameter(String key, Function transformer); + + boolean isParallelExecutionEnabled(); + + boolean isExtensionAutoDetectionEnabled(); + + ExecutionMode getDefaultExecutionMode(); + + ExecutionMode getDefaultClassesExecutionMode(); + + TestInstance.Lifecycle getDefaultTestInstanceLifecycle(); + + Predicate getExecutionConditionFilter(); + + DisplayNameGenerator getDefaultDisplayNameGenerator(); + + Optional getDefaultTestMethodOrderer(); + + Optional getDefaultTestClassOrderer(); + + CleanupMode getDefaultTempDirCleanupMode(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java new file mode 100644 index 00000000..ede68088 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java @@ -0,0 +1,5 @@ +/** + * Configuration specific to the JUnit Jupiter test engine. + */ + +package org.junit.jupiter.engine.config; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java new file mode 100644 index 00000000..90b6a932 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java @@ -0,0 +1,158 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toCollection; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.ExtensionValuesStore; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.hierarchical.Node; + +/** + * @since 5.0 + */ +abstract class AbstractExtensionContext implements ExtensionContext, AutoCloseable { + + private final ExtensionContext parent; + private final EngineExecutionListener engineExecutionListener; + private final T testDescriptor; + private final Set tags; + private final JupiterConfiguration configuration; + private final ExtensionValuesStore valuesStore; + private final ExecutableInvoker executableInvoker; + + AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, + JupiterConfiguration configuration, ExecutableInvoker executableInvoker) { + this.executableInvoker = executableInvoker; + + Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); + Preconditions.notNull(configuration, "JupiterConfiguration must not be null"); + + this.parent = parent; + this.engineExecutionListener = engineExecutionListener; + this.testDescriptor = testDescriptor; + this.configuration = configuration; + this.valuesStore = createStore(parent); + + // @formatter:off + this.tags = testDescriptor.getTags().stream() + .map(TestTag::getName) + .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); + // @formatter:on + } + + private ExtensionValuesStore createStore(ExtensionContext parent) { + ExtensionValuesStore parentStore = null; + if (parent != null) { + parentStore = ((AbstractExtensionContext) parent).valuesStore; + } + return new ExtensionValuesStore(parentStore); + } + + @Override + public void close() { + this.valuesStore.closeAllStoredCloseableValues(); + } + + @Override + public String getUniqueId() { + return getTestDescriptor().getUniqueId().toString(); + } + + @Override + public String getDisplayName() { + return getTestDescriptor().getDisplayName(); + } + + @Override + public void publishReportEntry(Map values) { + this.engineExecutionListener.reportingEntryPublished(this.testDescriptor, ReportEntry.from(values)); + } + + @Override + public Optional getParent() { + return Optional.ofNullable(this.parent); + } + + @Override + public ExtensionContext getRoot() { + if (this.parent != null) { + return this.parent.getRoot(); + } + return this; + } + + protected T getTestDescriptor() { + return this.testDescriptor; + } + + @Override + public Store getStore(Namespace namespace) { + Preconditions.notNull(namespace, "Namespace must not be null"); + return new NamespaceAwareStore(this.valuesStore, namespace); + } + + @Override + public Set getTags() { + // return modifiable copy + return new LinkedHashSet<>(this.tags); + } + + @Override + public Optional getConfigurationParameter(String key) { + return this.configuration.getRawConfigurationParameter(key); + } + + @Override + public Optional getConfigurationParameter(String key, Function transformer) { + return this.configuration.getRawConfigurationParameter(key, transformer); + } + + @Override + public ExecutionMode getExecutionMode() { + return toJupiterExecutionMode(getPlatformExecutionMode()); + } + + @Override + public ExecutableInvoker getExecutableInvoker() { + return executableInvoker; + } + + protected abstract Node.ExecutionMode getPlatformExecutionMode(); + + private ExecutionMode toJupiterExecutionMode(Node.ExecutionMode mode) { + switch (mode) { + case CONCURRENT: + return ExecutionMode.CONCURRENT; + case SAME_THREAD: + return ExecutionMode.SAME_THREAD; + } + throw new JUnitException("Unknown ExecutionMode: " + mode); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java new file mode 100644 index 00000000..d1fed951 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java @@ -0,0 +1,524 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.joining; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromConstructorParameters; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromExecutableParameters; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromFields; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods; +import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; +import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstanceFactory; +import org.junit.jupiter.api.extension.TestInstanceFactoryContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.api.extension.TestInstancePreConstructCallback; +import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.extension.TestInstantiationException; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; +import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; +import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; +import org.junit.jupiter.engine.execution.DefaultTestInstances; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall.VoidMethodInterceptorCall; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.execution.TestInstancesProvider; +import org.junit.jupiter.engine.extension.ExtensionRegistrar; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * {@link TestDescriptor} for tests based on Java classes. + * + * @since 5.5 + */ +@API(status = INTERNAL, since = "5.5") +public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor { + + private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); + + private final Class testClass; + protected final Set tags; + protected final Lifecycle lifecycle; + + private ExecutionMode defaultChildExecutionMode; + private TestInstanceFactory testInstanceFactory; + private List beforeAllMethods; + private List afterAllMethods; + + ClassBasedTestDescriptor(UniqueId uniqueId, Class testClass, Supplier displayNameSupplier, + JupiterConfiguration configuration) { + super(uniqueId, testClass, displayNameSupplier, ClassSource.from(testClass), configuration); + + this.testClass = testClass; + this.tags = getTags(testClass); + this.lifecycle = getTestInstanceLifecycle(testClass, configuration); + this.defaultChildExecutionMode = (this.lifecycle == Lifecycle.PER_CLASS ? ExecutionMode.SAME_THREAD : null); + } + + // --- TestDescriptor ------------------------------------------------------ + + public final Class getTestClass() { + return this.testClass; + } + + public abstract List> getEnclosingTestClasses(); + + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public String getLegacyReportingName() { + return this.testClass.getName(); + } + + // --- Node ---------------------------------------------------------------- + + @Override + protected Optional getExplicitExecutionMode() { + return getExecutionModeFromAnnotation(getTestClass()); + } + + @Override + protected Optional getDefaultChildExecutionMode() { + return Optional.ofNullable(this.defaultChildExecutionMode); + } + + public void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode) { + this.defaultChildExecutionMode = defaultChildExecutionMode; + } + + @Override + public Set getExclusiveResources() { + return getExclusiveResourcesFromAnnotation(getTestClass()); + } + + @Override + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { + MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( + context.getExtensionRegistry(), this.testClass); + + // Register extensions from static fields here, at the class level but + // after extensions registered via @ExtendWith. + registerExtensionsFromFields(registry, this.testClass, null); + + // Resolve the TestInstanceFactory at the class level in order to fail + // the entire class in case of configuration errors (e.g., more than + // one factory registered per class). + this.testInstanceFactory = resolveTestInstanceFactory(registry); + + if (this.testInstanceFactory == null) { + registerExtensionsFromConstructorParameters(registry, this.testClass); + } + + this.beforeAllMethods = findBeforeAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD); + this.afterAllMethods = findAfterAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD); + + this.beforeAllMethods.forEach(method -> registerExtensionsFromExecutableParameters(registry, method)); + // Since registerBeforeEachMethodAdapters() and registerAfterEachMethodAdapters() also + // invoke registerExtensionsFromExecutableParameters(), we invoke those methods before + // invoking registerExtensionsFromExecutableParameters() for @AfterAll methods, + // thereby ensuring proper registration order for extensions registered via @ExtendWith + // on parameters in lifecycle methods. + registerBeforeEachMethodAdapters(registry); + registerAfterEachMethodAdapters(registry); + this.afterAllMethods.forEach(method -> registerExtensionsFromExecutableParameters(registry, method)); + + ThrowableCollector throwableCollector = createThrowableCollector(); + ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); + ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(), + context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), throwableCollector, + executableInvoker); + + // @formatter:off + return context.extend() + .withTestInstancesProvider(testInstancesProvider(context, extensionContext)) + .withExtensionRegistry(registry) + .withExtensionContext(extensionContext) + .withThrowableCollector(throwableCollector) + .build(); + // @formatter:on + } + + @Override + public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) { + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + if (isPerClassLifecycle(context)) { + // Eagerly load test instance for BeforeAllCallbacks, if necessary, + // and store the instance in the ExtensionContext. + ClassExtensionContext extensionContext = (ClassExtensionContext) context.getExtensionContext(); + throwableCollector.execute(() -> { + TestInstances testInstances = context.getTestInstancesProvider().getTestInstances( + context.getExtensionRegistry(), throwableCollector); + extensionContext.setTestInstances(testInstances); + }); + } + + if (throwableCollector.isEmpty()) { + context.beforeAllCallbacksExecuted(true); + invokeBeforeAllCallbacks(context); + + if (throwableCollector.isEmpty()) { + context.beforeAllMethodsExecuted(true); + invokeBeforeAllMethods(context); + } + } + + throwableCollector.assertEmpty(); + + return context; + } + + @Override + public void after(JupiterEngineExecutionContext context) { + + ThrowableCollector throwableCollector = context.getThrowableCollector(); + Throwable previousThrowable = throwableCollector.getThrowable(); + + if (context.beforeAllMethodsExecuted()) { + invokeAfterAllMethods(context); + } + + if (context.beforeAllCallbacksExecuted()) { + invokeAfterAllCallbacks(context); + } + + if (isPerClassLifecycle(context) && context.getExtensionContext().getTestInstance().isPresent()) { + invokeTestInstancePreDestroyCallbacks(context); + } + + // If the previous Throwable was not null when this method was called, + // that means an exception was already thrown either before or during + // the execution of this Node. If an exception was already thrown, any + // later exceptions were added as suppressed exceptions to that original + // exception unless a more severe exception occurred in the meantime. + if (previousThrowable != throwableCollector.getThrowable()) { + throwableCollector.assertEmpty(); + } + } + + private TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registry) { + List factories = registry.getExtensions(TestInstanceFactory.class); + + if (factories.size() == 1) { + return factories.get(0); + } + + if (factories.size() > 1) { + String factoryNames = factories.stream()// + .map(factory -> factory.getClass().getName())// + .collect(joining(", ")); + + String errorMessage = String.format( + "The following TestInstanceFactory extensions were registered for test class [%s], but only one is permitted: %s", + testClass.getName(), factoryNames); + + throw new ExtensionConfigurationException(errorMessage); + } + + return null; + } + + private TestInstancesProvider testInstancesProvider(JupiterEngineExecutionContext parentExecutionContext, + ClassExtensionContext extensionContext) { + + return (registry, registrar, throwableCollector) -> extensionContext.getTestInstances().orElseGet( + () -> instantiateAndPostProcessTestInstance(parentExecutionContext, extensionContext, registry, registrar, + throwableCollector)); + } + + private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecutionContext parentExecutionContext, + ExtensionContext extensionContext, ExtensionRegistry registry, ExtensionRegistrar registrar, + ThrowableCollector throwableCollector) { + + TestInstances instances = instantiateTestClass(parentExecutionContext, registry, registrar, extensionContext, + throwableCollector); + throwableCollector.execute(() -> { + invokeTestInstancePostProcessors(instances.getInnermostInstance(), registry, extensionContext); + // In addition, we register extensions from instance fields here since the + // best time to do that is immediately following test class instantiation + // and post processing. + registerExtensionsFromFields(registrar, this.testClass, instances.getInnermostInstance()); + }); + return instances; + } + + protected abstract TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, + ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, + ThrowableCollector throwableCollector); + + protected TestInstances instantiateTestClass(Optional outerInstances, ExtensionRegistry registry, + ExtensionContext extensionContext) { + + Optional outerInstance = outerInstances.map(TestInstances::getInnermostInstance); + invokeTestInstancePreConstructCallbacks(new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), + registry, extensionContext); + Object instance = this.testInstanceFactory != null // + ? invokeTestInstanceFactory(outerInstance, extensionContext) // + : invokeTestClassConstructor(outerInstance, registry, extensionContext); + return outerInstances.map(instances -> DefaultTestInstances.of(instances, instance)).orElse( + DefaultTestInstances.of(instance)); + } + + private Object invokeTestInstanceFactory(Optional outerInstance, ExtensionContext extensionContext) { + Object instance; + + try { + instance = this.testInstanceFactory.createTestInstance( + new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), extensionContext); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + + if (throwable instanceof TestInstantiationException) { + throw (TestInstantiationException) throwable; + } + + String message = String.format("TestInstanceFactory [%s] failed to instantiate test class [%s]", + this.testInstanceFactory.getClass().getName(), this.testClass.getName()); + if (StringUtils.isNotBlank(throwable.getMessage())) { + message += ": " + throwable.getMessage(); + } + throw new TestInstantiationException(message, throwable); + } + + if (!this.testClass.isInstance(instance)) { + String testClassName = this.testClass.getName(); + Class instanceClass = (instance == null ? null : instance.getClass()); + String instanceClassName = (instanceClass == null ? "null" : instanceClass.getName()); + + // If the test instance was loaded via a different ClassLoader, append + // the identity hash codes to the type names to help users disambiguate + // between otherwise identical "fully qualified class names". + if (testClassName.equals(instanceClassName)) { + testClassName += "@" + Integer.toHexString(System.identityHashCode(this.testClass)); + instanceClassName += "@" + Integer.toHexString(System.identityHashCode(instanceClass)); + } + String message = String.format( + "TestInstanceFactory [%s] failed to return an instance of [%s] and instead returned an instance of [%s].", + this.testInstanceFactory.getClass().getName(), testClassName, instanceClassName); + + throw new TestInstantiationException(message); + } + + return instance; + } + + private Object invokeTestClassConstructor(Optional outerInstance, ExtensionRegistry registry, + ExtensionContext extensionContext) { + + Constructor constructor = ReflectionUtils.getDeclaredConstructor(this.testClass); + return executableInvoker.invoke(constructor, outerInstance, extensionContext, registry, + InvocationInterceptor::interceptTestClassConstructor); + } + + private void invokeTestInstancePreConstructCallbacks(TestInstanceFactoryContext factoryContext, + ExtensionRegistry registry, ExtensionContext context) { + registry.stream(TestInstancePreConstructCallback.class).forEach( + extension -> executeAndMaskThrowable(() -> extension.preConstructTestInstance(factoryContext, context))); + } + + private void invokeTestInstancePostProcessors(Object instance, ExtensionRegistry registry, + ExtensionContext context) { + + registry.stream(TestInstancePostProcessor.class).forEach( + extension -> executeAndMaskThrowable(() -> extension.postProcessTestInstance(instance, context))); + } + + private void executeAndMaskThrowable(Executable executable) { + try { + executable.execute(); + } + catch (Throwable throwable) { + ExceptionUtils.throwAsUncheckedException(throwable); + } + } + + private void invokeBeforeAllCallbacks(JupiterEngineExecutionContext context) { + ExtensionRegistry registry = context.getExtensionRegistry(); + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + for (BeforeAllCallback callback : registry.getExtensions(BeforeAllCallback.class)) { + throwableCollector.execute(() -> callback.beforeAll(extensionContext)); + if (throwableCollector.isNotEmpty()) { + break; + } + } + } + + private void invokeBeforeAllMethods(JupiterEngineExecutionContext context) { + ExtensionRegistry registry = context.getExtensionRegistry(); + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + Object testInstance = extensionContext.getTestInstance().orElse(null); + + for (Method method : this.beforeAllMethods) { + throwableCollector.execute(() -> { + try { + executableInvoker.invoke(method, testInstance, extensionContext, registry, + ReflectiveInterceptorCall.ofVoidMethod(InvocationInterceptor::interceptBeforeAllMethod)); + } + catch (Throwable throwable) { + invokeBeforeAllMethodExecutionExceptionHandlers(registry, extensionContext, throwable); + } + }); + if (throwableCollector.isNotEmpty()) { + break; + } + } + } + + private void invokeBeforeAllMethodExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, + Throwable throwable) { + + invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, + (handler, handledThrowable) -> handler.handleBeforeAllMethodExecutionException(context, handledThrowable)); + } + + private void invokeAfterAllMethods(JupiterEngineExecutionContext context) { + ExtensionRegistry registry = context.getExtensionRegistry(); + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + Object testInstance = extensionContext.getTestInstance().orElse(null); + + this.afterAllMethods.forEach(method -> throwableCollector.execute(() -> { + try { + executableInvoker.invoke(method, testInstance, extensionContext, registry, + ReflectiveInterceptorCall.ofVoidMethod(InvocationInterceptor::interceptAfterAllMethod)); + } + catch (Throwable throwable) { + invokeAfterAllMethodExecutionExceptionHandlers(registry, extensionContext, throwable); + } + })); + } + + private void invokeAfterAllMethodExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, + Throwable throwable) { + + invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, + (handler, handledThrowable) -> handler.handleAfterAllMethodExecutionException(context, handledThrowable)); + } + + private void invokeAfterAllCallbacks(JupiterEngineExecutionContext context) { + ExtensionRegistry registry = context.getExtensionRegistry(); + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + registry.getReversedExtensions(AfterAllCallback.class)// + .forEach(extension -> throwableCollector.execute(() -> extension.afterAll(extensionContext))); + } + + private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext context) { + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + context.getExtensionRegistry().getReversedExtensions(TestInstancePreDestroyCallback.class).forEach( + extension -> throwableCollector.execute(() -> extension.preDestroyTestInstance(extensionContext))); + } + + private boolean isPerClassLifecycle(JupiterEngineExecutionContext context) { + return context.getExtensionContext().getTestInstanceLifecycle().orElse( + Lifecycle.PER_METHOD) == Lifecycle.PER_CLASS; + } + + private void registerBeforeEachMethodAdapters(ExtensionRegistrar registrar) { + List beforeEachMethods = findBeforeEachMethods(this.testClass); + registerMethodsAsExtensions(beforeEachMethods, registrar, this::synthesizeBeforeEachMethodAdapter); + } + + private void registerAfterEachMethodAdapters(ExtensionRegistrar registrar) { + // Make a local copy since findAfterEachMethods() returns an immutable list. + List afterEachMethods = new ArrayList<>(findAfterEachMethods(this.testClass)); + + // Since the bottom-up ordering of afterEachMethods will later be reversed when the + // synthesized AfterEachMethodAdapters are executed within TestMethodTestDescriptor, + // we have to reverse the afterEachMethods list to put them in top-down order before + // we register them as synthesized extensions. + Collections.reverse(afterEachMethods); + + registerMethodsAsExtensions(afterEachMethods, registrar, this::synthesizeAfterEachMethodAdapter); + } + + private void registerMethodsAsExtensions(List methods, ExtensionRegistrar registrar, + Function extensionSynthesizer) { + + methods.forEach(method -> { + registerExtensionsFromExecutableParameters(registrar, method); + registrar.registerSyntheticExtension(extensionSynthesizer.apply(method), method); + }); + } + + private BeforeEachMethodAdapter synthesizeBeforeEachMethodAdapter(Method method) { + return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry, + InvocationInterceptor::interceptBeforeEachMethod); + } + + private AfterEachMethodAdapter synthesizeAfterEachMethodAdapter(Method method) { + return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry, + InvocationInterceptor::interceptAfterEachMethod); + } + + private void invokeMethodInExtensionContext(Method method, ExtensionContext context, ExtensionRegistry registry, + VoidMethodInterceptorCall interceptorCall) { + TestInstances testInstances = context.getRequiredTestInstances(); + Object target = testInstances.findInstance(this.testClass).orElseThrow( + () -> new JUnitException("Failed to find instance for method: " + method.toGenericString())); + + executableInvoker.invoke(method, target, context, registry, + ReflectiveInterceptorCall.ofVoidMethod(interceptorCall)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java new file mode 100644 index 00000000..57d40a86 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.support.hierarchical.Node; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * @since 5.0 + */ +final class ClassExtensionContext extends AbstractExtensionContext { + + private final Lifecycle lifecycle; + + private final ThrowableCollector throwableCollector; + + private TestInstances testInstances; + + /** + * Create a new {@code ClassExtensionContext} with {@link Lifecycle#PER_METHOD}. + * + * @see #ClassExtensionContext(ExtensionContext, EngineExecutionListener, ClassBasedTestDescriptor, + * Lifecycle, JupiterConfiguration, ThrowableCollector, ExecutableInvoker) + */ + ClassExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, + ClassBasedTestDescriptor testDescriptor, JupiterConfiguration configuration, + ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) { + + this(parent, engineExecutionListener, testDescriptor, Lifecycle.PER_METHOD, configuration, throwableCollector, + executableInvoker); + } + + ClassExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, + ClassBasedTestDescriptor testDescriptor, Lifecycle lifecycle, JupiterConfiguration configuration, + ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) { + + super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); + + this.lifecycle = lifecycle; + this.throwableCollector = throwableCollector; + } + + @Override + public Optional getElement() { + return Optional.of(getTestDescriptor().getTestClass()); + } + + @Override + public Optional> getTestClass() { + return Optional.of(getTestDescriptor().getTestClass()); + } + + @Override + public Optional getTestInstanceLifecycle() { + return Optional.of(this.lifecycle); + } + + @Override + public Optional getTestInstance() { + return getTestInstances().map(TestInstances::getInnermostInstance); + } + + @Override + public Optional getTestInstances() { + return Optional.ofNullable(testInstances); + } + + void setTestInstances(TestInstances testInstances) { + this.testInstances = testInstances; + } + + @Override + public Optional getTestMethod() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.ofNullable(this.throwableCollector.getThrowable()); + } + + @Override + protected Node.ExecutionMode getPlatformExecutionMode() { + return getTestDescriptor().getExecutionMode(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java new file mode 100644 index 00000000..22961298 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.Collections.emptyList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistrar; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * {@link TestDescriptor} for tests based on Java classes. + * + *

Default Display Names

+ * + *

The default display name for a top-level or nested static test class is + * the fully qualified name of the class with the package name and leading dot + * (".") removed. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class ClassTestDescriptor extends ClassBasedTestDescriptor { + + public static final String SEGMENT_TYPE = "class"; + + public ClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { + super(uniqueId, testClass, createDisplayNameSupplierForClass(testClass, configuration), configuration); + } + + // --- TestDescriptor ------------------------------------------------------ + + @Override + public Set getTags() { + // return modifiable copy + return new LinkedHashSet<>(this.tags); + } + + @Override + public List> getEnclosingTestClasses() { + return emptyList(); + } + + // --- Node ---------------------------------------------------------------- + + @Override + public ExecutionMode getExecutionMode() { + return getExplicitExecutionMode().orElseGet( + () -> JupiterTestDescriptor.toExecutionMode(configuration.getDefaultClassesExecutionMode())); + } + + @Override + protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, + ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, + ThrowableCollector throwableCollector) { + return instantiateTestClass(Optional.empty(), registry, extensionContext); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java new file mode 100644 index 00000000..f6e272bc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import org.junit.jupiter.api.extension.DynamicTestInvocationContext; +import org.junit.jupiter.api.function.Executable; + +/** + * Default implementation of the {@link DynamicTestInvocationContext} API. + * + * @since 5.8 + */ +class DefaultDynamicTestInvocationContext implements DynamicTestInvocationContext { + + private final Executable executable; + + DefaultDynamicTestInvocationContext(Executable executable) { + this.executable = executable; + } + + @Override + public Executable getExecutable() { + return this.executable; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java new file mode 100644 index 00000000..affdf5b5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.TestInstanceFactoryContext; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Default implementation of the {@link TestInstanceFactoryContext} API. + * + * @since 5.3 + */ +class DefaultTestInstanceFactoryContext implements TestInstanceFactoryContext { + + private final Class testClass; + private final Optional outerInstance; + + DefaultTestInstanceFactoryContext(Class testClass, Optional outerInstance) { + this.testClass = testClass; + this.outerInstance = outerInstance; + } + + @Override + public Class getTestClass() { + return this.testClass; + } + + @Override + public Optional getOuterInstance() { + return this.outerInstance; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("testClass", this.testClass) + .append("outerInstance", this.outerInstance) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java new file mode 100644 index 00000000..7e2b3130 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java @@ -0,0 +1,130 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.function.Supplier; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.DisplayNameGenerator.Simple; +import org.junit.jupiter.api.DisplayNameGenerator.Standard; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; + +/** + * Collection of utilities for working with display names. + * + * @since 5.4 + * @see DisplayName + * @see DisplayNameGenerator + * @see DisplayNameGeneration + */ +final class DisplayNameUtils { + + private static final Logger logger = LoggerFactory.getLogger(DisplayNameUtils.class); + + /** + * Pre-defined standard display name generator instance. + */ + private static final DisplayNameGenerator standardGenerator = DisplayNameGenerator.getDisplayNameGenerator( + Standard.class); + + /** + * Pre-defined simple display name generator instance. + */ + private static final DisplayNameGenerator simpleGenerator = DisplayNameGenerator.getDisplayNameGenerator( + Simple.class); + + /** + * Pre-defined display name generator instance replacing underscores. + */ + private static final DisplayNameGenerator replaceUnderscoresGenerator = DisplayNameGenerator.getDisplayNameGenerator( + ReplaceUnderscores.class); + + /** + * Pre-defined display name generator instance producing indicative sentences. + */ + private static final DisplayNameGenerator indicativeSentencesGenerator = DisplayNameGenerator.getDisplayNameGenerator( + IndicativeSentences.class); + + static String determineDisplayName(AnnotatedElement element, Supplier displayNameSupplier) { + Preconditions.notNull(element, "Annotated element must not be null"); + Optional displayNameAnnotation = findAnnotation(element, DisplayName.class); + if (displayNameAnnotation.isPresent()) { + String displayName = displayNameAnnotation.get().value().trim(); + + // TODO [#242] Replace logging with precondition check once we have a proper mechanism for + // handling validation exceptions during the TestEngine discovery phase. + if (StringUtils.isBlank(displayName)) { + logger.warn(() -> String.format( + "Configuration error: @DisplayName on [%s] must be declared with a non-empty value.", element)); + } + else { + return displayName; + } + } + // else let a 'DisplayNameGenerator' generate a display name + return displayNameSupplier.get(); + } + + static String determineDisplayNameForMethod(Class testClass, Method testMethod, + JupiterConfiguration configuration) { + DisplayNameGenerator generator = getDisplayNameGenerator(testClass, configuration); + return determineDisplayName(testMethod, () -> generator.generateDisplayNameForMethod(testClass, testMethod)); + } + + static Supplier createDisplayNameSupplierForClass(Class testClass, JupiterConfiguration configuration) { + return () -> getDisplayNameGenerator(testClass, configuration).generateDisplayNameForClass(testClass); + } + + static Supplier createDisplayNameSupplierForNestedClass(Class testClass, + JupiterConfiguration configuration) { + return () -> getDisplayNameGenerator(testClass, configuration).generateDisplayNameForNestedClass(testClass); + } + + private static DisplayNameGenerator getDisplayNameGenerator(Class testClass, + JupiterConfiguration configuration) { + Preconditions.notNull(testClass, "Test class must not be null"); + + return AnnotationUtils.findAnnotation(testClass, DisplayNameGeneration.class, true) // + .map(DisplayNameGeneration::value) // + .map(displayNameGeneratorClass -> { + if (displayNameGeneratorClass == Standard.class) { + return standardGenerator; + } + if (displayNameGeneratorClass == Simple.class) { + return simpleGenerator; + } + if (displayNameGeneratorClass == ReplaceUnderscores.class) { + return replaceUnderscoresGenerator; + } + if (displayNameGeneratorClass == IndicativeSentences.class) { + return indicativeSentencesGenerator; + } + return ReflectionUtils.newInstance(displayNameGeneratorClass); + }) // + .orElseGet(configuration::getDefaultDisplayNameGenerator); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java new file mode 100644 index 00000000..a72981df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.createDynamicDescriptor; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; + +/** + * {@link TestDescriptor} for a {@link DynamicContainer}. + * + * @since 5.0 + */ +class DynamicContainerTestDescriptor extends DynamicNodeTestDescriptor { + + private final DynamicContainer dynamicContainer; + private final TestSource testSource; + private final DynamicDescendantFilter dynamicDescendantFilter; + + DynamicContainerTestDescriptor(UniqueId uniqueId, int index, DynamicContainer dynamicContainer, + TestSource testSource, DynamicDescendantFilter dynamicDescendantFilter, + JupiterConfiguration configuration) { + + super(uniqueId, index, dynamicContainer, testSource, configuration); + this.dynamicContainer = dynamicContainer; + this.testSource = testSource; + this.dynamicDescendantFilter = dynamicDescendantFilter; + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) throws Exception { + + AtomicInteger index = new AtomicInteger(1); + try (Stream children = dynamicContainer.getChildren()) { + // @formatter:off + children.map(child -> { + Preconditions.notNull(child, "individual dynamic node must not be null"); + return toDynamicDescriptor(index.getAndIncrement(), child); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .forEachOrdered(dynamicTestExecutor::execute); + // @formatter:on + } + return context; + } + + private Optional toDynamicDescriptor(int index, DynamicNode childNode) { + return createDynamicDescriptor(this, childNode, index, testSource, dynamicDescendantFilter, configuration); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java new file mode 100644 index 00000000..48e45e0b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiPredicate; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; + +/** + * Filter for dynamic descendants of {@link TestDescriptor TestDescriptors} that + * implement {@link Filterable}. + * + * @since 5.1 + * @see Filterable + */ +@API(status = INTERNAL, since = "5.1") +public class DynamicDescendantFilter implements BiPredicate { + + private final Set allowedUniqueIds = new HashSet<>(); + private final Set allowedIndices = new HashSet<>(); + private Mode mode = Mode.EXPLICIT; + + public void allowUniqueIdPrefix(UniqueId uniqueId) { + if (this.mode == Mode.EXPLICIT) { + this.allowedUniqueIds.add(uniqueId); + } + } + + public void allowIndex(Set indices) { + if (this.mode == Mode.EXPLICIT) { + this.allowedIndices.addAll(indices); + } + } + + public void allowAll() { + this.mode = Mode.ALLOW_ALL; + this.allowedUniqueIds.clear(); + this.allowedIndices.clear(); + } + + @Override + public boolean test(UniqueId uniqueId, Integer index) { + return isEverythingAllowed() // + || isUniqueIdAllowed(uniqueId) // + || allowedIndices.contains(index); + } + + private boolean isEverythingAllowed() { + return allowedUniqueIds.isEmpty() && allowedIndices.isEmpty(); + } + + private boolean isUniqueIdAllowed(UniqueId uniqueId) { + return allowedUniqueIds.stream().anyMatch(allowedUniqueId -> isPrefixOrViceVersa(uniqueId, allowedUniqueId)); + } + + private boolean isPrefixOrViceVersa(UniqueId currentUniqueId, UniqueId allowedUniqueId) { + return allowedUniqueId.hasPrefix(currentUniqueId) || currentUniqueId.hasPrefix(allowedUniqueId); + } + + public DynamicDescendantFilter withoutIndexFiltering() { + return new WithoutIndexFiltering(); + } + + private enum Mode { + EXPLICIT, ALLOW_ALL + } + + private class WithoutIndexFiltering extends DynamicDescendantFilter { + + @Override + public boolean test(UniqueId uniqueId, Integer index) { + return isEverythingAllowed() || isUniqueIdAllowed(uniqueId); + } + + @Override + public DynamicDescendantFilter withoutIndexFiltering() { + return this; + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java new file mode 100644 index 00000000..92ec7b24 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.support.hierarchical.Node; + +class DynamicExtensionContext extends AbstractExtensionContext { + + DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, + DynamicNodeTestDescriptor testDescriptor, JupiterConfiguration configuration, + ExecutableInvoker executableInvoker) { + super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); + } + + @Override + public Optional getElement() { + return Optional.empty(); + } + + @Override + public Optional> getTestClass() { + return Optional.empty(); + } + + @Override + public Optional getTestInstanceLifecycle() { + return Optional.empty(); + } + + @Override + public Optional getTestInstance() { + return Optional.empty(); + } + + @Override + public Optional getTestInstances() { + return Optional.empty(); + } + + @Override + public Optional getTestMethod() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + protected Node.ExecutionMode getPlatformExecutionMode() { + return getTestDescriptor().getExecutionMode(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java new file mode 100644 index 00000000..bf5bde6c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; + +/** + * Base {@link TestDescriptor} for a {@link DynamicNode}. + * + * @since 5.0.3 + */ +abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor { + + private final int index; + + DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, TestSource testSource, + JupiterConfiguration configuration) { + super(uniqueId, dynamicNode.getDisplayName(), testSource, configuration); + this.index = index; + } + + @Override + public String getLegacyReportingName() { + // @formatter:off + return getParent() + .map(TestDescriptor::getLegacyReportingName) + .orElseGet(this::getDisplayName) + + "[" + index + "]"; + // @formatter:on + } + + @Override + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { + DynamicExtensionContext extensionContext = new DynamicExtensionContext(context.getExtensionContext(), + context.getExecutionListener(), this, context.getConfiguration(), new DefaultExecutableInvoker(context)); + // @formatter:off + return context.extend() + .withExtensionContext(extensionContext) + .build(); + // @formatter:on + } + + @Override + public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { + return SkipResult.doNotSkip(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java new file mode 100644 index 00000000..3fc75d00 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.extension.DynamicTestInvocationContext; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.InvocationInterceptorChain; +import org.junit.jupiter.engine.execution.InvocationInterceptorChain.InterceptorCall; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; + +/** + * {@link TestDescriptor} for a {@link DynamicTest}. + * + * @since 5.0 + */ +class DynamicTestTestDescriptor extends DynamicNodeTestDescriptor { + + private static final InvocationInterceptorChain interceptorChain = new InvocationInterceptorChain(); + + private DynamicTest dynamicTest; + + DynamicTestTestDescriptor(UniqueId uniqueId, int index, DynamicTest dynamicTest, TestSource source, + JupiterConfiguration configuration) { + super(uniqueId, index, dynamicTest, source, configuration); + this.dynamicTest = dynamicTest; + } + + @Override + public Type getType() { + return Type.TEST; + } + + @Override + public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) { + InvocationInterceptor.Invocation invocation = () -> { + dynamicTest.getExecutable().execute(); + return null; + }; + DynamicTestInvocationContext dynamicTestInvocationContext = new DefaultDynamicTestInvocationContext( + dynamicTest.getExecutable()); + ExtensionContext extensionContext = context.getExtensionContext(); + ExtensionRegistry extensionRegistry = context.getExtensionRegistry(); + interceptorChain.invoke(invocation, extensionRegistry, InterceptorCall.ofVoid( + (interceptor, wrappedInvocation) -> interceptor.interceptDynamicTest(wrappedInvocation, + dynamicTestInvocationContext, extensionContext))); + return context; + } + + /** + * Avoid an {@link OutOfMemoryError} by releasing the reference to this + * descriptor's {@link DynamicTest} which holds a reference to the user-supplied + * {@link Executable} which may potentially consume large amounts of memory + * on the heap. + * + * @since 5.5 + * @see Issue 1865 + */ + @Override + public void after(JupiterEngineExecutionContext context) throws Exception { + super.after(context); + this.dynamicTest = null; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java new file mode 100644 index 00000000..b444f632 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java @@ -0,0 +1,192 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.toList; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.util.ReflectionUtils.findFields; +import static org.junit.platform.commons.util.ReflectionUtils.getDeclaredConstructor; +import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.engine.extension.ExtensionRegistrar; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Collection of utilities for working with extensions and the extension registry. + * + * @since 5.1 + * @see ExtensionRegistrar + * @see MutableExtensionRegistry + * @see ExtendWith + * @see RegisterExtension + */ +final class ExtensionUtils { + + private ExtensionUtils() { + /* no-op */ + } + + /** + * Populate a new {@link MutableExtensionRegistry} from extension types declared via + * {@link ExtendWith @ExtendWith} on the supplied {@link AnnotatedElement}. + * + * @param parentRegistry the parent extension registry to set in the newly + * created registry; never {@code null} + * @param annotatedElement the annotated element on which to search for + * declarations of {@code @ExtendWith}; never {@code null} + * + * @return the new extension registry; never {@code null} + * @since 5.0 + */ + static MutableExtensionRegistry populateNewExtensionRegistryFromExtendWithAnnotation( + MutableExtensionRegistry parentRegistry, AnnotatedElement annotatedElement) { + + Preconditions.notNull(parentRegistry, "Parent ExtensionRegistry must not be null"); + Preconditions.notNull(annotatedElement, "AnnotatedElement must not be null"); + + return MutableExtensionRegistry.createRegistryFrom(parentRegistry, streamExtensionTypes(annotatedElement)); + } + + /** + * Register extensions using the supplied registrar from fields in the supplied + * class that are annotated with {@link ExtendWith @ExtendWith} or + * {@link RegisterExtension @RegisterExtension}. + * + *

The extensions will be sorted according to {@link Order @Order} semantics + * prior to registration. + * + * @param registrar the registrar with which to register the extensions; never {@code null} + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param instance the instance of the supplied class; may be {@code null} + * when searching for {@code static} fields in the class + */ + static void registerExtensionsFromFields(ExtensionRegistrar registrar, Class clazz, Object instance) { + Preconditions.notNull(registrar, "ExtensionRegistrar must not be null"); + Preconditions.notNull(clazz, "Class must not be null"); + + Predicate predicate = (instance == null ? ReflectionUtils::isStatic : ReflectionUtils::isNotStatic); + + findFields(clazz, predicate, TOP_DOWN).stream()// + .sorted(orderComparator)// + .forEach(field -> { + List> extensionTypes = streamExtensionTypes(field).collect(toList()); + boolean isExtendWithPresent = !extensionTypes.isEmpty(); + boolean isRegisterExtensionPresent = isAnnotated(field, RegisterExtension.class); + if (isExtendWithPresent) { + extensionTypes.forEach(registrar::registerExtension); + } + if (isRegisterExtensionPresent) { + tryToReadFieldValue(field, instance).ifSuccess(value -> { + Preconditions.condition(value instanceof Extension, () -> String.format( + "Failed to register extension via @RegisterExtension field [%s]: field value's type [%s] must implement an [%s] API.", + field, (value != null ? value.getClass().getName() : null), Extension.class.getName())); + + if (isExtendWithPresent) { + Class valueType = value.getClass(); + extensionTypes.forEach(extensionType -> { + Preconditions.condition(!extensionType.equals(valueType), + () -> String.format("Failed to register extension via field [%s]. " + + "The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, " + + "but only one registration of a given extension type is permitted.", + field, valueType.getName())); + }); + } + + registrar.registerExtension((Extension) value, field); + }); + } + }); + } + + /** + * Register extensions using the supplied registrar from parameters in the + * declared constructor of the supplied class that are annotated with + * {@link ExtendWith @ExtendWith}. + * + * @param registrar the registrar with which to register the extensions; never {@code null} + * @param clazz the class in which to find the declared constructor; never {@code null} + * @since 5.8 + */ + static void registerExtensionsFromConstructorParameters(ExtensionRegistrar registrar, Class clazz) { + registerExtensionsFromExecutableParameters(registrar, getDeclaredConstructor(clazz)); + } + + /** + * Register extensions using the supplied registrar from parameters in the + * supplied {@link Executable} (i.e., a {@link java.lang.reflect.Constructor} + * or {@link java.lang.reflect.Method}) that are annotated with + * {@link ExtendWith @ExtendWith}. + * + * @param registrar the registrar with which to register the extensions; never {@code null} + * @param executable the constructor or method whose parameters should be searched; never {@code null} + * @since 5.8 + */ + static void registerExtensionsFromExecutableParameters(ExtensionRegistrar registrar, Executable executable) { + Preconditions.notNull(registrar, "ExtensionRegistrar must not be null"); + Preconditions.notNull(executable, "Executable must not be null"); + + AtomicInteger index = new AtomicInteger(); + + // @formatter:off + Arrays.stream(executable.getParameters()) + .map(parameter -> findRepeatableAnnotations(parameter, index.getAndIncrement(), ExtendWith.class)) + .flatMap(ExtensionUtils::streamExtensionTypes) + .forEach(registrar::registerExtension); + // @formatter:on + } + + /** + * @since 5.8 + */ + private static Stream> streamExtensionTypes(AnnotatedElement annotatedElement) { + return streamExtensionTypes(findRepeatableAnnotations(annotatedElement, ExtendWith.class)); + } + + /** + * @since 5.8 + */ + private static Stream> streamExtensionTypes(List extendWithAnnotations) { + return extendWithAnnotations.stream().map(ExtendWith::value).flatMap(Arrays::stream); + } + + /** + * @since 5.4 + */ + private static final Comparator orderComparator = // + Comparator.comparingInt(ExtensionUtils::getOrder); + + /** + * @since 5.4 + */ + private static int getOrder(Field field) { + return findAnnotation(field, Order.class).map(Order::value).orElse(Order.DEFAULT); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java new file mode 100644 index 00000000..a9ca7ad1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; + +/** + * {@code Filterable} is implemented by + * {@link org.junit.platform.engine.TestDescriptor TestDescriptors} that may + * register dynamic tests during execution and support selective test execution. + * + * @since 5.1 + * @see DynamicDescendantFilter + */ +@API(status = INTERNAL, since = "5.1") +public interface Filterable { + + DynamicDescendantFilter getDynamicDescendantFilter(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java new file mode 100644 index 00000000..3a87753c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.toExecutionMode; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.engine.support.hierarchical.Node; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class JupiterEngineDescriptor extends EngineDescriptor implements Node { + + public static final String ENGINE_ID = "junit-jupiter"; + private final JupiterConfiguration configuration; + + public JupiterEngineDescriptor(UniqueId uniqueId, JupiterConfiguration configuration) { + super(uniqueId, "JUnit Jupiter"); + this.configuration = configuration; + } + + public JupiterConfiguration getConfiguration() { + return configuration; + } + + @Override + public ExecutionMode getExecutionMode() { + return toExecutionMode(configuration.getDefaultExecutionMode()); + } + + @Override + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { + MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( + context.getConfiguration()); + EngineExecutionListener executionListener = context.getExecutionListener(); + ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); + ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionListener, this, + context.getConfiguration(), executableInvoker); + + // @formatter:off + return context.extend() + .withExtensionRegistry(extensionRegistry) + .withExtensionContext(extensionContext) + .build(); + // @formatter:on + } + + @Override + public void cleanUp(JupiterEngineExecutionContext context) throws Exception { + context.close(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java new file mode 100644 index 00000000..26eb83f7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.support.hierarchical.Node; + +/** + * @since 5.0 + */ +final class JupiterEngineExtensionContext extends AbstractExtensionContext { + + JupiterEngineExtensionContext(EngineExecutionListener engineExecutionListener, + JupiterEngineDescriptor testDescriptor, JupiterConfiguration configuration, + ExecutableInvoker executableInvoker) { + + super(null, engineExecutionListener, testDescriptor, configuration, executableInvoker); + } + + @Override + public Optional getElement() { + return Optional.empty(); + } + + @Override + public Optional> getTestClass() { + return Optional.empty(); + } + + @Override + public Optional getTestInstanceLifecycle() { + return Optional.empty(); + } + + @Override + public Optional getTestInstance() { + return Optional.empty(); + } + + @Override + public Optional getTestInstances() { + return Optional.empty(); + } + + @Override + public Optional getTestMethod() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + protected Node.ExecutionMode getPlatformExecutionMode() { + return getTestDescriptor().getExecutionMode(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java new file mode 100644 index 00000000..5492e8a6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java @@ -0,0 +1,238 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toSet; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayName; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; + +import java.lang.reflect.AnnotatedElement; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.ConditionEvaluator; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; +import org.junit.platform.engine.support.hierarchical.Node; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public abstract class JupiterTestDescriptor extends AbstractTestDescriptor + implements Node { + + private static final Logger logger = LoggerFactory.getLogger(JupiterTestDescriptor.class); + + private static final ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); + + final JupiterConfiguration configuration; + + JupiterTestDescriptor(UniqueId uniqueId, AnnotatedElement element, Supplier displayNameSupplier, + TestSource source, JupiterConfiguration configuration) { + this(uniqueId, determineDisplayName(element, displayNameSupplier), source, configuration); + } + + JupiterTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, + JupiterConfiguration configuration) { + super(uniqueId, displayName, source); + this.configuration = configuration; + } + + // --- TestDescriptor ------------------------------------------------------ + + static Set getTags(AnnotatedElement element) { + // @formatter:off + return findRepeatableAnnotations(element, Tag.class).stream() + .map(Tag::value) + .filter(tag -> { + boolean isValid = TestTag.isValid(tag); + if (!isValid) { + // TODO [#242] Replace logging with precondition check once we have a proper mechanism for + // handling validation exceptions during the TestEngine discovery phase. + // + // As an alternative to a precondition check here, we could catch any + // PreconditionViolationException thrown by TestTag::create. + logger.warn(() -> String.format( + "Configuration error: invalid tag syntax in @Tag(\"%s\") declaration on [%s]. Tag will be ignored.", + tag, element)); + } + return isValid; + }) + .map(TestTag::create) + .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); + // @formatter:on + } + + /** + * Invoke exception handlers for the supplied {@code Throwable} one-by-one + * until none are left or the throwable to handle has been swallowed. + */ + void invokeExecutionExceptionHandlers(Class handlerType, ExtensionRegistry registry, + Throwable throwable, ExceptionHandlerInvoker handlerInvoker) { + + invokeExecutionExceptionHandlers(registry.getReversedExtensions(handlerType), throwable, handlerInvoker); + } + + private void invokeExecutionExceptionHandlers(List exceptionHandlers, Throwable throwable, + ExceptionHandlerInvoker handlerInvoker) { + + // No handlers left? + if (exceptionHandlers.isEmpty()) { + ExceptionUtils.throwAsUncheckedException(throwable); + } + + try { + // Invoke next available handler + handlerInvoker.invoke(exceptionHandlers.remove(0), throwable); + } + catch (Throwable handledThrowable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(handledThrowable); + invokeExecutionExceptionHandlers(exceptionHandlers, handledThrowable, handlerInvoker); + } + } + + // --- Node ---------------------------------------------------------------- + + @Override + public ExecutionMode getExecutionMode() { + Optional executionMode = getExplicitExecutionMode(); + if (executionMode.isPresent()) { + return executionMode.get(); + } + Optional parent = getParent(); + while (parent.isPresent() && parent.get() instanceof JupiterTestDescriptor) { + JupiterTestDescriptor jupiterParent = (JupiterTestDescriptor) parent.get(); + executionMode = jupiterParent.getExplicitExecutionMode(); + if (executionMode.isPresent()) { + return executionMode.get(); + } + executionMode = jupiterParent.getDefaultChildExecutionMode(); + if (executionMode.isPresent()) { + return executionMode.get(); + } + parent = jupiterParent.getParent(); + } + return toExecutionMode(configuration.getDefaultExecutionMode()); + } + + Optional getExplicitExecutionMode() { + return Optional.empty(); + } + + Optional getDefaultChildExecutionMode() { + return Optional.empty(); + } + + Optional getExecutionModeFromAnnotation(AnnotatedElement element) { + // @formatter:off + return findAnnotation(element, Execution.class) + .map(Execution::value) + .map(JupiterTestDescriptor::toExecutionMode); + // @formatter:on + } + + public static ExecutionMode toExecutionMode(org.junit.jupiter.api.parallel.ExecutionMode mode) { + switch (mode) { + case CONCURRENT: + return ExecutionMode.CONCURRENT; + case SAME_THREAD: + return ExecutionMode.SAME_THREAD; + } + throw new JUnitException("Unknown ExecutionMode: " + mode); + } + + Set getExclusiveResourcesFromAnnotation(AnnotatedElement element) { + // @formatter:off + return findRepeatableAnnotations(element, ResourceLock.class).stream() + .map(resource -> new ExclusiveResource(resource.value(), toLockMode(resource.mode()))) + .collect(toSet()); + // @formatter:on + } + + private static LockMode toLockMode(ResourceAccessMode mode) { + switch (mode) { + case READ: + return LockMode.READ; + case READ_WRITE: + return LockMode.READ_WRITE; + } + throw new JUnitException("Unknown ResourceAccessMode: " + mode); + } + + @Override + public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) throws Exception { + context.getThrowableCollector().assertEmpty(); + ConditionEvaluationResult evaluationResult = conditionEvaluator.evaluate(context.getExtensionRegistry(), + context.getConfiguration(), context.getExtensionContext()); + return toSkipResult(evaluationResult); + } + + private SkipResult toSkipResult(ConditionEvaluationResult evaluationResult) { + if (evaluationResult.isDisabled()) { + return SkipResult.skip(evaluationResult.getReason().orElse("")); + } + return SkipResult.doNotSkip(); + } + + /** + * Must be overridden and return a new context so cleanUp() does not accidentally close the parent context. + */ + @Override + public abstract JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception; + + @Override + public void cleanUp(JupiterEngineExecutionContext context) throws Exception { + context.close(); + } + + /** + * @since 5.5 + */ + @FunctionalInterface + interface ExceptionHandlerInvoker { + + /** + * Invoke the supplied {@code exceptionHandler} with the supplied {@code throwable}. + */ + void invoke(E exceptionHandler, Throwable throwable) throws Throwable; + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java new file mode 100644 index 00000000..9adb38ff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedMethods; +import static org.junit.platform.commons.util.ReflectionUtils.returnsVoid; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; + +/** + * Collection of utilities for working with test lifecycle methods. + * + * @since 5.0 + */ +final class LifecycleMethodUtils { + + private LifecycleMethodUtils() { + /* no-op */ + } + + static List findBeforeAllMethods(Class testClass, boolean requireStatic) { + return findMethodsAndAssertStaticAndNonPrivate(testClass, requireStatic, BeforeAll.class, + HierarchyTraversalMode.TOP_DOWN); + } + + static List findAfterAllMethods(Class testClass, boolean requireStatic) { + return findMethodsAndAssertStaticAndNonPrivate(testClass, requireStatic, AfterAll.class, + HierarchyTraversalMode.BOTTOM_UP); + } + + static List findBeforeEachMethods(Class testClass) { + return findMethodsAndAssertNonStaticAndNonPrivate(testClass, BeforeEach.class, HierarchyTraversalMode.TOP_DOWN); + } + + static List findAfterEachMethods(Class testClass) { + return findMethodsAndAssertNonStaticAndNonPrivate(testClass, AfterEach.class, HierarchyTraversalMode.BOTTOM_UP); + } + + private static List findMethodsAndAssertStaticAndNonPrivate(Class testClass, boolean requireStatic, + Class annotationType, HierarchyTraversalMode traversalMode) { + + List methods = findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode); + if (requireStatic) { + methods.forEach(method -> assertStatic(annotationType, method)); + } + return methods; + } + + private static List findMethodsAndAssertNonStaticAndNonPrivate(Class testClass, + Class annotationType, HierarchyTraversalMode traversalMode) { + + List methods = findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode); + methods.forEach(method -> assertNonStatic(annotationType, method)); + return methods; + } + + private static List findMethodsAndCheckVoidReturnType(Class testClass, + Class annotationType, HierarchyTraversalMode traversalMode) { + + List methods = findAnnotatedMethods(testClass, annotationType, traversalMode); + methods.forEach(method -> assertVoid(annotationType, method)); + return methods; + } + + private static void assertStatic(Class annotationType, Method method) { + if (ReflectionUtils.isNotStatic(method)) { + throw new JUnitException(String.format( + "@%s method '%s' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).", + annotationType.getSimpleName(), method.toGenericString())); + } + } + + private static void assertNonStatic(Class annotationType, Method method) { + if (ReflectionUtils.isStatic(method)) { + throw new JUnitException(String.format("@%s method '%s' must not be static.", + annotationType.getSimpleName(), method.toGenericString())); + } + } + + private static void assertVoid(Class annotationType, Method method) { + if (!returnsVoid(method)) { + throw new JUnitException(String.format("@%s method '%s' must not return a value.", + annotationType.getSimpleName(), method.toGenericString())); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java new file mode 100644 index 00000000..bb0d3907 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -0,0 +1,149 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod; + +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; + +/** + * Base class for {@link TestDescriptor TestDescriptors} based on Java methods. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor { + + private static final Logger logger = LoggerFactory.getLogger(MethodBasedTestDescriptor.class); + + private final Class testClass; + private final Method testMethod; + + /** + * Set of method-level tags; does not contain tags from parent. + */ + private final Set tags; + + MethodBasedTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, + JupiterConfiguration configuration) { + this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod, + configuration); + } + + MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, + JupiterConfiguration configuration) { + super(uniqueId, displayName, MethodSource.from(testClass, testMethod), configuration); + + this.testClass = Preconditions.notNull(testClass, "Class must not be null"); + this.testMethod = testMethod; + this.tags = getTags(testMethod); + } + + @Override + public final Set getTags() { + // return modifiable copy + Set allTags = new LinkedHashSet<>(this.tags); + getParent().ifPresent(parentDescriptor -> allTags.addAll(parentDescriptor.getTags())); + return allTags; + } + + @Override + public Set getExclusiveResources() { + return getExclusiveResourcesFromAnnotation(getTestMethod()); + } + + @Override + protected Optional getExplicitExecutionMode() { + return getExecutionModeFromAnnotation(getTestMethod()); + } + + public final Class getTestClass() { + return this.testClass; + } + + public final Method getTestMethod() { + return this.testMethod; + } + + @Override + public String getLegacyReportingName() { + return String.format("%s(%s)", testMethod.getName(), + ClassUtils.nullSafeToString(Class::getSimpleName, testMethod.getParameterTypes())); + } + + /** + * Invoke {@link TestWatcher#testDisabled(ExtensionContext, Optional)} on each + * registered {@link TestWatcher}, in registration order. + * + * @since 5.4 + */ + @Override + public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) { + if (context != null) { + invokeTestWatchers(context, false, + watcher -> watcher.testDisabled(context.getExtensionContext(), result.getReason())); + } + } + + /** + * @since 5.4 + */ + protected void invokeTestWatchers(JupiterEngineExecutionContext context, boolean reverseOrder, + Consumer callback) { + + ExtensionRegistry registry = context.getExtensionRegistry(); + + List watchers = reverseOrder // + ? registry.getReversedExtensions(TestWatcher.class) + : registry.getExtensions(TestWatcher.class); + + watchers.forEach(watcher -> { + try { + callback.accept(watcher); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + ExtensionContext extensionContext = context.getExtensionContext(); + logger.warn(throwable, + () -> String.format("Failed to invoke TestWatcher [%s] for method [%s] with display name [%s]", + watcher.getClass().getName(), + ReflectionUtils.getFullyQualifiedMethodName(extensionContext.getRequiredTestClass(), + extensionContext.getRequiredTestMethod()), + getDisplayName())); + } + }); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java new file mode 100644 index 00000000..22db33b3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.support.hierarchical.Node; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * @since 5.0 + */ +final class MethodExtensionContext extends AbstractExtensionContext { + + private final ThrowableCollector throwableCollector; + + private TestInstances testInstances; + + MethodExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, + TestMethodTestDescriptor testDescriptor, JupiterConfiguration configuration, + ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) { + + super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); + + this.throwableCollector = throwableCollector; + } + + @Override + public Optional getElement() { + return Optional.of(getTestDescriptor().getTestMethod()); + } + + @Override + public Optional> getTestClass() { + return Optional.of(getTestDescriptor().getTestClass()); + } + + @Override + public Optional getTestInstanceLifecycle() { + return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); + } + + @Override + public Optional getTestInstance() { + return getTestInstances().map(TestInstances::getInnermostInstance); + } + + @Override + public Optional getTestInstances() { + return Optional.ofNullable(this.testInstances); + } + + void setTestInstances(TestInstances testInstances) { + this.testInstances = testInstances; + } + + @Override + public Optional getTestMethod() { + return Optional.of(getTestDescriptor().getTestMethod()); + } + + @Override + public Optional getExecutionException() { + return Optional.ofNullable(this.throwableCollector.getThrowable()); + } + + @Override + protected Node.ExecutionMode getPlatformExecutionMode() { + return getTestDescriptor().getExecutionMode(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java new file mode 100644 index 00000000..fd461ba4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.net.URI; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.support.descriptor.MethodSource; + +/** + * Jupiter internal support for creating {@link MethodSource} from {@link URI}. + * + * @since 5.5 + * @see MethodSource + * @see MethodSelector + */ +class MethodSourceSupport { + + static final String METHOD_SCHEME = "method"; + + /** + * Create a new {@code MethodSource} from the supplied {@link URI}. + * + *

The supplied {@link URI} should be of the form {@code method:} + * where FQMN is the fully qualified method name. See + * {@link DiscoverySelectors#selectMethod(String)} for the supported formats. + * + *

The {@link URI#getSchemeSpecificPart() scheme-specific part} + * component of the {@code URI} will be used as fully qualified class name. + * The {@linkplain URI#getFragment() fragment} component of the {@code URI} + * will be used to retrieve the method name and method parameter types. + * + * @param uri the {@code URI} for the method; never {@code null} + * @return a new {@code MethodSource}; never {@code null} + * @since 5.5 + * @see #METHOD_SCHEME + * @see DiscoverySelectors#selectMethod(String) + */ + static MethodSource from(URI uri) { + Preconditions.notNull(uri, "URI must not be null"); + Preconditions.condition(METHOD_SCHEME.equals(uri.getScheme()), + () -> "URI [" + uri + "] must have [" + METHOD_SCHEME + "] scheme"); + String schemeSpecificPart = Preconditions.notNull(uri.getSchemeSpecificPart(), + () -> "Invalid method URI (scheme-specific part must not be null). Please consult the Javadoc of " + + DiscoverySelectors.class.getName() + + "#selectMethod(String) for details on the supported formats."); + String fragment = Preconditions.notNull(uri.getFragment(), + () -> "Invalid method URI (fragment must not be null). Please consult the Javadoc of " + + DiscoverySelectors.class.getName() + + "#selectMethod(String) for details on the supported formats."); + + String fullyQualifiedMethodName = schemeSpecificPart + "#" + fragment; + String[] methodSpec = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); + return MethodSource.from(methodSpec[0], methodSpec[1], methodSpec[2]); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java new file mode 100644 index 00000000..f750e2f5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.Collections.emptyList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistrar; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * {@link TestDescriptor} for tests based on nested (but not static) Java classes. + * + *

Default Display Names

+ * + *

The default display name for a non-static nested test class is the simple + * name of the class. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class NestedClassTestDescriptor extends ClassBasedTestDescriptor { + + public static final String SEGMENT_TYPE = "nested-class"; + + public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { + super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration); + } + + // --- TestDescriptor ------------------------------------------------------ + + @Override + public final Set getTags() { + // return modifiable copy + Set allTags = new LinkedHashSet<>(this.tags); + getParent().ifPresent(parentDescriptor -> allTags.addAll(parentDescriptor.getTags())); + return allTags; + } + + @Override + public List> getEnclosingTestClasses() { + TestDescriptor parent = getParent().orElse(null); + if (parent instanceof ClassBasedTestDescriptor) { + ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent; + List> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses()); + result.add(parentClassDescriptor.getTestClass()); + return result; + } + return emptyList(); + } + + // --- Node ---------------------------------------------------------------- + + @Override + protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, + ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, + ThrowableCollector throwableCollector) { + + // Extensions registered for nested classes and below are not to be used for instantiating and initializing outer classes + ExtensionRegistry extensionRegistryForOuterInstanceCreation = parentExecutionContext.getExtensionRegistry(); + TestInstances outerInstances = parentExecutionContext.getTestInstancesProvider().getTestInstances( + extensionRegistryForOuterInstanceCreation, registrar, throwableCollector); + return instantiateTestClass(Optional.of(outerInstances), registry, extensionContext); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java new file mode 100644 index 00000000..671cc6b6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -0,0 +1,210 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.MethodSourceSupport.METHOD_SCHEME; +import static org.junit.platform.engine.support.descriptor.ClassSource.CLASS_SCHEME; +import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; + +import java.lang.reflect.Method; +import java.net.URI; +import java.util.Iterator; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.CollectionUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; +import org.junit.platform.engine.support.descriptor.UriSource; + +/** + * {@link org.junit.platform.engine.TestDescriptor TestDescriptor} for + * {@link org.junit.jupiter.api.TestFactory @TestFactory} methods. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implements Filterable { + + public static final String SEGMENT_TYPE = "test-factory"; + public static final String DYNAMIC_CONTAINER_SEGMENT_TYPE = "dynamic-container"; + public static final String DYNAMIC_TEST_SEGMENT_TYPE = "dynamic-test"; + + private static final ReflectiveInterceptorCall interceptorCall = InvocationInterceptor::interceptTestFactoryMethod; + private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); + + private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); + + public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, + JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, configuration); + } + + // --- Filterable ---------------------------------------------------------- + + @Override + public DynamicDescendantFilter getDynamicDescendantFilter() { + return dynamicDescendantFilter; + } + + // --- TestDescriptor ------------------------------------------------------ + + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public boolean mayRegisterTests() { + return true; + } + + // --- Node ---------------------------------------------------------------- + + @Override + protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { + ExtensionContext extensionContext = context.getExtensionContext(); + + context.getThrowableCollector().execute(() -> { + Object instance = extensionContext.getRequiredTestInstance(); + Object testFactoryMethodResult = executableInvoker.invoke(getTestMethod(), instance, extensionContext, + context.getExtensionRegistry(), interceptorCall); + TestSource defaultTestSource = getSource().orElseThrow( + () -> new JUnitException("Illegal state: TestSource must be present")); + try (Stream dynamicNodeStream = toDynamicNodeStream(testFactoryMethodResult)) { + int index = 1; + Iterator iterator = dynamicNodeStream.iterator(); + while (iterator.hasNext()) { + DynamicNode dynamicNode = iterator.next(); + Optional descriptor = createDynamicDescriptor(this, dynamicNode, index, + defaultTestSource, getDynamicDescendantFilter(), configuration); + descriptor.ifPresent(dynamicTestExecutor::execute); + index++; + } + } + catch (ClassCastException ex) { + throw invalidReturnTypeException(ex); + } + dynamicTestExecutor.awaitFinished(); + }); + } + + @SuppressWarnings("unchecked") + private Stream toDynamicNodeStream(Object testFactoryMethodResult) { + if (testFactoryMethodResult instanceof DynamicNode) { + return Stream.of((DynamicNode) testFactoryMethodResult); + } + try { + return (Stream) CollectionUtils.toStream(testFactoryMethodResult); + } + catch (PreconditionViolationException ex) { + throw invalidReturnTypeException(ex); + } + } + + private JUnitException invalidReturnTypeException(Throwable cause) { + String message = String.format( + "@TestFactory method [%s] must return a single %2$s or a Stream, Collection, Iterable, Iterator, or array of %2$s.", + getTestMethod().toGenericString(), DynamicNode.class.getName()); + return new JUnitException(message, cause); + } + + static Optional createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node, + int index, TestSource defaultTestSource, DynamicDescendantFilter dynamicDescendantFilter, + JupiterConfiguration configuration) { + + UniqueId uniqueId; + Supplier descriptorCreator; + Optional customTestSource = node.getTestSourceUri().map(TestFactoryTestDescriptor::fromUri); + TestSource source = customTestSource.orElse(defaultTestSource); + + if (node instanceof DynamicTest) { + DynamicTest test = (DynamicTest) node; + uniqueId = parent.getUniqueId().append(DYNAMIC_TEST_SEGMENT_TYPE, "#" + index); + descriptorCreator = () -> new DynamicTestTestDescriptor(uniqueId, index, test, source, configuration); + } + else { + DynamicContainer container = (DynamicContainer) node; + uniqueId = parent.getUniqueId().append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#" + index); + descriptorCreator = () -> new DynamicContainerTestDescriptor(uniqueId, index, container, source, + dynamicDescendantFilter.withoutIndexFiltering(), configuration); + } + if (dynamicDescendantFilter.test(uniqueId, index - 1)) { + JupiterTestDescriptor descriptor = descriptorCreator.get(); + descriptor.setParent(parent); + return Optional.of(descriptor); + } + return Optional.empty(); + } + + /** + * @since 5.3 + */ + static TestSource fromUri(URI uri) { + Preconditions.notNull(uri, "URI must not be null"); + if (CLASSPATH_SCHEME.equals(uri.getScheme())) { + return ClasspathResourceSource.from(uri); + } + if (CLASS_SCHEME.equals(uri.getScheme())) { + return ClassSource.from(uri); + } + if (METHOD_SCHEME.equals(uri.getScheme())) { + return MethodSourceSupport.from(uri); + } + return UriSource.from(uri); + } + + /** + * Override {@link TestMethodTestDescriptor#nodeSkipped} as a no-op, since + * the {@code TestWatcher} API is not supported for {@code @TestFactory} + * containers. + * + * @since 5.4 + */ + @Override + public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) { + /* no-op */ + } + + /** + * Override {@link TestMethodTestDescriptor#nodeFinished} as a no-op, since + * the {@code TestWatcher} API is not supported for {@code @TestFactory} + * containers. + * + * @since 5.4 + */ + @Override + public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor, + TestExecutionResult result) { + + /* no-op */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java new file mode 100644 index 00000000..8aae97f9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.Preconditions; + +/** + * Collection of utilities for retrieving the test instance lifecycle mode. + * + * @since 5.0 + * @see TestInstance + * @see TestInstance.Lifecycle + */ +@API(status = INTERNAL, since = "5.0") +public final class TestInstanceLifecycleUtils { + + private TestInstanceLifecycleUtils() { + /* no-op */ + } + + static TestInstance.Lifecycle getTestInstanceLifecycle(Class testClass, JupiterConfiguration configuration) { + Preconditions.notNull(testClass, "testClass must not be null"); + Preconditions.notNull(configuration, "configuration must not be null"); + + // @formatter:off + return AnnotationUtils.findAnnotation(testClass, TestInstance.class) + .map(TestInstance::value) + .orElseGet(configuration::getDefaultTestInstanceLifecycle); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java new file mode 100644 index 00000000..c5c784b0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -0,0 +1,323 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromExecutableParameters; +import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; + +import java.lang.reflect.Method; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.extension.TestWatcher; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; +import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; +import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * {@link TestDescriptor} for {@link org.junit.jupiter.api.Test @Test} methods. + * + *

Default Display Names

+ * + *

The default display name for a test method is the name of the method + * concatenated with a comma-separated list of parameter types in parentheses. + * The names of parameter types are retrieved using {@link Class#getSimpleName()}. + * For example, the default display name for the following test method is + * {@code testUser(TestInfo, User)}. + * + *

+ *   {@literal @}Test
+ *   void testUser(TestInfo testInfo, {@literal @}Mock User user) { ... }
+ * 
+ * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class TestMethodTestDescriptor extends MethodBasedTestDescriptor { + + public static final String SEGMENT_TYPE = "method"; + private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); + private static final ReflectiveInterceptorCall defaultInterceptorCall = ReflectiveInterceptorCall.ofVoidMethod( + InvocationInterceptor::interceptTestMethod); + + private final ReflectiveInterceptorCall interceptorCall; + + public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, + JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, configuration); + this.interceptorCall = defaultInterceptorCall; + } + + TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, + JupiterConfiguration configuration, ReflectiveInterceptorCall interceptorCall) { + super(uniqueId, displayName, testClass, testMethod, configuration); + this.interceptorCall = interceptorCall; + } + + @Override + public Type getType() { + return Type.TEST; + } + + // --- Node ---------------------------------------------------------------- + + @Override + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { + MutableExtensionRegistry registry = populateNewExtensionRegistry(context); + ThrowableCollector throwableCollector = createThrowableCollector(); + ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); + MethodExtensionContext extensionContext = new MethodExtensionContext(context.getExtensionContext(), + context.getExecutionListener(), this, context.getConfiguration(), throwableCollector, executableInvoker); + throwableCollector.execute(() -> { + TestInstances testInstances = context.getTestInstancesProvider().getTestInstances(registry, + throwableCollector); + extensionContext.setTestInstances(testInstances); + }); + + // @formatter:off + return context.extend() + .withExtensionRegistry(registry) + .withExtensionContext(extensionContext) + .withThrowableCollector(throwableCollector) + .build(); + // @formatter:on + } + + protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { + MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( + context.getExtensionRegistry(), getTestMethod()); + registerExtensionsFromExecutableParameters(registry, getTestMethod()); + return registry; + } + + @Override + public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) { + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + // @formatter:off + invokeBeforeEachCallbacks(context); + if (throwableCollector.isEmpty()) { + invokeBeforeEachMethods(context); + if (throwableCollector.isEmpty()) { + invokeBeforeTestExecutionCallbacks(context); + if (throwableCollector.isEmpty()) { + invokeTestMethod(context, dynamicTestExecutor); + } + invokeAfterTestExecutionCallbacks(context); + } + invokeAfterEachMethods(context); + } + invokeAfterEachCallbacks(context); + // @formatter:on + + return context; + } + + @Override + public void cleanUp(JupiterEngineExecutionContext context) throws Exception { + if (isPerMethodLifecycle(context) && context.getExtensionContext().getTestInstance().isPresent()) { + invokeTestInstancePreDestroyCallbacks(context); + } + context.getThrowableCollector().execute(() -> super.cleanUp(context)); + context.getThrowableCollector().assertEmpty(); + } + + private boolean isPerMethodLifecycle(JupiterEngineExecutionContext context) { + return context.getExtensionContext().getTestInstanceLifecycle().orElse( + Lifecycle.PER_CLASS) == Lifecycle.PER_METHOD; + } + + private void invokeBeforeEachCallbacks(JupiterEngineExecutionContext context) { + invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeEachCallback.class, context, + (callback, extensionContext) -> callback.beforeEach(extensionContext)); + } + + private void invokeBeforeEachMethods(JupiterEngineExecutionContext context) { + ExtensionRegistry registry = context.getExtensionRegistry(); + invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeEachMethodAdapter.class, context, + (adapter, extensionContext) -> { + try { + adapter.invokeBeforeEachMethod(extensionContext, registry); + } + catch (Throwable throwable) { + invokeBeforeEachExecutionExceptionHandlers(extensionContext, registry, throwable); + } + }); + } + + private void invokeBeforeEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry, + Throwable throwable) { + + invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, + (handler, handledThrowable) -> handler.handleBeforeEachMethodExecutionException(context, handledThrowable)); + } + + private void invokeBeforeTestExecutionCallbacks(JupiterEngineExecutionContext context) { + invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeTestExecutionCallback.class, context, + (callback, extensionContext) -> callback.beforeTestExecution(extensionContext)); + } + + private void invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(Class type, + JupiterEngineExecutionContext context, CallbackInvoker callbackInvoker) { + + ExtensionRegistry registry = context.getExtensionRegistry(); + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + for (T callback : registry.getExtensions(type)) { + throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext)); + if (throwableCollector.isNotEmpty()) { + break; + } + } + } + + protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + throwableCollector.execute(() -> { + try { + Method testMethod = getTestMethod(); + Object instance = extensionContext.getRequiredTestInstance(); + executableInvoker.invoke(testMethod, instance, extensionContext, context.getExtensionRegistry(), + interceptorCall); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + invokeTestExecutionExceptionHandlers(context.getExtensionRegistry(), extensionContext, throwable); + } + }); + } + + private void invokeTestExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, + Throwable throwable) { + + invokeExecutionExceptionHandlers(TestExecutionExceptionHandler.class, registry, throwable, + (handler, handledThrowable) -> handler.handleTestExecutionException(context, handledThrowable)); + } + + private void invokeAfterTestExecutionCallbacks(JupiterEngineExecutionContext context) { + invokeAllAfterMethodsOrCallbacks(AfterTestExecutionCallback.class, context, + (callback, extensionContext) -> callback.afterTestExecution(extensionContext)); + } + + private void invokeAfterEachMethods(JupiterEngineExecutionContext context) { + ExtensionRegistry registry = context.getExtensionRegistry(); + invokeAllAfterMethodsOrCallbacks(AfterEachMethodAdapter.class, context, (adapter, extensionContext) -> { + try { + adapter.invokeAfterEachMethod(extensionContext, registry); + } + catch (Throwable throwable) { + invokeAfterEachExecutionExceptionHandlers(extensionContext, registry, throwable); + } + }); + } + + private void invokeAfterEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry, + Throwable throwable) { + + invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, + (handler, handledThrowable) -> handler.handleAfterEachMethodExecutionException(context, handledThrowable)); + } + + private void invokeAfterEachCallbacks(JupiterEngineExecutionContext context) { + invokeAllAfterMethodsOrCallbacks(AfterEachCallback.class, context, + (callback, extensionContext) -> callback.afterEach(extensionContext)); + } + + private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext context) { + invokeAllAfterMethodsOrCallbacks(TestInstancePreDestroyCallback.class, context, + TestInstancePreDestroyCallback::preDestroyTestInstance); + } + + private void invokeAllAfterMethodsOrCallbacks(Class type, + JupiterEngineExecutionContext context, CallbackInvoker callbackInvoker) { + + ExtensionRegistry registry = context.getExtensionRegistry(); + ExtensionContext extensionContext = context.getExtensionContext(); + ThrowableCollector throwableCollector = context.getThrowableCollector(); + + registry.getReversedExtensions(type).forEach(callback -> { + throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext)); + }); + } + + /** + * Invoke {@link TestWatcher#testSuccessful testSuccessful()}, + * {@link TestWatcher#testAborted testAborted()}, or + * {@link TestWatcher#testFailed testFailed()} on each + * registered {@link TestWatcher} according to the status of the supplied + * {@link TestExecutionResult}, in reverse registration order. + * + * @since 5.4 + */ + @Override + public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor, + TestExecutionResult result) { + + if (context != null) { + ExtensionContext extensionContext = context.getExtensionContext(); + TestExecutionResult.Status status = result.getStatus(); + + invokeTestWatchers(context, true, watcher -> { + switch (status) { + case SUCCESSFUL: + watcher.testSuccessful(extensionContext); + break; + case ABORTED: + watcher.testAborted(extensionContext, result.getThrowable().orElse(null)); + break; + case FAILED: + watcher.testFailed(extensionContext, result.getThrowable().orElse(null)); + break; + } + }); + } + } + + /** + * @since 5.5 + */ + @FunctionalInterface + private interface CallbackInvoker { + + void invoke(T t, ExtensionContext context) throws Throwable; + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java new file mode 100644 index 00000000..5a3506f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.support.hierarchical.Node; + +/** + * @since 5.0 + */ +final class TestTemplateExtensionContext extends AbstractExtensionContext { + + private final TestInstances testInstances; + + TestTemplateExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, + TestTemplateTestDescriptor testDescriptor, JupiterConfiguration configuration, TestInstances testInstances, + ExecutableInvoker executableInvoker) { + + super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); + this.testInstances = testInstances; + } + + @Override + public Optional getElement() { + return Optional.of(getTestDescriptor().getTestMethod()); + } + + @Override + public Optional> getTestClass() { + return Optional.of(getTestDescriptor().getTestClass()); + } + + @Override + public Optional getTestInstanceLifecycle() { + return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); + } + + @Override + public Optional getTestInstance() { + return getTestInstances().map(TestInstances::getInnermostInstance); + } + + @Override + public Optional getTestInstances() { + return Optional.ofNullable(this.testInstances); + } + + @Override + public Optional getTestMethod() { + return Optional.of(getTestDescriptor().getTestMethod()); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + protected Node.ExecutionMode getPlatformExecutionMode() { + return getTestDescriptor().getExecutionMode(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java new file mode 100644 index 00000000..4d522615 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.Collections.emptySet; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.reflect.Method; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; + +/** + * {@link TestDescriptor} for a {@link org.junit.jupiter.api.TestTemplate @TestTemplate} + * invocation. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescriptor { + + public static final String SEGMENT_TYPE = "test-template-invocation"; + private static final ReflectiveInterceptorCall interceptorCall = ReflectiveInterceptorCall.ofVoidMethod( + InvocationInterceptor::interceptTestTemplateMethod); + + private TestTemplateInvocationContext invocationContext; + private final int index; + + TestTemplateInvocationTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, + TestTemplateInvocationContext invocationContext, int index, JupiterConfiguration configuration) { + super(uniqueId, invocationContext.getDisplayName(index), testClass, templateMethod, configuration, + interceptorCall); + this.invocationContext = invocationContext; + this.index = index; + } + + @Override + public Set getExclusiveResources() { + // @ResourceLock annotations are already collected and returned by the enclosing container + return emptySet(); + } + + @Override + public String getLegacyReportingName() { + return super.getLegacyReportingName() + "[" + index + "]"; + } + + @Override + protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { + MutableExtensionRegistry registry = super.populateNewExtensionRegistry(context); + invocationContext.getAdditionalExtensions().forEach( + extension -> registry.registerExtension(extension, invocationContext)); + return registry; + } + + @Override + public void after(JupiterEngineExecutionContext context) { + // forget invocationContext so it can be garbage collected + invocationContext = null; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java new file mode 100644 index 00000000..885a07d9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -0,0 +1,155 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; + +/** + * {@link TestDescriptor} for {@link org.junit.jupiter.api.TestTemplate @TestTemplate} + * methods. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implements Filterable { + + public static final String SEGMENT_TYPE = "test-template"; + private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); + + public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, + JupiterConfiguration configuration) { + super(uniqueId, testClass, templateMethod, configuration); + } + + // --- Filterable ---------------------------------------------------------- + + @Override + public DynamicDescendantFilter getDynamicDescendantFilter() { + return dynamicDescendantFilter; + } + + // --- TestDescriptor ------------------------------------------------------ + + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public boolean mayRegisterTests() { + return true; + } + + // --- Node ---------------------------------------------------------------- + + @Override + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception { + MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( + context.getExtensionRegistry(), getTestMethod()); + + // The test instance should be properly maintained by the enclosing class's ExtensionContext. + TestInstances testInstances = context.getExtensionContext().getTestInstances().orElse(null); + + ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); + ExtensionContext extensionContext = new TestTemplateExtensionContext(context.getExtensionContext(), + context.getExecutionListener(), this, context.getConfiguration(), testInstances, executableInvoker); + + // @formatter:off + return context.extend() + .withExtensionRegistry(registry) + .withExtensionContext(extensionContext) + .build(); + // @formatter:on + } + + @Override + public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) throws Exception { + + ExtensionContext extensionContext = context.getExtensionContext(); + List providers = validateProviders(extensionContext, + context.getExtensionRegistry()); + AtomicInteger invocationIndex = new AtomicInteger(); + // @formatter:off + providers.stream() + .flatMap(provider -> provider.provideTestTemplateInvocationContexts(extensionContext)) + .map(invocationContext -> createInvocationTestDescriptor(invocationContext, invocationIndex.incrementAndGet())) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(invocationTestDescriptor -> execute(dynamicTestExecutor, invocationTestDescriptor)); + // @formatter:on + validateWasAtLeastInvokedOnce(invocationIndex.get(), providers); + return context; + } + + private List validateProviders(ExtensionContext extensionContext, + ExtensionRegistry extensionRegistry) { + + // @formatter:off + List providers = extensionRegistry.stream(TestTemplateInvocationContextProvider.class) + .filter(provider -> provider.supportsTestTemplate(extensionContext)) + .collect(toList()); + // @formatter:on + + return Preconditions.notEmpty(providers, + () -> String.format("You must register at least one %s that supports @TestTemplate method [%s]", + TestTemplateInvocationContextProvider.class.getSimpleName(), getTestMethod())); + } + + private Optional createInvocationTestDescriptor(TestTemplateInvocationContext invocationContext, + int index) { + UniqueId uniqueId = getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); + if (getDynamicDescendantFilter().test(uniqueId, index - 1)) { + return Optional.of(new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(), + invocationContext, index, configuration)); + } + return Optional.empty(); + } + + private void execute(DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { + testDescriptor.setParent(this); + dynamicTestExecutor.execute(testDescriptor); + } + + private void validateWasAtLeastInvokedOnce(int invocationIndex, + List providers) { + + Preconditions.condition(invocationIndex > 0, + () -> "None of the supporting " + TestTemplateInvocationContextProvider.class.getSimpleName() + "s " + + providers.stream().map(provider -> provider.getClass().getSimpleName()).collect( + joining(", ", "[", "]")) + + " provided a non-empty stream"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java new file mode 100644 index 00000000..e3e0028c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java @@ -0,0 +1,5 @@ +/** + * Test descriptors used within the JUnit Jupiter test engine. + */ + +package org.junit.jupiter.engine.descriptor; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java new file mode 100644 index 00000000..31666b8c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.List; +import java.util.Optional; + +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; + +/** + * Abstract base class for wrappers for test descriptors based on annotated + * elements. + * + * @since 5.8 + */ +abstract class AbstractAnnotatedDescriptorWrapper { + + private final TestDescriptor testDescriptor; + private final E annotatedElement; + + AbstractAnnotatedDescriptorWrapper(TestDescriptor testDescriptor, E annotatedElement) { + this.testDescriptor = testDescriptor; + this.annotatedElement = annotatedElement; + } + + E getAnnotatedElement() { + return this.annotatedElement; + } + + TestDescriptor getTestDescriptor() { + return this.testDescriptor; + } + + public final String getDisplayName() { + return this.testDescriptor.getDisplayName(); + } + + public final boolean isAnnotated(Class annotationType) { + Preconditions.notNull(annotationType, "annotationType must not be null"); + return AnnotationUtils.isAnnotated(getAnnotatedElement(), annotationType); + } + + public final Optional findAnnotation(Class annotationType) { + Preconditions.notNull(annotationType, "annotationType must not be null"); + return AnnotationUtils.findAnnotation(getAnnotatedElement(), annotationType); + } + + public final List findRepeatableAnnotations(Class annotationType) { + Preconditions.notNull(annotationType, "annotationType must not be null"); + return AnnotationUtils.findRepeatableAnnotations(getAnnotatedElement(), annotationType); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java new file mode 100644 index 00000000..e19e5c96 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java @@ -0,0 +1,189 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static java.util.stream.Collectors.toCollection; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestDescriptor; + +/** + * Abstract base class for {@linkplain TestDescriptor.Visitor visitors} that + * order children nodes. + * + * @param the parent container type to search in for matching children + * @param the type of children (containers or tests) to order + * @param the wrapper type for the children to order + * @since 5.8 + */ +abstract class AbstractOrderingVisitor> + implements TestDescriptor.Visitor { + + private static final Logger logger = LoggerFactory.getLogger(AbstractOrderingVisitor.class); + + @SuppressWarnings("unchecked") + protected void doWithMatchingDescriptor(Class parentTestDescriptorType, TestDescriptor testDescriptor, + Consumer action, Function errorMessageBuilder) { + + if (parentTestDescriptorType.isInstance(testDescriptor)) { + PARENT parentTestDescriptor = (PARENT) testDescriptor; + try { + action.accept(parentTestDescriptor); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + logger.error(t, () -> errorMessageBuilder.apply(parentTestDescriptor)); + } + } + } + + protected void orderChildrenTestDescriptors(TestDescriptor parentTestDescriptor, Class matchingChildrenType, + Function descriptorWrapperFactory, DescriptorWrapperOrderer descriptorWrapperOrderer) { + + Set children = parentTestDescriptor.getChildren(); + + List matchingDescriptorWrappers = children.stream()// + .filter(matchingChildrenType::isInstance)// + .map(matchingChildrenType::cast)// + .map(descriptorWrapperFactory)// + .collect(toCollection(ArrayList::new)); + + // If there are no children to order, abort early. + if (matchingDescriptorWrappers.isEmpty()) { + return; + } + + if (descriptorWrapperOrderer.canOrderWrappers()) { + List nonMatchingTestDescriptors = children.stream()// + .filter(childTestDescriptor -> !matchingChildrenType.isInstance(childTestDescriptor))// + .collect(Collectors.toList()); + + // Make a local copy for later validation + Set originalWrappers = new LinkedHashSet<>(matchingDescriptorWrappers); + + descriptorWrapperOrderer.orderWrappers(matchingDescriptorWrappers); + + int difference = matchingDescriptorWrappers.size() - originalWrappers.size(); + if (difference > 0) { + descriptorWrapperOrderer.logDescriptorsAddedWarning(difference); + } + else if (difference < 0) { + descriptorWrapperOrderer.logDescriptorsRemovedWarning(difference); + } + + Set orderedTestDescriptors = matchingDescriptorWrappers.stream()// + .filter(originalWrappers::contains)// + .map(AbstractAnnotatedDescriptorWrapper::getTestDescriptor)// + .collect(toCollection(LinkedHashSet::new)); + + // There is currently no way to removeAll or addAll children at once, so we + // first remove them all and then add them all back. + Stream.concat(orderedTestDescriptors.stream(), nonMatchingTestDescriptors.stream())// + .forEach(parentTestDescriptor::removeChild); + + // If we are ordering children of type ClassBasedTestDescriptor, that means we + // are ordering top-level classes or @Nested test classes. Thus, the + // nonMatchingTestDescriptors list is either empty (for top-level classes) or + // contains only local test methods (for @Nested classes) which must be executed + // before tests in @Nested test classes. So we add the test methods before adding + // the @Nested test classes. + if (matchingChildrenType == ClassBasedTestDescriptor.class) { + Stream.concat(nonMatchingTestDescriptors.stream(), orderedTestDescriptors.stream())// + .forEach(parentTestDescriptor::addChild); + } + // Otherwise, we add the ordered descriptors before the non-matching descriptors, + // which is the case when we are ordering test methods. In other words, local + // test methods always get added before @Nested test classes. + else { + Stream.concat(orderedTestDescriptors.stream(), nonMatchingTestDescriptors.stream())// + .forEach(parentTestDescriptor::addChild); + } + } + + // Recurse through the children in order to support ordering for @Nested test classes. + matchingDescriptorWrappers.forEach(descriptorWrapper -> { + TestDescriptor newParentTestDescriptor = descriptorWrapper.getTestDescriptor(); + DescriptorWrapperOrderer newDescriptorWrapperOrderer = getDescriptorWrapperOrderer(descriptorWrapperOrderer, + descriptorWrapper); + + orderChildrenTestDescriptors(newParentTestDescriptor, matchingChildrenType, descriptorWrapperFactory, + newDescriptorWrapperOrderer); + }); + } + + /** + * Get the {@link DescriptorWrapperOrderer} for the supplied {@link AbstractAnnotatedDescriptorWrapper}. + * + *

The default implementation returns the supplied {@code DescriptorWrapperOrderer}. + * + * @return a new {@code DescriptorWrapperOrderer} or the one supplied as an argument + */ + protected DescriptorWrapperOrderer getDescriptorWrapperOrderer( + DescriptorWrapperOrderer inheritedDescriptorWrapperOrderer, + AbstractAnnotatedDescriptorWrapper descriptorWrapper) { + + return inheritedDescriptorWrapperOrderer; + } + + protected class DescriptorWrapperOrderer { + + private final Consumer> orderingAction; + + private final MessageGenerator descriptorsAddedMessageGenerator; + + private final MessageGenerator descriptorsRemovedMessageGenerator; + + DescriptorWrapperOrderer(Consumer> orderingAction, + MessageGenerator descriptorsAddedMessageGenerator, + MessageGenerator descriptorsRemovedMessageGenerator) { + + this.orderingAction = orderingAction; + this.descriptorsAddedMessageGenerator = descriptorsAddedMessageGenerator; + this.descriptorsRemovedMessageGenerator = descriptorsRemovedMessageGenerator; + } + + private boolean canOrderWrappers() { + return this.orderingAction != null; + } + + private void orderWrappers(List wrappers) { + this.orderingAction.accept(wrappers); + } + + private void logDescriptorsAddedWarning(int number) { + logger.warn(() -> this.descriptorsAddedMessageGenerator.generateMessage(number)); + } + + private void logDescriptorsRemovedWarning(int number) { + logger.warn(() -> this.descriptorsRemovedMessageGenerator.generateMessage(Math.abs(number))); + } + + } + + @FunctionalInterface + protected interface MessageGenerator { + + String generateMessage(int number); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java new file mode 100644 index 00000000..0b8ea6d0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import java.lang.reflect.AnnotatedElement; +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.TestClassOrder; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.TestDescriptor; + +/** + * @since 5.8 + */ +class ClassOrderingVisitor + extends AbstractOrderingVisitor { + + private final JupiterConfiguration configuration; + + ClassOrderingVisitor(JupiterConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public void visit(TestDescriptor testDescriptor) { + ClassOrderer globalClassOrderer = this.configuration.getDefaultTestClassOrderer().orElse(null); + doWithMatchingDescriptor(JupiterEngineDescriptor.class, testDescriptor, + descriptor -> orderContainedClasses(descriptor, globalClassOrderer), + descriptor -> "Failed to order classes"); + } + + private void orderContainedClasses(JupiterEngineDescriptor jupiterEngineDescriptor, ClassOrderer classOrderer) { + orderChildrenTestDescriptors(// + jupiterEngineDescriptor, // + ClassBasedTestDescriptor.class, // + DefaultClassDescriptor::new, // + createDescriptorWrapperOrderer(classOrderer)); + } + + @Override + protected DescriptorWrapperOrderer getDescriptorWrapperOrderer( + DescriptorWrapperOrderer inheritedDescriptorWrapperOrderer, + AbstractAnnotatedDescriptorWrapper descriptorWrapper) { + + AnnotatedElement annotatedElement = descriptorWrapper.getAnnotatedElement(); + return AnnotationUtils.findAnnotation(annotatedElement, TestClassOrder.class)// + .map(TestClassOrder::value)// + . map(ReflectionUtils::newInstance)// + .map(this::createDescriptorWrapperOrderer)// + .orElse(inheritedDescriptorWrapperOrderer); + } + + private DescriptorWrapperOrderer createDescriptorWrapperOrderer(ClassOrderer classOrderer) { + Consumer> orderingAction = classOrderer == null ? null : // + classDescriptors -> classOrderer.orderClasses( + new DefaultClassOrdererContext(classDescriptors, this.configuration)); + + MessageGenerator descriptorsAddedMessageGenerator = number -> String.format( + "ClassOrderer [%s] added %s ClassDescriptor(s) which will be ignored.", nullSafeToString(classOrderer), + number); + MessageGenerator descriptorsRemovedMessageGenerator = number -> String.format( + "ClassOrderer [%s] removed %s ClassDescriptor(s) which will be retained with arbitrary ordering.", + nullSafeToString(classOrderer), number); + + return new DescriptorWrapperOrderer(orderingAction, descriptorsAddedMessageGenerator, + descriptorsRemovedMessageGenerator); + } + + private static String nullSafeToString(ClassOrderer classOrderer) { + return (classOrderer != null ? classOrderer.getClass().getName() : ""); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java new file mode 100644 index 00000000..fd986367 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -0,0 +1,162 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static java.util.function.Predicate.isEqual; +import static java.util.stream.Collectors.toCollection; +import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; +import static org.junit.platform.commons.support.ReflectionSupport.findNestedClasses; +import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; +import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.NestedClassSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.support.discovery.SelectorResolver; + +/** + * @since 5.5 + */ +class ClassSelectorResolver implements SelectorResolver { + + private static final IsTestClassWithTests isTestClassWithTests = new IsTestClassWithTests(); + private static final IsNestedTestClass isNestedTestClass = new IsNestedTestClass(); + + private final Predicate classNameFilter; + private final JupiterConfiguration configuration; + + ClassSelectorResolver(Predicate classNameFilter, JupiterConfiguration configuration) { + this.classNameFilter = classNameFilter; + this.configuration = configuration; + } + + @Override + public Resolution resolve(ClassSelector selector, Context context) { + Class testClass = selector.getJavaClass(); + if (isTestClassWithTests.test(testClass)) { + // Nested tests are never filtered out + if (classNameFilter.test(testClass.getName())) { + return toResolution( + context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass)))); + } + } + else if (isNestedTestClass.test(testClass)) { + return toResolution(context.addToParent(() -> DiscoverySelectors.selectClass(testClass.getEnclosingClass()), + parent -> Optional.of(newNestedClassTestDescriptor(parent, testClass)))); + } + return unresolved(); + } + + @Override + public Resolution resolve(NestedClassSelector selector, Context context) { + if (isNestedTestClass.test(selector.getNestedClass())) { + return toResolution(context.addToParent(() -> selectClass(selector.getEnclosingClasses()), + parent -> Optional.of(newNestedClassTestDescriptor(parent, selector.getNestedClass())))); + } + return unresolved(); + } + + @Override + public Resolution resolve(UniqueIdSelector selector, Context context) { + UniqueId uniqueId = selector.getUniqueId(); + UniqueId.Segment lastSegment = uniqueId.getLastSegment(); + if (ClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { + String className = lastSegment.getValue(); + return ReflectionUtils.tryToLoadClass(className).toOptional().filter(isTestClassWithTests).map( + testClass -> toResolution( + context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass))))).orElse( + unresolved()); + } + if (NestedClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { + String simpleClassName = lastSegment.getValue(); + return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { + if (parent instanceof ClassBasedTestDescriptor) { + Class parentTestClass = ((ClassBasedTestDescriptor) parent).getTestClass(); + return ReflectionUtils.findNestedClasses(parentTestClass, + isNestedTestClass.and( + where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst().flatMap( + testClass -> Optional.of(newNestedClassTestDescriptor(parent, testClass))); + } + return Optional.empty(); + })); + } + return unresolved(); + } + + private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class testClass) { + return new ClassTestDescriptor( + parent.getUniqueId().append(ClassTestDescriptor.SEGMENT_TYPE, testClass.getName()), testClass, + configuration); + } + + private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { + return new NestedClassTestDescriptor( + parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), testClass, + configuration); + } + + private Resolution toResolution(Optional testDescriptor) { + return testDescriptor.map(it -> { + Class testClass = it.getTestClass(); + List> testClasses = new ArrayList<>(it.getEnclosingTestClasses()); + testClasses.add(testClass); + // @formatter:off + return Resolution.match(Match.exact(it, () -> { + Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream() + .map(method -> selectMethod(testClasses, method)); + Stream nestedClasses = findNestedClasses(testClass, isNestedTestClass).stream() + .map(nestedClass -> DiscoverySelectors.selectNestedClass(testClasses, nestedClass)); + return Stream.concat(methods, nestedClasses).collect(toCollection((Supplier>) LinkedHashSet::new)); + })); + // @formatter:on + }).orElse(unresolved()); + } + + private DiscoverySelector selectClass(List> classes) { + if (classes.size() == 1) { + return DiscoverySelectors.selectClass(classes.get(0)); + } + int lastIndex = classes.size() - 1; + return DiscoverySelectors.selectNestedClass(classes.subList(0, lastIndex), classes.get(lastIndex)); + } + + private DiscoverySelector selectMethod(List> classes, Method method) { + if (classes.size() == 1) { + return DiscoverySelectors.selectMethod(classes.get(0), method); + } + int lastIndex = classes.size() - 1; + return DiscoverySelectors.selectNestedMethod(classes.subList(0, lastIndex), classes.get(lastIndex), method); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java new file mode 100644 index 00000000..b11c1b02 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import org.junit.jupiter.api.ClassDescriptor; +import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Default implementation of {@link ClassDescriptor}, backed by + * a {@link ClassBasedTestDescriptor}. + * + * @since 5.8 + */ +class DefaultClassDescriptor extends AbstractAnnotatedDescriptorWrapper> implements ClassDescriptor { + + DefaultClassDescriptor(ClassBasedTestDescriptor testDescriptor) { + super(testDescriptor, testDescriptor.getTestClass()); + } + + @Override + public final Class getTestClass() { + return getAnnotatedElement(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("class", getTestClass().toGenericString()).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java new file mode 100644 index 00000000..2c8c8743 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.ClassDescriptor; +import org.junit.jupiter.api.ClassOrdererContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; + +/** + * Default implementation of {@link ClassOrdererContext}. + * + * @since 5.8 + */ +class DefaultClassOrdererContext implements ClassOrdererContext { + + private final List classDescriptors; + private final JupiterConfiguration configuration; + + DefaultClassOrdererContext(List classDescriptors, JupiterConfiguration configuration) { + this.classDescriptors = classDescriptors; + this.configuration = configuration; + } + + @Override + public List getClassDescriptors() { + return this.classDescriptors; + } + + @Override + public Optional getConfigurationParameter(String key) { + return this.configuration.getRawConfigurationParameter(key); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java new file mode 100644 index 00000000..a8cdd12c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.MethodDescriptor; +import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Default implementation of {@link MethodDescriptor}, backed by + * a {@link MethodBasedTestDescriptor}. + * + * @since 5.4 + */ +class DefaultMethodDescriptor extends AbstractAnnotatedDescriptorWrapper implements MethodDescriptor { + + DefaultMethodDescriptor(MethodBasedTestDescriptor testDescriptor) { + super(testDescriptor, testDescriptor.getTestMethod()); + } + + @Override + public final Method getMethod() { + return getAnnotatedElement(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("method", getMethod().toGenericString()).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java new file mode 100644 index 00000000..fd51d4b7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.MethodDescriptor; +import org.junit.jupiter.api.MethodOrdererContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Default implementation of {@link MethodOrdererContext}. + * + * @since 5.4 + */ +class DefaultMethodOrdererContext implements MethodOrdererContext { + + private final Class testClass; + private final List methodDescriptors; + private final JupiterConfiguration configuration; + + DefaultMethodOrdererContext(Class testClass, List methodDescriptors, + JupiterConfiguration configuration) { + + this.testClass = testClass; + this.methodDescriptors = methodDescriptors; + this.configuration = configuration; + } + + @Override + public final Class getTestClass() { + return this.testClass; + } + + @Override + public List getMethodDescriptors() { + return this.methodDescriptors; + } + + @Override + public Optional getConfigurationParameter(String key) { + return this.configuration.getRawConfigurationParameter(key); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("testClass", this.testClass.getName()).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java new file mode 100644 index 00000000..42771f96 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; + +/** + * {@code DiscoverySelectorResolver} resolves {@link TestDescriptor TestDescriptors} + * for containers and tests selected by DiscoverySelectors with the help of the + * {@code JavaElementsResolver}. + * + *

This class is the only public entry point into the discovery package. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class DiscoverySelectorResolver { + + // @formatter:off + private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() + .addClassContainerSelectorResolver(new IsTestClassWithTests()) + .addSelectorResolver(context -> new ClassSelectorResolver(context.getClassNameFilter(), context.getEngineDescriptor().getConfiguration())) + .addSelectorResolver(context -> new MethodSelectorResolver(context.getEngineDescriptor().getConfiguration())) + .addTestDescriptorVisitor(context -> new ClassOrderingVisitor(context.getEngineDescriptor().getConfiguration())) + .addTestDescriptorVisitor(context -> new MethodOrderingVisitor(context.getEngineDescriptor().getConfiguration())) + .addTestDescriptorVisitor(context -> TestDescriptor::prune) + .build(); + // @formatter:on + + public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescriptor engineDescriptor) { + resolver.resolve(request, engineDescriptor); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java new file mode 100644 index 00000000..648ad453 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.0 + */ +class MethodFinder { + + // Pattern: methodName(comma-separated list of parameter type names) + private static final Pattern METHOD_PATTERN = Pattern.compile("(.+)\\((.*)\\)"); + + Optional findMethod(String methodSpecPart, Class clazz) { + Matcher matcher = METHOD_PATTERN.matcher(methodSpecPart); + + Preconditions.condition(matcher.matches(), + () -> String.format("Method [%s] does not match pattern [%s]", methodSpecPart, METHOD_PATTERN)); + + String methodName = matcher.group(1); + String parameterTypeNames = matcher.group(2); + return ReflectionUtils.findMethod(clazz, methodName, parameterTypeNames); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java new file mode 100644 index 00000000..e1e7c8af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; +import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.TestDescriptor; + +/** + * @since 5.5 + */ +class MethodOrderingVisitor + extends AbstractOrderingVisitor { + + private final JupiterConfiguration configuration; + + MethodOrderingVisitor(JupiterConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public void visit(TestDescriptor testDescriptor) { + doWithMatchingDescriptor(ClassBasedTestDescriptor.class, testDescriptor, + descriptor -> orderContainedMethods(descriptor, descriptor.getTestClass()), + descriptor -> "Failed to order methods for " + descriptor.getTestClass()); + } + + /** + * @since 5.4 + */ + private void orderContainedMethods(ClassBasedTestDescriptor classBasedTestDescriptor, Class testClass) { + findAnnotation(testClass, TestMethodOrder.class)// + .map(TestMethodOrder::value)// + . map(ReflectionUtils::newInstance)// + .map(Optional::of)// + .orElseGet(configuration::getDefaultTestMethodOrderer)// + .ifPresent(methodOrderer -> { + + Consumer> orderingAction = methodDescriptors -> methodOrderer.orderMethods( + new DefaultMethodOrdererContext(testClass, methodDescriptors, this.configuration)); + + MessageGenerator descriptorsAddedMessageGenerator = number -> String.format( + "MethodOrderer [%s] added %s MethodDescriptor(s) for test class [%s] which will be ignored.", + methodOrderer.getClass().getName(), number, testClass.getName()); + MessageGenerator descriptorsRemovedMessageGenerator = number -> String.format( + "MethodOrderer [%s] removed %s MethodDescriptor(s) for test class [%s] which will be retained with arbitrary ordering.", + methodOrderer.getClass().getName(), number, testClass.getName()); + + DescriptorWrapperOrderer descriptorWrapperOrderer = new DescriptorWrapperOrderer(orderingAction, + descriptorsAddedMessageGenerator, descriptorsRemovedMessageGenerator); + + orderChildrenTestDescriptors(classBasedTestDescriptor, // + MethodBasedTestDescriptor.class, // + DefaultMethodDescriptor::new, // + descriptorWrapperOrderer); + + // Note: MethodOrderer#getDefaultExecutionMode() is guaranteed + // to be invoked after MethodOrderer#orderMethods(). + methodOrderer.getDefaultExecutionMode()// + .map(JupiterTestDescriptor::toExecutionMode)// + .ifPresent(classBasedTestDescriptor::setDefaultChildExecutionMode); + }); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java new file mode 100644 index 00000000..62698001 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java @@ -0,0 +1,251 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.matches; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; +import org.junit.jupiter.engine.descriptor.Filterable; +import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; +import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; +import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; +import org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod; +import org.junit.jupiter.engine.discovery.predicates.IsTestMethod; +import org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.NestedMethodSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.support.discovery.SelectorResolver; + +/** + * @since 5.5 + */ +class MethodSelectorResolver implements SelectorResolver { + + private static final Logger logger = LoggerFactory.getLogger(MethodSelectorResolver.class); + private static final MethodFinder methodFinder = new MethodFinder(); + private static final Predicate> testClassPredicate = new IsTestClassWithTests().or( + new IsNestedTestClass()); + + protected final JupiterConfiguration configuration; + + MethodSelectorResolver(JupiterConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public Resolution resolve(MethodSelector selector, Context context) { + return resolve(context, emptyList(), selector.getJavaClass(), selector::getJavaMethod, Match::exact); + } + + @Override + public Resolution resolve(NestedMethodSelector selector, Context context) { + return resolve(context, selector.getEnclosingClasses(), selector.getNestedClass(), selector::getMethod, + Match::exact); + } + + private Resolution resolve(Context context, List> enclosingClasses, Class testClass, + Supplier methodSupplier, + BiFunction>, Match> matchFactory) { + if (!testClassPredicate.test(testClass)) { + return unresolved(); + } + Method method = methodSupplier.get(); + // @formatter:off + Set matches = Arrays.stream(MethodType.values()) + .map(methodType -> methodType.resolve(enclosingClasses, testClass, method, context, configuration)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(testDescriptor -> matchFactory.apply(testDescriptor, expansionCallback(testDescriptor))) + .collect(toSet()); + // @formatter:on + if (matches.size() > 1) { + logger.warn(() -> { + Stream testDescriptors = matches.stream().map(Match::getTestDescriptor); + return String.format( + "Possible configuration error: method [%s] resulted in multiple TestDescriptors %s. " + + "This is typically the result of annotating a method with multiple competing annotations " + + "such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.", + method.toGenericString(), testDescriptors.map(d -> d.getClass().getName()).collect(toList())); + }); + } + return matches.isEmpty() ? unresolved() : matches(matches); + } + + @Override + public Resolution resolve(UniqueIdSelector selector, Context context) { + UniqueId uniqueId = selector.getUniqueId(); + // @formatter:off + return Arrays.stream(MethodType.values()) + .map(methodType -> methodType.resolveUniqueIdIntoTestDescriptor(uniqueId, context, configuration)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(testDescriptor -> { + boolean exactMatch = uniqueId.equals(testDescriptor.getUniqueId()); + if (testDescriptor instanceof Filterable) { + Filterable filterable = (Filterable) testDescriptor; + if (exactMatch) { + filterable.getDynamicDescendantFilter().allowAll(); + } + else { + filterable.getDynamicDescendantFilter().allowUniqueIdPrefix(uniqueId); + } + } + return Resolution.match(exactMatch ? Match.exact(testDescriptor) : Match.partial(testDescriptor, expansionCallback(testDescriptor))); + }) + .findFirst() + .orElse(unresolved()); + // @formatter:on + } + + @Override + public Resolution resolve(IterationSelector selector, Context context) { + if (selector.getParentSelector() instanceof MethodSelector) { + MethodSelector methodSelector = (MethodSelector) selector.getParentSelector(); + return resolve(context, emptyList(), methodSelector.getJavaClass(), methodSelector::getJavaMethod, + (testDescriptor, childSelectorsSupplier) -> { + if (testDescriptor instanceof Filterable) { + Filterable filterable = (Filterable) testDescriptor; + filterable.getDynamicDescendantFilter().allowIndex(selector.getIterationIndices()); + } + return Match.partial(testDescriptor, childSelectorsSupplier); + }); + } + return unresolved(); + } + + private Supplier> expansionCallback(TestDescriptor testDescriptor) { + return () -> { + if (testDescriptor instanceof Filterable) { + Filterable filterable = (Filterable) testDescriptor; + filterable.getDynamicDescendantFilter().allowAll(); + } + return emptySet(); + }; + } + + private enum MethodType { + + TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { + @Override + protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, + JupiterConfiguration configuration) { + return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration); + } + }, + + TEST_FACTORY(new IsTestFactoryMethod(), TestFactoryTestDescriptor.SEGMENT_TYPE, + TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE, + TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE) { + @Override + protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, + JupiterConfiguration configuration) { + return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration); + } + }, + + TEST_TEMPLATE(new IsTestTemplateMethod(), TestTemplateTestDescriptor.SEGMENT_TYPE, + TestTemplateInvocationTestDescriptor.SEGMENT_TYPE) { + @Override + protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, + JupiterConfiguration configuration) { + return new TestTemplateTestDescriptor(uniqueId, testClass, method, configuration); + } + }; + + private final Predicate methodPredicate; + private final String segmentType; + private final Set dynamicDescendantSegmentTypes; + + MethodType(Predicate methodPredicate, String segmentType, String... dynamicDescendantSegmentTypes) { + this.methodPredicate = methodPredicate; + this.segmentType = segmentType; + this.dynamicDescendantSegmentTypes = new LinkedHashSet<>(Arrays.asList(dynamicDescendantSegmentTypes)); + } + + private Optional resolve(List> enclosingClasses, Class testClass, Method method, + Context context, JupiterConfiguration configuration) { + if (!methodPredicate.test(method)) { + return Optional.empty(); + } + return context.addToParent(() -> selectClass(enclosingClasses, testClass), // + parent -> Optional.of( + createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); + } + + private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { + if (enclosingClasses.isEmpty()) { + return DiscoverySelectors.selectClass(testClass); + } + return DiscoverySelectors.selectNestedClass(enclosingClasses, testClass); + } + + private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniqueId, Context context, + JupiterConfiguration configuration) { + UniqueId.Segment lastSegment = uniqueId.getLastSegment(); + if (segmentType.equals(lastSegment.getType())) { + return context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { + String methodSpecPart = lastSegment.getValue(); + Class testClass = ((ClassBasedTestDescriptor) parent).getTestClass(); + // @formatter:off + return methodFinder.findMethod(methodSpecPart, testClass) + .filter(methodPredicate) + .map(method -> createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration)); + // @formatter:on + }); + } + if (dynamicDescendantSegmentTypes.contains(lastSegment.getType())) { + return resolveUniqueIdIntoTestDescriptor(uniqueId.removeLastSegment(), context, configuration); + } + return Optional.empty(); + } + + private UniqueId createUniqueId(Method method, TestDescriptor parent) { + String methodId = String.format("%s(%s)", method.getName(), + ClassUtils.nullSafeToString(method.getParameterTypes())); + return parent.getUniqueId().append(segmentType, methodId); + } + + protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, + JupiterConfiguration configuration); + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java new file mode 100644 index 00000000..0426627d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java @@ -0,0 +1,6 @@ +/** + * Internal classes for test discovery within the JUnit Jupiter test engine. + * Contains resolvers for Java elements. + */ + +package org.junit.jupiter.engine.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java new file mode 100644 index 00000000..c46822be --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; +import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; + +import java.util.function.Predicate; + +import org.apiguardian.api.API; + +/** + * Test if a class is a non-private inner class (i.e., a non-static nested class). + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class IsInnerClass implements Predicate> { + + @Override + public boolean test(Class candidate) { + // Do not collapse into a single return statement. + if (isPrivate(candidate)) { + return false; + } + if (!isInnerClass(candidate)) { + return false; + } + + return true; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java new file mode 100644 index 00000000..ceaa9f3d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; + +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.Nested; + +/** + * Test if a class is a JUnit Jupiter {@link Nested @Nested} test class. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class IsNestedTestClass implements Predicate> { + + private static final IsInnerClass isInnerClass = new IsInnerClass(); + + @Override + public boolean test(Class candidate) { + //please do not collapse into single return + if (!isInnerClass.test(candidate)) { + return false; + } + return isAnnotated(candidate, Nested.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java new file mode 100644 index 00000000..525e9aaf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; +import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; +import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; + +import java.util.function.Predicate; + +import org.apiguardian.api.API; + +/** + * Test if a class is a potential top-level JUnit Jupiter test container, even if + * it does not contain tests. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class IsPotentialTestContainer implements Predicate> { + + @Override + public boolean test(Class candidate) { + // Please do not collapse the following into a single statement. + if (isPrivate(candidate)) { + return false; + } + if (isAbstract(candidate)) { + return false; + } + if (candidate.isLocalClass()) { + return false; + } + if (candidate.isAnonymousClass()) { + return false; + } + return !isInnerClass(candidate); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java new file mode 100644 index 00000000..edec7724 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.reflect.Method; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Test if a class is a JUnit Jupiter test class containing executable tests, + * test factories, test templates, or nested tests. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.1") +public class IsTestClassWithTests implements Predicate> { + + private static final IsTestMethod isTestMethod = new IsTestMethod(); + + private static final IsTestFactoryMethod isTestFactoryMethod = new IsTestFactoryMethod(); + + private static final IsTestTemplateMethod isTestTemplateMethod = new IsTestTemplateMethod(); + + public static final Predicate isTestOrTestFactoryOrTestTemplateMethod = isTestMethod.or( + isTestFactoryMethod).or(isTestTemplateMethod); + + private static final IsPotentialTestContainer isPotentialTestContainer = new IsPotentialTestContainer(); + + private static final IsNestedTestClass isNestedTestClass = new IsNestedTestClass(); + + @Override + public boolean test(Class candidate) { + return isPotentialTestContainer.test(candidate) + && (hasTestOrTestFactoryOrTestTemplateMethods(candidate) || hasNestedTests(candidate)); + } + + private boolean hasTestOrTestFactoryOrTestTemplateMethods(Class candidate) { + return ReflectionUtils.isMethodPresent(candidate, isTestOrTestFactoryOrTestTemplateMethod); + } + + private boolean hasNestedTests(Class candidate) { + return !ReflectionUtils.findNestedClasses(candidate, isNestedTestClass).isEmpty(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java new file mode 100644 index 00000000..186d5ab6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestFactory; + +/** + * Test if a method is a JUnit Jupiter {@link TestFactory @TestFactory} method. + * + *

NOTE: this predicate does not check if a candidate method + * has an appropriate return type for a {@code @TestFactory} method. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class IsTestFactoryMethod extends IsTestableMethod { + + public IsTestFactoryMethod() { + super(TestFactory.class, false); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java new file mode 100644 index 00000000..9d30765d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.Test; + +/** + * Test if a method is a JUnit Jupiter {@link Test @Test} method. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class IsTestMethod extends IsTestableMethod { + + public IsTestMethod() { + super(Test.class, true); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java new file mode 100644 index 00000000..70a4391a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestTemplate; + +/** + * Test if a method is a JUnit Jupiter {@link TestTemplate @TestTemplate} method. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class IsTestTemplateMethod extends IsTestableMethod { + + public IsTestTemplateMethod() { + super(TestTemplate.class, true); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java new file mode 100644 index 00000000..e8906f0d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; +import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; +import static org.junit.platform.commons.util.ReflectionUtils.isStatic; +import static org.junit.platform.commons.util.ReflectionUtils.returnsVoid; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.function.Predicate; + +/** + * @since 5.0 + */ +abstract class IsTestableMethod implements Predicate { + + private final Class annotationType; + private final boolean mustReturnVoid; + + IsTestableMethod(Class annotationType, boolean mustReturnVoid) { + this.annotationType = annotationType; + this.mustReturnVoid = mustReturnVoid; + } + + @Override + public boolean test(Method candidate) { + // Please do not collapse the following into a single statement. + if (isStatic(candidate)) { + return false; + } + if (isPrivate(candidate)) { + return false; + } + if (isAbstract(candidate)) { + return false; + } + if (returnsVoid(candidate) != this.mustReturnVoid) { + return false; + } + + return isAnnotated(candidate, this.annotationType); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java new file mode 100644 index 00000000..579eb486 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java @@ -0,0 +1,5 @@ +/** + * Internal predicate classes used by test discovery within the JUnit Jupiter test engine. + */ + +package org.junit.jupiter.engine.discovery.predicates; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java new file mode 100644 index 00000000..2b9dff68 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; + +/** + * Functional interface for registering an {@link AfterEach @AfterEach} method + * as a pseudo-extension. + * + * @since 5.0 + */ +@FunctionalInterface +@API(status = INTERNAL, since = "5.0") +public interface AfterEachMethodAdapter extends Extension { + + void invokeAfterEachMethod(ExtensionContext context, ExtensionRegistry registry) throws Throwable; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java new file mode 100644 index 00000000..709e35e8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; + +/** + * Functional interface for registering a {@link BeforeEach @BeforeEach} method + * as a pseudo-extension. + * + * @since 5.0 + */ +@FunctionalInterface +@API(status = INTERNAL, since = "5.0") +public interface BeforeEachMethodAdapter extends Extension { + + void invokeBeforeEachMethod(ExtensionContext context, ExtensionRegistry registry) throws Throwable; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java new file mode 100644 index 00000000..ddafc0a4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.JUnitException; + +/** + * Thrown if an error is encountered while evaluating an + * {@link ExecutionCondition}. + * + * @since 5.0 + * @see ConditionEvaluator + */ +class ConditionEvaluationException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ConditionEvaluationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java new file mode 100644 index 00000000..fb5e0ff2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static java.lang.String.format; +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.StringUtils; + +/** + * {@code ConditionEvaluator} evaluates {@link ExecutionCondition} extensions. + * + * @since 5.0 + * @see ExecutionCondition + */ +@API(status = INTERNAL, since = "5.0") +public class ConditionEvaluator { + + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + + private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( + "No 'disabled' conditions encountered"); + + /** + * Evaluate all {@link ExecutionCondition} extensions registered for the + * supplied {@link ExtensionContext}. + * + * @param context the current {@code ExtensionContext} + * @return the first disabled {@code ConditionEvaluationResult}, + * or a default enabled {@code ConditionEvaluationResult} if no + * disabled conditions are encountered + */ + public ConditionEvaluationResult evaluate(ExtensionRegistry extensionRegistry, JupiterConfiguration configuration, + ExtensionContext context) { + + // @formatter:off + return extensionRegistry.stream(ExecutionCondition.class) + .filter(configuration.getExecutionConditionFilter()) + .map(condition -> evaluate(condition, context)) + .filter(ConditionEvaluationResult::isDisabled) + .findFirst() + .orElse(ENABLED); + // @formatter:on + } + + private ConditionEvaluationResult evaluate(ExecutionCondition condition, ExtensionContext context) { + try { + ConditionEvaluationResult result = condition.evaluateExecutionCondition(context); + logResult(condition.getClass(), result, context); + return result; + } + catch (Exception ex) { + throw evaluationException(condition.getClass(), ex); + } + } + + private void logResult(Class conditionType, ConditionEvaluationResult result, ExtensionContext context) { + logger.trace(() -> format("Evaluation of condition [%s] on [%s] resulted in: %s", conditionType.getName(), + context.getElement().get(), result)); + } + + private ConditionEvaluationException evaluationException(Class conditionType, Exception ex) { + String cause = StringUtils.isNotBlank(ex.getMessage()) ? ": " + ex.getMessage() : ""; + return new ConditionEvaluationException( + format("Failed to evaluate condition [%s]%s", conditionType.getName(), cause), ex); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java new file mode 100644 index 00000000..4100d667 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static java.util.Collections.unmodifiableList; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.platform.commons.util.ReflectionUtils; + +class ConstructorInvocation implements Invocation, ReflectiveInvocationContext> { + + private final Constructor constructor; + private final Object[] arguments; + + ConstructorInvocation(Constructor constructor, Object[] arguments) { + this.constructor = constructor; + this.arguments = arguments; + } + + @Override + public Class getTargetClass() { + return this.constructor.getDeclaringClass(); + } + + @Override + public Constructor getExecutable() { + return this.constructor; + } + + @Override + public List getArguments() { + return unmodifiableList(Arrays.asList(this.arguments)); + } + + @Override + public Optional getTarget() { + return Optional.empty(); + } + + @Override + public T proceed() { + return ReflectionUtils.newInstance(this.constructor, this.arguments); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java new file mode 100644 index 00000000..2276cc94 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameters; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.9 + */ +@API(status = INTERNAL, since = "5.9") +public class DefaultExecutableInvoker implements ExecutableInvoker { + + private final ExtensionContext extensionContext; + private final ExtensionRegistry extensionRegistry; + + public DefaultExecutableInvoker(ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { + this.extensionContext = extensionContext; + this.extensionRegistry = extensionRegistry; + } + + public DefaultExecutableInvoker(JupiterEngineExecutionContext context) { + this(context.getExtensionContext(), context.getExtensionRegistry()); + } + + @Override + public T invoke(Constructor constructor, Object outerInstance) { + Object[] arguments = resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance), + extensionContext, extensionRegistry); + return ReflectionUtils.newInstance(constructor, arguments); + } + + @Override + public Object invoke(Method method, Object target) { + Object[] arguments = resolveParameters(method, Optional.ofNullable(target), extensionContext, + extensionRegistry); + return ReflectionUtils.invokeMethod(method, target, arguments); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java new file mode 100644 index 00000000..900289b3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * @since 5.0 + */ +class DefaultParameterContext implements ParameterContext { + + private final Parameter parameter; + private final int index; + private final Optional target; + + DefaultParameterContext(Parameter parameter, int index, Optional target) { + Preconditions.condition(index >= 0, "index must be greater than or equal to zero"); + this.parameter = Preconditions.notNull(parameter, "parameter must not be null"); + this.index = index; + this.target = Preconditions.notNull(target, "target must not be null"); + } + + @Override + public Parameter getParameter() { + return this.parameter; + } + + @Override + public int getIndex() { + return this.index; + } + + @Override + public Optional getTarget() { + return this.target; + } + + @Override + public boolean isAnnotated(Class annotationType) { + return AnnotationUtils.isAnnotated(this.parameter, this.index, annotationType); + } + + @Override + public Optional findAnnotation(Class annotationType) { + return AnnotationUtils.findAnnotation(this.parameter, this.index, annotationType); + } + + @Override + public List findRepeatableAnnotations(Class annotationType) { + return AnnotationUtils.findRepeatableAnnotations(this.parameter, this.index, annotationType); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("parameter", this.parameter) + .append("index", this.index) + .append("target", this.target) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java new file mode 100644 index 00000000..dc611f69 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.platform.commons.util.Preconditions; + +@API(status = INTERNAL, since = "5.4") +public class DefaultTestInstances implements TestInstances { + + public static DefaultTestInstances of(Object instance) { + return new DefaultTestInstances(Collections.singletonList(instance)); + } + + public static DefaultTestInstances of(TestInstances testInstances, Object instance) { + List allInstances = new ArrayList<>(testInstances.getAllInstances()); + allInstances.add(instance); + return new DefaultTestInstances(Collections.unmodifiableList(allInstances)); + } + + private final List instances; + + private DefaultTestInstances(List instances) { + this.instances = Preconditions.notEmpty(instances, "instances must not be empty"); + } + + @Override + public Object getInnermostInstance() { + return instances.get(instances.size() - 1); + } + + @Override + public List getEnclosingInstances() { + return instances.subList(0, instances.size() - 1); + } + + @Override + public List getAllInstances() { + return instances; + } + + @Override + public Optional findInstance(Class requiredType) { + Preconditions.notNull(requiredType, "requiredType must not be null"); + ListIterator iterator = instances.listIterator(instances.size()); + while (iterator.hasPrevious()) { + Object instance = iterator.previous(); + if (requiredType.isInstance(instance)) { + return Optional.of(requiredType.cast(instance)); + } + } + return Optional.empty(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java new file mode 100644 index 00000000..133e3d6b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java @@ -0,0 +1,245 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; +import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; +import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo; + +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +import org.junit.jupiter.api.extension.ExtensionContextException; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * {@code ExtensionValuesStore} is used inside implementations of + * {@link ExtensionContext} to store and retrieve values. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class ExtensionValuesStore { + + private static final Comparator REVERSE_INSERT_ORDER = Comparator. comparing( + it -> it.order).reversed(); + + private final AtomicInteger insertOrderSequence = new AtomicInteger(); + private final ConcurrentMap storedValues = new ConcurrentHashMap<>(4); + private final ExtensionValuesStore parentStore; + + public ExtensionValuesStore(ExtensionValuesStore parentStore) { + this.parentStore = parentStore; + } + + /** + * Close all values that implement {@link CloseableResource}. + * + * @implNote Only close values stored in this instance. This implementation + * does not close values in parent stores. + */ + public void closeAllStoredCloseableValues() { + ThrowableCollector throwableCollector = createThrowableCollector(); + storedValues.values().stream() // + .filter(storedValue -> storedValue.evaluateSafely() instanceof CloseableResource) // + .sorted(REVERSE_INSERT_ORDER) // + .map(storedValue -> (CloseableResource) storedValue.evaluate()) // + .forEach(resource -> throwableCollector.execute(resource::close)); + throwableCollector.assertEmpty(); + } + + Object get(Namespace namespace, Object key) { + StoredValue storedValue = getStoredValue(new CompositeKey(namespace, key)); + return (storedValue != null ? storedValue.evaluate() : null); + } + + T get(Namespace namespace, Object key, Class requiredType) { + Object value = get(namespace, key); + return castToRequiredType(key, value, requiredType); + } + + Object getOrComputeIfAbsent(Namespace namespace, K key, Function defaultCreator) { + CompositeKey compositeKey = new CompositeKey(namespace, key); + StoredValue storedValue = getStoredValue(compositeKey); + if (storedValue == null) { + StoredValue newValue = storedValue(new MemoizingSupplier(() -> defaultCreator.apply(key))); + storedValue = Optional.ofNullable(storedValues.putIfAbsent(compositeKey, newValue)).orElse(newValue); + } + return storedValue.evaluate(); + } + + V getOrComputeIfAbsent(Namespace namespace, K key, Function defaultCreator, Class requiredType) { + Object value = getOrComputeIfAbsent(namespace, key, defaultCreator); + return castToRequiredType(key, value, requiredType); + } + + void put(Namespace namespace, Object key, Object value) { + storedValues.put(new CompositeKey(namespace, key), storedValue(() -> value)); + } + + private StoredValue storedValue(Supplier value) { + return new StoredValue(insertOrderSequence.getAndIncrement(), value); + } + + Object remove(Namespace namespace, Object key) { + StoredValue previous = storedValues.remove(new CompositeKey(namespace, key)); + return (previous != null ? previous.evaluate() : null); + } + + T remove(Namespace namespace, Object key, Class requiredType) { + Object value = remove(namespace, key); + return castToRequiredType(key, value, requiredType); + } + + private StoredValue getStoredValue(CompositeKey compositeKey) { + StoredValue storedValue = storedValues.get(compositeKey); + if (storedValue != null) { + return storedValue; + } + if (parentStore != null) { + return parentStore.getStoredValue(compositeKey); + } + return null; + } + + @SuppressWarnings("unchecked") + private T castToRequiredType(Object key, Object value, Class requiredType) { + if (value == null) { + return null; + } + if (isAssignableTo(value, requiredType)) { + if (requiredType.isPrimitive()) { + return (T) getWrapperType(requiredType).cast(value); + } + return requiredType.cast(value); + } + // else + throw new ExtensionContextException( + String.format("Object stored under key [%s] is not of required type [%s]", key, requiredType.getName())); + } + + private static class CompositeKey { + + private final Namespace namespace; + private final Object key; + + private CompositeKey(Namespace namespace, Object key) { + this.namespace = namespace; + this.key = key; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CompositeKey that = (CompositeKey) o; + return this.namespace.equals(that.namespace) && this.key.equals(that.key); + } + + @Override + public int hashCode() { + return Objects.hash(namespace, key); + } + + } + + private static class StoredValue { + + private final int order; + private final Supplier supplier; + + public StoredValue(int order, Supplier supplier) { + this.order = order; + this.supplier = supplier; + } + + private Object evaluateSafely() { + try { + return evaluate(); + } + catch (RuntimeException e) { + return null; + } + } + + private Object evaluate() { + return supplier.get(); + } + + } + + private static class MemoizingSupplier implements Supplier { + + private static final Object NO_VALUE_SET = new Object(); + + private final Lock lock = new ReentrantLock(); + private final Supplier delegate; + private volatile Object value = NO_VALUE_SET; + + private MemoizingSupplier(Supplier delegate) { + this.delegate = delegate; + } + + @Override + public Object get() { + if (value == NO_VALUE_SET) { + computeValue(); + } + if (value instanceof Failure) { + throw ((Failure) value).exception; + } + return value; + } + + private void computeValue() { + lock.lock(); + try { + if (value == NO_VALUE_SET) { + value = delegate.get(); + } + } + catch (RuntimeException e) { + value = new Failure(e); + } + finally { + lock.unlock(); + } + } + + private static class Failure { + + private final RuntimeException exception; + + public Failure(RuntimeException exception) { + this.exception = exception; + } + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java new file mode 100644 index 00000000..b0a1fdb2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameters; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.jupiter.engine.extension.ExtensionRegistry; + +/** + * {@code InterceptingExecutableInvoker} encapsulates the invocation of a + * {@link java.lang.reflect.Executable} (i.e., method or constructor), + * including support for dynamic resolution of method parameters via + * {@link ParameterResolver ParameterResolvers}. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class InterceptingExecutableInvoker { + + private static final InvocationInterceptorChain interceptorChain = new InvocationInterceptorChain(); + + /** + * Invoke the supplied constructor with the supplied outer instance and + * dynamic parameter resolution. + * + * @param constructor the constructor to invoke and resolve parameters for + * @param outerInstance the outer instance to supply as the first argument + * to the constructor; empty, for top-level classes + * @param extensionContext the current {@code ExtensionContext} + * @param extensionRegistry the {@code ExtensionRegistry} to retrieve + * {@code ParameterResolvers} from + * @param interceptorCall the call for intercepting this constructor + * invocation via all registered {@linkplain InvocationInterceptor + * interceptors} + */ + public T invoke(Constructor constructor, Optional outerInstance, ExtensionContext extensionContext, + ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall, T> interceptorCall) { + + Object[] arguments = resolveParameters(constructor, Optional.empty(), outerInstance, extensionContext, + extensionRegistry); + ConstructorInvocation invocation = new ConstructorInvocation<>(constructor, arguments); + return invoke(invocation, invocation, extensionContext, extensionRegistry, interceptorCall); + } + + /** + * Invoke the supplied method with dynamic parameter resolution. + * + * @param method the method to invoke and resolve parameters for + * @param target the target on which the executable will be invoked, + * potentially wrapped in an {@link Optional}; can be {@code null} or an + * empty {@code Optional} for a {@code static} method + * @param extensionContext the current {@code ExtensionContext} + * @param extensionRegistry the {@code ExtensionRegistry} to retrieve + * {@code ParameterResolvers} from + * @param interceptorCall the call for intercepting this method invocation + * via all registered {@linkplain InvocationInterceptor interceptors} + */ + public T invoke(Method method, Object target, ExtensionContext extensionContext, + ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall interceptorCall) { + + @SuppressWarnings("unchecked") + Optional optionalTarget = (target instanceof Optional ? (Optional) target + : Optional.ofNullable(target)); + Object[] arguments = resolveParameters(method, optionalTarget, extensionContext, extensionRegistry); + MethodInvocation invocation = new MethodInvocation<>(method, optionalTarget, arguments); + return invoke(invocation, invocation, extensionContext, extensionRegistry, interceptorCall); + } + + private T invoke(Invocation originalInvocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, + ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall call) { + return interceptorChain.invoke(originalInvocation, extensionRegistry, (interceptor, + wrappedInvocation) -> call.apply(interceptor, wrappedInvocation, invocationContext, extensionContext)); + } + + public interface ReflectiveInterceptorCall { + + T apply(InvocationInterceptor interceptor, Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable; + + static ReflectiveInterceptorCall ofVoidMethod(VoidMethodInterceptorCall call) { + return ((interceptorChain, invocation, invocationContext, extensionContext) -> { + call.apply(interceptorChain, invocation, invocationContext, extensionContext); + return null; + }); + } + + interface VoidMethodInterceptorCall { + void apply(InvocationInterceptor interceptor, Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java new file mode 100644 index 00000000..f622a386 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java @@ -0,0 +1,160 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static java.util.stream.Collectors.joining; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ExceptionUtils; + +@API(status = INTERNAL, since = "5.5") +public class InvocationInterceptorChain { + + public T invoke(Invocation invocation, ExtensionRegistry extensionRegistry, InterceptorCall call) { + List interceptors = extensionRegistry.getExtensions(InvocationInterceptor.class); + if (interceptors.isEmpty()) { + return proceed(invocation); + } + return chainAndInvoke(invocation, call, interceptors); + } + + private T chainAndInvoke(Invocation invocation, InterceptorCall call, + List interceptors) { + + ValidatingInvocation validatingInvocation = new ValidatingInvocation<>(invocation, interceptors); + Invocation chainedInvocation = chainInterceptors(validatingInvocation, call, interceptors); + T result = proceed(chainedInvocation); + validatingInvocation.verifyInvokedAtLeastOnce(); + return result; + } + + private Invocation chainInterceptors(Invocation invocation, InterceptorCall call, + List interceptors) { + + Invocation result = invocation; + ListIterator iterator = interceptors.listIterator(interceptors.size()); + while (iterator.hasPrevious()) { + InvocationInterceptor interceptor = iterator.previous(); + result = new InterceptedInvocation<>(result, call, interceptor); + } + return result; + } + + private T proceed(Invocation invocation) { + try { + return invocation.proceed(); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(t); + } + } + + @FunctionalInterface + public interface InterceptorCall { + + T apply(InvocationInterceptor interceptor, Invocation invocation) throws Throwable; + + static InterceptorCall ofVoid(VoidInterceptorCall call) { + return ((interceptorChain, invocation) -> { + call.apply(interceptorChain, invocation); + return null; + }); + } + + } + + @FunctionalInterface + public interface VoidInterceptorCall { + + void apply(InvocationInterceptor interceptor, Invocation invocation) throws Throwable; + + } + + private static class InterceptedInvocation implements Invocation { + + private final Invocation invocation; + private final InterceptorCall call; + private final InvocationInterceptor interceptor; + + InterceptedInvocation(Invocation invocation, InterceptorCall call, InvocationInterceptor interceptor) { + this.invocation = invocation; + this.call = call; + this.interceptor = interceptor; + } + + @Override + public T proceed() throws Throwable { + return call.apply(interceptor, invocation); + } + + @Override + public void skip() { + invocation.skip(); + } + } + + private static class ValidatingInvocation implements Invocation { + + private static final Logger logger = LoggerFactory.getLogger(ValidatingInvocation.class); + + private final AtomicBoolean invokedOrSkipped = new AtomicBoolean(); + private final Invocation delegate; + private final List interceptors; + + ValidatingInvocation(Invocation delegate, List interceptors) { + this.delegate = delegate; + this.interceptors = interceptors; + } + + @Override + public T proceed() throws Throwable { + markInvokedOrSkipped(); + return delegate.proceed(); + } + + @Override + public void skip() { + logger.debug(() -> "The invocation is skipped"); + markInvokedOrSkipped(); + delegate.skip(); + } + + private void markInvokedOrSkipped() { + if (!invokedOrSkipped.compareAndSet(false, true)) { + fail("Chain of InvocationInterceptors called invocation multiple times instead of just once"); + } + } + + void verifyInvokedAtLeastOnce() { + if (!invokedOrSkipped.get()) { + fail("Chain of InvocationInterceptors never called invocation"); + } + } + + private void fail(String prefix) { + String commaSeparatedInterceptorClasses = interceptors.stream().map(Object::getClass).map( + Class::getName).collect(joining(", ")); + throw new JUnitException(prefix + ": " + commaSeparatedInterceptorClasses); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java new file mode 100644 index 00000000..a31c9c02 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java @@ -0,0 +1,195 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class JupiterEngineExecutionContext implements EngineExecutionContext { + + private static final Logger logger = LoggerFactory.getLogger(JupiterEngineExecutionContext.class); + + private final State state; + + // The following is not "cloneable" State. + private boolean beforeAllCallbacksExecuted = false; + private boolean beforeAllMethodsExecuted = false; + + public JupiterEngineExecutionContext(EngineExecutionListener executionListener, + JupiterConfiguration configuration) { + this(new State(executionListener, configuration)); + } + + private JupiterEngineExecutionContext(State state) { + this.state = state; + } + + public void close() throws Exception { + ExtensionContext extensionContext = getExtensionContext(); + if (extensionContext instanceof AutoCloseable) { + try { + ((AutoCloseable) extensionContext).close(); + } + catch (Exception e) { + logger.error(e, () -> "Caught exception while closing extension context: " + extensionContext); + throw e; + } + } + } + + public EngineExecutionListener getExecutionListener() { + return this.state.executionListener; + } + + public JupiterConfiguration getConfiguration() { + return this.state.configuration; + } + + public TestInstancesProvider getTestInstancesProvider() { + return this.state.testInstancesProvider; + } + + public MutableExtensionRegistry getExtensionRegistry() { + return this.state.extensionRegistry; + } + + public ExtensionContext getExtensionContext() { + return this.state.extensionContext; + } + + public ThrowableCollector getThrowableCollector() { + return this.state.throwableCollector; + } + + /** + * Track that an attempt was made to execute {@code BeforeAllCallback} extensions. + * + * @since 5.3 + */ + public void beforeAllCallbacksExecuted(boolean beforeAllCallbacksExecuted) { + this.beforeAllCallbacksExecuted = beforeAllCallbacksExecuted; + } + + /** + * @return {@code true} if an attempt was made to execute {@code BeforeAllCallback} + * extensions + * @since 5.3 + */ + public boolean beforeAllCallbacksExecuted() { + return beforeAllCallbacksExecuted; + } + + /** + * Track that an attempt was made to execute {@code @BeforeAll} methods. + */ + public void beforeAllMethodsExecuted(boolean beforeAllMethodsExecuted) { + this.beforeAllMethodsExecuted = beforeAllMethodsExecuted; + } + + /** + * @return {@code true} if an attempt was made to execute {@code @BeforeAll} + * methods + */ + public boolean beforeAllMethodsExecuted() { + return this.beforeAllMethodsExecuted; + } + + public Builder extend() { + return new Builder(this.state); + } + + private static final class State implements Cloneable { + + final EngineExecutionListener executionListener; + final JupiterConfiguration configuration; + TestInstancesProvider testInstancesProvider; + MutableExtensionRegistry extensionRegistry; + ExtensionContext extensionContext; + ThrowableCollector throwableCollector; + + State(EngineExecutionListener executionListener, JupiterConfiguration configuration) { + this.executionListener = executionListener; + this.configuration = configuration; + } + + @Override + public State clone() { + try { + return (State) super.clone(); + } + catch (CloneNotSupportedException e) { + throw new JUnitException("State could not be cloned", e); + } + } + + } + + public static class Builder { + + private State originalState; + private State newState = null; + + private Builder(State originalState) { + this.originalState = originalState; + } + + public Builder withTestInstancesProvider(TestInstancesProvider testInstancesProvider) { + newState().testInstancesProvider = testInstancesProvider; + return this; + } + + public Builder withExtensionRegistry(MutableExtensionRegistry extensionRegistry) { + newState().extensionRegistry = extensionRegistry; + return this; + } + + public Builder withExtensionContext(ExtensionContext extensionContext) { + newState().extensionContext = extensionContext; + return this; + } + + public Builder withThrowableCollector(ThrowableCollector throwableCollector) { + newState().throwableCollector = throwableCollector; + return this; + } + + public JupiterEngineExecutionContext build() { + if (newState != null) { + originalState = newState; + newState = null; + } + return new JupiterEngineExecutionContext(originalState); + } + + private State newState() { + if (newState == null) { + this.newState = originalState.clone(); + } + return newState; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java new file mode 100644 index 00000000..a8bb6ae7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static java.util.Collections.unmodifiableList; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.platform.commons.util.ReflectionUtils; + +class MethodInvocation implements Invocation, ReflectiveInvocationContext { + + private final Method method; + private final Optional target; + private final Object[] arguments; + + MethodInvocation(Method method, Optional target, Object[] arguments) { + this.method = method; + this.target = target; + this.arguments = arguments; + } + + @Override + public Class getTargetClass() { + return this.target.> map(Object::getClass).orElseGet(this.method::getDeclaringClass); + } + + @Override + @SuppressWarnings("unchecked") + public Optional getTarget() { + return this.target; + } + + @Override + public Method getExecutable() { + return this.method; + } + + @Override + public List getArguments() { + return unmodifiableList(Arrays.asList(this.arguments)); + } + + @Override + @SuppressWarnings("unchecked") + public T proceed() { + return (T) ReflectionUtils.invokeMethod(this.method, this.target.orElse(null), this.arguments); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java new file mode 100644 index 00000000..1b563045 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class NamespaceAwareStore implements Store { + + private final ExtensionValuesStore valuesStore; + private final Namespace namespace; + + public NamespaceAwareStore(ExtensionValuesStore valuesStore, Namespace namespace) { + this.valuesStore = valuesStore; + this.namespace = namespace; + } + + @Override + public Object get(Object key) { + Preconditions.notNull(key, "key must not be null"); + return this.valuesStore.get(this.namespace, key); + } + + @Override + public T get(Object key, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return this.valuesStore.get(this.namespace, key, requiredType); + } + + @Override + public Object getOrComputeIfAbsent(K key, Function defaultCreator) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); + return this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator); + } + + @Override + public V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType); + } + + @Override + public void put(Object key, Object value) { + Preconditions.notNull(key, "key must not be null"); + this.valuesStore.put(this.namespace, key, value); + } + + @Override + public Object remove(Object key) { + Preconditions.notNull(key, "key must not be null"); + return this.valuesStore.remove(this.namespace, key); + } + + @Override + public T remove(Object key, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return this.valuesStore.remove(this.namespace, key, requiredType); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java new file mode 100644 index 00000000..87e121a3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java @@ -0,0 +1,194 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * {@code ParameterResolutionUtils} provides support for dynamic resolution + * of executable parameters via {@link ParameterResolver ParameterResolvers}. + * + * @since 5.9 + */ +@API(status = INTERNAL, since = "5.9") +public class ParameterResolutionUtils { + + private static final Logger logger = LoggerFactory.getLogger(ParameterResolutionUtils.class); + + /** + * Resolve the array of parameters for the supplied method and target. + * + * @param method the method for which to resolve parameters + * @param target an {@code Optional} containing the target on which the + * executable will be invoked; never {@code null} but should be empty for + * static methods and constructors + * @param extensionContext the current {@code ExtensionContext} + * @param extensionRegistry the {@code ExtensionRegistry} to retrieve + * {@code ParameterResolvers} from + * @return the array of Objects to be used as parameters in the executable + * invocation; never {@code null} though potentially empty + */ + public static Object[] resolveParameters(Method method, Optional target, ExtensionContext extensionContext, + ExtensionRegistry extensionRegistry) { + + return resolveParameters(method, target, Optional.empty(), extensionContext, extensionRegistry); + } + + /** + * Resolve the array of parameters for the supplied executable, target, and + * outer instance. + * + * @param executable the executable for which to resolve parameters + * @param target an {@code Optional} containing the target on which the + * executable will be invoked; never {@code null} but should be empty for + * static methods and constructors + * @param outerInstance the outer instance that will be supplied as the + * first argument to a constructor for an inner class; should be {@code null} + * for methods and constructors for top-level or static classes + * @param extensionContext the current {@code ExtensionContext} + * @param extensionRegistry the {@code ExtensionRegistry} to retrieve + * {@code ParameterResolvers} from + * @return the array of Objects to be used as parameters in the executable + * invocation; never {@code null} though potentially empty + */ + public static Object[] resolveParameters(Executable executable, Optional target, + Optional outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { + + Preconditions.notNull(target, "target must not be null"); + + Parameter[] parameters = executable.getParameters(); + Object[] values = new Object[parameters.length]; + int start = 0; + + // Ensure that the outer instance is resolved as the first parameter if + // the executable is a constructor for an inner class. + if (outerInstance.isPresent()) { + values[0] = outerInstance.get(); + start = 1; + } + + // Resolve remaining parameters dynamically + for (int i = start; i < parameters.length; i++) { + ParameterContext parameterContext = new DefaultParameterContext(parameters[i], i, target); + values[i] = resolveParameter(parameterContext, executable, extensionContext, extensionRegistry); + } + return values; + } + + private static Object resolveParameter(ParameterContext parameterContext, Executable executable, + ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { + + try { + // @formatter:off + List matchingResolvers = extensionRegistry.stream(ParameterResolver.class) + .filter(resolver -> resolver.supportsParameter(parameterContext, extensionContext)) + .collect(toList()); + // @formatter:on + + if (matchingResolvers.isEmpty()) { + throw new ParameterResolutionException( + String.format("No ParameterResolver registered for parameter [%s] in %s [%s].", + parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); + } + + if (matchingResolvers.size() > 1) { + // @formatter:off + String resolvers = matchingResolvers.stream() + .map(StringUtils::defaultToString) + .collect(joining(", ")); + // @formatter:on + throw new ParameterResolutionException( + String.format("Discovered multiple competing ParameterResolvers for parameter [%s] in %s [%s]: %s", + parameterContext.getParameter(), asLabel(executable), executable.toGenericString(), resolvers)); + } + + ParameterResolver resolver = matchingResolvers.get(0); + Object value = resolver.resolveParameter(parameterContext, extensionContext); + validateResolvedType(parameterContext.getParameter(), value, executable, resolver); + + logger.trace(() -> String.format( + "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] in %s [%s].", + resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), + parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); + + return value; + } + catch (ParameterResolutionException ex) { + throw ex; + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + + String message = String.format("Failed to resolve parameter [%s] in %s [%s]", + parameterContext.getParameter(), asLabel(executable), executable.toGenericString()); + + if (StringUtils.isNotBlank(throwable.getMessage())) { + message += ": " + throwable.getMessage(); + } + + throw new ParameterResolutionException(message, throwable); + } + } + + private static void validateResolvedType(Parameter parameter, Object value, Executable executable, + ParameterResolver resolver) { + + Class type = parameter.getType(); + + // Note: null is permissible as a resolved value but only for non-primitive types. + if (!isAssignableTo(value, type)) { + String message; + if (value == null && type.isPrimitive()) { + message = String.format( + "ParameterResolver [%s] resolved a null value for parameter [%s] " + + "in %s [%s], but a primitive of type [%s] is required.", + resolver.getClass().getName(), parameter, asLabel(executable), executable.toGenericString(), + type.getName()); + } + else { + message = String.format( + "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] " + + "in %s [%s], but a value assignment compatible with [%s] is required.", + resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameter, + asLabel(executable), executable.toGenericString(), type.getName()); + } + + throw new ParameterResolutionException(message); + } + } + + private static String asLabel(Executable executable) { + return executable instanceof Constructor ? "constructor" : "method"; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java new file mode 100644 index 00000000..1f5819a6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.extension.ExtensionRegistrar; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * @since 5.0 + */ +@FunctionalInterface +@API(status = INTERNAL, since = "5.0") +public interface TestInstancesProvider { + + default TestInstances getTestInstances(MutableExtensionRegistry extensionRegistry, + ThrowableCollector throwableCollector) { + return getTestInstances(extensionRegistry, extensionRegistry, throwableCollector); + } + + TestInstances getTestInstances(ExtensionRegistry extensionRegistry, ExtensionRegistrar extensionRegistrar, + ThrowableCollector throwableCollector); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java new file mode 100644 index 00000000..028111e0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java @@ -0,0 +1,5 @@ +/** + * Internal classes for test execution within the JUnit Jupiter test engine. + */ + +package org.junit.jupiter.engine.execution; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java new file mode 100644 index 00000000..6279cac6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.reflect.AnnotatedElement; + +import org.junit.jupiter.api.Disabled; +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.util.StringUtils; + +/** + * {@link ExecutionCondition} that supports the {@code @Disabled} annotation. + * + * @since 5.0 + * @see Disabled + * @see #evaluateExecutionCondition(ExtensionContext) + */ +class DisabledCondition implements ExecutionCondition { + + private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( + "@Disabled is not present"); + + /** + * Containers/tests are disabled if {@code @Disabled} is present on the test + * class or method. + */ + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + AnnotatedElement element = context.getElement().orElse(null); + return findAnnotation(element, Disabled.class) // + .map(annotation -> toResult(element, annotation)) // + .orElse(ENABLED); + } + + private ConditionEvaluationResult toResult(AnnotatedElement element, Disabled annotation) { + String value = annotation.value(); + String reason = StringUtils.isNotBlank(value) ? value : element + " is @Disabled"; + return ConditionEvaluationResult.disabled(reason); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java new file mode 100644 index 00000000..e555aecd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.Extension; + +/** + * An {@code ExtensionRegistrar} is used to register extensions. + * + * @since 5.5 + */ +@API(status = INTERNAL, since = "5.5") +public interface ExtensionRegistrar { + + /** + * Instantiate an extension of the given type using its default constructor + * and register it in the registry. + * + *

A new {@link Extension} should not be registered if an extension of the + * given type already exists in the registry or a parent registry. + * + * @param extensionType the type of extension to register + * @since 5.8 + */ + void registerExtension(Class extensionType); + + /** + * Register the supplied {@link Extension}, without checking if an extension + * of that type has already been registered. + * + *

Semantics for Source

+ * + *

If an extension is registered declaratively via + * {@link org.junit.jupiter.api.extension.ExtendWith @ExtendWith}, the + * {@code source} and the {@code extension} should be the same object. + * However, if an extension is registered programmatically via + * {@link org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension}, + * the {@code source} object should be the {@link java.lang.reflect.Field} + * that is annotated with {@code @RegisterExtension}. Similarly, if an + * extension is registered programmatically as a lambda expression + * or method reference, the {@code source} object should be the underlying + * {@link java.lang.reflect.Method} that implements the extension API. + * + * @param extension the extension to register; never {@code null} + * @param source the source of the extension; never {@code null} + */ + void registerExtension(Extension extension, Object source); + + /** + * Register the supplied {@link Extension} as a synthetic extension, + * without checking if an extension of that type has already been registered. + * + * @param extension the extension to register; never {@code null} + * @param source the source of the extension; never {@code null} + * @since 5.8 + * @see #registerExtension(Extension, Object) + */ + void registerSyntheticExtension(Extension extension, Object source); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java new file mode 100644 index 00000000..d859d6b8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.stream.Collectors.toCollection; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.Extension; + +/** + * An {@code ExtensionRegistry} holds all registered extensions (i.e. + * instances of {@link Extension}) for a given + * {@link org.junit.platform.engine.support.hierarchical.Node}. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public interface ExtensionRegistry { + + /** + * Stream all {@code Extensions} of the specified type that are present + * in this registry or one of its ancestors. + * + * @param extensionType the type of {@link Extension} to stream + * @see #getReversedExtensions(Class) + * @see #getExtensions(Class) + */ + Stream stream(Class extensionType); + + /** + * Get all {@code Extensions} of the specified type that are present + * in this registry or one of its ancestors. + * + * @param extensionType the type of {@link Extension} to get + * @see #getReversedExtensions(Class) + * @see #stream(Class) + */ + default List getExtensions(Class extensionType) { + return stream(extensionType).collect(toCollection(ArrayList::new)); + } + + /** + * Get all {@code Extensions} of the specified type that are present + * in this registry or one of its ancestors, in reverse order. + * + * @param extensionType the type of {@link Extension} to get + * @see #getExtensions(Class) + * @see #stream(Class) + */ + default List getReversedExtensions(Class extensionType) { + List extensions = getExtensions(extensionType); + Collections.reverse(extensions); + return extensions; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java new file mode 100644 index 00000000..2856cdc3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java @@ -0,0 +1,209 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.stream.Stream.concat; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Default, mutable implementation of {@link ExtensionRegistry}. + * + *

A registry has a reference to its parent registry, and all lookups are + * performed first in the current registry itself and then recursively in its + * ancestors. + * + * @since 5.5 + */ +@API(status = INTERNAL, since = "5.5") +public class MutableExtensionRegistry implements ExtensionRegistry, ExtensionRegistrar { + + private static final Logger logger = LoggerFactory.getLogger(MutableExtensionRegistry.class); + + private static final List DEFAULT_STATELESS_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(// + new DisabledCondition(), // + new TimeoutExtension(), // + new RepeatedTestExtension(), // + new TestInfoParameterResolver(), // + new TestReporterParameterResolver())); + + /** + * Factory for creating and populating a new root registry with the default + * extensions. + * + *

If the {@link org.junit.jupiter.engine.Constants#EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME} + * configuration parameter has been set to {@code true}, extensions will be + * auto-detected using Java's {@link ServiceLoader} mechanism and automatically + * registered after the default extensions. + * + * @param configuration configuration parameters used to retrieve the extension + * auto-detection flag; never {@code null} + * @return a new {@code ExtensionRegistry}; never {@code null} + */ + public static MutableExtensionRegistry createRegistryWithDefaultExtensions(JupiterConfiguration configuration) { + MutableExtensionRegistry extensionRegistry = new MutableExtensionRegistry(null); + + DEFAULT_STATELESS_EXTENSIONS.forEach(extensionRegistry::registerDefaultExtension); + + extensionRegistry.registerDefaultExtension(new TempDirectory(configuration)); + + if (configuration.isExtensionAutoDetectionEnabled()) { + registerAutoDetectedExtensions(extensionRegistry); + } + + return extensionRegistry; + } + + private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry) { + ServiceLoader.load(Extension.class, ClassLoaderUtils.getDefaultClassLoader())// + .forEach(extensionRegistry::registerAutoDetectedExtension); + } + + /** + * Factory for creating and populating a new registry from a list of + * extension types and a parent registry. + * + * @param parentRegistry the parent registry + * @param extensionTypes the types of extensions to be registered in + * the new registry + * @return a new {@code ExtensionRegistry}; never {@code null} + */ + public static MutableExtensionRegistry createRegistryFrom(MutableExtensionRegistry parentRegistry, + Stream> extensionTypes) { + + Preconditions.notNull(parentRegistry, "parentRegistry must not be null"); + + MutableExtensionRegistry registry = new MutableExtensionRegistry(parentRegistry); + extensionTypes.forEach(registry::registerExtension); + return registry; + } + + private final MutableExtensionRegistry parent; + + private final Set> registeredExtensionTypes = new LinkedHashSet<>(); + + private final List registeredExtensions = new ArrayList<>(); + + private MutableExtensionRegistry(MutableExtensionRegistry parent) { + this.parent = parent; + } + + @Override + public Stream stream(Class extensionType) { + if (this.parent == null) { + return streamLocal(extensionType); + } + return concat(this.parent.stream(extensionType), streamLocal(extensionType)); + } + + /** + * Stream all {@code Extensions} of the specified type that are present + * in this registry. + * + *

Extensions in ancestors are ignored. + * + * @param extensionType the type of {@link Extension} to stream + * @see #getReversedExtensions(Class) + */ + private Stream streamLocal(Class extensionType) { + // @formatter:off + return this.registeredExtensions.stream() + .filter(extensionType::isInstance) + .map(extensionType::cast); + // @formatter:on + } + + @Override + public void registerExtension(Class extensionType) { + if (!isAlreadyRegistered(extensionType)) { + registerLocalExtension(ReflectionUtils.newInstance(extensionType)); + } + } + + /** + * Determine if the supplied type is already registered in this registry or in a + * parent registry. + */ + private boolean isAlreadyRegistered(Class extensionType) { + return (this.registeredExtensionTypes.contains(extensionType) + || (this.parent != null && this.parent.isAlreadyRegistered(extensionType))); + } + + @Override + public void registerExtension(Extension extension, Object source) { + Preconditions.notNull(source, "source must not be null"); + registerExtension("local", extension, source); + } + + @Override + public void registerSyntheticExtension(Extension extension, Object source) { + registerExtension("synthetic", extension, source); + } + + private void registerDefaultExtension(Extension extension) { + registerExtension("default", extension); + } + + private void registerAutoDetectedExtension(Extension extension) { + registerExtension("auto-detected", extension); + } + + private void registerLocalExtension(Extension extension) { + registerExtension("local", extension); + } + + private void registerExtension(String category, Extension extension) { + registerExtension(category, extension, null); + } + + private void registerExtension(String category, Extension extension, Object source) { + Preconditions.notBlank(category, "category must not be null or blank"); + Preconditions.notNull(extension, "Extension must not be null"); + + logger.trace( + () -> String.format("Registering %s extension [%s]%s", category, extension, buildSourceInfo(source))); + + this.registeredExtensions.add(extension); + this.registeredExtensionTypes.add(extension.getClass()); + } + + private String buildSourceInfo(Object source) { + if (source == null) { + return ""; + } + if (source instanceof Member) { + Member member = (Member) source; + Object type = (member instanceof Method ? "method" : "field"); + source = String.format("%s %s.%s", type, member.getDeclaringClass().getName(), member.getName()); + } + return " from source [" + source + "]"; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java new file mode 100644 index 00000000..1b2657fd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.RepeatedTest.CURRENT_REPETITION_PLACEHOLDER; +import static org.junit.jupiter.api.RepeatedTest.DISPLAY_NAME_PLACEHOLDER; +import static org.junit.jupiter.api.RepeatedTest.TOTAL_REPETITIONS_PLACEHOLDER; + +import org.junit.jupiter.api.RepeatedTest; + +/** + * Display name formatter for a {@link RepeatedTest @RepeatedTest}. + * + * @since 5.0 + */ +class RepeatedTestDisplayNameFormatter { + + private final String pattern; + private final String displayName; + + RepeatedTestDisplayNameFormatter(String pattern, String displayName) { + this.pattern = pattern; + this.displayName = displayName; + } + + String format(int currentRepetition, int totalRepetitions) { + return this.pattern// + .replace(DISPLAY_NAME_PLACEHOLDER, this.displayName)// + .replace(CURRENT_REPETITION_PLACEHOLDER, String.valueOf(currentRepetition))// + .replace(TOTAL_REPETITIONS_PLACEHOLDER, String.valueOf(totalRepetitions)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java new file mode 100644 index 00000000..ce561b58 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; + +import java.lang.reflect.Method; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code TestTemplateInvocationContextProvider} that supports the + * {@link RepeatedTest @RepeatedTest} annotation. + * + * @since 5.0 + */ +class RepeatedTestExtension implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return isAnnotated(context.getTestMethod(), RepeatedTest.class); + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + Method testMethod = context.getRequiredTestMethod(); + String displayName = context.getDisplayName(); + RepeatedTest repeatedTest = AnnotationUtils.findAnnotation(testMethod, RepeatedTest.class).get(); + int totalRepetitions = totalRepetitions(repeatedTest, testMethod); + RepeatedTestDisplayNameFormatter formatter = displayNameFormatter(repeatedTest, testMethod, displayName); + + // @formatter:off + return IntStream + .rangeClosed(1, totalRepetitions) + .mapToObj(repetition -> new RepeatedTestInvocationContext(repetition, totalRepetitions, formatter)); + // @formatter:on + } + + private int totalRepetitions(RepeatedTest repeatedTest, Method method) { + int repetitions = repeatedTest.value(); + Preconditions.condition(repetitions > 0, () -> String.format( + "Configuration error: @RepeatedTest on method [%s] must be declared with a positive 'value'.", method)); + return repetitions; + } + + private RepeatedTestDisplayNameFormatter displayNameFormatter(RepeatedTest repeatedTest, Method method, + String displayName) { + String pattern = Preconditions.notBlank(repeatedTest.name().trim(), () -> String.format( + "Configuration error: @RepeatedTest on method [%s] must be declared with a non-empty name.", method)); + return new RepeatedTestDisplayNameFormatter(pattern, displayName); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java new file mode 100644 index 00000000..1ec271b7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.Collections.singletonList; + +import java.util.List; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; + +/** + * {@code TestTemplateInvocationContext} for a {@link org.junit.jupiter.api.RepeatedTest @RepeatedTest}. + * + * @since 5.0 + */ +class RepeatedTestInvocationContext implements TestTemplateInvocationContext { + + private final int currentRepetition; + private final int totalRepetitions; + private final RepeatedTestDisplayNameFormatter formatter; + + public RepeatedTestInvocationContext(int currentRepetition, int totalRepetitions, + RepeatedTestDisplayNameFormatter formatter) { + + this.currentRepetition = currentRepetition; + this.totalRepetitions = totalRepetitions; + this.formatter = formatter; + } + + @Override + public String getDisplayName(int invocationIndex) { + return this.formatter.format(this.currentRepetition, this.totalRepetitions); + } + + @Override + public List getAdditionalExtensions() { + return singletonList(new RepetitionInfoParameterResolver(this.currentRepetition, this.totalRepetitions)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java new file mode 100644 index 00000000..be104eed --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import org.junit.jupiter.api.RepetitionInfo; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@link ParameterResolver} that resolves the {@link RepetitionInfo} for + * the currently executing {@code @RepeatedTest}. + * + * @since 5.0 + */ +class RepetitionInfoParameterResolver implements ParameterResolver { + + private final int currentRepetition; + private final int totalRepetitions; + + public RepetitionInfoParameterResolver(int currentRepetition, int totalRepetitions) { + this.currentRepetition = currentRepetition; + this.totalRepetitions = totalRepetitions; + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return (parameterContext.getParameter().getType() == RepetitionInfo.class); + } + + @Override + public RepetitionInfo resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return new DefaultRepetitionInfo(this.currentRepetition, this.totalRepetitions); + } + + private static class DefaultRepetitionInfo implements RepetitionInfo { + + private final int currentRepetition; + private final int totalRepetitions; + + DefaultRepetitionInfo(int currentRepetition, int totalRepetitions) { + this.currentRepetition = currentRepetition; + this.totalRepetitions = totalRepetitions; + } + + @Override + public int getCurrentRepetition() { + return this.currentRepetition; + } + + @Override + public int getTotalRepetitions() { + return this.totalRepetitions; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("currentRepetition", this.currentRepetition) + .append("totalRepetitions", this.totalRepetitions) + .toString(); + // @formatter:on + } + + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java new file mode 100644 index 00000000..e620151d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.function.Supplier; + +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * @since 5.5 + */ +class SameThreadTimeoutInvocation implements Invocation { + + private final Invocation delegate; + private final TimeoutDuration timeout; + private final ScheduledExecutorService executor; + private final Supplier descriptionSupplier; + + SameThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, ScheduledExecutorService executor, + Supplier descriptionSupplier) { + this.delegate = delegate; + this.timeout = timeout; + this.executor = executor; + this.descriptionSupplier = descriptionSupplier; + } + + @Override + public T proceed() throws Throwable { + InterruptTask interruptTask = new InterruptTask(Thread.currentThread()); + ScheduledFuture future = executor.schedule(interruptTask, timeout.getValue(), timeout.getUnit()); + Throwable failure = null; + T result = null; + try { + result = delegate.proceed(); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + failure = t; + } + finally { + boolean cancelled = future.cancel(false); + if (!cancelled) { + future.get(); + } + if (interruptTask.executed) { + Thread.interrupted(); + failure = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, failure); + } + } + if (failure != null) { + throw failure; + } + return result; + } + + static class InterruptTask implements Runnable { + + private final Thread thread; + private volatile boolean executed; + + InterruptTask(Thread thread) { + this.thread = thread; + } + + @Override + public void run() { + executed = true; + thread.interrupt(); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java new file mode 100644 index 00000000..9453577a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + +import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; + +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; + +/** + * @since 5.9 + */ +class SeparateThreadTimeoutInvocation implements Invocation { + + private final Invocation delegate; + private final TimeoutDuration timeout; + private final Supplier descriptionSupplier; + + SeparateThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, + Supplier descriptionSupplier) { + this.delegate = delegate; + this.timeout = timeout; + this.descriptionSupplier = descriptionSupplier; + } + + @Override + public T proceed() throws Throwable { + return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier, + (__, messageSupplier, cause) -> { + TimeoutException exception = TimeoutExceptionFactory.create(messageSupplier.get(), timeout, null); + exception.initCause(cause); + return exception; + }); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java new file mode 100644 index 00000000..624c9adb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java @@ -0,0 +1,393 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.nio.file.FileVisitResult.CONTINUE; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.io.CleanupMode.DEFAULT; +import static org.junit.jupiter.api.io.CleanupMode.NEVER; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Predicate; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.engine.config.EnumConfigurationParameterConverter; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code TempDirectory} is a JUnit Jupiter extension that creates and cleans + * up temporary directories if field in a test class or a parameter in a + * lifecycle method or test method is annotated with {@code @TempDir}. + * + *

Consult the Javadoc for {@link TempDir} for details on the contract. + * + * @since 5.4 + * @see TempDir + * @see Files#createTempDirectory + */ +class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { + + static final Namespace NAMESPACE = Namespace.create(TempDirectory.class); + private static final String KEY = "temp.dir"; + private static final String TEMP_DIR_PREFIX = "junit"; + + // for testing purposes + static final String FILE_OPERATIONS_KEY = "file.operations"; + + private final JupiterConfiguration configuration; + + public TempDirectory(JupiterConfiguration configuration) { + this.configuration = configuration; + } + + /** + * Perform field injection for non-private, {@code static} fields (i.e., + * class fields) of type {@link Path} or {@link File} that are annotated with + * {@link TempDir @TempDir}. + */ + @Override + public void beforeAll(ExtensionContext context) { + injectStaticFields(context, context.getRequiredTestClass()); + } + + /** + * Perform field injection for non-private, non-static fields (i.e., + * instance fields) of type {@link Path} or {@link File} that are annotated + * with {@link TempDir @TempDir}. + */ + @Override + public void beforeEach(ExtensionContext context) { + context.getRequiredTestInstances().getAllInstances() // + .forEach(instance -> injectInstanceFields(context, instance)); + } + + private void injectStaticFields(ExtensionContext context, Class testClass) { + injectFields(context, null, testClass, ReflectionUtils::isStatic); + } + + private void injectInstanceFields(ExtensionContext context, Object instance) { + injectFields(context, instance, instance.getClass(), ReflectionUtils::isNotStatic); + } + + private void injectFields(ExtensionContext context, Object testInstance, Class testClass, + Predicate predicate) { + + findAnnotatedFields(testClass, TempDir.class, predicate).forEach(field -> { + assertNonFinalField(field); + assertSupportedType("field", field.getType()); + + try { + CleanupMode cleanupMode = determineCleanupModeForField(field); + makeAccessible(field).set(testInstance, getPathOrFile(field, field.getType(), cleanupMode, context)); + } + catch (Throwable t) { + ExceptionUtils.throwAsUncheckedException(t); + } + }); + } + + /** + * Determine if the {@link Parameter} in the supplied {@link ParameterContext} + * is annotated with {@link TempDir @TempDir}. + */ + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + boolean annotated = parameterContext.isAnnotated(TempDir.class); + if (annotated && parameterContext.getDeclaringExecutable() instanceof Constructor) { + throw new ParameterResolutionException( + "@TempDir is not supported on constructor parameters. Please use field injection instead."); + } + return annotated; + } + + /** + * Resolve the current temporary directory for the {@link Parameter} in the + * supplied {@link ParameterContext}. + */ + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + Class parameterType = parameterContext.getParameter().getType(); + assertSupportedType("parameter", parameterType); + CleanupMode cleanupMode = determineCleanupModeForParameter(parameterContext); + return getPathOrFile(parameterContext.getParameter(), parameterType, cleanupMode, extensionContext); + } + + private CleanupMode determineCleanupModeForField(Field field) { + TempDir tempDir = findAnnotation(field, TempDir.class).orElseThrow( + () -> new JUnitException("Field " + field + " must be annotated with @TempDir")); + return determineCleanupMode(tempDir); + } + + private CleanupMode determineCleanupModeForParameter(ParameterContext parameterContext) { + TempDir tempDir = parameterContext.findAnnotation(TempDir.class).orElseThrow(() -> new JUnitException( + "Parameter " + parameterContext.getParameter() + " must be annotated with @TempDir")); + return determineCleanupMode(tempDir); + } + + private CleanupMode determineCleanupMode(TempDir tempDir) { + CleanupMode cleanupMode = tempDir.cleanup(); + return cleanupMode == DEFAULT ? this.configuration.getDefaultTempDirCleanupMode() : cleanupMode; + } + + private void assertNonFinalField(Field field) { + if (ReflectionUtils.isFinal(field)) { + throw new ExtensionConfigurationException("@TempDir field [" + field + "] must not be declared as final."); + } + } + + private void assertSupportedType(String target, Class type) { + if (type != Path.class && type != File.class) { + throw new ExtensionConfigurationException("Can only resolve @TempDir " + target + " of type " + + Path.class.getName() + " or " + File.class.getName() + " but was: " + type.getName()); + } + } + + private Object getPathOrFile(AnnotatedElement sourceElement, Class type, CleanupMode cleanupMode, + ExtensionContext extensionContext) { + Namespace namespace = getScope(extensionContext) == Scope.PER_DECLARATION // + ? NAMESPACE.append(sourceElement) // + : NAMESPACE; + Path path = extensionContext.getStore(namespace) // + .getOrComputeIfAbsent(KEY, __ -> createTempDir(cleanupMode, extensionContext), CloseablePath.class) // + .get(); + + return (type == Path.class) ? path : path.toFile(); + } + + @SuppressWarnings("deprecation") + private Scope getScope(ExtensionContext context) { + return context.getRoot().getStore(NAMESPACE).getOrComputeIfAbsent( // + Scope.class, // + __ -> new EnumConfigurationParameterConverter<>(Scope.class, "@TempDir scope") // + .get(TempDir.SCOPE_PROPERTY_NAME, context::getConfigurationParameter, Scope.PER_DECLARATION), // + Scope.class // + ); + } + + static CloseablePath createTempDir(CleanupMode cleanupMode, ExtensionContext executionContext) { + try { + return new CloseablePath(Files.createTempDirectory(TEMP_DIR_PREFIX), cleanupMode, executionContext); + } + catch (Exception ex) { + throw new ExtensionConfigurationException("Failed to create default temp directory", ex); + } + } + + static class CloseablePath implements CloseableResource { + + private static final Logger logger = LoggerFactory.getLogger(CloseablePath.class); + + private final Path dir; + private final CleanupMode cleanupMode; + private final ExtensionContext executionContext; + + CloseablePath(Path dir, CleanupMode cleanupMode, ExtensionContext executionContext) { + this.dir = dir; + this.cleanupMode = cleanupMode; + this.executionContext = executionContext; + } + + Path get() { + return dir; + } + + @Override + public void close() throws IOException { + if (cleanupMode == NEVER + || (cleanupMode == ON_SUCCESS && executionContext.getExecutionException().isPresent())) { + logger.info(() -> "Skipping cleanup of temp dir " + dir + " due to cleanup mode configuration."); + return; + } + + FileOperations fileOperations = executionContext.getStore(NAMESPACE) // + .getOrDefault(FILE_OPERATIONS_KEY, FileOperations.class, FileOperations.DEFAULT); + + SortedMap failures = deleteAllFilesAndDirectories(fileOperations); + if (!failures.isEmpty()) { + throw createIOExceptionWithAttachedFailures(failures); + } + } + + private SortedMap deleteAllFilesAndDirectories(FileOperations fileOperations) + throws IOException { + if (Files.notExists(dir)) { + return Collections.emptySortedMap(); + } + + SortedMap failures = new TreeMap<>(); + Set retriedPaths = new HashSet<>(); + resetPermissions(dir); + Files.walkFileTree(dir, new SimpleFileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + if (!dir.equals(CloseablePath.this.dir)) { + resetPermissions(dir); + } + return CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + // IOException includes `AccessDeniedException` thrown by non-readable or non-executable flags + resetPermissionsAndTryToDeleteAgain(file, exc); + return CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) { + return deleteAndContinue(file); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + return deleteAndContinue(dir); + } + + private FileVisitResult deleteAndContinue(Path path) { + try { + fileOperations.delete(path); + } + catch (NoSuchFileException ignore) { + // ignore + } + catch (DirectoryNotEmptyException exception) { + failures.put(path, exception); + } + catch (IOException exception) { + // IOException includes `AccessDeniedException` thrown by non-readable or non-executable flags + resetPermissionsAndTryToDeleteAgain(path, exception); + } + return CONTINUE; + } + + private void resetPermissionsAndTryToDeleteAgain(Path path, IOException exception) { + boolean notYetRetried = retriedPaths.add(path); + if (notYetRetried) { + try { + resetPermissions(path); + if (Files.isDirectory(path)) { + Files.walkFileTree(path, this); + } + else { + fileOperations.delete(path); + } + } + catch (Exception suppressed) { + exception.addSuppressed(suppressed); + failures.put(path, exception); + } + } + else { + failures.put(path, exception); + } + } + }); + return failures; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static void resetPermissions(Path path) { + File file = path.toFile(); + file.setReadable(true); + file.setWritable(true); + if (Files.isDirectory(path)) { + file.setExecutable(true); + } + } + + private IOException createIOExceptionWithAttachedFailures(SortedMap failures) { + Path emptyPath = Paths.get(""); + String joinedPaths = failures.keySet().stream() // + .map(this::tryToDeleteOnExit) // + .map(this::relativizeSafely) // + .map(path -> emptyPath.equals(path) ? "" : path.toString()) // + .collect(joining(", ")); + IOException exception = new IOException("Failed to delete temp directory " + dir.toAbsolutePath() + + ". The following paths could not be deleted (see suppressed exceptions for details): " + + joinedPaths); + failures.values().forEach(exception::addSuppressed); + return exception; + } + + private Path tryToDeleteOnExit(Path path) { + try { + path.toFile().deleteOnExit(); + } + catch (UnsupportedOperationException ignore) { + } + return path; + } + + private Path relativizeSafely(Path path) { + try { + return dir.relativize(path); + } + catch (IllegalArgumentException e) { + return path; + } + } + } + + enum Scope { + + PER_CONTEXT, + + PER_DECLARATION + + } + + interface FileOperations { + + FileOperations DEFAULT = Files::delete; + + void delete(Path path) throws IOException; + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java new file mode 100644 index 00000000..1fdd3c7b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.Set; + +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@link ParameterResolver} that resolves the {@link TestInfo} for + * the currently executing test. + * + * @since 5.0 + */ +class TestInfoParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return (parameterContext.getParameter().getType() == TestInfo.class); + } + + @Override + public TestInfo resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return new DefaultTestInfo(extensionContext); + } + + private static class DefaultTestInfo implements TestInfo { + + private final String displayName; + private final Set tags; + private final Optional> testClass; + private final Optional testMethod; + + DefaultTestInfo(ExtensionContext extensionContext) { + this.displayName = extensionContext.getDisplayName(); + this.tags = extensionContext.getTags(); + this.testClass = extensionContext.getTestClass(); + this.testMethod = extensionContext.getTestMethod(); + } + + @Override + public String getDisplayName() { + return this.displayName; + } + + @Override + public Set getTags() { + return this.tags; + } + + @Override + public Optional> getTestClass() { + return this.testClass; + } + + @Override + public Optional getTestMethod() { + return this.testMethod; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("displayName", this.displayName) + .append("tags", this.tags) + .append("testClass", nullSafeGet(this.testClass)) + .append("testMethod", nullSafeGet(this.testMethod)) + .toString(); + // @formatter:on + } + + private static Object nullSafeGet(Optional optional) { + return optional != null ? optional.orElse(null) : null; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java new file mode 100644 index 00000000..ffa5cf06 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * {@link ParameterResolver} that injects a {@link TestReporter}. + * + * @since 5.0 + */ +class TestReporterParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return (parameterContext.getParameter().getType() == TestReporter.class); + } + + @Override + public TestReporter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return extensionContext::publishReportEntry; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java new file mode 100644 index 00000000..1391d4cd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java @@ -0,0 +1,146 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; +import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Timeout.ThreadMode; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * @since 5.5 + */ +class TimeoutConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(TimeoutConfiguration.class); + + private final TimeoutDurationParser parser = new TimeoutDurationParser(); + private final Map> cache = new ConcurrentHashMap<>(); + private final AtomicReference> threadMode = new AtomicReference<>(); + private final ExtensionContext extensionContext; + + TimeoutConfiguration(ExtensionContext extensionContext) { + this.extensionContext = extensionContext; + } + + Optional getDefaultTestMethodTimeout() { + return parseOrDefault(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTestableMethodTimeout); + } + + Optional getDefaultTestTemplateMethodTimeout() { + return parseOrDefault(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, + this::getDefaultTestableMethodTimeout); + } + + Optional getDefaultTestFactoryMethodTimeout() { + return parseOrDefault(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTestableMethodTimeout); + } + + Optional getDefaultBeforeAllMethodTimeout() { + return parseOrDefault(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); + } + + Optional getDefaultBeforeEachMethodTimeout() { + return parseOrDefault(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); + } + + Optional getDefaultAfterEachMethodTimeout() { + return parseOrDefault(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); + } + + Optional getDefaultAfterAllMethodTimeout() { + return parseOrDefault(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); + } + + private Optional getDefaultTestableMethodTimeout() { + return parseOrDefault(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTimeout); + } + + private Optional getDefaultLifecycleMethodTimeout() { + return parseOrDefault(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTimeout); + } + + private Optional getDefaultTimeout() { + return parseTimeoutDuration(DEFAULT_TIMEOUT_PROPERTY_NAME); + } + + private Optional parseOrDefault(String propertyName, + Supplier> defaultSupplier) { + Optional timeoutConfiguration = parseTimeoutDuration(propertyName); + return timeoutConfiguration.isPresent() ? timeoutConfiguration : defaultSupplier.get(); + } + + private Optional parseTimeoutDuration(String propertyName) { + return cache.computeIfAbsent(propertyName, key -> extensionContext.getConfigurationParameter(key).map(value -> { + try { + return parser.parse(value); + } + catch (Exception e) { + logger.warn(e, + () -> String.format("Ignored invalid timeout '%s' set via the '%s' configuration parameter.", value, + key)); + return null; + } + })); + } + + Optional getDefaultTimeoutThreadMode() { + if (threadMode.get() != null) { + return threadMode.get(); + } + else { + Optional configuredThreadMode = parseTimeoutThreadModeConfiguration(); + threadMode.set(configuredThreadMode); + return configuredThreadMode; + } + } + + private Optional parseTimeoutThreadModeConfiguration() { + return extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME).map(value -> { + try { + ThreadMode threadMode = ThreadMode.valueOf(value.toUpperCase()); + if (threadMode == ThreadMode.INFERRED) { + logger.warn(() -> String.format( + "Invalid timeout thread mode '%s', only %s and %s can be used as configuration parameter for %s.", + value, SAME_THREAD, SEPARATE_THREAD, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)); + return null; + } + return threadMode; + } + catch (Exception e) { + logger.warn(e, + () -> String.format("Invalid timeout thread mode '%s' set via the '%s' configuration parameter.", + value, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)); + return null; + } + }); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java new file mode 100644 index 00000000..eb058aef --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Timeout; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.5 + */ +class TimeoutDuration { + + static TimeoutDuration from(Timeout timeout) { + return new TimeoutDuration(timeout.value(), timeout.unit()); + } + + private final long value; + private final TimeUnit unit; + + TimeoutDuration(long value, TimeUnit unit) { + Preconditions.condition(value > 0, () -> "timeout duration must be a positive number: " + value); + this.value = value; + this.unit = Preconditions.notNull(unit, "timeout unit must not be null"); + } + + public long getValue() { + return value; + } + + public TimeUnit getUnit() { + return unit; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TimeoutDuration that = (TimeoutDuration) o; + return value == that.value && unit == that.unit; + } + + @Override + public int hashCode() { + return Objects.hash(value, unit); + } + + @Override + public String toString() { + String label = unit.name().toLowerCase(); + if (value == 1 && label.endsWith("s")) { + label = label.substring(0, label.length() - 1); + } + return value + " " + label; + } + + public Duration toDuration() { + return Duration.of(value, toChronoUnit()); + } + + private ChronoUnit toChronoUnit() { + switch (unit) { + case NANOSECONDS: + return ChronoUnit.NANOS; + case MICROSECONDS: + return ChronoUnit.MICROS; + case MILLISECONDS: + return ChronoUnit.MILLIS; + case SECONDS: + return ChronoUnit.SECONDS; + case MINUTES: + return ChronoUnit.MINUTES; + case HOURS: + return ChronoUnit.HOURS; + case DAYS: + return ChronoUnit.DAYS; + default: + throw new JUnitException("Could not map TimeUnit " + unit + " to ChronoUnit"); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java new file mode 100644 index 00000000..9412983b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static java.util.regex.Pattern.UNICODE_CASE; + +import java.time.format.DateTimeParseException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @since 5.5 + */ +class TimeoutDurationParser { + + private static final Pattern PATTERN = Pattern.compile("([1-9]\\d*) ?((?:[nμm]?s)|m|h|d)?", + CASE_INSENSITIVE | UNICODE_CASE); + private static final Map UNITS_BY_ABBREVIATION; + + static { + Map unitsByAbbreviation = new HashMap<>(); + unitsByAbbreviation.put("ns", NANOSECONDS); + unitsByAbbreviation.put("μs", MICROSECONDS); + unitsByAbbreviation.put("ms", MILLISECONDS); + unitsByAbbreviation.put("s", SECONDS); + unitsByAbbreviation.put("m", MINUTES); + unitsByAbbreviation.put("h", HOURS); + unitsByAbbreviation.put("d", DAYS); + UNITS_BY_ABBREVIATION = Collections.unmodifiableMap(unitsByAbbreviation); + } + + TimeoutDuration parse(CharSequence text) throws DateTimeParseException { + Matcher matcher = PATTERN.matcher(text); + if (matcher.matches()) { + long value = Long.parseLong(matcher.group(1)); + String unitAbbreviation = matcher.group(2); + TimeUnit unit = unitAbbreviation == null ? SECONDS + : UNITS_BY_ABBREVIATION.get(unitAbbreviation.toLowerCase(Locale.ENGLISH)); + return new TimeoutDuration(value, unit); + } + throw new DateTimeParseException("Timeout duration is not in the expected format ( [ns|μs|ms|s|m|h|d])", + text, 0); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java new file mode 100644 index 00000000..82c39e5d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.util.concurrent.TimeoutException; + +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.9 + */ +class TimeoutExceptionFactory { + + private TimeoutExceptionFactory() { + } + + static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration, Throwable failure) { + String message = String.format("%s timed out after %s", + Preconditions.notNull(methodSignature, "method signature must not be null"), + Preconditions.notNull(timeoutDuration, "timeout duration must not be null")); + TimeoutException timeoutException = new TimeoutException(message); + if (failure != null) { + timeoutException.addSuppressed(failure); + } + return timeoutException; + } + + static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration) { + return create(methodSignature, timeoutDuration, null); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java new file mode 100644 index 00000000..9efe2255 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java @@ -0,0 +1,232 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; +import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.function.Function; + +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.Timeout.ThreadMode; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.RuntimeUtils; + +/** + * @since 5.5 + */ +class TimeoutExtension implements BeforeAllCallback, BeforeEachCallback, InvocationInterceptor { + + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(Timeout.class); + private static final String TESTABLE_METHOD_TIMEOUT_KEY = "testable_method_timeout_from_annotation"; + private static final String TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY = "testable_method_timeout_thread_mode_from_annotation"; + private static final String GLOBAL_TIMEOUT_CONFIG_KEY = "global_timeout_config"; + private static final String ENABLED_MODE_VALUE = "enabled"; + private static final String DISABLED_MODE_VALUE = "disabled"; + private static final String DISABLED_ON_DEBUG_MODE_VALUE = "disabled_on_debug"; + + @Override + public void beforeAll(ExtensionContext context) { + readAndStoreTimeoutSoChildrenInheritIt(context); + } + + @Override + public void beforeEach(ExtensionContext context) { + readAndStoreTimeoutSoChildrenInheritIt(context); + } + + private void readAndStoreTimeoutSoChildrenInheritIt(ExtensionContext context) { + readTimeoutFromAnnotation(context.getElement()).ifPresent( + timeout -> context.getStore(NAMESPACE).put(TESTABLE_METHOD_TIMEOUT_KEY, timeout)); + readTimeoutThreadModeFromAnnotation(context.getElement()).ifPresent( + timeoutThreadMode -> context.getStore(NAMESPACE).put(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, + timeoutThreadMode)); + } + + @Override + public void interceptBeforeAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + + interceptLifecycleMethod(invocation, invocationContext, extensionContext, + TimeoutConfiguration::getDefaultBeforeAllMethodTimeout); + } + + @Override + public void interceptBeforeEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + + interceptLifecycleMethod(invocation, invocationContext, extensionContext, + TimeoutConfiguration::getDefaultBeforeEachMethodTimeout); + } + + @Override + public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { + + interceptTestableMethod(invocation, invocationContext, extensionContext, + TimeoutConfiguration::getDefaultTestMethodTimeout); + } + + @Override + public void interceptTestTemplateMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + + interceptTestableMethod(invocation, invocationContext, extensionContext, + TimeoutConfiguration::getDefaultTestTemplateMethodTimeout); + } + + @Override + public T interceptTestFactoryMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + + return interceptTestableMethod(invocation, invocationContext, extensionContext, + TimeoutConfiguration::getDefaultTestFactoryMethodTimeout); + } + + @Override + public void interceptAfterEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + + interceptLifecycleMethod(invocation, invocationContext, extensionContext, + TimeoutConfiguration::getDefaultAfterEachMethodTimeout); + } + + @Override + public void interceptAfterAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + + interceptLifecycleMethod(invocation, invocationContext, extensionContext, + TimeoutConfiguration::getDefaultAfterAllMethodTimeout); + } + + private void interceptLifecycleMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, + TimeoutProvider defaultTimeoutProvider) throws Throwable { + + TimeoutDuration timeout = readTimeoutFromAnnotation(Optional.of(invocationContext.getExecutable())).orElse( + null); + intercept(invocation, invocationContext, extensionContext, timeout, defaultTimeoutProvider); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Optional readTimeoutFromAnnotation(Optional element) { + return AnnotationSupport.findAnnotation(element, Timeout.class).map(TimeoutDuration::from); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Optional readTimeoutThreadModeFromAnnotation(Optional element) { + return AnnotationSupport.findAnnotation(element, Timeout.class).map(Timeout::threadMode); + } + + private T interceptTestableMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, + TimeoutProvider defaultTimeoutProvider) throws Throwable { + + TimeoutDuration timeout = extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_KEY, + TimeoutDuration.class); + return intercept(invocation, invocationContext, extensionContext, timeout, defaultTimeoutProvider); + } + + private T intercept(Invocation invocation, ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext, TimeoutDuration explicitTimeout, TimeoutProvider defaultTimeoutProvider) + throws Throwable { + + TimeoutDuration timeout = explicitTimeout == null ? getDefaultTimeout(extensionContext, defaultTimeoutProvider) + : explicitTimeout; + return decorate(invocation, invocationContext, extensionContext, timeout).proceed(); + } + + private TimeoutDuration getDefaultTimeout(ExtensionContext extensionContext, + TimeoutProvider defaultTimeoutProvider) { + + return defaultTimeoutProvider.apply(getGlobalTimeoutConfiguration(extensionContext)).orElse(null); + } + + private TimeoutConfiguration getGlobalTimeoutConfiguration(ExtensionContext extensionContext) { + ExtensionContext root = extensionContext.getRoot(); + return root.getStore(NAMESPACE).getOrComputeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, + key -> new TimeoutConfiguration(root), TimeoutConfiguration.class); + } + + private Invocation decorate(Invocation invocation, ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext, TimeoutDuration timeout) { + + if (timeout == null || isTimeoutDisabled(extensionContext)) { + return invocation; + } + + ThreadMode threadMode = resolveTimeoutThreadMode(extensionContext); + return new TimeoutInvocationFactory(extensionContext.getRoot().getStore(NAMESPACE)).create(threadMode, + new TimeoutInvocationParameters<>(invocation, timeout, + () -> describe(invocationContext, extensionContext))); + } + + private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext) { + ThreadMode annotationThreadMode = getAnnotationThreadMode(extensionContext); + if (annotationThreadMode == null || annotationThreadMode == ThreadMode.INFERRED) { + return getGlobalTimeoutConfiguration(extensionContext).getDefaultTimeoutThreadMode().orElse(SAME_THREAD); + } + return annotationThreadMode; + } + + private ThreadMode getAnnotationThreadMode(ExtensionContext extensionContext) { + return extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, ThreadMode.class); + } + + private String describe(ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { + Method method = invocationContext.getExecutable(); + Optional> testClass = extensionContext.getTestClass(); + if (testClass.isPresent() && invocationContext.getTargetClass().equals(testClass.get())) { + return String.format("%s(%s)", method.getName(), ClassUtils.nullSafeToString(method.getParameterTypes())); + } + return ReflectionUtils.getFullyQualifiedMethodName(invocationContext.getTargetClass(), method); + } + + /** + * Determine if timeouts are disabled for the supplied extension context. + */ + private boolean isTimeoutDisabled(ExtensionContext extensionContext) { + Optional mode = extensionContext.getConfigurationParameter(TIMEOUT_MODE_PROPERTY_NAME); + return mode.map(this::isTimeoutDisabled).orElse(false); + } + + /** + * Determine if timeouts are disabled for the supplied mode. + */ + private boolean isTimeoutDisabled(String mode) { + switch (mode) { + case ENABLED_MODE_VALUE: + return false; + case DISABLED_MODE_VALUE: + return true; + case DISABLED_ON_DEBUG_MODE_VALUE: + return RuntimeUtils.isDebugMode(); + default: + throw new ExtensionConfigurationException("Unsupported timeout mode: " + mode); + } + } + + @FunctionalInterface + private interface TimeoutProvider extends Function> { + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java new file mode 100644 index 00000000..08f6851f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java @@ -0,0 +1,114 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Timeout.ThreadMode; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.9 + */ +class TimeoutInvocationFactory { + + private final Store store; + + TimeoutInvocationFactory(Store store) { + this.store = Preconditions.notNull(store, "store must not be null"); + } + + Invocation create(ThreadMode threadMode, TimeoutInvocationParameters timeoutInvocationParameters) { + Preconditions.notNull(threadMode, "thread mode must not be null"); + Preconditions.condition(threadMode != ThreadMode.INFERRED, "thread mode must not be INFERRED"); + Preconditions.notNull(timeoutInvocationParameters, "timeout invocation parameters must not be null"); + if (threadMode == ThreadMode.SEPARATE_THREAD) { + return new SeparateThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), + timeoutInvocationParameters.getTimeoutDuration(), timeoutInvocationParameters.getDescriptionSupplier()); + } + return new SameThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), + timeoutInvocationParameters.getTimeoutDuration(), getThreadExecutorForSameThreadInvocation(), + timeoutInvocationParameters.getDescriptionSupplier()); + } + + private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() { + return store.getOrComputeIfAbsent(SingleThreadExecutorResource.class).get(); + } + + private static abstract class ExecutorResource implements CloseableResource { + + protected final ScheduledExecutorService executor; + + ExecutorResource(ScheduledExecutorService executor) { + this.executor = executor; + } + + ScheduledExecutorService get() { + return executor; + } + + @Override + public void close() throws Throwable { + executor.shutdown(); + boolean terminated = executor.awaitTermination(5, TimeUnit.SECONDS); + if (!terminated) { + executor.shutdownNow(); + throw new JUnitException("Scheduled executor could not be stopped in an orderly manner"); + } + } + } + + static class SingleThreadExecutorResource extends ExecutorResource { + + @SuppressWarnings("unused") + SingleThreadExecutorResource() { + super(Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread thread = new Thread(runnable, "junit-jupiter-timeout-watcher"); + thread.setPriority(Thread.MAX_PRIORITY); + return thread; + })); + } + } + + static class TimeoutInvocationParameters { + + private final Invocation invocation; + private final TimeoutDuration timeout; + private final Supplier descriptionSupplier; + + TimeoutInvocationParameters(Invocation invocation, TimeoutDuration timeout, + Supplier descriptionSupplier) { + this.invocation = Preconditions.notNull(invocation, "invocation must not be null"); + this.timeout = Preconditions.notNull(timeout, "timeout must not be null"); + this.descriptionSupplier = Preconditions.notNull(descriptionSupplier, + "description supplier must not be null"); + } + + public Invocation getInvocation() { + return invocation; + } + + public TimeoutDuration getTimeoutDuration() { + return timeout; + } + + public Supplier getDescriptionSupplier() { + return descriptionSupplier; + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java new file mode 100644 index 00000000..ff29b09a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java @@ -0,0 +1,5 @@ +/** + * Test extensions specific to the JUnit Jupiter test engine. + */ + +package org.junit.jupiter.engine.extension; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java new file mode 100644 index 00000000..17af1fec --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java @@ -0,0 +1,5 @@ +/** + * Core package for the JUnit Jupiter test engine. + */ + +package org.junit.jupiter.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java new file mode 100644 index 00000000..49a80ddb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.support; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * Factory for creating {@link ThrowableCollector ThrowableCollectors} within + * the JUnit Jupiter test engine. + * + * @since 5.4 + * @see ThrowableCollector + */ +@API(status = INTERNAL, since = "5.4") +public class JupiterThrowableCollectorFactory { + + /** + * Create a new {@link ThrowableCollector} that treats instances of the + * OTA's {@link org.opentest4j.TestAbortedException} and JUnit 4's + * {@code org.junit.AssumptionViolatedException} as aborting. + */ + public static ThrowableCollector createThrowableCollector() { + return new OpenTest4JAndJUnit4AwareThrowableCollector(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java new file mode 100644 index 00000000..5df8ff6a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.support; + +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; +import org.opentest4j.TestAbortedException; + +/** + * Specialization of {@link ThrowableCollector} that treats instances of the + * OTA's {@link org.opentest4j.TestAbortedException} and JUnit 4's + * {@code org.junit.AssumptionViolatedException} as aborting. + * + * @since 5.4 + * @see ThrowableCollector + * @see org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector + */ +class OpenTest4JAndJUnit4AwareThrowableCollector extends ThrowableCollector { + + private static final Logger logger = LoggerFactory.getLogger(OpenTest4JAndJUnit4AwareThrowableCollector.class); + + private static final String ASSUMPTION_VIOLATED_EXCEPTION = "org.junit.internal.AssumptionViolatedException"; + + private static final String COMMON_FAILURE_MESSAGE = "Failed to load class " + ASSUMPTION_VIOLATED_EXCEPTION + + ": only supporting " + TestAbortedException.class.getName() + " for aborted execution."; + + private static final Predicate abortedExecutionPredicate = createAbortedExecutionPredicate(); + + OpenTest4JAndJUnit4AwareThrowableCollector() { + super(abortedExecutionPredicate); + } + + private static Predicate createAbortedExecutionPredicate() { + Predicate otaPredicate = TestAbortedException.class::isInstance; + + // Additionally support JUnit 4's AssumptionViolatedException? + try { + Class clazz = ReflectionUtils.tryToLoadClass(ASSUMPTION_VIOLATED_EXCEPTION).get(); + if (clazz != null) { + return otaPredicate.or(clazz::isInstance); + } + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + Supplier messageSupplier = (throwable instanceof NoClassDefFoundError) + ? () -> COMMON_FAILURE_MESSAGE + " Note that " + ASSUMPTION_VIOLATED_EXCEPTION + + " requires that Hamcrest is on the classpath." + : () -> COMMON_FAILURE_MESSAGE; + logger.debug(throwable, messageSupplier); + } + + // Else just OTA's TestAbortedException + return otaPredicate; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java new file mode 100644 index 00000000..6eecfd0f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java @@ -0,0 +1,5 @@ +/** + * Internal support classes for the JUnit Jupiter test engine. + */ + +package org.junit.jupiter.engine.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine new file mode 100644 index 00000000..581900d5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine @@ -0,0 +1 @@ +org.junit.jupiter.engine.JupiterTestEngine \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java new file mode 100644 index 00000000..946b1dd3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Provides the JUnit Jupiter {@linkplain org.junit.platform.engine.TestEngine} + * implementation. + * + * @since 5.0 + * @uses org.junit.jupiter.api.extension.Extension + * @provides org.junit.platform.engine.TestEngine The {@code JupiterTestEngine} + * runs Jupiter based tests on the platform. + */ +module org.junit.jupiter.engine { + requires static org.apiguardian.api; + requires org.junit.jupiter.api; + requires org.junit.platform.commons; + requires org.junit.platform.engine; + requires org.opentest4j; + + // exports org.junit.jupiter.engine; // Constants... + + uses org.junit.jupiter.api.extension.Extension; + + provides org.junit.platform.engine.TestEngine + with org.junit.jupiter.engine.JupiterTestEngine; + + opens org.junit.jupiter.engine.extension to org.junit.platform.commons; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy new file mode 100644 index 00000000..7554f942 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy @@ -0,0 +1,192 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +import java.util.function.Supplier +import static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.PrimitiveAndWrapperTypeHelpers.* + +class GroovyAssertEqualsTests { + + Supplier supplier = { '' } + + @Test + void "null references can be passed to assertEquals"() { + Object null1 = null + Object null2 = null + + assertEquals(null1, null) + assertEquals(null, null2) + assertEquals(null1, null2) + } + + @Test + void "integers can be passed to assertEquals"() { + assertEquals(i(42), i(42)) + assertEquals(i(42), I(42)) + assertEquals(I(42), i(42)) + assertEquals(I(42), I(42)) + + assertEquals(i(42), i(42), '') + assertEquals(i(42), I(42), '') + assertEquals(I(42), i(42), '') + assertEquals(I(42), I(42), '') + + assertEquals(i(42), i(42), supplier) + assertEquals(i(42), I(42), supplier) + assertEquals(I(42), i(42), supplier) + assertEquals(I(42), I(42), supplier) + } + + @Test + void "floats can be passed to assertEquals"() { + assertEquals(f(42), f(42)) + assertEquals(f(42), F(42)) + assertEquals(F(42), f(42)) + assertEquals(F(42), F(42)) + + assertEquals(f(42), f(42), '') + assertEquals(f(42), F(42), '') + assertEquals(F(42), f(42), '') + assertEquals(F(42), F(42), '') + + assertEquals(f(42), f(42), supplier) + assertEquals(f(42), F(42), supplier) + assertEquals(F(42), f(42), supplier) + assertEquals(F(42), F(42), supplier) + } + + @Test + void "floats can be passed to assertEquals with delta"() { + assertEquals(f(42), f(42), 0.01f) + assertEquals(f(42), F(42), 0.01f) + assertEquals(F(42), f(42), 0.01f) + assertEquals(F(42), F(42), 0.01f) + + assertEquals(f(42), f(42), 0.01f, '') + assertEquals(f(42), F(42), 0.01f, '') + assertEquals(F(42), f(42), 0.01f, '') + assertEquals(F(42), F(42), 0.01f, '') + + assertEquals(f(42), f(42), 0.01f, supplier) + assertEquals(f(42), F(42), 0.01f, supplier) + assertEquals(F(42), f(42), 0.01f, supplier) + assertEquals(F(42), F(42), 0.01f, supplier) + } + + @Test + void "bytes can be passed to assertEquals"() { + assertEquals(b(42), b(42)) + assertEquals(b(42), B(42)) + assertEquals(B(42), b(42)) + assertEquals(B(42), B(42)) + + assertEquals(b(42), b(42), '') + assertEquals(b(42), B(42), '') + assertEquals(B(42), b(42), '') + assertEquals(B(42), B(42), '') + + assertEquals(b(42), b(42), supplier) + assertEquals(b(42), B(42), supplier) + assertEquals(B(42), b(42), supplier) + assertEquals(B(42), B(42), supplier) + } + + @Test + void "doubles can be passed to assertEquals"() { + assertEquals(d(42), d(42)) + assertEquals(d(42), D(42)) + assertEquals(D(42), d(42)) + assertEquals(D(42), D(42)) + + assertEquals(d(42), d(42), '') + assertEquals(d(42), D(42), '') + assertEquals(D(42), d(42), '') + assertEquals(D(42), D(42), '') + + assertEquals(d(42), d(42), supplier) + assertEquals(d(42), D(42), supplier) + assertEquals(D(42), d(42), supplier) + assertEquals(D(42), D(42), supplier) + } + + @Test + void "doubles can be passed to assertEquals with delta"() { + assertEquals(d(42), d(42), 0.01d) + assertEquals(d(42), D(42), 0.01d) + assertEquals(D(42), d(42), 0.01d) + assertEquals(D(42), D(42), 0.01d) + + assertEquals(d(42), d(42), 0.01d, '') + assertEquals(d(42), D(42), 0.01d, '') + assertEquals(D(42), d(42), 0.01d, '') + assertEquals(D(42), D(42), 0.01d, '') + + assertEquals(d(42), d(42), 0.01d, supplier) + assertEquals(d(42), D(42), 0.01d, supplier) + assertEquals(D(42), d(42), 0.01d, supplier) + assertEquals(D(42), D(42), 0.01d, supplier) + } + + @Test + void "chars can be passed to assertEquals"() { + assertEquals(c(42), c(42)) + assertEquals(c(42), C(42)) + assertEquals(C(42), c(42)) + assertEquals(C(42), C(42)) + + assertEquals(c(42), c(42), '') + assertEquals(c(42), C(42), '') + assertEquals(C(42), c(42), '') + assertEquals(C(42), C(42), '') + + assertEquals(c(42), c(42), supplier) + assertEquals(c(42), C(42), supplier) + assertEquals(C(42), c(42), supplier) + assertEquals(C(42), C(42), supplier) + } + + @Test + void "longs can be passed to assertEquals"() { + assertEquals(l(42), l(42)) + assertEquals(l(42), L(42)) + assertEquals(L(42), l(42)) + assertEquals(L(42), L(42)) + + assertEquals(l(42), l(42), '') + assertEquals(l(42), L(42), '') + assertEquals(L(42), l(42), '') + assertEquals(L(42), L(42), '') + + assertEquals(l(42), l(42), supplier) + assertEquals(l(42), L(42), supplier) + assertEquals(L(42), l(42), supplier) + assertEquals(L(42), L(42), supplier) + } + + @Test + void "shorts can be passed to assertEquals"() { + assertEquals(s(42), s(42)) + assertEquals(s(42), S(42)) + assertEquals(S(42), s(42)) + assertEquals(S(42), S(42)) + + assertEquals(s(42), s(42), '') + assertEquals(s(42), S(42), '') + assertEquals(S(42), s(42), '') + assertEquals(S(42), S(42), '') + + assertEquals(s(42), s(42), supplier) + assertEquals(s(42), S(42), supplier) + assertEquals(S(42), s(42), supplier) + assertEquals(S(42), S(42), supplier) + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy new file mode 100644 index 00000000..bf95310e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy @@ -0,0 +1,196 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +import org.opentest4j.AssertionFailedError + +import java.util.function.Supplier + +import static org.junit.jupiter.api.Assertions.assertNotEquals +import static org.junit.jupiter.api.Assertions.assertThrows +import static org.junit.jupiter.api.PrimitiveAndWrapperTypeHelpers.* + +class GroovyAssertNotEqualsTests { + + Supplier supplier = { '' } + + @Test + void "null references can be passed to assertNotEquals"() { + Object null1 = null + Object null2 = null + + assertThrows(AssertionFailedError, { assertNotEquals(null1, null) } ) + assertThrows(AssertionFailedError, { assertNotEquals(null, null2) } ) + assertThrows(AssertionFailedError, { assertNotEquals(null1, null2) } ) + } + + @Test + void "integers can be passed to assertNotEquals"() { + assertNotEquals(i(42), i(2)) + assertNotEquals(i(42), I(2)) + assertNotEquals(I(42), i(2)) + assertNotEquals(I(42), I(2)) + + assertNotEquals(i(42), i(2), '') + assertNotEquals(i(42), I(2), '') + assertNotEquals(I(42), i(2), '') + assertNotEquals(I(42), I(2), '') + + assertNotEquals(i(42), i(2), supplier) + assertNotEquals(i(42), I(2), supplier) + assertNotEquals(I(42), i(2), supplier) + assertNotEquals(I(42), I(2), supplier) + } + + @Test + void "floats can be passed to assertNotEquals"() { + assertNotEquals(f(42), f(2)) + assertNotEquals(f(42), F(2)) + assertNotEquals(F(42), f(2)) + assertNotEquals(F(42), F(2)) + + assertNotEquals(f(42), f(2), '') + assertNotEquals(f(42), F(2), '') + assertNotEquals(F(42), f(2), '') + assertNotEquals(F(42), F(2), '') + + assertNotEquals(f(42), f(2), supplier) + assertNotEquals(f(42), F(2), supplier) + assertNotEquals(F(42), f(2), supplier) + assertNotEquals(F(42), F(2), supplier) + } + + @Test + void "floats can be passed to assertNotEquals with delta"() { + assertNotEquals(f(42), f(2), 0.01f) + assertNotEquals(f(42), F(2), 0.01f) + assertNotEquals(F(42), f(2), 0.01f) + assertNotEquals(F(42), F(2), 0.01f) + + assertNotEquals(f(42), f(2), 0.01f, '') + assertNotEquals(f(42), F(2), 0.01f, '') + assertNotEquals(F(42), f(2), 0.01f, '') + assertNotEquals(F(42), F(2), 0.01f, '') + + assertNotEquals(f(42), f(2), 0.01f, supplier) + assertNotEquals(f(42), F(2), 0.01f, supplier) + assertNotEquals(F(42), f(2), 0.01f, supplier) + assertNotEquals(F(42), F(2), 0.01f, supplier) + } + + @Test + void "bytes can be passed to assertNotEquals"() { + assertNotEquals(b(42), b(2)) + assertNotEquals(b(42), B(2)) + assertNotEquals(B(42), b(2)) + assertNotEquals(B(42), B(2)) + + assertNotEquals(b(42), b(2), '') + assertNotEquals(b(42), B(2), '') + assertNotEquals(B(42), b(2), '') + assertNotEquals(B(42), B(2), '') + + assertNotEquals(b(42), b(2), supplier) + assertNotEquals(b(42), B(2), supplier) + assertNotEquals(B(42), b(2), supplier) + assertNotEquals(B(42), B(2), supplier) + } + + @Test + void "doubles can be passed to assertNotEquals"() { + assertNotEquals(d(42), d(2)) + assertNotEquals(d(42), D(2)) + assertNotEquals(D(42), d(2)) + assertNotEquals(D(42), D(2)) + + assertNotEquals(d(42), d(2), '') + assertNotEquals(d(42), D(2), '') + assertNotEquals(D(42), d(2), '') + assertNotEquals(D(42), D(2), '') + + assertNotEquals(d(42), d(2), supplier) + assertNotEquals(d(42), D(2), supplier) + assertNotEquals(D(42), d(2), supplier) + assertNotEquals(D(42), D(2), supplier) + } + + @Test + void "doubles can be passed to assertNotEquals with delta"() { + assertNotEquals(d(42), d(2), 0.01d) + assertNotEquals(d(42), D(2), 0.01d) + assertNotEquals(D(42), d(2), 0.01d) + assertNotEquals(D(42), D(2), 0.01d) + + assertNotEquals(d(42), d(2), 0.01d, '') + assertNotEquals(d(42), D(2), 0.01d, '') + assertNotEquals(D(42), d(2), 0.01d, '') + assertNotEquals(D(42), D(2), 0.01d, '') + + assertNotEquals(d(42), d(2), 0.01d, supplier) + assertNotEquals(d(42), D(2), 0.01d, supplier) + assertNotEquals(D(42), d(2), 0.01d, supplier) + assertNotEquals(D(42), D(2), 0.01d, supplier) + } + + @Test + void "chars can be passed to assertNotEquals"() { + assertNotEquals(c(42), c(2)) + assertNotEquals(c(42), C(2)) + assertNotEquals(C(42), c(2)) + assertNotEquals(C(42), C(2)) + + assertNotEquals(c(42), c(2), '') + assertNotEquals(c(42), C(2), '') + assertNotEquals(C(42), c(2), '') + assertNotEquals(C(42), C(2), '') + + assertNotEquals(c(42), c(2), supplier) + assertNotEquals(c(42), C(2), supplier) + assertNotEquals(C(42), c(2), supplier) + assertNotEquals(C(42), C(2), supplier) + } + + @Test + void "longs can be passed to assertNotEquals"() { + assertNotEquals(l(42), l(2)) + assertNotEquals(l(42), L(2)) + assertNotEquals(L(42), l(2)) + assertNotEquals(L(42), L(2)) + + assertNotEquals(l(42), l(2), '') + assertNotEquals(l(42), L(2), '') + assertNotEquals(L(42), l(2), '') + assertNotEquals(L(42), L(2), '') + + assertNotEquals(l(42), l(2), supplier) + assertNotEquals(l(42), L(2), supplier) + assertNotEquals(L(42), l(2), supplier) + assertNotEquals(L(42), L(2), supplier) + } + + @Test + void "shorts can be passed to assertNotEquals"() { + assertNotEquals(s(42), s(2)) + assertNotEquals(s(42), S(2)) + assertNotEquals(S(42), s(2)) + assertNotEquals(S(42), S(2)) + + assertNotEquals(s(42), s(2), '') + assertNotEquals(s(42), S(2), '') + assertNotEquals(S(42), s(2), '') + assertNotEquals(S(42), S(2), '') + + assertNotEquals(s(42), s(2), supplier) + assertNotEquals(s(42), S(2), supplier) + assertNotEquals(S(42), s(2), supplier) + assertNotEquals(S(42), S(2), supplier) + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy new file mode 100644 index 00000000..a33d8031 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +class PrimitiveAndWrapperTypeHelpers { + + static char c(int number) { + return (char) number + } + + static Character C(int number) { + return Character.valueOf((char) number) + } + + static byte b(int number) { + return (byte) number + } + + static Byte B(int number) { + return Byte.valueOf((byte) number) + } + + static double d(int number) { + return (double) number + } + + static Double D(int number) { + return Double.valueOf((double) number) + } + + static float f(int number) { + return (float) number + } + + static Float F(int number) { + return Float.valueOf((float) number) + } + + static long l(int number) { + return (long) number + } + + static Long L(int number) { + return Long.valueOf( (long) number) + } + + static short s(int number) { + return (short) number + } + + static Short S(int number) { + return Short.valueOf( (short) number) + } + + static int i(int number) { + return number + } + + static Integer I(int number) { + return Integer.valueOf( number) + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java new file mode 100644 index 00000000..3b10c68b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java @@ -0,0 +1,28 @@ + +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Simple test case that is used to verify proper support for classpath scanning + * within the default package. + * + * @since 5.0 + */ +@Disabled("Only used reflectively by other tests") +class DefaultPackageTestCase { + + @Test + void test() { + // do nothing + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java new file mode 100644 index 00000000..95ab9959 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +/** + * @since 5.8 + */ +public class B_TestCase { + + public static List callSequence; + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + if (callSequence != null) { + callSequence.add(testInfo.getTestClass().get().getName()); + } + } + + @Test + void a() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java new file mode 100644 index 00000000..470fb7be --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * Test suite for the JUnit Jupiter programming model, extension model, and + * {@code TestEngine} implementation. + * + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @since 5.0 + */ +@Suite +@SelectPackages("org.junit.jupiter") +@IncludeClassNamePatterns(".*Tests?") +@IncludeEngines("junit-jupiter") +class JupiterTestSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java new file mode 100644 index 00000000..ac8d045e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java @@ -0,0 +1,224 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.PreconditionViolationException; +import org.opentest4j.AssertionFailedError; +import org.opentest4j.MultipleFailuresError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertAllAssertionsTests { + + @Test + void assertAllWithNullExecutableArray() { + assertPrecondition("executables array must not be null or empty", () -> assertAll((Executable[]) null)); + } + + @Test + void assertAllWithNullExecutableCollection() { + assertPrecondition("executables collection must not be null", () -> assertAll((Collection) null)); + } + + @Test + void assertAllWithNullExecutableStream() { + assertPrecondition("executables stream must not be null", () -> assertAll((Stream) null)); + } + + @Test + void assertAllWithNullInExecutableArray() { + assertPrecondition("individual executables must not be null", () -> assertAll((Executable) null)); + } + + @Test + void assertAllWithNullInExecutableCollection() { + assertPrecondition("individual executables must not be null", () -> assertAll(asList((Executable) null))); + } + + @Test + void assertAllWithNullInExecutableStream() { + assertPrecondition("individual executables must not be null", () -> assertAll(Stream.of((Executable) null))); + } + + @Test + void assertAllWithExecutablesThatDoNotThrowExceptions() { + // @formatter:off + assertAll( + () -> assertTrue(true), + () -> assertFalse(false) + ); + assertAll("heading", + () -> assertTrue(true), + () -> assertFalse(false) + ); + assertAll(asList( + () -> assertTrue(true), + () -> assertFalse(false) + )); + assertAll("heading", asList( + () -> assertTrue(true), + () -> assertFalse(false) + )); + assertAll(Stream.of( + () -> assertTrue(true), + () -> assertFalse(false) + )); + assertAll("heading", Stream.of( + () -> assertTrue(true), + () -> assertFalse(false) + )); + // @formatter:on + } + + @Test + void assertAllWithExecutablesThatThrowAssertionErrors() { + // @formatter:off + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> + assertAll( + () -> assertFalse(true), + () -> assertFalse(true) + ) + ); + // @formatter:on + + assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); + } + + @Test + void assertAllWithCollectionOfExecutablesThatThrowAssertionErrors() { + // @formatter:off + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> + assertAll(asList( + () -> assertFalse(true), + () -> assertFalse(true) + )) + ); + // @formatter:on + + assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); + } + + @Test + void assertAllWithStreamOfExecutablesThatThrowAssertionErrors() { + // @formatter:off + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> + assertAll(Stream.of( + () -> assertFalse(true), + () -> assertFalse(true) + )) + ); + // @formatter:on + + assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); + } + + @Test + void assertAllWithExecutableThatThrowsThrowable() { + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { + throw new EnigmaThrowable(); + })); + + assertExpectedExceptionTypes(multipleFailuresError, EnigmaThrowable.class); + } + + @Test + void assertAllWithExecutableThatThrowsCheckedException() { + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { + throw new IOException(); + })); + + assertExpectedExceptionTypes(multipleFailuresError, IOException.class); + } + + @Test + void assertAllWithExecutableThatThrowsRuntimeException() { + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { + throw new IllegalStateException(); + })); + + assertExpectedExceptionTypes(multipleFailuresError, IllegalStateException.class); + } + + @Test + void assertAllWithExecutableThatThrowsError() { + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, + () -> assertAll(AssertionTestUtils::recurseIndefinitely)); + + assertExpectedExceptionTypes(multipleFailuresError, StackOverflowError.class); + } + + @Test + void assertAllWithExecutableThatThrowsUnrecoverableException() { + OutOfMemoryError outOfMemoryError = assertThrows(OutOfMemoryError.class, + () -> assertAll(AssertionTestUtils::runOutOfMemory)); + + assertEquals("boom", outOfMemoryError.getMessage()); + } + + @Test + void assertAllWithParallelStream() { + Executable executable = () -> { + throw new RuntimeException(); + }; + MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, + () -> assertAll(Stream.generate(() -> executable).parallel().limit(100))); + + assertThat(multipleFailuresError.getFailures()).hasSize(100).doesNotContainNull(); + } + + private void assertPrecondition(String msg, Executable executable) { + PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, executable); + assertMessageEquals(exception, msg); + } + + @SafeVarargs + static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError, + Class... exceptionTypes) { + + assertNotNull(multipleFailuresError, "MultipleFailuresError"); + List failures = multipleFailuresError.getFailures(); + assertEquals(exceptionTypes.length, failures.size(), "number of failures"); + + // Verify that exceptions are also present as suppressed exceptions. + // https://github.com/junit-team/junit5/issues/1602 + Throwable[] suppressed = multipleFailuresError.getSuppressed(); + assertEquals(exceptionTypes.length, suppressed.length, "number of suppressed exceptions"); + + for (int i = 0; i < exceptionTypes.length; i++) { + assertEquals(exceptionTypes[i], failures.get(i).getClass(), "exception type [" + i + "]"); + assertEquals(exceptionTypes[i], suppressed[i].getClass(), "suppressed exception type [" + i + "]"); + } + } + + @SuppressWarnings("serial") + private static class EnigmaThrowable extends Throwable { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java new file mode 100644 index 00000000..5900e356 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java @@ -0,0 +1,1977 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertArrayEqualsAssertionsTests { + + @Test + void assertArrayEqualsWithNulls() { + assertArrayEquals((boolean[]) null, (boolean[]) null); + assertArrayEquals((char[]) null, (char[]) null); + assertArrayEquals((byte[]) null, (byte[]) null); + assertArrayEquals((int[]) null, (int[]) null); + assertArrayEquals((long[]) null, (long[]) null); + assertArrayEquals((float[]) null, (float[]) null); + assertArrayEquals((double[]) null, (double[]) null); + assertArrayEquals((Object[]) null, (Object[]) null); + } + + @Test + void assertArrayEqualsBooleanArrays() { + assertArrayEquals(new boolean[] {}, new boolean[] {}); + assertArrayEquals(new boolean[] {}, new boolean[] {}, "message"); + assertArrayEquals(new boolean[] {}, new boolean[] {}, () -> "message"); + assertArrayEquals(new boolean[] { true }, new boolean[] { true }); + assertArrayEquals(new boolean[] { false, true, false, false }, new boolean[] { false, true, false, false }); + } + + @Test + void assertArrayEqualsBooleanArrayVsNull() { + try { + assertArrayEquals(null, new boolean[] { true, false }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new boolean[] { true, false }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsBooleanArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new boolean[] { true, false }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new boolean[] { true, false }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsBooleanArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new boolean[] { true, false }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new boolean[] { true, false }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsBooleanArraysOfDifferentLength() { + try { + assertArrayEquals(new boolean[] { true, false }, new boolean[] { true, false, true }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertArrayEqualsBooleanArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new boolean[] { true, false, false }, new boolean[] { true }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <1>"); + } + } + + @Test + void assertArrayEqualsBooleanArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new boolean[] { true }, new boolean[] { true, false }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <1> but was: <2>"); + } + } + + @Test + void assertArrayEqualsDifferentBooleanArrays() { + try { + assertArrayEquals(new boolean[] { true, false, false }, new boolean[] { true, false, true }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [2], expected: but was: "); + } + } + + @Test + void assertArrayEqualsDifferentBooleanArraysAndMessage() { + try { + assertArrayEquals(new boolean[] { true, true }, new boolean[] { false, true }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [0], expected: but was: "); + } + } + + @Test + void assertArrayEqualsDifferentBooleanArraysAndMessageSupplier() { + try { + assertArrayEquals(new boolean[] { false, false, false }, new boolean[] { false, true, true }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: but was: "); + } + } + + @Test + void assertArrayEqualsCharArrays() { + assertArrayEquals(new char[] {}, new char[] {}); + assertArrayEquals(new char[] {}, new char[] {}, "message"); + assertArrayEquals(new char[] {}, new char[] {}, () -> "message"); + assertArrayEquals(new char[] { 'a' }, new char[] { 'a' }); + assertArrayEquals(new char[] { 'j', 'u', 'n', 'i', 't' }, new char[] { 'j', 'u', 'n', 'i', 't' }); + } + + @Test + void assertArrayEqualsCharArrayVsNull() { + try { + assertArrayEquals(null, new char[] { 'a', 'z' }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new char[] { 'a', 'z' }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsCharArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new char[] { 'a', 'b', 'z' }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new char[] { 'a', 'b', 'z' }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsCharArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new char[] { 'z', 'x', 'y' }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new char[] { 'z', 'x', 'y' }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsCharArraysOfDifferentLength() { + try { + assertArrayEquals(new char[] { 'q', 'w', 'e' }, new char[] { 'q', 'w' }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <2>"); + } + } + + @Test + void assertArrayEqualsCharArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new char[] { 'a', 's', 'd' }, new char[] { 'd' }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <1>"); + } + } + + @Test + void assertArrayEqualsCharArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new char[] { 'q' }, new char[] { 't', 'u' }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <1> but was: <2>"); + } + } + + @Test + void assertArrayEqualsDifferentCharArrays() { + try { + assertArrayEquals(new char[] { 'a', 'b', 'c' }, new char[] { 'a', 'b', 'a' }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [2], expected: but was: "); + } + } + + @Test + void assertArrayEqualsDifferentCharArraysAndMessage() { + try { + assertArrayEquals(new char[] { 'z', 'x', 'c', 'v' }, new char[] { 'x', 'x', 'c', 'v' }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [0], expected: but was: "); + } + } + + @Test + void assertArrayEqualsDifferentCharArraysAndMessageSupplier() { + try { + assertArrayEquals(new char[] { 'r', 't', 'y' }, new char[] { 'r', 'y', 'u' }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: but was: "); + } + } + + @Test + void assertArrayEqualsByteArrays() { + assertArrayEquals(new byte[] {}, new byte[] {}); + assertArrayEquals(new byte[] {}, new byte[] {}, "message"); + assertArrayEquals(new byte[] {}, new byte[] {}, () -> "message"); + assertArrayEquals(new byte[] { 42 }, new byte[] { 42 }); + assertArrayEquals(new byte[] { 1, 2, 3, 42 }, new byte[] { 1, 2, 3, 42 }); + } + + @Test + void assertArrayEqualsByteArrayVsNull() { + try { + assertArrayEquals(null, new byte[] { 7, 8, 9 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new byte[] { 7, 8, 9 }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsByteArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new byte[] { 9, 8, 7 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new byte[] { 9, 8, 7 }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsByteArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new byte[] { 10, 20, 30 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new byte[] { 10, 20, 30 }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsByteArraysOfDifferentLength() { + try { + assertArrayEquals(new byte[] { 1, 2, 100 }, new byte[] { 1, 2, 100, 101 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <4>"); + } + } + + @Test + void assertArrayEqualsByteArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new byte[] { 1, 2 }, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <9>"); + } + } + + @Test + void assertArrayEqualsByteArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new byte[] { 88, 99 }, new byte[] { 99, 88, 77 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertArrayEqualsDifferentByteArrays() { + try { + assertArrayEquals(new byte[] { 12, 13, 12, 13 }, new byte[] { 12, 13, 12, 14 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [3], expected: <13> but was: <14>"); + } + } + + @Test + void assertArrayEqualsDifferentByteArraysAndMessage() { + try { + assertArrayEquals(new byte[] { 1, 2, 3, 4, 5 }, new byte[] { 1, 2, 3, 5, 5 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [3], expected: <4> but was: <5>"); + } + } + + @Test + void assertArrayEqualsDifferentByteArraysAndMessageSupplier() { + try { + assertArrayEquals(new byte[] { 127, 126, -128, +127 }, new byte[] { 127, 126, -128, -127 }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [3], expected: <127> but was: <-127>"); + } + } + + @Test + void assertArrayEqualsShortArrays() { + assertArrayEquals(new short[] {}, new short[] {}); + assertArrayEquals(new short[] {}, new short[] {}, "message"); + assertArrayEquals(new short[] {}, new short[] {}, () -> "message"); + assertArrayEquals(new short[] { 999 }, new short[] { 999 }); + assertArrayEquals(new short[] { 111, 222, 333, 444, 999 }, new short[] { 111, 222, 333, 444, 999 }); + } + + @Test + void assertArrayEqualsShortArrayVsNull() { + try { + assertArrayEquals(null, new short[] { 5, 10, 12 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new short[] { 5, 10, 12 }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsShortArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new short[] { 128, 129, 130 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new short[] { -129, -130, -131 }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsShortArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new short[] { 1, 2, 3, 4 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new short[] { -2000, 1, 2, 3, 4 }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsShortArraysOfDifferentLength() { + try { + assertArrayEquals(new short[] { 1, 2, 3, 4, 5, 6 }, new short[] { 1, 2, 3 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <6> but was: <3>"); + } + } + + @Test + void assertArrayEqualsShortArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new short[] { 1, 2, 3, 10_000 }, new short[] { 10_000, 1, 2, 3, 4, 5, 6, 7 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <8>"); + } + } + + @Test + void assertArrayEqualsShortArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new short[] { 150, 151 }, new short[] { 150, 151, 152 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertArrayEqualsDifferentShortArrays() { + try { + assertArrayEquals(new short[] { 10, 100, 1000, 10000 }, new short[] { 1, 10, 100, 1000 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [0], expected: <10> but was: <1>"); + } + } + + @Test + void assertArrayEqualsDifferentShortArraysAndMessage() { + try { + assertArrayEquals(new short[] { 1, 2, 100, -200 }, new short[] { 1, 2, 100, -500 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [3], expected: <-200> but was: <-500>"); + } + } + + @Test + void assertArrayEqualsDifferentShortArraysAndMessageSupplier() { + try { + assertArrayEquals(new short[] { 1000, 2000, +3000, 42 }, new short[] { 1000, 2000, -3000, 42 }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [2], expected: <3000> but was: <-3000>"); + } + } + + @Test + void assertArrayEqualsIntArrays() { + assertArrayEquals(new int[] {}, new int[] {}); + assertArrayEquals(new int[] {}, new int[] {}, "message"); + assertArrayEquals(new int[] {}, new int[] {}, () -> "message"); + assertArrayEquals(new int[] { Integer.MAX_VALUE }, new int[] { Integer.MAX_VALUE }); + assertArrayEquals(new int[] { 1, 2, 3, 4, 5, 99_999 }, new int[] { 1, 2, 3, 4, 5, 99_999 }); + } + + @Test + void assertArrayEqualsIntArrayVsNull() { + try { + assertArrayEquals(null, new int[] { Integer.MIN_VALUE, 2, 10 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new int[] { Integer.MIN_VALUE, 2, 10 }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsIntArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new int[] { 99_999, 88_888, 1 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new int[] { 99_999, 77_7777, 2 }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsIntArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new int[] { 1, 10, 100, 1000, 10000, 100000 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new int[] { 100000, 10000, 1000, 100, 10, 1 }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsIntArraysOfDifferentLength() { + try { + assertArrayEquals(new int[] { 1, 2, 3, Integer.MIN_VALUE, 4 }, new int[] { 1, Integer.MAX_VALUE, 2 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <5> but was: <3>"); + } + } + + @Test + void assertArrayEqualsIntArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new int[] { 100_000, 200_000, 1, 2 }, new int[] { 1, 2, 3 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <3>"); + } + } + + @Test + void assertArrayEqualsIntArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new int[] { Integer.MAX_VALUE, Integer.MIN_VALUE }, new int[] { 1, 2, 3 }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertArrayEqualsDifferentIntArrays() { + try { + assertArrayEquals(new int[] { Integer.MIN_VALUE, 1, 2, 10 }, new int[] { Integer.MIN_VALUE, 1, 10, 10 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [2], expected: <2> but was: <10>"); + } + } + + @Test + void assertArrayEqualsDifferentIntArraysAndMessage() { + try { + assertArrayEquals(new int[] { 9, 10, 100, 100_000, 7 }, new int[] { 9, 10, 100, 100_000, 200_000 }, + "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [4], expected: <7> but was: <200000>"); + } + } + + @Test + void assertArrayEqualsDifferentIntArraysAndMessageSupplier() { + try { + assertArrayEquals(new int[] { 1, Integer.MIN_VALUE, 2 }, new int[] { 1, Integer.MAX_VALUE, 2 }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, + "array contents differ at index [1], expected: <-2147483648> but was: <2147483647>"); + } + } + + @Test + void assertArrayEqualsLongArrays() { + assertArrayEquals(new long[] {}, new long[] {}); + assertArrayEquals(new long[] {}, new long[] {}, "message"); + assertArrayEquals(new long[] {}, new long[] {}, () -> "message"); + assertArrayEquals(new long[] { Long.MAX_VALUE }, new long[] { Long.MAX_VALUE }); + assertArrayEquals(new long[] { Long.MIN_VALUE, 10, 20, 30 }, new long[] { Long.MIN_VALUE, 10, 20, 30 }); + } + + @Test + void assertArrayEqualsLongArrayVsNull() { + try { + assertArrayEquals(null, new long[] { Long.MAX_VALUE, 2, 10 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new long[] { Long.MAX_VALUE, 2, 10 }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsLongArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new long[] { 42, 4242, 424242, 4242424242L }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new long[] { 4242424242L, 424242, 4242, 42 }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsLongArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new long[] { 12345678910L, 10, 9, 8 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new long[] { 8, 9, 10, 12345678910L }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsLongArraysOfDifferentLength() { + try { + assertArrayEquals(new long[] { 1, 2, 3, Long.MIN_VALUE, 4 }, new long[] { 1, Long.MAX_VALUE, 2 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <5> but was: <3>"); + } + } + + @Test + void assertArrayEqualsLongArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new long[] { 100_000L, 200_000L, 1L, 2L }, new long[] { 1L, 2L, 3L }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <3>"); + } + } + + @Test + void assertArrayEqualsLongArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new long[] { Long.MAX_VALUE, Long.MIN_VALUE }, new long[] { 1L, 2L, 42L }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertArrayEqualsDifferentLongArrays() { + try { + assertArrayEquals(new long[] { Long.MIN_VALUE, 17, 18L, 19 }, new long[] { Long.MIN_VALUE, 17, 18, 20 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [3], expected: <19> but was: <20>"); + } + } + + @Test + void assertArrayEqualsDifferentLongArraysAndMessage() { + try { + assertArrayEquals(new long[] { 6, 5, 4, 3, 2, Long.MIN_VALUE }, new long[] { 6, 5, 4, 3, 2, 1 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, + "array contents differ at index [5], expected: <-9223372036854775808> but was: <1>"); + } + } + + @Test + void assertArrayEqualsDifferentLongArraysAndMessageSupplier() { + try { + assertArrayEquals(new long[] { 42, -9999L, 2 }, new long[] { 42L, +9999L, 2L }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: <-9999> but was: <9999>"); + } + } + + @Test + void assertArrayEqualsFloatArrays() { + assertArrayEquals(new float[] {}, new float[] {}); + assertArrayEquals(new float[] {}, new float[] {}, "message"); + assertArrayEquals(new float[] {}, new float[] {}, () -> "message"); + assertArrayEquals(new float[] { Float.MAX_VALUE }, new float[] { Float.MAX_VALUE }); + assertArrayEquals(new float[] { Float.MIN_VALUE, 5F, 5.5F, 1.00F }, + new float[] { Float.MIN_VALUE, 5F, 5.5F, 1.00F }); + + assertArrayEquals(new float[] { Float.NaN }, new float[] { Float.NaN }); + assertArrayEquals(new float[] { 10.18F, Float.NaN, 42.9F }, new float[] { 10.18F, Float.NaN, 42.9F }); + } + + @Test + void assertArrayEqualsFloatArrayVsNull() { + try { + assertArrayEquals(null, new float[] { Float.MAX_VALUE, 4.2F, 9.0F }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new float[] { Float.MIN_VALUE, 2.3F, 10.10F }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsFloatArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new float[] { 42.42F, 42.4242F, 19.20F }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new float[] { 11.101F, 12.101F, 99.9F }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsFloatArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new float[] { 5F, 6F, 7.77F, 8.88F }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new float[] { 1F, 1.1F, 1.11F, 1.111F }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsFloatArraysOfDifferentLength() { + try { + assertArrayEquals(new float[] { Float.MIN_VALUE, 1F, 2F, 3F }, new float[] { Float.MAX_VALUE, 7.1F }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <2>"); + } + } + + @Test + void assertArrayEqualsFloatArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new float[] { 19.1F, 12.77F, 18.F }, new float[] { .9F, .8F, 5.123F, .10F }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <4>"); + } + } + + @Test + void assertArrayEqualsFloatArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new float[] { 1.1F, 1.2F, 1.3F }, new float[] { 1F, 2F, 3F, 4F, 5F, 6F }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <6>"); + } + } + + @Test + void assertArrayEqualsDifferentFloatArrays() { + try { + assertArrayEquals(new float[] { 5.5F, 6.5F, 7.5F, 8.5F }, new float[] { 5.5F, 6.5F, 7.4F, 8.5F }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [2], expected: <7.5> but was: <7.4>"); + } + + try { + assertArrayEquals(new float[] { 1.0F, 2.0F, 3.0F, Float.NaN }, new float[] { 1.0F, 2.0F, 3.0F, 4.0F }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [3], expected: but was: <4.0>"); + } + } + + @Test + void assertArrayEqualsDifferentFloatArraysAndMessage() { + try { + assertArrayEquals(new float[] { 1.9F, 0.5F, 0.4F, 0.3F }, new float[] { 1.9F, 0.5F, 0.4F, -0.333F }, + "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [3], expected: <0.3> but was: <-0.333>"); + } + } + + @Test + void assertArrayEqualsDifferentFloatArraysAndMessageSupplier() { + try { + assertArrayEquals(new float[] { 0.3F, 0.9F, 8F }, new float[] { 0.3F, Float.MIN_VALUE, 8F }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.9> but was: <1.4E-45>"); + } + } + + @Test + void assertArrayEqualsDeltaFloatArrays() { + assertArrayEquals(new float[] {}, new float[] {}, 0.001F); + assertArrayEquals(new float[] {}, new float[] {}, 0.001F, "message"); + assertArrayEquals(new float[] {}, new float[] {}, 0.001F, () -> "message"); + assertArrayEquals(new float[] { Float.MAX_VALUE }, new float[] { Float.MAX_VALUE }, 0.0001F); + assertArrayEquals(new float[] { Float.MIN_VALUE, 2.111F, 2.521F, 1.01F }, + new float[] { Float.MIN_VALUE, 2.119F, 2.523F, 1.01001F }, 0.01F); + + assertArrayEquals(new float[] { Float.NaN }, new float[] { Float.NaN }, 0.1F); + assertArrayEquals(new float[] { 10.18F, Float.NaN, 42.9F }, new float[] { 10.98F, Float.NaN, 43.9F }, 1F); + } + + @Test + void assertArrayEqualsDeltaFloatArraysThrowsForIllegalDelta() { + try { + assertArrayEquals(new float[] {}, new float[] {}, -0.5F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); + } + try { + assertArrayEquals(new float[] {}, new float[] {}, Float.NaN); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: "); + } + + try { + assertArrayEquals(new float[] { 12.9F, 7F, 13F }, new float[] { 12.9F, 7F, 13F }, -0.5F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); + } + try { + assertArrayEquals(new float[] { 1.11F, 1.11F, 9F }, new float[] { 1.11F, 1.11F, 9F, 10F }, Float.NaN); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: "); + } + } + + @Test + void assertArrayEqualsDeltaFloatArrayVsNull() { + try { + assertArrayEquals(null, new float[] { Float.MAX_VALUE, 4.2F, 9.0F }, 0.001F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new float[] { Float.MIN_VALUE, 2.3F, 10.10F }, null, 0.01F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDeltaFloatArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new float[] { 42.42F, 42.4242F, 19.20F }, 0.0001F, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new float[] { 11.101F, 12.101F, 99.9F }, null, 0.01F, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDeltaFloatArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new float[] { 5F, 6F, 7.77F, 8.88F }, 0.1F, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new float[] { 1F, 1.1F, 1.11F, 1.111F }, null, 0.1F, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDeltaFloatArraysOfDifferentLength() { + try { + assertArrayEquals(new float[] { Float.MIN_VALUE, 1F, 2F, 3F }, new float[] { Float.MAX_VALUE, 7.1F }, 0.1F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <2>"); + } + } + + @Test + void assertArrayEqualsDeltaFloatArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new float[] { 19.1F, 12.77F }, new float[] { .9F, .8F, 5.123F }, 0.1F, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertArrayEqualsDeltaFloatArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new float[] { 1.1F, 1.2F, 1.3F }, new float[] { 1F, 2F, 3F, 4F }, 0.1F, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <4>"); + } + } + + @Test + void assertArrayEqualsDeltaDifferentFloatArrays() { + try { + assertArrayEquals(new float[] { 5.6F, 3.2F, 9.1F, 0.5F }, new float[] { 5.55F, 3.3F, 9.201F, 0.51F }, 0.1F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [2], expected: <9.1> but was: <9.201>"); + } + + try { + assertArrayEquals(new float[] { 1.0F, 2.0F, 3.0F, Float.NaN }, new float[] { 1.5F, 1.5F, 2.9F, 4.0F }, + 0.5F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [3], expected: but was: <4.0>"); + } + } + + @Test + void assertArrayEqualsDeltaDifferentFloatArraysAndMessage() { + try { + assertArrayEquals(new float[] { 1.91F, 0.5F, .4F, 0.3F }, new float[] { 2F, 0.509F, .499F, -0.333F }, 0.1F, + "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [3], expected: <0.3> but was: <-0.333>"); + } + } + + @Test + void assertArrayEqualsDeltaDifferentFloatArraysAndMessageSupplier() { + try { + assertArrayEquals(new float[] { 0.3F, 0.9F, 8F }, new float[] { 0.6F, Float.MIN_VALUE, 8.4F }, 0.5F, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.9> but was: <1.4E-45>"); + } + } + + @Test + void assertArrayEqualsDoubleArrays() { + assertArrayEquals(new double[] {}, new double[] {}); + assertArrayEquals(new double[] {}, new double[] {}, "message"); + assertArrayEquals(new double[] {}, new double[] {}, () -> "message"); + assertArrayEquals(new double[] { Double.MAX_VALUE }, new double[] { Double.MAX_VALUE }); + assertArrayEquals(new double[] { Double.MIN_VALUE, 2.1, 5.5, 1.0 }, + new double[] { Double.MIN_VALUE, 2.1, 5.5, 1.0 }); + + assertArrayEquals(new double[] { Double.NaN }, new double[] { Double.NaN }); + assertArrayEquals(new double[] { 1.2, 10.8, Double.NaN, 42.9 }, new double[] { 1.2, 10.8, Double.NaN, 42.9 }); + } + + @Test + void assertArrayEqualsDoubleArrayVsNull() { + try { + assertArrayEquals(null, new double[] { Double.MAX_VALUE, 17.4, 98.7654321 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new double[] { Double.MIN_VALUE, 93.0, 92.000001 }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDoubleArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new double[] { 33.3, 34.9, 20.1, 11.0011 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new double[] { 44.4, 20.19, 11.3, 0.11 }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDoubleArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new double[] { 1.2, 1.3, 1.4, 2.2002 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new double[] { 13.13, 43.33, 100 }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDoubleArraysOfDifferentLength() { + try { + assertArrayEquals(new double[] { Double.MIN_VALUE, 1.0, 2.0, 3.0 }, + new double[] { Double.MAX_VALUE, 1.1, 1.0 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <3>"); + } + } + + @Test + void assertArrayEqualsDoubleArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new double[] { 11.1, 99.1, 2 }, new double[] { .9, .1, .0, .1, .3 }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <5>"); + } + } + + @Test + void assertArrayEqualsDoubleArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new double[] { 1.15D, 2.2, 2.3 }, new double[] { 1.15D, 1.15D }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <2>"); + } + } + + @Test + void assertArrayEqualsDifferentDoubleArrays() { + try { + assertArrayEquals(new double[] { 1.17, 1.19, 1.21, 5 }, new double[] { 1.17, 1.00019, 1.21, 5 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [1], expected: <1.19> but was: <1.00019>"); + } + + try { + assertArrayEquals(new double[] { 0.1, 0.2, 0.3, 0.4, 0.5 }, + new double[] { 0.1, 0.2, 0.3, 0.4, Double.NaN }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [4], expected: <0.5> but was: "); + } + } + + @Test + void assertArrayEqualsDifferentDoubleArraysAndMessage() { + try { + assertArrayEquals(new double[] { 1.01, 9.031, .123, 4.23 }, new double[] { 1.01, 9.099, .123, 4.23 }, + "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: <9.031> but was: <9.099>"); + } + } + + @Test + void assertArrayEqualsDifferentDoubleArraysAndMessageSupplier() { + try { + assertArrayEquals(new double[] { 0.7, .1, 8 }, new double[] { 0.7, Double.MIN_VALUE, 8 }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.1> but was: <4.9E-324>"); + } + } + + @Test + void assertArrayEqualsDeltaDoubleArrays() { + assertArrayEquals(new double[] {}, new double[] {}, 0.5); + assertArrayEquals(new double[] {}, new double[] {}, 0.5, "message"); + assertArrayEquals(new double[] {}, new double[] {}, 0.5, () -> "message"); + assertArrayEquals(new double[] { Double.MAX_VALUE, 0.1 }, new double[] { Double.MAX_VALUE, 0.2 }, 0.2); + assertArrayEquals(new double[] { Double.MIN_VALUE, 3.1, 1.3, 2.7 }, + new double[] { Double.MIN_VALUE, 3.4, 1.7, 2.4 }, 0.5); + + assertArrayEquals(new double[] { Double.NaN }, new double[] { Double.NaN }, 0.01); + assertArrayEquals(new double[] { 1.2, 1.8, Double.NaN, 4.9 }, new double[] { 1.25, 1.7, Double.NaN, 4.8 }, 0.2); + } + + @Test + void assertArrayEqualsDeltaDoubleArraysThrowsForIllegalDelta() { + try { + assertArrayEquals(new double[] {}, new double[] {}, -0.5F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); + } + try { + assertArrayEquals(new double[] {}, new double[] {}, Float.NaN); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: "); + } + + try { + assertArrayEquals(new double[] { 1.2, 1.3, 10 }, new double[] { 1.2, 1.3, 10 }, -0.5F); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); + } + try { + assertArrayEquals(new double[] { 0.1, 10 }, new double[] { 0.1, 10, 11 }, Float.NaN); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "positive delta expected but was: "); + } + } + + @Test + void assertArrayEqualsDeltaDoubleArrayVsNull() { + try { + assertArrayEquals(null, new double[] { Double.MAX_VALUE, 11.1, 12.12 }, 0.5); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new double[] { Double.MIN_VALUE, 90, 91.9 }, null, 0.1); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDeltaDoubleArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new double[] { 33.3, 34.9, 20.1, 11.0011 }, 0.1, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new double[] { 44.4, 20.19, 11.3, 0.11 }, null, 0.5, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDeltaDoubleArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new double[] { 1.2, 1.3, 1.4, 2.2002 }, 1, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new double[] { 13.13, 43.33, 100 }, null, 1.5, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsDeltaDoubleArraysOfDifferentLength() { + try { + assertArrayEquals(new double[] { Double.MIN_VALUE, 2.0, 3.0, 4.0 }, + new double[] { Double.MAX_VALUE, 2.1, 3.1 }, 0.001); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <3>"); + } + } + + @Test + void assertArrayEqualsDeltaDoubleArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new double[] { 1.1, 99.1, 3.1 }, new double[] { .9, .1, .0, .1, .3 }, 0.1, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <5>"); + } + } + + @Test + void assertArrayEqualsDeltaDoubleArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new double[] { 1.77D, 2.1, 3 }, new double[] { 8.8, 0.11 }, 1, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <2>"); + } + } + + @Test + void assertArrayEqualsDeltaDifferentDoubleArrays() { + try { + assertArrayEquals(new double[] { 1.12, 2.92, 1.201 }, new double[] { 1.1201, 2.94, 1.201 }, 0.01); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [1], expected: <2.92> but was: <2.94>"); + } + + try { + assertArrayEquals(new double[] { 0.6, 0.12, 19.9, 5.5 }, new double[] { 1.0, 0.42, 20, Double.NaN }, 0.5); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [3], expected: <5.5> but was: "); + } + } + + @Test + void assertArrayEqualsDeltaDifferentDoubleArraysAndMessage() { + try { + assertArrayEquals(new double[] { 1.01, 9.031, .123, 4.23 }, new double[] { 1.1, 9.231, .13, 4.3 }, 0.1, + "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: <9.031> but was: <9.231>"); + } + } + + @Test + void assertArrayEqualsDeltaDifferentDoubleArraysAndMessageSupplier() { + try { + assertArrayEquals(new double[] { 0.7, 0.3001, 8 }, new double[] { 0.7, 0.4002, 8 }, 0.1, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.3001> but was: <0.4002>"); + } + } + + @Test + void assertArrayEqualsObjectArrays() { + Object[] array = { "a", 'b', 1, 2 }; + assertArrayEquals(array, array); + + assertArrayEquals(new Object[] {}, new Object[] {}); + assertArrayEquals(new Object[] {}, new Object[] {}, "message"); + assertArrayEquals(new Object[] {}, new Object[] {}, () -> "message"); + assertArrayEquals(new Object[] { "abc" }, new Object[] { "abc" }); + assertArrayEquals(new Object[] { "abc", 1, 2L, 3D }, new Object[] { "abc", 1, 2L, 3D }); + + assertArrayEquals(new Object[] { new Object[] { new Object[] {} } }, + new Object[] { new Object[] { new Object[] {} } }); + + assertArrayEquals( + new Object[] { null, new Object[] { null, new Object[] { null, null } }, null, new Object[] { null } }, + new Object[] { null, new Object[] { null, new Object[] { null, null } }, null, new Object[] { null } }); + + assertArrayEquals(new Object[] { "a", new Object[] { new Object[] { "b", new Object[] { "c", "d" } } }, "e" }, + new Object[] { "a", new Object[] { new Object[] { "b", new Object[] { "c", "d" } } }, "e" }); + + assertArrayEquals( + new Object[] { new Object[] { 1 }, new Object[] { 2 }, + new Object[] { new Object[] { 3, new Object[] { 4 } } } }, + new Object[] { new Object[] { 1 }, new Object[] { 2 }, + new Object[] { new Object[] { 3, new Object[] { 4 } } } }); + + assertArrayEquals( + new Object[] { new Object[] { + new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { "abc" } } } } } } }, + new Object[] { new Object[] { + new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { "abc" } } } } } } }); + + assertArrayEquals( + new Object[] { null, new Object[] { null, Double.NaN, new Object[] { Float.NaN, null, new Object[] {} } } }, + new Object[] { null, + new Object[] { null, Double.NaN, new Object[] { Float.NaN, null, new Object[] {} } } }); + + assertArrayEquals( + new Object[] { new String("a"), Integer.valueOf(1), new Object[] { Double.parseDouble("1.1"), "b" } }, + new Object[] { new String("a"), Integer.valueOf(1), new Object[] { Double.parseDouble("1.1"), "b" } }); + + assertArrayEquals( + new Object[] { 1, 2, + new Object[] { 3, new int[] { 4, 5 }, new long[] { 6 }, + new Object[] { new Object[] { new int[] { 7 } } } }, + new int[] { 8 }, new Object[] { new long[] { 9 } } }, + new Object[] { 1, 2, + new Object[] { 3, new int[] { 4, 5 }, new long[] { 6 }, + new Object[] { new Object[] { new int[] { 7 } } } }, + new int[] { 8 }, new Object[] { new long[] { 9 } } }); + + assertArrayEquals( + new Object[] { "a", new char[] { 'b', 'c' }, new int[] { 'd' }, + new Object[] { new Object[] { new String[] { "ef" }, new Object[] { new String[] { "ghi" } } } } }, + new Object[] { "a", new char[] { 'b', 'c' }, new int[] { 'd' }, + new Object[] { new Object[] { new String[] { "ef" }, new Object[] { new String[] { "ghi" } } } } }); + } + + @Test + void assertArrayEqualsObjectArrayVsNull() { + try { + assertArrayEquals(null, new Object[] { "a", "b", 1, new Object() }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was "); + } + + try { + assertArrayEquals(new Object[] { 'a', 1, new Object(), 10L }, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsNestedObjectArrayVsNull() { + try { + assertArrayEquals(// + new Object[] { new Object[] {}, 1, "2", new Object[] { '3', new Object[] { null } } }, // + new Object[] { new Object[] {}, 1, "2", new Object[] { '3', new Object[] { new Object[] { "4" } } } }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected array was at index [3][1][0]"); + } + + try { + assertArrayEquals( + new Object[] { 1, 2, new Object[] { 3, new Object[] { "4", new Object[] { 5, new Object[] { 6 } } } }, + "7" }, + new Object[] { 1, 2, new Object[] { 3, new Object[] { "4", new Object[] { 5, null } } }, "7" }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual array was at index [2][1][1][1]"); + } + } + + @Test + void assertArrayEqualsObjectArrayVsNullAndMessage() { + try { + assertArrayEquals(null, new Object[] { 'a', "b", 10, 20D }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new Object[] { "hello", 42 }, null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsNestedObjectArrayVsNullAndMessage() { + try { + assertArrayEquals(new Object[] { 1, new Object[] { 2, 3, new Object[] { 4, 5, new Object[] { null } } } }, + new Object[] { 1, new Object[] { 2, 3, new Object[] { 4, 5, new Object[] { new Object[] { 6 } } } } }, + "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was at index [1][2][2][0]"); + } + + try { + assertArrayEquals( + new Object[] { 1, new Object[] { 2, new Object[] { 3, new Object[] { new Object[] { 4 } } } } }, + new Object[] { 1, new Object[] { 2, new Object[] { 3, new Object[] { null } } } }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was at index [1][1][1][0]"); + } + } + + @Test + void assertArrayEqualsObjectArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(null, new Object[] { 42, "42", new float[] { 42F }, 42D }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was "); + } + + try { + assertArrayEquals(new Object[] { new Object[] { "a" }, new Object() }, null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was "); + } + } + + @Test + void assertArrayEqualsNestedObjectArrayVsNullAndMessageSupplier() { + try { + assertArrayEquals(new Object[] { "1", "2", "3", new Object[] { "4", new Object[] { null } } }, + new Object[] { "1", "2", "3", new Object[] { "4", new Object[] { new int[] { 5 } } } }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected array was at index [3][1][0]"); + } + + try { + assertArrayEquals( + new Object[] { 1, 2, new Object[] { "3", new Object[] { '4', new Object[] { 5, 6, new long[] {} } } } }, + new Object[] { 1, 2, new Object[] { "3", new Object[] { '4', new Object[] { 5, 6, null } } } }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual array was at index [2][1][1][2]"); + } + } + + @Test + void assertArrayEqualsObjectArraysOfDifferentLength() { + try { + assertArrayEquals(new Object[] { 'a', "b", 'c' }, new Object[] { 'a', "b", 'c', 1 }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <4>"); + } + } + + @Test + void assertArrayEqualsNestedObjectArraysOfDifferentLength() { + try { + assertArrayEquals( + new Object[] { "a", new Object[] { "b", new Object[] { "c", "d", new Object[] { "e", 1, 2, 3 } } } }, + new Object[] { "a", + new Object[] { "b", new Object[] { "c", "d", new Object[] { "e", 1, 2, 3, 4, 5 } } } }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ at index [1][1][2], expected: <4> but was: <6>"); + } + + try { + assertArrayEquals( + new Object[] { new Object[] { + new Object[] { new Object[] { new Object[] { new Object[] { new char[] { 'a' } } } } } } }, + new Object[] { new Object[] { new Object[] { + new Object[] { new Object[] { new Object[] { new char[] { 'a', 'b' } } } } } } }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array lengths differ at index [0][0][0][0][0][0], expected: <1> but was: <2>"); + } + } + + @Test + void assertArrayEqualsObjectArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(new Object[] { 'a', 1 }, new Object[] { 'a', 1, new Object() }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertArrayEqualsNestedObjectArraysOfDifferentLengthAndMessage() { + try { + assertArrayEquals(// + new Object[] { 'a', 1, new Object[] { 2, 3 } }, // + new Object[] { 'a', 1, new Object[] { 2, 3, 4, 5 } }, // + "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ at index [2], expected: <2> but was: <4>"); + } + } + + @Test + void assertArrayEqualsObjectArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(new Object[] { "a", "b", "c" }, new Object[] { "a", "b", "c", "d", "e", "f" }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <6>"); + } + } + + @Test + void assertArrayEqualsNestedObjectArraysOfDifferentLengthAndMessageSupplier() { + try { + assertArrayEquals(// + new Object[] { "a", new Object[] { 1, 2, 3, new double[] { 4.0, 5.1, 6.1 }, 7 } }, // + new Object[] { "a", new Object[] { 1, 2, 3, new double[] { 4.0, 5.1, 6.1, 7.0 }, 8 } }, // + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array lengths differ at index [1][3], expected: <3> but was: <4>"); + } + } + + @Test + void assertArrayEqualsDifferentObjectArrays() { + try { + assertArrayEquals(new Object[] { 1L, "2", '3', 4, 5D }, new Object[] { 1L, "2", '9', 4, 5D }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [2], expected: <3> but was: <9>"); + } + + try { + assertArrayEquals(new Object[] { "a", 10, 11, 12, Double.NaN }, new Object[] { "a", 10, 11, 12, 13.55D }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [4], expected: but was: <13.55>"); + } + } + + @Test + void assertArrayEqualsDifferentNestedObjectArrays() { + try { + assertArrayEquals( + new Object[] { 1, 2, new Object[] { 3, new Object[] { 4, new boolean[] { false, true } } } }, + new Object[] { 1, 2, new Object[] { 3, new Object[] { 4, new boolean[] { true, false } } } }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [2][1][1][0], expected: but was: "); + } + + try { + assertArrayEquals(new Object[] { 1, 2, 3, new Object[] { new Object[] { 4, new Object[] { 5 } } } }, + new Object[] { 1, 2, 3, new Object[] { new Object[] { 4, new Object[] { new Object[] {} } } } }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "array contents differ at index [3][0][1][0], expected: <5> but was: <[]>"); + } + } + + @Test + void assertArrayEqualsDifferentObjectArraysAndMessage() { + try { + assertArrayEquals(new Object[] { 1.1D, 2L, "3" }, new Object[] { 1D, 2L, "3" }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [0], expected: <1.1> but was: <1.0>"); + } + } + + @Test + void assertArrayEqualsDifferentNestedObjectArraysAndMessage() { + try { + assertArrayEquals(new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new Object[] { "2", '1' } } }, + new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new Object[] { "99", '1' } } }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>"); + } + + try { + assertArrayEquals(new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new String[] { "2", "1" } } }, + new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new String[] { "99", "1" } } }, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>"); + } + } + + @Test + void assertArrayEqualsDifferentObjectArraysAndMessageSupplier() { + try { + assertArrayEquals(new Object[] { "one", 1L, Double.MIN_VALUE, "abc" }, + new Object[] { "one", 1L, 42.42, "abc" }, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [2], expected: <4.9E-324> but was: <42.42>"); + } + } + + @Test + void assertArrayEqualsDifferentNestedObjectArraysAndMessageSupplier() { + try { + assertArrayEquals( + new Object[] { "one", 1L, new Object[] { "a", 'b', new Object[] { 1, new Object[] { 2, 3 } } }, "abc" }, + new Object[] { "one", 1L, new Object[] { "a", 'b', new Object[] { 1, new Object[] { 2, 4 } } }, "abc" }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [2][2][1][1], expected: <3> but was: <4>"); + } + + try { + assertArrayEquals( + new Object[] { "j", new String[] { "a" }, new int[] { 42 }, "ab", new Object[] { 1, new int[] { 3 } } }, + new Object[] { "j", new String[] { "a" }, new int[] { 42 }, "ab", new Object[] { 1, new int[] { 5 } } }, + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "array contents differ at index [4][1][0], expected: <3> but was: <5>"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java new file mode 100644 index 00000000..77bc9ce4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java @@ -0,0 +1,321 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.concurrent.FutureTask; + +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.2 + */ +class AssertDoesNotThrowAssertionsTests { + + private static final Executable nix = () -> { + }; + + private static final ThrowingSupplier something = () -> "enigma"; + + @Test + void assertDoesNotThrowWithMethodReferenceForNonVoidReturnType() { + FutureTask future = new FutureTask<>(() -> { + return "foo"; + }); + future.run(); + + String result; + + // Current compiler's type inference: does NOT compile since the compiler + // cannot figure out which overloaded variant of assertDoesNotThrow() to + // invoke (i.e., Executable vs. ThrowingSupplier). + // + // result = assertDoesNotThrow(future::get); + + // Explicitly as an Executable + assertDoesNotThrow((Executable) future::get); + + // Explicitly as a ThrowingSupplier + result = assertDoesNotThrow((ThrowingSupplier) future::get); + assertEquals("foo", result); + } + + @Test + void assertDoesNotThrowWithMethodReferenceForVoidReturnType() { + var foo = new Foo(); + + // Note: the following does not compile since the compiler cannot properly + // perform type inference for a method reference for an overloaded method + // that has a void return type such as Foo.overloaded(...), IFF the + // compiler is simultaneously trying to pick which overloaded variant + // of assertDoesNotThrow() to invoke. + // + // assertDoesNotThrow(foo::overloaded); + + // Current compiler's type inference + assertDoesNotThrow(foo::normalMethod); + + // Explicitly as an Executable + assertDoesNotThrow((Executable) foo::normalMethod); + assertDoesNotThrow((Executable) foo::overloaded); + } + + // --- executable ---------------------------------------------------------- + + @Test + void assertDoesNotThrowAnythingWithExecutable() { + assertDoesNotThrow(nix); + } + + @Test + void assertDoesNotThrowAnythingWithExecutableAndMessage() { + assertDoesNotThrow(nix, "message"); + } + + @Test + void assertDoesNotThrowAnythingWithExecutableAndMessageSupplier() { + assertDoesNotThrow(nix, () -> "message"); + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsACheckedException() { + try { + assertDoesNotThrow((Executable) () -> { + throw new IOException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsACheckedExceptionWithMessage() { + String message = "Checked exception message"; + try { + assertDoesNotThrow((Executable) () -> { + throw new IOException(message); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName() + ": " + message); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsARuntimeException() { + try { + assertDoesNotThrow((Executable) () -> { + throw new IllegalStateException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Unexpected exception thrown: " + IllegalStateException.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsARuntimeExceptionWithMessage() { + String message = "Runtime exception message"; + try { + assertDoesNotThrow((Executable) () -> { + throw new IllegalStateException(message); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, + "Unexpected exception thrown: " + IllegalStateException.class.getName() + ": " + message); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsAnError() { + try { + assertDoesNotThrow((Executable) AssertionTestUtils::recurseIndefinitely); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Unexpected exception thrown: " + StackOverflowError.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageString() { + try { + assertDoesNotThrow((Executable) () -> { + throw new IllegalStateException(); + }, "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, + "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageWithMessageString() { + String message = "Runtime exception message"; + try { + assertDoesNotThrow((Executable) () -> { + throw new IllegalStateException(message); + }, "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + + IllegalStateException.class.getName() + ": " + message); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageSupplier() { + try { + assertDoesNotThrow((Executable) () -> { + throw new IllegalStateException(); + }, () -> "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, + "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageWithMessageSupplier() { + String message = "Runtime exception message"; + try { + assertDoesNotThrow((Executable) () -> { + throw new IllegalStateException(message); + }, () -> "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + + IllegalStateException.class.getName() + ": " + message); + } + } + + // --- supplier ------------------------------------------------------------ + + @Test + void assertDoesNotThrowAnythingWithSupplier() { + assertEquals("enigma", assertDoesNotThrow(something)); + } + + @Test + void assertDoesNotThrowAnythingWithSupplierAndMessage() { + assertEquals("enigma", assertDoesNotThrow(something, "message")); + } + + @Test + void assertDoesNotThrowAnythingWithSupplierAndMessageSupplier() { + assertEquals("enigma", assertDoesNotThrow(something, () -> "message")); + } + + @Test + void assertDoesNotThrowWithSupplierThatThrowsACheckedException() { + try { + assertDoesNotThrow((ThrowingSupplier) () -> { + throw new IOException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithSupplierThatThrowsARuntimeException() { + try { + assertDoesNotThrow((ThrowingSupplier) () -> { + throw new IllegalStateException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Unexpected exception thrown: " + IllegalStateException.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithSupplierThatThrowsAnError() { + try { + assertDoesNotThrow((ThrowingSupplier) () -> { + throw new StackOverflowError(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Unexpected exception thrown: " + StackOverflowError.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithSupplierThatThrowsAnExceptionWithMessageString() { + try { + assertDoesNotThrow((ThrowingSupplier) () -> { + throw new IllegalStateException(); + }, "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, + "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); + } + } + + @Test + void assertDoesNotThrowWithSupplierThatThrowsAnExceptionWithMessageSupplier() { + try { + assertDoesNotThrow((ThrowingSupplier) () -> { + throw new IllegalStateException(); + }, () -> "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, + "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); + } + } + + // ------------------------------------------------------------------------- + + private static class Foo { + + void normalMethod() { + } + + void overloaded() { + } + + @SuppressWarnings("unused") + void overloaded(int i) { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java new file mode 100644 index 00000000..8c09c72c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java @@ -0,0 +1,771 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.function.Executable; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertEqualsAssertionsTests { + + @Test + void assertEqualsByte() { + byte expected = 1; + byte actual = 1; + assertEquals(expected, actual); + assertEquals(expected, actual, "message"); + assertEquals(expected, actual, () -> "message"); + } + + @Test + void assertEqualsByteWithUnequalValues() { + byte expected = 1; + byte actual = 2; + try { + assertEquals(expected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, expected, actual); + } + } + + @Test + void assertEqualsByteWithUnequalValuesAndMessage() { + byte expected = 1; + byte actual = 2; + try { + assertEquals(expected, actual, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, expected, actual); + } + } + + @Test + void assertEqualsByteWithUnequalValuesAndMessageSupplier() { + byte expected = 1; + byte actual = 2; + try { + assertEquals(expected, actual, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, expected, actual); + } + } + + @Test + void assertEqualsShort() { + short expected = 1; + short actual = 1; + assertEquals(expected, actual); + assertEquals(expected, actual, "message"); + assertEquals(expected, actual, () -> "message"); + } + + @Test + void assertEqualsShortWithUnequalValues() { + short expected = 1; + short actual = 2; + try { + assertEquals(expected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, expected, actual); + } + } + + @Test + void assertEqualsShortWithUnequalValuesAndMessage() { + short expected = 1; + short actual = 2; + try { + assertEquals(expected, actual, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, expected, actual); + } + } + + @Test + void assertEqualsShortWithUnequalValuesAndMessageSupplier() { + short expected = 1; + short actual = 2; + try { + assertEquals(expected, actual, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, expected, actual); + } + } + + @Test + void assertEqualsInt() { + assertEquals(1, 1); + assertEquals(1, 1, "message"); + assertEquals(1, 1, () -> "message"); + } + + @Test + void assertEqualsIntWithUnequalValues() { + try { + assertEquals(1, 2); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, 1, 2); + } + } + + @Test + void assertEqualsIntWithUnequalValuesAndMessage() { + try { + assertEquals(1, 2, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, 1, 2); + } + } + + @Test + void assertEqualsIntWithUnequalValuesAndMessageSupplier() { + try { + assertEquals(1, 2, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, 1, 2); + } + } + + @Test + void assertEqualsLong() { + assertEquals(1L, 1L); + assertEquals(1L, 1L, "message"); + assertEquals(1L, 1L, () -> "message"); + } + + @Test + void assertEqualsLongWithUnequalValues() { + try { + assertEquals(1L, 2L); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, 1L, 2L); + } + } + + @Test + void assertEqualsLongWithUnequalValuesAndMessage() { + try { + assertEquals(1L, 2L, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, 1L, 2L); + } + } + + @Test + void assertEqualsLongWithUnequalValuesAndMessageSupplier() { + try { + assertEquals(1L, 2L, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1> but was: <2>"); + assertExpectedAndActualValues(ex, 1L, 2L); + } + } + + @Test + void assertEqualsChar() { + assertEquals('a', 'a'); + assertEquals('a', 'a', "message"); + assertEquals('a', 'a', () -> "message"); + } + + @Test + void assertEqualsCharWithUnequalValues() { + try { + assertEquals('a', 'b'); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, 'a', 'b'); + } + } + + @Test + void assertEqualsCharWithUnequalValuesAndMessage() { + try { + assertEquals('a', 'b', "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, 'a', 'b'); + } + } + + @Test + void assertEqualsCharWithUnequalValuesAndMessageSupplier() { + try { + assertEquals('a', 'b', () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, 'a', 'b'); + } + } + + @Test + void assertEqualsFloat() { + assertEquals(1.0f, 1.0f); + assertEquals(1.0f, 1.0f, "message"); + assertEquals(1.0f, 1.0f, () -> "message"); + assertEquals(Float.NaN, Float.NaN); + assertEquals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + assertEquals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + assertEquals(Float.MIN_VALUE, Float.MIN_VALUE); + assertEquals(Float.MAX_VALUE, Float.MAX_VALUE); + assertEquals(Float.MIN_NORMAL, Float.MIN_NORMAL); + assertEquals(Double.NaN, Float.NaN); + } + + @Test + void assertEqualsFloatWithUnequalValues() { + try { + assertEquals(1.0f, 1.1f); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: <1.0> but was: <1.1>"); + assertExpectedAndActualValues(ex, 1.0f, 1.1f); + } + } + + @Test + void assertEqualsFloatWithUnequalValuesAndMessage() { + try { + assertEquals(1.0f, 1.1f, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); + assertExpectedAndActualValues(ex, 1.0f, 1.1f); + } + } + + @Test + void assertEqualsFloatWithUnequalValuesAndMessageSupplier() { + try { + assertEquals(1.0f, 1.1f, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); + assertExpectedAndActualValues(ex, 1.0f, 1.1f); + } + } + + @Test + void assertEqualsFloatWithDelta() { + assertEquals(0.0f, 0.0f, 0.1f); + assertEquals(0.0f, 0.0f, 0.1f, "message"); + assertEquals(0.0f, 0.0f, 0.1f, () -> "message"); + assertEquals(0.56f, 0.6f, 0.05f); + assertEquals(0.01f, 0.011f, 0.002f); + assertEquals(Float.NaN, Float.NaN, 0.5f); + assertEquals(0.1f, 0.1f, 0.0f); + } + + @Test + void assertEqualsFloatWithIllegalDelta() { + AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1f, 0.2f, -0.9f)); + assertMessageEndsWith(e1, "positive delta expected but was: <-0.9>"); + + AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(.0f, .0f, -10.5f)); + assertMessageEndsWith(e2, "positive delta expected but was: <-10.5>"); + + AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(4.5f, 4.6f, Float.NaN)); + assertMessageEndsWith(e3, "positive delta expected but was: "); + } + + @Test + void assertEqualsFloatWithDeltaWithUnequalValues() { + AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.5f, 0.2f, 0.2f)); + assertMessageEndsWith(e1, "expected: <0.5> but was: <0.2>"); + + AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1f, 0.2f, 0.000001f)); + assertMessageEndsWith(e2, "expected: <0.1> but was: <0.2>"); + + AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(100.0f, 50.0f, 10.0f)); + assertMessageEndsWith(e3, "expected: <100.0> but was: <50.0>"); + + AssertionFailedError e4 = assertThrows(AssertionFailedError.class, () -> assertEquals(-3.5f, -3.3f, 0.01f)); + assertMessageEndsWith(e4, "expected: <-3.5> but was: <-3.3>"); + + AssertionFailedError e5 = assertThrows(AssertionFailedError.class, () -> assertEquals(+0.0f, -0.001f, .00001f)); + assertMessageEndsWith(e5, "expected: <0.0> but was: <-0.001>"); + } + + @Test + void assertEqualsFloatWithDeltaWithUnequalValuesAndMessage() { + Executable assertion = () -> assertEquals(0.5f, 0.45f, 0.03f, "message"); + + AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); + + assertMessageStartsWith(e, "message"); + assertMessageEndsWith(e, "expected: <0.5> but was: <0.45>"); + assertExpectedAndActualValues(e, 0.5f, 0.45f); + } + + @Test + void assertEqualsFloatWithDeltaWithUnequalValuesAndMessageSupplier() { + Executable assertion = () -> assertEquals(0.5f, 0.45f, 0.03f, () -> "message"); + + AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); + + assertMessageStartsWith(e, "message"); + assertMessageEndsWith(e, "expected: <0.5> but was: <0.45>"); + assertExpectedAndActualValues(e, 0.5f, 0.45f); + } + + @Test + void assertEqualsDouble() { + assertEquals(1.0d, 1.0d); + assertEquals(1.0d, 1.0d, "message"); + assertEquals(1.0d, 1.0d, () -> "message"); + assertEquals(Double.NaN, Double.NaN); + assertEquals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + assertEquals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + assertEquals(Double.MIN_VALUE, Double.MIN_VALUE); + assertEquals(Double.MAX_VALUE, Double.MAX_VALUE); + assertEquals(Double.MIN_NORMAL, Double.MIN_NORMAL); + } + + @Test + void assertEqualsDoubleWithUnequalValues() { + try { + assertEquals(1.0d, 1.1d); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: <1.0> but was: <1.1>"); + assertExpectedAndActualValues(ex, 1.0d, 1.1d); + } + } + + @Test + void assertEqualsDoubleWithUnequalValuesAndMessage() { + try { + assertEquals(1.0d, 1.1d, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); + assertExpectedAndActualValues(ex, 1.0d, 1.1d); + } + } + + @Test + void assertEqualsDoubleWithUnequalValuesAndMessageSupplier() { + try { + assertEquals(1.0d, 1.1d, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); + assertExpectedAndActualValues(ex, 1.0d, 1.1d); + } + } + + @Test + void assertEqualsDoubleWithDelta() { + assertEquals(0.0d, 0.0d, 0.1d); + assertEquals(0.0d, 0.0d, 0.1d, "message"); + assertEquals(0.0d, 0.0d, 0.1d, () -> "message"); + assertEquals(0.42d, 0.24d, 0.19d); + assertEquals(0.02d, 0.011d, 0.01d); + assertEquals(Double.NaN, Double.NaN, 0.2d); + assertEquals(0.001d, 0.001d, 0.0d); + } + + @Test + void assertEqualsDoubleWithIllegalDelta() { + AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(1.1d, 1.11d, -0.5d)); + assertMessageEndsWith(e1, "positive delta expected but was: <-0.5>"); + + AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(.55d, .56d, -10.5d)); + assertMessageEndsWith(e2, "positive delta expected but was: <-10.5>"); + + AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(1.1d, 1.1d, Double.NaN)); + assertMessageEndsWith(e3, "positive delta expected but was: "); + } + + @Test + void assertEqualsDoubleWithDeltaWithUnequalValues() { + AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(9.9d, 9.7d, 0.1d)); + assertMessageEndsWith(e1, "expected: <9.9> but was: <9.7>"); + assertExpectedAndActualValues(e1, 9.9d, 9.7d); + + AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1d, 0.05d, 0.001d)); + assertMessageEndsWith(e2, "expected: <0.1> but was: <0.05>"); + assertExpectedAndActualValues(e2, 0.1d, 0.05d); + + AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(17.11d, 15.11d, 1.1d)); + assertMessageEndsWith(e3, "expected: <17.11> but was: <15.11>"); + assertExpectedAndActualValues(e3, 17.11d, 15.11d); + + AssertionFailedError e4 = assertThrows(AssertionFailedError.class, () -> assertEquals(-7.2d, -5.9d, 1.1d)); + assertMessageEndsWith(e4, "expected: <-7.2> but was: <-5.9>"); + assertExpectedAndActualValues(e4, -7.2d, -5.9d); + + AssertionFailedError e5 = assertThrows(AssertionFailedError.class, () -> assertEquals(+0.0d, -0.001d, .00001d)); + assertMessageEndsWith(e5, "expected: <0.0> but was: <-0.001>"); + assertExpectedAndActualValues(e5, +0.0d, -0.001d); + } + + @Test + void assertEqualsDoubleWithDeltaWithUnequalValuesAndMessage() { + Executable assertion = () -> assertEquals(42.42d, 42.4d, 0.001d, "message"); + + AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); + + assertMessageStartsWith(e, "message"); + assertMessageEndsWith(e, "expected: <42.42> but was: <42.4>"); + assertExpectedAndActualValues(e, 42.42d, 42.4d); + } + + @Test + void assertEqualsDoubleWithDeltaWithUnequalValuesAndMessageSupplier() { + Executable assertion = () -> assertEquals(0.9d, 10.12d, 5.001d, () -> "message"); + + AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); + + assertMessageStartsWith(e, "message"); + assertMessageEndsWith(e, "expected: <0.9> but was: <10.12>"); + assertExpectedAndActualValues(e, 0.9d, 10.12d); + } + + @Test + void assertEqualsWithNullReferences() { + Object null1 = null; + Object null2 = null; + + assertEquals(null1, null); + assertEquals(null, null2); + assertEquals(null1, null2); + } + + @Test + void assertEqualsWithSameObject() { + Object foo = new Object(); + assertEquals(foo, foo); + assertEquals(foo, foo, "message"); + assertEquals(foo, foo, () -> "message"); + } + + @Test + void assertEqualsWithEquivalentStrings() { + assertEquals(new String("foo"), new String("foo")); + } + + @Test + void assertEqualsWithNullVsObject() { + try { + assertEquals(null, "foo"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, null, "foo"); + } + } + + @Test + void assertEqualsWithObjectVsNull() { + try { + assertEquals("foo", null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, "foo", null); + } + } + + @Test + void assertEqualsWithObjectWithNullStringReturnedFromToStringVsNull() { + try { + assertEquals("null", null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "expected: java.lang.String@"); + assertMessageEndsWith(ex, " but was: "); + assertExpectedAndActualValues(ex, "null", null); + } + } + + @Test + void assertEqualsWithNullVsObjectWithNullStringReturnedFromToString() { + try { + assertEquals(null, "null"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "expected: but was: java.lang.String@"); + assertMessageEndsWith(ex, ""); + assertExpectedAndActualValues(ex, null, "null"); + } + } + + @Test + void assertEqualsWithNullVsObjectAndMessageSupplier() { + try { + assertEquals(null, "foo", () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageEndsWith(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, null, "foo"); + } + } + + @Test + void assertEqualsWithObjectVsNullAndMessageSupplier() { + try { + assertEquals("foo", null, () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageEndsWith(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, "foo", null); + } + } + + @Test + void assertEqualsInvokesEqualsMethodForIdenticalObjects() { + Object obj = new EqualsThrowsException(); + assertThrows(NumberFormatException.class, () -> assertEquals(obj, obj)); + } + + @Test + void assertEqualsWithUnequalObjectWhoseToStringImplementationThrowsAnException() { + try { + assertEquals(new ToStringThrowsException(), "foo"); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "expected: <" + ToStringThrowsException.class.getName() + "@"); + assertMessageEndsWith(ex, "but was: "); + } + } + + // ------------------------------------------------------------------------- + + @Nested + class MixedBoxedAndUnboxedPrimitivesTests { + + @Test + void bytes() { + byte primitive = (byte) 42; + Byte wrapper = Byte.valueOf("42"); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, () -> "message"); + } + + @Test + void shorts() { + short primitive = (short) 42; + Short wrapper = Short.valueOf("42"); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, () -> "message"); + } + + @Test + void integers() { + int primitive = 42; + Integer wrapper = Integer.valueOf("42"); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, () -> "message"); + } + + @Test + void longs() { + long primitive = 42L; + Long wrapper = Long.valueOf("42"); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, () -> "message"); + } + + @Test + void floats() { + float primitive = 42.0f; + Float wrapper = Float.valueOf("42.0"); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, 0.0f); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, 0.0f, "message"); + assertEquals(primitive, wrapper, () -> "message"); + assertEquals(primitive, wrapper, 0.0f, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, 0.0f); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, 0.0f, "message"); + assertEquals(wrapper, primitive, () -> "message"); + assertEquals(wrapper, primitive, 0.0f, () -> "message"); + } + + @Test + void doubles() { + double primitive = 42.0d; + Double wrapper = Double.valueOf("42.0"); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, 0.0d); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, 0.0d, "message"); + assertEquals(primitive, wrapper, () -> "message"); + assertEquals(primitive, wrapper, 0.0d, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, 0.0d); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, 0.0d, "message"); + assertEquals(wrapper, primitive, () -> "message"); + assertEquals(wrapper, primitive, 0.0d, () -> "message"); + } + + @Test + void booleans() { + boolean primitive = true; + Boolean wrapper = Boolean.valueOf("true"); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, () -> "message"); + } + + @Test + void chars() { + char primitive = 'a'; + Character wrapper = Character.valueOf('a'); + + assertEquals(primitive, wrapper); + assertEquals(primitive, wrapper, "message"); + assertEquals(primitive, wrapper, () -> "message"); + + assertEquals(wrapper, primitive); + assertEquals(wrapper, primitive, "message"); + assertEquals(wrapper, primitive, () -> "message"); + } + + } + + // ------------------------------------------------------------------------- + + private static class EqualsThrowsException { + + @Override + public boolean equals(Object obj) { + throw new NumberFormatException(); + } + } + + private static class ToStringThrowsException { + + @Override + public String toString() { + throw new NumberFormatException(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java new file mode 100644 index 00000000..ee2f2f61 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertFalseAssertionsTests { + + @Test + void assertFalseWithBooleanFalse() { + assertFalse(false); + assertFalse(false, "test"); + assertFalse(false, () -> "test"); + } + + @Test + void assertFalseWithBooleanSupplierFalse() { + assertFalse(() -> false); + assertFalse(() -> false, "test"); + assertFalse(() -> false, () -> "test"); + } + + @Test + void assertFalseWithBooleanFalseAndMessageSupplier() { + assertFalse(false, () -> "test"); + } + + @Test + void assertFalseWithBooleanSupplierFalseAndMessageSupplier() { + assertFalse(() -> false, () -> "test"); + } + + @Test + void assertFalseWithBooleanTrueAndDefaultMessageWithExpectedAndActualValues() { + try { + assertFalse(true); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, false, true); + } + } + + @Test + void assertFalseWithBooleanTrueAndString() { + try { + assertFalse(true, "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, false, true); + } + } + + @Test + void assertFalseWithBooleanSupplierTrueAndString() { + try { + assertFalse(() -> true, "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, false, true); + } + } + + @Test + void assertFalseWithBooleanTrueAndMessageSupplier() { + try { + assertFalse(true, () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, false, true); + } + } + + @Test + void assertFalseWithBooleanSupplierTrueAndMessageSupplier() { + try { + assertFalse(() -> true, () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, false, true); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java new file mode 100644 index 00000000..70d5882b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; + +import org.junit.jupiter.api.function.Executable; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions#assertInstanceOf(Class, Object)}. + * + * @since 5.8 + */ +class AssertInstanceOfAssertionsTests { + + @Test + void assertInstanceOfFailsNullValue() { + assertInstanceOfFails(String.class, null, "null value"); + } + + @Test + void assertInstanceOfFailsWrongTypeValue() { + assertInstanceOfFails(String.class, 1, "type"); + } + + @Test + void assertInstanceOfFailsWrongExceptionValue() { + assertInstanceOfFails(RuntimeException.class, new IOException(), "type"); + } + + @Test + void assertInstanceOfFailsSuperTypeExceptionValue() { + assertInstanceOfFails(IllegalArgumentException.class, new RuntimeException(), "type"); + } + + private static class BaseClass { + } + + private static class SubClass extends BaseClass { + + } + + @Test + void assertInstanceOfFailsSuperTypeValue() { + assertInstanceOfFails(SubClass.class, new BaseClass(), "type"); + } + + @Test + void assertInstanceOfSucceedsSameTypeValue() { + assertInstanceOfSucceeds(String.class, "indeed a String"); + assertInstanceOfSucceeds(BaseClass.class, new BaseClass()); + assertInstanceOfSucceeds(SubClass.class, new SubClass()); + } + + @Test + void assertInstanceOfSucceedsExpectSuperClassOfValue() { + assertInstanceOfSucceeds(CharSequence.class, "indeed a CharSequence"); + assertInstanceOfSucceeds(BaseClass.class, new SubClass()); + } + + @Test + void assertInstanceOfSucceedsSameTypeExceptionValue() { + assertInstanceOfSucceeds(UnsupportedOperationException.class, new UnsupportedOperationException()); + } + + @Test + void assertInstanceOfSucceedsExpectSuperClassOfExceptionValue() { + assertInstanceOfSucceeds(RuntimeException.class, new IllegalArgumentException("is a RuntimeException")); + } + + private void assertInstanceOfSucceeds(Class expectedType, Object actualValue) { + T res = assertInstanceOf(expectedType, actualValue); + assertSame(res, actualValue); + res = assertInstanceOf(expectedType, actualValue, "extra"); + assertSame(res, actualValue); + res = assertInstanceOf(expectedType, actualValue, () -> "extra"); + assertSame(res, actualValue); + } + + private void assertInstanceOfFails(Class expectedType, Object actualValue, String unexpectedSort) { + String valueType = actualValue == null ? "null" : actualValue.getClass().getCanonicalName(); + String expectedMessage = String.format("Unexpected %s, expected: <%s> but was: <%s>", unexpectedSort, + expectedType.getCanonicalName(), valueType); + + assertThrowsWithMessage(expectedMessage, () -> assertInstanceOf(expectedType, actualValue)); + assertThrowsWithMessage("extra ==> " + expectedMessage, + () -> assertInstanceOf(expectedType, actualValue, "extra")); + assertThrowsWithMessage("extra ==> " + expectedMessage, + () -> assertInstanceOf(expectedType, actualValue, () -> "extra")); + } + + private void assertThrowsWithMessage(String expectedMessage, Executable executable) { + assertEquals(expectedMessage, assertThrows(AssertionFailedError.class, executable).getMessage()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java new file mode 100644 index 00000000..843f2af7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java @@ -0,0 +1,478 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.IterableFactory.listOf; +import static org.junit.jupiter.api.IterableFactory.setOf; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertIterableEqualsAssertionsTests { + + @Test + void assertIterableEqualsEqualToSelf() { + List list = listOf("a", 'b', 1, 2); + assertIterableEquals(list, list); + assertIterableEquals(list, list, "message"); + assertIterableEquals(list, list, () -> "message"); + + Set set = setOf("a", 'b', 1, 2); + assertIterableEquals(set, set); + } + + @Test + void assertIterableEqualsEqualObjectsOfSameType() { + assertIterableEquals(listOf(), listOf()); + assertIterableEquals(listOf("abc"), listOf("abc")); + assertIterableEquals(listOf("abc", 1, 2L, 3D), listOf("abc", 1, 2L, 3D)); + assertIterableEquals(setOf(), setOf()); + assertIterableEquals(setOf("abc"), setOf("abc")); + assertIterableEquals(setOf("abc", 1, 2L, 3D), setOf("abc", 1, 2L, 3D)); + } + + @Test + void assertIterableEqualsNestedIterables() { + assertIterableEquals(listOf(listOf(listOf())), listOf(listOf(listOf()))); + assertIterableEquals(setOf(setOf(setOf())), setOf(setOf(setOf()))); + } + + @Test + void assertIterableEqualsNestedIterablesWithNull() { + assertIterableEquals(listOf(null, listOf(null, listOf(null, null)), null, listOf((List) null)), + listOf(null, listOf(null, listOf(null, null)), null, listOf((List) null))); + assertIterableEquals(setOf(null, setOf(null, setOf(null, null)), null, setOf((Set) null)), + setOf(null, setOf(null, setOf(null, null)), null, setOf((Set) null))); + } + + @Test + void assertIterableEqualsNestedIterablesWithStrings() { + assertIterableEquals(listOf("a", listOf(listOf("b", listOf("c", "d"))), "e"), + listOf("a", listOf(listOf("b", listOf("c", "d"))), "e")); + assertIterableEquals(setOf("a", setOf(setOf("b", setOf("c", "d"))), "e"), + setOf("a", setOf(setOf("b", setOf("c", "d"))), "e")); + } + + @Test + void assertIterableEqualsNestedIterablesWithIntegers() { + assertIterableEquals(listOf(listOf(1), listOf(2), listOf(listOf(3, listOf(4)))), + listOf(listOf(1), listOf(2), listOf(listOf(3, listOf(4))))); + assertIterableEquals(setOf(setOf(1), setOf(2), setOf(setOf(3, setOf(4)))), + setOf(setOf(1), setOf(2), setOf(setOf(3, setOf(4))))); + assertIterableEquals(listOf(listOf(1), listOf(listOf(1))), setOf(setOf(1), setOf(setOf(1)))); + } + + @Test + void assertIterableEqualsNestedIterablesWithDeeplyNestedObject() { + assertIterableEquals(listOf(listOf(listOf(listOf(listOf(listOf(listOf("abc"))))))), + listOf(listOf(listOf(listOf(listOf(listOf(listOf("abc")))))))); + assertIterableEquals(setOf(setOf(setOf(setOf(setOf(setOf(setOf("abc"))))))), + setOf(setOf(setOf(setOf(setOf(setOf(setOf("abc")))))))); + } + + @Test + void assertIterableEqualsNestedIterablesWithNaN() { + assertIterableEquals(listOf(null, listOf(null, Double.NaN, listOf(Float.NaN, null, listOf()))), + listOf(null, listOf(null, Double.NaN, listOf(Float.NaN, null, listOf())))); + assertIterableEquals(setOf(null, setOf(null, Double.NaN, setOf(Float.NaN, null, setOf()))), + setOf(null, setOf(null, Double.NaN, setOf(Float.NaN, null, setOf())))); + } + + @Test + void assertIterableEqualsNestedIterablesWithObjectsOfDifferentTypes() { + assertIterableEquals(listOf(new String("a"), Integer.valueOf(1), listOf(Double.parseDouble("1.1"), "b")), + listOf(new String("a"), Integer.valueOf(1), listOf(Double.parseDouble("1.1"), "b"))); + assertIterableEquals(setOf(new String("a"), Integer.valueOf(1), setOf(Double.parseDouble("1.1"), "b")), + setOf(new String("a"), Integer.valueOf(1), setOf(Double.parseDouble("1.1"), "b"))); + } + + @Test + void assertIterableEqualsNestedIterablesOfMixedSubtypes() { + assertIterableEquals( + listOf(1, 2, listOf(3, setOf(4, 5), setOf(6L), listOf(listOf(setOf(7)))), setOf(8), listOf(setOf(9L))), + listOf(1, 2, listOf(3, setOf(4, 5), setOf(6L), listOf(listOf(setOf(7)))), setOf(8), listOf(setOf(9L)))); + + assertIterableEquals( + listOf("a", setOf('b', 'c'), setOf((int) 'd'), listOf(listOf(listOf("ef"), listOf(listOf("ghi"))))), + setOf("a", listOf('b', 'c'), listOf((int) 'd'), setOf(setOf(setOf("ef"), setOf(setOf("ghi")))))); + } + + @Test + void assertIterableEqualsIterableVsNull() { + try { + assertIterableEquals(null, listOf("a", "b", 1, listOf())); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected iterable was "); + } + + try { + assertIterableEquals(listOf('a', 1, new Object(), 10L), null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual iterable was "); + } + } + + @Test + void assertIterableEqualsNestedIterableVsNull() { + try { + assertIterableEquals(listOf(listOf(), 1, "2", setOf('3', listOf((List) null))), + listOf(listOf(), 1, "2", setOf('3', listOf(listOf("4"))))); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected iterable was at index [3][1][0]"); + } + + try { + assertIterableEquals(setOf(1, 2, listOf(3, listOf("4", setOf(5, setOf(6)))), "7"), + setOf(1, 2, listOf(3, listOf("4", setOf(5, null))), "7")); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "actual iterable was at index [2][1][1][1]"); + } + } + + @Test + void assertIterableEqualsIterableVsNullAndMessage() { + try { + assertIterableEquals(null, listOf('a', "b", 10, 20D), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected iterable was "); + } + + try { + assertIterableEquals(listOf("hello", 42), null, "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual iterable was "); + } + } + + @Test + void assertIterableEqualsNestedIterableVsNullAndMessage() { + try { + assertIterableEquals(listOf(1, listOf(2, 3, listOf(4, 5, listOf((List) null)))), + listOf(1, listOf(2, 3, listOf(4, 5, listOf(listOf(6))))), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected iterable was at index [1][2][2][0]"); + } + + try { + assertIterableEquals(listOf(1, listOf(2, listOf(3, listOf(listOf(4))))), + listOf(1, listOf(2, listOf(3, listOf((List) null)))), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual iterable was at index [1][1][1][0]"); + } + } + + @Test + void assertIterableEqualsIterableVsNullAndMessageSupplier() { + try { + assertIterableEquals(null, setOf(42, "42", listOf(42F), 42D), () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected iterable was "); + } + + try { + assertIterableEquals(listOf(listOf("a"), listOf()), null, () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual iterable was "); + } + } + + @Test + void assertIterableEqualsNestedIterableVsNullAndMessageSupplier() { + try { + assertIterableEquals(listOf("1", "2", "3", listOf("4", listOf((List) null))), + listOf("1", "2", "3", listOf("4", listOf(listOf(5)))), () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "expected iterable was at index [3][1][0]"); + } + + try { + assertIterableEquals(setOf(1, 2, setOf("3", setOf('4', setOf(5, 6, setOf())))), + setOf(1, 2, setOf("3", setOf('4', setOf(5, 6, null)))), () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "actual iterable was at index [2][1][1][2]"); + } + } + + @Test + void assertIterableEqualsIterablesOfDifferentLength() { + try { + assertIterableEquals(listOf('a', "b", 'c'), listOf('a', "b", 'c', 1)); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "iterable lengths differ, expected: <3> but was: <4>"); + } + } + + @Test + void assertIterableEqualsNestedIterablesOfDifferentLength() { + try { + assertIterableEquals(listOf("a", setOf("b", listOf("c", "d", setOf("e", 1, 2, 3)))), + listOf("a", setOf("b", listOf("c", "d", setOf("e", 1, 2, 3, 4, 5))))); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "iterable lengths differ at index [1][1][2], expected: <4> but was: <6>"); + } + + try { + assertIterableEquals(listOf(listOf(listOf(listOf(listOf(listOf(listOf('a'))))))), + listOf(listOf(listOf(listOf(listOf(listOf(listOf('a', 'b')))))))); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "iterable lengths differ at index [0][0][0][0][0][0], expected: <1> but was: <2>"); + } + } + + @Test + void assertIterableEqualsIterablesOfDifferentLengthAndMessage() { + try { + assertIterableEquals(setOf('a', 1), setOf('a', 1, new Object()), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable lengths differ, expected: <2> but was: <3>"); + } + } + + @Test + void assertIterableEqualsNestedIterablesOfDifferentLengthAndMessage() { + try { + assertIterableEquals(listOf('a', 1, listOf(2, 3)), listOf('a', 1, listOf(2, 3, 4, 5)), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable lengths differ at index [2], expected: <2> but was: <4>"); + } + } + + @Test + void assertIterableEqualsIterablesOfDifferentLengthAndMessageSupplier() { + try { + assertIterableEquals(setOf("a", "b", "c"), setOf("a", "b", "c", "d", "e", "f"), () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable lengths differ, expected: <3> but was: <6>"); + } + } + + @Test + void assertIterableEqualsNestedIterablesOfDifferentLengthAndMessageSupplier() { + try { + assertIterableEquals(listOf("a", setOf(1, 2, 3, listOf(4.0, 5.1, 6.1), 7)), + listOf("a", setOf(1, 2, 3, listOf(4.0, 5.1, 6.1, 7.0), 8)), () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable lengths differ at index [1][3], expected: <3> but was: <4>"); + } + } + + @Test + void assertIterableEqualsDifferentIterables() { + try { + assertIterableEquals(listOf(1L, "2", '3', 4, 5D), listOf(1L, "2", '9', 4, 5D)); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "iterable contents differ at index [2], expected: <3> but was: <9>"); + } + + try { + assertIterableEquals(listOf("a", 10, 11, 12, Double.NaN), listOf("a", 10, 11, 12, 13.55D)); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "iterable contents differ at index [4], expected: but was: <13.55>"); + } + } + + @Test + void assertIterableEqualsDifferentNestedIterables() { + try { + assertIterableEquals(listOf(1, 2, listOf(3, listOf(4, listOf(false, true)))), + listOf(1, 2, listOf(3, listOf(4, listOf(true, false))))); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, + "iterable contents differ at index [2][1][1][0], expected: but was: "); + } + + List differentElement = listOf(); + try { + assertIterableEquals(listOf(1, 2, 3, listOf(listOf(4, listOf(5)))), + listOf(1, 2, 3, listOf(listOf(4, listOf(differentElement))))); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, + "iterable contents differ at index [3][0][1][0], expected: <5> but was: <" + differentElement + ">"); + } + } + + @Test + void assertIterableEqualsDifferentIterablesAndMessage() { + try { + assertIterableEquals(listOf(1.1D, 2L, "3"), listOf(1D, 2L, "3"), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable contents differ at index [0], expected: <1.1> but was: <1.0>"); + } + } + + @Test + void assertIterableEqualsDifferentNestedIterablesAndMessage() { + try { + assertIterableEquals(listOf(9, 8, '6', listOf(5, 4, "3", listOf("2", '1'))), + listOf(9, 8, '6', listOf(5, 4, "3", listOf("99", '1'))), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>"); + } + + try { + assertIterableEquals(listOf(9, 8, '6', listOf(5, 4, "3", listOf("2", "1"))), + listOf(9, 8, '6', listOf(5, 4, "3", listOf("99", "1"))), "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>"); + } + } + + @Test + void assertIterableEqualsDifferentIterablesAndMessageSupplier() { + try { + assertIterableEquals(setOf("one", 1L, Double.MIN_VALUE, "abc"), setOf("one", 1L, 42.42, "abc"), + () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable contents differ at index [2], expected: <4.9E-324> but was: <42.42>"); + } + } + + @Test + void assertIterableEqualsDifferentNestedIterablesAndMessageSupplier() { + try { + assertIterableEquals(setOf("one", 1L, setOf("a", 'b', setOf(1, setOf(2, 3))), "abc"), + setOf("one", 1L, setOf("a", 'b', setOf(1, setOf(2, 4))), "abc"), () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable contents differ at index [2][2][1][1], expected: <3> but was: <4>"); + } + + try { + assertIterableEquals(listOf("j", listOf("a"), setOf(42), "ab", setOf(1, listOf(3))), + listOf("j", listOf("a"), setOf(42), "ab", setOf(1, listOf(5))), () -> "message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "message"); + assertMessageEndsWith(ex, "iterable contents differ at index [4][1][0], expected: <3> but was: <5>"); + } + } + + @Test + // https://github.com/junit-team/junit5/issues/2157 + void assertIterableEqualsWithListOfPath() { + var expected = listOf(Path.of("1")); + var actual = listOf(Path.of("1")); + assertDoesNotThrow(() -> assertIterableEquals(expected, actual)); + } + + @Test + void assertIterableEqualsThrowsStackOverflowErrorForInterlockedRecursiveStructures() { + var expected = new ArrayList<>(); + var actual = new ArrayList<>(); + actual.add(expected); + expected.add(actual); + assertThrows(StackOverflowError.class, () -> assertIterableEquals(expected, actual)); + } + + @Test + // https://github.com/junit-team/junit5/issues/2915 + void assertIterableEqualsWithDifferentListOfPath() { + try { + var expected = listOf(Path.of("1").resolve("2")); + var actual = listOf(Path.of("1").resolve("3")); + assertIterableEquals(expected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "iterable contents differ at index [0][1], expected: <2> but was: <3>"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java new file mode 100644 index 00000000..97263cd6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java @@ -0,0 +1,337 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertLinesMatch.isFastForwardLine; +import static org.junit.jupiter.api.AssertLinesMatch.parseFastForwardLimit; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.platform.commons.PreconditionViolationException; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertLinesMatchAssertionsTests { + + @Test + void assertLinesMatchEmptyLists() { + assertLinesMatch(Collections.emptyList(), new ArrayList<>()); + } + + @Test + void assertLinesMatchSameListInstance() { + List list = List.of("first line", "second line", "third line", "last line"); + assertLinesMatch(list, list); + } + + @Test + void assertLinesMatchPlainEqualLists() { + List expected = List.of("first line", "second line", "third line", "last line"); + List actual = List.of("first line", "second line", "third line", "last line"); + assertLinesMatch(expected, actual); + } + + @Test + void assertLinesMatchUsingRegexPatterns() { + List expected = List.of("^first.+line", "second\\s*line", "th.rd l.ne", "last line$"); + List actual = List.of("first line", "second line", "third line", "last line"); + assertLinesMatch(expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerAtEndOfExpectedLines() { + List expected = List.of("first line", ">> ignore all following lines >>"); + List actual = List.of("first line", "I", "II", "III", "IV", "V", "VI", "last line"); + assertLinesMatch(expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarker() { + List expected = List.of("first line", ">> skip lines until next matches >>", "V", "last line"); + List actual = List.of("first line", "I", "II", "III", "IV", "V", "last line"); + assertLinesMatch(expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerWithLimit1() { + List expected = List.of("first line", ">> 1 >>", "last line"); + List actual = List.of("first line", "skipped", "last line"); + assertLinesMatch(expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerWithLimit3() { + List expected = Collections.singletonList(">> 3 >>"); + List actual = List.of("first line", "skipped", "last line"); + assertLinesMatch(expected, actual); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + void assertLinesMatchWithNullFails() { + assertThrows(PreconditionViolationException.class, () -> assertLinesMatch((List) null, (List) null)); + assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(null, Collections.emptyList())); + assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(Collections.emptyList(), null)); + } + + @Test + void assertLinesMatchWithNullElementsFails() { + var list = List.of("1", "2", "3"); + var withNullElement = Arrays.asList("1", null, "3"); // List.of() doesn't permit null values. + assertDoesNotThrow(() -> assertLinesMatch(withNullElement, withNullElement)); + var e1 = assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(withNullElement, list)); + assertEquals("expected line must not be null", e1.getMessage()); + var e2 = assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(list, withNullElement)); + assertEquals("actual line must not be null", e2.getMessage()); + } + + private void assertError(AssertionFailedError error, String expectedMessage, List expectedLines, + List actualLines) { + assertEquals(expectedMessage, error.getMessage()); + assertEquals(String.join(System.lineSeparator(), expectedLines), error.getExpected().getStringRepresentation()); + assertEquals(String.join(System.lineSeparator(), actualLines), error.getActual().getStringRepresentation()); + } + + @Test + void assertLinesMatchMoreExpectedThanActualAvailableFails() { + var expected = List.of("first line", "second line", "third line"); + var actual = List.of("first line", "third line"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + assertError(error, "expected 3 lines, but only got 2", expected, actual); + } + + @Test + void assertLinesMatchFailsWithDescriptiveErrorMessage() { + var expected = List.of("first line", "second line", "third line"); + var actual = List.of("first line", "sec0nd line", "third line"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + var expectedMessage = String.join(System.lineSeparator(), List.of( // + "expected line #2 doesn't match actual line #2", // + "\texpected: `second line`", // + "\t actual: `sec0nd line`")); + assertError(error, expectedMessage, expected, actual); + } + + @Test + void assertLinesMatchMoreActualLinesThenExpectedFails() { + var expected = List.of("first line", "second line", "third line"); + var actual = List.of("first line", "second line", "third line", "last line"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + assertError(error, "more actual lines than expected: 1", expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerWithTooLowLimitFails() { + var expected = List.of("first line", ">> 1 >>"); + var actual = List.of("first line", "skipped", "last line"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + assertError(error, "terminal fast-forward(1) error: fast-forward(2) expected", expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerWithTooHighLimitFails() { + var expected = List.of("first line", ">> 100 >>"); + var actual = List.of("first line", "skipped", "last line"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + assertError(error, "terminal fast-forward(100) error: fast-forward(2) expected", expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerWithTooHighLimitAndFollowingLineFails() { + /* + * It is important here that the line counts are expected <= actual, that the + * fast-forward exceeds the available actual lines and that it is not a + * terminal fast-forward. + */ + var expected = List.of("first line", ">> 3 >>", "not present"); + var actual = List.of("first line", "first skipped", "second skipped"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + assertError(error, "fast-forward(3) error: not enough actual lines remaining (2)", expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerWithoutMatchingNextLineFails() { + var expected = List.of("first line", ">> fails, because next line is >>", "not present"); + var actual = List.of("first line", "skipped", "last line"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + assertError(error, "fast-forward(∞) didn't find: `not present`", expected, actual); + } + + @Test + void assertLinesMatchUsingFastForwardMarkerWithExtraExpectLineFails() { + var expected = List.of("first line", ">> fails, because final line is missing >>", "last line", "not present"); + var actual = List.of("first line", "first skipped", "second skipped", "last line"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); + assertError(error, "expected line #4:`not present` not found - actual lines depleted", expected, actual); + } + + @Test + void assertLinesMatchIsFastForwardLine() { + assertAll("valid fast-forward lines", // + () -> assertTrue(isFastForwardLine(">>>>")), () -> assertTrue(isFastForwardLine(">> >>")), + () -> assertTrue(isFastForwardLine(">> stacktrace >>")), + () -> assertTrue(isFastForwardLine(">> single line, non Integer.parse()-able comment >>")), + () -> assertTrue(isFastForwardLine(">>9>>")), () -> assertTrue(isFastForwardLine(">> 9 >>")), + () -> assertTrue(isFastForwardLine(">> -9 >>")), () -> assertTrue(isFastForwardLine(" >> 9 >> ")), + () -> assertTrue(isFastForwardLine(" >> 9 >> "))); + } + + @Test + void assertLinesMatchParseFastForwardLimit() { + assertAll("valid fast-forward limits", // + () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">>>>")), + () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> >>")), + () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> stacktrace >>")), + () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> non Integer.parse()-able comment >>")), + () -> assertEquals(9, parseFastForwardLimit(">>9>>")), + () -> assertEquals(9, parseFastForwardLimit(">> 9 >>")), + () -> assertEquals(9, parseFastForwardLimit(" >> 9 >> ")), + () -> assertEquals(9, parseFastForwardLimit(" >> 9 >> "))); + Throwable error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>0>>")); + assertMessageEquals(error, "fast-forward(0) limit must be greater than zero"); + error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>-1>>")); + assertMessageEquals(error, "fast-forward(-1) limit must be greater than zero"); + error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>-2147483648>>")); + assertMessageEquals(error, "fast-forward(-2147483648) limit must be greater than zero"); + } + + @Test + void assertLinesMatchMatches() { + Random random = new Random(); + assertAll("do match", // + () -> assertTrue( + AssertLinesMatch.matches("duration: [\\d]+ ms", "duration: " + random.nextInt(1000) + " ms")), + () -> assertTrue(AssertLinesMatch.matches("123", "123")), + () -> assertTrue(AssertLinesMatch.matches(".*", "123")), + () -> assertTrue(AssertLinesMatch.matches("\\d+", "123"))); + assertAll("don't match", // + () -> assertFalse( + AssertLinesMatch.matches("duration: [\\d]+ ms", "duration: " + random.nextGaussian() + " ms")), + () -> assertFalse(AssertLinesMatch.matches("12", "123")), + () -> assertFalse(AssertLinesMatch.matches("..+", "1")), + () -> assertFalse(AssertLinesMatch.matches("\\d\\d+", "1"))); + } + + @Test + void largeListsThatDoNotMatchAreTruncated() { + var expected = IntStream.range(1, 999).boxed().map(Object::toString).collect(Collectors.toList()); + var actual = IntStream.range(0, 1000).boxed().map(Object::toString).collect(Collectors.toList()); + var error = assertThrows(AssertionFailedError.class, + () -> assertLinesMatch(expected, actual, "custom message")); + var expectedMessage = String.join(System.lineSeparator(), List.of( // + "custom message ==> expected line #1 doesn't match actual line #1", // + "\texpected: `1`", // + "\t actual: `0`")); + assertError(error, expectedMessage, expected, actual); + } + + /** + * @since 5.5 + */ + @Nested + class WithCustomFailureMessage { + @Test + void simpleStringMessage() { + String message = "XXX"; + var expected = List.of("a", "b", "c"); + var actual = List.of("a", "d", "c"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual, message)); + var expectedMessage = String.join(System.lineSeparator(), List.of( // + message + " ==> expected line #2 doesn't match actual line #2", // + "\texpected: `b`", // + "\t actual: `d`")); + assertError(error, expectedMessage, expected, actual); + } + + @Test + void stringSupplierWithMultiLineMessage() { + var message = "XXX\nYYY"; + Supplier supplier = () -> message; + var expected = List.of("a", "b", "c"); + var actual = List.of("a", "d", "c"); + var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual, supplier)); + var expectedMessage = String.join(System.lineSeparator(), List.of( // + message + " ==> expected line #2 doesn't match actual line #2", // + "\texpected: `b`", // + "\t actual: `d`")); + assertError(error, expectedMessage, expected, actual); + } + } + + @Nested + class WithStreamsOfStrings { + @Test + void assertLinesMatchEmptyStreams() { + assertLinesMatch(Stream.empty(), Stream.empty()); + } + + @Test + void assertLinesMatchSameListInstance() { + Stream stream = Stream.of("first line", "second line", "third line", "last line"); + assertLinesMatch(stream, stream); + } + + @Test + void assertLinesMatchPlainEqualLists() { + var expected = """ + first line + second line + third line + last line + """; + var actual = """ + first line + second line + third line + last line + """; + assertLinesMatch(expected.lines(), actual.lines()); + } + + @Test + void assertLinesMatchUsingRegexPatterns() { + var expected = """ + ^first.+line + second\\s*line + th.rd l.ne + last line$ + """; + var actual = """ + first line + second line + third line + last line + """; + assertLinesMatch(expected.lines(), actual.lines()); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java new file mode 100644 index 00000000..1606c6be --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java @@ -0,0 +1,768 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertNotEqualsAssertionsTests { + + @Nested + class AssertNotEqualsByte { + + @Test + void assertNotEqualsByte() { + byte unexpected = 1; + byte actual = 2; + assertNotEquals(unexpected, actual); + assertNotEquals(unexpected, actual, "message"); + assertNotEquals(unexpected, actual, () -> "message"); + } + + @Test + void withEqualValues() { + byte unexpected = 1; + byte actual = 1; + try { + assertNotEquals(unexpected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessage() { + byte unexpected = 1; + byte actual = 1; + try { + assertNotEquals(unexpected, actual, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + byte unexpected = 1; + byte actual = 1; + try { + assertNotEquals(unexpected, actual, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + } + + @Nested + class AssertNotEqualsShort { + + @Test + void assertNotEqualsShort() { + short unexpected = 1; + short actual = 2; + assertNotEquals(unexpected, actual); + assertNotEquals(unexpected, actual, "message"); + assertNotEquals(unexpected, actual, () -> "message"); + } + + @Test + void withEqualValues() { + short unexpected = 1; + short actual = 1; + try { + assertNotEquals(unexpected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessage() { + short unexpected = 1; + short actual = 1; + try { + assertNotEquals(unexpected, actual, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + short unexpected = 1; + short actual = 1; + try { + assertNotEquals(unexpected, actual, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + } + + @Nested + class AssertNotEqualsChar { + + @Test + void assertNotEqualsChar() { + char unexpected = 'a'; + char actual = 'b'; + assertNotEquals(unexpected, actual); + assertNotEquals(unexpected, actual, "message"); + assertNotEquals(unexpected, actual, () -> "message"); + } + + @Test + void withEqualValues() { + char unexpected = 'a'; + char actual = 'a'; + try { + assertNotEquals(unexpected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: "); + } + } + + @Test + void withEqualValuesWithMessage() { + char unexpected = 'a'; + char actual = 'a'; + try { + assertNotEquals(unexpected, actual, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: "); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + char unexpected = 'a'; + char actual = 'a'; + try { + assertNotEquals(unexpected, actual, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: "); + } + } + + } + + @Nested + class AssertNotEqualsInt { + + @Test + void assertNotEqualsInt() { + int unexpected = 1; + int actual = 2; + assertNotEquals(unexpected, actual); + assertNotEquals(unexpected, actual, "message"); + assertNotEquals(unexpected, actual, () -> "message"); + } + + @Test + void withEqualValues() { + int unexpected = 1; + int actual = 1; + try { + assertNotEquals(unexpected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessage() { + int unexpected = 1; + int actual = 1; + try { + assertNotEquals(unexpected, actual, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + int unexpected = 1; + int actual = 1; + try { + assertNotEquals(unexpected, actual, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + } + + @Nested + class AssertNotEqualsLong { + + @Test + void assertNotEqualsLong() { + long unexpected = 1L; + long actual = 2L; + assertNotEquals(unexpected, actual); + assertNotEquals(unexpected, actual, "message"); + assertNotEquals(unexpected, actual, () -> "message"); + } + + @Test + void withEqualValues() { + long unexpected = 1L; + long actual = 1L; + try { + assertNotEquals(unexpected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessage() { + long unexpected = 1L; + long actual = 1L; + try { + assertNotEquals(unexpected, actual, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + long unexpected = 1L; + long actual = 1L; + try { + assertNotEquals(unexpected, actual, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1>"); + } + } + + } + + @Nested + class AssertNotEqualsFloatWithoutDelta { + + @Test + void assertNotEqualsFloat() { + float unexpected = 1.0f; + float actual = 2.0f; + assertNotEquals(unexpected, actual); + assertNotEquals(unexpected, actual, "message"); + assertNotEquals(unexpected, actual, () -> "message"); + } + + @Test + void assertNotEqualsForTwoNaNFloat() { + try { + assertNotEquals(Float.NaN, Float.NaN); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: "); + } + } + + @Test + void assertNotEqualsForPositiveInfinityFloat() { + try { + assertNotEquals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: "); + } + } + + @Test + void assertNotEqualsForNegativeInfinityFloat() { + try { + assertNotEquals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <-Infinity>"); + } + } + + @Test + void withEqualValues() { + float unexpected = 1.0f; + float actual = 1.0f; + try { + assertNotEquals(unexpected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1.0>"); + } + } + + @Test + void withEqualValuesWithMessage() { + float unexpected = 1.0f; + float actual = 1.0f; + try { + assertNotEquals(unexpected, actual, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + float unexpected = 1.0f; + float actual = 1.0f; + try { + assertNotEquals(unexpected, actual, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); + } + } + + } + + @Nested + class AssertNotEqualsFloatWithDelta { + + @Test + void assertNotEqualsFloat() { + assertNotEquals(1.0f, 1.5f, 0.4f); + assertNotEquals(1.0f, 1.5f, 0.4f, "message"); + assertNotEquals(1.0f, 1.5f, 0.4f, () -> "message"); + } + + @Test + void withEqualValues() { + float unexpected = 1.0f; + float actual = 1.5f; + float delta = 0.5f; + try { + assertNotEquals(unexpected, actual, delta); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1.5>"); + } + } + + @Test + void withEqualValuesWithMessage() { + float unexpected = 1.0f; + float actual = 1.5f; + float delta = 0.5f; + try { + assertNotEquals(unexpected, actual, delta, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + float unexpected = 1.0f; + float actual = 1.5f; + float delta = 0.5f; + try { + assertNotEquals(unexpected, actual, delta, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); + } + } + + } + + @Nested + class AssertNotEqualsDoubleWithoutDelta { + + @Test + void assertNotEqualsDouble() { + double unexpected = 1.0d; + double actual = 2.0d; + assertNotEquals(unexpected, actual); + assertNotEquals(unexpected, actual, "message"); + assertNotEquals(unexpected, actual, () -> "message"); + } + + @Test + void assertNotEqualsForTwoNaNDouble() { + try { + assertNotEquals(Double.NaN, Double.NaN); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: "); + } + } + + @Test + void withEqualValues() { + double unexpected = 1.0d; + double actual = 1.0d; + try { + assertNotEquals(unexpected, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1.0>"); + } + } + + @Test + void withEqualValuesWithMessage() { + double unexpected = 1.0d; + double actual = 1.0d; + try { + assertNotEquals(unexpected, actual, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + double unexpected = 1.0d; + double actual = 1.0d; + try { + assertNotEquals(unexpected, actual, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); + } + } + + } + + @Nested + class AssertNotEqualsDoubleWithDelta { + + @Test + void assertNotEqualsDouble() { + assertNotEquals(1.0d, 1.5d, 0.4d); + assertNotEquals(1.0d, 1.5d, 0.4d, "message"); + assertNotEquals(1.0d, 1.5d, 0.4d, () -> "message"); + } + + @Test + void withEqualValues() { + double unexpected = 1.0d; + double actual = 1.5d; + double delta = 0.5d; + try { + assertNotEquals(unexpected, actual, delta); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not equal but was: <1.5>"); + } + } + + @Test + void withEqualValuesWithMessage() { + double unexpected = 1.0d; + double actual = 1.5d; + double delta = 0.5d; + try { + assertNotEquals(unexpected, actual, delta, "custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); + } + } + + @Test + void withEqualValuesWithMessageSupplier() { + double unexpected = 1.0d; + double actual = 1.5d; + double delta = 0.5d; + try { + assertNotEquals(unexpected, actual, delta, () -> "custom message from supplier"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "custom message from supplier"); + assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); + } + } + + } + + @Nested + class AssertNotEqualsObject { + + @Test + void assertNotEqualsWithNullVsObject() { + assertNotEquals(null, "foo"); + } + + @Test + void assertNotEqualsWithObjectVsNull() { + assertNotEquals("foo", null); + } + + @Test + void assertNotEqualsWithDifferentObjects() { + assertNotEquals(new Object(), new Object()); + assertNotEquals(new Object(), new Object(), "message"); + assertNotEquals(new Object(), new Object(), () -> "message"); + } + + @Test + void assertNotEqualsWithNullVsObjectAndMessageSupplier() { + assertNotEquals(null, "foo", () -> "test"); + } + + @Test + void assertNotEqualsWithEquivalentStringsAndMessage() { + try { + assertNotEquals(new String("foo"), new String("foo"), "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageEndsWith(ex, "expected: not equal but was: "); + } + } + + @Test + void assertNotEqualsWithEquivalentStringsAndMessageSupplier() { + try { + assertNotEquals(new String("foo"), new String("foo"), () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageEndsWith(ex, "expected: not equal but was: "); + } + } + + @Test + void assertNotEqualsInvokesEqualsMethodForIdenticalObjects() { + Object obj = new EqualsThrowsExceptionClass(); + assertThrows(NumberFormatException.class, () -> assertNotEquals(obj, obj)); + } + + } + + // ------------------------------------------------------------------------- + + @Nested + class MixedBoxedAndUnboxedPrimitivesTests { + + @Test + void bytes() { + byte primitive = (byte) 42; + Byte wrapper = Byte.valueOf("99"); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + } + + @Test + void shorts() { + short primitive = (short) 42; + Short wrapper = Short.valueOf("99"); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + } + + @Test + void integers() { + int primitive = 42; + Integer wrapper = Integer.valueOf("99"); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + } + + @Test + void longs() { + long primitive = 42L; + Long wrapper = Long.valueOf("99"); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + } + + @Test + void floats() { + float primitive = 42.0f; + Float wrapper = Float.valueOf("99.0"); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, 0.0f); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, 0.0f, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + assertNotEquals(primitive, wrapper, 0.0f, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, 0.0f); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, 0.0f, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + assertNotEquals(wrapper, primitive, 0.0f, () -> "message"); + } + + @Test + void doubles() { + double primitive = 42.0d; + Double wrapper = Double.valueOf("99.0"); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, 0.0d); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, 0.0d, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + assertNotEquals(primitive, wrapper, 0.0d, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, 0.0d); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, 0.0d, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + assertNotEquals(wrapper, primitive, 0.0d, () -> "message"); + } + + @Test + void booleans() { + boolean primitive = true; + Boolean wrapper = Boolean.valueOf("false"); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + } + + @Test + void chars() { + char primitive = 'a'; + Character wrapper = Character.valueOf('z'); + + assertNotEquals(primitive, wrapper); + assertNotEquals(primitive, wrapper, "message"); + assertNotEquals(primitive, wrapper, () -> "message"); + + assertNotEquals(wrapper, primitive); + assertNotEquals(wrapper, primitive, "message"); + assertNotEquals(wrapper, primitive, () -> "message"); + } + + } + + // ------------------------------------------------------------------------- + + private static class EqualsThrowsExceptionClass { + + @Override + public boolean equals(Object obj) { + throw new NumberFormatException(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java new file mode 100644 index 00000000..bf234197 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertNotNullAssertionsTests { + + @Test + void assertNotNullWithNonNullObject() { + assertNotNull("foo"); + assertNotNull("foo", "message"); + assertNotNull("foo", () -> "message"); + } + + @Test + void assertNotNullWithNonNullObjectAndMessageSupplier() { + assertNotNull("foo", () -> "should not fail"); + } + + @Test + @SuppressWarnings("unused") + void assertNotNullWithNull() { + try { + assertNotNull(null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not "); + } + } + + @Test + @SuppressWarnings("unused") + void assertNotNullWithNullAndMessageSupplier() { + try { + assertNotNull(null, () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageEndsWith(ex, "expected: not "); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java new file mode 100644 index 00000000..4429f33b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertNotSameAssertionsTests { + + @Test + void assertNotSameWithDifferentObjects() { + assertNotSame(new Object(), new Object()); + assertNotSame(new Object(), new Object(), "message"); + assertNotSame(new Object(), new Object(), () -> "message"); + } + + @Test + void assertNotSameWithDifferentObjectsAndMessageSupplier() { + assertNotSame(new Object(), new Object(), () -> "should not fail"); + } + + @Test + void assertNotSameWithObjectVsNull() { + assertNotSame(new Object(), null); + } + + @Test + void assertNotSameWithNullVsObject() { + assertNotSame(null, new Object()); + } + + @Test + void assertNotSameWithTwoNulls() { + try { + assertNotSame(null, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: not same but was: "); + } + } + + @Test + void assertNotSameWithSameObjectAndMessage() { + try { + Object foo = new Object(); + assertNotSame(foo, foo, "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageContains(ex, "expected: not same but was: "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageContains(ex, "expected: not same but was: "message"); + } + + @Test + void assertNullWithNullAndMessageSupplier() { + assertNull(null, () -> "test"); + } + + @Test + @SuppressWarnings("unused") + void assertNullWithNonNullObject() { + try { + assertNull("foo"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, null, "foo"); + } + } + + @Test + void assertNullWithNonNullObjectWithNullStringReturnedFromToString() { + assertNullWithNonNullObjectWithNullStringReturnedFromToString(null); + } + + @Test + void assertNullWithNonNullObjectWithNullStringReturnedFromToStringAndMessageSupplier() { + assertNullWithNonNullObjectWithNullStringReturnedFromToString(() -> "boom"); + } + + @SuppressWarnings("unused") + private void assertNullWithNonNullObjectWithNullStringReturnedFromToString(Supplier messageSupplier) { + String actual = "null"; + try { + if (messageSupplier == null) { + assertNull(actual); + } + else { + assertNull(actual, messageSupplier); + } + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Should look something like: + // expected: but was: java.lang.String@264b3504 + String prefix = (messageSupplier != null ? messageSupplier.get() + " ==> " : ""); + assertMessageMatches(ex, prefix + "expected: but was: java\\.lang\\.String@.+"); + assertExpectedAndActualValues(ex, null, actual); + } + } + + @Test + void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString() { + assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(null); + } + + @Test + void assertNullWithNonNullObjectWithNullReferenceReturnedFromToStringAndMessageSupplier() { + assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(() -> "boom"); + } + + @SuppressWarnings("unused") + private void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(Supplier messageSupplier) { + Object actual = new NullToString(); + try { + if (messageSupplier == null) { + assertNull(actual); + } + else { + assertNull(actual, messageSupplier); + } + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Should look something like: + // expected: but was: org.junit.jupiter.api.AssertNullAssertionsTests$NullToString@4e7912d8 + String prefix = (messageSupplier != null ? messageSupplier.get() + " ==> " : ""); + assertMessageMatches(ex, prefix + + "expected: but was: org\\.junit\\.jupiter\\.api\\.AssertNullAssertionsTests\\$NullToString@.+"); + assertExpectedAndActualValues(ex, null, actual); + } + } + + @Test + @SuppressWarnings("unused") + void assertNullWithNonNullObjectAndMessage() { + try { + assertNull("foo", "a message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "a message ==> expected: but was: "); + assertExpectedAndActualValues(ex, null, "foo"); + } + } + + @Test + @SuppressWarnings("unused") + void assertNullWithNonNullObjectAndMessageSupplier() { + try { + assertNull("foo", () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, null, "foo"); + } + } + + private static class NullToString { + + @Override + public String toString() { + return null; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java new file mode 100644 index 00000000..e9ab08de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertSameAssertionsTests { + + @Test + void assertSameWithTwoNulls() { + assertSame(null, null); + assertSame(null, null, () -> "should not fail"); + } + + @Test + void assertSameWithSameObject() { + Object foo = new Object(); + assertSame(foo, foo); + assertSame(foo, foo, "message"); + assertSame(foo, foo, () -> "message"); + } + + @Test + void assertSameWithObjectVsNull() { + Object expected = new Object(); + try { + assertSame(expected, null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageContains(ex, "expected: "); + assertExpectedAndActualValues(ex, expected, null); + } + } + + @Test + void assertSameWithNullVsObject() { + Object actual = new Object(); + try { + assertSame(null, actual); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "test"); + assertMessageContains(ex, "expected: java.lang.String@"); + assertMessageContains(ex, "but was: java.lang.String@"); + assertExpectedAndActualValues(ex, expected, actual); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java new file mode 100644 index 00000000..7a807db6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java @@ -0,0 +1,295 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.Serial; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +import org.junit.jupiter.api.function.Executable; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +@SuppressWarnings("ExcessiveLambdaUsage") +class AssertThrowsAssertionsTests { + + private static final Executable nix = () -> { + }; + + @Test + void assertThrowsWithMethodReferenceForNonVoidReturnType() { + FutureTask future = new FutureTask<>(() -> { + throw new RuntimeException("boom"); + }); + future.run(); + + ExecutionException exception = assertThrows(ExecutionException.class, future::get); + assertEquals("boom", exception.getCause().getMessage()); + } + + @Test + void assertThrowsWithMethodReferenceForVoidReturnType() { + var object = new Object(); + IllegalMonitorStateException exception; + + exception = assertThrows(IllegalMonitorStateException.class, object::notify); + assertNotNull(exception); + + // Note that Object.wait(...) is an overloaded method with a void return type + exception = assertThrows(IllegalMonitorStateException.class, object::wait); + assertNotNull(exception); + } + + @Test + void assertThrowsWithExecutableThatThrowsThrowable() { + EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { + throw new EnigmaThrowable(); + }); + assertNotNull(enigmaThrowable); + } + + @Test + void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { + EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { + throw new EnigmaThrowable(); + }, "message"); + assertNotNull(enigmaThrowable); + } + + @Test + void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { + EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { + throw new EnigmaThrowable(); + }, () -> "message"); + assertNotNull(enigmaThrowable); + } + + @Test + void assertThrowsWithExecutableThatThrowsCheckedException() { + IOException exception = assertThrows(IOException.class, () -> { + throw new IOException(); + }); + assertNotNull(exception); + } + + @Test + void assertThrowsWithExecutableThatThrowsRuntimeException() { + IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, () -> { + throw new IllegalStateException(); + }); + assertNotNull(illegalStateException); + } + + @Test + void assertThrowsWithExecutableThatThrowsError() { + StackOverflowError stackOverflowError = assertThrows(StackOverflowError.class, + AssertionTestUtils::recurseIndefinitely); + assertNotNull(stackOverflowError); + } + + @Test + void assertThrowsWithExecutableThatDoesNotThrowAnException() { + try { + assertThrows(IllegalStateException.class, nix); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Expected java.lang.IllegalStateException to be thrown, but nothing was thrown."); + } + } + + @Test + void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageString() { + try { + assertThrows(IOException.class, nix, "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionError ex) { + assertMessageEquals(ex, + "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); + } + } + + @Test + void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() { + try { + assertThrows(IOException.class, nix, () -> "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionError ex) { + assertMessageEquals(ex, + "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { + try { + assertThrows(IllegalStateException.class, () -> { + throw new NumberFormatException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { + try { + assertThrows(IllegalStateException.class, () -> { + throw new NumberFormatException(); + }, "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Should look something like this: + // Custom message ==> Unexpected exception type thrown, expected: but was: + assertMessageStartsWith(ex, "Custom message ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { + try { + assertThrows(IllegalStateException.class, () -> { + throw new NumberFormatException(); + }, () -> "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Should look something like this: + // Custom message ==> Unexpected exception type thrown, expected: but was: + assertMessageStartsWith(ex, "Custom message ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); + } + } + + @Test + @SuppressWarnings("serial") + void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { + try { + assertThrows(IllegalStateException.class, () -> { + throw new NumberFormatException() { + }; + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + // As of the time of this writing, the class name of the above anonymous inner + // class is org.junit.jupiter.api.AssertionsAssertThrowsTests$2; however, hard + // coding "$2" is fragile. So we just check for the presence of the "$" + // appended to this class's name. + assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { + try { + assertThrows(IllegalStateException.class, () -> { + throw new LocalException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + // The following verifies that the canonical name is used (i.e., "." instead of "$"). + assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); + assertThat(ex).hasCauseInstanceOf(LocalException.class); + } + } + + @Test + @SuppressWarnings("unchecked") + void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoader() throws Exception { + try (EnigmaClassLoader enigmaClassLoader = new EnigmaClassLoader()) { + + // Load expected exception type from different class loader + Class enigmaThrowableClass = (Class) enigmaClassLoader.loadClass( + EnigmaThrowable.class.getName()); + + try { + assertThrows(enigmaThrowableClass, () -> { + throw new EnigmaThrowable(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Example Output: + // + // Unexpected exception type thrown, + // expected: + // but was: + + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + // The presence of the "@" sign is sufficient to indicate that the hash was + // generated to disambiguate between the two identical class names. + assertMessageContains(ex, "expected: loadClass(String name) throws ClassNotFoundException { + return (EnigmaThrowable.class.getName().equals(name) ? findClass(name) : super.loadClass(name)); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java new file mode 100644 index 00000000..79c1f328 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java @@ -0,0 +1,335 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import java.io.IOException; +import java.io.Serial; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +import org.junit.jupiter.api.function.Executable; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.8 + */ +@SuppressWarnings("ExcessiveLambdaUsage") +class AssertThrowsExactlyAssertionsTests { + + private static final Executable nix = () -> { + }; + + @Test + void assertThrowsExactlyTheSpecifiedExceptionClass() { + var actual = assertThrowsExactly(EnigmaThrowable.class, () -> { + throw new EnigmaThrowable(); + }); + assertNotNull(actual); + } + + @Test + void assertThrowsExactlyWithTheExpectedChildException() { + try { + assertThrowsExactly(RuntimeException.class, () -> { + throw new Exception(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(Exception.class); + } + } + + @Test + void assertThrowsExactlyWithTheExpectedParentException() { + try { + assertThrowsExactly(RuntimeException.class, () -> { + throw new NumberFormatException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); + } + } + + @Test + void assertThrowsWithMethodReferenceForNonVoidReturnType() { + FutureTask future = new FutureTask<>(() -> { + throw new RuntimeException("boom"); + }); + future.run(); + + ExecutionException exception = assertThrowsExactly(ExecutionException.class, future::get); + assertEquals("boom", exception.getCause().getMessage()); + } + + @Test + void assertThrowsWithMethodReferenceForVoidReturnType() { + var object = new Object(); + IllegalMonitorStateException exception; + + exception = assertThrowsExactly(IllegalMonitorStateException.class, object::notify); + assertNotNull(exception); + + // Note that Object.wait(...) is an overloaded method with a void return type + exception = assertThrowsExactly(IllegalMonitorStateException.class, object::wait); + assertNotNull(exception); + } + + @Test + void assertThrowsWithExecutableThatThrowsThrowable() { + EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { + throw new EnigmaThrowable(); + }); + assertNotNull(enigmaThrowable); + } + + @Test + void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { + EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { + throw new EnigmaThrowable(); + }, "message"); + assertNotNull(enigmaThrowable); + } + + @Test + void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { + EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { + throw new EnigmaThrowable(); + }, () -> "message"); + assertNotNull(enigmaThrowable); + } + + @Test + void assertThrowsWithExecutableThatThrowsCheckedException() { + IOException exception = assertThrowsExactly(IOException.class, () -> { + throw new IOException(); + }); + assertNotNull(exception); + } + + @Test + void assertThrowsWithExecutableThatThrowsRuntimeException() { + IllegalStateException illegalStateException = assertThrowsExactly(IllegalStateException.class, () -> { + throw new IllegalStateException(); + }); + assertNotNull(illegalStateException); + } + + @Test + void assertThrowsWithExecutableThatThrowsError() { + StackOverflowError stackOverflowError = assertThrowsExactly(StackOverflowError.class, + AssertionTestUtils::recurseIndefinitely); + assertNotNull(stackOverflowError); + } + + @Test + void assertThrowsWithExecutableThatDoesNotThrowAnException() { + try { + assertThrowsExactly(IllegalStateException.class, nix); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "Expected java.lang.IllegalStateException to be thrown, but nothing was thrown."); + } + } + + @Test + void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageString() { + try { + assertThrowsExactly(IOException.class, nix, "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionError ex) { + assertMessageEquals(ex, + "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); + } + } + + @Test + void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() { + try { + assertThrowsExactly(IOException.class, nix, () -> "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionError ex) { + assertMessageEquals(ex, + "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { + try { + assertThrowsExactly(IllegalStateException.class, () -> { + throw new NumberFormatException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { + try { + assertThrowsExactly(IllegalStateException.class, () -> { + throw new NumberFormatException(); + }, "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Should look something like this: + // Custom message ==> Unexpected exception type thrown, expected: but was: + assertMessageStartsWith(ex, "Custom message ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { + try { + assertThrowsExactly(IllegalStateException.class, () -> { + throw new NumberFormatException(); + }, () -> "Custom message"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Should look something like this: + // Custom message ==> Unexpected exception type thrown, expected: but was: + assertMessageStartsWith(ex, "Custom message ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); + } + } + + @Test + @SuppressWarnings("serial") + void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { + try { + assertThrowsExactly(IllegalStateException.class, () -> { + throw new NumberFormatException() { + }; + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + // As of the time of this writing, the class name of the above anonymous inner + // class is org.junit.jupiter.api.AssertThrowsExactlyAssertionsTests$2; however, hard + // coding "$2" is fragile. So we just check for the presence of the "$" + // appended to this class's name. + assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); + } + } + + @Test + void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { + try { + assertThrowsExactly(IllegalStateException.class, () -> { + throw new LocalException(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + assertMessageContains(ex, "expected: "); + // The following verifies that the canonical name is used (i.e., "." instead of "$"). + assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); + assertThat(ex).hasCauseExactlyInstanceOf(LocalException.class); + } + } + + @Test + @SuppressWarnings("unchecked") + void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoader() throws Exception { + try (EnigmaClassLoader enigmaClassLoader = new EnigmaClassLoader()) { + + // Load expected exception type from different class loader + Class enigmaThrowableClass = (Class) enigmaClassLoader.loadClass( + EnigmaThrowable.class.getName()); + + try { + assertThrowsExactly(enigmaThrowableClass, () -> { + throw new EnigmaThrowable(); + }); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + // Example Output: + // + // Unexpected exception type thrown, + // expected: + // but was: + + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); + // The presence of the "@" sign is sufficient to indicate that the hash was + // generated to disambiguate between the two identical class names. + assertMessageContains(ex, "expected: loadClass(String name) throws ClassNotFoundException { + return (EnigmaThrowable.class.getName().equals(name) ? findClass(name) : super.loadClass(name)); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java new file mode 100644 index 00000000..65ef3620 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.time.Duration.ofMillis; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeout; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.ExceptionUtils; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for {@link AssertTimeout}. + * + * @since 5.0 + */ +class AssertTimeoutAssertionsTests { + + private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); + + private final Executable nix = () -> { + }; + + // --- executable ---------------------------------------------------------- + + @Test + void assertTimeoutForExecutableThatCompletesBeforeTheTimeout() { + changed.get().set(false); + assertTimeout(ofMillis(500), () -> changed.get().set(true)); + assertTrue(changed.get().get(), "should have executed in the same thread"); + assertTimeout(ofMillis(500), nix, "message"); + assertTimeout(ofMillis(500), nix, () -> "message"); + } + + @Test + void assertTimeoutForExecutableThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, () -> assertTimeout(ofMillis(500), () -> { + throw new RuntimeException("not this time"); + })); + assertMessageEquals(exception, "not this time"); + } + + @Test + void assertTimeoutForExecutableThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, + () -> assertTimeout(ofMillis(500), () -> fail("enigma"))); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeout(ofMillis(10), this::nap)); + assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); + } + + @Test + void assertTimeoutWithMessageForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeout(ofMillis(10), this::nap, "Tempus Fugit")); + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); + } + + @Test + void assertTimeoutWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeout(ofMillis(10), this::nap, () -> "Tempus" + " " + "Fugit")); + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); + } + + // --- supplier ------------------------------------------------------------ + + @Test + void assertTimeoutForSupplierThatCompletesBeforeTheTimeout() { + changed.get().set(false); + String result = assertTimeout(ofMillis(500), () -> { + changed.get().set(true); + return "Tempus Fugit"; + }); + assertTrue(changed.get().get(), "should have executed in the same thread"); + assertEquals("Tempus Fugit", result); + assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", "message")); + assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", () -> "message")); + } + + @Test + void assertTimeoutForSupplierThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, () -> { + assertTimeout(ofMillis(500), () -> { + ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); + return "Tempus Fugit"; + }); + }); + assertMessageEquals(exception, "not this time"); + } + + @Test + void assertTimeoutForSupplierThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { + assertTimeout(ofMillis(500), () -> { + fail("enigma"); + return "Tempus Fugit"; + }); + }); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeout(ofMillis(10), () -> { + nap(); + return "Tempus Fugit"; + }); + }); + assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); + } + + @Test + void assertTimeoutWithMessageForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeout(ofMillis(10), () -> { + nap(); + return "Tempus Fugit"; + }, "Tempus Fugit"); + }); + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); + } + + @Test + void assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeout(ofMillis(10), () -> { + nap(); + return "Tempus Fugit"; + }, () -> "Tempus" + " " + "Fugit"); + }); + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); + } + + /** + * Take a nap for 100 milliseconds. + */ + private void nap() throws InterruptedException { + long start = System.currentTimeMillis(); + // workaround for imprecise clocks (yes, Windows, I'm talking about you) + do { + Thread.sleep(100); + } while (System.currentTimeMillis() - start < 100); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java new file mode 100644 index 00000000..c1570e6a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java @@ -0,0 +1,255 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.time.Duration.ofMillis; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.OS.WINDOWS; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.ExceptionUtils; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for {@link AssertTimeoutPreemptively}. + * + * @since 5.0 + */ +class AssertTimeoutPreemptivelyAssertionsTests { + + private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); + private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, + ____) -> new TimeoutException(); + + private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); + + private final Executable nix = () -> { + }; + + // --- executable ---------------------------------------------------------- + + @Test + void assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { + changed.get().set(false); + assertTimeoutPreemptively(ofMillis(500), () -> changed.get().set(true)); + assertFalse(changed.get().get(), "should have executed in a different thread"); + assertTimeoutPreemptively(ofMillis(500), nix, "message"); + assertTimeoutPreemptively(ofMillis(500), nix, () -> "message"); + } + + @Test + void assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, + () -> assertTimeoutPreemptively(ofMillis(500), () -> { + throw new RuntimeException("not this time"); + })); + assertMessageEquals(exception, "not this time"); + } + + @Test + void assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"))); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt)); + assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, "Tempus Fugit")); + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, + () -> "Tempus" + " " + "Fugit")); + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { + assertTimeoutPreemptively(ofMillis(500), nix, () -> "Tempus" + " " + "Fugit"); + } + + // --- supplier ------------------------------------------------------------ + + @Test + void assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { + changed.get().set(false); + String result = assertTimeoutPreemptively(ofMillis(500), () -> { + changed.get().set(true); + return "Tempus Fugit"; + }); + assertFalse(changed.get().get(), "should have executed in a different thread"); + assertEquals("Tempus Fugit", result); + assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", "message")); + assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", () -> "message")); + } + + @Test + void assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, () -> { + assertTimeoutPreemptively(ofMillis(500), () -> { + ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); + return "Tempus Fugit"; + }); + }); + assertMessageEquals(exception, "not this time"); + } + + @Test + void assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(ofMillis(500), () -> { + fail("enigma"); + return "Tempus Fugit"; + }); + }); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }); + }); + + assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, "Tempus Fugit"); + }); + + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, () -> "Tempus" + " " + "Fugit"); + }); + + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { + AtomicReference threadName = new AtomicReference<>(""); + assertTimeoutPreemptively(ofMillis(1000), () -> threadName.set(Thread.currentThread().getName())); + assertTrue(threadName.get().startsWith("junit-timeout-thread-"), + "Thread name does not match the expected prefix"); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { + assertThrows(TimeoutException.class, () -> Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, + () -> Assertions.assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, + () -> Assertions.assertTimeoutPreemptively(ofMillis(500), + () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); + assertMessageEquals(exception, ":("); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() + throws Exception { + var result = Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", + () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY); + + assertThat(result).isEqualTo("Tempus Fugit"); + } + + private void waitForInterrupt() { + try { + assertFalse(Thread.interrupted(), "Already interrupted"); + new CountDownLatch(1).await(); + } + catch (InterruptedException ignore) { + // ignore + } + } + + /** + * Assert the given stack trace elements contain an element with the given class name and method name. + */ + private static void assertStackTraceContains(StackTraceElement[] stackTrace, String className, String methodName) { + assertThat(stackTrace).anySatisfy(element -> { + assertThat(element.getClassName()).endsWith(className); + assertThat(element.getMethodName()).isEqualTo(methodName); + }); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java new file mode 100644 index 00000000..edbda5c9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class AssertTrueAssertionsTests { + + @Test + void assertTrueWithBooleanTrue() { + assertTrue(true); + assertTrue(true, "test"); + assertTrue(true, () -> "test"); + } + + @Test + void assertTrueWithBooleanSupplierTrue() { + assertTrue(() -> true); + assertTrue(() -> true, "test"); + assertTrue(() -> true, () -> "test"); + } + + @Test + void assertTrueWithBooleanTrueAndMessageSupplier() { + assertTrue(true, () -> "test"); + } + + @Test + void assertTrueWithBooleanSupplierTrueAndMessageSupplier() { + assertTrue(() -> true, () -> "test"); + } + + @Test + void assertTrueWithBooleanFalseAndDefaultMessageWithExpectedAndActualValues() { + try { + assertTrue(false); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "expected: but was: "); + assertExpectedAndActualValues(ex, true, false); + } + } + + @Test + void assertTrueWithBooleanFalseAndString() { + try { + assertTrue(false, "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, true, false); + } + } + + @Test + void assertTrueWithBooleanFalseAndMessageSupplier() { + try { + assertTrue(false, () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, true, false); + } + } + + @Test + void assertTrueWithBooleanSupplierFalseAndString() { + try { + assertTrue(() -> false, "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, true, false); + } + } + + @Test + void assertTrueWithBooleanSupplierFalseAndMessageSupplier() { + try { + assertTrue(() -> false, () -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test ==> expected: but was: "); + assertExpectedAndActualValues(ex, true, false); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java new file mode 100644 index 00000000..3db35842 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import java.io.Serializable; +import java.util.Objects; + +import org.opentest4j.AssertionFailedError; +import org.opentest4j.ValueWrapper; + +class AssertionTestUtils { + + private AssertionTestUtils() { + /* no-op */ + } + + static void expectAssertionFailedError() { + throw new AssertionError("Should have thrown an " + AssertionFailedError.class.getName()); + } + + static void assertEmptyMessage(Throwable ex) throws AssertionError { + if (!ex.getMessage().isEmpty()) { + throw new AssertionError("Exception message should be empty, but was [" + ex.getMessage() + "]."); + } + } + + static void assertMessageEquals(Throwable ex, String msg) throws AssertionError { + if (!msg.equals(ex.getMessage())) { + throw new AssertionError("Exception message should be [" + msg + "], but was [" + ex.getMessage() + "]."); + } + } + + static void assertMessageMatches(Throwable ex, String regex) throws AssertionError { + if (!ex.getMessage().matches(regex)) { + throw new AssertionError("Exception message should match regular expression [" + regex + "], but was [" + + ex.getMessage() + "]."); + } + } + + static void assertMessageStartsWith(Throwable ex, String msg) throws AssertionError { + if (!ex.getMessage().startsWith(msg)) { + throw new AssertionError( + "Exception message should start with [" + msg + "], but was [" + ex.getMessage() + "]."); + } + } + + static void assertMessageEndsWith(Throwable ex, String msg) throws AssertionError { + if (!ex.getMessage().endsWith(msg)) { + throw new AssertionError( + "Exception message should end with [" + msg + "], but was [" + ex.getMessage() + "]."); + } + } + + static void assertMessageContains(Throwable ex, String msg) throws AssertionError { + if (!ex.getMessage().contains(msg)) { + throw new AssertionError( + "Exception message should contain [" + msg + "], but was [" + ex.getMessage() + "]."); + } + } + + static void assertExpectedAndActualValues(AssertionFailedError ex, Object expected, Object actual) + throws AssertionError { + if (!wrapsEqualValue(ex.getExpected(), expected)) { + throw new AssertionError("Expected value in AssertionFailedError should equal [" + + ValueWrapper.create(expected) + "], but was [" + ex.getExpected() + "]."); + } + if (!wrapsEqualValue(ex.getActual(), actual)) { + throw new AssertionError("Actual value in AssertionFailedError should equal [" + ValueWrapper.create(actual) + + "], but was [" + ex.getActual() + "]."); + } + } + + static boolean wrapsEqualValue(ValueWrapper wrapper, Object value) { + if (value == null || value instanceof Serializable) { + return Objects.equals(value, wrapper.getValue()); + } + return wrapper.getIdentityHashCode() == System.identityHashCode(value) + && Objects.equals(wrapper.getStringRepresentation(), String.valueOf(value)) + && Objects.equals(wrapper.getType(), value.getClass()); + } + + static void recurseIndefinitely() { + // simulate infinite recursion + throw new StackOverflowError(); + } + + static void runOutOfMemory() { + // simulate running out of memory + throw new OutOfMemoryError("boom"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java new file mode 100644 index 00000000..badeff93 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java @@ -0,0 +1,245 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.Assumptions.assumingThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.junit.jupiter.api.function.Executable; +import org.opentest4j.TestAbortedException; + +/** + * Unit tests for JUnit Jupiter {@link Assumptions}. + * + * @since 5.0 + */ +class AssumptionsTests { + + // --- assumeTrue ---------------------------------------------------- + + @Test + void assumeTrueWithBooleanTrue() { + String foo = null; + try { + assumeTrue(true); + assumeTrue(true, "message"); + assumeTrue(true, () -> "message"); + foo = "foo"; + } + finally { + assertNotNull(foo); + } + } + + @Test + void assumeTrueWithBooleanSupplierTrue() { + String foo = null; + try { + assumeTrue(() -> true); + assumeTrue(() -> true, "message"); + assumeTrue(() -> true, () -> "message"); + foo = "foo"; + } + finally { + assertNotNull(foo); + } + } + + @Test + void assumeTrueWithBooleanFalse() { + assertAssumptionFailure("assumption is not true", () -> assumeTrue(false)); + } + + @Test + void assumeTrueWithBooleanSupplierFalse() { + assertAssumptionFailure("assumption is not true", () -> assumeTrue(() -> false)); + } + + @Test + void assumeTrueWithBooleanFalseAndStringMessage() { + assertAssumptionFailure("test", () -> assumeTrue(false, "test")); + } + + @Test + void assumeTrueWithBooleanFalseAndNullStringMessage() { + assertAssumptionFailure(null, () -> assumeTrue(false, (String) null)); + } + + @Test + void assumeTrueWithBooleanSupplierFalseAndStringMessage() { + assertAssumptionFailure("test", () -> assumeTrue(() -> false, "test")); + } + + @Test + void assumeTrueWithBooleanSupplierFalseAndMessageSupplier() { + assertAssumptionFailure("test", () -> assumeTrue(() -> false, () -> "test")); + } + + @Test + void assumeTrueWithBooleanFalseAndMessageSupplier() { + assertAssumptionFailure("test", () -> assumeTrue(false, () -> "test")); + } + + // --- assumeFalse ---------------------------------------------------- + + @Test + void assumeFalseWithBooleanFalse() { + String foo = null; + try { + assumeFalse(false); + assumeFalse(false, "message"); + assumeFalse(false, () -> "message"); + foo = "foo"; + } + finally { + assertNotNull(foo); + } + } + + @Test + void assumeFalseWithBooleanSupplierFalse() { + String foo = null; + try { + assumeFalse(() -> false); + assumeFalse(() -> false, "message"); + assumeFalse(() -> false, () -> "message"); + foo = "foo"; + } + finally { + assertNotNull(foo); + } + } + + @Test + void assumeFalseWithBooleanTrue() { + assertAssumptionFailure("assumption is not false", () -> assumeFalse(true)); + } + + @Test + void assumeFalseWithBooleanSupplierTrue() { + assertAssumptionFailure("assumption is not false", () -> assumeFalse(() -> true)); + } + + @Test + void assumeFalseWithBooleanTrueAndStringMessage() { + assertAssumptionFailure("test", () -> assumeFalse(true, "test")); + } + + @Test + void assumeFalseWithBooleanSupplierTrueAndMessage() { + assertAssumptionFailure("test", () -> assumeFalse(() -> true, "test")); + } + + @Test + void assumeFalseWithBooleanSupplierTrueAndMessageSupplier() { + assertAssumptionFailure("test", () -> assumeFalse(() -> true, () -> "test")); + } + + @Test + void assumeFalseWithBooleanTrueAndMessageSupplier() { + assertAssumptionFailure("test", () -> assumeFalse(true, () -> "test")); + } + + // --- assumingThat -------------------------------------------------- + + @Test + void assumingThatWithBooleanTrue() { + List list = new ArrayList<>(); + assumingThat(true, () -> list.add("test")); + assertEquals(1, list.size()); + assertEquals("test", list.get(0)); + } + + @Test + void assumingThatWithBooleanSupplierTrue() { + List list = new ArrayList<>(); + assumingThat(() -> true, () -> list.add("test")); + assertEquals(1, list.size()); + assertEquals("test", list.get(0)); + } + + @Test + void assumingThatWithBooleanFalse() { + List list = new ArrayList<>(); + assumingThat(false, () -> list.add("test")); + assertEquals(0, list.size()); + } + + @Test + void assumingThatWithBooleanSupplierFalse() { + List list = new ArrayList<>(); + assumingThat(() -> false, () -> list.add("test")); + assertEquals(0, list.size()); + } + + @Test + void assumingThatWithFailingExecutable() { + assertThrows(EnigmaThrowable.class, () -> assumingThat(true, () -> { + throw new EnigmaThrowable(); + })); + } + + // --- abort --------------------------------------------------------- + + @Test + void abortWithNoArguments() { + assertTestAbortedException(null, Assumptions::abort); + } + + @Test + void abortWithStringMessage() { + assertTestAbortedException("test", () -> abort("test")); + } + + @Test + void abortWithStringSupplier() { + assertTestAbortedException("test", () -> abort(() -> "test")); + } + + // ------------------------------------------------------------------- + + private static void assertAssumptionFailure(String msg, Executable executable) { + assertTestAbortedException(msg == null ? "Assumption failed" : "Assumption failed: " + msg, executable); + } + + private static void assertTestAbortedException(String expectedMessage, Executable executable) { + try { + executable.execute(); + expectTestAbortedException(); + } + catch (Throwable ex) { + assertTrue(ex instanceof TestAbortedException); + assertMessageEquals(ex, expectedMessage); + } + } + + private static void expectTestAbortedException() { + throw new AssertionError("Should have thrown a " + TestAbortedException.class.getName()); + } + + private static void assertMessageEquals(Throwable t, String expectedMessage) throws AssertionError { + if (!Objects.equals(expectedMessage, t.getMessage())) { + throw new AssertionError("Message in TestAbortedException should be [" + expectedMessage + "], but was [" + + t.getMessage() + "]."); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java new file mode 100644 index 00000000..427ebdfc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; + +/** + * @since 5.8 + */ +@DisplayNameGeneration(ReplaceUnderscores.class) +class DisplayNameGenerationInheritanceTestCase { + + @Nested + class InnerNestedTestCase { + + @Test + void this_is_a_test() { + } + } + + static class StaticNestedTestCase { + + @Test + void this_is_a_test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java new file mode 100644 index 00000000..a02a9adf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java @@ -0,0 +1,441 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.lang.reflect.Method; +import java.util.EmptyStackException; +import java.util.Stack; + +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.engine.TestDescriptor; + +/** + * Check generated display names. + * + * @see DisplayName + * @see DisplayNameGenerator + * @see DisplayNameGeneration + * @since 5.4 + */ +class DisplayNameGenerationTests extends AbstractJupiterTestEngineTests { + + @Test + void standardGenerator() { + check(DefaultStyleTestCase.class, // + "CONTAINER: DisplayNameGenerationTests$DefaultStyleTestCase", // + "TEST: @DisplayName prevails", // + "TEST: test()", // + "TEST: test(TestInfo)", // + "TEST: testUsingCamelCaseStyle()", // + "TEST: testUsingCamelCase_and_also_UnderScores()", // + "TEST: testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact(TestInfo)", // + "TEST: test_with_underscores()" // + ); + } + + @Test + void simpleGenerator() { + check(SimpleStyleTestCase.class, // + "CONTAINER: DisplayNameGenerationTests$SimpleStyleTestCase", // + "TEST: @DisplayName prevails", // + "TEST: test", // + "TEST: test (TestInfo)", // + "TEST: testUsingCamelCaseStyle", // + "TEST: testUsingCamelCase_and_also_UnderScores", // + "TEST: testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact (TestInfo)", // + "TEST: test_with_underscores" // + ); + } + + @Test + void underscoreGenerator() { + var expectedDisplayNames = new String[] { // + "", // + "TEST: @DisplayName prevails", // + "TEST: test", // + "TEST: test (TestInfo)", // + "TEST: test with underscores", // + "TEST: testUsingCamelCase and also UnderScores", // + "TEST: testUsingCamelCase and also UnderScores keepingParameterTypeNamesIntact (TestInfo)", // + "TEST: testUsingCamelCaseStyle" // + }; + + expectedDisplayNames[0] = "CONTAINER: DisplayNameGenerationTests$UnderscoreStyleTestCase"; + check(UnderscoreStyleTestCase.class, expectedDisplayNames); + + expectedDisplayNames[0] = "CONTAINER: DisplayNameGenerationTests$UnderscoreStyleInheritedFromSuperClassTestCase"; + check(UnderscoreStyleInheritedFromSuperClassTestCase.class, expectedDisplayNames); + } + + @Test + void indicativeSentencesGeneratorOnStaticNestedClass() { + check(IndicativeStyleTestCase.class, // + "CONTAINER: DisplayNameGenerationTests$IndicativeStyleTestCase", // + "TEST: @DisplayName prevails", // + "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test", // + "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test (TestInfo)", // + "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test with underscores", // + "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCase and also UnderScores", // + "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCase and also UnderScores keepingParameterTypeNamesIntact (TestInfo)", // + "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCaseStyle" // + ); + } + + @Test + void indicativeSentencesGeneratorOnTopLevelClass() { + check(IndicativeSentencesTopLevelTestCase.class, // + "CONTAINER: IndicativeSentencesTopLevelTestCase", // + "CONTAINER: IndicativeSentencesTopLevelTestCase -> A year is a leap year", // + "TEST: IndicativeSentencesTopLevelTestCase -> A year is a leap year -> if it is divisible by 4 but not by 100" // + ); + } + + @Test + void indicativeSentencesGeneratorOnNestedClass() { + check(IndicativeSentencesNestedTestCase.class, // + "CONTAINER: IndicativeSentencesNestedTestCase", // + "CONTAINER: A year is a leap year", // + "TEST: A year is a leap year -> if it is divisible by 4 but not by 100" // + ); + } + + @Test + void noNameGenerator() { + check(NoNameStyleTestCase.class, // + "CONTAINER: nn", // + "TEST: @DisplayName prevails", // + "TEST: nn", // + "TEST: nn", // + "TEST: nn", // + "TEST: nn", // + "TEST: nn", // + "TEST: nn" // + ); + } + + @Test + void checkDisplayNameGeneratedForTestingAStackDemo() { + check(StackTestCase.class, // + "CONTAINER: A stack", // + "TEST: is instantiated using its noarg constructor", // + "CONTAINER: A new stack", // + "TEST: throws an EmptyStackException when peeked", // + "TEST: throws an EmptyStackException when popped", // + "TEST: is empty", // + "CONTAINER: After pushing an element to an empty stack", // + "TEST: peek returns that element without removing it from the stack", // + "TEST: pop returns that element and leaves an empty stack", // + "TEST: the stack is no longer empty" // + ); + } + + @Test + void checkDisplayNameGeneratedForIndicativeGeneratorTestCase() { + check(IndicativeGeneratorTestCase.class, // + "CONTAINER: A stack", // + "TEST: A stack, is instantiated with new constructor", // + "CONTAINER: A stack, when new", // + "TEST: A stack, when new, throws EmptyStackException when peeked", // + "CONTAINER: A stack, when new, after pushing an element to an empty stack", // + "TEST: A stack, when new, after pushing an element to an empty stack, is no longer empty" // + ); + } + + @Test + void checkDisplayNameGeneratedForIndicativeGeneratorWithCustomSeparatorTestCase() { + check(IndicativeGeneratorWithCustomSeparatorTestCase.class, // + "CONTAINER: A stack", // + "TEST: A stack >> is instantiated with new constructor", // + "CONTAINER: A stack >> when new", // + "TEST: A stack >> when new >> throws EmptyStackException when peeked", // + "CONTAINER: A stack >> when new >> after pushing an element to an empty stack", // + "TEST: A stack >> when new >> after pushing an element to an empty stack >> is no longer empty" // + ); + } + + @Test + void displayNameGenerationInheritance() { + check(DisplayNameGenerationInheritanceTestCase.InnerNestedTestCase.class, // + "CONTAINER: DisplayNameGenerationInheritanceTestCase", // + "CONTAINER: InnerNestedTestCase", // + "TEST: this is a test"// + ); + + check(DisplayNameGenerationInheritanceTestCase.StaticNestedTestCase.class, // + "CONTAINER: DisplayNameGenerationInheritanceTestCase$StaticNestedTestCase", // + "TEST: this_is_a_test()"// + ); + } + + @Test + void indicativeSentencesGenerationInheritance() { + check(IndicativeSentencesGenerationInheritanceTestCase.InnerNestedTestCase.class, // + "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase", // + "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase -> InnerNestedTestCase", // + "TEST: IndicativeSentencesGenerationInheritanceTestCase -> InnerNestedTestCase -> this is a test"// + ); + + check(IndicativeSentencesGenerationInheritanceTestCase.StaticNestedTestCase.class, // + "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase$StaticNestedTestCase", // + "TEST: this_is_a_test()"// + ); + } + + private void check(Class testClass, String... expectedDisplayNames) { + var request = request().selectors(selectClass(testClass)).build(); + var descriptors = discoverTests(request).getDescendants(); + assertThat(descriptors).map(this::describe).containsExactlyInAnyOrder(expectedDisplayNames); + } + + private String describe(TestDescriptor descriptor) { + return descriptor.getType() + ": " + descriptor.getDisplayName(); + } + + // ------------------------------------------------------------------- + + static class NoNameGenerator implements DisplayNameGenerator { + + @Override + public String generateDisplayNameForClass(Class testClass) { + return "nn"; + } + + @Override + public String generateDisplayNameForNestedClass(Class nestedClass) { + return "nn"; + } + + @Override + public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + return "nn"; + } + } + + @DisplayNameGeneration(NoNameGenerator.class) + static abstract class AbstractTestCase { + @Test + void test() { + } + + @Test + void test(TestInfo testInfo) { + testInfo.getDisplayName(); + } + + @Test + void testUsingCamelCaseStyle() { + } + + @Test + void testUsingCamelCase_and_also_UnderScores() { + } + + @Test + void testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact(TestInfo testInfo) { + testInfo.getDisplayName(); + } + + @Test + void test_with_underscores() { + } + + @DisplayName("@DisplayName prevails") + @Test + void testDisplayNamePrevails() { + } + } + + @DisplayNameGeneration(DisplayNameGenerator.Standard.class) + static class DefaultStyleTestCase extends AbstractTestCase { + } + + @DisplayNameGeneration(DisplayNameGenerator.Simple.class) + static class SimpleStyleTestCase extends AbstractTestCase { + } + + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + static class UnderscoreStyleTestCase extends AbstractTestCase { + } + + @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) + static class IndicativeStyleTestCase extends AbstractTestCase { + } + + @DisplayNameGeneration(NoNameGenerator.class) + static class NoNameStyleTestCase extends AbstractTestCase { + } + + // No annotation here! @DisplayNameGeneration is inherited from super class + static class UnderscoreStyleInheritedFromSuperClassTestCase extends UnderscoreStyleTestCase { + } + + // ------------------------------------------------------------------- + + @DisplayName("A stack") + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + static class StackTestCase { + + Stack stack; + + @Test + void is_instantiated_using_its_noarg_constructor() { + new Stack<>(); + } + + @Nested + class A_new_stack { + + @BeforeEach + void createNewStack() { + stack = new Stack<>(); + } + + @Test + void is_empty() { + assertTrue(stack.isEmpty()); + } + + @Test + void throws_an_EmptyStackException_when_popped() { + assertThrows(EmptyStackException.class, () -> stack.pop()); + } + + @Test + void throws_an_EmptyStackException_when_peeked() { + assertThrows(EmptyStackException.class, () -> stack.peek()); + } + + @Nested + class After_pushing_an_element_to_an_empty_stack { + + String anElement = "an element"; + + @BeforeEach + void pushAnElement() { + stack.push(anElement); + } + + @Test + void the_stack_is_no_longer_empty() { + assertFalse(stack.isEmpty()); + } + + @Test + void pop_returns_that_element_and_leaves_an_empty_stack() { + assertEquals(anElement, stack.pop()); + assertTrue(stack.isEmpty()); + } + + @Test + void peek_returns_that_element_without_removing_it_from_the_stack() { + assertEquals(anElement, stack.peek()); + assertFalse(stack.isEmpty()); + } + } + } + } + + // ------------------------------------------------------------------- + + @DisplayName("A stack") + @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) + static class IndicativeGeneratorTestCase { + + Stack stack; + + @Test + void is_instantiated_with_new_constructor() { + new Stack<>(); + } + + @Nested + class when_new { + + @BeforeEach + void create_with_new_stack() { + stack = new Stack<>(); + } + + @Test + void throws_EmptyStackException_when_peeked() { + assertThrows(EmptyStackException.class, () -> stack.peek()); + } + + @Nested + class after_pushing_an_element_to_an_empty_stack { + + String anElement = "an element"; + + @BeforeEach + void push_an_element() { + stack.push(anElement); + } + + @Test + void is_no_longer_empty() { + assertFalse(stack.isEmpty()); + } + } + } + } + + // ------------------------------------------------------------------- + + @DisplayName("A stack") + @IndicativeSentencesGeneration(separator = " >> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) + static class IndicativeGeneratorWithCustomSeparatorTestCase { + + Stack stack; + + @Test + void is_instantiated_with_new_constructor() { + new Stack<>(); + } + + @Nested + class when_new { + + @BeforeEach + void create_with_new_stack() { + stack = new Stack<>(); + } + + @Test + void throws_EmptyStackException_when_peeked() { + assertThrows(EmptyStackException.class, () -> stack.peek()); + } + + @Nested + class after_pushing_an_element_to_an_empty_stack { + + String anElement = "an element"; + + @BeforeEach + void push_an_element() { + stack.push(anElement); + } + + @Test + void is_no_longer_empty() { + assertFalse(stack.isEmpty()); + } + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java new file mode 100644 index 00000000..91a49501 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java @@ -0,0 +1,203 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static java.util.Collections.emptyIterator; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; +import org.opentest4j.AssertionFailedError; + +/** + * @since 5.0 + */ +class DynamicTestTests { + + private static final Executable nix = () -> { + }; + + private final List assertedValues = new ArrayList<>(); + + @Test + void streamFromStreamPreconditions() { + ThrowingConsumer testExecutor = input -> { + }; + Function displayNameGenerator = Object::toString; + + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream((Stream) null, displayNameGenerator, testExecutor)); + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream(Stream.empty(), null, testExecutor)); + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream(Stream.empty(), displayNameGenerator, null)); + } + + @Test + void streamFromIteratorPreconditions() { + ThrowingConsumer testExecutor = input -> { + }; + Function displayNameGenerator = Object::toString; + + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream((Iterator) null, displayNameGenerator, testExecutor)); + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream(emptyIterator(), null, testExecutor)); + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream(emptyIterator(), displayNameGenerator, null)); + } + + @Test + void streamFromStreamWithNamesPreconditions() { + ThrowingConsumer testExecutor = input -> { + }; + + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream((Stream>) null, testExecutor)); + assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(Stream.empty(), null)); + } + + @Test + void streamFromIteratorWithNamesPreconditions() { + ThrowingConsumer testExecutor = input -> { + }; + + assertThrows(PreconditionViolationException.class, + () -> DynamicTest.stream((Iterator>) null, testExecutor)); + assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(emptyIterator(), null)); + } + + @Test + void streamFromStream() throws Throwable { + Stream stream = DynamicTest.stream(Stream.of("foo", "bar", "baz"), String::toUpperCase, + this::throwingConsumer); + assertStream(stream); + } + + @Test + void streamFromIterator() throws Throwable { + Stream stream = DynamicTest.stream(List.of("foo", "bar", "baz").iterator(), String::toUpperCase, + this::throwingConsumer); + assertStream(stream); + } + + @Test + void streamFromStreamWithNames() throws Throwable { + Stream stream = DynamicTest.stream( + Stream.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")), this::throwingConsumer); + assertStream(stream); + } + + @Test + void streamFromIteratorWithNames() throws Throwable { + Stream stream = DynamicTest.stream( + List.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")).iterator(), + this::throwingConsumer); + assertStream(stream); + } + + private void assertStream(Stream stream) throws Throwable { + List dynamicTests = stream.collect(Collectors.toList()); + + assertThat(dynamicTests).extracting(DynamicTest::getDisplayName).containsExactly("FOO", "BAR", "BAZ"); + + assertThat(assertedValues).isEmpty(); + + dynamicTests.get(0).getExecutable().execute(); + assertThat(assertedValues).containsExactly("foo"); + + dynamicTests.get(1).getExecutable().execute(); + assertThat(assertedValues).containsExactly("foo", "bar"); + + Throwable t = assertThrows(Throwable.class, () -> dynamicTests.get(2).getExecutable().execute()); + assertThat(t).hasMessage("Baz!"); + assertThat(assertedValues).containsExactly("foo", "bar"); + } + + private void throwingConsumer(String str) throws Throwable { + if ("baz".equals(str)) { + throw new Throwable("Baz!"); + } + this.assertedValues.add(str); + } + + @Test + void reflectiveOperationsThrowingAssertionFailedError() { + Throwable t48 = assertThrows(AssertionFailedError.class, + () -> dynamicTest("1 == 48", this::assert1Equals48Directly).getExecutable().execute()); + assertThat(t48).hasMessage("expected: <1> but was: <48>"); + + Throwable t49 = assertThrows(AssertionFailedError.class, () -> dynamicTest("1 == 49", + this::assert1Equals49ReflectivelyAndUnwrapInvocationTargetException).getExecutable().execute()); + assertThat(t49).hasMessage("expected: <1> but was: <49>"); + } + + @Test + void reflectiveOperationThrowingInvocationTargetException() { + Throwable t50 = assertThrows(InvocationTargetException.class, + () -> dynamicTest("1 == 50", this::assert1Equals50Reflectively).getExecutable().execute()); + assertThat(t50.getCause()).hasMessage("expected: <1> but was: <50>"); + } + + @Test + void sourceUriIsNotPresentByDefault() { + DynamicTest test = dynamicTest("foo", nix); + assertThat(test.getTestSourceUri()).isNotPresent(); + assertThat(test.toString()).isEqualTo("DynamicTest [displayName = 'foo', testSourceUri = null]"); + DynamicContainer container = dynamicContainer("bar", Stream.of(test)); + assertThat(container.getTestSourceUri()).isNotPresent(); + assertThat(container.toString()).isEqualTo("DynamicContainer [displayName = 'bar', testSourceUri = null]"); + } + + @Test + void sourceUriIsReturnedWhenSupplied() { + URI testSourceUri = URI.create("any://test"); + DynamicTest test = dynamicTest("foo", testSourceUri, nix); + URI containerSourceUri = URI.create("other://container"); + DynamicContainer container = dynamicContainer("bar", containerSourceUri, Stream.of(test)); + + assertThat(test.getTestSourceUri().get()).isSameAs(testSourceUri); + assertThat(test.toString()).isEqualTo("DynamicTest [displayName = 'foo', testSourceUri = any://test]"); + assertThat(container.getTestSourceUri().get()).isSameAs(containerSourceUri); + assertThat(container.toString()).isEqualTo( + "DynamicContainer [displayName = 'bar', testSourceUri = other://container]"); + } + + private void assert1Equals48Directly() { + Assertions.assertEquals(1, 48); + } + + private void assert1Equals49ReflectivelyAndUnwrapInvocationTargetException() throws Throwable { + Method method = Assertions.class.getMethod("assertEquals", int.class, int.class); + ReflectionSupport.invokeMethod(method, null, 1, 49); + } + + private void assert1Equals50Reflectively() throws Throwable { + Method method = Assertions.class.getMethod("assertEquals", int.class, int.class); + method.invoke(null, 1, 50); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java new file mode 100644 index 00000000..c3ce9611 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +/** + * This is a top-level type in order to avoid issues with + * {@link Class#getCanonicalName()} when using different class + * loaders in tests. + * + * @since 5.0 + */ +@SuppressWarnings("serial") +class EnigmaThrowable extends Throwable { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java new file mode 100644 index 00000000..fa2edce6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.junit.jupiter.api.AssertionTestUtils.assertEmptyMessage; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for JUnit Jupiter {@link Assertions}. + * + * @since 5.0 + */ +class FailAssertionsTests { + + @Test + void failWithoutArgument() { + try { + fail(); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertEmptyMessage(ex); + } + } + + @Test + void failWithString() { + try { + fail("test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test"); + } + } + + @Test + void failWithMessageSupplier() { + try { + fail(() -> "test"); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "test"); + } + } + + @Test + void failWithNullString() { + try { + fail((String) null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertEmptyMessage(ex); + } + } + + @Test + void failWithNullMessageSupplier() { + try { + fail((Supplier) null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertEmptyMessage(ex); + } + } + + @Test + void failWithStringAndThrowable() { + try { + fail("message", new Throwable("cause")); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "message"); + Throwable cause = ex.getCause(); + assertMessageContains(cause, "cause"); + } + } + + @Test + void failWithThrowable() { + try { + fail(new Throwable("cause")); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertEmptyMessage(ex); + Throwable cause = ex.getCause(); + assertMessageContains(cause, "cause"); + } + } + + @Test + void failWithStringAndNullThrowable() { + try { + fail("message", (Throwable) null); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertMessageEquals(ex, "message"); + if (ex.getCause() != null) { + throw new AssertionError("Cause should have been null"); + } + } + } + + @Test + void failWithNullStringAndThrowable() { + try { + fail((String) null, new Throwable("cause")); + expectAssertionFailedError(); + } + catch (AssertionFailedError ex) { + assertEmptyMessage(ex); + Throwable cause = ex.getCause(); + assertMessageContains(cause, "cause"); + } + } + + @Test + void failUsableAsAnExpression() { + // @formatter:off + long count = Stream.empty() + .peek(element -> fail("peek should never be called")) + .filter(element -> fail("filter should never be called", new Throwable("cause"))) + .map(element -> fail(new Throwable("map should never be called"))) + .sorted((e1, e2) -> fail(() -> "sorted should never be called")) + .count(); + // @formatter:on + assertEquals(0L, count); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java new file mode 100644 index 00000000..1a506ee0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; + +/** + * @since 5.8 + */ +@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) +class IndicativeSentencesGenerationInheritanceTestCase { + + @Nested + class InnerNestedTestCase { + + @Test + void this_is_a_test() { + } + } + + static class StaticNestedTestCase { + + @Test + void this_is_a_test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java new file mode 100644 index 00000000..8b6db62d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; + +/** + * This test case declares {@link IndicativeSentencesGeneration} on a test class + * that is nested directly within a top-level test class. + * + * @see IndicativeSentencesTopLevelTestCase + * @since 5.8 + */ +class IndicativeSentencesNestedTestCase { + + @Nested + @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) + class A_year_is_a_leap_year { + + @Test + void if_it_is_divisible_by_4_but_not_by_100() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java new file mode 100644 index 00000000..d1cdbcdb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; + +/** + * This test case declares {@link IndicativeSentencesGeneration} on a top-level + * test class that contains a nested test class. + * + * @see IndicativeSentencesNestedTestCase + * @since 5.8 + */ +@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) +class IndicativeSentencesTopLevelTestCase { + + @Nested + class A_year_is_a_leap_year { + + @Test + void if_it_is_divisible_by_4_but_not_by_100() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java new file mode 100644 index 00000000..0bf31285 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +final class IterableFactory { + + static List listOf(Object... objects) { + return Arrays.asList(objects); + } + + static Set setOf(Object... objects) { + return new LinkedHashSet<>(listOf(objects)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java new file mode 100644 index 00000000..7e02a7d3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.ReflectionUtils.findMethod; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.commons.util.ReflectionUtils.newInstance; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Abstract base class for unit testing a concrete {@link ExecutionCondition} + * implementation. + * + *

WARNING: this abstract base class currently does not + * support tests in {@code @Nested} test classes within the + * {@linkplain #getTestClass() test class}, since {@link #beforeEach(TestInfo)} + * instantiates the test class using the no-args default constructor. + * + * @since 5.1 + */ +@TestInstance(Lifecycle.PER_CLASS) +abstract class AbstractExecutionConditionTests { + + private final ExtensionContext context = mock(); + + private ConditionEvaluationResult result; + + @BeforeAll + void ensureAllTestMethodsAreCovered() { + Predicate isTestMethod = method -> method.isAnnotationPresent(Test.class); + + List methodsToTest = findMethods(getTestClass(), isTestMethod).stream()// + .map(Method::getName).sorted().collect(toList()); + + List localTestMethods = findMethods(getClass(), isTestMethod).stream()// + .map(Method::getName).sorted().collect(toList()); + + assertThat(localTestMethods).isEqualTo(methodsToTest); + } + + @BeforeEach + void beforeEach(TestInfo testInfo) { + when(this.context.getElement()).thenReturn(method(testInfo)); + when(this.context.getTestInstance()).thenReturn(Optional.of(newInstance(getTestClass()))); + doReturn(getTestClass()).when(this.context).getRequiredTestClass(); + } + + protected abstract ExecutionCondition getExecutionCondition(); + + protected abstract Class getTestClass(); + + protected void evaluateCondition() { + this.result = getExecutionCondition().evaluateExecutionCondition(this.context); + } + + protected void assertEnabled() { + assertTrue(!this.result.isDisabled(), "Should be enabled"); + } + + protected void assertDisabled() { + assertTrue(this.result.isDisabled(), "Should be disabled"); + } + + protected void assertReasonContains(String text) { + assertThat(this.result.getReason()).hasValueSatisfying(reason -> assertThat(reason).contains(text)); + } + + protected void assertCustomDisabledReasonIs(String text) { + if (this.result.isDisabled()) { + assertThat(this.result.getReason()).hasValueSatisfying( + reason -> assertThat(reason).contains(" ==> " + text)); + } + } + + private Optional method(TestInfo testInfo) { + return method(getTestClass(), testInfo.getTestMethod().get().getName()); + } + + private Optional method(Class testClass, String methodName) { + return Optional.of(findMethod(testClass, methodName).get()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java new file mode 100644 index 00000000..9ccce353 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DisabledForJreRange}. + * + *

Note that test method names MUST match the test method names in + * {@link DisabledForJreRangeIntegrationTests}. + * + * @since 5.6 + */ +class DisabledForJreRangeConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new DisabledForJreRangeCondition(); + } + + @Override + protected Class getTestClass() { + return DisabledForJreRangeIntegrationTests.class; + } + + /** + * @see DisabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@DisabledForJreRange is not present"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#defaultValues() + */ + @Test + void defaultValues() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessageContaining("You must declare a non-default value for min or max in @DisabledForJreRange"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#java17() + */ + @Test + void java17() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava17()); + } + + /** + * @see DisabledForJreRangeIntegrationTests#java18to19() + */ + @Test + void java18to19() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava18() || onJava19()); + assertCustomDisabledReasonIs("Disabled on some JRE"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#javaMax18() + */ + @Test + void javaMax18() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() + || onJava14() || onJava15() || onJava16() || onJava17() || onJava18()); + } + + /** + * @see DisabledForJreRangeIntegrationTests#javaMin18() + */ + @Test + void javaMin18() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(!(onJava17())); + } + + /** + * @see DisabledForJreRangeIntegrationTests#other() + */ + @Test + void other() { + evaluateCondition(); + assertDisabledOnCurrentJreIf( + !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() + || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); + } + + private void assertDisabledOnCurrentJreIf(boolean condition) { + if (condition) { + assertDisabled(); + assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); + } + else { + assertEnabled(); + assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java new file mode 100644 index 00000000..bcca45f9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.OTHER; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link DisabledForJreRange}. + * + * @since 5.6 + */ +class DisabledForJreRangeIntegrationTests { + + @Test + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange + void defaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @DisabledForJreRange(min = JAVA_17, max = JAVA_17) + void java17() { + assertFalse(onJava17()); + } + + @Test + @DisabledForJreRange(min = JAVA_18, max = JAVA_19, disabledReason = "Disabled on some JRE") + void java18to19() { + assertFalse(onJava18() || onJava19()); + } + + @Test + @DisabledForJreRange(max = JAVA_18) + void javaMax18() { + assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18()); + } + + @Test + @DisabledForJreRange(min = JAVA_18) + void javaMin18() { + assertFalse(onJava18() || onJava19()); + assertTrue(onJava17()); + } + + @Test + @DisabledForJreRange(min = OTHER, max = OTHER) + void other() { + assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java new file mode 100644 index 00000000..fc1524d8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; + +/** + * Unit tests for {@link DisabledIf}. + * + *

Note that test method names MUST match the test method names in + * {@link DisabledIfIntegrationTests}. + * + * @since 5.7 + */ +public class DisabledIfConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new DisabledIfCondition(); + } + + @Override + protected Class getTestClass() { + return DisabledIfIntegrationTests.class; + } + + /** + * @see DisabledIfIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@DisabledIf is not present"); + } + + /** + * @see DisabledIfIntegrationTests#disabledBecauseStaticConditionMethodReturnsTrue() + */ + @Test + void disabledBecauseStaticConditionMethodReturnsTrue() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("Disabled for some reason"); + } + + /** + * @see DisabledIfIntegrationTests#enabledBecauseStaticConditionMethodReturnsFalse() + */ + @Test + void enabledBecauseStaticConditionMethodReturnsFalse() { + evaluateCondition(); + assertEnabled(); + assertReasonContains(""" + @DisabledIf("staticMethodThatReturnsFalse") evaluated to false"""); + } + + /** + * @see DisabledIfIntegrationTests#disabledBecauseConditionMethodReturnsTrue() + */ + @Test + void disabledBecauseConditionMethodReturnsTrue() { + evaluateCondition(); + assertDisabled(); + assertReasonContains(""" + @DisabledIf("methodThatReturnsTrue") evaluated to true"""); + } + + /** + * @see DisabledIfIntegrationTests#enabledBecauseConditionMethodReturnsFalse() + */ + @Test + void enabledBecauseConditionMethodReturnsFalse() { + evaluateCondition(); + assertEnabled(); + assertReasonContains(""" + @DisabledIf("methodThatReturnsFalse") evaluated to false"""); + } + + /** + * @see DisabledIfIntegrationTests.ExternalConditionMethod#disabledBecauseStaticExternalConditionMethodReturnsTrue() + */ + @Test + void disabledBecauseStaticExternalConditionMethodReturnsTrue() { + evaluateCondition(); + assertDisabled(); + assertReasonContains(""" + @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") evaluated to true"""); + } + + /** + * @see DisabledIfIntegrationTests.ExternalConditionMethod#enabledBecauseStaticExternalConditionMethodReturnsFalse() + */ + @Test + void enabledBecauseStaticExternalConditionMethodReturnsFalse() { + evaluateCondition(); + assertEnabled(); + assertReasonContains( + """ + @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") evaluated to false"""); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java new file mode 100644 index 00000000..07a3e782 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.ENIGMA; +import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY1; +import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY2; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DisabledIfEnvironmentVariableCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link DisabledIfEnvironmentVariableIntegrationTests}. + * + * @since 5.1 + */ +class DisabledIfEnvironmentVariableConditionTests extends AbstractExecutionConditionTests { + + /** + * Stubbed subclass of {@link DisabledIfEnvironmentVariableCondition}. + */ + private ExecutionCondition condition = new DisabledIfEnvironmentVariableCondition() { + + @Override + protected String getEnvironmentVariable(String name) { + return KEY1.equals(name) ? ENIGMA : null; + } + }; + + @Override + protected ExecutionCondition getExecutionCondition() { + return this.condition; + } + + @Override + protected Class getTestClass() { + return DisabledIfEnvironmentVariableIntegrationTests.class; + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains( + "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#blankNamedAttribute() + */ + @Test + void blankNamedAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#blankMatchesAttribute() + */ + @Test + void blankMatchesAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableMatchesExactly() + */ + @Test + void disabledBecauseEnvironmentVariableMatchesExactly() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("matches regular expression"); + assertCustomDisabledReasonIs("That's an enigma"); + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() + */ + @Test + void disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() { + this.condition = new DisabledIfEnvironmentVariableCondition() { + + @Override + protected String getEnvironmentVariable(String name) { + return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; + } + }; + + evaluateCondition(); + assertDisabled(); + assertReasonContains("matches regular expression"); + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableMatchesPattern() + */ + @Test + void disabledBecauseEnvironmentVariableMatchesPattern() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("matches regular expression"); + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableDoesNotMatch() + */ + @Test + void enabledBecauseEnvironmentVariableDoesNotMatch() { + evaluateCondition(); + assertEnabled(); + assertReasonContains( + "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableDoesNotExist() + */ + @Test + void enabledBecauseEnvironmentVariableDoesNotExist() { + evaluateCondition(); + assertEnabled(); + assertReasonContains( + "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java new file mode 100644 index 00000000..cbc06823 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link DisabledIfEnvironmentVariable}. + * + * @since 5.1 + */ +@Disabled("Disabled since the required environment variables are not set") +// Tests (except those with intentional configuration errors) will pass if you set +// the following environment variables: +// DisabledIfEnvironmentVariableTests.key1 = enigma +// DisabledIfEnvironmentVariableTests.key2 = enigma +class DisabledIfEnvironmentVariableIntegrationTests { + + static final String KEY1 = "DisabledIfEnvironmentVariableTests.key1"; + static final String KEY2 = "DisabledIfEnvironmentVariableTests.key2"; + static final String ENIGMA = "enigma"; + static final String BOGUS = "bogus"; + + @Test + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @DisabledIfEnvironmentVariable(named = " ", matches = ENIGMA) + void blankNamedAttribute() { + } + + @Test + @DisabledIfEnvironmentVariable(named = KEY1, matches = " ") + void blankMatchesAttribute() { + } + + @Test + @DisabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA, disabledReason = "That's an enigma") + void disabledBecauseEnvironmentVariableMatchesExactly() { + fail("should be disabled"); + } + + @Test + @DisabledIfEnvironmentVariable(named = KEY1, matches = BOGUS) + @CustomDisabled + void disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() { + fail("should be disabled"); + } + + @Test + @DisabledIfEnvironmentVariable(named = KEY1, matches = ".*e.+gma$") + void disabledBecauseEnvironmentVariableMatchesPattern() { + fail("should be disabled"); + } + + @Test + @DisabledIfEnvironmentVariable(named = KEY1, matches = BOGUS) + void enabledBecauseEnvironmentVariableDoesNotMatch() { + assertNotEquals(BOGUS, System.getenv(KEY1)); + } + + @Test + @DisabledIfEnvironmentVariable(named = BOGUS, matches = "doesn't matter") + void enabledBecauseEnvironmentVariableDoesNotExist() { + assertNull(System.getenv(BOGUS)); + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @DisabledIfEnvironmentVariable(named = KEY2, matches = ENIGMA) + @interface CustomDisabled { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java new file mode 100644 index 00000000..c783d322 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link DisabledIf}. + * + * @since 5.7 + */ +public class DisabledIfIntegrationTests { + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @DisabledIf(value = "staticMethodThatReturnsTrue", disabledReason = "Disabled for some reason") + void disabledBecauseStaticConditionMethodReturnsTrue() { + fail("Should be disabled"); + } + + @Test + @DisabledIf("staticMethodThatReturnsFalse") + void enabledBecauseStaticConditionMethodReturnsFalse() { + } + + @Test + @DisabledIf("methodThatReturnsTrue") + void disabledBecauseConditionMethodReturnsTrue() { + fail("Should be disabled"); + } + + @Test + @DisabledIf("methodThatReturnsFalse") + void enabledBecauseConditionMethodReturnsFalse() { + } + + @Test + @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") + void disabledBecauseStaticExternalConditionMethodReturnsTrue() { + fail("Should be disabled"); + } + + @Test + @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") + void enabledBecauseStaticExternalConditionMethodReturnsFalse() { + } + + @Nested + @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") + class ConditionallyDisabledClass { + + @Test + void disabledBecauseConditionMethodReturnsTrue() { + fail("Should be disabled"); + } + + } + + // ------------------------------------------------------------------------- + + @SuppressWarnings("unused") + private static boolean staticMethodThatReturnsTrue() { + return true; + } + + @SuppressWarnings("unused") + private static boolean staticMethodThatReturnsFalse() { + return false; + } + + @SuppressWarnings("unused") + private boolean methodThatReturnsTrue() { + return true; + } + + @SuppressWarnings("unused") + private boolean methodThatReturnsFalse() { + return false; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java new file mode 100644 index 00000000..d7be1177 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DisabledIfSystemPropertyCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link DisabledIfSystemPropertyIntegrationTests}. + * + * @since 5.1 + */ +class DisabledIfSystemPropertyConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new DisabledIfSystemPropertyCondition(); + } + + @Override + protected Class getTestClass() { + return DisabledIfSystemPropertyIntegrationTests.class; + } + + @BeforeAll + static void setSystemProperties() { + DisabledIfSystemPropertyIntegrationTests.setSystemProperties(); + } + + @AfterAll + static void clearSystemProperties() { + DisabledIfSystemPropertyIntegrationTests.clearSystemProperties(); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#blankNamedAttribute() + */ + @Test + void blankNamedAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#blankMatchesAttribute() + */ + @Test + void blankMatchesAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyMatchesExactly() + */ + @Test + void disabledBecauseSystemPropertyMatchesExactly() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("matches regular expression"); + assertCustomDisabledReasonIs("That's an enigma"); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() + */ + @Test + void disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("matches regular expression"); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyMatchesPattern() + */ + @Test + void disabledBecauseSystemPropertyMatchesPattern() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("matches regular expression"); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyDoesNotMatch() + */ + @Test + void enabledBecauseSystemPropertyDoesNotMatch() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyDoesNotExist() + */ + @Test + void enabledBecauseSystemPropertyDoesNotExist() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java new file mode 100644 index 00000000..7dd479d3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link DisabledIfSystemProperty}. + * + * @since 5.1 + */ +class DisabledIfSystemPropertyIntegrationTests { + + private static final String KEY1 = "DisabledIfSystemPropertyTests.key1"; + private static final String KEY2 = "DisabledIfSystemPropertyTests.key2"; + private static final String ENIGMA = "enigma"; + private static final String BOGUS = "bogus"; + + @BeforeAll + static void setSystemProperties() { + System.setProperty(KEY1, ENIGMA); + System.setProperty(KEY2, ENIGMA); + } + + @AfterAll + static void clearSystemProperties() { + System.clearProperty(KEY1); + System.clearProperty(KEY2); + } + + @Test + void enabledBecauseAnnotationIsNotPresent() { + // no-op + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledIfSystemProperty(named = " ", matches = ENIGMA) + void blankNamedAttribute() { + fail("should be disabled"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledIfSystemProperty(named = KEY1, matches = " ") + void blankMatchesAttribute() { + fail("should be disabled"); + } + + @Test + @DisabledIfSystemProperty(named = KEY1, matches = ENIGMA, disabledReason = "That's an enigma") + void disabledBecauseSystemPropertyMatchesExactly() { + fail("should be disabled"); + } + + @Test + @DisabledIfSystemProperty(named = KEY1, matches = BOGUS) + @CustomDisabled + void disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() { + fail("should be disabled"); + } + + @Test + @DisabledIfSystemProperty(named = KEY1, matches = ".*e.+gma$") + void disabledBecauseSystemPropertyMatchesPattern() { + fail("should be disabled"); + } + + @Test + @DisabledIfSystemProperty(named = KEY1, matches = BOGUS) + void enabledBecauseSystemPropertyDoesNotMatch() { + assertNotEquals(BOGUS, System.getProperty(KEY1)); + } + + @Test + @DisabledIfSystemProperty(named = BOGUS, matches = "doesn't matter") + void enabledBecauseSystemPropertyDoesNotExist() { + assertNull(System.getProperty(BOGUS)); + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @DisabledIfSystemProperty(named = KEY2, matches = ENIGMA) + @interface CustomDisabled { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java new file mode 100644 index 00000000..35fa2e52 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java @@ -0,0 +1,231 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DisabledOnJreCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link DisabledOnJreIntegrationTests}. + * + * @since 5.1 + */ +class DisabledOnJreConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new DisabledOnJreCondition(); + } + + @Override + protected Class getTestClass() { + return DisabledOnJreIntegrationTests.class; + } + + /** + * @see DisabledOnJreIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@DisabledOnJre is not present"); + } + + /** + * @see DisabledOnJreIntegrationTests#missingJreDeclaration() + */ + @Test + void missingJreDeclaration() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("You must declare at least one JRE"); + } + + /** + * @see DisabledOnJreIntegrationTests#disabledOnAllJavaVersions() + */ + @Test + void disabledOnAllJavaVersions() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(true); + assertCustomDisabledReasonIs("Disabled on every JRE"); + } + + /** + * @see DisabledOnJreIntegrationTests#java8() + */ + @Test + void java8() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava8()); + } + + /** + * @see DisabledOnJreIntegrationTests#java9() + */ + @Test + void java9() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava9()); + } + + /** + * @see DisabledOnJreIntegrationTests#java10() + */ + @Test + void java10() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava10()); + } + + /** + * @see DisabledOnJreIntegrationTests#java11() + */ + @Test + void java11() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava11()); + } + + /** + * @see DisabledOnJreIntegrationTests#java12() + */ + @Test + void java12() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava12()); + } + + /** + * @see DisabledOnJreIntegrationTests#java13() + */ + @Test + void java13() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava13()); + } + + /** + * @see DisabledOnJreIntegrationTests#java14() + */ + @Test + void java14() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava14()); + } + + /** + * @see DisabledOnJreIntegrationTests#java15() + */ + @Test + void java15() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava15()); + } + + /** + * @see DisabledOnJreIntegrationTests#java16() + */ + @Test + void java16() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava16()); + } + + /** + * @see DisabledOnJreIntegrationTests#java17() + */ + @Test + void java17() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava17()); + } + + /** + * @see DisabledOnJreIntegrationTests#java18() + */ + @Test + void java18() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava18()); + } + + /** + * @see DisabledOnJreIntegrationTests#java19() + */ + @Test + void java19() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava19()); + } + + /** + * @see DisabledOnJreIntegrationTests#java20() + */ + @Test + void java20() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava20()); + } + + /** + * @see DisabledOnJreIntegrationTests#java21() + */ + @Test + void java21() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava21()); + } + + /** + * @see DisabledOnJreIntegrationTests#other() + */ + @Test + void other() { + evaluateCondition(); + assertDisabledOnCurrentJreIf( + !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() + || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); + } + + private void assertDisabledOnCurrentJreIf(boolean condition) { + if (condition) { + assertDisabled(); + assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); + } + else { + assertEnabled(); + assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java new file mode 100644 index 00000000..12ece2e5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java @@ -0,0 +1,165 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; +import static org.junit.jupiter.api.condition.JRE.JAVA_10; +import static org.junit.jupiter.api.condition.JRE.JAVA_11; +import static org.junit.jupiter.api.condition.JRE.JAVA_12; +import static org.junit.jupiter.api.condition.JRE.JAVA_13; +import static org.junit.jupiter.api.condition.JRE.JAVA_14; +import static org.junit.jupiter.api.condition.JRE.JAVA_15; +import static org.junit.jupiter.api.condition.JRE.JAVA_16; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.JAVA_20; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; +import static org.junit.jupiter.api.condition.JRE.JAVA_8; +import static org.junit.jupiter.api.condition.JRE.JAVA_9; +import static org.junit.jupiter.api.condition.JRE.OTHER; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link DisabledOnJre}. + * + * @since 5.1 + */ +class DisabledOnJreIntegrationTests { + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledOnJre({}) + void missingJreDeclaration() { + } + + @Test + @DisabledOnJre(value = { JAVA_8, JAVA_9, JAVA_10, JAVA_11, JAVA_12, JAVA_13, JAVA_14, JAVA_15, JAVA_16, JAVA_17, + JAVA_18, JAVA_19, JAVA_20, JAVA_21, OTHER }, disabledReason = "Disabled on every JRE") + void disabledOnAllJavaVersions() { + fail("should be disabled"); + } + + @Test + @DisabledOnJre(JAVA_8) + void java8() { + assertFalse(onJava8()); + } + + @Test + @DisabledOnJre(JAVA_9) + void java9() { + assertFalse(onJava9()); + } + + @Test + @DisabledOnJre(JAVA_10) + void java10() { + assertFalse(onJava10()); + } + + @Test + @DisabledOnJre(JAVA_11) + void java11() { + assertFalse(onJava11()); + } + + @Test + @DisabledOnJre(JAVA_12) + void java12() { + assertFalse(onJava12()); + } + + @Test + @DisabledOnJre(JAVA_13) + void java13() { + assertFalse(onJava13()); + } + + @Test + @DisabledOnJre(JAVA_14) + void java14() { + assertFalse(onJava14()); + } + + @Test + @DisabledOnJre(JAVA_15) + void java15() { + assertFalse(onJava15()); + } + + @Test + @DisabledOnJre(JAVA_16) + void java16() { + assertFalse(onJava16()); + } + + @Test + @DisabledOnJre(JAVA_17) + void java17() { + assertFalse(onJava17()); + } + + @Test + @DisabledOnJre(JAVA_18) + void java18() { + assertFalse(onJava18()); + } + + @Test + @DisabledOnJre(JAVA_19) + void java19() { + assertFalse(onJava19()); + } + + @Test + @DisabledOnJre(JAVA_20) + void java20() { + assertFalse(onJava20()); + } + + @Test + @DisabledOnJre(JAVA_21) + void java21() { + assertFalse(onJava21()); + } + + @Test + @DisabledOnJre(OTHER) + void other() { + assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java new file mode 100644 index 00000000..bab7f53d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java @@ -0,0 +1,267 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DisabledOnOsCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link DisabledOnOsIntegrationTests}. + * + * @since 5.1 + */ +class DisabledOnOsConditionTests extends AbstractExecutionConditionTests { + + private static final String OS_NAME = System.getProperty("os.name"); + private static final String ARCH = System.getProperty("os.arch"); + + @Override + protected ExecutionCondition getExecutionCondition() { + return new DisabledOnOsCondition(); + } + + @Override + protected Class getTestClass() { + return DisabledOnOsIntegrationTests.class; + } + + /** + * @see DisabledOnOsIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@DisabledOnOs is not present"); + } + + /** + * @see DisabledOnOsIntegrationTests#missingOsAndArchitectureDeclaration() + */ + @Test + void missingOsAndArchitectureDeclaration() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("You must declare at least one OS or architecture"); + } + + /** + * @see DisabledOnOsIntegrationTests#disabledOnEveryOs() + */ + @Test + void disabledOnEveryOs() { + evaluateCondition(); + assertDisabled(); + assertReasonContains(String.format("Disabled on operating system: %s ==> Disabled on every OS", OS_NAME)); + } + + /** + * @see DisabledOnOsIntegrationTests#aix() + */ + @Test + void aix() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onAix()); + } + + /** + * @see DisabledOnOsIntegrationTests#freebsd() + */ + @Test + void freebsd() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onFreebsd()); + } + + /** + * @see DisabledOnOsIntegrationTests#linux() + */ + @Test + void linux() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onLinux()); + } + + /** + * @see DisabledOnOsIntegrationTests#macOs() + */ + @Test + void macOs() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onMac()); + } + + /** + * @see DisabledOnOsIntegrationTests#macOsWithComposedAnnotation() + */ + @Test + void macOsWithComposedAnnotation() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onMac()); + } + + /** + * @see DisabledOnOsIntegrationTests#openbsd() + */ + @Test + void openbsd() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onOpenbsd()); + } + + /** + * @see DisabledOnOsIntegrationTests#windows() + */ + @Test + void windows() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onWindows()); + } + + /** + * @see DisabledOnOsIntegrationTests#solaris() + */ + @Test + void solaris() { + evaluateCondition(); + assertDisabledOnCurrentOsIf(onSolaris()); + } + + /** + * @see DisabledOnOsIntegrationTests#other() + */ + @Test + void other() { + evaluateCondition(); + assertDisabledOnCurrentOsIf( + !(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows())); + } + + /** + * @see DisabledOnOsIntegrationTests#architectureX86_64() + */ + @Test + void architectureX86_64() { + evaluateCondition(); + assertDisabledOnCurrentArchitectureIf(onArchitecture("x86_64")); + } + + /** + * @see DisabledOnOsIntegrationTests#architectureAarch64() + */ + @Test + void architectureAarch64() { + evaluateCondition(); + assertDisabledOnCurrentArchitectureIf(onArchitecture("aarch64")); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureX86_64WithMacOs() + */ + @Test + void architectureX86_64WithMacOs() { + evaluateCondition(); + assertDisabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("x86_64")); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureX86_64WithWindows() + */ + @Test + void architectureX86_64WithWindows() { + evaluateCondition(); + assertDisabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("x86_64")); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureX86_64WithLinux() + */ + @Test + void architectureX86_64WithLinux() { + evaluateCondition(); + assertDisabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("x86_64")); + } + + /** + * @see EnabledOnOsIntegrationTests#aarch64WithMacOs() + */ + @Test + void aarch64WithMacOs() { + evaluateCondition(); + assertDisabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("aarch64")); + } + + /** + * @see EnabledOnOsIntegrationTests#aarch64WithWindows() + */ + @Test + void aarch64WithWindows() { + evaluateCondition(); + assertDisabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("aarch64")); + } + + /** + * @see EnabledOnOsIntegrationTests#aarch64WithLinux() + */ + @Test + void aarch64WithLinux() { + evaluateCondition(); + assertDisabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("aarch64")); + } + + private void assertDisabledOnCurrentOsIf(boolean condition) { + if (condition) { + assertDisabled(); + assertReasonContains(String.format("Disabled on operating system: %s", OS_NAME)); + } + else { + assertEnabled(); + assertReasonContains(String.format("Enabled on operating system: %s", OS_NAME)); + } + } + + private void assertDisabledOnCurrentArchitectureIf(boolean condition) { + if (condition) { + assertDisabled(); + assertReasonContains(String.format("Disabled on architecture: %s", ARCH)); + } + else { + assertEnabled(); + assertReasonContains(String.format("Enabled on architecture: %s", ARCH)); + } + } + + private void assertDisabledOnCurrentOsAndArchitectureIf(boolean condition) { + if (condition) { + assertDisabled(); + assertReasonContains(String.format("Disabled on operating system: %s (%s)", OS_NAME, ARCH)); + } + else { + assertEnabled(); + assertReasonContains(String.format("Enabled on operating system: %s (%s)", OS_NAME, ARCH)); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java new file mode 100644 index 00000000..d5a27e7e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; +import static org.junit.jupiter.api.condition.OS.AIX; +import static org.junit.jupiter.api.condition.OS.FREEBSD; +import static org.junit.jupiter.api.condition.OS.LINUX; +import static org.junit.jupiter.api.condition.OS.MAC; +import static org.junit.jupiter.api.condition.OS.OPENBSD; +import static org.junit.jupiter.api.condition.OS.OTHER; +import static org.junit.jupiter.api.condition.OS.SOLARIS; +import static org.junit.jupiter.api.condition.OS.WINDOWS; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link DisabledOnOs}. + * + * @since 5.1 + */ +class DisabledOnOsIntegrationTests { + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledOnOs({}) + void missingOsAndArchitectureDeclaration() { + } + + @Test + @DisabledOnOs(value = { AIX, FREEBSD, LINUX, MAC, OPENBSD, WINDOWS, SOLARIS, + OTHER }, disabledReason = "Disabled on every OS") + void disabledOnEveryOs() { + fail("should be disabled"); + } + + @Test + @DisabledOnOs(AIX) + void aix() { + assertFalse(onAix()); + } + + @Test + @DisabledOnOs(FREEBSD) + void freebsd() { + assertFalse(onFreebsd()); + } + + @Test + @DisabledOnOs(LINUX) + void linux() { + assertFalse(onLinux()); + } + + @Test + @DisabledOnOs(MAC) + void macOs() { + assertFalse(onMac()); + } + + @Test + @DisabledOnMac + void macOsWithComposedAnnotation() { + assertFalse(onMac()); + } + + @Test + @DisabledOnOs(OPENBSD) + void openbsd() { + assertFalse(onOpenbsd()); + } + + @Test + @DisabledOnOs(WINDOWS) + void windows() { + assertFalse(onWindows()); + } + + @Test + @DisabledOnOs(SOLARIS) + void solaris() { + assertFalse(onSolaris()); + } + + @Test + @DisabledOnOs(OTHER) + void other() { + assertTrue(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows()); + } + + @Test + @DisabledOnOs(architectures = "x86_64") + void architectureX86_64() { + assertFalse(onArchitecture("x_86_64")); + } + + @Test + @DisabledOnOs(architectures = "aarch64") + void architectureAarch64() { + assertFalse(onArchitecture("aarch64")); + } + + @Test + @DisabledOnOs(value = MAC, architectures = "x86_64") + void architectureX86_64WithMacOs() { + assertFalse(onMac() && onArchitecture("x_86_64")); + } + + @Test + @DisabledOnOs(value = WINDOWS, architectures = "x86_64") + void architectureX86_64WithWindows() { + assertFalse(onWindows() && onArchitecture("x86_64")); + } + + @Test + @DisabledOnOs(value = LINUX, architectures = "x86_64") + void architectureX86_64WithLinux() { + assertFalse(onLinux() && onArchitecture("x86_64")); + } + + @Test + @DisabledOnOs(value = MAC, architectures = "aarch64") + void aarch64WithMacOs() { + assertFalse(onMac() && onArchitecture("aarch64")); + } + + @Test + @DisabledOnOs(value = WINDOWS, architectures = "aarch64") + void aarch64WithWindows() { + assertFalse(onWindows() && onArchitecture("aarch64")); + } + + @Test + @DisabledOnOs(value = LINUX, architectures = "aarch64") + void aarch64WithLinux() { + assertFalse(onLinux() && onArchitecture("aarch64")); + } + // ------------------------------------------------------------------------- + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @DisabledOnOs(MAC) + @interface DisabledOnMac { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java new file mode 100644 index 00000000..f8946952 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link EnabledForJreRange}. + * + *

Note that test method names MUST match the test method names in + * {@link EnabledForJreRangeIntegrationTests}. + * + * @since 5.6 + */ +class EnabledForJreRangeConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new EnabledForJreRangeCondition(); + } + + @Override + protected Class getTestClass() { + return EnabledForJreRangeIntegrationTests.class; + } + + /** + * @see EnabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@EnabledForJreRange is not present"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#defaultValues() + */ + @Test + void defaultValues() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessageContaining("You must declare a non-default value for min or max in @EnabledForJreRange"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#java17() + */ + @Test + void java17() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava17()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#java18to19() + */ + @Test + void java18to19() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava18() || onJava19()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#javaMax18() + */ + @Test + void javaMax18() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() + || onJava14() || onJava15() || onJava16() || onJava17() || onJava18()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#javaMin18() + */ + @Test + void javaMin18() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(!(onJava17())); + } + + /** + * @see EnabledForJreRangeIntegrationTests#other() + */ + @Test + void other() { + evaluateCondition(); + assertEnabledOnCurrentJreIf( + !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() + || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); + } + + private void assertEnabledOnCurrentJreIf(boolean condition) { + if (condition) { + assertEnabled(); + assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); + } + else { + assertDisabled(); + assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java new file mode 100644 index 00000000..5007240e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.OTHER; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link EnabledForJreRange}. + * + * @since 5.6 + */ +class EnabledForJreRangeIntegrationTests { + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange + void defaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @EnabledForJreRange(min = JAVA_17, max = JAVA_17) + void java17() { + assertTrue(onJava17()); + } + + @Test + @EnabledForJreRange(min = JAVA_18, max = JAVA_19) + void java18to19() { + assertTrue(onJava18() || onJava19()); + assertFalse(onJava17()); + } + + @Test + @EnabledForJreRange(max = JAVA_18) + void javaMax18() { + assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18()); + assertFalse(onJava19()); + } + + @Test + @EnabledForJreRange(min = JAVA_18) + void javaMin18() { + assertTrue(onJava18() || onJava19() || onJava20() || onJava21()); + assertFalse(onJava17()); + } + + @Test + @EnabledForJreRange(min = OTHER, max = OTHER) + void other() { + assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java new file mode 100644 index 00000000..ec249667 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; + +/** + * Unit tests for {@link EnabledIf}. + * + *

Note that test method names MUST match the test method names in + * {@link EnabledIfIntegrationTests}. + * + * @since 5.7 + */ +public class EnabledIfConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new EnabledIfCondition(); + } + + @Override + protected Class getTestClass() { + return EnabledIfIntegrationTests.class; + } + + /** + * @see EnabledIfIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@EnabledIf is not present"); + } + + /** + * @see EnabledIfIntegrationTests#enabledBecauseStaticConditionMethodReturnsTrue() + */ + @Test + void enabledBecauseStaticConditionMethodReturnsTrue() { + evaluateCondition(); + assertEnabled(); + assertReasonContains(""" + @EnabledIf("staticMethodThatReturnsTrue") evaluated to true"""); + } + + /** + * @see EnabledIfIntegrationTests#disabledBecauseStaticConditionMethodReturnsFalse() + */ + @Test + void disabledBecauseStaticConditionMethodReturnsFalse() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("Disabled for some reason"); + } + + /** + * @see EnabledIfIntegrationTests#enabledBecauseConditionMethodReturnsTrue() + */ + @Test + void enabledBecauseConditionMethodReturnsTrue() { + evaluateCondition(); + assertEnabled(); + assertReasonContains(""" + @EnabledIf("methodThatReturnsTrue") evaluated to true"""); + } + + /** + * @see EnabledIfIntegrationTests#disabledBecauseConditionMethodReturnsFalse() + */ + @Test + void disabledBecauseConditionMethodReturnsFalse() { + evaluateCondition(); + assertDisabled(); + assertReasonContains(""" + @EnabledIf("methodThatReturnsFalse") evaluated to false"""); + } + + /** + * @see EnabledIfIntegrationTests.ExternalConditionMethod#enabledBecauseStaticExternalConditionMethodReturnsTrue() + */ + @Test + void enabledBecauseStaticExternalConditionMethodReturnsTrue() { + evaluateCondition(); + assertEnabled(); + assertReasonContains(""" + @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") evaluated to true"""); + } + + /** + * @see EnabledIfIntegrationTests.ExternalConditionMethod#disabledBecauseStaticExternalConditionMethodReturnsFalse() + */ + @Test + void disabledBecauseStaticExternalConditionMethodReturnsFalse() { + evaluateCondition(); + assertDisabled(); + assertReasonContains( + """ + @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") evaluated to false"""); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java new file mode 100644 index 00000000..c90c28c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java @@ -0,0 +1,164 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.BOGUS; +import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.ENIGMA; +import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY1; +import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY2; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link EnabledIfEnvironmentVariableCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link EnabledIfEnvironmentVariableIntegrationTests}. + * + * @since 5.1 + */ +class EnabledIfEnvironmentVariableConditionTests extends AbstractExecutionConditionTests { + + /** + * Stubbed subclass of {@link EnabledIfEnvironmentVariableCondition}. + */ + private ExecutionCondition condition = new EnabledIfEnvironmentVariableCondition() { + + @Override + protected String getEnvironmentVariable(String name) { + return KEY1.equals(name) ? ENIGMA : null; + } + }; + + @Override + protected ExecutionCondition getExecutionCondition() { + return condition; + } + + @Override + protected Class getTestClass() { + return EnabledIfEnvironmentVariableIntegrationTests.class; + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains( + "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#blankNamedAttribute() + */ + @Test + void blankNamedAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#blankMatchesAttribute() + */ + @Test + void blankMatchesAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableMatchesExactly() + */ + @Test + void enabledBecauseEnvironmentVariableMatchesExactly() { + evaluateCondition(); + assertEnabled(); + assertReasonContains( + "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseBothEnvironmentVariablesMatchExactly() + */ + @Test + void enabledBecauseBothEnvironmentVariablesMatchExactly() { + this.condition = new EnabledIfEnvironmentVariableCondition() { + + @Override + protected String getEnvironmentVariable(String name) { + return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; + } + }; + + evaluateCondition(); + assertEnabled(); + assertReasonContains( + "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableMatchesPattern() + */ + @Test + void enabledBecauseEnvironmentVariableMatchesPattern() { + evaluateCondition(); + assertEnabled(); + assertReasonContains( + "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableDoesNotMatch() + */ + @Test + void disabledBecauseEnvironmentVariableDoesNotMatch() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("does not match regular expression"); + assertCustomDisabledReasonIs("Not bogus"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() + */ + @Test + void disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() { + this.condition = new EnabledIfEnvironmentVariableCondition() { + + @Override + protected String getEnvironmentVariable(String name) { + return KEY1.equals(name) ? ENIGMA : KEY2.equals(name) ? BOGUS : null; + } + }; + + evaluateCondition(); + assertDisabled(); + assertReasonContains("does not match regular expression"); + } + + /** + * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableDoesNotExist() + */ + @Test + void disabledBecauseEnvironmentVariableDoesNotExist() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("does not exist"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java new file mode 100644 index 00000000..2e0224c3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link EnabledIfEnvironmentVariable}. + * + * @since 5.1 + */ +@Disabled("Disabled since the required environment variables are not set") +// Tests (except those with intentional configuration errors) will pass if you set +// the following environment variables: +// EnabledIfEnvironmentVariableTests.key1 = enigma +// EnabledIfEnvironmentVariableTests.key2 = enigma +class EnabledIfEnvironmentVariableIntegrationTests { + + static final String KEY1 = "EnabledIfEnvironmentVariableTests.key1"; + static final String KEY2 = "EnabledIfEnvironmentVariableTests.key2"; + static final String ENIGMA = "enigma"; + static final String PUZZLE = "puzzle"; + static final String BOGUS = "bogus"; + + @Test + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @EnabledIfEnvironmentVariable(named = " ", matches = ENIGMA) + void blankNamedAttribute() { + } + + @Test + @EnabledIfEnvironmentVariable(named = KEY1, matches = " ") + void blankMatchesAttribute() { + } + + @Test + @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) + void enabledBecauseEnvironmentVariableMatchesExactly() { + assertEquals(ENIGMA, System.getenv(KEY1)); + } + + @Test + @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) + @EnabledIfEnvironmentVariable(named = KEY2, matches = ENIGMA) + void enabledBecauseBothEnvironmentVariablesMatchExactly() { + assertEquals(ENIGMA, System.getenv(KEY1)); + assertEquals(ENIGMA, System.getenv(KEY2)); + } + + @Test + @EnabledIfEnvironmentVariable(named = KEY1, matches = ".*e.+ma$") + void enabledBecauseEnvironmentVariableMatchesPattern() { + assertEquals(ENIGMA, System.getenv(KEY1)); + } + + @Test + @EnabledIfEnvironmentVariable(named = KEY1, matches = BOGUS, disabledReason = "Not bogus") + void disabledBecauseEnvironmentVariableDoesNotMatch() { + fail("should be disabled"); + } + + @Test + @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) + @CustomEnabled + void disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() { + fail("should be disabled"); + } + + @Test + @EnabledIfEnvironmentVariable(named = BOGUS, matches = "doesn't matter") + void disabledBecauseEnvironmentVariableDoesNotExist() { + fail("should be disabled"); + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @EnabledIfEnvironmentVariable(named = KEY2, matches = PUZZLE) + @interface CustomEnabled { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java new file mode 100644 index 00000000..60eac3ad --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link EnabledIf}. + * + * @since 5.7 + */ +public class EnabledIfIntegrationTests { + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @EnabledIf("staticMethodThatReturnsTrue") + void enabledBecauseStaticConditionMethodReturnsTrue() { + } + + @Test + @EnabledIf(value = "staticMethodThatReturnsFalse", disabledReason = "Disabled for some reason") + void disabledBecauseStaticConditionMethodReturnsFalse() { + fail("Should be disabled"); + } + + @Test + @EnabledIf("methodThatReturnsTrue") + void enabledBecauseConditionMethodReturnsTrue() { + } + + @Test + @EnabledIf("methodThatReturnsFalse") + void disabledBecauseConditionMethodReturnsFalse() { + fail("Should be disabled"); + } + + @Test + @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") + void enabledBecauseStaticExternalConditionMethodReturnsTrue() { + } + + @Test + @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") + void disabledBecauseStaticExternalConditionMethodReturnsFalse() { + fail("Should be disabled"); + } + + @Nested + @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") + class ConditionallyDisabledClass { + + @Test + void disabledBecauseConditionMethodReturnsFalse() { + fail("Should be disabled"); + } + + } + + // ------------------------------------------------------------------------- + + @SuppressWarnings("unused") + private static boolean staticMethodThatReturnsTrue() { + return true; + } + + @SuppressWarnings("unused") + private static boolean staticMethodThatReturnsFalse() { + return false; + } + + @SuppressWarnings("unused") + private boolean methodThatReturnsTrue() { + return true; + } + + @SuppressWarnings("unused") + private boolean methodThatReturnsFalse() { + return false; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java new file mode 100644 index 00000000..399687dd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link EnabledIfSystemPropertyCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link EnabledIfSystemPropertyIntegrationTests}. + * + * @since 5.1 + */ +class EnabledIfSystemPropertyConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new EnabledIfSystemPropertyCondition(); + } + + @Override + protected Class getTestClass() { + return EnabledIfSystemPropertyIntegrationTests.class; + } + + @BeforeAll + static void setSystemProperties() { + EnabledIfSystemPropertyIntegrationTests.setSystemProperties(); + } + + @AfterAll + static void clearSystemProperties() { + EnabledIfSystemPropertyIntegrationTests.clearSystemProperties(); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#blankNamedAttribute() + */ + @Test + void blankNamedAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#blankMatchesAttribute() + */ + @Test + void blankMatchesAttribute() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyMatchesExactly() + */ + @Test + void enabledBecauseSystemPropertyMatchesExactly() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseBothSystemPropertiesMatchExactly() + */ + @Test + void enabledBecauseBothSystemPropertiesMatchExactly() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyMatchesPattern() + */ + @Test + void enabledBecauseSystemPropertyMatchesPattern() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyDoesNotMatch() + */ + @Test + void disabledBecauseSystemPropertyDoesNotMatch() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("does not match regular expression"); + assertCustomDisabledReasonIs("Not bogus"); + } + + @Test + void disabledBecauseSystemPropertyForComposedAnnotationDoesNotMatch() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("does not match regular expression"); + } + + /** + * @see EnabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyDoesNotExist() + */ + @Test + void disabledBecauseSystemPropertyDoesNotExist() { + evaluateCondition(); + assertDisabled(); + assertReasonContains("does not exist"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java new file mode 100644 index 00000000..2a6010e3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link EnabledIfSystemProperty}. + * + * @since 5.1 + */ +class EnabledIfSystemPropertyIntegrationTests { + + private static final String KEY1 = "EnabledIfSystemPropertyTests.key1"; + private static final String KEY2 = "EnabledIfSystemPropertyTests.key2"; + private static final String ENIGMA = "enigma"; + private static final String BOGUS = "bogus"; + + @BeforeAll + static void setSystemProperties() { + System.setProperty(KEY1, ENIGMA); + System.setProperty(KEY2, ENIGMA); + } + + @AfterAll + static void clearSystemProperties() { + System.clearProperty(KEY1); + System.clearProperty(KEY2); + } + + @Test + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledIfSystemProperty(named = " ", matches = ENIGMA) + void blankNamedAttribute() { + fail("should be disabled"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledIfSystemProperty(named = KEY1, matches = " ") + void blankMatchesAttribute() { + fail("should be disabled"); + } + + @Test + @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) + void enabledBecauseSystemPropertyMatchesExactly() { + assertEquals(ENIGMA, System.getProperty(KEY1)); + } + + @Test + @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) + @EnabledIfSystemProperty(named = KEY2, matches = ENIGMA) + void enabledBecauseBothSystemPropertiesMatchExactly() { + assertEquals(ENIGMA, System.getProperty(KEY1)); + assertEquals(ENIGMA, System.getProperty(KEY2)); + } + + @Test + @EnabledIfSystemProperty(named = KEY1, matches = ".*en.+gma$") + void enabledBecauseSystemPropertyMatchesPattern() { + assertEquals(ENIGMA, System.getProperty(KEY1)); + } + + @Test + @EnabledIfSystemProperty(named = KEY1, matches = BOGUS, disabledReason = "Not bogus") + void disabledBecauseSystemPropertyDoesNotMatch() { + fail("should be disabled"); + } + + @Test + @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) + @CustomEnabled + void disabledBecauseSystemPropertyForComposedAnnotationDoesNotMatch() { + fail("should be disabled"); + } + + @Test + @EnabledIfSystemProperty(named = BOGUS, matches = "doesn't matter") + void disabledBecauseSystemPropertyDoesNotExist() { + fail("should be disabled"); + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @EnabledIfSystemProperty(named = KEY2, matches = BOGUS) + @interface CustomEnabled { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java new file mode 100644 index 00000000..9c4bb4c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java @@ -0,0 +1,231 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; +import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link EnabledOnJreCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link EnabledOnJreIntegrationTests}. + * + * @since 5.1 + */ +class EnabledOnJreConditionTests extends AbstractExecutionConditionTests { + + @Override + protected ExecutionCondition getExecutionCondition() { + return new EnabledOnJreCondition(); + } + + @Override + protected Class getTestClass() { + return EnabledOnJreIntegrationTests.class; + } + + /** + * @see EnabledOnJreIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@EnabledOnJre is not present"); + } + + /** + * @see EnabledOnJreIntegrationTests#missingJreDeclaration() + */ + @Test + void missingJreDeclaration() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("You must declare at least one JRE"); + } + + /** + * @see EnabledOnJreIntegrationTests#enabledOnAllJavaVersions() + */ + @Test + void enabledOnAllJavaVersions() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(true); + } + + /** + * @see EnabledOnJreIntegrationTests#java8() + */ + @Test + void java8() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava8()); + } + + /** + * @see EnabledOnJreIntegrationTests#java9() + */ + @Test + void java9() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava9()); + } + + /** + * @see EnabledOnJreIntegrationTests#java10() + */ + @Test + void java10() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava10()); + } + + /** + * @see EnabledOnJreIntegrationTests#java11() + */ + @Test + void java11() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava11()); + } + + /** + * @see EnabledOnJreIntegrationTests#java12() + */ + @Test + void java12() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava12()); + } + + /** + * @see EnabledOnJreIntegrationTests#java13() + */ + @Test + void java13() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava13()); + } + + /** + * @see EnabledOnJreIntegrationTests#java14() + */ + @Test + void java14() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava14()); + } + + /** + * @see EnabledOnJreIntegrationTests#java15() + */ + @Test + void java15() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava15()); + } + + /** + * @see EnabledOnJreIntegrationTests#java16() + */ + @Test + void java16() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava16()); + } + + /** + * @see EnabledOnJreIntegrationTests#java17() + */ + @Test + void java17() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava17()); + } + + /** + * @see EnabledOnJreIntegrationTests#java18() + */ + @Test + void java18() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava18()); + } + + /** + * @see EnabledOnJreIntegrationTests#java19() + */ + @Test + void java19() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava19()); + } + + /** + * @see EnabledOnJreIntegrationTests#java20() + */ + @Test + void java20() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava20()); + } + + /** + * @see EnabledOnJreIntegrationTests#java21() + */ + @Test + void java21() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava21()); + } + + /** + * @see EnabledOnJreIntegrationTests#other() + */ + @Test + void other() { + evaluateCondition(); + assertEnabledOnCurrentJreIf( + !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() + || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); + assertCustomDisabledReasonIs("Disabled on almost every JRE"); + } + + private void assertEnabledOnCurrentJreIf(boolean condition) { + if (condition) { + assertEnabled(); + assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); + } + else { + assertDisabled(); + assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java new file mode 100644 index 00000000..a7a41448 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java @@ -0,0 +1,207 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.condition.JRE.JAVA_10; +import static org.junit.jupiter.api.condition.JRE.JAVA_11; +import static org.junit.jupiter.api.condition.JRE.JAVA_12; +import static org.junit.jupiter.api.condition.JRE.JAVA_13; +import static org.junit.jupiter.api.condition.JRE.JAVA_14; +import static org.junit.jupiter.api.condition.JRE.JAVA_15; +import static org.junit.jupiter.api.condition.JRE.JAVA_16; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.JAVA_20; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; +import static org.junit.jupiter.api.condition.JRE.JAVA_8; +import static org.junit.jupiter.api.condition.JRE.JAVA_9; +import static org.junit.jupiter.api.condition.JRE.OTHER; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link EnabledOnJre}. + * + * @since 5.1 + */ +class EnabledOnJreIntegrationTests { + + private static final String JAVA_VERSION = System.getProperty("java.version"); + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledOnJre({}) + void missingJreDeclaration() { + } + + @Test + @EnabledOnJre({ JAVA_8, JAVA_9, JAVA_10, JAVA_11, JAVA_12, JAVA_13, JAVA_14, JAVA_15, JAVA_16, JAVA_17, JAVA_18, + JAVA_19, JAVA_20, JAVA_21, OTHER }) + void enabledOnAllJavaVersions() { + } + + @Test + @EnabledOnJre(JAVA_8) + void java8() { + assertTrue(onJava8()); + } + + @Test + @EnabledOnJre(JAVA_9) + void java9() { + assertTrue(onJava9()); + } + + @Test + @EnabledOnJre(JAVA_10) + void java10() { + assertTrue(onJava10()); + } + + @Test + @EnabledOnJre(JAVA_11) + void java11() { + assertTrue(onJava11()); + } + + @Test + @EnabledOnJre(JAVA_12) + void java12() { + assertTrue(onJava12()); + } + + @Test + @EnabledOnJre(JAVA_13) + void java13() { + assertTrue(onJava13()); + } + + @Test + @EnabledOnJre(JAVA_14) + void java14() { + assertTrue(onJava14()); + } + + @Test + @EnabledOnJre(JAVA_15) + void java15() { + assertTrue(onJava15()); + } + + @Test + @EnabledOnJre(JAVA_16) + void java16() { + assertTrue(onJava16()); + } + + @Test + @EnabledOnJre(JAVA_17) + void java17() { + assertTrue(onJava17()); + } + + @Test + @EnabledOnJre(JAVA_18) + void java18() { + assertTrue(onJava18()); + } + + @Test + @EnabledOnJre(JAVA_19) + void java19() { + assertTrue(onJava19()); + } + + @Test + @EnabledOnJre(JAVA_20) + void java20() { + assertTrue(onJava20()); + } + + @Test + @EnabledOnJre(JAVA_21) + void java21() { + assertTrue(onJava21()); + } + + @Test + @EnabledOnJre(value = OTHER, disabledReason = "Disabled on almost every JRE") + void other() { + assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); + } + + static boolean onJava8() { + return JAVA_VERSION.startsWith("1.8"); + } + + static boolean onJava9() { + return JAVA_VERSION.startsWith("9"); + } + + static boolean onJava10() { + return JAVA_VERSION.startsWith("10"); + } + + static boolean onJava11() { + return JAVA_VERSION.startsWith("11"); + } + + static boolean onJava12() { + return JAVA_VERSION.startsWith("12"); + } + + static boolean onJava13() { + return JAVA_VERSION.startsWith("13"); + } + + static boolean onJava14() { + return JAVA_VERSION.startsWith("14"); + } + + static boolean onJava15() { + return JAVA_VERSION.startsWith("15"); + } + + static boolean onJava16() { + return JAVA_VERSION.startsWith("16"); + } + + static boolean onJava17() { + return JAVA_VERSION.startsWith("17"); + } + + static boolean onJava18() { + return JAVA_VERSION.startsWith("18"); + } + + static boolean onJava19() { + return JAVA_VERSION.startsWith("19"); + } + + static boolean onJava20() { + return JAVA_VERSION.startsWith("20"); + } + + static boolean onJava21() { + return JAVA_VERSION.startsWith("21"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java new file mode 100644 index 00000000..267e5e69 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java @@ -0,0 +1,267 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; +import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link EnabledOnOsCondition}. + * + *

Note that test method names MUST match the test method names in + * {@link EnabledOnOsIntegrationTests}. + * + * @since 5.1 + */ +class EnabledOnOsConditionTests extends AbstractExecutionConditionTests { + + private static final String OS_NAME = System.getProperty("os.name"); + private static final String ARCH = System.getProperty("os.arch"); + + @Override + protected ExecutionCondition getExecutionCondition() { + return new EnabledOnOsCondition(); + } + + @Override + protected Class getTestClass() { + return EnabledOnOsIntegrationTests.class; + } + + /** + * @see EnabledOnOsIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@EnabledOnOs is not present"); + } + + /** + * @see EnabledOnOsIntegrationTests#missingOsAndArchitectureDeclaration() + */ + @Test + void missingOsAndArchitectureDeclaration() { + Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); + assertThat(exception).hasMessageContaining("You must declare at least one OS or architecture"); + } + + /** + * @see EnabledOnOsIntegrationTests#enabledOnEveryOs() + */ + @Test + void enabledOnEveryOs() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(true); + } + + /** + * @see EnabledOnOsIntegrationTests#aix() + */ + @Test + void aix() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onAix()); + } + + /** + * @see EnabledOnOsIntegrationTests#freebsd() + */ + @Test + void freebsd() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onFreebsd()); + } + + /** + * @see EnabledOnOsIntegrationTests#linux() + */ + @Test + void linux() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onLinux()); + } + + /** + * @see EnabledOnOsIntegrationTests#macOs() + */ + @Test + void macOs() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onMac()); + } + + /** + * @see EnabledOnOsIntegrationTests#macOsWithComposedAnnotation() + */ + @Test + void macOsWithComposedAnnotation() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onMac()); + } + + /** + * @see EnabledOnOsIntegrationTests#openbsd() + */ + @Test + void openbsd() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onOpenbsd()); + } + + /** + * @see EnabledOnOsIntegrationTests#windows() + */ + @Test + void windows() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onWindows()); + } + + /** + * @see EnabledOnOsIntegrationTests#solaris() + */ + @Test + void solaris() { + evaluateCondition(); + assertEnabledOnCurrentOsIf(onSolaris()); + } + + /** + * @see EnabledOnOsIntegrationTests#other() + */ + @Test + void other() { + evaluateCondition(); + assertEnabledOnCurrentOsIf( + !(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows())); + assertCustomDisabledReasonIs("Disabled on almost every OS"); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureX86_64() + */ + @Test + void architectureX86_64() { + evaluateCondition(); + assertEnabledOnCurrentArchitectureIf(onArchitecture("x86_64")); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureAarch64() + */ + @Test + void architectureAarch64() { + evaluateCondition(); + assertEnabledOnCurrentArchitectureIf(onArchitecture("aarch64")); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureX86_64WithMacOs() + */ + @Test + void architectureX86_64WithMacOs() { + evaluateCondition(); + assertEnabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("x86_64")); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureX86_64WithWindows() + */ + @Test + void architectureX86_64WithWindows() { + evaluateCondition(); + assertEnabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("x86_64")); + } + + /** + * @see EnabledOnOsIntegrationTests#architectureX86_64WithLinux() + */ + @Test + void architectureX86_64WithLinux() { + evaluateCondition(); + assertEnabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("x86_64")); + } + + /** + * @see EnabledOnOsIntegrationTests#aarch64WithMacOs() + */ + @Test + void aarch64WithMacOs() { + evaluateCondition(); + assertEnabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("aarch64")); + } + + /** + * @see EnabledOnOsIntegrationTests#aarch64WithWindows() + */ + @Test + void aarch64WithWindows() { + evaluateCondition(); + assertEnabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("aarch64")); + } + + /** + * @see EnabledOnOsIntegrationTests#aarch64WithLinux() + */ + @Test + void aarch64WithLinux() { + evaluateCondition(); + assertEnabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("aarch64")); + } + + private void assertEnabledOnCurrentOsIf(boolean condition) { + if (condition) { + assertEnabled(); + assertReasonContains(String.format("Enabled on operating system: %s", OS_NAME)); + } + else { + assertDisabled(); + assertReasonContains(String.format("Disabled on operating system: %s", OS_NAME)); + } + } + + private void assertEnabledOnCurrentArchitectureIf(boolean condition) { + if (condition) { + assertEnabled(); + assertReasonContains(String.format("Enabled on architecture: %s", ARCH)); + } + else { + assertDisabled(); + assertReasonContains(String.format("Disabled on architecture: %s", ARCH)); + } + } + + private void assertEnabledOnCurrentOsAndArchitectureIf(boolean condition) { + if (condition) { + assertEnabled(); + assertReasonContains(String.format("Enabled on operating system: %s (%s)", OS_NAME, ARCH)); + } + else { + assertDisabled(); + assertReasonContains(String.format("Disabled on operating system: %s (%s)", OS_NAME, ARCH)); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java new file mode 100644 index 00000000..5e7b15c6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java @@ -0,0 +1,207 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.condition.OS.AIX; +import static org.junit.jupiter.api.condition.OS.FREEBSD; +import static org.junit.jupiter.api.condition.OS.LINUX; +import static org.junit.jupiter.api.condition.OS.MAC; +import static org.junit.jupiter.api.condition.OS.OPENBSD; +import static org.junit.jupiter.api.condition.OS.OTHER; +import static org.junit.jupiter.api.condition.OS.SOLARIS; +import static org.junit.jupiter.api.condition.OS.WINDOWS; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Locale; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link EnabledOnOs}. + * + * @since 5.1 + */ +class EnabledOnOsIntegrationTests { + + private static final String ARCH = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); + private static final String OS_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledOnOs({}) + void missingOsAndArchitectureDeclaration() { + } + + @Test + @EnabledOnOs({ AIX, FREEBSD, LINUX, MAC, OPENBSD, WINDOWS, SOLARIS, OTHER }) + void enabledOnEveryOs() { + } + + @Test + @EnabledOnOs(AIX) + void aix() { + assertTrue(onAix()); + } + + @Test + @EnabledOnOs(FREEBSD) + void freebsd() { + assertTrue(onFreebsd()); + } + + @Test + @EnabledOnOs(LINUX) + void linux() { + assertTrue(onLinux()); + } + + @Test + @EnabledOnOs(MAC) + void macOs() { + assertTrue(onMac()); + } + + @Test + @EnabledOnMac + void macOsWithComposedAnnotation() { + assertTrue(onMac()); + } + + @Test + @EnabledOnOs(OPENBSD) + void openbsd() { + assertTrue(onOpenbsd()); + } + + @Test + @EnabledOnOs(WINDOWS) + void windows() { + assertTrue(onWindows()); + } + + @Test + @EnabledOnOs(SOLARIS) + void solaris() { + assertTrue(onSolaris()); + } + + @Test + @EnabledOnOs(value = OTHER, disabledReason = "Disabled on almost every OS") + void other() { + assertFalse(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows()); + } + + @Test + @EnabledOnOs(architectures = "x86_64") + void architectureX86_64() { + assertFalse(onArchitecture("x_86_64")); + } + + @Test + @EnabledOnOs(architectures = "aarch64") + void architectureAarch64() { + assertTrue(onArchitecture("aarch64")); + } + + @Test + @EnabledOnOs(value = MAC, architectures = "x86_64") + void architectureX86_64WithMacOs() { + assertTrue(onMac()); + assertTrue(onArchitecture("x86_64")); + } + + @Test + @EnabledOnOs(value = WINDOWS, architectures = "x86_64") + void architectureX86_64WithWindows() { + assertTrue(onWindows()); + assertTrue(onArchitecture("x86_64")); + } + + @Test + @EnabledOnOs(value = LINUX, architectures = "x86_64") + void architectureX86_64WithLinux() { + assertTrue(onLinux()); + assertTrue(onArchitecture("x86_64")); + } + + @Test + @EnabledOnOs(value = MAC, architectures = "aarch64") + void aarch64WithMacOs() { + assertTrue(onMac()); + assertTrue(onArchitecture("aarch64")); + } + + @Test + @EnabledOnOs(value = WINDOWS, architectures = "aarch64") + void aarch64WithWindows() { + assertTrue(onWindows()); + assertTrue(onArchitecture("aarch64")); + } + + @Test + @EnabledOnOs(value = LINUX, architectures = "aarch64") + void aarch64WithLinux() { + assertTrue(onLinux()); + assertTrue(onArchitecture("aarch64")); + } + + static boolean onAix() { + return OS_NAME.contains("aix"); + } + + static boolean onArchitecture(String arch) { + return ARCH.contains(arch); + } + + static boolean onFreebsd() { + return OS_NAME.contains("freebsd"); + } + + static boolean onLinux() { + return OS_NAME.contains("linux"); + } + + static boolean onMac() { + return OS_NAME.contains("mac"); + } + + static boolean onOpenbsd() { + return OS_NAME.contains("openbsd"); + } + + static boolean onSolaris() { + return OS_NAME.contains("solaris"); + } + + static boolean onWindows() { + return OS_NAME.contains("windows"); + } + + // ------------------------------------------------------------------------- + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @EnabledOnOs(MAC) + @interface EnabledOnMac { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java new file mode 100644 index 00000000..f915146c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.JAVA_20; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; +import static org.junit.jupiter.api.condition.JRE.OTHER; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link JRE} + * + * @since 5.7 + */ +public class JRETests { + + @Test + @EnabledOnJre(JAVA_17) + void java17() { + assertEquals(JAVA_17, JRE.currentVersion()); + } + + @Test + @EnabledOnJre(JAVA_18) + void java18() { + assertEquals(JAVA_18, JRE.currentVersion()); + } + + @Test + @EnabledOnJre(JAVA_19) + void java19() { + assertEquals(JAVA_19, JRE.currentVersion()); + } + + @Test + @EnabledOnJre(JAVA_20) + void java20() { + assertEquals(JAVA_20, JRE.currentVersion()); + } + + @Test + @EnabledOnJre(JAVA_21) + void java21() { + assertEquals(JAVA_21, JRE.currentVersion()); + } + + @Test + @EnabledOnJre(OTHER) + void other() { + assertEquals(OTHER, JRE.currentVersion()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java new file mode 100644 index 00000000..1175c870 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +public class StaticConditionMethods { + + public static boolean returnsTrue() { + return true; + } + + public static boolean returnsFalse() { + return false; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java new file mode 100644 index 00000000..f96e1b63 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.reportEntry; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; + +public class CloseableResourceIntegrationTests extends AbstractJupiterTestEngineTests { + + @Test + void closesCloseableResourcesInReverseInsertOrder() { + executeTestsForClass(TestCase.class).allEvents().reportingEntryPublished() // + .assertEventsMatchExactly( // + reportEntry(Map.of("3", "closed")), // + reportEntry(Map.of("2", "closed")), // + reportEntry(Map.of("1", "closed"))); + } + + @ExtendWith(ExtensionContextParameterResolver.class) + static class TestCase { + @Test + void closesCloseableResourcesInExtensionContext(ExtensionContext extensionContext) { + ExtensionContext.Store store = extensionContext.getStore(GLOBAL); + store.put("foo", reportEntryOnClose(extensionContext, "1")); + store.put("bar", reportEntryOnClose(extensionContext, "2")); + store.put("baz", reportEntryOnClose(extensionContext, "3")); + } + + private ExtensionContext.Store.CloseableResource reportEntryOnClose(ExtensionContext extensionContext, + String key) { + return () -> extensionContext.publishReportEntry(Map.of(key, "closed")); + } + } + + @Test + void exceptionsDuringCloseAreReportedAsSuppressed() { + executeTestsForClass(ExceptionInCloseableResourceTestCase.class).testEvents() // + .assertEventsMatchLoosely(event( // + test(), // + finishedWithFailure( // + message("Exception in test"), // + suppressed(0, message("Exception in onClose"))))); + } + + @ExtendWith(ThrowingOnCloseExtension.class) + static class ExceptionInCloseableResourceTestCase { + + @Test + void test() { + throw new RuntimeException("Exception in test"); + } + + } + + static class ThrowingOnCloseExtension implements BeforeEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + context.getStore(GLOBAL).put("throwingResource", (ExtensionContext.Store.CloseableResource) () -> { + throw new RuntimeException("Exception in onClose"); + }); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java new file mode 100644 index 00000000..82a96d16 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * @since 5.9 + */ +public class ExecutableInvokerIntegrationTests extends AbstractJupiterTestEngineTests { + + @Test + void invokeConstructorViaExtensionContext() { + EngineExecutionResults results = executeTestsForClass(ExecuteConstructorTwiceTestCase.class); + + assertEquals(1, results.testEvents().succeeded().count()); + assertEquals(2, ExecuteConstructorTwiceTestCase.constructorInvocations); + } + + @Test + void invokeMethodViaExtensionContext() { + EngineExecutionResults results = executeTestsForClass(ExecuteTestsTwiceTestCase.class); + + assertEquals(1, results.testEvents().succeeded().count()); + assertEquals(2, ExecuteTestsTwiceTestCase.testInvocations); + } + + @ExtendWith(ExecuteTestsTwiceExtension.class) + static class ExecuteTestsTwiceTestCase { + + static int testInvocations = 0; + + @Test + void testWithResolvedParameter(TestInfo testInfo) { + assertNotNull(testInfo); + testInvocations++; + } + + } + + @ExtendWith(ExecuteConstructorTwiceExtension.class) + static class ExecuteConstructorTwiceTestCase { + + static int constructorInvocations = 0; + + public ExecuteConstructorTwiceTestCase(TestInfo testInfo) { + assertNotNull(testInfo); + constructorInvocations++; + } + + @Test + void test() { + + } + + } + + static class ExecuteTestsTwiceExtension implements AfterTestExecutionCallback { + + @Override + public void afterTestExecution(ExtensionContext context) { + context.getExecutableInvoker() // + .invoke(context.getRequiredTestMethod(), context.getRequiredTestInstance()); + } + + } + + static class ExecuteConstructorTwiceExtension implements BeforeAllCallback { + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + context.getExecutableInvoker() // + .invoke(context.getRequiredTestClass().getConstructor(TestInfo.class)); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java new file mode 100644 index 00000000..1ac243cf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.platform.commons.util.FunctionUtils.where; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for extension composability in JUnit Jupiter. + * + *

The purpose of these tests is to ensure that a concrete extension + * (a.k.a., the kitchen sink extension) is able to implement all extension + * APIs supported by JUnit Jupiter without any naming conflicts or + * ambiguities with regard to method names or method signatures. + * + * @since 5.0 + * @see KitchenSinkExtension + */ +class ExtensionComposabilityTests { + + @Test + void ensureJupiterExtensionApisAreComposable() { + + // 1) Find all existing top-level Extension APIs + List> extensionApis = ReflectionUtils.findAllClassesInPackage(Extension.class.getPackage().getName(), + this::isExtensionApi, name -> true); + + // 2) Determine which methods we expect the kitchen sink to implement... + + // @formatter:off + List expectedMethods = extensionApis.stream() + .map(Class::getDeclaredMethods) + .flatMap(Arrays::stream) + .filter(not(Method::isSynthetic)) + .filter(not(where(Method::getModifiers, Modifier::isStatic))) + .collect(toList()); + + List expectedMethodSignatures = expectedMethods.stream() + .map(this::methodSignature) + .sorted() + .collect(toList()); + + List expectedMethodNames = expectedMethods.stream() + .map(Method::getName) + .distinct() + .sorted() + .collect(toList()); + // @formatter:on + + // 3) Dynamically implement all Extension APIs + Object dynamicKitchenSinkExtension = Proxy.newProxyInstance(getClass().getClassLoader(), + extensionApis.toArray(Class[]::new), (proxy, method, args) -> null); + + // 4) Determine what ended up in the kitchen sink... + + // @formatter:off + List actualMethods = Arrays.stream(dynamicKitchenSinkExtension.getClass().getDeclaredMethods()) + .filter(ReflectionUtils::isNotStatic) + .collect(toList()); + + List actualMethodSignatures = actualMethods.stream() + .map(this::methodSignature) + .distinct() + .sorted() + .collect(toList()); + + List actualMethodNames = actualMethods.stream() + .map(Method::getName) + .distinct() + .sorted() + .collect(toList()); + // @formatter:on + + // 5) Remove methods from java.lang.Object + actualMethodSignatures.remove("equals(Object)"); + actualMethodSignatures.remove("hashCode()"); + actualMethodSignatures.remove("toString()"); + actualMethodNames.remove("equals"); + actualMethodNames.remove("hashCode"); + actualMethodNames.remove("toString"); + + // 6) Verify our expectations + + // @formatter:off + assertAll( + () -> assertThat(actualMethodSignatures).isEqualTo(expectedMethodSignatures), + () -> assertThat(actualMethodNames).isEqualTo(expectedMethodNames) + ); + // @formatter:on + } + + private boolean isExtensionApi(Class candidate) { + return candidate.isInterface() && (candidate != Extension.class) && Extension.class.isAssignableFrom(candidate); + } + + private String methodSignature(Method method) { + return String.format("%s(%s)", method.getName(), + ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java new file mode 100644 index 00000000..1740f609 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java @@ -0,0 +1,180 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Kitchen Sink extension that implements every extension API + * supported by JUnit Jupiter. + * + *

This extension should never actually be registered for any tests. + * Rather, its sole purpose is to help ensure (via visual inspection) + * that a concrete extension is able to implement all extension APIs + * supported by JUnit Jupiter without any naming conflicts or + * ambiguities with regard to method names or method signatures. + * {@link ExtensionComposabilityTests}, on the other hand, serves + * the same purpose in a dynamic and automated fashion. + * + * @since 5.0 + * @see ExtensionComposabilityTests + */ +// @formatter:off +public class KitchenSinkExtension implements + + // Lifecycle Callbacks + BeforeAllCallback, + BeforeEachCallback, + BeforeTestExecutionCallback, + TestExecutionExceptionHandler, + AfterTestExecutionCallback, + AfterEachCallback, + AfterAllCallback, + + // Lifecycle methods exception handling + LifecycleMethodExecutionExceptionHandler, + + // Dependency Injection + TestInstancePreConstructCallback, + TestInstanceFactory, + TestInstancePostProcessor, + TestInstancePreDestroyCallback, + ParameterResolver, + + // Conditional Test Execution + ExecutionCondition, + + // @TestTemplate + TestTemplateInvocationContextProvider, + + // Miscellaneous + TestWatcher, + InvocationInterceptor + +// @formatter:on +{ + + // --- Lifecycle Callbacks ------------------------------------------------- + + @Override + public void beforeAll(ExtensionContext context) { + } + + @Override + public void beforeEach(ExtensionContext context) { + } + + @Override + public void beforeTestExecution(ExtensionContext context) { + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { + } + + @Override + public void afterTestExecution(ExtensionContext context) { + } + + @Override + public void afterEach(ExtensionContext context) { + } + + @Override + public void afterAll(ExtensionContext context) { + } + + // --- Lifecycle methods exception handling + + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { + } + + // --- Dependency Injection ------------------------------------------------ + + @Override + public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) { + } + + @Override + public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { + return null; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + } + + @Override + public void preDestroyTestInstance(ExtensionContext context) { + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return null; + } + + // --- Conditional Test Execution ------------------------------------------ + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return null; + } + + // --- @TestTemplate ------------------------------------------------------- + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return false; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return null; + } + + // --- TestWatcher --------------------------------------------------------- + + @Override + public void testDisabled(ExtensionContext context, Optional reason) { + } + + @Override + public void testSuccessful(ExtensionContext context) { + } + + @Override + public void testAborted(ExtensionContext context, Throwable cause) { + } + + @Override + public void testFailed(ExtensionContext context, Throwable cause) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java new file mode 100644 index 00000000..40a333ef --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java @@ -0,0 +1,159 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.6 + */ +class TypeBasedParameterResolverTests { + + private final ParameterResolver basicTypeBasedParameterResolver = new BasicTypeBasedParameterResolver(); + private final ParameterResolver subClassedBasicTypeBasedParameterResolver = new SubClassedBasicTypeBasedParameterResolver(); + private final ParameterResolver parametrizedTypeBasedParameterResolver = new ParameterizedTypeBasedParameterResolver(); + + @Test + void missingTypeTypeBasedParameterResolver() { + PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, + MissingTypeTypeBasedParameterResolver::new); + assertEquals( + "Failed to discover parameter type supported by " + MissingTypeTypeBasedParameterResolver.class.getName() + + "; potentially caused by lacking parameterized type in class declaration.", + exception.getMessage()); + } + + @Test + void supportsParameterForBasicTypes() { + Parameter parameter1 = findParameterOfMethod("methodWithBasicTypeParameter", String.class); + assertTrue(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); + assertTrue(subClassedBasicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); + + Parameter parameter2 = findParameterOfMethod("methodWithObjectParameter", Object.class); + assertFalse(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter2), null)); + } + + @Test + void supportsParameterForParameterizedTypes() { + Parameter parameter1 = findParameterOfMethod("methodWithParameterizedTypeParameter", Map.class); + assertTrue(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); + + Parameter parameter3 = findParameterOfMethod("methodWithAnotherParameterizedTypeParameter", Map.class); + assertFalse(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter3), null)); + } + + @Test + void resolve() { + ExtensionContext extensionContext = extensionContext(); + ParameterContext parameterContext = parameterContext( + findParameterOfMethod("methodWithBasicTypeParameter", String.class)); + assertEquals("Displaying TestAnnotation", + basicTypeBasedParameterResolver.resolveParameter(parameterContext, extensionContext)); + + Parameter parameter2 = findParameterOfMethod("methodWithParameterizedTypeParameter", Map.class); + assertEquals(Map.of("ids", List.of(1, 42)), + parametrizedTypeBasedParameterResolver.resolveParameter(parameterContext(parameter2), extensionContext)); + } + + private static ParameterContext parameterContext(Parameter parameter) { + ParameterContext parameterContext = mock(); + when(parameterContext.getParameter()).thenReturn(parameter); + return parameterContext; + } + + private static ExtensionContext extensionContext() { + ExtensionContext extensionContext = mock(); + when(extensionContext.getDisplayName()).thenReturn("Displaying"); + return extensionContext; + } + + private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { + Method method = ReflectionUtils.findMethod(Sample.class, methodName, parameterTypes).get(); + return method.getParameters()[0]; + } + + // ------------------------------------------------------------------------- + + @SuppressWarnings("rawtypes") + static class MissingTypeTypeBasedParameterResolver extends TypeBasedParameterResolver { + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return "enigma"; + } + } + + static class BasicTypeBasedParameterResolver extends TypeBasedParameterResolver { + + @Override + public String resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + Class parameterAnnotation = parameterContext.getParameter().getAnnotations()[0].annotationType(); + return String.format("%s %s", extensionContext.getDisplayName(), parameterAnnotation.getSimpleName()); + } + } + + static class SubClassedBasicTypeBasedParameterResolver extends BasicTypeBasedParameterResolver { + } + + static class ParameterizedTypeBasedParameterResolver + extends TypeBasedParameterResolver>> { + + @Override + public Map> resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return Map.of("ids", List.of(1, 42)); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @interface TestAnnotation { + } + + static class Sample { + + void methodWithBasicTypeParameter(@TestAnnotation String string) { + } + + void methodWithObjectParameter(Object nothing) { + } + + void methodWithParameterizedTypeParameter(Map> map) { + } + + void methodWithAnotherParameterizedTypeParameter(Map> nothing) { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java new file mode 100644 index 00000000..0efa28c9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.subpackage; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; + +/** + * Tests which verify that {@link Assertions} can be subclassed. + * + * @since 5.3 + */ +class SubclassedAssertionsTests extends Assertions { + + @Test + void assertTrueWithBooleanTrue() { + assertTrue(true); + assertTrue(true, "test"); + assertTrue(true, () -> "test"); + } + + @Test + void assertFalseWithBooleanTrue() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertFalse(true)); + assertEquals("expected: but was: ", error.getMessage()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java new file mode 100644 index 00000000..0d5cc0bb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.subpackage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; + +/** + * Tests which verify that {@link Assumptions} can be subclassed. + * + * @since 5.3 + */ +class SubclassedAssumptionsTests extends Assumptions { + + @Test + void assumeTrueWithBooleanTrue() { + String foo = null; + try { + assumeTrue(true); + foo = "foo"; + } + finally { + assertEquals("foo", foo); + } + } + + @Test + void assumeFalseWithBooleanTrue() { + TestAbortedException exception = assertThrows(TestAbortedException.class, () -> assumeFalse(true)); + assertEquals("Assumption failed: assumption is not false", exception.getMessage()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java new file mode 100644 index 00000000..69264f90 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.util.Set; + +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; + +/** + * Abstract base class for tests involving the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +public abstract class AbstractJupiterTestEngineTests { + + private final JupiterTestEngine engine = new JupiterTestEngine(); + + protected EngineExecutionResults executeTestsForClass(Class testClass) { + return executeTests(selectClass(testClass)); + } + + protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { + return executeTests(request().selectors(selectors).build()); + } + + protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) { + return EngineTestKit.execute(this.engine, request); + } + + protected TestDescriptor discoverTests(DiscoverySelector... selectors) { + return discoverTests(request().selectors(selectors).build()); + } + + protected TestDescriptor discoverTests(LauncherDiscoveryRequest request) { + return engine.discover(request, UniqueId.forEngine(engine.getId())); + } + + protected UniqueId discoverUniqueId(Class clazz, String methodName) { + TestDescriptor engineDescriptor = discoverTests(selectMethod(clazz, methodName)); + Set descendants = engineDescriptor.getDescendants(); + // @formatter:off + TestDescriptor testDescriptor = descendants.stream() + .skip(descendants.size() - 1) + .findFirst() + .orElseGet(() -> fail("no descendants")); + // @formatter:on + return testDescriptor.getUniqueId(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java new file mode 100644 index 00000000..38bef70a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.engine.kotlin.ArbitraryNamingKotlinTestCase.METHOD_NAME; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.kotlin.ArbitraryNamingKotlinTestCase; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests for JVM languages that allow special characters + * in method names (e.g., Kotlin, Groovy, etc.) which are forbidden in + * Java source code. + * + * @since 5.1 + */ +class AtypicalJvmMethodNameTests extends AbstractJupiterTestEngineTests { + + @Test + void kotlinTestWithMethodNameContainingSpecialCharacters() { + EngineExecutionResults executionResults = executeTestsForClass(ArbitraryNamingKotlinTestCase.class); + assertThat(executionResults.testEvents().started().count()).isEqualTo(2); + + TestDescriptor testDescriptor1 = executionResults.testEvents().succeeded().list().get(0).getTestDescriptor(); + assertAll(// + () -> assertEquals(METHOD_NAME + "()", testDescriptor1.getDisplayName()), // + () -> assertEquals(METHOD_NAME + "()", testDescriptor1.getLegacyReportingName())); + + TestDescriptor testDescriptor2 = executionResults.testEvents().succeeded().list().get(1).getTestDescriptor(); + assertAll(// + () -> assertEquals("test name ends with parentheses()()", testDescriptor2.getDisplayName()), // + () -> assertEquals("test name ends with parentheses()()", testDescriptor2.getLegacyReportingName())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java new file mode 100644 index 00000000..2cbbf64a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Integration tests that verify support for {@link BeforeAll} and {@link AfterAll} + * when used as meta-annotations in the {@link JupiterTestEngine}. + * + * @since 5.0 + * @see BeforeEachAndAfterEachComposedAnnotationTests + */ +class BeforeAllAndAfterAllComposedAnnotationTests extends AbstractJupiterTestEngineTests { + + private static final List methodsInvoked = new ArrayList<>(); + + @Test + void beforeAllAndAfterAllAsMetaAnnotations() { + executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); + + assertThat(methodsInvoked).containsExactly("beforeAll", "test", "afterAll"); + } + + static class TestCase { + + @CustomBeforeAll + static void beforeAll() { + methodsInvoked.add("beforeAll"); + } + + @Test + void test() { + methodsInvoked.add("test"); + } + + @CustomAfterAll + static void afterAll() { + methodsInvoked.add("afterAll"); + } + + } + + @BeforeAll + @Retention(RetentionPolicy.RUNTIME) + private @interface CustomBeforeAll { + } + + @AfterAll + @Retention(RetentionPolicy.RUNTIME) + private @interface CustomAfterAll { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java new file mode 100644 index 00000000..e6c38048 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Integration tests that verify support for {@link BeforeEach} and {@link AfterEach} + * when used as meta-annotations in the {@link JupiterTestEngine}. + * + * @since 5.0 + * @see BeforeAllAndAfterAllComposedAnnotationTests + */ +class BeforeEachAndAfterEachComposedAnnotationTests extends AbstractJupiterTestEngineTests { + + private static final List methodsInvoked = new ArrayList<>(); + + @Test + void beforeEachAndAfterEachAsMetaAnnotations() { + executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); + + assertThat(methodsInvoked).containsExactly("beforeEach", "test", "afterEach"); + } + + static class TestCase { + + @CustomBeforeEach + void beforeEach() { + methodsInvoked.add("beforeEach"); + } + + @Test + void test() { + methodsInvoked.add("test"); + } + + @CustomAfterEach + void afterEach() { + methodsInvoked.add("afterEach"); + } + + } + + @BeforeEach + @Retention(RetentionPolicy.RUNTIME) + private @interface CustomBeforeEach { + } + + @AfterEach + @Retention(RetentionPolicy.RUNTIME) + private @interface CustomAfterEach { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java new file mode 100644 index 00000000..7dd37e1e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java @@ -0,0 +1,186 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; +import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.support.hierarchical.Node; +import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; + +class DefaultExecutionModeTests extends AbstractJupiterTestEngineTests { + + @Test + void defaultExecutionModeIsReadFromConfigurationParameter() { + assertUsesExpectedExecutionMode(null, SAME_THREAD); + assertUsesExpectedExecutionMode(SAME_THREAD, SAME_THREAD); + assertUsesExpectedExecutionMode(CONCURRENT, CONCURRENT); + } + + private void assertUsesExpectedExecutionMode(ExecutionMode defaultExecutionMode, + ExecutionMode expectedExecutionMode) { + var engineDescriptor = discoverTestsWithDefaultExecutionMode(TestCase.class, defaultExecutionMode); + assertExecutionModeRecursively(engineDescriptor, expectedExecutionMode); + } + + @Test + void annotationOverridesDefaultExecutionModeToConcurrentForAllDescendants() { + assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, null, CONCURRENT); + assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, SAME_THREAD, CONCURRENT); + assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, CONCURRENT, CONCURRENT); + } + + @Test + void annotationOverridesDefaultExecutionModeToSameThreadForAllDescendants() { + assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, null, SAME_THREAD); + assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, SAME_THREAD, + SAME_THREAD); + assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, CONCURRENT, SAME_THREAD); + } + + private void assertUsesExpectedExecutionModeForTestClassAndItsDescendants(Class testClass, + ExecutionMode defaultExecutionMode, ExecutionMode expectedExecutionMode) { + var engineDescriptor = discoverTestsWithDefaultExecutionMode(testClass, defaultExecutionMode); + engineDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, expectedExecutionMode)); + } + + private void assertExecutionModeRecursively(TestDescriptor testDescriptor, ExecutionMode expectedExecutionMode) { + assertExecutionMode(testDescriptor, expectedExecutionMode); + testDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, expectedExecutionMode)); + } + + @Test + void methodsInTestClassesWithInstancePerClassHaveExecutionModeSameThread() { + var engineDescriptor = discoverTestsWithDefaultExecutionMode(SimpleTestInstancePerClassTestCase.class, + CONCURRENT); + var classDescriptor = getOnlyElement(engineDescriptor.getChildren()); + classDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, SAME_THREAD)); + } + + @Test + void methodsInNestedTestClassesWithInstancePerClassInHierarchyHaveExecutionModeSameThread() { + var engineDescriptor = discoverTestsWithDefaultExecutionMode(OuterTestCase.class, CONCURRENT); + var outerTestCaseClassDescriptor = firstChild(engineDescriptor, ClassTestDescriptor.class); + var outerTestMethodDescriptor = firstChild(outerTestCaseClassDescriptor, TestMethodTestDescriptor.class); + var level1NestedClassDescriptor = firstChild(outerTestCaseClassDescriptor, NestedClassTestDescriptor.class); + var level1TestMethodDescriptor = firstChild(level1NestedClassDescriptor, TestMethodTestDescriptor.class); + var level2NestedClassDescriptor = firstChild(level1NestedClassDescriptor, NestedClassTestDescriptor.class); + var level2TestMethodDescriptor = firstChild(level2NestedClassDescriptor, TestMethodTestDescriptor.class); + var level3NestedClassDescriptor = firstChild(level2NestedClassDescriptor, NestedClassTestDescriptor.class); + var level3TestMethodDescriptor = firstChild(level3NestedClassDescriptor, TestMethodTestDescriptor.class); + + assertExecutionMode(outerTestCaseClassDescriptor, CONCURRENT); + assertExecutionMode(outerTestMethodDescriptor, CONCURRENT); + assertExecutionMode(level1NestedClassDescriptor, CONCURRENT); + assertExecutionMode(level1TestMethodDescriptor, CONCURRENT); + assertExecutionMode(level2NestedClassDescriptor, CONCURRENT); + assertExecutionMode(level2TestMethodDescriptor, SAME_THREAD); + assertExecutionMode(level3NestedClassDescriptor, SAME_THREAD); + assertExecutionMode(level3TestMethodDescriptor, SAME_THREAD); + } + + private JupiterEngineDescriptor discoverTestsWithDefaultExecutionMode(Class testClass, + ExecutionMode executionMode) { + LauncherDiscoveryRequestBuilder request = request().selectors(selectClass(testClass)); + if (executionMode != null) { + request.configurationParameter(Constants.DEFAULT_PARALLEL_EXECUTION_MODE, executionMode.name()); + } + return (JupiterEngineDescriptor) discoverTests(request.build()); + } + + private static void assertExecutionMode(TestDescriptor testDescriptor, ExecutionMode expectedExecutionMode) { + assertThat(((Node) testDescriptor).getExecutionMode()) // + .describedAs("ExecutionMode for %s", testDescriptor) // + .isEqualTo(expectedExecutionMode); + } + + @SuppressWarnings("unchecked") + private T firstChild(TestDescriptor engineDescriptor, Class testDescriptorClass) { + return (T) engineDescriptor.getChildren().stream() // + .filter(testDescriptorClass::isInstance) // + .findFirst() // + .orElseGet(() -> fail("No child of type " + testDescriptorClass + " found")); + } + + static class TestCase { + + @Test + void test() { + } + + @Nested + class NestedTestCase { + + @Test + void test() { + } + + } + + } + + @TestInstance(PER_CLASS) + static class SimpleTestInstancePerClassTestCase extends TestCase { + } + + @Execution(org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT) + static class ConcurrentTestCase extends TestCase { + } + + @Execution(org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD) + static class SameThreadTestCase extends TestCase { + } + + static class OuterTestCase { + @Nested + class LevelOne { + @Nested + @TestInstance(PER_CLASS) + class LevelTwo { + @Nested + class LevelThree { + @Test + void test() { + } + } + + @Test + void test() { + } + } + + @Test + void test() { + } + } + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java new file mode 100644 index 00000000..45d86c7c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java @@ -0,0 +1,230 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.engine.execution.injection.sample.DoubleParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests that verify support for selecting and executing default + * methods from interfaces in conjunction with the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class DefaultMethodTests extends AbstractJupiterTestEngineTests { + + private static boolean beforeAllInvoked; + private static boolean afterAllInvoked; + private static boolean defaultMethodInvoked; + private static boolean overriddenDefaultMethodInvoked; + private static boolean localMethodInvoked; + + @BeforeEach + void resetFlags() { + beforeAllInvoked = false; + afterAllInvoked = false; + defaultMethodInvoked = false; + overriddenDefaultMethodInvoked = false; + localMethodInvoked = false; + } + + @Test + void executeTestCaseWithDefaultMethodFromInterfaceSelectedByFullyQualifedMethodName() { + String fqmn = TestCaseWithDefaultMethod.class.getName() + "#test"; + LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); + EngineExecutionResults executionResults = executeTests(request); + + // @formatter:off + assertAll( + () -> assertTrue(beforeAllInvoked, "@BeforeAll static method invoked from interface"), + () -> assertTrue(afterAllInvoked, "@AfterAll static method invoked from interface"), + () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), + () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), + () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), + () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") + ); + // @formatter:on + } + + @Test + void executeTestCaseWithDefaultMethodFromGenericInterfaceSelectedByFullyQualifedMethodName() { + String fqmn = GenericTestCaseWithDefaultMethod.class.getName() + "#test(" + Long.class.getName() + ")"; + LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); + EngineExecutionResults executionResults = executeTests(request); + + // @formatter:off + assertAll( + () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), + () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), + () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), + () -> assertFalse(localMethodInvoked, "local @Test method should not have been invoked from class"), + () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), + () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), + () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") + ); + // @formatter:on + } + + @Test + void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByFullyQualifedMethodName() { + + String fqmn = GenericTestCaseWithDefaultMethod.class.getName() + "#test(" + Double.class.getName() + ")"; + LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); + EngineExecutionResults executionResults = executeTests(request); + + // @formatter:off + assertAll( + () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), + () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), + () -> assertFalse(defaultMethodInvoked, "default @Test method should not have been invoked from interface"), + () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), + () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), + () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), + () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") + ); + // @formatter:on + } + + @Test + void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByClass() { + Class clazz = GenericTestCaseWithDefaultMethod.class; + LauncherDiscoveryRequest request = request().selectors(selectClass(clazz)).build(); + EngineExecutionResults executionResults = executeTests(request); + + // @formatter:off + assertAll( + () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), + () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), + () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), + () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), + () -> assertEquals(2, executionResults.testEvents().started().count(), "# tests started"), + () -> assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"), + () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") + ); + // @formatter:on + } + + @Test + void executeTestCaseWithOverriddenGenericDefaultMethodSelectedByClass() { + Class clazz = GenericTestCaseWithOverriddenDefaultMethod.class; + LauncherDiscoveryRequest request = request().selectors(selectClass(clazz)).build(); + EngineExecutionResults executionResults = executeTests(request); + + // @formatter:off + assertAll( + () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), + () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), + () -> assertFalse(defaultMethodInvoked, "default @Test method should not have been invoked from interface"), + () -> assertTrue(overriddenDefaultMethodInvoked, "overridden default @Test method invoked from interface"), + () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), + // If defaultMethodInvoked is false and the following ends up being + // 3 instead of 2, that means that the overriding method gets invoked + // twice: once as itself and a second time "as" the default method which + // should not have been "discovered" since it is overridden. + () -> assertEquals(2, executionResults.testEvents().started().count(), "# tests started"), + () -> assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"), + () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") + ); + // @formatter:on + } + + // ------------------------------------------------------------------------- + + interface TestInterface { + + @BeforeAll + static void beforeAll() { + beforeAllInvoked = true; + } + + @Test + default void test() { + defaultMethodInvoked = true; + } + + @AfterAll + static void afterAll() { + afterAllInvoked = true; + } + + } + + static class TestCaseWithDefaultMethod implements TestInterface { + } + + @ExtendWith({ LongParameterResolver.class, DoubleParameterResolver.class }) + @TestInstance(Lifecycle.PER_CLASS) + interface GenericTestInterface { + + @BeforeAll + default void beforeAll() { + beforeAllInvoked = true; + } + + @Test + default void test(N number) { + defaultMethodInvoked = true; + assertThat(number.intValue()).isEqualTo(42); + } + + @AfterAll + default void afterAll() { + afterAllInvoked = true; + } + + } + + static class GenericTestCaseWithDefaultMethod implements GenericTestInterface { + + @Test + void test(Double number) { + localMethodInvoked = true; + assertThat(number).isEqualTo(42.0); + } + + } + + static class GenericTestCaseWithOverriddenDefaultMethod implements GenericTestInterface { + + @Test + @Override + public void test(Long number) { + overriddenDefaultMethodInvoked = true; + assertThat(number.intValue()).isEqualTo(42); + } + + @Test + void test(Double number) { + localMethodInvoked = true; + assertThat(number).isEqualTo(42.0); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java new file mode 100644 index 00000000..63f43367 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.test; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests that verify support for {@link Disabled @Disabled} in the + * {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class DisabledTests extends AbstractJupiterTestEngineTests { + + @Test + void executeTestsWithDisabledTestClass() { + EngineExecutionResults results = executeTestsForClass(DisabledTestClassTestCase.class); + + results.containerEvents().assertStatistics(stats -> stats.skipped(1)); + results.testEvents().assertStatistics(stats -> stats.started(0)); + } + + @Test + void executeTestsWithDisabledTestMethods() throws Exception { + String methodName = "disabledTest"; + Method method = DisabledTestMethodsTestCase.class.getDeclaredMethod(methodName); + + executeTestsForClass(DisabledTestMethodsTestCase.class).testEvents()// + .assertStatistics(stats -> stats.skipped(1).started(1).finished(1).aborted(0).succeeded(1).failed(0))// + .skipped().assertEventsMatchExactly( + event(test(methodName), skippedWithReason(method + " is @Disabled"))); + } + + // ------------------------------------------------------------------- + + @Disabled + static class DisabledTestClassTestCase { + + @Test + void disabledTest() { + fail("this should be @Disabled"); + } + } + + static class DisabledTestMethodsTestCase { + + @Test + void enabledTest() { + } + + @Test + @Disabled + void disabledTest() { + fail("this should be @Disabled"); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java new file mode 100644 index 00000000..87dcf40c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java @@ -0,0 +1,533 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE; +import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Event; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests for {@link TestFactory @TestFactory}, {@link DynamicTest}, + * and {@link org.junit.jupiter.api.DynamicContainer}. + * + * @since 5.0 + */ +class DynamicNodeGenerationTests extends AbstractJupiterTestEngineTests { + + @Test + void testFactoryMethodsAreCorrectlyDiscoveredForClassSelector() { + LauncherDiscoveryRequest request = request().selectors(selectClass(MyDynamicTestCase.class)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(13); + } + + @Test + void testFactoryMethodIsCorrectlyDiscoveredForMethodSelector() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyDynamicTestCase.class, "dynamicStream")).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(2); + } + + @Test + void dynamicTestsAreExecutedFromStream() { + EngineExecutionResults executionResults = executeTests(selectMethod(MyDynamicTestCase.class, "dynamicStream")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("dynamicStream"), started()), // + event(dynamicTestRegistered("dynamic-test:#1")), // + event(test("dynamic-test:#1", "succeedingTest"), started()), // + event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container("dynamicStream"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void dynamicTestsAreExecutedFromCollection() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "dynamicCollection")); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertAll( // + () -> assertEquals(3, containers.started().count(), "# container started"), + () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), + () -> assertEquals(2, tests.started().count(), "# tests started"), + () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), + () -> assertEquals(1, tests.failed().count(), "# tests failed"), + () -> assertEquals(3, containers.finished().count(), "# container finished")); + } + + @Test + void dynamicTestsAreExecutedFromIterator() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "dynamicIterator")); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertAll( // + () -> assertEquals(3, containers.started().count(), "# container started"), + () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), + () -> assertEquals(2, tests.started().count(), "# tests started"), + () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), + () -> assertEquals(1, tests.failed().count(), "# tests failed"), + () -> assertEquals(3, containers.finished().count(), "# container finished")); + } + + @Test + void dynamicTestsAreExecutedFromIterable() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "dynamicIterable")); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + // @TestFactory methods are counted as both container and test + assertAll( // + () -> assertEquals(3, containers.started().count(), "# container started"), + () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), + () -> assertEquals(2, tests.started().count(), "# tests started"), + () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), + () -> assertEquals(1, tests.failed().count(), "# tests failed"), + () -> assertEquals(3, containers.finished().count(), "# container finished")); + } + + @Test + void singleDynamicTestIsExecutedWhenDiscoveredByUniqueId() { + UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "dynamicStream") // + .append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); + + EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("dynamicStream"), started()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container("dynamicStream"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void singleDynamicTestIsExecutedWhenDiscoveredByIterationIndex() { + var methodSelector = selectMethod(MyDynamicTestCase.class, "dynamicStream"); + + EngineExecutionResults executionResults = executeTests(selectIteration(methodSelector, 1)); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("dynamicStream"), started()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container("dynamicStream"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void multipleDynamicTestsAreExecutedWhenDiscoveredByIterationIndexAndUniqueId() { + UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "threeTests") // + .append(DYNAMIC_TEST_SEGMENT_TYPE, "#3"); + + var methodSelector = selectMethod(MyDynamicTestCase.class, "threeTests"); + + EngineExecutionResults executionResults = executeTests(selectIteration(methodSelector, 1), + selectUniqueId(uniqueId)); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("threeTests"), started()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "two"), started()), // + event(test("dynamic-test:#2", "two"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamic-test:#3")), // + event(test("dynamic-test:#3", "three"), started()), // + event(test("dynamic-test:#3", "three"), finishedSuccessfully()), // + event(container("threeTests"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void dynamicContainersAreExecutedFromIterable() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "dynamicContainerWithIterable")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("dynamicContainerWithIterable"), started()), // + event(dynamicTestRegistered("dynamic-container:#1")), // + event(container("dynamic-container:#1"), started()), // + event(dynamicTestRegistered("dynamic-test:#1")), // + event(test("dynamic-test:#1", "succeedingTest"), started()), // + event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container("dynamic-container:#1"), finishedSuccessfully()), // + event(container("dynamicContainerWithIterable"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertAll( // + () -> assertEquals(4, containers.started().count(), "# container started"), + () -> assertEquals(1, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), + () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), + () -> assertEquals(2, tests.started().count(), "# tests started"), + () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), + () -> assertEquals(1, tests.failed().count(), "# tests failed"), + () -> assertEquals(4, containers.finished().count(), "# container finished")); + } + + @Test + void singleDynamicTestInNestedDynamicContainerIsExecutedWhenDiscoveredByUniqueId() { + UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "twoNestedContainersWithTwoTestsEach") // + .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1") // + .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1") // + .append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); + + EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("twoNestedContainersWithTwoTestsEach"), started()), // + event(dynamicTestRegistered(displayName("a"))), // + event(container(displayName("a")), started()), // + event(dynamicTestRegistered(displayName("a1"))), // + event(container(displayName("a1")), started()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container(displayName("a1")), finishedSuccessfully()), // + event(container(displayName("a")), finishedSuccessfully()), // + event(container("twoNestedContainersWithTwoTestsEach"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void allDynamicTestInNestedDynamicContainerAreExecutedWhenContainerIsDiscoveredByUniqueId() { + UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "twoNestedContainersWithTwoTestsEach") // + .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#2") // + .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1"); + + EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("twoNestedContainersWithTwoTestsEach"), started()), // + event(dynamicTestRegistered(displayName("b"))), // + event(container(displayName("b")), started()), // + event(dynamicTestRegistered(displayName("b1"))), // + event(container(displayName("b1")), started()), // + event(dynamicTestRegistered("dynamic-test:#1")), // + event(test("dynamic-test:#1", "succeedingTest"), started()), // + event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container(displayName("b1")), finishedSuccessfully()), // + event(container(displayName("b")), finishedSuccessfully()), // + event(container("twoNestedContainersWithTwoTestsEach"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void nestedDynamicContainersAreExecuted() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "nestedDynamicContainers")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("nestedDynamicContainers"), started()), // + event(dynamicTestRegistered(displayName("gift wrap"))), // + event(container(displayName("gift wrap")), started()), // + event(dynamicTestRegistered(displayName("box"))), // + event(container(displayName("box")), started()), // + event(dynamicTestRegistered("dynamic-test:#1")), // + event(test("dynamic-test:#1", "succeedingTest"), started()), // + event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container(displayName("box")), finishedSuccessfully()), // + event(container(displayName("gift wrap")), finishedSuccessfully()), // + event(container("nestedDynamicContainers"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertAll( // + () -> assertEquals(5, containers.started().count(), "# container started"), + () -> assertEquals(2, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), + () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), + () -> assertEquals(2, tests.started().count(), "# tests started"), + () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), + () -> assertEquals(1, tests.failed().count(), "# tests failed"), + () -> assertEquals(5, containers.finished().count(), "# container finished")); + } + + @Test + void legacyReportingNames() { + Events dynamicRegistrations = executeTests(selectMethod(MyDynamicTestCase.class, "nestedDynamicContainers"))// + .allEvents().dynamicallyRegistered(); + + // @formatter:off + Stream legacyReportingNames = dynamicRegistrations + .map(Event::getTestDescriptor) + .map(TestDescriptor::getLegacyReportingName); + assertThat(legacyReportingNames) + .containsExactly("nestedDynamicContainers()[1]", "nestedDynamicContainers()[1][1]", + "nestedDynamicContainers()[1][1][1]", "nestedDynamicContainers()[1][1][2]"); + // @formatter:on + } + + @Test + void dynamicContainersAreExecutedFromExceptionThrowingStream() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "dynamicContainerWithExceptionThrowingStream")); + + assertTrue(MyDynamicTestCase.exceptionThrowingStreamClosed.get(), "stream should be closed"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("dynamicContainerWithExceptionThrowingStream"), started()), // + event(dynamicTestRegistered("dynamic-container:#1")), // + event(container("dynamic-container:#1"), started()), // + event(dynamicTestRegistered("dynamic-test:#1")), // + event(test("dynamic-test:#1", "succeedingTest"), started()), // + event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container("dynamic-container:#1"), + finishedWithFailure(instanceOf(ArrayIndexOutOfBoundsException.class))), // + event(container("dynamicContainerWithExceptionThrowingStream"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertAll( // + () -> assertEquals(4, containers.started().count(), "# container started"), + () -> assertEquals(1, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), + () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), + () -> assertEquals(2, tests.started().count(), "# tests started"), + () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), + () -> assertEquals(1, tests.failed().count(), "# tests failed"), + () -> assertEquals(4, containers.finished().count(), "# container finished")); + } + + @Test + void dynamicContainersChildrenMustNotBeNull() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "dynamicContainerWithNullChildren")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("dynamicContainerWithNullChildren"), started()), // + event(dynamicTestRegistered("dynamic-container:#1")), // + event(container("dynamic-container:#1"), started()), // + event(container("dynamic-container:#1"), // + finishedWithFailure(message("individual dynamic node must not be null"))), // + event(container("dynamicContainerWithNullChildren"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void testFactoryMethodsMayReturnSingleDynamicContainer() { + EngineExecutionResults executionResults = executeTests( + selectMethod(MyDynamicTestCase.class, "singleContainer")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("singleContainer"), started()), // + event(dynamicTestRegistered("dynamic-container:#1")), // + event(container("dynamic-container:#1"), started()), // + event(dynamicTestRegistered("dynamic-test:#1")), // + event(test("dynamic-test:#1", "succeedingTest"), started()), // + event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamic-test:#2")), // + event(test("dynamic-test:#2", "failingTest"), started()), // + event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // + event(container("dynamic-container:#1"), finishedSuccessfully()), // + event(container("singleContainer"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void testFactoryMethodsMayReturnSingleDynamicTest() { + EngineExecutionResults executionResults = executeTests(selectMethod(MyDynamicTestCase.class, "singleTest")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MyDynamicTestCase.class), started()), // + event(container("singleTest"), started()), // + event(dynamicTestRegistered("dynamic-test:#1")), // + event(test("dynamic-test:#1", "succeedingTest"), started()), // + event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // + event(container("singleTest"), finishedSuccessfully()), // + event(container(MyDynamicTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + static class MyDynamicTestCase { + + private static final List list = Arrays.asList( + dynamicTest("succeedingTest", () -> assertTrue(true, "succeeding")), + dynamicTest("failingTest", () -> fail("failing"))); + + private static final AtomicBoolean exceptionThrowingStreamClosed = new AtomicBoolean(false); + + @TestFactory + Collection dynamicCollection() { + return list; + } + + @TestFactory + Stream dynamicStream() { + return list.stream(); + } + + @TestFactory + Iterator dynamicIterator() { + return list.iterator(); + } + + @TestFactory + Iterable dynamicIterable() { + return this::dynamicIterator; + } + + @TestFactory + Iterable dynamicContainerWithIterable() { + return singleton(dynamicContainer("box", list)); + } + + @TestFactory + Iterable nestedDynamicContainers() { + return singleton(dynamicContainer("gift wrap", singleton(dynamicContainer("box", list)))); + } + + @TestFactory + Stream twoNestedContainersWithTwoTestsEach() { + return Stream.of( // + dynamicContainer("a", singleton(dynamicContainer("a1", list))), // + dynamicContainer("b", singleton(dynamicContainer("b1", list))) // + ); + } + + @TestFactory + Iterable dynamicContainerWithExceptionThrowingStream() { + // @formatter:off + return singleton(dynamicContainer("box", + IntStream.rangeClosed(0, 100) + .mapToObj(list::get) + .onClose(() -> exceptionThrowingStreamClosed.set(true)))); + // @formatter:on + } + + @TestFactory + Iterable dynamicContainerWithNullChildren() { + return singleton(dynamicContainer("box", singleton(null))); + } + + @TestFactory + DynamicNode singleContainer() { + return dynamicContainer("box", list); + } + + @TestFactory + DynamicNode singleTest() { + return dynamicTest("succeedingTest", () -> assertTrue(true)); + } + + @TestFactory + Stream threeTests() { + return Stream.of( // + dynamicTest("one", () -> assertTrue(true)), // + dynamicTest("two", () -> assertTrue(true)), // + dynamicTest("three", () -> assertTrue(true)) // + ); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java new file mode 100644 index 00000000..b1662d65 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java @@ -0,0 +1,359 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; + +import java.io.IOException; +import java.util.Optional; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; +import org.opentest4j.AssertionFailedError; +import org.opentest4j.TestAbortedException; + +/** + * Integration tests that verify correct exception handling in the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class ExceptionHandlingTests extends AbstractJupiterTestEngineTests { + + @Test + void failureInTestMethodIsRegistered() { + EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "failingTest")); + Events tests = executionResults.testEvents(); + + tests.assertStatistics(stats -> stats.started(1).failed(1)); + + tests.failed().assertEventsMatchExactly( // + event(test("failingTest"), + finishedWithFailure(instanceOf(AssertionFailedError.class), message("always fails")))); + } + + @Test + void uncheckedExceptionInTestMethodIsRegistered() { + EngineExecutionResults executionResults = executeTests( + selectMethod(FailureTestCase.class, "testWithUncheckedException")); + Events tests = executionResults.testEvents(); + + tests.assertStatistics(stats -> stats.started(1).failed(1)); + + tests.failed().assertEventsMatchExactly( // + event(test("testWithUncheckedException"), + finishedWithFailure(instanceOf(RuntimeException.class), message("unchecked")))); + } + + @Test + void checkedExceptionInTestMethodIsRegistered() { + EngineExecutionResults executionResults = executeTests( + selectMethod(FailureTestCase.class, "testWithCheckedException")); + Events tests = executionResults.testEvents(); + + tests.assertStatistics(stats -> stats.started(1).failed(1)); + + tests.failed().assertEventsMatchExactly( // + event(test("testWithCheckedException"), + finishedWithFailure(instanceOf(IOException.class), message("checked")))); + } + + @Test + void checkedExceptionInBeforeEachIsRegistered() { + FailureTestCase.exceptionToThrowInBeforeEach = Optional.of(new IOException("checked")); + + EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); + Events tests = executionResults.testEvents(); + + tests.assertStatistics(stats -> stats.started(1).failed(1)); + + tests.failed().assertEventsMatchExactly( + event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked")))); + } + + @Test + void checkedExceptionInAfterEachIsRegistered() { + FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); + + EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); + Events tests = executionResults.testEvents(); + + tests.assertStatistics(stats -> stats.started(1).failed(1)); + + tests.failed().assertEventsMatchExactly( + event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked")))); + } + + @Test + void checkedExceptionInAfterEachIsSuppressedByExceptionInTest() { + Class testClass = FailureTestCase.class; + + FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); + + EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "testWithUncheckedException")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("testWithUncheckedException"), started()), // + event(test("testWithUncheckedException"), // + finishedWithFailure( // + instanceOf(RuntimeException.class), // + message("unchecked"), // + suppressed(0, instanceOf(IOException.class), message("checked")))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void exceptionInAfterEachTakesPrecedenceOverFailedAssumptionInTest() { + FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); + + EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "abortedTest")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(FailureTestCase.class), started()), // + event(test("abortedTest"), started()), // + event(test("abortedTest"), // + finishedWithFailure(instanceOf(IOException.class), message("checked"), // + suppressed(0, instanceOf(TestAbortedException.class)))), // + event(container(FailureTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void checkedExceptionInBeforeAllIsRegistered() { + Class testClass = FailureTestCase.class; + + FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new IOException("checked")); + + EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void checkedExceptionInAfterAllIsRegistered() { + Class testClass = FailureTestCase.class; + + FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked")); + + EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("succeedingTest"), started()), // + event(test("succeedingTest"), finishedSuccessfully()), // + event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void exceptionInAfterAllCallbackDoesNotHideExceptionInBeforeAllCallback() { + Class testClass = TestCaseWithThrowingBeforeAllAndAfterAllCallbacks.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), finishedWithFailure( // + message("beforeAll callback"), // + suppressed(0, message("afterAll callback")))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void exceptionsInConstructorAndAfterAllCallbackAreReportedWhenTestInstancePerMethodIsUsed() { + Class testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("test"), started()), // + event(test("test"), finishedWithFailure(message("constructor"))), // + event(container(testClass), finishedWithFailure(message("afterAll callback"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void exceptionInConstructorPreventsExecutionOfAfterAllCallbacksWhenTestInstancePerClassIsUsed() { + Class testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), finishedWithFailure(message("constructor"))), + event(engine(), finishedSuccessfully())); + } + + @Test + void failureInAfterAllTakesPrecedenceOverTestAbortedExceptionInBeforeAll() { + FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new TestAbortedException("aborted")); + FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked")); + + EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(FailureTestCase.class), started()), // + event(container(FailureTestCase.class), + finishedWithFailure(instanceOf(IOException.class), message("checked"), + suppressed(0, instanceOf(TestAbortedException.class), message("aborted")))), // + event(engine(), finishedSuccessfully())); + } + + @AfterEach + void cleanUpExceptions() { + FailureTestCase.exceptionToThrowInBeforeAll = Optional.empty(); + FailureTestCase.exceptionToThrowInAfterAll = Optional.empty(); + FailureTestCase.exceptionToThrowInBeforeEach = Optional.empty(); + FailureTestCase.exceptionToThrowInAfterEach = Optional.empty(); + } + + // ------------------------------------------------------------------------- + + static class FailureTestCase { + + static Optional exceptionToThrowInBeforeAll = Optional.empty(); + static Optional exceptionToThrowInAfterAll = Optional.empty(); + static Optional exceptionToThrowInBeforeEach = Optional.empty(); + static Optional exceptionToThrowInAfterEach = Optional.empty(); + + @BeforeAll + static void beforeAll() throws Throwable { + if (exceptionToThrowInBeforeAll.isPresent()) { + throw exceptionToThrowInBeforeAll.get(); + } + } + + @AfterAll + static void afterAll() throws Throwable { + if (exceptionToThrowInAfterAll.isPresent()) { + throw exceptionToThrowInAfterAll.get(); + } + } + + @BeforeEach + void beforeEach() throws Throwable { + if (exceptionToThrowInBeforeEach.isPresent()) { + throw exceptionToThrowInBeforeEach.get(); + } + } + + @AfterEach + void afterEach() throws Throwable { + if (exceptionToThrowInAfterEach.isPresent()) { + throw exceptionToThrowInAfterEach.get(); + } + } + + @Test + void succeedingTest() { + } + + @Test + void failingTest() { + Assertions.fail("always fails"); + } + + @Test + void testWithUncheckedException() { + throw new RuntimeException("unchecked"); + } + + @Test + void testWithCheckedException() throws IOException { + throw new IOException("checked"); + } + + @Test + void abortedTest() { + assumeFalse(true, "abortedTest"); + } + + } + + @TestInstance(PER_METHOD) + @ExtendWith(ThrowingAfterAllCallback.class) + static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle { + TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle() { + throw new IllegalStateException("constructor"); + } + + @Test + void test() { + } + + } + + @TestInstance(PER_CLASS) + @ExtendWith(ThrowingAfterAllCallback.class) + static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle { + TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle() { + throw new IllegalStateException("constructor"); + } + + @Test + void test() { + } + } + + @ExtendWith(ThrowingBeforeAllCallback.class) + @ExtendWith(ThrowingAfterAllCallback.class) + static class TestCaseWithThrowingBeforeAllAndAfterAllCallbacks { + @Test + void test() { + } + } + + static class ThrowingBeforeAllCallback implements BeforeAllCallback { + @Override + public void beforeAll(ExtensionContext context) { + throw new IllegalStateException("beforeAll callback"); + } + } + + static class ThrowingAfterAllCallback implements AfterAllCallback { + @Override + public void afterAll(ExtensionContext context) { + throw new IllegalStateException("afterAll callback"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java new file mode 100644 index 00000000..726eb885 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import org.junit.Assume; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests that verify support for failed assumptions in the + * {@link JupiterTestEngine}. + * + * @since 5.4 + */ +class FailedAssumptionsTests extends AbstractJupiterTestEngineTests { + + @Test + void testAbortedExceptionInBeforeAll() { + EngineExecutionResults results = executeTestsForClass(TestAbortedExceptionInBeforeAllTestCase.class); + + results.containerEvents().assertStatistics(stats -> stats.aborted(1)); + results.testEvents().assertStatistics(stats -> stats.started(0)); + } + + @Test + void assumptionViolatedExceptionInBeforeAll() { + EngineExecutionResults results = executeTestsForClass(AssumptionViolatedExceptionInBeforeAllTestCase.class); + + results.containerEvents().assertStatistics(stats -> stats.aborted(1)); + results.testEvents().assertStatistics(stats -> stats.started(0)); + } + + // ------------------------------------------------------------------- + + static class TestAbortedExceptionInBeforeAllTestCase { + + @BeforeAll + static void beforeAll() { + Assumptions.assumeTrue(false); + } + + @Test + void test() { + } + } + + static class AssumptionViolatedExceptionInBeforeAllTestCase { + + @BeforeAll + static void beforeAll() { + Assume.assumeTrue(false); + } + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java new file mode 100644 index 00000000..37dc1649 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests that verify proper handling of invalid configuration for + * lifecycle methods in conjunction with the {@link JupiterTestEngine}. + * + *

In general, configuration errors should not be thrown until the + * execution phase, thereby giving all containers a chance to execute. + * + * @since 5.0 + */ +class InvalidLifecycleMethodConfigurationTests extends AbstractJupiterTestEngineTests { + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticBeforeAllDeclaration() { + assertContainerFailed(TestCaseWithInvalidNonStaticBeforeAllMethod.class); + } + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticAfterAllDeclaration() { + assertContainerFailed(TestCaseWithInvalidNonStaticAfterAllMethod.class); + } + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidStaticBeforeEachDeclaration() { + assertContainerFailed(TestCaseWithInvalidStaticBeforeEachMethod.class); + } + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidStaticAfterEachDeclaration() { + assertContainerFailed(TestCaseWithInvalidStaticAfterEachMethod.class); + } + + private void assertContainerFailed(Class invalidTestClass) { + EngineExecutionResults executionResults = executeTests(selectClass(TestCase.class), + selectClass(invalidTestClass)); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + // @formatter:off + assertAll( + () -> assertEquals(3, containers.started().count(), "# containers started"), + () -> assertEquals(1, tests.started().count(), "# tests started"), + () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), + () -> assertEquals(0, tests.failed().count(), "# tests failed"), + () -> assertEquals(3, containers.finished().count(), "# containers finished"), + () -> assertEquals(1, containers.failed().count(), "# containers failed") + ); + // @formatter:on + } + + // ------------------------------------------------------------------------- + + static class TestCase { + + @Test + void test() { + } + } + + static class TestCaseWithInvalidNonStaticBeforeAllMethod { + + // must be static + @BeforeAll + void beforeAll() { + } + + @Test + void test() { + } + } + + static class TestCaseWithInvalidNonStaticAfterAllMethod { + + // must be static + @AfterAll + void afterAll() { + } + + @Test + void test() { + } + } + + static class TestCaseWithInvalidStaticBeforeEachMethod { + + // must NOT be static + @BeforeEach + static void beforeEach() { + } + + @Test + void test() { + } + } + + static class TestCaseWithInvalidStaticAfterEachMethod { + + // must NOT be static + @AfterEach + static void afterEach() { + } + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java new file mode 100644 index 00000000..5358e434 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Basic assertions regarding {@link org.junit.platform.engine.TestEngine} + * functionality in JUnit Jupiter. + * + * @since 5.0 + */ +class JupiterTestEngineBasicTests { + + private final JupiterTestEngine jupiter = new JupiterTestEngine(); + + @Test + void id() { + assertEquals("junit-jupiter", jupiter.getId()); + } + + @Test + void groupId() { + assertEquals("org.junit.jupiter", jupiter.getGroupId().orElseThrow()); + } + + @Test + void artifactId() { + assertEquals("junit-jupiter-engine", jupiter.getArtifactId().orElseThrow()); + } + + @Test + void version() { + assertThat(jupiter.getVersion().orElseThrow()).isIn( // + System.getProperty("developmentVersion"), // with Test Distribution + "DEVELOPMENT" // without Test Distribution + ); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java new file mode 100644 index 00000000..f2a584fe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java @@ -0,0 +1,194 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.subpackage.SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase; + +/** + * Integration tests that explicitly demonstrate the overriding and superseding + * rules for lifecycle methods in the {@link JupiterTestEngine}. + * + * @since 5.9 + */ +class LifecycleMethodOverridingAndSupersedingTests { + + @Nested + @DisplayName("A package-private lifecycle super-method can be overridden by") + class PackagePrivateSuperClassTests { + + @Nested + @DisplayName("a protected lifecycle method in the derived class") + class ProtectedExtendsPackagePrivateLifecycleMethod + extends SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @Override + @BeforeEach + protected void beforeEach() { + } + + } + + @Nested + @DisplayName("a package-private lifecycle method in the derived class") + class PackagePrivateExtendsPackagePrivateLifecycleMethod + extends SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @Override + @BeforeEach + void beforeEach() { + } + + } + + @Nested + @DisplayName("a public lifecycle method in the derived class") + class PublicExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @Override + @BeforeEach + public void beforeEach() { + } + + } + } + + @Nested + @DisplayName("A package-private lifecycle super-method from a different package can be superseded by") + class PackagePrivateSuperClassInDifferentPackageTests { + + @Nested + @DisplayName("a protected lifecycle method in the derived class") + class ProtectedExtendsPackagePrivateLifecycleMethod + extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { + + // @Override + @BeforeEach + protected void beforeEach() { + } + + } + + @Nested + @DisplayName("a package-private lifecycle method in the derived class") + class PackagePrivateExtendsPackagePrivateLifecycleMethod + extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { + + // @Override + @BeforeEach + void beforeEach() { + } + + } + + @Nested + @DisplayName("a public lifecycle method in the derived class") + class PublicExtendsPackagePrivateLifecycleMethod + extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { + + // @Override + @BeforeEach + public void beforeEach() { + } + + } + } + + @Nested + @DisplayName("A protected lifecycle super-method can be overridden by") + class ProtectedSuperClassTests { + + @Nested + @DisplayName("a protected lifecycle method in the derived class") + class ProtectedExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { + + @Override + @BeforeEach + protected void beforeEach() { + } + + } + + @Nested + @DisplayName("a public lifecycle method in the derived class") + class PublicExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { + + @Override + @BeforeEach + public void beforeEach() { + } + + } + } + + @Nested + @DisplayName("A public lifecycle super-method can be overridden by") + class PublicSuperClassTests { + + @Nested + @DisplayName("a public lifecycle method in the derived class") + class PublicExtendsPackagePrivate extends SuperClassWithPublicLifecycleMethodTestCase { + + @Override + @BeforeEach + public void beforeEach() { + } + + } + } + +} + +// ------------------------------------------------------------------------- + +class SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @BeforeEach + void beforeEach() { + fail(); + } + + @Test + void test() { + } + +} + +class SuperClassWithProtectedLifecycleMethodTestCase { + + @BeforeEach + protected void beforeEach() { + fail(); + } + + @Test + void test() { + } + +} + +class SuperClassWithPublicLifecycleMethodTestCase { + + @BeforeEach + public void beforeEach() { + fail(); + } + + @Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java new file mode 100644 index 00000000..f8ded865 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.RepetitionInfo; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; + +/** + * Integration tests that verify the correct behavior for methods annotated + * with multiple testable annotations simultaneously. + * + * @since 5.0 + */ +@TrackLogRecords +class MultipleTestableAnnotationsTests extends AbstractJupiterTestEngineTests { + + @Test + void testAndRepeatedTest(LogRecordListener listener) { + discoverTests(request().selectors(selectClass(TestCase.class)).build()); + + // @formatter:off + assertTrue(listener.stream(Level.WARNING) + .map(LogRecord::getMessage) + .anyMatch(m -> m.matches("Possible configuration error: method .+ resulted in multiple TestDescriptors .+"))); + // @formatter:on + } + + static class TestCase { + + @Test + @RepeatedTest(1) + void testAndRepeatedTest(RepetitionInfo repetitionInfo) { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java new file mode 100644 index 00000000..2788b157 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java @@ -0,0 +1,369 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.util.Throwables.getRootCause; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass; +import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedClass; +import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedSiblingClass; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests that verify support for {@linkplain Nested nested contexts} + * in the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class NestedTestClassesTests extends AbstractJupiterTestEngineTests { + + @Test + void nestedTestsAreCorrectlyDiscovered() { + LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithNesting.class)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(5, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void nestedTestsAreExecuted() { + EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithNesting.class); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(3, tests.started().count(), "# tests started"); + assertEquals(2, tests.succeeded().count(), "# tests succeeded"); + assertEquals(1, tests.failed().count(), "# tests failed"); + + assertEquals(3, containers.started().count(), "# containers started"); + assertEquals(3, containers.finished().count(), "# containers finished"); + } + + @Test + void doublyNestedTestsAreCorrectlyDiscovered() { + LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithDoubleNesting.class)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(8, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void doublyNestedTestsAreExecuted() { + EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithDoubleNesting.class); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(5, tests.started().count(), "# tests started"); + assertEquals(3, tests.succeeded().count(), "# tests succeeded"); + assertEquals(2, tests.failed().count(), "# tests failed"); + + assertEquals(4, containers.started().count(), "# containers started"); + assertEquals(4, containers.finished().count(), "# containers finished"); + + assertAll("before each counts", // + () -> assertEquals(5, TestCaseWithDoubleNesting.beforeTopCount), + () -> assertEquals(4, TestCaseWithDoubleNesting.beforeNestedCount), + () -> assertEquals(2, TestCaseWithDoubleNesting.beforeDoublyNestedCount)); + + assertAll("after each counts", // + () -> assertEquals(5, TestCaseWithDoubleNesting.afterTopCount), + () -> assertEquals(4, TestCaseWithDoubleNesting.afterNestedCount), + () -> assertEquals(2, TestCaseWithDoubleNesting.afterDoublyNestedCount)); + + } + + @Test + void inheritedNestedTestsAreExecuted() { + EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithInheritedNested.class); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(3, tests.started().count(), "# tests started"); + assertEquals(2, tests.succeeded().count(), "# tests succeeded"); + assertEquals(1, tests.failed().count(), "# tests failed"); + + assertEquals(4, containers.started().count(), "# containers started"); + assertEquals(4, containers.finished().count(), "# containers finished"); + } + + @Test + void extendedNestedTestsAreExecuted() { + EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithExtendedNested.class); + executionResults.allEvents().debug(); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(6, tests.started().count(), "# tests started"); + assertEquals(4, tests.succeeded().count(), "# tests succeeded"); + assertEquals(2, tests.failed().count(), "# tests failed"); + + assertEquals(8, containers.started().count(), "# containers started"); + assertEquals(8, containers.finished().count(), "# containers finished"); + } + + @Test + void deeplyNestedInheritedMethodsAreExecutedWhenSelectedViaUniqueId() { + EngineExecutionResults executionResults = executeTests(selectUniqueId( + "[engine:junit-jupiter]/[class:org.junit.jupiter.engine.NestedTestClassesTests$TestCaseWithExtendedNested]/[nested-class:ConcreteInner1]/[nested-class:NestedInAbstractClass]/[nested-class:SecondLevelInherited]/[method:test()]"), + selectUniqueId( + "[engine:junit-jupiter]/[class:org.junit.jupiter.engine.NestedTestClassesTests$TestCaseWithExtendedNested]/[nested-class:ConcreteInner2]/[nested-class:NestedInAbstractClass]/[nested-class:SecondLevelInherited]/[method:test()]")); + executionResults.allEvents().debug(); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(2, tests.started().count(), "# tests started"); + assertEquals(2, tests.succeeded().count(), "# tests succeeded"); + assertEquals(0, tests.failed().count(), "# tests failed"); + + assertEquals(8, containers.started().count(), "# containers started"); + assertEquals(8, containers.finished().count(), "# containers finished"); + } + + /** + * @since 1.6 + */ + @Test + void recursiveNestedTestClassHierarchiesAreNotExecuted() { + assertNestedCycle(OuterClass.class, RecursiveNestedClass.class, OuterClass.class); + assertNestedCycle(NestedClass.class, RecursiveNestedClass.class, OuterClass.class); + assertNestedCycle(RecursiveNestedClass.class, RecursiveNestedClass.class, OuterClass.class); + } + + /** + * NOTE: We do not actually support this as a feature, but we currently only + * check for cycles if a class is selected. Thus, the tests in this method + * pass, since the selection of a particular method does not result in a + * lookup for nested test classes. + * + * @since 1.6 + */ + @Test + void individualMethodsWithinRecursiveNestedTestClassHierarchiesAreExecuted() { + EngineExecutionResults executionResults = executeTests(selectMethod(OuterClass.class, "outer")); + executionResults.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); + + executionResults = executeTests(selectMethod(NestedClass.class, "nested")); + executionResults.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3)); + executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); + + executionResults = executeTests(selectMethod(RecursiveNestedClass.class, "nested")); + executionResults.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); + executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); + + executionResults = executeTests(selectMethod(RecursiveNestedSiblingClass.class, "nested")); + executionResults.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); + executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + private void assertNestedCycle(Class start, Class from, Class to) { + assertThatExceptionOfType(JUnitException.class)// + .isThrownBy(() -> executeTestsForClass(start))// + .withCauseExactlyInstanceOf(JUnitException.class)// + .satisfies(ex -> assertThat(getRootCause(ex)).hasMessageMatching( + String.format("Detected cycle in inner class hierarchy between .+%s and .+%s", from.getSimpleName(), + to.getSimpleName()))); + } + + // ------------------------------------------------------------------- + + static class TestCaseWithNesting { + + @Test + void someTest() { + } + + @Nested + class NestedTestCase { + + @Test + void successful() { + } + + @Test + void failing() { + Assertions.fail("Something went horribly wrong"); + } + } + } + + static class TestCaseWithDoubleNesting { + + static int beforeTopCount = 0; + static int beforeNestedCount = 0; + static int beforeDoublyNestedCount = 0; + + static int afterTopCount = 0; + static int afterNestedCount = 0; + static int afterDoublyNestedCount = 0; + + @BeforeEach + void beforeTop() { + beforeTopCount++; + } + + @AfterEach + void afterTop() { + afterTopCount++; + } + + @Test + void someTest() { + } + + @Nested + class NestedTestCase { + + @BeforeEach + void beforeNested() { + beforeNestedCount++; + } + + @AfterEach + void afterNested() { + afterNestedCount++; + } + + @Test + void successful() { + } + + @Test + void failing() { + Assertions.fail("Something went horribly wrong"); + } + + @Nested + class DoublyNestedTestCase { + + @BeforeEach + void beforeDoublyNested() { + beforeDoublyNestedCount++; + } + + @BeforeEach + void afterDoublyNested() { + afterDoublyNestedCount++; + } + + @Test + void successful() { + } + + @Test + void failing() { + Assertions.fail("Something went horribly wrong"); + } + } + } + } + + interface InterfaceWithNestedClass { + + @Nested + class NestedInInterface { + + @Test + void notExecutedByImplementingClass() { + Assertions.fail("class in interface is static and should have been filtered out"); + } + } + + } + + static abstract class AbstractSuperClass implements InterfaceWithNestedClass { + + @Nested + class NestedInAbstractClass { + + @Test + void successful() { + } + + @Test + void failing() { + Assertions.fail("something went wrong"); + } + + @Nested + class SecondLevelInherited { + @Test + void test() { + } + } + } + } + + static class TestCaseWithInheritedNested extends AbstractSuperClass { + // empty on purpose + } + + static class TestCaseWithExtendedNested { + @Nested + class ConcreteInner1 extends AbstractSuperClass { + } + + @Nested + class ConcreteInner2 extends AbstractSuperClass { + } + } + + static class AbstractOuterClass { + } + + static class OuterClass extends AbstractOuterClass { + + @Test + void outer() { + } + + @Nested + class NestedClass { + + @Test + void nested() { + } + + @Nested + class RecursiveNestedClass extends OuterClass { + + @Test + void nested() { + } + } + + @Nested + // sibling of OuterClass due to common super type + class RecursiveNestedSiblingClass extends AbstractOuterClass { + + @Test + void nested() { + } + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java new file mode 100644 index 00000000..529026ec --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class NestedWithInheritanceTests extends SuperClass { + + static List lifecycleInvokingClassNames; + + static String OUTER = NestedWithInheritanceTests.class.getSimpleName(); + static String NESTED = NestedClass.class.getSimpleName(); + static String NESTEDNESTED = NestedClass.NestedNestedClass.class.getSimpleName(); + + @Nested + class NestedClass extends SuperClass { + + @Test + public void test() { + assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED); + } + + @Nested + class NestedNestedClass extends SuperClass { + + @Test + public void test() { + assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED, NESTEDNESTED); + } + } + + } + +} + +class SuperClass { + + @BeforeAll + static void setup() { + NestedWithInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); + } + + @BeforeEach + public void beforeEach() { + String invokingClass = this.getClass().getSimpleName(); + NestedWithInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java new file mode 100644 index 00000000..8b69ac20 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class NestedWithSeparateInheritanceTests extends SuperClass1 { + + static List lifecycleInvokingClassNames; + + static String OUTER = NestedWithSeparateInheritanceTests.class.getSimpleName(); + static String NESTED = NestedClass.class.getSimpleName(); + static String NESTEDNESTED = NestedClass.NestedNestedClass.class.getSimpleName(); + + @Nested + class NestedClass extends SuperClass2 { + + @Test + public void test() { + assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED); + } + + @Nested + class NestedNestedClass extends SuperClass3 { + + @Test + public void test() { + assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED, NESTEDNESTED); + } + } + + } + +} + +class SuperClass1 { + + @BeforeAll + static void setup() { + NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); + } + + @BeforeEach + public void beforeEach() { + String invokingClass = this.getClass().getSimpleName(); + NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); + } + +} + +class SuperClass2 { + + @BeforeAll + static void setup() { + NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); + } + + @BeforeEach + public void beforeEach() { + String invokingClass = this.getClass().getSimpleName(); + NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); + } + +} + +class SuperClass3 { + + @BeforeAll + static void setup() { + NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); + } + + @BeforeEach + public void beforeEach() { + String invokingClass = this.getClass().getSimpleName(); + NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java new file mode 100644 index 00000000..8f44af79 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; + +class NonVoidTestableMethodIntegrationTests { + + @Test + void valid() { + } + + @Test + int invalidMethodReturningPrimitive() { + fail("This method should never have been called."); + return 1; + } + + @Test + String invalidMethodReturningObject() { + fail("This method should never have been called."); + return ""; + } + + @RepeatedTest(3) + int invalidMethodVerifyingTestTemplateMethod() { + fail("This method should never have been called."); + return 1; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java new file mode 100644 index 00000000..6f17f367 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.testkit.engine.Event; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests for support of overloaded test methods in conjunction with + * the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class OverloadedTestMethodTests extends AbstractJupiterTestEngineTests { + + @Test + void executeTestCaseWithOverloadedMethodsAndThenRerunOnlyOneOfTheMethodsSelectedByUniqueId() { + Events tests = executeTestsForClass(TestCase.class).testEvents(); + + tests.assertStatistics(stats -> stats.started(2).succeeded(2).failed(0)); + + Optional first = tests.succeeded().filter( + event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); + assertTrue(first.isPresent()); + TestIdentifier testIdentifier = TestIdentifier.from(first.get().getTestDescriptor()); + UniqueId uniqueId = testIdentifier.getUniqueIdObject(); + + tests = executeTests(selectUniqueId(uniqueId)).testEvents(); + + tests.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); + + first = tests.succeeded().filter( + event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); + assertTrue(first.isPresent()); + } + + @Test + void executeTestCaseWithOverloadedMethodsWithSingleMethodThatAcceptsArgumentsSelectedByFullyQualifedMethodName() { + String fqmn = TestCase.class.getName() + "#test(" + TestInfo.class.getName() + ")"; + Events tests = executeTests(selectMethod(fqmn)).testEvents(); + + tests.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); + + Optional first = tests.succeeded().stream().filter( + event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); + assertTrue(first.isPresent()); + } + + static class TestCase { + + @Test + void test() { + } + + @Test + void test(TestInfo testInfo) { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java new file mode 100644 index 00000000..f5a05db3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +class RecordTests extends AbstractJupiterTestEngineTests { + + @Test + void recordsAreTestClasses() { + executeTestsForClass(TestCase.class).testEvents() // + .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); + } + + record TestCase() { + + @Test + void succeedingTest() { + assertTrue(true); + } + + @Test + void failingTest() { + fail("always fails"); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java new file mode 100644 index 00000000..ca909b8d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static java.util.Collections.emptyMap; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 5.0 + */ +class ReportingTests extends AbstractJupiterTestEngineTests { + + @Test + void reportEntriesArePublished() { + executeTestsForClass(MyReportingTestCase.class).testEvents().assertStatistics(stats -> stats // + .started(2) // + .succeeded(2) // + .failed(0) // + .reportingEntryPublished(7)); + } + + static class MyReportingTestCase { + + @BeforeEach + void beforeEach(TestReporter reporter) { + reporter.publishEntry("@BeforeEach"); + } + + @AfterEach + void afterEach(TestReporter reporter) { + reporter.publishEntry("@AfterEach"); + } + + @Test + void succeedingTest(TestReporter reporter) { + reporter.publishEntry(emptyMap()); + reporter.publishEntry("user name", "dk38"); + reporter.publishEntry("message"); + } + + @Test + void invalidReportData(TestReporter reporter) { + + // Maps + Map map = new HashMap<>(); + + map.put("key", null); + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); + + map.clear(); + map.put(null, "value"); + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); + + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((Map) null)); + + // Key-Value pair + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(null, "bar")); + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry("foo", null)); + + // Value + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((String) null)); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java new file mode 100644 index 00000000..e1e0f7dc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +class SealedClassTests extends AbstractJupiterTestEngineTests { + + @Test + void sealedTestClassesAreTestClasses() { + executeTestsForClass(TestCase.class).testEvents() // + .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); + } + + sealed + abstract static class AbstractTestCase + permits TestCase + { + + @Test + void succeedingTest() { + assertTrue(true); + } + + @Test + void failingTest() { + fail("always fails"); + } + } + + static final class TestCase extends AbstractTestCase { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java new file mode 100644 index 00000000..230d5ebc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java @@ -0,0 +1,252 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; +import org.opentest4j.TestAbortedException; + +/** + * Tests for discovery and execution of standard test cases for the + * {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class StandardTestClassTests extends AbstractJupiterTestEngineTests { + + @Test + void standardTestClassIsCorrectlyDiscovered() { + LauncherDiscoveryRequest request = request().selectors(selectClass(MyStandardTestCase.class)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(1 /*class*/ + 6 /*methods*/, engineDescriptor.getDescendants().size(), + "# resolved test descriptors"); + } + + @Test + void moreThanOneTestClassIsCorrectlyDiscovered() { + LauncherDiscoveryRequest request = request().selectors(selectClass(SecondOfTwoTestCases.class)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(1 /*class*/ + 3 /*methods*/, engineDescriptor.getDescendants().size(), + "# resolved test descriptors"); + } + + @Test + void moreThanOneTestClassIsExecuted() { + LauncherDiscoveryRequest request = request().selectors(selectClass(FirstOfTwoTestCases.class), + selectClass(SecondOfTwoTestCases.class)).build(); + + EngineExecutionResults executionResults = executeTests(request); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(6, tests.started().count(), "# tests started"); + assertEquals(5, tests.succeeded().count(), "# tests succeeded"); + assertEquals(1, tests.failed().count(), "# tests failed"); + + assertEquals(3, containers.started().count(), "# containers started"); + assertEquals(3, containers.finished().count(), "# containers finished"); + } + + @Test + void allTestsInClassAreRunWithBeforeEachAndAfterEachMethods() { + EngineExecutionResults executionResults = executeTestsForClass(MyStandardTestCase.class); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(2, containers.started().count(), "# containers started"); + assertEquals(2, containers.finished().count(), "# containers finished"); + + assertEquals(6, tests.started().count(), "# tests started"); + assertEquals(2, tests.succeeded().count(), "# tests succeeded"); + assertEquals(3, tests.aborted().count(), "# tests aborted"); + assertEquals(1, tests.failed().count(), "# tests failed"); + + assertEquals(6, MyStandardTestCase.countBefore1, "# before1 calls"); + assertEquals(6, MyStandardTestCase.countBefore2, "# before2 calls"); + assertEquals(6, MyStandardTestCase.countAfter, "# after each calls"); + } + + @Test + void testsFailWhenBeforeEachFails() { + EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithFailingBefore.class); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(2, tests.started().count(), "# tests started"); + assertEquals(0, tests.succeeded().count(), "# tests succeeded"); + assertEquals(2, tests.failed().count(), "# tests failed"); + + assertEquals(2, containers.started().count(), "# containers started"); + assertEquals(2, containers.finished().count(), "# containers finished"); + + assertEquals(2, TestCaseWithFailingBefore.countBefore, "# before each calls"); + } + + @Test + void testsFailWhenAfterEachFails() { + EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithFailingAfter.class); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + assertEquals(1, tests.started().count(), "# tests started"); + assertEquals(0, tests.succeeded().count(), "# tests succeeded"); + assertEquals(1, tests.failed().count(), "# tests failed"); + + assertEquals(2, containers.started().count(), "# containers started"); + assertEquals(2, containers.finished().count(), "# containers finished"); + + assertTrue(TestCaseWithFailingAfter.testExecuted, "test executed?"); + } + + static class MyStandardTestCase { + + static int countBefore1 = 0; + static int countBefore2 = 0; + static int countAfter = 0; + + @BeforeEach + void before1() { + countBefore1++; + } + + @BeforeEach + void before2() { + countBefore2++; + } + + @AfterEach + void after() { + countAfter++; + } + + @Test + void succeedingTest1() { + assertTrue(true); + } + + @Test + void succeedingTest2() { + assertTrue(true); + } + + @Test + void failingTest() { + fail("always fails"); + } + + @Test + @DisplayName("Test aborted via the OTA's TestAbortedException") + void testAbortedOpenTest4J() { + throw new TestAbortedException("aborted!"); + } + + @Test + @DisplayName("Test aborted via JUnit 4's AssumptionViolatedException") + void testAbortedJUnit4() { + throw new org.junit.AssumptionViolatedException("aborted!"); + } + + @Test + @DisplayName("Test aborted via JUnit 4's legacy, deprecated AssumptionViolatedException") + @SuppressWarnings("deprecation") + void testAbortedJUnit4Legacy() { + throw new org.junit.internal.AssumptionViolatedException("aborted!"); + } + + } + + static class FirstOfTwoTestCases { + + @Test + void succeedingTest1() { + assertTrue(true); + } + + @Test + void succeedingTest2() { + assertTrue(true); + } + + @Test + void failingTest() { + fail("always fails"); + } + + } + + static class SecondOfTwoTestCases { + + @Test + void succeedingTest1() { + assertTrue(true); + } + + @Test + void succeedingTest2() { + assertTrue(true); + } + + @Test + void succeedingTest3() { + assertTrue(true); + } + + } + + static class TestCaseWithFailingBefore { + + static int countBefore = 0; + + @BeforeEach + void before() { + countBefore++; + throw new RuntimeException("Problem during setup"); + } + + @Test + void test1() { + } + + @Test + void test2() { + } + + } + + static class TestCaseWithFailingAfter { + + static boolean testExecuted = false; + + @AfterEach + void after() { + throw new RuntimeException("Problem during 'after'"); + } + + @Test + void test1() { + testExecuted = true; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java new file mode 100644 index 00000000..42648602 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +/** + * Integration tests that verify support for {@code static} {@link BeforeAll} and + * {@link AfterAll} methods in {@link Nested} tests on Java 16+. + * + * @since 5.9 + * @see BeforeAllAndAfterAllComposedAnnotationTests + */ +class StaticNestedBeforeAllAndAfterAllMethodsTests extends AbstractJupiterTestEngineTests { + + private static final List methodsInvoked = new ArrayList<>(); + + @DisplayName("static @BeforeAll and @AfterAll methods in @Nested test class") + @Test + void staticBeforeAllAndAfterAllMethodsInNestedTestClass() { + executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + + assertThat(methodsInvoked).containsExactly(// + "@BeforeAll: top-level", // + "@Test: top-level", // + "@BeforeAll: nested", // + "@Test: nested", // + "@AfterAll: nested", // + "@AfterAll: top-level"// + ); + } + + static class TestCase { + + @BeforeAll + static void beforeAll() { + methodsInvoked.add("@BeforeAll: top-level"); + } + + @Test + void test() { + methodsInvoked.add("@Test: top-level"); + } + + @AfterAll + static void afterAll() { + methodsInvoked.add("@AfterAll: top-level"); + } + + @Nested + // Lifecycle.PER_METHOD is the default, but we declare it here in order + // to be very explicit about what we are testing, namely static lifecycle + // methods in an inner class WITHOUT Lifecycle.PER_CLASS semantics. + @TestInstance(Lifecycle.PER_METHOD) + class NestedTestCase { + + @BeforeAll + static void beforeAllInner() { + methodsInvoked.add("@BeforeAll: nested"); + } + + @Test + void test() { + methodsInvoked.add("@Test: nested"); + } + + @AfterAll + static void afterAllInner() { + methodsInvoked.add("@AfterAll: nested"); + } + + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java new file mode 100644 index 00000000..12aa4566 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java @@ -0,0 +1,283 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests for test class hierarchy support in the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class TestClassInheritanceTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @BeforeEach + void initStatics() { + callSequence.clear(); + LocalTestCase.countBeforeInvoked = 0; + LocalTestCase.countAfterInvoked = 0; + AbstractTestCase.countSuperBeforeInvoked = 0; + AbstractTestCase.countSuperAfterInvoked = 0; + } + + @Test + void executeAllTestsInClass() { + EngineExecutionResults executionResults = executeTestsForClass(LocalTestCase.class); + + assertEquals(6, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(1, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(2, executionResults.testEvents().failed().count(), "# tests failed"); + + assertEquals(6, LocalTestCase.countBeforeInvoked, "# before calls"); + assertEquals(6, LocalTestCase.countAfterInvoked, "# after calls"); + assertEquals(6, AbstractTestCase.countSuperBeforeInvoked, "# super before calls"); + assertEquals(6, AbstractTestCase.countSuperAfterInvoked, "# super after calls"); + } + + @Test + void executeSingleTest() { + EngineExecutionResults executionResults = executeTests(selectMethod(LocalTestCase.class, "alwaysPasses")); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestDeclaredInSuperClass() { + EngineExecutionResults executionResults = executeTests(selectMethod(LocalTestCase.class, "superTest")); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + + assertEquals(1, LocalTestCase.countBeforeInvoked, "# after calls"); + assertEquals(1, LocalTestCase.countAfterInvoked, "# after calls"); + assertEquals(1, AbstractTestCase.countSuperBeforeInvoked, "# super before calls"); + assertEquals(1, AbstractTestCase.countSuperAfterInvoked, "# super after calls"); + + } + + @Test + void executeTestWithExceptionThrownInAfterMethod() { + EngineExecutionResults executionResults = executeTests( + selectMethod(LocalTestCase.class, "throwExceptionInAfterMethod")); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void beforeAndAfterMethodsInTestClassHierarchy() { + EngineExecutionResults executionResults = executeTestsForClass(TestCase3.class); + + // @formatter:off + assertAll( + () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), + () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), + () -> assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"), + () -> assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"), + () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") + ); + // @formatter:on + + // @formatter:off + assertEquals(asList( + "beforeAll1", + "beforeAll2", + "beforeAll3", + "beforeEach1", + "beforeEach2", + "beforeEach3", + "test3", + "afterEach3", + "afterEach2", + "afterEach1", + "afterAll3", + "afterAll2", + "afterAll1" + ), callSequence, "wrong call sequence"); + // @formatter:on + } + + // ------------------------------------------------------------------- + + private static abstract class AbstractTestCase { + + static int countSuperBeforeInvoked = 0; + static int countSuperAfterInvoked = 0; + + @BeforeEach + void superBefore() { + countSuperBeforeInvoked++; + } + + @AfterEach + void superAfter() { + countSuperAfterInvoked++; + } + + @Test + void superTest() { + /* no-op */ + } + } + + static class LocalTestCase extends AbstractTestCase { + + boolean throwExceptionInAfterMethod = false; + + static int countBeforeInvoked = 0; + static int countAfterInvoked = 0; + + @BeforeEach + void before() { + countBeforeInvoked++; + // Reset state, since the test instance is retained across all test methods; + // otherwise, after() always throws an exception. + this.throwExceptionInAfterMethod = false; + } + + @AfterEach + void after() { + countAfterInvoked++; + if (this.throwExceptionInAfterMethod) { + throw new RuntimeException("Exception thrown from @AfterEach method"); + } + } + + @Test + void otherTest() { + /* no-op */ + } + + @Test + void throwExceptionInAfterMethod() { + this.throwExceptionInAfterMethod = true; + } + + @Test + void alwaysPasses() { + /* no-op */ + } + + @Test + void aborted() { + assumeTrue(false); + } + + @Test + void alwaysFails() { + fail("#fail"); + } + } + + static class TestCase1 { + + @BeforeAll + static void beforeAll1() { + callSequence.add("beforeAll1"); + } + + @BeforeEach + void beforeEach1() { + callSequence.add("beforeEach1"); + } + + @AfterEach + void afterEach1() { + callSequence.add("afterEach1"); + } + + @AfterAll + static void afterAll1() { + callSequence.add("afterAll1"); + } + } + + static class TestCase2 extends TestCase1 { + + @BeforeAll + static void beforeAll2() { + callSequence.add("beforeAll2"); + } + + @BeforeEach + void beforeEach2() { + callSequence.add("beforeEach2"); + } + + @AfterEach + void afterEach2() { + callSequence.add("afterEach2"); + } + + @AfterAll + static void afterAll2() { + callSequence.add("afterAll2"); + } + } + + static class TestCase3 extends TestCase2 { + + @BeforeAll + static void beforeAll3() { + callSequence.add("beforeAll3"); + } + + @BeforeEach + void beforeEach3() { + callSequence.add("beforeEach3"); + } + + @Test + void test3() { + callSequence.add("test3"); + } + + @AfterEach + void afterEach3() { + callSequence.add("afterEach3"); + } + + @AfterAll + static void afterAll3() { + callSequence.add("afterAll3"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java new file mode 100644 index 00000000..87260717 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java @@ -0,0 +1,234 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests for {@link TestInstance @TestInstance} lifecycle + * configuration support, not to be confused with {@link TestInstanceLifecycleTests}. + * + *

Specifically, this class tests custom lifecycle configuration specified + * via {@code @TestInstance} as well as via {@link ConfigurationParameters} + * supplied to the {@link Launcher} or via a JVM system property. + * + * @since 5.0 + * @see TestInstanceLifecycleTests + */ +class TestInstanceLifecycleConfigurationTests extends AbstractJupiterTestEngineTests { + + private static final String KEY = Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; + + private static final List methodsInvoked = new ArrayList<>(); + + @BeforeEach + @AfterEach + void reset() { + methodsInvoked.clear(); + System.clearProperty(KEY); + } + + @Test + void instancePerMethodUsingStandardDefaultConfiguration() { + performAssertions(AssumedInstancePerTestMethodTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); + } + + @Test + void instancePerClassConfiguredViaExplicitAnnotationDeclaration() { + performAssertions(ExplicitInstancePerClassTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); + } + + @Test + void instancePerClassConfiguredViaSystemProperty() { + Class testClass = AssumedInstancePerClassTestCase.class; + + // Should fail by default... + performAssertions(testClass, 2, 1, 0); + + // Should pass with the system property set + System.setProperty(KEY, PER_CLASS.name()); + performAssertions(testClass, 2, 0, 1, "beforeAll", "test", "afterAll"); + } + + @Test + void instancePerClassConfiguredViaConfigParam() { + Class testClass = AssumedInstancePerClassTestCase.class; + + // Should fail by default... + performAssertions(testClass, 2, 1, 0); + + // Should pass with the config param + performAssertions(testClass, singletonMap(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); + } + + @Test + void instancePerClassConfiguredViaConfigParamThatOverridesSystemProperty() { + Class testClass = AssumedInstancePerClassTestCase.class; + + // Should fail with system property + System.setProperty(KEY, PER_METHOD.name()); + performAssertions(testClass, 2, 1, 0); + + // Should pass with the config param + performAssertions(testClass, singletonMap(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); + } + + @Test + void instancePerMethodConfiguredViaExplicitAnnotationDeclarationThatOverridesSystemProperty() { + System.setProperty(KEY, PER_CLASS.name()); + performAssertions(ExplicitInstancePerTestMethodTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); + } + + @Test + void instancePerMethodConfiguredViaExplicitAnnotationDeclarationThatOverridesConfigParam() { + Class testClass = ExplicitInstancePerTestMethodTestCase.class; + performAssertions(testClass, singletonMap(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); + } + + private void performAssertions(Class testClass, int containers, int containersFailed, int tests, + String... methods) { + + performAssertions(testClass, emptyMap(), containers, containersFailed, tests, methods); + } + + private void performAssertions(Class testClass, Map configParams, int numContainers, + int numFailedContainers, int numTests, String... methods) { + + // @formatter:off + EngineExecutionResults executionResults = executeTests( + request() + .selectors(selectClass(testClass)) + .configurationParameters(configParams) + .build() + ); + // @formatter:on + + executionResults.containerEvents().assertStatistics(// + stats -> stats.started(numContainers).finished(numContainers).failed(numFailedContainers)); + executionResults.testEvents().assertStatistics(// + stats -> stats.started(numTests).finished(numTests)); + + assertEquals(Arrays.asList(methods), methodsInvoked); + } + + // ------------------------------------------------------------------------- + + @TestInstance(PER_METHOD) + static class ExplicitInstancePerTestMethodTestCase { + + @BeforeAll + static void beforeAll() { + methodsInvoked.add("beforeAll"); + } + + @Test + void test() { + methodsInvoked.add("test"); + } + + @AfterAll + static void afterAllStatic() { + methodsInvoked.add("afterAll"); + } + + } + + /** + * "per-method" lifecycle mode is assumed since the {@code @BeforeAll} and + * {@code @AfterAll} methods are static, even though there is no explicit + * {@code @TestInstance} declaration. + */ + static class AssumedInstancePerTestMethodTestCase { + + @BeforeAll + static void beforeAll() { + methodsInvoked.add("beforeAll"); + } + + @Test + void test() { + methodsInvoked.add("test"); + } + + @AfterAll + static void afterAllStatic() { + methodsInvoked.add("afterAll"); + } + + } + + @TestInstance(PER_CLASS) + static class ExplicitInstancePerClassTestCase { + + @BeforeAll + void beforeAll(TestInfo testInfo) { + methodsInvoked.add("beforeAll"); + } + + @Test + void test() { + methodsInvoked.add("test"); + } + + @AfterAll + void afterAll(TestInfo testInfo) { + methodsInvoked.add("afterAll"); + } + + } + + /** + * "per-class" lifecycle mode is assumed since the {@code @BeforeAll} and + * {@code @AfterAll} methods are non-static, even though there is no + * explicit {@code @TestInstance} declaration. + */ + static class AssumedInstancePerClassTestCase { + + @BeforeAll + void beforeAll(TestInfo testInfo) { + methodsInvoked.add("beforeAll"); + } + + @Test + void test() { + methodsInvoked.add("test"); + } + + @AfterAll + void afterAll(TestInfo testInfo) { + methodsInvoked.add("afterAll"); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java new file mode 100644 index 00000000..50d059df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.engine.kotlin.InstancePerClassKotlinTestCase; +import org.junit.jupiter.engine.kotlin.InstancePerMethodKotlinTestCase; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Kotlin-specific integration tests for {@link TestInstance @TestInstance} + * lifecycle support. + * + * @since 5.1 + * @see TestInstanceLifecycleConfigurationTests + * @see TestInstanceLifecycleTests + */ +class TestInstanceLifecycleKotlinTests extends AbstractJupiterTestEngineTests { + + @Test + void instancePerClassCanBeUsedForKotlinTestClasses() { + Class testClass = InstancePerClassKotlinTestCase.class; + InstancePerClassKotlinTestCase.TEST_INSTANCES.clear(); + + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertThat(executionResults.testEvents().finished().count()).isEqualTo(2); + assertThat(InstancePerClassKotlinTestCase.TEST_INSTANCES.keySet()).hasSize(1); + assertThat(getOnlyElement(InstancePerClassKotlinTestCase.TEST_INSTANCES.values())) // + .containsEntry("beforeAll", 1) // + .containsEntry("beforeEach", 2) // + .containsEntry("test", 2) // + .containsEntry("afterEach", 2) // + .containsEntry("afterAll", 1); + } + + @Test + void instancePerMethodIsDefaultForKotlinTestClasses() { + Class testClass = InstancePerMethodKotlinTestCase.class; + InstancePerMethodKotlinTestCase.TEST_INSTANCES.clear(); + + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertThat(executionResults.testEvents().finished().count()).isEqualTo(2); + List instances = new ArrayList<>(InstancePerMethodKotlinTestCase.TEST_INSTANCES.keySet()); + assertThat(instances) // + .hasSize(3) // + .extracting(o -> (Object) o.getClass()) // + .containsExactly(InstancePerMethodKotlinTestCase.Companion.getClass(), // + InstancePerMethodKotlinTestCase.class, // + InstancePerMethodKotlinTestCase.class); + assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(0))) // + .containsEntry("beforeAll", 1) // + .containsEntry("afterAll", 1); + assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(1))) // + .containsEntry("beforeEach", 1) // + .containsEntry("test", 1) // + .containsEntry("afterEach", 1); + assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(2))) // + .containsEntry("beforeEach", 1) // + .containsEntry("test", 1) // + .containsEntry("afterEach", 1); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java new file mode 100644 index 00000000..91548496 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java @@ -0,0 +1,1067 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static java.lang.String.join; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.jupiter.engine.execution.DefaultTestInstances; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests for {@link TestInstance @TestInstance} lifecycle support. + * + * @since 5.0 + * @see TestInstanceLifecycleConfigurationTests + * @see TestInstanceLifecycleKotlinTests + */ +class TestInstanceLifecycleTests extends AbstractJupiterTestEngineTests { + + private static final Map, List> lifecyclesMap = new LinkedHashMap<>(); + private static final Map instanceMap = new LinkedHashMap<>(); + private static final List testsInvoked = new ArrayList<>(); + private static final Map, Integer> instanceCount = new LinkedHashMap<>(); + + private static int beforeAllCount; + private static int afterAllCount; + private static int beforeEachCount; + private static int afterEachCount; + + @BeforeEach + void init() { + lifecyclesMap.clear(); + instanceMap.clear(); + testsInvoked.clear(); + instanceCount.clear(); + beforeAllCount = 0; + afterAllCount = 0; + beforeEachCount = 0; + afterEachCount = 0; + } + + @Test + void instancePerMethod() { + Class testClass = InstancePerMethodTestCase.class; + int containers = 3; + int tests = 3; + Map.Entry, Integer>[] instances = instanceCounts(entry(InstancePerMethodTestCase.class, 3)); + int allMethods = 1; + int eachMethods = 3; + + performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); + + String containerExecutionConditionKey = executionConditionKey(testClass, null); + String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); + String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); + String beforeAllCallbackKey = beforeAllCallbackKey(testClass); + String afterAllCallbackKey = afterAllCallbackKey(testClass); + String testTemplateKey = testTemplateKey(testClass, "singletonTest"); + String testExecutionConditionKey1 = executionConditionKey(testClass, testsInvoked.get(0)); + String beforeEachCallbackKey1 = beforeEachCallbackKey(testClass, testsInvoked.get(0)); + String afterEachCallbackKey1 = afterEachCallbackKey(testClass, testsInvoked.get(0)); + String testExecutionConditionKey2 = executionConditionKey(testClass, testsInvoked.get(1)); + String beforeEachCallbackKey2 = beforeEachCallbackKey(testClass, testsInvoked.get(1)); + String afterEachCallbackKey2 = afterEachCallbackKey(testClass, testsInvoked.get(1)); + String testExecutionConditionKey3 = executionConditionKey(testClass, testsInvoked.get(2)); + String beforeEachCallbackKey3 = beforeEachCallbackKey(testClass, testsInvoked.get(2)); + String afterEachCallbackKey3 = afterEachCallbackKey(testClass, testsInvoked.get(2)); + + // @formatter:off + assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( + containerExecutionConditionKey, + beforeAllCallbackKey, + postProcessTestInstanceKey, + preDestroyCallbackTestInstanceKey, + testTemplateKey, + testExecutionConditionKey1, + beforeEachCallbackKey1, + afterEachCallbackKey1, + testExecutionConditionKey2, + beforeEachCallbackKey2, + afterEachCallbackKey2, + testExecutionConditionKey3, + beforeEachCallbackKey3, + afterEachCallbackKey3, + afterAllCallbackKey + ); + // @formatter:on + + assertNull(instanceMap.get(containerExecutionConditionKey)); + assertNull(instanceMap.get(beforeAllCallbackKey)); + assertNull(instanceMap.get(afterAllCallbackKey)); + + TestInstances testInstances = instanceMap.get(beforeEachCallbackKey1); + assertNotNull(testInstances.getInnermostInstance()); + assertSame(testInstances, instanceMap.get(afterEachCallbackKey1)); + assertSame(testInstances, instanceMap.get(testExecutionConditionKey1)); + + testInstances = instanceMap.get(beforeEachCallbackKey2); + assertNotNull(testInstances.getInnermostInstance()); + assertSame(testInstances, instanceMap.get(afterEachCallbackKey2)); + assertSame(testInstances, instanceMap.get(testExecutionConditionKey2)); + + testInstances = instanceMap.get(beforeEachCallbackKey3); + assertNotNull(testInstances.getInnermostInstance()); + assertSame(testInstances, instanceMap.get(afterEachCallbackKey3)); + assertSame(testInstances, instanceMap.get(testExecutionConditionKey3)); + assertSame(testInstances.getInnermostInstance(), + instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); + assertSame(testInstances.getInnermostInstance(), + instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); + + assertThat(lifecyclesMap.keySet()).containsExactly(testClass); + assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); + } + + @Test + void instancePerClass() { + instancePerClass(InstancePerClassTestCase.class, instanceCounts(entry(InstancePerClassTestCase.class, 1))); + } + + @Test + void instancePerClassWithInheritedLifecycleMode() { + instancePerClass(SubInstancePerClassTestCase.class, + instanceCounts(entry(SubInstancePerClassTestCase.class, 1))); + } + + private void instancePerClass(Class testClass, Map.Entry, Integer>[] instances) { + int containers = 3; + int tests = 3; + int allMethods = 2; + int eachMethods = 3; + + performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); + + String containerExecutionConditionKey = executionConditionKey(testClass, null); + String testTemplateKey = testTemplateKey(testClass, "singletonTest"); + String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); + String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); + String beforeAllCallbackKey = beforeAllCallbackKey(testClass); + String afterAllCallbackKey = afterAllCallbackKey(testClass); + String testExecutionConditionKey1 = executionConditionKey(testClass, testsInvoked.get(0)); + String beforeEachCallbackKey1 = beforeEachCallbackKey(testClass, testsInvoked.get(0)); + String afterEachCallbackKey1 = afterEachCallbackKey(testClass, testsInvoked.get(0)); + String testExecutionConditionKey2 = executionConditionKey(testClass, testsInvoked.get(1)); + String beforeEachCallbackKey2 = beforeEachCallbackKey(testClass, testsInvoked.get(1)); + String afterEachCallbackKey2 = afterEachCallbackKey(testClass, testsInvoked.get(1)); + String testExecutionConditionKey3 = executionConditionKey(testClass, testsInvoked.get(2)); + String beforeEachCallbackKey3 = beforeEachCallbackKey(testClass, testsInvoked.get(2)); + String afterEachCallbackKey3 = afterEachCallbackKey(testClass, testsInvoked.get(2)); + + // @formatter:off + assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( + postProcessTestInstanceKey, + preDestroyCallbackTestInstanceKey, + containerExecutionConditionKey, + beforeAllCallbackKey, + testTemplateKey, + testExecutionConditionKey1, + beforeEachCallbackKey1, + afterEachCallbackKey1, + testExecutionConditionKey2, + beforeEachCallbackKey2, + afterEachCallbackKey2, + testExecutionConditionKey3, + beforeEachCallbackKey3, + afterEachCallbackKey3, + afterAllCallbackKey + ); + // @formatter:on + + TestInstances testInstances = instanceMap.get(beforeAllCallbackKey); + assertNotNull(testInstances.getInnermostInstance()); + assertSame(testInstances, instanceMap.get(afterAllCallbackKey)); + assertSame(testInstances, instanceMap.get(testExecutionConditionKey1)); + assertSame(testInstances, instanceMap.get(beforeEachCallbackKey1)); + assertSame(testInstances, instanceMap.get(afterEachCallbackKey1)); + assertSame(testInstances, instanceMap.get(testExecutionConditionKey2)); + assertSame(testInstances, instanceMap.get(beforeEachCallbackKey2)); + assertSame(testInstances, instanceMap.get(afterEachCallbackKey2)); + assertSame(testInstances, instanceMap.get(testExecutionConditionKey3)); + assertSame(testInstances, instanceMap.get(beforeEachCallbackKey3)); + assertSame(testInstances, instanceMap.get(afterEachCallbackKey3)); + assertSame(testInstances.getInnermostInstance(), + instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); + assertSame(testInstances.getInnermostInstance(), + instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); + + assertNull(instanceMap.get(containerExecutionConditionKey)); + + assertThat(lifecyclesMap.keySet()).containsExactly(testClass); + assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); + } + + @Test + void instancePerMethodWithNestedTestClass() { + Class testClass = InstancePerMethodOuterTestCase.class; + Class nestedTestClass = InstancePerMethodOuterTestCase.NestedInstancePerMethodTestCase.class; + int containers = 4; + int tests = 4; + Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 4), entry(nestedTestClass, 3)); + int allMethods = 1; + int eachMethods = 3; + + performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); + + String containerExecutionConditionKey = executionConditionKey(testClass, null); + String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); + String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); + String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); + String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); + String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); + String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); + String beforeAllCallbackKey = beforeAllCallbackKey(testClass); + String afterAllCallbackKey = afterAllCallbackKey(testClass); + String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); + String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); + String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); + String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); + String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); + String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.get(0)); + String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); + String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); + String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); + String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); + String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); + String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); + String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); + String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); + + // @formatter:off + assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( + containerExecutionConditionKey, + nestedTestTemplateKey, + nestedContainerExecutionConditionKey, + postProcessTestInstanceKey, + nestedPostProcessTestInstanceKey, + preDestroyCallbackTestInstanceKey, + nestedPreDestroyCallbackTestInstanceKey, + beforeAllCallbackKey, + afterAllCallbackKey, + outerTestExecutionConditionKey, + beforeEachCallbackKey, + afterEachCallbackKey, + nestedBeforeAllCallbackKey, + nestedAfterAllCallbackKey, + nestedExecutionConditionKey1, + nestedBeforeEachCallbackKey1, + nestedAfterEachCallbackKey1, + nestedExecutionConditionKey2, + nestedBeforeEachCallbackKey2, + nestedAfterEachCallbackKey2, + nestedExecutionConditionKey3, + nestedBeforeEachCallbackKey3, + nestedAfterEachCallbackKey3 + ); + // @formatter:on + + assertNull(instanceMap.get(containerExecutionConditionKey)); + assertNull(instanceMap.get(beforeAllCallbackKey)); + assertNull(instanceMap.get(afterAllCallbackKey)); + assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); + assertNull(instanceMap.get(nestedBeforeAllCallbackKey)); + assertNull(instanceMap.get(nestedAfterAllCallbackKey)); + + TestInstances outerInstances = instanceMap.get(beforeEachCallbackKey); + assertNotNull(outerInstances.getInnermostInstance()); + assertSame(outerInstances, instanceMap.get(afterEachCallbackKey)); + assertSame(outerInstances, instanceMap.get(outerTestExecutionConditionKey)); + + TestInstances nestedInstances1 = instanceMap.get(nestedBeforeEachCallbackKey1); + assertNotNull(nestedInstances1.getInnermostInstance()); + assertNotSame(outerInstances.getInnermostInstance(), nestedInstances1.getInnermostInstance()); + assertSame(nestedInstances1, instanceMap.get(nestedAfterEachCallbackKey1)); + assertSame(nestedInstances1, instanceMap.get(nestedExecutionConditionKey1)); + + TestInstances nestedInstances2 = instanceMap.get(nestedBeforeEachCallbackKey2); + assertNotNull(nestedInstances2.getInnermostInstance()); + assertNotSame(outerInstances.getInnermostInstance(), nestedInstances2.getInnermostInstance()); + assertNotSame(nestedInstances1.getInnermostInstance(), nestedInstances2.getInnermostInstance()); + assertSame(nestedInstances2, instanceMap.get(nestedAfterEachCallbackKey2)); + assertSame(nestedInstances2, instanceMap.get(nestedExecutionConditionKey2)); + + TestInstances nestedInstances3 = instanceMap.get(nestedPostProcessTestInstanceKey); + assertNotNull(nestedInstances3.getInnermostInstance()); + assertNotSame(outerInstances.getInnermostInstance(), nestedInstances3.getInnermostInstance()); + assertNotSame(nestedInstances1.getInnermostInstance(), nestedInstances3.getInnermostInstance()); + assertSame(nestedInstances3.getInnermostInstance(), + instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); + assertSame(nestedInstances3.getInnermostInstance(), + instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); + assertSame(nestedInstances3.getInnermostInstance(), + instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); + + Object outerInstance1 = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); + Object outerInstance2 = instanceMap.get(nestedExecutionConditionKey2).findInstance(testClass).get(); + Object outerInstance3 = instanceMap.get(nestedExecutionConditionKey3).findInstance(testClass).get(); + assertNotSame(outerInstance1, outerInstance2); + assertNotSame(outerInstance1, outerInstance3); + assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance1, + nestedInstances1.getInnermostInstance()); + assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance2, + nestedInstances2.getInnermostInstance()); + assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance3, + nestedInstances3.getInnermostInstance()); + + // The last tracked instance stored under postProcessTestInstanceKey + // is only created in order to instantiate the nested test class for + // test2(). + assertSame(outerInstance3, instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); + + assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); + assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); + assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); + } + + @Test + void instancePerClassWithNestedTestClass() { + Class testClass = InstancePerClassOuterTestCase.class; + Class nestedTestClass = InstancePerClassOuterTestCase.NestedInstancePerClassTestCase.class; + int containers = 4; + int tests = 4; + Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 1), entry(nestedTestClass, 1)); + int allMethods = 2; + int eachMethods = 3; + + performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); + + String containerExecutionConditionKey = executionConditionKey(testClass, null); + String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); + String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); + String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); + String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); + String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); + String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); + String beforeAllCallbackKey = beforeAllCallbackKey(testClass); + String afterAllCallbackKey = afterAllCallbackKey(testClass); + String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); + String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); + String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); + String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); + String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); + String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.get(0)); + String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); + String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); + String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); + String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); + String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); + String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); + String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); + String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); + + // @formatter:off + assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( + containerExecutionConditionKey, + nestedTestTemplateKey, + nestedContainerExecutionConditionKey, + postProcessTestInstanceKey, + nestedPostProcessTestInstanceKey, + preDestroyCallbackTestInstanceKey, + nestedPreDestroyCallbackTestInstanceKey, + beforeAllCallbackKey, + afterAllCallbackKey, + outerTestExecutionConditionKey, + beforeEachCallbackKey, + afterEachCallbackKey, + nestedBeforeAllCallbackKey, + nestedAfterAllCallbackKey, + nestedExecutionConditionKey1, + nestedBeforeEachCallbackKey1, + nestedAfterEachCallbackKey1, + nestedExecutionConditionKey2, + nestedBeforeEachCallbackKey2, + nestedAfterEachCallbackKey2, + nestedExecutionConditionKey3, + nestedBeforeEachCallbackKey3, + nestedAfterEachCallbackKey3 + ); + // @formatter:on + + Object instance = instanceMap.get(postProcessTestInstanceKey).getInnermostInstance(); + assertNotNull(instance); + assertNull(instanceMap.get(containerExecutionConditionKey)); + assertSame(instance, instanceMap.get(beforeAllCallbackKey).getInnermostInstance()); + assertSame(instance, instanceMap.get(afterAllCallbackKey).getInnermostInstance()); + assertSame(instance, instanceMap.get(outerTestExecutionConditionKey).getInnermostInstance()); + assertSame(instance, instanceMap.get(beforeEachCallbackKey).getInnermostInstance()); + assertSame(instance, instanceMap.get(afterEachCallbackKey).getInnermostInstance()); + assertSame(instance, instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); + + Object nestedInstance = instanceMap.get(nestedPostProcessTestInstanceKey).getInnermostInstance(); + assertNotNull(nestedInstance); + assertNotSame(instance, nestedInstance); + assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); + assertSame(nestedInstance, instanceMap.get(nestedBeforeAllCallbackKey).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterAllCallbackKey).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey1).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey1).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey1).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey2).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey2).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey2).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey3).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); + + Object outerInstance = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); + assertSame(outerInstance, instance); + assertSame(outerInstance, instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); + assertSame(outerInstance, instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); + + assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedBeforeEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedAfterEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedBeforeEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedAfterEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedBeforeEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedAfterEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + + assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); + assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); + assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); + } + + @Test + void instancePerMethodOnOuterTestClassWithInstancePerClassOnNestedTestClass() { + Class testClass = MixedLifecyclesOuterTestCase.class; + Class nestedTestClass = MixedLifecyclesOuterTestCase.NestedInstancePerClassTestCase.class; + int containers = 4; + int tests = 4; + Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 2), entry(nestedTestClass, 1)); + int allMethods = 1; + int eachMethods = 7; + + performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); + + String containerExecutionConditionKey = executionConditionKey(testClass, null); + String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); + String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); + String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); + String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); + String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); + String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); + String beforeAllCallbackKey = beforeAllCallbackKey(testClass); + String afterAllCallbackKey = afterAllCallbackKey(testClass); + String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); + String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); + String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); + String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); + String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); + String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.get(0)); + String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); + String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); + String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); + String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); + String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); + String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); + String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); + String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); + + // @formatter:off + assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( + containerExecutionConditionKey, + nestedTestTemplateKey, + nestedContainerExecutionConditionKey, + postProcessTestInstanceKey, + nestedPostProcessTestInstanceKey, + preDestroyCallbackTestInstanceKey, + nestedPreDestroyCallbackTestInstanceKey, + beforeAllCallbackKey, + afterAllCallbackKey, + outerTestExecutionConditionKey, + beforeEachCallbackKey, + afterEachCallbackKey, + nestedBeforeAllCallbackKey, + nestedAfterAllCallbackKey, + nestedExecutionConditionKey1, + nestedBeforeEachCallbackKey1, + nestedAfterEachCallbackKey1, + nestedExecutionConditionKey2, + nestedBeforeEachCallbackKey2, + nestedAfterEachCallbackKey2, + nestedExecutionConditionKey3, + nestedBeforeEachCallbackKey3, + nestedAfterEachCallbackKey3 + ); + // @formatter:on + + assertNull(instanceMap.get(containerExecutionConditionKey)); + assertNull(instanceMap.get(beforeAllCallbackKey)); + assertNull(instanceMap.get(afterAllCallbackKey)); + + TestInstances outerInstances = instanceMap.get(beforeEachCallbackKey); + assertSame(outerInstances, instanceMap.get(afterEachCallbackKey)); + assertSame(outerInstances, instanceMap.get(outerTestExecutionConditionKey)); + + Object nestedInstance = instanceMap.get(nestedPostProcessTestInstanceKey).getInnermostInstance(); + assertNotNull(nestedInstance); + assertNotSame(outerInstances.getInnermostInstance(), nestedInstance); + assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); + assertSame(nestedInstance, instanceMap.get(nestedBeforeAllCallbackKey).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterAllCallbackKey).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey1).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey1).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey1).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey2).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey2).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey2).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey3).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); + assertSame(nestedInstance, instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); + + // The last tracked instance stored under postProcessTestInstanceKey + // is only created in order to instantiate the nested test class. + Object outerInstance = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); + assertEquals(outerInstances.getInnermostInstance().getClass(), outerInstance.getClass()); + assertNotSame(outerInstances.getInnermostInstance(), outerInstance); + assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedBeforeEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedAfterEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedBeforeEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedAfterEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedBeforeEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + assertThat(instanceMap.get(nestedAfterEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, + nestedInstance); + + assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); + assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); + assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); + } + + private void performAssertions(Class testClass, int numContainers, int numTests, + Map.Entry, Integer>[] instanceCountEntries, int allMethods, int eachMethods) { + + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + executionResults.containerEvents().assertStatistics( + stats -> stats.started(numContainers).finished(numContainers)); + executionResults.testEvents().assertStatistics(stats -> stats.started(numTests).finished(numTests)); + + // @formatter:off + assertAll( + () -> assertThat(instanceCount).describedAs("instance count").contains(instanceCountEntries), + () -> assertEquals(allMethods, beforeAllCount, "@BeforeAll count"), + () -> assertEquals(allMethods, afterAllCount, "@AfterAll count"), + () -> assertEquals(eachMethods, beforeEachCount, "@BeforeEach count"), + () -> assertEquals(eachMethods, afterEachCount, "@AfterEach count") + ); + // @formatter:on + } + + @SafeVarargs + @SuppressWarnings("varargs") + private final Map.Entry, Integer>[] instanceCounts(Map.Entry, Integer>... entries) { + return entries; + } + + private static void incrementInstanceCount(Class testClass) { + instanceCount.compute(testClass, (key, value) -> value == null ? 1 : value + 1); + } + + private static String executionConditionKey(Class testClass, String testMethod) { + return concat(ExecutionCondition.class, testClass, testMethod); + } + + private static String postProcessTestInstanceKey(Class testClass) { + return concat(TestInstancePostProcessor.class, testClass); + } + + private static String preDestroyCallbackTestInstanceKey(Class testClass) { + return concat(TestInstancePreDestroyCallback.class, testClass); + } + + private static String beforeAllCallbackKey(Class testClass) { + return concat(BeforeAllCallback.class, testClass); + } + + private static String afterAllCallbackKey(Class testClass) { + return concat(AfterAllCallback.class, testClass); + } + + private static String beforeEachCallbackKey(Class testClass, String testMethod) { + return concat(BeforeEachCallback.class, testClass, testMethod); + } + + private static String afterEachCallbackKey(Class testClass, String testMethod) { + return concat(AfterEachCallback.class, testClass, testMethod); + } + + private static String testTemplateKey(Class testClass, String testMethod) { + return concat(TestTemplateInvocationContextProvider.class, testClass, testMethod); + } + + private static String concat(Class c1, Class c2, String str) { + return concat(c1.getSimpleName(), c2.getSimpleName(), str); + } + + private static String concat(Class c1, Class c2) { + return concat(c1.getSimpleName(), c2.getSimpleName()); + } + + private static String concat(String... args) { + return join(".", args); + } + + // ------------------------------------------------------------------------- + + @ExtendWith(InstanceTrackingExtension.class) + // The following is commented out b/c it's the default. + // @TestInstance(Lifecycle.PER_METHOD) + static class InstancePerMethodTestCase { + + InstancePerMethodTestCase() { + incrementInstanceCount(InstancePerMethodTestCase.class); + } + + @BeforeAll + static void beforeAllStatic(TestInfo testInfo) { + assertNotNull(testInfo); + beforeAllCount++; + } + + @BeforeEach + void beforeEach() { + beforeEachCount++; + } + + @Test + void test1(TestInfo testInfo) { + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @Test + void test2(TestInfo testInfo) { + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @SingletonTest + void singletonTest(TestInfo testInfo) { + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @AfterEach + void afterEach() { + afterEachCount++; + } + + @AfterAll + static void afterAllStatic(TestInfo testInfo) { + assertNotNull(testInfo); + afterAllCount++; + } + + } + + @TestInstance(Lifecycle.PER_CLASS) + static class InstancePerClassTestCase extends InstancePerMethodTestCase { + + InstancePerClassTestCase() { + incrementInstanceCount(InstancePerClassTestCase.class); + } + + @BeforeAll + void beforeAll(TestInfo testInfo) { + assertNotNull(testInfo); + beforeAllCount++; + } + + @AfterAll + void afterAll(TestInfo testInfo) { + assertNotNull(testInfo); + afterAllCount++; + } + + } + + static class SubInstancePerClassTestCase extends InstancePerClassTestCase { + SubInstancePerClassTestCase() { + incrementInstanceCount(SubInstancePerClassTestCase.class); + } + } + + @ExtendWith(InstanceTrackingExtension.class) + // The following is commented out b/c it's the default. + // @TestInstance(Lifecycle.PER_METHOD) + static class InstancePerMethodOuterTestCase { + + InstancePerMethodOuterTestCase() { + incrementInstanceCount(InstancePerMethodOuterTestCase.class); + } + + @BeforeAll + static void beforeAll(TestInfo testInfo) { + assertNotNull(testInfo); + beforeAllCount++; + } + + @Test + void outerTest() { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + } + + @AfterAll + static void afterAll(TestInfo testInfo) { + assertNotNull(testInfo); + afterAllCount++; + } + + @Nested + // The following is commented out b/c it's the default. + // @TestInstance(Lifecycle.PER_METHOD) + class NestedInstancePerMethodTestCase { + + NestedInstancePerMethodTestCase() { + incrementInstanceCount(NestedInstancePerMethodTestCase.class); + } + + @BeforeEach + void beforeEach() { + beforeEachCount++; + } + + @Test + void test1(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @Test + void test2(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @SingletonTest + void singletonTest(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @AfterEach + void afterEach() { + afterEachCount++; + } + } + } + + @ExtendWith(InstanceTrackingExtension.class) + @TestInstance(Lifecycle.PER_CLASS) + static class InstancePerClassOuterTestCase { + + InstancePerClassOuterTestCase() { + incrementInstanceCount(InstancePerClassOuterTestCase.class); + } + + @BeforeAll + static void beforeAll(TestInfo testInfo) { + assertNotNull(testInfo); + beforeAllCount++; + } + + @Test + void outerTest() { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + } + + @AfterAll + static void afterAll(TestInfo testInfo) { + assertNotNull(testInfo); + afterAllCount++; + } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + class NestedInstancePerClassTestCase { + + NestedInstancePerClassTestCase() { + incrementInstanceCount(NestedInstancePerClassTestCase.class); + } + + @BeforeAll + void beforeAll(TestInfo testInfo) { + assertNotNull(testInfo); + beforeAllCount++; + } + + @BeforeEach + void beforeEach() { + beforeEachCount++; + } + + @Test + void test1(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @Test + void test2(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @SingletonTest + void singletonTest(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @AfterEach + void afterEach() { + afterEachCount++; + } + + @AfterAll + void afterAll(TestInfo testInfo) { + assertNotNull(testInfo); + afterAllCount++; + } + } + } + + @ExtendWith(InstanceTrackingExtension.class) + // The following is commented out b/c it's the default. + // @TestInstance(Lifecycle.PER_METHOD) + static class MixedLifecyclesOuterTestCase { + + MixedLifecyclesOuterTestCase() { + incrementInstanceCount(MixedLifecyclesOuterTestCase.class); + } + + @BeforeEach + void beforeEach() { + beforeEachCount++; + } + + @Test + void outerTest() { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + } + + @AfterEach + void afterEach() { + afterEachCount++; + } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + class NestedInstancePerClassTestCase { + + NestedInstancePerClassTestCase() { + incrementInstanceCount(NestedInstancePerClassTestCase.class); + } + + @BeforeAll + void beforeAll(TestInfo testInfo) { + assertNotNull(testInfo); + beforeAllCount++; + } + + @BeforeEach + void beforeEach() { + beforeEachCount++; + } + + @Test + void test1(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @Test + void test2(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @SingletonTest + void singletonTest(TestInfo testInfo) { + assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); + testsInvoked.add(testInfo.getTestMethod().get().getName()); + } + + @AfterEach + void afterEach() { + afterEachCount++; + } + + @AfterAll + void afterAll(TestInfo testInfo) { + assertNotNull(testInfo); + afterAllCount++; + } + } + } + + // Intentionally not implementing BeforeTestExecutionCallback, AfterTestExecutionCallback, + // and TestExecutionExceptionHandler, since they are analogous to BeforeEachCallback and + // AfterEachCallback with regard to instance scope and Lifecycle. + static class InstanceTrackingExtension + implements ExecutionCondition, TestInstancePostProcessor, TestInstancePreDestroyCallback, BeforeAllCallback, + AfterAllCallback, BeforeEachCallback, AfterEachCallback, TestTemplateInvocationContextProvider { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + trackLifecycle(context); + String testMethod = context.getTestMethod().map(Method::getName).orElse(null); + if (testMethod == null) { + assertThat(context.getTestInstance()).isNotPresent(); + assertThat(instanceCount.getOrDefault(context.getRequiredTestClass(), 0)).isEqualTo(0); + } + instanceMap.put(executionConditionKey(context.getRequiredTestClass(), testMethod), + context.getTestInstances().orElse(null)); + + return ConditionEvaluationResult.enabled("enigma"); + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + trackLifecycle(context); + assertThat(context.getTestInstance()).isNotPresent(); + assertNotNull(testInstance); + instanceMap.put(postProcessTestInstanceKey(context.getRequiredTestClass()), + DefaultTestInstances.of(testInstance)); + } + + @Override + public void preDestroyTestInstance(ExtensionContext context) { + trackLifecycle(context); + assertThat(context.getTestInstance()).isPresent(); + instanceMap.put(preDestroyCallbackTestInstanceKey(context.getRequiredTestClass()), + DefaultTestInstances.of(context.getTestInstance().get())); + } + + @Override + public void beforeAll(ExtensionContext context) { + trackLifecycle(context); + instanceMap.put(beforeAllCallbackKey(context.getRequiredTestClass()), + context.getTestInstances().orElse(null)); + } + + @Override + public void afterAll(ExtensionContext context) { + trackLifecycle(context); + instanceMap.put(afterAllCallbackKey(context.getRequiredTestClass()), + context.getTestInstances().orElse(null)); + } + + @Override + public void beforeEach(ExtensionContext context) { + trackLifecycle(context); + instanceMap.put( + beforeEachCallbackKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), + context.getRequiredTestInstances()); + } + + @Override + public void afterEach(ExtensionContext context) { + trackLifecycle(context); + instanceMap.put( + afterEachCallbackKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), + context.getRequiredTestInstances()); + } + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + trackLifecycle(context); + return isAnnotated(context.getTestMethod(), SingletonTest.class); + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + trackLifecycle(context); + instanceMap.put(testTemplateKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), + context.getTestInstances().orElse(null)); + + return Stream.of(new TestTemplateInvocationContext() { + }); + } + + private static void trackLifecycle(ExtensionContext context) { + assertThat(context.getRoot().getTestInstanceLifecycle()).isEmpty(); + lifecyclesMap.computeIfAbsent(context.getRequiredTestClass(), clazz -> new ArrayList<>()).add( + context.getTestInstanceLifecycle().orElse(null)); + } + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @TestTemplate + @interface SingletonTest { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java new file mode 100644 index 00000000..5b63eee0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java @@ -0,0 +1,792 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Event; +import org.junit.platform.testkit.engine.Events; +import org.opentest4j.AssertionFailedError; + +/** + * @since 5.0 + */ +class TestTemplateInvocationTests extends AbstractJupiterTestEngineTests { + + @Test + void templateWithSingleRegisteredExtensionIsInvoked() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithSingleRegisteredExtension")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithSingleRegisteredExtension"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), + finishedWithFailure(message("invocation is expected to fail"))), // + event(container("templateWithSingleRegisteredExtension"), finishedSuccessfully()))); + } + + @Test + void parentRelationshipIsEstablished() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithSingleRegisteredExtension")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + TestDescriptor templateMethodDescriptor = findTestDescriptor(executionResults, + container("templateWithSingleRegisteredExtension")); + TestDescriptor invocationDescriptor = findTestDescriptor(executionResults, test("test-template-invocation:#1")); + assertThat(invocationDescriptor.getParent()).hasValue(templateMethodDescriptor); + } + + @Test + void beforeAndAfterEachMethodsAreExecutedAroundInvocation() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(TestTemplateTestClassWithBeforeAndAfterEach.class, "testTemplateWithTwoInvocations")).build(); + + executeTests(request); + + assertThat(TestTemplateTestClassWithBeforeAndAfterEach.lifecycleEvents).containsExactly( + "beforeAll:TestTemplateInvocationTests$TestTemplateTestClassWithBeforeAndAfterEach", "beforeEach:[1]", + "afterEach:[1]", "beforeEach:[2]", "afterEach:[2]", + "afterAll:TestTemplateInvocationTests$TestTemplateTestClassWithBeforeAndAfterEach"); + } + + @Test + void templateWithTwoRegisteredExtensionsIsInvoked() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithTwoRegisteredExtensions")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithTwoRegisteredExtensions"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1]")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), + finishedWithFailure(message("invocation is expected to fail"))), // + event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // + event(test("test-template-invocation:#2"), started()), // + event(test("test-template-invocation:#2"), + finishedWithFailure(message("invocation is expected to fail"))), // + event(container("templateWithTwoRegisteredExtensions"), finishedSuccessfully()))); + } + + @Test + void legacyReportingNames() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithTwoRegisteredExtensions")).build(); + + EngineExecutionResults results = executeTests(request); + Events events = results.allEvents(); + + events.assertStatistics(stats -> stats.dynamicallyRegistered(2)); + // events.dynamicallyRegistered().debug(); + // results.testEvents().dynamicallyRegistered().debug(); + // results.containerEvents().dynamicallyRegistered().debug(); + + // @formatter:off + Stream legacyReportingNames = events.dynamicallyRegistered() + .map(Event::getTestDescriptor) + .map(TestDescriptor::getLegacyReportingName); + // @formatter:off + assertThat(legacyReportingNames).containsExactly("templateWithTwoRegisteredExtensions()[1]", + "templateWithTwoRegisteredExtensions()[2]"); + } + + @Test + void templateWithTwoInvocationsFromSingleExtensionIsInvoked() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithTwoInvocationsFromSingleExtension")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithTwoInvocationsFromSingleExtension"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1]")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), + finishedWithFailure(message("invocation is expected to fail"))), // + event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // + event(test("test-template-invocation:#2"), started()), // + event(test("test-template-invocation:#2"), + finishedWithFailure(message("invocation is expected to fail"))), // + event(container("templateWithTwoInvocationsFromSingleExtension"), finishedSuccessfully()))); + } + + @Test + void singleInvocationIsExecutedWhenDiscoveredByUniqueId() { + UniqueId uniqueId = discoverUniqueId(MyTestTemplateTestCase.class, + "templateWithTwoInvocationsFromSingleExtension") // + .append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); + + EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithTwoInvocationsFromSingleExtension"), started()), // + event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // + event(test("test-template-invocation:#2"), started()), // + event(test("test-template-invocation:#2"), + finishedWithFailure(message("invocation is expected to fail"))), // + event(container("templateWithTwoInvocationsFromSingleExtension"), finishedSuccessfully()))); + } + + @Test + void templateWithDisabledInvocationsIsSkipped() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithDisabledInvocations")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithDisabledInvocations"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1")), // + event(test("test-template-invocation:#1"), skippedWithReason("always disabled")), // + event(container("templateWithDisabledInvocations"), finishedSuccessfully()))); + } + + @Test + void disabledTemplateIsSkipped() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "disabledTemplate")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("disabledTemplate"), skippedWithReason("always disabled")))); + } + + @Test + void templateWithCustomizedDisplayNamesIsInvoked() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithCustomizedDisplayNames")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithCustomizedDisplayNames"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1"), + displayName("1 --> templateWithCustomizedDisplayNames()")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), + finishedWithFailure(message("invocation is expected to fail"))), // + event(container("templateWithCustomizedDisplayNames"), finishedSuccessfully()))); + } + + @Test + void templateWithDynamicParameterResolverIsInvoked() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(MyTestTemplateTestCase.class, + "templateWithDynamicParameterResolver", "java.lang.String")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithDynamicParameterResolver"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1] foo")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), finishedWithFailure(message("foo"))), // + event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2] bar")), // + event(test("test-template-invocation:#2"), started()), // + event(test("test-template-invocation:#2"), finishedWithFailure(message("bar"))), // + event(container("templateWithDynamicParameterResolver"), finishedSuccessfully()))); + } + + @Test + void contextParameterResolverCanResolveConstructorArguments() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCaseWithConstructor.class, "template", "java.lang.String")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("template"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1] foo")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), finishedSuccessfully()), // + event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2] bar")), // + event(test("test-template-invocation:#2"), started()), // + event(test("test-template-invocation:#2"), finishedSuccessfully()), // + event(container("template"), finishedSuccessfully()))); + } + + @Test + void templateWithDynamicTestInstancePostProcessorIsInvoked() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithDynamicTestInstancePostProcessor")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithDynamicTestInstancePostProcessor"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), finishedWithFailure(message("foo"))), // + event(dynamicTestRegistered("test-template-invocation:#2")), // + event(test("test-template-invocation:#2"), started()), // + event(test("test-template-invocation:#2"), finishedWithFailure(message("bar"))), // + event(container("templateWithDynamicTestInstancePostProcessor"), finishedSuccessfully()))); + } + + @Test + void lifecycleCallbacksAreExecutedForInvocation() { + LauncherDiscoveryRequest request = request().selectors( + selectClass(TestTemplateTestClassWithDynamicLifecycleCallbacks.class)).build(); + + executeTests(request); + + // @formatter:off + assertThat(TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents).containsExactly( + "beforeEach", + "beforeTestExecution", + "testTemplate:foo", + "handleTestExecutionException", + "afterTestExecution", + "afterEach", + "beforeEach", + "beforeTestExecution", + "testTemplate:bar", + "afterTestExecution", + "afterEach"); + // @formatter:on + } + + @Test + void extensionIsAskedForSupportBeforeItMustProvide() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithWrongParameterType", int.class.getName())).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithWrongParameterType"), started()), // + event(container("templateWithWrongParameterType"), finishedWithFailure(message(s -> s.startsWith( + "You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [")))))); + } + + @Test + void templateWithSupportingProviderButNoInvocationsReportsFailure() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithSupportingProviderButNoInvocations")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithSupportingProviderButNoInvocations"), started()), // + event(container("templateWithSupportingProviderButNoInvocations"), + finishedWithFailure(message("None of the supporting TestTemplateInvocationContextProviders [" + + InvocationContextProviderThatSupportsEverythingButProvidesNothing.class.getSimpleName() + + "] provided a non-empty stream"))))); + } + + @Test + void templateWithCloseableStream() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithCloseableStream")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + assertThat(InvocationContextProviderWithCloseableStream.streamClosed.get()).describedAs( + "streamClosed").isTrue(); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithCloseableStream"), started()), // + event(dynamicTestRegistered("test-template-invocation:#1")), // + event(test("test-template-invocation:#1"), started()), // + event(test("test-template-invocation:#1"), finishedSuccessfully()), // + event(container("templateWithCloseableStream"), finishedSuccessfully()))); + } + + private TestDescriptor findTestDescriptor(EngineExecutionResults executionResults, Condition condition) { + // @formatter:off + return executionResults.allEvents() + .filter(condition::matches) + .findAny() + .map(Event::getTestDescriptor) + .orElseThrow(() -> new AssertionFailedError("Could not find event for condition: " + condition)); + // @formatter:on + } + + @SafeVarargs + @SuppressWarnings({ "unchecked", "varargs", "rawtypes" }) + private final Condition[] wrappedInContainerEvents(Class clazz, + Condition... wrappedConditions) { + + List> conditions = new ArrayList<>(); + conditions.add(event(engine(), started())); + conditions.add(event(container(clazz), started())); + conditions.addAll(asList(wrappedConditions)); + conditions.add(event(container(clazz), finishedSuccessfully())); + conditions.add(event(engine(), finishedSuccessfully())); + return conditions.toArray(new Condition[0]); + } + + static class MyTestTemplateTestCase { + + @TestTemplate + void templateWithoutRegisteredExtension() { + } + + @ExtendWith(SingleInvocationContextProvider.class) + @TestTemplate + void templateWithSingleRegisteredExtension() { + fail("invocation is expected to fail"); + } + + @ExtendWith({ SingleInvocationContextProvider.class, + AnotherInvocationContextProviderWithASingleInvocation.class }) + @TestTemplate + void templateWithTwoRegisteredExtensions() { + fail("invocation is expected to fail"); + } + + @ExtendWith(TwoInvocationsContextProvider.class) + @TestTemplate + void templateWithTwoInvocationsFromSingleExtension() { + fail("invocation is expected to fail"); + } + + @ExtendWith({ SingleInvocationContextProviderWithDisabledInvocations.class }) + @TestTemplate + void templateWithDisabledInvocations() { + fail("this is never called"); + } + + @ExtendWith(AlwaysDisabledExecutionCondition.class) + @TestTemplate + void disabledTemplate() { + fail("this is never called"); + } + + @ExtendWith(InvocationContextProviderWithCustomizedDisplayNames.class) + @TestTemplate + void templateWithCustomizedDisplayNames() { + fail("invocation is expected to fail"); + } + + @ExtendWith(StringParameterResolvingInvocationContextProvider.class) + @TestTemplate + void templateWithDynamicParameterResolver(String parameter) { + fail(parameter); + } + + @ExtendWith(StringParameterResolvingInvocationContextProvider.class) + @TestTemplate + void templateWithWrongParameterType(int parameter) { + fail("never called: " + parameter); + } + + String parameterInstanceVariable; + + @ExtendWith(StringParameterInjectingInvocationContextProvider.class) + @TestTemplate + void templateWithDynamicTestInstancePostProcessor() { + fail(parameterInstanceVariable); + } + + @ExtendWith(InvocationContextProviderThatSupportsEverythingButProvidesNothing.class) + @TestTemplate + void templateWithSupportingProviderButNoInvocations() { + fail("never called"); + } + + @ExtendWith(InvocationContextProviderWithCloseableStream.class) + @TestTemplate + void templateWithCloseableStream() { + } + } + + @ExtendWith(StringParameterResolvingInvocationContextProvider.class) + static class MyTestTemplateTestCaseWithConstructor { + + private final String constructorParameter; + + MyTestTemplateTestCaseWithConstructor(String constructorParameter) { + this.constructorParameter = constructorParameter; + } + + @TestTemplate + void template(String parameter) { + assertEquals(constructorParameter, parameter); + } + } + + static class TestTemplateTestClassWithBeforeAndAfterEach { + + private static List lifecycleEvents = new ArrayList<>(); + + @BeforeAll + static void beforeAll(TestInfo testInfo) { + lifecycleEvents.add("beforeAll:" + testInfo.getDisplayName()); + } + + @AfterAll + static void afterAll(TestInfo testInfo) { + lifecycleEvents.add("afterAll:" + testInfo.getDisplayName()); + } + + @BeforeEach + void beforeEach(TestInfo testInfo) { + lifecycleEvents.add("beforeEach:" + testInfo.getDisplayName()); + } + + @AfterEach + void afterEach(TestInfo testInfo) { + lifecycleEvents.add("afterEach:" + testInfo.getDisplayName()); + } + + @ExtendWith(TwoInvocationsContextProvider.class) + @TestTemplate + void testTemplateWithTwoInvocations() { + fail("invocation is expected to fail"); + } + } + + static class TestTemplateTestClassWithDynamicLifecycleCallbacks { + + private static List lifecycleEvents = new ArrayList<>(); + + @ExtendWith(InvocationContextProviderWithDynamicLifecycleCallbacks.class) + @TestTemplate + void testTemplate(TestInfo testInfo) { + lifecycleEvents.add("testTemplate:" + testInfo.getDisplayName()); + assertEquals("bar", testInfo.getDisplayName()); + } + } + + private static class SingleInvocationContextProvider implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(emptyTestTemplateInvocationContext()); + } + } + + private static class SingleInvocationContextProviderWithDisabledInvocations + extends SingleInvocationContextProvider { + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(new TestTemplateInvocationContext() { + @Override + public List getAdditionalExtensions() { + return singletonList(new AlwaysDisabledExecutionCondition()); + } + }); + } + } + + private static class AnotherInvocationContextProviderWithASingleInvocation + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(emptyTestTemplateInvocationContext()); + } + } + + private static class TwoInvocationsContextProvider implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext()); + } + } + + private static class AlwaysDisabledExecutionCondition implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return ConditionEvaluationResult.disabled("always disabled"); + } + } + + private static class InvocationContextProviderWithCustomizedDisplayNames + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream. generate(() -> new TestTemplateInvocationContext() { + + @Override + public String getDisplayName(int invocationIndex) { + return invocationIndex + " --> " + context.getDisplayName(); + } + }).limit(1); + } + } + + private static class StringParameterResolvingInvocationContextProvider + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + // @formatter:off + return context.getTestMethod() + .map(Method::getParameterTypes) + .map(Arrays::stream) + .map(parameters -> parameters.anyMatch(Predicate.isEqual(String.class))) + .orElse(false); + // @formatter:on + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(createContext("foo"), createContext("bar")); + } + + private TestTemplateInvocationContext createContext(String argument) { + return new TestTemplateInvocationContext() { + + @Override + public String getDisplayName(int invocationIndex) { + return TestTemplateInvocationContext.super.getDisplayName(invocationIndex) + " " + argument; + } + + @Override + public List getAdditionalExtensions() { + return singletonList(new ParameterResolver() { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return true; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return argument; + } + }); + } + }; + } + } + + private static class StringParameterInjectingInvocationContextProvider + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(createContext("foo"), createContext("bar")); + } + + private TestTemplateInvocationContext createContext(String argument) { + return new TestTemplateInvocationContext() { + + @Override + public String getDisplayName(int invocationIndex) { + return TestTemplateInvocationContext.super.getDisplayName(invocationIndex) + " " + argument; + } + + @Override + public List getAdditionalExtensions() { + return singletonList((TestInstancePostProcessor) (testInstance, context) -> { + Field field = testInstance.getClass().getDeclaredField("parameterInstanceVariable"); + field.setAccessible(true); + field.set(testInstance, argument); + }); + } + }; + } + } + + private static class InvocationContextProviderWithDynamicLifecycleCallbacks + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(createContext("foo"), createContext("bar")); + } + + private TestTemplateInvocationContext createContext(String argument) { + return new TestTemplateInvocationContext() { + + @Override + public String getDisplayName(int invocationIndex) { + return argument; + } + + @Override + public List getAdditionalExtensions() { + return singletonList(new LifecycleCallbackExtension()); + } + }; + } + + private static class LifecycleCallbackExtension implements BeforeEachCallback, BeforeTestExecutionCallback, + TestExecutionExceptionHandler, AfterTestExecutionCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("beforeEach"); + } + + @Override + public void beforeTestExecution(ExtensionContext context) { + TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("beforeTestExecution"); + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { + TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("handleTestExecutionException"); + throw new AssertionError(throwable); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("afterTestExecution"); + } + + @Override + public void afterEach(ExtensionContext context) { + TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("afterEach"); + } + } + } + + private static class InvocationContextProviderThatSupportsEverythingButProvidesNothing + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.empty(); + } + } + + private static class InvocationContextProviderWithCloseableStream implements TestTemplateInvocationContextProvider { + + private static AtomicBoolean streamClosed = new AtomicBoolean(false); + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(emptyTestTemplateInvocationContext()).onClose(() -> streamClosed.set(true)); + } + } + + private static TestTemplateInvocationContext emptyTestTemplateInvocationContext() { + return new TestTemplateInvocationContext() { + }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java new file mode 100644 index 00000000..0bc75500 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @since 5.0 + */ +@ExtendWith(NumberResolver.class) +abstract class AbstractNonGenericTests { + + @Test + void mA() { + BridgeMethodTests.sequence.add("mA()"); + } + + @Test + void test(Number value) { + BridgeMethodTests.sequence.add("A.test(Number)"); + Assertions.assertEquals(42, value); + } + + static class B extends AbstractNonGenericTests { + + @Test + void mB() { + BridgeMethodTests.sequence.add("mB()"); + } + + @Test + void test(Byte value) { + BridgeMethodTests.sequence.add("B.test(Byte)"); + Assertions.assertEquals(123, value.intValue()); + } + + } + + static class C extends B { + + @Test + void mC() { + BridgeMethodTests.sequence.add("mC()"); + } + + @Override + @Test + void test(Byte value) { + BridgeMethodTests.sequence.add("C.test(Byte)"); + Assertions.assertEquals(123, value.intValue()); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java new file mode 100644 index 00000000..afecac33 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @since 5.0 + */ +@ExtendWith(NumberResolver.class) +abstract class AbstractNumberTests { + + @Test + void test(N number) { + BridgeMethodTests.sequence.add("test(N)"); + assertNotNull(number); + assertEquals(123, number.intValue()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java new file mode 100644 index 00000000..13150e88 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.bridge.NumberTestGroup.ByteTestCase; +import org.junit.jupiter.engine.bridge.NumberTestGroup.ShortTestCase; + +/** + * @since 5.0 + */ +class BridgeMethodTests extends AbstractJupiterTestEngineTests { + + static List sequence = new ArrayList<>(); + + @Test + void childrenHaveBridgeMethods() throws Exception { + assertFalse(ChildWithBridgeMethods.class.getMethod("anotherBeforeEach").isBridge()); + assertFalse(ChildWithBridgeMethods.class.getMethod("anotherAfterEach").isBridge()); + assertTrue(ChildWithBridgeMethods.class.getMethod("beforeEach").isBridge()); + assertTrue(ChildWithBridgeMethods.class.getMethod("afterEach").isBridge()); + + assertTrue(ByteTestCase.class.getDeclaredMethod("test", Number.class).isBridge()); + assertFalse(ByteTestCase.class.getDeclaredMethod("test", Byte.class).isBridge()); + + assertTrue(ShortTestCase.class.getDeclaredMethod("test", Number.class).isBridge()); + assertFalse(ShortTestCase.class.getDeclaredMethod("test", Short.class).isBridge()); + } + + @Test + void childHasNoBridgeMethods() throws Exception { + assertFalse(ChildWithoutBridgeMethods.class.getMethod("anotherBeforeEach").isBridge()); + assertFalse(ChildWithoutBridgeMethods.class.getMethod("anotherAfterEach").isBridge()); + assertFalse(ChildWithoutBridgeMethods.class.getMethod("beforeEach").isBridge()); + assertFalse(ChildWithoutBridgeMethods.class.getMethod("afterEach").isBridge()); + } + + @Test + void compareMethodExecutionSequenceOrder() { + String withoutBridgeMethods = execute(1, ChildWithoutBridgeMethods.class); + String withBridgeMethods = execute(1, ChildWithBridgeMethods.class); + assertEquals(withoutBridgeMethods, withBridgeMethods); + } + + @TestFactory + List ensureSingleTestMethodsExecute() { + return Arrays.asList( // + dynamicTest("Byte", // + () -> assertEquals("[test(Byte) BEGIN, test(N), test(Byte) END, test(Long) BEGIN, test(Long) END]", // + execute(2, ByteTestCase.class))), + dynamicTest("Short", // + () -> assertEquals("[test(Long) BEGIN, test(Long) END, test(Short) BEGIN, test(N), test(Short) END]", // + execute(2, ShortTestCase.class)))); + } + + @Test + void inheritedNonGenericMethodsAreExecuted() { + String b = execute(4, AbstractNonGenericTests.B.class); + assertAll("Missing expected test(s) in sequence: " + b, // + () -> assertTrue(b.contains("A.test(Number)")), // + () -> assertTrue(b.contains("mA()")), // + () -> assertTrue(b.contains("mB()")), // + () -> assertTrue(b.contains("B.test(Byte)")) // + ); + String c = execute(5, AbstractNonGenericTests.C.class); + assertAll("Missing expected test(s) in sequence: " + c, // + () -> assertTrue(c.contains("A.test(Number)")), // + () -> assertTrue(c.contains("mA()")), // + () -> assertTrue(c.contains("mB()")), // + () -> assertTrue(c.contains("mC()")), // + () -> assertTrue(c.contains("C.test(Byte)")) // + ); + } + + private String execute(int expectedTestFinishedCount, Class testClass) { + sequence.clear(); + executeTestsForClass(testClass).testEvents()// + .assertStatistics(stats -> stats.started(expectedTestFinishedCount)); + return sequence.toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java new file mode 100644 index 00000000..bd3cb864 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +// modifier "public" is necessary for creating bridge methods by the compiler +public class ChildWithBridgeMethods extends PackagePrivateParent { + + @BeforeEach + public void anotherBeforeEach() { + BridgeMethodTests.sequence.add("child.anotherBeforeEach()"); + } + + @Test + void test() { + BridgeMethodTests.sequence.add("child.test()"); + } + + @AfterEach + public void anotherAfterEach() { + BridgeMethodTests.sequence.add("child.anotherAfterEach()"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java new file mode 100644 index 00000000..2a516912 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +// modifier "public" is *not* present for not creating bridge methods by the compiler +class ChildWithoutBridgeMethods extends PackagePrivateParent { + + @BeforeEach + public void anotherBeforeEach() { + BridgeMethodTests.sequence.add("child.anotherBeforeEach()"); + } + + @Test + void test() { + BridgeMethodTests.sequence.add("child.test()"); + } + + @AfterEach + public void anotherAfterEach() { + BridgeMethodTests.sequence.add("child.anotherAfterEach()"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java new file mode 100644 index 00000000..114d6a61 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @since 5.0 + */ +class NumberResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + + return Number.class.isAssignableFrom(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + + Class type = parameterContext.getParameter().getType(); + if (type == Number.class) { + return 42; + } + try { + return type.getMethod("valueOf", String.class).invoke(null, "123"); + } + catch (Exception e) { + throw new AssertionError("Could not resolve number type: " + type, e); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java new file mode 100644 index 00000000..5f2146f6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +interface NumberTestGroup { + + class ByteTestCase extends AbstractNumberTests { + + @Test + @Override + void test(Byte number) { + BridgeMethodTests.sequence.add("test(Byte) BEGIN"); + super.test(number); + BridgeMethodTests.sequence.add("test(Byte) END"); + } + + @Test + void test(Long number) { + BridgeMethodTests.sequence.add("test(Long) BEGIN"); + assertNotNull(number); + assertEquals(123, number.intValue()); + BridgeMethodTests.sequence.add("test(Long) END"); + } + } + + class ShortTestCase extends AbstractNumberTests { + + @Test + @Override + void test(Short number) { + BridgeMethodTests.sequence.add("test(Short) BEGIN"); + super.test(number); + BridgeMethodTests.sequence.add("test(Short) END"); + } + + @Test + void test(Long number) { + BridgeMethodTests.sequence.add("test(Long) BEGIN"); + assertNotNull(number); + assertEquals(123, number.intValue()); + BridgeMethodTests.sequence.add("test(Long) END"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java new file mode 100644 index 00000000..909139a0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.bridge; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +/** + * @since 5.0 + */ +class PackagePrivateParent { + + @BeforeAll + static void beforeAll() { + BridgeMethodTests.sequence.add("static parent.beforeAll()"); + } + + @AfterAll + static void afterAll() { + BridgeMethodTests.sequence.add("static parent.afterAll()"); + } + + @BeforeEach + public void beforeEach() { + BridgeMethodTests.sequence.add("parent.beforeEach()"); + } + + @AfterEach + public void afterEach() { + BridgeMethodTests.sequence.add("parent.afterEach()"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java new file mode 100644 index 00000000..cb4f88f7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.io.CleanupMode.NEVER; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.function.Predicate; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; + +/** + * Unit tests for {@link CachingJupiterConfiguration}. + */ +class CachingJupiterConfigurationTests { + + private final JupiterConfiguration delegate = mock(); + private final JupiterConfiguration cache = new CachingJupiterConfiguration(delegate); + + @Test + void cachesDefaultExecutionMode() { + when(delegate.getDefaultExecutionMode()).thenReturn(ExecutionMode.CONCURRENT); + + assertThat(cache.getDefaultExecutionMode()).isEqualTo(ExecutionMode.CONCURRENT); + assertThat(cache.getDefaultExecutionMode()).isEqualTo(ExecutionMode.CONCURRENT); + + verify(delegate, only()).getDefaultExecutionMode(); + } + + @Test + void cachesDefaultTestInstanceLifecycle() { + when(delegate.getDefaultTestInstanceLifecycle()).thenReturn(Lifecycle.PER_CLASS); + + assertThat(cache.getDefaultTestInstanceLifecycle()).isEqualTo(Lifecycle.PER_CLASS); + assertThat(cache.getDefaultTestInstanceLifecycle()).isEqualTo(Lifecycle.PER_CLASS); + + verify(delegate, only()).getDefaultTestInstanceLifecycle(); + } + + @Test + void cachesExecutionConditionFilter() { + Predicate predicate = executionCondition -> true; + when(delegate.getExecutionConditionFilter()).thenReturn(predicate); + + assertThat(cache.getExecutionConditionFilter()).isSameAs(predicate); + assertThat(cache.getExecutionConditionFilter()).isSameAs(predicate); + + verify(delegate, only()).getExecutionConditionFilter(); + } + + @Test + void cachesExtensionAutoDetectionEnabled() { + when(delegate.isExtensionAutoDetectionEnabled()).thenReturn(true); + + assertThat(cache.isExtensionAutoDetectionEnabled()).isTrue(); + assertThat(cache.isExtensionAutoDetectionEnabled()).isTrue(); + + verify(delegate, only()).isExtensionAutoDetectionEnabled(); + } + + @Test + void cachesParallelExecutionEnabled() { + when(delegate.isParallelExecutionEnabled()).thenReturn(true); + + assertThat(cache.isParallelExecutionEnabled()).isTrue(); + assertThat(cache.isParallelExecutionEnabled()).isTrue(); + + verify(delegate, only()).isParallelExecutionEnabled(); + } + + @Test + void cachesDefaultDisplayNameGenerator() { + CustomDisplayNameGenerator customDisplayNameGenerator = new CustomDisplayNameGenerator(); + when(delegate.getDefaultDisplayNameGenerator()).thenReturn(customDisplayNameGenerator); + + // call `cache.getDefaultDisplayNameGenerator()` twice to verify the delegate method is called only once. + assertThat(cache.getDefaultDisplayNameGenerator()).isSameAs(customDisplayNameGenerator); + assertThat(cache.getDefaultDisplayNameGenerator()).isSameAs(customDisplayNameGenerator); + + verify(delegate, only()).getDefaultDisplayNameGenerator(); + } + + @Test + void cachesDefaultTestMethodOrderer() { + final Optional methodOrderer = Optional.of(new MethodOrderer.MethodName()); + when(delegate.getDefaultTestMethodOrderer()).thenReturn(methodOrderer); + + // call `cache.getDefaultTestMethodOrderer()` twice to verify the delegate method is called only once. + assertThat(cache.getDefaultTestMethodOrderer()).isSameAs(methodOrderer); + assertThat(cache.getDefaultTestMethodOrderer()).isSameAs(methodOrderer); + + verify(delegate, only()).getDefaultTestMethodOrderer(); + } + + @Test + void cachesDefaultTempDirCleanupMode() { + when(delegate.getDefaultTempDirCleanupMode()).thenReturn(NEVER); + + // call `cache.getDefaultTempStrategyDirCleanupMode()` twice to verify the delegate method is called only once. + assertThat(cache.getDefaultTempDirCleanupMode()).isSameAs(NEVER); + assertThat(cache.getDefaultTempDirCleanupMode()).isSameAs(NEVER); + + verify(delegate, only()).getDefaultTempDirCleanupMode(); + } + + @Test + void doesNotCacheRawParameters() { + when(delegate.getRawConfigurationParameter("foo")).thenReturn(Optional.of("bar")).thenReturn( + Optional.of("baz")); + + assertThat(cache.getRawConfigurationParameter("foo")).contains("bar"); + assertThat(cache.getRawConfigurationParameter("foo")).contains("baz"); + + verify(delegate, times(2)).getRawConfigurationParameter("foo"); + verifyNoMoreInteractions(delegate); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java new file mode 100644 index 00000000..2592fe25 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.engine.Constants; +import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.ConfigurationParameters; + +class DefaultJupiterConfigurationTests { + + private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; + + @Test + void getDefaultTestInstanceLifecyclePreconditions() { + PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, + () -> new DefaultJupiterConfiguration(null)); + assertThat(exception).hasMessage("ConfigurationParameters must not be null"); + } + + @Test + void getDefaultTestInstanceLifecycleWithNoConfigParamSet() { + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + Lifecycle lifecycle = configuration.getDefaultTestInstanceLifecycle(); + assertThat(lifecycle).isEqualTo(PER_METHOD); + } + + @Test + void getDefaultTempDirCleanupModeWithNoConfigParamSet() { + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + CleanupMode cleanupMode = configuration.getDefaultTempDirCleanupMode(); + assertThat(cleanupMode).isEqualTo(ALWAYS); + } + + @Test + void getDefaultTestInstanceLifecycleWithConfigParamSet() { + assertAll(// + () -> assertDefaultConfigParam(null, PER_METHOD), // + () -> assertDefaultConfigParam("", PER_METHOD), // + () -> assertDefaultConfigParam("bogus", PER_METHOD), // + () -> assertDefaultConfigParam(PER_METHOD.name(), PER_METHOD), // + () -> assertDefaultConfigParam(PER_METHOD.name().toLowerCase(), PER_METHOD), // + () -> assertDefaultConfigParam(" " + PER_METHOD.name() + " ", PER_METHOD), // + () -> assertDefaultConfigParam(PER_CLASS.name(), PER_CLASS), // + () -> assertDefaultConfigParam(PER_CLASS.name().toLowerCase(), PER_CLASS), // + () -> assertDefaultConfigParam(" " + PER_CLASS.name() + " ", Lifecycle.PER_CLASS) // + ); + } + + @Test + void shouldGetDefaultDisplayNameGeneratorWithConfigParamSet() { + ConfigurationParameters parameters = mock(); + String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; + when(parameters.get(key)).thenReturn(Optional.of(CustomDisplayNameGenerator.class.getName())); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + + DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); + + assertThat(defaultDisplayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); + } + + @Test + void shouldGetStandardAsDefaultDisplayNameGeneratorWithoutConfigParamSet() { + ConfigurationParameters parameters = mock(); + String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; + when(parameters.get(key)).thenReturn(Optional.empty()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + + DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); + + assertThat(defaultDisplayNameGenerator).isInstanceOf(DisplayNameGenerator.Standard.class); + } + + @Test + void shouldGetNothingAsDefaultTestMethodOrderWithoutConfigParamSet() { + ConfigurationParameters parameters = mock(); + String key = Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; + when(parameters.get(key)).thenReturn(Optional.empty()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + + final Optional defaultTestMethodOrder = configuration.getDefaultTestMethodOrderer(); + + assertThat(defaultTestMethodOrder).isEmpty(); + } + + private void assertDefaultConfigParam(String configValue, Lifecycle expected) { + ConfigurationParameters configParams = mock(); + when(configParams.get(KEY)).thenReturn(Optional.ofNullable(configValue)); + Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams).getDefaultTestInstanceLifecycle(); + assertThat(lifecycle).isEqualTo(expected); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java new file mode 100644 index 00000000..5848cda9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.engine.Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * @since 5.5 + */ +@TrackLogRecords +class InstantiatingConfigurationParameterConverterTests { + + private static final String KEY = DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; + + @Test + void shouldInstantiateConfiguredClass(LogRecordListener listener) { + + ConfigurationParameters configurationParameters = mock(); + when(configurationParameters.get(KEY)).thenReturn(Optional.of(CustomDisplayNameGenerator.class.getName())); + + InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( + DisplayNameGenerator.class, "display name generator"); + DisplayNameGenerator displayNameGenerator = converter.get(configurationParameters, KEY).orElseThrow(); + + assertThat(displayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); + assertExpectedLogMessage(listener, Level.CONFIG, + "Using default display name generator " + + "'org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator' set via the " + + "'junit.jupiter.displayname.generator.default' configuration parameter."); + } + + @Test + void shouldReturnEmptyOptionalIfNoConfigurationFound() { + ConfigurationParameters configurationParameters = mock(); + when(configurationParameters.get(KEY)).thenReturn(Optional.empty()); + + InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( + DisplayNameGenerator.class, "display name generator"); + Optional displayNameGenerator = converter.get(configurationParameters, KEY); + + assertThat(displayNameGenerator).isEmpty(); + } + + @Test + void shouldReturnEmptyOptionalIfConfigurationIsBlank() { + ConfigurationParameters configurationParameters = mock(); + when(configurationParameters.get(KEY)).thenReturn(Optional.of("")); + + InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( + DisplayNameGenerator.class, "display name generator"); + Optional displayNameGenerator = converter.get(configurationParameters, KEY); + + assertThat(displayNameGenerator).isEmpty(); + } + + @Test + void shouldTrimAndInstantiateConfiguredClass(LogRecordListener listener) { + ConfigurationParameters configurationParameters = mock(); + String classNameWithSpaces = " " + CustomDisplayNameGenerator.class.getName() + " "; + when(configurationParameters.get(KEY)).thenReturn(Optional.of(classNameWithSpaces)); + + InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( + DisplayNameGenerator.class, "display name generator"); + DisplayNameGenerator displayNameGenerator = converter.get(configurationParameters, KEY).orElseThrow(); + + assertThat(displayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); + assertExpectedLogMessage(listener, Level.CONFIG, + "Using default display name generator " + + "'org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator' set via the " + + "'junit.jupiter.displayname.generator.default' configuration parameter."); + } + + @Test + void shouldReturnEmptyOptionalIfNoClassFound(LogRecordListener listener) { + ConfigurationParameters configurationParameters = mock(); + when(configurationParameters.get(KEY)).thenReturn(Optional.of("random-string")); + + InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( + DisplayNameGenerator.class, "display name generator"); + Optional displayNameGenerator = converter.get(configurationParameters, KEY); + + assertThat(displayNameGenerator).isEmpty(); + assertExpectedLogMessage(listener, Level.WARNING, + "Failed to load default display name generator " + + "class 'random-string' set via the 'junit.jupiter.displayname.generator.default' " + + "configuration parameter. Falling back to default behavior."); + } + + @Test + void shouldReturnEmptyOptionalIfClassFoundIsNotATypeOfExpectedType(LogRecordListener listener) { + ConfigurationParameters configurationParameters = mock(); + when(configurationParameters.get(KEY)).thenReturn(Optional.of(Object.class.getName())); + + InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( + DisplayNameGenerator.class, "display name generator"); + Optional displayNameGenerator = converter.get(configurationParameters, KEY); + + assertThat(displayNameGenerator).isEmpty(); + assertExpectedLogMessage(listener, Level.WARNING, + "Failed to load default display name generator class 'java.lang.Object' " + + "set via the 'junit.jupiter.displayname.generator.default' configuration parameter. " + + "Falling back to default behavior."); + } + + @Test + void shouldReturnEmptyOptionalIfClassNameIsNotFullyQualified(LogRecordListener listener) { + ConfigurationParameters configurationParameters = mock(); + when(configurationParameters.get(KEY)).thenReturn( + Optional.of(CustomDisplayNameGenerator.class.getSimpleName())); + + InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( + DisplayNameGenerator.class, "display name generator"); + Optional displayNameGenerator = converter.get(configurationParameters, KEY); + + assertThat(displayNameGenerator).isEmpty(); + assertExpectedLogMessage(listener, Level.WARNING, + "Failed to load default display name generator class 'CustomDisplayNameGenerator' " + + "set via the 'junit.jupiter.displayname.generator.default' configuration parameter. " + + "Falling back to default behavior."); + } + + private void assertExpectedLogMessage(LogRecordListener listener, Level level, String expectedMessage) { + // @formatter:off + assertTrue(listener.stream(level) + .map(LogRecord::getMessage) + .anyMatch(expectedMessage::equals)); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java new file mode 100644 index 00000000..40ef487c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.DisplayNameGenerator; + +public class CustomDisplayNameGenerator implements DisplayNameGenerator { + + @Override + public String generateDisplayNameForClass(Class testClass) { + return "class-display-name"; + } + + @Override + public String generateDisplayNameForNestedClass(Class nestedClass) { + return "nested-class-display-name"; + } + + @Override + public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + return "method-display-name"; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java new file mode 100644 index 00000000..022f93af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java @@ -0,0 +1,191 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.logging.LogRecordListener; + +/** + * Unit tests for {@link DisplayNameUtils}. + * + * @since 5.5 + */ +class DisplayNameUtilsTests { + + @Nested + class ClassDisplayNameTests { + + @Test + void shouldGetDisplayNameFromDisplayNameAnnotation() { + + String displayName = DisplayNameUtils.determineDisplayName(MyTestCase.class, () -> "default-name"); + + assertThat(displayName).isEqualTo("my-test-case"); + } + + @Test + @TrackLogRecords + void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationWithBlankStringPresent( + LogRecordListener listener) { + + String displayName = DisplayNameUtils.determineDisplayName(BlankDisplayNameTestCase.class, + () -> "default-name"); + + assertThat(displayName).isEqualTo("default-name"); + assertThat(firstWarningLogRecord(listener).getMessage()).isEqualTo( + "Configuration error: @DisplayName on [class org.junit.jupiter.engine.descriptor.DisplayNameUtilsTests$BlankDisplayNameTestCase] must be declared with a non-empty value."); + } + + @Test + void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationPresent() { + + String displayName = DisplayNameUtils.determineDisplayName(NotDisplayNameTestCase.class, + () -> "default-name"); + + assertThat(displayName).isEqualTo("default-name"); + } + + @Nested + class ClassDisplayNameSupplierTests { + + private JupiterConfiguration configuration = mock(); + + @Test + void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass( + StandardDisplayNameTestCase.class, configuration); + + String name = StandardDisplayNameTestCase.class.getName(); + String expectedClassName = name.substring(name.lastIndexOf(".") + 1); + assertThat(displayName.get()).isEqualTo(expectedClassName); + } + + @Test + void shouldGetUnderscoreDisplayNameFromDisplayNameGenerationAnnotation() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass( + Underscore_DisplayName_TestCase.class, configuration); + + assertThat(displayName.get()).isEqualTo("DisplayNameUtilsTests$Underscore DisplayName TestCase"); + } + + @Test + void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass(MyTestCase.class, + configuration); + + assertThat(displayName.get()).isEqualTo("class-display-name"); + } + } + } + + @Nested + class NestedClassDisplayNameTests { + + private JupiterConfiguration configuration = mock(); + + @Test + void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + StandardDisplayNameTestCase.class, configuration); + + assertThat(displayName.get()).isEqualTo(StandardDisplayNameTestCase.class.getSimpleName()); + } + + @Test + void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + NestedTestCase.class, configuration); + + assertThat(displayName.get()).isEqualTo("nested-class-display-name"); + } + } + + @Nested + class MethodDisplayNameTests { + + private JupiterConfiguration configuration = mock(); + + @Test + void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() throws Exception { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + Method method = MyTestCase.class.getDeclaredMethod("test1"); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(StandardDisplayNameTestCase.class, + method, configuration); + + assertThat(displayName).isEqualTo("test1()"); + } + + @Test + void shouldGetDisplayNameFromDefaultNameGenerator() throws Exception { + Method method = MyTestCase.class.getDeclaredMethod("test1"); + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + + String displayName = DisplayNameUtils.determineDisplayNameForMethod(NotDisplayNameTestCase.class, method, + configuration); + + assertThat(displayName).isEqualTo("method-display-name"); + } + } + + private LogRecord firstWarningLogRecord(LogRecordListener listener) throws AssertionError { + return listener.stream(DisplayNameUtils.class, Level.WARNING).findFirst().orElseThrow( + () -> new AssertionError("Failed to find warning log record")); + } + + @DisplayName("my-test-case") + @DisplayNameGeneration(value = CustomDisplayNameGenerator.class) + static class MyTestCase { + + void test1() { + } + + } + + @DisplayName("") + static class BlankDisplayNameTestCase { + } + + @DisplayNameGeneration(value = DisplayNameGenerator.Standard.class) + static class StandardDisplayNameTestCase { + } + + @DisplayNameGeneration(value = DisplayNameGenerator.ReplaceUnderscores.class) + static class Underscore_DisplayName_TestCase { + } + + static class NotDisplayNameTestCase { + } + + @Nested + class NestedTestCase { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java new file mode 100644 index 00000000..af140f29 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java @@ -0,0 +1,345 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.DefaultTestInstances; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + +/** + * Unit tests for concrete implementations of {@link ExtensionContext}: + * {@link JupiterEngineExtensionContext}, {@link ClassExtensionContext}, and + * {@link MethodExtensionContext}. + * + * @since 5.0 + * @see org.junit.jupiter.engine.execution.ExtensionValuesStoreTests + */ +public class ExtensionContextTests { + + private final JupiterConfiguration configuration = mock(); + + @BeforeEach + void setUp() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); + when(configuration.getDefaultClassesExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); + } + + @Test + void fromJupiterEngineDescriptor() { + JupiterEngineDescriptor engineTestDescriptor = new JupiterEngineDescriptor( + UniqueId.root("engine", "junit-jupiter"), configuration); + + JupiterEngineExtensionContext engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor, + configuration, null); + + // @formatter:off + assertAll("engineContext", + () -> assertThat(engineContext.getElement()).isEmpty(), + () -> assertThat(engineContext.getTestClass()).isEmpty(), + () -> assertThat(engineContext.getTestInstance()).isEmpty(), + () -> assertThat(engineContext.getTestMethod()).isEmpty(), + () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestClass()), + () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestInstance()), + () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestMethod()), + () -> assertThat(engineContext.getDisplayName()).isEqualTo(engineTestDescriptor.getDisplayName()), + () -> assertThat(engineContext.getParent()).isEmpty(), + () -> assertThat(engineContext.getRoot()).isSameAs(engineContext), + () -> assertThat(engineContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) + ); + // @formatter:on + } + + @Test + @SuppressWarnings("resource") + void fromClassTestDescriptor() { + NestedClassTestDescriptor nestedClassDescriptor = nestedClassDescriptor(); + ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); + + ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, + configuration, null, null); + + // @formatter:off + assertAll("outerContext", + () -> assertThat(outerExtensionContext.getElement()).contains(OuterClass.class), + () -> assertThat(outerExtensionContext.getTestClass()).contains(OuterClass.class), + () -> assertThat(outerExtensionContext.getTestInstance()).isEmpty(), + () -> assertThat(outerExtensionContext.getTestMethod()).isEmpty(), + () -> assertThat(outerExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), + () -> assertThrows(PreconditionViolationException.class, () -> outerExtensionContext.getRequiredTestInstance()), + () -> assertThrows(PreconditionViolationException.class, () -> outerExtensionContext.getRequiredTestMethod()), + () -> assertThat(outerExtensionContext.getDisplayName()).isEqualTo(outerClassDescriptor.getDisplayName()), + () -> assertThat(outerExtensionContext.getParent()).isEmpty(), + () -> assertThat(outerExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) + ); + // @formatter:on + + ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, + nestedClassDescriptor, configuration, null, null); + assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext); + } + + @Test + @SuppressWarnings("resource") + void tagsCanBeRetrievedInExtensionContext() { + NestedClassTestDescriptor nestedClassDescriptor = nestedClassDescriptor(); + ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); + TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); + outerClassDescriptor.addChild(methodTestDescriptor); + + ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, + configuration, null, null); + + assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag"); + assertThat(outerExtensionContext.getRoot()).isSameAs(outerExtensionContext); + + ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, + nestedClassDescriptor, configuration, null, null); + assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag"); + assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext); + + MethodExtensionContext methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null, + methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null); + methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); + assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag"); + assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext); + } + + @Test + @SuppressWarnings("resource") + void fromMethodTestDescriptor() { + TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); + ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor); + JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(UniqueId.forEngine("junit-jupiter"), + configuration); + engineDescriptor.addChild(classTestDescriptor); + + Object testInstance = new OuterClass(); + Method testMethod = methodTestDescriptor.getTestMethod(); + + JupiterEngineExtensionContext engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, + configuration, null); + ClassExtensionContext classExtensionContext = new ClassExtensionContext(engineExtensionContext, null, + classTestDescriptor, configuration, null, null); + MethodExtensionContext methodExtensionContext = new MethodExtensionContext(classExtensionContext, null, + methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null); + methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance)); + + // @formatter:off + assertAll("methodContext", + () -> assertThat(methodExtensionContext.getElement()).contains(testMethod), + () -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClass.class), + () -> assertThat(methodExtensionContext.getTestInstance()).contains(testInstance), + () -> assertThat(methodExtensionContext.getTestMethod()).contains(testMethod), + () -> assertThat(methodExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), + () -> assertThat(methodExtensionContext.getRequiredTestInstance()).isEqualTo(testInstance), + () -> assertThat(methodExtensionContext.getRequiredTestMethod()).isEqualTo(testMethod), + () -> assertThat(methodExtensionContext.getDisplayName()).isEqualTo(methodTestDescriptor.getDisplayName()), + () -> assertThat(methodExtensionContext.getParent()).contains(classExtensionContext), + () -> assertThat(methodExtensionContext.getRoot()).isSameAs(engineExtensionContext), + () -> assertThat(methodExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) + ); + // @formatter:on + } + + @Test + @SuppressWarnings("resource") + void reportEntriesArePublishedToExecutionContext() { + ClassTestDescriptor classTestDescriptor = outerClassDescriptor(null); + EngineExecutionListener engineExecutionListener = Mockito.spy(EngineExecutionListener.class); + ExtensionContext extensionContext = new ClassExtensionContext(null, engineExecutionListener, + classTestDescriptor, configuration, null, null); + + Map map1 = Collections.singletonMap("key", "value"); + Map map2 = Collections.singletonMap("other key", "other value"); + + extensionContext.publishReportEntry(map1); + extensionContext.publishReportEntry(map2); + extensionContext.publishReportEntry("3rd key", "third value"); + extensionContext.publishReportEntry("status message"); + + ArgumentCaptor entryCaptor = ArgumentCaptor.forClass(ReportEntry.class); + Mockito.verify(engineExecutionListener, Mockito.times(4)).reportingEntryPublished( + ArgumentMatchers.eq(classTestDescriptor), entryCaptor.capture()); + + ReportEntry reportEntry1 = entryCaptor.getAllValues().get(0); + ReportEntry reportEntry2 = entryCaptor.getAllValues().get(1); + ReportEntry reportEntry3 = entryCaptor.getAllValues().get(2); + ReportEntry reportEntry4 = entryCaptor.getAllValues().get(3); + + assertEquals(map1, reportEntry1.getKeyValuePairs()); + assertEquals(map2, reportEntry2.getKeyValuePairs()); + assertEquals("third value", reportEntry3.getKeyValuePairs().get("3rd key")); + assertEquals("status message", reportEntry4.getKeyValuePairs().get("value")); + } + + @Test + @SuppressWarnings("resource") + void usingStore() { + TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); + ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor); + ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, configuration, null, + null); + MethodExtensionContext childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor, + configuration, new OpenTest4JAwareThrowableCollector(), null); + childContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); + + ExtensionContext.Store childStore = childContext.getStore(Namespace.GLOBAL); + ExtensionContext.Store parentStore = parentContext.getStore(Namespace.GLOBAL); + + final Object key1 = "key 1"; + final String value1 = "a value"; + childStore.put(key1, value1); + assertEquals(value1, childStore.get(key1)); + assertEquals(value1, childStore.remove(key1)); + assertNull(childStore.get(key1)); + + childStore.put(key1, value1); + assertEquals(value1, childStore.get(key1)); + assertEquals(value1, childStore.remove(key1, String.class)); + assertNull(childStore.get(key1)); + + final Object key2 = "key 2"; + final String value2 = "other value"; + assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2)); + assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2, String.class)); + assertEquals(value2, childStore.get(key2)); + assertEquals(value2, childStore.get(key2, String.class)); + + final Object parentKey = "parent key"; + final String parentValue = "parent value"; + parentStore.put(parentKey, parentValue); + assertEquals(parentValue, childStore.getOrComputeIfAbsent(parentKey, k -> "a different value")); + assertEquals(parentValue, childStore.get(parentKey)); + } + + @TestFactory + Stream configurationParameter() throws Exception { + JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters()); + String key = "123"; + Optional expected = Optional.of(key); + + UniqueId engineUniqueId = UniqueId.parse("[engine:junit-jupiter]"); + JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(engineUniqueId, configuration); + + UniqueId classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]"); + ClassTestDescriptor classTestDescriptor = new ClassTestDescriptor(classUniqueId, getClass(), configuration); + + Method method = getClass().getDeclaredMethod("configurationParameter"); + UniqueId methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); + TestMethodTestDescriptor methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, getClass(), method, + configuration); + + return Stream.of( // + (ExtensionContext) new JupiterEngineExtensionContext(null, engineDescriptor, echo, null), // + new ClassExtensionContext(null, null, classTestDescriptor, echo, null, null), // + new MethodExtensionContext(null, null, methodTestDescriptor, echo, null, null) // + ).map(context -> dynamicTest(context.getClass().getSimpleName(), + () -> assertEquals(expected, context.getConfigurationParameter(key)))); + } + + private NestedClassTestDescriptor nestedClassDescriptor() { + return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"), OuterClass.NestedClass.class, + configuration); + } + + private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) { + ClassTestDescriptor classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"), + OuterClass.class, configuration); + if (child != null) { + classTestDescriptor.addChild(child); + } + return classTestDescriptor; + } + + private TestMethodTestDescriptor methodDescriptor() { + try { + return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClass.class, + OuterClass.class.getDeclaredMethod("aMethod"), configuration); + } + catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Tag("outer-tag") + static class OuterClass { + + @Tag("nested-tag") + class NestedClass { + } + + @Tag("method-tag") + void aMethod() { + } + } + + private static class EchoParameters implements ConfigurationParameters { + + @Override + public Optional get(String key) { + return Optional.of(key); + } + + @Override + public Optional getBoolean(String key) { + throw new UnsupportedOperationException("getBoolean(String) should not be called"); + } + + @Override + @SuppressWarnings("deprecation") + public int size() { + throw new UnsupportedOperationException("size() should not be called"); + } + + @Override + public Set keySet() { + throw new UnsupportedOperationException("keySet() should not be called"); + + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java new file mode 100644 index 00000000..ecd65438 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java @@ -0,0 +1,406 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.JupiterTestDescriptorTests.StaticTestCase.StaticTestCaseLevel2; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.MethodSource; + +/** + * Unit tests for {@link ClassTestDescriptor}, {@link NestedClassTestDescriptor}, + * and {@link TestMethodTestDescriptor}. + * + * @since 5.0 + * @see org.junit.jupiter.engine.descriptor.LifecycleMethodUtilsTests + */ +class JupiterTestDescriptorTests { + + private static final UniqueId uniqueId = UniqueId.root("enigma", "foo"); + + private final JupiterConfiguration configuration = mock(); + + @BeforeEach + void setUp() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); + } + + @Test + void constructFromClass() { + ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); + + assertEquals(TestCase.class, descriptor.getTestClass()); + assertThat(descriptor.getTags()).containsExactly(TestTag.create("inherited-class-level-tag"), + TestTag.create("classTag1"), TestTag.create("classTag2")); + } + + @Test + void constructFromClassWithInvalidBeforeAllDeclaration() { + // Note: if we can instantiate the descriptor, then the invalid configuration + // will not be reported during the test engine discovery phase. + ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidBeforeAllMethod.class, + configuration); + + assertEquals(TestCaseWithInvalidBeforeAllMethod.class, descriptor.getTestClass()); + } + + @Test + void constructFromClassWithInvalidAfterAllDeclaration() { + // Note: if we can instantiate the descriptor, then the invalid configuration + // will not be reported during the test engine discovery phase. + ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidAfterAllMethod.class, + configuration); + + assertEquals(TestCaseWithInvalidAfterAllMethod.class, descriptor.getTestClass()); + } + + @Test + void constructFromClassWithInvalidBeforeEachDeclaration() { + // Note: if we can instantiate the descriptor, then the invalid configuration + // will not be reported during the test engine discovery phase. + ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidBeforeEachMethod.class, + configuration); + + assertEquals(TestCaseWithInvalidBeforeEachMethod.class, descriptor.getTestClass()); + } + + @Test + void constructFromClassWithInvalidAfterEachDeclaration() { + // Note: if we can instantiate the descriptor, then the invalid configuration + // will not be reported during the test engine discovery phase. + ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidAfterEachMethod.class, + configuration); + + assertEquals(TestCaseWithInvalidAfterEachMethod.class, descriptor.getTestClass()); + } + + @Test + void constructFromMethod() throws Exception { + Class testClass = TestCase.class; + Method testMethod = testClass.getDeclaredMethod("test"); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, + configuration); + + assertEquals(uniqueId, descriptor.getUniqueId()); + assertEquals(testMethod, descriptor.getTestMethod()); + assertEquals("test()", descriptor.getDisplayName(), "display name:"); + assertEquals("test()", descriptor.getLegacyReportingName(), "legacy name:"); + } + + @Test + void constructFromMethodWithAnnotations() throws Exception { + JupiterTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); + Method testMethod = TestCase.class.getDeclaredMethod("foo"); + TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, + configuration); + classDescriptor.addChild(methodDescriptor); + + assertEquals(testMethod, methodDescriptor.getTestMethod()); + assertEquals("custom test name", methodDescriptor.getDisplayName(), "display name:"); + assertEquals("foo()", methodDescriptor.getLegacyReportingName(), "legacy name:"); + + List tags = methodDescriptor.getTags().stream().map(TestTag::getName).collect(toList()); + assertThat(tags).containsExactlyInAnyOrder("inherited-class-level-tag", "classTag1", "classTag2", "methodTag1", + "methodTag2"); + } + + @Test + void constructFromMethodWithCustomTestAnnotation() throws Exception { + Method testMethod = TestCase.class.getDeclaredMethod("customTestAnnotation"); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, + configuration); + + assertEquals(testMethod, descriptor.getTestMethod()); + assertEquals("custom name", descriptor.getDisplayName(), "display name:"); + assertEquals("customTestAnnotation()", descriptor.getLegacyReportingName(), "legacy name:"); + assertThat(descriptor.getTags()).containsExactly(TestTag.create("custom-tag")); + } + + @Test + void constructFromMethodWithParameters() throws Exception { + Method testMethod = TestCase.class.getDeclaredMethod("test", String.class, BigDecimal.class); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, + configuration); + + assertEquals(testMethod, descriptor.getTestMethod()); + assertEquals("test(String, BigDecimal)", descriptor.getDisplayName(), "display name"); + assertEquals("test(String, BigDecimal)", descriptor.getLegacyReportingName(), "legacy name"); + } + + @Test + void constructFromMethodWithPrimitiveArrayParameter() throws Exception { + Method testMethod = TestCase.class.getDeclaredMethod("test", int[].class); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, + configuration); + + assertEquals(testMethod, descriptor.getTestMethod()); + assertEquals("test(int[])", descriptor.getDisplayName(), "display name"); + assertEquals("test(int[])", descriptor.getLegacyReportingName(), "legacy name"); + } + + @Test + void constructFromMethodWithObjectArrayParameter() throws Exception { + Method testMethod = TestCase.class.getDeclaredMethod("test", String[].class); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, + configuration); + + assertEquals(testMethod, descriptor.getTestMethod()); + assertEquals("test(String[])", descriptor.getDisplayName(), "display name"); + assertEquals("test(String[])", descriptor.getLegacyReportingName(), "legacy name"); + } + + @Test + void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exception { + Method testMethod = TestCase.class.getDeclaredMethod("test", int[][][][][].class); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, + configuration); + + assertEquals(testMethod, descriptor.getTestMethod()); + assertEquals("test(int[][][][][])", descriptor.getDisplayName(), "display name"); + assertEquals("test(int[][][][][])", descriptor.getLegacyReportingName(), "legacy name"); + } + + @Test + void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Exception { + Method testMethod = TestCase.class.getDeclaredMethod("test", String[][][][][].class); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, + configuration); + + assertEquals(testMethod, descriptor.getTestMethod()); + assertEquals("test(String[][][][][])", descriptor.getDisplayName(), "display name"); + assertEquals("test(String[][][][][])", descriptor.getLegacyReportingName(), "legacy name"); + } + + @Test + void constructFromInheritedMethod() throws Exception { + Method testMethod = ConcreteTestCase.class.getMethod("theTest"); + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, ConcreteTestCase.class, testMethod, + configuration); + + assertEquals(testMethod, descriptor.getTestMethod()); + + Optional sourceOptional = descriptor.getSource(); + assertThat(sourceOptional).containsInstanceOf(MethodSource.class); + + MethodSource methodSource = (MethodSource) sourceOptional.orElseThrow(); + assertEquals(ConcreteTestCase.class.getName(), methodSource.getClassName()); + assertEquals("theTest", methodSource.getMethodName()); + } + + @Test + void shouldTakeCustomMethodNameDescriptorFromConfigurationIfPresent() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + + ClassBasedTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, getClass(), configuration); + assertEquals("class-display-name", descriptor.getDisplayName()); + assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); + + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + assertEquals("nested-class-display-name", descriptor.getDisplayName()); + assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); + + descriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); + assertEquals("class-display-name", descriptor.getDisplayName()); + assertEquals(StaticTestCase.class.getName(), descriptor.getLegacyReportingName()); + + descriptor = new ClassTestDescriptor(uniqueId, StaticTestCaseLevel2.class, configuration); + assertEquals("class-display-name", descriptor.getDisplayName()); + assertEquals(StaticTestCaseLevel2.class.getName(), descriptor.getLegacyReportingName()); + } + + @Test + void defaultDisplayNamesForTestClasses() { + ClassBasedTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, getClass(), configuration); + assertEquals(getClass().getSimpleName(), descriptor.getDisplayName()); + assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); + + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + assertEquals(NestedTestCase.class.getSimpleName(), descriptor.getDisplayName()); + assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); + + descriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); + String staticDisplayName = getClass().getSimpleName() + "$" + StaticTestCase.class.getSimpleName(); + assertEquals(staticDisplayName, descriptor.getDisplayName()); + assertEquals(StaticTestCase.class.getName(), descriptor.getLegacyReportingName()); + + descriptor = new ClassTestDescriptor(uniqueId, StaticTestCaseLevel2.class, configuration); + staticDisplayName += "$" + StaticTestCaseLevel2.class.getSimpleName(); + assertEquals(staticDisplayName, descriptor.getDisplayName()); + assertEquals(StaticTestCaseLevel2.class.getName(), descriptor.getLegacyReportingName()); + } + + @Test + void enclosingClassesAreDerivedFromParent() { + ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, + configuration); + ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, + configuration); + assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); + assertThat(nestedDescriptor.getEnclosingTestClasses()).isEmpty(); + + parentDescriptor.addChild(nestedDescriptor); + assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); + assertThat(nestedDescriptor.getEnclosingTestClasses()).containsExactly(StaticTestCase.class); + } + + // ------------------------------------------------------------------------- + + @Test + @DisplayName("custom name") + @Tag(" custom-tag ") + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface CustomTestAnnotation { + } + + @Tag("inherited-class-level-tag") + private static abstract class AbstractTestCase { + } + + @Tag("classTag1") + @Tag("classTag2") + @DisplayName("custom class name") + @SuppressWarnings("unused") + private static class TestCase extends AbstractTestCase { + + void test() { + } + + void test(String txt, BigDecimal sum) { + } + + void test(int[] nums) { + } + + void test(int[][][][][] nums) { + } + + void test(String[] info) { + } + + void test(String[][][][][] info) { + } + + @Test + @DisplayName("custom test name") + @Tag("methodTag1") + @Tag("methodTag2") + @Tag("tag containing whitespace") + void foo() { + } + + @CustomTestAnnotation + void customTestAnnotation() { + } + + } + + private static class TestCaseWithInvalidBeforeAllMethod { + + // must be static + @BeforeAll + void beforeAll() { + } + + @Test + void test() { + } + + } + + private static class TestCaseWithInvalidAfterAllMethod { + + // must be static + @AfterAll + void afterAll() { + } + + @Test + void test() { + } + + } + + private static class TestCaseWithInvalidBeforeEachMethod { + + // must NOT be static + @BeforeEach + static void beforeEach() { + } + + @Test + void test() { + } + + } + + private static class TestCaseWithInvalidAfterEachMethod { + + // must NOT be static + @AfterEach + static void afterEach() { + } + + @Test + void test() { + } + + } + + @Nested + class NestedTestCase { + } + + static class StaticTestCase { + + static class StaticTestCaseLevel2 { + } + } + + private abstract static class AbstractTestBase { + + @Test + public void theTest() { + } + } + + private static class ConcreteTestCase extends AbstractTestBase { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java new file mode 100644 index 00000000..a77ec6ab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java @@ -0,0 +1,214 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods; +import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.platform.commons.JUnitException; + +/** + * Unit tests for {@link LifecycleMethodUtils}. + * + * @since 5.0 + */ +class LifecycleMethodUtilsTests { + + @Test + void findNonVoidBeforeAllMethodsWithStandardLifecycle() { + JUnitException exception = assertThrows(JUnitException.class, + () -> findBeforeAllMethods(TestCaseWithNonVoidLifecyleMethods.class, true)); + assertEquals( + "@BeforeAll method 'java.lang.Double org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.cc()' must not return a value.", + exception.getMessage()); + } + + @Test + void findNonVoidAfterAllMethodsWithStandardLifecycle() { + JUnitException exception = assertThrows(JUnitException.class, + () -> findAfterAllMethods(TestCaseWithNonVoidLifecyleMethods.class, true)); + assertEquals( + "@AfterAll method 'java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.dd()' must not return a value.", + exception.getMessage()); + } + + @Test + void findNonVoidBeforeEachMethodsWithStandardLifecycle() { + JUnitException exception = assertThrows(JUnitException.class, + () -> findBeforeEachMethods(TestCaseWithNonVoidLifecyleMethods.class)); + assertEquals( + "@BeforeEach method 'java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.aa()' must not return a value.", + exception.getMessage()); + } + + @Test + void findNonVoidAfterEachMethodsWithStandardLifecycle() { + JUnitException exception = assertThrows(JUnitException.class, + () -> findAfterEachMethods(TestCaseWithNonVoidLifecyleMethods.class)); + assertEquals( + "@AfterEach method 'int org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.bb()' must not return a value.", + exception.getMessage()); + } + + @Test + void findBeforeEachMethodsWithStandardLifecycle() { + List methods = findBeforeEachMethods(TestCaseWithStandardLifecycle.class); + + assertThat(namesOf(methods)).containsExactlyInAnyOrder("nine", "ten"); + } + + @Test + void findAfterEachMethodsWithStandardLifecycle() { + List methods = findAfterEachMethods(TestCaseWithStandardLifecycle.class); + + assertThat(namesOf(methods)).containsExactlyInAnyOrder("eleven", "twelve"); + } + + @Test + void findBeforeAllMethodsWithStandardLifecycleAndWithoutRequiringStatic() { + List methods = findBeforeAllMethods(TestCaseWithStandardLifecycle.class, false); + + assertThat(namesOf(methods)).containsExactly("one"); + } + + @Test + void findBeforeAllMethodsWithStandardLifecycleAndRequiringStatic() { + JUnitException exception = assertThrows(JUnitException.class, + () -> findBeforeAllMethods(TestCaseWithStandardLifecycle.class, true)); + assertEquals( + "@BeforeAll method 'void org.junit.jupiter.engine.descriptor.TestCaseWithStandardLifecycle.one()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).", + exception.getMessage()); + } + + @Test + void findBeforeAllMethodsWithLifeCyclePerClassAndRequiringStatic() { + List methods = findBeforeAllMethods(TestCaseWithLifecyclePerClass.class, false); + + assertThat(namesOf(methods)).containsExactlyInAnyOrder("three", "four"); + } + + @Test + void findAfterAllMethodsWithStandardLifecycleAndWithoutRequiringStatic() { + List methods = findAfterAllMethods(TestCaseWithStandardLifecycle.class, false); + + assertThat(namesOf(methods)).containsExactlyInAnyOrder("five", "six"); + } + + @Test + void findAfterAllMethodsWithStandardLifecycleAndRequiringStatic() { + assertThrows(JUnitException.class, () -> findAfterAllMethods(TestCaseWithStandardLifecycle.class, true)); + } + + @Test + void findAfterAllMethodsWithLifeCyclePerClassAndRequiringStatic() { + List methods = findAfterAllMethods(TestCaseWithLifecyclePerClass.class, false); + + assertThat(namesOf(methods)).containsExactlyInAnyOrder("seven", "eight"); + } + + private static List namesOf(List methods) { + return methods.stream().map(Method::getName).collect(toList()); + } + +} + +class TestCaseWithStandardLifecycle { + + @BeforeAll + void one() { + } + + @BeforeEach + void nine() { + } + + @BeforeEach + void ten() { + } + + @AfterEach + void eleven() { + } + + @AfterEach + void twelve() { + } + + @AfterAll + void five() { + } + + @AfterAll + void six() { + } + +} + +@TestInstance(Lifecycle.PER_CLASS) +class TestCaseWithLifecyclePerClass { + + @BeforeAll + void three() { + } + + @BeforeAll + void four() { + } + + @AfterAll + void seven() { + } + + @AfterAll + void eight() { + } + +} + +class TestCaseWithNonVoidLifecyleMethods { + + @BeforeEach + String aa() { + return null; + } + + @AfterEach + int bb() { + return 1; + } + + @BeforeAll + Double cc() { + return null; + } + + @AfterAll + String dd() { + return ""; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java new file mode 100644 index 00000000..7718e771 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -0,0 +1,195 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; +import org.junit.platform.engine.support.descriptor.DirectorySource; +import org.junit.platform.engine.support.descriptor.FilePosition; +import org.junit.platform.engine.support.descriptor.FileSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.engine.support.descriptor.UriSource; +import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; + +/** + * Unit tests for {@link TestFactoryTestDescriptor}. + * + * @since 5.0 + */ +class TestFactoryTestDescriptorTests { + + /** + * @since 5.3 + */ + @Nested + class TestSources { + + @Test + void classpathResourceSourceFromUriWithFilePosition() { + FilePosition position = FilePosition.from(42, 21); + URI uri = URI.create("classpath:/test.js?line=42&column=21"); + TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); + + assertThat(testSource).isInstanceOf(ClasspathResourceSource.class); + ClasspathResourceSource source = (ClasspathResourceSource) testSource; + assertThat(source.getClasspathResourceName()).isEqualTo("test.js"); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void fileSourceFromUriWithFilePosition() { + File file = new File("src/test/resources/log4j2-test.xml"); + assertThat(file).isFile(); + + FilePosition position = FilePosition.from(42, 21); + URI uri = URI.create(file.toURI().toString() + "?line=42&column=21"); + TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); + + assertThat(testSource).isInstanceOf(FileSource.class); + FileSource source = (FileSource) testSource; + assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath()); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void directorySourceFromUri() { + File file = new File("src/test/resources"); + assertThat(file).isDirectory(); + + URI uri = file.toURI(); + TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); + + assertThat(testSource).isInstanceOf(DirectorySource.class); + DirectorySource source = (DirectorySource) testSource; + assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath()); + } + + @Test + void defaultUriSourceFromUri() { + File file = new File("src/test/resources"); + assertThat(file).isDirectory(); + + URI uri = URI.create("https://example.com?foo=bar&line=42"); + TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); + + assertThat(testSource).isInstanceOf(UriSource.class); + assertThat(testSource.getClass().getSimpleName()).isEqualTo("DefaultUriSource"); + UriSource source = (UriSource) testSource; + assertThat(source.getUri()).isEqualTo(uri); + } + + @Test + void methodSourceFromUri() { + URI uri = URI.create("method:org.junit.Foo#bar(java.lang.String,%20java.lang.String[])"); + TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); + + assertThat(testSource).isInstanceOf(MethodSource.class); + assertThat(testSource.getClass().getSimpleName()).isEqualTo("MethodSource"); + MethodSource source = (MethodSource) testSource; + assertThat(source.getClassName()).isEqualTo("org.junit.Foo"); + assertThat(source.getMethodName()).isEqualTo("bar"); + assertThat(source.getMethodParameterTypes()).isEqualTo("java.lang.String, java.lang.String[]"); + } + } + + @Nested + class Streams { + + private JupiterEngineExecutionContext context; + private ExtensionContext extensionContext; + private TestFactoryTestDescriptor descriptor; + private boolean isClosed; + private JupiterConfiguration jupiterConfiguration; + + @BeforeEach + void before() throws Exception { + jupiterConfiguration = mock(); + when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + + extensionContext = mock(); + isClosed = false; + + context = new JupiterEngineExecutionContext(null, null) // + .extend() // + .withThrowableCollector(new OpenTest4JAwareThrowableCollector()) // + .withExtensionContext(extensionContext) // + .withExtensionRegistry(mock()) // + .build(); + + Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); + descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class, + testMethod, jupiterConfiguration); + when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); + } + + @Test + void streamsFromTestFactoriesShouldBeClosed() { + Stream dynamicTestStream = Stream.empty(); + prepareMockForTestInstanceWithCustomStream(dynamicTestStream); + + descriptor.invokeTestMethod(context, mock()); + + assertTrue(isClosed); + } + + @Test + void streamsFromTestFactoriesShouldBeClosedWhenTheyThrow() { + Stream integerStream = Stream.of(1, 2); + prepareMockForTestInstanceWithCustomStream(integerStream); + + descriptor.invokeTestMethod(context, mock()); + + assertTrue(isClosed); + } + + private void prepareMockForTestInstanceWithCustomStream(Stream stream) { + Stream mockStream = stream.onClose(() -> isClosed = true); + when(extensionContext.getRequiredTestInstance()).thenReturn(new CustomStreamTestCase(mockStream)); + } + + } + + private static class CustomStreamTestCase { + + private final Stream mockStream; + + CustomStreamTestCase(Stream mockStream) { + this.mockStream = mockStream; + } + + @TestFactory + Stream customStream() { + return mockStream; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java new file mode 100644 index 00000000..46fab2de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; +import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * Unit tests for {@link TestInstanceLifecycleUtils}. + * + *

NOTE: it doesn't make sense to unit test the JVM system property fallback + * support in this test class since that feature is a concrete implementation + * detail of {@code LauncherConfigurationParameters} which necessitates an + * integration test via the {@code Launcher} API. + * + * @since 5.0 + */ +class TestInstanceLifecycleUtilsTests { + + private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; + + @Test + void getTestInstanceLifecyclePreconditions() { + PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, + () -> getTestInstanceLifecycle(null, new DefaultJupiterConfiguration(mock()))); + assertThat(exception).hasMessage("testClass must not be null"); + + exception = assertThrows(PreconditionViolationException.class, + () -> getTestInstanceLifecycle(getClass(), null)); + assertThat(exception).hasMessage("configuration must not be null"); + } + + @Test + void getTestInstanceLifecycleWithNoConfigParamSet() { + Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(mock())); + assertThat(lifecycle).isEqualTo(PER_METHOD); + } + + @Test + void getTestInstanceLifecycleWithConfigParamSet() { + ConfigurationParameters configParams = mock(); + when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); + Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(configParams)); + assertThat(lifecycle).isEqualTo(PER_CLASS); + } + + @Test + void getTestInstanceLifecycleWithLocalConfigThatOverridesCustomDefaultSetViaConfigParam() { + ConfigurationParameters configParams = mock(); + when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); + Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, new DefaultJupiterConfiguration(configParams)); + assertThat(lifecycle).isEqualTo(PER_METHOD); + } + + @Test + void getTestInstanceLifecycleFromMetaAnnotationWithNoConfigParamSet() { + Class testClass = BaseMetaAnnotatedTestCase.class; + Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); + assertThat(lifecycle).isEqualTo(PER_CLASS); + } + + @Test + void getTestInstanceLifecycleFromSpecializedClassWithNoConfigParamSet() { + Class testClass = SpecializedTestCase.class; + Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); + assertThat(lifecycle).isEqualTo(PER_CLASS); + } + + @TestInstance(Lifecycle.PER_METHOD) + private static class TestCase { + } + + @Inherited + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @TestInstance(Lifecycle.PER_CLASS) + private @interface PerClassLifeCycle { + } + + @PerClassLifeCycle + private static class BaseMetaAnnotatedTestCase { + } + + private static class SpecializedTestCase extends BaseMetaAnnotatedTestCase { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java new file mode 100644 index 00000000..b00060eb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.engine.UniqueId; + +class TestTemplateInvocationTestDescriptorTests { + + @Test + void invocationsDoNotDeclareExclusiveResources() throws Exception { + Class testClass = MyTestCase.class; + Method testTemplateMethod = testClass.getDeclaredMethod("testTemplate"); + JupiterConfiguration configuration = mock(); + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + TestTemplateTestDescriptor parent = new TestTemplateTestDescriptor(UniqueId.root("segment", "template"), + testClass, testTemplateMethod, configuration); + TestTemplateInvocationContext invocationContext = mock(); + when(invocationContext.getDisplayName(anyInt())).thenReturn("invocation"); + + TestTemplateInvocationTestDescriptor testDescriptor = new TestTemplateInvocationTestDescriptor( + parent.getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "1"), testClass, + testTemplateMethod, invocationContext, 1, configuration); + + assertThat(parent.getExclusiveResources()).hasSize(1); + assertThat(testDescriptor.getExclusiveResources()).isEmpty(); + } + + static class MyTestCase { + @TestTemplate + @ResourceLock("a") + void testTemplate() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java new file mode 100644 index 00000000..f9649dda --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Set; + +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; + +/** + * Unit tests for {@link TestTemplateTestDescriptor}. + * + * @since 5.0 + */ +class TestTemplateTestDescriptorTests { + private JupiterConfiguration jupiterConfiguration = mock(); + + @Test + void inheritsTagsFromParent() throws Exception { + UniqueId rootUniqueId = UniqueId.root("segment", "template"); + UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); + AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, + singleton(TestTag.create("foo"))); + + when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + + TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( + parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, + MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + parent.addChild(testDescriptor); + + assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"), + TestTag.create("baz")); + } + + @Test + void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exception { + UniqueId rootUniqueId = UniqueId.root("segment", "template"); + UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); + AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, + singleton(TestTag.create("foo"))); + + when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); + + TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( + parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, + MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + parent.addChild(testDescriptor); + + assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name"); + } + + @Test + void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exception { + UniqueId rootUniqueId = UniqueId.root("segment", "template"); + UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); + AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, + singleton(TestTag.create("foo"))); + + when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + + TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( + parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, + MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + parent.addChild(testDescriptor); + + assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()"); + } + + private AbstractTestDescriptor containerTestDescriptorWithTags(UniqueId uniqueId, Set tags) { + return new AbstractTestDescriptor(uniqueId, "testDescriptor with tags") { + + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public Set getTags() { + return tags; + } + }; + } + + static class MyTestCase { + + @Tag("bar") + @Tag("baz") + @TestTemplate + void testTemplate() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java new file mode 100644 index 00000000..25091d05 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor.subpackage; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +public class Class1WithTestCases { + + @Test + void test1() { + + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java new file mode 100644 index 00000000..2732b274 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor.subpackage; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +public class Class2WithTestCases { + + @Test + void test2() { + + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java new file mode 100644 index 00000000..139f38e6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor.subpackage; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +public class ClassWithStaticInnerTestCases { + + public static class ShouldBeDiscovered { + + @Test + void test1() { + } + } + + @SuppressWarnings("unused") + private static class ShouldNotBeDiscovered { + + @Test + void test2() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java new file mode 100644 index 00000000..bb0d49fe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java @@ -0,0 +1,17 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor.subpackage; + +/** + * @since 5.0 + */ +public class ClassWithoutTestCases { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java new file mode 100644 index 00000000..a71d0665 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -0,0 +1,900 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static java.util.Collections.singleton; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE; +import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.engineId; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForClass; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForMethod; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestFactoryMethod; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTopLevelClass; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; +import static org.junit.platform.engine.SelectorResolutionResult.Status.RESOLVED; +import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; +import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.engine.discovery.PackageNameFilter.excludePackageNames; +import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.DynamicDescendantFilter; +import org.junit.jupiter.engine.descriptor.Filterable; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; +import org.junit.jupiter.engine.descriptor.subpackage.Class1WithTestCases; +import org.junit.jupiter.engine.descriptor.subpackage.Class2WithTestCases; +import org.junit.jupiter.engine.descriptor.subpackage.ClassWithStaticInnerTestCases; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.SelectorResolutionResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.mockito.ArgumentCaptor; + +/** + * @since 5.0 + */ +class DiscoverySelectorResolverTests { + + private final JupiterConfiguration configuration = mock(); + private final LauncherDiscoveryListener discoveryListener = mock(); + private final JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(engineId(), configuration); + + @BeforeEach + void setUp() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); + } + + @Test + void nonTestClassResolution() { + resolve(request().selectors(selectClass(NonTestClass.class))); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + } + + @Test + void doesNotAttemptToResolveMethodsForNonTestClasses() { + var methodSelector = selectMethod(NonTestClass.class, "doesNotExist"); + resolve(request().selectors(methodSelector)); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertUnresolved(methodSelector); + } + + @Test + void abstractClassResolution() { + resolve(request().selectors(selectClass(AbstractTestClass.class))); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertUnresolved(selectClass(AbstractTestClass.class)); + } + + @Test + void singleClassResolution() { + ClassSelector selector = selectClass(MyTestClass.class); + + resolve(request().selectors(selector)); + + assertEquals(4, engineDescriptor.getDescendants().size()); + assertUniqueIdsForMyTestClass(uniqueIds()); + } + + @Test + void classResolutionForNonexistentClass() { + ClassSelector selector = selectClass("org.example.DoesNotExist"); + + resolve(request().selectors(selector)); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + var result = verifySelectorProcessed(selector); + assertThat(result.getStatus()).isEqualTo(FAILED); + assertThat(result.getThrowable().get()).hasMessageContaining("Could not load class with name"); + } + + @Test + void duplicateClassSelectorOnlyResolvesOnce() { + resolve(request().selectors( // + selectClass(MyTestClass.class), // + selectClass(MyTestClass.class) // + )); + + assertEquals(4, engineDescriptor.getDescendants().size()); + assertUniqueIdsForMyTestClass(uniqueIds()); + } + + @Test + void twoClassesResolution() { + ClassSelector selector1 = selectClass(MyTestClass.class); + ClassSelector selector2 = selectClass(YourTestClass.class); + + resolve(request().selectors(selector1, selector2)); + + assertEquals(7, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertUniqueIdsForMyTestClass(uniqueIds); + assertThat(uniqueIds).contains(uniqueIdForClass(YourTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(YourTestClass.class, "test3()")); + assertThat(uniqueIds).contains(uniqueIdForMethod(YourTestClass.class, "test4()")); + } + + private void assertUniqueIdsForMyTestClass(List uniqueIds) { + assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); + assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test2()")); + assertThat(uniqueIds).contains(uniqueIdForTestFactoryMethod(MyTestClass.class, "dynamicTest()")); + } + + @Test + void classResolutionOfStaticNestedClass() { + ClassSelector selector = selectClass(OtherTestClass.NestedTestClass.class); + + resolve(request().selectors(selector)); + + assertEquals(3, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); + assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test6()")); + } + + @Test + void methodResolution() throws NoSuchMethodException { + Method test1 = MyTestClass.class.getDeclaredMethod("test1"); + MethodSelector selector = selectMethod(test1.getDeclaringClass(), test1); + + resolve(request().selectors(selector)); + + assertEquals(2, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); + } + + @Test + void methodResolutionFromInheritedMethod() throws NoSuchMethodException { + MethodSelector selector = selectMethod(HerTestClass.class, MyTestClass.class.getDeclaredMethod("test1")); + + resolve(request().selectors(selector)); + + assertEquals(2, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test1()")); + } + + @Test + void resolvingSelectorOfNonTestMethodResolvesNothing() throws NoSuchMethodException { + Method notATest = MyTestClass.class.getDeclaredMethod("notATest"); + MethodSelector selector = selectMethod(notATest.getDeclaringClass(), notATest); + + resolve(request().selectors(selector)); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + } + + @Test + void methodResolutionForNonexistentClass() { + String className = "org.example.DoesNotExist"; + String methodName = "bogus"; + MethodSelector selector = selectMethod(className, methodName, ""); + + resolve(request().selectors(selector)); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + var result = verifySelectorProcessed(selector); + assertThat(result.getStatus()).isEqualTo(FAILED); + assertThat(result.getThrowable().get())// + .isInstanceOf(PreconditionViolationException.class)// + .hasMessageStartingWith("Could not load class with name: " + className); + } + + @Test + void methodResolutionForNonexistentMethod() { + MethodSelector selector = selectMethod(MyTestClass.class, "bogus", ""); + + resolve(request().selectors(selector)); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + var result = verifySelectorProcessed(selector); + assertThat(result.getStatus()).isEqualTo(FAILED); + assertThat(result.getThrowable().get()).hasMessageContaining("Could not find method"); + } + + @Test + void classResolutionByUniqueId() { + UniqueIdSelector selector = selectUniqueId(uniqueIdForClass(MyTestClass.class).toString()); + + resolve(request().selectors(selector)); + + assertEquals(4, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertUniqueIdsForMyTestClass(uniqueIds); + } + + @Test + void staticNestedClassResolutionByUniqueId() { + UniqueIdSelector selector = selectUniqueId(uniqueIdForClass(OtherTestClass.NestedTestClass.class).toString()); + + resolve(request().selectors(selector)); + + assertEquals(3, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); + assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test6()")); + } + + @Test + void methodOfInnerClassByUniqueId() { + UniqueIdSelector selector = selectUniqueId( + uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()").toString()); + + resolve(request().selectors(selector)); + + assertEquals(2, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); + } + + @Test + void resolvingUniqueIdWithUnknownSegmentTypeResolvesNothing() { + UniqueId uniqueId = engineId().append("bogus", "enigma"); + UniqueIdSelector selector = selectUniqueId(uniqueId); + + resolve(request().selectors(selector)); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertUnresolved(selector); + } + + @Test + void resolvingUniqueIdOfNonTestMethodResolvesNothing() { + UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "notATest()")); + + resolve(request().selectors(selector)); + + assertThat(engineDescriptor.getDescendants()).isEmpty(); + assertUnresolved(selector); + } + + @Test + void methodResolutionByUniqueIdWithMissingMethodName() { + UniqueId uniqueId = uniqueIdForMethod(getClass(), "()"); + + resolve(request().selectors(selectUniqueId(uniqueId))); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + var result = verifySelectorProcessed(selectUniqueId(uniqueId)); + assertThat(result.getStatus()).isEqualTo(FAILED); + assertThat(result.getThrowable().get())// + .isInstanceOf(PreconditionViolationException.class)// + .hasMessageStartingWith("Method [()] does not match pattern"); + } + + @Test + void methodResolutionByUniqueIdWithMissingParameters() { + UniqueId uniqueId = uniqueIdForMethod(getClass(), "methodName"); + + resolve(request().selectors(selectUniqueId(uniqueId))); + + assertThat(engineDescriptor.getDescendants()).isEmpty(); + var result = verifySelectorProcessed(selectUniqueId(uniqueId)); + assertThat(result.getStatus()).isEqualTo(FAILED); + assertThat(result.getThrowable().get())// + .isInstanceOf(PreconditionViolationException.class)// + .hasMessageStartingWith("Method [methodName] does not match pattern"); + } + + @Test + void methodResolutionByUniqueIdWithBogusParameters() { + UniqueId uniqueId = uniqueIdForMethod(getClass(), "methodName(java.lang.String, junit.foo.Enigma)"); + + resolve(request().selectors(selectUniqueId(uniqueId))); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + var result = verifySelectorProcessed(selectUniqueId(uniqueId)); + assertThat(result.getStatus()).isEqualTo(FAILED); + assertThat(result.getThrowable().get())// + .isInstanceOf(JUnitException.class)// + .hasMessage("Failed to load parameter type [%s] for method [%s] in class [%s].", "junit.foo.Enigma", + "methodName", getClass().getName()); + } + + @Test + void methodResolutionByUniqueId() { + UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test1()").toString()); + + resolve(request().selectors(selector)); + + assertEquals(2, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); + } + + @Test + void methodResolutionByUniqueIdFromInheritedClass() { + UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(HerTestClass.class, "test1()").toString()); + + resolve(request().selectors(selector)); + + assertEquals(2, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + + assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test1()")); + } + + @Test + void methodResolutionByUniqueIdWithParams() { + UniqueIdSelector selector = selectUniqueId( + uniqueIdForMethod(HerTestClass.class, "test7(java.lang.String)").toString()); + + resolve(request().selectors(selector)); + + assertEquals(2, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test7(java.lang.String)")); + + } + + @Test + void resolvingUniqueIdWithWrongParamsResolvesNothing() { + UniqueId uniqueId = uniqueIdForMethod(HerTestClass.class, "test7(java.math.BigDecimal)"); + + resolve(request().selectors(selectUniqueId(uniqueId))); + + assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertUnresolved(selectUniqueId(uniqueId)); + } + + @Test + void twoMethodResolutionsByUniqueId() { + UniqueIdSelector selector1 = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test1()").toString()); + UniqueIdSelector selector2 = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test2()").toString()); + + // adding same selector twice should have no effect + resolve(request().selectors(selector1, selector2, selector2)); + + assertEquals(3, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); + assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test2()")); + + TestDescriptor classFromMethod1 = descriptorByUniqueId( + uniqueIdForMethod(MyTestClass.class, "test1()")).getParent().get(); + TestDescriptor classFromMethod2 = descriptorByUniqueId( + uniqueIdForMethod(MyTestClass.class, "test2()")).getParent().get(); + + assertEquals(classFromMethod1, classFromMethod2); + assertSame(classFromMethod1, classFromMethod2); + } + + @Test + void packageResolutionUsingExplicitBasePackage() { + PackageSelector selector = selectPackage("org.junit.jupiter.engine.descriptor.subpackage"); + + resolve(request().selectors(selector)); + + assertEquals(6, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); + assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); + assertThat(uniqueIds).contains( + uniqueIdForMethod(ClassWithStaticInnerTestCases.ShouldBeDiscovered.class, "test1()")); + } + + @Test + void packageResolutionUsingDefaultPackage() throws Exception { + resolve(request().selectors(selectPackage(""))); + + // 150 is completely arbitrary. The actual number is likely much higher. + assertThat(engineDescriptor.getDescendants().size())// + .describedAs("Too few test descriptors in classpath")// + .isGreaterThan(150); + + List uniqueIds = uniqueIds(); + assertThat(uniqueIds)// + .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// + .contains(uniqueIdForClass(ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get())); + assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); + assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); + } + + @Test + void classpathResolution() throws Exception { + Path classpath = Paths.get( + DiscoverySelectorResolverTests.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + + List selectors = selectClasspathRoots(singleton(classpath)); + + resolve(request().selectors(selectors)); + + // 150 is completely arbitrary. The actual number is likely much higher. + assertThat(engineDescriptor.getDescendants().size())// + .describedAs("Too few test descriptors in classpath")// + .isGreaterThan(150); + + List uniqueIds = uniqueIds(); + assertThat(uniqueIds)// + .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// + .contains(uniqueIdForClass(ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get())); + assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); + assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); + assertThat(uniqueIds).contains( + uniqueIdForMethod(ClassWithStaticInnerTestCases.ShouldBeDiscovered.class, "test1()")); + } + + @Test + void classpathResolutionForJarFiles() throws Exception { + URL jarUrl = getClass().getResource("/jupiter-testjar.jar"); + Path path = Paths.get(jarUrl.toURI()); + List selectors = selectClasspathRoots(singleton(path)); + + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try (URLClassLoader classLoader = new URLClassLoader(new URL[] { jarUrl })) { + Thread.currentThread().setContextClassLoader(classLoader); + + resolve(request().selectors(selectors)); + + assertThat(uniqueIds()) // + .contains(uniqueIdForTopLevelClass("com.example.project.FirstTest")) // + .contains(uniqueIdForTopLevelClass("com.example.project.SecondTest")); + } + finally { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + + @Test + void nestedTestResolutionFromBaseClass() { + ClassSelector selector = selectClass(TestCaseWithNesting.class); + + resolve(request().selectors(selector)); + + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).hasSize(6); + + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.class, "testA()")); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); + assertThat(uniqueIds).contains( + uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); + } + + @Test + void nestedTestResolutionFromNestedTestClass() { + ClassSelector selector = selectClass(TestCaseWithNesting.NestedTestCase.class); + + resolve(request().selectors(selector)); + + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).hasSize(5); + + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); + assertThat(uniqueIds).contains( + uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); + } + + @Test + void nestedTestResolutionFromUniqueId() { + UniqueIdSelector selector = selectUniqueId( + uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class).toString()); + + resolve(request().selectors(selector)); + + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).hasSize(4); + + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); + assertThat(uniqueIds).contains( + uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); + } + + @Test + void doubleNestedTestResolutionFromClass() { + ClassSelector selector = selectClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class); + + resolve(request().selectors(selector)); + + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).hasSize(4); + + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); + assertThat(uniqueIds).contains( + uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); + } + + @Test + void methodResolutionInDoubleNestedTestClass() throws NoSuchMethodException { + MethodSelector selector = selectMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, + TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class.getDeclaredMethod("testC")); + + resolve(request().selectors(selector)); + + assertEquals(4, engineDescriptor.getDescendants().size()); + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); + assertThat(uniqueIds).contains( + uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); + } + + @Test + void nestedTestResolutionFromUniqueIdToMethod() { + UniqueIdSelector selector = selectUniqueId( + uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()").toString()); + + resolve(request().selectors(selector)); + + List uniqueIds = uniqueIds(); + assertThat(uniqueIds).hasSize(3); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); + assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); + assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); + } + + @Test + void testFactoryMethodResolutionByUniqueId() { + Class clazz = MyTestClass.class; + UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); + + resolve(request().selectors(selectUniqueId(factoryUid))); + + assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); + } + + @Test + void testTemplateMethodResolutionByUniqueId() { + Class clazz = TestClassWithTemplate.class; + UniqueId templateUid = uniqueIdForTestTemplateMethod(clazz, "testTemplate()"); + + resolve(request().selectors(selectUniqueId(templateUid))); + + assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); + } + + @Test + void resolvingDynamicTestByUniqueIdResolvesUpToParentTestFactory() { + Class clazz = MyTestClass.class; + UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); + UniqueId dynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); + UniqueId differentDynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); + + resolve(request().selectors(selectUniqueId(dynamicTestUid))); + + assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); + TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + + TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); + assertThat(dynamicDescendantFilter.test(dynamicTestUid, 42)).isTrue(); + assertThat(dynamicDescendantFilter.test(differentDynamicTestUid, 42)).isFalse(); + + assertAllSelectorsResolved(); + } + + @Test + void resolvingDynamicContainerByUniqueIdResolvesUpToParentTestFactory() { + Class clazz = MyTestClass.class; + UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); + UniqueId dynamicContainerUid = factoryUid.append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1"); + UniqueId differentDynamicContainerUid = factoryUid.append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#2"); + UniqueId dynamicTestUid = dynamicContainerUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); + UniqueId differentDynamicTestUid = dynamicContainerUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); + + resolve(request().selectors(selectUniqueId(dynamicTestUid))); + + assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); + TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + + TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); + assertThat(dynamicDescendantFilter.test(dynamicTestUid, 42)).isTrue(); + assertThat(dynamicDescendantFilter.test(differentDynamicContainerUid, 42)).isFalse(); + assertThat(dynamicDescendantFilter.test(differentDynamicTestUid, 42)).isFalse(); + + assertAllSelectorsResolved(); + } + + @Test + void resolvingDynamicTestByUniqueIdAndTestFactoryByMethodSelectorResolvesTestFactory() { + Class clazz = MyTestClass.class; + UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); + UniqueId dynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); + + resolve(request().selectors(selectUniqueId(dynamicTestUid), selectMethod(clazz, "dynamicTest"))); + + assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); + TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); + assertThat(dynamicDescendantFilter.test(UniqueId.root("foo", "bar"), 42)).isTrue(); + } + + private DynamicDescendantFilter getDynamicDescendantFilter(TestDescriptor testDescriptor) { + assertThat(testDescriptor).isInstanceOf(JupiterTestDescriptor.class); + return ((Filterable) testDescriptor).getDynamicDescendantFilter(); + } + + @Test + void resolvingTestTemplateInvocationByUniqueIdResolvesOnlyUpToParentTestTemplate() { + Class clazz = TestClassWithTemplate.class; + UniqueId templateUid = uniqueIdForTestTemplateMethod(clazz, "testTemplate()"); + UniqueId invocationUid = templateUid.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); + + resolve(request().selectors(selectUniqueId(invocationUid))); + + assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); + } + + @Test + void includingPackageNameFilterExcludesClassesInNonMatchingPackages() { + resolve(request().selectors(selectClass(MatchingClass.class)).filters( + includePackageNames("org.junit.jupiter.engine.unknown"))); + + assertThat(engineDescriptor.getDescendants()).isEmpty(); + } + + @Test + void includingPackageNameFilterIncludesClassesInMatchingPackages() { + resolve(request().selectors(selectClass(MatchingClass.class)).filters( + includePackageNames("org.junit.jupiter.engine"))); + + assertThat(engineDescriptor.getDescendants()).hasSize(3); + } + + @Test + void excludingPackageNameFilterExcludesClassesInMatchingPackages() { + resolve(request().selectors(selectClass(MatchingClass.class)).filters( + excludePackageNames("org.junit.jupiter.engine"))); + + assertThat(engineDescriptor.getDescendants()).isEmpty(); + } + + @Test + void excludingPackageNameFilterIncludesClassesInNonMatchingPackages() { + resolve(request().selectors(selectClass(MatchingClass.class)).filters( + excludePackageNames("org.junit.jupiter.engine.unknown"))); + + assertThat(engineDescriptor.getDescendants()).hasSize(3); + } + + @Test + void classNamePatternFilterExcludesNonMatchingClasses() { + resolve(request().selectors(selectClass(MatchingClass.class), selectClass(OtherClass.class)).filters( + includeClassNamePatterns(".*MatchingClass"))); + + assertThat(engineDescriptor.getDescendants()).hasSize(3); + } + + private void resolve(LauncherDiscoveryRequestBuilder builder) { + new DiscoverySelectorResolver().resolveSelectors(builder.build(), engineDescriptor); + } + + private TestDescriptor descriptorByUniqueId(UniqueId uniqueId) { + return engineDescriptor.getDescendants().stream().filter( + d -> d.getUniqueId().equals(uniqueId)).findFirst().get(); + } + + private List uniqueIds() { + return engineDescriptor.getDescendants().stream().map(TestDescriptor::getUniqueId).collect(toList()); + } + + private LauncherDiscoveryRequestBuilder request() { + return LauncherDiscoveryRequestBuilder.request() // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .listeners(discoveryListener); + } + + private void assertAllSelectorsResolved() { + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); + verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-jupiter")), any(), + resultCaptor.capture()); + assertThat(resultCaptor.getAllValues()) // + .flatExtracting(SelectorResolutionResult::getStatus) // + .allMatch(Predicate.isEqual(RESOLVED)); + } + + private void assertUnresolved(DiscoverySelector selector) { + var result = verifySelectorProcessed(selector); + assertThat(result.getStatus()).isEqualTo(UNRESOLVED); + } + + private SelectorResolutionResult verifySelectorProcessed(DiscoverySelector selector) { + ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); + verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-jupiter")), eq(selector), + resultCaptor.capture()); + return resultCaptor.getValue(); + } + +} + +// ----------------------------------------------------------------------------- + +class NonTestClass { +} + +abstract class AbstractTestClass { + + @Test + void test() { + } +} + +class MyTestClass { + + @Test + void test1() { + } + + @Test + void test2() { + } + + void notATest() { + } + + @TestFactory + Stream dynamicTest() { + return new ArrayList().stream(); + } +} + +class YourTestClass { + + @Test + void test3() { + } + + @Test + void test4() { + } +} + +class HerTestClass extends MyTestClass { + + @Test + void test7(String param) { + } +} + +class OtherTestClass { + + static class NestedTestClass { + + @Test + void test5() { + } + + @Test + void test6() { + } + } +} + +class TestCaseWithNesting { + + @Test + void testA() { + } + + @Nested + class NestedTestCase { + + @Test + void testB() { + } + + @Nested + class DoubleNestedTestCase { + + @Test + void testC() { + } + } + } +} + +class TestClassWithTemplate { + + @TestTemplate + void testTemplate() { + } +} + +class MatchingClass { + @Nested + class NestedClass { + @Test + void test() { + } + } +} + +class OtherClass { + @Test + void test() { + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java new file mode 100644 index 00000000..3ab385d7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java @@ -0,0 +1,230 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * Test correct test discovery in simple test classes for the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class DiscoveryTests extends AbstractJupiterTestEngineTests { + + @Test + void discoverTestClass() { + LauncherDiscoveryRequest request = request().selectors(selectClass(LocalTestCase.class)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void doNotDiscoverAbstractTestClass() { + LauncherDiscoveryRequest request = request().selectors(selectClass(AbstractTestCase.class)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverMethodByUniqueId() { + LauncherDiscoveryRequest request = request().selectors( + selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test1()"))).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverMethodByUniqueIdForOverloadedMethod() { + LauncherDiscoveryRequest request = request().selectors( + selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test4()"))).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { + LauncherDiscoveryRequest request = request().selectors(selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod( + LocalTestCase.class, "test4(" + TestInfo.class.getName() + ")"))).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverMethodByMethodReference() throws NoSuchMethodException { + Method testMethod = LocalTestCase.class.getDeclaredMethod("test3", new Class[0]); + + LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, testMethod)).build(); + TestDescriptor engineDescriptor = discoverTests(request); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverMultipleMethodsOfSameClass() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, "test1"), + selectMethod(LocalTestCase.class, "test2")).build(); + + TestDescriptor engineDescriptor = discoverTests(request); + + assertThat(engineDescriptor.getChildren()).hasSize(1); + TestDescriptor classDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(classDescriptor.getChildren()).hasSize(2); + } + + @Test + void discoverCompositeSpec() { + LauncherDiscoveryRequest spec = request().selectors( + selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test2()")), + selectClass(LocalTestCase.class)).build(); + + TestDescriptor engineDescriptor = discoverTests(spec); + assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverTestTemplateMethodByUniqueId() { + LauncherDiscoveryRequest spec = request().selectors( + selectUniqueId(uniqueIdForTestTemplateMethod(TestTemplateClass.class, "testTemplate()"))).build(); + + TestDescriptor engineDescriptor = discoverTests(spec); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverTestTemplateMethodByMethodSelector() { + LauncherDiscoveryRequest spec = request().selectors( + selectMethod(TestTemplateClass.class, "testTemplate")).build(); + + TestDescriptor engineDescriptor = discoverTests(spec); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + void discoverDeeplyNestedTestMethodByNestedMethodSelector() throws Exception { + var selector = selectNestedMethod( + List.of(TestCaseWithExtendedNested.class, TestCaseWithExtendedNested.ConcreteInner1.class), + AbstractSuperClass.NestedInAbstractClass.class, + AbstractSuperClass.NestedInAbstractClass.class.getDeclaredMethod("test")); + LauncherDiscoveryRequest spec = request().selectors(selector).build(); + + TestDescriptor engineDescriptor = discoverTests(spec); + + ClassTestDescriptor topLevelClassDescriptor = (ClassTestDescriptor) getOnlyElement( + engineDescriptor.getChildren()); + assertThat(topLevelClassDescriptor.getTestClass()).isEqualTo(TestCaseWithExtendedNested.class); + + NestedClassTestDescriptor firstLevelNestedClassDescriptor = (NestedClassTestDescriptor) getOnlyElement( + topLevelClassDescriptor.getChildren()); + assertThat(firstLevelNestedClassDescriptor.getTestClass()).isEqualTo( + TestCaseWithExtendedNested.ConcreteInner1.class); + + NestedClassTestDescriptor secondLevelNestedClassDescriptor = (NestedClassTestDescriptor) getOnlyElement( + firstLevelNestedClassDescriptor.getChildren()); + assertThat(secondLevelNestedClassDescriptor.getTestClass()).isEqualTo( + AbstractSuperClass.NestedInAbstractClass.class); + + TestMethodTestDescriptor methodDescriptor = (TestMethodTestDescriptor) getOnlyElement( + secondLevelNestedClassDescriptor.getChildren()); + assertThat(methodDescriptor.getTestMethod().getName()).isEqualTo("test"); + } + + // ------------------------------------------------------------------- + + private static abstract class AbstractTestCase { + + @Test + void abstractTest() { + + } + } + + static class LocalTestCase { + + @Test + void test1() { + } + + @Test + void test2() { + } + + @Test + void test3() { + } + + @Test + void test4() { + } + + @Test + void test4(TestInfo testInfo) { + } + + @CustomTestAnnotation + void customTestAnnotation() { + /* no-op */ + } + + } + + @Test + @Retention(RetentionPolicy.RUNTIME) + @interface CustomTestAnnotation { + } + + static class TestTemplateClass { + + @TestTemplate + void testTemplate() { + } + + } + + static abstract class AbstractSuperClass { + @Nested + class NestedInAbstractClass { + @Test + void test() { + } + } + } + + static class TestCaseWithExtendedNested { + @Nested + class ConcreteInner1 extends AbstractSuperClass { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java new file mode 100644 index 00000000..99a41166 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +class IsInnerClassTests { + + private final Predicate> isInnerClass = new IsInnerClass(); + + @Test + void innerClassEvaluatesToTrue() { + assertThat(isInnerClass).accepts(InnerClassesTestCase.InnerClass.class); + } + + @Test + void staticNestedClassEvaluatesToFalse() { + assertThat(isInnerClass).rejects(InnerClassesTestCase.StaticNestedClass.class); + } + + @Test + void privateInnerClassEvaluatesToFalse() { + assertThat(isInnerClass).rejects(InnerClassesTestCase.PrivateInnerClass.class); + } + + private static class InnerClassesTestCase { + + class InnerClass { + } + + static class StaticNestedClass { + } + + private class PrivateInnerClass { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java new file mode 100644 index 00000000..9ce907e8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +class IsNestedTestClassTests { + + private final Predicate> isNestedTestClass = new IsNestedTestClass(); + + @Test + void innerClassEvaluatesToTrue() { + assertThat(isNestedTestClass).accepts(NestedClassesTestCase.InnerClass.class); + } + + @Test + void staticNestedClassEvaluatesToFalse() { + assertThat(isNestedTestClass).rejects(NestedClassesTestCase.StaticNestedClass.class); + } + + @Test + void privateNestedClassEvaluatesToFalse() { + assertThat(isNestedTestClass).rejects(NestedClassesTestCase.PrivateInnerClass.class); + } + + private static class NestedClassesTestCase { + + @Nested + class InnerClass { + } + + @Nested + static class StaticNestedClass { + } + + @Nested + private class PrivateInnerClass { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java new file mode 100644 index 00000000..09fee4aa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +class IsPotentialTestContainerTests { + + private final IsPotentialTestContainer isPotentialTestContainer = new IsPotentialTestContainer(); + + @Test + void staticClassEvaluatesToTrue() { + assertTrue(isPotentialTestContainer.test(StaticClass.class)); + } + + @Test + void privateStaticClassEvaluatesToFalse() { + assertFalse(isPotentialTestContainer.test(PrivateStaticClass.class)); + } + + @Test + void abstractClassEvaluatesToFalse() { + assertFalse(isPotentialTestContainer.test(AbstractClass.class)); + } + + @Test + void localClassEvaluatesToFalse() { + + class LocalClass { + } + + assertFalse(isPotentialTestContainer.test(LocalClass.class)); + } + + @Test + void anonymousClassEvaluatesToFalse() { + + Object object = new Object() { + @Override + public String toString() { + return ""; + } + }; + + assertFalse(isPotentialTestContainer.test(object.getClass())); + } + + private static class PrivateStaticClass { + } + + static class StaticClass { + } + +} + +abstract class AbstractClass { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java new file mode 100644 index 00000000..53537e9a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java @@ -0,0 +1,214 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Predicate; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestTemplate; + +/** + * Unit tests for {@link IsTestClassWithTests}. + * + * @since 5.0 + */ +class IsTestClassWithTestsTests { + + private final Predicate> isTestClassWithTests = new IsTestClassWithTests(); + + @Test + void classWithTestMethodEvaluatesToTrue() { + assertTrue(isTestClassWithTests.test(ClassWithTestMethod.class)); + } + + @Test + void classWithTestFactoryEvaluatesToTrue() { + assertTrue(isTestClassWithTests.test(ClassWithTestFactory.class)); + } + + @Test + void classWithTestTemplateEvaluatesToTrue() { + assertTrue(isTestClassWithTests.test(ClassWithTestTemplate.class)); + } + + @Test + void classWithNestedTestClassEvaluatesToTrue() { + assertTrue(isTestClassWithTests.test(ClassWithNestedTestClass.class)); + } + + @Test + void staticTestClassEvaluatesToTrue() { + assertTrue(isTestClassWithTests.test(StaticTestCase.class)); + } + + // ------------------------------------------------------------------------- + + @Test + void privateClassWithTestMethodEvaluatesToFalse() { + assertFalse(isTestClassWithTests.test(PrivateClassWithTestMethod.class)); + } + + @Test + void privateClassWithTestFactoryEvaluatesToFalse() { + assertFalse(isTestClassWithTests.test(PrivateClassWithTestFactory.class)); + } + + @Test + void privateClassWithTestTemplateEvaluatesToFalse() { + assertFalse(isTestClassWithTests.test(PrivateClassWithTestTemplate.class)); + } + + @Test + void privateClassWithNestedTestCasesEvaluatesToFalse() { + assertFalse(isTestClassWithTests.test(PrivateClassWithNestedTestClass.class)); + } + + @Test + void privateStaticTestClassEvaluatesToFalse() { + assertFalse(isTestClassWithTests.test(PrivateStaticTestCase.class)); + } + + /** + * @see https://github.com/junit-team/junit5/issues/2249 + */ + @Test + void recursiveHierarchies() { + assertTrue(isTestClassWithTests.test(OuterClass.class)); + assertFalse(isTestClassWithTests.test(OuterClass.RecursiveInnerClass.class)); + } + + // ------------------------------------------------------------------------- + + private class PrivateClassWithTestMethod { + + @Test + void test() { + } + + } + + private class PrivateClassWithTestFactory { + + @TestFactory + Collection factory() { + return new ArrayList<>(); + } + + } + + private class PrivateClassWithTestTemplate { + + @TestTemplate + void template(int a) { + } + + } + + private class PrivateClassWithNestedTestClass { + + @Nested + class InnerClass { + + @Test + void first() { + } + + @Test + void second() { + } + + } + } + + // ------------------------------------------------------------------------- + + static class StaticTestCase { + + @Test + void test() { + } + } + + private static class PrivateStaticTestCase { + + @Test + void test() { + } + } + + static class OuterClass { + + @Nested + class InnerClass { + + @Test + void test() { + } + } + + // Intentionally commented out so that RecursiveInnerClass is NOT a candidate test class + // @Nested + class RecursiveInnerClass extends OuterClass { + } + } + +} + +// ----------------------------------------------------------------------------- + +class ClassWithTestMethod { + + @Test + void test() { + } + +} + +class ClassWithTestFactory { + + @TestFactory + Collection factory() { + return new ArrayList<>(); + } + +} + +class ClassWithTestTemplate { + + @TestTemplate + void template(int a) { + } + +} + +class ClassWithNestedTestClass { + + @Nested + class InnerClass { + + @Test + void first() { + } + + @Test + void second() { + } + + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java new file mode 100644 index 00000000..e814cee4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link IsTestFactoryMethod}. + * + * @since 5.0 + */ +class IsTestFactoryMethodTests { + + private static final Predicate isTestFactoryMethod = new IsTestFactoryMethod(); + + @Test + void factoryMethodReturningCollectionOfDynamicTests() { + assertThat(isTestFactoryMethod).accepts(method("dynamicTestsFactory")); + } + + @Test + void bogusFactoryMethodReturningVoid() { + assertThat(isTestFactoryMethod).rejects(method("bogusVoidFactory")); + } + + // TODO [#949] Enable test once IsTestFactoryMethod properly checks return type. + @Disabled("Disabled until IsTestFactoryMethod properly checks return type") + @Test + void bogusFactoryMethodReturningObject() { + assertThat(isTestFactoryMethod).rejects(method("bogusObjectFactory")); + } + + // TODO [#949] Enable test once IsTestFactoryMethod properly checks return type. + @Disabled("Disabled until IsTestFactoryMethod properly checks return type") + @Test + void bogusFactoryMethodReturningCollectionOfStrings() { + assertThat(isTestFactoryMethod).rejects(method("bogusStringsFactory")); + } + + private static Method method(String name) { + return ReflectionUtils.findMethod(ClassWithTestFactoryMethods.class, name).get(); + } + + private static class ClassWithTestFactoryMethods { + + @TestFactory + Collection dynamicTestsFactory() { + return new ArrayList<>(); + } + + @TestFactory + void bogusVoidFactory() { + } + + @TestFactory + Object bogusObjectFactory() { + return new Object(); + } + + @TestFactory + Collection bogusStringsFactory() { + return new ArrayList<>(); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java new file mode 100644 index 00000000..f4d211f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java @@ -0,0 +1,146 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Method; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link IsTestMethod}. + * + * @since 5.0 + */ +class IsTestMethodTests { + + private static final Predicate isTestMethod = new IsTestMethod(); + + @Test + void publicTestMethod() { + Method method = method("publicTestMethod"); + // Ensure that somebody doesn't accidentally delete the public modifier again. + assertTrue(ReflectionUtils.isPublic(method)); + assertThat(isTestMethod).accepts(method); + } + + @Test + void publicTestMethodWithArgument() { + Method method = method("publicTestMethodWithArgument", TestInfo.class); + // Ensure that somebody doesn't accidentally delete the public modifier again. + assertTrue(ReflectionUtils.isPublic(method)); + assertThat(isTestMethod).accepts(method); + } + + @Test + void protectedTestMethod() { + assertThat(isTestMethod).accepts(method("protectedTestMethod")); + } + + @Test + void packageVisibleTestMethod() { + assertThat(isTestMethod).accepts(method("packageVisibleTestMethod")); + } + + @Test + void bogusAbstractTestMethod() { + assertThat(isTestMethod).rejects(abstractMethod("bogusAbstractTestMethod")); + } + + @Test + void bogusStaticTestMethod() { + assertThat(isTestMethod).rejects(method("bogusStaticTestMethod")); + } + + @Test + void bogusPrivateTestMethod() { + assertThat(isTestMethod).rejects(method("bogusPrivateTestMethod")); + } + + @Test + void bogusTestMethodReturningObject() { + assertThat(isTestMethod).rejects(method("bogusTestMethodReturningObject")); + } + + @Test + void bogusTestMethodReturningVoidReference() { + assertThat(isTestMethod).rejects(method("bogusTestMethodReturningVoidReference")); + } + + @Test + void bogusTestMethodReturningPrimitive() { + assertThat(isTestMethod).rejects(method("bogusTestMethodReturningPrimitive")); + } + + private static Method method(String name, Class... parameterTypes) { + return ReflectionUtils.findMethod(ClassWithTestMethods.class, name, parameterTypes).get(); + } + + private Method abstractMethod(String name) { + return ReflectionUtils.findMethod(AbstractClassWithAbstractTestMethod.class, name).get(); + } + + private static abstract class AbstractClassWithAbstractTestMethod { + + @Test + abstract void bogusAbstractTestMethod(); + + } + + private static class ClassWithTestMethods { + + @Test + static void bogusStaticTestMethod() { + } + + @Test + private void bogusPrivateTestMethod() { + } + + @Test + String bogusTestMethodReturningObject() { + return ""; + } + + @Test + Void bogusTestMethodReturningVoidReference() { + return null; + } + + @Test + int bogusTestMethodReturningPrimitive() { + return 0; + } + + @Test + public void publicTestMethod() { + } + + @Test + public void publicTestMethodWithArgument(TestInfo info) { + } + + @Test + protected void protectedTestMethod() { + } + + @Test + void packageVisibleTestMethod() { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java new file mode 100644 index 00000000..a260e0d6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery.predicates; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestTemplate; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link IsTestTemplateMethod}. + * + * @since 5.0 + */ +class IsTestTemplateMethodTests { + + private static final IsTestTemplateMethod isTestTemplateMethod = new IsTestTemplateMethod(); + + @Test + void testTemplateMethodReturningVoid() { + assertThat(isTestTemplateMethod).accepts(method("templateReturningVoid")); + } + + @Test + void bogusTestTemplateMethodReturningObject() { + assertThat(isTestTemplateMethod).rejects(method("bogusTemplateReturningObject")); + } + + private static Method method(String name) { + return ReflectionUtils.findMethod(ClassWithTestTemplateMethods.class, name).get(); + } + + private static class ClassWithTestTemplateMethods { + + @TestTemplate + void templateReturningVoid() { + } + + @TestTemplate + String bogusTemplateReturningObject() { + return ""; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java new file mode 100644 index 00000000..78678233 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.ConfigurableParameterResolver; +import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.ConstructorInjectionTestCase; +import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.MethodSource; +import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.NumberParameterResolver; +import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.StringParameterResolver; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.9 + */ +abstract class AbstractExecutableInvokerTests { + + private static final String ENIGMA = "enigma"; + + protected final MethodSource instance = mock(); + protected Method method; + + protected final ExtensionContext extensionContext = mock(); + + private final JupiterConfiguration configuration = mock(); + + protected final MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( + configuration); + + @Test + void constructorInjection() { + register(new StringParameterResolver(), new NumberParameterResolver()); + + Class outerClass = ConstructorInjectionTestCase.class; + Constructor constructor = ReflectionUtils.getDeclaredConstructor(outerClass); + ConstructorInjectionTestCase outer = invokeConstructor(constructor, null); + + assertNotNull(outer); + assertEquals(ENIGMA, outer.str); + + Class innerClass = ConstructorInjectionTestCase.NestedTestCase.class; + Constructor innerConstructor = ReflectionUtils.getDeclaredConstructor( + innerClass); + ConstructorInjectionTestCase.NestedTestCase inner = invokeConstructor(innerConstructor, outer); + + assertNotNull(inner); + assertEquals(42, inner.num); + } + + @Test + void resolveArgumentsViaParameterResolver() { + testMethodWithASingleStringParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo("argument"); + + invokeMethod(); + + verify(instance).singleStringParameter("argument"); + } + + private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) { + register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> argument)); + } + + private void testMethodWithASingleStringParameter() { + this.method = ReflectionUtils.findMethod(this.instance.getClass(), "singleStringParameter", String.class).get(); + } + + private void register(ParameterResolver... resolvers) { + for (ParameterResolver resolver : resolvers) { + extensionRegistry.registerExtension(resolver, this); + } + } + + abstract void invokeMethod(); + + abstract T invokeConstructor(Constructor constructor, Object outerInstance); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java new file mode 100644 index 00000000..ab54aade --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import java.lang.reflect.Constructor; + +/** + * Unit tests for {@link DefaultExecutableInvoker}. + * + * @since 5.9 + */ +class DefaultExecutableInvokerTests extends AbstractExecutableInvokerTests { + + @Override + void invokeMethod() { + newInvoker().invoke(this.method, this.instance); + } + + @Override + T invokeConstructor(Constructor constructor, Object outerInstance) { + return newInvoker().invoke(constructor, outerInstance); + } + + private DefaultExecutableInvoker newInvoker() { + return new DefaultExecutableInvoker(this.extensionContext, this.extensionRegistry); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java new file mode 100644 index 00000000..d3881a3d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class DefaultTestInstancesTests { + + @Test + void topLevelClass() { + DefaultTestInstances instances = DefaultTestInstances.of(this); + + assertThat(instances.getInnermostInstance()).isSameAs(this); + assertThat(instances.getAllInstances()).containsExactly(this); + assertThat(instances.getEnclosingInstances()).isEmpty(); + assertThat(instances.findInstance(Object.class)).contains(this); + assertThat(instances.findInstance(String.class)).isEmpty(); + } + + @Test + void nestedLevelClass() { + DefaultTestInstancesTests outermost = this; + Nested innermost = new Nested(); + DefaultTestInstances instances = DefaultTestInstances.of(DefaultTestInstances.of(outermost), innermost); + + assertThat(instances.getInnermostInstance()).isSameAs(innermost); + assertThat(instances.getAllInstances()).containsExactly(outermost, innermost); + assertThat(instances.getEnclosingInstances()).containsExactly(outermost); + assertThat(instances.findInstance(Object.class)).contains(innermost); + assertThat(instances.findInstance(Nested.class)).contains(innermost); + assertThat(instances.findInstance(DefaultTestInstancesTests.class)).contains(outermost); + assertThat(instances.findInstance(String.class)).isEmpty(); + } + + class Nested { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java new file mode 100644 index 00000000..c03c6581 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +/** + * Integration tests for dynamic tests. + * + * @since 5.5 + */ +class DynamicTestIntegrationTests { + + private static final int TEN_MB = 10 * 1024 * 1024; + + /** + * Without the fix in {@code DynamicTestTestDescriptor}, setting the + * {@code -mx200m} VM argument will cause an {@link OutOfMemoryError} before + * the 200 limit is reached. + * + * @see Issue 1865 + */ + @TestFactory + Stream generateDynamicTestsThatReferenceLargeAmountsOfMemory() { + return Stream.generate(() -> new byte[TEN_MB])// + // The lambda Executable in the following line *must* reference + // the `bytes` array in order to hold onto the allocated memory. + .map(bytes -> dynamicTest("test", () -> assertNotNull(bytes)))// + .limit(200); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java new file mode 100644 index 00000000..f09fb699 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +/** + * Concurrency tests for {@link NamespaceAwareStore} and {@link ExtensionValuesStore}. + * + * @since 5.0 + */ +class ExtensionContextStoreConcurrencyTests { + + private final AtomicInteger count = new AtomicInteger(); + + @Test + void concurrentAccessToDefaultStoreWithoutParentStore() { + // Run the actual test 100 times "for good measure". + IntStream.range(1, 100).forEach(i -> { + Store store = reset(); + // Simulate 100 extensions interacting concurrently with the Store. + IntStream.range(1, 100).parallel().forEach(j -> store.getOrComputeIfAbsent("key", this::newValue)); + assertEquals(1, count.get(), () -> "number of times newValue() was invoked in run #" + i); + }); + } + + private String newValue(String key) { + count.incrementAndGet(); + return "value"; + } + + private Store reset() { + count.set(0); + return new NamespaceAwareStore(new ExtensionValuesStore(null), Namespace.GLOBAL); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java new file mode 100644 index 00000000..16f40334 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ExtensionContextException; + +/** + * Unit tests for {@link NamespaceAwareStore} and {@link ExtensionValuesStore}. + * + * @since 5.5 + * @see ExtensionContextStoreConcurrencyTests + * @see ExtensionValuesStoreTests + */ +class ExtensionContextStoreTests { + + private static final String KEY = "key"; + private static final String VALUE = "value"; + + private final ExtensionValuesStore parentStore = new ExtensionValuesStore(null); + private final ExtensionValuesStore localStore = new ExtensionValuesStore(parentStore); + private final Store store = new NamespaceAwareStore(localStore, Namespace.GLOBAL); + + @Test + void getOrDefaultWithNoValuePresent() { + assertThat(store.get(KEY)).isNull(); + + assertThat(store.getOrDefault(KEY, boolean.class, true)).isTrue(); + assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); + } + + @Test + void getOrDefaultRequestingIncompatibleType() { + localStore.put(Namespace.GLOBAL, KEY, VALUE); + assertThat(store.get(KEY)).isEqualTo(VALUE); + + Exception exception = assertThrows(ExtensionContextException.class, + () -> store.getOrDefault(KEY, boolean.class, true)); + assertThat(exception).hasMessageContaining("is not of required type"); + } + + @Test + void getOrDefaultWithValueInLocalStore() { + localStore.put(Namespace.GLOBAL, KEY, VALUE); + assertThat(store.get(KEY)).isEqualTo(VALUE); + + assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); + } + + @Test + void getOrDefaultWithValueInParentStore() { + parentStore.put(Namespace.GLOBAL, KEY, VALUE); + assertThat(store.get(KEY)).isEqualTo(VALUE); + + assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); + } + + @Test + void getOrComputeIfAbsentWithFailingCreator() { + var invocations = new AtomicInteger(); + + var e1 = assertThrows(RuntimeException.class, () -> store.getOrComputeIfAbsent(KEY, __ -> { + invocations.incrementAndGet(); + throw new RuntimeException(); + })); + var e2 = assertThrows(RuntimeException.class, () -> store.get(KEY)); + assertSame(e1, e2); + + assertDoesNotThrow(localStore::closeAllStoredCloseableValues); + assertThat(invocations).hasValue(1); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java new file mode 100644 index 00000000..04d6fc00 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java @@ -0,0 +1,379 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContextException; + +/** + * Unit tests for {@link ExtensionValuesStore}. + * + * @since 5.0 + * @see org.junit.jupiter.engine.descriptor.ExtensionContextTests + */ +public class ExtensionValuesStoreTests { + + private final Object key = "key"; + private final Object value = "value"; + + private final Namespace namespace = Namespace.create("ns"); + + private final ExtensionValuesStore grandParentStore = new ExtensionValuesStore(null); + private final ExtensionValuesStore parentStore = new ExtensionValuesStore(grandParentStore); + private final ExtensionValuesStore store = new ExtensionValuesStore(parentStore); + + @Nested + class StoringValuesTests { + + @Test + void getWithUnknownKeyReturnsNull() { + assertNull(store.get(namespace, "unknown key")); + } + + @Test + void putAndGetWithSameKey() { + store.put(namespace, key, value); + assertEquals(value, store.get(namespace, key)); + } + + @Test + void valueCanBeReplaced() { + store.put(namespace, key, value); + + Object newValue = new Object(); + store.put(namespace, key, newValue); + + assertEquals(newValue, store.get(namespace, key)); + } + + @Test + void valueIsComputedIfAbsent() { + assertNull(store.get(namespace, key)); + assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> value)); + assertEquals(value, store.get(namespace, key)); + } + + @Test + void valueIsNotComputedIfPresentLocally() { + store.put(namespace, key, value); + + assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); + assertEquals(value, store.get(namespace, key)); + } + + @Test + void valueIsNotComputedIfPresentInParent() { + parentStore.put(namespace, key, value); + + assertEquals(value, store.getOrComputeIfAbsent(namespace, key, k -> "a different value")); + assertEquals(value, store.get(namespace, key)); + } + + @Test + void valueIsNotComputedIfPresentInGrandParent() { + grandParentStore.put(namespace, key, value); + + assertEquals(value, store.getOrComputeIfAbsent(namespace, key, k -> "a different value")); + assertEquals(value, store.get(namespace, key)); + } + + @Test + void nullIsAValidValueToPut() { + store.put(namespace, key, null); + + assertNull(store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); + assertNull(store.get(namespace, key)); + } + + @Test + void keysCanBeRemoved() { + store.put(namespace, key, value); + assertEquals(value, store.remove(namespace, key)); + + assertNull(store.get(namespace, key)); + assertEquals("a different value", + store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); + } + + @Test + void sameKeyWithDifferentNamespaces() { + Object value1 = createObject("value1"); + Namespace namespace1 = Namespace.create("ns1"); + + Object value2 = createObject("value2"); + Namespace namespace2 = Namespace.create("ns2"); + + store.put(namespace1, key, value1); + store.put(namespace2, key, value2); + + assertEquals(value1, store.get(namespace1, key)); + assertEquals(value2, store.get(namespace2, key)); + } + + @Test + void valueIsComputedIfAbsentInDifferentNamespace() { + Namespace namespace1 = Namespace.create("ns1"); + Namespace namespace2 = Namespace.create("ns2"); + + assertEquals(value, store.getOrComputeIfAbsent(namespace1, key, innerKey -> value)); + assertEquals(value, store.get(namespace1, key)); + + assertNull(store.get(namespace2, key)); + } + + @Test + void keyIsOnlyRemovedInGivenNamespace() { + Namespace namespace1 = Namespace.create("ns1"); + Namespace namespace2 = Namespace.create("ns2"); + + Object value1 = createObject("value1"); + Object value2 = createObject("value2"); + + store.put(namespace1, key, value1); + store.put(namespace2, key, value2); + store.remove(namespace1, key); + + assertNull(store.get(namespace1, key)); + assertEquals(value2, store.get(namespace2, key)); + } + + @Test + void getWithTypeSafetyAndInvalidRequiredTypeThrowsException() { + Integer key = 42; + String value = "enigma"; + store.put(namespace, key, value); + + Exception exception = assertThrows(ExtensionContextException.class, + () -> store.get(namespace, key, Number.class)); + assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", + exception.getMessage()); + } + + @Test + void getWithTypeSafety() { + Integer key = 42; + String value = "enigma"; + store.put(namespace, key, value); + + // The fact that we can declare this as a String suffices for testing the required type. + String requiredTypeValue = store.get(namespace, key, String.class); + assertEquals(value, requiredTypeValue); + } + + @Test + void getWithTypeSafetyAndPrimitiveValueType() { + String key = "enigma"; + int value = 42; + store.put(namespace, key, value); + + // The fact that we can declare this as an int/Integer suffices for testing the required type. + int requiredInt = store.get(namespace, key, int.class); + Integer requiredInteger = store.get(namespace, key, Integer.class); + assertEquals(value, requiredInt); + assertEquals(value, requiredInteger.intValue()); + } + + @Test + void getNullValueWithTypeSafety() { + store.put(namespace, key, null); + + // The fact that we can declare this as a String suffices for testing the required type. + String requiredTypeValue = store.get(namespace, key, String.class); + assertNull(requiredTypeValue); + } + + @Test + void getOrComputeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { + String key = "pi"; + Float value = 3.14f; + + // Store a Float... + store.put(namespace, key, value); + + // But declare that our function creates a String... + Function defaultCreator = k -> "enigma"; + + Exception exception = assertThrows(ExtensionContextException.class, + () -> store.getOrComputeIfAbsent(namespace, key, defaultCreator, String.class)); + assertEquals("Object stored under key [pi] is not of required type [java.lang.String]", + exception.getMessage()); + } + + @Test + void getOrComputeIfAbsentWithTypeSafety() { + Integer key = 42; + String value = "enigma"; + + // The fact that we can declare this as a String suffices for testing the required type. + String computedValue = store.getOrComputeIfAbsent(namespace, key, k -> value, String.class); + assertEquals(value, computedValue); + } + + @Test + void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { + String key = "enigma"; + int value = 42; + + // The fact that we can declare this as an int/Integer suffices for testing the required type. + int computedInt = store.getOrComputeIfAbsent(namespace, key, k -> value, int.class); + Integer computedInteger = store.getOrComputeIfAbsent(namespace, key, k -> value, Integer.class); + assertEquals(value, computedInt); + assertEquals(value, computedInteger.intValue()); + } + + @Test + void removeWithTypeSafetyAndInvalidRequiredTypeThrowsException() { + Integer key = 42; + String value = "enigma"; + store.put(namespace, key, value); + + Exception exception = assertThrows(ExtensionContextException.class, + () -> store.remove(namespace, key, Number.class)); + assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", + exception.getMessage()); + } + + @Test + void removeWithTypeSafety() { + Integer key = 42; + String value = "enigma"; + store.put(namespace, key, value); + + // The fact that we can declare this as a String suffices for testing the required type. + String removedValue = store.remove(namespace, key, String.class); + assertEquals(value, removedValue); + assertNull(store.get(namespace, key)); + } + + @Test + void removeWithTypeSafetyAndPrimitiveValueType() { + String key = "enigma"; + int value = 42; + store.put(namespace, key, value); + + // The fact that we can declare this as an int suffices for testing the required type. + int requiredInt = store.remove(namespace, key, int.class); + assertEquals(value, requiredInt); + + store.put(namespace, key, value); + // The fact that we can declare this as an Integer suffices for testing the required type. + Integer requiredInteger = store.get(namespace, key, Integer.class); + assertEquals(value, requiredInteger.intValue()); + } + + @Test + void removeNullValueWithTypeSafety() { + Integer key = 42; + store.put(namespace, key, null); + + // The fact that we can declare this as a String suffices for testing the required type. + String removedValue = store.remove(namespace, key, String.class); + assertNull(removedValue); + assertNull(store.get(namespace, key)); + } + + @Test + void simulateRaceConditionInGetOrComputeIfAbsent() throws Exception { + int threads = 10; + AtomicInteger counter = new AtomicInteger(); + ExtensionValuesStore localStore = new ExtensionValuesStore(null); + + List values = executeConcurrently(threads, // + () -> localStore.getOrComputeIfAbsent(namespace, key, it -> counter.incrementAndGet())); + + assertEquals(1, counter.get()); + assertThat(values).hasSize(threads).containsOnly(1); + } + } + + @Nested + class InheritedValuesTests { + + @Test + void valueFromParentIsVisible() { + parentStore.put(namespace, key, value); + assertEquals(value, store.get(namespace, key)); + } + + @Test + void valueFromParentCanBeOverriddenInChild() { + parentStore.put(namespace, key, value); + + Object otherValue = new Object(); + store.put(namespace, key, otherValue); + assertEquals(otherValue, store.get(namespace, key)); + + assertEquals(value, parentStore.get(namespace, key)); + } + } + + @Nested + class CompositeNamespaceTests { + + @Test + void namespacesEqualForSamePartsSequence() { + Namespace ns1 = Namespace.create("part1", "part2"); + Namespace ns2 = Namespace.create("part1", "part2"); + + assertEquals(ns1, ns2); + } + + @Test + void orderOfNamespacePartsDoesMatter() { + Namespace ns1 = Namespace.create("part1", "part2"); + Namespace ns2 = Namespace.create("part2", "part1"); + + assertNotEquals(ns1, ns2); + } + + @Test + void additionNamespacePartMakesADifference() { + + Namespace ns1 = Namespace.create("part1", "part2"); + Namespace ns2 = Namespace.create("part1"); + Namespace ns3 = Namespace.create("part1", "part2"); + + Object value2 = createObject("value2"); + + parentStore.put(ns1, key, value); + parentStore.put(ns2, key, value2); + + assertEquals(value, store.get(ns1, key)); + assertEquals(value, store.get(ns3, key)); + assertEquals(value2, store.get(ns2, key)); + } + + } + + private Object createObject(final String display) { + return new Object() { + + @Override + public String toString() { + return display; + } + }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java new file mode 100644 index 00000000..54431766 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.util.Optional; + +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; + +/** + * Unit tests for {@link InterceptingExecutableInvoker}. + * + * @since 5.0 + */ +class InterceptingExecutableInvokerTests extends AbstractExecutableInvokerTests { + + @Override + void invokeMethod() { + newInvoker().invoke(this.method, this.instance, this.extensionContext, this.extensionRegistry, + passthroughInterceptor()); + } + + @Override + T invokeConstructor(Constructor constructor, Object outerInstance) { + return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), extensionContext, extensionRegistry, + passthroughInterceptor()); + } + + private InterceptingExecutableInvoker newInvoker() { + return new InterceptingExecutableInvoker(); + } + + private static ReflectiveInterceptorCall passthroughInterceptor() { + return (interceptor, invocation, invocationContext, extensionContext) -> invocation.proceed(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java new file mode 100644 index 00000000..d6e619d8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; + +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.EngineExecutionListener; + +/** + * Unit tests for {@link JupiterEngineExecutionContext}. + * + * @since 5.0 + */ +class JupiterEngineExecutionContextTests { + + private final JupiterConfiguration configuration = mock(); + + private final EngineExecutionListener engineExecutionListener = mock(); + + private final JupiterEngineExecutionContext originalContext = new JupiterEngineExecutionContext( + engineExecutionListener, configuration); + + @Test + void executionListenerIsHandedOnWhenContextIsExtended() { + assertSame(engineExecutionListener, originalContext.getExecutionListener()); + JupiterEngineExecutionContext newContext = originalContext.extend().build(); + assertSame(engineExecutionListener, newContext.getExecutionListener()); + } + + @Test + void extendWithAllAttributes() { + ExtensionContext extensionContext = mock(); + MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( + configuration); + TestInstancesProvider testInstancesProvider = mock(); + JupiterEngineExecutionContext newContext = originalContext.extend() // + .withExtensionContext(extensionContext) // + .withExtensionRegistry(extensionRegistry) // + .withTestInstancesProvider(testInstancesProvider) // + .build(); + + assertSame(extensionContext, newContext.getExtensionContext()); + assertSame(extensionRegistry, newContext.getExtensionRegistry()); + assertSame(testInstancesProvider, newContext.getTestInstancesProvider()); + } + + @Test + void canOverrideAttributeWhenContextIsExtended() { + ExtensionContext extensionContext = mock(); + MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( + configuration); + TestInstancesProvider testInstancesProvider = mock(); + ExtensionContext newExtensionContext = mock(); + + JupiterEngineExecutionContext newContext = originalContext.extend() // + .withExtensionContext(extensionContext) // + .withExtensionRegistry(extensionRegistry) // + .withTestInstancesProvider(testInstancesProvider) // + .build() // + .extend() // + .withExtensionContext(newExtensionContext) // + .build(); + + assertSame(newExtensionContext, newContext.getExtensionContext()); + assertSame(extensionRegistry, newContext.getExtensionRegistry()); + assertSame(testInstancesProvider, newContext.getTestInstancesProvider()); + } + + @Test + @TrackLogRecords + void closeAttemptExceptionWillBeThrownDownTheCallStack(LogRecordListener logRecordListener) throws Exception { + ExtensionContext failingExtensionContext = mock(ExtensionContext.class, + withSettings().extraInterfaces(AutoCloseable.class)); + Exception expectedException = new Exception("test message"); + doThrow(expectedException).when(((AutoCloseable) failingExtensionContext)).close(); + + JupiterEngineExecutionContext newContext = originalContext.extend() // + .withExtensionContext(failingExtensionContext) // + .build(); + + Exception actualException = assertThrows(Exception.class, newContext::close); + + assertSame(expectedException, actualException); + assertThat(logRecordListener.stream(JupiterEngineExecutionContext.class, Level.SEVERE)) // + .extracting(LogRecord::getMessage) // + .containsOnly("Caught exception while closing extension context: " + failingExtensionContext); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java new file mode 100644 index 00000000..1c371f6f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java @@ -0,0 +1,465 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link ParameterResolutionUtils}. + * + * @since 5.9 + */ +class ParameterResolutionUtilsTests { + + private static final String ENIGMA = "enigma"; + + private final MethodSource instance = mock(); + private Method method; + + private final ExtensionContext extensionContext = mock(); + + private final JupiterConfiguration configuration = mock(); + + private final MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( + configuration); + + @Test + void resolveConstructorArguments() { + register(new StringParameterResolver()); + + Class topLevelClass = ConstructorInjectionTestCase.class; + Object[] arguments = resolveConstructorParameters(topLevelClass, null); + + assertThat(arguments).containsExactly(ENIGMA); + } + + @Test + void resolveNestedConstructorArguments() { + register(new NumberParameterResolver()); + + Class outerClass = ConstructorInjectionTestCase.class; + ConstructorInjectionTestCase outer = ReflectionUtils.newInstance(outerClass, "str"); + + Class innerClass = ConstructorInjectionTestCase.NestedTestCase.class; + Object[] arguments = resolveConstructorParameters(innerClass, outer); + + assertThat(arguments).containsExactly(outer, 42); + } + + @Test + void resolveConstructorArgumentsWithMissingResolver() { + Constructor constructor = ReflectionUtils.getDeclaredConstructor( + ConstructorInjectionTestCase.class); + + Exception exception = assertThrows(ParameterResolutionException.class, + () -> ParameterResolutionUtils.resolveParameters(constructor, Optional.empty(), Optional.empty(), + extensionContext, extensionRegistry)); + + assertThat(exception.getMessage())// + .contains("No ParameterResolver registered for parameter [java.lang.String")// + .contains("in constructor")// + .contains(ConstructorInjectionTestCase.class.getName()); + } + + @Test + void resolvingArgumentsForMethodsWithoutParameterDoesNotDependOnParameterResolvers() { + testMethodWithNoParameters(); + throwDuringParameterResolution(new RuntimeException("boom!")); + + Object[] arguments = resolveMethodParameters(); + + assertThat(arguments).isEmpty(); + } + + @Test + void resolveArgumentsViaParameterResolver() { + testMethodWithASingleStringParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo("argument"); + + Object[] arguments = resolveMethodParameters(); + + assertThat(arguments).containsExactly("argument"); + } + + @Test + void resolveMultipleArguments() { + testMethodWith("multipleParameters", String.class, Integer.class, Double.class); + register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> { + switch (parameterContext.getIndex()) { + case 0: + return "0"; + case 1: + return 1; + default: + return 2.0; + } + })); + + Object[] arguments = resolveMethodParameters(); + + assertThat(arguments).containsExactly("0", 1, 2.0); + } + + @Test + void onlyConsiderParameterResolversThatSupportAParticularParameter() { + testMethodWithASingleStringParameter(); + thereIsAParameterResolverThatDoesNotSupportThisParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo("something"); + + Object[] arguments = resolveMethodParameters(); + + assertThat(arguments).containsExactly("something"); + } + + @Test + void passContextInformationToParameterResolverMethods() { + anyTestMethodWithAtLeastOneParameter(); + ArgumentRecordingParameterResolver extension = new ArgumentRecordingParameterResolver(); + register(extension); + + resolveMethodParameters(); + + assertSame(extensionContext, extension.supportsArguments.extensionContext); + assertEquals(0, extension.supportsArguments.parameterContext.getIndex()); + assertSame(instance, extension.supportsArguments.parameterContext.getTarget().get()); + assertSame(extensionContext, extension.resolveArguments.extensionContext); + assertEquals(0, extension.resolveArguments.parameterContext.getIndex()); + assertSame(instance, extension.resolveArguments.parameterContext.getTarget().get()); + assertThat(extension.resolveArguments.parameterContext.toString())// + .contains("parameter", String.class.getTypeName(), "index", "0", "target", "Mock"); + } + + @Test + void resolvingArgumentsForMethodsWithPrimitiveTypesIsSupported() { + testMethodWithASinglePrimitiveIntParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo(42); + + Object[] arguments = resolveMethodParameters(); + + assertThat(arguments).containsExactly(42); + } + + @Test + void nullIsAViableArgumentIfAReferenceTypeParameterIsExpected() { + testMethodWithASingleStringParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo(null); + + Object[] arguments = resolveMethodParameters(); + + assertThat(arguments).hasSize(1); + assertNull(arguments[0]); + } + + @Test + void reportThatNullIsNotAViableArgumentIfAPrimitiveTypeIsExpected() { + testMethodWithASinglePrimitiveIntParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo(null); + + ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, + this::resolveMethodParameters); + + // @formatter:off + assertThat(caught.getMessage()) + .contains("in method") + .contains("resolved a null value for parameter [int") + .contains("but a primitive of type [int] is required."); + // @formatter:on + } + + @Test + void reportIfThereIsNoParameterResolverThatSupportsTheParameter() { + testMethodWithASingleStringParameter(); + + ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, + this::resolveMethodParameters); + + assertThat(caught.getMessage()).contains("parameter [java.lang.String").contains("in method"); + } + + @Test + void reportIfThereAreMultipleParameterResolversThatSupportTheParameter() { + testMethodWithASingleStringParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo("one"); + thereIsAParameterResolverThatResolvesTheParameterTo("two"); + + ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, + this::resolveMethodParameters); + + String className = Pattern.quote(ConfigurableParameterResolver.class.getName()); + + // @formatter:off + assertThat(caught.getMessage()) + .matches("Discovered multiple competing ParameterResolvers for parameter \\[java.lang.String .+?\\] " + + "in method .+: " + className + "@.+, " + className + "@.+"); + // @formatter:on + } + + @Test + void reportTypeMismatchBetweenParameterAndResolvedParameter() { + testMethodWithASingleStringParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo(BigDecimal.ONE); + + ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, + this::resolveMethodParameters); + + // @formatter:off + assertThat(caught.getMessage()) + .contains("resolved a value of type [java.math.BigDecimal] for parameter [java.lang.String") + .contains("in method") + .contains("but a value assignment compatible with [java.lang.String] is required."); + // @formatter:on + } + + @Test + void wrapAllExceptionsThrownDuringParameterResolutionIntoAParameterResolutionException() { + anyTestMethodWithAtLeastOneParameter(); + IllegalArgumentException cause = anyExceptionButParameterResolutionException(); + throwDuringParameterResolution(cause); + + ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, + this::resolveMethodParameters); + + assertSame(cause, caught.getCause(), () -> "cause should be present"); + assertThat(caught.getMessage())// + .matches("^Failed to resolve parameter \\[java.lang.String .+?\\] in method \\[.+?\\]$"); + } + + @Test + void exceptionMessageContainsMessageFromExceptionThrownDuringParameterResolution() { + anyTestMethodWithAtLeastOneParameter(); + RuntimeException cause = new RuntimeException("boom!"); + throwDuringParameterResolution(cause); + + ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, + this::resolveMethodParameters); + + assertSame(cause, caught.getCause(), () -> "cause should be present"); + assertThat(caught.getMessage())// + .matches("^Failed to resolve parameter \\[java.lang.String .+?\\] in method \\[.+?\\]: boom!$"); + } + + @Test + void doNotWrapThrownExceptionIfItIsAlreadyAParameterResolutionException() { + anyTestMethodWithAtLeastOneParameter(); + ParameterResolutionException cause = new ParameterResolutionException("custom message"); + throwDuringParameterResolution(cause); + + ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, + this::resolveMethodParameters); + + assertSame(cause, caught); + } + + private IllegalArgumentException anyExceptionButParameterResolutionException() { + return new IllegalArgumentException(); + } + + private void throwDuringParameterResolution(RuntimeException parameterResolutionException) { + register(ConfigurableParameterResolver.onAnyCallThrow(parameterResolutionException)); + } + + private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) { + register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> argument)); + } + + private void thereIsAParameterResolverThatDoesNotSupportThisParameter() { + register(ConfigurableParameterResolver.withoutSupport()); + } + + private void anyTestMethodWithAtLeastOneParameter() { + testMethodWithASingleStringParameter(); + } + + private void testMethodWithNoParameters() { + testMethodWith("noParameter"); + } + + private void testMethodWithASingleStringParameter() { + testMethodWith("singleStringParameter", String.class); + } + + private void testMethodWithASinglePrimitiveIntParameter() { + testMethodWith("primitiveParameterInt", int.class); + } + + private void testMethodWith(String methodName, Class... parameterTypes) { + this.method = ReflectionUtils.findMethod(this.instance.getClass(), methodName, parameterTypes).get(); + } + + private void register(ParameterResolver... resolvers) { + for (ParameterResolver resolver : resolvers) { + extensionRegistry.registerExtension(resolver, this); + } + } + + private Object[] resolveConstructorParameters(Class clazz, Object outerInstance) { + Constructor constructor = ReflectionUtils.getDeclaredConstructor(clazz); + return ParameterResolutionUtils.resolveParameters(constructor, Optional.empty(), + Optional.ofNullable(outerInstance), extensionContext, extensionRegistry); + } + + private Object[] resolveMethodParameters() { + return ParameterResolutionUtils.resolveParameters(this.method, Optional.of(this.instance), + this.extensionContext, this.extensionRegistry); + } + + // ------------------------------------------------------------------------- + + static class ArgumentRecordingParameterResolver implements ParameterResolver { + + ArgumentRecordingParameterResolver.Arguments supportsArguments; + ArgumentRecordingParameterResolver.Arguments resolveArguments; + + static class Arguments { + + final ParameterContext parameterContext; + final ExtensionContext extensionContext; + + Arguments(ParameterContext parameterContext, ExtensionContext extensionContext) { + this.parameterContext = parameterContext; + this.extensionContext = extensionContext; + } + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + supportsArguments = new ArgumentRecordingParameterResolver.Arguments(parameterContext, extensionContext); + return true; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + resolveArguments = new ArgumentRecordingParameterResolver.Arguments(parameterContext, extensionContext); + return null; + } + } + + static class ConfigurableParameterResolver implements ParameterResolver { + + static ParameterResolver onAnyCallThrow(RuntimeException runtimeException) { + return new ConfigurableParameterResolver(parameterContext -> { + throw runtimeException; + }, parameterContext -> { + throw runtimeException; + }); + } + + static ParameterResolver supportsAndResolvesTo(Function resolve) { + return new ConfigurableParameterResolver(parameterContext -> true, resolve); + } + + static ParameterResolver withoutSupport() { + return new ConfigurableParameterResolver(parameterContext -> false, parameter -> { + throw new UnsupportedOperationException(); + }); + } + + private final Predicate supports; + private final Function resolve; + + private ConfigurableParameterResolver(Predicate supports, + Function resolve) { + this.supports = supports; + this.resolve = resolve; + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return supports.test(parameterContext); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return resolve.apply(parameterContext); + } + } + + @SuppressWarnings("unused") + interface MethodSource { + + void noParameter(); + + void singleStringParameter(String parameter); + + void primitiveParameterInt(int parameter); + + void multipleParameters(String first, Integer second, Double third); + } + + static class StringParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == String.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return ENIGMA; + } + } + + static class NumberParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == Number.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 42; + } + } + + static class ConstructorInjectionTestCase { + + final String str; + + @SuppressWarnings("unused") + ConstructorInjectionTestCase(String str) { + this.str = str; + } + + class NestedTestCase { + + final Number num; + + @SuppressWarnings("unused") + NestedTestCase(Number num) { + this.num = num; + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java new file mode 100644 index 00000000..a5184a84 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.execution.injection.sample.PrimitiveArrayParameterResolver; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Event; + +/** + * Integration tests for {@link UniqueId#parse(String)} for methods + * with array type parameters. + * + * @see #810 + * @see org.junit.platform.engine.UniqueIdTests + * + * @since 5.0 + */ +class UniqueIdParsingForArrayParameterIntegrationTests extends AbstractJupiterTestEngineTests { + + @Test + void executeTestsForPrimitiveArrayMethodInjectionCases() { + EngineExecutionResults executionResults = executeTestsForClass(PrimitiveArrayMethodInjectionTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + UniqueId uniqueId = executionResults.allEvents() + .map(Event::getTestDescriptor) + .distinct() + .skip(2) + .map(TestDescriptor::getUniqueId) + .findFirst() + .orElseThrow(AssertionError::new); + // @formatter:on + + assertThat(UniqueId.parse(uniqueId.toString())).isEqualTo(uniqueId); + } + + @ExtendWith(PrimitiveArrayParameterResolver.class) + static class PrimitiveArrayMethodInjectionTestCase { + + @Test + void primitiveArray(int... ints) { + assertArrayEquals(new int[] { 1, 2, 3 }, ints); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java new file mode 100644 index 00000000..01d89a57 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @since 5.0 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface CustomAnnotation { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java new file mode 100644 index 00000000..b24aad8b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.0 + */ +public class CustomAnnotationParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + // We invoke parameterContext.isAnnotated() instead of parameterContext.getParameter().isAnnotationPresent() + // in order to verify support for the convenience method in the ParameterContext API. + return parameterContext.isAnnotated(CustomAnnotation.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return ReflectionUtils.newInstance(parameterContext.getParameter().getType()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java new file mode 100644 index 00000000..e9df48a0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import java.util.Date; + +/** + * @since 5.0 + */ +public class CustomType { + + private final Date date = new Date(); + + @Override + public String toString() { + return "CustomType: " + this.date; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java new file mode 100644 index 00000000..29078266 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @since 5.0 + */ +public class CustomTypeParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType().equals(CustomType.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return new CustomType(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java new file mode 100644 index 00000000..3f4ee2c5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * Example {@link ParameterResolver} that always resolves a {@link Double} + * parameter to {@code 42.0}. + * + * @since 5.0 + */ +public class DoubleParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == Double.class; + } + + @Override + public Double resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 42.0; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java new file mode 100644 index 00000000..9b796003 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * Example {@link ParameterResolver} that always resolves a {@link Long} + * parameter to {@code 42}. + * + *

This resolver also attempts to support generic parameter type + * declarations if the generic type is defined at the class level in a + * superclass or interface (extended or implemented by the test class) and + * is assignable from {@link Long}. + * + * @since 5.0 + */ +public class LongParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + // Exact match? + if (parameterContext.getParameter().getType() == Long.class) { + return true; + } + + Type typeInMethod = parameterContext.getParameter().getParameterizedType(); + + // Type variables in parameterized class + for (TypeVariable typeVariable : parameterContext.getDeclaringExecutable().getDeclaringClass().getTypeParameters()) { + boolean namesMatch = typeInMethod.getTypeName().equals(typeVariable.getName()); + boolean typesAreCompatible = typeVariable.getBounds().length == 1 && // + typeVariable.getBounds()[0] instanceof Class && // + ((Class) typeVariable.getBounds()[0]).isAssignableFrom(Long.class); + + if (namesMatch && typesAreCompatible) { + return true; + } + } + + return false; + + } + + @Override + public Long resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 42L; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java new file mode 100644 index 00000000..ce4f0f6f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; + +/** + * @since 5.6 + */ +public class MapOfListsTypeBasedParameterResolver extends TypeBasedParameterResolver>> { + + @Override + public Map> resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + + return Map.of("ids", List.of(1, 42)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java new file mode 100644 index 00000000..d8f94f09 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.TreeMap; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * Example {@link ParameterResolver} that resolves {@code Map} types. + * + * @since 5.0 + */ +public class MapOfStringsParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + Type type = parameterContext.getParameter().getParameterizedType(); + if (!(type instanceof ParameterizedType)) { + return false; + } + Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); + if (actualTypeArguments.length != 2) { + return false; + } + return actualTypeArguments[0] == String.class && actualTypeArguments[1] == String.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + Map map = new TreeMap<>(); + map.put("key", "value"); + return map; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java new file mode 100644 index 00000000..c44c9dd2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * Example {@link ParameterResolver} that always resolves an + * {@link Integer} or {@code int} parameter to a {@code null} value. + * + * @since 5.0 + */ +public class NullIntegerParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return Integer.class == parameterContext.getParameter().getType() + || int.class == parameterContext.getParameter().getType(); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return null; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java new file mode 100644 index 00000000..f1c826ae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * This is a non-realistic {@link ParameterResolver} that claims to + * resolve any {@link Number}, when in fact it always resolves an {@link Integer}. + * + *

This may appear nonsensical; however, there are use cases for which a + * resolver may think that it can support a particular parameter only to + * discover later that the actual resolved value is not assignment compatible. + * + *

For example, consider the case with Spring: the {@code SpringExtension} can + * theoretically resolve any type of {@code ApplicationContext}, but if the + * required parameter type is {@code WebApplicationContext} and the user + * neglects to annotate the test class with {@code @WebAppConfiguration} then + * the {@code ApplicationContext} loaded by Spring's testing support will in + * fact be an {@code ApplicationContext} but not a {@code WebApplicationContext}. + * Since Spring does not determine this in advance, such a scenario would lead to + * an {@link IllegalArgumentException} with the message "argument type mismatch" + * when JUnit attempts to invoke the test method. + * + * @since 5.0 + */ +public class NumberParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return Number.class.isAssignableFrom(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 42; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java new file mode 100644 index 00000000..2c6ce89a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * Example {@link ParameterResolver} that resolves arrays of primitive integers. + * + * @since 5.0 + */ +public class PrimitiveArrayParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return int[].class == parameterContext.getParameter().getType(); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return new int[] { 1, 2, 3 }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java new file mode 100644 index 00000000..9893412b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution.injection.sample; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * Example {@link ParameterResolver} that resolves primitive integers. + * + * @since 5.0 + */ +public class PrimitiveIntegerParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return int.class == parameterContext.getParameter().getType(); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return 42; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java new file mode 100644 index 00000000..57a128e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java @@ -0,0 +1,385 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; + +/** + * Integration tests that verify support for {@link BeforeAll}, {@link AfterAll}, + * {@link BeforeAllCallback}, and {@link AfterAllCallback} in the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class BeforeAndAfterAllTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + private static Optional actualExceptionInAfterAllCallback; + + @Test + void beforeAllAndAfterAllCallbacks() { + // @formatter:off + assertBeforeAllAndAfterAllCallbacks(TopLevelTestCase.class, + "fooBeforeAllCallback", + "barBeforeAllCallback", + "beforeAllMethod-1", + "test-1", + "afterAllMethod-1", + "barAfterAllCallback", + "fooAfterAllCallback" + ); + // @formatter:on + + assertThat(actualExceptionInAfterAllCallback).isEmpty(); + } + + @Test + void beforeAllAndAfterAllCallbacksInSubclass() { + // @formatter:off + assertBeforeAllAndAfterAllCallbacks(SecondLevelTestCase.class, + "fooBeforeAllCallback", + "barBeforeAllCallback", + "bazBeforeAllCallback", + "beforeAllMethod-1", + "beforeAllMethod-2", + "test-2", + "afterAllMethod-2", + "afterAllMethod-1", + "bazAfterAllCallback", + "barAfterAllCallback", + "fooAfterAllCallback" + ); + // @formatter:on + + assertThat(actualExceptionInAfterAllCallback).isEmpty(); + } + + @Test + void beforeAllAndAfterAllCallbacksInSubSubclass() { + // @formatter:off + assertBeforeAllAndAfterAllCallbacks(ThirdLevelTestCase.class, + "fooBeforeAllCallback", + "barBeforeAllCallback", + "bazBeforeAllCallback", + "quuxBeforeAllCallback", + "beforeAllMethod-1", + "beforeAllMethod-2", + "beforeAllMethod-3", + "test-3", + "afterAllMethod-3", + "afterAllMethod-2", + "afterAllMethod-1", + "quuxAfterAllCallback", + "bazAfterAllCallback", + "barAfterAllCallback", + "fooAfterAllCallback" + ); + // @formatter:on + + assertThat(actualExceptionInAfterAllCallback).isEmpty(); + } + + @Test + void beforeAllAndAfterAllCallbacksInSubSubclassWithStaticMethodHiding() { + // @formatter:off + assertBeforeAllAndAfterAllCallbacks(ThirdLevelStaticHidingTestCase.class, + "fooBeforeAllCallback", + "barBeforeAllCallback", + "bazBeforeAllCallback", + "quuxBeforeAllCallback", + "beforeAllMethod-1-hidden", + "beforeAllMethod-2-hidden", + "beforeAllMethod-3", + "test-3", + // The @AfterAll methods are executed as 1/2/3 due to + // the "stable" method sort order on the Platform. + "afterAllMethod-1-hidden", + "afterAllMethod-2-hidden", + "afterAllMethod-3", + "quuxAfterAllCallback", + "bazAfterAllCallback", + "barAfterAllCallback", + "fooAfterAllCallback" + ); + // @formatter:on + + assertThat(actualExceptionInAfterAllCallback).isEmpty(); + } + + @Test + void beforeAllMethodThrowsAnException() { + // @formatter:off + assertBeforeAllAndAfterAllCallbacks(ExceptionInBeforeAllMethodTestCase.class, 0, 0, + "fooBeforeAllCallback", + "beforeAllMethod", // throws an exception. + // test should not get invoked. + "afterAllMethod", + "fooAfterAllCallback" + ); + // @formatter:on + + assertThat(actualExceptionInAfterAllCallback).containsInstanceOf(EnigmaException.class); + } + + @Test + void beforeAllCallbackThrowsAnException() { + // @formatter:off + assertBeforeAllAndAfterAllCallbacks(ExceptionInBeforeAllCallbackTestCase.class, 0, 0, + "fooBeforeAllCallback", + "exceptionThrowingBeforeAllCallback", // throws an exception. + // beforeAllMethod should not get invoked. + // test should not get invoked. + // afterAllMethod should not get invoked. + "fooAfterAllCallback" + ); + // @formatter:on + + assertThat(actualExceptionInAfterAllCallback).containsInstanceOf(EnigmaException.class); + } + + private void assertBeforeAllAndAfterAllCallbacks(Class testClass, String... expectedCalls) { + assertBeforeAllAndAfterAllCallbacks(testClass, 1, 1, expectedCalls); + } + + private void assertBeforeAllAndAfterAllCallbacks(Class testClass, int testsStarted, int testsSuccessful, + String... expectedCalls) { + + callSequence.clear(); + + executeTestsForClass(testClass).testEvents()// + .assertStatistics(stats -> stats.started(testsStarted).succeeded(testsSuccessful)); + + assertEquals(asList(expectedCalls), callSequence, () -> "wrong call sequence for " + testClass.getName()); + } + + // ------------------------------------------------------------------------- + + // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @ExtendWith({ FooClassLevelCallbacks.class, BarClassLevelCallbacks.class }) + static class TopLevelTestCase { + + @BeforeAll + static void beforeAll1() { + callSequence.add("beforeAllMethod-1"); + } + + @AfterAll + static void afterAll1() { + callSequence.add("afterAllMethod-1"); + } + + @Test + void test() { + callSequence.add("test-1"); + } + } + + // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @ExtendWith(BazClassLevelCallbacks.class) + static class SecondLevelTestCase extends TopLevelTestCase { + + @BeforeAll + static void beforeAll2() { + callSequence.add("beforeAllMethod-2"); + } + + @AfterAll + static void afterAll2() { + callSequence.add("afterAllMethod-2"); + } + + @Test + @Override + void test() { + callSequence.add("test-2"); + } + } + + @ExtendWith(QuuxClassLevelCallbacks.class) + static class ThirdLevelTestCase extends SecondLevelTestCase { + + @BeforeAll + static void beforeAll3() { + callSequence.add("beforeAllMethod-3"); + } + + @AfterAll + static void afterAll3() { + callSequence.add("afterAllMethod-3"); + } + + @Test + @Override + void test() { + callSequence.add("test-3"); + } + } + + @ExtendWith(QuuxClassLevelCallbacks.class) + static class ThirdLevelStaticHidingTestCase extends SecondLevelTestCase { + + @BeforeAll + static void beforeAll1() { + callSequence.add("beforeAllMethod-1-hidden"); + } + + @BeforeAll + static void beforeAll2() { + callSequence.add("beforeAllMethod-2-hidden"); + } + + @BeforeAll + static void beforeAll3() { + callSequence.add("beforeAllMethod-3"); + } + + @AfterAll + static void afterAll1() { + callSequence.add("afterAllMethod-1-hidden"); + } + + @AfterAll + static void afterAll2() { + callSequence.add("afterAllMethod-2-hidden"); + } + + @AfterAll + static void afterAll3() { + callSequence.add("afterAllMethod-3"); + } + + @Test + @Override + void test() { + callSequence.add("test-3"); + } + } + + @ExtendWith(FooClassLevelCallbacks.class) + static class ExceptionInBeforeAllMethodTestCase { + + @BeforeAll + static void beforeAll() { + callSequence.add("beforeAllMethod"); + throw new EnigmaException("@BeforeAll"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterAll + static void afterAll() { + callSequence.add("afterAllMethod"); + } + } + + @ExtendWith({ FooClassLevelCallbacks.class, ExceptionThrowingBeforeAllCallback.class }) + static class ExceptionInBeforeAllCallbackTestCase { + + @BeforeAll + static void beforeAll() { + callSequence.add("beforeAllMethod"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterAll + static void afterAll() { + callSequence.add("afterAllMethod"); + } + } + + // ------------------------------------------------------------------------- + + static class FooClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + callSequence.add("fooBeforeAllCallback"); + } + + @Override + public void afterAll(ExtensionContext context) { + callSequence.add("fooAfterAllCallback"); + actualExceptionInAfterAllCallback = context.getExecutionException(); + } + } + + static class BarClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + callSequence.add("barBeforeAllCallback"); + } + + @Override + public void afterAll(ExtensionContext context) { + callSequence.add("barAfterAllCallback"); + } + } + + static class BazClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + callSequence.add("bazBeforeAllCallback"); + } + + @Override + public void afterAll(ExtensionContext context) { + callSequence.add("bazAfterAllCallback"); + } + } + + static class QuuxClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + callSequence.add("quuxBeforeAllCallback"); + } + + @Override + public void afterAll(ExtensionContext context) { + callSequence.add("quuxAfterAllCallback"); + } + } + + static class ExceptionThrowingBeforeAllCallback implements BeforeAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + callSequence.add("exceptionThrowingBeforeAllCallback"); + throw new EnigmaException("BeforeAllCallback"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java new file mode 100644 index 00000000..93f03748 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java @@ -0,0 +1,524 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests that verify support for {@link BeforeEach}, {@link AfterEach}, + * {@link BeforeEachCallback}, and {@link AfterEachCallback} in the {@link JupiterTestEngine}. + * + * @since 5.0 + * @see BeforeAndAfterTestExecutionCallbackTests + */ +class BeforeAndAfterEachTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + private static final List beforeEachMethodCallSequence = new ArrayList<>(); + + private static Optional actualExceptionInAfterEachCallback; + + @BeforeEach + void resetCallSequence() { + callSequence.clear(); + beforeEachMethodCallSequence.clear(); + actualExceptionInAfterEachCallback = null; + } + + @Test + void beforeEachAndAfterEachCallbacks() { + Events testEvents = executeTestsForClass(OuterTestCase.class).testEvents(); + + assertEquals(2, testEvents.started().count(), "# tests started"); + assertEquals(2, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(0, testEvents.failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + + // OuterTestCase + "fooBeforeEachCallback", + "barBeforeEachCallback", + "beforeEachMethod", + "testOuter", + "afterEachMethod", + "barAfterEachCallback", + "fooAfterEachCallback", + + // InnerTestCase + "fooBeforeEachCallback", + "barBeforeEachCallback", + "fizzBeforeEachCallback", + "beforeEachMethod", + "beforeEachInnerMethod", + "testInner", + "afterEachInnerMethod", + "afterEachMethod", + "fizzAfterEachCallback", + "barAfterEachCallback", + "fooAfterEachCallback" + + ), callSequence, "wrong call sequence"); + // @formatter:on + } + + @Test + void beforeEachAndAfterEachCallbacksDeclaredOnSuperclassAndSubclass() { + Events testEvents = executeTestsForClass(ChildTestCase.class).testEvents(); + + assertEquals(1, testEvents.started().count(), "# tests started"); + assertEquals(1, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(0, testEvents.failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "fooBeforeEachCallback", + "barBeforeEachCallback", + "testChild", + "barAfterEachCallback", + "fooAfterEachCallback" + ), callSequence, "wrong call sequence"); + // @formatter:on + } + + @Test + void beforeEachAndAfterEachCallbacksDeclaredOnInterfaceAndClass() { + Events testEvents = executeTestsForClass(TestInterfaceTestCase.class).testEvents(); + + assertEquals(2, testEvents.started().count(), "# tests started"); + assertEquals(2, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(0, testEvents.failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + + // Test Interface + "fooBeforeEachCallback", + "barBeforeEachCallback", + "defaultTestMethod", + "barAfterEachCallback", + "fooAfterEachCallback", + + // Test Class + "fooBeforeEachCallback", + "barBeforeEachCallback", + "localTestMethod", + "barAfterEachCallback", + "fooAfterEachCallback" + + ), callSequence, "wrong call sequence"); + // @formatter:on + } + + @Test + void beforeEachCallbackThrowsAnException() { + Events testEvents = executeTestsForClass(ExceptionInBeforeEachCallbackTestCase.class).testEvents(); + + assertEquals(1, testEvents.started().count(), "# tests started"); + assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(1, testEvents.failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "fooBeforeEachCallback", + "exceptionThrowingBeforeEachCallback", // throws an exception. + // barBeforeEachCallback should not get invoked. + // beforeEachMethod should not get invoked. + // test should not get invoked. + // afterEachMethod should not get invoked. + "barAfterEachCallback", + "fooAfterEachCallback" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); + } + + @Test + void afterEachCallbackThrowsAnException() { + Events testEvents = executeTestsForClass(ExceptionInAfterEachCallbackTestCase.class).testEvents(); + + assertEquals(1, testEvents.started().count(), "# tests started"); + assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(1, testEvents.failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "fooBeforeEachCallback", + "barBeforeEachCallback", + "beforeEachMethod", + "test", + "afterEachMethod", + "barAfterEachCallback", + "exceptionThrowingAfterEachCallback", // throws an exception. + "fooAfterEachCallback" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); + } + + @Test + void beforeEachMethodThrowsAnException() { + Events testEvents = executeTestsForClass(ExceptionInBeforeEachMethodTestCase.class).testEvents(); + + assertEquals(1, testEvents.started().count(), "# tests started"); + assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(1, testEvents.failed().count(), "# tests failed"); + + // Since the JVM does not guarantee the order in which methods are + // returned via reflection (and since JUnit Jupiter does not yet + // support ordering of @BeforeEach methods), we have to figure out + // which @BeforeEach method got executed first in order to determine + // the expected call sequence. + + // @formatter:off + List list1 = asList( + "fooBeforeEachCallback", + "beforeEachMethod1", // throws an exception. + // "beforeEachMethod2" should not get invoked + // test should not get invoked. + "afterEachMethod", + "fooAfterEachCallback" + ); + List list2 = asList( + "fooBeforeEachCallback", + "beforeEachMethod2", + "beforeEachMethod1", // throws an exception. + // test should not get invoked. + "afterEachMethod", + "fooAfterEachCallback" + ); + // @formatter:on + + List expected = beforeEachMethodCallSequence.get(0).equals("beforeEachMethod1") ? list1 : list2; + + assertEquals(expected, callSequence, "wrong call sequence"); + + assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); + } + + @Test + void afterEachMethodThrowsAnException() { + Events testEvents = executeTestsForClass(ExceptionInAfterEachMethodTestCase.class).testEvents(); + + assertEquals(1, testEvents.started().count(), "# tests started"); + assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(1, testEvents.failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "fooBeforeEachCallback", + "beforeEachMethod", + "test", + "afterEachMethod", // throws an exception. + "fooAfterEachCallback" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); + } + + @Test + void testMethodThrowsAnException() { + Events testEvents = executeTestsForClass(ExceptionInTestMethodTestCase.class).testEvents(); + + assertEquals(1, testEvents.started().count(), "# tests started"); + assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); + assertEquals(0, testEvents.skipped().count(), "# tests skipped"); + assertEquals(0, testEvents.aborted().count(), "# tests aborted"); + assertEquals(1, testEvents.failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "fooBeforeEachCallback", + "beforeEachMethod", + "test", // throws an exception. + "afterEachMethod", + "fooAfterEachCallback" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); + } + + // ------------------------------------------------------------------------- + + @ExtendWith(FooMethodLevelCallbacks.class) + static class ParentTestCase { + } + + @ExtendWith(BarMethodLevelCallbacks.class) + static class ChildTestCase extends ParentTestCase { + + @Test + void test() { + callSequence.add("testChild"); + } + } + + @ExtendWith(FooMethodLevelCallbacks.class) + private interface TestInterface { + + @Test + default void defaultTest() { + callSequence.add("defaultTestMethod"); + } + } + + @ExtendWith(BarMethodLevelCallbacks.class) + static class TestInterfaceTestCase implements TestInterface { + + @Test + void localTest() { + callSequence.add("localTestMethod"); + } + } + + @ExtendWith({ FooMethodLevelCallbacks.class, BarMethodLevelCallbacks.class }) + static class OuterTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void testOuter() { + callSequence.add("testOuter"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + + @Nested + @ExtendWith(FizzMethodLevelCallbacks.class) + class InnerTestCase { + + @BeforeEach + void beforeEachInnerMethod() { + callSequence.add("beforeEachInnerMethod"); + } + + @Test + void testInner() { + callSequence.add("testInner"); + } + + @AfterEach + void afterEachInnerMethod() { + callSequence.add("afterEachInnerMethod"); + } + } + } + + @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingBeforeEachCallback.class, + BarMethodLevelCallbacks.class }) + static class ExceptionInBeforeEachCallbackTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingAfterEachCallback.class, + BarMethodLevelCallbacks.class }) + static class ExceptionInAfterEachCallbackTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + @ExtendWith(FooMethodLevelCallbacks.class) + static class ExceptionInBeforeEachMethodTestCase { + + @BeforeEach + void beforeEach1() { + beforeEachMethodCallSequence.add("beforeEachMethod1"); + callSequence.add("beforeEachMethod1"); + throw new EnigmaException("@BeforeEach"); + } + + @BeforeEach + void beforeEach2() { + beforeEachMethodCallSequence.add("beforeEachMethod2"); + callSequence.add("beforeEachMethod2"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + @ExtendWith(FooMethodLevelCallbacks.class) + static class ExceptionInAfterEachMethodTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + throw new EnigmaException("@AfterEach"); + } + } + + @ExtendWith(FooMethodLevelCallbacks.class) + static class ExceptionInTestMethodTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test() { + callSequence.add("test"); + throw new EnigmaException("@Test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + // ------------------------------------------------------------------------- + + static class FooMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + callSequence.add("fooBeforeEachCallback"); + } + + @Override + public void afterEach(ExtensionContext context) { + callSequence.add("fooAfterEachCallback"); + actualExceptionInAfterEachCallback = context.getExecutionException(); + } + } + + static class BarMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + callSequence.add("barBeforeEachCallback"); + } + + @Override + public void afterEach(ExtensionContext context) { + callSequence.add("barAfterEachCallback"); + } + } + + static class FizzMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + callSequence.add("fizzBeforeEachCallback"); + } + + @Override + public void afterEach(ExtensionContext context) { + callSequence.add("fizzAfterEachCallback"); + } + } + + static class ExceptionThrowingBeforeEachCallback implements BeforeEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + callSequence.add("exceptionThrowingBeforeEachCallback"); + throw new EnigmaException("BeforeEachCallback"); + } + } + + static class ExceptionThrowingAfterEachCallback implements AfterEachCallback { + + @Override + public void afterEach(ExtensionContext context) { + callSequence.add("exceptionThrowingAfterEachCallback"); + throw new EnigmaException("AfterEachCallback"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java new file mode 100644 index 00000000..a06ceed8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java @@ -0,0 +1,465 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests that verify support for {@link BeforeTestExecutionCallback}, + * {@link AfterTestExecutionCallback}, {@link BeforeEach}, and {@link AfterEach} + * in the {@link JupiterTestEngine}. + * + * @since 5.0 + * @see BeforeAndAfterEachTests + */ +class BeforeAndAfterTestExecutionCallbackTests extends AbstractJupiterTestEngineTests { + + private static List callSequence = new ArrayList<>(); + + private static Optional actualExceptionInAfterTestExecution; + + @BeforeEach + void resetCallSequence() { + callSequence.clear(); + actualExceptionInAfterTestExecution = null; + } + + @Test + void beforeAndAfterTestExecutionCallbacks() { + EngineExecutionResults executionResults = executeTestsForClass(OuterTestCase.class); + + assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + + // OuterTestCase + "beforeEachMethodOuter", + "fooBeforeTestExecutionCallback", + "barBeforeTestExecutionCallback", + "testOuter", + "barAfterTestExecutionCallback", + "fooAfterTestExecutionCallback", + "afterEachMethodOuter", + + // InnerTestCase + "beforeEachMethodOuter", + "beforeEachMethodInner", + "fooBeforeTestExecutionCallback", + "barBeforeTestExecutionCallback", + "fizzBeforeTestExecutionCallback", + "testInner", + "fizzAfterTestExecutionCallback", + "barAfterTestExecutionCallback", + "fooAfterTestExecutionCallback", + "afterEachMethodInner", + "afterEachMethodOuter" + + ), callSequence, "wrong call sequence"); + // @formatter:on + } + + @Test + void beforeAndAfterTestExecutionCallbacksDeclaredOnSuperclassAndSubclass() { + EngineExecutionResults executionResults = executeTestsForClass(ChildTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "fooBeforeTestExecutionCallback", + "barBeforeTestExecutionCallback", + "testChild", + "barAfterTestExecutionCallback", + "fooAfterTestExecutionCallback" + ), callSequence, "wrong call sequence"); + // @formatter:on + } + + @Test + void beforeAndAfterTestExecutionCallbacksDeclaredOnInterfaceAndClass() { + EngineExecutionResults executionResults = executeTestsForClass(TestInterfaceTestCase.class); + + assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + + // Test Interface + "fooBeforeTestExecutionCallback", + "barBeforeTestExecutionCallback", + "defaultTestMethod", + "barAfterTestExecutionCallback", + "fooAfterTestExecutionCallback", + + // Test Class + "fooBeforeTestExecutionCallback", + "barBeforeTestExecutionCallback", + "localTestMethod", + "barAfterTestExecutionCallback", + "fooAfterTestExecutionCallback" + + ), callSequence, "wrong call sequence"); + // @formatter:on + } + + @Test + void beforeEachMethodThrowsAnException() { + EngineExecutionResults executionResults = executeTestsForClass(ExceptionInBeforeEachMethodTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "beforeEachMethod", // throws an exception. + // fooBeforeTestExecutionCallback should not get invoked. + // test should not get invoked. + // fooAfterTestExecutionCallback should not get invoked. + "afterEachMethod" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertNull(actualExceptionInAfterTestExecution, + "test exception (fooAfterTestExecutionCallback should not have been called)"); + } + + @Test + void beforeTestExecutionCallbackThrowsAnException() { + EngineExecutionResults executionResults = executeTestsForClass( + ExceptionInBeforeTestExecutionCallbackTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "beforeEachMethod", + "fooBeforeTestExecutionCallback", + "exceptionThrowingBeforeTestExecutionCallback", // throws an exception. + // barBeforeTestExecutionCallback should not get invoked. + // test() should not get invoked. + "barAfterTestExecutionCallback", + "fooAfterTestExecutionCallback", + "afterEachMethod" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertNotNull(actualExceptionInAfterTestExecution, "test exception"); + assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); + assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); + } + + @Test + void afterTestExecutionCallbackThrowsAnException() { + EngineExecutionResults executionResults = executeTestsForClass( + ExceptionInAfterTestExecutionCallbackTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "beforeEachMethod", + "fooBeforeTestExecutionCallback", + "barBeforeTestExecutionCallback", + "test", + "barAfterTestExecutionCallback", + "exceptionThrowingAfterTestExecutionCallback", // throws an exception. + "fooAfterTestExecutionCallback", + "afterEachMethod" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertNotNull(actualExceptionInAfterTestExecution, "test exception"); + assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); + assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); + } + + @Test + void testMethodThrowsAnException() { + EngineExecutionResults executionResults = executeTestsForClass(ExceptionInTestMethodTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + assertEquals(asList( + "beforeEachMethod", + "fooBeforeTestExecutionCallback", + "test", // throws an exception. + "fooAfterTestExecutionCallback", + "afterEachMethod" + ), callSequence, "wrong call sequence"); + // @formatter:on + + assertNotNull(actualExceptionInAfterTestExecution, "test exception"); + assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); + assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); + } + + // ------------------------------------------------------------------------- + + @ExtendWith(FooTestExecutionCallbacks.class) + static class ParentTestCase { + } + + @ExtendWith(BarTestExecutionCallbacks.class) + static class ChildTestCase extends ParentTestCase { + + @Test + void test() { + callSequence.add("testChild"); + } + } + + @ExtendWith(FooTestExecutionCallbacks.class) + private interface TestInterface { + + @Test + default void defaultTest() { + callSequence.add("defaultTestMethod"); + } + } + + @ExtendWith(BarTestExecutionCallbacks.class) + static class TestInterfaceTestCase implements TestInterface { + + @Test + void localTest() { + callSequence.add("localTestMethod"); + } + } + + @ExtendWith({ FooTestExecutionCallbacks.class, BarTestExecutionCallbacks.class }) + static class OuterTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethodOuter"); + } + + @Test + void testOuter() { + callSequence.add("testOuter"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethodOuter"); + } + + @Nested + @ExtendWith(FizzTestExecutionCallbacks.class) + class InnerTestCase { + + @BeforeEach + void beforeInnerMethod() { + callSequence.add("beforeEachMethodInner"); + } + + @Test + void testInner() { + callSequence.add("testInner"); + } + + @AfterEach + void afterInnerMethod() { + callSequence.add("afterEachMethodInner"); + } + } + } + + @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingBeforeTestExecutionCallback.class, + BarTestExecutionCallbacks.class }) + static class ExceptionInBeforeTestExecutionCallbackTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingAfterTestExecutionCallback.class, + BarTestExecutionCallbacks.class }) + static class ExceptionInAfterTestExecutionCallbackTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + @ExtendWith(FooTestExecutionCallbacks.class) + static class ExceptionInBeforeEachMethodTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + throw new EnigmaException("@BeforeEach"); + } + + @Test + void test() { + callSequence.add("test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + @ExtendWith(FooTestExecutionCallbacks.class) + static class ExceptionInTestMethodTestCase { + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test() { + callSequence.add("test"); + throw new EnigmaException("@Test"); + } + + @AfterEach + void afterEach() { + callSequence.add("afterEachMethod"); + } + } + + // ------------------------------------------------------------------------- + + static class FooTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + @Override + public void beforeTestExecution(ExtensionContext context) { + callSequence.add("fooBeforeTestExecutionCallback"); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + callSequence.add("fooAfterTestExecutionCallback"); + actualExceptionInAfterTestExecution = context.getExecutionException(); + } + } + + static class BarTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + @Override + public void beforeTestExecution(ExtensionContext context) { + callSequence.add("barBeforeTestExecutionCallback"); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + callSequence.add("barAfterTestExecutionCallback"); + } + } + + static class FizzTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + @Override + public void beforeTestExecution(ExtensionContext context) { + callSequence.add("fizzBeforeTestExecutionCallback"); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + callSequence.add("fizzAfterTestExecutionCallback"); + } + } + + static class ExceptionThrowingBeforeTestExecutionCallback implements BeforeTestExecutionCallback { + + @Override + public void beforeTestExecution(ExtensionContext context) { + callSequence.add("exceptionThrowingBeforeTestExecutionCallback"); + throw new EnigmaException("BeforeTestExecutionCallback"); + } + } + + static class ExceptionThrowingAfterTestExecutionCallback implements AfterTestExecutionCallback { + + @Override + public void afterTestExecution(ExtensionContext context) { + callSequence.add("exceptionThrowingAfterTestExecutionCallback"); + throw new EnigmaException("AfterTestExecutionCallback"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java new file mode 100644 index 00000000..7d955760 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.nio.file.Files.deleteIfExists; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; +import static org.junit.jupiter.api.io.CleanupMode.NEVER; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.Optional; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.execution.ExtensionValuesStore; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; + +/** + * Integration tests for cleanup of the {@link TempDirectory} when the {@link CleanupMode} is + * set to {@link CleanupMode#ALWAYS}, {@link CleanupMode#NEVER}, or {@link CleanupMode#ON_SUCCESS}. + * + * @since 5.9 + * + * @see TempDir + * @see CleanupMode + */ +class CloseablePathCleanupTests extends AbstractJupiterTestEngineTests { + + private final ExtensionContext extensionContext = mock(); + + private TempDirectory.CloseablePath closeablePath; + + @BeforeEach + void setUpExtensionContext() { + var store = new NamespaceAwareStore(new ExtensionValuesStore(null), Namespace.GLOBAL); + when(extensionContext.getStore(any())).thenReturn(store); + } + + @AfterEach + void cleanupTempDirectory() throws IOException { + deleteIfExists(closeablePath.get()); + } + + /** + * Ensure a closeable path is cleaned up for a cleanup mode of ALWAYS. + */ + @Test + void always() throws IOException { + closeablePath = TempDirectory.createTempDir(ALWAYS, extensionContext); + assertThat(closeablePath.get()).exists(); + + closeablePath.close(); + assertThat(closeablePath.get()).doesNotExist(); + } + + /** + * Ensure a closeable path is not cleaned up for a cleanup mode of NEVER. + */ + @Test + void never() throws IOException { + closeablePath = TempDirectory.createTempDir(NEVER, extensionContext); + assertThat(closeablePath.get()).exists(); + + closeablePath.close(); + assertThat(closeablePath.get()).exists(); + } + + /** + * Ensure a closeable path is not cleaned up for a cleanup mode of ON_SUCCESS, if there is a TestAbortedException. + */ + @Test + void onSuccessWithException() throws IOException { + when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); + + closeablePath = TempDirectory.createTempDir(ON_SUCCESS, extensionContext); + assertThat(closeablePath.get()).exists(); + + closeablePath.close(); + assertThat(closeablePath.get()).exists(); + } + + /** + * Ensure a closeable path is cleaned up for a cleanup mode of ON_SUCCESS, if there is no exception. + */ + @Test + void onSuccessWithNoException() throws IOException { + when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); + + closeablePath = TempDirectory.createTempDir(ON_SUCCESS, extensionContext); + assertThat(closeablePath.get()).exists(); + + closeablePath.close(); + assertThat(closeablePath.get()).doesNotExist(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java new file mode 100644 index 00000000..db1823b1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +@SuppressWarnings("serial") +class EnigmaException extends RuntimeException { + + EnigmaException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java new file mode 100644 index 00000000..811bde21 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.IntStream; + +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; + +/** + * @since 5.5 + */ +class EventuallyInterruptibleInvocation implements Invocation { + + @Override + public Void proceed() { + while (!Thread.currentThread().isInterrupted()) { + assertThat(IntStream.range(1, 1_000_000).sum()).isGreaterThan(0); + } + return null; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java new file mode 100644 index 00000000..33422f04 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java @@ -0,0 +1,210 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.engine.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.jupiter.engine.extension.sub.AlwaysDisabledCondition; +import org.junit.jupiter.engine.extension.sub.AnotherAlwaysDisabledCondition; +import org.junit.jupiter.engine.extension.sub.SystemPropertyCondition; +import org.junit.jupiter.engine.extension.sub.SystemPropertyCondition.SystemProperty; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests that verify support for the {@link ExecutionCondition} + * extension point in the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class ExecutionConditionTests extends AbstractJupiterTestEngineTests { + + private static final String FOO = "DisabledTests.foo"; + private static final String BAR = "DisabledTests.bar"; + private static final String BOGUS = "DisabledTests.bogus"; + private static final String DEACTIVATE = "*AnotherAlwaysDisable*, org.junit.jupiter.engine.extension.sub.AlwaysDisable*"; + + @BeforeEach + public void setUp() { + System.setProperty(FOO, BAR); + } + + @AfterEach + public void tearDown() { + System.clearProperty(FOO); + } + + @Test + void conditionWorksOnContainer() { + EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithExecutionConditionOnClass.class); + + executionResults.containerEvents().assertStatistics(stats -> stats.skipped(1)); + executionResults.testEvents().assertStatistics(stats -> stats.started(0)); + } + + @Test + void conditionWorksOnTest() { + Events tests = executeTestsForClass(TestCaseWithExecutionConditionOnMethods.class).testEvents(); + + tests.assertStatistics(stats -> stats.started(2).succeeded(2).skipped(3)); + } + + @Test + void overrideConditionsUsingFullyQualifiedClassName() { + String deactivatePattern = SystemPropertyCondition.class.getName() + "," + DEACTIVATE; + assertExecutionConditionOverride(deactivatePattern, 1, 1); + assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); + } + + @Test + void overrideConditionsUsingStar() { + // "*" should deactivate DisabledCondition and SystemPropertyCondition + String deactivatePattern = "*"; + assertExecutionConditionOverride(deactivatePattern, 2, 2); + assertExecutionConditionOverride(deactivatePattern, 5, 2, 3); + } + + @Test + void overrideConditionsUsingStarPlusSimpleClassName() { + // DisabledCondition should remain activated + String deactivatePattern = "*" + SystemPropertyCondition.class.getSimpleName() + ", " + DEACTIVATE; + assertExecutionConditionOverride(deactivatePattern, 1, 1); + assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); + } + + @Test + void overrideConditionsUsingPackageNamePlusDotStar() { + // DisabledCondition should remain activated + String deactivatePattern = DEACTIVATE + ", " + SystemPropertyCondition.class.getPackage().getName() + ".*"; + assertExecutionConditionOverride(deactivatePattern, 1, 1); + assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); + } + + @Test + void overrideConditionsUsingMultipleWildcards() { + // DisabledCondition should remain activated + String deactivatePattern = "org.junit.jupiter.*.System*Condition" + "," + DEACTIVATE; + assertExecutionConditionOverride(deactivatePattern, 1, 1); + assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); + } + + @Test + void deactivateAllConditions() { + // DisabledCondition should remain activated + String deactivatePattern = "org.junit.jupiter.*.System*Condition" + ", " + DEACTIVATE; + assertExecutionConditionOverride(deactivatePattern, 1, 1); + assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); + } + + private void assertExecutionConditionOverride(String deactivatePattern, int testStartedCount, int testFailedCount) { + // @formatter:off + LauncherDiscoveryRequest request = request() + .selectors(selectClass(TestCaseWithExecutionConditionOnClass.class)) + .configurationParameter(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, deactivatePattern) + .build(); + // @formatter:on + + EngineExecutionResults executionResults = executeTests(request); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + containers.assertStatistics(stats -> stats.skipped(0).started(2)); + tests.assertStatistics(stats -> stats.started(testStartedCount).failed(testFailedCount)); + } + + private void assertExecutionConditionOverride(String deactivatePattern, int started, int succeeded, int failed) { + // @formatter:off + LauncherDiscoveryRequest request = request() + .selectors(selectClass(TestCaseWithExecutionConditionOnMethods.class)) + .configurationParameter(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, deactivatePattern) + .build(); + // @formatter:on + + executeTests(request).testEvents().assertStatistics( + stats -> stats.started(started).succeeded(succeeded).failed(failed)); + } + + // ------------------------------------------------------------------- + + @SystemProperty(key = FOO, value = BOGUS) + @DeactivatedConditions + static class TestCaseWithExecutionConditionOnClass { + + @Test + void disabledTest() { + fail("this should be disabled"); + } + + @Test + @Disabled + void atDisabledTest() { + fail("this should be @Disabled"); + } + } + + static class TestCaseWithExecutionConditionOnMethods { + + @Test + void enabledTest() { + } + + @Test + @Disabled + @DeactivatedConditions + void atDisabledTest() { + fail("this should be @Disabled"); + } + + @Test + @SystemProperty(key = FOO, value = BAR) + void systemPropertyEnabledTest() { + } + + @Test + @DeactivatedConditions + @SystemProperty(key = FOO, value = BOGUS) + void systemPropertyWithIncorrectValueTest() { + fail("this should be disabled"); + } + + @Test + @DeactivatedConditions + @SystemProperty(key = BOGUS, value = "doesn't matter") + void systemPropertyNotSetTest() { + fail("this should be disabled"); + } + + } + + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @ExtendWith({ AlwaysDisabledCondition.class, AnotherAlwaysDisabledCondition.class }) + @interface DeactivatedConditions { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java new file mode 100644 index 00000000..f87d38cb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContextParameterResolver; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; + +class ExtensionContextExecutionTests extends AbstractJupiterTestEngineTests { + + @Test + @ExtendWith(ExtensionContextParameterResolver.class) + void extensionContextHierarchy(ExtensionContext methodExtensionContext) { + assertThat(methodExtensionContext).isNotNull(); + assertThat(methodExtensionContext.getElement()).containsInstanceOf(Method.class); + + Optional classExtensionContext = methodExtensionContext.getParent(); + assertThat(classExtensionContext).isNotEmpty(); + assertThat(classExtensionContext.orElse(null).getElement()).contains(ExtensionContextExecutionTests.class); + + Optional engineExtensionContext = classExtensionContext.orElse(null).getParent(); + assertThat(engineExtensionContext).isNotEmpty(); + assertThat(engineExtensionContext.orElse(null).getElement()).isEmpty(); + + assertThat(engineExtensionContext.orElse(null).getParent()).isEmpty(); + } + + @Test + void twoTestClassesCanShareStateViaEngineExtensionContext() { + Parent.counter.set(0); + + executeTests(selectClass(A.class), selectClass(B.class)).testEvents()// + .assertStatistics(stats -> stats.started(2)); + + assertThat(Parent.counter).hasValue(1); + } + + @ExtendWith(OnlyIncrementCounterOnce.class) + static class Parent { + static final AtomicInteger counter = new AtomicInteger(0); + + @Test + void test() { + } + } + + static class A extends Parent { + } + + static class B extends Parent { + } + + static class OnlyIncrementCounterOnce implements BeforeAllCallback { + @Override + public void beforeAll(ExtensionContext context) { + ExtensionContext.Store store = getRoot(context).getStore(ExtensionContext.Namespace.GLOBAL); + store.getOrComputeIfAbsent("counter", key -> Parent.counter.incrementAndGet()); + } + + private ExtensionContext getRoot(ExtensionContext context) { + return context.getParent().map(this::getRoot).orElse(context); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java new file mode 100644 index 00000000..5b8a208d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java @@ -0,0 +1,928 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; +import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Integration tests that verify support for extension registration via + * {@link ExtendWith @ExtendWith} on parameters and fields. + * + * @since 5.8 + */ +class ExtensionRegistrationViaParametersAndFieldsTests extends AbstractJupiterTestEngineTests { + + @Test + void constructorParameter() { + assertOneTestSucceeded(ConstructorParameterTestCase.class); + } + + @Test + void constructorParameterForNestedTestClass() { + assertTestsSucceeded(NestedConstructorParameterTestCase.class, 2); + } + + @Test + void beforeAllMethodParameter() { + assertOneTestSucceeded(BeforeAllParameterTestCase.class); + } + + @Test + void afterAllMethodParameter() { + assertOneTestSucceeded(AfterAllParameterTestCase.class); + } + + @Test + void beforeEachMethodParameter() { + assertOneTestSucceeded(BeforeEachParameterTestCase.class); + } + + @Test + void afterEachMethodParameter() { + assertOneTestSucceeded(AfterEachParameterTestCase.class); + } + + @Test + void testMethodParameter() { + assertOneTestSucceeded(TestMethodParameterTestCase.class); + } + + @Test + void testFactoryMethodParameter() { + assertTestsSucceeded(TestFactoryMethodParameterTestCase.class, 2); + } + + @Test + void testTemplateMethodParameter() { + assertTestsSucceeded(TestTemplateMethodParameterTestCase.class, 2); + } + + @Test + void staticField() { + assertOneTestSucceeded(StaticFieldTestCase.class); + } + + @Test + void instanceField() { + assertOneTestSucceeded(InstanceFieldTestCase.class); + } + + @Test + void fieldsWithTestInstancePerClass() { + assertOneTestSucceeded(TestInstancePerClassFieldTestCase.class); + } + + @Test + @TrackLogRecords + void multipleRegistrationsViaField(LogRecordListener listener) { + assertOneTestSucceeded(MultipleRegistrationsViaFieldTestCase.class); + assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension"); + } + + @Test + void duplicateRegistrationViaField() { + Class testClass = DuplicateRegistrationViaFieldTestCase.class; + String expectedMessage = "Failed to register extension via field " + + "[org.junit.jupiter.api.extension.Extension " + + "org.junit.jupiter.engine.extension.ExtensionRegistrationViaParametersAndFieldsTests$DuplicateRegistrationViaFieldTestCase.dummy]. " + + "The field registers an extension of type [org.junit.jupiter.engine.extension.DummyExtension] " + + "via @RegisterExtension and @ExtendWith, but only one registration of a given extension type is permitted."; + + executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, + finishedWithFailure(instanceOf(PreconditionViolationException.class), message(expectedMessage))); + } + + @Test + @TrackLogRecords + void registrationOrder(LogRecordListener listener) { + assertOneTestSucceeded(AllInOneWithTestInstancePerMethodTestCase.class); + assertThat(getRegisteredLocalExtensions(listener))// + .containsExactly(// + "ClassLevelExtension2", // @RegisterExtension on static field + "StaticField2", // @ExtendWith on static field + "ClassLevelExtension1", // @RegisterExtension on static field + "StaticField1", // @ExtendWith on static field + "ConstructorParameter", // @ExtendWith on parameter in constructor + "BeforeAllParameter", // @ExtendWith on parameter in static @BeforeAll method + "BeforeEachParameter", // @ExtendWith on parameter in @BeforeEach method + "AfterEachParameter", // @ExtendWith on parameter in @AfterEach method + "AfterAllParameter", // @ExtendWith on parameter in static @AfterAll method + "TestParameter", // @ExtendWith on parameter in @Test method + "InstanceLevelExtension1", // @RegisterExtension on instance field + "InstanceField1", // @ExtendWith on instance field + "InstanceLevelExtension2", // @RegisterExtension on instance field + "InstanceField2" // @ExtendWith on instance field + ); + + listener.clear(); + assertOneTestSucceeded(AllInOneWithTestInstancePerClassTestCase.class); + assertThat(getRegisteredLocalExtensions(listener))// + .containsExactly(// + "ClassLevelExtension2", // @RegisterExtension on static field + "StaticField2", // @ExtendWith on static field + "ClassLevelExtension1", // @RegisterExtension on static field + "StaticField1", // @ExtendWith on static field + "ConstructorParameter", // @ExtendWith on parameter in constructor + "BeforeAllParameter", // @ExtendWith on parameter in static @BeforeAll method + "BeforeEachParameter", // @ExtendWith on parameter in @BeforeEach method + "AfterEachParameter", // @ExtendWith on parameter in @AfterEach method + "AfterAllParameter", // @ExtendWith on parameter in static @AfterAll method + "InstanceLevelExtension1", // @RegisterExtension on instance field + "InstanceField1", // @ExtendWith on instance field + "InstanceLevelExtension2", // @RegisterExtension on instance field + "InstanceField2", // @ExtendWith on instance field + "TestParameter" // @ExtendWith on parameter in @Test method + ); + } + + private List getRegisteredLocalExtensions(LogRecordListener listener) { + // @formatter:off + return listener.stream(MutableExtensionRegistry.class, Level.FINER) + .map(LogRecord::getMessage) + .filter(message -> message.contains("local extension")) + .map(message -> { + message = message.replaceAll("from source .+", ""); + int indexOfDollarSign = message.indexOf("$"); + int indexOfAtSign = message.indexOf("@"); + int endIndex = (indexOfDollarSign > 1 ? indexOfDollarSign : indexOfAtSign); + return message.substring(message.lastIndexOf('.') + 1, endIndex); + }) + .collect(toList()); + // @formatter:on + } + + private void assertOneTestSucceeded(Class testClass) { + assertTestsSucceeded(testClass, 1); + } + + private void assertTestsSucceeded(Class testClass, int expected) { + executeTestsForClass(testClass).testEvents().assertStatistics( + stats -> stats.started(expected).succeeded(expected).skipped(0).aborted(0).failed(0)); + } + + // ------------------------------------------------------------------- + + /** + * The {@link MagicParameter.Extension} is first registered for the constructor + * and then used for lifecycle and test methods. + */ + @ExtendWith(LongParameterResolver.class) + static class ConstructorParameterTestCase { + + ConstructorParameterTestCase(@MagicParameter("constructor") String text) { + assertThat(text).isEqualTo("ConstructorParameterTestCase-0-constructor"); + } + + @BeforeEach + void beforeEach(String text, TestInfo testInfo) { + assertThat(text).isEqualTo("beforeEach-0-enigma"); + assertThat(testInfo).isNotNull(); + } + + @Test + void test(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-enigma"); + } + + /** + * Redeclaring {@code @MagicParameter} should not result in a + * {@link ParameterResolutionException}. + */ + @AfterEach + void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-method"); + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the constructor + * and then used for lifecycle and test methods. + */ + @Nested + @ExtendWith(LongParameterResolver.class) + class NestedConstructorParameterTestCase { + + NestedConstructorParameterTestCase(TestInfo testInfo, @MagicParameter("constructor") String text) { + assertThat(testInfo).isNotNull(); + // Index is 2 instead of 1, since constructors for non-static nested classes + // receive a reference to the enclosing instance as the first argument: this$0 + assertThat(text).isEqualTo("NestedConstructorParameterTestCase-2-constructor"); + } + + @BeforeEach + void beforeEach(String text, TestInfo testInfo) { + assertThat(text).isEqualTo("beforeEach-0-enigma"); + assertThat(testInfo).isNotNull(); + } + + @Test + void test(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-enigma"); + } + + /** + * Redeclaring {@code @MagicParameter} should not result in a + * {@link ParameterResolutionException}. + */ + @AfterEach + void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-method"); + } + + @Nested + class DoublyNestedConstructorParameterTestCase { + + DoublyNestedConstructorParameterTestCase(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + // Index is 2 instead of 1, since constructors for non-static nested classes + // receive a reference to the enclosing instance as the first argument: this$0 + assertThat(text).isEqualTo("DoublyNestedConstructorParameterTestCase-2-enigma"); + } + + @BeforeEach + void beforeEach(String text, TestInfo testInfo) { + assertThat(text).isEqualTo("beforeEach-0-enigma"); + assertThat(testInfo).isNotNull(); + } + + @Test + void test(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-enigma"); + } + + /** + * Redeclaring {@code @MagicParameter} should not result in a + * {@link ParameterResolutionException}. + */ + @AfterEach + void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-method"); + } + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the {@code @BeforeAll} + * method and then used for other lifecycle methods and test methods. + */ + @ExtendWith(LongParameterResolver.class) + static class BeforeAllParameterTestCase { + + @BeforeAll + static void beforeAll(@MagicParameter("method") String text, TestInfo testInfo) { + assertThat(text).isEqualTo("beforeAll-0-method"); + assertThat(testInfo).isNotNull(); + } + + @BeforeEach + void beforeEach(String text, TestInfo testInfo) { + assertThat(text).isEqualTo("beforeEach-0-enigma"); + assertThat(testInfo).isNotNull(); + } + + @Test + void test(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-enigma"); + } + + /** + * Redeclaring {@code @MagicParameter} should not result in a + * {@link ParameterResolutionException}. + */ + @AfterEach + void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-method"); + } + + @AfterAll + static void afterAll(String text, TestInfo testInfo) { + assertThat(text).isEqualTo("afterAll-0-enigma"); + assertThat(testInfo).isNotNull(); + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the {@code @AfterAll} + * method, but that registration occurs before the test method is invoked, which + * allows the string parameters in the after-each and test methods to be resolved + * by the {@link MagicParameter.Extension} as well. + */ + @ExtendWith(LongParameterResolver.class) + static class AfterAllParameterTestCase { + + @Test + void test(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-enigma"); + } + + @AfterEach + void afterEach(Long number, TestInfo testInfo, String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-enigma"); + } + + @AfterAll + static void afterAll(Long number, TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterAll-2-method"); + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the {@code @BeforeEach} + * method and then used for other lifecycle methods and test methods. + */ + @ExtendWith(LongParameterResolver.class) + static class BeforeEachParameterTestCase { + + @BeforeEach + void beforeEach(@MagicParameter("method") String text, TestInfo testInfo) { + assertThat(text).isEqualTo("beforeEach-0-method"); + assertThat(testInfo).isNotNull(); + } + + @Test + void test(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-enigma"); + } + + /** + * Redeclaring {@code @MagicParameter} should not result in a + * {@link ParameterResolutionException}. + */ + @AfterEach + void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-method"); + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the {@code @AfterEach} + * method, but that registration occurs before the test method is invoked, which + * allows the test method's parameter to be resolved by the {@link MagicParameter.Extension} + * as well. + */ + @ExtendWith(LongParameterResolver.class) + static class AfterEachParameterTestCase { + + @Test + void test(TestInfo testInfo, String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-enigma"); + } + + @AfterEach + void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-method"); + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the {@code @Test} + * method and then used for after-each lifecycle methods. + */ + @ExtendWith(LongParameterResolver.class) + static class TestMethodParameterTestCase { + + @Test + void test(TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("test-1-method"); + } + + @AfterEach + void afterEach(Long number, TestInfo testInfo, String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-enigma"); + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the {@code @TestFactory} + * method and then used for after-each lifecycle methods. + */ + @ExtendWith(LongParameterResolver.class) + static class TestFactoryMethodParameterTestCase { + + @TestFactory + Stream testFactory(TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("testFactory-1-method"); + + return IntStream.of(2, 4).mapToObj(num -> dynamicTest("" + num, () -> assertTrue(num % 2 == 0))); + } + + @AfterEach + void afterEach(Long number, TestInfo testInfo, String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-enigma"); + } + + } + + /** + * The {@link MagicParameter.Extension} is first registered for the {@code @TestTemplate} + * method and then used for after-each lifecycle methods. + */ + @ExtendWith(LongParameterResolver.class) + static class TestTemplateMethodParameterTestCase { + + @TestTemplate + @ExtendWith(TwoInvocationsContextProvider.class) + void testTemplate(TestInfo testInfo, @MagicParameter("method") String text) { + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("testTemplate-1-method"); + } + + @AfterEach + void afterEach(Long number, TestInfo testInfo, String text) { + assertThat(number).isEqualTo(42L); + assertThat(testInfo).isNotNull(); + assertThat(text).isEqualTo("afterEach-2-enigma"); + } + + } + + private static class TwoInvocationsContextProvider implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext()); + } + + private static TestTemplateInvocationContext emptyTestTemplateInvocationContext() { + return new TestTemplateInvocationContext() { + }; + } + } + + static class MultipleRegistrationsViaFieldTestCase { + + @ExtendWith(LongParameterResolver.class) + @RegisterExtension + Extension dummy = new DummyExtension(); + + @Test + void test() { + } + } + + static class DuplicateRegistrationViaFieldTestCase { + + @ExtendWith(DummyExtension.class) + @RegisterExtension + Extension dummy = new DummyExtension(); + + @Test + void test() { + } + } + + /** + * The {@link MagicField.Extension} is registered via a static field. + */ + static class StaticFieldTestCase { + + @MagicField + private static String staticField1; + + @MagicField + static String staticField2; + + @BeforeAll + static void beforeAll() { + assertThat(staticField1).isEqualTo("beforeAll - staticField1"); + assertThat(staticField2).isEqualTo("beforeAll - staticField2"); + } + + @Test + void test() { + assertThat(staticField1).isEqualTo("beforeAll - staticField1"); + assertThat(staticField2).isEqualTo("beforeAll - staticField2"); + } + } + + /** + * The {@link MagicField.Extension} is registered via an instance field. + */ + static class InstanceFieldTestCase { + + @MagicField + String instanceField1; + + @MagicField + private String instanceField2; + + @Test + void test() { + assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); + assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + } + } + + /** + * The {@link MagicField.Extension} is registered via a static field and + * an instance field. + */ + @TestInstance(Lifecycle.PER_CLASS) + static class TestInstancePerClassFieldTestCase { + + @MagicField + static String staticField; + + @MagicField + String instanceField; + + @BeforeAll + void beforeAll() { + assertThat(staticField).isEqualTo("beforeAll - staticField"); + assertThat(instanceField).isNull(); + } + + @Test + void test() { + assertThat(staticField).isEqualTo("beforeAll - staticField"); + assertThat(instanceField).isEqualTo("beforeEach - instanceField"); + } + } + + @TestInstance(Lifecycle.PER_METHOD) + static class AllInOneWithTestInstancePerMethodTestCase { + + @StaticField1 + @Order(Integer.MAX_VALUE) + static String staticField1; + + @StaticField2 + @ExtendWith(StaticField2.Extension.class) + @Order(3) + static String staticField2; + + @RegisterExtension + private static Extension classLevelExtension1 = new ClassLevelExtension1(); + + @RegisterExtension + @Order(1) + static Extension classLevelExtension2 = new ClassLevelExtension2(); + + @InstanceField1 + @Order(2) + String instanceField1; + + @InstanceField2 + @ExtendWith(InstanceField2.Extension.class) + String instanceField2; + + @RegisterExtension + @Order(1) + private Extension instanceLevelExtension1 = new InstanceLevelExtension1(); + + @RegisterExtension + @Order(3) + Extension instanceLevelExtension2 = new InstanceLevelExtension2(); + + AllInOneWithTestInstancePerMethodTestCase(@ConstructorParameter String text) { + assertThat(text).isEqualTo("enigma"); + } + + @BeforeAll + static void beforeAll(@ExtendWith(BeforeAllParameter.Extension.class) @BeforeAllParameter String text) { + assertThat(text).isEqualTo("enigma"); + assertThat(staticField1).isEqualTo("beforeAll - staticField1"); + assertThat(staticField2).isEqualTo("beforeAll - staticField2"); + } + + @BeforeEach + void beforeEach(@BeforeEachParameter String text) { + assertThat(text).isEqualTo("enigma"); + assertThat(staticField1).isEqualTo("beforeAll - staticField1"); + assertThat(staticField2).isEqualTo("beforeAll - staticField2"); + assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); + assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + } + + @Test + void test(@TestParameter String text) { + assertThat(text).isEqualTo("enigma"); + assertThat(staticField1).isEqualTo("beforeAll - staticField1"); + assertThat(staticField2).isEqualTo("beforeAll - staticField2"); + assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); + assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + } + + @AfterEach + void afterEach(@AfterEachParameter String text) { + assertThat(text).isEqualTo("enigma"); + assertThat(staticField1).isEqualTo("beforeAll - staticField1"); + assertThat(staticField2).isEqualTo("beforeAll - staticField2"); + assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); + assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + } + + @AfterAll + static void afterAll(@AfterAllParameter String text) { + assertThat(text).isEqualTo("enigma"); + assertThat(staticField1).isEqualTo("beforeAll - staticField1"); + assertThat(staticField2).isEqualTo("beforeAll - staticField2"); + } + + } + + @TestInstance(Lifecycle.PER_CLASS) + static class AllInOneWithTestInstancePerClassTestCase extends AllInOneWithTestInstancePerMethodTestCase { + + AllInOneWithTestInstancePerClassTestCase(@ConstructorParameter String text) { + super(text); + } + } + +} + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(MagicParameter.Extension.class) +@interface MagicParameter { + + String value(); + + class Extension implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == String.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + String text = parameterContext.findAnnotation(MagicParameter.class)// + .map(MagicParameter::value)// + .orElse("enigma"); + Executable declaringExecutable = parameterContext.getDeclaringExecutable(); + String name = declaringExecutable instanceof Constructor + ? declaringExecutable.getDeclaringClass().getSimpleName() + : declaringExecutable.getName(); + return String.format("%s-%d-%s", name, parameterContext.getIndex(), text); + } + } +} + +@SuppressWarnings("unused") +class BaseParameterExtension implements ParameterResolver { + + private final Class annotationType; + + @SuppressWarnings("unchecked") + BaseParameterExtension() { + Type genericSuperclass = getClass().getGenericSuperclass(); + this.annotationType = (Class) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; + } + + @Override + public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.isAnnotated(this.annotationType) + && parameterContext.getParameter().getType() == String.class; + } + + @Override + public final Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return "enigma"; + } +} + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(ConstructorParameter.Extension.class) +@interface ConstructorParameter { + class Extension extends BaseParameterExtension { + } +} + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +// Intentionally NOT annotated as follows +// @ExtendWith(BeforeAllParameter.Extension.class) +@interface BeforeAllParameter { + class Extension extends BaseParameterExtension { + } +} + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(AfterAllParameter.Extension.class) +@interface AfterAllParameter { + class Extension extends BaseParameterExtension { + } +} + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(BeforeEachParameter.Extension.class) +@interface BeforeEachParameter { + class Extension extends BaseParameterExtension { + } +} + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(AfterEachParameter.Extension.class) +@interface AfterEachParameter { + class Extension extends BaseParameterExtension { + } +} + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(TestParameter.Extension.class) +@interface TestParameter { + class Extension extends BaseParameterExtension { + } +} + +class DummyExtension implements Extension { +} + +class BaseFieldExtension implements BeforeAllCallback, BeforeEachCallback { + + private final Class annotationType; + + @SuppressWarnings("unchecked") + BaseFieldExtension() { + Type genericSuperclass = getClass().getGenericSuperclass(); + this.annotationType = (Class) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; + } + + @Override + public final void beforeAll(ExtensionContext context) throws Exception { + injectFields("beforeAll", context.getRequiredTestClass(), null, ReflectionUtils::isStatic); + } + + @Override + public final void beforeEach(ExtensionContext context) throws Exception { + injectFields("beforeEach", context.getRequiredTestClass(), context.getRequiredTestInstance(), + ReflectionUtils::isNotStatic); + } + + private void injectFields(String trigger, Class testClass, Object instance, Predicate predicate) { + findAnnotatedFields(testClass, this.annotationType, predicate).forEach(field -> { + try { + makeAccessible(field).set(instance, trigger + " - " + field.getName()); + } + catch (Throwable t) { + ExceptionUtils.throwAsUncheckedException(t); + } + }); + } +} + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(MagicField.Extension.class) +@interface MagicField { + class Extension extends BaseFieldExtension { + } +} + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(InstanceField1.Extension.class) +@interface InstanceField1 { + class Extension extends BaseFieldExtension { + } +} + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +// Intentionally NOT annotated as follows +// @ExtendWith(InstanceField2.Extension.class) +@interface InstanceField2 { + class Extension extends BaseFieldExtension { + } +} + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(StaticField1.Extension.class) +@interface StaticField1 { + class Extension extends BaseFieldExtension { + } +} + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +// Intentionally NOT annotated as follows +// @ExtendWith(StaticField2.Extension.class) +@interface StaticField2 { + class Extension extends BaseFieldExtension { + } +} + +class ClassLevelExtension1 implements Extension { +} + +class ClassLevelExtension2 implements Extension { +} + +class InstanceLevelExtension1 implements Extension { +} + +class InstanceLevelExtension2 implements Extension { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java new file mode 100644 index 00000000..cbe91c6e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java @@ -0,0 +1,213 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryFrom; +import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.jupiter.engine.config.JupiterConfiguration; + +/** + * Tests for the {@link MutableExtensionRegistry}. + * + * @since 5.0 + */ +class ExtensionRegistryTests { + + private static final int NUM_DEFAULT_EXTENSIONS = 6; + + private final JupiterConfiguration configuration = mock(); + + private MutableExtensionRegistry registry = createRegistryWithDefaultExtensions(configuration); + + @Test + void newRegistryWithoutParentHasDefaultExtensions() { + List extensions = registry.getExtensions(Extension.class); + + assertEquals(NUM_DEFAULT_EXTENSIONS, extensions.size()); + assertDefaultGlobalExtensionsAreRegistered(); + } + + @Test + void newRegistryWithoutParentHasDefaultExtensionsPlusAutodetectedExtensionsLoadedViaServiceLoader() { + + when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); + registry = createRegistryWithDefaultExtensions(configuration); + + List extensions = registry.getExtensions(Extension.class); + + assertEquals(NUM_DEFAULT_EXTENSIONS + 1, extensions.size()); + assertDefaultGlobalExtensionsAreRegistered(3); + + assertExtensionRegistered(registry, ServiceLoaderExtension.class); + assertEquals(3, countExtensions(registry, BeforeAllCallback.class)); + } + + @Test + void registerExtensionByImplementingClass() { + registry.registerExtension(MyExtension.class); + + assertExtensionRegistered(registry, MyExtension.class); + + registry.registerExtension(MyExtension.class); + registry.registerExtension(MyExtension.class); + registry.registerExtension(MyExtension.class); + + assertEquals(1, registry.getExtensions(MyExtension.class).size()); + assertExtensionRegistered(registry, MyExtension.class); + assertEquals(1, countExtensions(registry, MyExtensionApi.class)); + + registry.registerExtension(YourExtension.class); + assertExtensionRegistered(registry, YourExtension.class); + assertEquals(2, countExtensions(registry, MyExtensionApi.class)); + } + + @Test + void registerExtensionThatImplementsMultipleExtensionApis() { + registry.registerExtension(MultipleExtension.class); + + assertExtensionRegistered(registry, MultipleExtension.class); + + assertEquals(1, countExtensions(registry, MyExtensionApi.class)); + assertEquals(1, countExtensions(registry, AnotherExtensionApi.class)); + } + + @Test + void extensionsAreInheritedFromParent() { + MutableExtensionRegistry parent = registry; + parent.registerExtension(MyExtension.class); + + MutableExtensionRegistry child = createRegistryFrom(parent, Stream.of(YourExtension.class)); + assertExtensionRegistered(child, MyExtension.class); + assertExtensionRegistered(child, YourExtension.class); + assertEquals(2, countExtensions(child, MyExtensionApi.class)); + + ExtensionRegistry grandChild = createRegistryFrom(child, Stream.empty()); + assertExtensionRegistered(grandChild, MyExtension.class); + assertExtensionRegistered(grandChild, YourExtension.class); + assertEquals(2, countExtensions(grandChild, MyExtensionApi.class)); + } + + @Test + void registeringSameExtensionImplementationInParentAndChildDoesNotResultInDuplicate() { + MutableExtensionRegistry parent = registry; + parent.registerExtension(MyExtension.class); + assertEquals(1, countExtensions(parent, MyExtensionApi.class)); + + MutableExtensionRegistry child = createRegistryFrom(parent, Stream.of(MyExtension.class, YourExtension.class)); + assertExtensionRegistered(child, MyExtension.class); + assertExtensionRegistered(child, YourExtension.class); + assertEquals(2, countExtensions(child, MyExtensionApi.class)); + + ExtensionRegistry grandChild = createRegistryFrom(child, Stream.of(MyExtension.class, YourExtension.class)); + assertExtensionRegistered(grandChild, MyExtension.class); + assertExtensionRegistered(grandChild, YourExtension.class); + assertEquals(2, countExtensions(grandChild, MyExtensionApi.class)); + } + + @Test + void canStreamOverRegisteredExtension() { + registry.registerExtension(MyExtension.class); + + AtomicBoolean hasRun = new AtomicBoolean(false); + + registry.getExtensions(MyExtensionApi.class).forEach(extension -> { + assertEquals(MyExtension.class.getName(), extension.getClass().getName()); + hasRun.set(true); + }); + + assertTrue(hasRun.get()); + } + + private long countExtensions(ExtensionRegistry registry, Class extensionType) { + return registry.stream(extensionType).count(); + } + + private void assertExtensionRegistered(ExtensionRegistry registry, Class extensionType) { + assertFalse(registry.getExtensions(extensionType).isEmpty(), + () -> extensionType.getSimpleName() + " should be present"); + } + + private void assertDefaultGlobalExtensionsAreRegistered() { + assertDefaultGlobalExtensionsAreRegistered(2); + } + + private void assertDefaultGlobalExtensionsAreRegistered(long bacCount) { + assertExtensionRegistered(registry, DisabledCondition.class); + assertExtensionRegistered(registry, TempDirectory.class); + assertExtensionRegistered(registry, TimeoutExtension.class); + assertExtensionRegistered(registry, RepeatedTestExtension.class); + assertExtensionRegistered(registry, TestInfoParameterResolver.class); + assertExtensionRegistered(registry, TestReporterParameterResolver.class); + + assertEquals(bacCount, countExtensions(registry, BeforeAllCallback.class)); + assertEquals(2, countExtensions(registry, BeforeEachCallback.class)); + assertEquals(3, countExtensions(registry, ParameterResolver.class)); + assertEquals(1, countExtensions(registry, ExecutionCondition.class)); + assertEquals(1, countExtensions(registry, TestTemplateInvocationContextProvider.class)); + assertEquals(1, countExtensions(registry, InvocationInterceptor.class)); + } + + // ------------------------------------------------------------------------- + + interface MyExtensionApi extends Extension { + + void doNothing(String test); + } + + interface AnotherExtensionApi extends Extension { + + void doMore(); + } + + static class MyExtension implements MyExtensionApi { + + @Override + public void doNothing(String test) { + } + } + + static class YourExtension implements MyExtensionApi { + + @Override + public void doNothing(String test) { + } + } + + static class MultipleExtension implements MyExtensionApi, AnotherExtensionApi { + + @Override + public void doNothing(String test) { + } + + @Override + public void doMore() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java new file mode 100644 index 00000000..c04636c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java @@ -0,0 +1,355 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.DynamicTestInvocationContext; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +class InvocationInterceptorTests extends AbstractJupiterTestEngineTests { + + @Test + void failsTestWhenInterceptorChainDoesNotCallInvocation() { + var results = executeTestsForClass(InvocationIgnoringInterceptorTestCase.class); + + var tests = results.testEvents().assertStatistics(stats -> stats.failed(1).succeeded(0)); + tests.failed().assertEventsMatchExactly( + event(test("test"), finishedWithFailure(instanceOf(JUnitException.class), + message(it -> it.startsWith("Chain of InvocationInterceptors never called invocation"))))); + } + + static class InvocationIgnoringInterceptorTestCase { + @RegisterExtension + Extension interceptor = new InvocationInterceptor() { + @Override + public void interceptTestMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { + // do nothing + } + }; + + @Test + void test() { + // never called + } + } + + @Test + void successTestWhenInterceptorChainSkippedInvocation() { + var results = executeTestsForClass(InvocationSkippedTestCase.class); + + results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(1)); + } + + static class InvocationSkippedTestCase { + @RegisterExtension + Extension interceptor = new InvocationInterceptor() { + @Override + public void interceptTestMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { + invocation.skip(); + } + }; + + @Test + void test() { + fail("should not be called"); + } + } + + @Test + void failsTestWhenInterceptorChainCallsInvocationMoreThanOnce() { + var results = executeTestsForClass(DoubleInvocationInterceptorTestCase.class); + + var tests = results.testEvents().assertStatistics(stats -> stats.failed(1).succeeded(0)); + tests.failed().assertEventsMatchExactly( + event(test("test"), finishedWithFailure(instanceOf(JUnitException.class), message(it -> it.startsWith( + "Chain of InvocationInterceptors called invocation multiple times instead of just once"))))); + } + + static class DoubleInvocationInterceptorTestCase { + @RegisterExtension + Extension interceptor = new InvocationInterceptor() { + @Override + public void interceptTestMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + invocation.proceed(); + invocation.proceed(); + } + }; + + @Test + void test() { + // called twice + } + } + + @TestFactory + Stream callsInterceptors() { + var results = executeTestsForClass(TestCaseWithThreeInterceptors.class); + + results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(3)); + return Arrays.stream(InvocationType.values()) // + .map(invocationType -> dynamicTest(invocationType.name(), () -> { + assertThat(getEvents(results, EnumSet.of(invocationType)).distinct()) // + .containsExactly("before:foo", "before:bar", "before:baz", "test", "after:baz", "after:bar", + "after:foo"); + })); + } + + private Stream getEvents(EngineExecutionResults results, EnumSet types) { + return results.allEvents().reportingEntryPublished() // + .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) // + .map(ReportEntry::getKeyValuePairs) // + .filter(map -> map.keySet().stream().map(InvocationType::valueOf).anyMatch(types::contains)) // + .flatMap(map -> map.values().stream()); + } + + @ExtendWith({ FooInvocationInterceptor.class, BarInvocationInterceptor.class, BazInvocationInterceptor.class }) + static class TestCaseWithThreeInterceptors { + + public TestCaseWithThreeInterceptors(TestReporter reporter) { + publish(reporter, InvocationType.CONSTRUCTOR); + } + + @BeforeAll + static void beforeAll(TestReporter reporter) { + publish(reporter, InvocationType.BEFORE_ALL); + } + + @BeforeEach + void beforeEach(TestReporter reporter) { + publish(reporter, InvocationType.BEFORE_EACH); + } + + @Test + void test(TestReporter reporter) { + publish(reporter, InvocationType.TEST_METHOD); + } + + @RepeatedTest(1) + void testTemplate(TestReporter reporter) { + publish(reporter, InvocationType.TEST_TEMPLATE_METHOD); + } + + @TestFactory + DynamicTest testFactory(TestReporter reporter) { + publish(reporter, InvocationType.TEST_FACTORY_METHOD); + return dynamicTest("dynamicTest", () -> { + publish(reporter, InvocationType.DYNAMIC_TEST); + }); + } + + @AfterEach + void afterEach(TestReporter reporter) { + publish(reporter, InvocationType.AFTER_EACH); + } + + @AfterAll + static void afterAll(TestReporter reporter) { + publish(reporter, InvocationType.AFTER_ALL); + } + + static void publish(TestReporter reporter, InvocationType type) { + reporter.publishEntry(type.name(), "test"); + } + + } + + enum InvocationType { + BEFORE_ALL, + CONSTRUCTOR, + BEFORE_EACH, + TEST_METHOD, + TEST_TEMPLATE_METHOD, + TEST_FACTORY_METHOD, + DYNAMIC_TEST, + AFTER_EACH, + AFTER_ALL + } + + abstract static class ReportingInvocationInterceptor implements InvocationInterceptor { + private final Class testClass = TestCaseWithThreeInterceptors.class; + private final String name; + + ReportingInvocationInterceptor(String name) { + this.name = name; + } + + @Override + public void interceptBeforeAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertThat(invocationContext.getTarget()).isEmpty(); + assertEquals(testClass.getDeclaredMethod("beforeAll", TestReporter.class), + invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + reportAndProceed(invocation, extensionContext, InvocationType.BEFORE_ALL); + } + + @Override + public T interceptTestClassConstructor(Invocation invocation, + ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertEquals(testClass.getDeclaredConstructor(TestReporter.class), invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + return reportAndProceed(invocation, extensionContext, InvocationType.CONSTRUCTOR); + } + + @Override + public void interceptBeforeEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); + assertEquals(testClass.getDeclaredMethod("beforeEach", TestReporter.class), + invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + reportAndProceed(invocation, extensionContext, InvocationType.BEFORE_EACH); + } + + @Override + public void interceptTestMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); + assertEquals(testClass.getDeclaredMethod("test", TestReporter.class), invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + reportAndProceed(invocation, extensionContext, InvocationType.TEST_METHOD); + } + + @Override + public void interceptTestTemplateMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); + assertEquals(testClass.getDeclaredMethod("testTemplate", TestReporter.class), + invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + reportAndProceed(invocation, extensionContext, InvocationType.TEST_TEMPLATE_METHOD); + } + + @Override + public T interceptTestFactoryMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); + assertEquals(testClass.getDeclaredMethod("testFactory", TestReporter.class), + invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + return reportAndProceed(invocation, extensionContext, InvocationType.TEST_FACTORY_METHOD); + } + + @Override + public void interceptDynamicTest(Invocation invocation, DynamicTestInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { + assertThat(invocationContext.getExecutable()).isNotNull(); + assertThat(extensionContext.getUniqueId()).isNotBlank(); + assertThat(extensionContext.getElement()).isEmpty(); + assertThat(extensionContext.getParent().flatMap(ExtensionContext::getTestMethod)).contains( + testClass.getDeclaredMethod("testFactory", TestReporter.class)); + reportAndProceed(invocation, extensionContext, InvocationType.DYNAMIC_TEST); + } + + @Override + public void interceptAfterEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); + assertEquals(testClass.getDeclaredMethod("afterEach", TestReporter.class), + invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + reportAndProceed(invocation, extensionContext, InvocationType.AFTER_EACH); + } + + @Override + public void interceptAfterAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + throws Throwable { + assertEquals(testClass, invocationContext.getTargetClass()); + assertThat(invocationContext.getTarget()).isEmpty(); + assertEquals(testClass.getDeclaredMethod("afterAll", TestReporter.class), + invocationContext.getExecutable()); + assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); + reportAndProceed(invocation, extensionContext, InvocationType.AFTER_ALL); + } + + private T reportAndProceed(Invocation invocation, ExtensionContext extensionContext, InvocationType type) + throws Throwable { + extensionContext.publishReportEntry(type.name(), "before:" + name); + try { + return invocation.proceed(); + } + finally { + extensionContext.publishReportEntry(type.name(), "after:" + name); + } + } + } + + static class FooInvocationInterceptor extends ReportingInvocationInterceptor { + FooInvocationInterceptor() { + super("foo"); + } + } + + static class BarInvocationInterceptor extends ReportingInvocationInterceptor { + BarInvocationInterceptor() { + super("bar"); + } + } + + static class BazInvocationInterceptor extends ReportingInvocationInterceptor { + BazInvocationInterceptor() { + super("baz"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java new file mode 100644 index 00000000..4214dcfb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java @@ -0,0 +1,570 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Tests that verify the support for lifecycle method execution exception handling + * via {@link LifecycleMethodExecutionExceptionHandler} + * + * @since 5.5 + */ +class LifecycleMethodExecutionExceptionHandlerTests extends AbstractJupiterTestEngineTests { + + private static List handlerCalls = new ArrayList<>(); + private static boolean throwExceptionBeforeAll; + private static boolean throwExceptionBeforeEach; + private static boolean throwExceptionAfterEach; + private static boolean throwExceptionAfterAll; + + @BeforeEach + void resetStatics() { + throwExceptionBeforeAll = true; + throwExceptionBeforeEach = true; + throwExceptionAfterEach = true; + throwExceptionAfterAll = true; + handlerCalls.clear(); + + SwallowExceptionHandler.beforeAllCalls = 0; + SwallowExceptionHandler.beforeEachCalls = 0; + SwallowExceptionHandler.afterEachCalls = 0; + SwallowExceptionHandler.afterAllCalls = 0; + + RethrowExceptionHandler.beforeAllCalls = 0; + RethrowExceptionHandler.beforeEachCalls = 0; + RethrowExceptionHandler.afterEachCalls = 0; + RethrowExceptionHandler.afterAllCalls = 0; + + ConvertExceptionHandler.beforeAllCalls = 0; + ConvertExceptionHandler.beforeEachCalls = 0; + ConvertExceptionHandler.afterEachCalls = 0; + ConvertExceptionHandler.afterAllCalls = 0; + + UnrecoverableExceptionHandler.beforeAllCalls = 0; + UnrecoverableExceptionHandler.beforeEachCalls = 0; + UnrecoverableExceptionHandler.afterEachCalls = 0; + UnrecoverableExceptionHandler.afterAllCalls = 0; + + ShouldNotBeCalledHandler.beforeAllCalls = 0; + ShouldNotBeCalledHandler.beforeEachCalls = 0; + ShouldNotBeCalledHandler.afterEachCalls = 0; + ShouldNotBeCalledHandler.afterAllCalls = 0; + } + + @Test + void classLevelExceptionHandlersRethrowException() { + LauncherDiscoveryRequest request = request().selectors(selectClass(RethrowingTestCase.class)).build(); + EngineExecutionResults executionResults = executeTests(request); + + assertEquals(1, RethrowExceptionHandler.beforeAllCalls, "Exception should handled in @BeforeAll"); + assertEquals(1, RethrowExceptionHandler.afterAllCalls, "Exception should handled in @AfterAll"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(RethrowingTestCase.class), started()), // + event(container(RethrowingTestCase.class), finishedWithFailure(instanceOf(RuntimeException.class))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void testLevelExceptionHandlersRethrowException() { + throwExceptionBeforeAll = false; + throwExceptionAfterAll = false; + LauncherDiscoveryRequest request = request().selectors(selectClass(RethrowingTestCase.class)).build(); + EngineExecutionResults executionResults = executeTests(request); + + assertEquals(1, RethrowExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); + assertEquals(1, RethrowExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(RethrowingTestCase.class), started()), // + event(test("aTest"), started()), // + event(test("aTest"), finishedWithFailure(instanceOf(RuntimeException.class))), // + event(container(RethrowingTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void classLevelExceptionHandlersConvertException() { + LauncherDiscoveryRequest request = request().selectors(selectClass(ConvertingTestCase.class)).build(); + EngineExecutionResults executionResults = executeTests(request); + + assertEquals(1, ConvertExceptionHandler.beforeAllCalls, "Exception should handled in @BeforeAll"); + assertEquals(1, ConvertExceptionHandler.afterAllCalls, "Exception should handled in @AfterAll"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(ConvertingTestCase.class), started()), // + event(container(ConvertingTestCase.class), finishedWithFailure(instanceOf(IOException.class))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void testLevelExceptionHandlersConvertException() { + throwExceptionBeforeAll = false; + throwExceptionAfterAll = false; + LauncherDiscoveryRequest request = request().selectors(selectClass(ConvertingTestCase.class)).build(); + EngineExecutionResults executionResults = executeTests(request); + + assertEquals(1, ConvertExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); + assertEquals(1, ConvertExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(ConvertingTestCase.class), started()), // + event(test("aTest"), started()), // + event(test("aTest"), finishedWithFailure(instanceOf(IOException.class))), // + event(container(ConvertingTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void exceptionHandlersSwallowException() { + LauncherDiscoveryRequest request = request().selectors(selectClass(SwallowingTestCase.class)).build(); + EngineExecutionResults executionResults = executeTests(request); + + assertEquals(1, SwallowExceptionHandler.beforeAllCalls, "Exception should be handled in @BeforeAll"); + assertEquals(1, SwallowExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); + assertEquals(1, SwallowExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); + assertEquals(1, SwallowExceptionHandler.afterAllCalls, "Exception should be handled in @AfterAll"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(SwallowingTestCase.class), started()), // + event(test("aTest"), started()), // + event(test("aTest"), finishedSuccessfully()), // + event(container(SwallowingTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void perClassLifecycleMethodsAreHandled() { + LauncherDiscoveryRequest request = request().selectors(selectClass(PerClassLifecycleTestCase.class)).build(); + EngineExecutionResults executionResults = executeTests(request); + assertEquals(2, SwallowExceptionHandler.beforeAllCalls, "Exception should be handled in @BeforeAll"); + assertEquals(1, SwallowExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); + assertEquals(1, SwallowExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); + assertEquals(2, SwallowExceptionHandler.afterAllCalls, "Exception should be handled in @AfterAll"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(PerClassLifecycleTestCase.class), started()), // + event(test("aTest"), started()), // + event(test("aTest"), finishedSuccessfully()), // + event(container(PerClassLifecycleTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void multipleHandlersAreCalledInOrder() { + LauncherDiscoveryRequest request = request().selectors(selectClass(MultipleHandlersTestCase.class)).build(); + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(MultipleHandlersTestCase.class), started()), // + event(test("aTest"), started()), // + event(test("aTest"), finishedSuccessfully()), // + event(test("aTest2"), started()), // + event(test("aTest2"), finishedSuccessfully()), // + event(container(MultipleHandlersTestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); // + + assertEquals(Arrays.asList( + // BeforeAll chain (class level only) + "RethrowExceptionBeforeAll", "SwallowExceptionBeforeAll", + // BeforeEach chain for aTest (test + class level) + "ConvertExceptionBeforeEach", "RethrowExceptionBeforeEach", "SwallowExceptionBeforeEach", + // AfterEach chain for aTest (test + class level) + "ConvertExceptionAfterEach", "RethrowExceptionAfterEach", "SwallowExceptionAfterEach", + // BeforeEach chain for aTest2 (class level only) + "RethrowExceptionBeforeEach", "SwallowExceptionBeforeEach", + // AfterEach chain for aTest2 (class level only) + "RethrowExceptionAfterEach", "SwallowExceptionAfterEach", + // AfterAll chain (class level only) + "RethrowExceptionAfterAll", "SwallowExceptionAfterAll" // + ), handlerCalls, "Wrong order of handler calls"); + } + + @Test + void unrecoverableExceptionsAreNotPropagatedInBeforeAll() { + throwExceptionBeforeAll = true; + throwExceptionBeforeEach = false; + throwExceptionAfterEach = false; + throwExceptionAfterAll = false; + + boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); + assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); + assertEquals(1, UnrecoverableExceptionHandler.beforeAllCalls, "Exception should be handled in @BeforeAll"); + assertEquals(0, ShouldNotBeCalledHandler.beforeAllCalls, "Exception should not propagate in @BeforeAll"); + } + + @Test + void unrecoverableExceptionsAreNotPropagatedInBeforeEach() { + throwExceptionBeforeAll = false; + throwExceptionBeforeEach = true; + throwExceptionAfterEach = false; + throwExceptionAfterAll = false; + + boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); + assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); + assertEquals(1, UnrecoverableExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); + assertEquals(0, ShouldNotBeCalledHandler.beforeEachCalls, "Exception should not propagate in @BeforeEach"); + } + + @Test + void unrecoverableExceptionsAreNotPropagatedInAfterEach() { + throwExceptionBeforeAll = false; + throwExceptionBeforeEach = false; + throwExceptionAfterEach = true; + throwExceptionAfterAll = false; + + boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); + assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); + assertEquals(1, UnrecoverableExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); + assertEquals(0, ShouldNotBeCalledHandler.afterEachCalls, "Exception should not propagate in @AfterEach"); + } + + @Test + void unrecoverableExceptionsAreNotPropagatedInAfterAll() { + throwExceptionBeforeAll = false; + throwExceptionBeforeEach = false; + throwExceptionAfterEach = false; + throwExceptionAfterAll = true; + + boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); + assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); + assertEquals(1, UnrecoverableExceptionHandler.afterAllCalls, "Exception should be handled in @AfterAll"); + assertEquals(0, ShouldNotBeCalledHandler.afterAllCalls, "Exception should not propagate in @AfterAll"); + } + + private boolean executeThrowingOutOfMemoryException() { + LauncherDiscoveryRequest request = request().selectors( + selectClass(UnrecoverableExceptionTestCase.class)).build(); + try { + executeTests(request); + } + catch (OutOfMemoryError expected) { + return true; + } + return false; + } + + // ------------------------------------------ + + static class BaseTestCase { + @BeforeAll + static void throwBeforeAll() { + if (throwExceptionBeforeAll) { + throw new RuntimeException("BeforeAllEx"); + } + } + + @BeforeEach + void throwBeforeEach() { + if (throwExceptionBeforeEach) { + throw new RuntimeException("BeforeEachEx"); + } + } + + @Test + void aTest() { + } + + @AfterEach + void throwAfterEach() { + if (throwExceptionAfterEach) { + throw new RuntimeException("AfterEachEx"); + } + } + + @AfterAll + static void throwAfterAll() { + if (throwExceptionAfterAll) { + throw new RuntimeException("AfterAllEx"); + } + } + } + + @ExtendWith(RethrowExceptionHandler.class) + static class RethrowingTestCase extends BaseTestCase { + } + + @ExtendWith(ConvertExceptionHandler.class) + static class ConvertingTestCase extends BaseTestCase { + } + + @ExtendWith(SwallowExceptionHandler.class) + static class SwallowingTestCase extends BaseTestCase { + } + + @ExtendWith(ShouldNotBeCalledHandler.class) + @ExtendWith(UnrecoverableExceptionHandler.class) + static class UnrecoverableExceptionTestCase extends BaseTestCase { + } + + @ExtendWith(ShouldNotBeCalledHandler.class) + @ExtendWith(SwallowExceptionHandler.class) + @ExtendWith(RethrowExceptionHandler.class) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + static class MultipleHandlersTestCase extends BaseTestCase { + + @Override + @ExtendWith(ConvertExceptionHandler.class) + @Order(1) + @Test + void aTest() { + } + + @Order(2) + @Test + void aTest2() { + } + } + + @ExtendWith(SwallowExceptionHandler.class) + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + static class PerClassLifecycleTestCase extends BaseTestCase { + + @BeforeAll + void beforeAll() { + throw new RuntimeException("nonStaticBeforeAllEx"); + } + + @AfterAll + void afterAll() { + throw new RuntimeException("nonStaticAfterAllEx"); + } + } + + // ------------------------------------------ + + static class RethrowExceptionHandler implements LifecycleMethodExecutionExceptionHandler { + static int beforeAllCalls = 0; + static int beforeEachCalls = 0; + static int afterEachCalls = 0; + static int afterAllCalls = 0; + + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + beforeAllCalls++; + handlerCalls.add("RethrowExceptionBeforeAll"); + throw throwable; + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + beforeEachCalls++; + handlerCalls.add("RethrowExceptionBeforeEach"); + throw throwable; + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + afterEachCalls++; + handlerCalls.add("RethrowExceptionAfterEach"); + throw throwable; + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + afterAllCalls++; + handlerCalls.add("RethrowExceptionAfterAll"); + throw throwable; + } + } + + static class SwallowExceptionHandler implements LifecycleMethodExecutionExceptionHandler { + static int beforeAllCalls = 0; + static int beforeEachCalls = 0; + static int afterEachCalls = 0; + static int afterAllCalls = 0; + + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { + beforeAllCalls++; + handlerCalls.add("SwallowExceptionBeforeAll"); + // Do not rethrow + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { + beforeEachCalls++; + handlerCalls.add("SwallowExceptionBeforeEach"); + // Do not rethrow + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { + afterEachCalls++; + handlerCalls.add("SwallowExceptionAfterEach"); + // Do not rethrow + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { + afterAllCalls++; + handlerCalls.add("SwallowExceptionAfterAll"); + // Do not rethrow + } + } + + static class ConvertExceptionHandler implements LifecycleMethodExecutionExceptionHandler { + static int beforeAllCalls = 0; + static int beforeEachCalls = 0; + static int afterEachCalls = 0; + static int afterAllCalls = 0; + + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + beforeAllCalls++; + handlerCalls.add("ConvertExceptionBeforeAll"); + throw new IOException(throwable); + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + beforeEachCalls++; + handlerCalls.add("ConvertExceptionBeforeEach"); + throw new IOException(throwable); + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + afterEachCalls++; + handlerCalls.add("ConvertExceptionAfterEach"); + throw new IOException(throwable); + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + afterAllCalls++; + handlerCalls.add("ConvertExceptionAfterAll"); + throw new IOException(throwable); + } + } + + static class UnrecoverableExceptionHandler implements LifecycleMethodExecutionExceptionHandler { + static int beforeAllCalls = 0; + static int beforeEachCalls = 0; + static int afterEachCalls = 0; + static int afterAllCalls = 0; + + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { + beforeAllCalls++; + handlerCalls.add("UnrecoverableExceptionBeforeAll"); + throw new OutOfMemoryError(); + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { + beforeEachCalls++; + handlerCalls.add("UnrecoverableExceptionBeforeEach"); + throw new OutOfMemoryError(); + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { + afterEachCalls++; + handlerCalls.add("UnrecoverableExceptionAfterEach"); + throw new OutOfMemoryError(); + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { + afterAllCalls++; + handlerCalls.add("UnrecoverableExceptionAfterAll"); + throw new OutOfMemoryError(); + } + } + + static class ShouldNotBeCalledHandler implements LifecycleMethodExecutionExceptionHandler { + static int beforeAllCalls = 0; + static int beforeEachCalls = 0; + static int afterEachCalls = 0; + static int afterAllCalls = 0; + + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + beforeAllCalls++; + handlerCalls.add("ShouldNotBeCalledBeforeAll"); + throw throwable; + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + ShouldNotBeCalledHandler.beforeEachCalls++; + handlerCalls.add("ShouldNotBeCalledBeforeEach"); + throw throwable; + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + afterEachCalls++; + handlerCalls.add("ShouldNotBeCalledAfterEach"); + throw throwable; + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) + throws Throwable { + afterAllCalls++; + handlerCalls.add("ShouldNotBeCalledAfterAll"); + throw throwable; + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java new file mode 100644 index 00000000..09026c6b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java @@ -0,0 +1,270 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests for {@link ClassOrderer} support. + * + * @since 5.8 + */ +class OrderedClassTests { + + private static final List callSequence = Collections.synchronizedList(new ArrayList<>()); + + @BeforeEach + @AfterEach + void clearCallSequence() { + callSequence.clear(); + } + + @Test + void className() { + executeTests(ClassOrderer.ClassName.class)// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsExactly("A_TestCase", "B_TestCase", "C_TestCase"); + } + + @Test + void classNameAcrossPackages() { + try { + example.B_TestCase.callSequence = callSequence; + + // @formatter:off + executeTests(ClassOrderer.ClassName.class, selectClass(B_TestCase.class), selectClass(example.B_TestCase.class)) + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + // @formatter:on + + assertThat(callSequence)// + .containsExactly("example.B_TestCase", "B_TestCase"); + } + finally { + example.B_TestCase.callSequence = null; + } + } + + @Test + void displayName() { + executeTests(ClassOrderer.DisplayName.class)// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsExactly("C_TestCase", "B_TestCase", "A_TestCase"); + } + + @Test + void orderAnnotation() { + executeTests(ClassOrderer.OrderAnnotation.class)// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsExactly("A_TestCase", "C_TestCase", "B_TestCase"); + } + + @Test + void orderAnnotationOnNestedTestClassesWithGlobalConfig() { + executeTests(ClassOrderer.OrderAnnotation.class, selectClass(OuterWithGlobalConfig.class))// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsExactly("Inner2", "Inner1", "Inner0", "Inner3"); + } + + @Test + @TrackLogRecords + void orderAnnotationOnNestedTestClassesWithLocalConfig(LogRecordListener listener) { + executeTests(ClassOrderer.class, selectClass(OuterWithLocalConfig.class))// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + + // Ensure that supplying the ClassOrderer interface instead of an implementation + // class results in a WARNING log message. This also lets us know the local + // config is used. + assertTrue(listener.stream(Level.WARNING)// + .map(LogRecord::getMessage)// + .anyMatch(m -> m.startsWith( + "Failed to load default class orderer class 'org.junit.jupiter.api.ClassOrderer'"))); + + assertThat(callSequence)// + .containsExactly("Inner2", "Inner1", "Inner1Inner1", "Inner1Inner0", "Inner0", "Inner3"); + } + + @Test + void random() { + executeTests(ClassOrderer.Random.class)// + .assertStatistics(stats -> stats.succeeded(callSequence.size())); + } + + private Events executeTests(Class classOrderer) { + return executeTests(classOrderer, selectClass(A_TestCase.class), selectClass(B_TestCase.class), + selectClass(C_TestCase.class)); + } + + private Events executeTests(Class classOrderer, DiscoverySelector... selectors) { + // @formatter:off + return EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, classOrderer.getName()) + .selectors(selectors) + .execute() + .testEvents(); + // @formatter:on + } + + static abstract class BaseTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + var testClass = testInfo.getTestClass().get(); + + callSequence.add(testClass.getSimpleName()); + } + + @Test + void a() { + } + } + + @Order(2) + @DisplayName("Z") + static class A_TestCase extends BaseTestCase { + } + + static class B_TestCase extends BaseTestCase { + } + + @Order(10) + @DisplayName("A") + static class C_TestCase extends BaseTestCase { + } + + static class OuterWithGlobalConfig { + + @Nested + class Inner0 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @Order(2) + class Inner1 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @Order(1) + class Inner2 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @Order(Integer.MAX_VALUE) + class Inner3 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + } + + @TestClassOrder(ClassOrderer.OrderAnnotation.class) + static class OuterWithLocalConfig { + + @Nested + class Inner0 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @Order(2) + class Inner1 { + + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + + @Nested + @Order(2) + class Inner1Inner0 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @Order(1) + class Inner1Inner1 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + } + + @Nested + @Order(1) + class Inner2 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + + @Nested + @Order(Integer.MAX_VALUE) + class Inner3 { + @Test + void test() { + callSequence.add(getClass().getSimpleName()); + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java new file mode 100644 index 00000000..ed86fd2a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java @@ -0,0 +1,745 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME; +import static org.junit.jupiter.api.Order.DEFAULT; +import static org.junit.jupiter.engine.Constants.DEFAULT_PARALLEL_EXECUTION_MODE; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.MethodDescriptor; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.MethodOrderer.MethodName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.MethodOrderer.Random; +import org.junit.jupiter.api.MethodOrdererContext; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; +import org.mockito.Mockito; + +/** + * Integration tests that verify support for custom test method execution order + * in the {@link JupiterTestEngine}. + * + * @since 5.4 + */ +class OrderedMethodTests { + + private static final Set callSequence = Collections.synchronizedSet(new LinkedHashSet<>()); + private static final Set threadNames = Collections.synchronizedSet(new LinkedHashSet<>()); + + @BeforeEach + void clearCallSequence() { + callSequence.clear(); + threadNames.clear(); + } + + @Test + void alphanumeric() { + Class testClass = AlphanumericTestCase.class; + + // The name of the base class MUST start with a letter alphanumerically + // greater than "A" so that BaseTestCase comes after AlphanumericTestCase + // if methods are sorted by class name for the fallback ordering if two + // methods have the same name but different parameter lists. Note, however, + // that Alphanumeric actually does not order methods like that, but we want + // this check to remain in place to ensure that the ordering does not rely + // on the class names. + assertThat(testClass.getSuperclass().getName()).isGreaterThan(testClass.getName()); + + var tests = executeTestsInParallel(testClass); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence).containsExactly("$()", "AAA()", "AAA(org.junit.jupiter.api.TestInfo)", + "AAA(org.junit.jupiter.api.TestReporter)", "ZZ_Top()", "___()", "a1()", "a2()", "b()", "c()", "zzz()"); + assertThat(threadNames).hasSize(1); + } + + @Test + void methodName() { + Class testClass = MethodNameTestCase.class; + + // The name of the base class MUST start with a letter alphanumerically + // greater than "A" so that BaseTestCase comes after AlphanumericTestCase + // if methods are sorted by class name for the fallback ordering if two + // methods have the same name but different parameter lists. Note, however, + // that Alphanumeric actually does not order methods like that, but we want + // this check to remain in place to ensure that the ordering does not rely + // on the class names. + assertThat(testClass.getSuperclass().getName()).isLessThan(testClass.getName()); + + var tests = executeTestsInParallel(testClass); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence).containsExactly("$()", "AAA()", "AAA(org.junit.jupiter.api.TestInfo)", + "AAA(org.junit.jupiter.api.TestReporter)", "ZZ_Top()", "___()", "a1()", "a2()", "b()", "c()", "zzz()"); + assertThat(threadNames).hasSize(1); + } + + @Test + void displayName() { + var tests = executeTestsInParallel(DisplayNameTestCase.class); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsExactly("$", "AAA", "No_display_name_attribute_1_caps()", "No_display_name_attribute_2_caps()", + "ZZ_Top", "___", "a1", "a2", "b()", "no_display_name_attribute_1()", + "no_display_name_attribute_2()", "repetition 1 of 1", "⑦ϼ\uD83D\uDC69\u200D⚕\uD83E\uDDD8\u200D♂"); + assertThat(threadNames).hasSize(1); + } + + @Test + void orderAnnotation() { + assertOrderAnnotationSupport(OrderAnnotationTestCase.class); + } + + @Test + void orderAnnotationInNestedTestClass() { + assertOrderAnnotationSupport(OuterTestCase.class); + } + + @Test + void orderAnnotationWithNestedTestClass() { + var tests = executeTestsInParallel(OrderAnnotationWithNestedClassTestCase.class); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsExactly("test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "nestedTest1", + "nestedTest2"); + assertThat(threadNames).hasSize(1); + } + + private void assertOrderAnnotationSupport(Class testClass) { + var tests = executeTestsInParallel(testClass); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence)// + .containsExactly("test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8"); + assertThat(threadNames).hasSize(1); + } + + @Test + void random() { + var tests = executeTestsInParallel(RandomTestCase.class); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(threadNames).hasSize(1); + } + + @Test + void defaultOrderer() { + var tests = executeTestsInParallel(WithoutTestMethodOrderTestCase.class, OrderAnnotation.class); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + assertThat(callSequence).containsExactly("test1()", "test2()", "test3()"); + assertThat(threadNames).hasSize(1); + } + + @Test + @TrackLogRecords + void randomWithBogusSeedRepeatedly(LogRecordListener listener) { + var seed = "explode"; + var expectedMessagePattern = Pattern.compile( + "Failed to convert configuration parameter \\[" + Pattern.quote(Random.RANDOM_SEED_PROPERTY_NAME) + + "] with value \\[" + seed + "] to a long\\. Using default seed \\[\\d+] as fallback\\."); + + Set uniqueSequences = new HashSet<>(); + + for (var i = 0; i < 10; i++) { + callSequence.clear(); + listener.clear(); + + var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + uniqueSequences.add(String.join(",", callSequence)); + + // @formatter:off + assertThat(listener.stream(Random.class, Level.WARNING) + .map(LogRecord::getMessage)) + .anyMatch(expectedMessagePattern.asMatchPredicate()); + // @formatter:on + } + + assertThat(uniqueSequences).size().isEqualTo(1); + } + + @Test + @TrackLogRecords + void randomWithDifferentSeedConsecutively(LogRecordListener listener) { + Set uniqueSequences = new HashSet<>(); + + for (var i = 0; i < 10; i++) { + var seed = String.valueOf(i); + var expectedMessage = "Using custom seed for configuration parameter [" + Random.RANDOM_SEED_PROPERTY_NAME + + "] with value [" + seed + "]."; + callSequence.clear(); + listener.clear(); + + var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + uniqueSequences.add(String.join(",", callSequence)); + + // @formatter:off + assertThat(listener.stream(Random.class, Level.CONFIG) + .map(LogRecord::getMessage)) + .contains(expectedMessage); + // @formatter:on + + assertThat(threadNames).hasSize(i + 1); + } + + // We assume that at least 3 out of 10 are different... + assertThat(uniqueSequences).size().isGreaterThanOrEqualTo(3); + } + + @Test + @TrackLogRecords + void randomWithCustomSeed(LogRecordListener listener) { + var seed = "42"; + var expectedMessage = "Using custom seed for configuration parameter [" + Random.RANDOM_SEED_PROPERTY_NAME + + "] with value [" + seed + "]."; + + for (var i = 0; i < 10; i++) { + callSequence.clear(); + listener.clear(); + + var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + + // With a custom seed, the "randomness" must be the same for every iteration. + assertThat(callSequence).containsExactly("test2()", "test3()", "test4()", "repetition 1 of 1", "test1()"); + + // @formatter:off + assertThat(listener.stream(Random.class, Level.CONFIG) + .map(LogRecord::getMessage)) + .contains(expectedMessage); + // @formatter:on + } + + assertThat(threadNames).size().isGreaterThanOrEqualTo(3); + } + + @Test + @TrackLogRecords + void misbehavingMethodOrdererThatAddsElements(LogRecordListener listener) { + Class testClass = MisbehavingByAddingTestCase.class; + + executeTestsInParallel(testClass).assertStatistics(stats -> stats.succeeded(2)); + + assertThat(callSequence).containsExactlyInAnyOrder("test1()", "test2()"); + + var expectedMessage = "MethodOrderer [" + MisbehavingByAdding.class.getName() + + "] added 2 MethodDescriptor(s) for test class [" + testClass.getName() + "] which will be ignored."; + + assertExpectedLogMessage(listener, expectedMessage); + } + + @Test + @TrackLogRecords + void misbehavingMethodOrdererThatRemovesElements(LogRecordListener listener) { + Class testClass = MisbehavingByRemovingTestCase.class; + + executeTestsInParallel(testClass).assertStatistics(stats -> stats.succeeded(3)); + + assertThat(callSequence).containsExactlyInAnyOrder("test1()", "test2()", "test3()"); + + var expectedMessage = "MethodOrderer [" + MisbehavingByRemoving.class.getName() + + "] removed 2 MethodDescriptor(s) for test class [" + testClass.getName() + + "] which will be retained with arbitrary ordering."; + + assertExpectedLogMessage(listener, expectedMessage); + } + + private void assertExpectedLogMessage(LogRecordListener listener, String expectedMessage) { + // @formatter:off + assertThat(listener.stream(Level.WARNING) + .map(LogRecord::getMessage)) + .contains(expectedMessage); + // @formatter:on + } + + private Events executeTestsInParallel(Class testClass) { + return executeTestsInParallel(testClass, Random.class); + } + + private Events executeTestsInParallel(Class testClass, Class defaultOrderer) { + // @formatter:off + return EngineTestKit + .engine("junit-jupiter") + .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") + .configurationParameter(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent") + .configurationParameter(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, defaultOrderer.getName()) + .selectors(selectClass(testClass)) + .execute() + .testEvents(); + // @formatter:on + } + + private Events executeRandomTestCaseInParallelWithRandomSeed(String seed) { + var configurationParameters = Map.of(// + PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true", // + DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent", // + RANDOM_SEED_PROPERTY_NAME, seed // + ); + + // @formatter:off + return EngineTestKit + .engine("junit-jupiter") + .configurationParameters(configurationParameters) + .selectors(selectClass(RandomTestCase.class)) + .execute() + .testEvents(); + // @formatter:on + } + + // ------------------------------------------------------------------------- + + static class BaseTestCase { + + @Test + void AAA() { + } + + @Test + void c() { + } + + } + + @SuppressWarnings("unused") + @TestMethodOrder(MethodName.class) + static class MethodNameTestCase extends BaseTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + var method = testInfo.getTestMethod().orElseThrow(AssertionError::new); + var signature = String.format("%s(%s)", method.getName(), + ClassUtils.nullSafeToString(method.getParameterTypes())); + + callSequence.add(signature); + threadNames.add(Thread.currentThread().getName()); + } + + @TestFactory + DynamicTest b() { + return dynamicTest("dynamic", () -> { + }); + } + + @Test + void $() { + } + + @Test + void ___() { + } + + @Test + void AAA(TestReporter testReporter) { + } + + @Test + void AAA(TestInfo testInfo) { + } + + @Test + void ZZ_Top() { + } + + @Test + void a1() { + } + + @Test + void a2() { + } + + @RepeatedTest(1) + void zzz() { + } + } + + @SuppressWarnings({ "deprecation", "unused" }) + @TestMethodOrder(org.junit.jupiter.api.MethodOrderer.Alphanumeric.class) + static class AlphanumericTestCase extends BaseTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + var method = testInfo.getTestMethod().orElseThrow(AssertionError::new); + var signature = String.format("%s(%s)", method.getName(), + ClassUtils.nullSafeToString(method.getParameterTypes())); + + callSequence.add(signature); + threadNames.add(Thread.currentThread().getName()); + } + + @TestFactory + DynamicTest b() { + return dynamicTest("dynamic", () -> { + }); + } + + @Test + void $() { + } + + @Test + void ___() { + } + + @Test + void AAA(TestReporter testReporter) { + } + + @Test + void AAA(TestInfo testInfo) { + } + + @Test + void ZZ_Top() { + } + + @Test + void a1() { + } + + @Test + void a2() { + } + + @RepeatedTest(1) + void zzz() { + } + } + + @TestMethodOrder(MethodOrderer.DisplayName.class) + static class DisplayNameTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + callSequence.add(testInfo.getDisplayName()); + threadNames.add(Thread.currentThread().getName()); + } + + @TestFactory + DynamicTest b() { + return dynamicTest("dynamic", () -> { + }); + } + + @DisplayName("$") + @Test + void $() { + } + + @DisplayName("___") + @Test + void ___() { + } + + @DisplayName("AAA") + @Test + void AAA() { + } + + @DisplayName("ZZ_Top") + @Test + void ZZ_Top() { + } + + @DisplayName("a1") + @Test + void a1() { + } + + @DisplayName("a2") + @Test + void a2() { + } + + @DisplayName("zzz") + @RepeatedTest(1) + void zzz() { + } + + @Test + @DisplayName("⑦ϼ\uD83D\uDC69\u200D⚕\uD83E\uDDD8\u200D♂") + void special_characters() { + } + + @Test + void no_display_name_attribute_1() { + } + + @Test + void no_display_name_attribute_2() { + } + + @Test + void No_display_name_attribute_1_caps() { + } + + @Test + void No_display_name_attribute_2_caps() { + } + } + + @TestMethodOrder(OrderAnnotation.class) + static class OrderAnnotationTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + callSequence.add(testInfo.getDisplayName()); + threadNames.add(Thread.currentThread().getName()); + } + + @Test + @DisplayName("test8") + @Order(Integer.MAX_VALUE) + void maxInteger() { + } + + @Test + @DisplayName("test7") + @Order(DEFAULT + 1) + void defaultOrderValuePlusOne() { + } + + @Test + @DisplayName("test6") + // @Order(DEFAULT) + void defaultOrderValue() { + } + + @Test + @DisplayName("test3") + @Order(3) + void $() { + } + + @Test + @DisplayName("test5") + @Order(5) + void AAA() { + } + + @TestFactory + @DisplayName("test4") + @Order(4) + DynamicTest aaa() { + return dynamicTest("test4", () -> { + }); + } + + @Test + @DisplayName("test1") + @Order(1) + void zzz() { + } + + @RepeatedTest(value = 1, name = "{displayName}") + @DisplayName("test2") + @Order(2) + void ___() { + } + } + + static class OuterTestCase { + + @Nested + class NestedOrderAnnotationTestCase extends OrderAnnotationTestCase { + } + } + + @TestMethodOrder(Random.class) + static class RandomTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + callSequence.add(testInfo.getDisplayName()); + threadNames.add(Thread.currentThread().getName()); + } + + @Test + void test1() { + } + + @Test + void test2() { + } + + @Test + void test3() { + } + + @TestFactory + DynamicTest test4() { + return dynamicTest("dynamic", () -> { + }); + } + + @RepeatedTest(1) + void test5() { + } + } + + @TestMethodOrder(MisbehavingByAdding.class) + static class MisbehavingByAddingTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + callSequence.add(testInfo.getDisplayName()); + } + + @Test + void test1() { + } + + @Test + void test2() { + } + } + + @TestMethodOrder(MisbehavingByRemoving.class) + static class MisbehavingByRemovingTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + callSequence.add(testInfo.getDisplayName()); + } + + @Test + void test1() { + } + + @Test + void test2() { + } + + @Test + void test3() { + } + } + + static class OrderAnnotationWithNestedClassTestCase extends OrderAnnotationTestCase { + @Nested + @TestMethodOrder(OrderAnnotation.class) + class NestedTests { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + callSequence.add(testInfo.getDisplayName()); + } + + @Test + @Order(1) + @DisplayName("nestedTest1") + void nestedTest1() { + } + + @Test + @Order(2) + @DisplayName("nestedTest2") + void nestedTest2() { + } + } + } + + static class MisbehavingByAdding implements MethodOrderer { + + @Override + public void orderMethods(MethodOrdererContext context) { + context.getMethodDescriptors().add(mockMethodDescriptor()); + context.getMethodDescriptors().add(mockMethodDescriptor()); + } + + @SuppressWarnings("unchecked") + static T mockMethodDescriptor() { + return (T) Mockito.mock((Class) MethodDescriptor.class); + } + + } + + static class MisbehavingByRemoving implements MethodOrderer { + + @Override + public void orderMethods(MethodOrdererContext context) { + context.getMethodDescriptors().remove(0); + context.getMethodDescriptors().remove(0); + } + } + + static class WithoutTestMethodOrderTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + callSequence.add(testInfo.getDisplayName()); + threadNames.add(Thread.currentThread().getName()); + } + + @Test + @Order(2) + void test2() { + } + + @Test + @Order(3) + void test3() { + } + + @Test + @Order(1) + void test1() { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java new file mode 100644 index 00000000..6d74a431 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java @@ -0,0 +1,311 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Order.DEFAULT; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; + +/** + * Integration tests that verify support for {@linkplain Order ordered} programmatic + * extension registration via {@link RegisterExtension @RegisterExtension} in the + * {@link JupiterTestEngine}. + * + * @since 5.4 + * @see ProgrammaticExtensionRegistrationTests + */ +class OrderedProgrammaticExtensionRegistrationTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + /** + * This method basically verifies the implementation of + * {@link java.lang.String#hashCode()} (which needn't really be tested) + * in order to make reasonable assumptions about how fields are sorted + * in {@link org.junit.platform.commons.util.ReflectionUtils#defaultFieldSorter(Field, Field)}. + * + *

In other words, this method is just a sanity check for the chosen + * field names in the test cases used in these tests. + */ + @BeforeAll + static void assertAssumptionsAboutDefaultOrderingAlgorithm() { + String fieldName1 = "extension1"; + String fieldName2 = "extension2"; + String fieldName3 = "extension3"; + + assertThat(fieldName1.hashCode()).isLessThan(fieldName2.hashCode()); + assertThat(fieldName2.hashCode()).isLessThan(fieldName3.hashCode()); + } + + @BeforeEach + void clearCallSequence() { + callSequence.clear(); + } + + @Test + void instanceLevelWithDefaultOrder() { + assertOutcome(DefaultOrderInstanceLevelExtensionRegistrationTestCase.class, 1, 2, 3); + } + + @Test + void instanceLevelWithExplicitOrder() { + assertOutcome(ExplicitOrderInstanceLevelExtensionRegistrationTestCase.class, 3, 2, 1); + } + + @Test + void instanceLevelWithDefaultOrderAndExplicitOrder() { + assertOutcome(DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationTestCase.class, 3, 1, 2); + } + + /** + * Verify that an "after" callback can be registered first relative to other + * non-annotated "after" callbacks. + * + * @since 5.6 + * @see gh-1924 + */ + @Test + void instanceLevelWithDefaultOrderPlusOneAndDefaultOrder() { + assertOutcome(DefaultOrderPlusOneAndDefaultOrderInstanceLevelExtensionRegistrationTestCase.class, 1, 3, 2); + } + + @Test + void instanceLevelWithDefaultOrderAndExplicitOrderWithTestInstancePerClassLifecycle() { + assertOutcome( + DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase.class, + 3, 1, 2); + } + + @Test + void classLevelWithDefaultOrderAndExplicitOrder() { + assertOutcome(DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, 3, 1, 2); + } + + @Test + void classLevelWithDefaultOrderAndExplicitOrderInheritedFromSuperclass() { + assertOutcome(InheritedDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, 3, 1, 2); + } + + @Test + void classLevelWithDefaultOrderShadowingOrderFromSuperclass() { + assertOutcome(DefaultOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, 1, + 2, 3); + } + + @Test + void classLevelWithExplicitOrderShadowingOrderFromSuperclass() { + assertOutcome(ExplicitOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, + 3, 2, 1); + } + + @Test + void classLevelWithDefaultOrderAndExplicitOrderFromInterface() { + assertOutcome(DefaultOrderAndExplicitOrderExtensionRegistrationFromInterfaceTestCase.class, 3, 1, 2); + } + + private void assertOutcome(Class testClass, Integer... values) { + executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); + assertThat(callSequence).containsExactly(values); + } + + // ------------------------------------------------------------------- + + private static class AbstractTestCase { + + @Test + void test() { + } + + } + + static class DefaultOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { + + @RegisterExtension + Extension extension1 = new BeforeEachExtension(1); + + @RegisterExtension + Extension extension3 = new BeforeEachExtension(3); + + @RegisterExtension + Extension extension2 = new BeforeEachExtension(2); + + } + + static class ExplicitOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { + + @Order(3) + @RegisterExtension + Extension extension1 = new BeforeEachExtension(1); + + @Order(2) + @RegisterExtension + Extension extension2 = new BeforeEachExtension(2); + + @Order(1) + @RegisterExtension + Extension extension3 = new BeforeEachExtension(3); + + } + + static class DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { + + // @Order(3) + @RegisterExtension + Extension extension1 = new BeforeEachExtension(1); + + // @Order(2) + @RegisterExtension + Extension extension2 = new BeforeEachExtension(2); + + @Order(1) + @RegisterExtension + Extension extension3 = new BeforeEachExtension(3); + + } + + static class DefaultOrderPlusOneAndDefaultOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { + + @Order(DEFAULT + 1) + @RegisterExtension + Extension extension1 = new AfterEachExtension(1); + + @RegisterExtension + Extension extension2 = new AfterEachExtension(2); + + @RegisterExtension + Extension extension3 = new AfterEachExtension(3); + + } + + @TestInstance(PER_CLASS) + static class DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase + extends AbstractTestCase { + + // @Order(3) + @RegisterExtension + Extension extension1 = new BeforeEachExtension(1); + + // @Order(2) + @RegisterExtension + Extension extension2 = new BeforeEachExtension(2); + + @Order(1) + @RegisterExtension + Extension extension3 = new BeforeEachExtension(3); + + } + + static class DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase extends AbstractTestCase { + + // @Order(3) + @RegisterExtension + static Extension extension1 = new BeforeEachExtension(1); + + // @Order(2) + @RegisterExtension + static Extension extension2 = new BeforeEachExtension(2); + + @Order(1) + @RegisterExtension + static Extension extension3 = new BeforeEachExtension(3); + + } + + static class InheritedDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase + extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { + } + + static class DefaultOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase + extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { + + // @Order(1) + @RegisterExtension + static Extension extension3 = new BeforeEachExtension(3); + + } + + static class ExplicitOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase + extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { + + @Order(2) + @RegisterExtension + static Extension extension2 = new BeforeEachExtension(2); + + } + + interface DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface { + + // @Order(3) + @RegisterExtension + static Extension extension1 = new BeforeEachExtension(1); + + // @Order(2) + @RegisterExtension + static Extension extension2 = new BeforeEachExtension(2); + + @Order(1) + @RegisterExtension + static Extension extension3 = new BeforeEachExtension(3); + + } + + static class DefaultOrderAndExplicitOrderExtensionRegistrationFromInterfaceTestCase extends AbstractTestCase + implements DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface { + } + + private static class BeforeEachExtension implements BeforeEachCallback { + + private final int id; + + BeforeEachExtension(int id) { + this.id = id; + } + + @Override + public void beforeEach(ExtensionContext context) { + callSequence.add(this.id); + } + + } + + private static class AfterEachExtension implements AfterEachCallback { + + private final int id; + + AfterEachExtension(int id) { + this.id = id; + } + + @Override + public void afterEach(ExtensionContext context) { + callSequence.add(this.id); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java new file mode 100644 index 00000000..9809dc3a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java @@ -0,0 +1,501 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.jupiter.engine.execution.injection.sample.CustomAnnotation; +import org.junit.jupiter.engine.execution.injection.sample.CustomAnnotationParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.CustomType; +import org.junit.jupiter.engine.execution.injection.sample.CustomTypeParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.MapOfListsTypeBasedParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.MapOfStringsParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.NullIntegerParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.NumberParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.PrimitiveArrayParameterResolver; +import org.junit.jupiter.engine.execution.injection.sample.PrimitiveIntegerParameterResolver; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests that verify support for {@link ParameterResolver} + * extensions in the {@link JupiterTestEngine}. + * + * @since 5.0 + */ +class ParameterResolverTests extends AbstractJupiterTestEngineTests { + + @Test + void constructorInjection() { + EngineExecutionResults executionResults = executeTestsForClass(ConstructorInjectionTestCase.class); + + assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void constructorInjectionWithAnnotatedParameter() { + EngineExecutionResults executionResults = executeTestsForClass( + AnnotatedParameterConstructorInjectionTestCase.class); + + assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestsForMethodInjectionCases() { + EngineExecutionResults executionResults = executeTestsForClass(MethodInjectionTestCase.class); + + assertEquals(7, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(6, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestsForNullValuedMethodInjectionCases() { + EngineExecutionResults executionResults = executeTestsForClass(NullMethodInjectionTestCase.class); + Events tests = executionResults.testEvents(); + + assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + Predicate expectations = s -> + s.contains("NullIntegerParameterResolver") && + s.contains("resolved a null value for parameter") && + s.contains("but a primitive of type [int] is required"); + + tests.failed().assertEventsMatchExactly( + event( + test("injectPrimitive"), + finishedWithFailure(instanceOf(ParameterResolutionException.class), message(expectations)) + )); + // @formatter:on + } + + @Test + void executeTestsForPrimitiveIntegerMethodInjectionCases() { + EngineExecutionResults executionResults = executeTestsForClass(PrimitiveIntegerMethodInjectionTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestsForPrimitiveArrayMethodInjectionCases() { + EngineExecutionResults executionResults = executeTestsForClass(PrimitiveArrayMethodInjectionTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestsForPotentiallyIncompatibleTypeMethodInjectionCases() { + EngineExecutionResults executionResults = executeTestsForClass( + PotentiallyIncompatibleTypeMethodInjectionTestCase.class); + Events tests = executionResults.testEvents(); + + assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); + + // @formatter:off + Predicate expectations = s -> + s.contains("NumberParameterResolver") && + s.contains("resolved a value of type [java.lang.Integer]") && + s.contains("but a value assignment compatible with [java.lang.Double] is required"); + + tests.failed().assertEventsMatchExactly( + event( + test("doubleParameterInjection"), + finishedWithFailure(instanceOf(ParameterResolutionException.class), message(expectations) + ))); + // @formatter:on + } + + @Test + void executeTestsForMethodInjectionInBeforeAndAfterEachMethods() { + EngineExecutionResults executionResults = executeTestsForClass(BeforeAndAfterMethodInjectionTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestsForMethodInjectionInBeforeAndAfterAllMethods() { + EngineExecutionResults executionResults = executeTestsForClass(BeforeAndAfterAllMethodInjectionTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestsForMethodWithExtendWithAnnotation() { + EngineExecutionResults executionResults = executeTestsForClass(ExtendWithOnMethodTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + @Test + void executeTestsForParameterizedTypesSelectingByClass() { + assertEventsForParameterizedTypes(executeTestsForClass(ParameterizedTypeTestCase.class)); + } + + @Test + void executeTestsForParameterizedTypesSelectingByFullyQualifiedMethodName() { + String fqmn = ReflectionUtils.getFullyQualifiedMethodName(ParameterizedTypeTestCase.class, "testMapOfStrings", + Map.class); + + assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); + } + + @Test + void executeTestsForTypeBasedParameterResolverTestCaseSelectingByClass() { + assertEventsForParameterizedTypes(executeTestsForClass(TypeBasedParameterResolverTestCase.class)); + } + + @Test + void executeTestsForTypeBasedParameterResolverTestCaseSelectingByFullyQualifiedMethodName() { + String fqmn = ReflectionUtils.getFullyQualifiedMethodName(TypeBasedParameterResolverTestCase.class, + "testMapOfLists", Map.class); + + assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); + } + + @Disabled("Disabled until a decision has been made regarding #956") + @Test + void executeTestsForParameterizedTypesSelectingByFullyQualifiedMethodNameContainingGenericInfo() throws Exception { + Method method = ParameterizedTypeTestCase.class.getDeclaredMethod("testMapOfStrings", Map.class); + String genericParameterTypeName = method.getGenericParameterTypes()[0].getTypeName(); + String fqmn = String.format("%s#%s(%s)", ParameterizedTypeTestCase.class.getName(), "testMapOfStrings", + genericParameterTypeName); + + assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); + } + + private void assertEventsForParameterizedTypes(EngineExecutionResults executionResults) { + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); + assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); + } + + // ------------------------------------------------------------------- + + @ExtendWith(CustomTypeParameterResolver.class) + static class ConstructorInjectionTestCase { + + private final TestInfo outerTestInfo; + private final CustomType outerCustomType; + + ConstructorInjectionTestCase(TestInfo testInfo, CustomType customType) { + this.outerTestInfo = testInfo; + this.outerCustomType = customType; + } + + @Test + void test() { + assertNotNull(this.outerTestInfo); + assertNotNull(this.outerCustomType); + } + + @Nested + class NestedTestCase { + + private final TestInfo innerTestInfo; + private final CustomType innerCustomType; + + NestedTestCase(TestInfo testInfo, CustomType customType) { + this.innerTestInfo = testInfo; + this.innerCustomType = customType; + } + + @Test + void test() { + assertNotNull(outerTestInfo); + assertNotNull(outerCustomType); + assertNotNull(this.innerTestInfo); + assertNotNull(this.innerCustomType); + } + } + } + + @ExtendWith(CustomAnnotationParameterResolver.class) + static class AnnotatedParameterConstructorInjectionTestCase { + + private final TestInfo outerTestInfo; + private final CustomType outerCustomType; + + AnnotatedParameterConstructorInjectionTestCase(TestInfo testInfo, @CustomAnnotation CustomType customType) { + this.outerTestInfo = testInfo; + this.outerCustomType = customType; + } + + @Test + void test() { + assertNotNull(this.outerTestInfo); + assertNotNull(this.outerCustomType); + } + + @Nested + // See https://github.com/junit-team/junit5/issues/1345 + class AnnotatedConstructorParameterNestedTestCase { + + private final TestInfo innerTestInfo; + private final CustomType innerCustomType; + + AnnotatedConstructorParameterNestedTestCase(TestInfo testInfo, @CustomAnnotation CustomType customType) { + this.innerTestInfo = testInfo; + this.innerCustomType = customType; + } + + @Test + void test() { + assertNotNull(outerTestInfo); + assertNotNull(outerCustomType); + assertNotNull(this.innerTestInfo); + assertNotNull(this.innerCustomType); + } + } + } + + @ExtendWith({ CustomTypeParameterResolver.class, CustomAnnotationParameterResolver.class }) + static class MethodInjectionTestCase { + + @Test + void parameterInjectionOfTestInfo(TestInfo testInfo) { + assertNotNull(testInfo); + } + + @Test + void parameterInjectionWithCompetingResolversFail(@CustomAnnotation CustomType customType) { + // should fail + } + + @Test + void parameterInjectionByType(CustomType customType) { + assertNotNull(customType); + } + + @Test + void parameterInjectionByAnnotation(@CustomAnnotation String value) { + assertNotNull(value); + } + + // some overloaded methods + + @Test + void overloadedName() { + assertTrue(true); + } + + @Test + void overloadedName(CustomType customType) { + assertNotNull(customType); + } + + @Test + void overloadedName(CustomType customType, @CustomAnnotation String value) { + assertNotNull(customType); + assertNotNull(value); + } + } + + @ExtendWith(NullIntegerParameterResolver.class) + static class NullMethodInjectionTestCase { + + @Test + void injectWrapper(Integer number) { + assertNull(number); + } + + @Test + void injectPrimitive(int number) { + // should never be invoked since an int cannot be null + } + } + + @ExtendWith(PrimitiveIntegerParameterResolver.class) + static class PrimitiveIntegerMethodInjectionTestCase { + + @Test + void intPrimitive(int i) { + assertEquals(42, i); + } + } + + @ExtendWith(PrimitiveArrayParameterResolver.class) + static class PrimitiveArrayMethodInjectionTestCase { + + @Test + void primitiveArray(int... ints) { + assertArrayEquals(new int[] { 1, 2, 3 }, ints); + } + } + + @ExtendWith(NumberParameterResolver.class) + static class PotentiallyIncompatibleTypeMethodInjectionTestCase { + + @Test + void numberParameterInjection(Number number) { + assertEquals(Integer.valueOf(42), number); + } + + @Test + void integerParameterInjection(Integer number) { + assertEquals(Integer.valueOf(42), number); + } + + /** + * This test must fail, since {@link Double} is a {@link Number} but not an {@link Integer}. + * @see NumberParameterResolver + */ + @Test + void doubleParameterInjection(Double number) { + /* no-op */ + } + } + + static class BeforeAndAfterMethodInjectionTestCase { + + @BeforeEach + void before(TestInfo testInfo) { + assertEquals("custom name", testInfo.getDisplayName()); + } + + @Test + @DisplayName("custom name") + void customNamedTest() { + } + + @AfterEach + void after(TestInfo testInfo) { + assertEquals("custom name", testInfo.getDisplayName()); + } + } + + @DisplayName("custom class name") + static class BeforeAndAfterAllMethodInjectionTestCase { + + @BeforeAll + static void beforeAll(TestInfo testInfo) { + assertEquals("custom class name", testInfo.getDisplayName()); + } + + @Test + void aTest() { + } + + @AfterAll + static void afterAll(TestInfo testInfo) { + assertEquals("custom class name", testInfo.getDisplayName()); + } + } + + static class ExtendWithOnMethodTestCase { + + /** + * This set-up / tear-down method is here to verify that {@code @BeforeEach} + * and {@code @AfterEach} methods are properly invoked using the same + * {@code ExtensionRegistry} as the one used for the corresponding + * {@code @Test} method. + * + * @see #523 + */ + @BeforeEach + @AfterEach + void setUpAndTearDown(CustomType customType, @CustomAnnotation String value) { + assertNotNull(customType); + assertNotNull(value); + } + + @Test + @ExtendWith(CustomTypeParameterResolver.class) + @ExtendWith(CustomAnnotationParameterResolver.class) + void testMethodWithExtensionAnnotation(CustomType customType, @CustomAnnotation String value) { + assertNotNull(customType); + assertNotNull(value); + } + } + + static class ParameterizedTypeTestCase { + + @Test + @ExtendWith(MapOfStringsParameterResolver.class) + void testMapOfStrings(Map map) { + assertNotNull(map); + assertEquals("value", map.get("key")); + } + } + + static class TypeBasedParameterResolverTestCase { + @Test + @ExtendWith(MapOfListsTypeBasedParameterResolver.class) + void testMapOfLists(Map> map) { + assertNotNull(map); + assertEquals(asList(1, 42), map.get("ids")); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java new file mode 100644 index 00000000..5ae50c4c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java @@ -0,0 +1,756 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.allOf; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests that verify support for programmatic extension registration + * via {@link RegisterExtension @RegisterExtension} in the {@link JupiterTestEngine}. + * + * @since 5.1 + * @see OrderedProgrammaticExtensionRegistrationTests + */ +class ProgrammaticExtensionRegistrationTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @Test + void instanceLevel() { + assertOneTestSucceeded(InstanceLevelExtensionRegistrationTestCase.class); + } + + @Test + void instanceLevelWithInjectedExtension() { + assertOneTestSucceeded(InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase.class); + } + + @Test + void instanceLevelWithTestInstancePerClassLifecycle() { + assertOneTestSucceeded(InstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase.class); + } + + @Test + void classLevel() { + assertOneTestSucceeded(ClassLevelExtensionRegistrationTestCase.class); + } + + @Test + void classLevelFromSuperclass() { + assertOneTestSucceeded(SubClassLevelExtensionRegistrationTestCase.class); + } + + @Test + void classLevelFromInterface() { + assertOneTestSucceeded(ExtensionRegistrationFromInterfaceTestCase.class); + } + + @Test + void instanceLevelWithInheritedAndHiddenExtensions() { + callSequence.clear(); + Class testClass = InstanceLevelExtensionRegistrationParentTestCase.class; + String parent = testClass.getSimpleName(); + assertOneTestSucceeded(testClass); + assertThat(callSequence).containsExactly( // + parent + " :: extension1: before test", // + parent + " :: extension2: before test" // + ); + + callSequence.clear(); + testClass = InstanceLevelExtensionRegistrationChildTestCase.class; + String child = testClass.getSimpleName(); + assertOneTestSucceeded(testClass); + assertThat(callSequence).containsExactly( // + parent + " :: extension1: before test", // + child + " :: extension2: before test", // + child + " :: extension3: before test" // + ); + } + + @Test + void classLevelWithInheritedAndHiddenExtensions() { + callSequence.clear(); + Class testClass = ClassLevelExtensionRegistrationParentTestCase.class; + String parent = testClass.getSimpleName(); + assertOneTestSucceeded(testClass); + assertThat(callSequence).containsExactly( // + parent + " :: extension1: before test", // + parent + " :: extension2: before test" // + ); + + callSequence.clear(); + testClass = ClassLevelExtensionRegistrationChildTestCase.class; + String child = testClass.getSimpleName(); + assertOneTestSucceeded(testClass); + assertThat(callSequence).containsExactly( // + parent + " :: extension1: before test", // + child + " :: extension2: before test", // + child + " :: extension3: before test" // + ); + } + + /** + * @since 5.5 + */ + @Test + void instanceLevelWithFieldThatDoesNotImplementAnExtensionApi() { + callSequence.clear(); + assertOneTestSucceeded(InstanceLevelCustomExtensionApiTestCase.class); + assertThat(callSequence).containsExactly( // + CustomExtensionImpl.class.getSimpleName() + " :: before test", // + CustomExtensionImpl.class.getSimpleName() + " :: doSomething()" // + ); + } + + /** + * @since 5.5 + */ + @Test + void classLevelWithFieldThatDoesNotImplementAnExtensionApi() { + callSequence.clear(); + assertOneTestSucceeded(ClassLevelCustomExtensionApiTestCase.class); + assertThat(callSequence).containsExactly( // + CustomExtensionImpl.class.getSimpleName() + " :: before test", // + CustomExtensionImpl.class.getSimpleName() + " :: doSomething()" // + ); + } + + /** + * @since 5.5 + */ + @Test + void instanceLevelWithPrivateField() { + Class testClass = InstanceLevelExtensionRegistrationWithPrivateFieldTestCase.class; + executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); + } + + /** + * @since 5.5 + */ + @Test + void classLevelWithPrivateField() { + Class testClass = ClassLevelExtensionRegistrationWithPrivateFieldTestCase.class; + executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); + } + + @Test + void instanceLevelWithNullField() { + Class testClass = InstanceLevelExtensionRegistrationWithNullFieldTestCase.class; + + executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, finishedWithFailure( + instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, null)))); + } + + @Test + void classLevelWithNullField() { + Class testClass = ClassLevelExtensionRegistrationWithNullFieldTestCase.class; + + executeTestsForClass(testClass).containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure( + instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, null)))); + } + + /** + * @since 5.5 + */ + @Test + void instanceLevelWithNonExtensionFieldValue() { + Class testClass = InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCase.class; + + executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, finishedWithFailure( + instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, String.class)))); + } + + /** + * @since 5.5 + */ + @Test + void classLevelWithNonExtensionFieldValue() { + Class testClass = ClassLevelExtensionRegistrationWithNonExtensionFieldValueTestCase.class; + + executeTestsForClass(testClass).containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure( + instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, String.class)))); + } + + private String expectedMessage(Class testClass, Class valueType) { + return "Failed to register extension via @RegisterExtension field [" + field(testClass) + + "]: field value's type [" + (valueType != null ? valueType.getName() : null) + "] must implement an [" + + Extension.class.getName() + "] API."; + } + + private Field field(Class testClass) { + try { + return testClass.getDeclaredField("extension"); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Test + void propagatesCheckedExceptionThrownDuringInitializationOfStaticField() { + assertClassFails(ClassLevelExplosiveCheckedExceptionTestCase.class, + allOf(instanceOf(ExceptionInInitializerError.class), cause(instanceOf(Exception.class), message("boom")))); + } + + @Test + void propagatesUncheckedExceptionThrownDuringInitializationOfStaticField() { + assertClassFails(ClassLevelExplosiveUncheckedExceptionTestCase.class, allOf( + instanceOf(ExceptionInInitializerError.class), cause(instanceOf(RuntimeException.class), message("boom")))); + } + + @Test + void propagatesErrorThrownDuringInitializationOfStaticField() { + assertClassFails(ClassLevelExplosiveErrorTestCase.class, allOf(instanceOf(Error.class), message("boom"))); + } + + @Test + void propagatesCheckedExceptionThrownDuringInitializationOfInstanceField() { + assertTestFails(InstanceLevelExplosiveCheckedExceptionTestCase.class, + allOf(instanceOf(Exception.class), message("boom"))); + } + + @Test + void propagatesUncheckedExceptionThrownDuringInitializationOfInstanceField() { + assertTestFails(InstanceLevelExplosiveUncheckedExceptionTestCase.class, + allOf(instanceOf(RuntimeException.class), message("boom"))); + } + + @Test + void propagatesErrorThrownDuringInitializationOfInstanceField() { + assertTestFails(InstanceLevelExplosiveErrorTestCase.class, allOf(instanceOf(Error.class), message("boom"))); + } + + @Test + void storesExtensionInRegistryOfNestedTestMethods() { + var results = executeTestsForClass(TwoNestedClassesTestCase.class); + + results.testEvents().assertStatistics(stats -> stats.succeeded(4)); + } + + private void assertClassFails(Class testClass, Condition causeCondition) { + EngineExecutionResults executionResults = executeTestsForClass(testClass); + executionResults.containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure(causeCondition)); + } + + private void assertTestFails(Class testClass, Condition causeCondition) { + executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, + finishedWithFailure(causeCondition)); + } + + private void assertOneTestSucceeded(Class testClass) { + executeTestsForClass(testClass).testEvents().assertStatistics( + stats -> stats.started(1).succeeded(1).skipped(0).aborted(0).failed(0)); + } + + // ------------------------------------------------------------------- + + private static void assertWisdom(CrystalBall crystalBall, String wisdom, String useCase) { + assertNotNull(crystalBall, useCase); + assertEquals("Outlook good", wisdom, useCase); + } + + static class InstanceLevelExtensionRegistrationTestCase { + + @RegisterExtension + final CrystalBall crystalBall = new CrystalBall("Outlook good"); + + @BeforeEach + void beforeEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeEach"); + } + + @Test + void test(String wisdom) { + assertWisdom(crystalBall, wisdom, "@Test"); + } + + @AfterEach + void afterEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterEach"); + } + + } + + @ExtendWith(ExtensionInjector.class) + static class InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase { + + @RegisterExtension + protected CrystalBall crystalBall; // Injected by ExtensionInjector. + + @BeforeEach + void beforeEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeEach"); + } + + @Test + void test(String wisdom) { + assertWisdom(crystalBall, wisdom, "@Test"); + } + + @AfterEach + void afterEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterEach"); + } + + } + + @TestInstance(PER_CLASS) + static class InstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase { + + @RegisterExtension + final CrystalBall crystalBall = new CrystalBall("Outlook good"); + + @BeforeAll + void beforeAll(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeAll"); + } + + @BeforeEach + void beforeEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeEach"); + } + + @Test + void test(String wisdom) { + assertWisdom(crystalBall, wisdom, "@Test"); + } + + @AfterEach + void afterEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterEach"); + } + + @AfterAll + void afterAll(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterAll"); + } + + } + + static class ClassLevelExtensionRegistrationTestCase { + + @RegisterExtension + static final CrystalBall crystalBall = new CrystalBall("Outlook good"); + + @BeforeAll + static void beforeAll(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeAll"); + } + + @BeforeEach + void beforeEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeEach"); + } + + @Test + void test(String wisdom) { + assertWisdom(crystalBall, wisdom, "@Test"); + } + + @AfterEach + void afterEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterEach"); + } + + @AfterAll + static void afterAll(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterAll"); + } + + } + + static class SubClassLevelExtensionRegistrationTestCase extends ClassLevelExtensionRegistrationTestCase { + + @Test + @Override + void test(String wisdom) { + assertWisdom(crystalBall, wisdom, "Overridden @Test"); + } + + } + + interface ClassLevelExtensionRegistrationInterface { + + @RegisterExtension + static CrystalBall crystalBall = new CrystalBall("Outlook good"); + + @BeforeAll + static void beforeAll(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeAll"); + } + + @BeforeEach + default void beforeEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@BeforeEach"); + } + + @AfterEach + default void afterEach(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterEach"); + } + + @AfterAll + static void afterAll(String wisdom) { + assertWisdom(crystalBall, wisdom, "@AfterAll"); + } + + } + + static class ExtensionRegistrationFromInterfaceTestCase implements ClassLevelExtensionRegistrationInterface { + + @Test + void test(String wisdom) { + assertWisdom(crystalBall, wisdom, "@Test"); + } + + } + + private static class CrystalBall implements ParameterResolver { + + private final String wisdom; + + public CrystalBall(String wisdom) { + this.wisdom = wisdom; + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == String.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return this.wisdom; + } + + } + + static class ClassLevelExtensionRegistrationParentTestCase { + + @RegisterExtension + static BeforeEachCallback extension1 = context -> callSequence.add( + ClassLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension1: before " + + context.getRequiredTestMethod().getName()); + + @RegisterExtension + static BeforeEachCallback extension2 = context -> callSequence.add( + ClassLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension2: before " + + context.getRequiredTestMethod().getName()); + + @Test + void test() { + } + + } + + static class ClassLevelExtensionRegistrationChildTestCase extends ClassLevelExtensionRegistrationParentTestCase { + + // "Hides" ClassLevelExtensionRegistrationParentTestCase.extension2 + @RegisterExtension + static BeforeEachCallback extension2 = context -> callSequence.add( + ClassLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension2: before " + + context.getRequiredTestMethod().getName()); + + @RegisterExtension + static BeforeEachCallback extension3 = context -> callSequence.add( + ClassLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension3: before " + + context.getRequiredTestMethod().getName()); + + } + + static class InstanceLevelExtensionRegistrationParentTestCase { + + @RegisterExtension + BeforeEachCallback extension1 = context -> callSequence.add( + InstanceLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension1: before " + + context.getRequiredTestMethod().getName()); + + @RegisterExtension + BeforeEachCallback extension2 = context -> callSequence.add( + InstanceLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension2: before " + + context.getRequiredTestMethod().getName()); + + @Test + void test() { + } + + } + + static class InstanceLevelExtensionRegistrationChildTestCase + extends InstanceLevelExtensionRegistrationParentTestCase { + + // "Hides" InstanceLevelExtensionRegistrationParentTestCase.extension2 + @RegisterExtension + BeforeEachCallback extension2 = context -> callSequence.add( + InstanceLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension2: before " + + context.getRequiredTestMethod().getName()); + + @RegisterExtension + BeforeEachCallback extension3 = context -> callSequence.add( + InstanceLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension3: before " + + context.getRequiredTestMethod().getName()); + + } + + /** + * This interface intentionally does not implement a supported {@link Extension} API. + */ + interface CustomExtension { + + void doSomething(); + + } + + static class CustomExtensionImpl implements CustomExtension, BeforeEachCallback { + + @Override + public void doSomething() { + callSequence.add(getClass().getSimpleName() + " :: doSomething()"); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + callSequence.add(getClass().getSimpleName() + " :: before " + context.getRequiredTestMethod().getName()); + } + } + + static class InstanceLevelCustomExtensionApiTestCase { + + @RegisterExtension + CustomExtension extension = new CustomExtensionImpl(); + + @Test + void test() { + this.extension.doSomething(); + } + + } + + static class ClassLevelCustomExtensionApiTestCase { + + @RegisterExtension + static CustomExtension extension = new CustomExtensionImpl(); + + @Test + void test() { + extension.doSomething(); + } + + } + + static class AbstractTestCase { + + @Test + void test() { + } + + } + + static class InstanceLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { + + @RegisterExtension + private Extension extension = new Extension() { + }; + + } + + static class ClassLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { + + @RegisterExtension + private static Extension extension = new Extension() { + }; + + } + + static class InstanceLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { + + @RegisterExtension + Extension extension; + + } + + static class ClassLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { + + @RegisterExtension + static Extension extension; + + } + + static class InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { + + @RegisterExtension + Object extension = "not an extension type"; + + } + + static class ClassLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { + + @RegisterExtension + static Object extension = "not an extension type"; + + } + + static class ClassLevelExplosiveCheckedExceptionTestCase extends AbstractTestCase { + + @RegisterExtension + static Extension field = new ExplosiveExtension(new Exception("boom")); + + } + + static class ClassLevelExplosiveUncheckedExceptionTestCase extends AbstractTestCase { + + @RegisterExtension + static Extension field = new ExplosiveExtension(new RuntimeException("boom")); + + } + + static class ClassLevelExplosiveErrorTestCase extends AbstractTestCase { + + @RegisterExtension + static Extension field = new ExplosiveExtension(new Error("boom")); + + } + + static class InstanceLevelExplosiveCheckedExceptionTestCase extends AbstractTestCase { + + @RegisterExtension + Extension field = new ExplosiveExtension(new Exception("boom")); + + } + + static class InstanceLevelExplosiveUncheckedExceptionTestCase extends AbstractTestCase { + + @RegisterExtension + Extension field = new ExplosiveExtension(new RuntimeException("boom")); + + } + + static class InstanceLevelExplosiveErrorTestCase extends AbstractTestCase { + + @RegisterExtension + Extension field = new ExplosiveExtension(new Error("boom")); + + } + + static class ExplosiveExtension implements Extension { + + ExplosiveExtension(Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(t); + } + + } + + /** + * Mimics a dependency injection framework such as Spring, Guice, CDI, etc., + * where the instance of the extension registered via + * {@link RegisterExtension @RegisterExtension} is managed by the DI + * framework and injected into the test instance. + */ + private static class ExtensionInjector implements TestInstancePostProcessor { + + private static final Predicate isCrystalBall = field -> CrystalBall.class.isAssignableFrom( + field.getType()); + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + // @formatter:off + AnnotationUtils.findAnnotatedFields(testInstance.getClass(), RegisterExtension.class, isCrystalBall).stream() + .findFirst() + .ifPresent(field -> { + try { + ReflectionUtils.makeAccessible(field); + field.set(testInstance, new CrystalBall("Outlook good")); + } + catch (Throwable t) { + ExceptionUtils.throwAsUncheckedException(t); + } + }); + // @formatter:on + } + + } + + static class TwoNestedClassesTestCase { + + @RegisterExtension + Extension extension = new LongParameterResolver(); + + @Nested + class A { + + @Test + void first(Long n) { + assertEquals(42L, n); + } + + @Test + void second(Long n) { + assertEquals(42L, n); + } + + } + + @Nested + class B { + + @Test + void first(Long n) { + assertEquals(42L, n); + } + + @Test + void second(Long n) { + assertEquals(42L, n); + } + + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java new file mode 100644 index 00000000..dc2d5329 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; + +/** + * @since 5.8 + */ +class RandomlyOrderedTests { + + private static final Set callSequence = Collections.synchronizedSet(new LinkedHashSet<>()); + + @Test + void randomSeedForClassAndMethodOrderingIsDeterministic() { + IntStream.range(0, 20).forEach(i -> { + callSequence.clear(); + var tests = executeTests(1618034L); + + tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); + assertThat(callSequence).containsExactlyInAnyOrder("B_TestCase#b", "B_TestCase#c", "B_TestCase#a", + "C_TestCase#b", "C_TestCase#c", "C_TestCase#a", "A_TestCase#b", "A_TestCase#c", "A_TestCase#a"); + }); + } + + private Events executeTests(long randomSeed) { + // @formatter:off + return EngineTestKit + .engine("junit-jupiter") + .configurationParameter(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, ClassOrderer.Random.class.getName()) + .configurationParameter(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, MethodOrderer.Random.class.getName()) + .configurationParameter(MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME, String.valueOf(randomSeed)) + .selectors(selectClass(A_TestCase.class), selectClass(B_TestCase.class), selectClass(C_TestCase.class)) + .execute() + .testEvents(); + // @formatter:on + } + + abstract static class BaseTestCase { + + @BeforeEach + void trackInvocations(TestInfo testInfo) { + var testClass = testInfo.getTestClass().get(); + var testMethod = testInfo.getTestMethod().get(); + + callSequence.add(testClass.getSimpleName() + "#" + testMethod.getName()); + } + + @Test + void a() { + } + + @Test + void b() { + } + + @Test + void c() { + } + } + + static class A_TestCase extends BaseTestCase { + } + + static class B_TestCase extends BaseTestCase { + } + + static class C_TestCase extends BaseTestCase { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java new file mode 100644 index 00000000..44514c06 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java @@ -0,0 +1,176 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.RepetitionInfo; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests for {@link RepeatedTest @RepeatedTest} and supporting + * infrastructure. + * + * @since 5.0 + */ +class RepeatedTestTests extends AbstractJupiterTestEngineTests { + + private static int fortyTwo = 0; + + @BeforeEach + @AfterEach + void beforeAndAfterEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { + if (testInfo.getTestMethod().get().getName().equals("repeatedOnce")) { + assertThat(repetitionInfo.getCurrentRepetition()).isEqualTo(1); + assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(1); + } + else if (testInfo.getTestMethod().get().getName().equals("repeatedFortyTwoTimes")) { + assertThat(repetitionInfo.getCurrentRepetition()).isBetween(1, 42); + assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(42); + } + } + + @AfterAll + static void afterAll() { + assertEquals(42, fortyTwo); + } + + @RepeatedTest(1) + void repeatedOnce(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); + } + + @RepeatedTest(42) + void repeatedFortyTwoTimes(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).matches("repetition \\d+ of 42"); + fortyTwo++; + } + + @RepeatedTest(1) + @DisplayName("Repeat!") + void customDisplayName(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); + } + + @RepeatedTest(1) + @DisplayName(" \t ") + void customDisplayNameWithBlankName(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); + } + + @RepeatedTest(value = 1, name = "{displayName}") + @DisplayName("Repeat!") + void customDisplayNameWithPatternIncludingDisplayName(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("Repeat!"); + } + + @RepeatedTest(value = 1, name = "#{currentRepetition}") + @DisplayName("Repeat!") + void customDisplayNameWithPatternIncludingCurrentRepetition(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("#1"); + } + + @RepeatedTest(value = 1, name = "Repetition #{currentRepetition} for {displayName}") + @DisplayName("Repeat!") + void customDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("Repetition #1 for Repeat!"); + } + + @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) + @DisplayName("Repeat!") + void customDisplayNameWithPredefinedLongPattern(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("Repeat! :: repetition 1 of 1"); + } + + @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") + @DisplayName("Repeat!") + void customDisplayNameWithPatternIncludingDisplayNameCurrentRepetitionAndTotalRepetitions(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("Repeat! 1/1"); + } + + @RepeatedTest(value = 1, name = "Repetition #{currentRepetition} for {displayName}") + void defaultDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo testInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo( + "Repetition #1 for defaultDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo)"); + } + + @RepeatedTest(value = 5, name = "{displayName}") + void injectRepetitionInfo(TestInfo testInfo, RepetitionInfo repetitionInfo) { + assertThat(testInfo.getDisplayName()).isEqualTo("injectRepetitionInfo(TestInfo, RepetitionInfo)"); + assertThat(repetitionInfo.getCurrentRepetition()).isBetween(1, 5); + assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(5); + } + + @RepeatedTest(1) + void failsContainerOnEmptyPattern() { + executeTest("testWithEmptyPattern").assertThatEvents() // + .haveExactly(1, event(container(), displayName("testWithEmptyPattern()"), // + finishedWithFailure(message(value -> value.contains("must be declared with a non-empty name"))))); + } + + @RepeatedTest(1) + void failsContainerOnBlankPattern() { + executeTest("testWithBlankPattern").assertThatEvents() // + .haveExactly(1, event(container(), displayName("testWithBlankPattern()"), // + finishedWithFailure(message(value -> value.contains("must be declared with a non-empty name"))))); + } + + @RepeatedTest(1) + void failsContainerOnNegativeRepeatCount() { + executeTest("negativeRepeatCount").assertThatEvents() // + .haveExactly(1, event(container(), displayName("negativeRepeatCount()"), // + finishedWithFailure(message(value -> value.contains("must be declared with a positive 'value'"))))); + } + + @RepeatedTest(1) + void failsContainerOnZeroRepeatCount() { + executeTest("zeroRepeatCount").assertThatEvents() // + .haveExactly(1, event(container(), displayName("zeroRepeatCount()"), // + finishedWithFailure(message(value -> value.contains("must be declared with a positive 'value'"))))); + } + + private Events executeTest(String methodName) { + return executeTests(selectMethod(TestCase.class, methodName)).allEvents(); + } + + static class TestCase { + + @RepeatedTest(value = 1, name = "") + void testWithEmptyPattern() { + } + + @RepeatedTest(value = 1, name = " \t ") + void testWithBlankPattern() { + } + + @RepeatedTest(-99) + void negativeRepeatCount() { + } + + @RepeatedTest(0) + void zeroRepeatCount() { + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java new file mode 100644 index 00000000..81630493 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.ThrowingConsumer; + +/** + * @since 5.5 + */ +class SameThreadTimeoutInvocationTests { + + @Test + void resetsInterruptFlag() { + var exception = assertThrows(TimeoutException.class, () -> withExecutor(executor -> { + var delegate = new EventuallyInterruptibleInvocation(); + var duration = new TimeoutDuration(1, NANOSECONDS); + var timeoutInvocation = new SameThreadTimeoutInvocation<>(delegate, duration, executor, () -> "execution"); + timeoutInvocation.proceed(); + })); + assertFalse(Thread.currentThread().isInterrupted()); + assertThat(exception).hasMessage("execution timed out after 1 nanosecond"); + } + + private void withExecutor(ThrowingConsumer consumer) throws Throwable { + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + try { + consumer.accept(executor); + } + finally { + executor.shutdown(); + assertTrue(executor.awaitTermination(5, SECONDS)); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java new file mode 100644 index 00000000..8ea41562 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.condition.OS.WINDOWS; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout.ThreadMode; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.jupiter.engine.execution.ExtensionValuesStore; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; + +/** + * @since 5.9 + */ +@DisplayName("SeparateThreadTimeoutInvocation") +class SeparateThreadTimeoutInvocationTest { + + private static final long PREEMPTIVE_TIMEOUT_MILLIS = WINDOWS.isCurrentOs() ? 1000 : 100; + + @Test + @DisplayName("throws timeout exception when timeout duration is exceeded") + void throwsTimeoutException() { + AtomicReference threadName = new AtomicReference<>(); + var invocation = aSeparateThreadInvocation(() -> { + threadName.set(Thread.currentThread().getName()); + Thread.sleep(PREEMPTIVE_TIMEOUT_MILLIS * 2); + return null; + }); + + assertThatThrownBy(invocation::proceed) // + .hasMessage("method() timed out after " + PREEMPTIVE_TIMEOUT_MILLIS + " milliseconds") // + .isInstanceOf(TimeoutException.class) // + .hasRootCauseMessage("Execution timed out in thread " + threadName.get()); + } + + @Test + @DisplayName("executes invocation in a separate thread") + void runsInvocationUsingSeparateThread() throws Throwable { + var invocationThreadName = aSeparateThreadInvocation(() -> Thread.currentThread().getName()).proceed(); + assertThat(invocationThreadName).isNotEqualTo(Thread.currentThread().getName()); + } + + @Test + @DisplayName("throws invocation exception") + void shouldThrowInvocationException() { + var invocation = aSeparateThreadInvocation(() -> { + throw new RuntimeException("hi!"); + }); + assertThatThrownBy(invocation::proceed) // + .isInstanceOf(RuntimeException.class) // + .hasMessage("hi!"); + } + + private static SeparateThreadTimeoutInvocation aSeparateThreadInvocation(Invocation invocation) { + var namespace = ExtensionContext.Namespace.create(SeparateThreadTimeoutInvocationTest.class); + var store = new NamespaceAwareStore(new ExtensionValuesStore(null), namespace); + var parameters = new TimeoutInvocationParameters<>(invocation, + new TimeoutDuration(PREEMPTIVE_TIMEOUT_MILLIS, MILLISECONDS), () -> "method()"); + return (SeparateThreadTimeoutInvocation) new TimeoutInvocationFactory(store) // + .create(ThreadMode.SEPARATE_THREAD, parameters); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java new file mode 100644 index 00000000..abaf97bd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Demo extension for auto-detection of extensions loaded via Java's + * {@link java.util.ServiceLoader} mechanism. + * + * @since 5.0 + */ +public class ServiceLoaderExtension implements BeforeAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java new file mode 100644 index 00000000..ddaaa214 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java @@ -0,0 +1,369 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.nio.file.Files.deleteIfExists; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; +import static org.junit.jupiter.api.io.CleanupMode.NEVER; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.io.IOException; +import java.nio.file.Path; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * Test that {@linkplain TempDir temporary directories} are not deleted with + * {@link CleanupMode#NEVER}, are deleted with {@link CleanupMode#ON_SUCCESS} + * but only if the test passes, and are always deleted with {@link CleanupMode#ALWAYS}. + * + * @see CleanupMode + * @see TempDir + * @since 5.9 + */ +class TempDirectoryCleanupTests extends AbstractJupiterTestEngineTests { + + @Nested + class TempDirFieldTests { + + private static Path defaultFieldDir; + private static Path neverFieldDir; + private static Path alwaysFieldDir; + private static Path onSuccessFailingFieldDir; + private static Path onSuccessPassingFieldDir; + + /** + * Ensure the cleanup mode defaults to ALWAYS for fields. + *

+ * Expect the TempDir to be cleaned up. + */ + @Test + void cleanupModeDefaultField() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(DefaultFieldCase.class, "testDefaultField"))// + .build(); + executeTests(request); + + assertThat(defaultFieldDir).doesNotExist(); + } + + /** + * Ensure that a custom, global cleanup mode is used for fields. + *

+ * Expect the TempDir NOT to be cleaned up if set to NEVER. + */ + @Test + void cleanupModeCustomDefaultField() { + LauncherDiscoveryRequest request = request()// + .configurationParameter(TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME, "never")// + .selectors(selectMethod(DefaultFieldCase.class, "testDefaultField"))// + .build(); + executeTests(request); + + assertThat(defaultFieldDir).exists(); + } + + /** + * Ensure that NEVER cleanup modes are obeyed for fields. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeNeverField() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(NeverFieldCase.class, "testNeverField"))// + .build(); + executeTests(request); + + assertThat(neverFieldDir).exists(); + } + + /** + * Ensure that ALWAYS cleanup modes are obeyed for fields. + *

+ * Expect the TempDir to be cleaned up. + */ + @Test + void cleanupModeAlwaysField() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(AlwaysFieldCase.class, "testAlwaysField"))// + .build(); + executeTests(request); + + assertThat(alwaysFieldDir).doesNotExist(); + } + + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for passing field tests. + *

+ * Expect the TempDir to be cleaned up. + */ + @Test + void cleanupModeOnSuccessPassingField() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(OnSuccessPassingFieldCase.class, "testOnSuccessPassingField"))// + .build(); + executeTests(request); + + assertThat(onSuccessPassingFieldDir).doesNotExist(); + } + + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for failing field tests. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeOnSuccessFailingField() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(OnSuccessFailingFieldCase.class, "testOnSuccessFailingField"))// + .build(); + executeTests(request); + + assertThat(onSuccessFailingFieldDir).exists(); + } + + @AfterAll + static void afterAll() throws IOException { + deleteIfExists(defaultFieldDir); + deleteIfExists(neverFieldDir); + deleteIfExists(alwaysFieldDir); + deleteIfExists(onSuccessFailingFieldDir); + deleteIfExists(onSuccessPassingFieldDir); + } + + // ------------------------------------------------------------------- + + static class DefaultFieldCase { + + @TempDir + Path defaultFieldDir; + + @Test + void testDefaultField() { + TempDirFieldTests.defaultFieldDir = defaultFieldDir; + } + } + + static class NeverFieldCase { + + @TempDir(cleanup = NEVER) + Path neverFieldDir; + + @Test + void testNeverField() { + TempDirFieldTests.neverFieldDir = neverFieldDir; + } + } + + static class AlwaysFieldCase { + + @TempDir(cleanup = ALWAYS) + Path alwaysFieldDir; + + @Test + void testAlwaysField() { + TempDirFieldTests.alwaysFieldDir = alwaysFieldDir; + } + } + + static class OnSuccessPassingFieldCase { + + @TempDir(cleanup = ON_SUCCESS) + Path onSuccessPassingFieldDir; + + @Test + void testOnSuccessPassingField() { + TempDirFieldTests.onSuccessPassingFieldDir = onSuccessPassingFieldDir; + } + } + + static class OnSuccessFailingFieldCase { + + @TempDir(cleanup = ON_SUCCESS) + Path onSuccessFailingFieldDir; + + @Test + void testOnSuccessFailingField() { + TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; + fail(); + } + } + + } + + @Nested + class TempDirParameterTests { + + private static Path defaultParameterDir; + private static Path neverParameterDir; + private static Path alwaysParameterDir; + private static Path onSuccessFailingParameterDir; + private static Path onSuccessPassingParameterDir; + + /** + * Ensure the cleanup mode defaults to ALWAYS for parameters. + *

+ * Expect the TempDir to be cleaned up. + */ + @Test + void cleanupModeDefaultParameter() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(DefaultParameterCase.class, "testDefaultParameter", "java.nio.file.Path"))// + .build(); + executeTests(request); + + assertThat(defaultParameterDir).doesNotExist(); + } + + /** + * Ensure that a custom, global cleanup mode is used for parameters. + *

+ * Expect the TempDir NOT to be cleaned up if set to NEVER. + */ + @Test + void cleanupModeCustomDefaultParameter() { + LauncherDiscoveryRequest request = request()// + .configurationParameter(TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME, "never")// + .selectors(selectMethod(DefaultParameterCase.class, "testDefaultParameter", "java.nio.file.Path"))// + .build(); + executeTests(request); + + assertThat(defaultParameterDir).exists(); + } + + /** + * Ensure that NEVER cleanup modes are obeyed for parameters. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeNeverParameter() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(NeverParameterCase.class, "testNeverParameter", "java.nio.file.Path"))// + .build(); + executeTests(request); + + assertThat(neverParameterDir).exists(); + } + + /** + * Ensure that ALWAYS cleanup modes are obeyed for parameters. + *

+ * Expect the TempDir to be cleaned up. + */ + @Test + void cleanupModeAlwaysParameter() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(AlwaysParameterCase.class, "testAlwaysParameter", "java.nio.file.Path"))// + .build(); + executeTests(request); + + assertThat(alwaysParameterDir).doesNotExist(); + } + + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for passing parameter tests. + *

+ * Expect the TempDir to be cleaned up. + */ + @Test + void cleanupModeOnSuccessPassingParameter() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(OnSuccessPassingParameterCase.class, "testOnSuccessPassingParameter", + "java.nio.file.Path"))// + .build(); + executeTests(request); + + assertThat(onSuccessPassingParameterDir).doesNotExist(); + } + + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for failing parameter tests. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeOnSuccessFailingParameter() { + LauncherDiscoveryRequest request = request()// + .selectors(selectMethod(OnSuccessFailingParameterCase.class, "testOnSuccessFailingParameter", + "java.nio.file.Path"))// + .build(); + executeTests(request); + + assertThat(onSuccessFailingParameterDir).exists(); + } + + @AfterAll + static void afterAll() throws IOException { + deleteIfExists(defaultParameterDir); + deleteIfExists(neverParameterDir); + deleteIfExists(alwaysParameterDir); + deleteIfExists(onSuccessFailingParameterDir); + deleteIfExists(onSuccessPassingParameterDir); + } + + // ------------------------------------------------------------------- + + static class DefaultParameterCase { + + @Test + void testDefaultParameter(@TempDir Path defaultParameterDir) { + TempDirParameterTests.defaultParameterDir = defaultParameterDir; + } + } + + static class NeverParameterCase { + + @Test + void testNeverParameter(@TempDir(cleanup = NEVER) Path neverParameterDir) { + TempDirParameterTests.neverParameterDir = neverParameterDir; + } + } + + static class AlwaysParameterCase { + + @Test + void testAlwaysParameter(@TempDir(cleanup = ALWAYS) Path alwaysParameterDir) { + TempDirParameterTests.alwaysParameterDir = alwaysParameterDir; + } + } + + static class OnSuccessPassingParameterCase { + + @Test + void testOnSuccessPassingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessPassingParameterDir) { + TempDirParameterTests.onSuccessPassingParameterDir = onSuccessPassingParameterDir; + } + } + + static class OnSuccessFailingParameterCase { + + @Test + void testOnSuccessFailingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessFailingParameterDir) { + TempDirParameterTests.onSuccessFailingParameterDir = onSuccessFailingParameterDir; + fail(); + } + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java new file mode 100644 index 00000000..60c9f383 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java @@ -0,0 +1,1308 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Deque; +import java.util.LinkedList; +import java.util.function.Supplier; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests for the legacy behavior of the {@link TempDirectory} + * extension to create a single temp directory per context, i.e. test class or + * method. + * + * @since 5.4 + */ +@DisplayName("TempDirectory extension (per context)") +class TempDirectoryPerContextTests extends AbstractJupiterTestEngineTests { + + @SuppressWarnings("deprecation") + @Override + protected EngineExecutionResults executeTestsForClass(Class testClass) { + return executeTests(request() // + .selectors(selectClass(testClass)) // + .configurationParameter(TempDir.SCOPE_PROPERTY_NAME, TempDirectory.Scope.PER_CONTEXT.toString()) // + .build()); + } + + @BeforeEach + @AfterEach + void resetStaticVariables() { + BaseSharedTempDirFieldInjectionTestCase.staticTempDir = null; + BaseSharedTempDirParameterInjectionTestCase.tempDir = null; + BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.clear(); + BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.clear(); + } + + @Test + @DisplayName("does not prevent constructor parameter resolution") + void tempDirectoryDoesNotPreventConstructorParameterResolution() { + executeTestsForClass(TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("does not prevent user from deleting the temp dir within a test") + void tempDirectoryDoesNotPreventUserFromDeletingTempDir() { + executeTestsForClass(UserTempDirectoryDeletionDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("is capable of removing a read-only file") + void nonWritableFileDoesNotCauseFailure() { + executeTestsForClass(NonWritableFileDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("is capable of removing non-executable, non-writable, or non-readable directories and folders") + void nonMintPermissionsContentDoesNotCauseFailure() { + executeTestsForClass(NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(13).succeeded(13)); + } + + @Test + @DisplayName("is capable of removing a directory when its permissions have been changed") + void nonMintPermissionsDoNotCauseFailure() { + executeTestsForClass(NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(42).succeeded(42)); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + @DisplayName("is capable of removing a read-only file in a read-only dir") + void readOnlyFileInReadOnlyDirDoesNotCauseFailure() { + executeTestsForClass(ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + @DisplayName("is capable of removing a read-only file in a dir in a read-only dir") + void readOnlyFileInNestedReadOnlyDirDoesNotCauseFailure() { + executeTestsForClass(ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("can be used via instance field inside nested test classes") + void canBeUsedViaInstanceFieldInsideNestedTestClasses() { + executeTestsForClass(TempDirUsageInsideNestedClassesTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(3).succeeded(3)); + } + + @Test + @DisplayName("can be used via static field inside nested test classes") + void canBeUsedViaStaticFieldInsideNestedTestClasses() { + executeTestsForClass(StaticTempDirUsageInsideNestedClassTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + } + + @Test + @DisplayName("resolves java.io.File injection type") + void resolvesFileInstances() { + executeTestsForClass(FileInjectionTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Nested + @DisplayName("resolves shared temp dir") + @TestMethodOrder(OrderAnnotation.class) + class SharedTempDir { + + @Test + @DisplayName("when @TempDir is used on static field") + @Order(10) + void resolvesSharedTempDirWhenAnnotationIsUsedOnStaticField() { + assertSharedTempDirForFieldInjection(AnnotationOnStaticFieldTestCase.class); + } + + @Test + @DisplayName("when @TempDir is used on static field and @BeforeAll method parameter") + @Order(11) + void resolvesSharedTempDirWhenAnnotationIsUsedOnStaticFieldAndBeforeAllMethodParameter() { + assertSharedTempDirForFieldInjection(AnnotationOnStaticFieldAndBeforeAllMethodParameterTestCase.class); + } + + @Test + @DisplayName("when @TempDir is used on instance field and @BeforeAll method parameter") + @Order(14) + void resolvesSharedTempDirWhenAnnotationIsUsedOnInstanceFieldAndBeforeAllMethodParameter() { + assertSharedTempDirForFieldInjection(AnnotationOnInstanceFieldAndBeforeAllMethodParameterTestCase.class); + } + + @Test + @DisplayName("when @TempDir is used on instance field and @BeforeAll method parameter with @TestInstance(PER_CLASS)") + @Order(15) + void resolvesSharedTempDirWhenAnnotationIsUsedOnInstanceFieldAndBeforeAllMethodParameterWithTestInstancePerClass() { + assertSharedTempDirForFieldInjection( + AnnotationOnInstanceFieldAndBeforeAllMethodParameterWithTestInstancePerClassTestCase.class); + } + + @Test + @DisplayName("when @TempDir is used on @BeforeAll method parameter") + @Order(23) + void resolvesSharedTempDirWhenAnnotationIsUsedOnBeforeAllMethodParameter() { + assertSharedTempDirForParameterInjection(AnnotationOnBeforeAllMethodParameterTestCase.class); + } + + @Test + @DisplayName("when @TempDir is used on @BeforeAll method parameter with @TestInstance(PER_CLASS)") + @Order(24) + void resolvesSharedTempDirWhenAnnotationIsUsedOnBeforeAllMethodParameterWithTestInstancePerClass() { + assertSharedTempDirForParameterInjection( + AnnotationOnBeforeAllMethodParameterWithTestInstancePerClassTestCase.class); + } + + private void assertSharedTempDirForFieldInjection( + Class testClass) { + + assertSharedTempDirForParameterInjection(testClass, + () -> BaseSharedTempDirFieldInjectionTestCase.staticTempDir); + } + + private void assertSharedTempDirForParameterInjection( + Class testClass) { + + assertSharedTempDirForParameterInjection(testClass, + () -> BaseSharedTempDirParameterInjectionTestCase.tempDir); + } + + private void assertSharedTempDirForParameterInjection(Class testClass, Supplier staticTempDir) { + var results = executeTestsForClass(testClass); + + results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2)); + assertThat(staticTempDir.get()).isNotNull().doesNotExist(); + } + + } + + @Nested + @DisplayName("resolves separate temp dirs") + @TestMethodOrder(OrderAnnotation.class) + class SeparateTempDirs { + + @Test + @DisplayName("when @TempDir is used on instance field") + @Order(11) + void resolvesSeparateTempDirWhenAnnotationIsUsedOnInstanceField() { + assertSeparateTempDirsForFieldInjection( + SeparateTempDirsWhenUsedOnForEachLifecycleMethodsFieldInjectionTestCase.class); + assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getFirst()).doesNotExist(); + assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getLast()).doesNotExist(); + } + + @Test + @DisplayName("when @TempDir is used on instance field with @TestInstance(PER_CLASS)") + @Order(12) + void resolvesSeparateTempDirWhenAnnotationIsUsedOnInstanceFieldWithTestInstancePerClass() { + assertSeparateTempDirsForFieldInjection( + SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassFieldInjectionTestCase.class); + assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getFirst()).doesNotExist(); + assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getLast()).doesNotExist(); + } + + @Test + @DisplayName("when @TempDir is used on @BeforeEach/@AfterEach method parameters") + @Order(21) + void resolvesSeparateTempDirsWhenUsedOnForEachLifecycleMethods() { + assertSeparateTempDirsForParameterInjection( + SeparateTempDirsWhenUsedOnForEachLifecycleMethodsParameterInjectionTestCase.class); + assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getFirst()).doesNotExist(); + assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getLast()).doesNotExist(); + } + + @Test + @DisplayName("when @TempDir is used on @BeforeEach/@AfterEach method parameters with @TestInstance(PER_CLASS)") + @Order(22) + void resolvesSeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClass() { + assertSeparateTempDirsForParameterInjection( + SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassParameterInjectionTestCase.class); + assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getFirst()).doesNotExist(); + assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getLast()).doesNotExist(); + } + + @Test + @DisplayName("for @AfterAll method parameter when @TempDir is not used on constructor or @BeforeAll method parameter") + @Order(31) + void resolvesSeparateTempDirWhenAnnotationIsUsedOnAfterAllMethodParameterOnly() { + var results = executeTestsForClass(AnnotationOnAfterAllMethodParameterTestCase.class); + + results.testEvents().assertStatistics(stats -> stats.started(1).failed(0).succeeded(1)); + assertThat(AnnotationOnAfterAllMethodParameterTestCase.firstTempDir).isNotNull().doesNotExist(); + assertThat(AnnotationOnAfterAllMethodParameterTestCase.secondTempDir).isNotNull().doesNotExist(); + } + + } + + @Nested + @DisplayName("reports failure") + @TestMethodOrder(OrderAnnotation.class) + class Failures { + + @Test + @DisplayName("when @TempDir is used on static field of an unsupported type") + @Order(20) + void onlySupportsStaticFieldsOfTypePathAndFile() { + var results = executeTestsForClass(AnnotationOnStaticFieldWithUnsupportedTypeTestCase.class); + + assertSingleFailedContainer(results, ExtensionConfigurationException.class, + "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); + } + + @Test + @DisplayName("when @TempDir is used on instance field of an unsupported type") + @Order(21) + void onlySupportsInstanceFieldsOfTypePathAndFile() { + var results = executeTestsForClass(AnnotationOnInstanceFieldWithUnsupportedTypeTestCase.class); + + assertSingleFailedTest(results, ExtensionConfigurationException.class, + "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); + } + + @Test + @DisplayName("when @TempDir is used on parameter of an unsupported type") + @Order(22) + void onlySupportsParametersOfTypePathAndFile() { + var results = executeTestsForClass(InvalidTestCase.class); + + // @formatter:off + TempDirectoryPerContextTests.assertSingleFailedTest(results, + instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String"))); + // @formatter:on + } + + @Test + @DisplayName("when @TempDir is used on constructor parameter") + @Order(30) + void doesNotSupportTempDirAnnotationOnConstructorParameter() { + var results = executeTestsForClass(AnnotationOnConstructorParameterTestCase.class); + + assertSingleFailedTest(results, ParameterResolutionException.class, + "@TempDir is not supported on constructor parameters. Please use field injection instead."); + } + + @Test + @DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)") + @Order(31) + void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerClass() { + var results = executeTestsForClass(AnnotationOnConstructorParameterWithTestInstancePerClassTestCase.class); + + assertSingleFailedContainer(results, ParameterResolutionException.class, + "@TempDir is not supported on constructor parameters. Please use field injection instead."); + } + + } + + @Nested + @DisplayName("supports @TempDir") + @TestMethodOrder(OrderAnnotation.class) + class PrivateFields { + + @Test + @DisplayName("on private static field") + @Order(10) + void supportsPrivateInstanceFields() { + executeTestsForClass(AnnotationOnPrivateStaticFieldTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("on private instance field") + @Order(11) + void supportsPrivateStaticFields() { + executeTestsForClass(AnnotationOnPrivateInstanceFieldTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + } + + private static void assertSingleFailedContainer(EngineExecutionResults results, Class clazz, + String message) { + + assertSingleFailedContainer(results, instanceOf(clazz), message(actual -> actual.contains(message))); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static void assertSingleFailedContainer(EngineExecutionResults results, + Condition... conditions) { + + results.containerEvents()// + .assertStatistics(stats -> stats.started(2).failed(1).succeeded(1))// + .assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); + } + + private static void assertSingleFailedTest(EngineExecutionResults results, Class clazz, + String message) { + + assertSingleFailedTest(results, instanceOf(clazz), message(actual -> actual.contains(message))); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static void assertSingleFailedTest(EngineExecutionResults results, Condition... conditions) { + results.testEvents().assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); + results.testEvents().assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); + } + + private void assertSeparateTempDirsForFieldInjection( + Class testClass) { + + assertResolvesSeparateTempDirs(testClass, BaseSeparateTempDirsFieldInjectionTestCase.tempDirs); + } + + private void assertSeparateTempDirsForParameterInjection( + Class testClass) { + + assertResolvesSeparateTempDirs(testClass, BaseSeparateTempDirsParameterInjectionTestCase.tempDirs); + } + + private void assertResolvesSeparateTempDirs(Class testClass, Deque tempDirs) { + var results = executeTestsForClass(testClass); + + results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2)); + assertThat(tempDirs).hasSize(2); + } + + // ------------------------------------------------------------------------- + + static class BaseSharedTempDirFieldInjectionTestCase { + + static Path staticTempDir; + + @TempDir + Path tempDir; + + @BeforeEach + void beforeEach(@TempDir Path tempDir) { + if (BaseSharedTempDirFieldInjectionTestCase.staticTempDir != null) { + assertSame(BaseSharedTempDirFieldInjectionTestCase.staticTempDir, tempDir); + } + else { + BaseSharedTempDirFieldInjectionTestCase.staticTempDir = tempDir; + } + check(tempDir); + } + + @Test + void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @Test + void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @AfterEach + void afterEach(@TempDir Path tempDir) { + check(tempDir); + } + + void check(Path tempDir) { + assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir)// + .isNotNull()// + .isSameAs(tempDir)// + .isSameAs(this.tempDir); + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnStaticFieldTestCase extends BaseSharedTempDirFieldInjectionTestCase { + + @TempDir + static Path staticTempPath; + + @TempDir + static File staticTempFile; + + @Override + void check(Path tempDir) { + assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir)// + .isNotNull()// + .isSameAs(AnnotationOnStaticFieldTestCase.staticTempPath)// + .isSameAs(tempDir)// + .isSameAs(this.tempDir); + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnStaticFieldAndBeforeAllMethodParameterTestCase extends AnnotationOnStaticFieldTestCase { + + @BeforeAll + static void beforeAll(@TempDir Path tempDir) { + assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir).isNull(); + BaseSharedTempDirFieldInjectionTestCase.staticTempDir = tempDir; + assertThat(AnnotationOnStaticFieldTestCase.staticTempFile).isNotNull(); + assertThat(AnnotationOnStaticFieldTestCase.staticTempPath)// + .isNotNull()// + .isSameAs(tempDir); + assertThat(AnnotationOnStaticFieldTestCase.staticTempFile.toPath().toAbsolutePath())// + .isEqualTo(AnnotationOnStaticFieldTestCase.staticTempPath.toAbsolutePath()); + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnInstanceFieldAndBeforeAllMethodParameterTestCase + extends BaseSharedTempDirFieldInjectionTestCase { + + @BeforeAll + static void beforeAll(@TempDir Path tempDir) { + assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir).isNull(); + BaseSharedTempDirFieldInjectionTestCase.staticTempDir = tempDir; + assertTrue(Files.exists(tempDir)); + } + + } + + @TestInstance(PER_CLASS) + static class AnnotationOnInstanceFieldAndBeforeAllMethodParameterWithTestInstancePerClassTestCase + extends AnnotationOnInstanceFieldAndBeforeAllMethodParameterTestCase { + + } + + static class AnnotationOnPrivateInstanceFieldTestCase { + + @SuppressWarnings("unused") + @TempDir + private Path tempDir; + + @Test + void test() { + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnPrivateStaticFieldTestCase { + + @SuppressWarnings("unused") + @TempDir + private static Path tempDir; + + @Test + void test() { + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnStaticFieldWithUnsupportedTypeTestCase { + + @SuppressWarnings("unused") + @TempDir + static String tempDir; + + @Test + void test1() { + } + + } + + static class AnnotationOnInstanceFieldWithUnsupportedTypeTestCase { + + @SuppressWarnings("unused") + @TempDir + String tempDir; + + @Test + void test1() { + } + + } + + static class BaseSharedTempDirParameterInjectionTestCase { + + static Path tempDir; + + @BeforeEach + void beforeEach(@TempDir Path tempDir) { + check(tempDir); + } + + @Test + void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @Test + void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @AfterEach + void afterEach(@TempDir Path tempDir) { + check(tempDir); + } + + static void check(Path tempDir) { + assertThat(BaseSharedTempDirParameterInjectionTestCase.tempDir).isNotNull().isSameAs(tempDir); + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnConstructorParameterTestCase { + + AnnotationOnConstructorParameterTestCase(@SuppressWarnings("unused") @TempDir Path tempDir) { + // never called + } + + @Test + void test() { + // never called + } + + } + + @TestInstance(PER_CLASS) + static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase + extends AnnotationOnConstructorParameterTestCase { + + AnnotationOnConstructorParameterWithTestInstancePerClassTestCase(@TempDir Path tempDir) { + super(tempDir); + } + } + + static class AnnotationOnBeforeAllMethodParameterTestCase extends BaseSharedTempDirParameterInjectionTestCase { + + @BeforeAll + static void beforeAll(@TempDir Path tempDir) { + assertThat(BaseSharedTempDirParameterInjectionTestCase.tempDir).isNull(); + BaseSharedTempDirParameterInjectionTestCase.tempDir = tempDir; + check(tempDir); + } + } + + @TestInstance(PER_CLASS) + static class AnnotationOnBeforeAllMethodParameterWithTestInstancePerClassTestCase + extends BaseSharedTempDirParameterInjectionTestCase { + + @BeforeAll + void beforeAll(@TempDir Path tempDir) { + assertThat(BaseSharedTempDirParameterInjectionTestCase.tempDir).isNull(); + BaseSharedTempDirParameterInjectionTestCase.tempDir = tempDir; + check(tempDir); + } + } + + static class AnnotationOnAfterAllMethodParameterTestCase { + + static Path firstTempDir = null; + static Path secondTempDir = null; + + @Test + void test(@TempDir Path tempDir, TestInfo testInfo) throws Exception { + assertThat(firstTempDir).isNull(); + firstTempDir = tempDir; + writeFile(tempDir, testInfo); + } + + @AfterAll + static void afterAll(@TempDir Path tempDir) { + assertThat(firstTempDir).isNotNull(); + assertNotEquals(firstTempDir, tempDir); + secondTempDir = tempDir; + } + } + + static class BaseSeparateTempDirsFieldInjectionTestCase { + + static final Deque tempDirs = new LinkedList<>(); + + @TempDir + Path tempDir; + + @BeforeEach + void beforeEach(@TempDir Path tempDir) { + for (Path dir : tempDirs) { + assertThat(dir).doesNotExist(); + } + assertThat(tempDirs).doesNotContain(tempDir); + tempDirs.add(tempDir); + check(tempDir); + } + + @Test + void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @Test + void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @AfterEach + void afterEach(@TempDir Path tempDir) { + check(tempDir); + } + + void check(Path tempDir) { + assertThat(tempDirs.getLast())// + .isNotNull()// + .isSameAs(tempDir)// + .isSameAs(this.tempDir); + assertTrue(Files.exists(tempDir)); + } + + } + + static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsFieldInjectionTestCase + extends BaseSeparateTempDirsFieldInjectionTestCase { + } + + @TestInstance(PER_CLASS) + static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassFieldInjectionTestCase + extends SeparateTempDirsWhenUsedOnForEachLifecycleMethodsFieldInjectionTestCase { + } + + static class BaseSeparateTempDirsParameterInjectionTestCase { + + static final Deque tempDirs = new LinkedList<>(); + + @BeforeEach + void beforeEach(@TempDir Path tempDir) { + for (Path dir : tempDirs) { + assertThat(dir).doesNotExist(); + } + assertThat(tempDirs).doesNotContain(tempDir); + tempDirs.add(tempDir); + check(tempDir); + } + + @Test + void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @Test + void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @AfterEach + void afterEach(@TempDir Path tempDir) { + check(tempDir); + } + + void check(Path tempDir) { + assertSame(tempDirs.getLast(), tempDir); + assertTrue(Files.exists(tempDir)); + } + } + + static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsParameterInjectionTestCase + extends BaseSeparateTempDirsParameterInjectionTestCase { + } + + @TestInstance(PER_CLASS) + static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassParameterInjectionTestCase + extends SeparateTempDirsWhenUsedOnForEachLifecycleMethodsParameterInjectionTestCase { + } + + static class InvalidTestCase { + + @Test + void wrongParameterType(@SuppressWarnings("unused") @TempDir String ignored) { + fail("this should never be called"); + } + } + + static class FileInjectionTestCase { + + @TempDir + File fileTempDir; + + @TempDir + Path pathTempDir; + + @Test + void checkFile(@TempDir File tempDir, @TempDir Path ref) { + assertFileAndPathAreEqual(tempDir, ref); + assertFileAndPathAreEqual(this.fileTempDir, this.pathTempDir); + } + + private void assertFileAndPathAreEqual(File tempDir, Path ref) { + Path path = tempDir.toPath(); + assertEquals(ref.toAbsolutePath(), path.toAbsolutePath()); + assertTrue(Files.exists(path)); + } + + } + + private static void writeFile(Path tempDir, TestInfo testInfo) throws IOException { + Path file = tempDir.resolve(testInfo.getTestMethod().orElseThrow().getName() + ".txt"); + Files.write(file, testInfo.getDisplayName().getBytes()); + } + + // https://github.com/junit-team/junit5/issues/1748 + static class TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase { + + TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase(TestInfo testInfo) { + assertNotNull(testInfo); + } + + @Test + void test() { + } + + } + + // https://github.com/junit-team/junit5/issues/1801 + static class UserTempDirectoryDeletionDoesNotCauseFailureTestCase { + + @Test + void deleteTempDir(@TempDir Path tempDir) throws IOException { + Files.delete(tempDir); + assertThat(tempDir).doesNotExist(); + } + + } + + // https://github.com/junit-team/junit5/issues/2046 + static class NonWritableFileDoesNotCauseFailureTestCase { + + @Test + void createReadonlyFile(@TempDir Path tempDir) throws IOException { + // Removal of setWritable(false) files might fail (e.g. for Windows) + // The test verifies that @TempDir is capable of removing of such files + var path = Files.write(tempDir.resolve("test.txt"), new byte[0]); + assumeTrue(path.toFile().setWritable(false), + () -> "Unable to set file " + path + " readonly via .toFile().setWritable(false)"); + } + + } + + // https://github.com/junit-team/junit5/issues/2171 + static class ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase { + + @Test + void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { + File file = tempDir.toPath().resolve("file").toFile(); + assumeTrue(file.createNewFile()); + assumeTrue(tempDir.setReadOnly()); + assumeTrue(file.setReadOnly()); + } + + } + + // https://github.com/junit-team/junit5/issues/2171 + static class ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase { + + @Test + void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { + File file = tempDir.toPath().resolve("dir").resolve("file").toFile(); + assumeTrue(file.getParentFile().mkdirs()); + assumeTrue(file.createNewFile()); + assumeTrue(tempDir.setReadOnly()); + assumeTrue(file.getParentFile().setReadOnly()); + assumeTrue(file.setReadOnly()); + } + + } + + // https://github.com/junit-team/junit5/issues/2609 + @SuppressWarnings("ResultOfMethodCallIgnored") + static class NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase { + + @Test + void createFile(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile(); + } + + @Test + void createFolder(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile(); + } + + @Test + void createNonWritableFile(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + } + + @Test + void createNonReadableFile(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + } + + @Test + void createNonWritableDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + } + + @Test + void createNonReadableDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + } + + @Test + void createNonExecutableDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + } + + @Test + void createNonEmptyNonWritableDirectory(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + } + + @Test + void createNonEmptyNonReadableDirectory(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + } + + @Test + void createNonEmptyNonExecutableDirectory(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + } + + @Test + void createNonEmptyDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + } + + @Test + void createNonEmptyDirectoryWithNonWritableFile(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + } + + @Test + void createNonEmptyDirectoryWithNonReadableFile(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + } + } + + // https://github.com/junit-team/junit5/issues/2609 + @SuppressWarnings("ResultOfMethodCallIgnored") + static class NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase { + + @Nested + class NonWritable { + + @Test + void makeEmptyTempDirectoryNonWritable(@TempDir Path tempDir) { + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithFileNonWritable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFileNonWritable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFileNonWritable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonWritable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonWritable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + } + + @Nested + class NonReadable { + + @Test + void makeEmptyTempDirectoryNonReadable(@TempDir Path tempDir) { + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithFileNonReadable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFileNonReadable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFileNonReadable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonReadable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonReadable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + } + + @Nested + class NonExecutable { + + @Test + void makeEmptyTempDirectoryNonExecutable(@TempDir Path tempDir) { + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithFileNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFileNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFileNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonExecutableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonExecutableFolderNonExecutable(@TempDir Path tempDir) + throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonExecutable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonExecutable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + } + } + + // https://github.com/junit-team/junit5/issues/2079 + static class TempDirUsageInsideNestedClassesTestCase { + + @TempDir + File tempDir; + + @Test + void topLevel() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + } + + @Nested + class NestedTestClass { + + @Test + void nested() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + } + + @Nested + class EvenDeeperNestedTestClass { + + @Test + void deeplyNested() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + } + } + } + } + + static class StaticTempDirUsageInsideNestedClassTestCase { + + @TempDir + static File tempDir; + + static File initialTempDir; + + @Test + void topLevel() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + initialTempDir = tempDir; + } + + @Nested + class NestedTestClass { + + @Test + void nested() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + assertSame(initialTempDir, tempDir); + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java new file mode 100644 index 00000000..c98e99e0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java @@ -0,0 +1,1068 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; + +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.Constants; +import org.junit.jupiter.engine.extension.TempDirectory.FileOperations; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests for the new behavior of the {@link TempDirectory} extension + * to create separate temp directories for each {@link TempDir} declaration. + * + * @since 5.8 + */ +@DisplayName("TempDirectory extension (per declaration)") +class TempDirectoryPerDeclarationTests extends AbstractJupiterTestEngineTests { + + @BeforeEach + @AfterEach + void resetStaticVariables() { + AllPossibleDeclarationLocationsTestCase.tempDirs.clear(); + } + + @TestFactory + @DisplayName("resolves separate temp dirs for each annotation declaration") + Stream resolvesSeparateTempDirsForEachAnnotationDeclaration() { + return Arrays.stream(TestInstance.Lifecycle.values()).map( + lifecycle -> dynamicTest("with " + lifecycle + " lifecycle", () -> { + + var results = executeTests(request() // + .selectors(selectClass(AllPossibleDeclarationLocationsTestCase.class)) // + .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, + lifecycle.toString()).build()); + + results.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + + assertThat(AllPossibleDeclarationLocationsTestCase.tempDirs).hasSize(3); + + var classTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("class"); + assertThat(classTempDirs).containsOnlyKeys("staticField1", "staticField2", "beforeAll1", "beforeAll2", + "afterAll1", "afterAll2"); + assertThat(classTempDirs.values()).hasSize(6).doesNotHaveDuplicates(); + + var testATempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testA"); + assertThat(testATempDirs).containsOnlyKeys("staticField1", "staticField2", "instanceField1", + "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2"); + assertThat(testATempDirs.values()).hasSize(10).doesNotHaveDuplicates(); + + var testBTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testB"); + assertThat(testBTempDirs).containsOnlyKeys("staticField1", "staticField2", "instanceField1", + "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2"); + assertThat(testBTempDirs.values()).hasSize(10).doesNotHaveDuplicates(); + + assertThat(testATempDirs.get("staticField1")).isEqualTo(classTempDirs.get("staticField1")); + assertThat(testBTempDirs.get("staticField1")).isEqualTo(classTempDirs.get("staticField1")); + assertThat(testATempDirs.get("staticField2")).isEqualTo(classTempDirs.get("staticField2")); + assertThat(testBTempDirs.get("staticField2")).isEqualTo(classTempDirs.get("staticField2")); + + assertThat(testATempDirs.get("instanceField1")).isNotEqualTo(testBTempDirs.get("instanceField1")); + assertThat(testATempDirs.get("instanceField2")).isNotEqualTo(testBTempDirs.get("instanceField2")); + assertThat(testATempDirs.get("beforeEach1")).isNotEqualTo(testBTempDirs.get("beforeEach1")); + assertThat(testATempDirs.get("beforeEach2")).isNotEqualTo(testBTempDirs.get("beforeEach2")); + assertThat(testATempDirs.get("test1")).isNotEqualTo(testBTempDirs.get("test1")); + assertThat(testATempDirs.get("test2")).isNotEqualTo(testBTempDirs.get("test2")); + assertThat(testATempDirs.get("afterEach1")).isNotEqualTo(testBTempDirs.get("afterEach1")); + assertThat(testATempDirs.get("afterEach2")).isNotEqualTo(testBTempDirs.get("afterEach2")); + })); + } + + @Test + @DisplayName("does not prevent constructor parameter resolution") + void tempDirectoryDoesNotPreventConstructorParameterResolution() { + executeTestsForClass(TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("does not prevent user from deleting the temp dir within a test") + void tempDirectoryDoesNotPreventUserFromDeletingTempDir() { + executeTestsForClass(UserTempDirectoryDeletionDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("is capable of removing a read-only file") + void nonWritableFileDoesNotCauseFailure() { + executeTestsForClass(NonWritableFileDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("is capable of removing non-executable, non-writable, or non-readable directories and folders") + void nonMintPermissionsContentDoesNotCauseFailure() { + executeTestsForClass(NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(13).succeeded(13)); + } + + @Test + @DisplayName("is capable of removing a directory when its permissions have been changed") + void nonMintPermissionsDoNotCauseFailure() { + executeTestsForClass(NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(42).succeeded(42)); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + @DisplayName("is capable of removing a read-only file in a read-only dir") + void readOnlyFileInReadOnlyDirDoesNotCauseFailure() { + executeTestsForClass(ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + @DisplayName("is capable of removing a read-only file in a dir in a read-only dir") + void readOnlyFileInNestedReadOnlyDirDoesNotCauseFailure() { + executeTestsForClass(ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("can be used via instance field inside nested test classes") + void canBeUsedViaInstanceFieldInsideNestedTestClasses() { + executeTestsForClass(TempDirUsageInsideNestedClassesTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(3).succeeded(3)); + } + + @Test + @DisplayName("can be used via static field inside nested test classes") + void canBeUsedViaStaticFieldInsideNestedTestClasses() { + executeTestsForClass(StaticTempDirUsageInsideNestedClassTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + } + + @TestFactory + @DisplayName("only attempts to delete undeletable paths once") + Stream onlyAttemptsToDeleteUndeletablePathsOnce() { + return Stream.of( // + dynamicTest("directory", () -> onlyAttemptsToDeleteUndeletablePathOnce(UndeletableDirectoryTestCase.class)), // + dynamicTest("file", () -> onlyAttemptsToDeleteUndeletablePathOnce(UndeletableFileTestCase.class)) // + ); + } + + private void onlyAttemptsToDeleteUndeletablePathOnce(Class testClass) { + var results = executeTestsForClass(testClass); + + var tempDir = results.testEvents().reportingEntryPublished().stream().map( + it -> it.getPayload(ReportEntry.class).orElseThrow()).map( + it -> Path.of(it.getKeyValuePairs().get(UndeletableTestCase.TEMP_DIR))).findAny().orElseThrow(); + + assertSingleFailedTest(results, // + instanceOf(IOException.class), // + message("Failed to delete temp directory " + tempDir.toAbsolutePath() + ". " + // + "The following paths could not be deleted (see suppressed exceptions for details): , undeletable"), // + suppressed(0, instanceOf(DirectoryNotEmptyException.class)), // + suppressed(1, instanceOf(IOException.class), message("Simulated failure"))); + } + + @Nested + @DisplayName("reports failure") + @TestMethodOrder(OrderAnnotation.class) + class Failures { + + @Test + @DisplayName("when @TempDir is used on static field of an unsupported type") + @Order(20) + void onlySupportsStaticFieldsOfTypePathAndFile() { + var results = executeTestsForClass(AnnotationOnStaticFieldWithUnsupportedTypeTestCase.class); + + assertSingleFailedContainer(results, ExtensionConfigurationException.class, + "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); + } + + @Test + @DisplayName("when @TempDir is used on instance field of an unsupported type") + @Order(21) + void onlySupportsInstanceFieldsOfTypePathAndFile() { + var results = executeTestsForClass(AnnotationOnInstanceFieldWithUnsupportedTypeTestCase.class); + + assertSingleFailedTest(results, ExtensionConfigurationException.class, + "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); + } + + @Test + @DisplayName("when @TempDir is used on parameter of an unsupported type") + @Order(22) + void onlySupportsParametersOfTypePathAndFile() { + var results = executeTestsForClass(InvalidTestCase.class); + + // @formatter:off + TempDirectoryPerDeclarationTests.assertSingleFailedTest(results, + instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String"))); + // @formatter:on + } + + @Test + @DisplayName("when @TempDir is used on constructor parameter") + @Order(30) + void doesNotSupportTempDirAnnotationOnConstructorParameter() { + var results = executeTestsForClass(AnnotationOnConstructorParameterTestCase.class); + + assertSingleFailedTest(results, ParameterResolutionException.class, + "@TempDir is not supported on constructor parameters. Please use field injection instead."); + } + + @Test + @DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)") + @Order(31) + void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerClass() { + var results = executeTestsForClass(AnnotationOnConstructorParameterWithTestInstancePerClassTestCase.class); + + assertSingleFailedContainer(results, ParameterResolutionException.class, + "@TempDir is not supported on constructor parameters. Please use field injection instead."); + } + + } + + @Nested + @DisplayName("supports @TempDir") + @TestMethodOrder(OrderAnnotation.class) + class PrivateFields { + + @Test + @DisplayName("on private static field") + @Order(10) + void supportsPrivateInstanceFields() { + executeTestsForClass(AnnotationOnPrivateStaticFieldTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + @Test + @DisplayName("on private instance field") + @Order(11) + void supportsPrivateStaticFields() { + executeTestsForClass(AnnotationOnPrivateInstanceFieldTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + + } + + private static void assertSingleFailedContainer(EngineExecutionResults results, Class clazz, + String message) { + + assertSingleFailedContainer(results, instanceOf(clazz), message(actual -> actual.contains(message))); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static void assertSingleFailedContainer(EngineExecutionResults results, + Condition... conditions) { + + results.containerEvents()// + .assertStatistics(stats -> stats.started(2).failed(1).succeeded(1))// + .assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); + } + + private static void assertSingleFailedTest(EngineExecutionResults results, Class clazz, + String message) { + + assertSingleFailedTest(results, instanceOf(clazz), message(actual -> actual.contains(message))); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static void assertSingleFailedTest(EngineExecutionResults results, Condition... conditions) { + results.testEvents().assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); + results.testEvents().assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); + } + + // ------------------------------------------------------------------------- + + static class AnnotationOnPrivateInstanceFieldTestCase { + + @SuppressWarnings("unused") + @TempDir + private Path tempDir; + + @Test + void test() { + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnPrivateStaticFieldTestCase { + + @SuppressWarnings("unused") + @TempDir + private static Path tempDir; + + @Test + void test() { + assertTrue(Files.exists(tempDir)); + } + + } + + static class AnnotationOnStaticFieldWithUnsupportedTypeTestCase { + + @SuppressWarnings("unused") + @TempDir + static String tempDir; + + @Test + void test1() { + } + + } + + static class AnnotationOnInstanceFieldWithUnsupportedTypeTestCase { + + @SuppressWarnings("unused") + @TempDir + String tempDir; + + @Test + void test1() { + } + + } + + static class AnnotationOnConstructorParameterTestCase { + + AnnotationOnConstructorParameterTestCase(@SuppressWarnings("unused") @TempDir Path tempDir) { + // never called + } + + @Test + void test() { + // never called + } + + } + + @TestInstance(PER_CLASS) + static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase + extends AnnotationOnConstructorParameterTestCase { + + AnnotationOnConstructorParameterWithTestInstancePerClassTestCase(@TempDir Path tempDir) { + super(tempDir); + } + } + + static class InvalidTestCase { + + @Test + void wrongParameterType(@SuppressWarnings("unused") @TempDir String ignored) { + fail("this should never be called"); + } + } + + @Nested + @DisplayName("resolves java.io.File injection type") + class FileAndPathInjection { + + @TempDir + File fileTempDir; + + @TempDir + Path pathTempDir; + + @Test + @DisplayName("and injected File and Path do not reference the same temp directory") + void checkFile(@TempDir File tempDir, @TempDir Path ref) { + assertFileAndPathAreNotEqual(tempDir, ref); + assertFileAndPathAreNotEqual(this.fileTempDir, this.pathTempDir); + } + + private static void assertFileAndPathAreNotEqual(File tempDir, Path ref) { + Path path = tempDir.toPath(); + assertNotEquals(ref.toAbsolutePath(), path.toAbsolutePath()); + assertTrue(Files.exists(path)); + } + + } + + // https://github.com/junit-team/junit5/issues/1748 + static class TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase { + + @TempDir + Path tempDir; + + TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase(TestInfo testInfo) { + assertNotNull(testInfo); + } + + @Test + void test() { + assertNotNull(tempDir); + } + + } + + // https://github.com/junit-team/junit5/issues/1801 + static class UserTempDirectoryDeletionDoesNotCauseFailureTestCase { + + @Test + void deleteTempDir(@TempDir Path tempDir) throws IOException { + Files.delete(tempDir); + assertThat(tempDir).doesNotExist(); + } + + } + + // https://github.com/junit-team/junit5/issues/2046 + static class NonWritableFileDoesNotCauseFailureTestCase { + + @Test + void createReadonlyFile(@TempDir Path tempDir) throws IOException { + // Removal of setWritable(false) files might fail (e.g. for Windows) + // The test verifies that @TempDir is capable of removing of such files + var path = Files.write(tempDir.resolve("test.txt"), new byte[0]); + assumeTrue(path.toFile().setWritable(false), + () -> "Unable to set file " + path + " readonly via .toFile().setWritable(false)"); + } + + } + + // https://github.com/junit-team/junit5/issues/2171 + static class ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase { + + @Test + void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { + File file = tempDir.toPath().resolve("file").toFile(); + assumeTrue(file.createNewFile()); + assumeTrue(tempDir.setReadOnly()); + assumeTrue(file.setReadOnly()); + } + + } + + // https://github.com/junit-team/junit5/issues/2171 + static class ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase { + + @Test + void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { + File file = tempDir.toPath().resolve("dir").resolve("file").toFile(); + assumeTrue(file.getParentFile().mkdirs()); + assumeTrue(file.createNewFile()); + assumeTrue(tempDir.setReadOnly()); + assumeTrue(file.getParentFile().setReadOnly()); + assumeTrue(file.setReadOnly()); + } + + } + + // https://github.com/junit-team/junit5/issues/2609 + @SuppressWarnings("ResultOfMethodCallIgnored") + static class NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase { + + @Test + void createFile(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile(); + } + + @Test + void createFolder(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile(); + } + + @Test + void createNonWritableFile(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + } + + @Test + void createNonReadableFile(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + } + + @Test + void createNonWritableDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + } + + @Test + void createNonReadableDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + } + + @Test + void createNonExecutableDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + } + + @Test + void createNonEmptyNonWritableDirectory(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + } + + @Test + void createNonEmptyNonReadableDirectory(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + } + + @Test + void createNonEmptyNonExecutableDirectory(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + } + + @Test + void createNonEmptyDirectory(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + } + + @Test + void createNonEmptyDirectoryWithNonWritableFile(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + } + + @Test + void createNonEmptyDirectoryWithNonReadableFile(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + } + } + + // https://github.com/junit-team/junit5/issues/2609 + @SuppressWarnings("ResultOfMethodCallIgnored") + static class NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase { + + @Nested + class NonWritable { + + @Test + void makeEmptyTempDirectoryNonWritable(@TempDir Path tempDir) { + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithFileNonWritable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFileNonWritable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFileNonWritable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonWritable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setWritable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonWritable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setWritable(false); + } + } + + @Nested + class NonReadable { + + @Test + void makeEmptyTempDirectoryNonReadable(@TempDir Path tempDir) { + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithFileNonReadable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFileNonReadable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFileNonReadable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonReadable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setReadable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonReadable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setReadable(false); + } + } + + @Nested + class NonExecutable { + + @Test + void makeEmptyTempDirectoryNonExecutable(@TempDir Path tempDir) { + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithFileNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFileNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFileNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonExecutableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyNonExecutableFolderNonExecutable(@TempDir Path tempDir) + throws IOException { + Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + subDir.toFile().setExecutable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonExecutable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); + tempDir.toFile().setExecutable(false); + } + + @Test + void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonExecutable(@TempDir Path tempDir) + throws IOException { + Files.createDirectory(tempDir.resolve("test-sub-dir")); + Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); + tempDir.toFile().setExecutable(false); + } + } + } + + // https://github.com/junit-team/junit5/issues/2079 + static class TempDirUsageInsideNestedClassesTestCase { + + @TempDir + File tempDir; + + @Test + void topLevel() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + } + + @Nested + class NestedTestClass { + + @Test + void nested() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + } + + @Nested + class EvenDeeperNestedTestClass { + + @Test + void deeplyNested() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + } + } + } + } + + static class StaticTempDirUsageInsideNestedClassTestCase { + + @TempDir + static File tempDir; + + static File initialTempDir; + + @Test + void topLevel() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + initialTempDir = tempDir; + } + + @Nested + class NestedTestClass { + + @Test + void nested() { + assertNotNull(tempDir); + assertTrue(tempDir.exists()); + assertSame(initialTempDir, tempDir); + } + } + } + + @DisplayName("class") + static class AllPossibleDeclarationLocationsTestCase { + + static final Map> tempDirs = new HashMap<>(); + + @TempDir + static Path staticField1; + + @TempDir + static Path staticField2; + + @TempDir + Path instanceField1; + + @TempDir + Path instanceField2; + + @BeforeAll + static void beforeAll(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { + getTempDirs(testInfo).putAll(Map.of( // + "staticField1", staticField1, // + "staticField2", staticField2, // + "beforeAll1", param1, // + "beforeAll2", param2 // + )); + } + + @BeforeEach + void beforeEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { + getTempDirs(testInfo).putAll(Map.of( // + "staticField1", staticField1, // + "staticField2", staticField2, // + "instanceField1", instanceField1, // + "instanceField2", instanceField2, // + "beforeEach1", param1, // + "beforeEach2", param2 // + )); + } + + @Test + @DisplayName("testA") + void testA(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { + getTempDirs(testInfo).putAll(Map.of( // + "test1", param1, // + "test2", param2 // + )); + } + + @Test + @DisplayName("testB") + void testB(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { + getTempDirs(testInfo).putAll(Map.of( // + "test1", param1, // + "test2", param2 // + )); + } + + @AfterEach + void afterEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { + getTempDirs(testInfo).putAll(Map.of( // + "afterEach1", param1, // + "afterEach2", param2 // + )); + } + + @AfterAll + static void afterAll(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { + getTempDirs(testInfo).putAll(Map.of( // + "afterAll1", param1, // + "afterAll2", param2 // + )); + } + + private static Map getTempDirs(TestInfo testInfo) { + return tempDirs.computeIfAbsent(testInfo.getDisplayName(), __ -> new LinkedHashMap<>()); + } + } + + static class UndeletableTestCase { + + static final Path UNDELETABLE_PATH = Path.of("undeletable"); + static final String TEMP_DIR = "TEMP_DIR"; + + @RegisterExtension + BeforeEachCallback injector = context -> context // + .getStore(TempDirectory.NAMESPACE) // + .put(TempDirectory.FILE_OPERATIONS_KEY, (FileOperations) path -> { + if (path.endsWith(UNDELETABLE_PATH)) { + throw new IOException("Simulated failure"); + } + else { + Files.delete(path); + } + }); + + @TempDir + Path tempDir; + + @BeforeEach + void reportTempDir(TestReporter reporter) { + reporter.publishEntry(TEMP_DIR, tempDir.toString()); + } + } + + static class UndeletableDirectoryTestCase extends UndeletableTestCase { + @Test + void test() throws Exception { + Files.createDirectory(tempDir.resolve(UNDELETABLE_PATH)); + } + } + + static class UndeletableFileTestCase extends UndeletableTestCase { + @Test + void test() throws Exception { + Files.createFile(tempDir.resolve(UNDELETABLE_PATH)); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java new file mode 100644 index 00000000..4a5b020e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests for preconditions and assertions in the {@link TempDirectory} + * extension. + * + * @since 5.9 + */ +class TempDirectoryPreconditionTests extends AbstractJupiterTestEngineTests { + + @Test + @DisplayName("Valid and invalid @TempDir parameter types") + void parameterTypes() { + EngineExecutionResults executionResults = executeTestsForClass(ParameterTypeTestCase.class); + Events tests = executionResults.testEvents(); + tests.assertStatistics(stats -> stats.started(2).failed(1).succeeded(1)); + tests.succeeded().assertEventsMatchExactly(event(test("validTempDirType"), finishedSuccessfully())); + // @formatter:off + tests.failed().assertEventsMatchExactly(event(test("invalidTempDirType"), + finishedWithFailure(instanceOf(ParameterResolutionException.class), message(""" + Failed to resolve parameter [java.lang.String text] in method \ + [void org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$ParameterTypeTestCase.invalidTempDirType(java.lang.String)]: \ + Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String\ + """)))); + // @formatter:on + } + + @Test + @DisplayName("final static @TempDir fields are not supported") + void finalStaticFieldIsNotSupported() { + EngineExecutionResults executionResults = executeTestsForClass(FinalStaticFieldTestCase.class); + Events containers = executionResults.containerEvents(); + containers.assertStatistics(stats -> stats.started(2).failed(1).succeeded(1)); + containers.succeeded().assertEventsMatchExactly(event(container("junit-jupiter"), finishedSuccessfully())); + containers.failed().assertEventsMatchExactly(event(container("FinalStaticFieldTestCase"), + finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(""" + @TempDir field [static final java.nio.file.Path \ + org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$FinalStaticFieldTestCase.path] \ + must not be declared as final.\ + """)))); + } + + @Test + @DisplayName("final instance @TempDir fields are not supported") + void finalInstanceFieldIsNotSupported() { + EngineExecutionResults executionResults = executeTestsForClass(FinalInstanceFieldTestCase.class); + Events tests = executionResults.testEvents(); + tests.assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); + tests.failed().assertEventsMatchExactly( + event(test("test()"), finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(""" + @TempDir field [final java.nio.file.Path \ + org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$FinalInstanceFieldTestCase.path] \ + must not be declared as final.\ + """)))); + } + + // ------------------------------------------------------------------- + + static class ParameterTypeTestCase { + + @Test + void validTempDirType(@TempDir File file, @TempDir Path path) { + } + + @Test + void invalidTempDirType(@TempDir String text) { + } + } + + static class FinalStaticFieldTestCase { + + static final @TempDir Path path = Paths.get("."); + + @Test + void test() { + } + } + + static class FinalInstanceFieldTestCase { + + final @TempDir Path path = Paths.get("."); + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java new file mode 100644 index 00000000..1c4409c0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java @@ -0,0 +1,216 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests that verify support for {@link TestExecutionExceptionHandler}. + * + * @since 5.0 + */ +class TestExecutionExceptionHandlerTests extends AbstractJupiterTestEngineTests { + + static List handlerCalls = new ArrayList<>(); + + @BeforeEach + void resetStatics() { + handlerCalls.clear(); + RethrowException.handleExceptionCalled = false; + ConvertException.handleExceptionCalled = false; + SwallowException.handleExceptionCalled = false; + ShouldNotBeCalled.handleExceptionCalled = false; + } + + @Test + void exceptionHandlerRethrowsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testRethrow")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + assertTrue(RethrowException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testRethrow"), started()), // + event(test("testRethrow"), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void exceptionHandlerSwallowsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSwallow")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + assertTrue(SwallowException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testSwallow"), started()), // + event(test("testSwallow"), finishedSuccessfully()), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void exceptionHandlerConvertsException() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testConvert")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + assertTrue(ConvertException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testConvert"), started()), // + event(test("testConvert"), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void severalHandlersAreCalledInOrder() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSeveral")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + assertTrue(ConvertException.handleExceptionCalled, "ConvertException should have been called"); + assertTrue(RethrowException.handleExceptionCalled, "RethrowException should have been called"); + assertTrue(SwallowException.handleExceptionCalled, "SwallowException should have been called"); + assertFalse(ShouldNotBeCalled.handleExceptionCalled, "ShouldNotBeCalled should not have been called"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(ATestCase.class), started()), // + event(test("testSeveral"), started()), // + event(test("testSeveral"), finishedSuccessfully()), // + event(container(ATestCase.class), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + assertEquals(Arrays.asList("convert", "rethrow", "swallow"), handlerCalls); + } + + // ------------------------------------------------------------------- + + static class ATestCase { + + @Test + @ExtendWith(RethrowException.class) + void testRethrow() throws IOException { + throw new IOException("checked"); + } + + @Test + @ExtendWith(SwallowException.class) + void testSwallow() throws IOException { + throw new IOException("checked"); + } + + @Test + @ExtendWith(ConvertException.class) + void testConvert() { + throw new RuntimeException("unchecked"); + } + + @Test + @ExtendWith(ShouldNotBeCalled.class) + @ExtendWith(SwallowException.class) + @ExtendWith(RethrowException.class) + @ExtendWith(ConvertException.class) + void testSeveral() { + throw new RuntimeException("unchecked"); + } + } + + static class RethrowException implements TestExecutionExceptionHandler { + + static boolean handleExceptionCalled = false; + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + assertTrue(throwable instanceof IOException); + handleExceptionCalled = true; + handlerCalls.add("rethrow"); + + throw throwable; + } + } + + static class SwallowException implements TestExecutionExceptionHandler { + + static boolean handleExceptionCalled = false; + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { + assertTrue(throwable instanceof IOException); + handleExceptionCalled = true; + handlerCalls.add("swallow"); + //swallow exception by not rethrowing it + } + } + + static class ConvertException implements TestExecutionExceptionHandler { + + static boolean handleExceptionCalled = false; + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + assertTrue(throwable instanceof RuntimeException); + handleExceptionCalled = true; + handlerCalls.add("convert"); + throw new IOException("checked"); + } + + } + + static class ShouldNotBeCalled implements TestExecutionExceptionHandler { + + static boolean handleExceptionCalled = false; + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { + handleExceptionCalled = true; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java new file mode 100644 index 00000000..ba461079 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +/** + * Integration tests for {@link TestInfoParameterResolver}. + * + * @since 5.0 + */ +@Tag("class-tag") +class TestInfoParameterResolverTests { + + private static List allDisplayNames = Arrays.asList("defaultDisplayName(TestInfo)", "custom display name", + "getTags(TestInfo)", "customDisplayNameThatIsEmpty(TestInfo)"); + + @Test + void defaultDisplayName(TestInfo testInfo) { + assertEquals("defaultDisplayName(TestInfo)", testInfo.getDisplayName()); + } + + @Test + @DisplayName("custom display name") + void providedDisplayName(TestInfo testInfo) { + assertEquals("custom display name", testInfo.getDisplayName()); + } + + // TODO Update test to expect an exception once #743 is fixed. + @Test + @DisplayName("") + void customDisplayNameThatIsEmpty(TestInfo testInfo) { + assertEquals("customDisplayNameThatIsEmpty(TestInfo)", testInfo.getDisplayName()); + } + + @Test + @Tag("method-tag") + void getTags(TestInfo testInfo) { + assertEquals(2, testInfo.getTags().size()); + assertTrue(testInfo.getTags().contains("method-tag")); + assertTrue(testInfo.getTags().contains("class-tag")); + } + + @BeforeEach + @AfterEach + void beforeAndAfter(TestInfo testInfo) { + assertThat(allDisplayNames).contains(testInfo.getDisplayName()); + } + + @BeforeAll + static void beforeAll(TestInfo testInfo) { + Set tags = testInfo.getTags(); + assertEquals(1, tags.size()); + assertTrue(tags.contains("class-tag")); + } + + @BeforeAll + @AfterAll + static void beforeAndAfterAll(TestInfo testInfo) { + assertEquals(TestInfoParameterResolverTests.class.getSimpleName(), testInfo.getDisplayName()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java new file mode 100644 index 00000000..b773e9ee --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java @@ -0,0 +1,727 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.nestedContainer; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.extension.TestInstanceFactory; +import org.junit.jupiter.api.extension.TestInstanceFactoryContext; +import org.junit.jupiter.api.extension.TestInstantiationException; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests that verify support for {@link TestInstanceFactory}. + * + * @since 5.3 + */ +class TestInstanceFactoryTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @BeforeEach + void resetCallSequence() { + callSequence.clear(); + } + + @Test + void multipleFactoriesRegisteredOnSingleTestClass() { + Class testClass = MultipleFactoriesRegisteredOnSingleTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure(instanceOf(ExtensionConfigurationException.class), + message("The following TestInstanceFactory extensions were registered for test class [" + + testClass.getName() + "], but only one is permitted: " + + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void multipleFactoriesRegisteredWithinTestClassHierarchy() { + Class testClass = MultipleFactoriesRegisteredWithinClassHierarchyTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure(instanceOf(ExtensionConfigurationException.class), + message("The following TestInstanceFactory extensions were registered for test class [" + + testClass.getName() + "], but only one is permitted: " + + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void multipleFactoriesRegisteredWithinNestedClassStructure() { + Class outerClass = MultipleFactoriesRegisteredWithinNestedClassStructureTestCase.class; + Class nestedClass = MultipleFactoriesRegisteredWithinNestedClassStructureTestCase.InnerTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(outerClass); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(outerClass), started()), // + event(test("outerTest()"), started()), // + event(test("outerTest()"), finishedSuccessfully()), // + event(nestedContainer(nestedClass), started()), // + event(nestedContainer(nestedClass), + finishedWithFailure(instanceOf(ExtensionConfigurationException.class), + message("The following TestInstanceFactory extensions were registered for test class [" + + nestedClass.getName() + "], but only one is permitted: " + + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // + event(container(outerClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void nullTestInstanceFactoryWithPerMethodLifecycle() { + Class testClass = NullTestInstanceFactoryTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("testShouldNotBeCalled"), started()), // + event(test("testShouldNotBeCalled"), + finishedWithFailure(instanceOf(TestInstantiationException.class), + message(m -> m.equals("TestInstanceFactory [" + NullTestInstanceFactory.class.getName() + + "] failed to return an instance of [" + testClass.getName() + + "] and instead returned an instance of [null].")))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void nullTestInstanceFactoryWithPerClassLifecycle() { + Class testClass = PerClassLifecycleNullTestInstanceFactoryTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure(instanceOf(TestInstantiationException.class), + message(m -> m.equals("TestInstanceFactory [" + NullTestInstanceFactory.class.getName() + + "] failed to return an instance of [" + testClass.getName() + + "] and instead returned an instance of [null].")))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void bogusTestInstanceFactoryWithPerMethodLifecycle() { + Class testClass = BogusTestInstanceFactoryTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("testShouldNotBeCalled"), started()), // + event(test("testShouldNotBeCalled"), + finishedWithFailure(instanceOf(TestInstantiationException.class), + message(m -> m.equals("TestInstanceFactory [" + BogusTestInstanceFactory.class.getName() + + "] failed to return an instance of [" + testClass.getName() + + "] and instead returned an instance of [java.lang.String].")))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void bogusTestInstanceFactoryWithPerClassLifecycle() { + Class testClass = PerClassLifecycleBogusTestInstanceFactoryTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure(instanceOf(TestInstantiationException.class), + message(m -> m.equals("TestInstanceFactory [" + BogusTestInstanceFactory.class.getName() + + "] failed to return an instance of [" + testClass.getName() + + "] and instead returned an instance of [java.lang.String].")))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void explosiveTestInstanceFactoryWithPerMethodLifecycle() { + Class testClass = ExplosiveTestInstanceFactoryTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("testShouldNotBeCalled"), started()), // + event(test("testShouldNotBeCalled"), + finishedWithFailure(instanceOf(TestInstantiationException.class), + message("TestInstanceFactory [" + ExplosiveTestInstanceFactory.class.getName() + + "] failed to instantiate test class [" + testClass.getName() + "]: boom!"))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void explosiveTestInstanceFactoryWithPerClassLifecycle() { + Class testClass = PerClassLifecycleExplosiveTestInstanceFactoryTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), // + finishedWithFailure(instanceOf(TestInstantiationException.class), + message("TestInstanceFactory [" + ExplosiveTestInstanceFactory.class.getName() + + "] failed to instantiate test class [" + testClass.getName() + "]: boom!"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void proxyTestInstanceFactoryFailsDueToUseOfDifferentClassLoader() { + Class testClass = ProxiedTestCase.class; + EngineExecutionResults executionResults = executeTestsForClass(testClass); + + assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); + + executionResults.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), // + // NOTE: the test class names are the same even though the objects are + // instantiated using different ClassLoaders. Thus, we check for the + // appended "@" but ignore the actual hash code for the test class + // loaded by the different ClassLoader. + finishedWithFailure(instanceOf(TestInstantiationException.class), + message(m -> m.startsWith("TestInstanceFactory [" + ProxyTestInstanceFactory.class.getName() + "]") + && m.contains("failed to return an instance of [" + testClass.getName() + "@" + + Integer.toHexString(System.identityHashCode(testClass))) + && m.contains("and instead returned an instance of [" + testClass.getName() + "@")// + ))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void instanceFactoryOnTopLevelTestClass() { + EngineExecutionResults executionResults = executeTestsForClass(ParentTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "FooInstanceFactory instantiated: ParentTestCase", + "parentTest" + ); + // @formatter:on + } + + @Test + void instanceFactorySupportedWhenTestClassDeclaresMultipleConstructors() { + executeTestsForClass(MultipleConstructorsTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "MultipleConstructorsTestInstanceFactory instantiated: MultipleConstructorsTestCase", + "test: 42" + ); + // @formatter:on + } + + @Test + void inheritedFactoryInTestClassHierarchy() { + EngineExecutionResults executionResults = executeTestsForClass(InheritedFactoryTestCase.class); + + assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "FooInstanceFactory instantiated: InheritedFactoryTestCase", + "parentTest", + "FooInstanceFactory instantiated: InheritedFactoryTestCase", + "childTest" + ); + // @formatter:on + } + + @Test + void instanceFactoriesInNestedClassStructureAreInherited() { + EngineExecutionResults executionResults = executeTestsForClass(OuterTestCase.class); + + assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + + // OuterTestCase + "FooInstanceFactory instantiated: OuterTestCase", + "outerTest", + + // InnerTestCase + "FooInstanceFactory instantiated: OuterTestCase", + "FooInstanceFactory instantiated: InnerTestCase", + "innerTest1", + + // InnerInnerTestCase + "FooInstanceFactory instantiated: OuterTestCase", + "FooInstanceFactory instantiated: InnerTestCase", + "FooInstanceFactory instantiated: InnerInnerTestCase", + "innerTest2" + ); + // @formatter:on + } + + @Test + void instanceFactoryRegisteredViaTestInterface() { + EngineExecutionResults executionResults = executeTestsForClass(FactoryFromInterfaceTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "FooInstanceFactory instantiated: FactoryFromInterfaceTestCase", + "test" + ); + // @formatter:on + } + + @Test + void instanceFactoryRegisteredAsLambdaExpression() { + EngineExecutionResults executionResults = executeTestsForClass(LambdaFactoryTestCase.class); + + assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeEach: lambda", + "test: lambda" + ); + // @formatter:on + } + + @Test + void instanceFactoryWithPerClassLifecycle() { + EngineExecutionResults executionResults = executeTestsForClass(PerClassLifecycleTestCase.class); + + assertEquals(1, PerClassLifecycleTestCase.counter.get()); + + assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "FooInstanceFactory instantiated: PerClassLifecycleTestCase", + "@BeforeAll", + "@BeforeEach", + "test1", + "@BeforeEach", + "test2", + "@AfterAll" + ); + // @formatter:on + } + + // ------------------------------------------------------------------------- + + @ExtendWith({ FooInstanceFactory.class, BarInstanceFactory.class }) + static class MultipleFactoriesRegisteredOnSingleTestCase { + + @Test + void testShouldNotBeCalled() { + callSequence.add("testShouldNotBeCalled"); + } + } + + @ExtendWith(NullTestInstanceFactory.class) + static class NullTestInstanceFactoryTestCase { + + @Test + void testShouldNotBeCalled() { + callSequence.add("testShouldNotBeCalled"); + } + } + + @TestInstance(PER_CLASS) + static class PerClassLifecycleNullTestInstanceFactoryTestCase extends NullTestInstanceFactoryTestCase { + } + + @ExtendWith(BogusTestInstanceFactory.class) + static class BogusTestInstanceFactoryTestCase { + + @Test + void testShouldNotBeCalled() { + callSequence.add("testShouldNotBeCalled"); + } + } + + @TestInstance(PER_CLASS) + static class PerClassLifecycleBogusTestInstanceFactoryTestCase extends BogusTestInstanceFactoryTestCase { + } + + @ExtendWith(ExplosiveTestInstanceFactory.class) + static class ExplosiveTestInstanceFactoryTestCase { + + @Test + void testShouldNotBeCalled() { + callSequence.add("testShouldNotBeCalled"); + } + } + + @TestInstance(PER_CLASS) + static class PerClassLifecycleExplosiveTestInstanceFactoryTestCase extends ExplosiveTestInstanceFactoryTestCase { + } + + private static class MultipleConstructorsTestInstanceFactory implements TestInstanceFactory { + + @Override + public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { + instantiated(getClass(), factoryContext.getTestClass()); + return new MultipleConstructorsTestCase(42); + } + } + + @ExtendWith(MultipleConstructorsTestInstanceFactory.class) + static class MultipleConstructorsTestCase { + + private final int number; + + public MultipleConstructorsTestCase(String text) { + this.number = -1; + } + + public MultipleConstructorsTestCase(int number) { + this.number = number; + } + + @Test + void test() { + callSequence.add("test: " + this.number); + } + } + + @ExtendWith(FooInstanceFactory.class) + static class ParentTestCase { + + @Test + void parentTest() { + callSequence.add("parentTest"); + } + } + + static class InheritedFactoryTestCase extends ParentTestCase { + + @Test + void childTest() { + callSequence.add("childTest"); + } + } + + @ExtendWith(BarInstanceFactory.class) + static class MultipleFactoriesRegisteredWithinClassHierarchyTestCase extends ParentTestCase { + + @Test + void childTest() { + callSequence.add("childTest"); + } + } + + @ExtendWith(FooInstanceFactory.class) + static class OuterTestCase { + + @Test + void outerTest() { + callSequence.add("outerTest"); + } + + @Nested + class InnerTestCase { + + @Test + void innerTest1() { + callSequence.add("innerTest1"); + } + + @Nested + class InnerInnerTestCase { + + @Test + void innerTest2() { + callSequence.add("innerTest2"); + } + } + } + } + + @ExtendWith(FooInstanceFactory.class) + static class MultipleFactoriesRegisteredWithinNestedClassStructureTestCase { + + @Test + void outerTest() { + } + + @Nested + @ExtendWith(BarInstanceFactory.class) + class InnerTestCase { + + @Test + void innerTest() { + } + } + } + + @ExtendWith(FooInstanceFactory.class) + interface TestInterface { + } + + static class FactoryFromInterfaceTestCase implements TestInterface { + + @Test + void test() { + callSequence.add("test"); + } + } + + static class LambdaFactoryTestCase { + + private final String text; + + @RegisterExtension + static final TestInstanceFactory factory = (__, ___) -> new LambdaFactoryTestCase("lambda"); + + LambdaFactoryTestCase(String text) { + this.text = text; + } + + @BeforeEach + void beforeEach() { + callSequence.add("beforeEach: " + this.text); + } + + @Test + void test() { + callSequence.add("test: " + this.text); + } + } + + @ExtendWith(FooInstanceFactory.class) + @TestInstance(PER_CLASS) + static class PerClassLifecycleTestCase { + + static final AtomicInteger counter = new AtomicInteger(); + + PerClassLifecycleTestCase() { + counter.incrementAndGet(); + } + + @BeforeAll + void beforeAll() { + callSequence.add("@BeforeAll"); + } + + @BeforeEach + void beforeEach() { + callSequence.add("@BeforeEach"); + } + + @Test + void test1() { + callSequence.add("test1"); + } + + @Test + void test2() { + callSequence.add("test2"); + } + + @AfterAll + void afterAll() { + callSequence.add("@AfterAll"); + } + } + + @ExtendWith(ProxyTestInstanceFactory.class) + @TestInstance(PER_CLASS) + static class ProxiedTestCase { + + @Test + void test1() { + callSequence.add("test1"); + } + + @Test + void test2() { + callSequence.add("test2"); + } + } + + // ------------------------------------------------------------------------- + + private static abstract class AbstractTestInstanceFactory implements TestInstanceFactory { + + @Override + public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { + Class testClass = factoryContext.getTestClass(); + instantiated(getClass(), testClass); + + if (factoryContext.getOuterInstance().isPresent()) { + return ReflectionUtils.newInstance(testClass, factoryContext.getOuterInstance().get()); + } + // else + return ReflectionUtils.newInstance(testClass); + } + } + + private static class FooInstanceFactory extends AbstractTestInstanceFactory { + } + + private static class BarInstanceFactory extends AbstractTestInstanceFactory { + } + + /** + * {@link TestInstanceFactory} that returns null. + */ + private static class NullTestInstanceFactory implements TestInstanceFactory { + + @Override + public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { + return null; + } + } + + /** + * {@link TestInstanceFactory} that returns an object of a type that does + * not match the supplied test class. + */ + private static class BogusTestInstanceFactory implements TestInstanceFactory { + + @Override + public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { + return "bogus"; + } + } + + /** + * {@link TestInstanceFactory} that always throws an exception. + */ + private static class ExplosiveTestInstanceFactory implements TestInstanceFactory { + + @Override + public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { + throw new RuntimeException("boom!"); + } + } + + /** + * This does not actually create a proxy. Rather, it simulates what + * a proxy-based implementation might do, by loading the class from a + * different {@link ClassLoader}. + */ + private static class ProxyTestInstanceFactory implements TestInstanceFactory { + + @Override + public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { + Class testClass = factoryContext.getTestClass(); + String className = testClass.getName(); + instantiated(getClass(), testClass); + + try (ProxyClassLoader proxyClassLoader = new ProxyClassLoader()) { + // Load test class from different class loader + Class clazz = proxyClassLoader.loadClass(className); + return ReflectionUtils.newInstance(clazz); + } + catch (Exception ex) { + throw new RuntimeException("Failed to load class [" + className + "]", ex); + } + } + } + + private static class ProxyClassLoader extends URLClassLoader { + + ProxyClassLoader() { + super(new URL[] { ProxyClassLoader.class.getProtectionDomain().getCodeSource().getLocation() }, + getSystemClassLoader()); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return (name.startsWith(TestInstanceFactoryTests.class.getName()) ? findClass(name) + : super.loadClass(name)); + } + } + + private static boolean instantiated(Class factoryClass, Class testClass) { + return callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java new file mode 100644 index 00000000..2b684fa9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java @@ -0,0 +1,260 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.JupiterTestEngine; + +/** + * Integration tests that verify support for {@link TestInstancePostProcessor} + * and {@link TestInstancePreDestroyCallback} in the {@link JupiterTestEngine}. + * + * @since 5.6 + */ +class TestInstancePostProcessorAndPreDestroyCallbackTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @Test + void postProcessorAndPreDestroyCallbacks() { + // @formatter:off + assertPostProcessorAndPreDestroyCallbacks(TopLevelTestCase.class, + "fooPostProcessTestInstance", + "barPostProcessTestInstance", + "test-1", + "barPreDestroyTestInstance", + "fooPreDestroyTestInstance" + ); + // @formatter:on + } + + @Test + void postProcessorAndPreDestroyCallbacksInSubclass() { + // @formatter:off + assertPostProcessorAndPreDestroyCallbacks(SecondLevelTestCase.class, + "fooPostProcessTestInstance", + "barPostProcessTestInstance", + "bazPostProcessTestInstance", + "test-2", + "bazPreDestroyTestInstance", + "barPreDestroyTestInstance", + "fooPreDestroyTestInstance" + ); + // @formatter:on + } + + @Test + void postProcessorAndPreDestroyCallbacksInSubSubclass() { + // @formatter:off + assertPostProcessorAndPreDestroyCallbacks(ThirdLevelTestCase.class, + "fooPostProcessTestInstance", + "barPostProcessTestInstance", + "bazPostProcessTestInstance", + "quuxPostProcessTestInstance", + "test-3", + "quuxPreDestroyTestInstance", + "bazPreDestroyTestInstance", + "barPreDestroyTestInstance", + "fooPreDestroyTestInstance" + ); + // @formatter:on + } + + @Test + void preDestroyTestInstanceMethodThrowsAnException() { + // @formatter:off + assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestInstancePreDestroyCallbackTestCase.class, 0, + "fooPostProcessTestInstance", + "test", + "exceptionThrowingTestInstancePreDestroyCallback" + ); + // @formatter:on + } + + @Test + void postProcessTestInstanceMethodThrowsAnException() { + assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestInstancePostProcessorTestCase.class, 0, + "exceptionThrowingTestInstancePostProcessor", "exceptionPreDestroyTestInstance"); + } + + @Test + void testClassConstructorThrowsAnException() { + assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestClassConstructorTestCase.class, 0, + "exceptionThrowingConstructor"); + } + + private void assertPostProcessorAndPreDestroyCallbacks(Class testClass, String... expectedCalls) { + assertPostProcessorAndPreDestroyCallbacks(testClass, 1, expectedCalls); + } + + private void assertPostProcessorAndPreDestroyCallbacks(Class testClass, int testsSuccessful, + String... expectedCalls) { + + callSequence.clear(); + + executeTestsForClass(testClass).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(testsSuccessful)); + + assertEquals(asList(expectedCalls), callSequence, () -> "wrong call sequence for " + testClass.getName()); + } + + // ------------------------------------------------------------------------- + + // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @ExtendWith({ FooTestInstanceCallbacks.class, BarTestInstanceCallbacks.class }) + static class TopLevelTestCase { + + @Test + void test() { + callSequence.add("test-1"); + } + } + + // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @ExtendWith(BazTestInstanceCallbacks.class) + static class SecondLevelTestCase extends TopLevelTestCase { + + @Test + @Override + void test() { + callSequence.add("test-2"); + } + } + + @ExtendWith(QuuxTestInstanceCallbacks.class) + static class ThirdLevelTestCase extends SecondLevelTestCase { + + @Test + @Override + void test() { + callSequence.add("test-3"); + } + } + + @ExtendWith(ExceptionThrowingTestInstancePreDestroyCallback.class) + static class ExceptionInTestInstancePreDestroyCallbackTestCase { + + @Test + void test() { + callSequence.add("test"); + } + } + + @ExtendWith(ExceptionThrowingTestInstancePostProcessor.class) + static class ExceptionInTestInstancePostProcessorTestCase { + + @Test + void test() { + callSequence.add("test"); + } + } + + @ExtendWith(FooTestInstanceCallbacks.class) + static class ExceptionInTestClassConstructorTestCase { + + ExceptionInTestClassConstructorTestCase() { + callSequence.add("exceptionThrowingConstructor"); + throw new RuntimeException("in constructor"); + } + + @Test + void test() { + callSequence.add("test"); + } + } + + // ------------------------------------------------------------------------- + + static class FooTestInstanceCallbacks extends AbstractTestInstanceCallbacks { + + protected FooTestInstanceCallbacks() { + super("foo"); + } + } + + static class BarTestInstanceCallbacks extends AbstractTestInstanceCallbacks { + + protected BarTestInstanceCallbacks() { + super("bar"); + } + } + + static class BazTestInstanceCallbacks extends AbstractTestInstanceCallbacks { + + protected BazTestInstanceCallbacks() { + super("baz"); + } + } + + static class QuuxTestInstanceCallbacks extends AbstractTestInstanceCallbacks { + + protected QuuxTestInstanceCallbacks() { + super("quux"); + } + } + + static class ExceptionThrowingTestInstancePreDestroyCallback extends AbstractTestInstanceCallbacks { + + protected ExceptionThrowingTestInstancePreDestroyCallback() { + super("foo"); + } + + @Override + public void preDestroyTestInstance(ExtensionContext context) { + callSequence.add("exceptionThrowingTestInstancePreDestroyCallback"); + throw new EnigmaException("preDestroyTestInstance"); + } + } + + static class ExceptionThrowingTestInstancePostProcessor extends AbstractTestInstanceCallbacks { + + protected ExceptionThrowingTestInstancePostProcessor() { + super("exception"); + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + callSequence.add("exceptionThrowingTestInstancePostProcessor"); + throw new EnigmaException("postProcessTestInstance"); + } + } + + private static abstract class AbstractTestInstanceCallbacks + implements TestInstancePostProcessor, TestInstancePreDestroyCallback { + + private final String name; + + AbstractTestInstanceCallbacks(String name) { + this.name = name; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + callSequence.add(name + "PostProcessTestInstance"); + } + + @Override + public void preDestroyTestInstance(ExtensionContext context) { + callSequence.add(name + "PreDestroyTestInstance"); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java new file mode 100644 index 00000000..225e89de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java @@ -0,0 +1,172 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; + +/** + * Integration tests that verify support for {@link TestInstancePostProcessor}. + * + * @since 5.0 + */ +class TestInstancePostProcessorTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @BeforeEach + void resetCallSequence() { + callSequence.clear(); + } + + @Test + void instancePostProcessorsInNestedClasses() { + executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + + // OuterTestCase + "fooPostProcessTestInstance:OuterTestCase", + "beforeOuterMethod", + "testOuter", + + // InnerTestCase + + "fooPostProcessTestInstance:OuterTestCase", + "fooPostProcessTestInstance:InnerTestCase", + "barPostProcessTestInstance:InnerTestCase", + "beforeOuterMethod", + "beforeInnerMethod", + "testInner" + ); + // @formatter:on + } + + @Test + void testSpecificTestInstancePostProcessorIsCalled() { + executeTestsForClass(TestCaseWithTestSpecificTestInstancePostProcessor.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + + assertThat(callSequence).containsExactly( + "fooPostProcessTestInstance:TestCaseWithTestSpecificTestInstancePostProcessor", "beforeEachMethod", "test"); + } + + // ------------------------------------------------------------------- + + @ExtendWith(FooInstancePostProcessor.class) + static class OuterTestCase implements Named { + + private String outerName; + + @Override + public void setName(String name) { + this.outerName = name; + } + + @BeforeEach + void beforeOuterMethod() { + callSequence.add("beforeOuterMethod"); + } + + @Test + void testOuter() { + assertEquals("foo:" + OuterTestCase.class.getSimpleName(), outerName); + callSequence.add("testOuter"); + } + + @Nested + @ExtendWith(BarInstancePostProcessor.class) + class InnerTestCase implements Named { + + private String innerName; + + @Override + public void setName(String name) { + this.innerName = name; + } + + @BeforeEach + void beforeInnerMethod() { + callSequence.add("beforeInnerMethod"); + } + + @Test + void testInner() { + assertEquals("foo:" + OuterTestCase.class.getSimpleName(), outerName); + assertEquals("bar:" + InnerTestCase.class.getSimpleName(), innerName); + callSequence.add("testInner"); + } + } + + } + + static class TestCaseWithTestSpecificTestInstancePostProcessor implements Named { + + private String name; + + @Override + public void setName(String name) { + this.name = name; + } + + @BeforeEach + void beforeEachMethod() { + callSequence.add("beforeEachMethod"); + } + + @ExtendWith(FooInstancePostProcessor.class) + @Test + void test() { + callSequence.add("test"); + assertEquals("foo:" + getClass().getSimpleName(), name); + } + } + + static class FooInstancePostProcessor implements TestInstancePostProcessor { + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + if (testInstance instanceof Named) { + ((Named) testInstance).setName("foo:" + context.getRequiredTestClass().getSimpleName()); + } + callSequence.add("fooPostProcessTestInstance:" + testInstance.getClass().getSimpleName()); + } + } + + static class BarInstancePostProcessor implements TestInstancePostProcessor { + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + if (testInstance instanceof Named) { + ((Named) testInstance).setName("bar:" + context.getRequiredTestClass().getSimpleName()); + } + callSequence.add("barPostProcessTestInstance:" + testInstance.getClass().getSimpleName()); + } + } + + private interface Named { + + void setName(String name); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java new file mode 100644 index 00000000..100cb30f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java @@ -0,0 +1,432 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.extension.TestInstanceFactory; +import org.junit.jupiter.api.extension.TestInstanceFactoryContext; +import org.junit.jupiter.api.extension.TestInstancePreConstructCallback; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; + +/** + * Integration tests that verify support for {@link TestInstancePreConstructCallback}. + * + * @since 5.9 + */ +class TestInstancePreConstructCallbackTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @BeforeEach + void resetCallSequence() { + callSequence.clear(); + } + + @Test + void instancePreConstruct() { + executeTestsForClass(InstancePreConstructTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeAll", + + "PreConstructCallback: name=foo, testClass=InstancePreConstructTestCase, outerInstance: null", + "constructor", + "beforeEach", + "test1", + "afterEach", + + "PreConstructCallback: name=foo, testClass=InstancePreConstructTestCase, outerInstance: null", + "constructor", + "beforeEach", + "test2", + "afterEach", + + "afterAll" + ); + // @formatter:on + } + + @Test + void factoryPreConstruct() { + executeTestsForClass(FactoryPreConstructTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeAll", + + "PreConstructCallback: name=foo, testClass=FactoryPreConstructTestCase, outerInstance: null", + "testInstanceFactory", + "constructor", + "beforeEach", + "test1", + "afterEach", + + "PreConstructCallback: name=foo, testClass=FactoryPreConstructTestCase, outerInstance: null", + "testInstanceFactory", + "constructor", + "beforeEach", + "test2", + "afterEach", + + "afterAll" + ); + // @formatter:on + } + + @Test + void preConstructInNested() { + executeTestsForClass(PreConstructInNestedTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(3).succeeded(3)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeAll", + + "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", + "constructor", + "beforeEach", + "outerTest1", + "afterEach", + + "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", + "constructor", + "beforeEach", + "outerTest2", + "afterEach", + + "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", + "constructor", + + "PreConstructCallback: name=foo, testClass=InnerTestCase, outerInstance: #3", + "PreConstructCallback: name=bar, testClass=InnerTestCase, outerInstance: #3", + "PreConstructCallback: name=baz, testClass=InnerTestCase, outerInstance: #3", + "constructorInner", + "beforeEach", + "beforeEachInner", + "innerTest1", + "afterEachInner", + "afterEach", + + "afterAll" + ); + // @formatter:on + } + + @Test + void preConstructOnMethod() { + executeTestsForClass(PreConstructOnMethod.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "PreConstructCallback: name=foo, testClass=PreConstructOnMethod, outerInstance: null", + "constructor", + "beforeEach", + "test1", + "afterEach", + + "constructor", + "beforeEach", + "test2", + "afterEach" + ); + // @formatter:on + } + + @Test + void preConstructWithClassLifecycle() { + executeTestsForClass(PreConstructWithClassLifecycle.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "PreConstructCallback: name=foo, testClass=PreConstructWithClassLifecycle, outerInstance: null", + "PreConstructCallback: name=bar, testClass=PreConstructWithClassLifecycle, outerInstance: null", + "constructor", + "beforeEach", + "test1", + "beforeEach", + "test2" + ); + // @formatter:on + } + + private abstract static class CallSequenceRecordingTestCase { + protected static void record(String event) { + callSequence.add(event); + } + } + + @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) + static class InstancePreConstructTestCase extends CallSequenceRecordingTestCase { + + InstancePreConstructTestCase() { + record("constructor"); + } + + @BeforeAll + static void beforeAll() { + record("beforeAll"); + } + + @BeforeEach + void beforeEach() { + record("beforeEach"); + } + + @Test + void test1() { + record("test1"); + } + + @Test + void test2() { + record("test2"); + } + + @AfterEach + void afterEach() { + record("afterEach"); + } + + @AfterAll + static void afterAll() { + record("afterAll"); + } + } + + @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) + static class FactoryPreConstructTestCase extends CallSequenceRecordingTestCase { + + @RegisterExtension + static final TestInstanceFactory factory = (factoryContext, extensionContext) -> { + record("testInstanceFactory"); + return new FactoryPreConstructTestCase(); + }; + + FactoryPreConstructTestCase() { + record("constructor"); + } + + @BeforeAll + static void beforeAll() { + record("beforeAll"); + } + + @BeforeEach + void beforeEach() { + record("beforeEach"); + } + + @Test + void test1() { + record("test1"); + } + + @Test + void test2() { + record("test2"); + } + + @AfterEach + void afterEach() { + record("afterEach"); + } + + @AfterAll + static void afterAll() { + record("afterAll"); + } + } + + @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) + static class PreConstructInNestedTestCase extends CallSequenceRecordingTestCase { + + static AtomicInteger instanceCounter = new AtomicInteger(); + + private final String instanceId; + + PreConstructInNestedTestCase() { + record("constructor"); + instanceId = "#" + instanceCounter.incrementAndGet(); + } + + @BeforeAll + static void beforeAll() { + instanceCounter.set(0); + record("beforeAll"); + } + + @BeforeEach + void beforeEach() { + record("beforeEach"); + } + + @Test + void outerTest1() { + record("outerTest1"); + } + + @Test + void outerTest2() { + record("outerTest2"); + } + + @AfterEach + void afterEach() { + record("afterEach"); + } + + @AfterAll + static void afterAll() { + record("afterAll"); + } + + @Override + public String toString() { + return instanceId; + } + + @ExtendWith(InstancePreConstructCallbackRecordingBar.class) + abstract class InnerParent extends CallSequenceRecordingTestCase { + } + + @Nested + @ExtendWith(InstancePreConstructCallbackRecordingBaz.class) + class InnerTestCase extends InnerParent { + + InnerTestCase() { + record("constructorInner"); + } + + @BeforeEach + void beforeEachInner() { + record("beforeEachInner"); + } + + @Test + void innerTest1() { + record("innerTest1"); + } + + @AfterEach + void afterEachInner() { + record("afterEachInner"); + } + } + } + + static class PreConstructOnMethod extends CallSequenceRecordingTestCase { + PreConstructOnMethod() { + record("constructor"); + } + + @BeforeEach + void beforeEach() { + record("beforeEach"); + } + + @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) + @Test + void test1() { + record("test1"); + } + + @Test + void test2() { + record("test2"); + } + + @AfterEach + void afterEach() { + record("afterEach"); + } + } + + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) + @ExtendWith(InstancePreConstructCallbackRecordingBar.class) + static class PreConstructWithClassLifecycle extends CallSequenceRecordingTestCase { + PreConstructWithClassLifecycle() { + record("constructor"); + } + + @BeforeEach + void beforeEach() { + record("beforeEach"); + } + + @Test + void test1() { + callSequence.add("test1"); + } + + @Test + void test2() { + callSequence.add("test2"); + } + } + + static abstract class AbstractTestInstancePreConstructCallback implements TestInstancePreConstructCallback { + private final String name; + + AbstractTestInstancePreConstructCallback(String name) { + this.name = name; + } + + @Override + public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) { + assertThat(context.getTestInstance()).isNotPresent(); + assertThat(context.getTestClass()).isPresent(); + assertThat(factoryContext.getTestClass()).isSameAs(context.getTestClass().get()); + callSequence.add( + "PreConstructCallback: name=" + name + ", testClass=" + factoryContext.getTestClass().getSimpleName() + + ", outerInstance: " + factoryContext.getOuterInstance().orElse(null)); + } + } + + static class InstancePreConstructCallbackRecordingFoo extends AbstractTestInstancePreConstructCallback { + InstancePreConstructCallbackRecordingFoo() { + super("foo"); + } + } + + static class InstancePreConstructCallbackRecordingBar extends AbstractTestInstancePreConstructCallback { + InstancePreConstructCallbackRecordingBar() { + super("bar"); + } + } + + static class InstancePreConstructCallbackRecordingBaz extends AbstractTestInstancePreConstructCallback { + InstancePreConstructCallbackRecordingBaz() { + super("baz"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java new file mode 100644 index 00000000..a20d3460 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java @@ -0,0 +1,215 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; + +/** + * Integration tests that verify support for {@link TestInstancePreDestroyCallback}. + * + * @since 5.6 + */ +class TestInstancePreDestroyCallbackTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @BeforeEach + void resetCallSequence() { + callSequence.clear(); + } + + @Test + void instancePreDestroyCallbacksInNestedClasses() { + executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + // OuterTestCase + "beforeOuterMethod", + "testOuter", + "fooPreDestroyCallbackTestInstance:OuterTestCase", + + // InnerTestCase + "beforeOuterMethod", + "beforeInnerMethod", + "testInner", + "bazPreDestroyCallbackTestInstance:InnerTestCase", + "barPreDestroyCallbackTestInstance:InnerTestCase", + "fooPreDestroyCallbackTestInstance:InnerTestCase" + ); + // @formatter:on + } + + @Test + void testSpecificTestInstancePreDestroyCallbackIsCalled() { + executeTestsForClass(TestCaseWithTestSpecificTestInstancePreDestroyCallback.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeEachMethod", + "test", + "fooPreDestroyCallbackTestInstance:TestCaseWithTestSpecificTestInstancePreDestroyCallback" + ); + // @formatter:on + } + + @Test + void classLifecyclePreDestroyCallbacks() { + executeTestsForClass(PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeEachMethod", + "test1", + "beforeEachMethod", + "test2", + "barPreDestroyCallbackTestInstance:PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods", + "fooPreDestroyCallbackTestInstance:PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods" + ); + // @formatter:on + } + + // ------------------------------------------------------------------- + + private abstract static class Destroyable { + + boolean destroyed; + + void setDestroyed() { + this.destroyed = true; + } + } + + @ExtendWith(FooInstancePreDestroyCallback.class) + static class OuterTestCase extends Destroyable { + + @BeforeEach + void beforeOuterMethod() { + callSequence.add("beforeOuterMethod"); + } + + @Test + void testOuter() { + assertFalse(destroyed); + callSequence.add("testOuter"); + } + + @Nested + @ExtendWith(BarInstancePreDestroyCallback.class) + @ExtendWith(BazInstancePreDestroyCallback.class) + class InnerTestCase extends Destroyable { + + @BeforeEach + void beforeInnerMethod() { + assertFalse(destroyed); + callSequence.add("beforeInnerMethod"); + } + + @Test + void testInner() { + callSequence.add("testInner"); + } + } + } + + static class TestCaseWithTestSpecificTestInstancePreDestroyCallback extends Destroyable { + + @BeforeEach + void beforeEachMethod() { + assertFalse(destroyed); + callSequence.add("beforeEachMethod"); + } + + @ExtendWith(FooInstancePreDestroyCallback.class) + @Test + void test() { + callSequence.add("test"); + } + } + + @TestInstance(PER_CLASS) + @ExtendWith(FooInstancePreDestroyCallback.class) + @ExtendWith(BarInstancePreDestroyCallback.class) + static class PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods extends Destroyable { + + @BeforeEach + void beforeEachMethod() { + callSequence.add("beforeEachMethod"); + } + + @Test + void test1() { + callSequence.add("test1"); + } + + @Test + void test2() { + callSequence.add("test2"); + } + } + + static abstract class AbstractTestInstancePreDestroyCallback implements TestInstancePreDestroyCallback { + + private final String name; + + AbstractTestInstancePreDestroyCallback(String name) { + this.name = name; + } + + @Override + public void preDestroyTestInstance(ExtensionContext context) { + assertThat(context.getTestInstance()).isPresent(); + Object testInstance = context.getTestInstance().get(); + if (testInstance instanceof Destroyable) { + ((Destroyable) testInstance).setDestroyed(); + } + callSequence.add(name + "PreDestroyCallbackTestInstance:" + testInstance.getClass().getSimpleName()); + } + } + + static class FooInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { + + FooInstancePreDestroyCallback() { + super("foo"); + } + } + + static class BarInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { + + BarInstancePreDestroyCallback() { + super("bar"); + } + } + + static class BazInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { + + BazInstancePreDestroyCallback() { + super("baz"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java new file mode 100644 index 00000000..4ffff626 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.reportEntry; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; + +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; + +public class TestInstancePreDestroyCallbackUtilityMethodTests extends AbstractJupiterTestEngineTests { + + @TestFactory + Stream destroysWhatWasPostProcessed() { + var testClasses = Stream.of(PerMethodLifecycleOnAllLevels.class, PerMethodWithinPerClassLifecycle.class, + PerClassWithinPerMethodLifecycle.class, PerClassLifecycleOnAllLevels.class); + return testClasses.map(testClass -> dynamicTest( // + testClass.getSimpleName(), // + () -> executeTestsForClass(testClass).allEvents().debug() // + .assertStatistics(stats -> stats.reportingEntryPublished(4)) // + .assertEventsMatchLooselyInOrder( // + reportEntry(Map.of("post-process", testClass.getSimpleName())), + reportEntry(Map.of("post-process", "Inner")), // + event(test(), started()), // + reportEntry(Map.of("pre-destroy", "Inner")), // + reportEntry(Map.of("pre-destroy", testClass.getSimpleName())) // + ))); + } + + @ExtendWith(TestInstanceLifecycleExtension.class) + static class PerMethodLifecycleOnAllLevels { + @Nested + class Inner { + @Test + void test() { + } + } + } + + @ExtendWith(TestInstanceLifecycleExtension.class) + @TestInstance(PER_CLASS) + static class PerMethodWithinPerClassLifecycle { + @Nested + class Inner { + @Test + void test() { + } + } + } + + @ExtendWith(TestInstanceLifecycleExtension.class) + static class PerClassWithinPerMethodLifecycle { + @Nested + @TestInstance(PER_CLASS) + class Inner { + @Test + void test() { + } + } + } + + @ExtendWith(TestInstanceLifecycleExtension.class) + @TestInstance(PER_CLASS) + static class PerClassLifecycleOnAllLevels { + @Nested + @TestInstance(PER_CLASS) + class Inner { + @Test + void test() { + } + } + } + + private static class TestInstanceLifecycleExtension + implements TestInstancePostProcessor, TestInstancePreDestroyCallback { + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + context.publishReportEntry("post-process", testInstance.getClass().getSimpleName()); + } + + @Override + public void preDestroyTestInstance(ExtensionContext context) { + TestInstancePreDestroyCallback.preDestroyTestInstances(context, + testInstance -> context.publishReportEntry("pre-destroy", testInstance.getClass().getSimpleName())); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java new file mode 100644 index 00000000..6acbcafa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.0 + */ +class TestReporterParameterResolverTests { + + TestReporterParameterResolver resolver = new TestReporterParameterResolver(); + + @Test + void supports() { + Parameter parameter1 = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); + assertTrue(this.resolver.supportsParameter(parameterContext(parameter1), null)); + + Parameter parameter2 = findParameterOfMethod("methodWithoutTestReporterParameter", String.class); + assertFalse(this.resolver.supportsParameter(parameterContext(parameter2), null)); + } + + @Test + void resolve() { + Parameter parameter = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); + + TestReporter testReporter = this.resolver.resolveParameter(parameterContext(parameter), mock()); + assertNotNull(testReporter); + } + + private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { + Method method = ReflectionUtils.findMethod(Sample.class, methodName, parameterTypes).get(); + return method.getParameters()[0]; + } + + private static ParameterContext parameterContext(Parameter parameter) { + ParameterContext parameterContext = mock(); + when(parameterContext.getParameter()).thenReturn(parameter); + return parameterContext; + } + + static class Sample { + + void methodWithTestReporterParameter(TestReporter reporter) { + } + + void methodWithoutTestReporterParameter(String nothing) { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java new file mode 100644 index 00000000..4c446f6f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java @@ -0,0 +1,308 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.toUnmodifiableList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.testkit.engine.EngineExecutionResults; + +/** + * Integration tests for the {@link TestWatcher} extension API. + * + * @since 5.4 + */ +class TestWatcherTests extends AbstractJupiterTestEngineTests { + + private static final List testWatcherMethodNames = Arrays.stream(TestWatcher.class.getDeclaredMethods())// + .filter(not(Method::isSynthetic))// + .map(Method::getName)// + .collect(toUnmodifiableList()); + + @BeforeEach + void clearResults() { + TrackingTestWatcher.results.clear(); + } + + @Test + void testWatcherIsInvokedForTestMethodsInTopLevelAndNestedTestClasses() { + assertCommonStatistics(executeTestsForClass(TrackingTestWatcherTestMethodsTestCase.class)); + assertThat(TrackingTestWatcher.results.keySet()).containsAll(testWatcherMethodNames); + TrackingTestWatcher.results.values().forEach(uidList -> assertEquals(2, uidList.size())); + } + + @Test + void testWatcherIsInvokedForRepeatedTestMethods() { + EngineExecutionResults results = executeTestsForClass(TrackingTestWatcherRepeatedTestMethodsTestCase.class); + + results.containerEvents().assertStatistics( + stats -> stats.skipped(1).started(5).succeeded(5).aborted(0).failed(0)); + results.testEvents().assertStatistics( + stats -> stats.dynamicallyRegistered(6).skipped(0).started(6).succeeded(2).aborted(2).failed(2)); + + ArrayList expectedMethods = new ArrayList<>(testWatcherMethodNames); + // Since the @RepeatedTest container is disabled, the individual invocations never occur. + assertThat(TrackingTestWatcher.results.keySet()).containsAll(expectedMethods); + // 2 => number of iterations declared in @RepeatedTest(2). + TrackingTestWatcher.results.forEach( + (methodName, uidList) -> assertEquals("testDisabled".endsWith(methodName) ? 1 : 2, uidList.size())); + } + + @Test + void testWatcherIsNotInvokedForTestFactoryMethods() { + EngineExecutionResults results = executeTestsForClass(TrackingTestWatcherTestFactoryMethodsTestCase.class); + + results.containerEvents().assertStatistics( + stats -> stats.skipped(1).started(5).succeeded(5).aborted(0).failed(0)); + results.testEvents().assertStatistics( + stats -> stats.dynamicallyRegistered(6).skipped(0).started(6).succeeded(2).aborted(2).failed(2)); + + // There should be zero results, since the TestWatcher API is not supported for @TestFactory containers. + assertThat(TrackingTestWatcher.results).isEmpty(); + } + + @Test + @TrackLogRecords + void testWatcherExceptionsAreLoggedAndSwallowed(LogRecordListener logRecordListener) { + assertCommonStatistics(executeTestsForClass(ExceptionThrowingTestWatcherTestCase.class)); + + // @formatter:off + long exceptionCount = logRecordListener.stream(MethodBasedTestDescriptor.class, Level.WARNING) + .map(LogRecord::getThrown) + .filter(throwable -> throwable instanceof JUnitException) + .filter(throwable -> testWatcherMethodNames.contains(throwable.getStackTrace()[0].getMethodName())) + .count(); + // @formatter:on + + assertEquals(8, exceptionCount, "Thrown exceptions were not logged properly."); + } + + @Test + void testWatcherInvokedForTestMethodsInTestCaseWithProblematicConstructor() { + EngineExecutionResults results = executeTestsForClass(ProblematicConstructorTestCase.class); + results.testEvents().assertStatistics(stats -> stats.skipped(0).started(8).succeeded(0).aborted(0).failed(8)); + assertThat(TrackingTestWatcher.results.keySet()).containsExactly("testFailed"); + assertThat(TrackingTestWatcher.results.get("testFailed")).hasSize(8); + } + + private void assertCommonStatistics(EngineExecutionResults results) { + results.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3).failed(0)); + results.testEvents().assertStatistics(stats -> stats.skipped(2).started(6).succeeded(2).aborted(2).failed(2)); + } + + // ------------------------------------------------------------------------- + + private static abstract class AbstractTestCase { + + @Test + public void successfulTest() { + //no-op + } + + @Test + public void failedTest() { + fail("Must fail"); + } + + @Test + public void abortedTest() { + assumeTrue(false); + } + + @Test + @Disabled + public void skippedTest() { + //no-op + } + + @Nested + class SecondLevel { + + @Test + public void successfulTest() { + //no-op + } + + @Test + public void failedTest() { + fail("Must fail"); + } + + @Test + public void abortedTest() { + assumeTrue(false); + } + + @Test + @Disabled + public void skippedTest() { + //no-op + } + + } + + } + + @ExtendWith(TrackingTestWatcher.class) + static class TrackingTestWatcherTestMethodsTestCase extends AbstractTestCase { + } + + @ExtendWith(TrackingTestWatcher.class) + static class TrackingTestWatcherRepeatedTestMethodsTestCase { + + @RepeatedTest(2) + void successfulTest() { + //no-op + } + + @RepeatedTest(2) + void failedTest() { + fail("Must fail"); + } + + @RepeatedTest(2) + void abortedTest() { + assumeTrue(false); + } + + @RepeatedTest(2) + @Disabled + void skippedTest() { + //no-op + } + + } + + @ExtendWith(TrackingTestWatcher.class) + static class TrackingTestWatcherTestFactoryMethodsTestCase { + + @TestFactory + Stream successfulTest() { + return Stream.of("A", "B").map(text -> dynamicTest(text, () -> assertTrue(true))); + } + + @TestFactory + Stream failedTest() { + return Stream.of("A", "B").map(text -> dynamicTest(text, () -> fail("Must fail"))); + + } + + @TestFactory + Stream abortedTest() { + return Stream.of("A", "B").map(text -> dynamicTest(text, () -> assumeTrue(false))); + + } + + @TestFactory + @Disabled + Stream skippedTest() { + return Stream.of("A", "B").map(text -> dynamicTest(text, () -> assertTrue(false))); + } + + } + + @ExtendWith(ExceptionThrowingTestWatcher.class) + static class ExceptionThrowingTestWatcherTestCase extends AbstractTestCase { + } + + @ExtendWith(TrackingTestWatcher.class) + static class ProblematicConstructorTestCase extends AbstractTestCase { + ProblematicConstructorTestCase(Object ignore) { + } + } + + private static class TrackingTestWatcher implements TestWatcher { + + private static final Map> results = new HashMap<>(); + + @Override + public void testSuccessful(ExtensionContext context) { + trackResult("testSuccessful", context.getUniqueId()); + } + + @Override + public void testAborted(ExtensionContext context, Throwable cause) { + trackResult("testAborted", context.getUniqueId()); + } + + @Override + public void testFailed(ExtensionContext context, Throwable cause) { + trackResult("testFailed", context.getUniqueId()); + } + + @Override + public void testDisabled(ExtensionContext context, Optional reason) { + trackResult("testDisabled", context.getUniqueId()); + } + + protected void trackResult(String status, String uid) { + results.computeIfAbsent(status, k -> new ArrayList<>()).add(uid); + } + + } + + private static class ExceptionThrowingTestWatcher implements TestWatcher { + + @Override + public void testSuccessful(ExtensionContext context) { + throw new JUnitException("Exception in testSuccessful()"); + } + + @Override + public void testDisabled(ExtensionContext context, Optional reason) { + throw new JUnitException("Exception in testDisabled()"); + } + + @Override + public void testAborted(ExtensionContext context, Throwable cause) { + throw new JUnitException("Exception in testAborted()"); + } + + @Override + public void testFailed(ExtensionContext context, Throwable cause) { + throw new JUnitException("Exception in testFailed()"); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java new file mode 100644 index 00000000..d37d922f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; +import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; + +/** + * @since 5.5 + */ +class TimeoutConfigurationTests { + + ExtensionContext extensionContext = mock(); + TimeoutConfiguration config = new TimeoutConfiguration(extensionContext); + + @Test + void noTimeoutIfNoPropertiesAreSet() { + assertThat(config.getDefaultBeforeAllMethodTimeout()).isEmpty(); + assertThat(config.getDefaultBeforeEachMethodTimeout()).isEmpty(); + assertThat(config.getDefaultTestMethodTimeout()).isEmpty(); + assertThat(config.getDefaultTestTemplateMethodTimeout()).isEmpty(); + assertThat(config.getDefaultTestFactoryMethodTimeout()).isEmpty(); + assertThat(config.getDefaultAfterEachMethodTimeout()).isEmpty(); + assertThat(config.getDefaultAfterAllMethodTimeout()).isEmpty(); + assertThat(config.getDefaultTimeoutThreadMode()).isEmpty(); + } + + @Test + void defaultTimeoutIsUsedUnlessAMoreSpecificOneIsSet() { + when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("42")); + + assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); + assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); + assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); + assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); + assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); + assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); + assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); + } + + @Test + void defaultCategoryTimeoutIsUsedUnlessAMoreSpecificOneIsSet() { + when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("2")); + when(extensionContext.getConfigurationParameter(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("3")); + when(extensionContext.getConfigurationParameter(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("5")); + + assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); + assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); + assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); + assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); + assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); + assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); + assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); + } + + @Test + void specificTimeoutsAreUsedIfSet() { + when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("2")); + when(extensionContext.getConfigurationParameter(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("3")); + when(extensionContext.getConfigurationParameter(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("5")); + when(extensionContext.getConfigurationParameter(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("7ns")); + when(extensionContext.getConfigurationParameter(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("11μs")); + when(extensionContext.getConfigurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("13ms")); + when(extensionContext.getConfigurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("17s")); + when(extensionContext.getConfigurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("19m")); + when(extensionContext.getConfigurationParameter(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("23h")); + when(extensionContext.getConfigurationParameter(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("29d")); + + assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(7, NANOSECONDS)); + assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(11, MICROSECONDS)); + assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(13, MILLISECONDS)); + assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(17, SECONDS)); + assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(19, MINUTES)); + assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(23, HOURS)); + assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(29, DAYS)); + } + + @Test + @TrackLogRecords + void logsInvalidValues(LogRecordListener logRecordListener) { + when(extensionContext.getConfigurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( + Optional.of("invalid")); + + assertThat(config.getDefaultTestMethodTimeout()).isEmpty(); + assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // + .containsExactly( + "Ignored invalid timeout 'invalid' set via the 'junit.jupiter.execution.timeout.test.method.default' configuration parameter."); + } + + @Test + void specificThreadModeIsUsed() { + when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)).thenReturn( + Optional.of("SEPARATE_THREAD")); + assertThat(config.getDefaultTimeoutThreadMode()).isEqualTo(Optional.of(SEPARATE_THREAD)); + } + + @Test + @TrackLogRecords + void logsInvalidThreadModeValueAndReturnEmpty(LogRecordListener logRecordListener) { + when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)).thenReturn( + Optional.of("invalid")); + + assertThat(config.getDefaultTimeoutThreadMode()).isEqualTo(Optional.empty()); + assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // + .containsExactly( + "Invalid timeout thread mode 'invalid' set via the 'junit.jupiter.execution.timeout.thread.mode.default' configuration parameter."); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java new file mode 100644 index 00000000..7eee8ae4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.time.format.DateTimeParseException; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; + +/** + * @since 5.5 + */ +class TimeoutDurationParserTests { + + private final TimeoutDurationParser parser = new TimeoutDurationParser(); + + @Test + void parsesNumberWithoutUnitIntoSecondsDurations() { + assertEquals(new TimeoutDuration(42, SECONDS), parser.parse("42")); + } + + @TestFactory + Stream parsesNumbersWithUnits() { + var unitsWithRepresentations = Map.of( // + NANOSECONDS, "ns", // + MICROSECONDS, "μs", // + MILLISECONDS, "ms", // + SECONDS, "s", // + MINUTES, "m", // + HOURS, "h", // + DAYS, "d"); + return unitsWithRepresentations.entrySet().stream() // + .map(entry -> { + var unit = entry.getKey(); + var plainRepresentation = entry.getValue(); + var representations = Stream.of( // + plainRepresentation, // + " " + plainRepresentation, // + plainRepresentation.toUpperCase(), // + " " + plainRepresentation.toUpperCase()); + return dynamicContainer(unit.name().toLowerCase(), + representations.map(representation -> dynamicTest("\"" + representation + "\"", () -> { + var expected = new TimeoutDuration(42, unit); + var actual = parser.parse("42" + representation); + assertEquals(expected, actual); + }))); + }); + } + + @Test + void rejectsNumbersStartingWithZero() { + assertThrows(DateTimeParseException.class, () -> parser.parse("01")); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java new file mode 100644 index 00000000..494ea9df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.5 + */ +class TimeoutDurationTests { + + @Test + void formatsDurationNicely() { + assertThat(new TimeoutDuration(1, SECONDS)).hasToString("1 second"); + assertThat(new TimeoutDuration(2, SECONDS)).hasToString("2 seconds"); + } + + @Test + void fulfillsEqualsAndHashCodeContract() { + var oneSecond = new TimeoutDuration(1, SECONDS); + + assertThat(oneSecond) // + .isEqualTo(oneSecond) // + .isEqualTo(new TimeoutDuration(1, SECONDS)) // + .hasSameHashCodeAs(new TimeoutDuration(1, SECONDS)) // + .isNotEqualTo("foo") // + .isNotEqualTo(new TimeoutDuration(2, SECONDS)) // + .isNotEqualTo(new TimeoutDuration(1, MINUTES)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java new file mode 100644 index 00000000..95aa77c4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.engine.extension.TimeoutExceptionFactory.create; + +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * @since 5.9 + */ +@DisplayName("TimeoutExceptionFactory") +class TimeoutExceptionFactoryTest { + + private static final TimeoutDuration tenMillisDuration = new TimeoutDuration(10, MILLISECONDS); + private static final Exception suppressedException = new Exception("Winke!"); + private static final String methodSignature = "test()"; + + @Test + @DisplayName("creates exception with method signature and timeout") + void createExceptionWithMethodSignatureTimeout() { + TimeoutException exception = create(methodSignature, tenMillisDuration); + + assertThat(exception) // + .hasMessage("test() timed out after 10 milliseconds") // + .hasNoSuppressedExceptions(); + } + + @Test + @DisplayName("creates exception with method signature, timeout and throwable") + void createExceptionWithMethodSignatureTimeoutAndThrowable() { + TimeoutException exception = create(methodSignature, tenMillisDuration, suppressedException); + + assertThat(exception) // + .hasMessage("test() timed out after 10 milliseconds") // + .hasSuppressedException(suppressedException); + } + + @Nested + @DisplayName("throws exception when") + class ThrowException { + + @Test + @DisplayName("method signature is null") + void methodSignatureIsnull() { + assertThatThrownBy(() -> create(null, tenMillisDuration, suppressedException)) // + .hasMessage("method signature must not be null"); + } + + @Test + @DisplayName("method timeout duration is null") + void timeoutDurationIsnull() { + assertThatThrownBy(() -> create(methodSignature, null, suppressedException)) // + .hasMessage("timeout duration must not be null"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java new file mode 100644 index 00000000..6fc77244 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java @@ -0,0 +1,851 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; +import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; +import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.TIMEOUT_MODE_PROPERTY_NAME; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.RuntimeUtils; +import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Events; +import org.junit.platform.testkit.engine.Execution; +import org.opentest4j.AssertionFailedError; + +/** + * @since 5.5 + */ +@DisplayName("@Timeout") +class TimeoutExtensionTests extends AbstractJupiterTestEngineTests { + + @Test + @DisplayName("is applied on annotated @Test methods") + void appliesTimeoutOnAnnotatedTestMethods() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // + .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testMethod() timed out after 10 milliseconds"); + } + + @Test + @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") + void doesNotApplyTimeoutOnAnnotatedTestMethodsUsingDisabledTimeoutMode() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // + .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled").build()); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // + .isEmpty(); + } + + @Test + @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") + void applyTimeoutOnAnnotatedTestMethodsUsingDisabledOnDebugTimeoutMode() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // + .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled_on_debug").build()); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + // The check to see if debugging is pushing the timer just above 1 second + .isLessThan(Duration.ofSeconds(2)); + + // Should we test if we're debugging? This test will fail if we are debugging. + if (RuntimeUtils.isDebugMode()) { + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // + .isEmpty(); + } + else { + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testMethod() timed out after 10 milliseconds"); + } + } + + @Test + @DisplayName("is applied on annotated @TestTemplate methods") + void appliesTimeoutOnAnnotatedTestTemplateMethods() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testTemplateMethod")) // + .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Stream.of("repetition 1", "repetition 2").forEach(displayName -> { + Execution execution = findExecution(results.testEvents(), displayName); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testTemplateMethod() timed out after 10 milliseconds"); + }); + } + + @Test + @DisplayName("is applied on annotated @TestFactory methods") + void appliesTimeoutOnAnnotatedTestFactoryMethods() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testFactoryMethod")) // + .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Execution execution = findExecution(results.containerEvents(), "testFactoryMethod()"); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testFactoryMethod() timed out after 10 milliseconds"); + } + + @TestFactory + @DisplayName("is applied on testable methods in annotated classes") + Stream appliesTimeoutOnTestableMethodsInAnnotatedClasses() { + return Stream.of(TimeoutAnnotatedClassTestCase.class, InheritedTimeoutAnnotatedClassTestCase.class).map( + testClass -> dynamicTest(testClass.getSimpleName(), () -> { + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(testClass)) // + .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Stream.of("testMethod()", "repetition 1", "repetition 2", "testFactoryMethod()").forEach( + displayName -> { + Execution execution = findExecution(results.allEvents(), displayName); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessageEndingWith("timed out after 10000000 nanoseconds"); + }); + })); + } + + @Test + @DisplayName("fails methods that do not throw InterruptedException") + void failsMethodsWithoutInterruptedException() { + EngineExecutionResults results = executeTestsForClass(MethodWithoutInterruptedExceptionTestCase.class); + + Execution execution = findExecution(results.testEvents(), "methodThatDoesNotThrowInterruptedException()"); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(1)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getStatus()).isEqualTo(FAILED); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("methodThatDoesNotThrowInterruptedException() timed out after 1 millisecond"); + } + + @Test + @DisplayName("is applied on annotated @BeforeAll methods") + void appliesTimeoutOnAnnotatedBeforeAllMethods() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(TimeoutAnnotatedBeforeAllMethodTestCase.class)) // + .configurationParameter(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Execution execution = findExecution(results.containerEvents(), + TimeoutAnnotatedBeforeAllMethodTestCase.class.getSimpleName()); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("setUp() timed out after 10 milliseconds"); + } + + @Test + @DisplayName("is applied on annotated @BeforeEach methods") + void appliesTimeoutOnAnnotatedBeforeEachMethods() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(TimeoutAnnotatedBeforeEachMethodTestCase.class)) // + .configurationParameter(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("setUp() timed out after 10 milliseconds"); + } + + @Test + @DisplayName("is applied on annotated @AfterEach methods") + void appliesTimeoutOnAnnotatedAfterEachMethods() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(TimeoutAnnotatedAfterEachMethodTestCase.class)) // + .configurationParameter(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("tearDown() timed out after 10 milliseconds"); + } + + @Test + @DisplayName("is applied on annotated @AfterAll methods") + void appliesTimeoutOnAnnotatedAfterAllMethods() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(TimeoutAnnotatedAfterAllMethodTestCase.class)) // + .configurationParameter(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + Execution execution = findExecution(results.containerEvents(), + TimeoutAnnotatedAfterAllMethodTestCase.class.getSimpleName()); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("tearDown() timed out after 10 milliseconds"); + } + + @TestFactory + @DisplayName("is applied from configuration parameters by default") + Stream appliesDefaultTimeoutsFromConfigurationParameters() { + return Map.of(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "beforeAll()", // + DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "beforeEach()", // + DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "test()", // + DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "testTemplate()", // + DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "testFactory()", // + DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "afterEach()", // + DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "afterAll()" // + ).entrySet().stream().map(entry -> dynamicTest("uses " + entry.getKey() + " config param", () -> { + PlainTestCase.slowMethod = entry.getValue(); + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(PlainTestCase.class)) // + .configurationParameter(entry.getKey(), "1ns") // + .build()); + var failure = results.allEvents().executions().failed() // + .map(execution -> execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .findFirst(); + assertThat(failure).containsInstanceOf(TimeoutException.class); + assertThat(failure.get()).hasMessage(entry.getValue() + " timed out after 1 nanosecond"); + })); + } + + @Test + @DisplayName("does not swallow unrecoverable exceptions") + void doesNotSwallowUnrecoverableExceptions() { + assertThrows(OutOfMemoryError.class, () -> executeTestsForClass(UnrecoverableExceptionTestCase.class)); + } + + @Test + @DisplayName("does not affect tests that don't exceed the timeout") + void doesNotAffectTestsThatDoNotExceedTimeoutDuration() { + executeTestsForClass(NonTimeoutExceedingTestCase.class).allEvents().assertStatistics(stats -> stats.failed(0)); + } + + @Test + @DisplayName("includes fully qualified class name if method is not in the test class") + void includesClassNameIfMethodIsNotInTestClass() { + EngineExecutionResults results = executeTestsForClass(NestedClassWithOuterSetupMethodTestCase.class); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessageEndingWith( + "$NestedClassWithOuterSetupMethodTestCase#setUp() timed out after 10 milliseconds"); + } + + @Test + @DisplayName("reports illegal timeout durations") + void reportsIllegalTimeoutDurations() { + EngineExecutionResults results = executeTestsForClass(IllegalTimeoutDurationTestCase.class); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("timeout duration must be a positive number: 0"); + } + + private Execution findExecution(Events events, String displayName) { + return getOnlyElement(events // + .executions() // + .filter(execution -> execution.getTestDescriptor().getDisplayName().contains(displayName)) // + .collect(toList())); + } + + @Nested + @DisplayName("separate thread") + class SeparateThread { + @Test + @DisplayName("timeout exceeded") + void timeoutExceededInSeparateThread() { + EngineExecutionResults results = executeTestsForClass(TimeoutExceedingSeparateThreadTestCase.class); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + Throwable failure = execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow(); + assertThat(failure) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testMethod() timed out after 100 milliseconds"); + assertThat(failure.getCause()) // + .hasMessageStartingWith("Execution timed out in ") // + .hasStackTraceContaining(TimeoutExceedingSeparateThreadTestCase.class.getName() + ".testMethod"); + } + + @Test + @DisplayName("non timeout exceeded") + void nonTimeoutExceededInSeparateThread() { + executeTestsForClass(NonTimeoutExceedingSeparateThreadTestCase.class).allEvents() // + .assertStatistics(stats -> stats.failed(0)); + } + + @Test + @DisplayName("does not swallow unrecoverable exceptions") + void separateThreadDoesNotSwallowUnrecoverableExceptions() { + assertThrows(OutOfMemoryError.class, + () -> executeTestsForClass(UnrecoverableExceptionInSeparateThreadTestCase.class)); + } + + @Test + @DisplayName("handles invocation exceptions") + void separateThreadHandlesInvocationExceptions() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectMethod(ExceptionInSeparateThreadTestCase.class, "test")) // + .build()); + + Execution execution = findExecution(results.testEvents(), "test()"); + assertThat(execution.getDuration()) // + .isLessThan(Duration.ofSeconds(5)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(RuntimeException.class) // + .hasMessage("Oppps!"); + } + + @Test + @DisplayName("propagates assertion exceptions") + void separateThreadHandlesOpenTestFailedAssertion() { + EngineExecutionResults results = executeTestsForClass(FailedAssertionInSeparateThreadTestCase.class); + + Execution openTestFailure = findExecution(results.testEvents(), "testOpenTestAssertion()"); + assertThat(openTestFailure.getDuration()) // + .isLessThan(Duration.ofSeconds(5)); + assertThat(openTestFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(AssertionFailedError.class); + + Execution javaLangFailure = findExecution(results.testEvents(), "testJavaLangAssertion()"); + assertThat(javaLangFailure.getDuration()) // + .isLessThan(Duration.ofSeconds(5)); + assertThat(javaLangFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(AssertionError.class); + } + + @Test + @DisplayName("when one test is stuck \"forever\" the next tests should not get stuck") + void oneThreadStuckForever() { + EngineExecutionResults results = executeTestsForClass(OneTestStuckForeverAndTheOthersNotTestCase.class); + + Execution stuckExecution = findExecution(results.testEvents(), "stuck()"); + assertThat(stuckExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("stuck() timed out after 10 milliseconds"); + + Execution testZeroExecution = findExecution(results.testEvents(), "testZero()"); + assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getStatus()) // + .isEqualTo(Status.SUCCESSFUL); + + Execution testOneExecution = findExecution(results.testEvents(), "testOne()"); + assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getStatus()) // + .isEqualTo(Status.SUCCESSFUL); + } + + @Test + @DisplayName("mixed same thread and separate thread tests") + void mixedSameThreadAndSeparateThreadTests() { + EngineExecutionResults results = executeTestsForClass(MixedSameThreadAndSeparateThreadTestCase.class); + + Execution stuck = findExecution(results.testEvents(), "testZero()"); + assertThat(stuck.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testZero() timed out after 10 milliseconds"); + + Execution testZeroExecution = findExecution(results.testEvents(), "testOne()"); + assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testOne() timed out after 10 milliseconds"); + + Execution testOneExecution = findExecution(results.testEvents(), "testTwo()"); + assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("testTwo() timed out after 10 milliseconds"); + } + + @Test + @DisplayName("one test is stuck \"forever\" in separate thread and other tests in same thread not") + void oneThreadStuckForeverAndOtherTestsInSameThread() { + EngineExecutionResults results = executeTestsForClass( + OneTestStuckForeverAndTheOthersInSameThreadNotTestCase.class); + + Execution stuckExecution = findExecution(results.testEvents(), "stuck()"); + assertThat(stuckExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("stuck() timed out after 10 milliseconds"); + + Execution testZeroExecution = findExecution(results.testEvents(), "testZero()"); + assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getStatus()) // + .isEqualTo(Status.SUCCESSFUL); + + Execution testOneExecution = findExecution(results.testEvents(), "testOne()"); + assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getStatus()) // + .isEqualTo(Status.SUCCESSFUL); + } + + @Test + @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") + void doesNotApplyTimeoutOnAnnotatedTestMethodsUsingDisabledTimeoutMode() { + EngineExecutionResults results = executeTests(request() // + .selectors(selectMethod(TimeoutExceedingSeparateThreadTestCase.class, "testMethod")) // + .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled").build()); + + Execution execution = findExecution(results.testEvents(), "testMethod()"); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // + .isEmpty(); + } + + @Nested + @DisplayName("on class level") + class OnClassLevel { + @Test + @DisplayName("timeout exceeded") + void timeoutExceededInSeparateThreadOnClassLevel() { + EngineExecutionResults results = executeTestsForClass(TimeoutExceededOnClassLevelTestCase.class); + + Execution execution = findExecution(results.testEvents(), "exceptionThrown()"); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessage("exceptionThrown() timed out after 100 milliseconds"); + } + + @Test + @DisplayName("non timeout exceeded") + void nonTimeoutExceededInSeparateThreadOnClassLevel() { + executeTestsForClass(NonTimeoutExceededOnClassLevelTestCase.class).allEvents() // + .assertStatistics(stats -> stats.failed(0)); + } + } + } + + static class TimeoutAnnotatedTestMethodTestCase { + @Test + @Timeout(value = 10, unit = MILLISECONDS) + void testMethod() throws Exception { + Thread.sleep(1000); + } + + @RepeatedTest(2) + @Timeout(value = 10, unit = MILLISECONDS) + void testTemplateMethod() throws Exception { + Thread.sleep(1000); + } + + @TestFactory + @Timeout(value = 10, unit = MILLISECONDS) + Stream testFactoryMethod() throws Exception { + Thread.sleep(1000); + return Stream.empty(); + } + } + + static class TimeoutAnnotatedBeforeAllMethodTestCase { + @BeforeAll + @Timeout(value = 10, unit = MILLISECONDS) + static void setUp() throws Exception { + Thread.sleep(1000); + } + + @Test + void testMethod() { + // never called + } + } + + static class TimeoutAnnotatedBeforeEachMethodTestCase { + @BeforeEach + @Timeout(value = 10, unit = MILLISECONDS) + void setUp() throws Exception { + Thread.sleep(1000); + } + + @Test + void testMethod() { + // never called + } + } + + static class TimeoutAnnotatedAfterEachMethodTestCase { + @Test + void testMethod() { + // do nothing + } + + @AfterEach + @Timeout(value = 10, unit = MILLISECONDS) + void tearDown() throws Exception { + Thread.sleep(1000); + } + } + + static class TimeoutAnnotatedAfterAllMethodTestCase { + @Test + void testMethod() { + // do nothing + } + + @AfterAll + @Timeout(value = 10, unit = MILLISECONDS) + static void tearDown() throws Exception { + Thread.sleep(1000); + } + } + + @Timeout(value = 10_000_000, unit = NANOSECONDS) + static class TimeoutAnnotatedClassTestCase { + @Nested + class NestedClass { + @Test + void testMethod() throws Exception { + Thread.sleep(1000); + } + + @RepeatedTest(2) + void testTemplateMethod() throws Exception { + Thread.sleep(1000); + } + + @TestFactory + Stream testFactoryMethod() throws Exception { + Thread.sleep(1000); + return Stream.empty(); + } + } + } + + static class InheritedTimeoutAnnotatedClassTestCase extends TimeoutAnnotatedClassTestCase { + } + + static class MethodWithoutInterruptedExceptionTestCase { + @Test + @Timeout(value = 1, unit = MILLISECONDS) + void methodThatDoesNotThrowInterruptedException() { + new EventuallyInterruptibleInvocation().proceed(); + } + } + + static class PlainTestCase { + + public static String slowMethod; + + @BeforeAll + static void beforeAll() throws Exception { + waitForInterrupt("beforeAll()"); + } + + @BeforeEach + void beforeEach() throws Exception { + waitForInterrupt("beforeEach()"); + } + + @Test + void test() throws Exception { + waitForInterrupt("test()"); + } + + @RepeatedTest(2) + void testTemplate() throws Exception { + waitForInterrupt("testTemplate()"); + } + + @TestFactory + Stream testFactory() throws Exception { + waitForInterrupt("testFactory()"); + return Stream.empty(); + } + + @AfterEach + void afterEach() throws Exception { + waitForInterrupt("afterEach()"); + } + + @AfterAll + static void afterAll() throws Exception { + waitForInterrupt("afterAll()"); + } + + private static void waitForInterrupt(String methodName) throws InterruptedException { + if (methodName.equals(slowMethod)) { + blockUntilInterrupted(); + } + } + } + + static class UnrecoverableExceptionTestCase { + @Test + @Timeout(value = 1, unit = NANOSECONDS) + void test() { + new EventuallyInterruptibleInvocation().proceed(); + throw new OutOfMemoryError(); + } + } + + @Timeout(10) + static class NonTimeoutExceedingTestCase { + @Test + void testMethod() { + } + + @RepeatedTest(1) + void testTemplateMethod() { + } + + @TestFactory + Stream testFactoryMethod() { + return Stream.of(dynamicTest("dynamicTest", () -> { + })); + } + } + + static class NestedClassWithOuterSetupMethodTestCase { + + @Timeout(value = 10, unit = MILLISECONDS) + @BeforeEach + void setUp() throws Exception { + Thread.sleep(1000); + } + + @Nested + class NestedClass { + + @BeforeEach + void setUp() { + } + + @Test + void testMethod() { + } + + } + + } + + static class IllegalTimeoutDurationTestCase { + + @Test + @Timeout(0) + void testMethod() { + } + + } + + static class TimeoutExceedingWithInferredThreadModeTestCase { + @Test + @Timeout(value = 10, unit = MILLISECONDS) + void testMethod() throws InterruptedException { + Thread.sleep(1000); + } + } + + static class TimeoutExceedingSeparateThreadTestCase { + @Test + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void testMethod() throws InterruptedException { + Thread.sleep(1000); + } + } + + static class NonTimeoutExceedingSeparateThreadTestCase { + @Test + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void testMethod() { + } + } + + static class UnrecoverableExceptionInSeparateThreadTestCase { + @Test + @Timeout(value = 100, unit = SECONDS, threadMode = SEPARATE_THREAD) + void test() { + throw new OutOfMemoryError(); + } + } + + static class ExceptionInSeparateThreadTestCase { + @Test + @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) + void test() { + throw new RuntimeException("Oppps!"); + } + } + + static class FailedAssertionInSeparateThreadTestCase { + @Test + @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) + void testOpenTestAssertion() { + throw new AssertionFailedError(); + } + + @Test + @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) + void testJavaLangAssertion() { + throw new AssertionError(); + } + } + + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + static class TimeoutExceededOnClassLevelTestCase { + @Test + void exceptionThrown() throws InterruptedException { + Thread.sleep(1000); + } + } + + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + static class NonTimeoutExceededOnClassLevelTestCase { + @Test + void test() { + } + } + + @TestMethodOrder(OrderAnnotation.class) + static class OneTestStuckForeverAndTheOthersNotTestCase { + + @Test + @Order(0) + @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void stuck() throws InterruptedException { + blockUntilInterrupted(); + } + + @Test + @Order(1) + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void testZero() { + } + + @Test + @Order(2) + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void testOne() { + } + } + + static class MixedSameThreadAndSeparateThreadTestCase { + @Test + @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void testZero() throws InterruptedException { + Thread.sleep(1000); + } + + @Test + @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) + void testOne() throws InterruptedException { + Thread.sleep(1000); + } + + @Test + @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void testTwo() throws InterruptedException { + Thread.sleep(1000); + } + } + + @TestMethodOrder(OrderAnnotation.class) + static class OneTestStuckForeverAndTheOthersInSameThreadNotTestCase { + + @Test + @Order(0) + @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + void stuck() throws InterruptedException { + blockUntilInterrupted(); + } + + @Test + @Order(1) + @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) + void testZero() { + } + + @Test + @Order(2) + @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) + void testOne() { + } + } + + private static void blockUntilInterrupted() throws InterruptedException { + new CountDownLatch(1).await(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java new file mode 100644 index 00000000..31dda773 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout.ThreadMode; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.jupiter.engine.execution.ExtensionValuesStore; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.SingleThreadExecutorResource; +import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +@DisplayName("TimeoutInvocationFactory") +@ExtendWith(MockitoExtension.class) +class TimeoutInvocationFactoryTest { + + @Spy + private final Store store = new NamespaceAwareStore(new ExtensionValuesStore(null), + ExtensionContext.Namespace.create(TimeoutInvocationFactoryTest.class)); + @Mock + private Invocation invocation; + @Mock + private TimeoutDuration timeoutDuration; + private TimeoutInvocationFactory timeoutInvocationFactory; + private TimeoutInvocationParameters parameters; + + @BeforeEach + void setUp() { + parameters = new TimeoutInvocationParameters<>(invocation, timeoutDuration, () -> "description"); + timeoutInvocationFactory = new TimeoutInvocationFactory(store); + } + + @Test + @DisplayName("throws exception when null store is provided on create") + void shouldThrowExceptionWhenInstantiatingWithNullStore() { + assertThatThrownBy(() -> new TimeoutInvocationFactory(null)) // + .hasMessage("store must not be null"); + } + + @Test + @DisplayName("throws exception when null timeout thread mode is provided on create") + void shouldThrowExceptionWhenNullTimeoutThreadModeIsProvidedWhenCreate() { + assertThatThrownBy(() -> timeoutInvocationFactory.create(null, parameters)) // + .hasMessage("thread mode must not be null"); + } + + @Test + @DisplayName("throws exception when null timeout invocation parameters is provided on create") + void shouldThrowExceptionWhenNullTimeoutInvocationParametersIsProvidedWhenCreate() { + assertThatThrownBy(() -> timeoutInvocationFactory.create(ThreadMode.SAME_THREAD, null)) // + .hasMessage("timeout invocation parameters must not be null"); + } + + @Test + @DisplayName("creates timeout invocation for SAME_THREAD thread mode") + void shouldCreateTimeoutInvocationForSameThreadTimeoutThreadMode() { + var invocation = timeoutInvocationFactory.create(ThreadMode.SAME_THREAD, parameters); + assertThat(invocation).isInstanceOf(SameThreadTimeoutInvocation.class); + verify(store).getOrComputeIfAbsent(SingleThreadExecutorResource.class); + } + + @Test + @DisplayName("creates timeout invocation for SEPARATE_THREAD thread mode") + void shouldCreateTimeoutInvocationForSeparateThreadTimeoutThreadMode() { + var invocation = timeoutInvocationFactory.create(ThreadMode.SEPARATE_THREAD, parameters); + assertThat(invocation).isInstanceOf(SeparateThreadTimeoutInvocation.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java new file mode 100644 index 00000000..9e9533e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension.sub; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Intentionally in a subpackage in order to properly test deactivation + * of conditions based on patterns. In other words, we do not want this + * condition declared in the same package as the + * {@link org.junit.jupiter.engine.extension.DisabledCondition} + * + * ExecutionCondition always returns disabled, since we want to test the + * deactivation of the condition itself. + * + * @since 5.7 + */ +public class AlwaysDisabledCondition implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return ConditionEvaluationResult.disabled("Always Disabled"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java new file mode 100644 index 00000000..d8846b32 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension.sub; + +/** + * Intentionally in a subpackage in order to properly test deactivation + * of conditions based on patterns. In other words, we do not want this + * condition declared in the same package as the + * {@link org.junit.jupiter.engine.extension.DisabledCondition} + * + * ExecutionCondition always returns disabled, since we want to test the + * deactivation of the condition itself. + * + * @since 5.7 + */ +public class AnotherAlwaysDisabledCondition extends AlwaysDisabledCondition { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java new file mode 100644 index 00000000..f2f2cab4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension.sub; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Intentionally in a subpackage in order to properly test deactivation + * of conditions based on patterns. In other words, we do not want this + * condition declared in the same package as the + * {@link org.junit.jupiter.engine.extension.DisabledCondition} + * + * @since 5.0 + */ +public class SystemPropertyCondition implements ExecutionCondition { + + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @ExtendWith(SystemPropertyCondition.class) + public @interface SystemProperty { + + String key(); + + String value(); + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + Optional optional = findAnnotation(context.getElement(), SystemProperty.class); + + if (optional.isPresent()) { + SystemProperty systemProperty = optional.get(); + String key = systemProperty.key(); + String expected = systemProperty.value(); + String actual = System.getProperty(key); + + if (!Objects.equals(expected, actual)) { + return ConditionEvaluationResult.disabled( + String.format("System property [%s] has a value of [%s] instead of [%s]", key, actual, expected)); + } + } + + return ConditionEvaluationResult.enabled("@SystemProperty is not present"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java new file mode 100644 index 00000000..c2d18426 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.subpackage; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @since 5.9 + */ +public class SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { + + @BeforeEach + void beforeEach() { + fail(); + } + + @Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java new file mode 100644 index 00000000..a3b492b8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link OpenTest4JAndJUnit4AwareThrowableCollector}. + * + * @since 5.5.2 + */ +@TrackLogRecords +class OpenTest4JAndJUnit4AwareThrowableCollectorTests { + + @Test + void simulateJUnit4NotInTheClasspath(LogRecordListener listener) throws Throwable { + TestClassLoader classLoader = new TestClassLoader(true, false); + + doWithCustomClassLoader(classLoader, () -> { + // Ensure that our custom ClassLoader actually throws a ClassNotFoundException + // when attempting to load the AssumptionViolatedException class. + assertThrows(ClassNotFoundException.class, + () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); + + Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); + assertNotNull(ReflectionUtils.newInstance(clazz)); + + // @formatter:off + assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) + .isEqualTo( + "Failed to load class org.junit.internal.AssumptionViolatedException: " + + "only supporting org.opentest4j.TestAbortedException for aborted execution."); + // @formatter:on + }); + } + + @Test + void simulateHamcrestNotInTheClasspath(LogRecordListener listener) throws Throwable { + TestClassLoader classLoader = new TestClassLoader(false, true); + + doWithCustomClassLoader(classLoader, () -> { + // Ensure that our custom ClassLoader actually throws a NoClassDefFoundError + // when attempting to load the AssumptionViolatedException class. + assertThrows(NoClassDefFoundError.class, + () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); + + Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); + assertNotNull(ReflectionUtils.newInstance(clazz)); + + // @formatter:off + assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) + .isEqualTo( + "Failed to load class org.junit.internal.AssumptionViolatedException: " + + "only supporting org.opentest4j.TestAbortedException for aborted execution. " + + "Note that org.junit.internal.AssumptionViolatedException requires that Hamcrest is on the classpath."); + // @formatter:on + }); + } + + private void doWithCustomClassLoader(ClassLoader classLoader, Executable executable) throws Throwable { + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // We have to set our custom ClassLoader as the TCCL so that + // ReflectionUtils uses it (indirectly via ClassLoaderUtils). + Thread.currentThread().setContextClassLoader(classLoader); + + executable.execute(); + } + finally { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + + private static class TestClassLoader extends URLClassLoader { + + private static URL[] CLASSPATH_URLS = new URL[] { + OpenTest4JAndJUnit4AwareThrowableCollector.class.getProtectionDomain().getCodeSource().getLocation() }; + + private final boolean simulateJUnit4Missing; + private final boolean simulateHamcrestMissing; + + public TestClassLoader(boolean simulateJUnit4Missing, boolean simulateHamcrestMissing) { + super(CLASSPATH_URLS, getSystemClassLoader()); + this.simulateJUnit4Missing = simulateJUnit4Missing; + this.simulateHamcrestMissing = simulateHamcrestMissing; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + + // Load a new instance of the OpenTest4JAndJUnit4AwareThrowableCollector class + if (name.equals(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName())) { + return findClass(name); + } + + // Simulate that JUnit 4 is not in the classpath when loading AssumptionViolatedException + if (this.simulateJUnit4Missing && name.equals(AssumptionViolatedException.class.getName())) { + throw new ClassNotFoundException(AssumptionViolatedException.class.getName()); + } + + // Simulate that Hamcrest is not in the classpath when loading AssumptionViolatedException + if (this.simulateHamcrestMissing && name.equals(AssumptionViolatedException.class.getName())) { + throw new NoClassDefFoundError("org/hamcrest/SelfDescribing"); + } + + // Else + return super.loadClass(name); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt new file mode 100644 index 00000000..9170c22c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt @@ -0,0 +1,291 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals +import org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.platform.commons.util.ExceptionUtils +import org.opentest4j.AssertionFailedError +import java.time.Duration.ofMillis +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Unit tests for JUnit Jupiter [Assertions]. + * + * @since 5.5 + */ +internal class KotlinAssertTimeoutAssertionsTests { + + // --- executable ---------------------------------------------------------- + + @Test + fun assertTimeoutForExecutableThatCompletesBeforeTheTimeout() { + changed.get().set(false) + assertTimeout(ofMillis(500)) { changed.get().set(true) } + assertTrue(changed.get().get(), "should have executed in the same thread") + assertTimeout(ofMillis(500), "message") { } + assertTimeout(ofMillis(500), "message") { } + } + + @Test + fun assertTimeoutForExecutableThatThrowsAnException() { + val exception = assertThrows { + assertTimeout(ofMillis(500)) { + throw RuntimeException("not this time") + } + } + assertMessageEquals(exception, "not this time") + } + + @Test + fun assertTimeoutForExecutableThatThrowsAnAssertionFailedError() { + val exception = assertThrows { + assertTimeout(ofMillis(500)) { fail("enigma") } + } + assertMessageEquals(exception, "enigma") + } + + @Test + fun assertTimeoutForExecutableThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeout(ofMillis(10)) { this.nap() } + } + assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by") + } + + @Test + fun assertTimeoutWithMessageForExecutableThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeout(ofMillis(10), "Tempus Fugit") { this.nap() } + } + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") + } + + @Test + fun assertTimeoutWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeout(ofMillis(10), { "Tempus" + " " + "Fugit" }) { this.nap() } + } + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") + } + + // --- supplier ------------------------------------------------------------ + + @Test + fun assertTimeoutForSupplierThatCompletesBeforeTheTimeout() { + changed.get().set(false) + val result = assertTimeout(ofMillis(500)) { + changed.get().set(true) + "Tempus Fugit" + } + assertTrue(changed.get().get(), "should have executed in the same thread") + assertEquals("Tempus Fugit", result) + assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), "message") { "Tempus Fugit" }) + assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), { "message" }, { "Tempus Fugit" })) + } + + @Test + fun assertTimeoutForSupplierThatThrowsAnException() { + val exception = assertThrows { + assertTimeout(ofMillis(500)) { + ExceptionUtils.throwAsUncheckedException(RuntimeException("not this time")) + } + } + assertMessageEquals(exception, "not this time") + } + + @Test + fun assertTimeoutForSupplierThatThrowsAnAssertionFailedError() { + val exception = assertThrows { + assertTimeout(ofMillis(500)) { + fail("enigma") + } + } + assertMessageEquals(exception, "enigma") + } + + @Test + fun assertTimeoutForSupplierThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeout(ofMillis(10)) { + nap() + } + } + assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by") + } + + @Test + fun assertTimeoutWithMessageForSupplierThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeout(ofMillis(10), "Tempus Fugit") { + nap() + } + } + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") + } + + @Test + fun assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeout(ofMillis(10), { "Tempus" + " " + "Fugit" }) { + nap() + } + } + assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") + } + + // -- executable - preemptively --- + + @Test + fun assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { + changed.get().set(false) + assertTimeoutPreemptively(ofMillis(500)) { changed.get().set(true) } + assertFalse(changed.get().get(), "should have executed in a different thread") + assertTimeoutPreemptively(ofMillis(500), "message") {} + assertTimeoutPreemptively(ofMillis(500), { "message" }) {} + } + + @Test + fun assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { + val exception = assertThrows { + assertTimeoutPreemptively(ofMillis(500)) { throw RuntimeException("not this time") } + } + assertMessageEquals(exception, "not this time") + } + + @Test + fun assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { + val exception = assertThrows { + assertTimeoutPreemptively(ofMillis(500)) { fail("enigma") } + } + assertMessageEquals(exception, "enigma") + } + + @Test + fun assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeoutPreemptively(ofMillis(10)) { waitForInterrupt() } + } + assertMessageEquals(error, "execution timed out after 10 ms") + } + + @Test + fun assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeoutPreemptively(ofMillis(10), "Tempus Fugit") { waitForInterrupt() } + } + assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") + } + + @Test + fun assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeoutPreemptively(ofMillis(10), { "Tempus" + " " + "Fugit" }) { waitForInterrupt() } + } + assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") + } + + @Test + fun assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { + assertTimeoutPreemptively(ofMillis(500), { "Tempus" + " " + "Fugit" }) {} + } + + // -- supplier - preemptively --- + + @Test + fun assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { + changed.get().set(false) + val result = assertTimeoutPreemptively(ofMillis(500)) { + changed.get().set(true) + "Tempus Fugit" + } + assertFalse(changed.get().get(), "should have executed in a different thread") + assertEquals("Tempus Fugit", result) + assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), "message") { "Tempus Fugit" }) + assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), { "message" }) { "Tempus Fugit" }) + } + + @Test + fun assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { + val exception = assertThrows { + assertTimeoutPreemptively(ofMillis(500)) { + ExceptionUtils.throwAsUncheckedException(RuntimeException("not this time")) + } + } + assertMessageEquals(exception, "not this time") + } + + @Test + fun assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { + val exception = assertThrows { + assertTimeoutPreemptively(ofMillis(500)) { + fail("enigma") + } + } + assertMessageEquals(exception, "enigma") + } + + @Test + fun assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeoutPreemptively(ofMillis(10)) { + waitForInterrupt() + } + } + assertMessageEquals(error, "execution timed out after 10 ms") + } + + @Test + fun assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeoutPreemptively(ofMillis(10), "Tempus Fugit") { + waitForInterrupt() + } + } + assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") + } + + @Test + fun assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { + val error = assertThrows { + assertTimeoutPreemptively(ofMillis(10), { "Tempus" + " " + "Fugit" }) { + waitForInterrupt() + } + } + assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") + } + + /** + * Take a nap for 100 milliseconds. + */ + private fun nap() { + val start = System.currentTimeMillis() + // workaround for imprecise clocks (yes, Windows, I'm talking about you) + do { + Thread.sleep(100) + } while (System.currentTimeMillis() - start < 100) + } + + private fun waitForInterrupt() { + try { + CountDownLatch(1).await() + } catch (ignore: InterruptedException) { + // ignore + } + } + + companion object { + private val changed = ThreadLocal.withInitial { AtomicBoolean(false) } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt new file mode 100644 index 00000000..d283aad8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -0,0 +1,223 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals +import org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith +import org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DynamicContainer.dynamicContainer +import org.junit.jupiter.api.DynamicTest.dynamicTest +import org.opentest4j.AssertionFailedError +import org.opentest4j.MultipleFailuresError +import java.util.stream.Stream +import kotlin.reflect.KClass + +/** + * Unit tests for JUnit Jupiter [org.junit.jupiter.api] top-level assertion functions. + */ +class KotlinAssertionsTests { + + // Bonus: no null check tests as these get handled by the compiler! + + @Test + fun `assertAll with functions that do not throw exceptions`() { + assertAll(Stream.of({ assertTrue(true) }, { assertFalse(false) })) + assertAll("heading", Stream.of({ assertTrue(true) }, { assertFalse(false) })) + assertAll(setOf({ assertTrue(true) }, { assertFalse(false) })) + assertAll("heading", setOf({ assertTrue(true) }, { assertFalse(false) })) + assertAll({ assertTrue(true) }, { assertFalse(false) }) + assertAll("heading", { assertTrue(true) }, { assertFalse(false) }) + } + + @Test + fun `assertAll with functions that throw AssertionErrors`() { + val multipleFailuresError = assertThrows { + assertAll( + { assertFalse(true) }, + { assertFalse(true) } + ) + } + assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) + } + + @Test + fun `assertThrows and fail`() { + assertThrows { fail("message") } + assertThrows { fail("message", AssertionError()) } + assertThrows { fail("message", null) } + assertThrows("should fail") { fail({ "message" }) } + assertThrows({ "should fail" }) { fail(AssertionError()) } + assertThrows({ "should fail" }) { fail(null as Throwable?) } + } + + @Test + fun `expected context exception testing`() = runBlocking { + assertThrows("Should fail async") { + suspend { fail("Should fail async") }() + } + } + + @TestFactory + fun assertDoesNotThrow(): Stream = Stream.of( + dynamicContainer( + "succeeds when no exception thrown", + Stream.of( + dynamicTest("for no arguments variant") { + val actual = assertDoesNotThrow { 1 } + assertEquals(1, actual) + }, + dynamicTest("for no arguments variant (suspended)") { + runBlocking { + val actual = assertDoesNotThrow { suspend { 1 }() } + assertEquals(1, actual) + } + }, + dynamicTest("for message variant") { + val actual = assertDoesNotThrow("message") { 2 } + assertEquals(2, actual) + }, + dynamicTest("for message variant (suspended)") { + runBlocking { + val actual = assertDoesNotThrow("message") { suspend { 2 }() } + assertEquals(2, actual) + } + }, + dynamicTest("for message supplier variant") { + val actual = assertDoesNotThrow({ "message" }) { 3 } + assertEquals(3, actual) + }, + dynamicTest("for message supplier variant (suspended)") { + runBlocking { + val actual = assertDoesNotThrow({ "message" }) { suspend { 3 }() } + assertEquals(3, actual) + } + } + ) + ), + dynamicContainer( + "fails when an exception is thrown", + Stream.of( + dynamicTest("for no arguments variant") { + val exception = assertThrows { + assertDoesNotThrow { + fail("fail") + } + } + assertMessageEquals( + exception, + "Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" + ) + }, + dynamicTest("for no arguments variant (suspended)") { + runBlocking { + val exception = assertThrows { + assertDoesNotThrow { + suspend { fail("fail") }() + } + } + assertMessageEquals( + exception, + "Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" + ) + } + }, + dynamicTest("for message variant") { + val exception = assertThrows { + assertDoesNotThrow("Does not throw") { + fail("fail") + } + } + assertMessageEquals( + exception, + "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" + ) + }, + dynamicTest("for message variant (suspended)") { + runBlocking { + val exception = assertThrows { + assertDoesNotThrow("Does not throw") { + suspend { fail("fail") }() + } + } + assertMessageEquals( + exception, + "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" + ) + } + }, + dynamicTest("for message supplier variant") { + val exception = assertThrows { + assertDoesNotThrow({ "Does not throw" }) { + fail("fail") + } + } + assertMessageEquals( + exception, + "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" + ) + }, + dynamicTest("for message supplier variant (suspended)") { + runBlocking { + val exception = assertThrows { + assertDoesNotThrow({ "Does not throw" }) { + suspend { fail("fail") }() + } + } + assertMessageEquals( + exception, + "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" + ) + } + } + ) + ) + ) + + @Test + fun `assertAll with stream of functions that throw AssertionErrors`() { + val multipleFailuresError = assertThrows("Should have thrown multiple errors") { + assertAll(Stream.of({ assertFalse(true) }, { assertFalse(true) })) + } + assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) + } + + @Test + fun `assertAll with collection of functions that throw AssertionErrors`() { + val multipleFailuresError = assertThrows("Should have thrown multiple errors") { + assertAll(setOf({ assertFalse(true) }, { assertFalse(true) })) + } + assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) + } + + @Test + fun `assertThrows with function that does not throw an exception`() { + val assertionMessage = "This will not throw an exception" + val error = assertThrows("assertThrows did not throw the correct exception") { + assertThrows(assertionMessage) { } + // This should never execute: + expectAssertionFailedError() + } + assertMessageStartsWith(error, assertionMessage) + } + + companion object { + fun assertExpectedExceptionTypes( + multipleFailuresError: MultipleFailuresError, + vararg exceptionTypes: KClass + ) = + AssertAllAssertionsTests.assertExpectedExceptionTypes( + multipleFailuresError, *exceptionTypes.map { it.java }.toTypedArray() + ) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt new file mode 100644 index 00000000..00023d0a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +import org.junit.jupiter.api.AssertEquals.assertEquals +import org.junit.jupiter.api.AssertionTestUtils.assertEmptyMessage +import org.junit.jupiter.api.AssertionTestUtils.assertMessageContains +import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals +import org.opentest4j.AssertionFailedError +import java.util.stream.Stream + +class KotlinFailAssertionsTests { + + @Test + fun `fail with string`() { + val message = "test" + val ex = assertThrows { + fail(message) + } + assertMessageEquals(ex, message) + } + + @Test + fun `fail with message supplier`() { + val message = "test" + val ex = assertThrows { + fail { message } + } + assertMessageEquals(ex, message) + } + + @Test + fun `fail with null string`() { + val ex = assertThrows { + fail(null as String?) + } + assertEmptyMessage(ex) + } + + @Test + fun `fail with null message supplier`() { + val ex = assertThrows { + fail(null as (() -> String)?) + } + assertEmptyMessage(ex) + } + + @Test + fun `fail with string and throwable`() { + val message = "message" + val throwableCause = "cause" + val ex = assertThrows { + fail(message, Throwable(throwableCause)) + } + assertMessageEquals(ex, message) + val cause = ex.cause + assertMessageContains(cause, throwableCause) + } + + @Test + fun `fail with throwable`() { + val throwableCause = "cause" + val ex = assertThrows { + fail(Throwable(throwableCause)) + } + assertEmptyMessage(ex) + val cause = ex.cause + assertMessageContains(cause, throwableCause) + } + + @Test + fun `fail with string and null throwable`() { + val message = "message" + val ex = assertThrows { + fail(message, null) + } + assertMessageEquals(ex, message) + if (ex.cause != null) { + throw AssertionError("Cause should have been null") + } + } + + @Test + fun `fail with null string and throwable`() { + val throwableCause = "cause" + val ex = assertThrows { + fail(null, Throwable(throwableCause)) + } + assertEmptyMessage(ex) + val cause = ex.cause + assertMessageContains(cause, throwableCause) + } + + @Test + fun `fail usable as a stream expression`() { + val count = Stream.empty() + .peek { _ -> fail("peek should never be called") } + .filter { _ -> fail("filter should never be called", Throwable("cause")) } + .map { _ -> fail(Throwable("map should never be called")) } + .sorted { _, _ -> fail { "sorted should never be called" } } + .count() + assertEquals(0L, count) + } + + @Test + fun `fail usable as a sequence expression`() { + val count = emptyList() + .asSequence() + .onEach { _ -> fail("peek should never be called") } + .filter { _ -> fail("filter should never be called", Throwable("cause")) } + .map { _ -> fail(Throwable("map should never be called")) } + .count() + assertEquals(0, count) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt new file mode 100644 index 00000000..1b919a9f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.engine.kotlin + +import org.junit.jupiter.api.Test + +class ArbitraryNamingKotlinTestCase { + companion object { + @JvmField + val METHOD_NAME = "\uD83E\uDD86 ~|~test with a really, (really) terrible name & that needs to be changed!~|~" + } + + @Suppress("DANGEROUS_CHARACTERS") + @Test + fun `🦆 ~|~test with a really, (really) terrible name & that needs to be changed!~|~`() { } + + @Test + fun `test name ends with parentheses()`() { } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt new file mode 100644 index 00000000..f5c4e5b8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.engine.kotlin + +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS + +@TestInstance(PER_CLASS) +class InstancePerClassKotlinTestCase { + + companion object { + @JvmField + val TEST_INSTANCES: MutableMap> = HashMap() + } + + @BeforeAll + fun beforeAll() { + increment("beforeAll") + } + + @BeforeEach + fun beforeEach() { + increment("beforeEach") + } + + @AfterEach + fun afterEach() { + increment("afterEach") + } + + @AfterAll + fun afterAll() { + increment("afterAll") + } + + @Test + fun firstTest() { + increment("test") + } + + @Test + fun secondTest() { + increment("test") + } + + private fun increment(name: String) { + TEST_INSTANCES.computeIfAbsent(this, { _ -> HashMap() }) + .compute(name, { _, oldValue -> (oldValue ?: 0) + 1 }) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt new file mode 100644 index 00000000..b5845f9a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.engine.kotlin + +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class InstancePerMethodKotlinTestCase { + + companion object { + @JvmField + val TEST_INSTANCES: MutableMap> = LinkedHashMap() + + @JvmStatic + @BeforeAll + fun beforeAll() { + increment(this, "beforeAll") + } + + @JvmStatic + @AfterAll + fun afterAll() { + increment(this, "afterAll") + } + + private fun increment(instance: Any, name: String) { + TEST_INSTANCES.computeIfAbsent(instance, { _ -> LinkedHashMap() }) + .compute(name, { _, oldValue -> (oldValue ?: 0) + 1 }) + } + } + + @BeforeEach + fun beforeEach() { + increment(this, "beforeEach") + } + + @AfterEach + fun afterEach() { + increment(this, "afterEach") + } + + @Test + fun firstTest() { + increment(this, "test") + } + + @Test + fun secondTest() { + increment(this, "test") + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000..758c3e55 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +org.junit.jupiter.engine.extension.ServiceLoaderExtension diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..d734a7fa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java new file mode 100644 index 00000000..e80608fb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.discovery; + +import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; + +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; +import org.junit.platform.engine.UniqueId; + +/** + * Test data builder for unique IDs for JupiterTestEngine. + * + * Used to decouple tests from concrete unique ID strings. + * + * @since 5.0 + */ +public class JupiterUniqueIdBuilder { + + public static UniqueId uniqueIdForClass(Class clazz) { + UniqueId containerId = engineId(); + if (isInnerClass(clazz)) { + containerId = uniqueIdForClass(clazz.getEnclosingClass()); + return containerId.append(NestedClassTestDescriptor.SEGMENT_TYPE, clazz.getSimpleName()); + } + return containerId.append(ClassTestDescriptor.SEGMENT_TYPE, clazz.getName()); + } + + public static UniqueId uniqueIdForTopLevelClass(String className) { + return engineId().append(ClassTestDescriptor.SEGMENT_TYPE, className); + } + + public static UniqueId uniqueIdForMethod(Class clazz, String methodPart) { + return uniqueIdForClass(clazz).append(TestMethodTestDescriptor.SEGMENT_TYPE, methodPart); + } + + public static UniqueId uniqueIdForTestFactoryMethod(Class clazz, String methodPart) { + return uniqueIdForClass(clazz).append(TestFactoryTestDescriptor.SEGMENT_TYPE, methodPart); + } + + public static UniqueId uniqueIdForTestTemplateMethod(Class clazz, String methodPart) { + return uniqueIdForClass(clazz).append(TestTemplateTestDescriptor.SEGMENT_TYPE, methodPart); + } + + public static UniqueId appendTestTemplateInvocationSegment(UniqueId parentId, int index) { + return parentId.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); + } + + public static UniqueId engineId() { + return UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md new file mode 100644 index 00000000..730e2c8e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md @@ -0,0 +1,19 @@ +# Module junit-jupiter-migrationsupport + +This module provides support for JUnit 4 rules within JUnit Jupiter. +Currently, this support is limited to subclasses of the ```org.junit.rules.Verifier``` +and ```org.junit.rules.ExternalResource``` rules of JUnit 4, respectively. + +Please note that a general support for arbitrary ```org.junit.rules.TestRule``` +implementations is not possible within the JUnit Jupiter extension model. + +The main purpose of this module is to facilitate the migration of large +JUnit 4 codebases containing such JUnit 4 rules by minimizing the effort +needed to run such legacy tests under JUnit 5. +By using one of the two provided class-level extensions on a test class +such rules in legacy code bases can be left unchanged +including the JUnit 4 rule import statements. + +However, if you intend to develop a *new* extension for +JUnit 5 please use the new extension model of JUnit Jupiter instead +of the rule-based model of JUnit 4. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts new file mode 100644 index 00000000..ff570cc7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts @@ -0,0 +1,37 @@ +plugins { + `java-library-conventions` + `junit4-compatibility` + `testing-conventions` +} + +description = "JUnit Jupiter Migration Support" + +dependencies { + api(platform(projects.junitBom)) + api(libs.junit4) + api(projects.junitJupiterApi) + + compileOnlyApi(libs.apiguardian) + + testImplementation(projects.junitJupiterEngine) + testImplementation(projects.junitPlatformLauncher) + testImplementation(projects.junitPlatformSuiteEngine) + testImplementation(projects.junitPlatformTestkit) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks.jar { + bundle { + bnd(""" + # Import JUnit4 packages with a version + Import-Package: \ + ${extra["importAPIGuardian"]},\ + org.junit;version="[${libs.versions.junit4Min.get()},5)",\ + org.junit.platform.commons.logging;status=INTERNAL,\ + org.junit.rules;version="[${libs.versions.junit4Min.get()},5)",\ + * + """) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java new file mode 100644 index 00000000..a5ee8bc8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.migrationsupport.conditions.IgnoreCondition; +import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; +import org.junit.jupiter.migrationsupport.rules.ExpectedExceptionSupport; +import org.junit.jupiter.migrationsupport.rules.ExternalResourceSupport; +import org.junit.jupiter.migrationsupport.rules.VerifierSupport; + +/** + * {@code EnableJUnit4MigrationSupport} is a class-level annotation that + * enables all JUnit 4 migration support within JUnit Jupiter. + * + *

Specifically, this annotation registers all extensions supported by + * {@link EnableRuleMigrationSupport @EnableRuleMigrationSupport} and provides + * support for JUnit 4's {@link org.junit.Ignore @Ignore} annotation for + * disabling test classes and test methods. + * + *

Technically speaking, {@code @EnableJUnit4MigrationSupport} is a composed + * annotation which registers all of the following migration extensions: + * {@link VerifierSupport}, {@link ExternalResourceSupport}, + * {@link ExpectedExceptionSupport}, and {@link IgnoreCondition}. Note, however, + * that you can optionally register one or more of these extensions explicitly + * without the use of this composed annotation. + * + * @since 5.4 + * @see ExternalResourceSupport + * @see VerifierSupport + * @see ExpectedExceptionSupport + * @see IgnoreCondition + * @see EnableRuleMigrationSupport + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@API(status = STABLE, since = "5.7") +@EnableRuleMigrationSupport +@ExtendWith(IgnoreCondition.class) +public @interface EnableJUnit4MigrationSupport { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java new file mode 100644 index 00000000..477cedcb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.conditions; + +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.reflect.AnnotatedElement; + +import org.apiguardian.api.API; +import org.junit.Ignore; +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.util.StringUtils; + +/** + * {@link ExecutionCondition} that supports JUnit 4's {@link Ignore @Ignore} + * annotation. + * + * @since 5.4 + * @see org.junit.Ignore @Ignore + * @see org.junit.jupiter.api.Disabled @Disabled + * @see #evaluateExecutionCondition(ExtensionContext) + * @see org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport + */ +@API(status = STABLE, since = "5.7") +public class IgnoreCondition implements ExecutionCondition { + + private static final ConditionEvaluationResult ENABLED = // + ConditionEvaluationResult.enabled("@org.junit.Ignore is not present"); + + /** + * Containers/tests are disabled if {@link Ignore @Ignore} is present on + * the test class or method. + */ + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + AnnotatedElement element = context.getElement().orElse(null); + return findAnnotation(element, Ignore.class) // + .map(annotation -> toResult(element, annotation)) // + .orElse(ENABLED); + } + + private ConditionEvaluationResult toResult(AnnotatedElement element, Ignore annotation) { + String value = annotation.value(); + String reason = StringUtils.isNotBlank(value) ? value : element + " is disabled via @org.junit.Ignore"; + return ConditionEvaluationResult.disabled(reason); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java new file mode 100644 index 00000000..530ff605 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java @@ -0,0 +1,7 @@ +/** + * Extensions which provide support for conditional test execution features of + * JUnit 4 (e.g., the {@link org.junit.Ignore @Ignore} annotation) within JUnit + * Jupiter. + */ + +package org.junit.jupiter.migrationsupport.conditions; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java new file mode 100644 index 00000000..236a13fd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java @@ -0,0 +1,5 @@ +/** + * Support for migrating from JUnit 4 to JUnit Jupiter. + */ + +package org.junit.jupiter.migrationsupport; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java new file mode 100644 index 00000000..c215ecc8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * This class-level annotation enables native JUnit 4 rule support + * within JUnit Jupiter. + * + *

Currently, rules of type {@code Verifier}, {@code ExternalResource}, + * and {@code ExpectedException} rules are supported. + * + *

{@code @EnableRuleMigrationSupport} is a composed annotation which + * enables all supported extensions: {@link VerifierSupport}, + * {@link ExternalResourceSupport}, and {@link ExpectedExceptionSupport}. + * + * @since 5.0 + * @see ExternalResourceSupport + * @see VerifierSupport + * @see ExpectedExceptionSupport + * @see org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@API(status = STABLE, since = "5.7") +@ExtendWith(ExternalResourceSupport.class) +@ExtendWith(VerifierSupport.class) +@ExtendWith(ExpectedExceptionSupport.class) +public @interface EnableRuleMigrationSupport { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java new file mode 100644 index 00000000..538a053b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.migrationsupport.rules.adapter.ExpectedExceptionAdapter; +import org.junit.rules.ExpectedException; + +/** + * This {@code Extension} provides native support for the + * {@link ExpectedException} rule from JUnit 4. + * + *

By using this class-level extension on a test class, + * {@code ExpectedException} can continue to be used. + * + *

However, you should rather switch to + * {@link org.junit.jupiter.api.Assertions#assertThrows} for new code. + * + * @since 5.0 + * @see org.junit.jupiter.api.Assertions#assertThrows + * @see org.junit.rules.ExpectedException + * @see org.junit.rules.TestRule + * @see org.junit.Rule + */ +@API(status = STABLE, since = "5.7") +public class ExpectedExceptionSupport implements AfterEachCallback, TestExecutionExceptionHandler { + + private static final String EXCEPTION_WAS_HANDLED = "exceptionWasHandled"; + + private final TestRuleSupport support = new TestRuleSupport(ExpectedExceptionAdapter::new, ExpectedException.class); + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + getStore(context).put(EXCEPTION_WAS_HANDLED, TRUE); + this.support.handleTestExecutionException(context, throwable); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + boolean handled = getStore(context).getOrComputeIfAbsent(EXCEPTION_WAS_HANDLED, key -> FALSE, Boolean.class); + if (!handled) { + this.support.afterEach(context); + } + } + + private Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass(), context.getUniqueId())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java new file mode 100644 index 00000000..f31d6bc1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.migrationsupport.rules.adapter.ExternalResourceAdapter; +import org.junit.rules.ExternalResource; + +/** + * This {@code Extension} provides native support for subclasses of + * the {@link ExternalResource} rule from JUnit 4. + * + *

{@code @Rule}-annotated fields as well as methods are supported. + * + *

By using this class-level extension on a test class such + * {@code ExternalResource} implementations in legacy code bases + * can be left unchanged including the JUnit 4 rule import statements. + * + *

However, if you intend to develop a new extension for + * JUnit 5 please use the new extension model of JUnit Jupiter instead + * of the rule-based model of JUnit 4. + * + * @since 5.0 + * @see org.junit.rules.ExternalResource + * @see org.junit.rules.TestRule + * @see org.junit.Rule + */ +@API(status = STABLE, since = "5.7") +public class ExternalResourceSupport implements BeforeEachCallback, AfterEachCallback { + + private final TestRuleSupport support = new TestRuleSupport(ExternalResourceAdapter::new, ExternalResource.class); + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + this.support.beforeEach(context); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + this.support.afterEach(context); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java new file mode 100644 index 00000000..99a24d9b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java @@ -0,0 +1,168 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static java.util.Collections.unmodifiableList; +import static org.junit.platform.commons.util.AnnotationUtils.findPublicAnnotatedFields; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.junit.Rule; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.migrationsupport.rules.adapter.AbstractTestRuleAdapter; +import org.junit.jupiter.migrationsupport.rules.adapter.GenericBeforeAndAfterAdvice; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedField; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMethod; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.rules.TestRule; + +/** + * @since 5.0 + */ +class TestRuleSupport implements BeforeEachCallback, TestExecutionExceptionHandler, AfterEachCallback { + + private final Class ruleType; + private final Function adapterGenerator; + + TestRuleSupport(Function adapterGenerator, + Class ruleType) { + + this.adapterGenerator = adapterGenerator; + this.ruleType = ruleType; + } + + /** + * @see org.junit.runners.BlockJUnit4ClassRunner#withRules + * @see org.junit.rules.RunRules + */ + @SuppressWarnings("JavadocReference") + private List findRuleAnnotatedMembers(Object testInstance) { + List result = new ArrayList<>(); + // @formatter:off + // Instantiate rules from methods by calling them + findAnnotatedMethods(testInstance).stream() + .map(method -> new TestRuleAnnotatedMethod(testInstance, method)) + .forEach(result::add); + // Fields are already instantiated because we have a test instance + findAnnotatedFields(testInstance).stream() + .map(field -> new TestRuleAnnotatedField(testInstance, field)) + .forEach(result::add); + // @formatter:on + // Due to how rules are applied (see RunRules), the last rule gets called first. + // Rules from fields get called before those from methods. + // Thus, we first add methods and then fields and reverse the list in the end. + Collections.reverse(result); + return unmodifiableList(result); + } + + private List findAnnotatedMethods(Object testInstance) { + Predicate isRuleMethod = method -> isAnnotated(method, Rule.class); + Predicate hasCorrectReturnType = method -> TestRule.class.isAssignableFrom(method.getReturnType()); + + return findMethods(testInstance.getClass(), isRuleMethod.and(hasCorrectReturnType)); + } + + private List findAnnotatedFields(Object testInstance) { + return findPublicAnnotatedFields(testInstance.getClass(), TestRule.class, Rule.class); + } + + @Override + public void beforeEach(ExtensionContext context) { + invokeAppropriateMethodOnRuleAnnotatedMembers(context, false, GenericBeforeAndAfterAdvice::before); + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + int numRuleAnnotatedMembers = invokeAppropriateMethodOnRuleAnnotatedMembers(context, true, + advice -> advice.handleTestExecutionException(throwable)); + + // If no appropriate @Rule annotated members were discovered, we then + // have to rethrow the exception in order not to silently swallow it. + // Fixes bug: https://github.com/junit-team/junit5/issues/1069 + if (numRuleAnnotatedMembers == 0) { + throw throwable; + } + } + + @Override + public void afterEach(ExtensionContext context) { + invokeAppropriateMethodOnRuleAnnotatedMembers(context, true, GenericBeforeAndAfterAdvice::after); + } + + /** + * @return the number of appropriate rule-annotated members that were discovered + */ + private int invokeAppropriateMethodOnRuleAnnotatedMembers(ExtensionContext context, boolean reverseOrder, + AdviceInvoker adviceInvoker) { + + List ruleAnnotatedMembers = getRuleAnnotatedMembers(context); + if (reverseOrder) { + Collections.reverse(ruleAnnotatedMembers); + } + + AtomicInteger counter = new AtomicInteger(); + + // @formatter:off + ruleAnnotatedMembers.stream() + .filter(annotatedMember -> this.ruleType.isInstance(annotatedMember.getTestRule())) + .map(this.adapterGenerator) + .forEach(advice -> { + adviceInvoker.invokeAndMaskCheckedExceptions(advice); + counter.incrementAndGet(); + }); + // @formatter:on + + return counter.get(); + } + + /** + * @return a modifiable copy of the list of rule-annotated members + */ + @SuppressWarnings("unchecked") + private List getRuleAnnotatedMembers(ExtensionContext context) { + Object testInstance = context.getRequiredTestInstance(); + Namespace namespace = Namespace.create(TestRuleSupport.class, context.getRequiredTestClass()); + // @formatter:off + return new ArrayList<>(context.getStore(namespace) + .getOrComputeIfAbsent("rule-annotated-members", key -> findRuleAnnotatedMembers(testInstance), List.class)); + // @formatter:on + } + + @FunctionalInterface + private interface AdviceInvoker { + + default void invokeAndMaskCheckedExceptions(GenericBeforeAndAfterAdvice advice) { + try { + invoke(advice); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(t); + } + } + + void invoke(GenericBeforeAndAfterAdvice advice) throws Throwable; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java new file mode 100644 index 00000000..dca4784f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.migrationsupport.rules.adapter.VerifierAdapter; +import org.junit.rules.Verifier; + +/** + * This {@code Extension} provides native support for subclasses of + * the {@link Verifier} rule from JUnit 4. + * + *

{@code @Rule}-annotated fields as well as methods are supported. + * + *

By using this class-level extension on a test class such + * {@code Verifier} implementations in legacy code bases + * can be left unchanged including the JUnit 4 rule import statements. + * + *

However, if you intend to develop a new extension for + * JUnit 5 please use the new extension model of JUnit Jupiter instead + * of the rule-based model of JUnit 4. + * + * @since 5.0 + * @see org.junit.rules.Verifier + * @see org.junit.rules.TestRule + * @see org.junit.Rule + */ +@API(status = STABLE, since = "5.7") +public class VerifierSupport implements AfterEachCallback { + + private final TestRuleSupport support = new TestRuleSupport(VerifierAdapter::new, Verifier.class); + + @Override + public void afterEach(ExtensionContext context) throws Exception { + this.support.afterEach(context); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java new file mode 100644 index 00000000..223f609e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.adapter; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.findMethod; +import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; + +import java.lang.reflect.Method; + +import org.apiguardian.api.API; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.rules.TestRule; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public abstract class AbstractTestRuleAdapter implements GenericBeforeAndAfterAdvice { + + private final TestRule target; + + public AbstractTestRuleAdapter(TestRuleAnnotatedMember annotatedMember, Class adapteeClass) { + this.target = annotatedMember.getTestRule(); + Preconditions.condition(adapteeClass.isAssignableFrom(this.target.getClass()), + () -> adapteeClass + " is not assignable from " + this.target.getClass()); + } + + protected Object executeMethod(String name) { + return executeMethod(name, new Class[0]); + } + + protected Object executeMethod(String methodName, Class[] parameterTypes, Object... arguments) { + Method method = findMethod(this.target.getClass(), methodName, parameterTypes).orElseThrow( + () -> new JUnitException(String.format("Failed to find method %s(%s) in class %s", methodName, + ClassUtils.nullSafeToString(parameterTypes), this.target.getClass().getName()))); + + return invokeMethod(method, this.target, arguments); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java new file mode 100644 index 00000000..1b1971b2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.adapter; + +import static java.lang.Boolean.TRUE; +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; +import org.junit.rules.ExpectedException; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class ExpectedExceptionAdapter extends AbstractTestRuleAdapter { + + public ExpectedExceptionAdapter(TestRuleAnnotatedMember annotatedMember) { + super(annotatedMember, ExpectedException.class); + } + + @Override + public void handleTestExecutionException(Throwable cause) throws Throwable { + executeMethod("handleException", new Class[] { Throwable.class }, cause); + } + + @Override + public void after() { + if (TRUE.equals(executeMethod("isAnyExceptionExpected"))) { + executeMethod("failDueToMissingException"); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java new file mode 100644 index 00000000..b6905f42 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.adapter; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; +import org.junit.rules.ExternalResource; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class ExternalResourceAdapter extends AbstractTestRuleAdapter { + + public ExternalResourceAdapter(TestRuleAnnotatedMember annotatedMember) { + super(annotatedMember, ExternalResource.class); + } + + @Override + public void before() { + executeMethod("before"); + } + + @Override + public void after() { + executeMethod("after"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java new file mode 100644 index 00000000..ad384ce9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.adapter; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public interface GenericBeforeAndAfterAdvice { + + default void before() { + } + + default void handleTestExecutionException(Throwable cause) throws Throwable { + } + + default void after() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java new file mode 100644 index 00000000..ec00fc82 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.adapter; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; +import org.junit.rules.Verifier; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public class VerifierAdapter extends AbstractTestRuleAdapter { + + public VerifierAdapter(TestRuleAnnotatedMember annotatedMember) { + super(annotatedMember, Verifier.class); + } + + @Override + public void after() { + executeMethod("verify"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java new file mode 100644 index 00000000..d69bee97 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java @@ -0,0 +1,5 @@ +/** + * Simple wrappers for JUnit 4 rules to overcome visibility limitations of the JUnit 4 implementations. + */ + +package org.junit.jupiter.migrationsupport.rules.adapter; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java new file mode 100644 index 00000000..c7dcdd68 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.member; + +import org.junit.rules.TestRule; + +/** + * @since 5.0 + */ +abstract class AbstractTestRuleAnnotatedMember implements TestRuleAnnotatedMember { + + private final TestRule testRule; + + AbstractTestRuleAnnotatedMember(TestRule testRule) { + this.testRule = testRule; + } + + @Override + public TestRule getTestRule() { + return this.testRule; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java new file mode 100644 index 00000000..7a8dd0c5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.member; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; + +import java.lang.reflect.Field; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.rules.TestRule; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.1") +public final class TestRuleAnnotatedField extends AbstractTestRuleAnnotatedMember { + + public TestRuleAnnotatedField(Object testInstance, Field field) { + super(retrieveTestRule(testInstance, field)); + } + + private static TestRule retrieveTestRule(Object testInstance, Field field) { + try { + return (TestRule) makeAccessible(field).get(testInstance); + } + catch (IllegalAccessException exception) { + throw ExceptionUtils.throwAsUncheckedException(exception); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java new file mode 100644 index 00000000..0004b905 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.member; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.rules.TestRule; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public interface TestRuleAnnotatedMember { + + TestRule getTestRule(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java new file mode 100644 index 00000000..75fc5ad6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules.member; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.reflect.Method; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.rules.TestRule; + +/** + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.1") +public final class TestRuleAnnotatedMethod extends AbstractTestRuleAnnotatedMember { + + public TestRuleAnnotatedMethod(Object testInstance, Method method) { + super((TestRule) ReflectionUtils.invokeMethod(method, testInstance)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java new file mode 100644 index 00000000..85ce964a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java @@ -0,0 +1,5 @@ +/** + * Abstractions for members which can be targets of JUnit 4 rule annotations. + */ + +package org.junit.jupiter.migrationsupport.rules.member; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java new file mode 100644 index 00000000..752ebb7e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java @@ -0,0 +1,5 @@ +/** + * Extensions which provide (limited) support for JUnit 4 rules within JUnit Jupiter. + */ + +package org.junit.jupiter.migrationsupport.rules; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java new file mode 100644 index 00000000..3ace009e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Support for migrating from JUnit 4 to JUnit Jupiter. + * + * @since 5.0 + */ +module org.junit.jupiter.migrationsupport { + requires transitive junit; // 4 + requires static transitive org.apiguardian.api; + requires transitive org.junit.jupiter.api; + requires org.junit.platform.commons; + + exports org.junit.jupiter.migrationsupport; + exports org.junit.jupiter.migrationsupport.conditions; + exports org.junit.jupiter.migrationsupport.rules; + exports org.junit.jupiter.migrationsupport.rules.adapter; + exports org.junit.jupiter.migrationsupport.rules.member; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java new file mode 100644 index 00000000..f37c15b8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * Test suite for JUnit Jupiter migration support. + * + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @since 5.0 + */ +@Suite +@SelectPackages("org.junit.jupiter.migrationsupport") +@IncludeClassNamePatterns(".*Tests?") +@IncludeEngines("junit-jupiter") +class JupiterMigrationSupportTestSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java new file mode 100644 index 00000000..3befccdf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.conditions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport; + +/** + * Empirical integration tests for JUnit 4's {@link Ignore @Ignore} support in + * JUnit Jupiter, covering the {@link IgnoreCondition} and + * {@link EnableJUnit4MigrationSupport @EnableJUnit4MigrationSupport}. + * + * @since 5.4 + * @see IgnoreConditionTests + */ +class IgnoreAnnotationIntegrationTests { + + @Nested + @ExtendWith(IgnoreCondition.class) + class ExplicitIgnoreConditionRegistration extends BaseNestedTestCase { + } + + @Nested + @EnableJUnit4MigrationSupport + class ImplicitIgnoreConditionRegistration extends BaseNestedTestCase { + } + + @TestInstance(PER_CLASS) + private static abstract class BaseNestedTestCase { + + private static List tests = new ArrayList<>(); + + @BeforeAll + void clearTracking() { + tests.clear(); + } + + @AfterAll + void verifyTracking() { + assertThat(tests).containsExactly("notIgnored"); + } + + @BeforeEach + void track(TestInfo testInfo) { + tests.add(testInfo.getTestMethod().get().getName()); + } + + @Test + @Ignore + void ignored() { + fail("This method should have been disabled via @Ignore"); + } + + @Test + // @Ignore + void notIgnored() { + /* no-op */ + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java new file mode 100644 index 00000000..95be5fb3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java @@ -0,0 +1,150 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.conditions; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; + +import org.junit.Ignore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; + +/** + * Integration tests for JUnit 4's {@link Ignore @Ignore} support in JUnit + * Jupiter provided by the {@link IgnoreCondition}. + * + * @since 5.4 + * @see IgnoreAnnotationIntegrationTests + */ +class IgnoreConditionTests { + + @Test + void ignoredTestClassWithDefaultMessage() { + Class testClass = IgnoredClassWithDefaultMessageTestCase.class; + + // @formatter:off + executeTestsForClass(testClass).allEvents().assertEventsMatchExactly( + event(engine(), started()), + event(container(testClass), skippedWithReason(testClass + " is disabled via @org.junit.Ignore")), + event(engine(), finishedSuccessfully()) + ); + // @formatter:on + } + + @Test + void ignoredTestClassWithCustomMessage() { + Class testClass = IgnoredClassWithCustomMessageTestCase.class; + + // @formatter:off + executeTestsForClass(testClass).allEvents().assertEventsMatchExactly( + event(engine(), started()), + event(container(testClass), skippedWithReason("Ignored Class")), + event(engine(), finishedSuccessfully()) + ); + // @formatter:on + } + + @Test + void ignoredAndNotIgnoredTestMethods() { + EngineExecutionResults executionResults = executeTestsForClass(IgnoredMethodsTestCase.class); + Events containers = executionResults.containerEvents(); + Events tests = executionResults.testEvents(); + + // executionResults.allEvents().debug(); + // executionResults.allEvents().debug(System.err); + + // containers.debug(); + + // tests.debug(System.err); + // tests.debug(); + // tests.skipped().debug(); + // tests.started().debug(); + // tests.succeeded().debug(); + + // executionResults.allEvents().executions().debug(); + // containers.executions().debug(); + // tests.executions().debug(); + + executionResults.allEvents().executions().assertThatExecutions().hasSize(5); + containers.executions().assertThatExecutions().hasSize(2); + tests.executions().assertThatExecutions().hasSize(3); + + // @formatter:off + // tests.debug().assertEventsMatchExactly( + tests.assertEventsMatchExactly( + event(test("ignoredWithCustomMessage"), skippedWithReason("Ignored Method")), + event(test("notIgnored"), started()), + event(test("notIgnored"), finishedSuccessfully()), + event(test("ignoredWithDefaultMessage"), skippedWithReason( + reason -> reason.endsWith("ignoredWithDefaultMessage() is disabled via @org.junit.Ignore"))) + ); + // @formatter:on + } + + private EngineExecutionResults executeTestsForClass(Class testClass) { + return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()); + } + + // ------------------------------------------------------------------------- + + @ExtendWith(IgnoreCondition.class) + @Ignore + static class IgnoredClassWithDefaultMessageTestCase { + + @Test + void ignoredBecauseClassIsIgnored() { + /* no-op */ + } + } + + @ExtendWith(IgnoreCondition.class) + @Ignore("Ignored Class") + static class IgnoredClassWithCustomMessageTestCase { + + @Test + void ignoredBecauseClassIsIgnored() { + /* no-op */ + } + } + + @ExtendWith(IgnoreCondition.class) + static class IgnoredMethodsTestCase { + + @Test + void notIgnored() { + /* no-op */ + } + + @Test + @Ignore + void ignoredWithDefaultMessage() { + fail("This method should have been disabled via @Ignore"); + } + + @Test + @Ignore("Ignored Method") + void ignoredWithCustomMessage() { + fail("This method should have been disabled via @Ignore"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java new file mode 100644 index 00000000..4829db6d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.migrationsupport.rules.adapter.AbstractTestRuleAdapter; +import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.rules.ErrorCollector; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestRule; +import org.junit.rules.Verifier; + +/** + * @since 5.0 + */ +public class AbstractTestRuleAdapterTests { + + @Test + void constructionWithAssignableArgumentsIsSuccessful() { + new TestableTestRuleAdapter(new SimpleRuleAnnotatedMember(new ErrorCollector()), Verifier.class); + } + + @Test + void constructionWithUnassignableArgumentsFails() { + PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, + () -> new TestableTestRuleAdapter(new SimpleRuleAnnotatedMember(new TemporaryFolder()), Verifier.class)); + + assertEquals(exception.getMessage(), + "class org.junit.rules.Verifier is not assignable from class org.junit.rules.TemporaryFolder"); + } + + @Test + void exceptionsDuringMethodLookupAreWrappedAndThrown() { + AbstractTestRuleAdapter adapter = new AbstractTestRuleAdapter( + new SimpleRuleAnnotatedMember(new ErrorCollector()), Verifier.class) { + + @Override + public void before() { + super.executeMethod("foo"); + } + }; + + JUnitException exception = assertThrows(JUnitException.class, adapter::before); + + assertEquals(exception.getMessage(), "Failed to find method foo() in class org.junit.rules.ErrorCollector"); + } + + private static class TestableTestRuleAdapter extends AbstractTestRuleAdapter { + + TestableTestRuleAdapter(TestRuleAnnotatedMember annotatedMember, Class adapteeClass) { + super(annotatedMember, adapteeClass); + } + } + + private static class SimpleRuleAnnotatedMember implements TestRuleAnnotatedMember { + + private final TestRule testRule; + + SimpleRuleAnnotatedMember(TestRule testRule) { + this.testRule = testRule; + } + + @Override + public TestRule getTestRule() { + return this.testRule; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java new file mode 100644 index 00000000..00185dfd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.rules.ExternalResource; +import org.junit.rules.Verifier; + +@EnableRuleMigrationSupport +public class EnableRuleMigrationSupportWithBothRuleTypesTests { + + private static boolean afterOfRule1WasExecuted = false; + + private static boolean beforeOfRule2WasExecuted = false; + private static boolean afterOfRule2WasExecuted = false; + + private static int numberOfRule1InstancesCreated = 0; + private static int numberOfRule2InstancesCreated = 0; + + @Rule + public Verifier verifier1 = new Verifier() { + { + numberOfRule1InstancesCreated++; + } + + @Override + protected void verify() { + afterOfRule1WasExecuted = true; + } + }; + + @Rule + public ExternalResource getResource2() { + return new ExternalResource() { + { + numberOfRule2InstancesCreated++; + } + + private Object instance; + + @Override + protected void before() { + instance = this; + beforeOfRule2WasExecuted = true; + } + + @Override + protected void after() { + assertNotNull(instance); + assertSame(instance, this); + afterOfRule2WasExecuted = true; + } + }; + } + + @Test + void beforeMethodOfBothRule2WasExecuted() { + assertTrue(beforeOfRule2WasExecuted); + } + + @AfterAll + static void afterMethodsOfBothRulesWereExecuted() { + assertEquals(1, numberOfRule1InstancesCreated); + assertEquals(1, numberOfRule2InstancesCreated); + if (!afterOfRule1WasExecuted) + fail(); + if (!afterOfRule2WasExecuted) + fail(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java new file mode 100644 index 00000000..54845902 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.io.IOException; + +import org.junit.Rule; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; +import org.junit.rules.ExpectedException; + +/** + * Integration tests for {@link ExpectedExceptionSupport}. + * + * @since 5.0 + */ +class ExpectedExceptionSupportTests { + + @Test + void expectedExceptionIsProcessedCorrectly() { + Events tests = executeTestsForClass(ExpectedExceptionTestCase.class); + + tests.assertStatistics(stats -> stats.started(4).succeeded(1).aborted(0).failed(3)); + + tests.succeeded().assertThatEvents().have( + event(test("correctExceptionExpectedThrown"), finishedSuccessfully())); + + tests.failed().assertThatEvents()// + .haveExactly(1, // + event(test("noExceptionExpectedButThrown"), // + finishedWithFailure(message("no exception expected")))) // + .haveExactly(1, // + event(test("exceptionExpectedButNotThrown"), // + finishedWithFailure(instanceOf(AssertionError.class), // + message("Expected test to throw an instance of java.lang.RuntimeException")))) // + .haveExactly(1, // + event(test("wrongExceptionExpected"), // + finishedWithFailure(instanceOf(AssertionError.class), // + message(value -> value.contains("Expected: an instance of java.io.IOException"))))); + } + + @Test + void expectedExceptionSupportWithoutExpectedExceptionRule() { + Class testClass = ExpectedExceptionSupportWithoutExpectedExceptionRuleTestCase.class; + Events tests = executeTestsForClass(testClass); + + tests.assertStatistics(stats -> stats.started(2).succeeded(1).aborted(0).failed(1)); + + tests.succeeded().assertThatEvents().have(event(test("success"), finishedSuccessfully())); + + tests.failed().assertThatEvents()// + .haveExactly(1, event(test("failure"), finishedWithFailure(message("must fail")))); + } + + private Events executeTestsForClass(Class testClass) { + return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); + } + + @ExtendWith(ExpectedExceptionSupport.class) + static class ExpectedExceptionTestCase { + + @SuppressWarnings("deprecation") + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + void noExceptionExpectedButThrown() { + throw new RuntimeException("no exception expected"); + } + + @Test + void exceptionExpectedButNotThrown() { + thrown.expect(RuntimeException.class); + } + + @Test + void wrongExceptionExpected() { + thrown.expect(IOException.class); + throw new RuntimeException("wrong exception"); + } + + @Test + void correctExceptionExpectedThrown() { + thrown.expect(RuntimeException.class); + throw new RuntimeException("right exception"); + } + + } + + @ExtendWith(ExpectedExceptionSupport.class) + static class ExpectedExceptionSupportWithoutExpectedExceptionRuleTestCase { + + @Test + void success() { + /* no-op */ + } + + @Test + void failure() { + fail("must fail"); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java new file mode 100644 index 00000000..96629e2e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.ExternalResource; +import org.junit.rules.TestRule; + +@ExtendWith(ExternalResourceSupport.class) +class ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests { + + private static boolean beforeOfRule1WasExecuted = false; + private static boolean beforeOfRule2WasExecuted = false; + + private static boolean afterOfRule1WasExecuted = false; + private static boolean afterOfRule2WasExecuted = false; + + @Rule + public MyExternalResource1 getResource1() { + return new MyExternalResource1(); + } + + @Rule + public TestRule getResource2() { + return new ExternalResource() { + @Override + protected void before() { + beforeOfRule2WasExecuted = true; + } + + @Override + protected void after() { + afterOfRule2WasExecuted = true; + } + }; + } + + @Test + void beforeMethodsOfBothRulesWereExecuted() { + assertTrue(beforeOfRule1WasExecuted); + assertTrue(beforeOfRule2WasExecuted); + } + + @AfterAll + static void afterMethodsOfBothRulesWereExecuted() { + if (!afterOfRule1WasExecuted) + fail(); + if (!afterOfRule2WasExecuted) + fail(); + } + + private static class MyExternalResource1 extends ExternalResource { + @Override + protected void before() { + beforeOfRule1WasExecuted = true; + } + + @Override + protected void after() { + afterOfRule1WasExecuted = true; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java new file mode 100644 index 00000000..45a5e81e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.ExternalResource; + +@ExtendWith(ExternalResourceSupport.class) +public class ExternalResourceSupportForMixedMethodAndFieldRulesTests { + + private static List initEvents = new ArrayList<>(); + private static List beforeEvents = new ArrayList<>(); + private static List afterEvents = new ArrayList<>(); + + @BeforeAll + static void clear() { + initEvents.clear(); + beforeEvents.clear(); + afterEvents.clear(); + } + + @Rule + public ExternalResource fieldRule1 = new MyExternalResource("fieldRule1"); + + @Rule + public ExternalResource fieldRule2 = new MyExternalResource("fieldRule2"); + + @Rule + ExternalResource methodRule1() { + return new MyExternalResource("methodRule1"); + } + + @Rule + ExternalResource methodRule2() { + return new MyExternalResource("methodRule2"); + } + + @Test + void constructorsAndBeforeEachMethodsOfAllRulesWereExecuted() { + assertThat(initEvents).hasSize(4); + // the order of fields and methods is not stable, but fields are initialized before methods are called + assertThat(initEvents.subList(0, 2)).allMatch(item -> item.startsWith("fieldRule")); + assertThat(initEvents.subList(2, 4)).allMatch(item -> item.startsWith("methodRule")); + // beforeEach methods of rules from fields are run before those from methods but in reverse order of instantiation + assertEquals(asList(initEvents.get(1), initEvents.get(0), initEvents.get(3), initEvents.get(2)), beforeEvents); + } + + @AfterAll + static void afterMethodsOfAllRulesWereExecuted() { + // beforeEach methods of rules from methods are run before those from fields but in reverse order + if (!asList(initEvents.get(2), initEvents.get(3), initEvents.get(0), initEvents.get(1)).equals(afterEvents)) + fail(); + } + + static class MyExternalResource extends ExternalResource { + + private final String name; + + MyExternalResource(String name) { + this.name = name; + initEvents.add(name); + } + + @Override + protected void before() { + beforeEvents.add(name); + } + + @Override + protected void after() { + afterEvents.add(name); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java new file mode 100644 index 00000000..28bedf13 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.ExternalResource; + +@ExtendWith(ExternalResourceSupport.class) +public class ExternalResourceSupportForMultipleFieldRulesTests { + + private static boolean beforeOfRule1WasExecuted = false; + private static boolean beforeOfRule2WasExecuted = false; + + private static boolean afterOfRule1WasExecuted = false; + private static boolean afterOfRule2WasExecuted = false; + + @Rule + public ExternalResource resource1 = new ExternalResource() { + @Override + protected void before() { + beforeOfRule1WasExecuted = true; + } + + @Override + protected void after() { + afterOfRule1WasExecuted = true; + } + }; + + @Rule + public ExternalResource resource2 = new ExternalResource() { + @Override + protected void before() { + beforeOfRule2WasExecuted = true; + } + + @Override + protected void after() { + afterOfRule2WasExecuted = true; + } + }; + + @Test + void beforeMethodsOfBothRulesWereExecuted() { + assertTrue(beforeOfRule1WasExecuted); + assertTrue(beforeOfRule2WasExecuted); + } + + @AfterAll + static void afterMethodsOfBothRulesWereExecuted() { + if (!afterOfRule1WasExecuted) + fail(); + if (!afterOfRule2WasExecuted) + fail(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java new file mode 100644 index 00000000..7e15b4ab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.ExternalResource; + +@ExtendWith(ExternalResourceSupport.class) +public class ExternalResourceSupportForMultipleMethodRulesTests { + + private static boolean beforeOfRule1WasExecuted = false; + private static boolean beforeOfRule2WasExecuted = false; + + private static boolean afterOfRule1WasExecuted = false; + private static boolean afterOfRule2WasExecuted = false; + + @Rule + public ExternalResource getResource1() { + return new ExternalResource() { + @Override + protected void before() { + beforeOfRule1WasExecuted = true; + } + + @Override + protected void after() { + afterOfRule1WasExecuted = true; + } + }; + } + + @Rule + public ExternalResource getResource2() { + return new ExternalResource() { + @Override + protected void before() { + beforeOfRule2WasExecuted = true; + } + + @Override + protected void after() { + afterOfRule2WasExecuted = true; + } + }; + } + + @Test + void beforeMethodsOfBothRulesWereExecuted() { + assertTrue(beforeOfRule1WasExecuted); + assertTrue(beforeOfRule2WasExecuted); + } + + @AfterAll + static void afterMethodsOfBothRulesWereExecuted() { + if (!afterOfRule1WasExecuted) + fail(); + if (!afterOfRule2WasExecuted) + fail(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java new file mode 100644 index 00000000..4119170d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; + +import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.TemporaryFolder; + +@ExtendWith(ExternalResourceSupport.class) +public class ExternalResourceSupportForTemporaryFolderFieldTests { + + private File file; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @BeforeEach + void setup() throws IOException { + this.file = folder.newFile("temp.txt"); + } + + @Test + void checkTemporaryFolder() { + assertTrue(file.canRead()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java new file mode 100644 index 00000000..dc6f4fca --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java @@ -0,0 +1,16 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +public class ExternalResourceSupportWithInheritanceTests + extends ExternalResourceSupportForMixedMethodAndFieldRulesTests { + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java new file mode 100644 index 00000000..67c395b4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.Rule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.rules.TemporaryFolder; + +public class ExternalResourceWithoutAdapterTests { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @BeforeEach + void setup() { + try { + folder.newFile("temp.txt"); + } + catch (Exception exception) { + assertTrue(exception.getMessage().equals("the temporary folder has not yet been created")); + } + } + + @Test + void checkTemporaryFolder() { + // only needed to invoke testing at all + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java new file mode 100644 index 00000000..08d51f17 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +/** + * @since 5.0 + */ +class FailAfterAllHelper { + + static void fail() { + // hack: use this unrecoverable exception to fail the build, since all others would be swallowed... + throw new OutOfMemoryError("a postcondition was violated"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java new file mode 100644 index 00000000..37774078 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.junit.Rule; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; +import org.junit.rules.ErrorCollector; +import org.junit.rules.ExternalResource; +import org.junit.rules.Verifier; + +class LauncherBasedEnableRuleMigrationSupportTests { + + @Test + void enableRuleMigrationSupportAnnotationWorksForBothRuleTypes() { + Events tests = executeTestsForClass(EnableRuleMigrationSupportWithBothRuleTypesTestCase.class); + + tests.assertStatistics(stats -> stats.started(1).succeeded(1).aborted(0).failed(0)); + + assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.afterOfRule1WasExecuted, + "after of rule 1 executed?"); + assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.beforeOfRule2WasExecuted, + "before of rule 2 executed?"); + assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.afterOfRule2WasExecuted, + "before of rule 2 executed?"); + } + + @Test + void verifierSupportForErrorCollectorFieldFailsTheTest() { + Events tests = executeTestsForClass(VerifierSupportForErrorCollectorTestCase.class); + + tests.assertStatistics(stats -> stats.started(1).succeeded(0).aborted(0).failed(1)); + + assertTrue(VerifierSupportForErrorCollectorTestCase.survivedBothErrors, "after of rule 1 executed?"); + } + + private Events executeTestsForClass(Class testClass) { + return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); + } + + @EnableRuleMigrationSupport + static class EnableRuleMigrationSupportWithBothRuleTypesTestCase { + + static boolean afterOfRule1WasExecuted = false; + + static boolean beforeOfRule2WasExecuted = false; + static boolean afterOfRule2WasExecuted = false; + + @Rule + public Verifier verifier1 = new Verifier() { + + @Override + protected void verify() { + afterOfRule1WasExecuted = true; + } + }; + + private ExternalResource resource2 = new ExternalResource() { + @Override + protected void before() { + beforeOfRule2WasExecuted = true; + } + + @Override + protected void after() { + afterOfRule2WasExecuted = true; + } + }; + + @Rule + public ExternalResource getResource2() { + return resource2; + } + + @Test + void beforeMethodOfBothRule2WasExecuted() { + assertTrue(beforeOfRule2WasExecuted); + } + + } + + @ExtendWith(VerifierSupport.class) + static class VerifierSupportForErrorCollectorTestCase { + + static boolean survivedBothErrors = false; + + @Rule + public ErrorCollector collector = new ErrorCollector(); + + @Test + void addingTwoThrowablesToErrorCollectorFailsLate() { + collector.addError(new Throwable("first thing went wrong")); + collector.addError(new Throwable("second thing went wrong")); + + survivedBothErrors = true; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java new file mode 100644 index 00000000..a42db4c5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.Verifier; + +@ExtendWith(VerifierSupport.class) +public class VerifierSupportForMixedMethodAndFieldRulesTests { + + private static boolean afterOfRule1WasExecuted = false; + private static boolean afterOfRule2WasExecuted = false; + + @Rule + public Verifier verifier1 = new Verifier() { + + @Override + protected void verify() { + afterOfRule1WasExecuted = true; + } + }; + + private Verifier verifier2 = new Verifier() { + + @Override + protected void verify() { + afterOfRule2WasExecuted = true; + } + }; + + @Rule + public Verifier getVerifier2() { + return verifier2; + } + + @Test + void testNothing() { + //needed to start the test process at all + } + + @AfterAll + static void afterMethodsOfBothRulesWereExecuted() { + if (!afterOfRule1WasExecuted) + fail(); + if (!afterOfRule2WasExecuted) + fail(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java new file mode 100644 index 00000000..9b559cc0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.Verifier; + +@ExtendWith(ExternalResourceSupport.class) +public class WrongExtendWithForVerifierFieldTests { + + private static boolean afterOfRule1WasExecuted = false; + + @Rule + public Verifier verifier1 = new Verifier() { + + @Override + protected void verify() { + afterOfRule1WasExecuted = true; + } + }; + + @Test + void testNothing() { + //needed to start the test process at all + } + + @AfterAll + static void afterMethodOfRuleWasNotExecuted() { + if (afterOfRule1WasExecuted) + fail(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java new file mode 100644 index 00000000..9d566a58 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.migrationsupport.rules; + +import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; + +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.rules.Verifier; + +@ExtendWith(ExternalResourceSupport.class) +public class WrongExtendWithForVerifierMethodTests { + + private static boolean afterOfRule1WasExecuted = false; + + private Verifier verifier1 = new Verifier() { + + @Override + protected void verify() { + afterOfRule1WasExecuted = true; + } + }; + + @Rule + public Verifier getVerifier1() { + return verifier1; + } + + @Test + void testNothing() { + //needed to start the test process at all + } + + @AfterAll + static void afterMethodsOfBothRulesWereExecuted() { + if (afterOfRule1WasExecuted) + fail(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..1c1cbb8d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md new file mode 100644 index 00000000..f58ac2e9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md @@ -0,0 +1,168 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts new file mode 100644 index 00000000..528ef09c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -0,0 +1,51 @@ +plugins { + `kotlin-library-conventions` + `shadow-conventions` + `testing-conventions` +} + +description = "JUnit Jupiter Params" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitJupiterApi) + + compileOnlyApi(libs.apiguardian) + + shadowed(libs.univocity.parsers) + + testImplementation(projects.junitPlatformTestkit) + testImplementation(projects.junitJupiterEngine) + testImplementation(projects.junitPlatformLauncher) + testImplementation(projects.junitPlatformSuiteEngine) + testImplementation(testFixtures(projects.junitJupiterEngine)) + + compileOnly(kotlin("stdlib")) + testImplementation(kotlin("stdlib")) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + jar { + bundle { + bnd(""" + Require-Capability:\ + org.junit.platform.engine;\ + filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;${rootProject.property("version")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("version")!!}}})))';\ + effective:=active + """) + } + } +} + +tasks { + shadowJar { + relocate("com.univocity", "org.junit.jupiter.params.shadow.com.univocity") + from(projectDir) { + include("LICENSE-univocity-parsers.md") + into("META-INF") + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java new file mode 100644 index 00000000..1fcdd80b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java @@ -0,0 +1,246 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @ParameterizedTest} is used to signal that the annotated method is a + * parameterized test method. + * + *

Such methods must not be {@code private} or {@code static}. + * + *

Argument Providers and Sources

+ * + *

{@code @ParameterizedTest} methods must specify at least one + * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} + * via {@link org.junit.jupiter.params.provider.ArgumentsSource @ArgumentsSource} + * or a corresponding composed annotation (e.g., {@code @ValueSource}, + * {@code @CsvSource}, etc.). The provider is responsible for providing a + * {@link java.util.stream.Stream Stream} of + * {@link org.junit.jupiter.params.provider.Arguments Arguments} that will be + * used to invoke the parameterized test method. + * + *

Formal Parameter List

+ * + *

A {@code @ParameterizedTest} method may declare additional parameters at + * the end of the method's parameter list to be resolved by other + * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers} + * (e.g., {@code TestInfo}, {@code TestReporter}, etc). Specifically, a + * parameterized test method must declare formal parameters according to the + * following rules. + * + *

    + *
  1. Zero or more indexed arguments must be declared first.
  2. + *
  3. Zero or more aggregators must be declared next.
  4. + *
  5. Zero or more arguments supplied by other {@code ParameterResolver} + * implementations must be declared last.
  6. + *
+ * + *

In this context, an indexed argument is an argument for a given + * index in the {@code Arguments} provided by an {@code ArgumentsProvider} that + * is passed as an argument to the parameterized method at the same index in the + * method's formal parameter list. An aggregator is any parameter of type + * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor ArgumentsAccessor} + * or any parameter annotated with + * {@link org.junit.jupiter.params.aggregator.AggregateWith @AggregateWith}. + * + *

Argument Conversion

+ * + *

Method parameters may be annotated with + * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} + * or a corresponding composed annotation to specify an explicit + * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter}. + * Otherwise, JUnit Jupiter will attempt to perform an implicit + * conversion to the target type automatically (see the User Guide for further + * details). + * + *

Composed Annotations

+ * + *

{@code @ParameterizedTest} may also be used as a meta-annotation in order + * to create a custom composed annotation that inherits the semantics + * of {@code @ParameterizedTest}. + * + *

Test Execution Order

+ * + *

By default, test methods will be ordered using an algorithm that is + * deterministic but intentionally nonobvious. This ensures that subsequent runs + * of a test suite execute test methods in the same order, thereby allowing for + * repeatable builds. In this context, a test method is any instance + * method that is directly annotated or meta-annotated with {@code @Test}, + * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or + * {@code @TestTemplate}. + * + *

Although true unit tests typically should not rely on the order + * in which they are executed, there are times when it is necessary to enforce + * a specific test method execution order — for example, when writing + * integration tests or functional tests where the sequence of + * the tests is important, especially in conjunction with + * {@link org.junit.jupiter.api.TestInstance @TestInstance(Lifecycle.PER_CLASS)}. + * + *

To control the order in which test methods are executed, annotate your + * test class or test interface with + * {@link org.junit.jupiter.api.TestMethodOrder @TestMethodOrder} and specify + * the desired {@link org.junit.jupiter.api.MethodOrderer MethodOrderer} + * implementation. + * + * @since 5.0 + * @see org.junit.jupiter.params.provider.Arguments + * @see org.junit.jupiter.params.provider.ArgumentsProvider + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.provider.CsvFileSource + * @see org.junit.jupiter.params.provider.CsvSource + * @see org.junit.jupiter.params.provider.EnumSource + * @see org.junit.jupiter.params.provider.MethodSource + * @see org.junit.jupiter.params.provider.ValueSource + * @see org.junit.jupiter.params.aggregator.ArgumentsAccessor + * @see org.junit.jupiter.params.aggregator.AggregateWith + * @see org.junit.jupiter.params.converter.ArgumentConverter + * @see org.junit.jupiter.params.converter.ConvertWith + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@TestTemplate +@ExtendWith(ParameterizedTestExtension.class) +public @interface ParameterizedTest { + + /** + * Placeholder for the {@linkplain org.junit.jupiter.api.TestInfo#getDisplayName + * display name} of a {@code @ParameterizedTest} method: {displayName} + * + * @since 5.3 + * @see #name + */ + String DISPLAY_NAME_PLACEHOLDER = "{displayName}"; + + /** + * Placeholder for the current invocation index of a {@code @ParameterizedTest} + * method (1-based): {index} + * + * @since 5.3 + * @see #name + */ + String INDEX_PLACEHOLDER = "{index}"; + + /** + * Placeholder for the complete, comma-separated arguments list of the + * current invocation of a {@code @ParameterizedTest} method: + * {arguments} + * + * @since 5.3 + * @see #name + */ + String ARGUMENTS_PLACEHOLDER = "{arguments}"; + + /** + * Placeholder for the complete, comma-separated named arguments list + * of the current invocation of a {@code @ParameterizedTest} method: + * {argumentsWithNames} + * + *

Argument names will be retrieved via the {@link java.lang.reflect.Parameter#getName()} + * API if the byte code contains parameter names — for example, if + * the code was compiled with the {@code -parameters} command line argument + * for {@code javac}. + * + * @since 5.6 + * @see #name + */ + String ARGUMENTS_WITH_NAMES_PLACEHOLDER = "{argumentsWithNames}"; + + /** + * Default display name pattern for the current invocation of a + * {@code @ParameterizedTest} method: {@value} + * + *

Note that the default pattern does not include the + * {@linkplain #DISPLAY_NAME_PLACEHOLDER display name} of the + * {@code @ParameterizedTest} method. + * + * @since 5.3 + * @see #name + * @see #DISPLAY_NAME_PLACEHOLDER + * @see #INDEX_PLACEHOLDER + * @see #ARGUMENTS_WITH_NAMES_PLACEHOLDER + */ + String DEFAULT_DISPLAY_NAME = "[" + INDEX_PLACEHOLDER + "] " + ARGUMENTS_WITH_NAMES_PLACEHOLDER; + + /** + * The display name to be used for individual invocations of the + * parameterized test; never blank or consisting solely of whitespace. + * + *

Defaults to {default_display_name}. + * + *

If the default display name flag ({default_display_name}) + * is not overridden, JUnit will: + *

    + *
  • Look up the {@value ParameterizedTestExtension#DISPLAY_NAME_PATTERN_KEY} + * configuration parameter and use it if available. The configuration + * parameter can be supplied via the {@code Launcher} API, build tools (e.g., + * Gradle and Maven), a JVM system property, or the JUnit Platform configuration + * file (i.e., a file named {@code junit-platform.properties} in the root of + * the class path). Consult the User Guide for further information.
  • + *
  • Otherwise, the value of the {@link #DEFAULT_DISPLAY_NAME} constant will + * be used.
  • + *
+ * + *

Supported placeholders

+ *
    + *
  • {@link #DISPLAY_NAME_PLACEHOLDER}
  • + *
  • {@link #INDEX_PLACEHOLDER}
  • + *
  • {@link #ARGUMENTS_PLACEHOLDER}
  • + *
  • {@link #ARGUMENTS_WITH_NAMES_PLACEHOLDER}
  • + *
  • {0}, {1}, etc.: an individual argument (0-based)
  • + *
+ * + *

For the latter, you may use {@link java.text.MessageFormat} patterns + * to customize formatting. Please note that the original arguments are + * passed when formatting, regardless of any implicit or explicit argument + * conversions. + * + *

Note that {default_display_name} is a flag rather than a + * placeholder. + * + * @see java.text.MessageFormat + */ + String name() default "{default_display_name}"; + + /** + * Configure whether all arguments of the parameterized test that implement {@link AutoCloseable} + * will be closed after {@link org.junit.jupiter.api.AfterEach @AfterEach} methods + * and {@link org.junit.jupiter.api.extension.AfterEachCallback AfterEachCallback} + * extensions have been called for the current parameterized test invocation. + * + *

Defaults to {@code true}. + * + *

WARNING: if an argument that implements {@code AutoCloseable} + * is reused for multiple invocations of the same parameterized test method, + * you must set {@code autoCloseArguments} to {@code false} to ensure that + * the argument is not closed between invocations. + * + * @since 5.8 + * @see java.lang.AutoCloseable + */ + @API(status = EXPERIMENTAL, since = "5.8") + boolean autoCloseArguments() default true; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java new file mode 100644 index 00000000..3b9f8c3c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java @@ -0,0 +1,160 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.AnnotationConsumerInitializer; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.0 + */ +class ParameterizedTestExtension implements TestTemplateInvocationContextProvider { + + private static final String METHOD_CONTEXT_KEY = "context"; + static final String ARGUMENT_MAX_LENGTH_KEY = "junit.jupiter.params.displayname.argument.maxlength"; + private static final String DEFAULT_DISPLAY_NAME = "{default_display_name}"; + static final String DISPLAY_NAME_PATTERN_KEY = "junit.jupiter.params.displayname.default"; + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + if (!context.getTestMethod().isPresent()) { + return false; + } + + Method testMethod = context.getTestMethod().get(); + if (!isAnnotated(testMethod, ParameterizedTest.class)) { + return false; + } + + ParameterizedTestMethodContext methodContext = new ParameterizedTestMethodContext(testMethod); + + Preconditions.condition(methodContext.hasPotentiallyValidSignature(), + () -> String.format( + "@ParameterizedTest method [%s] declares formal parameters in an invalid order: " + + "argument aggregators must be declared after any indexed arguments " + + "and before any arguments resolved by another ParameterResolver.", + testMethod.toGenericString())); + + getStore(context).put(METHOD_CONTEXT_KEY, methodContext); + + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts( + ExtensionContext extensionContext) { + + Method templateMethod = extensionContext.getRequiredTestMethod(); + String displayName = extensionContext.getDisplayName(); + ParameterizedTestMethodContext methodContext = getStore(extensionContext)// + .get(METHOD_CONTEXT_KEY, ParameterizedTestMethodContext.class); + int argumentMaxLength = extensionContext.getConfigurationParameter(ARGUMENT_MAX_LENGTH_KEY, + Integer::parseInt).orElse(512); + ParameterizedTestNameFormatter formatter = createNameFormatter(extensionContext, templateMethod, methodContext, + displayName, argumentMaxLength); + AtomicLong invocationCount = new AtomicLong(0); + + // @formatter:off + return findRepeatableAnnotations(templateMethod, ArgumentsSource.class) + .stream() + .map(ArgumentsSource::value) + .map(this::instantiateArgumentsProvider) + .map(provider -> AnnotationConsumerInitializer.initialize(templateMethod, provider)) + .flatMap(provider -> arguments(provider, extensionContext)) + .map(Arguments::get) + .map(arguments -> consumedArguments(arguments, methodContext)) + .map(arguments -> { + invocationCount.incrementAndGet(); + return createInvocationContext(formatter, methodContext, arguments); + }) + .onClose(() -> + Preconditions.condition(invocationCount.get() > 0, + "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest")); + // @formatter:on + } + + @SuppressWarnings("ConstantConditions") + private ArgumentsProvider instantiateArgumentsProvider(Class clazz) { + try { + return ReflectionUtils.newInstance(clazz); + } + catch (Exception ex) { + if (ex instanceof NoSuchMethodException) { + String message = String.format("Failed to find a no-argument constructor for ArgumentsProvider [%s]. " + + "Please ensure that a no-argument constructor exists and " + + "that the class is either a top-level class or a static nested class", + clazz.getName()); + throw new JUnitException(message, ex); + } + throw ex; + } + } + + private ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(ParameterizedTestExtension.class, context.getRequiredTestMethod())); + } + + private TestTemplateInvocationContext createInvocationContext(ParameterizedTestNameFormatter formatter, + ParameterizedTestMethodContext methodContext, Object[] arguments) { + return new ParameterizedTestInvocationContext(formatter, methodContext, arguments); + } + + private ParameterizedTestNameFormatter createNameFormatter(ExtensionContext extensionContext, Method templateMethod, + ParameterizedTestMethodContext methodContext, String displayName, int argumentMaxLength) { + ParameterizedTest parameterizedTest = findAnnotation(templateMethod, ParameterizedTest.class).get(); + String pattern = parameterizedTest.name().equals(DEFAULT_DISPLAY_NAME) + ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY).orElse( + ParameterizedTest.DEFAULT_DISPLAY_NAME) + : parameterizedTest.name(); + pattern = Preconditions.notBlank(pattern.trim(), + () -> String.format( + "Configuration error: @ParameterizedTest on method [%s] must be declared with a non-empty name.", + templateMethod)); + return new ParameterizedTestNameFormatter(pattern, displayName, methodContext, argumentMaxLength); + } + + protected static Stream arguments(ArgumentsProvider provider, ExtensionContext context) { + try { + return provider.provideArguments(context); + } + catch (Exception e) { + throw ExceptionUtils.throwAsUncheckedException(e); + } + } + + private Object[] consumedArguments(Object[] arguments, ParameterizedTestMethodContext methodContext) { + int parameterCount = methodContext.getParameterCount(); + if (methodContext.hasAggregator()) { + return arguments; + } + return arguments.length > parameterCount ? Arrays.copyOf(arguments, parameterCount) : arguments; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java new file mode 100644 index 00000000..778fe936 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static java.util.Collections.singletonList; + +import java.util.List; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; + +/** + * @since 5.0 + */ +class ParameterizedTestInvocationContext implements TestTemplateInvocationContext { + + private final ParameterizedTestNameFormatter formatter; + private final ParameterizedTestMethodContext methodContext; + private final Object[] arguments; + + ParameterizedTestInvocationContext(ParameterizedTestNameFormatter formatter, + ParameterizedTestMethodContext methodContext, Object[] arguments) { + this.formatter = formatter; + this.methodContext = methodContext; + this.arguments = arguments; + } + + @Override + public String getDisplayName(int invocationIndex) { + return this.formatter.format(invocationIndex, this.arguments); + } + + @Override + public List getAdditionalExtensions() { + return singletonList(new ParameterizedTestParameterResolver(this.methodContext, this.arguments)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java new file mode 100644 index 00000000..fc0c3c0e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java @@ -0,0 +1,276 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static org.junit.jupiter.params.ParameterizedTestMethodContext.ResolverType.AGGREGATOR; +import static org.junit.jupiter.params.ParameterizedTestMethodContext.ResolverType.CONVERTER; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.params.aggregator.AggregateWith; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.aggregator.ArgumentsAggregator; +import org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor; +import org.junit.jupiter.params.converter.ArgumentConverter; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.converter.DefaultArgumentConverter; +import org.junit.jupiter.params.support.AnnotationConsumerInitializer; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; + +/** + * Encapsulates access to the parameters of a parameterized test method and + * caches the converters and aggregators used to resolve them. + * + * @since 5.3 + */ +class ParameterizedTestMethodContext { + + private final Parameter[] parameters; + private final Resolver[] resolvers; + private final List resolverTypes; + + ParameterizedTestMethodContext(Method testMethod) { + this.parameters = testMethod.getParameters(); + this.resolvers = new Resolver[this.parameters.length]; + this.resolverTypes = new ArrayList<>(this.parameters.length); + for (Parameter parameter : this.parameters) { + this.resolverTypes.add(isAggregator(parameter) ? AGGREGATOR : CONVERTER); + } + } + + /** + * Determine if the supplied {@link Parameter} is an aggregator (i.e., of + * type {@link ArgumentsAccessor} or annotated with {@link AggregateWith}). + * + * @return {@code true} if the parameter is an aggregator + */ + private static boolean isAggregator(Parameter parameter) { + return ArgumentsAccessor.class.isAssignableFrom(parameter.getType()) + || isAnnotated(parameter, AggregateWith.class); + } + + /** + * Determine if the {@link Method} represented by this context has a + * potentially valid signature (i.e., formal parameter + * declarations) with regard to aggregators. + * + *

This method takes a best-effort approach at enforcing the following + * policy for parameterized test methods that accept aggregators as arguments. + * + *

    + *
  1. zero or more indexed arguments come first.
  2. + *
  3. zero or more aggregators come next.
  4. + *
  5. zero or more arguments supplied by other {@code ParameterResolver} + * implementations come last.
  6. + *
+ * + * @return {@code true} if the method has a potentially valid signature + */ + boolean hasPotentiallyValidSignature() { + int indexOfPreviousAggregator = -1; + for (int i = 0; i < getParameterCount(); i++) { + if (isAggregator(i)) { + if ((indexOfPreviousAggregator != -1) && (i != indexOfPreviousAggregator + 1)) { + return false; + } + indexOfPreviousAggregator = i; + } + } + return true; + } + + /** + * Get the number of parameters of the {@link Method} represented by this + * context. + */ + int getParameterCount() { + return parameters.length; + } + + /** + * Get the name of the {@link Parameter} with the supplied index, if + * it is present and declared before the aggregators. + * + * @return an {@code Optional} containing the name of the parameter + */ + Optional getParameterName(int parameterIndex) { + if (parameterIndex >= getParameterCount()) { + return Optional.empty(); + } + Parameter parameter = this.parameters[parameterIndex]; + if (!parameter.isNamePresent()) { + return Optional.empty(); + } + if (hasAggregator() && parameterIndex >= indexOfFirstAggregator()) { + return Optional.empty(); + } + return Optional.of(parameter.getName()); + } + + /** + * Determine if the {@link Method} represented by this context declares at + * least one {@link Parameter} that is an + * {@linkplain #isAggregator aggregator}. + * + * @return {@code true} if the method has an aggregator + */ + boolean hasAggregator() { + return resolverTypes.contains(AGGREGATOR); + } + + /** + * Determine if the {@link Parameter} with the supplied index is an + * aggregator (i.e., of type {@link ArgumentsAccessor} or annotated with + * {@link AggregateWith}). + * + * @return {@code true} if the parameter is an aggregator + */ + boolean isAggregator(int parameterIndex) { + return resolverTypes.get(parameterIndex) == AGGREGATOR; + } + + /** + * Find the index of the first {@linkplain #isAggregator aggregator} + * {@link Parameter} in the {@link Method} represented by this context. + * + * @return the index of the first aggregator, or {@code -1} if not found + */ + int indexOfFirstAggregator() { + return resolverTypes.indexOf(AGGREGATOR); + } + + /** + * Resolve the parameter for the supplied context using the supplied + * arguments. + */ + Object resolve(ParameterContext parameterContext, Object[] arguments) { + return getResolver(parameterContext).resolve(parameterContext, arguments); + } + + private Resolver getResolver(ParameterContext parameterContext) { + int index = parameterContext.getIndex(); + if (resolvers[index] == null) { + resolvers[index] = resolverTypes.get(index).createResolver(parameterContext); + } + return resolvers[index]; + } + + enum ResolverType { + + CONVERTER { + @Override + Resolver createResolver(ParameterContext parameterContext) { + try { // @formatter:off + return AnnotationUtils.findAnnotation(parameterContext.getParameter(), ConvertWith.class) + .map(ConvertWith::value) + .map(clazz -> (ArgumentConverter) ReflectionUtils.newInstance(clazz)) + .map(converter -> AnnotationConsumerInitializer.initialize(parameterContext.getParameter(), converter)) + .map(Converter::new) + .orElse(Converter.DEFAULT); + } // @formatter:on + catch (Exception ex) { + throw parameterResolutionException("Error creating ArgumentConverter", ex, parameterContext); + } + } + }, + + AGGREGATOR { + @Override + Resolver createResolver(ParameterContext parameterContext) { + try { // @formatter:off + return AnnotationUtils.findAnnotation(parameterContext.getParameter(), AggregateWith.class) + .map(AggregateWith::value) + .map(clazz -> (ArgumentsAggregator) ReflectionSupport.newInstance(clazz)) + .map(Aggregator::new) + .orElse(Aggregator.DEFAULT); + } // @formatter:on + catch (Exception ex) { + throw parameterResolutionException("Error creating ArgumentsAggregator", ex, parameterContext); + } + } + }; + + abstract Resolver createResolver(ParameterContext parameterContext); + + } + + interface Resolver { + + Object resolve(ParameterContext parameterContext, Object[] arguments); + + } + + static class Converter implements Resolver { + + private static final Converter DEFAULT = new Converter(DefaultArgumentConverter.INSTANCE); + + private final ArgumentConverter argumentConverter; + + Converter(ArgumentConverter argumentConverter) { + this.argumentConverter = argumentConverter; + } + + @Override + public Object resolve(ParameterContext parameterContext, Object[] arguments) { + Object argument = arguments[parameterContext.getIndex()]; + try { + return this.argumentConverter.convert(argument, parameterContext); + } + catch (Exception ex) { + throw parameterResolutionException("Error converting parameter", ex, parameterContext); + } + } + + } + + static class Aggregator implements Resolver { + + private static final Aggregator DEFAULT = new Aggregator((accessor, context) -> accessor); + + private final ArgumentsAggregator argumentsAggregator; + + Aggregator(ArgumentsAggregator argumentsAggregator) { + this.argumentsAggregator = argumentsAggregator; + } + + @Override + public Object resolve(ParameterContext parameterContext, Object[] arguments) { + ArgumentsAccessor accessor = new DefaultArgumentsAccessor(arguments); + try { + return this.argumentsAggregator.aggregateArguments(accessor, parameterContext); + } + catch (Exception ex) { + throw parameterResolutionException("Error aggregating arguments for parameter", ex, parameterContext); + } + } + + } + + private static ParameterResolutionException parameterResolutionException(String message, Exception cause, + ParameterContext parameterContext) { + String fullMessage = message + " at index " + parameterContext.getIndex(); + if (StringUtils.isNotBlank(cause.getMessage())) { + fullMessage += ": " + cause.getMessage(); + } + return new ParameterResolutionException(fullMessage, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java new file mode 100644 index 00000000..a505bd81 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_PLACEHOLDER; +import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER; +import static org.junit.jupiter.params.ParameterizedTest.DISPLAY_NAME_PLACEHOLDER; +import static org.junit.jupiter.params.ParameterizedTest.INDEX_PLACEHOLDER; + +import java.text.Format; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Named; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.StringUtils; + +/** + * @since 5.0 + */ +class ParameterizedTestNameFormatter { + + private static final char ELLIPSIS = '\u2026'; + private static final String TEMPORARY_DISPLAY_NAME_PLACEHOLDER = "~~~JUNIT_DISPLAY_NAME~~~"; + + private final String pattern; + private final String displayName; + private final ParameterizedTestMethodContext methodContext; + private final int argumentMaxLength; + + ParameterizedTestNameFormatter(String pattern, String displayName, ParameterizedTestMethodContext methodContext, + int argumentMaxLength) { + this.pattern = pattern; + this.displayName = displayName; + this.methodContext = methodContext; + this.argumentMaxLength = argumentMaxLength; + } + + String format(int invocationIndex, Object... arguments) { + try { + return formatSafely(invocationIndex, arguments); + } + catch (Exception ex) { + String message = "The display name pattern defined for the parameterized test is invalid. " + + "See nested exception for further details."; + throw new JUnitException(message, ex); + } + } + + private String formatSafely(int invocationIndex, Object[] arguments) { + Object[] namedArguments = extractNamedArguments(arguments); + String pattern = prepareMessageFormatPattern(invocationIndex, namedArguments); + MessageFormat format = new MessageFormat(pattern); + Object[] humanReadableArguments = makeReadable(format, namedArguments); + String formatted = format.format(humanReadableArguments); + return formatted.replace(TEMPORARY_DISPLAY_NAME_PLACEHOLDER, this.displayName); + } + + private Object[] extractNamedArguments(Object[] arguments) { + return Arrays.stream(arguments) // + .map(argument -> argument instanceof Named ? ((Named) argument).getName() : argument) // + .toArray(); + } + + private String prepareMessageFormatPattern(int invocationIndex, Object[] arguments) { + String result = pattern// + .replace(DISPLAY_NAME_PLACEHOLDER, TEMPORARY_DISPLAY_NAME_PLACEHOLDER)// + .replace(INDEX_PLACEHOLDER, String.valueOf(invocationIndex)); + + if (result.contains(ARGUMENTS_WITH_NAMES_PLACEHOLDER)) { + result = result.replace(ARGUMENTS_WITH_NAMES_PLACEHOLDER, argumentsWithNamesPattern(arguments)); + } + + if (result.contains(ARGUMENTS_PLACEHOLDER)) { + result = result.replace(ARGUMENTS_PLACEHOLDER, argumentsPattern(arguments)); + } + + return result; + } + + private String argumentsWithNamesPattern(Object[] arguments) { + return IntStream.range(0, arguments.length) // + .mapToObj(index -> methodContext.getParameterName(index).map(name -> name + "=").orElse("") + "{" + + index + "}") // + .collect(joining(", ")); + } + + private String argumentsPattern(Object[] arguments) { + return IntStream.range(0, arguments.length) // + .mapToObj(index -> "{" + index + "}") // + .collect(joining(", ")); + } + + private Object[] makeReadable(MessageFormat format, Object[] arguments) { + Format[] formats = format.getFormatsByArgumentIndex(); + Object[] result = Arrays.copyOf(arguments, Math.min(arguments.length, formats.length), Object[].class); + for (int i = 0; i < result.length; i++) { + if (formats[i] == null) { + result[i] = truncateIfExceedsMaxLength(StringUtils.nullSafeToString(arguments[i])); + } + } + return result; + } + + private String truncateIfExceedsMaxLength(String argument) { + if (argument != null && argument.length() > argumentMaxLength) { + return argument.substring(0, argumentMaxLength - 1) + ELLIPSIS; + } + return argument; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java new file mode 100644 index 00000000..b9d6a7b4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.util.AnnotationUtils; + +/** + * @since 5.0 + */ +class ParameterizedTestParameterResolver implements ParameterResolver, AfterTestExecutionCallback { + + private static final Namespace NAMESPACE = Namespace.create(ParameterizedTestParameterResolver.class); + + private final ParameterizedTestMethodContext methodContext; + private final Object[] arguments; + + ParameterizedTestParameterResolver(ParameterizedTestMethodContext methodContext, Object[] arguments) { + this.methodContext = methodContext; + this.arguments = arguments; + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + Executable declaringExecutable = parameterContext.getDeclaringExecutable(); + Method testMethod = extensionContext.getTestMethod().orElse(null); + int parameterIndex = parameterContext.getIndex(); + + // Not a @ParameterizedTest method? + if (!declaringExecutable.equals(testMethod)) { + return false; + } + + // Current parameter is an aggregator? + if (this.methodContext.isAggregator(parameterIndex)) { + return true; + } + + // Ensure that the current parameter is declared before aggregators. + // Otherwise, a different ParameterResolver should handle it. + if (this.methodContext.hasAggregator()) { + return parameterIndex < this.methodContext.indexOfFirstAggregator(); + } + + // Else fallback to behavior for parameterized test methods without aggregators. + return parameterIndex < this.arguments.length; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return this.methodContext.resolve(parameterContext, extractPayloads(this.arguments)); + } + + /** + * @since 5.8 + */ + @Override + public void afterTestExecution(ExtensionContext context) { + ParameterizedTest parameterizedTest = AnnotationUtils.findAnnotation(context.getRequiredTestMethod(), + ParameterizedTest.class).get(); + if (!parameterizedTest.autoCloseArguments()) { + return; + } + + Store store = context.getStore(NAMESPACE); + AtomicInteger argumentIndex = new AtomicInteger(); + + Arrays.stream(this.arguments) // + .filter(AutoCloseable.class::isInstance) // + .map(AutoCloseable.class::cast) // + .map(CloseableArgument::new) // + .forEach(closeable -> store.put("closeableArgument#" + argumentIndex.incrementAndGet(), closeable)); + } + + private static class CloseableArgument implements Store.CloseableResource { + + private final AutoCloseable autoCloseable; + + CloseableArgument(AutoCloseable autoCloseable) { + this.autoCloseable = autoCloseable; + } + + @Override + public void close() throws Throwable { + this.autoCloseable.close(); + } + + } + + private Object[] extractPayloads(Object[] arguments) { + return Arrays.stream(arguments) // + .map(argument -> { + if (argument instanceof Named) { + return ((Named) argument).getPayload(); + } + return argument; + }) // + .toArray(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java new file mode 100644 index 00000000..57a0b88f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @AggregateWith} is an annotation that allows one to specify an + * {@link ArgumentsAggregator}. + * + *

This annotation may be applied to a parameter of a + * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method + * in order for an aggregated value to be resolved for the annotated parameter + * when the test method is invoked. + * + *

{@code @AggregateWith} may also be used as a meta-annotation in order to + * create a custom composed annotation that inherits the semantics + * of {@code @AggregateWith}. + * + * @since 5.2 + * @see ArgumentsAggregator + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) +@Documented +@API(status = STABLE, since = "5.7") +public @interface AggregateWith { + + Class value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java new file mode 100644 index 00000000..ce439621 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * {@code ArgumentAccessException} is an exception thrown by an + * {@link ArgumentsAccessor} if an error occurs while accessing + * or converting an argument. + * + * @since 5.2 + * @see ArgumentsAccessor + */ +@API(status = STABLE, since = "5.7") +public class ArgumentAccessException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ArgumentAccessException(String message) { + super(message); + } + + public ArgumentAccessException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java new file mode 100644 index 00000000..e62d7864 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java @@ -0,0 +1,191 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; + +import org.apiguardian.api.API; + +/** + * {@code ArgumentsAccessor} defines the public API for accessing arguments provided + * by an {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} + * for a single invocation of a + * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method. + * + *

Specifically, an {@code ArgumentsAccessor} aggregates a set of + * arguments for a given invocation of a parameterized test and provides convenience + * methods for accessing those arguments in a type-safe manner with support for + * automatic type conversion. + * + *

An instance of {@code ArgumentsAccessor} will be automatically supplied + * for any parameter of type {@code ArgumentsAccessor} in a parameterized test. + * In addition, {@link ArgumentsAggregator} implementations are given access to + * an {@code ArgumentsAccessor}. + * + *

This interface is not intended to be implemented by clients. + * + *

Additional Kotlin arguments accessors can be + * found as extension functions in the {@link org.junit.jupiter.params.aggregator} + * package. + * + * @since 5.2 + * @see ArgumentsAggregator + * @see org.junit.jupiter.params.ParameterizedTest + */ +@API(status = STABLE, since = "5.7") +public interface ArgumentsAccessor { + + /** + * Get the value of the argument at the given index as an {@link Object}. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + */ + Object get(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as an instance of the + * required type. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @param requiredType the required type of the value; never {@code null} + * @return the value at the given index, potentially {@code null} + */ + T get(int index, Class requiredType) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Character}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Character getCharacter(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Boolean}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Boolean getBoolean(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Byte}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Byte getByte(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Short}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Short getShort(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Integer}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Integer getInteger(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Long}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Long getLong(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Float}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Float getFloat(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link Double}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + Double getDouble(int index) throws ArgumentAccessException; + + /** + * Get the value of the argument at the given index as a {@link String}, + * performing automatic type conversion as necessary. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @throws ArgumentAccessException if the value cannot be accessed + * or converted to the desired type + */ + String getString(int index) throws ArgumentAccessException; + + /** + * Get the number of arguments in this accessor. + */ + int size(); + + /** + * Get all arguments in this accessor as an array. + */ + Object[] toArray(); + + /** + * Get all arguments in this accessor as an immutable list. + */ + List toList(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java new file mode 100644 index 00000000..10b96762 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * {@code ArgumentsAggregationException} is an exception thrown by an + * {@link ArgumentsAggregator} when an error occurs while aggregating + * arguments. + * + * @since 5.2 + * @see ArgumentsAggregator + */ +@API(status = STABLE, since = "5.7") +public class ArgumentsAggregationException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ArgumentsAggregationException(String message) { + super(message); + } + + public ArgumentsAggregationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java new file mode 100644 index 00000000..52887ed5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ParameterContext; + +/** + * {@code ArgumentsAggregator} is an abstraction for the aggregation of arguments + * provided by an {@link org.junit.jupiter.params.provider.ArgumentsProvider + * ArgumentsProvider} for a single invocation of a + * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method + * into a single object. + * + *

An {@code ArgumentsAggregator} is applied to a method parameter of a + * {@code @ParameterizedTest} method via the {@link AggregateWith @AggregateWith} + * annotation. + * + *

The result of the aggregation will be passed as an argument to the + * {@code @ParameterizedTest} method for the annotated parameter. + * + *

A common use case is the aggregation of multiple columns from a single line + * in a CSV file into a domain object such as a {@code Person}, {@code Address}, + * {@code Order}, etc. + * + *

Implementations must provide a no-args constructor and should not make any + * assumptions regarding when they are instantiated or how often they are called. + * Since instances may potentially be cached and called from different threads, + * they should be thread-safe and designed to be used as singletons. + * + * @since 5.2 + * @see AggregateWith + * @see ArgumentsAccessor + * @see org.junit.jupiter.params.ParameterizedTest + */ +@API(status = STABLE, since = "5.7") +public interface ArgumentsAggregator { + + /** + * Aggregate the arguments contained in the supplied {@code accessor} into a + * single object. + * + * @param accessor an {@link ArgumentsAccessor} containing the arguments to be + * aggregated; never {@code null} + * @param context the parameter context where the aggregated result is to be + * supplied; never {@code null} + * @return the aggregated result; may be {@code null} but only if the target + * type is a reference type + * @throws ArgumentsAggregationException if an error occurs during the + * aggregation + */ + Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) + throws ArgumentsAggregationException; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java new file mode 100644 index 00000000..d0e4d363 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static java.lang.String.format; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.jupiter.params.converter.DefaultArgumentConverter; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.Preconditions; + +/** + * Default implementation of the {@link ArgumentsAccessor} API. + * + *

Delegates conversion to {@link DefaultArgumentConverter}. + * + * @since 5.2 + * @see ArgumentsAccessor + * @see DefaultArgumentConverter + * @see org.junit.jupiter.params.ParameterizedTest + */ +@API(status = INTERNAL, since = "5.2") +public class DefaultArgumentsAccessor implements ArgumentsAccessor { + + private final Object[] arguments; + + public DefaultArgumentsAccessor(Object... arguments) { + Preconditions.notNull(arguments, "Arguments array must not be null"); + this.arguments = arguments; + } + + @Override + public Object get(int index) { + Preconditions.condition(index >= 0 && index < this.arguments.length, + () -> format("index must be >= 0 and < %d", this.arguments.length)); + return this.arguments[index]; + } + + @Override + public T get(int index, Class requiredType) { + Preconditions.notNull(requiredType, "requiredType must not be null"); + Object value = get(index); + try { + Object convertedValue = DefaultArgumentConverter.INSTANCE.convert(value, requiredType); + return requiredType.cast(convertedValue); + } + catch (Exception ex) { + String message = format( + "Argument at index [%d] with value [%s] and type [%s] could not be converted or cast to type [%s].", + index, value, ClassUtils.nullSafeToString(value == null ? null : value.getClass()), + requiredType.getName()); + throw new ArgumentAccessException(message, ex); + } + } + + @Override + public Character getCharacter(int index) { + return get(index, Character.class); + } + + @Override + public Boolean getBoolean(int index) { + return get(index, Boolean.class); + } + + @Override + public Byte getByte(int index) { + return get(index, Byte.class); + } + + @Override + public Short getShort(int index) { + return get(index, Short.class); + } + + @Override + public Integer getInteger(int index) { + return get(index, Integer.class); + } + + @Override + public Long getLong(int index) { + return get(index, Long.class); + } + + @Override + public Float getFloat(int index) { + return get(index, Float.class); + } + + @Override + public Double getDouble(int index) { + return get(index, Double.class); + } + + @Override + public String getString(int index) { + return get(index, String.class); + } + + @Override + public int size() { + return this.arguments.length; + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(this.arguments, this.arguments.length); + } + + @Override + public List toList() { + return Collections.unmodifiableList(Arrays.asList(this.arguments)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java new file mode 100644 index 00000000..e7129488 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java @@ -0,0 +1,7 @@ +/** + * The {@link org.junit.jupiter.params.aggregator.ArgumentsAggregator} and + * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor} interfaces and the + * {@link org.junit.jupiter.params.aggregator.AggregateWith} annotation. + */ + +package org.junit.jupiter.params.aggregator; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java new file mode 100644 index 00000000..0bd9513c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * {@code ArgumentConversionException} is an exception that can occur when an + * object is converted to another object by an implementation of an + * {@link ArgumentConverter}. + * + * @since 5.0 + * @see ArgumentConverter + */ +@API(status = STABLE, since = "5.7") +public class ArgumentConversionException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ArgumentConversionException(String message) { + super(message); + } + + public ArgumentConversionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java new file mode 100644 index 00000000..d1758965 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ParameterContext; + +/** + * {@code ArgumentConverter} is an abstraction that allows an input object to + * be converted to an instance of a different class. + * + *

Such an {@code ArgumentConverter} is applied to the method parameter + * of a {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} + * method with the help of a + * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} annotation. + * + *

Implementations must provide a no-args constructor and should not make any + * assumptions regarding when they are instantiated or how often they are called. + * Since instances may potentially be cached and called from different threads, + * they should be thread-safe and designed to be used as singletons. + * + *

Extend {@link SimpleArgumentConverter} if your implementation only needs + * to know the target type and does not need access to the {@link ParameterContext} + * to perform the conversion. + * + *

Extend {@link TypedArgumentConverter} if your implementation always converts + * from a given source type into a given target type and does not need access to + * the {@link ParameterContext} to perform the conversion. + * + * @since 5.0 + * @see SimpleArgumentConverter + * @see org.junit.jupiter.params.ParameterizedTest + * @see org.junit.jupiter.params.converter.ConvertWith + * @see org.junit.jupiter.params.support.AnnotationConsumer + * @see SimpleArgumentConverter + * @see TypedArgumentConverter + */ +@API(status = STABLE, since = "5.7") +public interface ArgumentConverter { + + /** + * Convert the supplied {@code source} object according to the supplied + * {@code context}. + * + * @param source the source object to convert; may be {@code null} + * @param context the parameter context where the converted object will be + * used; never {@code null} + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * @throws ArgumentConversionException if an error occurs during the + * conversion + */ + Object convert(Object source, ParameterContext context) throws ArgumentConversionException; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java new file mode 100644 index 00000000..42be3c88 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ConvertWith} is an annotation that allows one to specify an explicit + * {@link ArgumentConverter}. + + *

This annotation may be applied to parameters of + * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} methods + * which need to have their {@code Arguments} converted before consuming them. + * + * @since 5.0 + * @see org.junit.jupiter.params.ParameterizedTest + * @see org.junit.jupiter.params.converter.ArgumentConverter + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +public @interface ConvertWith { + + /** + * The type of {@link ArgumentConverter} to use. + */ + Class value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java new file mode 100644 index 00000000..0db0027c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java @@ -0,0 +1,302 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; + +import java.io.File; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Currency; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code DefaultArgumentConverter} is the default implementation of the + * {@link ArgumentConverter} API. + * + *

The {@code DefaultArgumentConverter} is able to convert from strings to a + * number of primitive types and their corresponding wrapper types (Byte, Short, + * Integer, Long, Float, and Double), date and time types from the + * {@code java.time} package, and some additional common Java types such as + * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency}, + * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc. + * + *

If the source and target types are identical the source object will not + * be modified. + * + * @since 5.0 + * @see org.junit.jupiter.params.converter.ArgumentConverter + */ +@API(status = INTERNAL, since = "5.0") +public class DefaultArgumentConverter extends SimpleArgumentConverter { + + public static final DefaultArgumentConverter INSTANCE = new DefaultArgumentConverter(); + + private static final List stringToObjectConverters = unmodifiableList(asList( // + new StringToBooleanAndCharPrimitiveConverter(), // + new StringToNumericPrimitiveConverter(), // + new StringToEnumConverter(), // + new StringToJavaTimeConverter(), // + new StringToCommonJavaTypesConverter(), // + new FallbackStringToObjectConverter() // + )); + + private DefaultArgumentConverter() { + // nothing to initialize + } + + @Override + public Object convert(Object source, Class targetType) { + if (source == null) { + if (targetType.isPrimitive()) { + throw new ArgumentConversionException( + "Cannot convert null to primitive value of type " + targetType.getName()); + } + return null; + } + + if (ReflectionUtils.isAssignableTo(source, targetType)) { + return source; + } + + return convertToTargetType(source, toWrapperType(targetType)); + } + + private Object convertToTargetType(Object source, Class targetType) { + if (source instanceof String) { + Optional converter = stringToObjectConverters.stream().filter( + candidate -> candidate.canConvert(targetType)).findFirst(); + if (converter.isPresent()) { + try { + return converter.get().convert((String) source, targetType); + } + catch (Exception ex) { + if (ex instanceof ArgumentConversionException) { + // simply rethrow it + throw (ArgumentConversionException) ex; + } + // else + throw new ArgumentConversionException( + "Failed to convert String \"" + source + "\" to type " + targetType.getName(), ex); + } + } + } + throw new ArgumentConversionException("No implicit conversion to convert object of type " + + source.getClass().getName() + " to type " + targetType.getName()); + } + + private static Class toWrapperType(Class targetType) { + Class wrapperType = getWrapperType(targetType); + return wrapperType != null ? wrapperType : targetType; + } + + interface StringToObjectConverter { + + boolean canConvert(Class targetType); + + Object convert(String source, Class targetType) throws Exception; + + } + + private static class StringToBooleanAndCharPrimitiveConverter implements StringToObjectConverter { + + private static final Map, Function> CONVERTERS; + static { + Map, Function> converters = new HashMap<>(); + converters.put(Boolean.class, Boolean::valueOf); + converters.put(Character.class, source -> { + Preconditions.condition(source.length() == 1, () -> "String must have length of 1: " + source); + return source.charAt(0); + }); + CONVERTERS = unmodifiableMap(converters); + } + + @Override + public boolean canConvert(Class targetType) { + return CONVERTERS.containsKey(targetType); + } + + @Override + public Object convert(String source, Class targetType) { + return CONVERTERS.get(targetType).apply(source); + } + } + + private static class StringToNumericPrimitiveConverter implements StringToObjectConverter { + + private static final Map, Function> CONVERTERS; + static { + Map, Function> converters = new HashMap<>(); + converters.put(Byte.class, Byte::decode); + converters.put(Short.class, Short::decode); + converters.put(Integer.class, Integer::decode); + converters.put(Long.class, Long::decode); + converters.put(Float.class, Float::valueOf); + converters.put(Double.class, Double::valueOf); + CONVERTERS = unmodifiableMap(converters); + } + + @Override + public boolean canConvert(Class targetType) { + return CONVERTERS.containsKey(targetType); + } + + @Override + public Object convert(String source, Class targetType) { + return CONVERTERS.get(targetType).apply(source.replace("_", "")); + } + } + + private static class StringToEnumConverter implements StringToObjectConverter { + + @Override + public boolean canConvert(Class targetType) { + return targetType.isEnum(); + } + + @Override + public Object convert(String source, Class targetType) throws Exception { + return valueOf(targetType, source); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Object valueOf(Class targetType, String source) { + return Enum.valueOf(targetType, source); + } + } + + private static class StringToJavaTimeConverter implements StringToObjectConverter { + + private static final Map, Function> CONVERTERS; + static { + Map, Function> converters = new HashMap<>(); + converters.put(Duration.class, Duration::parse); + converters.put(Instant.class, Instant::parse); + converters.put(LocalDate.class, LocalDate::parse); + converters.put(LocalDateTime.class, LocalDateTime::parse); + converters.put(LocalTime.class, LocalTime::parse); + converters.put(MonthDay.class, MonthDay::parse); + converters.put(OffsetDateTime.class, OffsetDateTime::parse); + converters.put(OffsetTime.class, OffsetTime::parse); + converters.put(Period.class, Period::parse); + converters.put(Year.class, Year::parse); + converters.put(YearMonth.class, YearMonth::parse); + converters.put(ZonedDateTime.class, ZonedDateTime::parse); + converters.put(ZoneId.class, ZoneId::of); + converters.put(ZoneOffset.class, ZoneOffset::of); + CONVERTERS = Collections.unmodifiableMap(converters); + } + + @Override + public boolean canConvert(Class targetType) { + return CONVERTERS.containsKey(targetType); + } + + @Override + public Object convert(String source, Class targetType) throws Exception { + return CONVERTERS.get(targetType).apply(source); + } + } + + private static class StringToCommonJavaTypesConverter implements StringToObjectConverter { + + private static final Map, Function> CONVERTERS; + + static { + Map, Function> converters = new HashMap<>(); + + // java.lang + converters.put(Class.class, StringToCommonJavaTypesConverter::toClass); + // java.io and java.nio + converters.put(File.class, File::new); + converters.put(Charset.class, Charset::forName); + converters.put(Path.class, Paths::get); + // java.net + converters.put(URI.class, URI::create); + converters.put(URL.class, StringToCommonJavaTypesConverter::toURL); + // java.math + converters.put(BigDecimal.class, BigDecimal::new); + converters.put(BigInteger.class, BigInteger::new); + // java.util + converters.put(Currency.class, Currency::getInstance); + converters.put(Locale.class, Locale::new); + converters.put(UUID.class, UUID::fromString); + + CONVERTERS = Collections.unmodifiableMap(converters); + } + + @Override + public boolean canConvert(Class targetType) { + return CONVERTERS.containsKey(targetType); + } + + @Override + public Object convert(String source, Class targetType) throws Exception { + return CONVERTERS.get(targetType).apply(source); + } + + private static Class toClass(String type) { + //@formatter:off + return ReflectionUtils + .tryToLoadClass(type) + .getOrThrow(cause -> new ArgumentConversionException( + "Failed to convert String \"" + type + "\" to type " + Class.class.getName(), cause)); + //@formatter:on + } + + private static URL toURL(String url) { + try { + return URI.create(url).toURL(); + } + catch (MalformedURLException ex) { + throw new ArgumentConversionException( + "Failed to convert String \"" + url + "\" to type " + URL.class.getName(), ex); + } + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java new file mode 100644 index 00000000..be2ea7d1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java @@ -0,0 +1,174 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.util.ReflectionUtils.findConstructors; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; +import static org.junit.platform.commons.util.ReflectionUtils.isNotPrivate; +import static org.junit.platform.commons.util.ReflectionUtils.isNotStatic; +import static org.junit.platform.commons.util.ReflectionUtils.newInstance; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.junit.jupiter.params.converter.DefaultArgumentConverter.StringToObjectConverter; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code FallbackStringToObjectConverter} is a {@link StringToObjectConverter} + * that provides a fallback conversion strategy for converting from a + * {@link String} to a given target type by invoking a static factory method + * or factory constructor defined in the target type. + * + *

Search Algorithm

+ * + *
    + *
  1. Search for a single, non-private static factory method in the target + * type that converts from a String to the target type. Use the factory method + * if present.
  2. + *
  3. Search for a single, non-private constructor in the target type that + * accepts a String. Use the constructor if present.
  4. + *
+ * + *

If multiple suitable factory methods are discovered they will be ignored. + * If neither a single factory method nor a single constructor is found, this + * converter acts as a no-op. + * + * @since 5.1 + * @see DefaultArgumentConverter + */ +class FallbackStringToObjectConverter implements StringToObjectConverter { + + /** + * Implementation of the NULL Object Pattern. + */ + private static final Function NULL_EXECUTABLE = source -> source; + + /** + * Cache for factory methods and factory constructors. + * + *

Searches that do not find a factory method or constructor are tracked + * by the presence of a {@link #NULL_EXECUTABLE} object stored in the map. + * This prevents the framework from repeatedly searching for things which + * are already known not to exist. + */ + private static final ConcurrentHashMap, Function> factoryExecutableCache // + = new ConcurrentHashMap<>(64); + + @Override + public boolean canConvert(Class targetType) { + return findFactoryExecutable(targetType) != NULL_EXECUTABLE; + } + + @Override + public Object convert(String source, Class targetType) throws Exception { + Function executable = findFactoryExecutable(targetType); + Preconditions.condition(executable != NULL_EXECUTABLE, + "Illegal state: convert() must not be called if canConvert() returned false"); + + return executable.apply(source); + } + + private static Function findFactoryExecutable(Class targetType) { + return factoryExecutableCache.computeIfAbsent(targetType, type -> { + Method factoryMethod = findFactoryMethod(type); + if (factoryMethod != null) { + return source -> invokeMethod(factoryMethod, null, source); + } + Constructor constructor = findFactoryConstructor(type); + if (constructor != null) { + return source -> newInstance(constructor, source); + } + return NULL_EXECUTABLE; + }); + } + + private static Method findFactoryMethod(Class targetType) { + List factoryMethods = findMethods(targetType, new IsFactoryMethod(targetType), BOTTOM_UP); + if (factoryMethods.size() == 1) { + return factoryMethods.get(0); + } + return null; + } + + private static Constructor findFactoryConstructor(Class targetType) { + List> constructors = findConstructors(targetType, new IsFactoryConstructor(targetType)); + if (constructors.size() == 1) { + return constructors.get(0); + } + return null; + } + + /** + * {@link Predicate} that determines if the {@link Method} supplied to + * {@link #test(Method)} is a non-private static factory method for the + * supplied {@link #targetType}. + */ + static class IsFactoryMethod implements Predicate { + + private final Class targetType; + + IsFactoryMethod(Class targetType) { + this.targetType = targetType; + } + + @Override + public boolean test(Method method) { + // Please do not collapse the following into a single statement. + if (!method.getReturnType().equals(this.targetType)) { + return false; + } + if (isNotStatic(method)) { + return false; + } + return isNotPrivateAndAcceptsSingleStringArgument(method); + } + + } + + /** + * {@link Predicate} that determines if the {@link Constructor} supplied to + * {@link #test(Constructor)} is a non-private factory constructor for the + * supplied {@link #targetType}. + */ + static class IsFactoryConstructor implements Predicate> { + + private final Class targetType; + + IsFactoryConstructor(Class targetType) { + this.targetType = targetType; + } + + @Override + public boolean test(Constructor constructor) { + // Please do not collapse the following into a single statement. + if (!constructor.getDeclaringClass().equals(this.targetType)) { + return false; + } + return isNotPrivateAndAcceptsSingleStringArgument(constructor); + } + + } + + private static boolean isNotPrivateAndAcceptsSingleStringArgument(Executable executable) { + return isNotPrivate(executable) // + && (executable.getParameterCount() == 1) // + && (executable.getParameterTypes()[0] == String.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java new file mode 100644 index 00000000..7c1defaa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoLocalDate; +import java.time.chrono.ChronoLocalDateTime; +import java.time.chrono.ChronoZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalQuery; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.params.support.AnnotationConsumer; + +/** + * @since 5.0 + */ +class JavaTimeArgumentConverter extends SimpleArgumentConverter + implements AnnotationConsumer { + + private static final Map, TemporalQuery> TEMPORAL_QUERIES; + static { + Map, TemporalQuery> queries = new LinkedHashMap<>(); + queries.put(ChronoLocalDate.class, ChronoLocalDate::from); + queries.put(ChronoLocalDateTime.class, ChronoLocalDateTime::from); + queries.put(ChronoZonedDateTime.class, ChronoZonedDateTime::from); + queries.put(LocalDate.class, LocalDate::from); + queries.put(LocalDateTime.class, LocalDateTime::from); + queries.put(LocalTime.class, LocalTime::from); + queries.put(OffsetDateTime.class, OffsetDateTime::from); + queries.put(OffsetTime.class, OffsetTime::from); + queries.put(Year.class, Year::from); + queries.put(YearMonth.class, YearMonth::from); + queries.put(ZonedDateTime.class, ZonedDateTime::from); + TEMPORAL_QUERIES = Collections.unmodifiableMap(queries); + } + + private String pattern; + + @Override + public void accept(JavaTimeConversionPattern annotation) { + pattern = annotation.value(); + } + + @Override + public Object convert(Object input, Class targetClass) throws ArgumentConversionException { + if (!TEMPORAL_QUERIES.containsKey(targetClass)) { + throw new ArgumentConversionException("Cannot convert to " + targetClass.getName() + ": " + input); + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + TemporalQuery temporalQuery = TEMPORAL_QUERIES.get(targetClass); + return formatter.parse(input.toString(), temporalQuery); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java new file mode 100644 index 00000000..bbfab9e6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.params.ParameterizedTest; + +/** + * {@code @JavaTimeConversionPattern} is an annotation that allows a date/time + * conversion pattern to be specified on a parameter of a + * {@link ParameterizedTest @ParameterizedTest} method. + * + * @since 5.0 + * @see org.junit.jupiter.params.ParameterizedTest + * @see java.time.format.DateTimeFormatterBuilder#appendPattern(String) + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ConvertWith(JavaTimeArgumentConverter.class) +public @interface JavaTimeConversionPattern { + + /** + * The date/time conversion pattern. + * + * @see java.time.format.DateTimeFormatterBuilder#appendPattern(String) + */ + String value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java new file mode 100644 index 00000000..dc1a32ea --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ParameterContext; + +/** + * {@code SimpleArgumentConverter} is an abstract base class for + * {@link ArgumentConverter} implementations that only need to know the target + * type and do not need access to the {@link ParameterContext} to perform the + * conversion. + * + * @since 5.0 + * @see ArgumentConverter + * @see TypedArgumentConverter + */ +@API(status = STABLE, since = "5.7") +public abstract class SimpleArgumentConverter implements ArgumentConverter { + + @Override + public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + return convert(source, context.getParameter().getType()); + } + + /** + * Convert the supplied {@code source} object into the supplied + * {@code targetType}. + * + * @param source the source object to convert; may be {@code null} + * @param targetType the target type the source object should be converted + * into; never {@code null} + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * @throws ArgumentConversionException in case an error occurs during the + * conversion + */ + protected abstract Object convert(Object source, Class targetType) throws ArgumentConversionException; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java new file mode 100644 index 00000000..505ba540 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code TypedArgumentConverter} is an abstract base class for + * {@link ArgumentConverter} implementations that always convert objects of a + * given source type into a given target type. + * + * @param the type of the source argument to convert + * @param the type of the target object to create from the source + * @since 5.7 + * @see ArgumentConverter + * @see SimpleArgumentConverter + */ +@API(status = EXPERIMENTAL, since = "5.7") +public abstract class TypedArgumentConverter implements ArgumentConverter { + + private final Class sourceType; + private final Class targetType; + + /** + * Create a new {@code TypedArgumentConverter}. + * + * @param sourceType the type of the argument to convert; never {@code null} + * @param targetType the type of the target object to create from the source; + * never {@code null} + */ + protected TypedArgumentConverter(Class sourceType, Class targetType) { + this.sourceType = Preconditions.notNull(sourceType, "sourceType must not be null"); + this.targetType = Preconditions.notNull(targetType, "targetType must not be null"); + } + + @Override + public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + if (source == null) { + return convert(null); + } + if (!this.sourceType.isInstance(source)) { + String message = String.format( + "%s cannot convert objects of type [%s]. Only source objects of type [%s] are supported.", + getClass().getSimpleName(), source.getClass().getName(), this.sourceType.getName()); + throw new ArgumentConversionException(message); + } + if (!ReflectionUtils.isAssignableTo(this.targetType, context.getParameter().getType())) { + String message = String.format("%s cannot convert to type [%s]. Only target type [%s] is supported.", + getClass().getSimpleName(), context.getParameter().getType().getName(), this.targetType.getName()); + throw new ArgumentConversionException(message); + } + return convert(this.sourceType.cast(source)); + } + + /** + * Convert the supplied {@code source} object of type {@code S} into an object + * of type {@code T}. + * + * @param source the source object to convert; may be {@code null} + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * @throws ArgumentConversionException if an error occurs during the + * conversion + */ + protected abstract T convert(S source) throws ArgumentConversionException; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java new file mode 100644 index 00000000..ff813c18 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java @@ -0,0 +1,7 @@ +/** + * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} + * implementations and the corresponding + * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} annotation. + */ + +package org.junit.jupiter.params.converter; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java new file mode 100644 index 00000000..aa0820df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java @@ -0,0 +1,5 @@ +/** + * JUnit Jupiter extension for parameterized tests. + */ + +package org.junit.jupiter.params; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java new file mode 100644 index 00000000..da3371f3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code Arguments} is an abstraction that provides access to an array of + * objects to be used for invoking a {@code @ParameterizedTest} method. + * + *

A {@link java.util.stream.Stream} of such {@code Arguments} will + * typically be provided by an {@link ArgumentsProvider}. + * + * @apiNote

This interface is specifically designed as a simple holder of + * arguments of a parameterized test. Therefore, if you end up + * {@linkplain java.util.stream.Stream#map(java.util.function.Function) transforming} + * or + * {@linkplain java.util.stream.Stream#filter(java.util.function.Predicate) filtering} + * the arguments, you should consider using one of the following in intermediate + * steps: + * + *

    + *
  • The standard collections
  • + *
  • Tuples from third-party libraries, e.g., + * Commons Lang, + * or javatuples
  • + *
  • Your own data class
  • + *
+ * + *

Alternatively, you can use an + * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} + * to convert some of the arguments from one type to another. + * + * @since 5.0 + * @see org.junit.jupiter.params.ParameterizedTest + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.provider.ArgumentsProvider + * @see org.junit.jupiter.params.converter.ArgumentConverter + */ +@API(status = STABLE, since = "5.7") +public interface Arguments { + + /** + * Get the arguments used for an invocation of the + * {@code @ParameterizedTest} method. + * + * @apiNote If you need a type-safe way to access some or all of the arguments, + * please read the {@linkplain Arguments class-level API note}. + * + * @return the arguments; must not be {@code null} + */ + Object[] get(); + + /** + * Factory method for creating an instance of {@code Arguments} based on + * the supplied {@code arguments}. + * + * @param arguments the arguments to be used for an invocation of the test + * method; must not be {@code null} + * @return an instance of {@code Arguments}; never {@code null} + * @see #arguments(Object...) + */ + static Arguments of(Object... arguments) { + Preconditions.notNull(arguments, "argument array must not be null"); + return () -> arguments; + } + + /** + * Factory method for creating an instance of {@code Arguments} based on + * the supplied {@code arguments}. + * + *

This method is an alias for {@link Arguments#of} and is + * intended to be used when statically imported — for example, via: + * {@code import static org.junit.jupiter.params.provider.Arguments.arguments;} + * + * @param arguments the arguments to be used for an invocation of the test + * method; must not be {@code null} + * @return an instance of {@code Arguments}; never {@code null} + * @since 5.3 + */ + static Arguments arguments(Object... arguments) { + return of(arguments); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java new file mode 100644 index 00000000..e57f5423 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * An {@code ArgumentsProvider} is responsible for {@linkplain #provideArguments + * providing} a stream of arguments to be passed to a {@code @ParameterizedTest} + * method. + * + *

An {@code ArgumentsProvider} can be registered via the + * {@link ArgumentsSource @ArgumentsSource} annotation. + * + *

Implementations must provide a no-args constructor. + * + * @since 5.0 + * @see org.junit.jupiter.params.ParameterizedTest + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.provider.Arguments + * @see org.junit.jupiter.params.support.AnnotationConsumer + */ +@API(status = STABLE, since = "5.7") +public interface ArgumentsProvider { + + /** + * Provide a {@link Stream} of {@link Arguments} to be passed to a + * {@code @ParameterizedTest} method. + * + * @param context the current extension context; never {@code null} + * @return a stream of arguments; never {@code null} + */ + Stream provideArguments(ExtensionContext context) throws Exception; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java new file mode 100644 index 00000000..04f7849d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ArgumentsSource} is a {@linkplain Repeatable repeatable} annotation + * that is used to register {@linkplain ArgumentsProvider argument providers} + * for the annotated test method. + * + *

{@code @ArgumentsSource} may also be used as a meta-annotation in order to + * create a custom composed annotation that inherits the semantics + * of {@code @ArgumentsSource}. + * + * @since 5.0 + * @see org.junit.jupiter.params.provider.ArgumentsProvider + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Repeatable(ArgumentsSources.class) +@API(status = STABLE, since = "5.7") +public @interface ArgumentsSource { + + /** + * The type of {@link ArgumentsProvider} to be used. + */ + Class value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java new file mode 100644 index 00000000..320c8ad8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ArgumentsSources} is a simple container for one or more + * {@link ArgumentsSource} annotations. + * + *

Note, however, that use of the {@code @ArgumentsSources} container is completely + * optional since {@code @ArgumentsSource} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.0 + * @see org.junit.jupiter.params.provider.ArgumentsSource + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +public @interface ArgumentsSources { + + /** + * An array of one or more {@link ArgumentsSource @ArgumentsSource} + * annotations. + */ + ArgumentsSource[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java new file mode 100644 index 00000000..ceecaccb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -0,0 +1,164 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.junit.jupiter.params.provider.CsvParserFactory.createParserFor; +import static org.junit.platform.commons.util.CollectionUtils.toSet; + +import java.io.StringReader; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import com.univocity.parsers.csv.CsvParser; + +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.support.AnnotationConsumer; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * @since 5.0 + */ +class CsvArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { + + private static final String LINE_SEPARATOR = "\n"; + + private CsvSource annotation; + private Set nullValues; + private CsvParser csvParser; + + @Override + public void accept(CsvSource annotation) { + this.annotation = annotation; + this.nullValues = toSet(annotation.nullValues()); + this.csvParser = createParserFor(annotation); + } + + @Override + public Stream provideArguments(ExtensionContext context) { + final boolean textBlockDeclared = !this.annotation.textBlock().isEmpty(); + Preconditions.condition(this.annotation.value().length > 0 ^ textBlockDeclared, + () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); + + return textBlockDeclared ? parseTextBlock() : parseValueArray(); + } + + private Stream parseTextBlock() { + String textBlock = this.annotation.textBlock(); + boolean useHeadersInDisplayName = this.annotation.useHeadersInDisplayName(); + List argumentsList = new ArrayList<>(); + + try { + List csvRecords = this.csvParser.parseAll(new StringReader(textBlock)); + String[] headers = useHeadersInDisplayName ? getHeaders(this.csvParser) : null; + + AtomicInteger index = new AtomicInteger(0); + for (String[] csvRecord : csvRecords) { + index.incrementAndGet(); + Preconditions.notNull(csvRecord, + () -> "Record at index " + index + " contains invalid CSV: \"\"\"\n" + textBlock + "\n\"\"\""); + argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); + } + } + catch (Throwable throwable) { + throw handleCsvException(throwable, this.annotation); + } + + return argumentsList.stream(); + } + + private Stream parseValueArray() { + boolean useHeadersInDisplayName = this.annotation.useHeadersInDisplayName(); + List argumentsList = new ArrayList<>(); + + try { + String[] headers = null; + AtomicInteger index = new AtomicInteger(0); + for (String input : this.annotation.value()) { + index.incrementAndGet(); + String[] csvRecord = this.csvParser.parseLine(input + LINE_SEPARATOR); + // Lazily retrieve headers if necessary. + if (useHeadersInDisplayName && headers == null) { + headers = getHeaders(this.csvParser); + continue; + } + Preconditions.notNull(csvRecord, + () -> "Record at index " + index + " contains invalid CSV: \"" + input + "\""); + argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); + } + } + catch (Throwable throwable) { + throw handleCsvException(throwable, this.annotation); + } + + return argumentsList.stream(); + } + + // Cannot get parsed headers until after parsing has started. + static String[] getHeaders(CsvParser csvParser) { + return Arrays.stream(csvParser.getContext().parsedHeaders())// + .map(String::trim)// + .toArray(String[]::new); + } + + /** + * Processes custom null values, supports wrapping of column values in + * {@link Named} if necessary (for CSV header support), and returns the + * CSV record wrapped in an {@link Arguments} instance. + */ + static Arguments processCsvRecord(Object[] csvRecord, Set nullValues, boolean useHeadersInDisplayName, + String[] headers) { + + // Nothing to process? + if (nullValues.isEmpty() && !useHeadersInDisplayName) { + return Arguments.of(csvRecord); + } + + Preconditions.condition(!useHeadersInDisplayName || (csvRecord.length <= headers.length), + () -> String.format( + "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", + csvRecord.length, headers.length, Arrays.toString(csvRecord))); + + Object[] arguments = new Object[csvRecord.length]; + for (int i = 0; i < csvRecord.length; i++) { + Object column = csvRecord[i]; + if (nullValues.contains(column)) { + column = null; + } + if (useHeadersInDisplayName) { + column = Named.of(headers[i] + " = " + column, column); + } + arguments[i] = column; + } + return Arguments.of(arguments); + } + + /** + * @return this method always throws an exception and therefore never + * returns anything; the return type is merely present to allow this + * method to be supplied as the operand in a {@code throw} statement + */ + static RuntimeException handleCsvException(Throwable throwable, Annotation annotation) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + if (throwable instanceof PreconditionViolationException) { + throw (PreconditionViolationException) throwable; + } + throw new CsvParsingException("Failed to parse CSV input configured via " + annotation, throwable); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java new file mode 100644 index 00000000..14650f66 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -0,0 +1,216 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static java.util.Spliterators.spliteratorUnknownSize; +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; +import static org.junit.jupiter.params.provider.CsvArgumentsProvider.getHeaders; +import static org.junit.jupiter.params.provider.CsvArgumentsProvider.handleCsvException; +import static org.junit.jupiter.params.provider.CsvArgumentsProvider.processCsvRecord; +import static org.junit.jupiter.params.provider.CsvParserFactory.createParserFor; +import static org.junit.platform.commons.util.CollectionUtils.toSet; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.stream.Stream; + +import com.univocity.parsers.csv.CsvParser; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.support.AnnotationConsumer; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.0 + */ +class CsvFileArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { + + private final InputStreamProvider inputStreamProvider; + + private CsvFileSource annotation; + private List sources; + private Charset charset; + private int numLinesToSkip; + private CsvParser csvParser; + + CsvFileArgumentsProvider() { + this(DefaultInputStreamProvider.INSTANCE); + } + + CsvFileArgumentsProvider(InputStreamProvider inputStreamProvider) { + this.inputStreamProvider = inputStreamProvider; + } + + @Override + public void accept(CsvFileSource annotation) { + this.annotation = annotation; + Stream resources = Arrays.stream(annotation.resources()).map(inputStreamProvider::classpathResource); + Stream files = Arrays.stream(annotation.files()).map(inputStreamProvider::file); + this.sources = Stream.concat(resources, files).collect(toList()); + this.charset = getCharsetFrom(annotation); + this.numLinesToSkip = annotation.numLinesToSkip(); + this.csvParser = createParserFor(annotation); + } + + private Charset getCharsetFrom(CsvFileSource annotation) { + try { + return Charset.forName(annotation.encoding()); + } + catch (Exception ex) { + throw new PreconditionViolationException("The charset supplied in " + annotation + " is invalid", ex); + } + } + + @Override + public Stream provideArguments(ExtensionContext context) { + // @formatter:off + return Preconditions.notEmpty(this.sources, "Resources or files must not be empty") + .stream() + .map(source -> source.open(context)) + .map(this::beginParsing) + .flatMap(this::toStream); + // @formatter:on + } + + private CsvParser beginParsing(InputStream inputStream) { + try { + this.csvParser.beginParsing(inputStream, this.charset); + } + catch (Throwable throwable) { + handleCsvException(throwable, this.annotation); + } + return this.csvParser; + } + + private Stream toStream(CsvParser csvParser) { + CsvParserIterator iterator = new CsvParserIterator(csvParser, this.annotation); + return stream(spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) // + .skip(this.numLinesToSkip) // + .onClose(() -> { + try { + csvParser.stopParsing(); + } + catch (Throwable throwable) { + handleCsvException(throwable, this.annotation); + } + }); + } + + private static class CsvParserIterator implements Iterator { + + private final CsvParser csvParser; + private final CsvFileSource annotation; + private final boolean useHeadersInDisplayName; + private final Set nullValues; + private Arguments nextArguments; + private String[] headers; + + CsvParserIterator(CsvParser csvParser, CsvFileSource annotation) { + this.csvParser = csvParser; + this.annotation = annotation; + this.useHeadersInDisplayName = annotation.useHeadersInDisplayName(); + this.nullValues = toSet(annotation.nullValues()); + advance(); + } + + @Override + public boolean hasNext() { + return this.nextArguments != null; + } + + @Override + public Arguments next() { + Arguments result = this.nextArguments; + advance(); + return result; + } + + private void advance() { + try { + String[] csvRecord = this.csvParser.parseNext(); + if (csvRecord != null) { + // Lazily retrieve headers if necessary. + if (this.useHeadersInDisplayName && this.headers == null) { + this.headers = getHeaders(this.csvParser); + } + this.nextArguments = processCsvRecord(csvRecord, this.nullValues, this.useHeadersInDisplayName, + this.headers); + } + else { + this.nextArguments = null; + } + } + catch (Throwable throwable) { + handleCsvException(throwable, this.annotation); + } + } + + } + + @FunctionalInterface + private interface Source { + + InputStream open(ExtensionContext context); + + } + + interface InputStreamProvider { + + InputStream openClasspathResource(Class baseClass, String path); + + InputStream openFile(String path); + + default Source classpathResource(String path) { + return context -> openClasspathResource(context.getRequiredTestClass(), path); + } + + default Source file(String path) { + return context -> openFile(path); + } + + } + + private static class DefaultInputStreamProvider implements InputStreamProvider { + + private static final DefaultInputStreamProvider INSTANCE = new DefaultInputStreamProvider(); + + @Override + public InputStream openClasspathResource(Class baseClass, String path) { + Preconditions.notBlank(path, () -> "Classpath resource [" + path + "] must not be null or blank"); + InputStream inputStream = baseClass.getResourceAsStream(path); + return Preconditions.notNull(inputStream, () -> "Classpath resource [" + path + "] does not exist"); + } + + @Override + public InputStream openFile(String path) { + Preconditions.notBlank(path, () -> "File [" + path + "] must not be null or blank"); + try { + return Files.newInputStream(Paths.get(path)); + } + catch (IOException e) { + throw new JUnitException("File [" + path + "] could not be read", e); + } + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java new file mode 100644 index 00000000..6c0a765c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java @@ -0,0 +1,227 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @CsvFileSource} is an {@link ArgumentsSource} which is used to load + * comma-separated value (CSV) files from one or more classpath {@link #resources} + * or {@link #files}. + * + *

The CSV records parsed from these resources and files will be provided as + * arguments to the annotated {@code @ParameterizedTest} method. Note that the + * first record may optionally be used to supply CSV headers (see + * {@link #useHeadersInDisplayName}). + * + *

Any line beginning with a {@code #} symbol will be interpreted as a comment + * and will be ignored. + * + *

The column delimiter (which defaults to a comma ({@code ,})) can be customized + * via either {@link #delimiter} or {@link #delimiterString}. + * + *

In contrast to the default syntax used in {@code @CsvSource}, {@code @CsvFileSource} + * uses a double quote ({@code "}) as its quote character by default, but this can + * be changed via {@link #quoteCharacter}. An empty, quoted value ({@code ""}) + * results in an empty {@link String} unless the {@link #emptyValue} attribute is + * set; whereas, an entirely empty value is interpreted as a {@code null} + * reference. By specifying one or more {@link #nullValues} a custom value can be + * interpreted as a {@code null} reference (see the User Guide for an example). An + * {@link org.junit.jupiter.params.converter.ArgumentConversionException + * ArgumentConversionException} is thrown if the target type of a {@code null} + * reference is a primitive type. + * + *

NOTE: An unquoted empty value will always be converted to a + * {@code null} reference regardless of any custom values configured via the + * {@link #nullValues} attribute. + * + *

Except within a quoted string, leading and trailing whitespace in a CSV + * column is trimmed by default. This behavior can be changed by setting the + * {@link #ignoreLeadingAndTrailingWhitespace} attribute to {@code true}. + * + * @since 5.0 + * @see CsvSource + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ArgumentsSource(CsvFileArgumentsProvider.class) +public @interface CsvFileSource { + + /** + * The CSV classpath resources to use as the sources of arguments; must not + * be empty unless {@link #files} is non-empty. + */ + String[] resources() default {}; + + /** + * The CSV files to use as the sources of arguments; must not be empty + * unless {@link #resources} is non-empty. + */ + String[] files() default {}; + + /** + * The encoding to use when reading the CSV files; must be a valid charset. + * + *

Defaults to {@code "UTF-8"}. + * + * @see java.nio.charset.StandardCharsets + */ + String encoding() default "UTF-8"; + + /** + * The line separator to use when reading the CSV files; must consist of 1 + * or 2 characters, typically {@code "\r"}, {@code "\n"}, or {@code "\r\n"}. + * + *

Defaults to {@code "\n"}. + */ + String lineSeparator() default "\n"; + + /** + * Configures whether the first CSV record should be treated as header names + * for columns. + * + *

When set to {@code true}, the header names will be used in the + * generated display name for each {@code @ParameterizedTest} method + * invocation. When using this feature, you must ensure that the display name + * pattern for {@code @ParameterizedTest} includes + * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_PLACEHOLDER} instead of + * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_WITH_NAMES_PLACEHOLDER} + * as demonstrated in the example below. + * + *

Defaults to {@code false}. + * + * + *

Example

+ *
+	 * {@literal @}ParameterizedTest(name = "[{index}] {arguments}")
+	 * {@literal @}CsvFileSource(resources = "fruits.csv", useHeadersInDisplayName = true)
+	 * void test(String fruit, int rank) {
+	 *     // ...
+	 * }
+ * + * @since 5.8.2 + */ + @API(status = EXPERIMENTAL, since = "5.8.2") + boolean useHeadersInDisplayName() default false; + + /** + * The quote character to use for quoted strings. + * + *

Defaults to a double quote ({@code "}). + * + *

You may change the quote character to anything that makes sense for + * your use case. + * + * @since 5.8.2 + */ + @API(status = EXPERIMENTAL, since = "5.8.2") + char quoteCharacter() default '"'; + + /** + * The column delimiter character to use when reading the CSV files. + * + *

This is an alternative to {@link #delimiterString} and cannot be + * used in conjunction with {@link #delimiterString}. + * + *

Defaults implicitly to {@code ','}, if neither delimiter attribute is + * explicitly set. + */ + char delimiter() default '\0'; + + /** + * The column delimiter string to use when reading the CSV files. + * + *

This is an alternative to {@link #delimiter} and cannot be used in + * conjunction with {@link #delimiter}. + * + *

Defaults implicitly to {@code ","}, if neither delimiter attribute is + * explicitly set. + * + * @since 5.6 + */ + String delimiterString() default ""; + + /** + * The number of lines to skip when reading the CSV files. + * + *

Typically used to skip header lines. + * + *

Defaults to {@code 0}. + * + * @since 5.1 + */ + int numLinesToSkip() default 0; + + /** + * The empty value to use when reading the CSV files. + * + *

This value replaces quoted empty strings read from the input. + * + *

Defaults to {@code ""}. + * + * @since 5.5 + */ + String emptyValue() default ""; + + /** + * A list of strings that should be interpreted as {@code null} references. + * + *

For example, you may wish for certain values such as {@code "N/A"} or + * {@code "NIL"} to be converted to {@code null} references. + * + *

Please note that unquoted empty values will always be converted + * to {@code null} references regardless of the value of this {@code nullValues} + * attribute; whereas, a quoted empty string will be treated as an + * {@link #emptyValue}. + * + *

Defaults to {@code {}}. + * + * @since 5.6 + */ + String[] nullValues() default {}; + + /** + * The maximum number of characters allowed per CSV column. + * + *

Must be a positive number. + * + *

Defaults to {@code 4096}. + * + * @since 5.7 + */ + @API(status = EXPERIMENTAL, since = "5.7") + int maxCharsPerColumn() default 4096; + + /** + * Controls whether leading and trailing whitespace characters of unquoted + * CSV columns should be ignored. + * + *

Defaults to {@code true}. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + boolean ignoreLeadingAndTrailingWhitespace() default true; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java new file mode 100644 index 00000000..b31f5223 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import java.lang.annotation.Annotation; + +import com.univocity.parsers.csv.CsvParser; +import com.univocity.parsers.csv.CsvParserSettings; + +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.6 + */ +class CsvParserFactory { + + private static final String DEFAULT_DELIMITER = ","; + private static final String LINE_SEPARATOR = "\n"; + private static final char EMPTY_CHAR = '\0'; + private static final boolean COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE = true; + + static CsvParser createParserFor(CsvSource annotation) { + String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString()); + boolean commentProcessingEnabled = !annotation.textBlock().isEmpty(); + return createParser(delimiter, LINE_SEPARATOR, annotation.quoteCharacter(), annotation.emptyValue(), + annotation.maxCharsPerColumn(), commentProcessingEnabled, annotation.useHeadersInDisplayName(), + annotation.ignoreLeadingAndTrailingWhitespace()); + } + + static CsvParser createParserFor(CsvFileSource annotation) { + String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString()); + return createParser(delimiter, annotation.lineSeparator(), annotation.quoteCharacter(), annotation.emptyValue(), + annotation.maxCharsPerColumn(), COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE, + annotation.useHeadersInDisplayName(), annotation.ignoreLeadingAndTrailingWhitespace()); + } + + private static String selectDelimiter(Annotation annotation, char delimiter, String delimiterString) { + Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), + () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); + + if (delimiter != EMPTY_CHAR) { + return String.valueOf(delimiter); + } + if (!delimiterString.isEmpty()) { + return delimiterString; + } + return DEFAULT_DELIMITER; + } + + private static CsvParser createParser(String delimiter, String lineSeparator, char quote, String emptyValue, + int maxCharsPerColumn, boolean commentProcessingEnabled, boolean headerExtractionEnabled, + boolean ignoreLeadingAndTrailingWhitespace) { + return new CsvParser(createParserSettings(delimiter, lineSeparator, quote, emptyValue, maxCharsPerColumn, + commentProcessingEnabled, headerExtractionEnabled, ignoreLeadingAndTrailingWhitespace)); + } + + private static CsvParserSettings createParserSettings(String delimiter, String lineSeparator, char quote, + String emptyValue, int maxCharsPerColumn, boolean commentProcessingEnabled, boolean headerExtractionEnabled, + boolean ignoreLeadingAndTrailingWhitespace) { + + CsvParserSettings settings = new CsvParserSettings(); + settings.setHeaderExtractionEnabled(headerExtractionEnabled); + settings.getFormat().setDelimiter(delimiter); + settings.getFormat().setLineSeparator(lineSeparator); + settings.getFormat().setQuote(quote); + settings.getFormat().setQuoteEscape(quote); + settings.setEmptyValue(emptyValue); + settings.setCommentProcessingEnabled(commentProcessingEnabled); + settings.setAutoConfigurationEnabled(false); + settings.setIgnoreLeadingWhitespaces(ignoreLeadingAndTrailingWhitespace); + settings.setIgnoreTrailingWhitespaces(ignoreLeadingAndTrailingWhitespace); + Preconditions.condition(maxCharsPerColumn > 0, + () -> "maxCharsPerColumn must be a positive number: " + maxCharsPerColumn); + settings.setMaxCharsPerColumn(maxCharsPerColumn); + // Do not use the built-in support for skipping rows/lines since it will + // throw an IllegalArgumentException if the file does not contain at least + // the number of specified lines to skip. + // settings.setNumberOfRowsToSkip(annotation.numLinesToSkip()); + return settings; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java new file mode 100644 index 00000000..25267061 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * Thrown if an error is encountered while parsing CSV input. + * + * @since 5.3 + * @see CsvSource + * @see CsvFileSource + */ +@API(status = STABLE, since = "5.7") +public class CsvParsingException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public CsvParsingException(String message) { + super(message); + } + + public CsvParsingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java new file mode 100644 index 00000000..be44973c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java @@ -0,0 +1,281 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @CsvSource} is an {@link ArgumentsSource} which reads comma-separated + * values (CSV) from one or more CSV records supplied via the {@link #value} + * attribute or {@link #textBlock} attribute. + * + *

The supplied values will be provided as arguments to the annotated + * {@code @ParameterizedTest} method. + * + *

The column delimiter (which defaults to a comma ({@code ,})) can be customized + * via either {@link #delimiter} or {@link #delimiterString}. + * + *

By default, {@code @CsvSource} uses a single quote ({@code '}) as its quote + * character, but this can be changed via {@link #quoteCharacter}. See the + * {@code 'lemon, lime'} examples in the documentation for the {@link #value} + * and {@link #textBlock} attributes. An empty, quoted value ({@code ''}) results + * in an empty {@link String} unless the {@link #emptyValue} attribute is set; + * whereas, an entirely empty value is interpreted as a {@code null} reference. + * By specifying one or more {@link #nullValues} a custom value can be interpreted + * as a {@code null} reference (see the User Guide for an example). An + * {@link org.junit.jupiter.params.converter.ArgumentConversionException + * ArgumentConversionException} is thrown if the target type of a {@code null} + * reference is a primitive type. + * + *

NOTE: An unquoted empty value will always be converted to a + * {@code null} reference regardless of any custom values configured via the + * {@link #nullValues} attribute. + * + *

Except within a quoted string, leading and trailing whitespace in a CSV + * column is trimmed by default. This behavior can be changed by setting the + * {@link #ignoreLeadingAndTrailingWhitespace} attribute to {@code true}. + * + *

In general, CSV records should not contain explicit newlines ({@code \n}) + * unless they are placed within quoted strings. Note that CSV records supplied + * via {@link #textBlock} will implicitly contain newlines at the end of each + * physical line within the text block. Thus, if a CSV column wraps across a + * new line in a text block, the column must be a quoted string. + * + * @since 5.0 + * @see CsvFileSource + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ArgumentsSource(CsvArgumentsProvider.class) +public @interface CsvSource { + + /** + * The CSV records to use as the source of arguments; must not be empty. + * + *

Defaults to an empty array. You therefore must supply CSV content + * via this attribute or the {@link #textBlock} attribute. + * + *

Each value corresponds to a record in a CSV file and will be split using + * the specified {@link #delimiter} or {@link #delimiterString}. Note that + * the first value may optionally be used to supply CSV headers (see + * {@link #useHeadersInDisplayName}). + * + *

If text block syntax is supported by your programming language, + * you may find it more convenient to declare your CSV content via the + * {@link #textBlock} attribute. + * + *

Example

+ *
+	 * {@literal @}ParameterizedTest
+	 * {@literal @}CsvSource({
+	 *     "apple,         1",
+	 *     "banana,        2",
+	 *     "'lemon, lime', 0xF1",
+	 *     "strawberry,    700_000"
+	 * })
+	 * void test(String fruit, int rank) {
+	 *     // ...
+	 * }
+ * + * @see #textBlock + */ + String[] value() default {}; + + /** + * The CSV records to use as the source of arguments, supplied as a single + * text block; must not be empty. + * + *

Defaults to an empty string. You therefore must supply CSV content + * via this attribute or the {@link #value} attribute. + * + *

Text block syntax is supported by various languages on the JVM + * including Java SE 15 or higher. If text blocks are not supported, you + * should declare your CSV content via the {@link #value} attribute. + * + *

Each record in the text block corresponds to a record in a CSV file and will + * be split using the specified {@link #delimiter} or {@link #delimiterString}. + * Note that the first record may optionally be used to supply CSV headers (see + * {@link #useHeadersInDisplayName}). + * + *

In contrast to CSV records supplied via {@link #value}, a text block + * can contain comments. Any line beginning with a hash tag ({@code #}) will + * be treated as a comment and ignored. Note, however, that the {@code #} + * symbol must be the first character on the line without any leading + * whitespace. It is therefore recommended that the closing text block + * delimiter {@code """} be placed either at the end of the last line of + * input or on the following line, vertically aligned with the rest of the + * input (as can be seen in the example below). + * + *

Java's text block + * feature automatically removes incidental whitespace when the code + * is compiled. However other JVM languages such as Groovy and Kotlin do not. + * Thus, if you are using a programming language other than Java and your text + * block contains comments or new lines within quoted strings, you will need + * to ensure that there is no leading whitespace within your text block. + * + *

Example

+ *
+	 * {@literal @}ParameterizedTest
+	 * {@literal @}CsvSource(quoteCharacter = '"', textBlock = """
+	 *     # FRUIT,       RANK
+	 *     apple,         1
+	 *     banana,        2
+	 *     "lemon, lime", 0xF1
+	 *     strawberry,    700_000
+	 *     """)
+	 * void test(String fruit, int rank) {
+	 *     // ...
+	 * }
+ * + * @since 5.8.1 + * @see #value + * @see #quoteCharacter + */ + @API(status = EXPERIMENTAL, since = "5.8.1") + String textBlock() default ""; + + /** + * Configures whether the first CSV record should be treated as header names + * for columns. + * + *

When set to {@code true}, the header names will be used in the + * generated display name for each {@code @ParameterizedTest} method + * invocation. When using this feature, you must ensure that the display name + * pattern for {@code @ParameterizedTest} includes + * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_PLACEHOLDER} instead of + * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_WITH_NAMES_PLACEHOLDER} + * as demonstrated in the example below. + * + *

Defaults to {@code false}. + * + *

Example

+ *
+	 * {@literal @}ParameterizedTest(name = "[{index}] {arguments}")
+	 * {@literal @}CsvSource(useHeadersInDisplayName = true, textBlock = """
+	 *     FRUIT,         RANK
+	 *     apple,         1
+	 *     banana,        2
+	 *     'lemon, lime', 0xF1
+	 *     strawberry,    700_000
+	 *     """)
+	 * void test(String fruit, int rank) {
+	 *     // ...
+	 * }
+ * + * @since 5.8.2 + */ + @API(status = EXPERIMENTAL, since = "5.8.2") + boolean useHeadersInDisplayName() default false; + + /** + * The quote character to use for quoted strings. + * + *

Defaults to a single quote ({@code '}). + * + *

You may change the quote character to anything that makes sense for + * your use case; however, the primary use case is to allow you to use double + * quotes in {@link #textBlock}. + * + * @since 5.8.2 + * @see #textBlock + */ + @API(status = EXPERIMENTAL, since = "5.8.2") + char quoteCharacter() default '\''; + + /** + * The column delimiter character to use when reading the {@linkplain #value records}. + * + *

This is an alternative to {@link #delimiterString} and cannot be + * used in conjunction with {@link #delimiterString}. + * + *

Defaults implicitly to {@code ','}, if neither delimiter attribute is + * explicitly set. + */ + char delimiter() default '\0'; + + /** + * The column delimiter string to use when reading the {@linkplain #value records}. + * + *

This is an alternative to {@link #delimiter} and cannot be used in + * conjunction with {@link #delimiter}. + * + *

Defaults implicitly to {@code ","}, if neither delimiter attribute is + * explicitly set. + * + * @since 5.6 + */ + String delimiterString() default ""; + + /** + * The empty value to use when reading the {@linkplain #value records}. + * + *

This value replaces quoted empty strings read from the input. + * + *

Defaults to {@code ""}. + * + * @since 5.5 + */ + String emptyValue() default ""; + + /** + * A list of strings that should be interpreted as {@code null} references. + * + *

For example, you may wish for certain values such as {@code "N/A"} or + * {@code "NIL"} to be converted to {@code null} references. + * + *

Please note that unquoted empty values will always be converted + * to {@code null} references regardless of the value of this {@code nullValues} + * attribute; whereas, a quoted empty string will be treated as an + * {@link #emptyValue}. + * + *

Defaults to {@code {}}. + * + * @since 5.6 + */ + String[] nullValues() default {}; + + /** + * The maximum number of characters allowed per CSV column. + * + *

Must be a positive number. + * + *

Defaults to {@code 4096}. + * + * @since 5.7 + */ + @API(status = EXPERIMENTAL, since = "5.7") + int maxCharsPerColumn() default 4096; + + /** + * Controls whether leading and trailing whitespace characters of unquoted + * CSV columns should be ignored. + * + *

Defaults to {@code true}. + * + * @since 5.8 + */ + @API(status = EXPERIMENTAL, since = "5.8") + boolean ignoreLeadingAndTrailingWhitespace() default true; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java new file mode 100644 index 00000000..8debbb3a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.4 + * @see EmptySource + */ +class EmptyArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + Method testMethod = context.getRequiredTestMethod(); + Class[] parameterTypes = testMethod.getParameterTypes(); + + Preconditions.condition(parameterTypes.length > 0, () -> String.format( + "@EmptySource cannot provide an empty argument to method [%s]: the method does not declare any formal parameters.", + testMethod.toGenericString())); + + Class parameterType = parameterTypes[0]; + + if (String.class.equals(parameterType)) { + return Stream.of(arguments("")); + } + if (List.class.equals(parameterType)) { + return Stream.of(arguments(Collections.emptyList())); + } + if (Set.class.equals(parameterType)) { + return Stream.of(arguments(Collections.emptySet())); + } + if (Map.class.equals(parameterType)) { + return Stream.of(arguments(Collections.emptyMap())); + } + if (parameterType.isArray()) { + Object array = Array.newInstance(parameterType.getComponentType(), 0); + return Stream.of(arguments(array)); + } + // else + throw new PreconditionViolationException( + String.format("@EmptySource cannot provide an empty argument to method [%s]: [%s] is not a supported type.", + testMethod.toGenericString(), parameterType.getName())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java new file mode 100644 index 00000000..99023a73 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @EmptySource} is an {@link ArgumentsSource} which provides a single + * empty argument to the annotated {@code @ParameterizedTest} method. + * + *

Supported Parameter Types

+ * + *

This argument source will only provide an empty argument for the following + * method parameter types. Subtypes of the supported types are not supported. + * + *

    + *
  • {@link java.lang.String}
  • + *
  • {@link java.util.List}
  • + *
  • {@link java.util.Set}
  • + *
  • {@link java.util.Map}
  • + *
  • primitive arrays — for example {@code int[]}, {@code char[][]}, etc.
  • + *
  • object arrays — for example {@code String[]}, {@code Integer[][]}, etc.
  • + *
+ * + * @since 5.4 + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + * @see NullSource + * @see NullAndEmptySource + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ArgumentsSource(EmptyArgumentsProvider.class) +public @interface EmptySource { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java new file mode 100644 index 00000000..a4853eeb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toSet; + +import java.lang.reflect.Method; +import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.support.AnnotationConsumer; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.0 + */ +class EnumArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { + + private EnumSource enumSource; + + @Override + public void accept(EnumSource enumSource) { + this.enumSource = enumSource; + } + + @Override + public Stream provideArguments(ExtensionContext context) { + Set> constants = getEnumConstants(context); + EnumSource.Mode mode = enumSource.mode(); + String[] declaredConstantNames = enumSource.names(); + if (declaredConstantNames.length > 0) { + Set uniqueNames = stream(declaredConstantNames).collect(toSet()); + Preconditions.condition(uniqueNames.size() == declaredConstantNames.length, + () -> "Duplicate enum constant name(s) found in " + enumSource); + mode.validate(enumSource, constants, uniqueNames); + constants.removeIf(constant -> !mode.select(constant, uniqueNames)); + } + return constants.stream().map(Arguments::of); + } + + private > Set getEnumConstants(ExtensionContext context) { + Class enumClass = determineEnumClass(context); + return EnumSet.allOf(enumClass); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private > Class determineEnumClass(ExtensionContext context) { + Class enumClass = enumSource.value(); + if (enumClass.equals(NullEnum.class)) { + Method method = context.getRequiredTestMethod(); + Class[] parameterTypes = method.getParameterTypes(); + Preconditions.condition(parameterTypes.length > 0, + () -> "Test method must declare at least one parameter: " + method.toGenericString()); + Preconditions.condition(Enum.class.isAssignableFrom(parameterTypes[0]), + () -> "First parameter must reference an Enum type (alternatively, use the annotation's 'value' attribute to specify the type explicitly): " + + method.toGenericString()); + enumClass = parameterTypes[0]; + } + return enumClass; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java new file mode 100644 index 00000000..2a215ae1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java @@ -0,0 +1,183 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static java.util.stream.Collectors.toSet; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; + +/** + * {@code @EnumSource} is an {@link ArgumentsSource} for constants of + * an {@link Enum}. + * + *

The enum constants will be provided as arguments to the annotated + * {@code @ParameterizedTest} method. + * + *

The enum type can be specified explicitly using the {@link #value} + * attribute. Otherwise, the declared type of the first parameter of the + * {@code @ParameterizedTest} method is used. + * + *

The set of enum constants can be restricted via the {@link #names} and + * {@link #mode} attributes. + * + * @since 5.0 + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ArgumentsSource(EnumArgumentsProvider.class) +public @interface EnumSource { + + /** + * The enum type that serves as the source of the enum constants. + * + *

If this attribute is not set explicitly, the declared type of the + * first parameter of the {@code @ParameterizedTest} method is used. + * + * @see #names + * @see #mode + */ + Class> value() default NullEnum.class; + + /** + * The names of enum constants to provide, or regular expressions to select + * the names of enum constants to provide. + * + *

If no names or regular expressions are specified, all enum constants + * declared in the specified {@linkplain #value enum type} will be provided. + * + *

The {@link #mode} determines how the names are interpreted. + * + * @see #value + * @see #mode + */ + String[] names() default {}; + + /** + * The enum constant selection mode. + * + *

Defaults to {@link Mode#INCLUDE INCLUDE}. + * + * @see Mode#INCLUDE + * @see Mode#EXCLUDE + * @see Mode#MATCH_ALL + * @see Mode#MATCH_ANY + * @see Mode#MATCH_NONE + * @see #names + */ + Mode mode() default Mode.INCLUDE; + + /** + * Enumeration of modes for selecting enum constants by name. + */ + enum Mode { + + /** + * Select only those enum constants whose names are supplied via the + * {@link EnumSource#names} attribute. + */ + INCLUDE(Mode::validateNames, (name, names) -> names.contains(name)), + + /** + * Select all declared enum constants except those supplied via the + * {@link EnumSource#names} attribute. + */ + EXCLUDE(Mode::validateNames, (name, names) -> !names.contains(name)), + + /** + * Select only those enum constants whose names match all patterns supplied + * via the {@link EnumSource#names} attribute. + * + * @see java.util.stream.Stream#allMatch(java.util.function.Predicate) + */ + MATCH_ALL(Mode::validatePatterns, (name, patterns) -> patterns.stream().allMatch(name::matches)), + + /** + * Select only those enum constants whose names match any pattern supplied + * via the {@link EnumSource#names} attribute. + * + * @see java.util.stream.Stream#anyMatch(java.util.function.Predicate) + */ + MATCH_ANY(Mode::validatePatterns, (name, patterns) -> patterns.stream().anyMatch(name::matches)), + + /** + * Select only those enum constants whose names match none of the patterns supplied + * via the {@link EnumSource#names} attribute. + * + * @since 5.9 + * @see java.util.stream.Stream#noneMatch(java.util.function.Predicate) + */ + @API(status = EXPERIMENTAL, since = "5.9") + MATCH_NONE(Mode::validatePatterns, (name, patterns) -> patterns.stream().noneMatch(name::matches)); + + private final Validator validator; + private final BiPredicate> selector; + + Mode(Validator validator, BiPredicate> selector) { + this.validator = validator; + this.selector = selector; + } + + void validate(EnumSource enumSource, Set> constants, Set names) { + Preconditions.notNull(enumSource, "EnumSource must not be null"); + Preconditions.notNull(names, "names must not be null"); + + validator.validate(enumSource, constants, names); + } + + boolean select(Enum constant, Set names) { + Preconditions.notNull(constant, "Enum constant must not be null"); + Preconditions.notNull(names, "names must not be null"); + + return selector.test(constant.name(), names); + } + + private static void validateNames(EnumSource enumSource, Set> constants, Set names) { + Set allNames = constants.stream().map(Enum::name).collect(toSet()); + Preconditions.condition(allNames.containsAll(names), + () -> "Invalid enum constant name(s) in " + enumSource + ". Valid names include: " + allNames); + } + + private static void validatePatterns(EnumSource enumSource, Set> constants, + Set names) { + try { + names.forEach(Pattern::compile); + } + catch (PatternSyntaxException e) { + throw new PreconditionViolationException( + "Pattern compilation failed for a regular expression supplied in " + enumSource, e); + } + } + + private interface Validator { + void validate(EnumSource enumSource, Set> constants, Set names); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java new file mode 100644 index 00000000..d9fdffab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -0,0 +1,207 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static java.lang.String.format; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.support.AnnotationConsumer; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.CollectionUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; + +/** + * @since 5.0 + */ +class MethodArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { + + private String[] methodNames; + + private static final Predicate isFactoryMethod = // + method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method); + + @Override + public void accept(MethodSource annotation) { + this.methodNames = annotation.value(); + } + + @Override + public Stream provideArguments(ExtensionContext context) { + Class testClass = context.getRequiredTestClass(); + Method testMethod = context.getRequiredTestMethod(); + Object testInstance = context.getTestInstance().orElse(null); + // @formatter:off + return stream(this.methodNames) + .map(factoryMethodName -> findFactoryMethod(testClass, testMethod, factoryMethodName)) + .map(factoryMethod -> context.getExecutableInvoker().invoke(factoryMethod, testInstance)) + .flatMap(CollectionUtils::toStream) + .map(MethodArgumentsProvider::toArguments); + // @formatter:on + } + + private static Method findFactoryMethod(Class testClass, Method testMethod, String factoryMethodName) { + String originalFactoryMethodName = factoryMethodName; + + // If the user did not provide a factory method name, find a "default" local + // factory method with the same name as the parameterized test method. + if (StringUtils.isBlank(factoryMethodName)) { + factoryMethodName = testMethod.getName(); + return findFactoryMethodBySimpleName(testClass, testMethod, factoryMethodName); + } + + // Convert local factory method name to fully-qualified method name. + if (!looksLikeAFullyQualifiedMethodName(factoryMethodName)) { + factoryMethodName = testClass.getName() + "#" + factoryMethodName; + } + + // Find factory method using fully-qualified name. + Method factoryMethod = findFactoryMethodByFullyQualifiedName(testMethod, factoryMethodName); + + // Ensure factory method has a valid return type and is not a test method. + Preconditions.condition(isFactoryMethod.test(factoryMethod), () -> format( + "Could not find valid factory method [%s] for test class [%s] but found the following invalid candidate: %s", + originalFactoryMethodName, testClass.getName(), factoryMethod)); + + return factoryMethod; + } + + private static boolean looksLikeAFullyQualifiedMethodName(String factoryMethodName) { + if (factoryMethodName.contains("#")) { + return true; + } + int indexOfDot = factoryMethodName.indexOf("."); + if (indexOfDot == -1) { + return false; + } + int indexOfOpeningParenthesis = factoryMethodName.indexOf("("); + if (indexOfOpeningParenthesis > 0) { + // Exclude simple method names with parameters + return indexOfDot < indexOfOpeningParenthesis; + } + // If we get this far, we conclude the supplied factory method name "looks" + // like it was intended to be a fully-qualified method name, even if the + // syntax is invalid. We do this in order to provide better diagnostics for + // the user when a fully-qualified method name is in fact invalid. + return true; + } + + private static Method findFactoryMethodByFullyQualifiedName(Method testMethod, String fullyQualifiedMethodName) { + String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); + String className = methodParts[0]; + String methodName = methodParts[1]; + String methodParameters = methodParts[2]; + Class clazz = loadRequiredClass(className); + + // Attempt to find an exact match first. + Method factoryMethod = ReflectionUtils.findMethod(clazz, methodName, methodParameters).orElse(null); + if (factoryMethod != null) { + return factoryMethod; + } + + boolean explicitParameterListSpecified = // + StringUtils.isNotBlank(methodParameters) || fullyQualifiedMethodName.endsWith("()"); + + // If we didn't find an exact match but an explicit parameter list was specified, + // that's a user configuration error. + Preconditions.condition(!explicitParameterListSpecified, + () -> format("Could not find factory method [%s(%s)] in class [%s]", methodName, methodParameters, + className)); + + // Otherwise, fall back to the same lenient search semantics that are used + // to locate a "default" local factory method. + return findFactoryMethodBySimpleName(clazz, testMethod, methodName); + } + + /** + * Find the factory method by searching for all methods in the given {@code clazz} + * with the desired {@code factoryMethodName} which have return types that can be + * converted to a {@link Stream}, ignoring the {@code testMethod} itself as well + * as any {@code @Test}, {@code @TestTemplate}, or {@code @TestFactory} methods + * with the same name. + * @return the single factory method matching the search criteria + * @throws PreconditionViolationException if the factory method was not found or + * multiple competing factory methods with the same name were found + */ + private static Method findFactoryMethodBySimpleName(Class clazz, Method testMethod, String factoryMethodName) { + Predicate isCandidate = candidate -> factoryMethodName.equals(candidate.getName()) + && !testMethod.equals(candidate); + List candidates = ReflectionUtils.findMethods(clazz, isCandidate); + + List factoryMethods = candidates.stream().filter(isFactoryMethod).collect(toList()); + + Preconditions.condition(factoryMethods.size() > 0, () -> { + // If we didn't find the factory method using the isFactoryMethod Predicate, perhaps + // the specified factory method has an invalid return type or is a test method. + // In that case, we report the invalid candidates that were found. + if (candidates.size() > 0) { + return format( + "Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s", + factoryMethodName, clazz.getName(), candidates); + } + // Otherwise, report that we didn't find anything. + return format("Could not find factory method [%s] in class [%s]", factoryMethodName, clazz.getName()); + }); + Preconditions.condition(factoryMethods.size() == 1, + () -> format("%d factory methods named [%s] were found in class [%s]: %s", factoryMethods.size(), + factoryMethodName, clazz.getName(), factoryMethods)); + return factoryMethods.get(0); + } + + private static boolean isTestMethod(Method candidate) { + return isAnnotated(candidate, Test.class) || isAnnotated(candidate, TestTemplate.class) + || isAnnotated(candidate, TestFactory.class); + } + + private static Class loadRequiredClass(String className) { + return ReflectionUtils.tryToLoadClass(className).getOrThrow( + cause -> new JUnitException(format("Could not load class [%s]", className), cause)); + } + + private static Arguments toArguments(Object item) { + + // Nothing to do except cast. + if (item instanceof Arguments) { + return (Arguments) item; + } + + // Pass all multidimensional arrays "as is", in contrast to Object[]. + // See https://github.com/junit-team/junit5/issues/1665 + if (ReflectionUtils.isMultidimensionalArray(item)) { + return arguments(item); + } + + // Special treatment for one-dimensional reference arrays. + // See https://github.com/junit-team/junit5/issues/1665 + if (item instanceof Object[]) { + return arguments((Object[]) item); + } + + // Pass everything else "as is". + return arguments(item); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java new file mode 100644 index 00000000..94f6225f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java @@ -0,0 +1,135 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.junit.jupiter.params.ParameterizedTest; + +/** + * {@code @MethodSource} is an {@link ArgumentsSource} which provides access + * to values returned from {@linkplain #value() factory methods} of the class in + * which this annotation is declared or from static factory methods in external + * classes referenced by fully qualified method name. + * + *

Each factory method must generate a stream of arguments, + * and each set of "arguments" within the "stream" will be provided as the physical + * arguments for individual invocations of the annotated + * {@link ParameterizedTest @ParameterizedTest} method. Generally speaking this + * translates to a {@link java.util.stream.Stream Stream} of {@link Arguments} + * (i.e., {@code Stream}); however, the actual concrete return type + * can take on many forms. In this context, a "stream" is anything that JUnit + * can reliably convert into a {@code Stream}, such as + * {@link java.util.stream.Stream Stream}, + * {@link java.util.stream.DoubleStream DoubleStream}, + * {@link java.util.stream.LongStream LongStream}, + * {@link java.util.stream.IntStream IntStream}, + * {@link java.util.Collection Collection}, + * {@link java.util.Iterator Iterator}, + * {@link Iterable}, an array of objects, or an array of primitives. Each set of + * "arguments" within the "stream" can be supplied as an instance of + * {@link Arguments}, an array of objects (e.g., {@code Object[]}, + * {@code String[]}, etc.), or a single value if the parameterized test + * method accepts a single argument. + * + *

Please note that a one-dimensional array of objects supplied as a set of + * "arguments" will be handled differently than other types of arguments. + * Specifically, all of the elements of a one-dimensional array of objects will + * be passed as individual physical arguments to the {@code @ParameterizedTest} + * method. This behavior can be seen in the table below for the + * {@code static Stream factory()} method: the {@code @ParameterizedTest} + * method accepts individual {@code String} and {@code int} arguments rather than + * a single {@code Object[]} array. In contrast, any multidimensional array + * supplied as a set of "arguments" will be passed as a single physical argument + * to the {@code @ParameterizedTest} method without modification. This behavior + * can be seen in the table below for the {@code static Stream factory()} + * and {@code static Stream factory()} methods: the + * {@code @ParameterizedTest} methods for those factories accept individual + * {@code int[][]} and {@code Object[][]} arguments, respectively. + * + *

Examples

+ * + *

The following table displays compatible method signatures for parameterized + * test methods and their corresponding factory methods. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Compatible method signatures and factory methods
{@code @ParameterizedTest} methodFactory method
{@code void test(int)}{@code static int[] factory()}
{@code void test(int)}{@code static IntStream factory()}
{@code void test(String)}{@code static String[] factory()}
{@code void test(String)}{@code static List factory()}
{@code void test(String)}{@code static Stream factory()}
{@code void test(String, String)}{@code static String[][] factory()}
{@code void test(String, int)}{@code static Object[][] factory()}
{@code void test(String, int)}{@code static Stream factory()}
{@code void test(String, int)}{@code static Stream factory()}
{@code void test(int[])}{@code static int[][] factory()}
{@code void test(int[])}{@code static Stream factory()}
{@code void test(int[][])}{@code static Stream factory()}
{@code void test(Object[][])}{@code static Stream factory()}
+ * + *

Factory methods within the test class must be {@code static} unless the + * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} + * test instance lifecycle mode is used; whereas, factory methods in external + * classes must always be {@code static}. + * + *

Factory methods can declare parameters, which will be provided by registered + * implementations of {@link org.junit.jupiter.api.extension.ParameterResolver}. + * + * @since 5.0 + * @see Arguments + * @see ArgumentsSource + * @see ParameterizedTest + * @see org.junit.jupiter.api.TestInstance + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ArgumentsSource(MethodArgumentsProvider.class) +public @interface MethodSource { + + /** + * The names of factory methods within the test class or in external classes + * to use as sources for arguments. + * + *

Factory methods in external classes must be referenced by + * fully-qualified method name — for example, + * {@code "com.example.StringsProviders#blankStrings"} or + * {@code "com.example.TopLevelClass$NestedClass#classMethod"} for a factory + * method in a static nested class. + * + *

If a factory method accepts arguments that are provided by a + * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolver}, + * you can supply the formal parameter list in the qualified method name to + * disambiguate between overloaded variants of the factory method. For example, + * {@code "blankStrings(int)"} for a local qualified method name or + * {@code "com.example.StringsProviders#blankStrings(int)"} for a fully-qualified + * method name. + * + *

If no factory method names are declared, a method within the test class + * that has the same name as the test method will be used as the factory + * method by default. + * + *

For further information, see the {@linkplain MethodSource class-level Javadoc}. + */ + String[] value() default ""; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java new file mode 100644 index 00000000..ebbd4162 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @NullAndEmptySource} is a composed annotation that combines + * the functionality of {@link NullSource @NullSource} and + * {@link EmptySource @EmptySource}. + * + *

Annotating a {@code @ParameterizedTest} method with + * {@code @NullAndEmptySource} is equivalent to annotating the method with + * {@code @NullSource} and {@code @EmptySource}. + * + * @since 5.4 + * @see org.junit.jupiter.params.ParameterizedTest + * @see NullSource + * @see EmptySource + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@NullSource +@EmptySource +public @interface NullAndEmptySource { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java new file mode 100644 index 00000000..569dffde --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.lang.reflect.Method; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.4 + * @see NullSource + */ +class NullArgumentsProvider implements ArgumentsProvider { + + private static final Arguments nullArguments = arguments(new Object[] { null }); + + @Override + public Stream provideArguments(ExtensionContext context) { + Method testMethod = context.getRequiredTestMethod(); + Preconditions.condition(testMethod.getParameterCount() > 0, () -> String.format( + "@NullSource cannot provide a null argument to method [%s]: the method does not declare any formal parameters.", + testMethod.toGenericString())); + + return Stream.of(nullArguments); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java new file mode 100644 index 00000000..f040c26c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; + +/** + * Dummy enum class used as default value for optional attributes of + * annotations. + * + * @since 5.6 + * @see EnumSource#value() + */ +@API(status = INTERNAL, since = "5.7") +public enum NullEnum { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java new file mode 100644 index 00000000..283e78bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @NullSource} is an {@link ArgumentsSource} which provides a single + * {@code null} argument to the annotated {@code @ParameterizedTest} method. + * + *

Note that {@code @NullSource} cannot be used for an argument that has + * a primitive type, unless the argument is converted to a corresponding wrapper + * type with an {@link org.junit.jupiter.params.converter.ArgumentConverter}. + * + * @since 5.4 + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + * @see EmptySource + * @see NullAndEmptySource + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ArgumentsSource(NullArgumentsProvider.class) +public @interface NullSource { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java new file mode 100644 index 00000000..8bda32cf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static java.util.stream.Collectors.toList; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.support.AnnotationConsumer; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.0 + */ +class ValueArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { + + private Object[] arguments; + + @Override + public void accept(ValueSource source) { + // @formatter:off + List arrays = + Stream.of( + source.shorts(), + source.bytes(), + source.ints(), + source.longs(), + source.floats(), + source.doubles(), + source.chars(), + source.booleans(), + source.strings(), + source.classes() + ) + .filter(array -> Array.getLength(array) > 0) + .collect(toList()); + // @formatter:on + + Preconditions.condition(arrays.size() == 1, () -> "Exactly one type of input must be provided in the @" + + ValueSource.class.getSimpleName() + " annotation, but there were " + arrays.size()); + + Object originalArray = arrays.get(0); + arguments = IntStream.range(0, Array.getLength(originalArray)) // + .mapToObj(index -> Array.get(originalArray, index)) // + .toArray(); + } + + @Override + public Stream provideArguments(ExtensionContext context) { + return Arrays.stream(arguments).map(Arguments::of); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java new file mode 100644 index 00000000..bc63eb03 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ValueSource} is an {@link ArgumentsSource} which provides access to + * an array of literal values. + * + *

Supported types include {@link #shorts}, {@link #bytes}, {@link #ints}, + * {@link #longs}, {@link #floats}, {@link #doubles}, {@link #chars}, + * {@link #booleans}, {@link #strings}, and {@link #classes}. Note, however, + * that only one of the supported types may be specified per + * {@code @ValueSource} declaration. + * + *

The supplied literal values will be provided as arguments to the + * annotated {@code @ParameterizedTest} method. + * + * @since 5.0 + * @see org.junit.jupiter.params.provider.ArgumentsSource + * @see org.junit.jupiter.params.ParameterizedTest + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +@ArgumentsSource(ValueArgumentsProvider.class) +public @interface ValueSource { + + /** + * The {@code short} values to use as sources of arguments; must not be empty. + * + * @since 5.1 + */ + short[] shorts() default {}; + + /** + * The {@code byte} values to use as sources of arguments; must not be empty. + * + * @since 5.1 + */ + byte[] bytes() default {}; + + /** + * The {@code int} values to use as sources of arguments; must not be empty. + */ + int[] ints() default {}; + + /** + * The {@code long} values to use as sources of arguments; must not be empty. + */ + long[] longs() default {}; + + /** + * The {@code float} values to use as sources of arguments; must not be empty. + * + * @since 5.1 + */ + float[] floats() default {}; + + /** + * The {@code double} values to use as sources of arguments; must not be empty. + */ + double[] doubles() default {}; + + /** + * The {@code char} values to use as sources of arguments; must not be empty. + * + * @since 5.1 + */ + char[] chars() default {}; + + /** + * The {@code boolean} values to use as sources of arguments; must not be empty. + * + * @since 5.5 + */ + boolean[] booleans() default {}; + + /** + * The {@link String} values to use as sources of arguments; must not be empty. + */ + String[] strings() default {}; + + /** + * The {@link Class} values to use as sources of arguments; must not be empty. + * + * @since 5.1 + */ + Class[] classes() default {}; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java new file mode 100644 index 00000000..a41697ab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java @@ -0,0 +1,8 @@ +/** + * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} + * implementations and their corresponding + * {@link org.junit.jupiter.params.provider.ArgumentsSource ArgumentsSource} + * annotations. + */ + +package org.junit.jupiter.params.provider; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java new file mode 100644 index 00000000..4930441c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.support; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Annotation; +import java.util.function.Consumer; + +import org.apiguardian.api.API; + +/** + * {@code AnnotationConsumer} is a {@linkplain FunctionalInterface functional + * interface} for consuming annotations. + * + *

It is typically implemented by implementations of + * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} + * and {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} + * in order to signal that they can {@link #accept} a certain annotation. + * + * @since 5.0 + */ +@FunctionalInterface +@API(status = STABLE, since = "5.7") +public interface AnnotationConsumer extends Consumer { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java new file mode 100644 index 00000000..a9e2f01a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.support; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.AnnotationUtils; + +/** + * {@code AnnotationConsumerInitializer} is an internal helper class for + * initializing {@link AnnotationConsumer AnnotationConsumers}. + * + * @since 5.0 + */ +@API(status = INTERNAL, since = "5.0") +public final class AnnotationConsumerInitializer { + + private AnnotationConsumerInitializer() { + /* no-op */ + } + + // @formatter:off + private static final Predicate isAnnotationConsumerAcceptMethod = method -> + method.getName().equals("accept") + && method.getParameterCount() == 1 + && method.getParameterTypes()[0].isAnnotation(); + // @formatter:on + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static T initialize(AnnotatedElement annotatedElement, T instance) { + if (instance instanceof AnnotationConsumer) { + Method method = findMethods(instance.getClass(), isAnnotationConsumerAcceptMethod, BOTTOM_UP).get(0); + Class annotationType = (Class) method.getParameterTypes()[0]; + Annotation annotation = AnnotationUtils.findAnnotation(annotatedElement, annotationType) // + .orElseThrow(() -> new JUnitException(instance.getClass().getName() + + " must be used with an annotation of type " + annotationType.getName())); + initializeAnnotationConsumer((AnnotationConsumer) instance, annotation); + } + return instance; + } + + private static void initializeAnnotationConsumer(AnnotationConsumer instance, + A annotation) { + try { + instance.accept(annotation); + } + catch (Exception ex) { + throw new JUnitException("Failed to initialize AnnotationConsumer: " + instance, ex); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java new file mode 100644 index 00000000..56772a43 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java @@ -0,0 +1,9 @@ +/** + * Support classes for building + * {@linkplain org.junit.jupiter.params.provider.ArgumentsProvider providers} + * and + * {@linkplain org.junit.jupiter.params.converter.ArgumentConverter converters} + * for arguments. + */ + +package org.junit.jupiter.params.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt new file mode 100644 index 00000000..bd1ef56d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +@file:API(status = API.Status.STABLE, since = "5.7") + +package org.junit.jupiter.params.aggregator + +import org.apiguardian.api.API + +/** + * Get the value of the argument at the given index as an instance of the + * reified type. + * + * @param index the index of the argument to get; must be greater than or + * equal to zero and less than {@link #size} + * @return the value at the given index, potentially {@code null} + * @since 5.3 + * @receiver[ArgumentsAccessor] + * @see ArgumentsAccessor.get(Int, Class!) + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // method is in fact not shadowed due to reified type +inline fun ArgumentsAccessor.get(index: Int): T = + this.get(index, T::class.java) diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java new file mode 100644 index 00000000..7f3ea055 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * JUnit Jupiter extension for parameterized tests. + * + * @since 5.0 + */ +module org.junit.jupiter.params { + requires static transitive org.apiguardian.api; + requires transitive org.junit.jupiter.api; + requires transitive org.junit.platform.commons; + + exports org.junit.jupiter.params; + exports org.junit.jupiter.params.aggregator; + exports org.junit.jupiter.params.converter; + exports org.junit.jupiter.params.provider; + exports org.junit.jupiter.params.support; + + opens org.junit.jupiter.params to org.junit.platform.commons; + opens org.junit.jupiter.params.converter to org.junit.platform.commons; + opens org.junit.jupiter.params.provider to org.junit.platform.commons; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java new file mode 100644 index 00000000..12f044db --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -0,0 +1,369 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.ParameterizedTestExtension.arguments; + +import java.io.FileNotFoundException; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.execution.ExtensionValuesStore; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link ParameterizedTestExtension}. + * + * @since 5.0 + */ +class ParameterizedTestExtensionTests { + + private final ParameterizedTestExtension parameterizedTestExtension = new ParameterizedTestExtension(); + + static boolean streamWasClosed = false; + + @Test + void supportsReturnsFalseForMissingTestMethod() { + var extensionContextWithoutTestMethod = getExtensionContextReturningSingleMethod(new TestCaseWithoutMethod()); + assertFalse(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithoutTestMethod)); + } + + @Test + void supportsReturnsFalseForTestMethodWithoutParameterizedTestAnnotation() { + var extensionContextWithUnAnnotatedTestMethod = getExtensionContextReturningSingleMethod( + new TestCaseWithMethod()); + assertFalse(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithUnAnnotatedTestMethod)); + } + + @Test + void supportsReturnsTrueForTestMethodWithParameterizedTestAnnotation() { + var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( + new TestCaseWithAnnotatedMethod()); + assertTrue(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithAnnotatedTestMethod)); + } + + @Test + void streamsReturnedByProvidersAreClosedWhenCallingProvide() { + var extensionContext = getExtensionContextReturningSingleMethod( + new ArgumentsProviderWithCloseHandlerTestCase()); + // we need to call supportsTestTemplate() first, because it creates and + // puts the ParameterizedTestMethodContext into the Store + this.parameterizedTestExtension.supportsTestTemplate(extensionContext); + + var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext); + + assertFalse(streamWasClosed); + // cause the stream to be evaluated + stream.count(); + assertTrue(streamWasClosed); + } + + @Test + void emptyDisplayNameIsIllegal() { + var extensionContext = getExtensionContextReturningSingleMethod(new EmptyDisplayNameProviderTestCase()); + assertThrows(PreconditionViolationException.class, + () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext)); + } + + @Test + void defaultDisplayNameWithEmptyStringInConfigurationIsIllegal() { + AtomicInteger invocations = new AtomicInteger(); + Function> configurationSupplier = key -> { + if (key.equals(ParameterizedTestExtension.DISPLAY_NAME_PATTERN_KEY)) { + invocations.incrementAndGet(); + return Optional.of(""); + } + else { + return Optional.empty(); + } + }; + var extensionContext = getExtensionContextReturningSingleMethod(new DefaultDisplayNameProviderTestCase(), + configurationSupplier); + assertThrows(PreconditionViolationException.class, + () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext)); + assertEquals(1, invocations.get()); + } + + @Test + void argumentsRethrowsOriginalExceptionFromProviderAsUncheckedException() { + ArgumentsProvider failingProvider = (context) -> { + throw new FileNotFoundException("a message"); + }; + + var exception = assertThrows(FileNotFoundException.class, () -> arguments(failingProvider, null)); + assertEquals("a message", exception.getMessage()); + } + + @Test + void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { + var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( + new TestCaseWithAnnotatedMethod()); + + var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( + extensionContextWithAnnotatedTestMethod); + // cause the stream to be evaluated + stream.toArray(); + var exception = assertThrows(JUnitException.class, stream::close); + + assertThat(exception).hasMessage( + "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest"); + } + + @Test + void throwsExceptionWhenArgumentsProviderIsNotStatic() { + var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( + new NonStaticArgumentsProviderTestCase()); + + var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( + extensionContextWithAnnotatedTestMethod); + + var exception = assertThrows(JUnitException.class, stream::toArray); + + assertArgumentsProviderInstantiationException(exception, NonStaticArgumentsProvider.class); + } + + @Test + void throwsExceptionWhenArgumentsProviderDoesNotContainNoArgumentConstructor() { + var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( + new MissingNoArgumentsConstructorArgumentsProviderTestCase()); + + var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( + extensionContextWithAnnotatedTestMethod); + + var exception = assertThrows(JUnitException.class, stream::toArray); + + assertArgumentsProviderInstantiationException(exception, MissingNoArgumentsConstructorArgumentsProvider.class); + } + + private void assertArgumentsProviderInstantiationException(JUnitException exception, Class clazz) { + assertThat(exception).hasMessage( + String.format("Failed to find a no-argument constructor for ArgumentsProvider [%s]. " + + "Please ensure that a no-argument constructor exists and " + + "that the class is either a top-level class or a static nested class", + clazz.getName())); + } + + private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { + return getExtensionContextReturningSingleMethod(testCase, ignored -> Optional.empty()); + } + + private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, + Function> configurationSupplier) { + + // @formatter:off + var optional = Arrays.stream(testCase.getClass().getDeclaredMethods()) + .filter(method -> method.getName().equals("method")) + .findFirst(); + // @formatter:on + + return new ExtensionContext() { + + private final ExtensionValuesStore store = new ExtensionValuesStore(null); + + @Override + public Optional getTestMethod() { + return optional; + } + + @Override + public Optional getParent() { + return Optional.empty(); + } + + @Override + public ExtensionContext getRoot() { + return this; + } + + @Override + public String getUniqueId() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public Set getTags() { + return null; + } + + @Override + public Optional getElement() { + return Optional.empty(); + } + + @Override + public Optional> getTestClass() { + return Optional.empty(); + } + + @Override + public Optional getTestInstanceLifecycle() { + return Optional.empty(); + } + + @Override + public java.util.Optional getTestInstance() { + return Optional.empty(); + } + + @Override + public Optional getTestInstances() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + public Optional getConfigurationParameter(String key) { + return configurationSupplier.apply(key); + } + + @Override + public Optional getConfigurationParameter(String key, Function transformer) { + return configurationSupplier.apply(key).map(transformer); + } + + @Override + public void publishReportEntry(Map map) { + } + + @Override + public Store getStore(Namespace namespace) { + return new NamespaceAwareStore(store, namespace); + } + + @Override + public ExecutionMode getExecutionMode() { + return ExecutionMode.SAME_THREAD; + } + + @Override + public ExecutableInvoker getExecutableInvoker() { + return null; + } + }; + } + + static class TestCaseWithoutMethod { + } + + static class TestCaseWithMethod { + + void method() { + } + } + + static class TestCaseWithAnnotatedMethod { + + @ParameterizedTest + void method() { + } + } + + static class ArgumentsProviderWithCloseHandlerTestCase { + + @ParameterizedTest + @ArgumentsSource(ArgumentsProviderWithCloseHandler.class) + void method(String parameter) { + } + } + + static class ArgumentsProviderWithCloseHandler implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + var argumentsStream = Stream.of("foo", "bar").map(Arguments::of); + return argumentsStream.onClose(() -> streamWasClosed = true); + } + } + + static class NonStaticArgumentsProviderTestCase { + + @ParameterizedTest + @ArgumentsSource(NonStaticArgumentsProvider.class) + void method() { + } + } + + class NonStaticArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return null; + } + } + + static class MissingNoArgumentsConstructorArgumentsProviderTestCase { + + @ParameterizedTest + @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) + void method() { + } + } + + static class EmptyDisplayNameProviderTestCase { + + @ParameterizedTest(name = "") + @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) + void method() { + } + } + + static class DefaultDisplayNameProviderTestCase { + + @ParameterizedTest + @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) + void method() { + } + } + + static class MissingNoArgumentsConstructorArgumentsProvider implements ArgumentsProvider { + + MissingNoArgumentsConstructorArgumentsProvider(String parameter) { + } + + @Override + public Stream provideArguments(ExtensionContext context) { + return null; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java new file mode 100644 index 00000000..24d2a711 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -0,0 +1,1483 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.Named.named; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.appendTestTemplateInvocationSegment; +import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.jupiter.params.aggregator.AggregateWith; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; +import org.junit.jupiter.params.aggregator.ArgumentsAggregator; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.CsvFileSource; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Event; +import org.opentest4j.TestAbortedException; + +/** + * @since 5.0 + */ +class ParameterizedTestIntegrationTests { + + @ParameterizedTest + @CsvSource(quoteCharacter = '"', textBlock = """ + + + # This is a comment preceded by multiple opening blank lines. + apple, 1 + banana, 2 + # This is a comment pointing out that the next line contains multiple explicit newlines in quoted text. + "lemon \s + + + \s lime", 0xF1 + # The next line is a blank line in the middle of the CSV rows. + + strawberry, 700_000 + # This is a comment followed by 2 closing blank line. + + """) + void executesLinesFromTextBlock(String fruit, int rank) { + switch (fruit) { + case "apple" -> assertThat(rank).isEqualTo(1); + case "banana" -> assertThat(rank).isEqualTo(2); + case "lemon \n\n\n lime" -> assertThat(rank).isEqualTo(241); + case "strawberry" -> assertThat(rank).isEqualTo(700_000); + default -> fail("Unexpected fruit : " + fruit); + } + } + + @ParameterizedTest(name = "[{index}] {arguments}") + @CsvSource(delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL", textBlock = """ + #--------------------------------- + FRUIT | RANK + #--------------------------------- + apple | 1 + #--------------------------------- + banana | 2 + #--------------------------------- + cherry | 3.14159265358979323846 + #--------------------------------- + | 0 + #--------------------------------- + NIL | 0 + #--------------------------------- + """) + void executesLinesFromTextBlockUsingTableFormatAndHeadersAndNullValues(String fruit, double rank, + TestInfo testInfo) { + assertFruitTable(fruit, rank, testInfo); + } + + @ParameterizedTest(name = "[{index}] {arguments}") + @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") + void executesLinesFromClasspathResourceUsingTableFormatAndHeadersAndNullValues(String fruit, double rank, + TestInfo testInfo) { + assertFruitTable(fruit, rank, testInfo); + } + + private void assertFruitTable(String fruit, double rank, TestInfo testInfo) { + String displayName = testInfo.getDisplayName(); + + if (fruit == null) { + assertThat(rank).isEqualTo(0); + assertThat(displayName).matches("\\[(4|5)\\] FRUIT = null, RANK = 0"); + return; + } + + switch (fruit) { + case "apple" -> { + assertThat(rank).isEqualTo(1); + assertThat(displayName).isEqualTo("[1] FRUIT = apple, RANK = 1"); + } + case "banana" -> { + assertThat(rank).isEqualTo(2); + assertThat(displayName).isEqualTo("[2] FRUIT = banana, RANK = 2"); + } + case "cherry" -> { + assertThat(rank).isCloseTo(Math.PI, within(0.0)); + assertThat(displayName).isEqualTo("[3] FRUIT = cherry, RANK = 3.14159265358979323846"); + } + default -> fail("Unexpected fruit : " + fruit); + } + } + + @Test + void executesWithSingleArgumentsProviderWithMultipleInvocations() { + var results = execute("testWithTwoSingleStringArgumentsProvider", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } + + @Test + void executesWithCsvSource() { + var results = execute("testWithCsvSource", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } + + @Test + void executesWithCustomName() { + var results = execute("testWithCustomName", String.class, int.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("foo and 23"), finishedWithFailure(message("foo, 23")))) // + .haveExactly(1, event(test(), displayName("bar and 42"), finishedWithFailure(message("bar, 42")))); + } + + /** + * @since 5.2 + */ + @Test + void executesWithPrimitiveWideningConversion() { + var results = execute("testWithPrimitiveWideningConversion", double.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] num=1"), finishedWithFailure(message("num: 1.0")))) // + .haveExactly(1, event(test(), displayName("[2] num=2"), finishedWithFailure(message("num: 2.0")))); + } + + /** + * @since 5.1 + */ + @Test + void executesWithImplicitGenericConverter() { + var results = execute("testWithImplicitGenericConverter", Book.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] book=book 1"), finishedWithFailure(message("book 1")))) // + .haveExactly(1, event(test(), displayName("[2] book=book 2"), finishedWithFailure(message("book 2")))); + } + + @Test + void legacyReportingNames() { + var results = execute("testWithCustomName", String.class, int.class); + + // @formatter:off + var legacyReportingNames = results.testEvents().dynamicallyRegistered() + .map(Event::getTestDescriptor) + .map(TestDescriptor::getLegacyReportingName); + // @formatter:on + assertThat(legacyReportingNames).containsExactly("testWithCustomName(String, int)[1]", + "testWithCustomName(String, int)[2]"); + } + + @Test + void executesWithExplicitConverter() { + var results = execute("testWithExplicitConverter", int.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] length=O"), finishedWithFailure(message("length: 1")))) // + .haveExactly(1, + event(test(), displayName("[2] length=XXX"), finishedWithFailure(message("length: 3")))); + } + + @Test + void executesWithAggregator() { + var results = execute("testWithAggregator", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, + event(test(), displayName("[1] ab, cd"), finishedWithFailure(message("concatenation: abcd")))) // + .haveExactly(1, + event(test(), displayName("[2] ef, gh"), finishedWithFailure(message("concatenation: efgh")))); + } + + @Test + void executesWithIgnoreLeadingAndTrailingSetToFalseForCsvSource() { + var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvSource", String.class, + String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: ' ab ', ' cd'")))) // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef ', 'gh'")))); + } + + @Test + void executesWithIgnoreLeadingAndTrailingSetToTrueForCsvSource() { + var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource", String.class, + String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ab', 'cd'")))) // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef', 'gh'")))); + } + + @Test + void executesWithIgnoreLeadingAndTrailingSetToFalseForCsvFileSource() { + var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource", String.class, + String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: ' ab ', ' cd'")))) // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef ', 'gh'")))); + } + + @Test + void executesWithIgnoreLeadingAndTrailingSetToTrueForCsvFileSource() { + var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource", String.class, + String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ab', 'cd'")))) // + .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef', 'gh'")))); + } + + @Test + void failsContainerOnEmptyName() { + var results = execute("testWithEmptyName", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(container(), displayName("testWithEmptyName(String)"), // + finishedWithFailure(message(msg -> msg.contains("must be declared with a non-empty name"))))); + } + + @Test + void reportsExceptionForErroneousConverter() { + var results = execute("testWithErroneousConverter", Object.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), finishedWithFailure(instanceOf(ParameterResolutionException.class), // + message("Error converting parameter at index 0: something went horribly wrong")))); + } + + @Test + void executesLifecycleMethods() { + // reset static collections + LifecycleTestCase.lifecycleEvents.clear(); + LifecycleTestCase.testMethods.clear(); + + var results = execute(selectClass(LifecycleTestCase.class)); + results.allEvents().assertThatEvents() // + .haveExactly(1, + event(test("test1"), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test("test1"), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + + List testMethods = new ArrayList<>(LifecycleTestCase.testMethods); + + // @formatter:off + assertThat(LifecycleTestCase.lifecycleEvents).containsExactly( + "beforeAll:ParameterizedTestIntegrationTests$LifecycleTestCase", + "providerMethod", + "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "beforeEach:[1] argument=foo", + testMethods.get(0) + ":[1] argument=foo", + "afterEach:[1] argument=foo", + "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "beforeEach:[2] argument=bar", + testMethods.get(0) + ":[2] argument=bar", + "afterEach:[2] argument=bar", + "providerMethod", + "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "beforeEach:[1] argument=foo", + testMethods.get(1) + ":[1] argument=foo", + "afterEach:[1] argument=foo", + "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "beforeEach:[2] argument=bar", + testMethods.get(1) + ":[2] argument=bar", + "afterEach:[2] argument=bar", + "afterAll:ParameterizedTestIntegrationTests$LifecycleTestCase"); + // @formatter:on + } + + @Test + void truncatesArgumentsThatExceedMaxLength() { + var results = EngineTestKit.engine(new JupiterTestEngine()) // + .configurationParameter(ParameterizedTestExtension.ARGUMENT_MAX_LENGTH_KEY, "2") // + .selectors(selectMethod(TestCase.class, "testWithCsvSource", String.class.getName())) // + .execute(); + results.testEvents().assertThatEvents() // + .haveExactly(1, event(displayName("[1] argument=f…"), started())) // + .haveExactly(1, event(displayName("[2] argument=b…"), started())); + } + + @Test + void displayNamePatternFromConfiguration() { + var results = EngineTestKit.engine(new JupiterTestEngine()) // + .configurationParameter(ParameterizedTestExtension.DISPLAY_NAME_PATTERN_KEY, "{index}") // + .selectors(selectMethod(TestCase.class, "testWithCsvSource", String.class.getName())) // + .execute(); + results.testEvents().assertThatEvents() // + .haveExactly(1, event(displayName("1"), started())) // + .haveExactly(1, event(displayName("2"), started())); + } + + private EngineExecutionResults execute(DiscoverySelector... selectors) { + return EngineTestKit.engine(new JupiterTestEngine()).selectors(selectors).execute(); + } + + private EngineExecutionResults execute(Class testClass, String methodName, Class... methodParameterTypes) { + return execute(selectMethod(testClass, methodName, ClassUtils.nullSafeToString(methodParameterTypes))); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return execute(TestCase.class, methodName, methodParameterTypes); + } + + /** + * @since 5.4 + */ + @Nested + class NullSourceIntegrationTests { + + @Test + void executesWithNullSourceForString() { + var results = execute("testWithNullSourceForString", String.class); + results.testEvents().failed().assertEventsMatchExactly( + event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); + } + + @Test + void executesWithNullSourceForStringAndTestInfo() { + var results = execute("testWithNullSourceForStringAndTestInfo", String.class, TestInfo.class); + results.testEvents().failed().assertEventsMatchExactly( + event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); + } + + @Test + void executesWithNullSourceForNumber() { + var results = execute("testWithNullSourceForNumber", Number.class); + results.testEvents().failed().assertEventsMatchExactly( + event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); + } + + @Test + void failsWithNullSourceWithZeroFormalParameters() { + var methodName = "testWithNullSourceWithZeroFormalParameters"; + execute(methodName).containerEvents().failed().assertEventsMatchExactly(// + event(container(methodName), // + finishedWithFailure(// + instanceOf(PreconditionViolationException.class), // + message(msg -> msg.matches( + "@NullSource cannot provide a null argument to method .+: the method does not declare any formal parameters."))))); + } + + @Test + void failsWithNullSourceForPrimitive() { + var results = execute("testWithNullSourceForPrimitive", int.class); + results.testEvents().failed().assertEventsMatchExactly(event(test(), displayName("[1] argument=null"), + finishedWithFailure(instanceOf(ParameterResolutionException.class), message( + "Error converting parameter at index 0: Cannot convert null to primitive value of type int")))); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return ParameterizedTestIntegrationTests.this.execute(NullSourceTestCase.class, methodName, + methodParameterTypes); + } + + } + + /** + * @since 5.4 + */ + @Nested + class EmptySourceIntegrationTests { + + @Test + void executesWithEmptySourceForString() { + var results = execute("testWithEmptySourceForString", String.class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument="))); + } + + @Test + void executesWithEmptySourceForStringAndTestInfo() { + var results = execute("testWithEmptySourceForStringAndTestInfo", String.class, TestInfo.class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument="))); + } + + @Test + void executesWithEmptySourceForList() { + var results = execute("testWithEmptySourceForList", List.class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + } + + @Test + void executesWithEmptySourceForSet() { + var results = execute("testWithEmptySourceForSet", Set.class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + } + + @Test + void executesWithEmptySourceForMap() { + var results = execute("testWithEmptySourceForMap", Map.class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument={}"))); + } + + @Test + void executesWithEmptySourceForOneDimensionalPrimitiveArray() { + var results = execute("testWithEmptySourceForOneDimensionalPrimitiveArray", int[].class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + } + + @Test + void executesWithEmptySourceForOneDimensionalStringArray() { + var results = execute("testWithEmptySourceForOneDimensionalStringArray", String[].class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + } + + @Test + void executesWithEmptySourceForTwoDimensionalPrimitiveArray() { + var results = execute("testWithEmptySourceForTwoDimensionalPrimitiveArray", int[][].class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + } + + @Test + void executesWithEmptySourceForTwoDimensionalStringArray() { + var results = execute("testWithEmptySourceForTwoDimensionalStringArray", String[][].class); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + } + + @Test + void failsWithEmptySourceWithZeroFormalParameters() { + var methodName = "testWithEmptySourceWithZeroFormalParameters"; + execute(methodName).containerEvents().failed().assertEventsMatchExactly(// + event(container(methodName), // + finishedWithFailure(// + instanceOf(PreconditionViolationException.class), // + message(msg -> msg.matches( + "@EmptySource cannot provide an empty argument to method .+: the method does not declare any formal parameters."))))); + } + + @ParameterizedTest(name = "{1}") + @CsvSource({ // + "testWithEmptySourceForPrimitive, int", // + "testWithEmptySourceForUnsupportedReferenceType, java.lang.Integer", // + "testWithEmptySourceForUnsupportedListSubtype, java.util.ArrayList", // + "testWithEmptySourceForUnsupportedSetSubtype, java.util.HashSet", // + "testWithEmptySourceForUnsupportedMapSubtype, java.util.HashMap"// + }) + void failsWithEmptySourceForUnsupportedType(String methodName, Class parameterType) { + execute(methodName, parameterType).containerEvents().failed().assertEventsMatchExactly(// + event(container(methodName), // + finishedWithFailure(// + instanceOf(PreconditionViolationException.class), // + message(msg -> msg.matches("@EmptySource cannot provide an empty argument to method .+: \\[" + + parameterType.getName() + "] is not a supported type."))// + ))); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return ParameterizedTestIntegrationTests.this.execute(EmptySourceTestCase.class, methodName, + methodParameterTypes); + } + + } + + /** + * @since 5.4 + */ + @Nested + class NullAndEmptySourceIntegrationTests { + + @Test + void executesWithNullAndEmptySourceForString() { + var results = execute("testWithNullAndEmptySourceForString", String.class); + assertNullAndEmptyString(results); + } + + @Test + void executesWithNullAndEmptySourceForStringAndTestInfo() { + var results = execute("testWithNullAndEmptySourceForStringAndTestInfo", String.class, TestInfo.class); + assertNullAndEmptyString(results); + } + + @Test + void executesWithNullAndEmptySourceForList() { + var results = execute("testWithNullAndEmptySourceForList", List.class); + assertNullAndEmpty(results); + } + + @Test + void executesWithNullAndEmptySourceForOneDimensionalPrimitiveArray() { + var results = execute("testWithNullAndEmptySourceForOneDimensionalPrimitiveArray", int[].class); + assertNullAndEmpty(results); + } + + @Test + void executesWithNullAndEmptySourceForTwoDimensionalStringArray() { + var results = execute("testWithNullAndEmptySourceForTwoDimensionalStringArray", String[][].class); + assertNullAndEmpty(results); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return ParameterizedTestIntegrationTests.this.execute(NullAndEmptySourceTestCase.class, methodName, + methodParameterTypes); + } + + private void assertNullAndEmptyString(EngineExecutionResults results) { + results.testEvents().succeeded().assertEventsMatchExactly(// + event(test(), displayName("[1] argument=null")), // + event(test(), displayName("[2] argument="))// + ); + } + + private void assertNullAndEmpty(EngineExecutionResults results) { + results.testEvents().succeeded().assertEventsMatchExactly(// + event(test(), displayName("[1] argument=null")), // + event(test(), displayName("[2] argument=[]"))// + ); + } + + } + + @Nested + class MethodSourceIntegrationTests { + + @Test + void emptyMethodSource() { + execute("emptyMethodSource", String.class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("empty method source")))); + } + + /** + * @since 5.3.2 + */ + @Test + void oneDimensionalPrimitiveArray() { + execute("oneDimensionalPrimitiveArray", int.class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("1"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("2")))); + } + + /** + * @since 5.3.2 + */ + @Test + void twoDimensionalPrimitiveArray() { + execute("twoDimensionalPrimitiveArray", int[].class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); + } + + /** + * @since 5.3.2 + */ + @Test + void oneDimensionalObjectArray() { + execute("oneDimensionalObjectArray", Object.class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("2"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("three")))); + } + + /** + * @since 5.3.2 + */ + @Test + void oneDimensionalStringArray() { + execute("oneDimensionalStringArray", String.class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("two")))); + } + + @Test + void twoDimensionalObjectArray() { + execute("twoDimensionalObjectArray", String.class, int.class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); + } + + /** + * @since 5.3.2 + */ + @Test + void twoDimensionalStringArray() { + execute("twoDimensionalStringArray", String.class, String.class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("one:two"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("three:four")))); + } + + /** + * @since 5.3.2 + */ + @Test + void streamOfOneDimensionalPrimitiveArrays() { + execute("streamOfOneDimensionalPrimitiveArrays", int[].class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); + } + + /** + * @since 5.3.2 + */ + @Test + void streamOfTwoDimensionalPrimitiveArrays() { + assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArrays"); + } + + /** + * @since 5.3.2 + */ + @Test + void streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays() { + assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays"); + } + + /** + * @since 5.3.2 + */ + @Test + void streamOfTwoDimensionalPrimitiveArraysWrappedInArguments() { + assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArraysWrappedInArguments"); + } + + private void assertStreamOfTwoDimensionalPrimitiveArrays(String methodName) { + execute(methodName, int[][].class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("[[1, 2], [3, 4]]"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("[[5, 6], [7, 8]]")))); + } + + /** + * @since 5.3.2 + */ + @Test + void streamOfOneDimensionalObjectArrays() { + execute("streamOfOneDimensionalObjectArrays", String.class, int.class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); + } + + /** + * @since 5.3.2 + */ + @Test + void streamOfTwoDimensionalObjectArrays() { + execute("streamOfTwoDimensionalObjectArrays", Object[][].class).testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(message("[[one, 2], [three, 4]]"))))// + .haveExactly(1, event(test(), finishedWithFailure(message("[[five, 6], [seven, 8]]")))); + } + + @Test + void reportsContainerWithAssumptionFailureInMethodSourceAsAborted() { + execute("assumptionFailureInMethodSourceFactoryMethod", String.class).allEvents().assertThatEvents() // + .haveExactly(1, event(container("test-template:assumptionFailureInMethodSourceFactoryMethod"), // + abortedWithReason(instanceOf(TestAbortedException.class), + message("Assumption failed: nothing to test")))); + } + + @Test + void namedParameters() { + execute("namedParameters", String.class).allEvents().assertThatEvents() // + .haveAtLeast(1, + event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) // + .haveAtLeast(1, + event(test(), displayName("default name"), finishedWithFailure(message("default name")))); + } + + @Test + void nameParametersAlias() { + execute("namedParametersAlias", String.class).allEvents().assertThatEvents() // + .haveAtLeast(1, + event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) // + .haveAtLeast(1, + event(test(), displayName("default name"), finishedWithFailure(message("default name")))); + } + + /** + * @since 5.9.1 + * @see https://github.com/junit-team/junit5/issues/3001 + */ + @Test + void duplicateMethodNames() { + // It is sufficient to assert that 8 tests started and finished, because + // without the fix for #3001 the 4 parameterized tests would fail. In + // other words, we're not really testing the support for @RepeatedTest + // and @TestFactory, but their presence also contributes to the bug + // reported in #3001. + ParameterizedTestIntegrationTests.this.execute(selectClass(DuplicateMethodNamesMethodSourceTestCase.class))// + .testEvents()// + .assertStatistics(stats -> stats.started(8).failed(0).finished(8)); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return ParameterizedTestIntegrationTests.this.execute(MethodSourceTestCase.class, methodName, + methodParameterTypes); + } + + } + + @Nested + class UnusedArgumentsIntegrationTests { + + @Test + void executesWithArgumentsSourceProvidingUnusedArguments() { + var results = execute("testWithTwoUnusedStringArgumentsProvider", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } + + @Test + void executesWithCsvSourceContainingUnusedArguments() { + var results = execute("testWithCsvSourceContainingUnusedArguments", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } + + @Test + void executesWithCsvFileSourceContainingUnusedArguments() { + var results = execute("testWithCsvFileSourceContainingUnusedArguments", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } + + @Test + void executesWithMethodSourceProvidingUnusedArguments() { + var results = execute("testWithMethodSourceProvidingUnusedArguments", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return ParameterizedTestIntegrationTests.this.execute(UnusedArgumentsTestCase.class, methodName, + methodParameterTypes); + } + + } + + @Test + void closeAutoCloseableArgumentsAfterTest() { + var results = execute("testWithAutoCloseableArgument", AutoCloseableArgument.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), finishedSuccessfully())); + + assertTrue(AutoCloseableArgument.isClosed); + } + + @Test + void executesTwoIterationsBasedOnIterationAndUniqueIdSelector() { + var methodId = uniqueIdForTestTemplateMethod(TestCase.class, "testWithThreeIterations(int)"); + var results = execute(selectUniqueId(appendTestTemplateInvocationSegment(methodId, 3)), + selectIteration(selectMethod(TestCase.class, "testWithThreeIterations", "int"), 1)); + + results.allEvents().assertThatEvents() // + .haveExactly(2, event(test(), finishedWithFailure())) // + .haveExactly(1, event(test(), displayName("[2] argument=3"), finishedWithFailure())) // + .haveExactly(1, event(test(), displayName("[3] argument=5"), finishedWithFailure())); + } + + // ------------------------------------------------------------------------- + + static class TestCase { + + @ParameterizedTest + @ArgumentsSource(TwoSingleStringArgumentsProvider.class) + void testWithTwoSingleStringArgumentsProvider(String argument) { + fail(argument); + } + + @ParameterizedTest + @CsvSource({ "foo", "bar" }) + void testWithCsvSource(String argument) { + fail(argument); + } + + @ParameterizedTest(name = "{0} and {1}") + @CsvSource({ "foo, 23", "bar, 42" }) + void testWithCustomName(String argument, int i) { + fail(argument + ", " + i); + } + + @ParameterizedTest + @ValueSource(shorts = { 1, 2 }) + void testWithPrimitiveWideningConversion(double num) { + fail("num: " + num); + } + + @ParameterizedTest + @ValueSource(strings = { "book 1", "book 2" }) + void testWithImplicitGenericConverter(Book book) { + fail(book.title); + } + + @ParameterizedTest + @ValueSource(strings = { "O", "XXX" }) + void testWithExplicitConverter(@ConvertWith(StringLengthConverter.class) int length) { + fail("length: " + length); + } + + @ParameterizedTest(name = " \t ") + @ValueSource(strings = "not important") + void testWithEmptyName(String argument) { + fail(argument); + } + + @ParameterizedTest + @ValueSource(ints = 42) + void testWithErroneousConverter(@ConvertWith(ErroneousConverter.class) Object ignored) { + fail("this should never be called"); + } + + @ParameterizedTest + @CsvSource({ "ab, cd", "ef, gh" }) + void testWithAggregator(@AggregateWith(StringAggregator.class) String concatenation) { + fail("concatenation: " + concatenation); + } + + @ParameterizedTest + @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = false) + void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvSource(String argument1, String argument2) { + fail("arguments: '" + argument1 + "', '" + argument2 + "'"); + } + + @ParameterizedTest + @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = true) + void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource(String argument1, String argument2) { + fail("arguments: '" + argument1 + "', '" + argument2 + "'"); + } + + @ParameterizedTest + @CsvFileSource(resources = "/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = false) + void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource(String argument1, String argument2) { + fail("arguments: '" + argument1 + "', '" + argument2 + "'"); + } + + @ParameterizedTest + @CsvFileSource(resources = "/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = true) + void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource(String argument1, String argument2) { + fail("arguments: '" + argument1 + "', '" + argument2 + "'"); + } + + @ParameterizedTest + @ArgumentsSource(AutoCloseableArgumentProvider.class) + void testWithAutoCloseableArgument(AutoCloseableArgument autoCloseable) { + assertFalse(AutoCloseableArgument.isClosed); + } + + @ParameterizedTest + @ValueSource(ints = { 2, 3, 5 }) + void testWithThreeIterations(int argument) { + fail("argument: " + argument); + } + } + + static class NullSourceTestCase { + + @ParameterizedTest + @NullSource + void testWithNullSourceForString(String argument) { + fail(String.valueOf(argument)); + } + + @ParameterizedTest + @NullSource + void testWithNullSourceForStringAndTestInfo(String argument, TestInfo testInfo) { + assertThat(testInfo).isNotNull(); + fail(String.valueOf(argument)); + } + + @ParameterizedTest + @NullSource + void testWithNullSourceForNumber(Number argument) { + fail(String.valueOf(argument)); + } + + @ParameterizedTest + @NullSource + void testWithNullSourceWithZeroFormalParameters() { + fail("should not have been executed"); + } + + @ParameterizedTest + @NullSource + void testWithNullSourceForPrimitive(int argument) { + fail("should not have been executed"); + } + + } + + static class EmptySourceTestCase { + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForString(String argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForStringAndTestInfo(String argument, TestInfo testInfo) { + assertThat(argument).isEmpty(); + assertThat(testInfo).isNotNull(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForList(List argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForSet(Set argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForMap(Map argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForOneDimensionalPrimitiveArray(int[] argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForOneDimensionalStringArray(String[] argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForTwoDimensionalPrimitiveArray(int[][] argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForTwoDimensionalStringArray(String[][] argument) { + assertThat(argument).isEmpty(); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceWithZeroFormalParameters() { + fail("should not have been executed"); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForPrimitive(int argument) { + fail("should not have been executed"); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForUnsupportedReferenceType(Integer argument) { + fail("should not have been executed"); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForUnsupportedListSubtype(ArrayList argument) { + fail("should not have been executed"); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForUnsupportedSetSubtype(HashSet argument) { + fail("should not have been executed"); + } + + @ParameterizedTest + @EmptySource + void testWithEmptySourceForUnsupportedMapSubtype(HashMap argument) { + fail("should not have been executed"); + } + + } + + static class NullAndEmptySourceTestCase { + + @ParameterizedTest + @NullAndEmptySource + void testWithNullAndEmptySourceForString(String argument) { + assertTrue(argument == null || argument.isEmpty()); + } + + @ParameterizedTest + @NullAndEmptySource + void testWithNullAndEmptySourceForStringAndTestInfo(String argument, TestInfo testInfo) { + assertTrue(argument == null || argument.isEmpty()); + assertThat(testInfo).isNotNull(); + } + + @ParameterizedTest + @NullAndEmptySource + void testWithNullAndEmptySourceForList(List argument) { + assertTrue(argument == null || argument.isEmpty()); + } + + @ParameterizedTest + @NullAndEmptySource + void testWithNullAndEmptySourceForOneDimensionalPrimitiveArray(int[] argument) { + assertTrue(argument == null || argument.length == 0); + } + + @ParameterizedTest + @NullAndEmptySource + void testWithNullAndEmptySourceForTwoDimensionalStringArray(String[][] argument) { + assertTrue(argument == null || argument.length == 0); + } + + } + + @TestMethodOrder(OrderAnnotation.class) + static class MethodSourceTestCase { + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ParameterizedTest(name = "{arguments}") + @MethodSource + @interface MethodSourceTest { + } + + @MethodSourceTest + void emptyMethodSource(String argument) { + fail(argument); + } + + @MethodSourceTest + @Order(1) + void oneDimensionalPrimitiveArray(int x) { + fail("" + x); + } + + @MethodSourceTest + @Order(2) + void twoDimensionalPrimitiveArray(int[] array) { + fail(Arrays.toString(array)); + } + + @MethodSourceTest + @Order(3) + void oneDimensionalObjectArray(Object o) { + fail("" + o); + } + + @MethodSourceTest + @Order(4) + void oneDimensionalStringArray(String s) { + fail(s); + } + + @MethodSourceTest + @Order(5) + void twoDimensionalObjectArray(String s, int x) { + fail(s + ":" + x); + } + + @MethodSourceTest + @Order(6) + void twoDimensionalStringArray(String s1, String s2) { + fail(s1 + ":" + s2); + } + + @MethodSourceTest + @Order(7) + void streamOfOneDimensionalPrimitiveArrays(int[] array) { + fail(Arrays.toString(array)); + } + + @MethodSourceTest + @Order(8) + void streamOfTwoDimensionalPrimitiveArrays(int[][] array) { + fail(Arrays.deepToString(array)); + } + + @MethodSourceTest + @Order(9) + void streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays(int[][] array) { + fail(Arrays.deepToString(array)); + } + + @MethodSourceTest + @Order(10) + void streamOfTwoDimensionalPrimitiveArraysWrappedInArguments(int[][] array) { + fail(Arrays.deepToString(array)); + } + + @MethodSourceTest + @Order(11) + void streamOfOneDimensionalObjectArrays(String s, int x) { + fail(s + ":" + x); + } + + @MethodSourceTest + @Order(12) + void streamOfTwoDimensionalObjectArrays(Object[][] array) { + fail(Arrays.deepToString(array)); + } + + @MethodSourceTest + @Order(13) + void namedParameters(String string) { + fail(string); + } + + @MethodSourceTest + @Order(14) + void namedParametersAlias(String string) { + fail(string); + } + + // --------------------------------------------------------------------- + + static Stream emptyMethodSource() { + return Stream.of(arguments("empty method source")); + } + + static int[] oneDimensionalPrimitiveArray() { + return new int[] { 1, 2 }; + } + + static int[][] twoDimensionalPrimitiveArray() { + return new int[][] { { 1, 2 }, { 3, 4 } }; + } + + static Object[] oneDimensionalObjectArray() { + return new Object[] { "one", 2, "three" }; + } + + static Object[] oneDimensionalStringArray() { + return new Object[] { "one", "two" }; + } + + static Object[][] twoDimensionalObjectArray() { + return new Object[][] { { "one", 2 }, { "three", 4 } }; + } + + static String[][] twoDimensionalStringArray() { + return new String[][] { { "one", "two" }, { "three", "four" } }; + } + + static Stream streamOfOneDimensionalPrimitiveArrays() { + return Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); + } + + static Stream streamOfTwoDimensionalPrimitiveArrays() { + return Stream.of(new int[][] { { 1, 2 }, { 3, 4 } }, new int[][] { { 5, 6 }, { 7, 8 } }); + } + + static Stream streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays() { + return Stream.of(new Object[] { new int[][] { { 1, 2 }, { 3, 4 } } }, + new Object[] { new int[][] { { 5, 6 }, { 7, 8 } } }); + } + + static Stream streamOfTwoDimensionalPrimitiveArraysWrappedInArguments() { + return Stream.of(arguments((Object) new int[][] { { 1, 2 }, { 3, 4 } }), + arguments((Object) new int[][] { { 5, 6 }, { 7, 8 } })); + } + + static Stream streamOfOneDimensionalObjectArrays() { + return Stream.of(new Object[] { "one", 2 }, new Object[] { "three", 4 }); + } + + static Stream streamOfTwoDimensionalObjectArrays() { + return Stream.of(new Object[][] { { "one", 2 }, { "three", 4 } }, + new Object[][] { { "five", 6 }, { "seven", 8 } }); + } + + static Stream namedParameters() { + return Stream.of(arguments(Named.of("cool name", "parameter value")), arguments("default name")); + } + + static Stream namedParametersAlias() { + return Stream.of(arguments(named("cool name", "parameter value")), arguments("default name")); + } + + // --------------------------------------------------------------------- + + @MethodSourceTest + void assumptionFailureInMethodSourceFactoryMethod(String test) { + } + + static List assumptionFailureInMethodSourceFactoryMethod() { + Assumptions.assumeFalse(true, "nothing to test"); + return null; + } + + } + + /** + * @since 5.9.1 + * @see https://github.com/junit-team/junit5/issues/3001 + */ + static class DuplicateMethodNamesMethodSourceTestCase { + + @ParameterizedTest + @MethodSource + void test(String value) { + test(1, value); + } + + @ParameterizedTest + @MethodSource("test") + void anotherTest(String value) { + assertTrue(test(value, 1)); + } + + @RepeatedTest(2) + void test(TestReporter testReporter) { + assertNotNull(testReporter); + } + + @TestFactory + Stream test(TestInfo testInfo) { + return test().map(value -> dynamicTest(value, () -> test(1, value))); + } + + // neither a test method nor a factory method. + // intentionally void. + private void test(int expectedLength, String value) { + assertEquals(expectedLength, value.length()); + } + + // neither a test method nor a factory method. + // intentionally non-void and also not convertible to a Stream. + private boolean test(String value, int expectedLength) { + return (value.length() == expectedLength); + } + + // legitimate factory method. + private static Stream test() { + return Stream.of("a", "b"); + } + + } + + static class UnusedArgumentsTestCase { + + @ParameterizedTest + @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) + void testWithTwoUnusedStringArgumentsProvider(String argument) { + fail(argument); + } + + @ParameterizedTest + @CsvSource({ "foo, unused1", "bar, unused2" }) + void testWithCsvSourceContainingUnusedArguments(String argument) { + fail(argument); + } + + @ParameterizedTest + @CsvFileSource(resources = "two-column.csv") + void testWithCsvFileSourceContainingUnusedArguments(String argument) { + fail(argument); + } + + @ParameterizedTest + @MethodSource("unusedArgumentsProviderMethod") + void testWithMethodSourceProvidingUnusedArguments(String argument) { + fail(argument); + } + + static Stream unusedArgumentsProviderMethod() { + return Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); + } + + } + + static class LifecycleTestCase { + + private static final List lifecycleEvents = new ArrayList<>(); + private static final Set testMethods = new LinkedHashSet<>(); + + public LifecycleTestCase(TestInfo testInfo) { + lifecycleEvents.add("constructor:" + testInfo.getDisplayName()); + } + + @BeforeAll + static void beforeAll(TestInfo testInfo) { + lifecycleEvents.add("beforeAll:" + testInfo.getDisplayName()); + } + + @AfterAll + static void afterAll(TestInfo testInfo) { + lifecycleEvents.add("afterAll:" + testInfo.getDisplayName()); + } + + @BeforeEach + void beforeEach(TestInfo testInfo) { + lifecycleEvents.add("beforeEach:" + testInfo.getDisplayName()); + } + + @AfterEach + void afterEach(TestInfo testInfo) { + lifecycleEvents.add("afterEach:" + testInfo.getDisplayName()); + } + + @ParameterizedTest + @MethodSource("providerMethod") + void test1(String argument, TestInfo testInfo) { + performTest(argument, testInfo); + } + + @ParameterizedTest + @MethodSource("providerMethod") + void test2(String argument, TestInfo testInfo) { + performTest(argument, testInfo); + } + + private void performTest(String argument, TestInfo testInfo) { + var testMethod = testInfo.getTestMethod().orElseThrow().getName(); + testMethods.add(testMethod); + lifecycleEvents.add(testMethod + ":" + testInfo.getDisplayName()); + fail(argument); + } + + static Stream providerMethod() { + lifecycleEvents.add("providerMethod"); + return Stream.of("foo", "bar"); + } + + } + + private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments("foo"), arguments("bar")); + } + } + + private static class TwoUnusedStringArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); + } + } + + private static class StringLengthConverter implements ArgumentConverter { + + @Override + public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + return String.valueOf(source).length(); + } + } + + private static class StringAggregator implements ArgumentsAggregator { + + @Override + public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) + throws ArgumentsAggregationException { + return accessor.getString(0) + accessor.getString(1); + } + } + + private static class ErroneousConverter implements ArgumentConverter { + + @Override + public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + throw new ArgumentConversionException("something went horribly wrong"); + } + } + + private static class AutoCloseableArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new AutoCloseableArgument())); + } + } + + static class AutoCloseableArgument implements AutoCloseable { + + static boolean isClosed = false; + + @Override + public void close() { + isClosed = true; + } + } + + static class Book { + + private final String title; + + private Book(String title) { + this.title = title; + } + + static Book factory(String title) { + return new Book(title); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java new file mode 100644 index 00000000..038c3502 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.CsvToPerson; +import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.Person; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Unit tests for {@link ParameterizedTestMethodContext}. + * + * @since 5.2 + */ +class ParameterizedTestMethodContextTests { + + @ParameterizedTest + @ValueSource(strings = { "onePrimitive", "twoPrimitives", "twoAggregators", "twoAggregatorsWithTestInfoAtTheEnd", + "mixedMode" }) + void validSignatures(String name) { + assertTrue(new ParameterizedTestMethodContext(method(name)).hasPotentiallyValidSignature()); + } + + @ParameterizedTest + @ValueSource(strings = { "twoAggregatorsWithPrimitiveInTheMiddle", "twoAggregatorsWithTestInfoInTheMiddle" }) + void invalidSignatures(String name) { + assertFalse(new ParameterizedTestMethodContext(method(name)).hasPotentiallyValidSignature()); + } + + private Method method(String name) { + return Arrays.stream(getClass().getDeclaredMethods()) // + .filter(m -> m.getName().equals(name)) // + .findFirst() // + .orElseThrow(); + } + + // --- VALID --------------------------------------------------------------- + + void onePrimitive(int num) { + } + + void twoPrimitives(int num1, int num2) { + } + + void twoAggregators(@CsvToPerson Person person, ArgumentsAccessor arguments) { + } + + void twoAggregatorsWithTestInfoAtTheEnd(@CsvToPerson Person person1, @CsvToPerson Person person2, + TestInfo testInfo) { + } + + void mixedMode(int num1, int num2, ArgumentsAccessor arguments1, ArgumentsAccessor arguments2, + @CsvToPerson Person person1, @CsvToPerson Person person2, TestInfo testInfo1, TestInfo testInfo2) { + } + + // --- INVALID ------------------------------------------------------------- + + void twoAggregatorsWithPrimitiveInTheMiddle(@CsvToPerson Person person1, int num, @CsvToPerson Person person2) { + } + + void twoAggregatorsWithTestInfoInTheMiddle(@CsvToPerson Person person1, TestInfo testInfo, + @CsvToPerson Person person2) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java new file mode 100644 index 00000000..1cbabc82 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java @@ -0,0 +1,303 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_PLACEHOLDER; +import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER; +import static org.junit.jupiter.params.ParameterizedTest.DISPLAY_NAME_PLACEHOLDER; +import static org.junit.jupiter.params.ParameterizedTest.INDEX_PLACEHOLDER; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.sql.Date; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Locale; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.aggregator.AggregateWith; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.aggregator.ArgumentsAggregator; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.0 + */ +class ParameterizedTestNameFormatterTests { + + private final Locale originalLocale = Locale.getDefault(); + + @AfterEach + void restoreLocale() { + Locale.setDefault(originalLocale); + } + + @Test + void formatsDisplayName() { + var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, "enigma"); + + assertEquals("enigma", formatter.format(1)); + assertEquals("enigma", formatter.format(2)); + } + + @Test + void formatsDisplayNameContainingApostrophe() { + String displayName = "display'Zero"; + var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, "display'Zero"); + + assertEquals(displayName, formatter.format(1)); + assertEquals(displayName, formatter.format(2)); + } + + @Test + void formatsDisplayNameContainingFormatElements() { + String displayName = "{enigma} {0} '{1}'"; + var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, displayName); + + assertEquals(displayName, formatter.format(1)); + assertEquals(displayName, formatter.format(2)); + } + + @Test + void formatsInvocationIndex() { + var formatter = formatter(INDEX_PLACEHOLDER, "enigma"); + + assertEquals("1", formatter.format(1)); + assertEquals("2", formatter.format(2)); + } + + @Test + void formatsIndividualArguments() { + var formatter = formatter("{0} -> {1}", "enigma"); + + assertEquals("foo -> 42", formatter.format(1, "foo", 42)); + } + + @Test + void formatsCompleteArgumentsList() { + var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); + + // @formatter:off + assertEquals("42, 99, enigma, null, [1, 2, 3], [foo, bar], [[2, 4], [3, 9]]", + formatter.format(1, + 42, + 99, + "enigma", + null, + new int[] { 1, 2, 3 }, + new String[] { "foo", "bar" }, + new Integer[][] { { 2, 4 }, { 3, 9 } } + )); + // @formatter:on + } + + @Test + void formatsCompleteArgumentsListWithNames() { + var testMethod = ParameterizedTestCases.getMethod("parameterizedTest", int.class, String.class, Object[].class); + var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); + + var formattedName = formatter.format(1, 42, "enigma", new Object[] { "foo", 1 }); + assertEquals("someNumber=42, someString=enigma, someArray=[foo, 1]", formattedName); + } + + @Test + void formatsCompleteArgumentsListWithoutNamesForAggregators() { + var testMethod = ParameterizedTestCases.getMethod("parameterizedTestWithAggregator", int.class, String.class); + var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); + + var formattedName = formatter.format(1, 42, "foo", "bar"); + assertEquals("someNumber=42, foo, bar", formattedName); + } + + @Test + void formatsCompleteArgumentsListWithArrays() { + var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); + + // Explicit test for https://github.com/junit-team/junit5/issues/814 + assertEquals("[foo, bar]", formatter.format(1, (Object) new String[] { "foo", "bar" })); + + assertEquals("[foo, bar], 42, true", formatter.format(1, new String[] { "foo", "bar" }, 42, true)); + } + + @Test + void formatsEverythingUsingCustomPattern() { + var pattern = DISPLAY_NAME_PLACEHOLDER + " " + INDEX_PLACEHOLDER + " :: " + ARGUMENTS_PLACEHOLDER + " :: {1}"; + var formatter = formatter(pattern, "enigma"); + + assertEquals("enigma 1 :: foo, bar :: bar", formatter.format(1, "foo", "bar")); + assertEquals("enigma 2 :: foo, 42 :: 42", formatter.format(2, "foo", 42)); + } + + @Test + void formatDoesNotAlterArgumentsArray() { + var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); + Object[] actual = { 1, "two", Byte.valueOf("-128"), new Integer[][] { { 2, 4 }, { 3, 9 } } }; + var expected = Arrays.copyOf(actual, actual.length); + assertEquals("1, two, -128, [[2, 4], [3, 9]]", formatter.format(1, actual)); + assertArrayEquals(expected, actual); + } + + @Test + void formatDoesNotRaiseAnArrayStoreException() { + var formatter = formatter("{0} -> {1}", "enigma"); + + Object[] arguments = new Number[] { 1, 2 }; + assertEquals("1 -> 2", formatter.format(1, arguments)); + } + + @Test + void throwsReadableExceptionForInvalidPattern() { + var formatter = formatter("{index", "enigma"); + + var exception = assertThrows(JUnitException.class, () -> formatter.format(1)); + assertNotNull(exception.getCause()); + assertEquals(IllegalArgumentException.class, exception.getCause().getClass()); + } + + @Test + void formattingDoesNotFailIfArgumentToStringImplementationReturnsNull() { + var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); + + var formattedName = formatter.format(1, new ToStringReturnsNull(), "foo"); + + assertThat(formattedName).isEqualTo("null, foo"); + } + + @Test + void formattingDoesNotFailIfArgumentToStringImplementationThrowsAnException() { + var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); + + var formattedName = formatter.format(1, new ToStringThrowsException(), "foo"); + + assertThat(formattedName).startsWith(ToStringThrowsException.class.getName() + "@"); + assertThat(formattedName).endsWith("foo"); + } + + @ParameterizedTest(name = "{0}") + @CsvSource(delimiter = '|', value = { "US | 42.23 is positive on 2019 Jan 13 at 12:34:56", + "DE | 42,23 is positive on 13.01.2019 at 12:34:56" }) + void customFormattingExpressionsAreSupported(Locale locale, String expectedValue) { + var pattern = "[{index}] {1,number,#.##} is {1,choice,0... parameterTypes) { + return ReflectionUtils.findMethod(ParameterizedTestCases.class, methodName, parameterTypes).orElseThrow(); + } + + @SuppressWarnings("unused") + void parameterizedTest(int someNumber, String someString, Object[] someArray) { + } + + @SuppressWarnings("unused") + void parameterizedTestWithAggregator(int someNumber, + @AggregateWith(CustomAggregator.class) String someAggregatedString) { + } + + private static class CustomAggregator implements ArgumentsAggregator { + @Override + public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) { + return accessor.get(0); + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java new file mode 100644 index 00000000..dd3a68eb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * Test suite for JUnit Jupiter parameterized test support. + * + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @since 5.0 + */ +@Suite +@SelectPackages("org.junit.jupiter.params") +@IncludeClassNamePatterns(".*Tests?") +@IncludeEngines("junit-jupiter") +class ParameterizedTestSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java new file mode 100644 index 00000000..97d814f6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java @@ -0,0 +1,408 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; + +/** + * Integration tests for {@link ArgumentsAccessor}, {@link AggregateWith}, + * and {@link ArgumentsAggregator}. + * + * @since 5.2 + */ +public class AggregatorIntegrationTests { + + @ParameterizedTest + @CsvSource({ // + "Jane, Doe, 1980-04-16, F, red", // + "Jack, Smith, 2000-11-22, M, blue" // + }) + void personAggregator(@AggregateWith(PersonAggregator.class) Person person) { + testPersonAggregator(person); + } + + @ParameterizedTest + @CsvSource({ // + "Jane, Doe, 1980-04-16, F, red", // + "Jack, Smith, 2000-11-22, M, blue" // + }) + void personAggregatorRegisteredViaCustomAnnotation(@CsvToPerson Person person) { + testPersonAggregator(person); + } + + @ParameterizedTest + @CsvSource({ // + "42 Peachtree Street, Atlanta, 30318", // + "99 Peachtree Road, Atlanta, 30318"// + }) + void addressAggregator(@CsvToAddress Address address) { + testAddressAggregator(address); + } + + @ParameterizedTest + @CsvSource({ // + "Jane, Doe, 1980-04-16, F, 42 Peachtree Street, Atlanta, 30318, red", // + "Jack, Smith, 2000-11-22, M, 99 Peachtree Road, Atlanta, 30318, blue"// + }) + void personAggregatorAndAddressAggregator(@CsvToPerson Person person, + @CsvToAddress @StartIndex(4) Address address) { + + testPersonAggregator(person); + testAddressAggregator(address); + } + + @ParameterizedTest(name = "Mixed Mode #1: {arguments}") + @CsvSource({ // + "gh-11111111, Jane, Doe, 1980-04-16, F, 42 Peachtree Street, Atlanta, 30318, red", // + "gh-22222222, Jack, Smith, 2000-11-22, M, 99 Peachtree Road, Atlanta, 30318, blue"// + }) + void mixedMode(String issueNumber, @CsvToPerson @StartIndex(1) Person person, + @CsvToAddress @StartIndex(5) Address address, TestInfo testInfo) { + + assertThat(issueNumber).startsWith("gh-"); + testPersonAggregator(person); + testAddressAggregator(address); + assertThat(testInfo.getDisplayName()).startsWith("Mixed Mode #1"); + } + + @ParameterizedTest + @CsvSource({ "cat, bird, mouse", "mouse, cat, bird", "mouse, bird, cat" }) + void mapAggregator(@AggregateWith(MapAggregator.class) Map map) { + assertThat(map).containsOnly(entry("cat", 3), entry("bird", 4), entry("mouse", 5)); + } + + @ParameterizedTest + @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) + void argumentsAccessor(ArgumentsAccessor arguments) { + assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); + } + + @ParameterizedTest(name = "2 ArgumentsAccessors: {arguments}") + @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) + void argumentsAccessors(ArgumentsAccessor arguments1, ArgumentsAccessor arguments2) { + assertArrayEquals(arguments1.toArray(), arguments2.toArray()); + } + + @ParameterizedTest(name = "ArgumentsAccessor and TestInfo: {arguments}") + @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) + void argumentsAccessorAndTestInfo(ArgumentsAccessor arguments, TestInfo testInfo) { + assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); + assertThat(testInfo.getDisplayName()).startsWith("ArgumentsAccessor and TestInfo"); + } + + @ParameterizedTest(name = "Indexed Arguments and ArgumentsAccessor: {arguments}") + @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) + void indexedArgumentsAndArgumentsAccessor(int num1, int num2, ArgumentsAccessor arguments) { + assertEquals(1, num1); + assertEquals(2, num2); + assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); + } + + @ParameterizedTest(name = "Indexed Arguments, ArgumentsAccessor, and TestInfo: {arguments}") + @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) + void indexedArgumentsArgumentsAccessorAndTestInfo(int num1, int num2, ArgumentsAccessor arguments, + TestInfo testInfo) { + + assertEquals(1, num1); + assertEquals(2, num2); + assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); + assertThat(testInfo.getDisplayName()).startsWith("Indexed Arguments, ArgumentsAccessor, and TestInfo"); + } + + @ParameterizedTest(name = "Indexed Arguments, 2 ArgumentsAccessors, and TestInfo: {arguments}") + @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) + void indexedArgumentsArgumentsAccessorsAndTestInfo(int num1, int num2, ArgumentsAccessor arguments1, + ArgumentsAccessor arguments2, TestInfo testInfo) { + + assertEquals(1, num1); + assertEquals(2, num2); + assertArrayEquals(arguments1.toArray(), arguments2.toArray()); + assertEquals(55, IntStream.range(0, arguments1.size()).map(arguments1::getInteger).sum()); + assertThat(testInfo.getDisplayName()).startsWith("Indexed Arguments, 2 ArgumentsAccessors, and TestInfo"); + } + + @ParameterizedTest + @CsvSource({ "foo, bar" }) + void nullAggregator(@AggregateWith(NullAggregator.class) Person person) { + assertNull(person); + } + + @Test + void reportsExceptionForErroneousAggregator() { + var results = execute( + selectMethod(ErroneousTestCases.class, "testWithErroneousAggregator", Object.class.getName())); + + results.testEvents().assertThatEvents()// + .haveExactly(1, event(test(), finishedWithFailure(instanceOf(ParameterResolutionException.class), // + message("Error aggregating arguments for parameter at index 0: something went horribly wrong")))); + } + + private void testPersonAggregator(Person person) { + if (person.firstName.equals("Jane")) { + assertEquals("Jane Doe", person.getFullName()); + assertEquals(1980, person.dateOfBirth.getYear()); + assertEquals(Gender.F, person.gender); + } + + if (person.firstName.equals("Jack")) { + assertEquals("Jack Smith", person.getFullName()); + assertEquals(2000, person.dateOfBirth.getYear()); + assertEquals(Gender.M, person.gender); + } + } + + private void testAddressAggregator(Address address) { + assertThat(address.street).contains("Peachtree"); + assertEquals("Atlanta", address.city); + assertEquals(30318, address.zipCode); + } + + private EngineExecutionResults execute(DiscoverySelector... selectors) { + return EngineTestKit.execute("junit-jupiter", request().selectors(selectors).build()); + } + + // ------------------------------------------------------------------------- + + public static class Person { + + final String firstName; + final String lastName; + final Gender gender; + final LocalDate dateOfBirth; + + Person(String firstName, String lastName, LocalDate dateOfBirth, Gender gender) { + this.firstName = firstName; + this.lastName = lastName; + this.gender = gender; + this.dateOfBirth = dateOfBirth; + } + + String getFullName() { + return this.firstName + " " + this.lastName; + } + } + + enum Gender { + F, M + } + + static class Address { + + final String street; + final String city; + final int zipCode; + + Address(String street, String city, int zipCode) { + this.street = street; + this.city = city; + this.zipCode = zipCode; + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @interface StartIndex { + int value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @AggregateWith(PersonAggregator.class) + public @interface CsvToPerson { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @AggregateWith(AddressAggregator.class) + @interface CsvToAddress { + } + + static class PersonAggregator implements ArgumentsAggregator { + + @Override + public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { + int startIndex = context.findAnnotation(StartIndex.class).map(StartIndex::value).orElse(0); + + // @formatter:off + return new Person( + arguments.getString(startIndex + 0), + arguments.getString(startIndex + 1), + arguments.get(startIndex + 2, LocalDate.class), + arguments.get(startIndex + 3, Gender.class) + ); + // @formatter:on + } + } + + static class AddressAggregator implements ArgumentsAggregator { + + @Override + public Address aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { + int startIndex = context.findAnnotation(StartIndex.class).map(StartIndex::value).orElse(0); + + // @formatter:off + return new Address( + arguments.getString(startIndex + 0), + arguments.getString(startIndex + 1), + arguments.getInteger(startIndex + 2) + ); + // @formatter:on + } + } + + /** + * Maps from String to length of String. + */ + static class MapAggregator implements ArgumentsAggregator { + + @Override + public Map aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { + // @formatter:off + return IntStream.range(0, arguments.size()) + .mapToObj(arguments::getString) + .collect(toMap(s -> s, String::length)); + // @formatter:on + } + } + + static class NullAggregator implements ArgumentsAggregator { + @Override + public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) { + Preconditions.condition(!context.getParameter().getType().isPrimitive(), + () -> "only supports reference types"); + return null; + } + } + + static class ErroneousAggregator implements ArgumentsAggregator { + @Override + public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) + throws ArgumentsAggregationException { + throw new ArgumentsAggregationException("something went horribly wrong"); + } + } + + static class ErroneousTestCases { + @ParameterizedTest + @ValueSource(ints = 42) + void testWithErroneousAggregator(@AggregateWith(ErroneousAggregator.class) Object ignored) { + fail("this should never be called"); + } + } + + @Test + @ResourceLock("InstanceCountingConverter.instanceCount") + void aggregatorIsInstantiatedOnlyOnce() { + InstanceCountingAggregator.instanceCount = 0; + CountingTestCase.output.clear(); + + execute(selectMethod(CountingTestCase.class, "testWithCountingConverterAggregator", + int.class.getName() + "," + Object.class.getName())); + + assertThat(InstanceCountingAggregator.instanceCount).isEqualTo(1); + assertThat(CountingTestCase.output)// + .containsExactly("noisy test(1, enigma)", "noisy test(2, enigma)", "noisy test(3, enigma)"); + } + + @Test + @ResourceLock("InstanceCountingConverter.instanceCount") + void converterIsInstantiatedOnlyOnce() { + InstanceCountingConverter.instanceCount = 0; + CountingTestCase.output.clear(); + + execute(selectMethod(CountingTestCase.class, "testWithCountingConverterAggregator", + int.class.getName() + "," + Object.class.getName())); + + assertThat(InstanceCountingConverter.instanceCount).isEqualTo(1); + assertThat(CountingTestCase.output)// + .containsExactly("noisy test(1, enigma)", "noisy test(2, enigma)", "noisy test(3, enigma)"); + } + + static class InstanceCountingConverter implements ArgumentConverter { + static int instanceCount; + + InstanceCountingConverter() { + instanceCount++; + } + + @Override + public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + return source; + } + } + + static class InstanceCountingAggregator implements ArgumentsAggregator { + static int instanceCount; + + InstanceCountingAggregator() { + instanceCount++; + } + + @Override + public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) + throws ArgumentsAggregationException { + return "enigma"; + } + } + + static class CountingTestCase { + + static final List output = new ArrayList<>(); + + @ParameterizedTest + @ValueSource(ints = { 1, 2, 3 }) + void testWithCountingConverterAggregator(@ConvertWith(InstanceCountingConverter.class) int i, + @AggregateWith(InstanceCountingAggregator.class) Object o) { + + output.add("noisy test(" + i + ", " + o + ")"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java new file mode 100644 index 00000000..198a05b4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java @@ -0,0 +1,166 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.aggregator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DefaultArgumentsAccessor}. + * + * @since 5.2 + */ +class DefaultArgumentsAccessorTests { + + @Test + void argumentsMustNotBeNull() { + assertThrows(PreconditionViolationException.class, () -> new DefaultArgumentsAccessor((Object[]) null)); + } + + @Test + void indexMustNotBeNegative() { + ArgumentsAccessor arguments = new DefaultArgumentsAccessor(1, 2); + Exception exception = assertThrows(PreconditionViolationException.class, () -> arguments.get(-1)); + assertThat(exception.getMessage()).containsSubsequence("index must be", ">= 0"); + } + + @Test + void indexMustBeSmallerThanLength() { + ArgumentsAccessor arguments = new DefaultArgumentsAccessor(1, 2); + Exception exception = assertThrows(PreconditionViolationException.class, () -> arguments.get(2)); + assertThat(exception.getMessage()).containsSubsequence("index must be", "< 2"); + } + + @Test + void getNull() { + assertNull(new DefaultArgumentsAccessor(new Object[] { null }).get(0)); + } + + @Test + void getWithNullCastToWrapperType() { + assertNull(new DefaultArgumentsAccessor((Object[]) new Integer[] { null }).get(0, Integer.class)); + } + + @Test + void get() { + assertEquals(1, new DefaultArgumentsAccessor(1).get(0)); + } + + @Test + void getWithCast() { + assertEquals(Integer.valueOf(1), new DefaultArgumentsAccessor(1).get(0, Integer.class)); + assertEquals(Character.valueOf('A'), new DefaultArgumentsAccessor('A').get(0, Character.class)); + } + + @Test + void getWithCastToPrimitiveType() { + Exception exception = assertThrows(ArgumentAccessException.class, + () -> new DefaultArgumentsAccessor(1).get(0, int.class)); + assertThat(exception.getMessage()).isEqualTo( + "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [int]."); + + exception = assertThrows(ArgumentAccessException.class, + () -> new DefaultArgumentsAccessor(new Object[] { null }).get(0, int.class)); + assertThat(exception.getMessage()).isEqualTo( + "Argument at index [0] with value [null] and type [null] could not be converted or cast to type [int]."); + } + + @Test + void getWithCastToIncompatibleType() { + Exception exception = assertThrows(ArgumentAccessException.class, + () -> new DefaultArgumentsAccessor(1).get(0, Character.class)); + assertThat(exception.getMessage()).isEqualTo( + "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [java.lang.Character]."); + } + + @Test + void getCharacter() { + assertEquals(Character.valueOf('A'), new DefaultArgumentsAccessor('A', 'B').getCharacter(0)); + } + + @Test + void getBoolean() { + assertEquals(Boolean.TRUE, new DefaultArgumentsAccessor(true, false).getBoolean(0)); + } + + @Test + void getByte() { + assertEquals(Byte.valueOf((byte) 42), new DefaultArgumentsAccessor((byte) 42).getByte(0)); + } + + @Test + void getShort() { + assertEquals(Short.valueOf((short) 42), new DefaultArgumentsAccessor((short) 42).getShort(0)); + } + + @Test + void getInteger() { + assertEquals(Integer.valueOf(42), new DefaultArgumentsAccessor(42).getInteger(0)); + } + + @Test + void getLong() { + assertEquals(Long.valueOf(42L), new DefaultArgumentsAccessor(42L).getLong(0)); + } + + @Test + void getFloat() { + assertEquals(Float.valueOf(42.0f), new DefaultArgumentsAccessor(42.0f).getFloat(0)); + } + + @Test + void getDouble() { + assertEquals(Double.valueOf(42.0), new DefaultArgumentsAccessor(42.0).getDouble(0)); + } + + @Test + void getString() { + assertEquals("foo", new DefaultArgumentsAccessor("foo", "bar").getString(0)); + } + + @Test + void toArray() { + var arguments = new DefaultArgumentsAccessor("foo", "bar"); + var copy = arguments.toArray(); + assertArrayEquals(new String[] { "foo", "bar" }, copy); + + // Modify local copy: + copy[0] = "Boom!"; + assertEquals("foo", arguments.toArray()[0]); + } + + @Test + void toList() { + var arguments = new DefaultArgumentsAccessor("foo", "bar"); + var copy = arguments.toList(); + assertIterableEquals(Arrays.asList("foo", "bar"), copy); + + // Modify local copy: + assertThrows(UnsupportedOperationException.class, () -> copy.set(0, "Boom!")); + } + + @Test + void size() { + assertEquals(0, new DefaultArgumentsAccessor().size()); + assertEquals(1, new DefaultArgumentsAccessor(42).size()); + assertEquals(5, new DefaultArgumentsAccessor('a', 'b', 'c', 'd', 'e').size()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java new file mode 100644 index 00000000..12ad9cca --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java @@ -0,0 +1,242 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.lang.Thread.State; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Currency; +import java.util.Locale; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link DefaultArgumentConverter}. + * + * @since 5.0 + */ +class DefaultArgumentConverterTests { + + @Test + void isAwareOfNull() { + assertConverts(null, Object.class, null); + assertConverts(null, String.class, null); + } + + @Test + void isAwareOfWrapperTypesForPrimitiveTypes() { + assertConverts(true, boolean.class, true); + assertConverts((byte) 1, byte.class, (byte) 1); + assertConverts('o', char.class, 'o'); + assertConverts((short) 1, short.class, (short) 1); + assertConverts(1, int.class, 1); + assertConverts(1L, long.class, 1L); + assertConverts(1.0f, float.class, 1.0f); + assertConverts(1.0d, double.class, 1.0d); + } + + @Test + void isAwareOfWideningConversions() { + assertConverts((byte) 1, short.class, (byte) 1); + assertConverts((short) 1, int.class, (short) 1); + assertConverts((char) 1, int.class, (char) 1); + assertConverts((byte) 1, long.class, (byte) 1); + assertConverts(1, long.class, 1); + assertConverts((char) 1, float.class, (char) 1); + assertConverts(1, float.class, 1); + assertConverts(1L, double.class, 1L); + assertConverts(1.0f, double.class, 1.0f); + } + + @Test + void convertsStringsToPrimitiveTypes() { + assertConverts("true", boolean.class, true); + assertConverts("o", char.class, 'o'); + assertConverts("1", byte.class, (byte) 1); + assertConverts("1_0", byte.class, (byte) 10); + assertConverts("1", short.class, (short) 1); + assertConverts("1_2", short.class, (short) 12); + assertConverts("42", int.class, 42); + assertConverts("700_050_000", int.class, 700_050_000); + assertConverts("42", long.class, 42L); + assertConverts("4_2", long.class, 42L); + assertConverts("42.23", float.class, 42.23f); + assertConverts("42.2_3", float.class, 42.23f); + assertConverts("42.23", double.class, 42.23); + assertConverts("42.2_3", double.class, 42.23); + } + + /** + * @since 5.4 + */ + @Test + @SuppressWarnings("OctalInteger") // We test parsing octal integers here as well as hex. + void convertsEncodedStringsToIntegralTypes() { + assertConverts("0x1f", byte.class, (byte) 0x1F); + assertConverts("-0x1F", byte.class, (byte) -0x1F); + assertConverts("010", byte.class, (byte) 010); + + assertConverts("0x1f00", short.class, (short) 0x1F00); + assertConverts("-0x1F00", short.class, (short) -0x1F00); + assertConverts("01000", short.class, (short) 01000); + + assertConverts("0x1f000000", int.class, 0x1F000000); + assertConverts("-0x1F000000", int.class, -0x1F000000); + assertConverts("010000000", int.class, 010000000); + + assertConverts("0x1f000000000", long.class, 0x1F000000000L); + assertConverts("-0x1F000000000", long.class, -0x1F000000000L); + assertConverts("0100000000000", long.class, 0100000000000L); + } + + @Test + void convertsStringsToEnumConstants() { + assertConverts("DAYS", TimeUnit.class, TimeUnit.DAYS); + } + + // --- java.io and java.nio ------------------------------------------------ + + @Test + void convertsStringToCharset() { + assertConverts("ISO-8859-1", Charset.class, StandardCharsets.ISO_8859_1); + assertConverts("UTF-8", Charset.class, StandardCharsets.UTF_8); + } + + @Test + void convertsStringToFile() { + assertConverts("file", File.class, new File("file")); + assertConverts("/file", File.class, new File("/file")); + assertConverts("/some/file", File.class, new File("/some/file")); + } + + @Test + void convertsStringToPath() { + assertConverts("path", Path.class, Paths.get("path")); + assertConverts("/path", Path.class, Paths.get("/path")); + assertConverts("/some/path", Path.class, Paths.get("/some/path")); + } + + // --- java.lang ----------------------------------------------------------- + + @Test + void convertsStringToClass() { + assertConverts("java.lang.Integer", Class.class, Integer.class); + assertConverts("java.lang.Thread$State", Class.class, State.class); + assertConverts("byte", Class.class, byte.class); + assertConverts("char[]", Class.class, char[].class); + assertConverts("java.lang.Long[][]", Class.class, Long[][].class); + assertConverts("[[[I", Class.class, int[][][].class); + assertConverts("[[Ljava.lang.String;", Class.class, String[][].class); + } + + // --- java.math ----------------------------------------------------------- + + @Test + void convertsStringToBigDecimal() { + assertConverts("123.456e789", BigDecimal.class, new BigDecimal("123.456e789")); + } + + @Test + void convertsStringToBigInteger() { + assertConverts("1234567890123456789", BigInteger.class, new BigInteger("1234567890123456789")); + } + + // --- java.net ------------------------------------------------------------ + + @Test + void convertsStringToURI() { + assertConverts("https://docs.oracle.com/en/java/javase/12/", URI.class, + URI.create("https://docs.oracle.com/en/java/javase/12/")); + } + + @Test + void convertsStringToURL() throws Exception { + assertConverts("https://junit.org/junit5", URL.class, URI.create("https://junit.org/junit5").toURL()); + } + + // --- java.time ----------------------------------------------------------- + + @Test + void convertsStringsToJavaTimeInstances() { + assertConverts("PT1234.5678S", Duration.class, Duration.ofSeconds(1234, 567800000)); + assertConverts("1970-01-01T00:00:00Z", Instant.class, Instant.ofEpochMilli(0)); + assertConverts("2017-03-14", LocalDate.class, LocalDate.of(2017, 3, 14)); + assertConverts("2017-03-14T12:34:56.789", LocalDateTime.class, + LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)); + assertConverts("12:34:56.789", LocalTime.class, LocalTime.of(12, 34, 56, 789_000_000)); + assertConverts("--03-14", MonthDay.class, MonthDay.of(3, 14)); + assertConverts("2017-03-14T12:34:56.789Z", OffsetDateTime.class, + OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); + assertConverts("12:34:56.789Z", OffsetTime.class, OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)); + assertConverts("P2M6D", Period.class, Period.of(0, 2, 6)); + assertConverts("2017", Year.class, Year.of(2017)); + assertConverts("2017-03", YearMonth.class, YearMonth.of(2017, 3)); + assertConverts("2017-03-14T12:34:56.789Z", ZonedDateTime.class, + ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); + assertConverts("Europe/Berlin", ZoneId.class, ZoneId.of("Europe/Berlin")); + assertConverts("+02:30", ZoneOffset.class, ZoneOffset.ofHoursMinutes(2, 30)); + } + + // --- java.util ----------------------------------------------------------- + + @Test + void convertsStringToCurrency() { + assertConverts("JPY", Currency.class, Currency.getInstance("JPY")); + } + + @Test + void convertsStringToLocale() { + assertConverts("en", Locale.class, Locale.ENGLISH); + assertConverts("en_us", Locale.class, new Locale(Locale.US.toString())); + } + + @Test + void convertsStringToUUID() { + var uuid = "d043e930-7b3b-48e3-bdbe-5a3ccfb833db"; + assertConverts(uuid, UUID.class, UUID.fromString(uuid)); + } + + // ------------------------------------------------------------------------- + + private void assertConverts(Object input, Class targetClass, Object expectedOutput) { + var result = DefaultArgumentConverter.INSTANCE.convert(input, targetClass); + + assertThat(result) // + .describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) // + .isEqualTo(expectedOutput); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java new file mode 100644 index 00000000..1d2cd21a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java @@ -0,0 +1,243 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.commons.util.ReflectionUtils.findMethod; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Objects; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.converter.FallbackStringToObjectConverter.IsFactoryConstructor; +import org.junit.jupiter.params.converter.FallbackStringToObjectConverter.IsFactoryMethod; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link FallbackStringToObjectConverter}, {@link IsFactoryMethod}, + * and {@link IsFactoryConstructor}. + * + * @since 5.1 + */ +class FallbackStringToObjectConverterTests { + + private static final IsFactoryMethod isBookFactoryMethod = new IsFactoryMethod(Book.class); + + private static final FallbackStringToObjectConverter converter = new FallbackStringToObjectConverter(); + + @Test + void isNotFactoryMethodForWrongParameterType() { + assertThat(isBookFactoryMethod).rejects(bookMethod("factory", Object.class)); + } + + @Test + void isNotFactoryMethodForPrivateMethod() { + assertThat(isBookFactoryMethod).rejects(bookMethod("privateFactory")); + } + + @Test + void isNotFactoryMethodForNonStaticMethod() { + assertThat(isBookFactoryMethod).rejects(bookMethod("nonStaticFactory")); + } + + @Test + void isFactoryMethodForValidMethods() { + assertThat(isBookFactoryMethod).accepts(bookMethod("factory")); + assertThat(new IsFactoryMethod(Newspaper.class)).accepts(newspaperMethod("from"), newspaperMethod("of")); + assertThat(new IsFactoryMethod(Magazine.class)).accepts(magazineMethod("from"), magazineMethod("of")); + } + + @Test + void isNotFactoryConstructorForPrivateConstructor() { + assertThat(new IsFactoryConstructor(Magazine.class)).rejects(constructor(Magazine.class)); + } + + @Test + void isFactoryConstructorForValidConstructors() { + assertThat(new IsFactoryConstructor(Book.class)).accepts(constructor(Book.class)); + assertThat(new IsFactoryConstructor(Journal.class)).accepts(constructor(Journal.class)); + assertThat(new IsFactoryConstructor(Newspaper.class)).accepts(constructor(Newspaper.class)); + } + + @Test + void convertsStringToBookViaStaticFactoryMethod() throws Exception { + assertConverts("enigma", Book.class, Book.factory("enigma")); + } + + @Test + void convertsStringToJournalViaFactoryConstructor() throws Exception { + assertConverts("enigma", Journal.class, new Journal("enigma")); + } + + @Test + void convertsStringToNewspaperViaConstructorIgnoringMultipleFactoryMethods() throws Exception { + assertConverts("enigma", Newspaper.class, new Newspaper("enigma")); + } + + @Test + @DisplayName("Cannot convert String to Diary because Diary has neither a static factory method nor a factory constructor") + void cannotConvertStringToDiary() { + assertThat(converter.canConvert(Diary.class)).isFalse(); + } + + @Test + @DisplayName("Cannot convert String to Magazine because Magazine has multiple static factory methods") + void cannotConvertStringToMagazine() { + assertThat(converter.canConvert(Magazine.class)).isFalse(); + } + + // ------------------------------------------------------------------------- + + private static Constructor constructor(Class clazz) { + return ReflectionUtils.findConstructors(clazz, + ctr -> ctr.getParameterCount() == 1 && ctr.getParameterTypes()[0] == String.class).get(0); + } + + private static Method bookMethod(String methodName) { + return bookMethod(methodName, String.class); + } + + private static Method bookMethod(String methodName, Class parameterType) { + return findMethod(Book.class, methodName, parameterType).orElseThrow(); + } + + private static Method newspaperMethod(String methodName) { + return findMethod(Newspaper.class, methodName, String.class).orElseThrow(); + } + + private static Method magazineMethod(String methodName) { + return findMethod(Magazine.class, methodName, String.class).orElseThrow(); + } + + private static void assertConverts(String input, Class targetType, Object expectedOutput) throws Exception { + assertThat(converter.canConvert(targetType)).isTrue(); + + var result = converter.convert(input, targetType); + + assertThat(result) // + .describedAs(input + " --(" + targetType.getName() + ")--> " + expectedOutput) // + .isEqualTo(expectedOutput); + } + + static class Book { + + private final String title; + + Book(String title) { + this.title = title; + } + + // static and non-private + static Book factory(String title) { + return new Book(title); + } + + // wrong parameter type + static Book factory(Object obj) { + return new Book(String.valueOf(obj)); + } + + @SuppressWarnings("unused") + private static Book privateFactory(String title) { + return new Book(title); + } + + Book nonStaticFactory(String title) { + return new Book(title); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Book)) { + return false; + } + var that = (Book) obj; + return Objects.equals(this.title, that.title); + } + + } + + static class Journal { + + private final String title; + + Journal(String title) { + this.title = title; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Journal)) { + return false; + } + var that = (Journal) obj; + return Objects.equals(this.title, that.title); + } + + } + + static class Newspaper { + + private final String title; + + Newspaper(String title) { + this.title = title; + } + + static Newspaper from(String title) { + return new Newspaper(title); + } + + static Newspaper of(String title) { + return new Newspaper(title); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Newspaper)) { + return false; + } + var that = (Newspaper) obj; + return Objects.equals(this.title, that.title); + } + + } + + static class Magazine { + + private Magazine(String title) { + } + + static Magazine from(String title) { + return new Magazine(title); + } + + static Magazine of(String title) { + return new Magazine(title); + } + + } + + static class Diary { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java new file mode 100644 index 00000000..3bb89074 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static java.time.ZoneOffset.UTC; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoLocalDate; +import java.time.chrono.ChronoLocalDateTime; +import java.time.chrono.ChronoZonedDateTime; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +class JavaTimeArgumentConverterTests { + + @Test + void convertsStringToChronoLocalDate() { + assertThat(convert("01.02.2017", "dd.MM.yyyy", ChronoLocalDate.class)) // + .isEqualTo(LocalDate.of(2017, 2, 1)); + } + + @Test + void convertsStringToChronoLocalDateTime() { + assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", ChronoLocalDateTime.class)) // + .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); + } + + @Test + void convertsStringToChronoZonedDateTime() { + assertThat(convert("01.02.2017 12:34:56.789 Z", "dd.MM.yyyy HH:mm:ss.SSS X", ChronoZonedDateTime.class)) // + .isEqualTo(ZonedDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, UTC)); + } + + @Test + void convertsStringToLocalDate() { + assertThat(convert("01.02.2017", "dd.MM.yyyy", LocalDate.class)) // + .isEqualTo(LocalDate.of(2017, 2, 1)); + } + + @Test + void convertsStringToLocalDateTime() { + assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", LocalDateTime.class)) // + .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); + } + + @Test + void convertsStringToLocalTime() { + assertThat(convert("12:34:56.789", "HH:mm:ss.SSS", LocalTime.class)) // + .isEqualTo(LocalTime.of(12, 34, 56, 789_000_000)); + } + + @Test + void convertsStringToOffsetDateTime() { + assertThat(convert("01.02.2017 12:34:56.789 +02", "dd.MM.yyyy HH:mm:ss.SSS X", OffsetDateTime.class)) // + .isEqualTo(OffsetDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, ZoneOffset.ofHours(2))); + } + + @Test + void convertsStringToOffsetTime() { + assertThat(convert("12:34:56.789 -02", "HH:mm:ss.SSS X", OffsetTime.class)) // + .isEqualTo(OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.ofHours(-2))); + } + + @Test + void convertsStringToYear() { + assertThat(convert("2017", "yyyy", Year.class)) // + .isEqualTo(Year.of(2017)); + } + + @Test + void convertsStringToYearMonth() { + assertThat(convert("03/2017", "MM/yyyy", YearMonth.class)) // + .isEqualTo(YearMonth.of(2017, 3)); + } + + @Test + void convertsStringToZonedDateTime() { + assertThat(convert("01.02.2017 12:34:56.789 Europe/Berlin", "dd.MM.yyyy HH:mm:ss.SSS VV", ZonedDateTime.class)) // + .isEqualTo(ZonedDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, ZoneId.of("Europe/Berlin"))); + } + + @Test + void throwsExceptionOnInvalidTargetType() { + var exception = assertThrows(ArgumentConversionException.class, () -> convert("2017", "yyyy", Integer.class)); + + assertThat(exception).hasMessage("Cannot convert to java.lang.Integer: 2017"); + } + + private Object convert(Object input, String pattern, Class targetClass) { + var converter = new JavaTimeArgumentConverter(); + var annotation = mock(JavaTimeConversionPattern.class); + when(annotation.value()).thenReturn(pattern); + converter.accept(annotation); + + return converter.convert(input, targetClass); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java new file mode 100644 index 00000000..c32a0313 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java @@ -0,0 +1,176 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Tests for {@link TypedArgumentConverter}. + * + * @since 5.7 + */ +class TypedArgumentConverterTests { + + @Nested + class UnitTests { + + private final StringLengthArgumentConverter converter = new StringLengthArgumentConverter(); + + /** + * @since 5.8 + */ + @Test + void preconditions() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> new StringLengthArgumentConverter(null, Integer.class))// + .withMessage("sourceType must not be null"); + + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> new StringLengthArgumentConverter(String.class, null))// + .withMessage("targetType must not be null"); + } + + @Test + void convertsSourceToTarget() { + assertAll(// + () -> assertConverts("abcd", 4), // + () -> assertConverts("", 0), // + () -> assertConverts(null, 0)// + ); + } + + private void assertConverts(String input, int expected) { + assertThat(this.converter.convert(input)).isEqualTo(expected); + } + + @Test + void sourceTypeMismatch() { + Parameter parameter = findParameterOfMethod("stringToBoolean", Boolean.class); + ParameterContext parameterContext = parameterContext(parameter); + assertThatExceptionOfType(ArgumentConversionException.class)// + .isThrownBy(() -> this.converter.convert(Boolean.TRUE, parameterContext))// + .withMessage("StringLengthArgumentConverter cannot convert objects of type [java.lang.Boolean]. " + + "Only source objects of type [java.lang.String] are supported."); + } + + @Test + void targetTypeMismatch() { + Parameter parameter = findParameterOfMethod("stringToBoolean", Boolean.class); + ParameterContext parameterContext = parameterContext(parameter); + assertThatExceptionOfType(ArgumentConversionException.class)// + .isThrownBy(() -> this.converter.convert("enigma", parameterContext))// + .withMessage("StringLengthArgumentConverter cannot convert to type [java.lang.Boolean]. " + + "Only target type [java.lang.Integer] is supported."); + } + + private ParameterContext parameterContext(Parameter parameter) { + ParameterContext parameterContext = mock(); + when(parameterContext.getParameter()).thenReturn(parameter); + return parameterContext; + } + + private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { + Method method = ReflectionUtils.findMethod(getClass(), methodName, parameterTypes).get(); + return method.getParameters()[0]; + } + + void stringToBoolean(Boolean b) { + } + + } + + /** + * @since 5.8 + */ + @Nested + class IntegrationTests { + + @ParameterizedTest + @NullSource + void nullStringToInteger(@StringLength Integer length) { + assertThat(length).isEqualTo(0); + } + + @ParameterizedTest + @NullSource + void nullStringToPrimitiveInt(@StringLength int length) { + assertThat(length).isEqualTo(0); + } + + @ParameterizedTest + @NullSource + void nullStringToPrimitiveLong(@StringLength long length) { + assertThat(length).isEqualTo(0); + } + + @ParameterizedTest + @ValueSource(strings = "enigma") + void stringToInteger(@StringLength Integer length) { + assertThat(length).isEqualTo(6); + } + + @ParameterizedTest + @ValueSource(strings = "enigma") + void stringToPrimitiveInt(@StringLength int length) { + assertThat(length).isEqualTo(6); + } + + @ParameterizedTest + @ValueSource(strings = "enigma") + void stringToPrimitiveLong(@StringLength long length) { + assertThat(length).isEqualTo(6); + } + + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + @ConvertWith(StringLengthArgumentConverter.class) + private @interface StringLength { + } + + private static class StringLengthArgumentConverter extends TypedArgumentConverter { + + StringLengthArgumentConverter() { + this(String.class, Integer.class); + } + + StringLengthArgumentConverter(Class sourceType, Class targetType) { + super(sourceType, targetType); + } + + @Override + protected Integer convert(String source) throws ArgumentConversionException { + return (source != null ? source.length() : 0); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java new file mode 100644 index 00000000..48958cf4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.jupiter.params.provider.Arguments.of; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link Arguments}. + * + * @since 5.0 + */ +class ArgumentsTests { + + @Test + void ofSupportsVarargs() { + var arguments = of(1, "2", 3.0); + + assertArrayEquals(new Object[] { 1, "2", 3.0 }, arguments.get()); + } + + @Test + void argumentsSupportsVarargs() { + var arguments = arguments(1, "2", 3.0); + + assertArrayEquals(new Object[] { 1, "2", 3.0 }, arguments.get()); + } + + @Test + void ofReturnsSameArrayUsedForCreating() { + Object[] input = { 1, "2", 3.0 }; + + var arguments = of(input); + + assertThat(arguments.get()).isSameAs(input); + } + + @Test + void argumentsReturnsSameArrayUsedForCreating() { + Object[] input = { 1, "2", 3.0 }; + + var arguments = arguments(input); + + assertThat(arguments.get()).isSameAs(input); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java new file mode 100644 index 00000000..67acc181 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -0,0 +1,389 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvSource; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 5.0 + */ +class CsvArgumentsProviderTests { + + @Test + void throwsExceptionForInvalidCsv() { + var annotation = csvSource("foo", "bar", ""); + + assertThatExceptionOfType(JUnitException.class)// + .isThrownBy(() -> provideArguments(annotation).toArray())// + .withMessage("Record at index 3 contains invalid CSV: \"\""); + } + + @Test + void throwsExceptionIfNeitherValueNorTextBlockIsDeclared() { + var annotation = csvSource().build(); + + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> provideArguments(annotation))// + .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); + } + + @Test + void throwsExceptionIfValueAndTextBlockAreDeclared() { + var annotation = csvSource().lines("foo").textBlock(""" + bar + baz + """).build(); + + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> provideArguments(annotation))// + .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); + } + + @Test + void providesSingleArgument() { + var annotation = csvSource("foo"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo")); + } + + @Test + void providesSingleArgumentFromTextBlock() { + var annotation = csvSource().textBlock("foo").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo")); + } + + @Test + void providesMultipleArguments() { + var annotation = csvSource("foo", "bar"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesMultipleArgumentsFromTextBlock() { + var annotation = csvSource().textBlock(""" + foo + bar + """).build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void splitsAndTrimsArguments() { + var annotation = csvSource(" foo , bar "); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo", "bar")); + } + + @Test + void trimsLeadingSpaces() { + var annotation = csvSource("'', 1", " '', 2", "'' , 3", " '' , 4"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(new Object[][] { { "", "1" }, { "", "2" }, { "", "3" }, { "", "4" } }); + } + + @Test + void trimsTrailingSpaces() { + var annotation = csvSource("1,''", "2, ''", "3,'' ", "4, '' "); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(new Object[][] { { "1", "" }, { "2", "" }, { "3", "" }, { "4", "" } }); + } + + @Test + void ignoresLeadingAndTrailingSpaces() { + var annotation = csvSource().lines("1,a", "2, b", "3,c ", "4, d ") // + .ignoreLeadingAndTrailingWhitespace(false).build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly( + new Object[][] { { "1", "a" }, { "2", " b" }, { "3", "c " }, { "4", " d " } }); + } + + @Test + void understandsQuotes() { + var annotation = csvSource("'foo, bar'"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo, bar")); + } + + @Test + void understandsQuotesInTextBlock() { + var annotation = csvSource().textBlock(""" + 'foo, bar' + """).build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo, bar")); + } + + @Test + void understandsCustomQuotes() { + var annotation = csvSource().quoteCharacter('~').lines("~foo, bar~").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo, bar")); + } + + @Test + void understandsCustomQuotesInTextBlock() { + var annotation = csvSource().quoteCharacter('"').textBlock(""" + "foo, bar" + """).build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo, bar")); + } + + @Test + void understandsEscapeCharacters() { + var annotation = csvSource("'foo or ''bar''', baz"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo or 'bar'", "baz")); + } + + @Test + void understandsEscapeCharactersWithCutomQuoteCharacter() { + var annotation = csvSource().quoteCharacter('~').lines("~foo or ~~bar~~~, baz").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo or ~bar~", "baz")); + } + + @Test + void doesNotTrimSpacesInsideQuotes() { + var annotation = csvSource("''", "' '", "'blank '", "' not blank '"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array(""), array(" "), array("blank "), array(" not blank ")); + } + + @Test + void providesArgumentsWithCharacterDelimiter() { + var annotation = csvSource().delimiter('|').lines("foo|bar", "bar|foo").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo", "bar"), array("bar", "foo")); + } + + @Test + void providesArgumentsWithStringDelimiter() { + var annotation = csvSource().delimiterString("~~~").lines("foo~~~ bar", "bar~~~ foo").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo", "bar"), array("bar", "foo")); + } + + @Test + void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { + var annotation = csvSource().delimiter('|').delimiterString("~~~").build(); + + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> provideArguments(annotation))// + .withMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// + .withMessageContaining("CsvSource"); + } + + @Test + void defaultEmptyValueAndDefaultNullValue() { + var annotation = csvSource("'', null, , apple"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("", "null", null, "apple")); + } + + @Test + void customEmptyValueAndDefaultNullValue() { + var annotation = csvSource().emptyValue("EMPTY").lines("'', null, , apple").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("EMPTY", "null", null, "apple")); + } + + @Test + void customNullValues() { + var annotation = csvSource().nullValues("N/A", "NIL").lines("apple, , NIL, '', N/A, banana").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana")); + } + + @Test + void convertsEmptyValuesToNullInLinesAfterFirstLine() { + var annotation = csvSource("'', ''", " , "); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(new Object[][] { { "", "" }, { null, null } }); + } + + @Test + void throwsExceptionIfSourceExceedsMaxCharsPerColumnConfig() { + var annotation = csvSource().lines("413").maxCharsPerColumn(2).build(); + + assertThatExceptionOfType(CsvParsingException.class)// + .isThrownBy(() -> provideArguments(annotation))// + .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// + .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + } + + @Test + void providesArgumentWithDefaultMaxCharsPerColumnConfig() { + var annotation = csvSource().lines("0".repeat(4096)).delimiter(';').build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments.toArray()).hasSize(1); + } + + @Test + void throwsExceptionWhenSourceExceedsDefaultMaxCharsPerColumnConfig() { + var annotation = csvSource().lines("0".repeat(4097)).delimiter(';').build(); + + assertThatExceptionOfType(CsvParsingException.class)// + .isThrownBy(() -> provideArguments(annotation))// + .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// + .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + } + + @Test + void providesArgumentsForExceedsSourceWithCustomMaxCharsPerColumnConfig() { + var annotation = csvSource().lines("0".repeat(4097)).delimiter(';').maxCharsPerColumn(4097).build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments.toArray()).hasSize(1); + } + + @Test + void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber() { + var annotation = csvSource().lines("41").delimiter(';').maxCharsPerColumn(-1).build(); + + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> provideArguments(annotation))// + .withMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); + } + + @Test + void ignoresCommentCharacterWhenUsingValueAttribute() { + var annotation = csvSource("#foo", "#bar,baz", "baz,#quux"); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("#foo"), array("#bar", "baz"), array("baz", "#quux")); + } + + @Test + void honorsCommentCharacterWhenUsingTextBlockAttribute() { + var annotation = csvSource().textBlock(""" + #foo + bar, #baz + '#bar', baz + """).build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("bar", "#baz"), array("#bar", "baz")); + } + + @Test + void supportsCsvHeadersWhenUsingTextBlockAttribute() { + supportsCsvHeaders(csvSource().useHeadersInDisplayName(true).textBlock(""" + FRUIT, RANK + apple, 1 + banana, 2 + """).build()); + } + + @Test + void supportsCsvHeadersWhenUsingValueAttribute() { + supportsCsvHeaders(csvSource().useHeadersInDisplayName(true)// + .lines("FRUIT, RANK", "apple, 1", "banana, 2").build()); + } + + private void supportsCsvHeaders(CsvSource csvSource) { + var arguments = provideArguments(csvSource); + Stream argumentsAsStrings = arguments.map(array -> { + String[] strings = new String[array.length]; + for (int i = 0; i < array.length; i++) { + strings[i] = String.valueOf(array[i]); + } + return strings; + }); + + assertThat(argumentsAsStrings).containsExactly(array("FRUIT = apple", "RANK = 1"), + array("FRUIT = banana", "RANK = 2")); + } + + @Test + void throwsExceptionIfColumnCountExceedsHeaderCount() { + var annotation = csvSource().useHeadersInDisplayName(true).textBlock(""" + FRUIT, RANK + apple, 1 + banana, 2, BOOM! + """).build(); + + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> provideArguments(annotation))// + .withMessage( + "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); + } + + private Stream provideArguments(CsvSource annotation) { + var provider = new CsvArgumentsProvider(); + provider.accept(annotation); + return provider.provideArguments(null).map(Arguments::get); + } + + @SuppressWarnings("unchecked") + private static T[] array(T... elements) { + return elements; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java new file mode 100644 index 00000000..1933e502 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -0,0 +1,536 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvFileSource; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.provider.CsvFileArgumentsProvider.InputStreamProvider; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 5.0 + */ +class CsvFileArgumentsProviderTests { + + @Test + void providesArgumentsForNewlineAndComma() { + var annotation = csvFileSource()// + .resources("test.csv")// + .lineSeparator("\n")// + .delimiter(',')// + .build(); + + var arguments = provideArguments(annotation, "foo, bar \n baz, qux \n"); + + assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); + } + + @Test + void providesArgumentsForCarriageReturnAndSemicolon() { + var annotation = csvFileSource()// + .resources("test.csv")// + .lineSeparator("\r")// + .delimiter(';')// + .build(); + + var arguments = provideArguments(annotation, "foo; bar \r baz; qux"); + + assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); + } + + @Test + void providesArgumentsWithCustomQuoteCharacter() { + var annotation = csvFileSource()// + .resources("test.csv")// + .quoteCharacter('\'')// + .build(); + + var arguments = provideArguments(annotation, "foo, 'bar \"and\" baz', qux \n 'lemon lime', banana, apple"); + + assertThat(arguments).containsExactly(array("foo", "bar \"and\" baz", "qux"), + array("lemon lime", "banana", "apple")); + } + + @Test + void providesArgumentsWithStringDelimiter() { + var annotation = csvFileSource()// + .resources("test.csv")// + .delimiterString(",")// + .build(); + + var arguments = provideArguments(annotation, "foo, bar \n baz, qux \n"); + + assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); + } + + @Test + void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { + var annotation = csvFileSource()// + .resources("test.csv")// + .delimiter(',')// + .delimiterString(";")// + .build(); + + var exception = assertThrows(PreconditionViolationException.class, () -> provideArguments(annotation, "foo")); + + assertThat(exception)// + .hasMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// + .hasMessageContaining("CsvFileSource"); + } + + @Test + void ignoresCommentedOutEntries() { + var annotation = csvFileSource()// + .resources("test.csv")// + .delimiter(',')// + .build(); + + var arguments = provideArguments(annotation, "foo, bar \n#baz, qux"); + + assertThat(arguments).containsExactly(array("foo", "bar")); + } + + @Test + void closesInputStreamForClasspathResource() { + var closed = new AtomicBoolean(false); + InputStream inputStream = new ByteArrayInputStream("foo".getBytes()) { + + @Override + public void close() { + closed.set(true); + } + }; + var annotation = csvFileSource().resources("test.csv").build(); + + var arguments = provideArguments(inputStream, annotation); + + assertThat(arguments.count()).isEqualTo(1); + assertThat(closed.get()).describedAs("closed").isTrue(); + } + + @Test + void closesInputStreamForFile(@TempDir Path tempDir) { + var closed = new AtomicBoolean(false); + InputStream inputStream = new ByteArrayInputStream("foo".getBytes()) { + + @Override + public void close() { + closed.set(true); + } + }; + var annotation = csvFileSource().files(tempDir.resolve("test.csv").toAbsolutePath().toString()).build(); + + var arguments = provideArguments(inputStream, annotation); + + assertThat(arguments.count()).isEqualTo(1); + assertThat(closed.get()).describedAs("closed").isTrue(); + } + + @Test + void readsFromSingleClasspathResource() { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv")// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); + } + + @Test + void readsFromSingleFileWithAbsolutePath(@TempDir Path tempDir) throws Exception { + var csvFile = writeClasspathResourceToFile("/single-column.csv", tempDir.resolve("single-column.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .files(csvFile.toAbsolutePath().toString())// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); + } + + @Test + void readsFromClasspathResourcesAndFiles(@TempDir Path tempDir) throws Exception { + var csvFile = writeClasspathResourceToFile("/single-column.csv", tempDir.resolve("single-column.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv")// + .files(csvFile.toAbsolutePath().toString())// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).hasSize(2 * 5); + } + + @Test + void readsFromSingleFileWithRelativePath() throws Exception { + var csvFile = writeClasspathResourceToFile("/single-column.csv", Path.of("single-column.csv")); + try { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .files(csvFile.getFileName().toString())// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); + } + finally { + Files.delete(csvFile); + } + } + + @Test + void readsFromSingleClasspathResourceWithCustomEmptyValue() { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv")// + .emptyValue("EMPTY")// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("EMPTY")); + } + + @Test + void readsFromMultipleClasspathResources() { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv", "/single-column.csv")// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).hasSize(10); + } + + @Test + void readsFromSingleClasspathResourceWithHeaders() { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv")// + .numLinesToSkip(1)// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).containsExactly(array("bar"), array("baz"), array("qux"), array("")); + } + + @Test + void readsFromSingleClasspathResourceWithMoreHeadersThanLines() { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv")// + .numLinesToSkip(10)// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).isEmpty(); + } + + @Test + void readsFromMultipleClasspathResourcesWithHeaders() { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv", "/single-column.csv")// + .numLinesToSkip(1)// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).containsExactly(array("bar"), array("baz"), array("qux"), array(""), array("bar"), + array("baz"), array("qux"), array("")); + } + + @Test + void supportsCsvHeadersInDisplayNames() { + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/single-column.csv")// + .useHeadersInDisplayName(true)// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + Stream argumentsAsStrings = arguments.map(array -> new String[] { String.valueOf(array[0]) }); + + assertThat(argumentsAsStrings).containsExactly(array("foo = bar"), array("foo = baz"), array("foo = qux"), + array("foo = ")); + } + + @Test + void throwsExceptionForMissingClasspathResource() { + var annotation = csvFileSource()// + .resources("/does-not-exist.csv")// + .build(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception).hasMessageContaining("Classpath resource [/does-not-exist.csv] does not exist"); + } + + @Test + void throwsExceptionForBlankClasspathResource() { + var annotation = csvFileSource()// + .resources(" ")// + .build(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception).hasMessageContaining("Classpath resource [ ] must not be null or blank"); + } + + @Test + void throwsExceptionForMissingFile() { + var annotation = csvFileSource()// + .files("does-not-exist.csv")// + .build(); + + var exception = assertThrows(JUnitException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception).hasMessageContaining("File [does-not-exist.csv] could not be read"); + } + + @Test + void throwsExceptionForBlankFile() { + var annotation = csvFileSource()// + .files(" ")// + .build(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception).hasMessageContaining("File [ ] must not be null or blank"); + } + + @Test + void throwsExceptionIfResourcesAndFilesAreEmpty() { + var annotation = csvFileSource()// + .resources()// + .files()// + .build(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception).hasMessageContaining("Resources or files must not be empty"); + } + + @Test + void throwsExceptionForInvalidCharset() { + var annotation = csvFileSource()// + .encoding("Bogus-Charset")// + .resources("/bogus-charset.csv")// + .build(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception)// + .hasMessageContaining("The charset supplied in Mock for CsvFileSource")// + .hasMessageEndingWith("is invalid"); + } + + @Test + void throwsExceptionForInvalidCsvFormat() { + var annotation = csvFileSource()// + .resources("/broken.csv")// + .build(); + + var exception = assertThrows(CsvParsingException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception)// + .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// + .hasRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + } + + @Test + void emptyValueIsAnEmptyWithCustomNullValueString() { + var annotation = csvFileSource()// + .resources("test.csv")// + .lineSeparator("\n")// + .delimiter(',')// + .nullValues("NIL")// + .build(); + + var arguments = provideArguments(annotation, "apple, , NIL, ''\nNIL, NIL, foo, bar"); + + assertThat(arguments).containsExactly(array("apple", null, null, "''"), array(null, null, "foo", "bar")); + } + + @Test + void readsLineFromDefaultMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws Exception { + var csvFile = writeClasspathResourceToFile("/default-max-chars.csv", tempDir.resolve("default-max-chars.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/default-max-chars.csv")// + .files(csvFile.toAbsolutePath().toString())// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).hasSize(2); + } + + @Test + void readsLineFromExceedsMaxCharsFileWithCustomConfig(@TempDir Path tempDir) throws java.io.IOException { + var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", + tempDir.resolve("exceeds-default-max-chars.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/exceeds-default-max-chars.csv")// + .maxCharsPerColumn(4097)// + .files(csvFile.toAbsolutePath().toString())// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).hasSize(2); + } + + @Test + void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber(@TempDir Path tempDir) throws java.io.IOException { + var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", + tempDir.resolve("exceeds-default-max-chars.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/exceeds-default-max-chars.csv")// + .maxCharsPerColumn(-1).files(csvFile.toAbsolutePath().toString())// + .build(); + + var exception = assertThrows(PreconditionViolationException.class, // + () -> provideArguments(new CsvFileArgumentsProvider(), annotation)); + + assertThat(exception)// + .hasMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); + } + + @Test + void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws java.io.IOException { + var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", + tempDir.resolve("exceeds-default-max-chars.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/exceeds-default-max-chars.csv")// + .files(csvFile.toAbsolutePath().toString())// + .build(); + + var exception = assertThrows(CsvParsingException.class, + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); + + assertThat(exception)// + .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// + .hasRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + } + + @Test + void ignoresLeadingAndTrailingSpaces(@TempDir Path tempDir) throws IOException { + var csvFile = writeClasspathResourceToFile("/leading-trailing-spaces.csv", + tempDir.resolve("leading-trailing-spaces.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/leading-trailing-spaces.csv")// + .files(csvFile.toAbsolutePath().toString())// + .ignoreLeadingAndTrailingWhitespace(true)// + .build(); + + var arguments = provideArguments(new ByteArrayInputStream(Files.readAllBytes(csvFile)), annotation); + + assertThat(arguments).containsExactly(array("ab", "cd"), array("ef", "gh")); + } + + @Test + void trimsLeadingAndTrailingSpaces(@TempDir Path tempDir) throws IOException { + var csvFile = writeClasspathResourceToFile("/leading-trailing-spaces.csv", + tempDir.resolve("leading-trailing-spaces.csv")); + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .resources("/leading-trailing-spaces.csv")// + .files(csvFile.toAbsolutePath().toString())// + .delimiter(',')// + .ignoreLeadingAndTrailingWhitespace(false)// + .build(); + + var arguments = provideArguments(new ByteArrayInputStream(Files.readAllBytes(csvFile)), annotation); + + assertThat(arguments).containsExactly(array(" ab ", " cd"), array("ef ", "gh")); + } + + private Stream provideArguments(CsvFileSource annotation, String content) { + return provideArguments(new ByteArrayInputStream(content.getBytes(UTF_8)), annotation); + } + + private Stream provideArguments(InputStream inputStream, CsvFileSource annotation) { + var provider = new CsvFileArgumentsProvider(new InputStreamProvider() { + @Override + public InputStream openClasspathResource(Class baseClass, String path) { + assertThat(path).isEqualTo(annotation.resources()[0]); + return inputStream; + } + + @Override + public InputStream openFile(String path) { + assertThat(path).isEqualTo(annotation.files()[0]); + return inputStream; + } + }); + return provideArguments(provider, annotation); + } + + private Stream provideArguments(CsvFileArgumentsProvider provider, CsvFileSource annotation) { + provider.accept(annotation); + var context = mock(ExtensionContext.class); + when(context.getTestClass()).thenReturn(Optional.of(CsvFileArgumentsProviderTests.class)); + doCallRealMethod().when(context).getRequiredTestClass(); + return provider.provideArguments(context).map(Arguments::get); + } + + @SuppressWarnings("unchecked") + private static T[] array(T... elements) { + return elements; + } + + private static Path writeClasspathResourceToFile(String name, Path target) throws IOException { + try (var in = CsvFileArgumentsProviderTests.class.getResourceAsStream(name)) { + Files.copy(in, target); + } + return target; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java new file mode 100644 index 00000000..eb265d3e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java @@ -0,0 +1,139 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithTwoConstants.BAR; +import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithTwoConstants.FOO; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 5.0 + */ +class EnumArgumentsProviderTests { + + private ExtensionContext extensionContext = mock(); + + @Test + void providesAllEnumConstants() { + var arguments = provideArguments(EnumWithTwoConstants.class); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); + } + + @Test + void provideSingleEnumConstant() { + var arguments = provideArguments(EnumWithTwoConstants.class, "FOO"); + + assertThat(arguments).containsExactly(new Object[] { FOO }); + } + + @Test + void provideAllEnumConstantsWithNamingAll() { + var arguments = provideArguments(EnumWithTwoConstants.class, "FOO", "BAR"); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); + } + + @Test + void duplicateConstantNameIsDetected() { + Exception exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithTwoConstants.class, "FOO", "BAR", "FOO")); + assertThat(exception).hasMessageContaining("Duplicate enum constant name(s) found"); + } + + @Test + void invalidConstantNameIsDetected() { + Exception exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithTwoConstants.class, "FO0", "B4R")); + assertThat(exception).hasMessageContaining("Invalid enum constant name(s) in"); + } + + @Test + void invalidPatternIsDetected() { + Exception exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithTwoConstants.class, Mode.MATCH_ALL, "(", ")")); + assertThat(exception).hasMessageContaining("Pattern compilation failed"); + } + + @Test + void providesEnumConstantsBasedOnTestMethod() throws Exception { + when(extensionContext.getRequiredTestMethod()).thenReturn( + TestCase.class.getDeclaredMethod("methodWithCorrectParameter", EnumWithTwoConstants.class)); + + var arguments = provideArguments(NullEnum.class); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); + } + + @Test + void incorrectParameterTypeIsDetected() throws Exception { + when(extensionContext.getRequiredTestMethod()).thenReturn( + TestCase.class.getDeclaredMethod("methodWithIncorrectParameter", Object.class)); + + Exception exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(NullEnum.class)); + assertThat(exception).hasMessageStartingWith("First parameter must reference an Enum type"); + } + + @Test + void methodsWithoutParametersAreDetected() throws Exception { + when(extensionContext.getRequiredTestMethod()).thenReturn( + TestCase.class.getDeclaredMethod("methodWithoutParameters")); + + Exception exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(NullEnum.class)); + assertThat(exception).hasMessageStartingWith("Test method must declare at least one parameter"); + } + + static class TestCase { + void methodWithCorrectParameter(EnumWithTwoConstants parameter) { + } + + void methodWithIncorrectParameter(Object parameter) { + } + + void methodWithoutParameters() { + } + } + + enum EnumWithTwoConstants { + FOO, BAR + } + + private > Stream provideArguments(Class enumClass, String... names) { + return provideArguments(enumClass, Mode.INCLUDE, names); + } + + private > Stream provideArguments(Class enumClass, Mode mode, String... names) { + var annotation = mock(EnumSource.class); + when(annotation.value()).thenAnswer(invocation -> enumClass); + when(annotation.mode()).thenAnswer(invocation -> mode); + when(annotation.names()).thenAnswer(invocation -> names); + when(annotation.toString()).thenReturn(String.format("@EnumSource(value=%s.class, mode=%s, names=%s)", + enumClass.getSimpleName(), mode, Arrays.toString(names))); + + var provider = new EnumArgumentsProvider(); + provider.accept(annotation); + return provider.provideArguments(extensionContext).map(Arguments::get); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java new file mode 100644 index 00000000..f36a3140 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; +import static org.junit.jupiter.params.provider.EnumSource.Mode.INCLUDE; +import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; +import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ANY; +import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_NONE; +import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.BAR; +import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.BAZ; +import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.FOO; + +import java.util.EnumSet; +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +/** + * @since 5.0 + */ +class EnumSourceTests { + + @Test + void includeNamesWithAll() { + assertAll("include names with all", // + () -> assertTrue(INCLUDE.select(FOO, allOf(EnumWithThreeConstants::name))), + () -> assertTrue(INCLUDE.select(BAR, allOf(EnumWithThreeConstants::name))), + () -> assertTrue(INCLUDE.select(BAZ, allOf(EnumWithThreeConstants::name))) // + ); + } + + @Test + void includeNamesWithSingleton() { + assertAll("include names with singleton", // + () -> assertTrue(INCLUDE.select(FOO, Set.of(FOO.name()))), + () -> assertTrue(INCLUDE.select(BAR, Set.of(BAR.name()))), + () -> assertTrue(INCLUDE.select(BAZ, Set.of(BAZ.name()))) // + ); + assertAll("include names with singleton complement", // + () -> assertFalse(INCLUDE.select(BAR, Set.of(FOO.name()))), + () -> assertFalse(INCLUDE.select(BAZ, Set.of(FOO.name()))) // + ); + } + + @Test + void excludeNames() { + assertAll("exclude name with none excluded", // + () -> assertTrue(EXCLUDE.select(FOO, Set.of())), // + () -> assertTrue(EXCLUDE.select(BAR, Set.of())), // + () -> assertTrue(EXCLUDE.select(BAZ, Set.of())) // + ); + assertAll("exclude name with FOO excluded", // + () -> assertFalse(EXCLUDE.select(FOO, Set.of(FOO.name()))), + () -> assertTrue(EXCLUDE.select(BAR, Set.of(FOO.name()))), + () -> assertTrue(EXCLUDE.select(BAZ, Set.of(FOO.name()))) // + ); + } + + @Test + void matchesAll() { + assertAll("matches all", // + () -> assertTrue(MATCH_ALL.select(FOO, Set.of("F.."))), + () -> assertTrue(MATCH_ALL.select(BAR, Set.of("B.."))), + () -> assertTrue(MATCH_ALL.select(BAZ, Set.of("B.."))) // + ); + assertAll("matches all fails if not all match", // + () -> assertFalse(MATCH_ALL.select(FOO, Set.of("F..", "."))), + () -> assertFalse(MATCH_ALL.select(BAR, Set.of("B..", "."))), + () -> assertFalse(MATCH_ALL.select(BAZ, Set.of("B..", "."))) // + ); + } + + @Test + void matchesAny() { + assertAll("matches any", // + () -> assertTrue(MATCH_ANY.select(FOO, Set.of("B..", "^F.*"))), + () -> assertTrue(MATCH_ANY.select(BAR, Set.of("B", "B.", "B.."))), + () -> assertTrue(MATCH_ANY.select(BAZ, Set.of("^.+[zZ]$")))); + } + + @Test + void matchesNone() { + assertAll("matches none fails if any match", // + () -> assertFalse(MATCH_NONE.select(FOO, Set.of("F.."))), + () -> assertFalse(MATCH_NONE.select(FOO, Set.of("B..", "F.."))), + () -> assertFalse(MATCH_NONE.select(BAZ, Set.of("B.", "F.", "^.+[zZ]$")))); + + assertAll("matches none", // + () -> assertTrue(MATCH_NONE.select(FOO, Set.of())), // + () -> assertTrue(MATCH_NONE.select(FOO, Set.of("F."))), + () -> assertTrue(MATCH_NONE.select(FOO, Set.of("B.."))), + () -> assertTrue(MATCH_NONE.select(BAZ, Set.of(".", "B.", "F.")))); + } + + enum EnumWithThreeConstants { + FOO, BAR, BAZ; + + } + + static Set allOf(Function mapper) { + return EnumSet.allOf(EnumWithThreeConstants.class).stream().map(mapper).collect(toSet()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java new file mode 100644 index 00000000..1bd49479 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java @@ -0,0 +1,998 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; +import static org.junit.platform.commons.util.ReflectionUtils.findMethod; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; +import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.0 + */ +class MethodArgumentsProviderTests { + + private MutableExtensionRegistry extensionRegistry; + + @Test + void providesArgumentsUsingStream() { + var arguments = provideArguments("stringStreamProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingDoubleStream() { + var arguments = provideArguments("doubleStreamProvider"); + + assertThat(arguments).containsExactly(array(1.2), array(3.4)); + } + + @Test + void providesArgumentsUsingLongStream() { + var arguments = provideArguments("longStreamProvider"); + + assertThat(arguments).containsExactly(array(1L), array(2L)); + } + + @Test + void providesArgumentsUsingIntStream() { + var arguments = provideArguments("intStreamProvider"); + + assertThat(arguments).containsExactly(array(1), array(2)); + } + + /** + * @since 5.3.2 + */ + @Test + void providesArgumentsUsingStreamOfIntArrays() { + var arguments = provideArguments("intArrayStreamProvider"); + + assertThat(arguments).containsExactly( // + new Object[] { new int[] { 1, 2 } }, // + new Object[] { new int[] { 3, 4 } } // + ); + } + + /** + * @since 5.3.2 + */ + @Test + void providesArgumentsUsingStreamOfTwoDimensionalIntArrays() { + var arguments = provideArguments("twoDimensionalIntArrayStreamProvider"); + + assertThat(arguments).containsExactly( // + array((Object) new int[][] { { 1, 2 }, { 2, 3 } }), // + array((Object) new int[][] { { 4, 5 }, { 5, 6 } }) // + ); + } + + @Test + void providesArgumentsUsingStreamOfObjectArrays() { + var arguments = provideArguments("objectArrayStreamProvider"); + + assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); + } + + /** + * @since 5.3.2 + */ + @Test + void providesArgumentsUsingStreamOfTwoDimensionalObjectArrays() { + var arguments = provideArguments("twoDimensionalObjectArrayStreamProvider"); + + assertThat(arguments).containsExactly( // + array((Object) array(array("a", 1), array("b", 2))), // + array((Object) array(array("c", 3), array("d", 4))) // + ); + } + + @Test + void providesArgumentsUsingStreamOfArguments() { + var arguments = provideArguments("argumentsStreamProvider"); + + assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); + } + + @Test + void providesArgumentsUsingIterable() { + var arguments = provideArguments("stringIterableProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingIterator() { + var arguments = provideArguments("stringIteratorProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingMultipleFactoryMethods() { + var arguments = provideArguments("stringStreamProvider", "stringIterableProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar"), array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingIterableOfObjectArrays() { + var arguments = provideArguments("objectArrayIterableProvider"); + + assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); + } + + @Test + void providesArgumentsUsingListOfStrings() { + var arguments = provideArguments("stringArrayListProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingListOfObjectArrays() { + var arguments = provideArguments("objectArrayListProvider"); + + assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); + } + + @Test + void throwsExceptionWhenNonStaticFactoryMethodIsReferencedAndStaticIsRequired() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(NonStaticTestCase.class, null, false, "nonStaticStringStreamProvider").toArray()); + + assertThat(exception).hasMessageContaining("Cannot invoke non-static method"); + } + + @Test + void providesArgumentsFromNonStaticFactoryMethodWhenStaticIsNotRequired() { + var arguments = provideArguments(NonStaticTestCase.class, null, true, "nonStaticStringStreamProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingDefaultFactoryMethodName() { + var testClass = DefaultFactoryMethodNameTestCase.class; + var methodName = "testDefaultFactoryMethodName"; + var testMethod = findMethod(testClass, methodName, String.class).get(); + + var arguments = provideArguments(testClass, testMethod, false, ""); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingExternalFactoryMethod() { + var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "#stringsProvider"); + + assertThat(arguments).containsExactly(array("string1"), array("string2")); + } + + @Test + void providesArgumentsUsingExternalFactoryMethodWithParentheses() { + var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "#stringsProvider()"); + + assertThat(arguments).containsExactly(array("string1"), array("string2")); + } + + @Test + void providesArgumentsUsingExternalFactoryMethodFromStaticNestedClass() { + var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "$Nested#stringsProvider()"); + + assertThat(arguments).containsExactly(array("nested string1"), array("nested string2")); + } + + @Test + void providesArgumentsUsingExternalAndInternalFactoryMethodsCombined() { + var arguments = provideArguments("stringStreamProvider", + ExternalFactoryMethods.class.getName() + "#stringsProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar"), array("string1"), array("string2")); + } + + @Nested + class PrimitiveArrays { + + @Test + void providesArgumentsUsingBooleanArray() { + var arguments = provideArguments("booleanArrayProvider"); + + assertThat(arguments).containsExactly(array(Boolean.TRUE), array(Boolean.FALSE)); + } + + @Test + void providesArgumentsUsingByteArray() { + var arguments = provideArguments("byteArrayProvider"); + + assertThat(arguments).containsExactly(array((byte) 1), array(Byte.MIN_VALUE)); + } + + @Test + void providesArgumentsUsingCharArray() { + var arguments = provideArguments("charArrayProvider"); + + assertThat(arguments).containsExactly(array((char) 1), array(Character.MIN_VALUE)); + } + + @Test + void providesArgumentsUsingDoubleArray() { + var arguments = provideArguments("doubleArrayProvider"); + + assertThat(arguments).containsExactly(array(1d), array(Double.MIN_VALUE)); + } + + @Test + void providesArgumentsUsingFloatArray() { + var arguments = provideArguments("floatArrayProvider"); + + assertThat(arguments).containsExactly(array(1f), array(Float.MIN_VALUE)); + } + + @Test + void providesArgumentsUsingIntArray() { + var arguments = provideArguments("intArrayProvider"); + + assertThat(arguments).containsExactly(array(47), array(Integer.MIN_VALUE)); + } + + @Test + void providesArgumentsUsingLongArray() { + var arguments = provideArguments("longArrayProvider"); + + assertThat(arguments).containsExactly(array(47L), array(Long.MIN_VALUE)); + } + + @Test + void providesArgumentsUsingShortArray() { + var arguments = provideArguments("shortArrayProvider"); + + assertThat(arguments).containsExactly(array((short) 47), array(Short.MIN_VALUE)); + } + + } + + @Nested + class ObjectArrays { + + @Test + void providesArgumentsUsingObjectArray() { + var arguments = provideArguments("objectArrayProvider"); + + assertThat(arguments).containsExactly(array(42), array("bar")); + } + + @Test + void providesArgumentsUsingStringArray() { + var arguments = provideArguments("stringArrayProvider"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsing2dObjectArray() { + var arguments = provideArguments("twoDimensionalObjectArrayProvider"); + + assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); + } + + } + + @Nested + class ParameterResolution { + + private final Method testMethod = findMethod(TestCase.class, "test").get(); + + @BeforeEach + void registerParameterResolver() { + extensionRegistry = createRegistryWithDefaultExtensions(mock()); + extensionRegistry.registerExtension(StringResolver.class); + extensionRegistry.registerExtension(StringArrayResolver.class); + extensionRegistry.registerExtension(IntArrayResolver.class); + } + + @Test + void providesArgumentsInferringDefaultFactoryMethodThatAcceptsArgument() { + Method testMethod = findMethod(TestCase.class, "overloadedStringStreamProvider", Object.class).get(); + String factoryMethodName = ""; // signals to use default + var arguments = provideArguments(testMethod, factoryMethodName); + + assertThat(arguments).containsExactly(array("foo!"), array("bar!")); + } + + @Test + void providesArgumentsUsingSimpleNameForFactoryMethodThatAcceptsArgumentWithoutSpecifyingParameterList() { + var arguments = provideArguments("stringStreamProviderWithParameter"); + + assertThat(arguments).containsExactly(array("foo!"), array("bar!")); + } + + @Test + void providesArgumentsUsingFullyQualifiedNameForFactoryMethodThatAcceptsArgumentWithoutSpecifyingParameterList() { + var arguments = provideArguments(TestCase.class.getName() + "#stringStreamProviderWithParameter"); + + assertThat(arguments).containsExactly(array("foo!"), array("bar!")); + } + + @Test + void providesArgumentsUsingFullyQualifiedNameSpecifyingParameter() { + var arguments = provideArguments( + TestCase.class.getName() + "#stringStreamProviderWithParameter(java.lang.String)"); + + assertThat(arguments).containsExactly(array("foo!"), array("bar!")); + } + + @Test + void providesArgumentsUsingLocalQualifiedNameSpecifyingParameter() { + var arguments = provideArguments(testMethod, "stringStreamProviderWithParameter(java.lang.String)"); + + assertThat(arguments).containsExactly(array("foo!"), array("bar!")); + } + + @Test + void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodSpecifyingEmptyParameterList() { + var arguments = provideArguments( + TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter()"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodSpecifyingEmptyParameterList() { + var arguments = provideArguments(this.testMethod, "stringStreamProviderWithOrWithoutParameter()"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodSpecifyingParameter() { + var arguments = provideArguments( + TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter(java.lang.String)"); + + assertThat(arguments).containsExactly(array("foo!"), array("bar!")); + } + + @Test + void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodSpecifyingParameter() { + var arguments = provideArguments(testMethod, + "stringStreamProviderWithOrWithoutParameter(java.lang.String)"); + + assertThat(arguments).containsExactly(array("foo!"), array("bar!")); + } + + @Test + void failsToProvideArgumentsUsingFullyQualifiedNameSpecifyingInvalidParameterType() { + String method = TestCase.class.getName() + "#stringStreamProviderWithParameter(example.FooBar)"; + var exception = assertThrows(JUnitException.class, () -> provideArguments(method).toArray()); + + assertThat(exception).hasMessage(""" + Failed to load parameter type [example.FooBar] for method [stringStreamProviderWithParameter] \ + in class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]."""); + } + + @Test + void failsToProvideArgumentsUsingLocalQualifiedNameSpecifyingInvalidParameterType() { + var method = "stringStreamProviderWithParameter(example.FooBar)"; + var exception = assertThrows(JUnitException.class, + () -> provideArguments(this.testMethod, method).toArray()); + + assertThat(exception).hasMessage(""" + Failed to load parameter type [example.FooBar] for method [stringStreamProviderWithParameter] \ + in class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]."""); + } + + @Test + void failsToProvideArgumentsUsingFullyQualifiedNameSpecifyingIncorrectParameterType() { + String method = TestCase.class.getName() + "#stringStreamProviderWithParameter(java.lang.Integer)"; + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(method).toArray()); + + assertThat(exception).hasMessage(""" + Could not find factory method [stringStreamProviderWithParameter(java.lang.Integer)] in \ + class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]"""); + } + + @Test + void failsToProvideArgumentsUsingLocalQualifiedNameSpecifyingIncorrectParameterType() { + var method = "stringStreamProviderWithParameter(java.lang.Integer)"; + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(this.testMethod, method).toArray()); + + assertThat(exception).hasMessage(""" + Could not find factory method [stringStreamProviderWithParameter(java.lang.Integer)] in \ + class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]"""); + } + + @ParameterizedTest + @ValueSource(strings = { + "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter(java.lang.String[])", + "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter([Ljava.lang.String;)", }) + void providesArgumentsUsingFullyQualifiedNameSpecifyingObjectArrayParameter(String method) { + var arguments = provideArguments(method); + + assertThat(arguments).containsExactly(array("foo :)"), array("bar :)")); + } + + @ParameterizedTest + @ValueSource(strings = { // + "stringStreamProviderWithArrayParameter(java.lang.String[])", + "stringStreamProviderWithArrayParameter([Ljava.lang.String;)" }) + void providesArgumentsUsingLocalQualifiedNameSpecifyingObjectArrayParameter(String method) { + var arguments = provideArguments(this.testMethod, method); + + assertThat(arguments).containsExactly(array("foo :)"), array("bar :)")); + } + + @ParameterizedTest + @ValueSource(strings = { + "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter(int[])", + "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter([I)", }) + void providesArgumentsUsingFullyQualifiedNameSpecifyingPrimitiveArrayParameter(String method) { + var arguments = provideArguments(method); + + assertThat(arguments).containsExactly(array("foo 42"), array("bar 42")); + } + + @ParameterizedTest + @ValueSource(strings = { // + "stringStreamProviderWithArrayParameter(int[])", // + "stringStreamProviderWithArrayParameter([I)" }) + void providesArgumentsUsingLocalQualifiedNameSpecifyingPrimitiveArrayParameter(String method) { + var arguments = provideArguments(this.testMethod, method); + + assertThat(arguments).containsExactly(array("foo 42"), array("bar 42")); + } + + @ParameterizedTest + @ValueSource(strings = { "java.lang.String,java.lang.String", "java.lang.String, java.lang.String", + "java.lang.String, java.lang.String" }) + void providesArgumentsUsingFullyQualifiedNameSpecifyingMultipleParameters(String params) { + var method = TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter(" + params + ")"; + var arguments = provideArguments(method); + + assertThat(arguments).containsExactly(array("foo!!"), array("bar!!")); + } + + @ParameterizedTest + @ValueSource(strings = { "java.lang.String,java.lang.String", "java.lang.String, java.lang.String", + "java.lang.String, java.lang.String" }) + void providesArgumentsUsingLocalQualifiedNameSpecifyingMultipleParameters(String params) { + var arguments = provideArguments(this.testMethod, + "stringStreamProviderWithOrWithoutParameter(" + params + ")"); + + assertThat(arguments).containsExactly(array("foo!!"), array("bar!!")); + } + + @Test + void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodWhenParameterListIsNotSpecified() { + var arguments = provideArguments(TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter"); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + @Test + void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodWhenParameterListIsNotSpecified() { + var arguments = provideArguments("stringStreamProviderWithOrWithoutParameter").toArray(); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + } + + @Nested + class ErrorCases { + + @Test + void throwsExceptionWhenFullyQualifiedMethodNameSyntaxIsInvalid() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments("org.example.wrongSyntax").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "[org.example.wrongSyntax] is not a valid fully qualified method name: " + + "it must start with a fully qualified class name followed by a '#' and then the method name, " + + "optionally followed by a parameter list enclosed in parentheses."); + } + + @Test + void throwsExceptionWhenClassForExternalFactoryMethodCannotBeLoaded() { + var exception = assertThrows(JUnitException.class, + () -> provideArguments("com.example.NonExistentClass#stringsProvider").toArray()); + + assertThat(exception.getMessage()).isEqualTo("Could not load class [com.example.NonExistentClass]"); + } + + @Test + void throwsExceptionWhenExternalFactoryMethodDoesNotExist() { + String factoryClass = ExternalFactoryMethods.class.getName(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(factoryClass + "#nonExistentMethod").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "Could not find factory method [nonExistentMethod] in class [%s]", factoryClass); + } + + @Test + void throwsExceptionWhenLocalFactoryMethodDoesNotExist() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments("nonExistentMethod").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "Could not find factory method [nonExistentMethod] in class [%s]", TestCase.class.getName()); + } + + @Test + void throwsExceptionWhenExternalFactoryMethodAcceptingSingleArgumentDoesNotExist() { + String factoryClass = ExternalFactoryMethods.class.getName(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(factoryClass + "#nonExistentMethod(int)").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "Could not find factory method [nonExistentMethod(int)] in class [%s]", factoryClass); + } + + @Test + void throwsExceptionWhenLocalFactoryMethodAcceptingSingleArgumentDoesNotExist() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments("nonExistentMethod(int)").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "Could not find factory method [nonExistentMethod(int)] in class [%s]", TestCase.class.getName()); + } + + @Test + void throwsExceptionWhenExternalFactoryMethodAcceptingMultipleArgumentsDoesNotExist() { + String factoryClass = ExternalFactoryMethods.class.getName(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(factoryClass + "#nonExistentMethod(int, java.lang.String)").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "Could not find factory method [nonExistentMethod(int, java.lang.String)] in class [%s]", factoryClass); + } + + @Test + void throwsExceptionWhenLocalFactoryMethodAcceptingMultipleArgumentsDoesNotExist() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments("nonExistentMethod(java.lang.String,int)").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "Could not find factory method [nonExistentMethod(java.lang.String,int)] in class [%s]", + TestCase.class.getName()); + } + + @Test + void throwsExceptionWhenExternalFactoryMethodHasInvalidReturnType() { + String testClass = TestCase.class.getName(); + String factoryClass = ExternalFactoryMethods.class.getName(); + String factoryMethod = factoryClass + "#factoryWithInvalidReturnType"; + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(TestCase.class, null, false, factoryMethod).toArray()); + + assertThat(exception.getMessage())// + .containsSubsequence("Could not find valid factory method [" + factoryMethod + "] for test class [", + testClass + "]", // + "but found the following invalid candidate: ", + "static java.lang.Object " + factoryClass + ".factoryWithInvalidReturnType()"); + } + + @Test + void throwsExceptionWhenLocalFactoryMethodHasInvalidReturnType() { + String testClass = TestCase.class.getName(); + String factoryClass = testClass; + String factoryMethod = "factoryWithInvalidReturnType"; + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(factoryMethod).toArray()); + + assertThat(exception.getMessage())// + .containsSubsequence("Could not find valid factory method [" + factoryMethod + "] for test class [", + factoryClass + "]", // + "but found the following invalid candidate: ", // + "static java.lang.Object " + factoryClass + ".factoryWithInvalidReturnType()"); + } + + @Test + void throwsExceptionWhenMultipleDefaultFactoryMethodCandidatesExist() { + var testClass = MultipleDefaultFactoriesTestCase.class; + var methodName = "test"; + var testMethod = findMethod(testClass, methodName, String.class).get(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(testClass, testMethod, false, "").toArray()); + + assertThat(exception.getMessage()).contains(// + "2 factory methods named [test] were found in class [", testClass.getName() + "]: ", // + "$MultipleDefaultFactoriesTestCase.test()", // + "$MultipleDefaultFactoriesTestCase.test(int)"// + ); + } + + @Test + void throwsExceptionWhenMultipleInvalidDefaultFactoryMethodCandidatesExist() { + var testClass = MultipleInvalidDefaultFactoriesTestCase.class; + var methodName = "test"; + var testMethod = findMethod(testClass, methodName, String.class).get(); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(testClass, testMethod, false, "").toArray()); + + assertThat(exception.getMessage()).contains(// + "Could not find valid factory method [test] in class [", testClass.getName() + "]", // + "but found the following invalid candidates: ", // + "$MultipleInvalidDefaultFactoriesTestCase.test()", // + "$MultipleInvalidDefaultFactoriesTestCase.test(int)"// + ); + } + + } + + // ------------------------------------------------------------------------- + + private static Object[] array(Object... objects) { + return objects; + } + + private Stream provideArguments(String... factoryMethodNames) { + return provideArguments(TestCase.class, null, false, factoryMethodNames); + } + + private Stream provideArguments(Method testMethod, String factoryMethodName) { + return provideArguments(TestCase.class, testMethod, false, factoryMethodName); + } + + private Stream provideArguments(Class testClass, Method testMethod, boolean allowNonStaticMethod, + String... factoryMethodNames) { + + if (testMethod == null) { + try { + class DummyClass { + @SuppressWarnings("unused") + public void dummyMethod() { + }; + } + // ensure we have a non-null method, even if it's not a real test method. + testMethod = DummyClass.class.getMethod("dummyMethod"); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + var methodSource = mock(MethodSource.class); + + when(methodSource.value()).thenReturn(factoryMethodNames); + + var extensionContext = mock(ExtensionContext.class); + when(extensionContext.getTestClass()).thenReturn(Optional.of(testClass)); + when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); + when(extensionContext.getExecutableInvoker()).thenReturn( + new DefaultExecutableInvoker(extensionContext, extensionRegistry)); + + doCallRealMethod().when(extensionContext).getRequiredTestMethod(); + doCallRealMethod().when(extensionContext).getRequiredTestClass(); + + var testInstance = allowNonStaticMethod ? ReflectionUtils.newInstance(testClass) : null; + when(extensionContext.getTestInstance()).thenReturn(Optional.ofNullable(testInstance)); + + var provider = new MethodArgumentsProvider(); + provider.accept(methodSource); + return provider.provideArguments(extensionContext).map(Arguments::get); + } + + // ------------------------------------------------------------------------- + + static class DefaultFactoryMethodNameTestCase { + + // Test + void testDefaultFactoryMethodName(String param) { + } + + // Factory + static Stream testDefaultFactoryMethodName() { + return Stream.of("foo", "bar"); + } + } + + static class MultipleDefaultFactoriesTestCase { + + // Test + void test(String param) { + } + + // Factory + static Stream test() { + return Stream.of(); + } + + // Another Factory + static Stream test(int num) { + return Stream.of(); + } + } + + static class MultipleInvalidDefaultFactoriesTestCase { + + // Test + void test(String param) { + } + + // NOT a Factory + static String test() { + return null; + } + + // Also NOT a Factory + static Object test(int num) { + return null; + } + } + + static class TestCase { + + void test() { + } + + // --- Invalid --------------------------------------------------------- + + static Object factoryWithInvalidReturnType() { + return -1; + } + + // --- Stream ---------------------------------------------------------- + + static Stream stringStreamProvider() { + return Stream.of("foo", "bar"); + } + + static Stream stringStreamProviderWithParameter(String parameter) { + return Stream.of("foo" + parameter, "bar" + parameter); + } + + static Stream stringStreamProviderWithArrayParameter(String[] parameter) { + String suffix = Arrays.stream(parameter).collect(Collectors.joining()); + return Stream.of("foo " + suffix, "bar " + suffix); + } + + static Stream stringStreamProviderWithArrayParameter(int[] parameter) { + return stringStreamProviderWithArrayParameter( + Arrays.stream(parameter).mapToObj(String::valueOf).toArray(String[]::new)); + } + + static Stream stringStreamProviderWithOrWithoutParameter() { + return stringStreamProvider(); + } + + static Stream stringStreamProviderWithOrWithoutParameter(String parameter) { + return stringStreamProviderWithParameter(parameter); + } + + static Stream stringStreamProviderWithOrWithoutParameter(String parameter1, String parameter2) { + return stringStreamProviderWithParameter(parameter1 + parameter2); + } + + // Overloaded method, but not a valid return type for a factory method + static void stringStreamProviderWithOrWithoutParameter(String parameter1, int parameter2) { + } + + // @ParameterizedTest + // @MethodSource // use default, inferred factory method + void overloadedStringStreamProvider(Object parameter) { + // test implementation + } + + // Default factory method for overloadedStringStreamProvider(Object) + static Stream overloadedStringStreamProvider(String parameter) { + return stringStreamProviderWithParameter(parameter); + } + + static DoubleStream doubleStreamProvider() { + return DoubleStream.of(1.2, 3.4); + } + + static LongStream longStreamProvider() { + return LongStream.of(1L, 2L); + } + + static IntStream intStreamProvider() { + return IntStream.of(1, 2); + } + + static Stream intArrayStreamProvider() { + return Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); + } + + static Stream twoDimensionalIntArrayStreamProvider() { + return Stream.of(new int[][] { { 1, 2 }, { 2, 3 } }, new int[][] { { 4, 5 }, { 5, 6 } }); + } + + static Stream objectArrayStreamProvider() { + return Stream.of(new Object[] { "foo", 42 }, new Object[] { "bar", 23 }); + } + + static Stream twoDimensionalObjectArrayStreamProvider() { + return Stream.of(new Object[][] { { "a", 1 }, { "b", 2 } }, new Object[][] { { "c", 3 }, { "d", 4 } }); + } + + static Stream argumentsStreamProvider() { + return objectArrayStreamProvider().map(Arguments::of); + } + + // --- Iterable / Collection ------------------------------------------- + + static Iterable stringIterableProvider() { + return TestCase::stringIteratorProvider; + } + + static Iterable objectArrayIterableProvider() { + return objectArrayListProvider(); + } + + static List stringArrayListProvider() { + return Arrays.asList("foo", "bar"); + } + + static List objectArrayListProvider() { + return Arrays.asList(array("foo", 42), array("bar", 23)); + } + + // --- Iterator -------------------------------------------------------- + + static Iterator stringIteratorProvider() { + return Arrays.asList("foo", "bar").iterator(); + } + + // --- Array of primitives --------------------------------------------- + + static boolean[] booleanArrayProvider() { + return new boolean[] { true, false }; + } + + static byte[] byteArrayProvider() { + return new byte[] { (byte) 1, Byte.MIN_VALUE }; + } + + static char[] charArrayProvider() { + return new char[] { (char) 1, Character.MIN_VALUE }; + } + + static double[] doubleArrayProvider() { + return new double[] { 1d, Double.MIN_VALUE }; + } + + static float[] floatArrayProvider() { + return new float[] { 1f, Float.MIN_VALUE }; + } + + static int[] intArrayProvider() { + return new int[] { 47, Integer.MIN_VALUE }; + } + + static long[] longArrayProvider() { + return new long[] { 47L, Long.MIN_VALUE }; + } + + static short[] shortArrayProvider() { + return new short[] { (short) 47, Short.MIN_VALUE }; + } + + // --- Array of objects ------------------------------------------------ + + static Object[] objectArrayProvider() { + return new Object[] { 42, "bar" }; + } + + static String[] stringArrayProvider() { + return new String[] { "foo", "bar" }; + } + + static Object[][] twoDimensionalObjectArrayProvider() { + return new Object[][] { { "foo", 42 }, { "bar", 23 } }; + } + + } + + // This test case mimics @TestInstance(Lifecycle.PER_CLASS) + static class NonStaticTestCase { + + Stream nonStaticStringStreamProvider() { + return Stream.of("foo", "bar"); + } + } + + static class ExternalFactoryMethods { + + static Object factoryWithInvalidReturnType() { + return -1; + } + + static Stream stringsProvider() { + return Stream.of("string1", "string2"); + } + + static class Nested { + + static Stream stringsProvider() { + return Stream.of("nested string1", "nested string2"); + } + } + } + + static class StringResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == String.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return "!"; + } + } + + static class StringArrayResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == String[].class; + } + + @Override + public String[] resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return new String[] { ":", ")" }; + } + } + + static class IntArrayResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == int[].class; + } + + @Override + public int[] resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return new int[] { 4, 2 }; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java new file mode 100644 index 00000000..629709cb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java @@ -0,0 +1,210 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.annotation.Annotation; + +/** + * @since 5.6 + */ +abstract class MockCsvAnnotationBuilder> { + + static CsvSource csvSource(String... lines) { + return csvSource().lines(lines).build(); + } + + static MockCsvSourceBuilder csvSource() { + return new MockCsvSourceBuilder(); + } + + static MockCsvFileSourceBuilder csvFileSource() { + return new MockCsvFileSourceBuilder(); + } + + // ------------------------------------------------------------------------- + + private boolean useHeadersInDisplayName = false; + private char quoteCharacter = '\0'; + protected char delimiter = '\0'; + protected String delimiterString = ""; + protected String emptyValue = ""; + protected String[] nullValues = new String[0]; + protected int maxCharsPerColumn = 4096; + protected boolean ignoreLeadingAndTrailingWhitespace = true; + + private MockCsvAnnotationBuilder() { + } + + protected abstract B getSelf(); + + B useHeadersInDisplayName(boolean useHeadersInDisplayName) { + this.useHeadersInDisplayName = useHeadersInDisplayName; + return getSelf(); + } + + B quoteCharacter(char quoteCharacter) { + this.quoteCharacter = quoteCharacter; + return getSelf(); + } + + B delimiter(char delimiter) { + this.delimiter = delimiter; + return getSelf(); + } + + B delimiterString(String delimiterString) { + this.delimiterString = delimiterString; + return getSelf(); + } + + B emptyValue(String emptyValue) { + this.emptyValue = emptyValue; + return getSelf(); + } + + B nullValues(String... nullValues) { + this.nullValues = nullValues; + return getSelf(); + } + + B maxCharsPerColumn(int maxCharsPerColumn) { + this.maxCharsPerColumn = maxCharsPerColumn; + return getSelf(); + } + + B ignoreLeadingAndTrailingWhitespace(boolean ignoreLeadingAndTrailingWhitespace) { + this.ignoreLeadingAndTrailingWhitespace = ignoreLeadingAndTrailingWhitespace; + return getSelf(); + } + + abstract A build(); + + // ------------------------------------------------------------------------- + + static class MockCsvSourceBuilder extends MockCsvAnnotationBuilder { + + private String[] lines = new String[0]; + private String textBlock = ""; + + private MockCsvSourceBuilder() { + super.quoteCharacter = '\''; + } + + @Override + protected MockCsvSourceBuilder getSelf() { + return this; + } + + MockCsvSourceBuilder lines(String... lines) { + this.lines = lines; + return this; + } + + MockCsvSourceBuilder textBlock(String textBlock) { + this.textBlock = textBlock; + return this; + } + + @Override + CsvSource build() { + var annotation = mock(CsvSource.class); + + // Common + when(annotation.useHeadersInDisplayName()).thenReturn(super.useHeadersInDisplayName); + when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter); + when(annotation.delimiter()).thenReturn(super.delimiter); + when(annotation.delimiterString()).thenReturn(super.delimiterString); + when(annotation.emptyValue()).thenReturn(super.emptyValue); + when(annotation.nullValues()).thenReturn(super.nullValues); + when(annotation.maxCharsPerColumn()).thenReturn(super.maxCharsPerColumn); + when(annotation.ignoreLeadingAndTrailingWhitespace()).thenReturn(super.ignoreLeadingAndTrailingWhitespace); + + // @CsvSource + when(annotation.value()).thenReturn(this.lines); + when(annotation.textBlock()).thenReturn(this.textBlock); + + return annotation; + } + + } + + static class MockCsvFileSourceBuilder extends MockCsvAnnotationBuilder { + + private String[] resources = {}; + private String[] files = {}; + private String encoding = "UTF-8"; + private String lineSeparator = "\n"; + private int numLinesToSkip = 0; + + private MockCsvFileSourceBuilder() { + super.quoteCharacter = '"'; + } + + @Override + protected MockCsvFileSourceBuilder getSelf() { + return this; + } + + MockCsvFileSourceBuilder resources(String... resources) { + this.resources = resources; + return this; + } + + MockCsvFileSourceBuilder files(String... files) { + this.files = files; + return this; + } + + MockCsvFileSourceBuilder encoding(String encoding) { + this.encoding = encoding; + return this; + } + + MockCsvFileSourceBuilder lineSeparator(String lineSeparator) { + this.lineSeparator = lineSeparator; + return this; + } + + MockCsvFileSourceBuilder numLinesToSkip(int numLinesToSkip) { + this.numLinesToSkip = numLinesToSkip; + return this; + } + + @Override + CsvFileSource build() { + var annotation = mock(CsvFileSource.class); + + // Common + when(annotation.useHeadersInDisplayName()).thenReturn(super.useHeadersInDisplayName); + when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter); + when(annotation.delimiter()).thenReturn(super.delimiter); + when(annotation.delimiterString()).thenReturn(super.delimiterString); + when(annotation.emptyValue()).thenReturn(super.emptyValue); + when(annotation.nullValues()).thenReturn(super.nullValues); + when(annotation.maxCharsPerColumn()).thenReturn(super.maxCharsPerColumn); + when(annotation.ignoreLeadingAndTrailingWhitespace()).thenReturn(super.ignoreLeadingAndTrailingWhitespace); + + // @CsvFileSource + when(annotation.resources()).thenReturn(this.resources); + when(annotation.files()).thenReturn(this.files); + when(annotation.encoding()).thenReturn(this.encoding); + when(annotation.lineSeparator()).thenReturn(this.lineSeparator); + when(annotation.numLinesToSkip()).thenReturn(this.numLinesToSkip); + + return annotation; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java new file mode 100644 index 00000000..b9416ba4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 5.0 + */ +class ValueArgumentsProviderTests { + + @Test + void multipleInputsAreNotAllowed() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(new short[1], new byte[0], new int[1], new long[0], new float[0], new double[0], + new char[0], new boolean[0], new String[0], new Class[0])); + + assertThat(exception).hasMessageContaining( + "Exactly one type of input must be provided in the @ValueSource annotation, but there were 2"); + } + + @Test + void onlyEmptyInputsAreNotAllowed() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], + new char[0], new boolean[0], new String[0], new Class[0])); + + assertThat(exception).hasMessageContaining( + "Exactly one type of input must be provided in the @ValueSource annotation, but there were 0"); + } + + /** + * @since 5.1 + */ + @Test + void providesShorts() { + var arguments = provideArguments(new short[] { 23, 42 }, new byte[0], new int[0], new long[0], new float[0], + new double[0], new char[0], new boolean[0], new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array((short) 23), array((short) 42)); + } + + /** + * @since 5.1 + */ + @Test + void providesBytes() { + var arguments = provideArguments(new short[0], new byte[] { 23, 42 }, new int[0], new long[0], new float[0], + new double[0], new char[0], new boolean[0], new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array((byte) 23), array((byte) 42)); + } + + @Test + void providesInts() { + var arguments = provideArguments(new short[0], new byte[0], new int[] { 23, 42 }, new long[0], new float[0], + new double[0], new char[0], new boolean[0], new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array(23), array(42)); + } + + @Test + void providesLongs() { + var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[] { 23, 42 }, new float[0], + new double[0], new char[0], new boolean[0], new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array(23L), array(42L)); + } + + /** + * @since 5.1 + */ + @Test + void providesFloats() { + var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], + new float[] { 23.32F, 42.24F }, new double[0], new char[0], new boolean[0], new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array(23.32F), array(42.24F)); + } + + @Test + void providesDoubles() { + var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], + new double[] { 23.32, 42.24 }, new char[0], new boolean[0], new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array(23.32), array(42.24)); + } + + /** + * @since 5.1 + */ + @Test + void providesChars() { + var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], + new double[0], new char[] { 'a', 'b', 'c' }, new boolean[0], new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array('a'), array('b'), array('c')); + } + + /** + * @since 5.5 + */ + @Test + void providesBooleans() { + var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], + new double[0], new char[0], new boolean[] { true, false }, new String[0], new Class[0]); + + assertThat(arguments).containsExactly(array(true), array(false)); + } + + @Test + void providesStrings() { + var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], + new double[0], new char[0], new boolean[0], new String[] { "foo", "bar" }, new Class[0]); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); + } + + /** + * @since 5.1 + */ + @Test + void providesClasses() { + var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], + new double[0], new char[0], new boolean[0], new String[0], new Class[] { Integer.class, getClass() }); + + assertThat(arguments).containsExactly(array(Integer.class), array(getClass())); + } + + private static Stream provideArguments(short[] shorts, byte[] bytes, int[] ints, long[] longs, + float[] floats, double[] doubles, char[] chars, boolean[] booleans, String[] strings, Class[] classes) { + + var annotation = mock(ValueSource.class); + when(annotation.shorts()).thenReturn(shorts); + when(annotation.bytes()).thenReturn(bytes); + when(annotation.ints()).thenReturn(ints); + when(annotation.longs()).thenReturn(longs); + when(annotation.floats()).thenReturn(floats); + when(annotation.doubles()).thenReturn(doubles); + when(annotation.chars()).thenReturn(chars); + when(annotation.booleans()).thenReturn(booleans); + when(annotation.strings()).thenReturn(strings); + when(annotation.classes()).thenReturn(classes); + + var provider = new ValueArgumentsProvider(); + provider.accept(annotation); + return provider.provideArguments(null).map(Arguments::get); + } + + private static Object[] array(Object... objects) { + return objects; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt new file mode 100644 index 00000000..829f98ae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.TestInfo +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class ParameterizedTestNameFormatterIntegrationTests { + + @ValueSource(strings = ["foo", "bar"]) + @ParameterizedTest + fun defaultDisplayName(param: String, info: TestInfo) { + if (param.equals("foo")) { + assertEquals("[1] foo", info.displayName) + } else { + assertEquals("[2] bar", info.displayName) + } + } + + @ValueSource(strings = ["foo", "bar"]) + @ParameterizedTest(name = "{0}") + fun `1st argument`(param: String, info: TestInfo) { + if (param.equals("foo")) { + assertEquals("foo", info.displayName) + } else { + assertEquals("bar", info.displayName) + } + } + + @ValueSource(strings = ["foo", "bar"]) + @ParameterizedTest(name = "{displayName}") + fun `it's an {enigma} '{0}'`(@Suppress("UNUSED_PARAMETER") param: String, info: TestInfo) { + assertEquals("it's an {enigma} '{0}'(String, TestInfo)", info.displayName) + } + + @ValueSource(strings = ["foo", "bar"]) + @ParameterizedTest(name = "{displayName} - {0}") + fun `displayName and 1st 'argument'`(param: String, info: TestInfo) { + if (param.equals("foo")) { + assertEquals("displayName and 1st 'argument'(String, TestInfo) - foo", info.displayName) + } else { + assertEquals("displayName and 1st 'argument'(String, TestInfo) - bar", info.displayName) + } + } + + @ValueSource(strings = ["foo", "bar"]) + @ParameterizedTest(name = "{0} - {displayName}") + fun `1st 'argument' and displayName`(param: String, info: TestInfo) { + if (param.equals("foo")) { + assertEquals("foo - 1st 'argument' and displayName(String, TestInfo)", info.displayName) + } else { + assertEquals("bar - 1st 'argument' and displayName(String, TestInfo)", info.displayName) + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt new file mode 100644 index 00000000..17d9bb15 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.params.aggregator + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +/** + * Unit tests for using [ArgumentsAccessor] from Kotlin. + */ +class ArgumentsAccessorKotlinTests { + + @Test + fun `get() with reified type and index`() { + assertEquals(1, DefaultArgumentsAccessor(1).get(0)) + assertEquals('A', DefaultArgumentsAccessor('A').get(0)) + } + + @Test + fun `get() with reified type and index for incompatible type`() { + val exception = assertThrows { + DefaultArgumentsAccessor(Integer.valueOf(1)).get(0) + } + + assertThat(exception).hasMessage( + "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [java.lang.Character]." + ) + } + + @Test + fun `get() with index`() { + assertEquals(1, DefaultArgumentsAccessor(1).get(0)) + assertEquals('A', DefaultArgumentsAccessor('A').get(0)) + } + + @Test + fun `get() with index and class reference`() { + assertEquals(1, DefaultArgumentsAccessor(1).get(0, Integer::class.java)) + assertEquals('A', DefaultArgumentsAccessor('A').get(0, Character::class.java)) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt new file mode 100644 index 00000000..96b717ad --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +@file:Suppress("unused") + +package org.junit.jupiter.params.aggregator + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.TestInfo +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource + +// https://github.com/junit-team/junit5/issues/1836 +object DisplayNameTests { + @JvmStatic + fun data() = arrayOf( + arrayOf("A", 1), + arrayOf("B", 2), + arrayOf("C", 3), + arrayOf("", 4), // empty is okay + arrayOf(null, 5) // null was the problem + ) + + @ParameterizedTest + @MethodSource("data") + fun test(char: String?, number: Int, info: TestInfo) { + assertEquals("[$number] $char, $number", info.displayName) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv new file mode 100644 index 00000000..c62f19df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv @@ -0,0 +1,2 @@ +# more than 512 columns +x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x, \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv new file mode 100644 index 00000000..3931019b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv @@ -0,0 +1 @@ +"41,6,8469,0,22,6336,9177,null,3,15,31,5734,6509,8770,9379,2,4,10,20,28,39,1490,5775,6410,6986,8493,8823,9298,9975,1,null,null,5,8,12,16,21,26,29,34,40,506,4491,5737,6034,6384,6434,6853,8178,8492,8503,8779,9106,9283,9339,9774,9982,null,null,null,null,7,9,11,13,null,17,null,null,23,27,null,30,32,35,null,null,176,837,3317,5715,5735,5743,5944,6310,6338,6398,6425,6436,6802,6917,7473,8397,8472,null,8494,8711,8776,8782,9009,9153,9210,9289,9336,9371,9400,9953,9980,9993,null,null,null,null,null,null,null,14,null,18,null,25,null,null,null,null,null,33,null,36,53,316,739,1409,3011,3924,4845,5730,null,5736,5741,5754,5807,5968,6056,6329,6337,6345,6387,6400,6420,6428,6435,6467,6575,6814,6911,6970,7111,7761,8283,8461,8470,8473,null,8496,8674,8722,8773,8778,8780,8803,8924,9078,9111,9172,9185,9270,9285,9296,9307,9337,9369,9376,9391,9633,9933,9967,9979,9981,9989,9994,null,null,null,19,24,null,null,null,null,37,46,140,199,505,669,814,1281,1430,1976,3096,3627,3934,4646,5455,5719,5733,null,null,5740,5742,5752,5758,5790,5828,5948,5974,6051,6278,6326,6331,null,null,6339,6365,6385,6388,6399,6405,6417,6424,6426,6433,null,null,6441,6492,6572,6739,6808,6845,6867,6915,6931,6985,7086,7463,7758,7797,8179,8349,8421,8467,null,8471,null,8476,8495,8497,8576,8708,8719,8761,8771,8774,8777,null,null,8781,8793,8806,8882,8937,9054,9084,9107,9134,9166,9175,9181,9187,9255,9282,9284,9287,9292,9297,9305,9319,null,9338,9342,9370,9375,9378,9380,9396,9546,9713,9798,9946,9956,9973,9978,null,null,null,9986,9991,null,9999,null,null,null,null,null,38,45,49,111,175,196,203,352,null,631,673,799,832,1056,1366,1415,1480,1819,2471,3016,3223,3532,3770,3931,4018,4592,4835,5233,5477,5718,5728,5732,null,5739,null,null,null,5745,5753,5756,5766,5785,5803,5823,5875,5946,5950,5971,5994,6043,6052,6124,6298,6317,6328,6330,6332,null,6341,6348,6369,null,6386,null,6396,null,null,6404,6408,6415,6419,6422,null,null,6427,6430,null,6440,6458,6471,6503,6520,6574,6721,6750,6807,6810,6843,6849,6863,6893,6912,6916,6919,6947,6983,null,6993,7088,7124,7466,7703,7759,7787,7962,null,8195,8331,8362,8411,8453,8462,8468,null,null,8474,8482,null,null,null,8500,8573,8619,8682,8710,8712,8721,8755,8767,null,8772,null,8775,null,null,null,null,8787,8795,8805,8819,8862,8886,8929,8978,9014,9076,9080,9087,null,9110,9112,9139,9163,9170,9173,9176,9178,9183,9186,9188,9240,9267,9278,null,null,null,9286,9288,9290,9293,null,null,9303,9306,9312,9326,null,null,9340,9366,null,null,9374,null,9377,null,null,9383,9393,9397,9493,9601,9652,9726,9789,9905,9942,9947,9954,9959,9969,9974,9976,null,9984,9988,9990,9992,9997,null,null,null,44,null,48,52,92,125,156,null,183,197,202,235,325,366,629,656,670,712,746,802,816,835,1026,1212,1293,1403,1414,1420,1471,1483,1604,1903,2432,2870,3014,3062,3219,3311,3336,3565,3730,3918,3927,3933,3975,4330,4546,4627,4709,4840,5158,5399,5456,5697,5716,null,5727,5729,5731,null,5738,null,5744,5746,null,null,5755,5757,5762,5774,5778,5786,5795,5805,5822,5824,5848,5895,5945,5947,5949,5961,5969,5973,5976,6014,6042,6049,null,6053,6117,6139,6290,6308,6314,6320,6327,null,null,null,null,6335,6340,6344,6346,6354,6366,6383,null,null,6390,6397,6401,null,6406,6409,6413,6416,6418,null,6421,6423,null,null,6429,6431,6438,null,6443,6462,6470,6472,6497,6506,6512,6528,6573,null,6648,6733,6748,6767,6805,null,6809,6813,6828,6844,6847,6852,6854,6864,6875,6910,null,6914,null,null,6918,6922,6944,6968,6976,6984,6991,7047,7087,7091,7112,7260,7464,7467,7656,7740,null,7760,7774,7795,7876,8110,8187,8202,8315,8341,8359,8391,8410,8414,8448,8455,null,8463,null,null,null,8475,8478,8483,8498,8502,8531,8575,8600,8624,8681,8688,8709,null,null,8716,8720,null,8735,8760,8766,8768,null,null,null,null,8784,8791,8794,8797,8804,null,8815,8820,8825,8867,8884,8887,8927,8936,8974,8986,9013,9041,9067,9077,9079,9081,9086,9093,9109,null,null,9119,9138,9144,9159,9164,9167,9171,null,9174,null,null,null,9179,9182,9184,null,null,null,9189,9223,9247,9258,9269,9275,9279,null,null,null,null,null,9291,null,9295,9301,9304,null,null,9310,9315,9324,9330,null,9341,9358,9367,9372,null,null,null,9382,9386,9392,9395,null,9398,9488,9509,9547,9616,9643,9702,94,122" diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv new file mode 100644 index 00000000..469411a2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv @@ -0,0 +1 @@ +"41,6,8469,0,22,6336,9177,null,3,15,31,5734,6509,8770,9379,2,4,10,20,28,39,1490,5775,6410,6986,8493,8823,9298,9975,1,null,null,5,8,12,16,21,26,29,34,40,506,4491,5737,6034,6384,6434,6853,8178,8492,8503,8779,9106,9283,9339,9774,9982,null,null,null,null,7,9,11,13,null,17,null,null,23,27,null,30,32,35,null,null,176,837,3317,5715,5735,5743,5944,6310,6338,6398,6425,6436,6802,6917,7473,8397,8472,null,8494,8711,8776,8782,9009,9153,9210,9289,9336,9371,9400,9953,9980,9993,null,null,null,null,null,null,null,14,null,18,null,25,null,null,null,null,null,33,null,36,53,316,739,1409,3011,3924,4845,5730,null,5736,5741,5754,5807,5968,6056,6329,6337,6345,6387,6400,6420,6428,6435,6467,6575,6814,6911,6970,7111,7761,8283,8461,8470,8473,null,8496,8674,8722,8773,8778,8780,8803,8924,9078,9111,9172,9185,9270,9285,9296,9307,9337,9369,9376,9391,9633,9933,9967,9979,9981,9989,9994,null,null,null,19,24,null,null,null,null,37,46,140,199,505,669,814,1281,1430,1976,3096,3627,3934,4646,5455,5719,5733,null,null,5740,5742,5752,5758,5790,5828,5948,5974,6051,6278,6326,6331,null,null,6339,6365,6385,6388,6399,6405,6417,6424,6426,6433,null,null,6441,6492,6572,6739,6808,6845,6867,6915,6931,6985,7086,7463,7758,7797,8179,8349,8421,8467,null,8471,null,8476,8495,8497,8576,8708,8719,8761,8771,8774,8777,null,null,8781,8793,8806,8882,8937,9054,9084,9107,9134,9166,9175,9181,9187,9255,9282,9284,9287,9292,9297,9305,9319,null,9338,9342,9370,9375,9378,9380,9396,9546,9713,9798,9946,9956,9973,9978,null,null,null,9986,9991,null,9999,null,null,null,null,null,38,45,49,111,175,196,203,352,null,631,673,799,832,1056,1366,1415,1480,1819,2471,3016,3223,3532,3770,3931,4018,4592,4835,5233,5477,5718,5728,5732,null,5739,null,null,null,5745,5753,5756,5766,5785,5803,5823,5875,5946,5950,5971,5994,6043,6052,6124,6298,6317,6328,6330,6332,null,6341,6348,6369,null,6386,null,6396,null,null,6404,6408,6415,6419,6422,null,null,6427,6430,null,6440,6458,6471,6503,6520,6574,6721,6750,6807,6810,6843,6849,6863,6893,6912,6916,6919,6947,6983,null,6993,7088,7124,7466,7703,7759,7787,7962,null,8195,8331,8362,8411,8453,8462,8468,null,null,8474,8482,null,null,null,8500,8573,8619,8682,8710,8712,8721,8755,8767,null,8772,null,8775,null,null,null,null,8787,8795,8805,8819,8862,8886,8929,8978,9014,9076,9080,9087,null,9110,9112,9139,9163,9170,9173,9176,9178,9183,9186,9188,9240,9267,9278,null,null,null,9286,9288,9290,9293,null,null,9303,9306,9312,9326,null,null,9340,9366,null,null,9374,null,9377,null,null,9383,9393,9397,9493,9601,9652,9726,9789,9905,9942,9947,9954,9959,9969,9974,9976,null,9984,9988,9990,9992,9997,null,null,null,44,null,48,52,92,125,156,null,183,197,202,235,325,366,629,656,670,712,746,802,816,835,1026,1212,1293,1403,1414,1420,1471,1483,1604,1903,2432,2870,3014,3062,3219,3311,3336,3565,3730,3918,3927,3933,3975,4330,4546,4627,4709,4840,5158,5399,5456,5697,5716,null,5727,5729,5731,null,5738,null,5744,5746,null,null,5755,5757,5762,5774,5778,5786,5795,5805,5822,5824,5848,5895,5945,5947,5949,5961,5969,5973,5976,6014,6042,6049,null,6053,6117,6139,6290,6308,6314,6320,6327,null,null,null,null,6335,6340,6344,6346,6354,6366,6383,null,null,6390,6397,6401,null,6406,6409,6413,6416,6418,null,6421,6423,null,null,6429,6431,6438,null,6443,6462,6470,6472,6497,6506,6512,6528,6573,null,6648,6733,6748,6767,6805,null,6809,6813,6828,6844,6847,6852,6854,6864,6875,6910,null,6914,null,null,6918,6922,6944,6968,6976,6984,6991,7047,7087,7091,7112,7260,7464,7467,7656,7740,null,7760,7774,7795,7876,8110,8187,8202,8315,8341,8359,8391,8410,8414,8448,8455,null,8463,null,null,null,8475,8478,8483,8498,8502,8531,8575,8600,8624,8681,8688,8709,null,null,8716,8720,null,8735,8760,8766,8768,null,null,null,null,8784,8791,8794,8797,8804,null,8815,8820,8825,8867,8884,8887,8927,8936,8974,8986,9013,9041,9067,9077,9079,9081,9086,9093,9109,null,null,9119,9138,9144,9159,9164,9167,9171,null,9174,null,null,null,9179,9182,9184,null,null,null,9189,9223,9247,9258,9269,9275,9279,null,null,null,null,null,9291,null,9295,9301,9304,null,null,9310,9315,9324,9330,null,9341,9358,9367,9372,null,null,null,9382,9386,9392,9395,null,9398,9488,9509,9547,9616,9643,9702,94,1223" diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv new file mode 100644 index 00000000..260888ce --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv @@ -0,0 +1,2 @@ + ab , cd +ef ,gh \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..1c1cbb8d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv new file mode 100644 index 00000000..9a9fd17d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv @@ -0,0 +1,13 @@ +#--------------------------------- + FRUIT | RANK +#--------------------------------- + apple | 1 +#--------------------------------- + banana | 2 +#--------------------------------- + cherry | 3.14159265358979323846 +#--------------------------------- + | 0 +#--------------------------------- + NIL | 0 +#--------------------------------- diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv new file mode 100644 index 00000000..b58e4e1e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv @@ -0,0 +1,4 @@ +foo,1 +bar,2 +baz,3 +qux,4 diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv new file mode 100644 index 00000000..f118269a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv @@ -0,0 +1,5 @@ +foo +bar +baz +qux +"" diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts new file mode 100644 index 00000000..181698f0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts @@ -0,0 +1,16 @@ +plugins { + `java-library-conventions` +} + +description = "JUnit Jupiter (Aggregator)" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitJupiterApi) + api(projects.junitJupiterParams) + + runtimeOnly(projects.junitJupiterEngine) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java new file mode 100644 index 00000000..986039ad --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Aggregates all JUnit Jupiter modules. + * + * @since 5.4 + */ +module org.junit.jupiter { + requires transitive org.junit.jupiter.api; + requires transitive org.junit.jupiter.engine; + requires transitive org.junit.jupiter.params; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts new file mode 100644 index 00000000..393d655e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts @@ -0,0 +1,40 @@ +import org.junit.gradle.java.ExecJarAction + +plugins { + `java-library-conventions` + `java-multi-release-sources` + `java-repackage-jars` + `java-test-fixtures` +} + +description = "JUnit Platform Commons" + +dependencies { + api(platform(projects.junitBom)) + + compileOnlyApi(libs.apiguardian) +} + +tasks.jar { + val release9ClassesDir = sourceSets.mainRelease9.get().output.classesDirs.singleFile + inputs.dir(release9ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) + doLast(objects.newInstance(ExecJarAction::class).apply { + javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) + args.addAll( + "--update", + "--file", archiveFile.get().asFile.absolutePath, + "--release", "9", + "-C", release9ClassesDir.absolutePath, "." + ) + }) +} + +tasks.codeCoverageClassesJar { + exclude("org/junit/platform/commons/util/ModuleUtils.class") +} + +eclipse { + classpath { + sourceSets -= project.sourceSets.mainRelease9.get() + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java new file mode 100644 index 00000000..20c6d943 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * Base class for all {@link RuntimeException RuntimeExceptions} thrown + * by JUnit. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.5") +public class JUnitException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public JUnitException(String message) { + super(message); + } + + public JUnitException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java new file mode 100644 index 00000000..e858ad26 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * Thrown if a precondition is violated. + * + * @since 1.5 + */ +@API(status = STABLE, since = "1.5") +@SuppressWarnings({ "deprecation", "exports" }) +public class PreconditionViolationException extends org.junit.platform.commons.util.PreconditionViolationException { + + private static final long serialVersionUID = 1L; + + public PreconditionViolationException(String message) { + super(message); + } + + public PreconditionViolationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java new file mode 100644 index 00000000..476289b2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.annotation; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.apiguardian.api.API; + +/** + * {@code @Testable} is used to signal to IDEs and tooling vendors that the + * annotated or meta-annotated element is testable. + * + *

In this context, the term "testable" means that the annotated element + * (typically a method, field, or class) can be executed by a {@code TestEngine} + * as a test or test container on the JUnit Platform. + * + *

Motivation for {@code @Testable}

+ *

Some clients of the JUnit Platform, notably IDEs such as IntelliJ IDEA, + * operate only on sources for test discovery. Thus, they cannot use the full + * runtime discovery mechanism of the JUnit Platform since it relies on compiled + * classes. {@code @Testable} therefore serves as an alternative mechanism for + * IDEs to discover potential tests by analyzing the source code only. + * + *

Common Use Cases

+ *

{@code @Testable} will typically be used as a meta-annotation in order to + * create a custom composed annotation that inherits the semantics + * of {@code @Testable}. For example, the {@code @Test} and {@code @TestFactory} + * annotations in JUnit Jupiter are meta-annotated with {@code @Testable}. + *

For test programming models that do not rely on annotations, test classes, + * test methods, or test fields may be directly annotated with {@code @Testable}. + * Alternatively, if concrete test classes extend from a base class, the base class + * can be annotated with {@code @Testable}. Note that {@code @Testable} is an + * {@link Inherited @Inherited} annotation. + * + *

Requirements for IDEs and Tooling Vendors

+ *
    + *
  • If a top-level class, static nested class, or inner class is not + * annotated or meta-annotated with {@code @Testable} but contains a method or field + * that is annotated or meta-annotated with {@code @Testable}, the class must + * be considered to be a testable class.
  • + *
  • If annotation hierarchies containing {@code @Testable} are present on + * classes, methods, or fields in compiled byte code (e.g., in JARs in the user's + * classpath), IDEs and tooling vendors must also take such annotation + * hierarchies into consideration when performing annotation processing for + * source code.
  • + *
+ * + *

Restrictions for TestEngine Implementations

+ *

A {@code TestEngine} must not in any way perform + * discovery based on the presence of {@code @Testable}. In terms of + * discovery, the presence of {@code @Testable} should only be meaningful to + * clients such as IDEs and tooling vendors. A {@code TestEngine} implementation + * is therefore required to discover tests based on information specific to + * that test engine (e.g., annotations specific to that test engine). + * + *

Supported Target Elements

+ *

Since JUnit Platform version 1.7, {@code @Testable} may target any + * declaration {@linkplain java.lang.annotation.ElementType element type}. This + * includes the aforementioned method, field, and class elements. + * + * @since 1.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@API(status = STABLE, since = "1.0") +public @interface Testable { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java new file mode 100644 index 00000000..f5f0eaf5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java @@ -0,0 +1,5 @@ +/** + * Common annotations for the JUnit Platform. + */ + +package org.junit.platform.commons.annotation; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java new file mode 100644 index 00000000..9d7c5997 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java @@ -0,0 +1,374 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.function; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * A container object which may either contain a nullable value in case of + * success or an exception in case of failure. + * + *

Instances of this class should be returned by methods instead of + * {@link Optional} when callers might want to report the exception via logging + * or by wrapping it in another exception at a later point in time, e.g. via + * {@link #getOrThrow(Function)}. + * + *

Moreover, it makes it particularly convenient to attach follow-up actions + * should the {@code Try} have been successful (cf. {@link #andThen} and + * {@link #andThenTry}) or fallback actions should it not have been (cf. + * {@link #orElse} and {@link #orElseTry}). + * + * @since 1.4 + */ +@API(status = MAINTAINED, since = "1.4") +public abstract class Try { + + /** + * Call the supplied {@link Callable} and return a successful {@code Try} + * that contains the returned value or, in case an exception was thrown, a + * failed {@code Try} that contains the exception. + * + * @param action the action to try; must not be {@code null} + * @return a succeeded or failed {@code Try} depending on the outcome of the + * supplied action; never {@code null} + * @see #success(Object) + * @see #failure(Exception) + */ + public static Try call(Callable action) { + checkNotNull(action, "action"); + return Try.of(() -> success(action.call())); + } + + /** + * Convert the supplied value into a succeeded {@code Try}. + * + * @param value the value to wrap; potentially {@code null} + * @return a succeeded {@code Try} that contains the supplied value; never + * {@code null} + */ + public static Try success(V value) { + return new Success<>(value); + } + + /** + * Convert the supplied exception into a failed {@code Try}. + * + * @param cause the exception to wrap; must not be {@code null} + * @return a failed {@code Try} that contains the supplied value; never + * {@code null} + */ + public static Try failure(Exception cause) { + return new Failure<>(checkNotNull(cause, "cause")); + } + + // Cannot use Preconditions due to package cycle + private static T checkNotNull(T input, String title) { + if (input == null) { + // Cannot use PreconditionViolationException due to package cycle + throw new JUnitException(title + " must not be null"); + } + return input; + } + + private static Try of(Callable> action) { + try { + return action.call(); + } + catch (Exception e) { + return failure(e); + } + } + + private Try() { + /* no-op */ + } + + /** + * If this {@code Try} is a success, apply the supplied transformer to its + * value and return a new successful or failed {@code Try} depending on the + * transformer's outcome; if this {@code Try} is a failure, do nothing. + * + * @param transformer the transformer to try; must not be {@code null} + * @return a succeeded or failed {@code Try}; never {@code null} + */ + public abstract Try andThenTry(Transformer transformer); + + /** + * If this {@code Try} is a success, apply the supplied function to its + * value and return the resulting {@code Try}; if this {@code Try} is a + * failure, do nothing. + * + * @param function the function to apply; must not be {@code null} + * @return a succeeded or failed {@code Try}; never {@code null} + */ + public abstract Try andThen(Function> function); + + /** + * If this {@code Try} is a failure, call the supplied action and return a + * new successful or failed {@code Try} depending on the action's outcome; + * if this {@code Try} is a success, do nothing. + * + * @param action the action to try; must not be {@code null} + * @return a succeeded or failed {@code Try}; never {@code null} + */ + public abstract Try orElseTry(Callable action); + + /** + * If this {@code Try} is a failure, call the supplied supplier and return + * the resulting {@code Try}; if this {@code Try} is a success, do nothing. + * + * @param supplier the supplier to call; must not be {@code null} + * @return a succeeded or failed {@code Try}; never {@code null} + */ + public abstract Try orElse(Supplier> supplier); + + /** + * If this {@code Try} is a success, get the contained value; if this + * {@code Try} is a failure, throw the contained exception. + * + * @return the contained value, if available; potentially {@code null} + * @throws Exception if this {@code Try} is a failure + */ + public abstract V get() throws Exception; + + /** + * If this {@code Try} is a success, get the contained value; if this + * {@code Try} is a failure, call the supplied {@link Function} with the + * contained exception and throw the resulting {@link Exception}. + * + * @param exceptionTransformer the transformer to be called with the + * contained exception, if available; must not be {@code null} + * @return the contained value, if available + * @throws E if this {@code Try} is a failure + */ + public abstract V getOrThrow(Function exceptionTransformer) throws E; + + /** + * If this {@code Try} is a success, call the supplied {@link Consumer} with + * the contained value; otherwise, do nothing. + * + * @param valueConsumer the consumer to be called with the contained value, + * if available; must not be {@code null} + * @return the same {@code Try} for method chaining + */ + public abstract Try ifSuccess(Consumer valueConsumer); + + /** + * If this {@code Try} is a failure, call the supplied {@link Consumer} with + * the contained exception; otherwise, do nothing. + * + * @param causeConsumer the consumer to be called with the contained + * exception, if available; must not be {@code null} + * @return the same {@code Try} for method chaining + */ + public abstract Try ifFailure(Consumer causeConsumer); + + /** + * If this {@code Try} is a failure, return an empty {@link Optional}; if + * this {@code Try} is a success, wrap the contained value using + * {@link Optional#ofNullable(Object)}. + * + * @return an {@link Optional}; never {@code null} but potentially + * empty + */ + public abstract Optional toOptional(); + + /** + * A transformer for values of type {@code S} to type {@code T}. + * + *

The {@code Transformer} interface is similar to {@link Function}, + * except that a {@code Transformer} may throw an exception. + */ + @FunctionalInterface + public interface Transformer { + + /** + * Apply this transformer to the supplied value. + * + * @throws Exception if the transformation fails + */ + T apply(S value) throws Exception; + + } + + private static class Success extends Try { + + private final V value; + + Success(V value) { + this.value = value; + } + + @Override + public Try andThenTry(Transformer transformer) { + checkNotNull(transformer, "transformer"); + return Try.call(() -> transformer.apply(this.value)); + } + + @Override + public Try andThen(Function> function) { + checkNotNull(function, "function"); + return Try.of(() -> function.apply(this.value)); + } + + @Override + public Try orElseTry(Callable action) { + // don't call action because this Try is a success + return this; + } + + @Override + public Try orElse(Supplier> supplier) { + // don't call supplier because this Try is a success + return this; + } + + @Override + public V get() { + return this.value; + } + + @Override + public V getOrThrow(Function exceptionTransformer) { + // don't call exceptionTransformer because this Try is a success + return this.value; + } + + @Override + public Try ifSuccess(Consumer valueConsumer) { + checkNotNull(valueConsumer, "valueConsumer"); + valueConsumer.accept(this.value); + return this; + } + + @Override + public Try ifFailure(Consumer causeConsumer) { + // don't call causeConsumer because this Try was a success + return this; + } + + @Override + public Optional toOptional() { + return Optional.ofNullable(this.value); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null || this.getClass() != that.getClass()) { + return false; + } + return Objects.equals(this.value, ((Success) that).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } + + private static class Failure extends Try { + + private final Exception cause; + + Failure(Exception cause) { + this.cause = cause; + } + + @Override + public Try andThenTry(Transformer transformer) { + // don't call transformer because this Try is a failure + return uncheckedCast(); + } + + @Override + public Try andThen(Function> function) { + // don't call function because this Try is a failure + return uncheckedCast(); + } + + @SuppressWarnings("unchecked") + private Try uncheckedCast() { + return (Try) this; + } + + @Override + public Try orElseTry(Callable action) { + checkNotNull(action, "action"); + return Try.call(action); + } + + @Override + public Try orElse(Supplier> supplier) { + checkNotNull(supplier, "supplier"); + return Try.of(supplier::get); + } + + @Override + public V get() throws Exception { + throw this.cause; + } + + @Override + public V getOrThrow(Function exceptionTransformer) throws E { + checkNotNull(exceptionTransformer, "exceptionTransformer"); + throw exceptionTransformer.apply(this.cause); + } + + @Override + public Try ifSuccess(Consumer valueConsumer) { + // don't call valueConsumer because this Try is a failure + return this; + } + + @Override + public Try ifFailure(Consumer causeConsumer) { + checkNotNull(causeConsumer, "causeConsumer"); + causeConsumer.accept(this.cause); + return this; + } + + @Override + public Optional toOptional() { + return Optional.empty(); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null || this.getClass() != that.getClass()) { + return false; + } + return Objects.equals(this.cause, ((Failure) that).cause); + } + + @Override + public int hashCode() { + return Objects.hash(cause); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java new file mode 100644 index 00000000..5990d37b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java @@ -0,0 +1,5 @@ +/** + * Maintained functional interfaces and support classes. + */ + +package org.junit.platform.commons.function; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java new file mode 100644 index 00000000..3b0c357f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java @@ -0,0 +1,149 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.logging; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * {@code LogRecordListener} is only intended for testing purposes within + * JUnit's own test suite. + * + * @since 1.1 + */ +@API(status = INTERNAL, since = "1.1") +public class LogRecordListener { + + // capture log records by thread to support parallel test execution + private final ThreadLocal> logRecords = ThreadLocal.withInitial(ArrayList::new); + + /** + * Inform the listener of a {@link LogRecord} that was submitted to JUL for + * processing. + */ + public void logRecordSubmitted(LogRecord logRecord) { + this.logRecords.get().add(logRecord); + } + + /** + * Get a stream of {@link LogRecord log records} that have been + * {@linkplain #logRecordSubmitted submitted} to this listener by the + * current thread. + * + *

As stated in the Javadoc for {@code LogRecord}, a submitted + * {@code LogRecord} should not be updated by the client application. Thus, + * the {@code LogRecords} in the returned stream should only be inspected for + * testing purposes and not modified in any way. + * + * @see #stream(Level) + * @see #stream(Class) + * @see #stream(Class, Level) + */ + public Stream stream() { + return this.logRecords.get().stream(); + } + + /** + * Get a stream of {@link LogRecord log records} that have been + * {@linkplain #logRecordSubmitted submitted} to this listener by the current + * thread at the given log level. + * + *

As stated in the Javadoc for {@code LogRecord}, a submitted + * {@code LogRecord} should not be updated by the client application. Thus, + * the {@code LogRecords} in the returned stream should only be inspected for + * testing purposes and not modified in any way. + * + * @param level the log level for which to get the log records; never {@code null} + * @since 1.4 + * @see #stream() + * @see #stream(Class) + * @see #stream(Class, Level) + */ + public Stream stream(Level level) { + // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here + // since that would introduce a package cycle. + if (level == null) { + throw new JUnitException("Level must not be null"); + } + + return stream().filter(logRecord -> logRecord.getLevel() == level); + } + + /** + * Get a stream of {@link LogRecord log records} that have been + * {@linkplain #logRecordSubmitted submitted} to this listener by the current + * thread for the logger name equal to the name of the given class. + * + *

As stated in the Javadoc for {@code LogRecord}, a submitted + * {@code LogRecord} should not be updated by the client application. Thus, + * the {@code LogRecords} in the returned stream should only be inspected for + * testing purposes and not modified in any way. + * + * @param clazz the class for which to get the log records; never {@code null} + * @see #stream() + * @see #stream(Level) + * @see #stream(Class, Level) + */ + public Stream stream(Class clazz) { + // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here + // since that would introduce a package cycle. + if (clazz == null) { + throw new JUnitException("Class must not be null"); + } + + return stream().filter(logRecord -> logRecord.getLoggerName().equals(clazz.getName())); + } + + /** + * Get a stream of {@link LogRecord log records} that have been + * {@linkplain #logRecordSubmitted submitted} to this listener by the current + * thread for the logger name equal to the name of the given class at the given + * log level. + * + *

As stated in the Javadoc for {@code LogRecord}, a submitted + * {@code LogRecord} should not be updated by the client application. Thus, + * the {@code LogRecords} in the returned stream should only be inspected for + * testing purposes and not modified in any way. + * + * @param clazz the class for which to get the log records; never {@code null} + * @param level the log level for which to get the log records; never {@code null} + * @see #stream() + * @see #stream(Level) + * @see #stream(Class) + */ + public Stream stream(Class clazz, Level level) { + // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here + // since that would introduce a package cycle. + if (level == null) { + throw new JUnitException("Level must not be null"); + } + + return stream(clazz).filter(logRecord -> logRecord.getLevel() == level); + } + + /** + * Clear all existing {@link LogRecord log records} that have been + * {@linkplain #logRecordSubmitted submitted} to this listener by the + * current thread. + */ + public void clear() { + this.logRecords.get().clear(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java new file mode 100644 index 00000000..5283e18d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.logging; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.function.Supplier; + +import org.apiguardian.api.API; + +/** + * The {@code Logger} API serves as a simple logging facade for + * {@code java.util.logging} (JUL). + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public interface Logger { + + /** + * Log the message from the provided {@code messageSupplier} at error level. + * + *

Maps to {@link java.util.logging.Level#SEVERE} in JUL. + */ + void error(Supplier messageSupplier); + + /** + * Log the provided {@code Throwable} and message from the provided + * {@code messageSupplier} at error level. + * + *

Maps to {@link java.util.logging.Level#SEVERE} in JUL. + */ + void error(Throwable throwable, Supplier messageSupplier); + + /** + * Log the message from the provided {@code messageSupplier} at warning level. + * + *

Maps to {@link java.util.logging.Level#WARNING} in JUL. + */ + void warn(Supplier messageSupplier); + + /** + * Log the provided {@code Throwable} and message from the provided + * {@code messageSupplier} at warning level. + * + *

Maps to {@link java.util.logging.Level#WARNING} in JUL. + */ + void warn(Throwable throwable, Supplier messageSupplier); + + /** + * Log the message from the provided {@code messageSupplier} at info level. + * + *

Maps to {@link java.util.logging.Level#INFO} in JUL. + */ + void info(Supplier messageSupplier); + + /** + * Log the provided {@code Throwable} and message from the provided + * {@code messageSupplier} at info level. + * + *

Maps to {@link java.util.logging.Level#INFO} in JUL. + */ + void info(Throwable throwable, Supplier messageSupplier); + + /** + * Log the message from the provided {@code messageSupplier} at config level. + * + *

Maps to {@link java.util.logging.Level#CONFIG} in JUL. + */ + void config(Supplier messageSupplier); + + /** + * Log the provided {@code Throwable} and message from the provided + * {@code messageSupplier} at config level. + * + *

Maps to {@link java.util.logging.Level#CONFIG} in JUL. + */ + void config(Throwable throwable, Supplier messageSupplier); + + /** + * Log the message from the provided {@code messageSupplier} at debug level. + * + *

Maps to {@link java.util.logging.Level#FINE} in JUL. + */ + void debug(Supplier messageSupplier); + + /** + * Log the provided {@code Throwable} and message from the provided + * {@code messageSupplier} at debug level. + * + *

Maps to {@link java.util.logging.Level#FINE} in JUL. + */ + void debug(Throwable throwable, Supplier messageSupplier); + + /** + * Log the message from the provided {@code messageSupplier} at trace level. + * + *

Maps to {@link java.util.logging.Level#FINER} in JUL. + */ + void trace(Supplier messageSupplier); + + /** + * Log the provided {@code Throwable} and message from the provided + * {@code messageSupplier} at trace level. + * + *

Maps to {@link java.util.logging.Level#FINER} in JUL. + */ + void trace(Throwable throwable, Supplier messageSupplier); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java new file mode 100644 index 00000000..5e06b26f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java @@ -0,0 +1,187 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.logging; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * Factory for the {@link Logger} facade for JUL. + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class LoggerFactory { + + private LoggerFactory() { + /* no-op */ + } + + private static final Set listeners = ConcurrentHashMap.newKeySet(); + + /** + * Get a {@link Logger} for the specified class. + * + * @param clazz the class for which to get the logger; never {@code null} + * @return the logger + */ + public static Logger getLogger(Class clazz) { + // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here + // since that would introduce a package cycle. + if (clazz == null) { + throw new JUnitException("Class must not be null"); + } + + return new DelegatingLogger(clazz.getName()); + } + + /** + * Add the supplied {@link LogRecordListener} to the set of registered + * listeners. + */ + public static void addListener(LogRecordListener listener) { + listeners.add(listener); + } + + /** + * Remove the supplied {@link LogRecordListener} from the set of registered + * listeners. + */ + public static void removeListener(LogRecordListener listener) { + listeners.remove(listener); + } + + private static final class DelegatingLogger implements Logger { + + private static final String FQCN = DelegatingLogger.class.getName(); + + private final String name; + + private final java.util.logging.Logger julLogger; + + DelegatingLogger(String name) { + this.name = name; + this.julLogger = java.util.logging.Logger.getLogger(this.name); + } + + @Override + public void error(Supplier messageSupplier) { + log(Level.SEVERE, null, messageSupplier); + } + + @Override + public void error(Throwable throwable, Supplier messageSupplier) { + log(Level.SEVERE, throwable, messageSupplier); + } + + @Override + public void warn(Supplier messageSupplier) { + log(Level.WARNING, null, messageSupplier); + } + + @Override + public void warn(Throwable throwable, Supplier messageSupplier) { + log(Level.WARNING, throwable, messageSupplier); + } + + @Override + public void info(Supplier messageSupplier) { + log(Level.INFO, null, messageSupplier); + } + + @Override + public void info(Throwable throwable, Supplier messageSupplier) { + log(Level.INFO, throwable, messageSupplier); + } + + @Override + public void config(Supplier messageSupplier) { + log(Level.CONFIG, null, messageSupplier); + } + + @Override + public void config(Throwable throwable, Supplier messageSupplier) { + log(Level.CONFIG, throwable, messageSupplier); + } + + @Override + public void debug(Supplier messageSupplier) { + log(Level.FINE, null, messageSupplier); + } + + @Override + public void debug(Throwable throwable, Supplier messageSupplier) { + log(Level.FINE, throwable, messageSupplier); + } + + @Override + public void trace(Supplier messageSupplier) { + log(Level.FINER, null, messageSupplier); + } + + @Override + public void trace(Throwable throwable, Supplier messageSupplier) { + log(Level.FINER, throwable, messageSupplier); + } + + private void log(Level level, Throwable throwable, Supplier messageSupplier) { + boolean loggable = this.julLogger.isLoggable(level); + if (loggable || !listeners.isEmpty()) { + LogRecord logRecord = createLogRecord(level, throwable, nullSafeGet(messageSupplier)); + if (loggable) { + this.julLogger.log(logRecord); + } + listeners.forEach(listener -> listener.logRecordSubmitted(logRecord)); + } + } + + private LogRecord createLogRecord(Level level, Throwable throwable, String message) { + String sourceClassName = null; + String sourceMethodName = null; + boolean found = false; + for (StackTraceElement element : new Throwable().getStackTrace()) { + String className = element.getClassName(); + if (FQCN.equals(className)) { + found = true; + } + else if (found) { + sourceClassName = className; + sourceMethodName = element.getMethodName(); + break; + } + } + + LogRecord logRecord = new LogRecord(level, message); + logRecord.setLoggerName(this.name); + logRecord.setThrown(throwable); + logRecord.setSourceClassName(sourceClassName); + logRecord.setSourceMethodName(sourceMethodName); + logRecord.setResourceBundleName(this.julLogger.getResourceBundleName()); + logRecord.setResourceBundle(this.julLogger.getResourceBundle()); + + return logRecord; + } + + private static String nullSafeGet(Supplier messageSupplier) { + return (messageSupplier != null ? messageSupplier.get() : null); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java new file mode 100644 index 00000000..75260c34 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java @@ -0,0 +1,11 @@ +/** + * Internal logging package. + * + *

DISCLAIMER

+ * + *

These classes are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + */ + +package org.junit.platform.commons.logging; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java new file mode 100644 index 00000000..66e1cf2a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java @@ -0,0 +1,11 @@ +/** + * Common APIs and support utilities for the JUnit Platform. + * + *

DISCLAIMER

+ * + *

Any API annotated with {@code @API(status = INTERNAL)} is intended solely + * for usage within the JUnit framework itself. Any usage of internal + * APIs by external parties is not supported! + */ + +package org.junit.platform.commons; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java new file mode 100644 index 00000000..037c9ee5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java @@ -0,0 +1,488 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code AnnotationSupport} provides static utility methods for common tasks + * regarding annotations — for example, checking if a class, method, or + * field is annotated with a particular annotation; finding annotations on a + * given class, method, or field; finding fields or methods annotated with + * a particular annotation, etc. + * + *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension + * authors are encouraged to use these supported methods in order to align with + * the behavior of the JUnit Platform. + * + * @since 1.0 + * @see ClassSupport + * @see ModifierSupport + * @see ReflectionSupport + */ +@API(status = MAINTAINED, since = "1.0") +public final class AnnotationSupport { + + private AnnotationSupport() { + /* no-op */ + } + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the supplied optional + * {@code element}. + * + * @param element an {@link Optional} containing the element on which to + * search for the annotation; may be {@code null} or empty + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @since 1.3 + * @see #isAnnotated(AnnotatedElement, Class) + * @see #findAnnotation(Optional, Class) + */ + @API(status = MAINTAINED, since = "1.3") + public static boolean isAnnotated(Optional element, + Class annotationType) { + + return AnnotationUtils.isAnnotated(element, annotationType); + } + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the supplied + * {@code element}. + * + * @param element the element on which to search for the annotation; may be + * {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @see #isAnnotated(Optional, Class) + * @see #findAnnotation(AnnotatedElement, Class) + */ + public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { + return AnnotationUtils.isAnnotated(element, annotationType); + } + + /** + * Find the first annotation of {@code annotationType} that is either + * present or meta-present on the supplied optional + * {@code element}. + * + * @param the annotation type + * @param element an {@link Optional} containing the element on which to + * search for the annotation; may be {@code null} or empty + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @since 1.1 + * @see #findAnnotation(AnnotatedElement, Class) + */ + @API(status = MAINTAINED, since = "1.1") + public static Optional findAnnotation(Optional element, + Class annotationType) { + + return AnnotationUtils.findAnnotation(element, annotationType); + } + + /** + * Find the first annotation of {@code annotationType} that is either + * directly present, meta-present, or indirectly + * present on the supplied {@code element}. + * + *

If the element is a class and the annotation is neither directly + * present nor meta-present on the class, this method will + * additionally search on interfaces implemented by the class before + * finding an annotation that is indirectly present on the class. + * + * @param the annotation type + * @param element the element on which to search for the annotation; may be + * {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + */ + public static Optional findAnnotation(AnnotatedElement element, Class annotationType) { + return AnnotationUtils.findAnnotation(element, annotationType); + } + + /** + * Find the first annotation of the specified type that is either + * directly present, meta-present, or indirectly + * present on the supplied class. + * + *

If the annotation is neither directly present nor meta-present + * on the class, this method will additionally search on interfaces implemented + * by the class before searching for an annotation that is indirectly present + * on the class (i.e., within the class inheritance hierarchy). + * + *

If the annotation still has not been found, this method will optionally + * search recursively through the enclosing class hierarchy if + * {@link SearchOption#INCLUDE_ENCLOSING_CLASSES} is specified. + * + *

If {@link SearchOption#DEFAULT} is specified, this method has the same + * semantics as {@link #findAnnotation(AnnotatedElement, Class)}. + * + * @param the annotation type + * @param clazz the class on which to search for the annotation; may be {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param searchOption the {@code SearchOption} to use; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @since 1.8 + * @see SearchOption + * @see #findAnnotation(AnnotatedElement, Class) + */ + @API(status = EXPERIMENTAL, since = "1.8") + public static Optional findAnnotation(Class clazz, Class annotationType, + SearchOption searchOption) { + + Preconditions.notNull(searchOption, "SearchOption must not be null"); + + return AnnotationUtils.findAnnotation(clazz, annotationType, + searchOption == SearchOption.INCLUDE_ENCLOSING_CLASSES); + } + + /** + * Find all repeatable {@linkplain Annotation annotations} of the + * supplied {@code annotationType} that are either present, + * indirectly present, or meta-present on the supplied + * optional {@code element}. + * + *

See {@link #findRepeatableAnnotations(AnnotatedElement, Class)} for + * details of the algorithm used. + * + * @param the annotation type + * @param element an {@link Optional} containing the element on which to + * search for the annotation; may be {@code null} or empty + * @param annotationType the repeatable annotation type to search for; never {@code null} + * @return an immutable list of all such annotations found; never {@code null} + * @since 1.5 + * @see java.lang.annotation.Repeatable + * @see java.lang.annotation.Inherited + * @see #findRepeatableAnnotations(AnnotatedElement, Class) + */ + @API(status = MAINTAINED, since = "1.5") + public static List findRepeatableAnnotations(Optional element, + Class annotationType) { + + return AnnotationUtils.findRepeatableAnnotations(element, annotationType); + } + + /** + * Find all repeatable {@linkplain Annotation annotations} of the + * supplied {@code annotationType} that are either present, + * indirectly present, or meta-present on the supplied + * {@link AnnotatedElement}. + * + *

This method extends the functionality of + * {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)} + * with additional support for meta-annotations. + * + *

In addition, if the element is a class and the repeatable annotation + * is {@link java.lang.annotation.Inherited @Inherited}, this method will + * search on superclasses first in order to support top-down semantics. + * The result is that this algorithm finds repeatable annotations that + * would be shadowed and therefore not visible according to Java's + * standard semantics for inherited, repeatable annotations, but most + * developers will naturally assume that all repeatable annotations in JUnit + * are discovered regardless of whether they are declared stand-alone, in a + * container, or as a meta-annotation (e.g., multiple declarations of + * {@code @ExtendWith} within a test class hierarchy). + * + *

If the element is a class and the repeatable annotation is not + * discovered within the class hierarchy, this method will additionally + * search on interfaces implemented by each class in the hierarchy. + * + *

If the supplied {@code element} is {@code null}, this method returns + * an empty list. + * + *

As of JUnit Platform 1.5, the search algorithm will also find + * repeatable annotations used as meta-annotations on other repeatable + * annotations. + * + * @param the annotation type + * @param element the element to search on; may be {@code null} + * @param annotationType the repeatable annotation type to search for; never {@code null} + * @return an immutable list of all such annotations found; never {@code null} + * @see java.lang.annotation.Repeatable + * @see java.lang.annotation.Inherited + */ + public static List findRepeatableAnnotations(AnnotatedElement element, + Class annotationType) { + + return AnnotationUtils.findRepeatableAnnotations(element, annotationType); + } + + /** + * Find all {@code public} {@linkplain Field fields} of the supplied class + * or interface that are declared to be of the specified {@code fieldType} + * and are annotated or meta-annotated with the specified + * {@code annotationType}. + * + *

Consult the Javadoc for {@link Class#getFields()} for details on + * inheritance and ordering. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param fieldType the declared type of fields to find; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @return the list of all such fields found; neither {@code null} nor mutable + * @see Class#getFields() + * @see Field#getType() + * @see #findAnnotatedFields(Class, Class) + * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#tryToReadFieldValue(Field, Object) + */ + public static List findPublicAnnotatedFields(Class clazz, Class fieldType, + Class annotationType) { + + return AnnotationUtils.findPublicAnnotatedFields(clazz, fieldType, annotationType); + } + + /** + * Find all {@linkplain Field fields} of the supplied class or interface + * that are annotated or meta-annotated with the specified + * {@code annotationType}, using top-down search semantics within the type + * hierarchy. + * + *

Fields declared in the same class or interface will be ordered using + * an algorithm that is deterministic but intentionally nonobvious. + * + *

The results will not contain fields that are hidden or + * {@linkplain Field#isSynthetic() synthetic}. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @return the list of all such fields found; neither {@code null} nor mutable + * @since 1.4 + * @see Class#getDeclaredFields() + * @see #findPublicAnnotatedFields(Class, Class, Class) + * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#tryToReadFieldValue(Field, Object) + */ + @API(status = MAINTAINED, since = "1.4") + public static List findAnnotatedFields(Class clazz, Class annotationType) { + return AnnotationUtils.findAnnotatedFields(clazz, annotationType, field -> true); + } + + /** + * Find all {@linkplain Field fields} of the supplied class or interface + * that are annotated or meta-annotated with the specified + * {@code annotationType} and match the specified {@code predicate}, using + * the supplied hierarchy traversal mode. + * + *

Fields declared in the same class or interface will be ordered using + * an algorithm that is deterministic but intentionally nonobvious. + * + *

The results will not contain fields that are hidden or + * {@linkplain Field#isSynthetic() synthetic}. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param predicate the field filter; never {@code null} + * @param traversalMode the hierarchy traversal mode; never {@code null} + * @return the list of all such fields found; neither {@code null} nor mutable + * @since 1.4 + * @see Class#getDeclaredFields() + * @see #findAnnotatedFields(Class, Class) + * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#tryToReadFieldValue(Field, Object) + */ + @API(status = MAINTAINED, since = "1.4") + public static List findAnnotatedFields(Class clazz, Class annotationType, + Predicate predicate, HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + return AnnotationUtils.findAnnotatedFields(clazz, annotationType, predicate, + ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); + } + + /** + * Find the values of all non-static {@linkplain Field fields} of the supplied + * {@code instance} that are annotated or meta-annotated with the + * specified {@code annotationType}, using top-down search semantics within + * the type hierarchy. + * + *

Values from fields declared in the same class or interface will be + * ordered using an algorithm that is deterministic but intentionally + * nonobvious. + * + *

The results will not contain values from fields that are hidden + * or {@linkplain Field#isSynthetic() synthetic}. + * + * @param instance the instance in which to find the fields; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @return the list of all such field values found; neither {@code null} nor mutable + * @since 1.4 + * @see #findAnnotatedFields(Class, Class) + * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#tryToReadFieldValue(Field, Object) + */ + @API(status = MAINTAINED, since = "1.4") + public static List findAnnotatedFieldValues(Object instance, Class annotationType) { + Preconditions.notNull(instance, "instance must not be null"); + + List fields = findAnnotatedFields(instance.getClass(), annotationType, ModifierSupport::isNotStatic, + HierarchyTraversalMode.TOP_DOWN); + + return ReflectionUtils.readFieldValues(fields, instance); + } + + /** + * Find the values of all static {@linkplain Field fields} of the supplied + * class or interface that are annotated or meta-annotated with the + * specified {@code annotationType}, using top-down search semantics within + * the type hierarchy. + * + *

Values from fields declared in the same class or interface will be + * ordered using an algorithm that is deterministic but intentionally + * nonobvious. + * + *

The results will not contain values from fields that are hidden + * or {@linkplain Field#isSynthetic() synthetic}. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @return the list of all such field values found; neither {@code null} nor mutable + * @since 1.4 + * @see #findAnnotatedFields(Class, Class) + * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#tryToReadFieldValue(Field, Object) + */ + @API(status = MAINTAINED, since = "1.4") + public static List findAnnotatedFieldValues(Class clazz, Class annotationType) { + + List fields = findAnnotatedFields(clazz, annotationType, ModifierSupport::isStatic, + HierarchyTraversalMode.TOP_DOWN); + + return ReflectionUtils.readFieldValues(fields, null); + } + + /** + * Find the values of all non-static {@linkplain Field fields} of the supplied + * {@code instance} that are declared to be of the specified {@code fieldType} + * and are annotated or meta-annotated with the specified + * {@code annotationType}, using top-down search semantics within the type + * hierarchy. + * + *

Values from fields declared in the same class or interface will be + * ordered using an algorithm that is deterministic but intentionally + * nonobvious. + * + *

The results will not contain values from fields that are hidden + * or {@linkplain Field#isSynthetic() synthetic}. + * + * @param instance the instance in which to find the fields; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param fieldType the declared type of fields to find; never {@code null} + * @return the list of all such field values found; neither {@code null} nor mutable + * @since 1.4 + * @see Field#getType() + * @see #findAnnotatedFields(Class, Class) + * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#tryToReadFieldValue(Field, Object) + */ + @SuppressWarnings("unchecked") + @API(status = MAINTAINED, since = "1.4") + public static List findAnnotatedFieldValues(Object instance, Class annotationType, + Class fieldType) { + + Preconditions.notNull(instance, "instance must not be null"); + Preconditions.notNull(fieldType, "fieldType must not be null"); + + Predicate predicate = // + field -> ModifierSupport.isNotStatic(field) && fieldType.isAssignableFrom(field.getType()); + + List fields = findAnnotatedFields(instance.getClass(), annotationType, predicate, + HierarchyTraversalMode.TOP_DOWN); + + return (List) ReflectionUtils.readFieldValues(fields, instance); + } + + /** + * Find the values of all static {@linkplain Field fields} of the supplied + * class or interface that are declared to be of the specified + * {@code fieldType} and are annotated or meta-annotated with the + * specified {@code annotationType}, using top-down search semantics within + * the type hierarchy. + * + *

Values from fields declared in the same class or interface will be + * ordered using an algorithm that is deterministic but intentionally + * nonobvious. + * + *

The results will not contain values from fields that are hidden + * or {@linkplain Field#isSynthetic() synthetic}. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param fieldType the declared type of fields to find; never {@code null} + * @return the list of all such field values found; neither {@code null} nor mutable + * @since 1.4 + * @see Field#getType() + * @see #findAnnotatedFields(Class, Class) + * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) + * @see ReflectionSupport#tryToReadFieldValue(Field, Object) + */ + @SuppressWarnings("unchecked") + @API(status = MAINTAINED, since = "1.4") + public static List findAnnotatedFieldValues(Class clazz, Class annotationType, + Class fieldType) { + + Preconditions.notNull(fieldType, "fieldType must not be null"); + + Predicate predicate = // + field -> ModifierSupport.isStatic(field) && fieldType.isAssignableFrom(field.getType()); + + List fields = findAnnotatedFields(clazz, annotationType, predicate, HierarchyTraversalMode.TOP_DOWN); + + return (List) ReflectionUtils.readFieldValues(fields, null); + } + + /** + * Find all {@linkplain Method methods} of the supplied class or interface + * that are annotated or meta-annotated with the specified + * {@code annotationType}. + * + * @param clazz the class or interface in which to find the methods; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param traversalMode the hierarchy traversal mode; never {@code null} + * @return the list of all such methods found; neither {@code null} nor mutable + */ + public static List findAnnotatedMethods(Class clazz, Class annotationType, + HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + return AnnotationUtils.findAnnotatedMethods(clazz, annotationType, + ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java new file mode 100644 index 00000000..5039fd87 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ClassUtils; + +/** + * {@code ClassSupport} provides static utility methods for common tasks + * regarding {@linkplain Class classes} — for example, generating a + * comma-separated list of fully qualified class names for a set of supplied + * classes. + * + *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension + * authors are encouraged to use these supported methods in order to align with + * the behavior of the JUnit Platform. + * + * @since 1.1 + * @see AnnotationSupport + * @see ModifierSupport + * @see ReflectionSupport + */ +@API(status = MAINTAINED, since = "1.1") +public final class ClassSupport { + + private ClassSupport() { + /* no-op */ + } + + /** + * Generate a comma-separated list of fully qualified class names for the + * supplied classes. + * + * @param classes the classes whose names should be included in the + * generated string + * @return a comma-separated list of fully qualified class names, or an empty + * string if the supplied class array is {@code null} or empty + * @see #nullSafeToString(Function, Class...) + */ + public static String nullSafeToString(Class... classes) { + return ClassUtils.nullSafeToString(classes); + } + + /** + * Generate a comma-separated list of mapped values for the supplied classes. + * + *

The values are generated by the supplied {@code mapper} + * (e.g., {@code Class::getName}, {@code Class::getSimpleName}, etc.), unless + * a class reference is {@code null} in which case it will be mapped to + * {@code "null"}. + * + * @param mapper the mapper to use; never {@code null} + * @param classes the classes to map + * @return a comma-separated list of mapped values, or an empty string if + * the supplied class array is {@code null} or empty + * @see #nullSafeToString(Class...) + */ + public static String nullSafeToString(Function, ? extends String> mapper, Class... classes) { + return ClassUtils.nullSafeToString(mapper, classes); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java new file mode 100644 index 00000000..aefcb1e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import org.apiguardian.api.API; + +/** + * Modes in which a hierarchy can be traversed — for example, when + * searching for methods or fields within a class hierarchy. + * + * @since 1.0 + * @see #TOP_DOWN + * @see #BOTTOM_UP + */ +@API(status = MAINTAINED, since = "1.0") +public enum HierarchyTraversalMode { + + /** + * Traverse the hierarchy using top-down semantics. + */ + TOP_DOWN, + + /** + * Traverse the hierarchy using bottom-up semantics. + */ + BOTTOM_UP; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java new file mode 100644 index 00000000..7dbb7489 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java @@ -0,0 +1,241 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.reflect.Member; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code ModifierSupport} provides static utility methods for working with + * class and member {@linkplain java.lang.reflect.Modifier modifiers} — + * for example, to determine if a class or member is declared as + * {@code public}, {@code private}, {@code abstract}, {@code static}, etc. + * + *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension + * authors are encouraged to use these supported methods in order to align with + * the behavior of the JUnit Platform. + * + * @since 1.4 + * @see java.lang.reflect.Modifier + * @see AnnotationSupport + * @see ClassSupport + * @see ReflectionSupport + */ +@API(status = MAINTAINED, since = "1.4") +public final class ModifierSupport { + + private ModifierSupport() { + /* no-op */ + } + + /** + * Determine if the supplied class is {@code public}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is {@code public} + * @see java.lang.reflect.Modifier#isPublic(int) + */ + public static boolean isPublic(Class clazz) { + return ReflectionUtils.isPublic(clazz); + } + + /** + * Determine if the supplied member is {@code public}. + * + * @param member the member to check; never {@code null} + * @return {@code true} if the member is {@code public} + * @see java.lang.reflect.Modifier#isPublic(int) + */ + public static boolean isPublic(Member member) { + return ReflectionUtils.isPublic(member); + } + + /** + * Determine if the supplied class is {@code private}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is {@code private} + * @see java.lang.reflect.Modifier#isPrivate(int) + */ + public static boolean isPrivate(Class clazz) { + return ReflectionUtils.isPrivate(clazz); + } + + /** + * Determine if the supplied member is {@code private}. + * + * @param member the member to check; never {@code null} + * @return {@code true} if the member is {@code private} + * @see java.lang.reflect.Modifier#isPrivate(int) + */ + public static boolean isPrivate(Member member) { + return ReflectionUtils.isPrivate(member); + } + + /** + * Determine if the supplied class is not {@code private}. + * + *

In other words this method will return {@code true} for classes + * declared as {@code public}, {@code protected}, or + * package private and {@code false} for classes declared as + * {@code private}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is not {@code private} + * @see java.lang.reflect.Modifier#isPublic(int) + * @see java.lang.reflect.Modifier#isProtected(int) + * @see java.lang.reflect.Modifier#isPrivate(int) + */ + public static boolean isNotPrivate(Class clazz) { + return ReflectionUtils.isNotPrivate(clazz); + } + + /** + * Determine if the supplied member is not {@code private}. + * + *

In other words this method will return {@code true} for members + * declared as {@code public}, {@code protected}, or + * package private and {@code false} for members declared as + * {@code private}. + * + * @param member the member to check; never {@code null} + * @return {@code true} if the member is not {@code private} + * @see java.lang.reflect.Modifier#isPublic(int) + * @see java.lang.reflect.Modifier#isProtected(int) + * @see java.lang.reflect.Modifier#isPrivate(int) + */ + public static boolean isNotPrivate(Member member) { + return ReflectionUtils.isNotPrivate(member); + } + + /** + * Determine if the supplied class is {@code abstract}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is {@code abstract} + * @see java.lang.reflect.Modifier#isAbstract(int) + */ + public static boolean isAbstract(Class clazz) { + return ReflectionUtils.isAbstract(clazz); + } + + /** + * Determine if the supplied member is {@code abstract}. + * + * @param member the class to check; never {@code null} + * @return {@code true} if the member is {@code abstract} + * @see java.lang.reflect.Modifier#isAbstract(int) + */ + public static boolean isAbstract(Member member) { + return ReflectionUtils.isAbstract(member); + } + + /** + * Determine if the supplied class is {@code static}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is {@code static} + * @see java.lang.reflect.Modifier#isStatic(int) + */ + public static boolean isStatic(Class clazz) { + return ReflectionUtils.isStatic(clazz); + } + + /** + * Determine if the supplied member is {@code static}. + * + * @param member the member to check; never {@code null} + * @return {@code true} if the member is {@code static} + * @see java.lang.reflect.Modifier#isStatic(int) + */ + public static boolean isStatic(Member member) { + return ReflectionUtils.isStatic(member); + } + + /** + * Determine if the supplied class is not {@code static}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is not {@code static} + * @see java.lang.reflect.Modifier#isStatic(int) + */ + public static boolean isNotStatic(Class clazz) { + return ReflectionUtils.isNotStatic(clazz); + } + + /** + * Determine if the supplied member is not {@code static}. + * + * @param member the member to check; never {@code null} + * @return {@code true} if the member is not {@code static} + * @see java.lang.reflect.Modifier#isStatic(int) + */ + public static boolean isNotStatic(Member member) { + return ReflectionUtils.isNotStatic(member); + } + + /** + * Determine if the supplied class is {@code final}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is {@code final} + * @since 1.5 + * @see java.lang.reflect.Modifier#isFinal(int) + */ + @API(status = MAINTAINED, since = "1.5") + public static boolean isFinal(Class clazz) { + return ReflectionUtils.isFinal(clazz); + } + + /** + * Determine if the supplied class is not {@code final}. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is not {@code final} + * @since 1.5 + * @see java.lang.reflect.Modifier#isFinal(int) + */ + @API(status = MAINTAINED, since = "1.5") + public static boolean isNotFinal(Class clazz) { + return ReflectionUtils.isNotFinal(clazz); + } + + /** + * Determine if the supplied member is {@code final}. + * + * @param member the member to check; never {@code null} + * @return {@code true} if the member is {@code final} + * @since 1.5 + * @see java.lang.reflect.Modifier#isFinal(int) + */ + @API(status = MAINTAINED, since = "1.5") + public static boolean isFinal(Member member) { + return ReflectionUtils.isFinal(member); + } + + /** + * Determine if the supplied member is not {@code final}. + * + * @param member the member to check; never {@code null} + * @return {@code true} if the member is not {@code final} + * @since 1.5 + * @see java.lang.reflect.Modifier#isFinal(int) + */ + @API(status = MAINTAINED, since = "1.5") + public static boolean isNotFinal(Member member) { + return ReflectionUtils.isNotFinal(member); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java new file mode 100644 index 00000000..819d5809 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -0,0 +1,336 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code ReflectionSupport} provides static utility methods for common + * reflection tasks — for example, scanning for classes in the class-path + * or module-path, loading classes, finding methods, invoking methods, etc. + * + *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension + * authors are encouraged to use these supported methods in order to align with + * the behavior of the JUnit Platform. + * + * @since 1.0 + * @see AnnotationSupport + * @see ClassSupport + * @see ModifierSupport + */ +@API(status = MAINTAINED, since = "1.0") +public final class ReflectionSupport { + + private ReflectionSupport() { + /* no-op */ + } + + /** + * Load a class by its primitive name or fully qualified name, + * using the default {@link ClassLoader}. + * + *

Class names for arrays may be specified using either the JVM's internal + * String representation (e.g., {@code [[I} for {@code int[][]}, + * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or + * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, + * etc.). + * + * @param name the name of the class to load; never {@code null} or blank + * @return an {@code Optional} containing the loaded class; never {@code null} + * but potentially empty if no such class could be loaded + * @deprecated Please use {@link #tryToLoadClass(String)} instead. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + @SuppressWarnings("deprecation") + public static Optional> loadClass(String name) { + return ReflectionUtils.loadClass(name); + } + + /** + * Try to load a class by its primitive name or fully qualified name, + * using the default {@link ClassLoader}. + * + *

Class names for arrays may be specified using either the JVM's internal + * String representation (e.g., {@code [[I} for {@code int[][]}, + * {@code [Lava.lang.String;} for {@code java.lang.String[]}, etc.) or + * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, + * etc.). + * + * @param name the name of the class to load; never {@code null} or blank + * @return a successful {@code Try} containing the loaded class or a failed + * {@code Try} containing the exception if no such class could be loaded; + * never {@code null} + * @since 1.4 + */ + @API(status = MAINTAINED, since = "1.4") + public static Try> tryToLoadClass(String name) { + return ReflectionUtils.tryToLoadClass(name); + } + + /** + * Find all {@linkplain Class classes} in the supplied classpath {@code root} + * that match the specified {@code classFilter} and {@code classNameFilter} + * predicates. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param root the URI for the classpath root in which to scan; never + * {@code null} + * @param classFilter the class type filter; never {@code null} + * @param classNameFilter the class name filter; never {@code null} + * @return an immutable list of all such classes found; never {@code null} + * but potentially empty + * @see #findAllClassesInPackage(String, Predicate, Predicate) + * @see #findAllClassesInModule(String, Predicate, Predicate) + */ + public static List> findAllClassesInClasspathRoot(URI root, Predicate> classFilter, + Predicate classNameFilter) { + + return ReflectionUtils.findAllClassesInClasspathRoot(root, classFilter, classNameFilter); + } + + /** + * Find all {@linkplain Class classes} in the supplied {@code basePackageName} + * that match the specified {@code classFilter} and {@code classNameFilter} + * predicates. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning within the supplied base package. + * + * @param basePackageName the name of the base package in which to start + * scanning; must not be {@code null} and must be valid in terms of Java + * syntax + * @param classFilter the class type filter; never {@code null} + * @param classNameFilter the class name filter; never {@code null} + * @return an immutable list of all such classes found; never {@code null} + * but potentially empty + * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) + * @see #findAllClassesInModule(String, Predicate, Predicate) + */ + public static List> findAllClassesInPackage(String basePackageName, Predicate> classFilter, + Predicate classNameFilter) { + + return ReflectionUtils.findAllClassesInPackage(basePackageName, classFilter, classNameFilter); + } + + /** + * Find all {@linkplain Class classes} in the supplied {@code moduleName} + * that match the specified {@code classFilter} and {@code classNameFilter} + * predicates. + * + *

The module-path scanning algorithm searches recursively in all + * packages contained in the module. + * + * @param moduleName the name of the module to scan; never {@code null} or + * empty + * @param classFilter the class type filter; never {@code null} + * @param classNameFilter the class name filter; never {@code null} + * @return an immutable list of all such classes found; never {@code null} + * but potentially empty + * @since 1.1.1 + * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) + * @see #findAllClassesInPackage(String, Predicate, Predicate) + */ + public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, + Predicate classNameFilter) { + + return ReflectionUtils.findAllClassesInModule(moduleName, classFilter, classNameFilter); + } + + /** + * Create a new instance of the specified {@link Class} by invoking + * the constructor whose argument list matches the types of the supplied + * arguments. + * + *

The constructor will be made accessible if necessary, and any checked + * exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked} + * as an unchecked exception. + * + * @param clazz the class to instantiate; never {@code null} + * @param args the arguments to pass to the constructor, none of which may + * be {@code null} + * @return the new instance; never {@code null} + * @see ExceptionUtils#throwAsUncheckedException(Throwable) + */ + public static T newInstance(Class clazz, Object... args) { + return ReflectionUtils.newInstance(clazz, args); + } + + /** + * Invoke the supplied method, making it accessible if necessary and + * {@linkplain ExceptionUtils#throwAsUncheckedException masking} any + * checked exception as an unchecked exception. + * + * @param method the method to invoke; never {@code null} + * @param target the object on which to invoke the method; may be + * {@code null} if the method is {@code static} + * @param args the arguments to pass to the method + * @return the value returned by the method invocation or {@code null} + * if the return type is {@code void} + * @see ExceptionUtils#throwAsUncheckedException(Throwable) + */ + public static Object invokeMethod(Method method, Object target, Object... args) { + return ReflectionUtils.invokeMethod(method, target, args); + } + + /** + * Find all {@linkplain Field fields} of the supplied class or interface + * that match the specified {@code predicate}. + * + *

Fields declared in the same class or interface will be ordered using + * an algorithm that is deterministic but intentionally nonobvious. + * + *

The results will not contain fields that are hidden or + * {@linkplain Field#isSynthetic() synthetic}. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param predicate the field filter; never {@code null} + * @param traversalMode the hierarchy traversal mode; never {@code null} + * @return an immutable list of all such fields found; never {@code null} + * but potentially empty + * @since 1.4 + */ + @API(status = MAINTAINED, since = "1.4") + public static List findFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + return ReflectionUtils.findFields(clazz, predicate, + ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); + } + + /** + * Try to read the value of a potentially inaccessible field. + * + *

If an exception occurs while reading the field, a failed {@link Try} + * is returned that contains the corresponding exception. + * + * @param field the field to read; never {@code null} + * @param instance the instance from which the value is to be read; may + * be {@code null} for a static field + * @since 1.4 + */ + @API(status = MAINTAINED, since = "1.4") + public static Try tryToReadFieldValue(Field field, Object instance) { + return ReflectionUtils.tryToReadFieldValue(field, instance); + } + + /** + * Find the first {@link Method} of the supplied class or interface that + * meets the specified criteria, beginning with the specified class or + * interface and traversing up the type hierarchy until such a method is + * found or the type hierarchy is exhausted. + * + *

The algorithm does not search for methods in {@link java.lang.Object}. + * + * @param clazz the class or interface in which to find the method; never {@code null} + * @param methodName the name of the method to find; never {@code null} or empty + * @param parameterTypeNames the fully qualified names of the types of parameters + * accepted by the method, if any, provided as a comma-separated list + * @return an {@code Optional} containing the method found; never {@code null} + * but potentially empty if no such method could be found + * @see #findMethod(Class, String, Class...) + */ + public static Optional findMethod(Class clazz, String methodName, String parameterTypeNames) { + return ReflectionUtils.findMethod(clazz, methodName, parameterTypeNames); + } + + /** + * Find the first {@link Method} of the supplied class or interface that + * meets the specified criteria, beginning with the specified class or + * interface and traversing up the type hierarchy until such a method is + * found or the type hierarchy is exhausted. + * + *

The algorithm does not search for methods in {@link java.lang.Object}. + * + * @param clazz the class or interface in which to find the method; never {@code null} + * @param methodName the name of the method to find; never {@code null} or empty + * @param parameterTypes the types of parameters accepted by the method, if any; + * never {@code null} + * @return an {@code Optional} containing the method found; never {@code null} + * but potentially empty if no such method could be found + * @see #findMethod(Class, String, String) + */ + public static Optional findMethod(Class clazz, String methodName, Class... parameterTypes) { + return ReflectionUtils.findMethod(clazz, methodName, parameterTypes); + } + + /** + * Find all distinct {@linkplain Method methods} of the supplied class or + * interface that match the specified {@code predicate}. + * + *

The results will not contain instance methods that are overridden + * or {@code static} methods that are hidden. + * + *

If you're are looking for methods annotated with a certain annotation + * type, consider using + * {@link AnnotationSupport#findAnnotatedMethods(Class, Class, HierarchyTraversalMode)}. + * + * @param clazz the class or interface in which to find the methods; never {@code null} + * @param predicate the method filter; never {@code null} + * @param traversalMode the hierarchy traversal mode; never {@code null} + * @return an immutable list of all such methods found; never {@code null} + * but potentially empty + */ + public static List findMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + return ReflectionUtils.findMethods(clazz, predicate, + ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); + } + + /** + * Find all nested classes within the supplied class, or inherited by the + * supplied class, that conform to the supplied predicate. + * + *

This method does not search for nested classes + * recursively. + * + *

As of JUnit Platform 1.6, this method detects cycles in inner + * class hierarchies — from the supplied class up to the outermost + * enclosing class — and throws a {@link JUnitException} if such a cycle + * is detected. Cycles within inner class hierarchies below the + * supplied class are not detected by this method. + * + * @param clazz the class to be searched; never {@code null} + * @param predicate the predicate against which the list of nested classes is + * checked; never {@code null} + * @return an immutable list of all such classes found; never {@code null} + * but potentially empty + * @throws JUnitException if a cycle is detected within an inner class hierarchy + */ + public static List> findNestedClasses(Class clazz, Predicate> predicate) + throws JUnitException { + + return ReflectionUtils.findNestedClasses(clazz, predicate); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java new file mode 100644 index 00000000..005167cc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * Search options for finding an annotation within a class hierarchy. + * + * @since 1.8 + * @see #DEFAULT + * @see #INCLUDE_ENCLOSING_CLASSES + */ +@API(status = EXPERIMENTAL, since = "1.8") +public enum SearchOption { + + /** + * Search the inheritance hierarchy (i.e., the current class, implemented + * interfaces, and superclasses), but do not search on enclosing classes. + * + * @see Class#getSuperclass() + * @see Class#getInterfaces() + */ + DEFAULT, + + /** + * Search the inheritance hierarchy as with the {@link #DEFAULT} search option + * but also search the {@linkplain Class#getEnclosingClass() enclosing class} + * hierarchy for inner classes (i.e., a non-static member classes). + */ + INCLUDE_ENCLOSING_CLASSES; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java new file mode 100644 index 00000000..6e9c4601 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java @@ -0,0 +1,11 @@ +/** + * Maintained common support APIs provided by the JUnit Platform. + * + *

The purpose of this package is to provide {@code TestEngine} and + * {@code Extension} authors convenient access to a subset of internal utility + * methods to assist with their implementation. This prevents re-inventing the + * wheel and ensures that common tasks are handled in third-party engines and + * extensions with the same semantics as within the JUnit Platform itself. + */ + +package org.junit.platform.commons.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java new file mode 100644 index 00000000..9da0cb56 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java @@ -0,0 +1,510 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.Arrays.asList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; +import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; + +/** + * Collection of utilities for working with {@linkplain Annotation annotations}. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + *

Some utilities are published via the maintained {@code AnnotationSupport} + * class. + * + * @since 1.0 + * @see Annotation + * @see AnnotatedElement + * @see org.junit.platform.commons.support.AnnotationSupport + */ +@API(status = INTERNAL, since = "1.0") +public final class AnnotationUtils { + + private AnnotationUtils() { + /* no-op */ + } + + private static final ConcurrentHashMap, Boolean> repeatableAnnotationContainerCache = // + new ConcurrentHashMap<>(16); + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the supplied optional + * {@code element}. + * + * @see #findAnnotation(Optional, Class) + * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(Optional, Class) + */ + public static boolean isAnnotated(Optional element, + Class annotationType) { + + return findAnnotation(element, annotationType).isPresent(); + } + + /** + * @since 1.8 + * @see #findAnnotation(Parameter, int, Class) + */ + public static boolean isAnnotated(Parameter parameter, int index, Class annotationType) { + return findAnnotation(parameter, index, annotationType).isPresent(); + } + + /** + * Determine if an annotation of {@code annotationType} is either + * present or meta-present on the supplied + * {@code element}. + * + * @param element the element on which to search for the annotation; may be + * {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @return {@code true} if the annotation is present or meta-present + * @see #findAnnotation(AnnotatedElement, Class) + * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(AnnotatedElement, Class) + */ + public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { + return findAnnotation(element, annotationType).isPresent(); + } + + /** + * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(Optional, Class) + */ + public static Optional findAnnotation(Optional element, + Class annotationType) { + + if (element == null || !element.isPresent()) { + return Optional.empty(); + } + + return findAnnotation(element.get(), annotationType); + } + + /** + * @since 1.8 + * @see #findAnnotation(AnnotatedElement, Class) + */ + public static Optional findAnnotation(Parameter parameter, int index, + Class annotationType) { + + return findAnnotation(getEffectiveAnnotatedParameter(parameter, index), annotationType); + } + + /** + * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(AnnotatedElement, Class) + */ + public static Optional findAnnotation(AnnotatedElement element, Class annotationType) { + Preconditions.notNull(annotationType, "annotationType must not be null"); + boolean inherited = annotationType.isAnnotationPresent(Inherited.class); + return findAnnotation(element, annotationType, inherited, new HashSet<>()); + } + + private static Optional findAnnotation(AnnotatedElement element, Class annotationType, + boolean inherited, Set visited) { + + Preconditions.notNull(annotationType, "annotationType must not be null"); + + if (element == null) { + return Optional.empty(); + } + + // Directly present? + A annotation = element.getDeclaredAnnotation(annotationType); + if (annotation != null) { + return Optional.of(annotation); + } + + // Meta-present on directly present annotations? + Optional directMetaAnnotation = findMetaAnnotation(annotationType, element.getDeclaredAnnotations(), + inherited, visited); + if (directMetaAnnotation.isPresent()) { + return directMetaAnnotation; + } + + if (element instanceof Class) { + Class clazz = (Class) element; + + // Search on interfaces + for (Class ifc : clazz.getInterfaces()) { + if (ifc != Annotation.class) { + Optional annotationOnInterface = findAnnotation(ifc, annotationType, inherited, visited); + if (annotationOnInterface.isPresent()) { + return annotationOnInterface; + } + } + } + + // Indirectly present? + // Search in class hierarchy + if (inherited) { + Class superclass = clazz.getSuperclass(); + if (superclass != null && superclass != Object.class) { + Optional annotationOnSuperclass = findAnnotation(superclass, annotationType, inherited, visited); + if (annotationOnSuperclass.isPresent()) { + return annotationOnSuperclass; + } + } + } + } + + // Meta-present on indirectly present annotations? + return findMetaAnnotation(annotationType, element.getAnnotations(), inherited, visited); + } + + private static Optional findMetaAnnotation(Class annotationType, + Annotation[] candidates, boolean inherited, Set visited) { + + for (Annotation candidateAnnotation : candidates) { + Class candidateAnnotationType = candidateAnnotation.annotationType(); + if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidateAnnotation)) { + Optional metaAnnotation = findAnnotation(candidateAnnotationType, annotationType, inherited, + visited); + if (metaAnnotation.isPresent()) { + return metaAnnotation; + } + } + } + return Optional.empty(); + } + + /** + * Find the first annotation of the specified type that is either + * directly present, meta-present, or indirectly + * present on the supplied class, optionally searching recursively + * through the enclosing class hierarchy if not found on the supplied class. + * + *

The enclosing class hierarchy will only be searched above an inner + * class (i.e., a non-static member class). + * + * @param the annotation type + * @param clazz the class on which to search for the annotation; may be {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param searchEnclosingClasses whether the enclosing class hierarchy should + * be searched + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty + * @since 1.8 + * @see #findAnnotation(AnnotatedElement, Class) + */ + public static Optional findAnnotation(Class clazz, Class annotationType, + boolean searchEnclosingClasses) { + + Preconditions.notNull(annotationType, "annotationType must not be null"); + + if (!searchEnclosingClasses) { + return findAnnotation(clazz, annotationType); + } + + Class candidate = clazz; + while (candidate != null) { + Optional annotation = findAnnotation(candidate, annotationType); + if (annotation.isPresent()) { + return annotation; + } + candidate = (isInnerClass(candidate) ? candidate.getEnclosingClass() : null); + } + return Optional.empty(); + } + + /** + * @since 1.5 + * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(Optional, Class) + */ + public static List findRepeatableAnnotations(Optional element, + Class annotationType) { + + if (element == null || !element.isPresent()) { + return Collections.emptyList(); + } + + return findRepeatableAnnotations(element.get(), annotationType); + } + + /** + * @since 1.8 + * @see #findRepeatableAnnotations(AnnotatedElement, Class) + */ + public static List findRepeatableAnnotations(Parameter parameter, int index, + Class annotationType) { + + return findRepeatableAnnotations(getEffectiveAnnotatedParameter(parameter, index), annotationType); + } + + /** + * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(AnnotatedElement, Class) + */ + public static List findRepeatableAnnotations(AnnotatedElement element, + Class annotationType) { + + Preconditions.notNull(annotationType, "annotationType must not be null"); + Repeatable repeatable = annotationType.getAnnotation(Repeatable.class); + Preconditions.notNull(repeatable, () -> annotationType.getName() + " must be @Repeatable"); + Class containerType = repeatable.value(); + boolean inherited = containerType.isAnnotationPresent(Inherited.class); + + // Short circuit the search algorithm. + if (element == null) { + return Collections.emptyList(); + } + + // We use a LinkedHashSet because the search algorithm may discover + // duplicates, but we need to maintain the original order. + Set found = new LinkedHashSet<>(16); + findRepeatableAnnotations(element, annotationType, containerType, inherited, found, new HashSet<>(16)); + // unmodifiable since returned from public, non-internal method(s) + return Collections.unmodifiableList(new ArrayList<>(found)); + } + + private static void findRepeatableAnnotations(AnnotatedElement element, + Class annotationType, Class containerType, boolean inherited, Set found, + Set visited) { + + if (element instanceof Class) { + Class clazz = (Class) element; + + // Recurse first in order to support top-down semantics for inherited, repeatable annotations. + if (inherited) { + Class superclass = clazz.getSuperclass(); + if (superclass != null && superclass != Object.class) { + findRepeatableAnnotations(superclass, annotationType, containerType, inherited, found, visited); + } + } + + // Search on interfaces + for (Class ifc : clazz.getInterfaces()) { + if (ifc != Annotation.class) { + findRepeatableAnnotations(ifc, annotationType, containerType, inherited, found, visited); + } + } + } + + // Find annotations that are directly present or meta-present on directly present annotations. + findRepeatableAnnotations(element.getDeclaredAnnotations(), annotationType, containerType, inherited, found, + visited); + + // Find annotations that are indirectly present or meta-present on indirectly present annotations. + findRepeatableAnnotations(element.getAnnotations(), annotationType, containerType, inherited, found, visited); + } + + @SuppressWarnings("unchecked") + private static void findRepeatableAnnotations(Annotation[] candidates, + Class annotationType, Class containerType, boolean inherited, Set found, + Set visited) { + + for (Annotation candidate : candidates) { + Class candidateAnnotationType = candidate.annotationType(); + if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidate)) { + // Exact match? + if (candidateAnnotationType.equals(annotationType)) { + found.add(annotationType.cast(candidate)); + } + // Container? + else if (candidateAnnotationType.equals(containerType)) { + // Note: it's not a legitimate containing annotation type if it doesn't declare + // a 'value' attribute that returns an array of the contained annotation type. + // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.3 + Method method = ReflectionUtils.tryToGetMethod(containerType, "value").getOrThrow( + cause -> new JUnitException(String.format( + "Container annotation type '%s' must declare a 'value' attribute of type %s[].", + containerType, annotationType), cause)); + + Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); + found.addAll((Collection) asList(containedAnnotations)); + } + // Nested container annotation? + else if (isRepeatableAnnotationContainer(candidateAnnotationType)) { + Method method = ReflectionUtils.tryToGetMethod(candidateAnnotationType, "value").toOptional().get(); + Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); + + for (Annotation containedAnnotation : containedAnnotations) { + findRepeatableAnnotations(containedAnnotation.getClass(), annotationType, containerType, + inherited, found, visited); + } + } + // Otherwise search recursively through the meta-annotation hierarchy... + else { + findRepeatableAnnotations(candidateAnnotationType, annotationType, containerType, inherited, found, + visited); + } + } + } + } + + /** + * Determine if the supplied annotation type is a container for a repeatable + * annotation. + * + * @since 1.5 + */ + private static boolean isRepeatableAnnotationContainer(Class candidateContainerType) { + return repeatableAnnotationContainerCache.computeIfAbsent(candidateContainerType, candidate -> { + // @formatter:off + Repeatable repeatable = Arrays.stream(candidate.getMethods()) + .filter(attribute -> attribute.getName().equals("value") && attribute.getReturnType().isArray()) + .findFirst() + .map(attribute -> attribute.getReturnType().getComponentType().getAnnotation(Repeatable.class)) + .orElse(null); + // @formatter:on + + return repeatable != null && candidate.equals(repeatable.value()); + }); + } + + /** + * Due to a bug in {@code javac} on JDK versions prior to JDK 9, looking up + * annotations directly on a {@link Parameter} will fail for inner class + * constructors. + * + *

Bug in {@code javac} on JDK versions prior to JDK 9

+ * + *

The parameter annotations array in the compiled byte code for the user's + * class excludes an entry for the implicit enclosing instance + * parameter for an inner class constructor. + * + *

Workaround

+ * + *

This method provides a workaround for this off-by-one error by helping + * JUnit maintainers and extension authors to access annotations on the preceding + * {@link Parameter} object (i.e., {@code index - 1}). If the supplied + * {@code index} is zero in such situations this method will return {@code null} + * since the parameter for the implicit enclosing instance will never + * be annotated. + * + *

WARNING

+ * + *

The {@code AnnotatedElement} returned by this method should never be cast and + * treated as a {@code Parameter} since the metadata (e.g., {@link Parameter#getName()}, + * {@link Parameter#getType()}, etc.) will not match those for the declared parameter + * at the given index in an inner class constructor for code compiled with JDK 8. + * + * @return the supplied {@code Parameter}, or the effective {@code Parameter} + * if the aforementioned bug is detected, or {@code null} if the bug is detected and + * the supplied {@code index} is {@code 0} + * @since 1.8 + */ + private static AnnotatedElement getEffectiveAnnotatedParameter(Parameter parameter, int index) { + Preconditions.notNull(parameter, "Parameter must not be null"); + Executable executable = parameter.getDeclaringExecutable(); + + if (executable instanceof Constructor && isInnerClass(executable.getDeclaringClass()) + && executable.getParameterAnnotations().length == executable.getParameterCount() - 1) { + + if (index == 0) { + return null; + } + + return executable.getParameters()[index - 1]; + } + + return parameter; + } + + /** + * @see org.junit.platform.commons.support.AnnotationSupport#findPublicAnnotatedFields(Class, Class, Class) + */ + public static List findPublicAnnotatedFields(Class clazz, Class fieldType, + Class annotationType) { + + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(fieldType, "fieldType must not be null"); + Preconditions.notNull(annotationType, "annotationType must not be null"); + + // @formatter:off + return Arrays.stream(clazz.getFields()) + .filter(field -> fieldType.isAssignableFrom(field.getType()) && isAnnotated(field, annotationType)) + .collect(toUnmodifiableList()); + // @formatter:on + } + + /** + * Find all {@linkplain Field fields} of the supplied class or interface + * that are annotated or meta-annotated with the specified + * {@code annotationType} and match the specified {@code predicate}, using + * top-down search semantics within the type hierarchy. + * + * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) + */ + public static List findAnnotatedFields(Class clazz, Class annotationType, + Predicate predicate) { + + return findAnnotatedFields(clazz, annotationType, predicate, HierarchyTraversalMode.TOP_DOWN); + } + + /** + * Find all {@linkplain Field fields} of the supplied class or interface + * that are annotated or meta-annotated with the specified + * {@code annotationType} and match the specified {@code predicate}. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param predicate the field filter; never {@code null} + * @param traversalMode the hierarchy traversal mode; never {@code null} + * @return the list of all such fields found; neither {@code null} nor mutable + */ + public static List findAnnotatedFields(Class clazz, Class annotationType, + Predicate predicate, HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(annotationType, "annotationType must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + + Predicate annotated = field -> isAnnotated(field, annotationType); + + return ReflectionUtils.findFields(clazz, annotated.and(predicate), traversalMode); + } + + /** + * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotatedMethods(Class, Class, org.junit.platform.commons.support.HierarchyTraversalMode) + */ + public static List findAnnotatedMethods(Class clazz, Class annotationType, + HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(annotationType, "annotationType must not be null"); + + return ReflectionUtils.findMethods(clazz, method -> isAnnotated(method, annotationType), traversalMode); + } + + private static boolean isInJavaLangAnnotationPackage(Class annotationType) { + return (annotationType != null && annotationType.getName().startsWith("java.lang.annotation")); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java new file mode 100644 index 00000000..2c82dd50 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.DEPRECATED; + +import org.apiguardian.api.API; + +/** + * Internal utilities for working with unrecoverable exceptions. + * + *

Unrecoverable exceptions are those that should always terminate + * test plan execution immediately. + * + *

Currently Unrecoverable Exceptions

+ *
    + *
  • {@link OutOfMemoryError}
  • + *
+ * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + * @deprecated Use {@link UnrecoverableExceptions} instead. + */ +@Deprecated +@API(status = DEPRECATED, since = "1.7") +public final class BlacklistedExceptions { + + private BlacklistedExceptions() { + /* no-op */ + } + + /** + * Rethrow the supplied {@link Throwable exception} if it is + * unrecoverable. + * + *

If the supplied {@code exception} is not unrecoverable, this + * method does nothing. + * + * @deprecated Use {@link UnrecoverableExceptions#rethrowIfUnrecoverable} + * instead. + */ + @Deprecated + public static void rethrowIfBlacklisted(Throwable exception) { + UnrecoverableExceptions.rethrowIfUnrecoverable(exception); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java new file mode 100644 index 00000000..a6844206 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.nio.file.FileVisitResult.CONTINUE; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.function.Consumer; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * @since 1.0 + */ +class ClassFileVisitor extends SimpleFileVisitor { + + private static final Logger logger = LoggerFactory.getLogger(ClassFileVisitor.class); + + static final String CLASS_FILE_SUFFIX = ".class"; + private static final String PACKAGE_INFO_FILE_NAME = "package-info" + CLASS_FILE_SUFFIX; + private static final String MODULE_INFO_FILE_NAME = "module-info" + CLASS_FILE_SUFFIX; + + private final Consumer classFileConsumer; + + ClassFileVisitor(Consumer classFileConsumer) { + this.classFileConsumer = classFileConsumer; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) { + if (isNotPackageInfo(file) && isNotModuleInfo(file) && isClassFile(file)) { + classFileConsumer.accept(file); + } + return CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException ex) { + logger.warn(ex, () -> "I/O error visiting file: " + file); + return CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException ex) { + if (ex != null) { + logger.warn(ex, () -> "I/O error visiting directory: " + dir); + } + return CONTINUE; + } + + private static boolean isNotPackageInfo(Path path) { + return !path.endsWith(PACKAGE_INFO_FILE_NAME); + } + + private static boolean isNotModuleInfo(Path path) { + return !path.endsWith(MODULE_INFO_FILE_NAME); + } + + private static boolean isClassFile(Path file) { + return file.getFileName().toString().endsWith(CLASS_FILE_SUFFIX); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java new file mode 100644 index 00000000..63c15fb7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.function.Predicate; + +import org.apiguardian.api.API; + +/** + * Class-related predicate used by reflection utilities. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.1 + */ +@API(status = INTERNAL, since = "1.1") +public class ClassFilter implements Predicate> { + + /** + * Create a {@link ClassFilter} instance that accepts all names but filters classes. + */ + public static ClassFilter of(Predicate> classPredicate) { + return of(name -> true, classPredicate); + } + + /** + * Create a {@link ClassFilter} instance that filters by names and classes. + */ + public static ClassFilter of(Predicate namePredicate, Predicate> classPredicate) { + return new ClassFilter(namePredicate, classPredicate); + } + + private final Predicate namePredicate; + private final Predicate> classPredicate; + + private ClassFilter(Predicate namePredicate, Predicate> classPredicate) { + this.namePredicate = Preconditions.notNull(namePredicate, "name predicate must not be null"); + this.classPredicate = Preconditions.notNull(classPredicate, "class predicate must not be null"); + } + + /** + * Test name using the stored name predicate. + */ + public boolean match(String name) { + return namePredicate.test(name); + } + + /** + * Test class using the stored class predicate. + */ + public boolean match(Class type) { + return classPredicate.test(type); + } + + /** + * @implNote This implementation combines all tests stored in the predicates + * of this instance. Any new predicate must be added to this test method as + * well. + */ + @Override + public boolean test(Class type) { + return match(type.getName()) && match(type); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java new file mode 100644 index 00000000..0bd035b9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.net.URL; +import java.security.CodeSource; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@linkplain ClassLoader} and associated tasks. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class ClassLoaderUtils { + + private ClassLoaderUtils() { + /* no-op */ + } + + public static ClassLoader getDefaultClassLoader() { + try { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + return contextClassLoader; + } + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + /* otherwise ignore */ + } + return ClassLoader.getSystemClassLoader(); + } + + /** + * Get the location from which the supplied object's class was loaded. + * + * @param object the object for whose class the location should be retrieved + * @return an {@code Optional} containing the URL of the class' location; never + * {@code null} but potentially empty + */ + public static Optional getLocation(Object object) { + Preconditions.notNull(object, "object must not be null"); + // determine class loader + ClassLoader loader = object.getClass().getClassLoader(); + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + while (loader != null && loader.getParent() != null) { + loader = loader.getParent(); + } + } + // try finding resource by name + if (loader != null) { + String name = object.getClass().getName(); + name = name.replace(".", "/") + ".class"; + try { + return Optional.ofNullable(loader.getResource(name)); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + /* otherwise ignore */ + } + } + // try protection domain + try { + CodeSource codeSource = object.getClass().getProtectionDomain().getCodeSource(); + if (codeSource != null) { + return Optional.ofNullable(codeSource.getLocation()); + } + } + catch (SecurityException ignore) { + /* ignore */ + } + return Optional.empty(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java new file mode 100644 index 00000000..cea745d1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for creating filters based on class names. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.7 + */ +@API(status = INTERNAL, since = "1.7") +public class ClassNamePatternFilterUtils { + + private ClassNamePatternFilterUtils() { + /* no-op */ + } + + public static final String DEACTIVATE_ALL_PATTERN = "*"; + + /** + * Create a {@link Predicate} that can be used to exclude (i.e., filter out) + * objects of type {@code T} whose fully qualified class names match any of + * the supplied patterns. + * + * @param patterns a comma-separated list of patterns + */ + public static Predicate excludeMatchingClasses(String patterns) { + // @formatter:off + return Optional.ofNullable(patterns) + .filter(StringUtils::isNotBlank) + .map(String::trim) + .map(ClassNamePatternFilterUtils::createPredicateFromPatterns) + .orElse(object -> true); + // @formatter:on + } + + private static Predicate createPredicateFromPatterns(String patterns) { + if (DEACTIVATE_ALL_PATTERN.equals(patterns)) { + return object -> false; + } + List patternList = convertToRegularExpressions(patterns); + return object -> patternList.stream().noneMatch(it -> it.matcher(object.getClass().getName()).matches()); + } + + private static List convertToRegularExpressions(String patterns) { + // @formatter:off + return Arrays.stream(patterns.split(",")) + .filter(StringUtils::isNotBlank) + .map(String::trim) + .map(ClassNamePatternFilterUtils::replaceRegExElements) + .map(Pattern::compile) + .collect(toList()); + // @formatter:on + } + + private static String replaceRegExElements(String pattern) { + return Matcher.quoteReplacement(pattern) + // Match "." against "." and "$" since users may declare a "." instead of a + // "$" as the separator between classes and nested classes. + .replace(".", "[.$]") + // Convert our "*" wildcard into a proper RegEx pattern. + .replace("*", ".+"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java new file mode 100644 index 00000000..41c44ddb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.function.Function; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@link Class classes}. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class ClassUtils { + + private ClassUtils() { + /* no-op */ + } + + /** + * Get the fully qualified name of the supplied class. + * + *

This is a null-safe variant of {@link Class#getName()}. + * + * @param clazz the class whose name should be retrieved, potentially + * {@code null} + * @return the fully qualified class name or {@code "null"} if the supplied + * class reference is {@code null} + * @since 1.3 + * @see #nullSafeToString(Class...) + * @see StringUtils#nullSafeToString(Object) + */ + public static String nullSafeToString(Class clazz) { + return clazz == null ? "null" : clazz.getName(); + } + + /** + * Generate a comma-separated list of fully qualified class names for the + * supplied classes. + * + * @param classes the classes whose names should be included in the + * generated string + * @return a comma-separated list of fully qualified class names, or an empty + * string if the supplied class array is {@code null} or empty + * @see #nullSafeToString(Function, Class...) + * @see StringUtils#nullSafeToString(Object) + */ + public static String nullSafeToString(Class... classes) { + return nullSafeToString(Class::getName, classes); + } + + /** + * Generate a comma-separated list of mapped values for the supplied classes. + * + *

The values are generated by the supplied {@code mapper} + * (e.g., {@code Class::getName}, {@code Class::getSimpleName}, etc.), unless + * a class reference is {@code null} in which case it will be mapped to + * {@code "null"}. + * + * @param mapper the mapper to use; never {@code null} + * @param classes the classes to map + * @return a comma-separated list of mapped values, or an empty string if + * the supplied class array is {@code null} or empty + * @see #nullSafeToString(Class...) + * @see StringUtils#nullSafeToString(Object) + */ + public static String nullSafeToString(Function, ? extends String> mapper, Class... classes) { + Preconditions.notNull(mapper, "Mapping function must not be null"); + + if (classes == null || classes.length == 0) { + return ""; + } + return stream(classes).map(clazz -> clazz == null ? "null" : mapper.apply(clazz)).collect(joining(", ")); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java new file mode 100644 index 00000000..c4589503 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java @@ -0,0 +1,261 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.junit.platform.commons.util.ClassFileVisitor.CLASS_FILE_SUFFIX; +import static org.junit.platform.commons.util.StringUtils.isNotBlank; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +class ClasspathScanner { + + private static final Logger logger = LoggerFactory.getLogger(ClasspathScanner.class); + + private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/'; + private static final String CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING = String.valueOf( + CLASSPATH_RESOURCE_PATH_SEPARATOR); + private static final char PACKAGE_SEPARATOR_CHAR = '.'; + private static final String PACKAGE_SEPARATOR_STRING = String.valueOf(PACKAGE_SEPARATOR_CHAR); + + /** + * Malformed class name InternalError like reported in #401. + */ + private static final String MALFORMED_CLASS_NAME_ERROR_MESSAGE = "Malformed class name"; + + private final Supplier classLoaderSupplier; + + private final BiFunction>> loadClass; + + ClasspathScanner(Supplier classLoaderSupplier, + BiFunction>> loadClass) { + + this.classLoaderSupplier = classLoaderSupplier; + this.loadClass = loadClass; + } + + List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) { + Preconditions.condition( + PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), + "basePackageName must not be null or blank"); + Preconditions.notNull(classFilter, "classFilter must not be null"); + basePackageName = basePackageName.trim(); + + return findClassesForUris(getRootUrisForPackageNameOnClassPathAndModulePath(basePackageName), basePackageName, + classFilter); + } + + List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter) { + Preconditions.notNull(root, "root must not be null"); + Preconditions.notNull(classFilter, "classFilter must not be null"); + + return findClassesForUri(root, PackageUtils.DEFAULT_PACKAGE_NAME, classFilter); + } + + /** + * Recursively scan for classes in all of the supplied source directories. + */ + private List> findClassesForUris(List baseUris, String basePackageName, ClassFilter classFilter) { + // @formatter:off + return baseUris.stream() + .map(baseUri -> findClassesForUri(baseUri, basePackageName, classFilter)) + .flatMap(Collection::stream) + .distinct() + .collect(toList()); + // @formatter:on + } + + private List> findClassesForUri(URI baseUri, String basePackageName, ClassFilter classFilter) { + try (CloseablePath closeablePath = CloseablePath.create(baseUri)) { + Path baseDir = closeablePath.getPath(); + return findClassesForPath(baseDir, basePackageName, classFilter); + } + catch (PreconditionViolationException ex) { + throw ex; + } + catch (Exception ex) { + logger.warn(ex, () -> "Error scanning files for URI " + baseUri); + return emptyList(); + } + } + + private List> findClassesForPath(Path baseDir, String basePackageName, ClassFilter classFilter) { + Preconditions.condition(Files.exists(baseDir), () -> "baseDir must exist: " + baseDir); + List> classes = new ArrayList<>(); + try { + Files.walkFileTree(baseDir, new ClassFileVisitor( + classFile -> processClassFileSafely(baseDir, basePackageName, classFilter, classFile, classes::add))); + } + catch (IOException ex) { + logger.warn(ex, () -> "I/O error scanning files in " + baseDir); + } + return classes; + } + + private void processClassFileSafely(Path baseDir, String basePackageName, ClassFilter classFilter, Path classFile, + Consumer> classConsumer) { + try { + String fullyQualifiedClassName = determineFullyQualifiedClassName(baseDir, basePackageName, classFile); + if (classFilter.match(fullyQualifiedClassName)) { + try { + // @formatter:off + loadClass.apply(fullyQualifiedClassName, getClassLoader()) + .toOptional() + .filter(classFilter) // Always use ".filter(classFilter)" to include future predicates. + .ifPresent(classConsumer); + // @formatter:on + } + catch (InternalError internalError) { + handleInternalError(classFile, fullyQualifiedClassName, internalError); + } + } + } + catch (Throwable throwable) { + handleThrowable(classFile, throwable); + } + } + + private String determineFullyQualifiedClassName(Path baseDir, String basePackageName, Path classFile) { + // @formatter:off + return Stream.of( + basePackageName, + determineSubpackageName(baseDir, classFile), + determineSimpleClassName(classFile) + ) + .filter(value -> !value.isEmpty()) // Handle default package appropriately. + .collect(joining(PACKAGE_SEPARATOR_STRING)); + // @formatter:on + } + + private String determineSimpleClassName(Path classFile) { + String fileName = classFile.getFileName().toString(); + return fileName.substring(0, fileName.length() - CLASS_FILE_SUFFIX.length()); + } + + private String determineSubpackageName(Path baseDir, Path classFile) { + Path relativePath = baseDir.relativize(classFile.getParent()); + String pathSeparator = baseDir.getFileSystem().getSeparator(); + String subpackageName = relativePath.toString().replace(pathSeparator, PACKAGE_SEPARATOR_STRING); + if (subpackageName.endsWith(pathSeparator)) { + // Workaround for JDK bug: https://bugs.openjdk.java.net/browse/JDK-8153248 + subpackageName = subpackageName.substring(0, subpackageName.length() - pathSeparator.length()); + } + return subpackageName; + } + + private void handleInternalError(Path classFile, String fullyQualifiedClassName, InternalError ex) { + if (MALFORMED_CLASS_NAME_ERROR_MESSAGE.equals(ex.getMessage())) { + logMalformedClassName(classFile, fullyQualifiedClassName, ex); + } + else { + logGenericFileProcessingException(classFile, ex); + } + } + + private void handleThrowable(Path classFile, Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + logGenericFileProcessingException(classFile, throwable); + } + + private void logMalformedClassName(Path classFile, String fullyQualifiedClassName, InternalError ex) { + try { + logger.debug(ex, () -> format("The java.lang.Class loaded from path [%s] has a malformed class name [%s].", + classFile.toAbsolutePath(), fullyQualifiedClassName)); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + ex.addSuppressed(t); + logGenericFileProcessingException(classFile, ex); + } + } + + private void logGenericFileProcessingException(Path classFile, Throwable throwable) { + logger.debug(throwable, () -> format("Failed to load java.lang.Class for path [%s] during classpath scanning.", + classFile.toAbsolutePath())); + } + + private ClassLoader getClassLoader() { + return this.classLoaderSupplier.get(); + } + + private List getRootUrisForPackageNameOnClassPathAndModulePath(String basePackageName) { + Set uriSet = new LinkedHashSet<>(getRootUrisForPackage(basePackageName)); + if (!basePackageName.isEmpty() && !basePackageName.endsWith(PACKAGE_SEPARATOR_STRING)) { + getRootUrisForPackage(basePackageName + PACKAGE_SEPARATOR_STRING).stream() // + .map(ClasspathScanner::removeTrailingClasspathResourcePathSeparator) // + .forEach(uriSet::add); + } + return new ArrayList<>(uriSet); + } + + private static URI removeTrailingClasspathResourcePathSeparator(URI uri) { + String string = uri.toString(); + if (string.endsWith(CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING)) { + return URI.create(string.substring(0, string.length() - 1)); + } + return uri; + } + + private static String packagePath(String packageName) { + if (packageName.isEmpty()) { + return ""; + } + return packageName.replace(PACKAGE_SEPARATOR_CHAR, CLASSPATH_RESOURCE_PATH_SEPARATOR); + } + + private List getRootUrisForPackage(String basePackageName) { + try { + Enumeration resources = getClassLoader().getResources(packagePath(basePackageName)); + List uris = new ArrayList<>(); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + uris.add(resource.toURI()); + } + return uris; + } + catch (Exception ex) { + logger.warn(ex, () -> "Error reading URIs from class loader for base package " + basePackageName); + return emptyList(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java new file mode 100644 index 00000000..e6bb3ddd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.Collections.emptyMap; + +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +/** + * @since 1.0 + */ +final class CloseablePath implements Closeable { + + private static final String FILE_URI_SCHEME = "file"; + private static final String JAR_URI_SCHEME = "jar"; + private static final String JAR_FILE_EXTENSION = ".jar"; + private static final String JAR_URI_SEPARATOR = "!"; + + private static final Closeable NULL_CLOSEABLE = () -> { + }; + + private static final ConcurrentMap MANAGED_FILE_SYSTEMS = new ConcurrentHashMap<>(); + + private final Path path; + private final Closeable delegate; + + static CloseablePath create(URI uri) throws URISyntaxException { + if (JAR_URI_SCHEME.equals(uri.getScheme())) { + String[] parts = uri.toString().split(JAR_URI_SEPARATOR); + String jarUri = parts[0]; + String jarEntry = parts[1]; + return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry)); + } + if (uri.getScheme().equals(FILE_URI_SCHEME) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) { + return createForJarFileSystem(new URI(JAR_URI_SCHEME + ':' + uri), + fileSystem -> fileSystem.getRootDirectories().iterator().next()); + } + return new CloseablePath(Paths.get(uri), NULL_CLOSEABLE); + } + + private static CloseablePath createForJarFileSystem(URI jarUri, Function pathProvider) { + ManagedFileSystem managedFileSystem = MANAGED_FILE_SYSTEMS.compute(jarUri, + (__, oldValue) -> oldValue == null ? new ManagedFileSystem(jarUri) : oldValue.retain()); + Path path = pathProvider.apply(managedFileSystem.fileSystem); + return new CloseablePath(path, + () -> MANAGED_FILE_SYSTEMS.compute(jarUri, (__, ___) -> managedFileSystem.release())); + } + + private CloseablePath(Path path, Closeable delegate) { + this.path = path; + this.delegate = delegate; + } + + public Path getPath() { + return path; + } + + @Override + public void close() throws IOException { + delegate.close(); + } + + private static class ManagedFileSystem { + + private final AtomicInteger referenceCount = new AtomicInteger(1); + private final FileSystem fileSystem; + private final URI jarUri; + + ManagedFileSystem(URI jarUri) { + this.jarUri = jarUri; + try { + fileSystem = FileSystems.newFileSystem(jarUri, emptyMap()); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to create file system for " + jarUri, e); + } + } + + private ManagedFileSystem retain() { + referenceCount.incrementAndGet(); + return this; + } + + private ManagedFileSystem release() { + if (referenceCount.decrementAndGet() == 0) { + close(); + return null; + } + return this; + } + + private void close() { + try { + fileSystem.close(); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to close file system for " + jarUri, e); + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java new file mode 100644 index 00000000..dc6bf37a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java @@ -0,0 +1,206 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.Spliterator.ORDERED; +import static java.util.Spliterators.spliteratorUnknownSize; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Collection of utilities for working with {@link Collection Collections}. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class CollectionUtils { + + private CollectionUtils() { + /* no-op */ + } + + /** + * Read the only element of a collection of size 1. + * + * @param collection the collection to get the element from + * @return the only element of the collection + * @throws PreconditionViolationException if the collection is {@code null} + * or does not contain exactly one element + */ + public static T getOnlyElement(Collection collection) { + Preconditions.notNull(collection, "collection must not be null"); + Preconditions.condition(collection.size() == 1, + () -> "collection must contain exactly one element: " + collection); + return collection.iterator().next(); + } + + /** + * Convert the supplied array of values to a {@link Set}. + * + * @param values the array of values; never {@code null} + * @return a set of the values + * @throws PreconditionViolationException if the array is {@code null} + * @since 1.6 + */ + @API(status = INTERNAL, since = "1.6") + public static Set toSet(T[] values) { + Preconditions.notNull(values, "values array must not be null"); + if (values.length == 0) { + return Collections.emptySet(); + } + if (values.length == 1) { + return Collections.singleton(values[0]); + } + Set set = new HashSet<>(); + Collections.addAll(set, values); + return set; + } + + /** + * Return a {@code Collector} that accumulates the input elements into a + * new unmodifiable list, in encounter order. + * + *

There are no guarantees on the type or serializability of the list + * returned, so if more control over the returned list is required, + * consider creating a new {@code Collector} implementation like the + * following: + * + *

+	 * public static <T> Collector<T, ?, List<T>> toUnmodifiableList(Supplier<List<T>> listSupplier) {
+	 *     return Collectors.collectingAndThen(Collectors.toCollection(listSupplier), Collections::unmodifiableList);
+	 * }
+	 * 
+ * + * @param the type of the input elements + * @return a {@code Collector} which collects all the input elements into + * an unmodifiable list, in encounter order + */ + public static Collector> toUnmodifiableList() { + return collectingAndThen(toList(), Collections::unmodifiableList); + } + + /** + * Determine if an instance of the supplied type can be converted into a + * {@code Stream}. + * + *

If this method returns {@code true}, {@link #toStream(Object)} can + * successfully convert an object of the specified type into a stream. See + * {@link #toStream(Object)} for supported types. + * + * @param type the type to check; may be {@code null} + * @return {@code true} if an instance of the type can be converted into a stream + * @since 1.9.1 + * @see #toStream(Object) + */ + @API(status = INTERNAL, since = "1.9.1") + public static boolean isConvertibleToStream(Class type) { + if (type == null || type == void.class) { + return false; + } + return (Stream.class.isAssignableFrom(type)// + || DoubleStream.class.isAssignableFrom(type)// + || IntStream.class.isAssignableFrom(type)// + || LongStream.class.isAssignableFrom(type)// + || Iterable.class.isAssignableFrom(type)// + || Iterator.class.isAssignableFrom(type)// + || Object[].class.isAssignableFrom(type)// + || (type.isArray() && type.getComponentType().isPrimitive())); + } + + /** + * Convert an object of one of the following supported types into a {@code Stream}. + * + *

    + *
  • {@link Stream}
  • + *
  • {@link DoubleStream}
  • + *
  • {@link IntStream}
  • + *
  • {@link LongStream}
  • + *
  • {@link Collection}
  • + *
  • {@link Iterable}
  • + *
  • {@link Iterator}
  • + *
  • {@link Object} array
  • + *
  • primitive array
  • + *
+ * + * @param object the object to convert into a stream; never {@code null} + * @return the resulting stream + * @throws PreconditionViolationException if the supplied object is {@code null} + * or not one of the supported types + * @see #isConvertibleToStream(Class) + */ + public static Stream toStream(Object object) { + Preconditions.notNull(object, "Object must not be null"); + if (object instanceof Stream) { + return (Stream) object; + } + if (object instanceof DoubleStream) { + return ((DoubleStream) object).boxed(); + } + if (object instanceof IntStream) { + return ((IntStream) object).boxed(); + } + if (object instanceof LongStream) { + return ((LongStream) object).boxed(); + } + if (object instanceof Collection) { + return ((Collection) object).stream(); + } + if (object instanceof Iterable) { + return stream(((Iterable) object).spliterator(), false); + } + if (object instanceof Iterator) { + return stream(spliteratorUnknownSize((Iterator) object, ORDERED), false); + } + if (object instanceof Object[]) { + return Arrays.stream((Object[]) object); + } + if (object instanceof double[]) { + return DoubleStream.of((double[]) object).boxed(); + } + if (object instanceof int[]) { + return IntStream.of((int[]) object).boxed(); + } + if (object instanceof long[]) { + return LongStream.of((long[]) object).boxed(); + } + if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) { + return IntStream.range(0, Array.getLength(object)).mapToObj(i -> Array.get(object, i)); + } + throw new PreconditionViolationException( + "Cannot convert instance of " + object.getClass().getName() + " into a Stream: " + object); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java new file mode 100644 index 00000000..d8177037 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with exceptions. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class ExceptionUtils { + + private ExceptionUtils() { + /* no-op */ + } + + /** + * Throw the supplied {@link Throwable}, masked as an + * unchecked exception. + * + *

The supplied {@code Throwable} will not be wrapped. Rather, it + * will be thrown as is using an exploit of the Java language + * that relies on a combination of generics and type erasure to trick + * the Java compiler into believing that the thrown exception is an + * unchecked exception even if it is a checked exception. + * + *

Warning

+ * + *

This method should be used sparingly. + * + * @param t the {@code Throwable} to throw as an unchecked exception; + * never {@code null} + * @return this method always throws an exception and therefore never + * returns anything; the return type is merely present to allow this + * method to be supplied as the operand in a {@code throw} statement + */ + public static RuntimeException throwAsUncheckedException(Throwable t) { + Preconditions.notNull(t, "Throwable must not be null"); + ExceptionUtils.throwAs(t); + + // Appeasing the compiler: the following line will never be executed. + return null; + } + + @SuppressWarnings("unchecked") + private static void throwAs(Throwable t) throws T { + throw (T) t; + } + + /** + * Read the stacktrace of the supplied {@link Throwable} into a String. + */ + public static String readStackTrace(Throwable throwable) { + Preconditions.notNull(throwable, "Throwable must not be null"); + StringWriter stringWriter = new StringWriter(); + try (PrintWriter printWriter = new PrintWriter(stringWriter)) { + throwable.printStackTrace(printWriter); + } + return stringWriter.toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java new file mode 100644 index 00000000..492df5c6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@link Function Functions}, + * {@link Predicate Predicates}, etc. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class FunctionUtils { + + private FunctionUtils() { + /* no-op */ + } + + /** + * Return a predicate that first applies the specified function and then + * tests the specified predicate against the result of the function. + * + * @param function the function to apply + * @param predicate the predicate to test against the result of the function + */ + public static Predicate where(Function function, Predicate predicate) { + Preconditions.notNull(function, "function must not be null"); + Preconditions.notNull(predicate, "predicate must not be null"); + return input -> predicate.test(function.apply(input)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java new file mode 100644 index 00000000..dc955396 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apiguardian.api.API; + +/** + * A simple LRU cache with a maximum size. + * + *

This class is not thread-safe. + * + * @param the type of keys maintained by this cache + * @param the type of values maintained by this cache + * @since 1.6 + */ +@API(status = INTERNAL, since = "1.6") +public class LruCache extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + + private final int maxSize; + + /** + * Create a new LRU cache that maintains at most the supplied number of + * entries. + * + *

For optimal use of the internal data structures, you should pick a + * number that's one below a power of two since this is based on a + * {@link java.util.HashMap} and the eldest entry will be evicted after + * adding the entry that increases the {@linkplain #size() size} to be above + * {@code maxSize}. + */ + public LruCache(int maxSize) { + super(maxSize + 1, 1, true); + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java new file mode 100644 index 00000000..b230c921 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * Collection of utilities for working with {@code java.lang.Module} + * and friends. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.1 + */ +@API(status = INTERNAL, since = "1.1") +public class ModuleUtils { + + private static final Logger logger = LoggerFactory.getLogger(ModuleUtils.class); + + /** + * Find all non-system boot modules names. + * + * @return a set of all such module names; never {@code null} but + * potentially empty + */ + public static Set findAllNonSystemBootModuleNames() { + logger.config(() -> "Basic version of findAllNonSystemBootModuleNames() always returns an empty set!"); + return emptySet(); + } + + /** + * Determine if the current Java runtime supports the Java Platform Module System. + * + * @return {@code true} if the Java Platform Module System is available, + * otherwise {@code false} + */ + public static boolean isJavaPlatformModuleSystemAvailable() { + return false; + } + + /** + * Return the name of the module that the class or interface is a member of. + * + * @param type class or interface to analyze + * @return the module name; never {@code null} but potentially empty + */ + public static Optional getModuleName(Class type) { + return Optional.empty(); + } + + /** + * Return the raw version of the module that the class or interface is a member of. + * + * @param type class or interface to analyze + * @return the raw module version; never {@code null} but potentially empty + */ + public static Optional getModuleVersion(Class type) { + return Optional.empty(); + } + + /** + * Find all classes for the given module name. + * + * @param moduleName the name of the module to scan; never {@code null} or + * empty + * @param filter the class filter to apply; never {@code null} + * @return an immutable list of all such classes found; never {@code null} + * but potentially empty + */ + public static List> findAllClassesInModule(String moduleName, ClassFilter filter) { + Preconditions.notBlank(moduleName, "Module name must not be null or empty"); + Preconditions.notNull(filter, "Class filter must not be null"); + + logger.config(() -> "Basic version of findAllClassesInModule() always returns an empty list!"); + return emptyList(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java new file mode 100644 index 00000000..2156b2df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java @@ -0,0 +1,102 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.File; +import java.net.URL; +import java.util.Optional; +import java.util.function.Function; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@linkplain Package packages}. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class PackageUtils { + + private PackageUtils() { + /* no-op */ + } + + static final String DEFAULT_PACKAGE_NAME = ""; + + /** + * Get the package attribute for the supplied {@code type} using the + * supplied {@code function}. + * + *

This method only returns a non-empty {@link Optional} value holder + * if the class loader for the supplied type created a {@link Package} + * object and the supplied function does not return {@code null} when + * applied. + * + * @param type the type to get the package attribute for + * @param function a function that computes the package attribute value + * (e.g., {@code Package::getImplementationTitle}); never {@code null} + * @return an {@code Optional} containing the attribute value; never + * {@code null} but potentially empty + * @throws org.junit.platform.commons.PreconditionViolationException if the + * supplied type or function is {@code null} + * @see Class#getPackage() + * @see Package#getImplementationTitle() + * @see Package#getImplementationVersion() + */ + public static Optional getAttribute(Class type, Function function) { + Preconditions.notNull(type, "type must not be null"); + Preconditions.notNull(function, "function must not be null"); + return Optional.ofNullable(type.getPackage()).map(function); + } + + /** + * Get the value of the specified attribute name, specified as a string, + * or an empty {@link Optional} if the attribute was not found. The attribute + * name is case-insensitive. + * + *

This method also returns an empty {@link Optional} value holder + * if any exception is caught while loading the manifest file via the + * JAR file of the specified type. + * + * @param type the type to get the attribute for + * @param name the attribute name as a string + * @return an {@code Optional} containing the attribute value; never + * {@code null} but potentially empty + * @throws org.junit.platform.commons.PreconditionViolationException if the + * supplied type is {@code null} or the specified name is blank + * @see Manifest#getMainAttributes() + */ + public static Optional getAttribute(Class type, String name) { + Preconditions.notNull(type, "type must not be null"); + Preconditions.notBlank(name, "name must not be blank"); + try { + URL jarUrl = type.getProtectionDomain().getCodeSource().getLocation(); + try (JarFile jarFile = new JarFile(new File(jarUrl.toURI()))) { + Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); + return Optional.ofNullable(mainAttributes.getValue(name)); + } + } + catch (Exception e) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java new file mode 100644 index 00000000..67dcdde4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.DEPRECATED; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * Thrown if a precondition is violated. + * + * @since 1.0 + * @see Preconditions + * @deprecated Use {@linkplain org.junit.platform.commons.PreconditionViolationException} instead. + */ +@API(status = DEPRECATED, since = "1.5") +@Deprecated +public class PreconditionViolationException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public PreconditionViolationException(String message) { + super(message); + } + + public PreconditionViolationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java new file mode 100644 index 00000000..6e64150c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java @@ -0,0 +1,318 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Collection of utilities for asserting preconditions for method and + * constructor arguments. + * + *

Each method in this class throws a {@link PreconditionViolationException} + * if the precondition is violated. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class Preconditions { + + private Preconditions() { + /* no-op */ + } + + /** + * Assert that the supplied {@link Object} is not {@code null}. + * + * @param object the object to check + * @param message precondition violation message + * @return the supplied object as a convenience + * @throws PreconditionViolationException if the supplied object is {@code null} + * @see #notNull(Object, Supplier) + */ + public static T notNull(T object, String message) throws PreconditionViolationException { + condition(object != null, message); + return object; + } + + /** + * Assert that the supplied {@link Object} is not {@code null}. + * + * @param object the object to check + * @param messageSupplier precondition violation message supplier + * @return the supplied object as a convenience + * @throws PreconditionViolationException if the supplied object is {@code null} + * @see #condition(boolean, Supplier) + */ + public static T notNull(T object, Supplier messageSupplier) throws PreconditionViolationException { + condition(object != null, messageSupplier); + return object; + } + + /** + * Assert that the supplied array is neither {@code null} nor empty. + * + * @param array the array to check + * @param message precondition violation message + * @return the supplied array as a convenience + * @throws PreconditionViolationException if the supplied array is + * {@code null} or empty + * @since 1.9 + * @see #condition(boolean, String) + */ + @API(status = EXPERIMENTAL, since = "1.9") + public static int[] notEmpty(int[] array, String message) throws PreconditionViolationException { + condition(array != null && array.length > 0, message); + return array; + } + + /** + * Assert that the supplied array is neither {@code null} nor empty. + * + *

WARNING: this method does NOT check if the supplied + * array contains any {@code null} elements. + * + * @param array the array to check + * @param message precondition violation message + * @return the supplied array as a convenience + * @throws PreconditionViolationException if the supplied array is + * {@code null} or empty + * @see #containsNoNullElements(Object[], String) + * @see #condition(boolean, String) + */ + public static T[] notEmpty(T[] array, String message) throws PreconditionViolationException { + condition(array != null && array.length > 0, message); + return array; + } + + /** + * Assert that the supplied array is neither {@code null} nor empty. + * + *

WARNING: this method does NOT check if the supplied + * array contains any {@code null} elements. + * + * @param array the array to check + * @param messageSupplier precondition violation message supplier + * @return the supplied array as a convenience + * @throws PreconditionViolationException if the supplied array is + * {@code null} or empty + * @see #containsNoNullElements(Object[], String) + * @see #condition(boolean, String) + */ + public static T[] notEmpty(T[] array, Supplier messageSupplier) throws PreconditionViolationException { + condition(array != null && array.length > 0, messageSupplier); + return array; + } + + /** + * Assert that the supplied {@link Collection} is neither {@code null} nor empty. + * + *

WARNING: this method does NOT check if the supplied + * collection contains any {@code null} elements. + * + * @param collection the collection to check + * @param message precondition violation message + * @return the supplied collection as a convenience + * @throws PreconditionViolationException if the supplied collection is {@code null} or empty + * @see #containsNoNullElements(Collection, String) + * @see #condition(boolean, String) + */ + public static > T notEmpty(T collection, String message) + throws PreconditionViolationException { + + condition(collection != null && !collection.isEmpty(), message); + return collection; + } + + /** + * Assert that the supplied {@link Collection} is neither {@code null} nor empty. + * + *

WARNING: this method does NOT check if the supplied + * collection contains any {@code null} elements. + * + * @param collection the collection to check + * @param messageSupplier precondition violation message supplier + * @return the supplied collection as a convenience + * @throws PreconditionViolationException if the supplied collection is {@code null} or empty + * @see #containsNoNullElements(Collection, String) + * @see #condition(boolean, String) + */ + public static > T notEmpty(T collection, Supplier messageSupplier) + throws PreconditionViolationException { + + condition(collection != null && !collection.isEmpty(), messageSupplier); + return collection; + } + + /** + * Assert that the supplied array contains no {@code null} elements. + * + *

WARNING: this method does NOT check if the supplied + * array is {@code null} or empty. + * + * @param array the array to check + * @param message precondition violation message + * @return the supplied array as a convenience + * @throws PreconditionViolationException if the supplied array contains + * any {@code null} elements + * @see #notNull(Object, String) + */ + public static T[] containsNoNullElements(T[] array, String message) throws PreconditionViolationException { + if (array != null) { + Arrays.stream(array).forEach(object -> notNull(object, message)); + } + return array; + } + + /** + * Assert that the supplied array contains no {@code null} elements. + * + *

WARNING: this method does NOT check if the supplied + * array is {@code null} or empty. + * + * @param array the array to check + * @param messageSupplier precondition violation message supplier + * @return the supplied array as a convenience + * @throws PreconditionViolationException if the supplied array contains + * any {@code null} elements + * @see #notNull(Object, String) + */ + public static T[] containsNoNullElements(T[] array, Supplier messageSupplier) + throws PreconditionViolationException { + + if (array != null) { + Arrays.stream(array).forEach(object -> notNull(object, messageSupplier)); + } + return array; + } + + /** + * Assert that the supplied collection contains no {@code null} elements. + * + *

WARNING: this method does NOT check if the supplied + * collection is {@code null} or empty. + * + * @param collection the collection to check + * @param message precondition violation message + * @return the supplied collection as a convenience + * @throws PreconditionViolationException if the supplied collection contains + * any {@code null} elements + * @see #notNull(Object, String) + */ + public static > T containsNoNullElements(T collection, String message) + throws PreconditionViolationException { + + if (collection != null) { + collection.forEach(object -> notNull(object, message)); + } + return collection; + } + + /** + * Assert that the supplied collection contains no {@code null} elements. + * + *

WARNING: this method does NOT check if the supplied + * collection is {@code null} or empty. + * + * @param collection the collection to check + * @param messageSupplier precondition violation message supplier + * @return the supplied collection as a convenience + * @throws PreconditionViolationException if the supplied collection contains + * any {@code null} elements + * @see #notNull(Object, String) + */ + public static > T containsNoNullElements(T collection, Supplier messageSupplier) + throws PreconditionViolationException { + + if (collection != null) { + collection.forEach(object -> notNull(object, messageSupplier)); + } + return collection; + } + + /** + * Assert that the supplied {@link String} is not blank. + * + *

A {@code String} is blank if it is {@code null} or consists + * only of whitespace characters. + * + * @param str the string to check + * @param message precondition violation message + * @return the supplied string as a convenience + * @throws PreconditionViolationException if the supplied string is blank + * @see #notBlank(String, Supplier) + */ + public static String notBlank(String str, String message) throws PreconditionViolationException { + condition(StringUtils.isNotBlank(str), message); + return str; + } + + /** + * Assert that the supplied {@link String} is not blank. + * + *

A {@code String} is blank if it is {@code null} or consists + * only of whitespace characters. + * + * @param str the string to check + * @param messageSupplier precondition violation message supplier + * @return the supplied string as a convenience + * @throws PreconditionViolationException if the supplied string is blank + * @see StringUtils#isNotBlank(String) + * @see #condition(boolean, Supplier) + */ + public static String notBlank(String str, Supplier messageSupplier) throws PreconditionViolationException { + condition(StringUtils.isNotBlank(str), messageSupplier); + return str; + } + + /** + * Assert that the supplied {@code predicate} is {@code true}. + * + * @param predicate the predicate to check + * @param message precondition violation message + * @throws PreconditionViolationException if the predicate is {@code false} + * @see #condition(boolean, Supplier) + */ + public static void condition(boolean predicate, String message) throws PreconditionViolationException { + if (!predicate) { + throw new PreconditionViolationException(message); + } + } + + /** + * Assert that the supplied {@code predicate} is {@code true}. + * + * @param predicate the predicate to check + * @param messageSupplier precondition violation message supplier + * @throws PreconditionViolationException if the predicate is {@code false} + */ + public static void condition(boolean predicate, Supplier messageSupplier) + throws PreconditionViolationException { + + if (!predicate) { + throw new PreconditionViolationException(messageSupplier.get()); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java new file mode 100644 index 00000000..c20c93b9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -0,0 +1,1757 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.lang.String.format; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; + +import java.io.File; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * Collection of utilities for working with the Java reflection APIs. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + *

Some utilities are published via the maintained {@code ReflectionSupport} + * class. + * + * @since 1.0 + * @see org.junit.platform.commons.support.ReflectionSupport + */ +@API(status = INTERNAL, since = "1.0") +public final class ReflectionUtils { + + private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class); + + private ReflectionUtils() { + /* no-op */ + } + + /** + * Modes in which a hierarchy can be traversed — for example, when + * searching for methods or fields within a class hierarchy. + */ + public enum HierarchyTraversalMode { + + /** + * Traverse the hierarchy using top-down semantics. + */ + TOP_DOWN, + + /** + * Traverse the hierarchy using bottom-up semantics. + */ + BOTTOM_UP; + } + + // Pattern: "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. + private static final Pattern VM_INTERNAL_OBJECT_ARRAY_PATTERN = Pattern.compile("^(\\[+)L(.+);$"); + + /** + * Pattern: "[x", "[[[[x", etc., where x is Z, B, C, D, F, I, J, S, etc. + * + *

The pattern intentionally captures the last bracket with the + * capital letter so that the combination can be looked up via + * {@link #classNameToTypeMap}. For example, the last matched group + * will contain {@code "[I"} instead of {@code "I"}. + * + * @see Class#getName() + */ + private static final Pattern VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN = Pattern.compile("^(\\[+)(\\[[ZBCDFIJS])$"); + + // Pattern: "java.lang.String[]", "int[]", "int[][][][]", etc. + // ?> => non-capturing atomic group + // ++ => possessive quantifier + private static final Pattern SOURCE_CODE_SYNTAX_ARRAY_PATTERN = Pattern.compile("^([^\\[\\]]+)((?>\\[\\])++)$"); + + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + private static final ClasspathScanner classpathScanner = new ClasspathScanner( + ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); + + /** + * Set of fully qualified class names for which no cycles have been detected + * in inner class hierarchies. + *

This serves as a cache to avoid repeated cycle detection for classes + * that have already been checked. + * @since 1.6 + * @see #detectInnerClassCycle(Class) + */ + private static final Set noCyclesDetectedCache = ConcurrentHashMap.newKeySet(); + + /** + * Internal cache of common class names mapped to their types. + */ + private static final Map> classNameToTypeMap; + + /** + * Internal cache of primitive types mapped to their wrapper types. + */ + private static final Map, Class> primitiveToWrapperMap; + + static { + // @formatter:off + List> commonTypes = Arrays.asList( + boolean.class, + byte.class, + char.class, + short.class, + int.class, + long.class, + float.class, + double.class, + + boolean[].class, + byte[].class, + char[].class, + short[].class, + int[].class, + long[].class, + float[].class, + double[].class, + + boolean[][].class, + byte[][].class, + char[][].class, + short[][].class, + int[][].class, + long[][].class, + float[][].class, + double[][].class, + + Boolean.class, + Byte.class, + Character.class, + Short.class, + Integer.class, + Long.class, + Float.class, + Double.class, + String.class, + + Boolean[].class, + Byte[].class, + Character[].class, + Short[].class, + Integer[].class, + Long[].class, + Float[].class, + Double[].class, + String[].class, + + Boolean[][].class, + Byte[][].class, + Character[][].class, + Short[][].class, + Integer[][].class, + Long[][].class, + Float[][].class, + Double[][].class, + String[][].class + ); + // @formatter:on + + Map> classNamesToTypes = new HashMap<>(64); + + commonTypes.forEach(type -> { + classNamesToTypes.put(type.getName(), type); + classNamesToTypes.put(type.getCanonicalName(), type); + }); + + classNameToTypeMap = Collections.unmodifiableMap(classNamesToTypes); + + Map, Class> primitivesToWrappers = new IdentityHashMap<>(8); + + primitivesToWrappers.put(boolean.class, Boolean.class); + primitivesToWrappers.put(byte.class, Byte.class); + primitivesToWrappers.put(char.class, Character.class); + primitivesToWrappers.put(short.class, Short.class); + primitivesToWrappers.put(int.class, Integer.class); + primitivesToWrappers.put(long.class, Long.class); + primitivesToWrappers.put(float.class, Float.class); + primitivesToWrappers.put(double.class, Double.class); + + primitiveToWrapperMap = Collections.unmodifiableMap(primitivesToWrappers); + } + + public static boolean isPublic(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return Modifier.isPublic(clazz.getModifiers()); + } + + public static boolean isPublic(Member member) { + Preconditions.notNull(member, "Member must not be null"); + return Modifier.isPublic(member.getModifiers()); + } + + public static boolean isPrivate(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return Modifier.isPrivate(clazz.getModifiers()); + } + + public static boolean isPrivate(Member member) { + Preconditions.notNull(member, "Member must not be null"); + return Modifier.isPrivate(member.getModifiers()); + } + + @API(status = INTERNAL, since = "1.4") + public static boolean isNotPrivate(Class clazz) { + return !isPrivate(clazz); + } + + @API(status = INTERNAL, since = "1.1") + public static boolean isNotPrivate(Member member) { + return !isPrivate(member); + } + + public static boolean isAbstract(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return Modifier.isAbstract(clazz.getModifiers()); + } + + public static boolean isAbstract(Member member) { + Preconditions.notNull(member, "Member must not be null"); + return Modifier.isAbstract(member.getModifiers()); + } + + public static boolean isStatic(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return Modifier.isStatic(clazz.getModifiers()); + } + + @API(status = INTERNAL, since = "1.4") + public static boolean isNotStatic(Class clazz) { + return !isStatic(clazz); + } + + public static boolean isStatic(Member member) { + Preconditions.notNull(member, "Member must not be null"); + return Modifier.isStatic(member.getModifiers()); + } + + @API(status = INTERNAL, since = "1.1") + public static boolean isNotStatic(Member member) { + return !isStatic(member); + } + + /** + * @since 1.5 + */ + @API(status = INTERNAL, since = "1.5") + public static boolean isFinal(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return Modifier.isFinal(clazz.getModifiers()); + } + + /** + * @since 1.5 + */ + @API(status = INTERNAL, since = "1.5") + public static boolean isNotFinal(Class clazz) { + return !isFinal(clazz); + } + + /** + * @since 1.5 + */ + @API(status = INTERNAL, since = "1.5") + public static boolean isFinal(Member member) { + Preconditions.notNull(member, "Member must not be null"); + return Modifier.isFinal(member.getModifiers()); + } + + /** + * @since 1.5 + */ + @API(status = INTERNAL, since = "1.5") + public static boolean isNotFinal(Member member) { + return !isFinal(member); + } + + /** + * Determine if the supplied class is an inner class (i.e., a + * non-static member class). + * + *

Technically speaking (i.e., according to the Java Language + * Specification), "an inner class may be a non-static member class, a + * local class, or an anonymous class." However, this method does not + * return {@code true} for a local or anonymous class. + * + * @param clazz the class to check; never {@code null} + * @return {@code true} if the class is an inner class + */ + public static boolean isInnerClass(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return !isStatic(clazz) && clazz.isMemberClass(); + } + + public static boolean returnsVoid(Method method) { + return method.getReturnType().equals(Void.TYPE); + } + + /** + * Determine if the supplied object is an array. + * + * @param obj the object to test; potentially {@code null} + * @return {@code true} if the object is an array + */ + public static boolean isArray(Object obj) { + return (obj != null && obj.getClass().isArray()); + } + + /** + * Determine if the supplied object is a multidimensional array. + * + * @param obj the object to test; potentially {@code null} + * @return {@code true} if the object is a multidimensional array + * @since 1.3.2 + */ + @API(status = INTERNAL, since = "1.3.2") + public static boolean isMultidimensionalArray(Object obj) { + return (obj != null && obj.getClass().isArray() && obj.getClass().getComponentType().isArray()); + } + + /** + * Determine if an object of the supplied source type can be assigned to the + * supplied target type for the purpose of reflective method invocations. + * + *

In contrast to {@link Class#isAssignableFrom(Class)}, this method + * returns {@code true} if the target type represents a primitive type whose + * wrapper matches the supplied source type. In addition, this method + * also supports + * + * widening conversions for primitive target types. + * + * @param sourceType the non-primitive target type; never {@code null} + * @param targetType the target type; never {@code null} + * @return {@code true} if an object of the source type is assignment compatible + * with the target type + * @since 1.8 + * @see Class#isInstance(Object) + * @see Class#isAssignableFrom(Class) + * @see #isAssignableTo(Object, Class) + */ + public static boolean isAssignableTo(Class sourceType, Class targetType) { + Preconditions.notNull(sourceType, "source type must not be null"); + Preconditions.condition(!sourceType.isPrimitive(), "source type must not be a primitive type"); + Preconditions.notNull(targetType, "target type must not be null"); + + if (targetType.isAssignableFrom(sourceType)) { + return true; + } + + if (targetType.isPrimitive()) { + return sourceType == primitiveToWrapperMap.get(targetType) || isWideningConversion(sourceType, targetType); + } + + return false; + } + + /** + * Determine if the supplied object can be assigned to the supplied target + * type for the purpose of reflective method invocations. + * + *

In contrast to {@link Class#isInstance(Object)}, this method returns + * {@code true} if the target type represents a primitive type whose + * wrapper matches the supplied object's type. In addition, this method + * also supports + * + * widening conversions for primitive types and their corresponding + * wrapper types. + * + *

If the supplied object is {@code null} and the supplied type does not + * represent a primitive type, this method returns {@code true}. + * + * @param obj the object to test for assignment compatibility; potentially {@code null} + * @param targetType the type to check against; never {@code null} + * @return {@code true} if the object is assignment compatible + * @see Class#isInstance(Object) + * @see Class#isAssignableFrom(Class) + * @see #isAssignableTo(Class, Class) + */ + public static boolean isAssignableTo(Object obj, Class targetType) { + Preconditions.notNull(targetType, "target type must not be null"); + + if (obj == null) { + return !targetType.isPrimitive(); + } + + if (targetType.isInstance(obj)) { + return true; + } + + if (targetType.isPrimitive()) { + Class sourceType = obj.getClass(); + return sourceType == primitiveToWrapperMap.get(targetType) || isWideningConversion(sourceType, targetType); + } + + return false; + } + + /** + * Determine if Java supports a widening primitive conversion from the + * supplied source type to the supplied primitive target type. + */ + static boolean isWideningConversion(Class sourceType, Class targetType) { + Preconditions.condition(targetType.isPrimitive(), "targetType must be primitive"); + + boolean isPrimitive = sourceType.isPrimitive(); + boolean isWrapper = primitiveToWrapperMap.containsValue(sourceType); + + // Neither a primitive nor a wrapper? + if (!isPrimitive && !isWrapper) { + return false; + } + + if (isPrimitive) { + sourceType = primitiveToWrapperMap.get(sourceType); + } + + // @formatter:off + if (sourceType == Byte.class) { + return + targetType == short.class || + targetType == int.class || + targetType == long.class || + targetType == float.class || + targetType == double.class; + } + + if (sourceType == Short.class || sourceType == Character.class) { + return + targetType == int.class || + targetType == long.class || + targetType == float.class || + targetType == double.class; + } + + if (sourceType == Integer.class) { + return + targetType == long.class || + targetType == float.class || + targetType == double.class; + } + + if (sourceType == Long.class) { + return + targetType == float.class || + targetType == double.class; + } + + if (sourceType == Float.class) { + return + targetType == double.class; + } + // @formatter:on + + return false; + } + + /** + * Get the wrapper type for the supplied primitive type. + * + * @param type the primitive type for which to retrieve the wrapper type + * @return the corresponding wrapper type or {@code null} if the + * supplied type is {@code null} or not a primitive type + */ + public static Class getWrapperType(Class type) { + return primitiveToWrapperMap.get(type); + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#newInstance(Class, Object...) + * @see #newInstance(Constructor, Object...) + */ + public static T newInstance(Class clazz, Object... args) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(args, "Argument array must not be null"); + Preconditions.containsNoNullElements(args, "Individual arguments must not be null"); + + try { + Class[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); + return newInstance(clazz.getDeclaredConstructor(parameterTypes), args); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); + } + } + + /** + * Create a new instance of type {@code T} by invoking the supplied constructor + * with the supplied arguments. + * + *

The constructor will be made accessible if necessary, and any checked + * exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked} + * as an unchecked exception. + * + * @param constructor the constructor to invoke; never {@code null} + * @param args the arguments to pass to the constructor + * @return the new instance; never {@code null} + * @see #newInstance(Class, Object...) + * @see ExceptionUtils#throwAsUncheckedException(Throwable) + */ + public static T newInstance(Constructor constructor, Object... args) { + Preconditions.notNull(constructor, "Constructor must not be null"); + + try { + return makeAccessible(constructor).newInstance(args); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); + } + } + + /** + * Read the value of a potentially inaccessible or nonexistent field. + * + *

If the field does not exist or the value of the field is {@code null}, + * an empty {@link Optional} will be returned. + * + * @param clazz the class where the field is declared; never {@code null} + * @param fieldName the name of the field; never {@code null} or empty + * @param instance the instance from where the value is to be read; may + * be {@code null} for a static field + * @see #readFieldValue(Field) + * @see #readFieldValue(Field, Object) + * @deprecated Please use {@link #tryToReadFieldValue(Class, String, Object)} + * instead. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + public static Optional readFieldValue(Class clazz, String fieldName, T instance) { + return tryToReadFieldValue(clazz, fieldName, instance).toOptional(); + } + + /** + * Try to read the value of a potentially inaccessible or nonexistent field. + * + *

If the field does not exist or an exception occurs while reading it, a + * failed {@link Try} is returned that contains the corresponding exception. + * + * @param clazz the class where the field is declared; never {@code null} + * @param fieldName the name of the field; never {@code null} or empty + * @param instance the instance from where the value is to be read; may + * be {@code null} for a static field + * @since 1.4 + * @see #tryToReadFieldValue(Field) + * @see #tryToReadFieldValue(Field, Object) + */ + @API(status = INTERNAL, since = "1.4") + public static Try tryToReadFieldValue(Class clazz, String fieldName, T instance) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notBlank(fieldName, "Field name must not be null or blank"); + + // @formatter:off + return Try.call(() -> clazz.getDeclaredField(fieldName)) + .andThen(field -> tryToReadFieldValue(field, instance)); + // @formatter:on + } + + /** + * Read the value of the supplied static field, making it accessible if + * necessary and {@linkplain ExceptionUtils#throwAsUncheckedException masking} + * any checked exception as an unchecked exception. + * + *

If the value of the field is {@code null}, an empty {@link Optional} + * will be returned. + * + * @param field the field to read; never {@code null} + * @see #readFieldValue(Field, Object) + * @see #readFieldValue(Class, String, Object) + * @deprecated Please use {@link #tryToReadFieldValue(Field)} instead. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + public static Optional readFieldValue(Field field) { + return tryToReadFieldValue(field).toOptional(); + } + + /** + * Try to read the value of a potentially inaccessible static field. + * + *

If an exception occurs while reading the field, a failed {@link Try} + * is returned that contains the corresponding exception. + * + * @param field the field to read; never {@code null} + * @since 1.4 + * @see #tryToReadFieldValue(Field, Object) + * @see #tryToReadFieldValue(Class, String, Object) + */ + @API(status = INTERNAL, since = "1.4") + public static Try tryToReadFieldValue(Field field) { + return tryToReadFieldValue(field, null); + } + + /** + * Read the value of the supplied field, making it accessible if necessary + * and {@linkplain ExceptionUtils#throwAsUncheckedException masking} any + * checked exception as an unchecked exception. + * + *

If the value of the field is {@code null}, an empty {@link Optional} + * will be returned. + * + * @param field the field to read; never {@code null} + * @param instance the instance from which the value is to be read; may + * be {@code null} for a static field + * @see #readFieldValue(Field) + * @see #readFieldValue(Class, String, Object) + * @deprecated Please use {@link #tryToReadFieldValue(Field, Object)} + * instead. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + public static Optional readFieldValue(Field field, Object instance) { + return tryToReadFieldValue(field, instance).toOptional(); + } + + /** + * @since 1.4 + * @see org.junit.platform.commons.support.ReflectionSupport#tryToReadFieldValue(Field, Object) + * @see #tryToReadFieldValue(Class, String, Object) + */ + @API(status = INTERNAL, since = "1.4") + public static Try tryToReadFieldValue(Field field, Object instance) { + Preconditions.notNull(field, "Field must not be null"); + Preconditions.condition((instance != null || isStatic(field)), + () -> String.format("Cannot read non-static field [%s] on a null instance.", field)); + + return Try.call(() -> makeAccessible(field).get(instance)); + } + + /** + * Read the values of the supplied fields, making each field accessible if + * necessary and {@linkplain ExceptionUtils#throwAsUncheckedException masking} + * any checked exception as an unchecked exception. + * + * @param fields the list of fields to read; never {@code null} + * @param instance the instance from which the values are to be read; may + * be {@code null} for static fields + * @return an immutable list of the values of the specified fields; never + * {@code null} but may be empty or contain {@code null} entries + */ + public static List readFieldValues(List fields, Object instance) { + return readFieldValues(fields, instance, field -> true); + } + + /** + * Read the values of the supplied fields, making each field accessible if + * necessary, {@linkplain ExceptionUtils#throwAsUncheckedException masking} + * any checked exception as an unchecked exception, and filtering out fields + * that do not pass the supplied {@code predicate}. + * + * @param fields the list of fields to read; never {@code null} + * @param instance the instance from which the values are to be read; may + * be {@code null} for static fields + * @param predicate the field filter; never {@code null} + * @return an immutable list of the values of the specified fields; never + * {@code null} but may be empty or contain {@code null} entries + */ + public static List readFieldValues(List fields, Object instance, Predicate predicate) { + Preconditions.notNull(fields, "fields list must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + + // @formatter:off + return fields.stream() + .filter(predicate) + .map(field -> + tryToReadFieldValue(field, instance) + .getOrThrow(ExceptionUtils::throwAsUncheckedException)) + .collect(toUnmodifiableList()); + // @formatter:on + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#invokeMethod(Method, Object, Object...) + */ + public static Object invokeMethod(Method method, Object target, Object... args) { + Preconditions.notNull(method, "Method must not be null"); + Preconditions.condition((target != null || isStatic(method)), + () -> String.format("Cannot invoke non-static method [%s] on a null target.", method.toGenericString())); + + try { + return makeAccessible(method).invoke(target, args); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); + } + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#loadClass(String) + * @deprecated Please use {@link #tryToLoadClass(String)} instead. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + public static Optional> loadClass(String name) { + return tryToLoadClass(name).toOptional(); + } + + /** + * @since 1.4 + * @see org.junit.platform.commons.support.ReflectionSupport#tryToLoadClass(String) + */ + @API(status = INTERNAL, since = "1.4") + public static Try> tryToLoadClass(String name) { + return tryToLoadClass(name, ClassLoaderUtils.getDefaultClassLoader()); + } + + /** + * Load a class by its primitive name or fully qualified name, + * using the supplied {@link ClassLoader}. + * + *

See {@link org.junit.platform.commons.support.ReflectionSupport#loadClass(String)} + * for details on support for class names for arrays. + * + * @param name the name of the class to load; never {@code null} or blank + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @see #loadClass(String) + * @deprecated Please use {@link #tryToLoadClass(String, ClassLoader)} + * instead. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + public static Optional> loadClass(String name, ClassLoader classLoader) { + return tryToLoadClass(name, classLoader).toOptional(); + } + + /** + * Try to load a class by its primitive name or fully qualified + * name, using the supplied {@link ClassLoader}. + * + *

See {@link org.junit.platform.commons.support.ReflectionSupport#tryToLoadClass(String)} + * for details on support for class names for arrays. + * + * @param name the name of the class to load; never {@code null} or blank + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @since 1.4 + * @see #tryToLoadClass(String) + */ + @API(status = INTERNAL, since = "1.4") + public static Try> tryToLoadClass(String name, ClassLoader classLoader) { + Preconditions.notBlank(name, "Class name must not be null or blank"); + Preconditions.notNull(classLoader, "ClassLoader must not be null"); + String trimmedName = name.trim(); + + if (classNameToTypeMap.containsKey(trimmedName)) { + return Try.success(classNameToTypeMap.get(trimmedName)); + } + + return Try.call(() -> { + Matcher matcher; + + // Primitive arrays such as "[I", "[[[[D", etc. + matcher = VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN.matcher(trimmedName); + if (matcher.matches()) { + String brackets = matcher.group(1); + String componentTypeName = matcher.group(2); + // Calculate dimensions by counting brackets. + int dimensions = brackets.length(); + + return loadArrayType(classLoader, componentTypeName, dimensions); + } + + // Object arrays such as "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. + matcher = VM_INTERNAL_OBJECT_ARRAY_PATTERN.matcher(trimmedName); + if (matcher.matches()) { + String brackets = matcher.group(1); + String componentTypeName = matcher.group(2); + // Calculate dimensions by counting brackets. + int dimensions = brackets.length(); + + return loadArrayType(classLoader, componentTypeName, dimensions); + } + + // Arrays such as "java.lang.String[]", "int[]", "int[][][][]", etc. + matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); + if (matcher.matches()) { + String componentTypeName = matcher.group(1); + String bracketPairs = matcher.group(2); + // Calculate dimensions by counting bracket pairs. + int dimensions = bracketPairs.length() / 2; + + return loadArrayType(classLoader, componentTypeName, dimensions); + } + + // Fallback to standard VM class loading + return classLoader.loadClass(trimmedName); + }); + } + + private static Class loadArrayType(ClassLoader classLoader, String componentTypeName, int dimensions) + throws ClassNotFoundException { + + Class componentType = classNameToTypeMap.containsKey(componentTypeName) + ? classNameToTypeMap.get(componentTypeName) + : classLoader.loadClass(componentTypeName); + + return Array.newInstance(componentType, new int[dimensions]).getClass(); + } + + /** + * Build the fully qualified method name for the method described by the + * supplied class and method. + * + *

Note that the class is not necessarily the class in which the method is + * declared. + * + * @param clazz the class from which the method should be referenced; never {@code null} + * @param method the method; never {@code null} + * @return fully qualified method name; never {@code null} + * @since 1.4 + * @see #getFullyQualifiedMethodName(Class, String, Class...) + */ + public static String getFullyQualifiedMethodName(Class clazz, Method method) { + Preconditions.notNull(method, "Method must not be null"); + + return getFullyQualifiedMethodName(clazz, method.getName(), method.getParameterTypes()); + } + + /** + * Build the fully qualified method name for the method described by the + * supplied class, method name, and parameter types. + * + *

Note that the class is not necessarily the class in which the method is + * declared. + * + * @param clazz the class from which the method should be referenced; never {@code null} + * @param methodName the name of the method; never {@code null} or blank + * @param parameterTypes the parameter types of the method; may be {@code null} or empty + * @return fully qualified method name; never {@code null} + * @see #getFullyQualifiedMethodName(Class, Method) + */ + public static String getFullyQualifiedMethodName(Class clazz, String methodName, Class... parameterTypes) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + + return String.format("%s#%s(%s)", clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes)); + } + + /** + * Parse the supplied fully qualified method name into a 3-element + * {@code String[]} with the following content. + * + *

    + *
  • index {@code 0}: the fully qualified class name
  • + *
  • index {@code 1}: the name of the method
  • + *
  • index {@code 2}: a comma-separated list of parameter types, or a + * blank string if the method does not declare any formal parameters
  • + *
+ * + * @param fullyQualifiedMethodName a fully qualified method name, + * never {@code null} or blank + * @return a 3-element array of strings containing the parsed values + */ + public static String[] parseFullyQualifiedMethodName(String fullyQualifiedMethodName) { + Preconditions.notBlank(fullyQualifiedMethodName, "fullyQualifiedMethodName must not be null or blank"); + + int indexOfFirstHashtag = fullyQualifiedMethodName.indexOf('#'); + boolean validSyntax = (indexOfFirstHashtag > 0) + && (indexOfFirstHashtag < fullyQualifiedMethodName.length() - 1); + + Preconditions.condition(validSyntax, + () -> "[" + fullyQualifiedMethodName + "] is not a valid fully qualified method name: " + + "it must start with a fully qualified class name followed by a '#' " + + "and then the method name, optionally followed by a parameter list enclosed in parentheses."); + + String className = fullyQualifiedMethodName.substring(0, indexOfFirstHashtag); + String methodPart = fullyQualifiedMethodName.substring(indexOfFirstHashtag + 1); + String methodName = methodPart; + String methodParameters = ""; + + if (methodPart.endsWith("()")) { + methodName = methodPart.substring(0, methodPart.length() - 2); + } + else if (methodPart.endsWith(")")) { + int indexOfLastOpeningParenthesis = methodPart.lastIndexOf('('); + if ((indexOfLastOpeningParenthesis > 0) && (indexOfLastOpeningParenthesis < methodPart.length() - 1)) { + methodName = methodPart.substring(0, indexOfLastOpeningParenthesis); + methodParameters = methodPart.substring(indexOfLastOpeningParenthesis + 1, methodPart.length() - 1); + } + } + return new String[] { className, methodName, methodParameters }; + } + + /** + * Get the outermost instance of the required type, searching recursively + * through enclosing instances. + * + *

If the supplied inner object is of the required type, it will be + * returned. + * + * @param inner the inner object from which to begin the search; never {@code null} + * @param requiredType the required type of the outermost instance; never {@code null} + * @return an {@code Optional} containing the outermost instance; never {@code null} + * but potentially empty + * @deprecated Please discontinue use of this method since it relies on internal + * implementation details of the JDK that may not work in the future. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + public static Optional getOutermostInstance(Object inner, Class requiredType) { + Preconditions.notNull(inner, "inner object must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + + if (requiredType.isInstance(inner)) { + return Optional.of(inner); + } + + Optional candidate = getOuterInstance(inner); + if (candidate.isPresent()) { + return getOutermostInstance(candidate.get(), requiredType); + } + + return Optional.empty(); + } + + private static Optional getOuterInstance(Object inner) { + // This is risky since it depends on the name of the field which is nowhere guaranteed + // but has been stable so far in all JDKs + + // @formatter:off + return Arrays.stream(inner.getClass().getDeclaredFields()) + .filter(field -> field.getName().startsWith("this$")) + .findFirst() + .map(field -> { + try { + return makeAccessible(field).get(inner); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(t); + } + }); + // @formatter:on + } + + public static Set getAllClasspathRootDirectories() { + // This is quite a hack, since sometimes the classpath is quite different + String fullClassPath = System.getProperty("java.class.path"); + // @formatter:off + return Arrays.stream(fullClassPath.split(File.pathSeparator)) + .map(Paths::get) + .filter(Files::isDirectory) + .collect(toSet()); + // @formatter:on + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInClasspathRoot(URI, Predicate, Predicate) + */ + public static List> findAllClassesInClasspathRoot(URI root, Predicate> classFilter, + Predicate classNameFilter) { + // unmodifiable since returned by public, non-internal method(s) + return findAllClassesInClasspathRoot(root, ClassFilter.of(classNameFilter, classFilter)); + } + + /** + * @since 1.1 + */ + public static List> findAllClassesInClasspathRoot(URI root, ClassFilter classFilter) { + return Collections.unmodifiableList(classpathScanner.scanForClassesInClasspathRoot(root, classFilter)); + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInPackage(String, Predicate, Predicate) + */ + public static List> findAllClassesInPackage(String basePackageName, Predicate> classFilter, + Predicate classNameFilter) { + // unmodifiable since returned by public, non-internal method(s) + return findAllClassesInPackage(basePackageName, ClassFilter.of(classNameFilter, classFilter)); + } + + /** + * @since 1.1 + */ + public static List> findAllClassesInPackage(String basePackageName, ClassFilter classFilter) { + return Collections.unmodifiableList(classpathScanner.scanForClassesInPackage(basePackageName, classFilter)); + } + + /** + * @since 1.1.1 + * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate) + */ + public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, + Predicate classNameFilter) { + // unmodifiable since returned by public, non-internal method(s) + return findAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter)); + } + + /** + * @since 1.1.1 + */ + public static List> findAllClassesInModule(String moduleName, ClassFilter classFilter) { + return Collections.unmodifiableList(ModuleUtils.findAllClassesInModule(moduleName, classFilter)); + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#findNestedClasses(Class, Predicate) + */ + public static List> findNestedClasses(Class clazz, Predicate> predicate) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + + Set> candidates = new LinkedHashSet<>(); + findNestedClasses(clazz, predicate, candidates); + return Collections.unmodifiableList(new ArrayList<>(candidates)); + } + + private static void findNestedClasses(Class clazz, Predicate> predicate, Set> candidates) { + if (!isSearchable(clazz)) { + return; + } + + if (isInnerClass(clazz) && predicate.test(clazz)) { + detectInnerClassCycle(clazz); + } + + try { + // Candidates in current class + for (Class nestedClass : clazz.getDeclaredClasses()) { + if (predicate.test(nestedClass)) { + detectInnerClassCycle(nestedClass); + candidates.add(nestedClass); + } + } + } + catch (NoClassDefFoundError error) { + logger.debug(error, () -> "Failed to retrieve declared classes for " + clazz.getName()); + } + + // Search class hierarchy + findNestedClasses(clazz.getSuperclass(), predicate, candidates); + + // Search interface hierarchy + for (Class ifc : clazz.getInterfaces()) { + findNestedClasses(ifc, predicate, candidates); + } + } + + /** + * Detect a cycle in the inner class hierarchy in which the supplied class + * resides — from the supplied class up to the outermost enclosing class + * — and throw a {@link JUnitException} if a cycle is detected. + * + *

This method does not detect cycles within inner class + * hierarchies below the supplied class. + * + *

If the supplied class is not an inner class and does not have a + * searchable superclass, this method is effectively a no-op. + * + * @since 1.6 + * @see #isInnerClass(Class) + * @see #isSearchable(Class) + */ + private static void detectInnerClassCycle(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + String className = clazz.getName(); + + if (noCyclesDetectedCache.contains(className)) { + return; + } + + Class superclass = clazz.getSuperclass(); + if (isInnerClass(clazz) && isSearchable(superclass)) { + for (Class enclosing = clazz.getEnclosingClass(); enclosing != null; enclosing = enclosing.getEnclosingClass()) { + if (superclass.equals(enclosing)) { + throw new JUnitException(String.format("Detected cycle in inner class hierarchy between %s and %s", + className, enclosing.getName())); + } + } + } + + noCyclesDetectedCache.add(className); + } + + /** + * Get the sole declared, non-synthetic {@link Constructor} for the supplied class. + * + *

Throws a {@link org.junit.platform.commons.PreconditionViolationException} + * if the supplied class declares more than one non-synthetic constructor. + * + * @param clazz the class to get the constructor for + * @return the sole declared constructor; never {@code null} + * @see Class#getDeclaredConstructors() + * @see Class#isSynthetic() + */ + @SuppressWarnings("unchecked") + public static Constructor getDeclaredConstructor(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + try { + List> constructors = Arrays.stream(clazz.getDeclaredConstructors())// + .filter(ctor -> !ctor.isSynthetic())// + .collect(toList()); + + Preconditions.condition(constructors.size() == 1, + () -> String.format("Class [%s] must declare a single constructor", clazz.getName())); + + return (Constructor) constructors.get(0); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); + } + } + + /** + * Find all constructors in the supplied class that match the supplied predicate. + * + *

Note that this method may return {@linkplain Class#isSynthetic() synthetic} + * constructors. If you wish to ignore synthetic constructors, you may filter + * them out with the supplied {@code predicate} or filter them out of the list + * returned by this method. + * + * @param clazz the class in which to search for constructors; never {@code null} + * @param predicate the predicate to use to test for a match; never {@code null} + * @return an immutable list of all such constructors found; never {@code null} + * but potentially empty + * @see Class#getDeclaredConstructors() + * @see Class#isSynthetic() + */ + public static List> findConstructors(Class clazz, Predicate> predicate) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + + try { + // @formatter:off + return Arrays.stream(clazz.getDeclaredConstructors()) + .filter(predicate) + .collect(toUnmodifiableList()); + // @formatter:on + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); + } + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#findFields(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) + */ + public static List findFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + // @formatter:off + return findAllFieldsInHierarchy(clazz, traversalMode).stream() + .filter(predicate) + // unmodifiable since returned by public, non-internal method(s) + .collect(toUnmodifiableList()); + // @formatter:on + } + + private static List findAllFieldsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + // @formatter:off + List localFields = getDeclaredFields(clazz).stream() + .filter(field -> !field.isSynthetic()) + .collect(toList()); + List superclassFields = getSuperclassFields(clazz, traversalMode).stream() + .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) + .collect(toList()); + List interfaceFields = getInterfaceFields(clazz, traversalMode).stream() + .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) + .collect(toList()); + // @formatter:on + + List fields = new ArrayList<>(); + if (traversalMode == TOP_DOWN) { + fields.addAll(superclassFields); + fields.addAll(interfaceFields); + } + fields.addAll(localFields); + if (traversalMode == BOTTOM_UP) { + fields.addAll(interfaceFields); + fields.addAll(superclassFields); + } + return fields; + } + + /** + * Determine if a {@link Method} matching the supplied {@link Predicate} + * is present within the type hierarchy of the specified class, beginning + * with the specified class or interface and traversing up the type + * hierarchy until such a method is found or the type hierarchy is exhausted. + * + * @param clazz the class or interface in which to find the method; never + * {@code null} + * @param predicate the predicate to use to test for a match; never + * {@code null} + * @return {@code true} if such a method is present + * @see #findMethod(Class, String, String) + * @see #findMethod(Class, String, Class...) + */ + public static boolean isMethodPresent(Class clazz, Predicate predicate) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + + return findMethod(clazz, predicate).isPresent(); + } + + /** + * Get the {@link Method} in the specified class with the specified name + * and parameter types. + * + *

This method delegates to {@link Class#getMethod(String, Class...)} but + * swallows any exception thrown. + * + * @param clazz the class in which to search for the method; never {@code null} + * @param methodName the name of the method to get; never {@code null} or blank + * @param parameterTypes the parameter types of the method; may be {@code null} + * or empty + * @return an {@code Optional} containing the method; never {@code null} but + * empty if the invocation of {@code Class#getMethod()} throws a + * {@link NoSuchMethodException} + * @deprecated Please use {@link #tryToGetMethod(Class, String, Class[])} + * instead. + */ + @API(status = DEPRECATED, since = "1.4") + @Deprecated + static Optional getMethod(Class clazz, String methodName, Class... parameterTypes) { + return tryToGetMethod(clazz, methodName, parameterTypes).toOptional(); + } + + /** + * Try to get the {@link Method} in the specified class with the specified + * name and parameter types. + * + *

This method delegates to {@link Class#getMethod(String, Class...)} but + * catches any exception thrown. + * + * @param clazz the class in which to search for the method; never {@code null} + * @param methodName the name of the method to get; never {@code null} or blank + * @param parameterTypes the parameter types of the method; may be {@code null} + * or empty + * @return a successful {@link Try} containing the method or a failed + * {@link Try} containing the {@link NoSuchMethodException} thrown by + * {@code Class#getMethod()}; never {@code null} + * @since 1.4 + */ + @API(status = INTERNAL, since = "1.4") + public static Try tryToGetMethod(Class clazz, String methodName, Class... parameterTypes) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + + return Try.call(() -> clazz.getMethod(methodName, parameterTypes)); + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, String) + */ + public static Optional findMethod(Class clazz, String methodName, String parameterTypeNames) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + return findMethod(clazz, methodName, resolveParameterTypes(clazz, methodName, parameterTypeNames)); + } + + private static Class[] resolveParameterTypes(Class clazz, String methodName, String parameterTypeNames) { + if (StringUtils.isBlank(parameterTypeNames)) { + return EMPTY_CLASS_ARRAY; + } + + // @formatter:off + return Arrays.stream(parameterTypeNames.split(",")) + .map(String::trim) + .map(typeName -> loadRequiredParameterType(clazz, methodName, typeName)) + .toArray(Class[]::new); + // @formatter:on + } + + private static Class loadRequiredParameterType(Class clazz, String methodName, String typeName) { + // @formatter:off + return tryToLoadClass(typeName) + .getOrThrow(cause -> new JUnitException( + String.format("Failed to load parameter type [%s] for method [%s] in class [%s].", + typeName, methodName, clazz.getName()), cause)); + // @formatter:on + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, Class...) + */ + public static Optional findMethod(Class clazz, String methodName, Class... parameterTypes) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + Preconditions.notNull(parameterTypes, "Parameter types array must not be null"); + Preconditions.containsNoNullElements(parameterTypes, "Individual parameter types must not be null"); + + return findMethod(clazz, method -> hasCompatibleSignature(method, methodName, parameterTypes)); + } + + private static Optional findMethod(Class clazz, Predicate predicate) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + + for (Class current = clazz; isSearchable(current); current = current.getSuperclass()) { + // Search for match in current type + List methods = current.isInterface() ? getMethods(current) : getDeclaredMethods(current, BOTTOM_UP); + for (Method method : methods) { + if (predicate.test(method)) { + return Optional.of(method); + } + } + + // Search for match in interfaces implemented by current type + for (Class ifc : current.getInterfaces()) { + Optional optional = findMethod(ifc, predicate); + if (optional.isPresent()) { + return optional; + } + } + } + + return Optional.empty(); + } + + /** + * Find the first {@link Method} of the supplied class or interface that + * meets the specified criteria, beginning with the specified class or + * interface and traversing up the type hierarchy until such a method is + * found or the type hierarchy is exhausted. + * + *

Use this method as an alternative to + * {@link #findMethod(Class, String, Class...)} for use cases in which the + * method is required to be present. + * + * @param clazz the class or interface in which to find the method; + * never {@code null} + * @param methodName the name of the method to find; never {@code null} + * or empty + * @param parameterTypes the types of parameters accepted by the method, + * if any; never {@code null} + * @return the {@code Method} found; never {@code null} + * @throws JUnitException if no method is found + * + * @since 1.7 + * @see #findMethod(Class, String, Class...) + */ + @API(status = STABLE, since = "1.7") + public static Method getRequiredMethod(Class clazz, String methodName, Class... parameterTypes) { + return ReflectionUtils.findMethod(clazz, methodName, parameterTypes).orElseThrow( + () -> new JUnitException(format("Could not find method [%s] in class [%s]", methodName, clazz.getName()))); + } + + /** + * Find all {@linkplain Method methods} of the supplied class or interface + * that match the specified {@code predicate}, using top-down search semantics + * within the type hierarchy. + * + *

The results will not contain instance methods that are overridden + * or {@code static} methods that are hidden. + * + * @param clazz the class or interface in which to find the methods; never {@code null} + * @param predicate the method filter; never {@code null} + * @return an immutable list of all such methods found; never {@code null} + * @see HierarchyTraversalMode#TOP_DOWN + * @see #findMethods(Class, Predicate, HierarchyTraversalMode) + */ + public static List findMethods(Class clazz, Predicate predicate) { + return findMethods(clazz, predicate, TOP_DOWN); + } + + /** + * @see org.junit.platform.commons.support.ReflectionSupport#findMethods(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) + */ + public static List findMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(predicate, "Predicate must not be null"); + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + // @formatter:off + return findAllMethodsInHierarchy(clazz, traversalMode).stream() + .filter(predicate) + .distinct() + // unmodifiable since returned by public, non-internal method(s) + .collect(toUnmodifiableList()); + // @formatter:on + } + + /** + * Find all non-synthetic methods in the superclass and interface hierarchy, + * excluding Object. + */ + private static List findAllMethodsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + // @formatter:off + List localMethods = getDeclaredMethods(clazz, traversalMode).stream() + .filter(method -> !method.isSynthetic()) + .collect(toList()); + List superclassMethods = getSuperclassMethods(clazz, traversalMode).stream() + .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) + .collect(toList()); + List interfaceMethods = getInterfaceMethods(clazz, traversalMode).stream() + .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) + .collect(toList()); + // @formatter:on + + List methods = new ArrayList<>(); + if (traversalMode == TOP_DOWN) { + methods.addAll(superclassMethods); + methods.addAll(interfaceMethods); + } + methods.addAll(localMethods); + if (traversalMode == BOTTOM_UP) { + methods.addAll(interfaceMethods); + methods.addAll(superclassMethods); + } + return methods; + } + + /** + * Custom alternative to {@link Class#getFields()} that sorts the fields + * and converts them to a mutable list. + */ + private static List getFields(Class clazz) { + return toSortedMutableList(clazz.getFields()); + } + + /** + * Custom alternative to {@link Class#getDeclaredFields()} that sorts the + * fields and converts them to a mutable list. + */ + private static List getDeclaredFields(Class clazz) { + return toSortedMutableList(clazz.getDeclaredFields()); + } + + /** + * Custom alternative to {@link Class#getMethods()} that sorts the methods + * and converts them to a mutable list. + */ + private static List getMethods(Class clazz) { + return toSortedMutableList(clazz.getMethods()); + } + + /** + * Custom alternative to {@link Class#getDeclaredMethods()} that sorts the + * methods and converts them to a mutable list. + * + *

In addition, the list returned by this method includes interface + * default methods which are either prepended or appended to the list of + * declared methods depending on the supplied traversal mode. + */ + private static List getDeclaredMethods(Class clazz, HierarchyTraversalMode traversalMode) { + // Note: getDefaultMethods() already sorts the methods, + List defaultMethods = getDefaultMethods(clazz); + List declaredMethods = toSortedMutableList(clazz.getDeclaredMethods()); + + // Take the traversal mode into account in order to retain the inherited + // nature of interface default methods. + if (traversalMode == BOTTOM_UP) { + declaredMethods.addAll(defaultMethods); + return declaredMethods; + } + else { + defaultMethods.addAll(declaredMethods); + return defaultMethods; + } + } + + /** + * Get a sorted, mutable list of all default methods present in interfaces + * implemented by the supplied class which are also visible within + * the supplied class. + * + * @see Method Visibility + * in the Java Language Specification + */ + private static List getDefaultMethods(Class clazz) { + // @formatter:off + // Visible default methods are interface default methods that have not + // been overridden. + List visibleDefaultMethods = Arrays.stream(clazz.getMethods()) + .filter(Method::isDefault) + .collect(toCollection(ArrayList::new)); + if (visibleDefaultMethods.isEmpty()) { + return visibleDefaultMethods; + } + return Arrays.stream(clazz.getInterfaces()) + .map(ReflectionUtils::getMethods) + .flatMap(List::stream) + .filter(visibleDefaultMethods::contains) + .collect(toCollection(ArrayList::new)); + // @formatter:on + } + + private static List toSortedMutableList(Field[] fields) { + // @formatter:off + return Arrays.stream(fields) + .sorted(ReflectionUtils::defaultFieldSorter) + // Use toCollection() instead of toList() to ensure list is mutable. + .collect(toCollection(ArrayList::new)); + // @formatter:on + } + + private static List toSortedMutableList(Method[] methods) { + // @formatter:off + return Arrays.stream(methods) + .sorted(ReflectionUtils::defaultMethodSorter) + // Use toCollection() instead of toList() to ensure list is mutable. + .collect(toCollection(ArrayList::new)); + // @formatter:on + } + + /** + * Field comparator inspired by JUnit 4's {@code org.junit.internal.MethodSorter} + * implementation. + */ + private static int defaultFieldSorter(Field field1, Field field2) { + return Integer.compare(field1.getName().hashCode(), field2.getName().hashCode()); + } + + /** + * Method comparator based upon JUnit 4's {@code org.junit.internal.MethodSorter} + * implementation. + */ + private static int defaultMethodSorter(Method method1, Method method2) { + String name1 = method1.getName(); + String name2 = method2.getName(); + int comparison = Integer.compare(name1.hashCode(), name2.hashCode()); + if (comparison == 0) { + comparison = name1.compareTo(name2); + if (comparison == 0) { + comparison = method1.toString().compareTo(method2.toString()); + } + } + return comparison; + } + + private static List getInterfaceMethods(Class clazz, HierarchyTraversalMode traversalMode) { + List allInterfaceMethods = new ArrayList<>(); + for (Class ifc : clazz.getInterfaces()) { + + // @formatter:off + List localInterfaceMethods = getMethods(ifc).stream() + .filter(m -> !isAbstract(m)) + .collect(toList()); + + List superinterfaceMethods = getInterfaceMethods(ifc, traversalMode).stream() + .filter(method -> !isMethodShadowedByLocalMethods(method, localInterfaceMethods)) + .collect(toList()); + // @formatter:on + + if (traversalMode == TOP_DOWN) { + allInterfaceMethods.addAll(superinterfaceMethods); + } + allInterfaceMethods.addAll(localInterfaceMethods); + if (traversalMode == BOTTOM_UP) { + allInterfaceMethods.addAll(superinterfaceMethods); + } + } + return allInterfaceMethods; + } + + private static List getInterfaceFields(Class clazz, HierarchyTraversalMode traversalMode) { + List allInterfaceFields = new ArrayList<>(); + for (Class ifc : clazz.getInterfaces()) { + List localInterfaceFields = getFields(ifc); + + // @formatter:off + List superinterfaceFields = getInterfaceFields(ifc, traversalMode).stream() + .filter(field -> !isFieldShadowedByLocalFields(field, localInterfaceFields)) + .collect(toList()); + // @formatter:on + + if (traversalMode == TOP_DOWN) { + allInterfaceFields.addAll(superinterfaceFields); + } + allInterfaceFields.addAll(localInterfaceFields); + if (traversalMode == BOTTOM_UP) { + allInterfaceFields.addAll(superinterfaceFields); + } + } + return allInterfaceFields; + } + + private static List getSuperclassFields(Class clazz, HierarchyTraversalMode traversalMode) { + Class superclass = clazz.getSuperclass(); + if (!isSearchable(superclass)) { + return Collections.emptyList(); + } + return findAllFieldsInHierarchy(superclass, traversalMode); + } + + private static boolean isFieldShadowedByLocalFields(Field field, List localFields) { + return localFields.stream().anyMatch(local -> local.getName().equals(field.getName())); + } + + private static List getSuperclassMethods(Class clazz, HierarchyTraversalMode traversalMode) { + Class superclass = clazz.getSuperclass(); + if (!isSearchable(superclass)) { + return Collections.emptyList(); + } + return findAllMethodsInHierarchy(superclass, traversalMode); + } + + private static boolean isMethodShadowedByLocalMethods(Method method, List localMethods) { + return localMethods.stream().anyMatch(local -> isMethodShadowedBy(method, local)); + } + + private static boolean isMethodShadowedBy(Method upper, Method lower) { + return hasCompatibleSignature(upper, lower.getName(), lower.getParameterTypes()); + } + + /** + * Determine if the supplied candidate method (typically a method higher in + * the type hierarchy) has a signature that is compatible with a method that + * has the supplied name and parameter types, taking method sub-signatures + * and generics into account. + */ + private static boolean hasCompatibleSignature(Method candidate, String methodName, Class[] parameterTypes) { + if (!methodName.equals(candidate.getName())) { + return false; + } + if (parameterTypes.length != candidate.getParameterCount()) { + return false; + } + // trivial case: parameter types exactly match + if (Arrays.equals(parameterTypes, candidate.getParameterTypes())) { + return true; + } + // param count is equal, but types do not match exactly: check for method sub-signatures + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2 + for (int i = 0; i < parameterTypes.length; i++) { + Class lowerType = parameterTypes[i]; + Class upperType = candidate.getParameterTypes()[i]; + if (!upperType.isAssignableFrom(lowerType)) { + return false; + } + } + // lower is sub-signature of upper: check for generics in upper method + if (isGeneric(candidate)) { + return true; + } + return false; + } + + static boolean isGeneric(Method method) { + return isGeneric(method.getGenericReturnType()) + || Arrays.stream(method.getGenericParameterTypes()).anyMatch(ReflectionUtils::isGeneric); + } + + private static boolean isGeneric(Type type) { + return type instanceof TypeVariable || type instanceof GenericArrayType; + } + + @SuppressWarnings("deprecation") // "AccessibleObject.isAccessible()" is deprecated in Java 9 + public static T makeAccessible(T object) { + if (!object.isAccessible()) { + object.setAccessible(true); + } + return object; + } + + /** + * Return all classes and interfaces that can be used as assignment types + * for instances of the specified {@link Class}, including itself. + * + * @param clazz the {@code Class} to look up + * @see Class#isAssignableFrom + */ + public static Set> getAllAssignmentCompatibleClasses(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + + Set> result = new LinkedHashSet<>(); + getAllAssignmentCompatibleClasses(clazz, result); + return result; + } + + private static void getAllAssignmentCompatibleClasses(Class clazz, Set> result) { + for (Class current = clazz; current != null; current = current.getSuperclass()) { + result.add(current); + for (Class interfaceClass : current.getInterfaces()) { + if (!result.contains(interfaceClass)) { + getAllAssignmentCompatibleClasses(interfaceClass, result); + } + } + } + } + + /** + * Determine if the supplied class is searchable: is non-null and is + * not equal to the class reference for {@code java.lang.Object}. + * + *

This method is often used to determine if a superclass should be + * searched but may be applicable for other use cases as well. + * @since 1.6 + */ + private static boolean isSearchable(Class clazz) { + return (clazz != null && clazz != Object.class); + } + + /** + * Get the underlying cause of the supplied {@link Throwable}. + * + *

If the supplied {@code Throwable} is an instance of + * {@link InvocationTargetException}, this method will be invoked + * recursively with the underlying + * {@linkplain InvocationTargetException#getTargetException() target + * exception}; otherwise, this method returns the supplied {@code Throwable}. + */ + private static Throwable getUnderlyingCause(Throwable t) { + if (t instanceof InvocationTargetException) { + return getUnderlyingCause(((InvocationTargetException) t).getTargetException()); + } + return t; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java new file mode 100644 index 00000000..cdbdc1fb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@link Runtime}, + * {@link java.lang.management.RuntimeMXBean}, etc. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.6 + */ +@API(status = INTERNAL, since = "1.6") +public final class RuntimeUtils { + + private RuntimeUtils() { + /* no-op */ + } + + /** + * Try to determine whether the VM was started in debug mode or not. + */ + public static boolean isDebugMode() { + return getInputArguments() // + .map(args -> args.stream().anyMatch( + arg -> arg.startsWith("-agentlib:jdwp") || arg.startsWith("-Xrunjdwp"))) // + .orElse(false); + } + + /** + * Try to get the input arguments the VM was started with. + */ + static Optional> getInputArguments() { + Optional> managementFactoryClass = ReflectionUtils.tryToLoadClass( + "java.lang.management.ManagementFactory").toOptional(); + if (!managementFactoryClass.isPresent()) { + return Optional.empty(); + } + // Can't use "java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments()" + // directly as module "java.management" might not be available and/or the current platform + // doesn't support the Java Management Extensions (JMX) API (like Android?). + // See https://github.com/junit-team/junit4/pull/1187 + try { + Object bean = managementFactoryClass.get().getMethod("getRuntimeMXBean").invoke(null); + Class mx = ReflectionUtils.tryToLoadClass("java.lang.management.RuntimeMXBean").get(); + @SuppressWarnings("unchecked") + List args = (List) mx.getMethod("getInputArguments").invoke(bean); + return Optional.of(args); + } + catch (Exception e) { + return Optional.empty(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java new file mode 100644 index 00000000..11155c0c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java @@ -0,0 +1,259 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Arrays; +import java.util.regex.Pattern; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@link String Strings}, + * {@link CharSequence CharSequences}, etc. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public final class StringUtils { + + private static final Pattern ISO_CONTROL_PATTERN = compileIsoControlPattern(); + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s"); + + /** + * Guard against "IllegalArgumentException: Unsupported flags: 256" errors. + * @see #1800 + */ + static Pattern compileIsoControlPattern() { + // https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#posix + try { + // All of the characters that Unicode refers to as 'control characters' + return Pattern.compile("\\p{Cntrl}", UNICODE_CHARACTER_CLASS); + } + catch (IllegalArgumentException e) { + // Fall-back to ASCII control characters only: [\x00-\x1F\x7F] + return Pattern.compile("\\p{Cntrl}"); + } + } + + private StringUtils() { + /* no-op */ + } + + /** + * Determine if the supplied {@link String} is blank (i.e., + * {@code null} or consisting only of whitespace characters). + * + * @param str the string to check; may be {@code null} + * @return {@code true} if the string is blank + * @see #isNotBlank(String) + */ + public static boolean isBlank(String str) { + return (str == null || str.trim().isEmpty()); + } + + /** + * Determine if the supplied {@link String} is not {@linkplain #isBlank + * blank}. + * + * @param str the string to check; may be {@code null} + * @return {@code true} if the string is not blank + * @see #isBlank(String) + */ + public static boolean isNotBlank(String str) { + return !isBlank(str); + } + + /** + * Determine if the supplied {@link String} contains any whitespace characters. + * + * @param str the string to check; may be {@code null} + * @return {@code true} if the string contains whitespace + * @see #containsIsoControlCharacter(String) + * @see Character#isWhitespace(int) + */ + public static boolean containsWhitespace(String str) { + return str != null && str.codePoints().anyMatch(Character::isWhitespace); + } + + /** + * Determine if the supplied {@link String} does not contain any whitespace + * characters. + * + * @param str the string to check; may be {@code null} + * @return {@code true} if the string does not contain whitespace + * @see #containsWhitespace(String) + * @see #containsIsoControlCharacter(String) + * @see Character#isWhitespace(int) + */ + public static boolean doesNotContainWhitespace(String str) { + return !containsWhitespace(str); + } + + /** + * Determine if the supplied {@link String} contains any ISO control characters. + * + * @param str the string to check; may be {@code null} + * @return {@code true} if the string contains an ISO control character + * @see #containsWhitespace(String) + * @see Character#isISOControl(int) + */ + public static boolean containsIsoControlCharacter(String str) { + return str != null && str.codePoints().anyMatch(Character::isISOControl); + } + + /** + * Determine if the supplied {@link String} does not contain any ISO control + * characters. + * + * @param str the string to check; may be {@code null} + * @return {@code true} if the string does not contain an ISO control character + * @see #containsIsoControlCharacter(String) + * @see #containsWhitespace(String) + * @see Character#isISOControl(int) + */ + public static boolean doesNotContainIsoControlCharacter(String str) { + return !containsIsoControlCharacter(str); + } + + /** + * Convert the supplied {@code Object} to a {@code String} using the + * following algorithm. + * + *

    + *
  • If the supplied object is {@code null}, this method returns {@code "null"}.
  • + *
  • If the supplied object is a primitive array, the appropriate + * {@code Arrays#toString(...)} variant will be used to convert it to a String.
  • + *
  • If the supplied object is an object array, {@code Arrays#deepToString(Object[])} + * will be used to convert it to a String.
  • + *
  • Otherwise, {@code toString()} will be invoked on the object. If the + * result is non-null, that result will be returned. If the result is + * {@code null}, {@code "null"} will be returned.
  • + *
  • If any of the above results in an exception, this method delegates to + * {@link #defaultToString(Object)}
  • + *
+ * + * @param obj the object to convert to a String; may be {@code null} + * @return a String representation of the supplied object; never {@code null} + * @see Arrays#deepToString(Object[]) + * @see ClassUtils#nullSafeToString(Class...) + */ + public static String nullSafeToString(Object obj) { + if (obj == null) { + return "null"; + } + + try { + if (obj.getClass().isArray()) { + if (obj.getClass().getComponentType().isPrimitive()) { + if (obj instanceof boolean[]) { + return Arrays.toString((boolean[]) obj); + } + if (obj instanceof char[]) { + return Arrays.toString((char[]) obj); + } + if (obj instanceof short[]) { + return Arrays.toString((short[]) obj); + } + if (obj instanceof byte[]) { + return Arrays.toString((byte[]) obj); + } + if (obj instanceof int[]) { + return Arrays.toString((int[]) obj); + } + if (obj instanceof long[]) { + return Arrays.toString((long[]) obj); + } + if (obj instanceof float[]) { + return Arrays.toString((float[]) obj); + } + if (obj instanceof double[]) { + return Arrays.toString((double[]) obj); + } + } + return Arrays.deepToString((Object[]) obj); + } + + // else + String result = obj.toString(); + return result != null ? result : "null"; + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + + return defaultToString(obj); + } + } + + /** + * Convert the supplied {@code Object} to a default {@code String} + * representation using the following algorithm. + * + *
    + *
  • If the supplied object is {@code null}, this method returns {@code "null"}.
  • + *
  • Otherwise, the String returned by this method will be generated analogous + * to the default implementation of {@link Object#toString()} by using the supplied + * object's class name and hash code as follows: + * {@code obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj))}
  • + *
+ * + * @param obj the object to convert to a String; may be {@code null} + * @return the default String representation of the supplied object; never {@code null} + * @see #nullSafeToString(Object) + * @see ClassUtils#nullSafeToString(Class...) + */ + public static String defaultToString(Object obj) { + if (obj == null) { + return "null"; + } + + return obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj)); + } + + /** + * Replace all ISO control characters in the supplied {@link String}. + * + * @param str the string in which to perform the replacement; may be {@code null} + * @param replacement the replacement string; never {@code null} + * @return the supplied string with all control characters replaced, or + * {@code null} if the supplied string was {@code null} + * @since 1.4 + */ + @API(status = INTERNAL, since = "1.4") + public static String replaceIsoControlCharacters(String str, String replacement) { + Preconditions.notNull(replacement, "replacement must not be null"); + return str == null ? null : ISO_CONTROL_PATTERN.matcher(str).replaceAll(replacement); + } + + /** + * Replace all whitespace characters in the supplied {@link String}. + * + * @param str the string in which to perform the replacement; may be {@code null} + * @param replacement the replacement string; never {@code null} + * @return the supplied string with all whitespace characters replaced, or + * {@code null} if the supplied string was {@code null} + * @since 1.4 + */ + @API(status = INTERNAL, since = "1.4") + public static String replaceWhitespaceCharacters(String str, String replacement) { + Preconditions.notNull(replacement, "replacement must not be null"); + return str == null ? null : WHITESPACE_PATTERN.matcher(str).replaceAll(replacement); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java new file mode 100644 index 00000000..72695f50 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.lang.String.join; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.ArrayList; +import java.util.List; + +import org.apiguardian.api.API; + +/** + * Simple builder for generating strings in custom implementations of + * {@link Object#toString toString()}. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public class ToStringBuilder { + + private final String typeName; + + private final List values = new ArrayList<>(); + + public ToStringBuilder(Object obj) { + this(Preconditions.notNull(obj, "Object must not be null").getClass().getSimpleName()); + } + + public ToStringBuilder(Class type) { + this(Preconditions.notNull(type, "Class must not be null").getSimpleName()); + } + + @API(status = INTERNAL, since = "1.7") + public ToStringBuilder(String typeName) { + this.typeName = Preconditions.notNull(typeName, "Type name must not be null"); + } + + public ToStringBuilder append(String name, Object value) { + Preconditions.notBlank(name, "Name must not be null or blank"); + this.values.add(name + " = " + toString(value)); + return this; + } + + private String toString(Object obj) { + return (obj instanceof CharSequence) ? ("'" + obj + "'") : StringUtils.nullSafeToString(obj); + } + + @Override + public String toString() { + return this.typeName + " [" + join(", ", this.values) + "]"; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java new file mode 100644 index 00000000..dd730e30 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; + +/** + * Internal utilities for working with unrecoverable exceptions. + * + *

Unrecoverable exceptions are those that should always terminate + * test plan execution immediately. + * + *

Currently Unrecoverable Exceptions

+ *
    + *
  • {@link OutOfMemoryError}
  • + *
+ * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.7 + */ +@API(status = INTERNAL, since = "1.7") +public final class UnrecoverableExceptions { + + private UnrecoverableExceptions() { + /* no-op */ + } + + /** + * Rethrow the supplied {@link Throwable exception} if it is + * unrecoverable. + * + *

If the supplied {@code exception} is not unrecoverable, this + * method does nothing. + */ + public static void rethrowIfUnrecoverable(Throwable exception) { + if (exception instanceof OutOfMemoryError) { + ExceptionUtils.throwAsUncheckedException(exception); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java new file mode 100644 index 00000000..11994068 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java @@ -0,0 +1,11 @@ +/** + * Internal common utilities for JUnit. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + */ + +package org.junit.platform.commons.util; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java new file mode 100644 index 00000000..ff2b0566 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java @@ -0,0 +1,218 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.function.Predicate.isEqual; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toSet; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.IOException; +import java.lang.module.Configuration; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +/** + * Collection of utilities for working with {@code java.lang.Module} + * and friends. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.1 + */ +@API(status = INTERNAL, since = "1.1") +public class ModuleUtils { + + private static final Logger logger = LoggerFactory.getLogger(ModuleUtils.class); + + /** + * Find all non-system boot modules names. + * + * @return a set of all such module names; never {@code null} but + * potentially empty + */ + public static Set findAllNonSystemBootModuleNames() { + // @formatter:off + Set systemModules = ModuleFinder.ofSystem().findAll().stream() + .map(reference -> reference.descriptor().name()) + .collect(toSet()); + return streamResolvedModules(name -> !systemModules.contains(name)) + .map(ResolvedModule::name) + .collect(toCollection(LinkedHashSet::new)); + // @formatter:on + } + + /** + * Java 9+ runtime supports the Java Platform Module System. + * + * @return {@code true} + */ + public static boolean isJavaPlatformModuleSystemAvailable() { + return true; + } + + public static Optional getModuleName(Class type) { + Preconditions.notNull(type, "Class type must not be null"); + + return Optional.ofNullable(type.getModule().getName()); + } + + public static Optional getModuleVersion(Class type) { + Preconditions.notNull(type, "Class type must not be null"); + + Module module = type.getModule(); + return module.isNamed() ? module.getDescriptor().rawVersion() : Optional.empty(); + } + + /** + * Find all classes for the given module name. + * + * @param moduleName the name of the module to scan; never {@code null} or + * empty + * @param filter the class filter to apply; never {@code null} + * @return an immutable list of all such classes found; never {@code null} + * but potentially empty + */ + public static List> findAllClassesInModule(String moduleName, ClassFilter filter) { + Preconditions.notBlank(moduleName, "Module name must not be null or empty"); + Preconditions.notNull(filter, "Class filter must not be null"); + + logger.debug(() -> "Looking for classes in module: " + moduleName); + // @formatter:off + Set moduleReferences = streamResolvedModules(isEqual(moduleName)) + .map(ResolvedModule::reference) + .collect(toSet()); + // @formatter:on + return scan(moduleReferences, filter, ModuleUtils.class.getClassLoader()); + } + + /** + * Stream resolved modules from current (or boot) module layer. + */ + private static Stream streamResolvedModules(Predicate moduleNamePredicate) { + Module module = ModuleUtils.class.getModule(); + ModuleLayer layer = module.getLayer(); + if (layer == null) { + logger.config(() -> ModuleUtils.class + " is a member of " + module + + " - using boot layer returned by ModuleLayer.boot() as fall-back."); + layer = ModuleLayer.boot(); + } + return streamResolvedModules(moduleNamePredicate, layer); + } + + /** + * Stream resolved modules from the supplied layer. + */ + private static Stream streamResolvedModules(Predicate moduleNamePredicate, + ModuleLayer layer) { + logger.debug(() -> "Streaming modules for layer @" + System.identityHashCode(layer) + ": " + layer); + Configuration configuration = layer.configuration(); + logger.debug(() -> "Module layer configuration: " + configuration); + Stream stream = configuration.modules().stream(); + return stream.filter(module -> moduleNamePredicate.test(module.name())); + } + + /** + * Scan for classes using the supplied set of module references, class + * filter, and loader. + */ + private static List> scan(Set references, ClassFilter filter, ClassLoader loader) { + logger.debug(() -> "Scanning " + references.size() + " module references: " + references); + ModuleReferenceScanner scanner = new ModuleReferenceScanner(filter, loader); + List> classes = new ArrayList<>(); + for (ModuleReference reference : references) { + classes.addAll(scanner.scan(reference)); + } + logger.debug(() -> "Found " + classes.size() + " classes: " + classes); + return Collections.unmodifiableList(classes); + } + + /** + * {@link ModuleReference} scanner. + */ + static class ModuleReferenceScanner { + + private final ClassFilter classFilter; + private final ClassLoader classLoader; + + ModuleReferenceScanner(ClassFilter classFilter, ClassLoader classLoader) { + this.classFilter = classFilter; + this.classLoader = classLoader; + } + + /** + * Scan module reference for classes that potentially contain testable methods. + */ + List> scan(ModuleReference reference) { + try (ModuleReader reader = reference.open()) { + try (Stream names = reader.list()) { + // @formatter:off + return names.filter(name -> name.endsWith(".class")) + .map(this::className) + .filter(name -> !name.equals("module-info")) + .filter(classFilter::match) + .map(this::loadClassUnchecked) + .filter(classFilter::match) + .collect(Collectors.toList()); + // @formatter:on + } + } + catch (IOException e) { + throw new JUnitException("Failed to read contents of " + reference + ".", e); + } + } + + /** + * Convert resource name to binary class name. + */ + private String className(String resourceName) { + resourceName = resourceName.substring(0, resourceName.length() - 6); // 6 = ".class".length() + resourceName = resourceName.replace('/', '.'); + return resourceName; + } + + /** + * Load class by its binary name. + * + * @see ClassLoader#loadClass(String) + */ + private Class loadClassUnchecked(String binaryName) { + try { + return classLoader.loadClass(binaryName); + } + catch (ClassNotFoundException e) { + throw new JUnitException("Failed to load class with name '" + binaryName + "'.", e); + } + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java new file mode 100644 index 00000000..f33ffd31 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Common APIs and support utilities for the JUnit Platform. + * + * @since 1.0 + */ +module org.junit.platform.commons { + requires java.logging; + requires java.management; // needed by RuntimeUtils to determine input arguments + requires static transitive org.apiguardian.api; + + exports org.junit.platform.commons; + exports org.junit.platform.commons.annotation; + exports org.junit.platform.commons.function; + exports org.junit.platform.commons.logging to + org.junit.jupiter.api, + org.junit.jupiter.engine, + org.junit.jupiter.migrationsupport, + org.junit.jupiter.params, + org.junit.platform.console, + org.junit.platform.engine, + org.junit.platform.launcher, + org.junit.platform.reporting, + org.junit.platform.runner, + org.junit.platform.suite.api, + org.junit.platform.suite.engine, + org.junit.platform.testkit, + org.junit.vintage.engine; + exports org.junit.platform.commons.support; + exports org.junit.platform.commons.util to + org.junit.jupiter.api, + org.junit.jupiter.engine, + org.junit.jupiter.migrationsupport, + org.junit.jupiter.params, + org.junit.platform.console, + org.junit.platform.engine, + org.junit.platform.launcher, + org.junit.platform.reporting, + org.junit.platform.runner, + org.junit.platform.suite.api, + org.junit.platform.suite.commons, + org.junit.platform.suite.engine, + org.junit.platform.testkit, + org.junit.vintage.engine; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java new file mode 100644 index 00000000..70dabdf0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.test; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ConcurrencyTestingUtils { + + public static void executeConcurrently(int threads, Runnable action) throws Exception { + executeConcurrently(threads, () -> { + action.run(); + return null; + }); + } + + public static List executeConcurrently(int threads, Callable action) throws Exception { + ExecutorService executorService = Executors.newFixedThreadPool(threads); + try { + CountDownLatch latch = new CountDownLatch(threads); + List> futures = new ArrayList<>(); + for (int i = 0; i < threads; i++) { + futures.add(CompletableFuture.supplyAsync(() -> { + try { + latch.countDown(); + latch.await(); + return action.call(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new CompletionException(e); + } + catch (Exception e) { + throw new CompletionException("Action failed", e); + } + }, executorService)); + } + List list = new ArrayList<>(); + for (CompletableFuture future : futures) { + list.add(future.get(5, SECONDS)); + } + return list; + } + finally { + executorService.shutdownNow(); + var terminated = executorService.awaitTermination(5, SECONDS); + if (!terminated) { + //noinspection ThrowFromFinallyBlock + throw new AssertionError("ExecutorService did not cleanly shut down"); + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts new file mode 100644 index 00000000..6a42539e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -0,0 +1,93 @@ +import org.junit.gradle.java.WriteArtifactsFile + +plugins { + `java-library-conventions` + `shadow-conventions` +} + +description = "JUnit Platform Console Standalone" + +dependencies { + shadowed(projects.junitPlatformReporting) + shadowed(projects.junitPlatformConsole) + shadowed(projects.junitPlatformSuite) + shadowed(projects.junitJupiterEngine) + shadowed(projects.junitJupiterParams) + shadowed(projects.junitVintageEngine) + shadowed(libs.apiguardian) { + because("downstream projects need it to avoid compiler warnings") + } +} + +val jupiterVersion = rootProject.version +val vintageVersion: String by project + +tasks { + jar { + manifest { + attributes("Main-Class" to "org.junit.platform.console.ConsoleLauncher") + } + } + val shadowedArtifactsFile by registering(WriteArtifactsFile::class) { + from(configurations.shadowed) + outputFile.set(layout.buildDirectory.file("shadowed-artifacts")) + } + shadowJar { + // https://github.com/junit-team/junit5/issues/2557 + // exclude compiled module declarations from any source (e.g. /*, /META-INF/versions/N/*) + exclude("**/module-info.class") + // https://github.com/junit-team/junit5/issues/761 + // prevent duplicates, add 3rd-party licenses explicitly + exclude("META-INF/LICENSE*.md") + from(project.projects.junitPlatformConsole.dependencyProject.projectDir) { + include("LICENSE-picocli.md") + into("META-INF") + } + from(project.projects.junitJupiterParams.dependencyProject.projectDir) { + include("LICENSE-univocity-parsers.md") + into("META-INF") + } + from(shadowedArtifactsFile) { + into("META-INF") + } + + bundle { + bnd(""" + # Customize the imports because this is an aggregate jar + Import-Package: \ + ${extra["importAPIGuardian"]},\ + kotlin.*;resolution:="optional",\ + * + # Disable the APIGuardian plugin since everything was already + # processed, again because this is an aggregate jar + -export-apiguardian: + """) + } + + mergeServiceFiles() + manifest.apply { + inheritFrom(jar.get().manifest) + attributes(mapOf( + "Specification-Title" to project.name, + "Implementation-Title" to project.name, + // Generate test engine version information in single shared manifest file. + // Pattern of key and value: `"Engine-Version-{YourTestEngine#getId()}": "47.11"` + "Engine-Version-junit-jupiter" to jupiterVersion, + "Engine-Version-junit-vintage" to vintageVersion, + // Version-aware binaries are already included - set Multi-Release flag here. + // See https://openjdk.java.net/jeps/238 for details + // Note: the "jar --update ... --release X" command does not work with the + // shadowed JAR as it contains nested classes that do not comply with multi-release jars. + "Multi-Release" to true + )) + } + } + + // This jar contains some Java 9 code + // (org.junit.platform.console.ConsoleLauncherToolProvider which implements + // java.util.spi.ToolProvider which is @since 9). + // So in order to resolve this, it can only run on Java 9 + osgiProperties { + property("-runee", "JavaSE-9") + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md new file mode 100644 index 00000000..6aec502b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md @@ -0,0 +1,194 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts new file mode 100644 index 00000000..6006a8c8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts @@ -0,0 +1,75 @@ +plugins { + `java-library-conventions` + `shadow-conventions` + `java-multi-release-sources` + `java-repackage-jars` +} + +description = "JUnit Platform Console" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformReporting) + + compileOnlyApi(libs.apiguardian) + + compileOnly(libs.openTestReporting.events) + + shadowed(libs.picocli) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + compileModule { + options.compilerArgs.addAll(listOf( + "--add-modules", "org.opentest4j.reporting.events", + "--add-reads", "org.junit.platform.reporting=org.opentest4j.reporting.events" + )) + } + shadowJar { + val release17ClassesDir = sourceSets.mainRelease17.get().output.classesDirs.singleFile + inputs.dir(release17ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) + exclude("META-INF/versions/9/module-info.class") + relocate("picocli", "org.junit.platform.console.shadow.picocli") + from(projectDir) { + include("LICENSE-picocli.md") + into("META-INF") + } + from(sourceSets.mainRelease9.get().output.classesDirs) + doLast(objects.newInstance(org.junit.gradle.java.ExecJarAction::class).apply { + javaLauncher.set(project.javaToolchains.launcherFor(java.toolchain)) + args.addAll( + "--update", + "--file", archiveFile.get().asFile.absolutePath, + "--main-class", "org.junit.platform.console.ConsoleLauncher", + "--release", "17", + "-C", release17ClassesDir.absolutePath, "." + ) + }) + } + codeCoverageClassesJar { + exclude("org/junit/platform/console/options/ConsoleUtils.class") + } + jar { + manifest { + attributes("Main-Class" to "org.junit.platform.console.ConsoleLauncher") + } + } + + // This jar contains some Java 9 code + // (org.junit.platform.console.ConsoleLauncherToolProvider which implements + // java.util.spi.ToolProvider which is @since 9). + // So in order to resolve this, it can only run on Java 9 + osgiProperties { + property("-runee", "JavaSE-9") + } +} + +eclipse { + classpath { + sourceSets -= project.sourceSets.mainRelease9.get() + sourceSets -= project.sourceSets.mainRelease17.get() + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java new file mode 100644 index 00000000..d697987c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Comparator; +import java.util.StringJoiner; +import java.util.stream.StreamSupport; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.console.options.CommandLineOptionsParser; +import org.junit.platform.console.options.PicocliCommandLineOptionsParser; +import org.junit.platform.console.tasks.ConsoleTestExecutor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +/** + * The {@code ConsoleLauncher} is a stand-alone application for launching the + * JUnit Platform from the console. + * + * @since 1.0 + */ +@API(status = MAINTAINED, since = "1.0") +public class ConsoleLauncher { + + public static void main(String... args) { + int exitCode = execute(System.out, System.err, args).getExitCode(); + System.exit(exitCode); + } + + @API(status = INTERNAL, since = "1.0") + public static ConsoleLauncherExecutionResult execute(PrintStream out, PrintStream err, String... args) { + return execute(new PrintWriter(out), new PrintWriter(err), args); + } + + @API(status = INTERNAL, since = "1.0") + public static ConsoleLauncherExecutionResult execute(PrintWriter out, PrintWriter err, String... args) { + CommandLineOptionsParser parser = new PicocliCommandLineOptionsParser(); + ConsoleLauncher consoleLauncher = new ConsoleLauncher(parser, out, err); + return consoleLauncher.execute(args); + } + + private final CommandLineOptionsParser commandLineOptionsParser; + private final PrintWriter out; + private final PrintWriter err; + + ConsoleLauncher(CommandLineOptionsParser commandLineOptionsParser, PrintWriter out, PrintWriter err) { + this.commandLineOptionsParser = commandLineOptionsParser; + this.out = out; + this.err = err; + } + + ConsoleLauncherExecutionResult execute(String... args) { + try { + CommandLineOptions options = commandLineOptionsParser.parse(args); + if (options.isListEngines()) { + displayEngines(out); + return ConsoleLauncherExecutionResult.success(); + } + if (!options.isBannerDisabled()) { + displayBanner(out); + } + if (options.isDisplayHelp()) { + commandLineOptionsParser.printHelp(out, options.isAnsiColorOutputDisabled()); + return ConsoleLauncherExecutionResult.success(); + } + return executeTests(options, out); + } + catch (JUnitException ex) { + err.println(ex.getMessage()); + err.println(); + commandLineOptionsParser.printHelp(err, false); + return ConsoleLauncherExecutionResult.failed(); + } + finally { + out.flush(); + err.flush(); + } + } + + void displayBanner(PrintWriter out) { + out.println(); + out.println("Thanks for using JUnit! Support its development at https://junit.org/sponsoring"); + out.println(); + } + + void displayEngines(PrintWriter out) { + ServiceLoaderTestEngineRegistry registry = new ServiceLoaderTestEngineRegistry(); + Iterable engines = registry.loadTestEngines(); + StreamSupport.stream(engines.spliterator(), false) // + .sorted(Comparator.comparing(TestEngine::getId)) // + .forEach(engine -> displayEngine(out, engine)); + } + + private void displayEngine(PrintWriter out, TestEngine engine) { + StringJoiner details = new StringJoiner(":", " (", ")"); + engine.getGroupId().ifPresent(details::add); + engine.getArtifactId().ifPresent(details::add); + engine.getVersion().ifPresent(details::add); + out.println(engine.getId() + details); + } + + private ConsoleLauncherExecutionResult executeTests(CommandLineOptions options, PrintWriter out) { + try { + TestExecutionSummary testExecutionSummary = new ConsoleTestExecutor(options).execute(out); + return ConsoleLauncherExecutionResult.forSummary(testExecutionSummary, options); + } + catch (Exception exception) { + exception.printStackTrace(err); + err.println(); + commandLineOptionsParser.printHelp(out, options.isAnsiColorOutputDisabled()); + return ConsoleLauncherExecutionResult.failed(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java new file mode 100644 index 00000000..23dd8225 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public class ConsoleLauncherExecutionResult { + + /** + * Exit code indicating successful execution + */ + private static final int SUCCESS = 0; + + /** + * Exit code indicating test failure(s) + */ + private static final int TEST_FAILED = 1; + + /** + * Exit code indicating no tests found + */ + private static final int NO_TESTS_FOUND = 2; + + /** + * Exit code indicating any failure(s) + */ + private static final int FAILED = -1; + + public static int computeExitCode(TestExecutionSummary summary, CommandLineOptions options) { + if (options.isFailIfNoTests() && summary.getTestsFoundCount() == 0) { + return NO_TESTS_FOUND; + } + return summary.getTotalFailureCount() == 0 ? SUCCESS : TEST_FAILED; + } + + static ConsoleLauncherExecutionResult success() { + return new ConsoleLauncherExecutionResult(SUCCESS, null); + } + + static ConsoleLauncherExecutionResult failed() { + return new ConsoleLauncherExecutionResult(FAILED, null); + } + + static ConsoleLauncherExecutionResult forSummary(TestExecutionSummary summary, CommandLineOptions options) { + int exitCode = computeExitCode(summary, options); + return new ConsoleLauncherExecutionResult(exitCode, summary); + } + + private final int exitCode; + private final TestExecutionSummary testExecutionSummary; + + private ConsoleLauncherExecutionResult(int exitCode, TestExecutionSummary testExecutionSummary) { + this.testExecutionSummary = testExecutionSummary; + this.exitCode = exitCode; + } + + public int getExitCode() { + return exitCode; + } + + public Optional getTestExecutionSummary() { + return Optional.ofNullable(testExecutionSummary); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java new file mode 100644 index 00000000..fce8ad50 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java @@ -0,0 +1,444 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.junit.platform.console.options.CommandLineOptions.DEFAULT_DETAILS; +import static org.junit.platform.console.options.CommandLineOptions.DEFAULT_DETAILS_NAME; +import static org.junit.platform.console.options.CommandLineOptions.DEFAULT_THEME; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UriSelector; + +import picocli.CommandLine; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.ParseResult; +import picocli.CommandLine.Spec; + +/** + * @since 1.0 + */ +@Command(// + name = "ConsoleLauncher", // + abbreviateSynopsis = true, // + sortOptions = false, // + usageHelpWidth = 95, // + showAtFileInUsageHelp = true, // + usageHelpAutoWidth = true, // + description = "Launches the JUnit Platform for test discovery and execution.", // + footerHeading = "%n", // + footer = "For more information, please refer to the JUnit User Guide at%n" // + + "@|underline https://junit.org/junit5/docs/current/user-guide/|@"// +) +class AvailableOptions { + + private static final String CP_OPTION = "cp"; + + @ArgGroup(validate = false, order = 1, heading = "%n@|bold COMMANDS|@%n%n") + CommandOptions commandOptions; + + @ArgGroup(validate = false, order = 2, heading = "%n@|bold SELECTORS|@%n%n") + SelectorOptions selectorOptions; + + @ArgGroup(validate = false, order = 3, heading = "%n@|bold FILTERS|@%n%n") + FilterOptions filterOptions; + + @ArgGroup(validate = false, order = 4, heading = "%n@|bold RUNTIME CONFIGURATION|@%n%n") + RuntimeConfigurationOptions runtimeConfigurationOptions; + + @ArgGroup(validate = false, order = 5, heading = "%n@|bold REPORTING|@%n%n") + ReportingOptions reportingOptions; + + @ArgGroup(validate = false, order = 6, heading = "%n@|bold CONSOLE OUTPUT|@%n%n") + ConsoleOutputOptions consoleOutputOptions; + + static class CommandOptions { + + @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display help information.") + private boolean helpRequested; + + @Option(names = { "--h", "-help" }, help = true, hidden = true) + private boolean helpRequested2; + + @Option(names = { "--list-engines" }, description = "List all observable test engines.") + private boolean listEnginesRequested; + + private void applyTo(CommandLineOptions result) { + result.setDisplayHelp(this.helpRequested || this.helpRequested2); + result.setListEngines(this.listEnginesRequested); + } + } + + static class SelectorOptions { + + @Option(names = { "--scan-classpath", + "--scan-class-path" }, converter = ClasspathEntriesConverter.class, paramLabel = "PATH", arity = "0..1", description = "Scan all directories on the classpath or explicit classpath roots. " // + + "Without arguments, only directories on the system classpath as well as additional classpath " // + + "entries supplied via -" + CP_OPTION + " (directories and JAR files) are scanned. " // + + "Explicit classpath roots that are not on the classpath will be silently ignored. " // + + "This option can be repeated.") + private List selectedClasspathEntries = new ArrayList<>(); + + @Option(names = { "-scan-class-path", + "-scan-classpath" }, converter = ClasspathEntriesConverter.class, arity = "0..1", hidden = true) + private List selectedClasspathEntries2 = new ArrayList<>(); + + @Option(names = "--scan-modules", description = "EXPERIMENTAL: Scan all resolved modules for test discovery.") + private boolean scanModulepath; + + @Option(names = "-scan-modules", hidden = true) + private boolean scanModulepath2; + + @Option(names = { "-u", + "--select-uri" }, paramLabel = "URI", arity = "1", converter = SelectorConverter.Uri.class, description = "Select a URI for test discovery. This option can be repeated.") + private List selectedUris = new ArrayList<>(); + + @Option(names = { "--u", "-select-uri" }, arity = "1", hidden = true, converter = SelectorConverter.Uri.class) + private List selectedUris2 = new ArrayList<>(); + + @Option(names = { "-f", + "--select-file" }, paramLabel = "FILE", arity = "1", converter = SelectorConverter.File.class, description = "Select a file for test discovery. This option can be repeated.") + private List selectedFiles = new ArrayList<>(); + + @Option(names = { "--f", "-select-file" }, arity = "1", hidden = true, converter = SelectorConverter.File.class) + private List selectedFiles2 = new ArrayList<>(); + + @Option(names = { "-d", + "--select-directory" }, paramLabel = "DIR", arity = "1", converter = SelectorConverter.Directory.class, description = "Select a directory for test discovery. This option can be repeated.") + private List selectedDirectories = new ArrayList<>(); + + @Option(names = { "--d", + "-select-directory" }, arity = "1", hidden = true, converter = SelectorConverter.Directory.class) + private List selectedDirectories2 = new ArrayList<>(); + + @Option(names = { "-o", + "--select-module" }, paramLabel = "NAME", arity = "1", converter = SelectorConverter.Module.class, description = "EXPERIMENTAL: Select single module for test discovery. This option can be repeated.") + private List selectedModules = new ArrayList<>(); + + @Option(names = { "--o", + "-select-module" }, arity = "1", converter = SelectorConverter.Module.class, hidden = true) + private List selectedModules2 = new ArrayList<>(); + + @Option(names = { "-p", + "--select-package" }, paramLabel = "PKG", arity = "1", converter = SelectorConverter.Package.class, description = "Select a package for test discovery. This option can be repeated.") + private List selectedPackages = new ArrayList<>(); + + @Option(names = { "--p", + "-select-package" }, arity = "1", hidden = true, converter = SelectorConverter.Package.class) + private List selectedPackages2 = new ArrayList<>(); + + @Option(names = { "-c", + "--select-class" }, paramLabel = "CLASS", arity = "1", converter = SelectorConverter.Class.class, description = "Select a class for test discovery. This option can be repeated.") + private List selectedClasses = new ArrayList<>(); + + @Option(names = { "--c", + "-select-class" }, arity = "1", hidden = true, converter = SelectorConverter.Class.class) + private List selectedClasses2 = new ArrayList<>(); + + @Option(names = { "-m", + "--select-method" }, paramLabel = "NAME", arity = "1", converter = SelectorConverter.Method.class, description = "Select a method for test discovery. This option can be repeated.") + private List selectedMethods = new ArrayList<>(); + + @Option(names = { "--m", + "-select-method" }, arity = "1", hidden = true, converter = SelectorConverter.Method.class) + private List selectedMethods2 = new ArrayList<>(); + + @Option(names = { "-r", + "--select-resource" }, paramLabel = "RESOURCE", arity = "1", converter = SelectorConverter.ClasspathResource.class, description = "Select a classpath resource for test discovery. This option can be repeated.") + private List selectedClasspathResources = new ArrayList<>(); + + @Option(names = { "--r", + "-select-resource" }, arity = "1", hidden = true, converter = SelectorConverter.ClasspathResource.class) + private List selectedClasspathResources2 = new ArrayList<>(); + + @Option(names = { "-i", + "--select-iteration" }, paramLabel = "TYPE:VALUE[INDEX(..INDEX)?(,INDEX(..INDEX)?)*]", arity = "1", converter = SelectorConverter.Iteration.class, description = "Select iterations for test discovery (e.g. method:com.acme.Foo#m()[1..2]). This option can be repeated.") + private List selectedIterations = new ArrayList<>(); + + @Option(names = { "--i", + "-select-iteration" }, arity = "1", hidden = true, converter = SelectorConverter.Iteration.class) + private List selectedIterations2 = new ArrayList<>(); + + private void applyTo(ParseResult parseResult, CommandLineOptions result) { + result.setScanClasspath(parseResult.hasMatchedOption("scan-class-path")); // flag was specified + result.setScanModulepath(this.scanModulepath || this.scanModulepath2); + result.setSelectedModules(merge(this.selectedModules, this.selectedModules2)); + result.setSelectedClasspathEntries(merge(this.selectedClasspathEntries, this.selectedClasspathEntries2)); + result.setSelectedUris(merge(this.selectedUris, this.selectedUris2)); + result.setSelectedFiles(merge(this.selectedFiles, this.selectedFiles2)); + result.setSelectedDirectories(merge(this.selectedDirectories, this.selectedDirectories2)); + result.setSelectedPackages(merge(this.selectedPackages, this.selectedPackages2)); + result.setSelectedClasses(merge(this.selectedClasses, this.selectedClasses2)); + result.setSelectedMethods(merge(this.selectedMethods, this.selectedMethods2)); + result.setSelectedClasspathResources( + merge(this.selectedClasspathResources, this.selectedClasspathResources2)); + result.setSelectedIterations(merge(this.selectedIterations, this.selectedIterations2)); + } + } + + static class FilterOptions { + + @Option(names = { "-n", + "--include-classname" }, paramLabel = "PATTERN", defaultValue = ClassNameFilter.STANDARD_INCLUDE_PATTERN, arity = "1", description = "Provide a regular expression to include only classes whose fully qualified names match. " // + + "To avoid loading classes unnecessarily, the default pattern only includes class " // + + "names that begin with \"Test\" or end with \"Test\" or \"Tests\". " // + + "When this option is repeated, all patterns will be combined using OR semantics. " // + + "Default: ${DEFAULT-VALUE}") + private List includeClassNamePatterns = new ArrayList<>(); + + @Option(names = { "--n", "-include-classname" }, arity = "1", hidden = true) + private List includeClassNamePatterns2 = new ArrayList<>(); + + @Option(names = { "-N", + "--exclude-classname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to exclude those classes whose fully qualified names match. " // + + "When this option is repeated, all patterns will be combined using OR semantics.") + private List excludeClassNamePatterns = new ArrayList<>(); + + @Option(names = { "--N", "-exclude-classname" }, arity = "1", hidden = true) + private List excludeClassNamePatterns2 = new ArrayList<>(); + + @Option(names = { + "--include-package" }, paramLabel = "PKG", arity = "1", description = "Provide a package to be included in the test run. This option can be repeated.") + private List includePackages = new ArrayList<>(); + + @Option(names = { "-include-package" }, arity = "1", hidden = true) + private List includePackages2 = new ArrayList<>(); + + @Option(names = { + "--exclude-package" }, paramLabel = "PKG", arity = "1", description = "Provide a package to be excluded from the test run. This option can be repeated.") + private List excludePackages = new ArrayList<>(); + + @Option(names = { "-exclude-package" }, arity = "1", hidden = true) + private List excludePackages2 = new ArrayList<>(); + + @Option(names = { "-t", + "--include-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to include only tests whose tags match. " + + // + "When this option is repeated, all patterns will be combined using OR semantics.") + private List includedTags = new ArrayList<>(); + + @Option(names = { "--t", "-include-tag" }, arity = "1", hidden = true) + private List includedTags2 = new ArrayList<>(); + + @Option(names = { "-T", + "--exclude-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to exclude those tests whose tags match. " + + // + "When this option is repeated, all patterns will be combined using OR semantics.") + private List excludedTags = new ArrayList<>(); + + @Option(names = { "--T", "-exclude-tag" }, arity = "1", hidden = true) + private List excludedTags2 = new ArrayList<>(); + + @Option(names = { "-e", + "--include-engine" }, paramLabel = "ID", arity = "1", description = "Provide the ID of an engine to be included in the test run. This option can be repeated.") + private List includedEngines = new ArrayList<>(); + + @Option(names = { "--e", "-include-engine" }, arity = "1", hidden = true) + private List includedEngines2 = new ArrayList<>(); + + @Option(names = { "-E", + "--exclude-engine" }, paramLabel = "ID", arity = "1", description = "Provide the ID of an engine to be excluded from the test run. This option can be repeated.") + private List excludedEngines = new ArrayList<>(); + + @Option(names = { "--E", "-exclude-engine" }, arity = "1", hidden = true) + private List excludedEngines2 = new ArrayList<>(); + + private void applyTo(CommandLineOptions result) { + result.setIncludedClassNamePatterns(merge(this.includeClassNamePatterns, this.includeClassNamePatterns2)); + result.setExcludedClassNamePatterns(merge(this.excludeClassNamePatterns, this.excludeClassNamePatterns2)); + result.setIncludedPackages(merge(this.includePackages, this.includePackages2)); + result.setExcludedPackages(merge(this.excludePackages, this.excludePackages2)); + result.setIncludedTagExpressions(merge(this.includedTags, this.includedTags2)); + result.setExcludedTagExpressions(merge(this.excludedTags, this.excludedTags2)); + result.setIncludedEngines(merge(this.includedEngines, this.includedEngines2)); + result.setExcludedEngines(merge(this.excludedEngines, this.excludedEngines2)); + } + } + + static class RuntimeConfigurationOptions { + + @Option(names = { "-" + CP_OPTION, "--classpath", + "--class-path" }, converter = ClasspathEntriesConverter.class, paramLabel = "PATH", arity = "1", description = "Provide additional classpath entries " + + "-- for example, for adding engines and their dependencies. This option can be repeated.") + private List additionalClasspathEntries = new ArrayList<>(); + + @Option(names = { "--cp", "-classpath", + "-class-path" }, converter = ClasspathEntriesConverter.class, hidden = true) + private List additionalClasspathEntries2 = new ArrayList<>(); + + // Implementation note: the @Option annotation is on a setter method to allow validation. + private Map configurationParameters = new LinkedHashMap<>(); + + @Spec + private CommandSpec spec; + + @Option(names = { "-config" }, arity = "1", hidden = true) + public void setConfigurationParameters2(Map keyValuePairs) { + setConfigurationParameters(keyValuePairs); + } + + /** + * Adds the specified key-value pair (or pairs) to the configuration parameters. + * A {@code ParameterException} is thrown if the same key is specified multiple times + * on the command line. + * + * @param map the key-value pairs to add + * @throws picocli.CommandLine.ParameterException if the map already contains this key + * @see #1308 + */ + @Option(names = "--config", paramLabel = "KEY=VALUE", arity = "1", description = "Set a configuration parameter for test discovery and execution. This option can be repeated.") + public void setConfigurationParameters(Map map) { + for (String key : map.keySet()) { + String newValue = map.get(key); + validateUnique(key, newValue); + configurationParameters.put(key, newValue); + } + } + + private void validateUnique(String key, String newValue) { + String existing = configurationParameters.get(key); + if (existing != null && !existing.equals(newValue)) { + throw new ParameterException(spec.commandLine(), + String.format("Duplicate key '%s' for values '%s' and '%s'.", key, existing, newValue)); + } + } + + private void applyTo(CommandLineOptions result) { + result.setAdditionalClasspathEntries(merge(additionalClasspathEntries, additionalClasspathEntries2)); + result.setConfigurationParameters(configurationParameters); + } + } + + static class ReportingOptions { + + @Option(names = "--fail-if-no-tests", description = "Fail and return exit status code 2 if no tests are found.") + private boolean failIfNoTests; // no single-dash equivalent: was introduced in 5.3-M1 + + @Option(names = "--reports-dir", paramLabel = "DIR", description = "Enable report output into a specified local directory (will be created if it does not exist).") + private Path reportsDir; + + @Option(names = "-reports-dir", hidden = true) + private Path reportsDir2; + + private void applyTo(CommandLineOptions result) { + result.setFailIfNoTests(this.failIfNoTests); + result.setReportsDir(choose(this.reportsDir, this.reportsDir2, null)); + } + } + + static class ConsoleOutputOptions { + @Option(names = "--disable-ansi-colors", description = "Disable ANSI colors in output (not supported by all terminals).") + private boolean disableAnsiColors; + + @Option(names = "-disable-ansi-colors", hidden = true) + private boolean disableAnsiColors2; + + @Option(names = "--color-palette", paramLabel = "FILE", description = "Specify a path to a properties file to customize ANSI style of output (not supported by all terminals).") + private Path colorPalette; + @Option(names = "-color-palette", hidden = true) + private Path colorPalette2; + + @Option(names = "--single-color", description = "Style test output using only text attributes, no color (not supported by all terminals).") + private boolean singleColorPalette; + @Option(names = "-single-color", hidden = true) + private boolean singleColorPalette2; + + @Option(names = "--disable-banner", description = "Disable print out of the welcome message.") + private boolean disableBanner; + + @Option(names = "-disable-banner", hidden = true) + private boolean disableBanner2; + + @Option(names = "--details", paramLabel = "MODE", defaultValue = DEFAULT_DETAILS_NAME, description = "Select an output details mode for when tests are executed. " // + + "Use one of: ${COMPLETION-CANDIDATES}. If 'none' is selected, " // + + "then only the summary and test failures are shown. Default: ${DEFAULT-VALUE}.") + private Details details = DEFAULT_DETAILS; + + @Option(names = "-details", hidden = true, defaultValue = DEFAULT_DETAILS_NAME) + private Details details2 = DEFAULT_DETAILS; + + @Option(names = "--details-theme", paramLabel = "THEME", description = "Select an output details tree theme for when tests are executed. " + + "Use one of: ${COMPLETION-CANDIDATES}. Default is detected based on default character encoding.") + private Theme theme = DEFAULT_THEME; + + @Option(names = "-details-theme", hidden = true) + private Theme theme2 = DEFAULT_THEME; + + private void applyTo(CommandLineOptions result) { + result.setAnsiColorOutputDisabled(disableAnsiColors || disableAnsiColors2); + result.setColorPalettePath(choose(colorPalette, colorPalette2, null)); + result.setSingleColorPalette(singleColorPalette || singleColorPalette2); + result.setBannerDisabled(disableBanner || disableBanner2); + result.setDetails(choose(details, details2, DEFAULT_DETAILS)); + result.setTheme(choose(theme, theme2, DEFAULT_THEME)); + } + } + + AvailableOptions() { + } + + CommandLine getParser() { + CommandLine result = new CommandLine(this); + result.setCaseInsensitiveEnumValuesAllowed(true); + result.setAtFileCommentChar(null); // for --select-method com.acme.Foo#m() + return result; + } + + CommandLineOptions toCommandLineOptions(ParseResult parseResult) { + CommandLineOptions result = new CommandLineOptions(); + if (commandOptions != null) { + this.commandOptions.applyTo(result); + } + if (this.selectorOptions != null) { + this.selectorOptions.applyTo(parseResult, result); + } + if (this.filterOptions != null) { + this.filterOptions.applyTo(result); + } + if (this.runtimeConfigurationOptions != null) { + this.runtimeConfigurationOptions.applyTo(result); + } + if (this.reportingOptions != null) { + this.reportingOptions.applyTo(result); + } + if (this.consoleOutputOptions != null) { + this.consoleOutputOptions.applyTo(result); + } + return result; + } + + private static List merge(List list1, List list2) { + List result = new ArrayList<>(list1); + result.addAll(list2); + return result; + } + + private static T choose(T left, T right, T defaultValue) { + return left == right ? left : (left == defaultValue ? right : left); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java new file mode 100644 index 00000000..8ea5670c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import picocli.CommandLine; + +class ClasspathEntriesConverter implements CommandLine.ITypeConverter> { + @Override + public List convert(String value) { + return Stream.of(value.split(File.pathSeparator)).map(Paths::get).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java new file mode 100644 index 00000000..4321acbc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java @@ -0,0 +1,363 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UriSelector; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public class CommandLineOptions { + + static final String DEFAULT_DETAILS_NAME = "tree"; + static final Details DEFAULT_DETAILS = Details.valueOf(DEFAULT_DETAILS_NAME.toUpperCase(Locale.ROOT)); + static final Theme DEFAULT_THEME = Theme.valueOf(ConsoleUtils.charset()); + + private boolean displayHelp; + private boolean listEngines; + private boolean ansiColorOutputDisabled; + private Path colorPalettePath; + private boolean isSingleColorPalette; + private boolean bannerDisabled; + private Details details = DEFAULT_DETAILS; + private Theme theme = DEFAULT_THEME; + private List additionalClasspathEntries = emptyList(); + private boolean failIfNoTests; + + private boolean scanClasspath; + private List selectedClasspathEntries = emptyList(); + + private boolean scanModulepath; + + private List selectedModules = emptyList(); + private List selectedUris = emptyList(); + private List selectedFiles = emptyList(); + private List selectedDirectories = emptyList(); + private List selectedPackages = emptyList(); + private List selectedClasses = emptyList(); + private List selectedMethods = emptyList(); + private List selectedClasspathResources = emptyList(); + private List selectedIterations = emptyList(); + + private List includedClassNamePatterns = singletonList(STANDARD_INCLUDE_PATTERN); + private List excludedClassNamePatterns = emptyList(); + private List includedPackages = emptyList(); + private List excludedPackages = emptyList(); + private List includedEngines = emptyList(); + private List excludedEngines = emptyList(); + private List includedTagExpressions = emptyList(); + private List excludedTagExpressions = emptyList(); + + private Path reportsDir; + + private Map configurationParameters = emptyMap(); + + public boolean isDisplayHelp() { + return this.displayHelp; + } + + public void setDisplayHelp(boolean displayHelp) { + this.displayHelp = displayHelp; + } + + public boolean isListEngines() { + return this.listEngines; + } + + public void setListEngines(boolean listEngines) { + this.listEngines = listEngines; + } + + public boolean isAnsiColorOutputDisabled() { + return this.ansiColorOutputDisabled; + } + + public void setAnsiColorOutputDisabled(boolean ansiColorOutputDisabled) { + this.ansiColorOutputDisabled = ansiColorOutputDisabled; + } + + public Path getColorPalettePath() { + return colorPalettePath; + } + + public void setColorPalettePath(Path colorPalettePath) { + this.colorPalettePath = colorPalettePath; + } + + public boolean isSingleColorPalette() { + return isSingleColorPalette; + } + + public void setSingleColorPalette(boolean singleColorPalette) { + this.isSingleColorPalette = singleColorPalette; + } + + public boolean isBannerDisabled() { + return this.bannerDisabled; + } + + public void setBannerDisabled(boolean bannerDisabled) { + this.bannerDisabled = bannerDisabled; + } + + public boolean isScanModulepath() { + return this.scanModulepath; + } + + public void setScanModulepath(boolean scanModulepath) { + this.scanModulepath = scanModulepath; + } + + public boolean isScanClasspath() { + return this.scanClasspath; + } + + public void setScanClasspath(boolean scanClasspath) { + this.scanClasspath = scanClasspath; + } + + public Details getDetails() { + return this.details; + } + + public void setDetails(Details details) { + this.details = details; + } + + public Theme getTheme() { + return this.theme; + } + + public void setTheme(Theme theme) { + this.theme = theme; + } + + public List getExistingAdditionalClasspathEntries() { + return this.additionalClasspathEntries.stream().filter(Files::exists).collect(toList()); + } + + public List getAdditionalClasspathEntries() { + return this.additionalClasspathEntries; + } + + public void setAdditionalClasspathEntries(List additionalClasspathEntries) { + this.additionalClasspathEntries = additionalClasspathEntries; + } + + public boolean isFailIfNoTests() { + return this.failIfNoTests; + } + + public void setFailIfNoTests(boolean failIfNoTests) { + this.failIfNoTests = failIfNoTests; + } + + public List getSelectedClasspathEntries() { + return this.selectedClasspathEntries; + } + + public void setSelectedClasspathEntries(List selectedClasspathEntries) { + this.selectedClasspathEntries = selectedClasspathEntries; + } + + public List getSelectedUris() { + return selectedUris; + } + + public void setSelectedUris(List selectedUris) { + this.selectedUris = selectedUris; + } + + public List getSelectedFiles() { + return selectedFiles; + } + + public void setSelectedFiles(List selectedFiles) { + this.selectedFiles = selectedFiles; + } + + public List getSelectedDirectories() { + return selectedDirectories; + } + + public void setSelectedDirectories(List selectedDirectories) { + this.selectedDirectories = selectedDirectories; + } + + public List getSelectedModules() { + return selectedModules; + } + + public void setSelectedModules(List selectedModules) { + this.selectedModules = selectedModules; + } + + public List getSelectedPackages() { + return selectedPackages; + } + + public void setSelectedPackages(List selectedPackages) { + this.selectedPackages = selectedPackages; + } + + public List getSelectedClasses() { + return selectedClasses; + } + + public void setSelectedClasses(List selectedClasses) { + this.selectedClasses = selectedClasses; + } + + public List getSelectedMethods() { + return selectedMethods; + } + + public void setSelectedMethods(List selectedMethods) { + this.selectedMethods = selectedMethods; + } + + public List getSelectedClasspathResources() { + return selectedClasspathResources; + } + + public void setSelectedClasspathResources(List selectedClasspathResources) { + this.selectedClasspathResources = selectedClasspathResources; + } + + public List getSelectedIterations() { + return selectedIterations; + } + + public void setSelectedIterations(List selectedIterations) { + this.selectedIterations = selectedIterations; + } + + public List getExplicitSelectors() { + List selectors = new ArrayList<>(); + selectors.addAll(getSelectedUris()); + selectors.addAll(getSelectedFiles()); + selectors.addAll(getSelectedDirectories()); + selectors.addAll(getSelectedModules()); + selectors.addAll(getSelectedPackages()); + selectors.addAll(getSelectedClasses()); + selectors.addAll(getSelectedMethods()); + selectors.addAll(getSelectedClasspathResources()); + selectors.addAll(getSelectedIterations()); + return selectors; + } + + public List getIncludedClassNamePatterns() { + return this.includedClassNamePatterns; + } + + public void setIncludedClassNamePatterns(List includedClassNamePatterns) { + this.includedClassNamePatterns = includedClassNamePatterns; + } + + public List getExcludedClassNamePatterns() { + return this.excludedClassNamePatterns; + } + + public void setExcludedClassNamePatterns(List excludedClassNamePatterns) { + this.excludedClassNamePatterns = excludedClassNamePatterns; + } + + public List getIncludedPackages() { + return this.includedPackages; + } + + public void setIncludedPackages(List includedPackages) { + this.includedPackages = includedPackages; + } + + public List getExcludedPackages() { + return this.excludedPackages; + } + + public void setExcludedPackages(List excludedPackages) { + this.excludedPackages = excludedPackages; + } + + public List getIncludedEngines() { + return this.includedEngines; + } + + public void setIncludedEngines(List includedEngines) { + this.includedEngines = includedEngines; + } + + public List getExcludedEngines() { + return this.excludedEngines; + } + + public void setExcludedEngines(List excludedEngines) { + this.excludedEngines = excludedEngines; + } + + public List getIncludedTagExpressions() { + return this.includedTagExpressions; + } + + public void setIncludedTagExpressions(List includedTags) { + this.includedTagExpressions = includedTags; + } + + public List getExcludedTagExpressions() { + return this.excludedTagExpressions; + } + + public void setExcludedTagExpressions(List excludedTags) { + this.excludedTagExpressions = excludedTags; + } + + public Optional getReportsDir() { + return Optional.ofNullable(this.reportsDir); + } + + public void setReportsDir(Path reportsDir) { + this.reportsDir = reportsDir; + } + + public Map getConfigurationParameters() { + return this.configurationParameters; + } + + public void setConfigurationParameters(Map configurationParameters) { + this.configurationParameters = configurationParameters; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java new file mode 100644 index 00000000..b460540e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.Writer; + +import org.apiguardian.api.API; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public interface CommandLineOptionsParser { + + CommandLineOptions parse(String... arguments); + + void printHelp(Writer writer, boolean ansiColorOutputDisabled); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java new file mode 100644 index 00000000..da43806a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.nio.charset.Charset; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@code java.io.Console} + * and friends. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.9 + */ +@API(status = INTERNAL, since = "1.9") +public class ConsoleUtils { + + /** + * {@return the charset of the console} + */ + public static Charset charset() { + return Charset.defaultCharset(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java new file mode 100644 index 00000000..7e6e4bdd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public enum Details { + + /** + * No test plan execution details are printed. + */ + NONE, + + /** + * Print summary table of counts only. + */ + SUMMARY, + + /** + * Test plan execution details are rendered in a flat, line-by-line mode. + */ + FLAT, + + /** + * Test plan execution details are rendered as a simple tree. + */ + TREE, + + /** + * Combines {@link #TREE} and {@link #FLAT} modes. + */ + VERBOSE; + + /** + * Return lower case {@link #name} for easier usage in help text for + * available options. + */ + @Override + public String toString() { + return name().toLowerCase(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java new file mode 100644 index 00000000..a064c128 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.IOException; +import java.io.Writer; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +import picocli.CommandLine; +import picocli.CommandLine.ParseResult; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public class PicocliCommandLineOptionsParser implements CommandLineOptionsParser { + + @Override + public CommandLineOptions parse(String... arguments) { + AvailableOptions availableOptions = getAvailableOptions(); + CommandLine parser = availableOptions.getParser(); + try { + ParseResult detectedOptions = parser.parseArgs(arguments); + return availableOptions.toCommandLineOptions(detectedOptions); + } + catch (Exception ex) { + throw new JUnitException("Error parsing command-line arguments: " + ex.getMessage(), ex); + } + } + + @Override + public void printHelp(Writer writer, boolean ansiColorOutputDisabled) { + try { + CommandLine parser = getAvailableOptions().getParser(); + if (ansiColorOutputDisabled) { + parser.setColorScheme(CommandLine.Help.defaultColorScheme(CommandLine.Help.Ansi.OFF)); + } + writer.append(parser.getUsageMessage()); + } + catch (IOException ex) { + throw new JUnitException("Error printing help", ex); + } + } + + private AvailableOptions getAvailableOptions() { + return new AvailableOptions(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java new file mode 100644 index 00000000..65f338da --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java @@ -0,0 +1,149 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UriSelector; +import picocli.CommandLine.ITypeConverter; + +class SelectorConverter { + + static class Module implements ITypeConverter { + @Override + public ModuleSelector convert(String value) { + return selectModule(value); + } + } + + static class Uri implements ITypeConverter { + @Override + public UriSelector convert(String value) { + return selectUri(value); + } + } + + static class File implements ITypeConverter { + @Override + public FileSelector convert(String value) { + return selectFile(value); + } + } + + static class Directory implements ITypeConverter { + @Override + public DirectorySelector convert(String value) { + return selectDirectory(value); + } + } + + static class Package implements ITypeConverter { + @Override + public PackageSelector convert(String value) { + return selectPackage(value); + } + } + + static class Class implements ITypeConverter { + @Override + public ClassSelector convert(String value) { + return selectClass(value); + } + } + + static class Method implements ITypeConverter { + @Override + public MethodSelector convert(String value) { + return selectMethod(value); + } + } + + static class ClasspathResource implements ITypeConverter { + @Override + public ClasspathResourceSelector convert(String value) { + return selectClasspathResource(value); + } + } + + static class Iteration implements ITypeConverter { + + public static final Pattern PATTERN = Pattern.compile( + "(?[a-z]+):(?.*)\\[(?(\\d+)(\\.\\.\\d+)?(\\s*,\\s*(\\d+)(\\.\\.\\d+)?)*)]"); + + @Override + public IterationSelector convert(String value) { + Matcher matcher = PATTERN.matcher(value); + Preconditions.condition(matcher.matches(), "Invalid format: must be TYPE:VALUE[INDEX(,INDEX)*]"); + DiscoverySelector parentSelector = createParentSelector(matcher.group("type"), matcher.group("value")); + int[] iterationIndices = Arrays.stream(matcher.group("indices").split(",")) // + .flatMapToInt(this::parseIndexDefinition) // + .toArray(); + return selectIteration(parentSelector, iterationIndices); + } + + private IntStream parseIndexDefinition(String value) { + String[] parts = value.split("\\.\\.", 2); + int firstIndex = Integer.parseInt(parts[0]); + if (parts.length == 2) { + int lastIndex = Integer.parseInt(parts[1]); + return IntStream.rangeClosed(firstIndex, lastIndex); + } + return IntStream.of(firstIndex); + } + + private DiscoverySelector createParentSelector(String type, String value) { + switch (type) { + case "module": + return selectModule(value); + case "uri": + return selectUri(value); + case "file": + return selectFile(value); + case "directory": + return selectDirectory(value); + case "package": + return selectPackage(value); + case "class": + return selectClass(value); + case "method": + return selectMethod(value); + case "resource": + return selectClasspathResource(value); + default: + throw new IllegalArgumentException("Unknown type: " + type); + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java new file mode 100644 index 00000000..8981def9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java @@ -0,0 +1,140 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestExecutionResult; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public enum Theme { + + /** + * ASCII 7-bit characters form the tree branch. + * + *

Example test plan execution tree: + *

+	 * +-- engine alpha
+	 * | '-- container BEGIN
+	 * |   +-- test 00 [OK]
+	 * |   '-- test 01 [OK]
+	 * '-- engine omega
+	 *   +-- container END
+	 *   | +-- test 10 [OK]
+	 *   | '-- test 11 [A] aborted
+	 *   '-- container FINAL
+	 *     +-- skipped [S] because
+	 *     '-- failing [X] BäMM
+	 * 
+ */ + ASCII(".", "| ", "+--", "'--", "[OK]", "[A]", "[X]", "[S]"), + + /** + * Unicode (extended ASCII) characters are used to display the test execution tree. + * + *

Example test plan execution tree: + *

+	 * ├─ engine alpha ✔
+	 * │  └─ container BEGIN ✔
+	 * │     ├─ test 00 ✔
+	 * │     └─ test 01 ✔
+	 * └─ engine omega ✔
+	 *    ├─ container END ✔
+	 *    │  ├─ test 10 ✔
+	 *    │  └─ test 11 ■ aborted
+	 *    └─ container FINAL ✔
+	 *       ├─ skipped ↷ because
+	 *       └─ failing ✘ BäMM
+	 * 
+ */ + UNICODE("╷", "│ ", "├─", "└─", "✔", "■", "✘", "↷"); + + public static Theme valueOf(Charset charset) { + if (StandardCharsets.UTF_8.equals(charset)) { + return UNICODE; + } + return ASCII; + } + + private final String[] tiles; + private final String blank; + + Theme(String... tiles) { + this.tiles = tiles; + this.blank = new String(new char[vertical().length()]).replace('\0', ' '); + } + + public final String root() { + return tiles[0]; + } + + public final String vertical() { + return tiles[1]; + } + + public final String blank() { + return blank; + } + + public final String entry() { + return tiles[2]; + } + + public final String end() { + return tiles[3]; + } + + public final String successful() { + return tiles[4]; + } + + public final String aborted() { + return tiles[5]; + } + + public final String failed() { + return tiles[6]; + } + + public final String skipped() { + return tiles[7]; + } + + public final String status(TestExecutionResult result) { + switch (result.getStatus()) { + case SUCCESSFUL: + return successful(); + case ABORTED: + return aborted(); + case FAILED: + return failed(); + default: + return result.getStatus().name(); + } + } + + /** + * Return lower case {@link #name()} for easier usage in help text for + * available options. + */ + @Override + public final String toString() { + return name().toLowerCase(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java new file mode 100644 index 00000000..babb46e2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java @@ -0,0 +1,5 @@ +/** + * Configuration options for JUnit's console launcher. + */ + +package org.junit.platform.console.options; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java new file mode 100644 index 00000000..73bc1b41 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java @@ -0,0 +1,5 @@ +/** + * Support for launching the JUnit Platform from the console. + */ + +package org.junit.platform.console; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java new file mode 100644 index 00000000..d330be01 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestIdentifier; + +/** + * @since 1.0 + */ +enum Color { + + NONE(0), + + BLACK(30), + + RED(31), + + GREEN(32), + + YELLOW(33), + + BLUE(34), + + PURPLE(35), + + CYAN(36), + + WHITE(37); + + static Color valueOf(TestExecutionResult result) { + switch (result.getStatus()) { + case SUCCESSFUL: + return Color.SUCCESSFUL; + case ABORTED: + return Color.ABORTED; + case FAILED: + return Color.FAILED; + default: + return Color.NONE; + } + } + + static Color valueOf(TestIdentifier testIdentifier) { + return testIdentifier.isContainer() ? CONTAINER : TEST; + } + + static final Color SUCCESSFUL = GREEN; + + static final Color ABORTED = YELLOW; + + static final Color FAILED = RED; + + static final Color SKIPPED = PURPLE; + + static final Color CONTAINER = CYAN; + + static final Color TEST = BLUE; + + static final Color DYNAMIC = PURPLE; + + static final Color REPORTED = WHITE; + + private final String ansiString; + + Color(int ansiCode) { + this.ansiString = "\u001B[" + ansiCode + "m"; + } + + @Override + public String toString() { + return this.ansiString; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java new file mode 100644 index 00000000..a587980f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Map; +import java.util.Properties; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @since 1.9 + */ +class ColorPalette { + + public static final ColorPalette SINGLE_COLOR = new ColorPalette(singleColorPalette(), false); + public static final ColorPalette DEFAULT = new ColorPalette(defaultPalette(), false); + public static final ColorPalette NONE = new ColorPalette(new EnumMap<>(Style.class), true); + + private final Map colorsToAnsiSequences; + private final boolean disableAnsiColors; + + // https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters + private static Map defaultPalette() { + Map colorsToAnsiSequences = new EnumMap<>(Style.class); + colorsToAnsiSequences.put(Style.NONE, "0"); + colorsToAnsiSequences.put(Style.SUCCESSFUL, "32"); + colorsToAnsiSequences.put(Style.ABORTED, "33"); + colorsToAnsiSequences.put(Style.FAILED, "31"); + colorsToAnsiSequences.put(Style.SKIPPED, "35"); + colorsToAnsiSequences.put(Style.CONTAINER, "36"); + colorsToAnsiSequences.put(Style.TEST, "34"); + colorsToAnsiSequences.put(Style.DYNAMIC, "35"); + colorsToAnsiSequences.put(Style.REPORTED, "37"); + return colorsToAnsiSequences; + } + + private static Map singleColorPalette() { + Map colorsToAnsiSequences = new EnumMap<>(Style.class); + colorsToAnsiSequences.put(Style.NONE, "0"); + colorsToAnsiSequences.put(Style.SUCCESSFUL, "1"); + colorsToAnsiSequences.put(Style.ABORTED, "4"); + colorsToAnsiSequences.put(Style.FAILED, "7"); + colorsToAnsiSequences.put(Style.SKIPPED, "9"); + colorsToAnsiSequences.put(Style.CONTAINER, "1"); + colorsToAnsiSequences.put(Style.TEST, "0"); + colorsToAnsiSequences.put(Style.DYNAMIC, "0"); + colorsToAnsiSequences.put(Style.REPORTED, "2"); + return colorsToAnsiSequences; + } + + public ColorPalette(Map overrides) { + this(defaultPalette(), false); + + if (overrides.containsKey(Style.NONE)) { + throw new IllegalArgumentException("Cannot override the standard style 'NONE'"); + } + this.colorsToAnsiSequences.putAll(overrides); + } + + public ColorPalette(Properties properties) { + this(toOverrideMap(properties)); + } + + public ColorPalette(Reader reader) { + this(getProperties(reader)); + } + + public ColorPalette(Path path) { + this(getProperties(path)); + } + + private ColorPalette(Map colorsToAnsiSequences, boolean disableAnsiColors) { + this.colorsToAnsiSequences = colorsToAnsiSequences; + this.disableAnsiColors = disableAnsiColors; + } + + private static Map toOverrideMap(Properties properties) { + Map upperCaseProperties = properties.entrySet().stream().collect( + Collectors.toMap(entry -> ((String) entry.getKey()).toUpperCase(), entry -> (String) entry.getValue())); + + return Arrays.stream(Style.values()).filter(style -> upperCaseProperties.containsKey(style.name())).collect( + Collectors.toMap(Function.identity(), style -> upperCaseProperties.get(style.name()))); + } + + private static Properties getProperties(Reader reader) { + Properties properties = new Properties(); + try { + properties.load(reader); + } + catch (IOException e) { + throw new IllegalArgumentException("Could not read color palette properties", e); + } + return properties; + } + + private static Properties getProperties(Path path) { + try (FileReader fileReader = new FileReader(path.toFile())) { + return getProperties(fileReader); + } + catch (IOException e) { + throw new IllegalArgumentException("Could not open color palette properties file", e); + } + } + + public String paint(Style style, String text) { + return this.disableAnsiColors || style == Style.NONE ? text + : getAnsiFormatter(style) + text + getAnsiFormatter(Style.NONE); + } + + private String getAnsiFormatter(Style style) { + return String.format("\u001B[%sm", this.colorsToAnsiSequences.get(style)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java new file mode 100644 index 00000000..a9e540a3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java @@ -0,0 +1,150 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.PrintWriter; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.console.options.Details; +import org.junit.platform.console.options.Theme; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0") +public class ConsoleTestExecutor { + + private final CommandLineOptions options; + private final Supplier launcherSupplier; + + public ConsoleTestExecutor(CommandLineOptions options) { + this(options, LauncherFactory::create); + } + + // for tests only + ConsoleTestExecutor(CommandLineOptions options, Supplier launcherSupplier) { + this.options = options; + this.launcherSupplier = launcherSupplier; + } + + public TestExecutionSummary execute(PrintWriter out) throws Exception { + return new CustomContextClassLoaderExecutor(createCustomClassLoader()).invoke(() -> executeTests(out)); + } + + private TestExecutionSummary executeTests(PrintWriter out) { + Launcher launcher = launcherSupplier.get(); + SummaryGeneratingListener summaryListener = registerListeners(out, launcher); + + LauncherDiscoveryRequest discoveryRequest = new DiscoveryRequestCreator().toDiscoveryRequest(options); + launcher.execute(discoveryRequest); + + TestExecutionSummary summary = summaryListener.getSummary(); + if (summary.getTotalFailureCount() > 0 || options.getDetails() != Details.NONE) { + printSummary(summary, out); + } + + return summary; + } + + private Optional createCustomClassLoader() { + List additionalClasspathEntries = options.getExistingAdditionalClasspathEntries(); + if (!additionalClasspathEntries.isEmpty()) { + URL[] urls = additionalClasspathEntries.stream().map(this::toURL).toArray(URL[]::new); + ClassLoader parentClassLoader = ClassLoaderUtils.getDefaultClassLoader(); + ClassLoader customClassLoader = URLClassLoader.newInstance(urls, parentClassLoader); + return Optional.of(customClassLoader); + } + return Optional.empty(); + } + + private URL toURL(Path path) { + try { + return path.toUri().toURL(); + } + catch (Exception ex) { + throw new JUnitException("Invalid classpath entry: " + path, ex); + } + } + + private SummaryGeneratingListener registerListeners(PrintWriter out, Launcher launcher) { + // always register summary generating listener + SummaryGeneratingListener summaryListener = new SummaryGeneratingListener(); + launcher.registerTestExecutionListeners(summaryListener); + // optionally, register test plan execution details printing listener + createDetailsPrintingListener(out).ifPresent(launcher::registerTestExecutionListeners); + // optionally, register XML reports writing listener + createXmlWritingListener(out).ifPresent(launcher::registerTestExecutionListeners); + return summaryListener; + } + + private Optional createDetailsPrintingListener(PrintWriter out) { + ColorPalette colorPalette = getColorPalette(); + Theme theme = options.getTheme(); + switch (options.getDetails()) { + case SUMMARY: + // summary listener is always created and registered + return Optional.empty(); + case FLAT: + return Optional.of(new FlatPrintingListener(out, colorPalette)); + case TREE: + return Optional.of(new TreePrintingListener(out, colorPalette, theme)); + case VERBOSE: + return Optional.of(new VerboseTreePrintingListener(out, colorPalette, 16, theme)); + default: + return Optional.empty(); + } + } + + private ColorPalette getColorPalette() { + if (options.isAnsiColorOutputDisabled()) { + return ColorPalette.NONE; + } + if (options.getColorPalettePath() != null) { + return new ColorPalette(options.getColorPalettePath()); + } + if (options.isSingleColorPalette()) { + return ColorPalette.SINGLE_COLOR; + } + return ColorPalette.DEFAULT; + } + + private Optional createXmlWritingListener(PrintWriter out) { + return options.getReportsDir().map(reportsDir -> new LegacyXmlReportGeneratingListener(reportsDir, out)); + } + + private void printSummary(TestExecutionSummary summary, PrintWriter out) { + // Otherwise the failures have already been printed in detail + if (EnumSet.of(Details.NONE, Details.SUMMARY, Details.TREE).contains(options.getDetails())) { + summary.printFailuresTo(out); + } + summary.printTo(out); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java new file mode 100644 index 00000000..19794e9b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import java.util.Optional; +import java.util.concurrent.Callable; + +/** + * @since 1.0 + */ +class CustomContextClassLoaderExecutor { + + private final Optional customClassLoader; + + CustomContextClassLoaderExecutor(Optional customClassLoader) { + this.customClassLoader = customClassLoader; + } + + T invoke(Callable callable) throws Exception { + if (customClassLoader.isPresent()) { + // Only get/set context class loader when necessary to prevent problems with + // security managers + return replaceThreadContextClassLoaderAndInvoke(customClassLoader.get(), callable); + } + return callable.call(); + } + + private T replaceThreadContextClassLoaderAndInvoke(ClassLoader customClassLoader, Callable callable) + throws Exception { + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(customClassLoader); + return callable.call(); + } + finally { + Thread.currentThread().setContextClassLoader(originalClassLoader); + if (customClassLoader instanceof AutoCloseable) { + ((AutoCloseable) customClassLoader).close(); + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java new file mode 100644 index 00000000..2fb1ea03 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java @@ -0,0 +1,145 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.platform.engine.discovery.ClassNameFilter.excludeClassNamePatterns; +import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModules; +import static org.junit.platform.engine.discovery.PackageNameFilter.excludePackageNames; +import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; +import static org.junit.platform.launcher.EngineFilter.excludeEngines; +import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.TagFilter.excludeTags; +import static org.junit.platform.launcher.TagFilter.includeTags; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.nio.file.Path; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.junit.platform.commons.util.ModuleUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; + +/** + * @since 1.0 + */ +class DiscoveryRequestCreator { + + LauncherDiscoveryRequest toDiscoveryRequest(CommandLineOptions options) { + LauncherDiscoveryRequestBuilder requestBuilder = request(); + List selectors = createDiscoverySelectors(options); + requestBuilder.selectors(selectors); + addFilters(requestBuilder, options, selectors); + requestBuilder.configurationParameters(options.getConfigurationParameters()); + return requestBuilder.build(); + } + + private List createDiscoverySelectors(CommandLineOptions options) { + List explicitSelectors = options.getExplicitSelectors(); + if (options.isScanClasspath()) { + Preconditions.condition(explicitSelectors.isEmpty(), + "Scanning the classpath and using explicit selectors at the same time is not supported"); + return createClasspathRootSelectors(options); + } + if (options.isScanModulepath()) { + Preconditions.condition(explicitSelectors.isEmpty(), + "Scanning the module-path and using explicit selectors at the same time is not supported"); + return selectModules(ModuleUtils.findAllNonSystemBootModuleNames()); + } + return Preconditions.notEmpty(explicitSelectors, "No arguments were supplied to the ConsoleLauncher"); + } + + private List createClasspathRootSelectors(CommandLineOptions options) { + Set classpathRoots = determineClasspathRoots(options); + return selectClasspathRoots(classpathRoots); + } + + private Set determineClasspathRoots(CommandLineOptions options) { + if (options.getSelectedClasspathEntries().isEmpty()) { + Set rootDirs = new LinkedHashSet<>(ReflectionUtils.getAllClasspathRootDirectories()); + rootDirs.addAll(options.getExistingAdditionalClasspathEntries()); + return rootDirs; + } + return new LinkedHashSet<>(options.getSelectedClasspathEntries()); + } + + private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, CommandLineOptions options, + List selectors) { + requestBuilder.filters(includedClassNamePatterns(options, selectors)); + + if (!options.getExcludedClassNamePatterns().isEmpty()) { + requestBuilder.filters( + excludeClassNamePatterns(options.getExcludedClassNamePatterns().toArray(new String[0]))); + } + + if (!options.getIncludedPackages().isEmpty()) { + requestBuilder.filters(includePackageNames(options.getIncludedPackages())); + } + + if (!options.getExcludedPackages().isEmpty()) { + requestBuilder.filters(excludePackageNames(options.getExcludedPackages())); + } + + if (!options.getIncludedTagExpressions().isEmpty()) { + requestBuilder.filters(includeTags(options.getIncludedTagExpressions())); + } + + if (!options.getExcludedTagExpressions().isEmpty()) { + requestBuilder.filters(excludeTags(options.getExcludedTagExpressions())); + } + + if (!options.getIncludedEngines().isEmpty()) { + requestBuilder.filters(includeEngines(options.getIncludedEngines())); + } + + if (!options.getExcludedEngines().isEmpty()) { + requestBuilder.filters(excludeEngines(options.getExcludedEngines())); + } + } + + private ClassNameFilter includedClassNamePatterns(CommandLineOptions options, + List selectors) { + Stream patternStreams = Stream.concat( // + options.getIncludedClassNamePatterns().stream(), // + selectors.stream() // + .map(selector -> selector instanceof IterationSelector + ? ((IterationSelector) selector).getParentSelector() + : selector) // + .map(selector -> { + if (selector instanceof ClassSelector) { + return ((ClassSelector) selector).getClassName(); + } + if (selector instanceof MethodSelector) { + return ((MethodSelector) selector).getClassName(); + } + return null; + }) // + .filter(Objects::nonNull) // + .map(Pattern::quote)); + return includeClassNamePatterns(patternStreams.toArray(String[]::new)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java new file mode 100644 index 00000000..bb68bbff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import java.io.PrintWriter; +import java.util.regex.Pattern; + +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0 + */ +class FlatPrintingListener implements TestExecutionListener { + + private static final Pattern LINE_START_PATTERN = Pattern.compile("(?m)^"); + + static final String INDENTATION = " "; + + private final PrintWriter out; + private final ColorPalette colorPalette; + + FlatPrintingListener(PrintWriter out, ColorPalette colorPalette) { + this.out = out; + this.colorPalette = colorPalette; + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + this.out.printf("Test execution started. Number of static tests: %d%n", + testPlan.countTestIdentifiers(TestIdentifier::isTest)); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + this.out.println("Test execution finished."); + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + printlnTestDescriptor(Style.DYNAMIC, "Registered:", testIdentifier); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + printlnTestDescriptor(Style.SKIPPED, "Skipped:", testIdentifier); + printlnMessage(Style.SKIPPED, "Reason", reason); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + printlnTestDescriptor(Style.valueOf(testIdentifier), "Started:", testIdentifier); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + Style style = Style.valueOf(testExecutionResult); + printlnTestDescriptor(style, "Finished:", testIdentifier); + testExecutionResult.getThrowable().ifPresent(t -> printlnException(style, t)); + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + printlnTestDescriptor(Style.REPORTED, "Reported:", testIdentifier); + printlnMessage(Style.REPORTED, "Reported values", entry.toString()); + } + + private void printlnTestDescriptor(Style style, String message, TestIdentifier testIdentifier) { + println(style, "%-10s %s (%s)", message, testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); + } + + private void printlnException(Style style, Throwable throwable) { + printlnMessage(style, "Exception", ExceptionUtils.readStackTrace(throwable)); + } + + private void printlnMessage(Style style, String message, String detail) { + println(style, INDENTATION + "=> " + message + ": %s", indented(detail)); + } + + private void println(Style style, String format, Object... args) { + this.out.println(colorPalette.paint(style, String.format(format, args))); + } + + /** + * Indent the given message if it is a multi-line string. + * + *

{@link #INDENTATION} is used to prefix the start of each new line + * except the first one. + * + * @param message the message to indent + * @return indented message + */ + private static String indented(String message) { + return LINE_START_PATTERN.matcher(message).replaceAll(INDENTATION).trim(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java new file mode 100644 index 00000000..31f530b8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestIdentifier; + +/** + * @since 1.9 + */ +enum Style { + + NONE, SUCCESSFUL, ABORTED, FAILED, SKIPPED, CONTAINER, TEST, DYNAMIC, REPORTED; + + static Style valueOf(TestExecutionResult result) { + switch (result.getStatus()) { + case SUCCESSFUL: + return Style.SUCCESSFUL; + case ABORTED: + return Style.ABORTED; + case FAILED: + return Style.FAILED; + default: + return Style.NONE; + } + } + + static Style valueOf(TestIdentifier testIdentifier) { + return testIdentifier.isContainer() ? CONTAINER : TEST; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java new file mode 100644 index 00000000..40fdbf93 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestIdentifier; + +/** + * @since 1.0 + */ +class TreeNode { + + private final String caption; + private final long creation; + long duration; + private String reason; + private TestIdentifier identifier; + private TestExecutionResult result; + final Queue reports = new ConcurrentLinkedQueue<>(); + final Queue children = new ConcurrentLinkedQueue<>(); + boolean visible; + + TreeNode(String caption) { + this.caption = caption; + this.creation = System.currentTimeMillis(); + this.visible = false; + } + + TreeNode(TestIdentifier identifier) { + this(createCaption(identifier.getDisplayName())); + this.identifier = identifier; + this.visible = true; + } + + TreeNode(TestIdentifier identifier, String reason) { + this(identifier); + this.reason = reason; + } + + TreeNode addChild(TreeNode node) { + children.add(node); + return this; + } + + TreeNode addReportEntry(ReportEntry reportEntry) { + reports.add(reportEntry); + return this; + } + + TreeNode setResult(TestExecutionResult result) { + this.result = result; + this.duration = System.currentTimeMillis() - creation; + return this; + } + + public String caption() { + return caption; + } + + Optional reason() { + return Optional.ofNullable(reason); + } + + Optional result() { + return Optional.ofNullable(result); + } + + Optional identifier() { + return Optional.ofNullable(identifier); + } + + static String createCaption(String displayName) { + boolean normal = displayName.length() <= 80; + String caption = normal ? displayName : displayName.substring(0, 80) + "..."; + String whites = StringUtils.replaceWhitespaceCharacters(caption, " "); + return StringUtils.replaceIsoControlCharacters(whites, "."); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java new file mode 100644 index 00000000..4f72afff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java @@ -0,0 +1,172 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; + +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.console.options.Theme; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * @since 1.0 + */ +class TreePrinter { + + private final PrintWriter out; + private final Theme theme; + private final ColorPalette colorPalette; + + TreePrinter(PrintWriter out, Theme theme, ColorPalette colorPalette) { + this.out = out; + this.theme = theme; + this.colorPalette = colorPalette; + } + + void print(TreeNode node) { + out.println(color(Style.CONTAINER, theme.root())); + print(node, "", true); + out.flush(); + } + + private void print(TreeNode node, String indent, boolean continuous) { + if (node.visible) { + printVisible(node, indent, continuous); + } + if (node.children.isEmpty()) { + return; + } + if (node.visible) { + indent += continuous ? theme.vertical() : theme.blank(); + } + Iterator iterator = node.children.iterator(); + while (iterator.hasNext()) { + print(iterator.next(), indent, iterator.hasNext()); + } + } + + private void printVisible(TreeNode node, String indent, boolean continuous) { + String bullet = continuous ? theme.entry() : theme.end(); + String prefix = color(Style.CONTAINER, indent + bullet); + String tabbed = color(Style.CONTAINER, indent + tab(node, continuous)); + String caption = colorCaption(node); + String duration = color(Style.CONTAINER, node.duration + " ms"); + String icon = color(Style.SKIPPED, theme.skipped()); + if (node.result().isPresent()) { + TestExecutionResult result = node.result().get(); + Style resultStyle = Style.valueOf(result); + icon = color(resultStyle, theme.status(result)); + } + out.print(prefix); + out.print(" "); + out.print(caption); + if (node.duration > 10000 && node.children.isEmpty()) { + out.print(" "); + out.print(duration); + } + out.print(" "); + out.print(icon); + node.result().ifPresent(result -> printThrowable(tabbed, result)); + node.reason().ifPresent(reason -> printMessage(Style.SKIPPED, tabbed, reason)); + node.reports.forEach(e -> printReportEntry(tabbed, e)); + out.println(); + } + + private String tab(TreeNode node, boolean continuous) { + // We might be the "last" node in this level, that means + // `continuous == false`, but still need to include a vertical + // bar for printing stack traces, messages and reports. + // See https://github.com/junit-team/junit5/issues/1531 + if (node.children.size() > 0) { + return theme.blank() + theme.vertical(); + } + return (continuous ? theme.vertical() : theme.blank()) + theme.blank(); + } + + private String colorCaption(TreeNode node) { + String caption = node.caption(); + if (node.result().isPresent()) { + TestExecutionResult result = node.result().get(); + Style resultStyle = Style.valueOf(result); + if (result.getStatus() != Status.SUCCESSFUL) { + return color(resultStyle, caption); + } + } + if (node.reason().isPresent()) { + return color(Style.SKIPPED, caption); + } + Style style = node.identifier().map(Style::valueOf).orElse(Style.NONE); + return color(style, caption); + } + + private void printThrowable(String indent, TestExecutionResult result) { + if (!result.getThrowable().isPresent()) { + return; + } + Throwable throwable = result.getThrowable().get(); + String message = throwable.getMessage(); + if (StringUtils.isBlank(message)) { + message = throwable.toString(); + } + printMessage(Style.FAILED, indent, message); + } + + private void printReportEntry(String indent, ReportEntry reportEntry) { + out.println(); + out.print(indent); + out.print(reportEntry.getTimestamp()); + Set> entries = reportEntry.getKeyValuePairs().entrySet(); + if (entries.size() == 1) { + printReportEntry(" ", getOnlyElement(entries)); + return; + } + for (Map.Entry entry : entries) { + out.println(); + printReportEntry(indent + theme.blank(), entry); + } + } + + private void printReportEntry(String indent, Map.Entry mapEntry) { + out.print(indent); + out.print(color(Style.ABORTED, mapEntry.getKey())); + out.print(" = `"); + out.print(color(Style.SUCCESSFUL, mapEntry.getValue())); + out.print("`"); + } + + private void printMessage(Style style, String indent, String message) { + String[] lines = message.split("\\R"); + out.print(" "); + out.print(color(style, lines[0])); + if (lines.length > 1) { + for (int i = 1; i < lines.length; i++) { + out.println(); + out.print(indent); + if (StringUtils.isNotBlank(lines[i])) { + String extra = theme.blank(); + out.print(color(style, extra + lines[i])); + } + } + } + } + + private String color(Style style, String text) { + return colorPalette.paint(style, text); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java new file mode 100644 index 00000000..685ddff5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import java.io.PrintWriter; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import org.junit.platform.console.options.Theme; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0 + */ +class TreePrintingListener implements TestExecutionListener { + + private final Map nodesByUniqueId = new ConcurrentHashMap<>(); + private TreeNode root; + private final TreePrinter treePrinter; + + TreePrintingListener(PrintWriter out, ColorPalette colorPalette, Theme theme) { + this.treePrinter = new TreePrinter(out, theme, colorPalette); + } + + private void addNode(TestIdentifier testIdentifier, Supplier nodeSupplier) { + TreeNode node = nodeSupplier.get(); + nodesByUniqueId.put(testIdentifier.getUniqueIdObject(), node); + testIdentifier.getParentIdObject().map(nodesByUniqueId::get).orElse(root).addChild(node); + } + + private TreeNode getNode(TestIdentifier testIdentifier) { + return nodesByUniqueId.get(testIdentifier.getUniqueIdObject()); + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + root = new TreeNode(testPlan.toString()); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + treePrinter.print(root); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + addNode(testIdentifier, () -> new TreeNode(testIdentifier)); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + getNode(testIdentifier).setResult(testExecutionResult); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + addNode(testIdentifier, () -> new TreeNode(testIdentifier, reason)); + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + getNode(testIdentifier).addReportEntry(entry); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java new file mode 100644 index 00000000..9031a383 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java @@ -0,0 +1,189 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; +import static org.junit.platform.console.tasks.Style.NONE; + +import java.io.PrintWriter; +import java.util.ArrayDeque; +import java.util.Deque; + +import org.junit.platform.console.options.Theme; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0 + */ +class VerboseTreePrintingListener implements TestExecutionListener { + + private final PrintWriter out; + private final Theme theme; + private final ColorPalette colorPalette; + private final Deque frames; + private final String[] verticals; + private long executionStartedMillis; + + VerboseTreePrintingListener(PrintWriter out, ColorPalette colorPalette, int maxContainerNestingLevel, Theme theme) { + this.out = out; + this.colorPalette = colorPalette; + this.theme = theme; + + // create frame stack and push initial root frame + this.frames = new ArrayDeque<>(); + this.frames.push(0L); + + // create and populate vertical indentation lookup table + this.verticals = new String[Math.max(10, maxContainerNestingLevel) + 1]; + this.verticals[0] = ""; // no frame + this.verticals[1] = ""; // synthetic root "/" level + this.verticals[2] = ""; // "engine" level + + for (int i = 3; i < verticals.length; i++) { + verticals[i] = verticals[i - 1] + theme.vertical(); + } + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + frames.push(System.currentTimeMillis()); + + long tests = testPlan.countTestIdentifiers(TestIdentifier::isTest); + printf(NONE, "%s", "Test plan execution started. Number of static tests: "); + printf(Style.TEST, "%d%n", tests); + printf(Style.CONTAINER, "%s%n", theme.root()); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + frames.pop(); + + long tests = testPlan.countTestIdentifiers(TestIdentifier::isTest); + printf(NONE, "%s", "Test plan execution finished. Number of all tests: "); + printf(Style.TEST, "%d%n", tests); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + this.executionStartedMillis = System.currentTimeMillis(); + if (testIdentifier.isContainer()) { + printVerticals(theme.entry()); + printf(Style.CONTAINER, " %s", testIdentifier.getDisplayName()); + printf(NONE, "%n"); + frames.push(System.currentTimeMillis()); + } + if (testIdentifier.isContainer()) { + return; + } + printVerticals(theme.entry()); + printf(Style.valueOf(testIdentifier), " %s%n", testIdentifier.getDisplayName()); + printDetails(testIdentifier); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + testExecutionResult.getThrowable().ifPresent(t -> printDetail(Style.FAILED, "caught", readStackTrace(t))); + if (testIdentifier.isContainer()) { + Long creationMillis = frames.pop(); + printVerticals(theme.end()); + printf(Style.CONTAINER, " %s", testIdentifier.getDisplayName()); + printf(NONE, " finished after %d ms.%n", System.currentTimeMillis() - creationMillis); + return; + } + printDetail(NONE, "duration", "%d ms%n", System.currentTimeMillis() - executionStartedMillis); + String status = theme.status(testExecutionResult) + " " + testExecutionResult.getStatus(); + printDetail(Style.valueOf(testExecutionResult), "status", "%s%n", status); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + printVerticals(theme.entry()); + printf(Style.valueOf(testIdentifier), " %s%n", testIdentifier.getDisplayName()); + printDetails(testIdentifier); + printDetail(Style.SKIPPED, "reason", reason); + printDetail(Style.SKIPPED, "status", theme.skipped() + " SKIPPED"); + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + printVerticals(theme.entry()); + printf(Style.DYNAMIC, " %s", testIdentifier.getDisplayName()); + printf(NONE, "%s%n", " dynamically registered"); + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + printDetail(Style.REPORTED, "reports", entry.toString()); + } + + /** + * Print static information about the test identifier. + */ + private void printDetails(TestIdentifier testIdentifier) { + printDetail(NONE, "tags", "%s%n", testIdentifier.getTags()); + printDetail(NONE, "uniqueId", "%s%n", testIdentifier.getUniqueId()); + printDetail(NONE, "parent", "%s%n", testIdentifier.getParentId().orElse("[]")); + testIdentifier.getSource().ifPresent(source -> printDetail(NONE, "source", "%s%n", source)); + } + + private String verticals() { + return verticals(frames.size()); + } + + private String verticals(int index) { + return verticals[Math.min(index, verticals.length - 1)]; + } + + private void printVerticals(String tile) { + printf(NONE, verticals()); + printf(NONE, tile); + } + + private void printf(Style style, String message, Object... args) { + out.printf(colorPalette.paint(style, message), args); + out.flush(); + } + + /** + * Print single detail with a potential multi-line message. + */ + private void printDetail(Style style, String detail, String format, Object... args) { + // print initial verticals - expecting to be at start of the line + String verticals = verticals(frames.size() + 1); + printf(NONE, verticals); + String detailFormat = "%9s"; + // omit detail string if it's empty + if (!detail.isEmpty()) { + printf(NONE, "%s", String.format(detailFormat + ": ", detail)); + } + // trivial case: at least one arg is given? Let printf do the entire work + if (args.length > 0) { + printf(style, format, args); + return; + } + // still here? Split format into separate lines and indent them from the second line on + String[] lines = format.split("\\R"); + printf(style, "%s", lines[0]); + if (lines.length > 1) { + String delimiter = System.lineSeparator() + verticals + String.format(detailFormat + " ", ""); + for (int i = 1; i < lines.length; i++) { + printf(NONE, "%s", delimiter); + printf(style, "%s", lines[i]); + } + } + printf(NONE, "%n"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java new file mode 100644 index 00000000..bcfe3d83 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java @@ -0,0 +1,5 @@ +/** + * Internal execution tasks for JUnit's console launcher. + */ + +package org.junit.platform.console.tasks; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java new file mode 100644 index 00000000..89698f10 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.Console; +import java.nio.charset.Charset; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@code java.io.Console} + * and friends. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.9 + */ +@API(status = INTERNAL, since = "1.9") +public class ConsoleUtils { + + /** + * {@return the charset of the console} + */ + public static Charset charset() { + Console console = System.console(); + return console != null ? console.charset() : Charset.defaultCharset(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java new file mode 100644 index 00000000..d0a830a4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.io.PrintWriter; +import java.util.spi.ToolProvider; + +import org.apiguardian.api.API; + +/** + * Run the JUnit Platform Console Launcher as a service. + * + * @since 1.6 + */ +@API(status = EXPERIMENTAL, since = "1.6") +public class ConsoleLauncherToolProvider implements ToolProvider { + + @Override + public String name() { + return "junit"; + } + + @Override + public int run(PrintWriter out, PrintWriter err, String... args) { + return ConsoleLauncher.execute(out, err, args).getExitCode(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider new file mode 100644 index 00000000..ac107bc0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider @@ -0,0 +1 @@ +org.junit.platform.console.ConsoleLauncherToolProvider diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java new file mode 100644 index 00000000..08d28b43 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Support for launching the JUnit Platform from the console. + * + * @since 1.0 + * @provides java.util.spi.ToolProvider + */ +module org.junit.platform.console { + requires static org.apiguardian.api; + requires org.junit.platform.commons; + requires org.junit.platform.engine; + requires org.junit.platform.launcher; + requires org.junit.platform.reporting; + + provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts new file mode 100644 index 00000000..6c780cea --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts @@ -0,0 +1,19 @@ +plugins { + `java-library-conventions` + `java-test-fixtures` +} + +description = "JUnit Platform Engine API" + +dependencies { + api(platform(projects.junitBom)) + api(libs.opentest4j) + api(projects.junitPlatformCommons) + + compileOnlyApi(libs.apiguardian) + + testImplementation(libs.assertj) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java new file mode 100644 index 00000000..41522e85 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; +import static org.junit.platform.engine.FilterResult.included; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Predicate; + +import org.junit.platform.commons.util.Preconditions; + +/** + * Combines a collection of {@link Filter Filters} into a new filter that will + * include elements if and only if all of the filters in the specified collection + * include it. + * + * @since 1.0 + */ +class CompositeFilter implements Filter { + + @SuppressWarnings("rawtypes") + private static final Filter ALWAYS_INCLUDED_FILTER = new Filter() { + @Override + public FilterResult apply(Object obj) { + return ALWAYS_INCLUDED_RESULT; + } + + @Override + public Predicate toPredicate() { + return obj -> true; + } + }; + + private static final FilterResult ALWAYS_INCLUDED_RESULT = included("Always included"); + private static final FilterResult INCLUDED_BY_ALL_FILTERS = included("Element was included by all filters."); + + @SuppressWarnings("unchecked") + static Filter alwaysIncluded() { + return ALWAYS_INCLUDED_FILTER; + } + + private final Collection> filters; + + CompositeFilter(Collection> filters) { + this.filters = new ArrayList<>(Preconditions.notEmpty(filters, "filters must not be empty")); + } + + @Override + public FilterResult apply(T element) { + // @formatter:off + return filters.stream() + .map(filter -> filter.apply(element)) + .filter(FilterResult::excluded) + .findFirst() + .orElse(INCLUDED_BY_ALL_FILTERS); + // @formatter:on + } + + @Override + public Predicate toPredicate() { + // @formatter:off + return filters.stream() + .map(Filter::toPredicate) + .reduce(Predicate::and) + .get(); // it's safe to call get() here because the constructor ensures filters is not empty + // @formatter:on + } + + @Override + public String toString() { + // @formatter:off + return filters.stream() + .map(Object::toString) + .map(value -> format("(%s)", value)) + .collect(joining(" and ")); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java new file mode 100644 index 00000000..7c859ab3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java @@ -0,0 +1,152 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; + +/** + * Configuration parameters that {@link TestEngine TestEngines} may use to + * influence test discovery and execution. + * + *

For example, the JUnit Jupiter engine uses a configuration parameter to + * enable IDEs and build tools to deactivate conditional test execution. + * + *

As of JUnit Platform 1.8, configuration parameters are also made available to + * implementations of the {@link org.junit.platform.launcher.TestExecutionListener} + * API via the {@link org.junit.platform.launcher.TestPlan}. + * + * @since 1.0 + * @see TestEngine + * @see EngineDiscoveryRequest + * @see ExecutionRequest + */ +@API(status = STABLE, since = "1.0") +public interface ConfigurationParameters { + + /** + * Name of the JUnit Platform configuration file: {@value}. + * + *

If a properties file with this name is present in the root of the + * classpath, it will be used as a source for configuration + * parameters. If multiple files are present, only the first one + * detected in the classpath will be used. + * + * @see java.util.Properties + */ + String CONFIG_FILE_NAME = "junit-platform.properties"; + + /** + * Get the configuration parameter stored under the specified {@code key}. + * + *

If no such key is present in this {@code ConfigurationParameters}, + * an attempt will be made to look up the value as a JVM system property. + * If no such system property exists, an attempt will be made to look up + * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties + * file}. + * + * @param key the key to look up; never {@code null} or blank + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @see #getBoolean(String) + * @see System#getProperty(String) + * @see #CONFIG_FILE_NAME + */ + Optional get(String key); + + /** + * Get the boolean configuration parameter stored under the specified + * {@code key}. + * + *

If no such key is present in this {@code ConfigurationParameters}, + * an attempt will be made to look up the value as a JVM system property. + * If no such system property exists, an attempt will be made to look up + * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties + * file}. + * + * @param key the key to look up; never {@code null} or blank + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @see #get(String) + * @see Boolean#parseBoolean(String) + * @see System#getProperty(String) + * @see #CONFIG_FILE_NAME + */ + Optional getBoolean(String key); + + /** + * Get and transform the configuration parameter stored under the specified + * {@code key} using the specified {@code transformer}. + * + *

If no such key is present in this {@code ConfigurationParameters}, + * an attempt will be made to look up the value as a JVM system property. + * If no such system property exists, an attempt will be made to look up + * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties + * file}. + * + *

In case the transformer throws an exception, it will be wrapped in a + * {@link JUnitException} with a helpful message. + * + * @param key the key to look up; never {@code null} or blank + * @param transformer the transformer to apply in case a value is found; + * never {@code null} + * @return an {@code Optional} containing the value; never {@code null} + * but potentially empty + * + * @since 1.3 + * @see #getBoolean(String) + * @see System#getProperty(String) + * @see #CONFIG_FILE_NAME + */ + @API(status = STABLE, since = "1.3") + default Optional get(String key, Function transformer) { + Preconditions.notNull(transformer, "transformer must not be null"); + return get(key).map(input -> { + try { + return transformer.apply(input); + } + catch (Exception ex) { + String message = String.format( + "Failed to transform configuration parameter with key '%s' and initial value '%s'", key, input); + throw new JUnitException(message, ex); + } + }); + } + + /** + * Get the number of configuration parameters stored directly in this + * {@code ConfigurationParameters}. + * @deprecated as of JUnit Platform 1.9 in favor of {@link #keySet()} + */ + @Deprecated + @API(status = DEPRECATED, since = "1.9") + int size(); + + /** + * Get the keys of all configuration parameters stored in this + * {@code ConfigurationParameters}. + * + * @return the set of keys contained in this {@code ConfigurationParameters} + */ + @API(status = STABLE, since = "1.9") + Set keySet(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java new file mode 100644 index 00000000..68b5b1d5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * A {@link DiscoveryFilter} is applied during test discovery to determine if + * a given container or test should be included in the test plan. + * + *

{@link TestEngine TestEngines} should apply {@code DiscoveryFilters} + * during the test discovery phase. + * + * @since 1.0 + * @see EngineDiscoveryRequest + * @see TestEngine + */ +@API(status = STABLE, since = "1.0") +public interface DiscoveryFilter extends Filter { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java new file mode 100644 index 00000000..06c222ac --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * A selector defines what a {@link TestEngine} can use to discover tests + * — for example, the name of a Java class, the path to a file or + * directory, etc. + * + * @since 1.0 + * @see EngineDiscoveryRequest + * @see org.junit.platform.engine.discovery.DiscoverySelectors + */ +@API(status = STABLE, since = "1.0") +public interface DiscoverySelector { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java new file mode 100644 index 00000000..615ce857 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * {@code EngineDiscoveryListener} contains {@link TestEngine} access to the + * information necessary to discover tests and containers. + * + *

All methods in this interface have empty default implementations. + * Concrete implementations may therefore override one or more of these methods + * to be notified of the selected events. + * + *

The methods declared in this interface should be called by + * each {@link TestEngine} during test discovery. However, since this interface + * was only added in 1.6, older engines might not yet do so. + * + * @since 1.6 + * @see EngineDiscoveryRequest#getDiscoveryListener() + */ +@API(status = EXPERIMENTAL, since = "1.6") +public interface EngineDiscoveryListener { + + /** + * No-op implementation of {@code EngineDiscoveryListener} + */ + EngineDiscoveryListener NOOP = new EngineDiscoveryListener() { + }; + + /** + * Must be called after a discovery selector has been processed by a test + * engine. + * + *

Exceptions thrown by implementations of this method will cause test + * discovery of the current engine to be aborted. + * + * @param engineId the unique ID of the engine descriptor + * @param selector the processed selector + * @param result the resolution result of the supplied engine and selector + * @see SelectorResolutionResult + */ + default void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java new file mode 100644 index 00000000..e5062594 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; + +import org.apiguardian.api.API; + +/** + * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the + * information necessary to discover tests and containers. + * + *

A request is comprised of {@linkplain DiscoverySelector selectors} and + * {@linkplain DiscoveryFilter filters}. While the former select + * resources that engines can use to discover tests, the latter specify how + * such resources are to be filtered. All of the filters + * have to include a resource for it to end up in the test plan. + * + *

In addition, the supplied {@linkplain ConfigurationParameters + * configuration parameters} can be used to influence the discovery process. + * + * @since 1.0 + * @see TestEngine + * @see TestDescriptor + * @see DiscoverySelector + * @see DiscoveryFilter + * @see ConfigurationParameters + */ +@API(status = STABLE, since = "1.0") +public interface EngineDiscoveryRequest { + + /** + * Get the {@link DiscoverySelector DiscoverySelectors} for this request, + * filtered by a particular type. + * + * @param selectorType the type of {@link DiscoverySelector} to filter by + * @return all selectors of this request that are instances of + * {@code selectorType}; never {@code null} but potentially empty + */ + List getSelectorsByType(Class selectorType); + + /** + * Get the {@link DiscoveryFilter DiscoveryFilters} for this request, + * filtered by a particular type. + * + *

The returned filters are to be combined using AND semantics, i.e. all + * of them have to include a resource for it to end up in the test plan. + * + * @param filterType the type of {@link DiscoveryFilter} to filter by + * @return all filters of this request that are instances of + * {@code filterType}; never {@code null} but potentially empty + */ + > List getFiltersByType(Class filterType); + + /** + * Get the {@link ConfigurationParameters} for this request. + * + * @return the configuration parameters; never {@code null} + */ + ConfigurationParameters getConfigurationParameters(); + + /** + * Get the {@link EngineDiscoveryListener} for this request. + * + *

The default implementation returns a no-op listener that ignores all + * calls so that engines that call this methods can be used with an earlier + * version of the JUnit Platform that did not yet include this API. + * + * @return the discovery listener; never {@code null} + * @since 1.6 + */ + @API(status = EXPERIMENTAL, since = "1.6") + default EngineDiscoveryListener getDiscoveryListener() { + return EngineDiscoveryListener.NOOP; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java new file mode 100644 index 00000000..315548ff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java @@ -0,0 +1,140 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * Listener to be notified of test execution events by + * {@linkplain TestEngine test engines}. + * + *

Contrary to JUnit 4, {@linkplain TestEngine test engines} + * must report events not only for {@linkplain TestDescriptor test descriptors} + * that represent executable leaves but also for all intermediate containers. + * + * @since 1.0 + * @see TestEngine + * @see ExecutionRequest + */ +@API(status = STABLE, since = "1.0") +public interface EngineExecutionListener { + + /** + * No-op implementation of {@code EngineExecutionListener} + */ + EngineExecutionListener NOOP = new EngineExecutionListener() { + }; + + /** + * Must be called when a new, dynamic {@link TestDescriptor} has been + * registered. + * + *

A dynamic test is a test that is not known a-priori and + * therefore was not present in the test tree when discovering tests. + * + * @param testDescriptor the descriptor of the newly registered test + * or container + */ + default void dynamicTestRegistered(TestDescriptor testDescriptor) { + } + + /** + * Must be called when the execution of a leaf or subtree of the test tree + * has been skipped. + * + *

The {@link TestDescriptor} may represent a test or a container. In the + * case of a container, engines must not fire any additional events for its + * descendants. + * + *

A skipped test or subtree of tests must not be reported as + * {@linkplain #executionStarted started} or + * {@linkplain #executionFinished finished}. + * + * @param testDescriptor the descriptor of the skipped test or container + * @param reason a human-readable message describing why the execution + * has been skipped + */ + default void executionSkipped(TestDescriptor testDescriptor, String reason) { + } + + /** + * Must be called when the execution of a leaf or subtree of the test tree + * is about to be started. + * + *

The {@link TestDescriptor} may represent a test or a container. In the + * case of a container, engines have to fire additional events for its + * children. + * + *

This method may only be called if the test or container has not + * been {@linkplain #executionSkipped skipped}. + * + *

This method must be called for a container {@code TestDescriptor} + * before {@linkplain #executionStarted starting} or + * {@linkplain #executionSkipped skipping} any of its children. + * + * @param testDescriptor the descriptor of the started test or container + */ + default void executionStarted(TestDescriptor testDescriptor) { + } + + /** + * Must be called when the execution of a leaf or subtree of the test tree + * has finished, regardless of the outcome. + * + *

The {@link TestDescriptor} may represent a test or a container. + * + *

This method may only be called if the test or container has not + * been {@linkplain #executionSkipped skipped}. + * + *

This method must be called for a container {@code TestIdentifier} + * after all of its children have been + * {@linkplain #executionSkipped skipped} or have + * {@linkplain #executionFinished finished}. + * + *

The {@link TestExecutionResult} describes the result of the execution + * for the supplied {@code testDescriptor}. The result does not include or + * aggregate the results of its children. For example, a container with a + * failing test must be reported as {@link Status#SUCCESSFUL SUCCESSFUL} even + * if one or more of its children are reported as {@link Status#FAILED FAILED}. + * + * @param testDescriptor the descriptor of the finished test or container + * @param testExecutionResult the (unaggregated) result of the execution for + * the supplied {@code TestDescriptor} + * + * @see TestExecutionResult + */ + default void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + } + + /** + * Can be called for any {@link TestDescriptor} in order to publish additional + * information to the reporting infrastructure — for example: + * + *

    + *
  • Output that would otherwise go to {@code System.out}
  • + *
  • Information about test context or test data
  • + *
+ * + *

The current lifecycle state of the supplied {@code TestDescriptor} is + * not relevant: reporting events can occur at any time. + * + * @param testDescriptor the descriptor of the test or container to which + * the reporting entry belongs + * @param entry a {@code ReportEntry} instance to be published + */ + default void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java new file mode 100644 index 00000000..2c77f1fd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * Provides a single {@link TestEngine} access to the information necessary to + * execute its tests. + * + *

A request contains an engine's root {@link TestDescriptor}, the + * {@link EngineExecutionListener} to be notified of test execution events, and + * {@link ConfigurationParameters} that the engine may use to influence test + * execution. + * + * @since 1.0 + * @see TestEngine + */ +@API(status = STABLE, since = "1.0") +public class ExecutionRequest { + + private final TestDescriptor rootTestDescriptor; + + private final EngineExecutionListener engineExecutionListener; + + private final ConfigurationParameters configurationParameters; + + @API(status = INTERNAL, since = "1.0") + public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, + ConfigurationParameters configurationParameters) { + this.rootTestDescriptor = rootTestDescriptor; + this.engineExecutionListener = engineExecutionListener; + this.configurationParameters = configurationParameters; + } + + /** + * Factory for creating an execution request. + * + * @param rootTestDescriptor the engine's root {@link TestDescriptor} + * @param engineExecutionListener the {@link EngineExecutionListener} to be + * notified of test execution events + * @param configurationParameters {@link ConfigurationParameters} that the + * engine may use to influence test execution + * @return a new {@code ExecutionRequest}; never {@code null} + * @since 1.9 + */ + @API(status = STABLE, since = "1.9") + public static ExecutionRequest create(TestDescriptor rootTestDescriptor, + EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { + return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters); + } + + /** + * Get the root {@link TestDescriptor} of the engine that processes this + * request. + * + *

Note: the root descriptor is the + * {@code TestDescriptor} returned by + * {@link TestEngine#discover(EngineDiscoveryRequest, UniqueId)}. + */ + public TestDescriptor getRootTestDescriptor() { + return this.rootTestDescriptor; + } + + /** + * Get the {@link EngineExecutionListener} to be notified of test execution + * events. + */ + public EngineExecutionListener getEngineExecutionListener() { + return this.engineExecutionListener; + } + + /** + * Get the {@link ConfigurationParameters} that the engine may use to + * influence test execution. + */ + public ConfigurationParameters getConfigurationParameters() { + return this.configurationParameters; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java new file mode 100644 index 00000000..2d77ff1e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static java.util.Arrays.asList; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.CompositeFilter.alwaysIncluded; + +import java.util.Collection; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +/** + * A {@link Filter} can be applied to determine if an object should be + * included or excluded in a result set. + * + *

For example, tests may be filtered during or after test discovery + * based on certain criteria. + * + *

Clients should not implement this interface directly but rather one of + * its subinterfaces. + * + * @since 1.0 + * @see DiscoveryFilter + */ +@FunctionalInterface +@API(status = STABLE, since = "1.0") +public interface Filter { + + /** + * Return a filter that will include elements if and only if all of the + * filters in the supplied array of {@link Filter filters} include it. + * + *

If the array is empty, the returned filter will include all elements + * it is asked to filter. + * + * @param filters the array of filters to compose; never {@code null} + * @see #composeFilters(Collection) + */ + @SafeVarargs + @SuppressWarnings("varargs") + static Filter composeFilters(Filter... filters) { + Preconditions.notNull(filters, "filters array must not be null"); + Preconditions.containsNoNullElements(filters, "individual filters must not be null"); + + if (filters.length == 0) { + return alwaysIncluded(); + } + if (filters.length == 1) { + return filters[0]; + } + return new CompositeFilter<>(asList(filters)); + } + + /** + * Return a filter that will include elements if and only if all of the + * filters in the supplied collection of {@link Filter filters} include it. + * + *

If the collection is empty, the returned filter will include all + * elements it is asked to filter. + * + * @param filters the collection of filters to compose; never {@code null} + * @see #composeFilters(Filter...) + */ + static Filter composeFilters(Collection> filters) { + Preconditions.notNull(filters, "Filters must not be null"); + + if (filters.isEmpty()) { + return alwaysIncluded(); + } + if (filters.size() == 1) { + return getOnlyElement(filters); + } + return new CompositeFilter<>(filters); + } + + /** + * Return a filter that will include elements if and only if the adapted + * {@code Filter} includes the value converted using the supplied + * {@link Function}. + * + * @param adaptee the filter to be adapted + * @param converter the converter function to apply + */ + static Filter adaptFilter(Filter adaptee, Function converter) { + return input -> adaptee.apply(converter.apply(input)); + } + + /** + * Apply this filter to the supplied object. + */ + FilterResult apply(T object); + + /** + * Return a {@link Predicate} that returns {@code true} if this filter + * includes the object supplied to the predicate's + * {@link Predicate#test test} method. + */ + default Predicate toPredicate() { + return object -> apply(object).included(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java new file mode 100644 index 00000000..622205aa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Optional; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * The result of applying a {@link Filter}. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public class FilterResult { + + /** + * Factory for creating included results. + * + * @param reason the reason why the filtered object was included + * @return an included {@code FilterResult} with the given reason + */ + public static FilterResult included(String reason) { + return new FilterResult(true, reason); + } + + /** + * Factory for creating excluded results. + * + * @param reason the reason why the filtered object was excluded + * @return an excluded {@code FilterResult} with the given reason + */ + public static FilterResult excluded(String reason) { + return new FilterResult(false, reason); + } + + /** + * Factory for creating filter results based on the condition given. + * + * @param included whether or not the filtered object should be included + * @return a valid {@code FilterResult} for the given condition + */ + public static FilterResult includedIf(boolean included) { + return includedIf(included, () -> null, () -> null); + } + + /** + * Factory for creating filter results based on the condition given. + * + * @param included whether or not the filtered object should be included + * @param inclusionReasonSupplier supplier for the reason in case of inclusion + * @param exclusionReasonSupplier supplier for the reason in case of exclusion + * @return a valid {@code FilterResult} for the given condition + */ + public static FilterResult includedIf(boolean included, Supplier inclusionReasonSupplier, + Supplier exclusionReasonSupplier) { + return included ? included(inclusionReasonSupplier.get()) : excluded(exclusionReasonSupplier.get()); + } + + private final boolean included; + + private final Optional reason; + + private FilterResult(boolean included, String reason) { + this.included = included; + this.reason = Optional.ofNullable(reason); + } + + /** + * @return {@code true} if the filtered object should be included + */ + public boolean included() { + return this.included; + } + + /** + * @return {@code true} if the filtered object should be excluded + */ + public boolean excluded() { + return !included(); + } + + /** + * Get the reason why the filtered object should be included or excluded, + * if available. + */ + public Optional getReason() { + return this.reason; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("included", this.included) + .append("reason", this.reason.orElse("")) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java new file mode 100644 index 00000000..22291017 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java @@ -0,0 +1,125 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code SelectorResolutionResult} encapsulates the result of resolving a + * {@link DiscoverySelector} by a {@link TestEngine}. + * + *

A {@code SelectorResolutionResult} consists of a mandatory + * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. + * + * @since 1.6 + */ +@API(status = EXPERIMENTAL, since = "1.6") +public class SelectorResolutionResult { + + /** + * Status of resolving a {@link DiscoverySelector}. + */ + public enum Status { + + /** + * Indicates that the {@link TestEngine} has successfully resolved the + * selector. + */ + RESOLVED, + + /** + * Indicates that the {@link TestEngine} was unable to resolve the + * selector. + */ + UNRESOLVED, + + /** + * Indicates that the {@link TestEngine} has encountered an error while + * resolving the selector. + */ + FAILED + + } + + private static final SelectorResolutionResult RESOLVED_RESULT = new SelectorResolutionResult(Status.RESOLVED, null); + private static final SelectorResolutionResult UNRESOLVED_RESULT = new SelectorResolutionResult(Status.UNRESOLVED, + null); + + /** + * Create a {@code SelectorResolutionResult} for a resolved + * selector. + * @return the {@code SelectorResolutionResult}; never {@code null} + */ + public static SelectorResolutionResult resolved() { + return RESOLVED_RESULT; + } + + /** + * Create a {@code SelectorResolutionResult} for an unresolved + * selector. + * @return the {@code SelectorResolutionResult}; never {@code null} + */ + public static SelectorResolutionResult unresolved() { + return UNRESOLVED_RESULT; + } + + /** + * Create a {@code SelectorResolutionResult} for a failed + * selector resolution. + * @return the {@code SelectorResolutionResult}; never {@code null} + */ + public static SelectorResolutionResult failed(Throwable throwable) { + return new SelectorResolutionResult(Status.FAILED, throwable); + } + + private final Status status; + private final Throwable throwable; + + private SelectorResolutionResult(Status status, Throwable throwable) { + this.status = status; + this.throwable = throwable; + } + + /** + * Get the {@linkplain Status status} of this result. + * + * @return the status; never {@code null} + */ + public Status getStatus() { + return status; + } + + /** + * Get the throwable that caused this result, if available. + * + * @return an {@code Optional} containing the throwable; never {@code null} + * but potentially empty + */ + public Optional getThrowable() { + return Optional.ofNullable(throwable); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("status", status) + .append("throwable", throwable) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java new file mode 100644 index 00000000..b16a8d43 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java @@ -0,0 +1,309 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +/** + * Mutable descriptor for a test or container that has been discovered by a + * {@link TestEngine}. + * + * @since 1.0 + * @see TestEngine + */ +@API(status = STABLE, since = "1.0") +public interface TestDescriptor { + + /** + * Get the unique identifier (UID) for this descriptor. + * + *

Uniqueness must be guaranteed across an entire test plan, + * regardless of how many engines are used behind the scenes. + * + * @return the {@code UniqueId} for this descriptor; never {@code null} + */ + UniqueId getUniqueId(); + + /** + * Get the display name for this descriptor. + * + *

A display name is a human-readable name for a test or + * container that is typically used for test reporting in IDEs and build + * tools. Display names may contain spaces, special characters, and emoji, + * and the format may be customized by {@link TestEngine TestEngines} or + * potentially by end users as well. Consequently, display names should + * never be parsed; rather, they should be used for display purposes only. + * + * @return the display name for this descriptor; never {@code null} or blank + * @see #getSource() + */ + String getDisplayName(); + + /** + * Get the name of this descriptor in a format that is suitable for legacy + * reporting infrastructure — for example, for reporting systems built + * on the Ant-based XML reporting format for JUnit 4. + * + *

The default implementation delegates to {@link #getDisplayName()}. + * + * @return the legacy reporting name; never {@code null} or blank + */ + default String getLegacyReportingName() { + return getDisplayName(); + } + + /** + * Get the set of {@linkplain TestTag tags} associated with this descriptor. + * + * @return the set of tags associated with this descriptor; never {@code null} + * but potentially empty + * @see TestTag + */ + Set getTags(); + + /** + * Get the {@linkplain TestSource source} of the test or container described + * by this descriptor, if available. + * + * @see TestSource + */ + Optional getSource(); + + /** + * Get the parent of this descriptor, if available. + */ + Optional getParent(); + + /** + * Set the parent of this descriptor. + * + * @param parent the new parent of this descriptor; may be {@code null}. + */ + void setParent(TestDescriptor parent); + + /** + * Get the immutable set of children of this descriptor. + * + * @return the set of children of this descriptor; neither {@code null} + * nor mutable, but potentially empty + * @see #getDescendants() + */ + Set getChildren(); + + /** + * Get the immutable set of all descendants of this descriptor. + * + *

A descendant is a child of this descriptor or a child of one of + * its children, recursively. + * + * @see #getChildren() + */ + default Set getDescendants() { + Set descendants = new LinkedHashSet<>(); + descendants.addAll(getChildren()); + for (TestDescriptor child : getChildren()) { + descendants.addAll(child.getDescendants()); + } + return Collections.unmodifiableSet(descendants); + } + + /** + * Add a child to this descriptor. + * + * @param descriptor the child to add to this descriptor; never {@code null} + */ + void addChild(TestDescriptor descriptor); + + /** + * Remove a child from this descriptor. + * + * @param descriptor the child to remove from this descriptor; never + * {@code null} + */ + void removeChild(TestDescriptor descriptor); + + /** + * Remove this non-root descriptor from its parent and remove all the + * children from this descriptor. + * + *

If this method is invoked on a {@linkplain #isRoot root} descriptor, + * this method must throw a {@link org.junit.platform.commons.JUnitException + * JUnitException} explaining that a root cannot be removed from the + * hierarchy. + */ + void removeFromHierarchy(); + + /** + * Determine if this descriptor is a root descriptor. + * + *

A root descriptor is a descriptor without a parent. + */ + default boolean isRoot() { + return !getParent().isPresent(); + } + + /** + * Determine the {@link Type} of this descriptor. + * + * @return the descriptor type; never {@code null}. + * @see #isContainer() + * @see #isTest() + */ + Type getType(); + + /** + * Determine if this descriptor describes a container. + * + *

The default implementation delegates to {@link Type#isContainer()}. + */ + default boolean isContainer() { + return getType().isContainer(); + } + + /** + * Determine if this descriptor describes a test. + * + *

The default implementation delegates to {@link Type#isTest()}. + */ + default boolean isTest() { + return getType().isTest(); + } + + /** + * Determine if this descriptor may register dynamic tests during execution. + * + *

The default implementation assumes tests are usually known during + * discovery and thus returns {@code false}. + */ + default boolean mayRegisterTests() { + return false; + } + + /** + * Determine if the supplied descriptor (or any of its descendants) + * {@linkplain TestDescriptor#isTest() is a test} or + * {@linkplain TestDescriptor#mayRegisterTests() may potentially register + * tests dynamically}. + * + * @param testDescriptor the {@code TestDescriptor} to check for tests; never + * {@code null} + * @return {@code true} if the descriptor is a test, contains tests, or may + * later register tests dynamically + */ + static boolean containsTests(TestDescriptor testDescriptor) { + Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); + return testDescriptor.isTest() || testDescriptor.mayRegisterTests() + || testDescriptor.getChildren().stream().anyMatch(TestDescriptor::containsTests); + } + + /** + * Remove this descriptor from the hierarchy unless it is a root or contains + * tests. + * + *

A concrete {@link TestEngine} may override this method in order to + * implement a different algorithm or to skip pruning altogether. + * + * @see #isRoot() + * @see #containsTests(TestDescriptor) + * @see #removeFromHierarchy() + */ + default void prune() { + if (!isRoot() && !containsTests(this)) { + removeFromHierarchy(); + } + } + + /** + * Find the descriptor with the supplied unique ID. + * + *

The search algorithm begins with this descriptor and then searches + * through its descendants. + * + * @param uniqueId the {@code UniqueId} to search for; never {@code null} + */ + Optional findByUniqueId(UniqueId uniqueId); + + /** + * Accept a {@link Visitor} to the subtree starting with this descriptor. + * + * @param visitor the {@code Visitor} to accept; never {@code null} + */ + default void accept(Visitor visitor) { + Preconditions.notNull(visitor, "Visitor must not be null"); + visitor.visit(this); + // Create a copy of the set in order to avoid a ConcurrentModificationException + new LinkedHashSet<>(this.getChildren()).forEach(child -> child.accept(visitor)); + } + + /** + * Visitor for the tree-like {@link TestDescriptor} structure. + * + * @see TestDescriptor#accept(Visitor) + */ + @FunctionalInterface + interface Visitor { + + /** + * Visit a {@link TestDescriptor}. + * + * @param descriptor the {@code TestDescriptor} to visit; never {@code null} + */ + void visit(TestDescriptor descriptor); + + } + + /** + * Supported types for {@link TestDescriptor TestDescriptors}. + */ + enum Type { + + /** + * Denotes that the {@link TestDescriptor} is for a container. + */ + CONTAINER, + + /** + * Denotes that the {@link TestDescriptor} is for a test. + */ + TEST, + + /** + * Denotes that the {@link TestDescriptor} is for a test + * that may potentially also be a container. + */ + CONTAINER_AND_TEST; + + /** + * @return {@code true} if this type represents a descriptor that can + * contain other descriptors + */ + public boolean isContainer() { + return this == CONTAINER || this == CONTAINER_AND_TEST; + } + + /** + * @return {@code true} if this type represents a descriptor for a test + */ + public boolean isTest() { + return this == TEST || this == CONTAINER_AND_TEST; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java new file mode 100644 index 00000000..401fa193 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java @@ -0,0 +1,195 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ModuleUtils; +import org.junit.platform.commons.util.PackageUtils; + +/** + * A {@code TestEngine} facilitates discovery and execution of + * tests for a particular programming model. + * + *

For example, JUnit provides a {@code TestEngine} that discovers and + * executes tests written using the JUnit Jupiter programming model. + * + *

Every {@code TestEngine} must {@linkplain #getId provide its own unique ID}, + * {@linkplain #discover discover tests} from + * {@link EngineDiscoveryRequest EngineDiscoveryRequests}, + * and {@linkplain #execute execute those tests} according to + * {@link ExecutionRequest ExecutionRequests}. + * + *

In order to facilitate test discovery within IDEs and tools prior + * to launching the JUnit Platform, {@code TestEngine} implementations are + * encouraged to make use of the + * {@link org.junit.platform.commons.annotation.Testable @Testable} annotation. + * For example, the {@code @Test} and {@code @TestFactory} annotations in JUnit + * Jupiter are meta-annotated with {@code @Testable}. Consult the Javadoc for + * {@code @Testable} for further details. + * + * @since 1.0 + * @see org.junit.platform.engine.EngineDiscoveryRequest + * @see org.junit.platform.engine.ExecutionRequest + * @see org.junit.platform.commons.annotation.Testable + */ +@API(status = STABLE, since = "1.0") +public interface TestEngine { + + /** + * Get the ID that uniquely identifies this test engine. + * + *

Each test engine must provide a unique ID. For example, JUnit Vintage + * and JUnit Jupiter use {@code "junit-vintage"} and {@code "junit-jupiter"}, + * respectively. When in doubt, you may use the fully qualified name of your + * custom {@code TestEngine} implementation class. + */ + String getId(); + + /** + * Discover tests according to the supplied {@link EngineDiscoveryRequest}. + * + *

The supplied {@link UniqueId} must be used as the unique ID of the + * returned root {@link TestDescriptor}. In addition, the {@code UniqueId} + * must be used to create unique IDs for children of the root's descriptor + * by calling {@link UniqueId#append}. + * + * @param discoveryRequest the discovery request; never {@code null} + * @param uniqueId the unique ID to be used for this test engine's + * {@code TestDescriptor}; never {@code null} + * @return the root {@code TestDescriptor} of this engine, typically an + * instance of {@code EngineDescriptor} + * @see org.junit.platform.engine.support.descriptor.EngineDescriptor + */ + TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId); + + /** + * Execute tests according to the supplied {@link ExecutionRequest}. + * + *

The {@code request} passed to this method contains the root + * {@link TestDescriptor} that was previously returned by {@link #discover}, + * the {@link EngineExecutionListener} to be notified of test execution + * events, and {@link ConfigurationParameters} that may influence test execution. + * + * @param request the request to execute tests for; never {@code null} + */ + void execute(ExecutionRequest request); + + /** + * Get the Group ID of the JAR in which this test engine is packaged. + * + *

This information is used solely for debugging and reporting purposes. + * + *

The default implementation returns an empty {@link Optional}, + * signaling that the group ID is unknown. + * + *

Concrete test engine implementations may override this method in + * order to provide a known group ID. + * + * @return an {@code Optional} containing the group ID; never {@code null} + * but potentially empty if the group ID is unknown + * @see #getArtifactId() + * @see #getVersion() + */ + default Optional getGroupId() { + return Optional.empty(); + } + + /** + * Get the Artifact ID of the JAR in which this test engine is packaged. + * + *

This information is used solely for debugging and reporting purposes. + * + *

The default implementation assumes the implementation title is equivalent + * to the artifact ID and therefore attempts to query the + * {@linkplain Package#getImplementationTitle() implementation title} + * from the package attributes for the {@link Package} in which the engine + * resides. Note that a package only has attributes if the information is + * defined in the {@link java.util.jar.Manifest Manifest} of the JAR + * containing that package, and if the class loader created the + * {@link Package} instance with the attributes from the manifest. + * + *

If the implementation title cannot be queried from the package + * attributes, the default implementation returns an empty + * {@link Optional}. + * + *

Concrete test engine implementations may override this method in + * order to determine the artifact ID by some other means. + * + * @implNote Since JUnit Platform version 1.1 this default implementation + * returns the "module name" stored in the module (modular jar on the + * module-path) of this test engine. + * + * @return an {@code Optional} containing the artifact ID; never + * {@code null} but potentially empty if the artifact ID is unknown + * @see Class#getPackage() + * @see Package#getImplementationTitle() + * @see #getGroupId() + * @see #getVersion() + */ + default Optional getArtifactId() { + Optional moduleName = ModuleUtils.getModuleName(getClass()); + if (moduleName.isPresent()) { + return moduleName; + } + return PackageUtils.getAttribute(getClass(), Package::getImplementationTitle); + } + + /** + * Get the version of this test engine. + * + *

This information is used solely for debugging and reporting purposes. + * + *

Initially, the default implementation tries to retrieve the engine + * version from the manifest attribute named: {@code "Engine-Version-" + getId()} + * + *

Then the default implementation attempts to query the + * {@linkplain Package#getImplementationVersion() implementation version} + * from the package attributes for the {@link Package} in which the engine + * resides. Note that a package only has attributes if the information is + * defined in the {@link java.util.jar.Manifest Manifest} of the JAR + * containing that package, and if the class loader created the + * {@link Package} instance with the attributes from the manifest. + * + *

If the implementation version cannot be queried from the package + * attributes, the default implementation returns {@code "DEVELOPMENT"}. + * + *

Concrete test engine implementations may override this method to + * determine the version by some other means. + * + *

implNote: Since JUnit Platform version 1.1 this default implementation + * honors the "raw version" information stored in the module (modular jar + * on the module-path) of this test engine. + * + * @return an {@code Optional} containing the version; never {@code null} + * but potentially empty if the version is unknown + * @see Class#getPackage() + * @see Package#getImplementationVersion() + * @see #getGroupId() + * @see #getArtifactId() + */ + default Optional getVersion() { + Optional standalone = PackageUtils.getAttribute(getClass(), "Engine-Version-" + getId()); + if (standalone.isPresent()) { + return standalone; + } + String fallback = "DEVELOPMENT"; + Optional moduleVersion = ModuleUtils.getModuleVersion(getClass()); + if (moduleVersion.isPresent()) { + return moduleVersion; + } + return Optional.of(PackageUtils.getAttribute(getClass(), Package::getImplementationVersion).orElse(fallback)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java new file mode 100644 index 00000000..6509ecd4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java @@ -0,0 +1,132 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code TestExecutionResult} encapsulates the result of executing a single test + * or container. + * + *

A {@code TestExecutionResult} consists of a mandatory + * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public class TestExecutionResult { + + /** + * Status of executing a single test or container. + */ + public enum Status { + + /** + * Indicates that the execution of a test or container was + * successful. + */ + SUCCESSFUL, + + /** + * Indicates that the execution of a test or container was + * aborted (started but not finished). + */ + ABORTED, + + /** + * Indicates that the execution of a test or container failed. + */ + FAILED; + + } + + private static final TestExecutionResult SUCCESSFUL_RESULT = new TestExecutionResult(SUCCESSFUL, null); + + /** + * Create a {@code TestExecutionResult} for a successful execution + * of a test or container. + * @return the {@code TestExecutionResult}; never {@code null} + */ + public static TestExecutionResult successful() { + return SUCCESSFUL_RESULT; + } + + /** + * Create a {@code TestExecutionResult} for an aborted execution + * of a test or container with the supplied {@link Throwable throwable}. + * + * @param throwable the throwable that caused the aborted execution; may be + * {@code null} + * @return the {@code TestExecutionResult}; never {@code null} + */ + public static TestExecutionResult aborted(Throwable throwable) { + return new TestExecutionResult(ABORTED, throwable); + } + + /** + * Create a {@code TestExecutionResult} for a failed execution + * of a test or container with the supplied {@link Throwable throwable}. + * + * @param throwable the throwable that caused the failed execution; may be + * {@code null} + * @return the {@code TestExecutionResult}; never {@code null} + */ + public static TestExecutionResult failed(Throwable throwable) { + return new TestExecutionResult(FAILED, throwable); + } + + private final Status status; + private final Throwable throwable; + + private TestExecutionResult(Status status, Throwable throwable) { + this.status = Preconditions.notNull(status, "Status must not be null"); + this.throwable = throwable; + } + + /** + * Get the {@linkplain Status status} of this result. + * + * @return the status; never {@code null} + */ + public Status getStatus() { + return status; + } + + /** + * Get the throwable that caused this result, if available. + * + * @return an {@code Optional} containing the throwable; never {@code null} + * but potentially empty + */ + public Optional getThrowable() { + return Optional.ofNullable(throwable); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("status", status) + .append("throwable", throwable) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java new file mode 100644 index 00000000..0f3d8738 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.Serializable; + +import org.apiguardian.api.API; + +/** + * Representation of the source of a test or container used to navigate to + * its location by IDEs and build tools. + * + *

This is a marker interface. Clients need to check instances for concrete + * subclasses or subinterfaces. + * + *

Implementations of this interface need to ensure that they are + * serializable and immutable since they may be used as data + * transfer objects. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public interface TestSource extends Serializable { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java new file mode 100644 index 00000000..9a32e07f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java @@ -0,0 +1,153 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; + +/** + * Immutable value object for a tag that is assigned to a test or + * container. + * + * @since 1.0 + * @see #isValid(String) + * @see #create(String) + */ +@API(status = STABLE, since = "1.0") +public final class TestTag implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String name; + + /** + * Reserved characters that are not permissible as part of a tag name. + * + *

    + *
  • {@code ,}: comma
  • + *
  • {@code (}: left parenthesis
  • + *
  • {@code )}: right parenthesis
  • + *
  • {@code &}: ampersand
  • + *
  • {@code |}: vertical bar
  • + *
  • {@code !}: exclamation point
  • + *
+ */ + public static final Set RESERVED_CHARACTERS = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList(",", "(", ")", "&", "|", "!"))); + + /** + * Determine if the supplied tag name is valid with regard to the supported + * syntax for tags. + * + *

Syntax Rules for Tags

+ *
    + *
  • A tag must not be {@code null}.
  • + *
  • A tag must not be blank.
  • + *
  • A trimmed tag must not contain whitespace.
  • + *
  • A trimmed tag must not contain ISO control characters.
  • + *
  • A trimmed tag must not contain {@linkplain #RESERVED_CHARACTERS + * reserved characters}.
  • + *
+ * + *

If this method returns {@code true} for a given name, it is then a + * valid candidate for the {@link TestTag#create(String) create()} factory + * method. + * + * @param name the name of the tag to validate; may be {@code null} or blank + * @return {@code true} if the supplied tag name conforms to the supported + * syntax for tags + * @see StringUtils#isNotBlank(String) + * @see String#trim() + * @see StringUtils#doesNotContainWhitespace(String) + * @see StringUtils#doesNotContainIsoControlCharacter(String) + * @see #RESERVED_CHARACTERS + * @see TestTag#create(String) + */ + public static boolean isValid(String name) { + if (name == null) { + return false; + } + name = name.trim(); + + return !name.isEmpty() && // + StringUtils.doesNotContainWhitespace(name) && // + StringUtils.doesNotContainIsoControlCharacter(name) && // + doesNotContainReservedCharacter(name); + } + + private static boolean doesNotContainReservedCharacter(String str) { + return RESERVED_CHARACTERS.stream().noneMatch(str::contains); + } + + /** + * Create a {@code TestTag} from the supplied {@code name}. + * + *

Consider checking whether the syntax of the supplied {@code name} + * is {@linkplain #isValid(String) valid} before attempting to create a + * {@code TestTag} using this factory method. + * + *

Note: the supplied {@code name} will be {@linkplain String#trim() trimmed}. + * + * @param name the name of the tag; must be syntactically valid + * @throws PreconditionViolationException if the supplied tag name is not + * syntactically valid + * @see TestTag#isValid(String) + */ + public static TestTag create(String name) throws PreconditionViolationException { + return new TestTag(name); + } + + private TestTag(String name) { + Preconditions.condition(TestTag.isValid(name), + () -> String.format("Tag name [%s] must be syntactically valid", name)); + this.name = name.trim(); + } + + /** + * Get the name of this tag. + * + * @return the name of this tag; never {@code null} or blank + */ + public String getName() { + return this.name; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TestTag) { + TestTag that = (TestTag) obj; + return Objects.equals(this.name, that.name); + } + return false; + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public String toString() { + return this.name; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java new file mode 100644 index 00000000..f4d516cf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java @@ -0,0 +1,363 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code UniqueId} encapsulates the creation, parsing, and display of unique IDs + * for {@link TestDescriptor TestDescriptors}. + * + *

Instances of this class have value semantics and are immutable. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public class UniqueId implements Cloneable, Serializable { + + private static final long serialVersionUID = 1L; + + private static final String ENGINE_SEGMENT_TYPE = "engine"; + + /** + * Parse a {@code UniqueId} from the supplied string representation using the + * default format. + * + * @param uniqueId the string representation to parse; never {@code null} or blank + * @return a properly constructed {@code UniqueId} + * @throws JUnitException if the string cannot be parsed + */ + public static UniqueId parse(String uniqueId) throws JUnitException { + Preconditions.notBlank(uniqueId, "Unique ID string must not be null or blank"); + return UniqueIdFormat.getDefault().parse(uniqueId); + } + + /** + * Create an engine's unique ID from its {@code engineId} using the default + * format. + * + *

The engine ID will be stored in a {@link Segment} with + * {@link Segment#getType type} {@code "engine"}. + * + * @param engineId the engine ID; never {@code null} or blank + * @see #root(String, String) + */ + public static UniqueId forEngine(String engineId) { + Preconditions.notBlank(engineId, "engineId must not be null or blank"); + return root(ENGINE_SEGMENT_TYPE, engineId); + } + + /** + * Create a root unique ID from the supplied {@code segmentType} and + * {@code value} using the default format. + * + * @param segmentType the segment type; never {@code null} or blank + * @param value the value; never {@code null} or blank + * @see #forEngine(String) + */ + public static UniqueId root(String segmentType, String value) { + return new UniqueId(UniqueIdFormat.getDefault(), new Segment(segmentType, value)); + } + + private final UniqueIdFormat uniqueIdFormat; + + @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (singletonList() or ArrayList) + private final List segments; + + // lazily computed + private transient int hashCode; + + // lazily computed + private transient SoftReference toString; + + private UniqueId(UniqueIdFormat uniqueIdFormat, Segment segment) { + this(uniqueIdFormat, singletonList(segment)); + } + + /** + * Initialize a {@code UniqueId} instance. + * + * @implNote A defensive copy of the segment list is not created by + * this implementation. All callers should immediately drop the reference + * to the list instance that they pass into this constructor. + */ + UniqueId(UniqueIdFormat uniqueIdFormat, List segments) { + this.uniqueIdFormat = uniqueIdFormat; + this.segments = segments; + } + + final Optional getRoot() { + return this.segments.isEmpty() ? Optional.empty() : Optional.of(this.segments.get(0)); + } + + /** + * Get the engine ID stored in this {@code UniqueId}, if available. + * + * @see #forEngine(String) + */ + public final Optional getEngineId() { + return getRoot().filter(segment -> segment.getType().equals(ENGINE_SEGMENT_TYPE)).map(Segment::getValue); + } + + /** + * Get the immutable list of {@linkplain Segment segments} that make up this + * {@code UniqueId}. + */ + public final List getSegments() { + return unmodifiableList(this.segments); + } + + /** + * Construct a new {@code UniqueId} by appending a new {@link Segment}, based + * on the supplied {@code segmentType} and {@code value}, to the end of this + * {@code UniqueId}. + * + *

This {@code UniqueId} will not be modified. + * + *

Neither the {@code segmentType} nor the {@code value} may contain any + * of the special characters used for constructing the string representation + * of this {@code UniqueId}. + * + * @param segmentType the type of the segment; never {@code null} or blank + * @param value the value of the segment; never {@code null} or blank + */ + public final UniqueId append(String segmentType, String value) { + return append(new Segment(segmentType, value)); + } + + /** + * Construct a new {@code UniqueId} by appending a new {@link Segment} to + * the end of this {@code UniqueId}. + * + *

This {@code UniqueId} will not be modified. + * + * @param segment the segment to be appended; never {@code null} + * + * @since 1.1 + */ + @API(status = STABLE, since = "1.1") + public final UniqueId append(Segment segment) { + Preconditions.notNull(segment, "segment must not be null"); + List baseSegments = new ArrayList<>(this.segments.size() + 1); + baseSegments.addAll(this.segments); + baseSegments.add(segment); + return new UniqueId(this.uniqueIdFormat, baseSegments); + } + + /** + * Construct a new {@code UniqueId} by appending a new {@link Segment}, based + * on the supplied {@code engineId}, to the end of this {@code UniqueId}. + * + *

This {@code UniqueId} will not be modified. + * + *

The engine ID will be stored in a {@link Segment} with + * {@link Segment#getType type} {@value ENGINE_SEGMENT_TYPE}. + * + * @param engineId the engine ID; never {@code null} or blank + * + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + public UniqueId appendEngine(String engineId) { + return append(new Segment(ENGINE_SEGMENT_TYPE, engineId)); + } + + /** + * Determine if the supplied {@code UniqueId} is a prefix for this + * {@code UniqueId}. + * + * @param potentialPrefix the {@code UniqueId} to be checked; never {@code null} + * + * @since 1.1 + */ + @API(status = STABLE, since = "1.1") + public boolean hasPrefix(UniqueId potentialPrefix) { + Preconditions.notNull(potentialPrefix, "potentialPrefix must not be null"); + int size = this.segments.size(); + int prefixSize = potentialPrefix.segments.size(); + return size >= prefixSize && this.segments.subList(0, prefixSize).equals(potentialPrefix.segments); + } + + /** + * Construct a new {@code UniqueId} and removing the last {@link Segment} of + * this {@code UniqueId}. + * + *

This {@code UniqueId} will not be modified. + * + * @return a new {@code UniqueId}; never {@code null} + * @throws org.junit.platform.commons.PreconditionViolationException + * if this {@code UniqueId} contains a single segment + * @since 1.5 + */ + @API(status = STABLE, since = "1.5") + public UniqueId removeLastSegment() { + Preconditions.condition(this.segments.size() > 1, "Cannot remove last remaining segment"); + return new UniqueId(uniqueIdFormat, new ArrayList<>(this.segments.subList(0, this.segments.size() - 1))); + } + + /** + * Get the last {@link Segment} of this {@code UniqueId}. + * + * @return the last {@code Segment}; never {@code null} + * @since 1.5 + */ + @API(status = STABLE, since = "1.5") + public Segment getLastSegment() { + return this.segments.get(this.segments.size() - 1); + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + UniqueId that = (UniqueId) o; + return this.segments.equals(that.segments); + } + + @Override + public int hashCode() { + int value = this.hashCode; + if (value == 0) { + value = this.segments.hashCode(); + if (value == 0) { + // handle the edge case of the computed hashCode being 0 + value = 1; + } + // this is a benign race like String#hash + // we potentially read and write values from multiple threads + // without a happens-before relationship + // however the JMM guarantees us that we only ever see values + // that were valid at one point, either 0 or the hash code + // so we might end up not seeing a value that a different thread + // has computed or multiple threads writing the same value + this.hashCode = value; + } + return value; + } + + /** + * Generate the unique, formatted string representation of this {@code UniqueId} + * using the configured {@link UniqueIdFormat}. + */ + @Override + public String toString() { + SoftReference s = this.toString; + String value = s == null ? null : s.get(); + if (value == null) { + value = this.uniqueIdFormat.format(this); + // this is a benign race like String#hash + // we potentially read and write values from multiple threads + // without a happens-before relationship + // however the JMM guarantees us that we only ever see values + // that were valid at one point, either null or the toString value + // so we might end up not seeing a value that a different thread + // has computed or multiple threads writing the same value + this.toString = new SoftReference<>(value); + } + return value; + } + + /** + * A segment of a {@link UniqueId} comprises a type and a + * value. + */ + @API(status = STABLE, since = "1.0") + public static class Segment implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String type; + private final String value; + + /** + * Create a new {@code Segment} using the supplied {@code type} and + * {@code value}. + * + * @param type the type of this segment + * @param value the value of this segment + */ + Segment(String type, String value) { + Preconditions.notBlank(type, "type must not be null or blank"); + Preconditions.notBlank(value, "value must not be null or blank"); + this.type = type; + this.value = value; + } + + /** + * Get the type of this segment. + */ + public String getType() { + return this.type; + } + + /** + * Get the value of this segment. + */ + public String getValue() { + return this.value; + } + + @Override + public int hashCode() { + return Objects.hash(this.type, this.value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Segment that = (Segment) o; + return Objects.equals(this.type, that.type) && Objects.equals(this.value, that.value); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("type", this.type) + .append("value", this.value) + .toString(); + // @formatter:on + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java new file mode 100644 index 00000000..f89dddd9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java @@ -0,0 +1,161 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.UniqueId.Segment; + +/** + * Used to {@link #parse} a {@link UniqueId} from a string representation + * or to {@link #format} a {@link UniqueId} into a string representation. + * + * @since 1.0 + */ +class UniqueIdFormat implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final UniqueIdFormat defaultFormat = new UniqueIdFormat('[', ':', ']', '/'); + + static UniqueIdFormat getDefault() { + return defaultFormat; + } + + private static String quote(char c) { + return Pattern.quote(String.valueOf(c)); + } + + private static String encode(char c) { + try { + return URLEncoder.encode(String.valueOf(c), StandardCharsets.UTF_8.name()); + } + catch (UnsupportedEncodingException e) { + throw new AssertionError("UTF-8 should be supported", e); + } + } + + private final char openSegment; + private final char closeSegment; + private final char segmentDelimiter; + private final char typeValueSeparator; + private final Pattern segmentPattern; + private final HashMap encodedCharacterMap = new HashMap<>(); + + UniqueIdFormat(char openSegment, char typeValueSeparator, char closeSegment, char segmentDelimiter) { + this.openSegment = openSegment; + this.typeValueSeparator = typeValueSeparator; + this.closeSegment = closeSegment; + this.segmentDelimiter = segmentDelimiter; + this.segmentPattern = Pattern.compile( + String.format("%s(.+)%s(.+)%s", quote(openSegment), quote(typeValueSeparator), quote(closeSegment)), + Pattern.DOTALL); + + // Compute "forbidden" character encoding map. + // Note that the map is always empty at this point. Thus the use of + // computeIfAbsent() is purely syntactic sugar. + encodedCharacterMap.computeIfAbsent('%', UniqueIdFormat::encode); + encodedCharacterMap.computeIfAbsent('+', UniqueIdFormat::encode); + encodedCharacterMap.computeIfAbsent(openSegment, UniqueIdFormat::encode); + encodedCharacterMap.computeIfAbsent(typeValueSeparator, UniqueIdFormat::encode); + encodedCharacterMap.computeIfAbsent(closeSegment, UniqueIdFormat::encode); + encodedCharacterMap.computeIfAbsent(segmentDelimiter, UniqueIdFormat::encode); + } + + /** + * Parse a {@code UniqueId} from the supplied string representation. + * + * @return a properly constructed {@code UniqueId} + * @throws JUnitException if the string cannot be parsed + */ + UniqueId parse(String source) throws JUnitException { + String[] parts = source.split(String.valueOf(this.segmentDelimiter)); + List segments = Arrays.stream(parts).map(this::createSegment).collect(toList()); + return new UniqueId(this, segments); + } + + private Segment createSegment(String segmentString) throws JUnitException { + Matcher segmentMatcher = this.segmentPattern.matcher(segmentString); + if (!segmentMatcher.matches()) { + throw new JUnitException(String.format("'%s' is not a well-formed UniqueId segment", segmentString)); + } + String type = decode(checkAllowed(segmentMatcher.group(1))); + String value = decode(checkAllowed(segmentMatcher.group(2))); + return new Segment(type, value); + } + + private String checkAllowed(String typeOrValue) { + checkDoesNotContain(typeOrValue, this.segmentDelimiter); + checkDoesNotContain(typeOrValue, this.typeValueSeparator); + checkDoesNotContain(typeOrValue, this.openSegment); + checkDoesNotContain(typeOrValue, this.closeSegment); + return typeOrValue; + } + + private void checkDoesNotContain(String typeOrValue, char forbiddenCharacter) { + Preconditions.condition(typeOrValue.indexOf(forbiddenCharacter) < 0, + () -> String.format("type or value '%s' must not contain '%s'", typeOrValue, forbiddenCharacter)); + } + + /** + * Format and return the string representation of the supplied {@code UniqueId}. + */ + String format(UniqueId uniqueId) { + // @formatter:off + return uniqueId.getSegments().stream() + .map(this::describe) + .collect(joining(String.valueOf(this.segmentDelimiter))); + // @formatter:on + } + + private String describe(Segment segment) { + String body = encode(segment.getType()) + typeValueSeparator + encode(segment.getValue()); + return openSegment + body + closeSegment; + } + + private String encode(String s) { + StringBuilder builder = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + String value = encodedCharacterMap.get(c); + if (value == null) { + builder.append(c); + continue; + } + builder.append(value); + } + return builder.toString(); + } + + private static String decode(String s) { + try { + return URLDecoder.decode(s, StandardCharsets.UTF_8.name()); + } + catch (UnsupportedEncodingException e) { + throw new JUnitException("UTF-8 should be supported", e); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java new file mode 100644 index 00000000..d8512db3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.junit.platform.commons.util.Preconditions; + +/** + * Abstract {@link ClassNameFilter} that servers as a superclass + * for filters including or excluding fully qualified class names + * based on pattern-matching. + * + * @since 1.0 + */ +abstract class AbstractClassNameFilter implements ClassNameFilter { + + protected final List patterns; + protected final String patternDescription; + + AbstractClassNameFilter(String... patterns) { + Preconditions.notEmpty(patterns, "patterns array must not be null or empty"); + Preconditions.containsNoNullElements(patterns, "patterns array must not contain null elements"); + this.patterns = Arrays.stream(patterns).map(Pattern::compile).collect(toList()); + this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'")); + } + + @Override + public abstract Predicate toPredicate(); + + protected Optional findMatchingPattern(String className) { + return this.patterns.stream().filter(pattern -> pattern.matcher(className).matches()).findAny(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java new file mode 100644 index 00000000..ae404989 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.engine.DiscoveryFilter; + +/** + * {@link DiscoveryFilter} that is applied to the name of a {@link Class}. + * + * @since 1.0 + * @see #includeClassNamePatterns(String...) + * @see #excludeClassNamePatterns(String...) + * @see PackageNameFilter + */ +@API(status = STABLE, since = "1.0") +public interface ClassNameFilter extends DiscoveryFilter { + + /** + * Standard include pattern in the form of a regular expression that is + * used to match against fully qualified class names: + * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} + * + *

This pattern matches against class names beginning with {@code Test} + * or ending with {@code Test} or {@code Tests} (in any package). + */ + // Implementation notes: + // - Test.* :: "Test" prefix for classes in default package + // - .+[.$]Test.* :: "Test" prefix for top-level and nested classes in a named package + // - .*Tests? :: "Test" and "Tests" suffixes in any package + String STANDARD_INCLUDE_PATTERN = "^(Test.*|.+[.$]Test.*|.*Tests?)$"; + + /** + * Create a new include {@link ClassNameFilter} based on the + * supplied patterns. + * + *

The patterns are combined using OR semantics, i.e. if the fully + * qualified name of a class matches against at least one of the patterns, + * the class will be included in the result set. + * + * @param patterns regular expressions to match against fully qualified + * class names; never {@code null}, empty, or containing {@code null} + * @see Class#getName() + * @see #excludeClassNamePatterns(String...) + */ + static ClassNameFilter includeClassNamePatterns(String... patterns) { + return new IncludeClassNameFilter(patterns); + } + + /** + * Create a new exclude {@link ClassNameFilter} based on the + * supplied patterns. + * + *

The patterns are combined using OR semantics, i.e. if the fully + * qualified name of a class matches against at least one of the patterns, + * the class will be excluded from the result set. + * + * @param patterns regular expressions to match against fully qualified + * class names; never {@code null}, empty, or containing {@code null} + * @see Class#getName() + * @see #includeClassNamePatterns(String...) + */ + static ClassNameFilter excludeClassNamePatterns(String... patterns) { + return new ExcludeClassNameFilter(patterns); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java new file mode 100644 index 00000000..30f69069 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a {@link Class} or class name so + * that {@link org.junit.platform.engine.TestEngine TestEngines} can discover + * tests or containers based on classes. + * + *

If a Java {@link Class} reference is provided, the selector will return + * that {@code Class} and its class name accordingly. If a class name is + * provided, the selector will only attempt to lazily load the {@link Class} + * if {@link #getJavaClass()} is invoked. + * + *

In this context, Java {@link Class} means anything that can be referenced + * as a {@link Class} on the JVM — for example, classes from other JVM + * languages such Groovy, Scala, etc. + * + * @since 1.0 + * @see DiscoverySelectors#selectClass(String) + * @see DiscoverySelectors#selectClass(Class) + * @see org.junit.platform.engine.support.descriptor.ClassSource + */ +@API(status = STABLE, since = "1.0") +public class ClassSelector implements DiscoverySelector { + + private final String className; + + private Class javaClass; + + ClassSelector(String className) { + this.className = className; + } + + ClassSelector(Class javaClass) { + this.className = javaClass.getName(); + this.javaClass = javaClass; + } + + /** + * Get the selected class name. + */ + public String getClassName() { + return this.className; + } + + /** + * Get the selected {@link Class}. + * + *

If the {@link Class} was not provided, but only the name, this method + * attempts to lazily load the {@link Class} based on its name and throws a + * {@link PreconditionViolationException} if the class cannot be loaded. + */ + public Class getJavaClass() { + if (this.javaClass == null) { + this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( + cause -> new PreconditionViolationException("Could not load class with name: " + this.className, + cause)); + } + return this.javaClass; + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClassSelector that = (ClassSelector) o; + return Objects.equals(this.className, that.className); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return this.className.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("className", this.className).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java new file mode 100644 index 00000000..b257e6c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects the name of a classpath resource + * so that {@link org.junit.platform.engine.TestEngine TestEngines} can load resources + * from the classpath — for example, to load XML or JSON files from the classpath, + * potentially within JARs. + * + *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not + * expected to modify the classpath, the classpath resource represented by this + * selector must be on the classpath of the + * {@linkplain Thread#getContextClassLoader() context class loader} of the + * {@linkplain Thread thread} that uses it. + * + * @since 1.0 + * @see DiscoverySelectors#selectClasspathResource(String) + * @see ClasspathRootSelector + * @see #getClasspathResourceName() + */ +@API(status = STABLE, since = "1.0") +public class ClasspathResourceSelector implements DiscoverySelector { + + private final String classpathResourceName; + private final FilePosition position; + + ClasspathResourceSelector(String classpathResourceName, FilePosition position) { + boolean startsWithSlash = classpathResourceName.startsWith("/"); + this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); + this.position = position; + } + + /** + * Get the name of the selected classpath resource. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + * @see ClassLoader#getResource(String) + * @see ClassLoader#getResourceAsStream(String) + * @see ClassLoader#getResources(String) + */ + public String getClasspathResourceName() { + return this.classpathResourceName; + } + + /** + * Get the selected {@code FilePosition} within the classpath resource. + */ + public Optional getPosition() { + return Optional.ofNullable(this.position); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClasspathResourceSelector that = (ClasspathResourceSelector) o; + return Objects.equals(this.classpathResourceName, that.classpathResourceName) + && Objects.equals(this.position, that.position); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return Objects.hash(this.classpathResourceName, this.position); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("classpathResourceName", this.classpathResourceName).append("position", + this.position).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java new file mode 100644 index 00000000..b9d03f48 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.net.URI; +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a classpath root so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can search for class + * files or resources within the physical classpath — for example, to + * scan for test classes. + * + *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not + * expected to modify the classpath, the classpath root represented by this + * selector must be on the classpath of the + * {@linkplain Thread#getContextClassLoader() context class loader} of the + * {@linkplain Thread thread} that uses this selector. + * + * @since 1.0 + * @see DiscoverySelectors#selectClasspathRoots(java.util.Set) + * @see ClasspathResourceSelector + * @see Thread#getContextClassLoader() + */ +@API(status = STABLE, since = "1.0") +public class ClasspathRootSelector implements DiscoverySelector { + + private final URI classpathRoot; + + ClasspathRootSelector(URI classpathRoot) { + this.classpathRoot = classpathRoot; + } + + /** + * Get the selected classpath root directory as an {@link URI}. + */ + public URI getClasspathRoot() { + return this.classpathRoot; + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClasspathRootSelector that = (ClasspathRootSelector) o; + return Objects.equals(this.classpathRoot, that.classpathRoot); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return this.classpathRoot.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("classpathRoot", this.classpathRoot).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java new file mode 100644 index 00000000..167da07b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.File; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a directory so that + * {@link org.junit.platform.engine.TestEngine TestEngines} + * can discover tests or containers based on directories in the + * file system. + * + * @since 1.0 + * @see DiscoverySelectors#selectDirectory(String) + * @see DiscoverySelectors#selectDirectory(File) + * @see FileSelector + * @see #getDirectory() + * @see #getPath() + * @see #getRawPath() + */ +@API(status = STABLE, since = "1.0") +public class DirectorySelector implements DiscoverySelector { + + private final String path; + + DirectorySelector(String path) { + this.path = path; + } + + /** + * Get the selected directory as a {@link java.io.File}. + * + * @see #getPath() + * @see #getRawPath() + */ + public File getDirectory() { + return new File(this.path); + } + + /** + * Get the selected directory as a {@link java.nio.file.Path} using the + * {@linkplain FileSystems#getDefault default} {@link FileSystem}. + * + * @see #getDirectory() + * @see #getRawPath() + */ + public Path getPath() { + return Paths.get(this.path); + } + + /** + * Get the selected directory as a raw path. + * + * @see #getDirectory() + * @see #getPath() + */ + public String getRawPath() { + return this.path; + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DirectorySelector that = (DirectorySelector) o; + return Objects.equals(this.path, that.path); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return this.path.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("path", this.path).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java new file mode 100644 index 00000000..77336370 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java @@ -0,0 +1,716 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.UniqueId; + +/** + * Collection of {@code static} factory methods for creating + * {@link DiscoverySelector DiscoverySelectors}. + * + * @since 1.0 + * @see UriSelector + * @see FileSelector + * @see DirectorySelector + * @see ClasspathRootSelector + * @see ClasspathResourceSelector + * @see ModuleSelector + * @see PackageSelector + * @see ClassSelector + * @see MethodSelector + * @see NestedClassSelector + * @see NestedMethodSelector + * @see UniqueIdSelector + */ +@API(status = STABLE, since = "1.0") +public final class DiscoverySelectors { + + private DiscoverySelectors() { + /* no-op */ + } + + /** + * Create a {@code UriSelector} for the supplied URI. + * + * @param uri the URI to select; never {@code null} or blank + * @see UriSelector + * @see #selectUri(URI) + * @see #selectFile(String) + * @see #selectFile(File) + * @see #selectDirectory(String) + * @see #selectDirectory(File) + */ + public static UriSelector selectUri(String uri) { + Preconditions.notBlank(uri, "URI must not be null or blank"); + try { + return new UriSelector(new URI(uri)); + } + catch (URISyntaxException ex) { + throw new PreconditionViolationException("Failed to create a java.net.URI from: " + uri, ex); + } + } + + /** + * Create a {@code UriSelector} for the supplied {@link URI}. + * + * @param uri the URI to select; never {@code null} + * @see UriSelector + * @see #selectUri(String) + * @see #selectFile(String) + * @see #selectFile(File) + * @see #selectDirectory(String) + * @see #selectDirectory(File) + */ + public static UriSelector selectUri(URI uri) { + Preconditions.notNull(uri, "URI must not be null"); + return new UriSelector(uri); + } + + /** + * Create a {@code FileSelector} for the supplied file path. + * + *

This method selects the file using the supplied path as is, + * without verifying if the file exists. + * + * @param path the path to the file to select; never {@code null} or blank + * @see FileSelector + * @see #selectFile(File) + * @see #selectFile(String, FilePosition) + * @see #selectFile(File, FilePosition) + * @see #selectDirectory(String) + * @see #selectDirectory(File) + */ + public static FileSelector selectFile(String path) { + return selectFile(path, null); + } + + /** + * Create a {@code FileSelector} for the supplied {@linkplain File file}. + * + *

This method selects the file in its {@linkplain File#getCanonicalPath() + * canonical} form and throws a {@link PreconditionViolationException} if the + * file does not exist. + * + * @param file the file to select; never {@code null} + * @see FileSelector + * @see #selectFile(String) + * @see #selectFile(File, FilePosition) + * @see #selectFile(String, FilePosition) + * @see #selectDirectory(String) + * @see #selectDirectory(File) + */ + public static FileSelector selectFile(File file) { + return selectFile(file, null); + } + + /** + * Create a {@code FileSelector} for the supplied file path. + * + *

This method selects the file using the supplied path as is, + * without verifying if the file exists. + * + * @param path the path to the file to select; never {@code null} or blank + * @param position the position inside the file; may be {@code null} + * @see FileSelector + * @see #selectFile(String) + * @see #selectFile(File) + * @see #selectFile(File, FilePosition) + * @see #selectDirectory(String) + * @see #selectDirectory(File) + */ + public static FileSelector selectFile(String path, FilePosition position) { + Preconditions.notBlank(path, "File path must not be null or blank"); + return new FileSelector(path, position); + } + + /** + * Create a {@code FileSelector} for the supplied {@linkplain File file}. + * + *

This method selects the file in its {@linkplain File#getCanonicalPath() + * canonical} form and throws a {@link PreconditionViolationException} if the + * file does not exist. + * + * @param file the file to select; never {@code null} + * @param position the position inside the file; may be {@code null} + * @see FileSelector + * @see #selectFile(File) + * @see #selectFile(String) + * @see #selectFile(String, FilePosition) + * @see #selectDirectory(String) + * @see #selectDirectory(File) + */ + public static FileSelector selectFile(File file, FilePosition position) { + Preconditions.notNull(file, "File must not be null"); + Preconditions.condition(file.isFile(), + () -> String.format("The supplied java.io.File [%s] must represent an existing file", file)); + try { + return new FileSelector(file.getCanonicalPath(), position); + } + catch (IOException ex) { + throw new PreconditionViolationException("Failed to retrieve canonical path for file: " + file, ex); + } + } + + /** + * Create a {@code DirectorySelector} for the supplied directory path. + * + *

This method selects the directory using the supplied path as is, + * without verifying if the directory exists. + * + * @param path the path to the directory to select; never {@code null} or blank + * @see DirectorySelector + * @see #selectDirectory(File) + * @see #selectFile(String) + * @see #selectFile(File) + */ + public static DirectorySelector selectDirectory(String path) { + Preconditions.notBlank(path, "Directory path must not be null or blank"); + return new DirectorySelector(path); + } + + /** + * Create a {@code DirectorySelector} for the supplied {@linkplain File directory}. + * + *

This method selects the directory in its {@linkplain File#getCanonicalPath() + * canonical} form and throws a {@link PreconditionViolationException} if the + * directory does not exist. + * + * @param directory the directory to select; never {@code null} + * @see DirectorySelector + * @see #selectDirectory(String) + * @see #selectFile(String) + * @see #selectFile(File) + */ + public static DirectorySelector selectDirectory(File directory) { + Preconditions.notNull(directory, "Directory must not be null"); + Preconditions.condition(directory.isDirectory(), + () -> String.format("The supplied java.io.File [%s] must represent an existing directory", directory)); + try { + return new DirectorySelector(directory.getCanonicalPath()); + } + catch (IOException ex) { + throw new PreconditionViolationException("Failed to retrieve canonical path for directory: " + directory, + ex); + } + } + + /** + * Create a list of {@code ClasspathRootSelectors} for the supplied + * classpath roots (directories or JAR files). + * + *

Since the supplied paths are converted to {@link URI URIs}, the + * {@link java.nio.file.FileSystem} that created them must be the + * {@linkplain java.nio.file.FileSystems#getDefault() default} or one that + * has been created by an installed + * {@link java.nio.file.spi.FileSystemProvider}. + * + *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not + * expected to modify the classpath, the classpath roots represented by the + * resulting selectors must be on the classpath of the + * {@linkplain Thread#getContextClassLoader() context class loader} of the + * {@linkplain Thread thread} that uses these selectors. + * + * @param classpathRoots set of directories and JAR files in the filesystem + * that represent classpath roots; never {@code null} + * @return a list of selectors for the supplied classpath roots; elements + * which do not physically exist in the filesystem will be filtered out + * @see ClasspathRootSelector + * @see Thread#getContextClassLoader() + */ + public static List selectClasspathRoots(Set classpathRoots) { + Preconditions.notNull(classpathRoots, "classpathRoots must not be null"); + + // @formatter:off + return classpathRoots.stream() + .filter(Files::exists) + .map(Path::toUri) + .map(ClasspathRootSelector::new) + // unmodifiable since selectClasspathRoots is a public, non-internal method + .collect(toUnmodifiableList()); + // @formatter:on + } + + /** + * Create a {@code ClasspathResourceSelector} for the supplied classpath + * resource name. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not + * expected to modify the classpath, the supplied classpath resource must be + * on the classpath of the + * {@linkplain Thread#getContextClassLoader() context class loader} of the + * {@linkplain Thread thread} that uses the resulting selector. + * + * @param classpathResourceName the name of the classpath resource; never + * {@code null} or blank + * @see #selectClasspathResource(String, FilePosition) + * @see ClasspathResourceSelector + * @see ClassLoader#getResource(String) + * @see ClassLoader#getResourceAsStream(String) + * @see ClassLoader#getResources(String) + */ + public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName) { + return selectClasspathResource(classpathResourceName, null); + } + + /** + * Create a {@code ClasspathResourceSelector} for the supplied classpath + * resource name. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not + * expected to modify the classpath, the supplied classpath resource must be + * on the classpath of the + * {@linkplain Thread#getContextClassLoader() context class loader} of the + * {@linkplain Thread thread} that uses the resulting selector. + * + * @param classpathResourceName the name of the classpath resource; never + * {@code null} or blank + * @param position the position inside the classpath resource; may be {@code null} + * @see #selectClasspathResource(String) + * @see ClasspathResourceSelector + * @see ClassLoader#getResource(String) + * @see ClassLoader#getResourceAsStream(String) + * @see ClassLoader#getResources(String) + */ + public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, + FilePosition position) { + Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); + return new ClasspathResourceSelector(classpathResourceName, position); + } + + /** + * Create a {@code ModuleSelector} for the supplied module name. + * + *

The unnamed module is not supported. + * + * @param moduleName the module name to select; never {@code null} or blank + * @since 1.1 + * @see ModuleSelector + */ + @API(status = EXPERIMENTAL, since = "1.1") + public static ModuleSelector selectModule(String moduleName) { + Preconditions.notBlank(moduleName, "Module name must not be null or blank"); + return new ModuleSelector(moduleName.trim()); + } + + /** + * Create a list of {@code ModuleSelectors} for the supplied module names. + * + *

The unnamed module is not supported. + * + * @param moduleNames the module names to select; never {@code null}, never + * containing {@code null} or blank + * @since 1.1 + * @see ModuleSelector + */ + @API(status = EXPERIMENTAL, since = "1.1") + public static List selectModules(Set moduleNames) { + Preconditions.notNull(moduleNames, "Module names must not be null"); + Preconditions.containsNoNullElements(moduleNames, "Individual module name must not be null"); + + // @formatter:off + return moduleNames.stream() + .map(DiscoverySelectors::selectModule) + // unmodifiable since this is a public, non-internal method + .collect(toUnmodifiableList()); + // @formatter:on + } + + /** + * Create a {@code PackageSelector} for the supplied package name. + * + *

The default package is represented by an empty string ({@code ""}). + * + * @param packageName the package name to select; never {@code null} and + * never containing whitespace only + * @see PackageSelector + */ + public static PackageSelector selectPackage(String packageName) { + Preconditions.notNull(packageName, "Package name must not be null"); + Preconditions.condition(packageName.isEmpty() || !packageName.trim().isEmpty(), + "Package name must not contain only whitespace"); + return new PackageSelector(packageName.trim()); + } + + /** + * Create a {@code ClassSelector} for the supplied {@link Class}. + * + * @param clazz the class to select; never {@code null} + * @see ClassSelector + */ + public static ClassSelector selectClass(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return new ClassSelector(clazz); + } + + /** + * Create a {@code ClassSelector} for the supplied class name. + * + * @param className the fully qualified name of the class to select; + * never {@code null} or blank + * @see ClassSelector + */ + public static ClassSelector selectClass(String className) { + Preconditions.notBlank(className, "Class name must not be null or blank"); + return new ClassSelector(className); + } + + /** + * Create a {@code MethodSelector} for the supplied fully qualified + * method name. + * + *

The following formats are supported. + * + *

    + *
  • {@code [fully qualified class name]#[methodName]}
  • + *
  • {@code [fully qualified class name]#[methodName](parameter type list)} + *
+ * + *

The parameter type list is a comma-separated list of primitive + * names or fully qualified class names for the types of parameters accepted + * by the method. + * + *

Array parameter types may be specified using either the JVM's internal + * String representation (e.g., {@code [[I} for {@code int[][]}, + * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or + * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, + * etc.). + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples
MethodFully Qualified Method Name
{@code java.lang.String.chars()}{@code java.lang.String#chars}
{@code java.lang.String.chars()}{@code java.lang.String#chars()}
{@code java.lang.String.equalsIgnoreCase(String)}{@code java.lang.String#equalsIgnoreCase(java.lang.String)}
{@code java.lang.String.substring(int, int)}{@code java.lang.String#substring(int, int)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg([I)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg(int[])}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply([[D)}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply(double[][])}
{@code example.Service.process(String[])}{@code example.Service#process([Ljava.lang.String;)}
{@code example.Service.process(String[])}{@code example.Service#process(java.lang.String[])}
{@code example.Service.process(String[][])}{@code example.Service#process([[Ljava.lang.String;)}
{@code example.Service.process(String[][])}{@code example.Service#process(java.lang.String[][])}
+ * + * @param fullyQualifiedMethodName the fully qualified name of the method to select; never + * {@code null} or blank + * @see MethodSelector + */ + public static MethodSelector selectMethod(String fullyQualifiedMethodName) throws PreconditionViolationException { + String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); + return selectMethod(methodParts[0], methodParts[1], methodParts[2]); + } + + /** + * Create a {@code MethodSelector} for the supplied class name and method name. + * + * @param className the fully qualified name of the class in which the method + * is declared, or a subclass thereof; never {@code null} or blank + * @param methodName the name of the method to select; never {@code null} or blank + * @see MethodSelector + */ + public static MethodSelector selectMethod(String className, String methodName) { + Preconditions.notBlank(className, "Class name must not be null or blank"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + return new MethodSelector(className, methodName); + } + + /** + * Create a {@code MethodSelector} for the supplied class name, method name, + * and method parameter types. + * + *

The parameter types {@code String} is typically a comma-separated list + * of atomic types, fully qualified class names, or array types; however, + * the exact syntax depends on the underlying test engine. + * + * @param className the fully qualified name of the class in which the method + * is declared, or a subclass thereof; never {@code null} or blank + * @param methodName the name of the method to select; never {@code null} or blank + * @param methodParameterTypes the method parameter types as a single string; never + * {@code null} though potentially an empty string if the method does not accept + * arguments + * @see MethodSelector + */ + public static MethodSelector selectMethod(String className, String methodName, String methodParameterTypes) { + Preconditions.notBlank(className, "Class name must not be null or blank"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); + return new MethodSelector(className, methodName, methodParameterTypes.trim()); + } + + /** + * Create a {@code MethodSelector} for the supplied {@link Class} and method name. + * + * @param javaClass the class in which the method is declared, or a subclass thereof; + * never {@code null} + * @param methodName the name of the method to select; never {@code null} or blank + * @see MethodSelector + */ + public static MethodSelector selectMethod(Class javaClass, String methodName) { + Preconditions.notNull(javaClass, "Class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + return new MethodSelector(javaClass, methodName); + } + + /** + * Create a {@code MethodSelector} for the supplied {@link Class}, method name, + * and method parameter types. + * + *

The parameter types {@code String} is typically a comma-separated list + * of atomic types, fully qualified class names, or array types; however, + * the exact syntax depends on the underlying test engine. + * + * @param javaClass the class in which the method is declared, or a subclass thereof; + * never {@code null} + * @param methodName the name of the method to select; never {@code null} or blank + * @param methodParameterTypes the method parameter types as a single string; never + * {@code null} though potentially an empty string if the method does not accept + * arguments + * @see MethodSelector + */ + public static MethodSelector selectMethod(Class javaClass, String methodName, String methodParameterTypes) { + Preconditions.notNull(javaClass, "Class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); + return new MethodSelector(javaClass, methodName, methodParameterTypes.trim()); + } + + /** + * Create a {@code MethodSelector} for the supplied {@link Class} and {@link Method}. + * + * @param javaClass the class in which the method is declared, or a subclass thereof; + * never {@code null} + * @param method the method to select; never {@code null} + * @see MethodSelector + */ + public static MethodSelector selectMethod(Class javaClass, Method method) { + Preconditions.notNull(javaClass, "Class must not be null"); + Preconditions.notNull(method, "Method must not be null"); + return new MethodSelector(javaClass, method); + } + + /** + * Create a {@code NestedClassSelector} for the supplied nested {@link Class} and its + * enclosing classes. + * + * @param enclosingClasses the path to the nested class to select; never {@code null} or empty + * @param nestedClass the nested class to select; never {@code null} + * @since 1.6 + * @see NestedClassSelector + */ + @API(status = STABLE, since = "1.6") + public static NestedClassSelector selectNestedClass(List> enclosingClasses, Class nestedClass) { + Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); + Preconditions.notNull(nestedClass, "Nested class must not be null"); + return new NestedClassSelector(enclosingClasses, nestedClass); + } + + /** + * Create a {@code NestedClassSelector} for the supplied class name and its enclosing + * classes' names. + * + * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty + * @param nestedClassName the name of the nested class to select; never {@code null} or blank + * @since 1.6 + * @see NestedClassSelector + */ + @API(status = STABLE, since = "1.6") + public static NestedClassSelector selectNestedClass(List enclosingClassNames, String nestedClassName) { + Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); + Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); + return new NestedClassSelector(enclosingClassNames, nestedClassName); + } + + /** + * Create a {@code NestedMethodSelector} for the supplied nested class name and method name. + * + * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty + * @param nestedClassName the name of the nested class to select; never {@code null} or blank + * @param methodName the name of the method to select; never {@code null} or blank + * @since 1.6 + * @see NestedMethodSelector + */ + @API(status = STABLE, since = "1.6") + public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName, + String methodName) { + + Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); + Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName); + } + + /** + * Create a {@code NestedMethodSelector} for the supplied nested class name, method name, + * and method parameter types. + * + *

The parameter types {@code String} is typically a comma-separated list + * of atomic types, fully qualified class names, or array types; however, + * the exact syntax depends on the underlying test engine. + * + * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty + * @param nestedClassName the name of the nested class to select; never {@code null} or blank + * @param methodName the name of the method to select; never {@code null} or blank + * @param methodParameterTypes the method parameter types as a single string; never + * {@code null} though potentially an empty string if the method does not accept + * arguments + * @since 1.6 + * @see NestedMethodSelector + */ + @API(status = STABLE, since = "1.6") + public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName, + String methodName, String methodParameterTypes) { + + Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); + Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); + return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName, methodParameterTypes); + } + + /** + * Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and method name. + * + * @param enclosingClasses the path to the nested class to select; never {@code null} or empty + * @param nestedClass the nested class to select; never {@code null} + * @param methodName the name of the method to select; never {@code null} or blank + * @since 1.6 + * @see NestedMethodSelector + */ + @API(status = STABLE, since = "1.6") + public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, + String methodName) { + + Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); + Preconditions.notNull(nestedClass, "Nested class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + return new NestedMethodSelector(enclosingClasses, nestedClass, methodName); + } + + /** + * Create a {@code NestedMethodSelector} for the supplied {@link Class}, method name, + * and method parameter types. + * + *

The parameter types {@code String} is typically a comma-separated list + * of atomic types, fully qualified class names, or array types; however, + * the exact syntax depends on the underlying test engine. + * + * @param enclosingClasses the path to the nested class to select; never {@code null} or empty + * @param nestedClass the nested class to select; never {@code null} + * @param methodName the name of the method to select; never {@code null} or blank + * @param methodParameterTypes the method parameter types as a single string; never + * {@code null} though potentially an empty string if the method does not accept + * arguments + * @since 1.6 + * @see NestedMethodSelector + */ + @API(status = STABLE, since = "1.6") + public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, + String methodName, String methodParameterTypes) { + + Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); + Preconditions.notNull(nestedClass, "Nested class must not be null"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); + return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, methodParameterTypes); + } + + /** + * Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and {@link Method}. + * + * @param enclosingClasses the path to the nested class to select; never {@code null} or empty + * @param nestedClass the nested class to select; never {@code null} + * @param method the method to select; never {@code null} + * @since 1.6 + * @see NestedMethodSelector + */ + @API(status = STABLE, since = "1.6") + public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, + Method method) { + + Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); + Preconditions.notNull(nestedClass, "Nested class must not be null"); + Preconditions.notNull(method, "Method must not be null"); + return new NestedMethodSelector(enclosingClasses, nestedClass, method); + } + + /** + * Create a {@code UniqueIdSelector} for the supplied {@link UniqueId}. + * + * @param uniqueId the {@code UniqueId} to select; never {@code null} + * @see UniqueIdSelector + */ + public static UniqueIdSelector selectUniqueId(UniqueId uniqueId) { + Preconditions.notNull(uniqueId, "UniqueId must not be null"); + return new UniqueIdSelector(uniqueId); + } + + /** + * Create a {@code UniqueIdSelector} for the supplied unique ID. + * + * @param uniqueId the unique ID to select; never {@code null} or blank + * @see UniqueIdSelector + */ + public static UniqueIdSelector selectUniqueId(String uniqueId) { + Preconditions.notBlank(uniqueId, "Unique ID must not be null or blank"); + return new UniqueIdSelector(UniqueId.parse(uniqueId)); + } + + /** + * Create an {@code IterationSelector} for the supplied parent selector and + * iteration indices. + * + * @param parentSelector the parent selector to select iterations for; never + * {@code null} + * @param iterationIndices the iteration indices to select; never + * {@code null} or empty + * @since 1.9 + * @see IterationSelector + */ + @API(status = EXPERIMENTAL, since = "1.9") + public static IterationSelector selectIteration(DiscoverySelector parentSelector, int... iterationIndices) { + Preconditions.notNull(parentSelector, "Parent selector must not be null"); + Preconditions.notEmpty(iterationIndices, "iteration indices must not be empty"); + return new IterationSelector(parentSelector, iterationIndices); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java new file mode 100644 index 00000000..8a3aa725 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.included; + +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.junit.platform.engine.FilterResult; + +/** + * {@link ClassNameFilter} that matches fully qualified class names against + * patterns in the form of regular expressions. + * + *

If the fully qualified name of a class matches against at least one + * pattern, the class will be excluded. + * + * @since 1.0 + */ +class ExcludeClassNameFilter extends AbstractClassNameFilter { + + ExcludeClassNameFilter(String... patterns) { + super(patterns); + } + + @Override + public FilterResult apply(String className) { + return findMatchingPattern(className) // + .map(pattern -> excluded(formatExclusionReason(className, pattern))) // + .orElseGet(() -> included(formatInclusionReason(className))); + } + + private String formatInclusionReason(String className) { + return String.format("Class name [%s] does not match any excluded pattern: %s", className, patternDescription); + } + + private String formatExclusionReason(String className, Pattern pattern) { + return String.format("Class name [%s] matches excluded pattern: '%s'", className, pattern); + } + + @Override + public Predicate toPredicate() { + return className -> !findMatchingPattern(className).isPresent(); + } + + @Override + public String toString() { + return String.format("%s that excludes class names that match one of the following regular expressions: %s", + getClass().getSimpleName(), this.patternDescription); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java new file mode 100644 index 00000000..37fb11ca --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static java.util.stream.Collectors.joining; +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.included; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.FilterResult; + +/** + * {@link PackageNameFilter} that matches fully qualified package names that + * are not prefixed by one of the package names provided to the filter. + * + *

If the fully qualified name of a package starts with at least one of the + * packages names of the filter, the package will be excluded. + * + * @since 1.0 + */ +class ExcludePackageNameFilter implements PackageNameFilter { + + private final List packageNames; + private final String patternDescription; + + ExcludePackageNameFilter(String... packageNames) { + Preconditions.notEmpty(packageNames, "packageNames must not be null or empty"); + Preconditions.containsNoNullElements(packageNames, "packageNames must not contain null elements"); + this.packageNames = Arrays.asList(packageNames); + this.patternDescription = Arrays.stream(packageNames).collect(joining("' OR '", "'", "'")); + } + + @Override + public FilterResult apply(String packageName) { + return findMatchingName(packageName) // + .map(matchedName -> excluded(formatExclusionReason(packageName, matchedName))) // + .orElseGet(() -> included(formatInclusionReason(packageName))); + } + + private String formatInclusionReason(String packageName) { + return String.format("Package name [%s] does not match any excluded names: %s", packageName, + this.patternDescription); + } + + private String formatExclusionReason(String packageName, String matchedName) { + return String.format("Package name [%s] matches excluded name: '%s'", packageName, matchedName); + } + + @Override + public Predicate toPredicate() { + return packageName -> !findMatchingName(packageName).isPresent(); + } + + private Optional findMatchingName(String packageName) { + return this.packageNames.stream().filter( + name -> name.equals(packageName) || packageName.startsWith(name + ".")).findAny(); + } + + @Override + public String toString() { + return String.format( + "%s that excludes packages whose names are either equal to or start with one of the following: %s", + getClass().getSimpleName(), this.patternDescription); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java new file mode 100644 index 00000000..4245187a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java @@ -0,0 +1,183 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Position inside a file represented by {@linkplain #getLine line} and + * {@linkplain #getColumn column} numbers. + * + * @implNote This class is a copy of + * {@link org.junit.platform.engine.support.descriptor.FilePosition FilePosition}, + * which is not accessible from this package. The decision to duplicate it is + * motivated by an eventual divergence between the two classes in the future. + * + * @since 1.7 + */ +@API(status = STABLE, since = "1.7") +public class FilePosition implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final Logger logger = LoggerFactory.getLogger(FilePosition.class); + + /** + * Create a new {@code FilePosition} using the supplied {@code line} number + * and an undefined column number. + * + * @param line the line number; must be greater than zero + * @return a {@link FilePosition} with the given line number + */ + public static FilePosition from(int line) { + return new FilePosition(line); + } + + /** + * Create a new {@code FilePosition} using the supplied {@code line} and + * {@code column} numbers. + * + * @param line the line number; must be greater than zero + * @param column the column number; must be greater than zero + * @return a {@link FilePosition} with the given line and column numbers + */ + public static FilePosition from(int line, int column) { + return new FilePosition(line, column); + } + + /** + * Create an optional {@code FilePosition} by parsing the supplied + * {@code query} string. + * + *

Examples of valid {@code query} strings: + *

    + *
  • {@code "line=23"}
  • + *
  • {@code "line=23&column=42"}
  • + *
+ * + * @param query the query string; may be {@code null} + * @return an {@link Optional} containing a {@link FilePosition} with + * the parsed line and column numbers; never {@code null} but potentially + * empty + * @since 1.3 + * @see #from(int) + * @see #from(int, int) + */ + public static Optional fromQuery(String query) { + FilePosition result = null; + Integer line = null; + Integer column = null; + if (StringUtils.isNotBlank(query)) { + try { + for (String pair : query.split("&")) { + String[] data = pair.split("="); + if (data.length == 2) { + String key = data[0]; + if (line == null && "line".equals(key)) { + line = Integer.valueOf(data[1]); + } + else if (column == null && "column".equals(key)) { + column = Integer.valueOf(data[1]); + } + } + + // Already found what we're looking for? + if (line != null && column != null) { + break; + } + } + } + catch (IllegalArgumentException ex) { + logger.debug(ex, () -> "Failed to parse 'line' and/or 'column' from query string: " + query); + // fall-through and continue + } + + if (line != null) { + result = column == null ? new FilePosition(line) : new FilePosition(line, column); + } + } + return Optional.ofNullable(result); + } + + private final int line; + private final Integer column; + + private FilePosition(int line) { + Preconditions.condition(line > 0, "line number must be greater than zero"); + this.line = line; + this.column = null; + } + + private FilePosition(int line, int column) { + Preconditions.condition(line > 0, "line number must be greater than zero"); + Preconditions.condition(column > 0, "column number must be greater than zero"); + this.line = line; + this.column = column; + } + + /** + * Get the line number of this {@code FilePosition}. + * + * @return the line number + */ + public int getLine() { + return this.line; + } + + /** + * Get the column number of this {@code FilePosition}, if available. + * + * @return an {@code Optional} containing the column number; never + * {@code null} but potentially empty + */ + public Optional getColumn() { + return Optional.ofNullable(this.column); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FilePosition that = (FilePosition) o; + return (this.line == that.line) && Objects.equals(this.column, that.column); + } + + @Override + public int hashCode() { + return Objects.hash(this.line, this.column); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("line", this.line) + .append("column", getColumn().orElse(-1)) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java new file mode 100644 index 00000000..78bf501c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java @@ -0,0 +1,120 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.File; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a file so that + * {@link org.junit.platform.engine.TestEngine TestEngines} + * can discover tests or containers based on files in the + * file system. + * + * @since 1.0 + * @see DiscoverySelectors#selectFile(String) + * @see DiscoverySelectors#selectFile(File) + * @see DirectorySelector + * @see #getFile() + * @see #getPath() + * @see #getRawPath() + */ +@API(status = STABLE, since = "1.0") +public class FileSelector implements DiscoverySelector { + + private final String path; + private final FilePosition position; + + FileSelector(String path, FilePosition position) { + this.path = path; + this.position = position; + } + + /** + * Get the selected file as a {@link java.io.File}. + * + * @see #getPath() + * @see #getRawPath() + */ + public File getFile() { + return new File(this.path); + } + + /** + * Get the selected file as a {@link java.nio.file.Path} using the + * {@linkplain FileSystems#getDefault default} {@link FileSystem}. + * + * @see #getFile() + * @see #getRawPath() + */ + public Path getPath() { + return Paths.get(this.path); + } + + /** + * Get the selected file as a raw path. + * + * @see #getFile() + * @see #getPath() + */ + public String getRawPath() { + return this.path; + } + + /** + * Get the selected position within the file as a {@link FilePosition}. + */ + public Optional getPosition() { + return Optional.ofNullable(this.position); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FileSelector that = (FileSelector) o; + return Objects.equals(this.path, that.path) && Objects.equals(this.position, that.position); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return Objects.hash(path, position); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("path", this.path).append("position", this.position).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java new file mode 100644 index 00000000..5cd49e14 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.included; + +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.junit.platform.engine.FilterResult; + +/** + * {@link ClassNameFilter} that matches fully qualified class names against + * patterns in the form of regular expressions. + * + *

If the fully qualified name of a class matches against at least one + * pattern, the class will be included. + * + * @since 1.0 + */ +class IncludeClassNameFilter extends AbstractClassNameFilter { + + IncludeClassNameFilter(String... patterns) { + super(patterns); + } + + @Override + public FilterResult apply(String className) { + return findMatchingPattern(className) // + .map(pattern -> included(formatInclusionReason(className, pattern))) // + .orElseGet(() -> excluded(formatExclusionReason(className))); + } + + private String formatInclusionReason(String className, Pattern pattern) { + return String.format("Class name [%s] matches included pattern: '%s'", className, pattern); + } + + private String formatExclusionReason(String className) { + return String.format("Class name [%s] does not match any included pattern: %s", className, + this.patternDescription); + } + + @Override + public Predicate toPredicate() { + return className -> findMatchingPattern(className).isPresent(); + } + + @Override + public String toString() { + return String.format("%s that includes class names that match one of the following regular expressions: %s", + getClass().getSimpleName(), this.patternDescription); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java new file mode 100644 index 00000000..2ceb30f4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static java.util.stream.Collectors.joining; +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.included; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.FilterResult; + +/** + * {@link PackageNameFilter} that matches fully qualified package names that + * are prefixed by one of the package names provided to the filter. + * + *

If the fully qualified name of a package starts with at least one of the + * packages names of the filter, the package will be included. + * + * @since 1.0 + */ +class IncludePackageNameFilter implements PackageNameFilter { + + private final List packageNames; + private final String patternDescription; + + IncludePackageNameFilter(String... packageNames) { + Preconditions.notEmpty(packageNames, "packageNames array must not be null or empty"); + Preconditions.containsNoNullElements(packageNames, "packageNames array must not contain null elements"); + this.packageNames = Arrays.asList(packageNames); + this.patternDescription = Arrays.stream(packageNames).collect(joining("' OR '", "'", "'")); + } + + @Override + public FilterResult apply(String packageName) { + return findMatchingName(packageName) // + .map(matchedName -> included(formatInclusionReason(packageName, matchedName))) // + .orElseGet(() -> excluded(formatExclusionReason(packageName))); + } + + private String formatInclusionReason(String packageName, String matchedName) { + return String.format("Package name [%s] matches included name: '%s'", packageName, matchedName); + } + + private String formatExclusionReason(String packageName) { + return String.format("Package name [%s] does not match any included names: %s", packageName, + this.patternDescription); + } + + @Override + public Predicate toPredicate() { + return packageName -> findMatchingName(packageName).isPresent(); + } + + private Optional findMatchingName(String packageName) { + return this.packageNames.stream().filter( + name -> name.equals(packageName) || packageName.startsWith(name + ".")).findAny(); + } + + @Override + public String toString() { + return String.format( + "%s that includes packages whose names are either equal to or start with one of the following: %s", + getClass().getSimpleName(), this.patternDescription); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java new file mode 100644 index 00000000..fe9ff20a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toCollection; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects the iterations of a parent + * {@code DiscoverySelector} via their indices so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can discover + * a subset of the iterations of tests or containers. + * + * @since 1.9 + * @see DiscoverySelectors#selectIteration(DiscoverySelector, int...) + */ +@API(status = EXPERIMENTAL, since = "1.9") +public class IterationSelector implements DiscoverySelector { + + private final DiscoverySelector parentSelector; + private final SortedSet iterationIndices; + + IterationSelector(DiscoverySelector parentSelector, int... iterationIndices) { + this.parentSelector = parentSelector; + this.iterationIndices = toSortedSet(iterationIndices); + } + + private SortedSet toSortedSet(int[] iterationIndices) { + return Arrays.stream(iterationIndices) // + .boxed() // + .collect(collectingAndThen(toCollection(TreeSet::new), Collections::unmodifiableSortedSet)); + } + + /** + * Get the selected parent {@link DiscoverySelector}. + */ + public DiscoverySelector getParentSelector() { + return parentSelector; + } + + /** + * Get the selected iteration indices. + */ + public SortedSet getIterationIndices() { + return iterationIndices; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IterationSelector that = (IterationSelector) o; + return parentSelector.equals(that.parentSelector) && iterationIndices.equals(that.iterationIndices); + } + + @Override + public int hashCode() { + return Objects.hash(parentSelector, iterationIndices); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("parentSelector", this.parentSelector) + .append("iterationIndices", this.iterationIndices) + .toString(); + // @formatter:on + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java new file mode 100644 index 00000000..ff7e627a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java @@ -0,0 +1,220 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.reflect.Method; +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a {@link Method} or a combination of + * class name, method name, and parameter types so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can discover tests + * or containers based on methods. + * + *

If a Java {@link Method} is provided, the selector will return that + * {@linkplain #getJavaMethod() method} and its method name, class name, and + * parameter types accordingly. If a {@link Class} and method name, a class name + * and method name, or a fully qualified method name is provided, + * this selector will only attempt to lazily load the {@link Class} and + * {@link Method} if {@link #getJavaClass()} or {@link #getJavaMethod()} is + * invoked. + * + *

In this context, a Java {@code Method} means anything that can be referenced + * as a {@link Method} on the JVM — for example, methods from Java classes + * or methods from other JVM languages such Groovy, Scala, etc. + * + * @since 1.0 + * @see DiscoverySelectors#selectMethod(String) + * @see DiscoverySelectors#selectMethod(String, String) + * @see DiscoverySelectors#selectMethod(String, String, String) + * @see DiscoverySelectors#selectMethod(Class, String) + * @see DiscoverySelectors#selectMethod(Class, String, String) + * @see DiscoverySelectors#selectMethod(Class, Method) + * @see org.junit.platform.engine.support.descriptor.MethodSource + */ +@API(status = STABLE, since = "1.0") +public class MethodSelector implements DiscoverySelector { + + private final String className; + private final String methodName; + private final String methodParameterTypes; + + private Class javaClass; + private Method javaMethod; + + MethodSelector(String className, String methodName) { + this(className, methodName, ""); + } + + MethodSelector(String className, String methodName, String methodParameterTypes) { + this.className = className; + this.methodName = methodName; + this.methodParameterTypes = methodParameterTypes; + } + + MethodSelector(Class javaClass, String methodName) { + this(javaClass, methodName, ""); + } + + MethodSelector(Class javaClass, String methodName, String methodParameterTypes) { + this.javaClass = javaClass; + this.className = javaClass.getName(); + this.methodName = methodName; + this.methodParameterTypes = methodParameterTypes; + } + + MethodSelector(Class javaClass, Method method) { + this.javaClass = javaClass; + this.className = javaClass.getName(); + this.javaMethod = method; + this.methodName = method.getName(); + this.methodParameterTypes = ClassUtils.nullSafeToString(method.getParameterTypes()); + } + + /** + * Get the selected class name. + */ + public String getClassName() { + return this.className; + } + + /** + * Get the selected method name. + */ + public String getMethodName() { + return this.methodName; + } + + /** + * Get the parameter types for the selected method as a {@link String}, + * typically a comma-separated list of primitive types, fully qualified + * class names, or array types. + * + *

Note: the parameter types are provided as a single string instead of + * a collection in order to allow this selector to be used in a generic + * fashion by various test engines. It is therefore the responsibility of + * the caller of this method to determine how to parse the returned string. + * + * @return the parameter types supplied to this {@code MethodSelector} via + * a constructor or deduced from a {@code Method} supplied via a constructor; + * never {@code null} + */ + public String getMethodParameterTypes() { + return this.methodParameterTypes; + } + + /** + * Get the {@link Class} in which the selected {@linkplain #getJavaMethod + * method} is declared, or a subclass thereof. + * + *

If the {@link Class} was not provided, but only the name, this method + * attempts to lazily load the {@code Class} based on its name and throws a + * {@link PreconditionViolationException} if the class cannot be loaded. + * + * @see #getJavaMethod() + */ + public Class getJavaClass() { + lazyLoadJavaClass(); + return this.javaClass; + } + + /** + * Get the selected {@link Method}. + * + *

If the {@link Method} was not provided, but only the name, this method + * attempts to lazily load the {@code Method} based on its name and throws a + * {@link PreconditionViolationException} if the method cannot be loaded. + * + * @see #getJavaClass() + */ + public Method getJavaMethod() { + lazyLoadJavaMethod(); + return this.javaMethod; + } + + private void lazyLoadJavaClass() { + if (this.javaClass == null) { + // @formatter:off + this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( + cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); + // @formatter:on + } + } + + private void lazyLoadJavaMethod() { + lazyLoadJavaClass(); + + if (this.javaMethod == null) { + if (StringUtils.isNotBlank(this.methodParameterTypes)) { + this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName, + this.methodParameterTypes).orElseThrow( + () -> new PreconditionViolationException(String.format( + "Could not find method with name [%s] and parameter types [%s] in class [%s].", + this.methodName, this.methodParameterTypes, this.javaClass.getName()))); + } + else { + this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName).orElseThrow( + () -> new PreconditionViolationException( + String.format("Could not find method with name [%s] in class [%s].", this.methodName, + this.javaClass.getName()))); + } + } + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MethodSelector that = (MethodSelector) o; + return Objects.equals(this.className, that.className)// + && Objects.equals(this.methodName, that.methodName)// + && Objects.equals(this.methodParameterTypes, that.methodParameterTypes); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return Objects.hash(this.className, this.methodName, this.methodParameterTypes); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("className", this.className) + .append("methodName", this.methodName) + .append("methodParameterTypes", this.methodParameterTypes) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java new file mode 100644 index 00000000..68825eb0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a module name so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can discover + * tests or containers based on modules. + * + * @since 1.1 + * @see DiscoverySelectors#selectModule(String) + * @see DiscoverySelectors#selectModules(java.util.Set) + */ +@API(status = STABLE, since = "1.1") +public class ModuleSelector implements DiscoverySelector { + + private final String moduleName; + + ModuleSelector(String moduleName) { + this.moduleName = moduleName; + } + + /** + * Get the selected module name. + */ + public String getModuleName() { + return this.moduleName; + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModuleSelector that = (ModuleSelector) o; + return Objects.equals(this.moduleName, that.moduleName); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return this.moduleName.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("moduleName", this.moduleName).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java new file mode 100644 index 00000000..5d463eab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java @@ -0,0 +1,127 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a nested {@link Class} + * or class name enclosed in other classes so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can discover + * tests or containers based on classes. + * + *

If Java {@link Class} references are provided for the nested class or + * the enclosing classes, the selector will return these {@code Class} and + * their class names accordingly. If class names are provided, the selector + * will only attempt to lazily load the {@link Class} if + * {@link #getEnclosingClasses()} or {@link #getNestedClass()} are invoked. + * + *

In this context, Java {@link Class} means anything that can be referenced + * as a {@link Class} on the JVM — for example, classes from other JVM + * languages such Groovy, Scala, etc. + * + * @since 1.6 + * @see DiscoverySelectors#selectNestedClass(List, Class) + * @see DiscoverySelectors#selectNestedClass(List, String) + * @see org.junit.platform.engine.support.descriptor.ClassSource + * @see ClassSelector + */ +@API(status = STABLE, since = "1.6") +public class NestedClassSelector implements DiscoverySelector { + + private List enclosingClassSelectors; + private ClassSelector nestedClassSelector; + + NestedClassSelector(List enclosingClassNames, String nestedClassName) { + this.enclosingClassSelectors = enclosingClassNames.stream().map(ClassSelector::new).collect(toList()); + this.nestedClassSelector = new ClassSelector(nestedClassName); + } + + NestedClassSelector(List> enclosingClasses, Class nestedClass) { + this.enclosingClassSelectors = enclosingClasses.stream().map(ClassSelector::new).collect(toList()); + this.nestedClassSelector = new ClassSelector(nestedClass); + } + + /** + * Get the names of the classes enclosing the selected nested class. + */ + public List getEnclosingClassNames() { + return enclosingClassSelectors.stream().map(ClassSelector::getClassName).collect(toList()); + } + + /** + * Get the list of {@link Class} enclosing the selected nested + * {@link Class}. + * + *

If the {@link Class} were not provided, but only the name of the + * nested class and its enclosing classes, this method attempts to lazily + * load the list of enclosing {@link Class} and throws a + * {@link PreconditionViolationException} if the classes cannot be loaded. + */ + public List> getEnclosingClasses() { + return enclosingClassSelectors.stream().map(ClassSelector::getJavaClass).collect(toList()); + } + + /** + * Get the name of the selected nested class. + */ + public String getNestedClassName() { + return nestedClassSelector.getClassName(); + } + + /** + * Get the selected nested {@link Class}. + * + *

If the {@link Class} were not provided, but only the name of the + * nested class and its enclosing classes, this method attempts to lazily + * load the nested {@link Class} and throws a + * {@link PreconditionViolationException} if the class cannot be loaded. + */ + public Class getNestedClass() { + return nestedClassSelector.getJavaClass(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NestedClassSelector that = (NestedClassSelector) o; + return enclosingClassSelectors.equals(that.enclosingClassSelectors) + && nestedClassSelector.equals(that.nestedClassSelector); + } + + @Override + public int hashCode() { + return Objects.hash(enclosingClassSelectors, nestedClassSelector); + } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("enclosingClassNames", getEnclosingClassNames()) // + .append("nestedClassName", getNestedClassName()) // + .toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java new file mode 100644 index 00000000..28f00afa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java @@ -0,0 +1,188 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a nested {@link Method} + * or a combination of enclosing classes names, class name, method + * name, and parameter types so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can discover + * tests or containers based on methods. + * + *

If a Java {@link Method} is provided, the selector will return that + * {@linkplain #getMethod() method} and its method name, class name, enclosing + * classes names and parameter types accordingly. If class or methods names are + * provided, this selector will only attempt to lazily load the {@link Class} + * and {@link Method} if {@link #getEnclosingClasses()}, + * {@link #getNestedClass()} or {@link #getMethod()} is invoked. + * + *

In this context, a Java {@code Method} means anything that can be referenced + * as a {@link Method} on the JVM — for example, methods from Java classes + * or methods from other JVM languages such Groovy, Scala, etc. + * + * @since 1.6 + * @see DiscoverySelectors#selectNestedMethod(List, String, String) + * @see DiscoverySelectors#selectNestedMethod(List, String, String, String) + * @see DiscoverySelectors#selectNestedMethod(List, Class, String) + * @see DiscoverySelectors#selectNestedMethod(List, Class, String, String) + * @see DiscoverySelectors#selectNestedMethod(List, Class, Method) + * @see org.junit.platform.engine.support.descriptor.MethodSource + * @see NestedClassSelector + * @see MethodSelector + */ +@API(status = STABLE, since = "1.6") +public class NestedMethodSelector implements DiscoverySelector { + + private final NestedClassSelector nestedClassSelector; + private final MethodSelector methodSelector; + + NestedMethodSelector(List enclosingClassNames, String nestedClassName, String methodName) { + this.nestedClassSelector = new NestedClassSelector(enclosingClassNames, nestedClassName); + this.methodSelector = new MethodSelector(nestedClassName, methodName); + } + + NestedMethodSelector(List enclosingClassNames, String nestedClassName, String methodName, + String methodParameterTypes) { + this.nestedClassSelector = new NestedClassSelector(enclosingClassNames, nestedClassName); + this.methodSelector = new MethodSelector(nestedClassName, methodName, methodParameterTypes); + } + + NestedMethodSelector(List> enclosingClasses, Class nestedClass, String methodName) { + this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); + this.methodSelector = new MethodSelector(nestedClass, methodName); + } + + NestedMethodSelector(List> enclosingClasses, Class nestedClass, String methodName, + String methodParameterTypes) { + this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); + this.methodSelector = new MethodSelector(nestedClass, methodName, methodParameterTypes); + } + + NestedMethodSelector(List> enclosingClasses, Class nestedClass, Method method) { + this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); + this.methodSelector = new MethodSelector(nestedClass, method); + } + + /** + * Get the names of the classes enclosing the nested class + * containing the selected method. + */ + public List getEnclosingClassNames() { + return nestedClassSelector.getEnclosingClassNames(); + } + + /** + * Get the list of {@link Class} enclosing the nested {@link Class} + * containing the selected {@link Method}. + * + *

If the {@link Class} were not provided, but only the name of the + * nested class and its enclosing classes, this method attempts to lazily + * load the list of enclosing {@link Class} and throws a + * {@link PreconditionViolationException} if the classes cannot be loaded. + */ + public List> getEnclosingClasses() { + return nestedClassSelector.getEnclosingClasses(); + } + + /** + * Get the name of the nested class containing the selected method. + */ + public String getNestedClassName() { + return nestedClassSelector.getNestedClassName(); + } + + /** + * Get the nested {@link Class} containing the selected {@link Method}. + * + *

If the {@link Class} were not provided, but only the name of the + * nested class and its enclosing classes, this method attempts to lazily + * load the nested {@link Class} and throws a + * {@link PreconditionViolationException} if the class cannot be loaded. + */ + public Class getNestedClass() { + return nestedClassSelector.getNestedClass(); + } + + /** + * Get the name of the selected method. + */ + public String getMethodName() { + return methodSelector.getMethodName(); + } + + /** + * Get the selected {@link Method}. + * + *

If the {@link Method} was not provided, but only the name, this method + * attempts to lazily load the {@code Method} based on its name and throws a + * {@link PreconditionViolationException} if the method cannot be loaded. + */ + public Method getMethod() { + return methodSelector.getJavaMethod(); + } + + /** + * Get the parameter types for the selected method as a {@link String}, + * typically a comma-separated list of primitive types, fully qualified + * class names, or array types. + * + *

Note: the parameter types are provided as a single string instead of + * a collection in order to allow this selector to be used in a generic + * fashion by various test engines. It is therefore the responsibility of + * the caller of this method to determine how to parse the returned string. + * + * @return the parameter types supplied to this {@code NestedMethodSelector} + * via a constructor or deduced from a {@code Method} supplied via a + * constructor; never {@code null} + */ + public String getMethodParameterTypes() { + return methodSelector.getMethodParameterTypes(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NestedMethodSelector that = (NestedMethodSelector) o; + return nestedClassSelector.equals(that.nestedClassSelector) && methodSelector.equals(that.methodSelector); + } + + @Override + public int hashCode() { + return Objects.hash(nestedClassSelector, methodSelector); + } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("enclosingClassNames", getEnclosingClassNames()) // + .append("nestedClassName", getNestedClassName()) // + .append("methodName", getMethodName()) // + .append("methodParameterTypes", getMethodParameterTypes()) // + .toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java new file mode 100644 index 00000000..55a808ef --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.engine.DiscoveryFilter; + +/** + * {@link DiscoveryFilter} that is applied to the name of a {@link Package}. + * + * @since 1.0 + * @see #includePackageNames(String...) + * @see #excludePackageNames(String...) + * @see ClassNameFilter + */ +@API(status = STABLE, since = "1.0") +public interface PackageNameFilter extends DiscoveryFilter { + + /** + * Create a new include {@link PackageNameFilter} based on the + * supplied package names. + * + *

The names are combined using OR semantics, i.e. if the fully + * qualified name of a package starts with at least one of the names, + * the package will be included in the result set. + * + * @param names package names that we be compared against fully qualified + * package names; never {@code null}, empty, or containing {@code null} + * @see Package#getName() + * @see #includePackageNames(List) + * @see #excludePackageNames(String...) + */ + static PackageNameFilter includePackageNames(String... names) { + return new IncludePackageNameFilter(names); + } + + /** + * Create a new include {@link PackageNameFilter} based on the + * supplied package names. + * + *

The names are combined using OR semantics, i.e. if the fully + * qualified name of a package starts with at least one of the names, + * the package will be included in the result set. + * + * @param names package names that we be compared against fully qualified + * package names; never {@code null}, empty, or containing {@code null} + * @see Package#getName() + * @see #includePackageNames(String...) + * @see #excludePackageNames(String...) + */ + static PackageNameFilter includePackageNames(List names) { + return includePackageNames(names.toArray(new String[0])); + } + + /** + * Create a new exclude {@link PackageNameFilter} based on the + * supplied package names. + * + *

The names are combined using OR semantics, i.e. if the fully + * qualified name of a package starts with at least one of the names, + * the package will be excluded in the result set. + * + * @param names package names that we be compared against fully qualified + * package names; never {@code null}, empty, or containing {@code null} + * @see Package#getName() + * @see #excludePackageNames(List) + * @see #includePackageNames(String...) + */ + static PackageNameFilter excludePackageNames(String... names) { + return new ExcludePackageNameFilter(names); + } + + /** + * Create a new exclude {@link PackageNameFilter} based on the + * supplied package names. + * + *

The names are combined using OR semantics, i.e. if the fully + * qualified name of a package starts with at least one of the names, + * the package will be excluded in the result set. + * + * @param names package names that we be compared against fully qualified + * package names; never {@code null}, empty, or containing {@code null} + * @see Package#getName() + * @see #excludePackageNames(String...) + * @see #includePackageNames(String...) + */ + static PackageNameFilter excludePackageNames(List names) { + return excludePackageNames(names.toArray(new String[0])); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java new file mode 100644 index 00000000..781fc2af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a package name so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can discover + * tests or containers based on packages. + * + * @since 1.0 + * @see DiscoverySelectors#selectPackage(String) + * @see org.junit.platform.engine.support.descriptor.PackageSource + */ +@API(status = STABLE, since = "1.0") +public class PackageSelector implements DiscoverySelector { + + private final String packageName; + + PackageSelector(String packageName) { + this.packageName = packageName; + } + + /** + * Get the selected package name. + */ + public String getPackageName() { + return this.packageName; + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PackageSelector that = (PackageSelector) o; + return Objects.equals(this.packageName, that.packageName); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return this.packageName.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("packageName", this.packageName).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java new file mode 100644 index 00000000..4fee909e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.UniqueId; + +/** + * A {@link DiscoverySelector} that selects a {@link UniqueId} so that + * {@link org.junit.platform.engine.TestEngine TestEngines} can discover + * tests or containers based on unique IDs. + * + * @since 1.0 + * @see DiscoverySelectors#selectUniqueId(String) + * @see DiscoverySelectors#selectUniqueId(UniqueId) + */ +@API(status = STABLE, since = "1.0") +public class UniqueIdSelector implements DiscoverySelector { + + private final UniqueId uniqueId; + + UniqueIdSelector(UniqueId uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * Get the selected {@link UniqueId}. + */ + public UniqueId getUniqueId() { + return this.uniqueId; + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UniqueIdSelector that = (UniqueIdSelector) o; + return Objects.equals(this.uniqueId, that.uniqueId); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return this.uniqueId.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("uniqueId", this.uniqueId).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java new file mode 100644 index 00000000..c673851d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.net.URI; +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.DiscoverySelector; + +/** + * A {@link DiscoverySelector} that selects a {@link URI} so that + * {@link org.junit.platform.engine.TestEngine TestEngines} + * can discover tests or containers based on URIs. + * + * @since 1.0 + * @see DiscoverySelectors#selectUri(String) + * @see DiscoverySelectors#selectUri(URI) + * @see FileSelector + * @see DirectorySelector + * @see org.junit.platform.engine.support.descriptor.UriSource + */ +@API(status = STABLE, since = "1.0") +public class UriSelector implements DiscoverySelector { + + private final URI uri; + + UriSelector(URI uri) { + this.uri = uri; + } + + /** + * Get the selected {@link URI}. + */ + public URI getUri() { + return this.uri; + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UriSelector that = (UriSelector) o; + return Objects.equals(this.uri, that.uri); + } + + /** + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + @Override + public int hashCode() { + return this.uri.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("uri", this.uri).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java new file mode 100644 index 00000000..55321771 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java @@ -0,0 +1,7 @@ +/** + * Concrete {@linkplain org.junit.platform.engine.DiscoverySelector selectors} and + * {@linkplain org.junit.platform.engine.DiscoveryFilter filters} to be used in + * {@linkplain org.junit.platform.engine.EngineDiscoveryRequest discovery requests}. + */ + +package org.junit.platform.engine.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java new file mode 100644 index 00000000..63bb4172 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java @@ -0,0 +1,5 @@ +/** + * Public API for test engines. + */ + +package org.junit.platform.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java new file mode 100644 index 00000000..6e114191 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.reporting; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.STABLE; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code ReportEntry} encapsulates a time-stamped map of {@code String}-based + * key-value pairs to be published to the reporting infrastructure. + * + * @since 1.0 + * @see #from(Map) + * @see #from(String, String) + */ +@API(status = STABLE, since = "1.0") +public final class ReportEntry { + + private final LocalDateTime timestamp = LocalDateTime.now(); + private final Map keyValuePairs = new LinkedHashMap<>(); + + /** + * @deprecated Use {@link #from(String, String)} or {@link #from(Map)} + */ + @API(status = DEPRECATED, since = "5.8") + @Deprecated + public ReportEntry() { + } + + /** + * Factory for creating a new {@code ReportEntry} from a map of key-value pairs. + * + * @param keyValuePairs the map of key-value pairs to be published; never + * {@code null}; keys and values within entries in the map also must not be + * {@code null} or blank + */ + public static ReportEntry from(Map keyValuePairs) { + Preconditions.notNull(keyValuePairs, "keyValuePairs must not be null"); + + ReportEntry reportEntry = new ReportEntry(); + keyValuePairs.forEach(reportEntry::add); + return reportEntry; + } + + /** + * Factory for creating a new {@code ReportEntry} from a key-value pair. + * + * @param key the key under which the value should published; never + * {@code null} or blank + * @param value the value to publish; never {@code null} or blank + */ + public static ReportEntry from(String key, String value) { + ReportEntry reportEntry = new ReportEntry(); + reportEntry.add(key, value); + return reportEntry; + } + + private void add(String key, String value) { + Preconditions.notBlank(key, "key must not be null or blank"); + Preconditions.notBlank(value, "value must not be null or blank"); + this.keyValuePairs.put(key, value); + } + + /** + * Get an unmodifiable copy of the map of key-value pairs to be published. + * + * @return a copy of the map of key-value pairs; never {@code null} + */ + public final Map getKeyValuePairs() { + return Collections.unmodifiableMap(this.keyValuePairs); + } + + /** + * Get the timestamp for when this {@code ReportEntry} was created. + * + *

Can be used, for example, to order entries. + * + * @return when this entry was created; never {@code null} + */ + public final LocalDateTime getTimestamp() { + return this.timestamp; + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + builder.append("timestamp", this.timestamp); + this.keyValuePairs.forEach(builder::append); + return builder.toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java new file mode 100644 index 00000000..11965058 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes used by test engines to report additional data to execution + * listeners. + */ + +package org.junit.platform.engine.reporting; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java new file mode 100644 index 00000000..f50e9a32 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.config; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * View of {@link ConfigurationParameters} that applies a supplied prefix to all + * queries. + * + * @since 1.3 + */ +@API(status = EXPERIMENTAL, since = "1.3") +public class PrefixedConfigurationParameters implements ConfigurationParameters { + + private final ConfigurationParameters delegate; + private final String prefix; + + /** + * Create a new view of the supplied {@link ConfigurationParameters} that + * applies the supplied prefix to all queries. + * + * @param delegate the {@link ConfigurationParameters} to delegate to; never + * {@code null} + * @param prefix the prefix to apply to all queries; never {@code null} or + * blank + */ + public PrefixedConfigurationParameters(ConfigurationParameters delegate, String prefix) { + this.delegate = Preconditions.notNull(delegate, "delegate must not be null"); + this.prefix = Preconditions.notBlank(prefix, "prefix must not be null or blank"); + } + + @Override + public Optional get(String key) { + return delegate.get(prefixed(key)); + } + + @Override + public Optional getBoolean(String key) { + return delegate.getBoolean(prefixed(key)); + } + + @Override + public Optional get(String key, Function transformer) { + return delegate.get(prefixed(key), transformer); + } + + private String prefixed(String key) { + return prefix + key; + } + + @Override + @SuppressWarnings("deprecation") + public int size() { + return delegate.size(); + } + + @Override + public Set keySet() { + return delegate.keySet(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java new file mode 100644 index 00000000..05f555a2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.junit.platform.engine.ConfigurationParameters}-related support + * classes intended to be used by test engine implementations. + */ + +package org.junit.platform.engine.support.config; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java new file mode 100644 index 00000000..dd0a53e8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java @@ -0,0 +1,187 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static java.util.Collections.emptySet; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; + +/** + * Abstract base implementation of {@link TestDescriptor} that may be used by + * custom {@link org.junit.platform.engine.TestEngine TestEngines}. + * + *

Subclasses should provide a {@link TestSource} in their constructor, if + * possible, and override {@link #getTags()}, if appropriate. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public abstract class AbstractTestDescriptor implements TestDescriptor { + + private final UniqueId uniqueId; + + private final String displayName; + + private final TestSource source; + + private TestDescriptor parent; + + /** + * The synchronized set of children associated with this {@code TestDescriptor}. + * + *

This set is used in methods such as {@link #addChild(TestDescriptor)}, + * {@link #removeChild(TestDescriptor)}, {@link #removeFromHierarchy()}, and + * {@link #findByUniqueId(UniqueId)}, and an immutable copy of this set is + * returned by {@link #getChildren()}. + * + *

If a subclass overrides any of the methods related to children, this + * set should be used instead of a set local to the subclass. + */ + protected final Set children = Collections.synchronizedSet(new LinkedHashSet<>(16)); + + /** + * Create a new {@code AbstractTestDescriptor} with the supplied + * {@link UniqueId} and display name. + * + * @param uniqueId the unique ID of this {@code TestDescriptor}; never + * {@code null} + * @param displayName the display name for this {@code TestDescriptor}; + * never {@code null} or blank + * @see #AbstractTestDescriptor(UniqueId, String, TestSource) + */ + protected AbstractTestDescriptor(UniqueId uniqueId, String displayName) { + this(uniqueId, displayName, null); + } + + /** + * Create a new {@code AbstractTestDescriptor} with the supplied + * {@link UniqueId}, display name, and source. + * + * @param uniqueId the unique ID of this {@code TestDescriptor}; never + * {@code null} + * @param displayName the display name for this {@code TestDescriptor}; + * never {@code null} or blank + * @param source the source of the test or container described by this + * {@code TestDescriptor}; can be {@code null} + * @see #AbstractTestDescriptor(UniqueId, String) + */ + protected AbstractTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) { + this.uniqueId = Preconditions.notNull(uniqueId, "UniqueId must not be null"); + this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank"); + this.source = source; + } + + @Override + public final UniqueId getUniqueId() { + return this.uniqueId; + } + + @Override + public final String getDisplayName() { + return this.displayName; + } + + @Override + public Set getTags() { + return emptySet(); + } + + @Override + public Optional getSource() { + return Optional.ofNullable(this.source); + } + + @Override + public final Optional getParent() { + return Optional.ofNullable(this.parent); + } + + @Override + public final void setParent(TestDescriptor parent) { + this.parent = parent; + } + + @Override + public final Set getChildren() { + return Collections.unmodifiableSet(this.children); + } + + @Override + public void addChild(TestDescriptor child) { + Preconditions.notNull(child, "child must not be null"); + child.setParent(this); + this.children.add(child); + } + + @Override + public void removeChild(TestDescriptor child) { + Preconditions.notNull(child, "child must not be null"); + this.children.remove(child); + child.setParent(null); + } + + @Override + public void removeFromHierarchy() { + Preconditions.condition(!isRoot(), "cannot remove the root of a hierarchy"); + this.parent.removeChild(this); + this.children.forEach(child -> child.setParent(null)); + this.children.clear(); + } + + @Override + public Optional findByUniqueId(UniqueId uniqueId) { + Preconditions.notNull(uniqueId, "UniqueId must not be null"); + if (getUniqueId().equals(uniqueId)) { + return Optional.of(this); + } + // @formatter:off + return this.children.stream() + .map(child -> child.findByUniqueId(uniqueId)) + .filter(Optional::isPresent) + .findAny() + .orElse(Optional.empty()); + // @formatter:on + } + + @Override + public final int hashCode() { + return this.uniqueId.hashCode(); + } + + @Override + public final boolean equals(Object other) { + if (other == null) { + return false; + } + if (this.getClass() != other.getClass()) { + return false; + } + TestDescriptor that = (TestDescriptor) other; + return this.getUniqueId().equals(that.getUniqueId()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": " + getUniqueId(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java new file mode 100644 index 00000000..89f33add --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java @@ -0,0 +1,223 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.net.URI; +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestSource; + +/** + * Class based {@link org.junit.platform.engine.TestSource TestSource} with + * an optional {@linkplain FilePosition file position}. + * + *

If a Java {@link Class} reference is provided, the {@code ClassSource} + * will contain that {@code Class} and its class name accordingly. If a class + * name is provided, the {@code ClassSource} will contain the class name and + * will only attempt to lazily load the {@link Class} if {@link #getJavaClass()} + * is invoked. + * + *

In this context, Java {@link Class} means anything that can be referenced + * as a {@link Class} on the JVM — for example, classes from other JVM + * languages such Groovy, Scala, etc. + * + * @since 1.0 + * @see org.junit.platform.engine.discovery.ClassSelector + */ +@API(status = STABLE, since = "1.0") +public class ClassSource implements TestSource { + + private static final long serialVersionUID = 1L; + + /** + * {@link URI} {@linkplain URI#getScheme() scheme} for class sources: {@value} + * + * @since 1.8 + */ + @API(status = STABLE, since = "1.8") + public static final String CLASS_SCHEME = "class"; + + /** + * Create a new {@code ClassSource} using the supplied class name. + * + * @param className the class name; must not be {@code null} or blank + */ + public static ClassSource from(String className) { + return new ClassSource(className); + } + + /** + * Create a new {@code ClassSource} using the supplied class name and + * {@linkplain FilePosition file position}. + * + * @param className the class name; must not be {@code null} or blank + * @param filePosition the position in the source file; may be {@code null} + */ + public static ClassSource from(String className, FilePosition filePosition) { + return new ClassSource(className, filePosition); + } + + /** + * Create a new {@code ClassSource} using the supplied {@linkplain Class class}. + * + * @param javaClass the Java class; must not be {@code null} + */ + public static ClassSource from(Class javaClass) { + return new ClassSource(javaClass); + } + + /** + * Create a new {@code ClassSource} using the supplied {@linkplain Class class} + * and {@linkplain FilePosition file position}. + * + * @param javaClass the Java class; must not be {@code null} + * @param filePosition the position in the Java source file; may be {@code null} + */ + public static ClassSource from(Class javaClass, FilePosition filePosition) { + return new ClassSource(javaClass, filePosition); + } + + /** + * Create a new {@code ClassSource} from the supplied {@link URI}. + * + *

URIs should be formatted as {@code class:fully.qualified.class.Name}. + * The {@linkplain URI#getQuery() query} component of the {@code URI}, if + * present, will be used to retrieve the {@link FilePosition} via + * {@link FilePosition#fromQuery(String)}. For example, line 42 and column + * 13 can be referenced in class {@code org.example.MyType} via the following + * URI: {@code class:com.example.MyType?line=42&column=13}. The URI fragment, + * if present, will be ignored. + * + * @param uri the {@code URI} for the class source; never {@code null} + * @return a new {@code ClassSource}; never {@code null} + * @throws PreconditionViolationException if the supplied {@code URI} is + * {@code null}, if the scheme of the supplied {@code URI} is not equal + * to the {@link #CLASS_SCHEME}, or if the specified class name is empty + * @since 1.8 + * @see #CLASS_SCHEME + */ + @API(status = STABLE, since = "1.8") + public static ClassSource from(URI uri) { + Preconditions.notNull(uri, "URI must not be null"); + Preconditions.condition(CLASS_SCHEME.equals(uri.getScheme()), + () -> "URI [" + uri + "] must have [" + CLASS_SCHEME + "] scheme"); + + String className = uri.getSchemeSpecificPart(); + FilePosition filePosition = null; + int indexOfQuery = className.indexOf('?'); + if (indexOfQuery >= 0) { + filePosition = FilePosition.fromQuery(className.substring(indexOfQuery + 1)).orElse(null); + className = className.substring(0, indexOfQuery); + } + + return ClassSource.from(className, filePosition); + } + + private final String className; + private final FilePosition filePosition; + private Class javaClass; + + private ClassSource(String className) { + this(className, null); + } + + private ClassSource(String className, FilePosition filePosition) { + this.className = Preconditions.notBlank(className, "Class name must not be null or blank"); + this.filePosition = filePosition; + } + + private ClassSource(Class javaClass) { + this(javaClass, null); + } + + private ClassSource(Class javaClass, FilePosition filePosition) { + this.javaClass = Preconditions.notNull(javaClass, "Class must not be null"); + this.className = this.javaClass.getName(); + this.filePosition = filePosition; + } + + /** + * Get the class name of this source. + * + * @see #getJavaClass() + * @see #getPosition() + */ + public final String getClassName() { + return this.className; + } + + /** + * Get the {@linkplain Class Java class} of this source. + * + *

If the {@link Class} was not provided, but only the name, this method + * attempts to lazily load the {@link Class} based on its name and throws a + * {@link PreconditionViolationException} if the class cannot be loaded. + * + * @see #getClassName() + * @see #getPosition() + */ + public final Class getJavaClass() { + if (this.javaClass == null) { + // @formatter:off + this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( + cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); + // @formatter:on + } + return this.javaClass; + } + + /** + * Get the {@linkplain FilePosition position} in the source file for + * the associated {@linkplain #getClassName class}, if available. + * + * @see #getClassName() + * @see #getJavaClass() + */ + public final Optional getPosition() { + return Optional.ofNullable(this.filePosition); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClassSource that = (ClassSource) o; + return Objects.equals(this.className, that.className) && Objects.equals(this.filePosition, that.filePosition); + } + + @Override + public int hashCode() { + return Objects.hash(this.className, this.filePosition); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("className", this.className) + .append("filePosition", this.filePosition) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java new file mode 100644 index 00000000..0a288d32 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java @@ -0,0 +1,174 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.net.URI; +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestSource; + +/** + * Classpath resource based {@link org.junit.platform.engine.TestSource} + * with an optional {@linkplain FilePosition position}. + * + * @since 1.0 + * @see org.junit.platform.engine.discovery.ClasspathResourceSelector + */ +@API(status = STABLE, since = "1.0") +public class ClasspathResourceSource implements TestSource { + + private static final long serialVersionUID = 1L; + + /** + * {@link URI} {@linkplain URI#getScheme() scheme} for classpath + * resources: {@value} + * + * @since 1.3 + */ + public static final String CLASSPATH_SCHEME = "classpath"; + + /** + * Create a new {@code ClasspathResourceSource} using the supplied classpath + * resource name. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + * @param classpathResourceName the name of the classpath resource; never + * {@code null} or blank + * @see ClassLoader#getResource(String) + * @see ClassLoader#getResourceAsStream(String) + * @see ClassLoader#getResources(String) + */ + public static ClasspathResourceSource from(String classpathResourceName) { + return new ClasspathResourceSource(classpathResourceName); + } + + /** + * Create a new {@code ClasspathResourceSource} using the supplied classpath + * resource name and {@link FilePosition}. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + * @param classpathResourceName the name of the classpath resource; never + * {@code null} or blank + * @param filePosition the position in the classpath resource; may be {@code null} + */ + public static ClasspathResourceSource from(String classpathResourceName, FilePosition filePosition) { + return new ClasspathResourceSource(classpathResourceName, filePosition); + } + + /** + * Create a new {@code ClasspathResourceSource} from the supplied {@link URI}. + * + *

The {@link URI#getPath() path} component of the {@code URI} (excluding + * the query) will be used as the classpath resource name. The + * {@linkplain URI#getQuery() query} component of the {@code URI}, if present, + * will be used to retrieve the {@link FilePosition} via + * {@link FilePosition#fromQuery(String)}. + * + * @param uri the {@code URI} for the classpath resource; never {@code null} + * @return a new {@code ClasspathResourceSource}; never {@code null} + * @throws PreconditionViolationException if the supplied {@code URI} is + * {@code null} or if the scheme of the supplied {@code URI} is not equal + * to the {@link #CLASSPATH_SCHEME} + * @since 1.3 + * @see #CLASSPATH_SCHEME + */ + @API(status = STABLE, since = "1.3") + public static ClasspathResourceSource from(URI uri) { + Preconditions.notNull(uri, "URI must not be null"); + Preconditions.condition(CLASSPATH_SCHEME.equals(uri.getScheme()), + () -> "URI [" + uri + "] must have [" + CLASSPATH_SCHEME + "] scheme"); + + String classpathResource = ResourceUtils.stripQueryComponent(uri).getPath(); + FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null); + return ClasspathResourceSource.from(classpathResource, filePosition); + } + + private final String classpathResourceName; + private final FilePosition filePosition; + + private ClasspathResourceSource(String classpathResourceName) { + this(classpathResourceName, null); + } + + private ClasspathResourceSource(String classpathResourceName, FilePosition filePosition) { + Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); + boolean startsWithSlash = classpathResourceName.startsWith("/"); + this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); + this.filePosition = filePosition; + } + + /** + * Get the name of the source classpath resource. + * + *

The name of a classpath resource follows the semantics for + * resource paths as defined in {@link ClassLoader#getResource(String)}. + * + * @see ClassLoader#getResource(String) + * @see ClassLoader#getResourceAsStream(String) + * @see ClassLoader#getResources(String) + */ + public String getClasspathResourceName() { + return this.classpathResourceName; + } + + /** + * Get the {@link FilePosition}, if available. + */ + public final Optional getPosition() { + return Optional.ofNullable(this.filePosition); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClasspathResourceSource that = (ClasspathResourceSource) o; + return Objects.equals(this.classpathResourceName, that.classpathResourceName) + && Objects.equals(this.filePosition, that.filePosition); + } + + @Override + public int hashCode() { + return Objects.hash(this.classpathResourceName, this.filePosition); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("classpathResourceName", this.classpathResourceName) + .append("filePosition", this.filePosition) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java new file mode 100644 index 00000000..3fa3ccc9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static java.util.Collections.unmodifiableList; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestSource; + +/** + * A {@code CompositeTestSource} contains one or more {@link TestSource TestSources}. + * + *

{@code CompositeTestSource} and its {@link #getSources sources} are immutable. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public class CompositeTestSource implements TestSource { + + private static final long serialVersionUID = 1L; + + /** + * Create a new {@code CompositeTestSource} based on the supplied + * collection of {@link TestSource sources}. + * + *

This constructor makes a defensive copy of the supplied collection + * and stores the sources as a list in the order in which they are + * returned by the collection's iterator. + * + * @param sources the collection of sources to store in this + * {@code CompositeTestSource}; never {@code null} or empty + */ + public static CompositeTestSource from(Collection sources) { + return new CompositeTestSource(sources); + } + + @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (unmodifiableList()) + private final List sources; + + private CompositeTestSource(Collection sources) { + Preconditions.notEmpty(sources, "TestSource collection must not be null or empty"); + Preconditions.containsNoNullElements(sources, "individual TestSources must not be null"); + this.sources = unmodifiableList(new ArrayList<>(sources)); + } + + /** + * Get an immutable list of the {@linkplain TestSource sources} stored in this + * {@code CompositeTestSource}. + * + * @return the sources stored in this {@code CompositeTestSource}; never {@code null} + */ + public final List getSources() { + return this.sources; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + CompositeTestSource that = (CompositeTestSource) obj; + return this.sources.equals(that.sources); + } + + @Override + public int hashCode() { + return this.sources.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("sources", this.sources).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java new file mode 100644 index 00000000..dd7a602a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import java.net.URI; +import java.util.Objects; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Default implementation of {@link UriSource}. + * + * @since 1.3 + */ +class DefaultUriSource implements UriSource { + + private static final long serialVersionUID = 1L; + + private final URI uri; + + DefaultUriSource(URI uri) { + this.uri = Preconditions.notNull(uri, "URI must not be null"); + } + + @Override + public URI getUri() { + return uri; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultUriSource that = (DefaultUriSource) o; + return Objects.equals(this.uri, that.uri); + } + + @Override + public int hashCode() { + return this.uri.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("uri", this.uri).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java new file mode 100644 index 00000000..c64c2dba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java @@ -0,0 +1,99 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.File; +import java.io.IOException; +import java.net.URI; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Directory based {@link org.junit.platform.engine.TestSource}. + * + * @since 1.0 + * @see org.junit.platform.engine.discovery.DirectorySelector + */ +@API(status = STABLE, since = "1.0") +public class DirectorySource implements FileSystemSource { + + private static final long serialVersionUID = 1L; + + /** + * Create a new {@code DirectorySource} using the supplied + * {@linkplain File directory}. + * + * @param directory the source directory; must not be {@code null} + */ + public static DirectorySource from(File directory) { + return new DirectorySource(directory); + } + + private final File directory; + + private DirectorySource(File directory) { + Preconditions.notNull(directory, "directory must not be null"); + try { + this.directory = directory.getCanonicalFile(); + } + catch (IOException ex) { + throw new JUnitException("Failed to retrieve canonical path for directory: " + directory, ex); + } + } + + /** + * Get the {@link URI} for the source {@linkplain #getFile directory}. + * + * @return the source {@code URI}; never {@code null} + */ + @Override + public final URI getUri() { + return getFile().toURI(); + } + + /** + * Get the source {@linkplain File directory}. + * + * @return the source directory; never {@code null} + */ + @Override + public final File getFile() { + return this.directory; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DirectorySource that = (DirectorySource) o; + return this.directory.equals(that.directory); + } + + @Override + public int hashCode() { + return this.directory.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("directory", this.directory).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java new file mode 100644 index 00000000..fbf57519 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.engine.UniqueId; + +/** + * An {@code EngineDescriptor} is a {@link org.junit.platform.engine.TestDescriptor + * TestDescriptor} for a specific {@link org.junit.platform.engine.TestEngine TestEngine}. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public class EngineDescriptor extends AbstractTestDescriptor { + + /** + * Create a new {@code EngineDescriptor} with the supplied {@link UniqueId} + * and display name. + * + * @param uniqueId the {@code UniqueId} for the described {@code TestEngine}; + * never {@code null} + * @param displayName the display name for the described {@code TestEngine}; + * never {@code null} or blank + * @see org.junit.platform.engine.TestEngine#getId() + * @see org.junit.platform.engine.TestDescriptor#getDisplayName() + */ + public EngineDescriptor(UniqueId uniqueId, String displayName) { + super(uniqueId, displayName); + } + + /** + * Returns {@link org.junit.platform.engine.TestDescriptor.Type#CONTAINER}. + * + * @see org.junit.platform.engine.TestDescriptor#isContainer() + * @see org.junit.platform.engine.TestDescriptor#isTest() + */ + @Override + public Type getType() { + return Type.CONTAINER; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java new file mode 100644 index 00000000..a0fb2c94 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java @@ -0,0 +1,178 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * Position inside a file represented by {@linkplain #getLine line} and + * {@linkplain #getColumn column} numbers. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public class FilePosition implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final Logger logger = LoggerFactory.getLogger(FilePosition.class); + + /** + * Create a new {@code FilePosition} using the supplied {@code line} number + * and an undefined column number. + * + * @param line the line number; must be greater than zero + * @return a {@link FilePosition} with the given line number + */ + public static FilePosition from(int line) { + return new FilePosition(line); + } + + /** + * Create a new {@code FilePosition} using the supplied {@code line} and + * {@code column} numbers. + * + * @param line the line number; must be greater than zero + * @param column the column number; must be greater than zero + * @return a {@link FilePosition} with the given line and column numbers + */ + public static FilePosition from(int line, int column) { + return new FilePosition(line, column); + } + + /** + * Create an optional {@code FilePosition} by parsing the supplied + * {@code query} string. + * + *

Examples of valid {@code query} strings: + *

    + *
  • {@code "line=23"}
  • + *
  • {@code "line=23&column=42"}
  • + *
+ * + * @param query the query string; may be {@code null} + * @return an {@link Optional} containing a {@link FilePosition} with + * the parsed line and column numbers; never {@code null} but potentially + * empty + * @since 1.3 + * @see #from(int) + * @see #from(int, int) + */ + public static Optional fromQuery(String query) { + FilePosition result = null; + Integer line = null; + Integer column = null; + if (StringUtils.isNotBlank(query)) { + try { + for (String pair : query.split("&")) { + String[] data = pair.split("="); + if (data.length == 2) { + String key = data[0]; + if (line == null && "line".equals(key)) { + line = Integer.valueOf(data[1]); + } + else if (column == null && "column".equals(key)) { + column = Integer.valueOf(data[1]); + } + } + + // Already found what we're looking for? + if (line != null && column != null) { + break; + } + } + } + catch (IllegalArgumentException ex) { + logger.debug(ex, () -> "Failed to parse 'line' and/or 'column' from query string: " + query); + // fall-through and continue + } + + if (line != null) { + result = column == null ? new FilePosition(line) : new FilePosition(line, column); + } + } + return Optional.ofNullable(result); + } + + private final int line; + private final Integer column; + + private FilePosition(int line) { + Preconditions.condition(line > 0, "line number must be greater than zero"); + this.line = line; + this.column = null; + } + + private FilePosition(int line, int column) { + Preconditions.condition(line > 0, "line number must be greater than zero"); + Preconditions.condition(column > 0, "column number must be greater than zero"); + this.line = line; + this.column = column; + } + + /** + * Get the line number of this {@code FilePosition}. + * + * @return the line number + */ + public int getLine() { + return this.line; + } + + /** + * Get the column number of this {@code FilePosition}, if available. + * + * @return an {@code Optional} containing the column number; never + * {@code null} but potentially empty + */ + public Optional getColumn() { + return Optional.ofNullable(this.column); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FilePosition that = (FilePosition) o; + return (this.line == that.line) && Objects.equals(this.column, that.column); + } + + @Override + public int hashCode() { + return Objects.hash(this.line, this.column); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("line", this.line) + .append("column", getColumn().orElse(-1)) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java new file mode 100644 index 00000000..7e4ab6c9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java @@ -0,0 +1,130 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Objects; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * File based {@link org.junit.platform.engine.TestSource} with an optional + * {@linkplain FilePosition position}. + * + * @since 1.0 + * @see org.junit.platform.engine.discovery.FileSelector + */ +@API(status = STABLE, since = "1.0") +public class FileSource implements FileSystemSource { + + private static final long serialVersionUID = 1L; + + /** + * Create a new {@code FileSource} using the supplied {@link File file}. + * + * @param file the source file; must not be {@code null} + */ + public static FileSource from(File file) { + return new FileSource(file); + } + + /** + * Create a new {@code FileSource} using the supplied {@link File file} and + * {@link FilePosition filePosition}. + * + * @param file the source file; must not be {@code null} + * @param filePosition the position in the source file; may be {@code null} + */ + public static FileSource from(File file, FilePosition filePosition) { + return new FileSource(file, filePosition); + } + + private final File file; + private final FilePosition filePosition; + + private FileSource(File file) { + this(file, null); + } + + private FileSource(File file, FilePosition filePosition) { + Preconditions.notNull(file, "file must not be null"); + try { + this.file = file.getCanonicalFile(); + } + catch (IOException ex) { + throw new JUnitException("Failed to retrieve canonical path for file: " + file, ex); + } + this.filePosition = filePosition; + } + + /** + * Get the {@link URI} for the source {@linkplain #getFile file}. + * + * @return the source {@code URI}; never {@code null} + */ + @Override + public final URI getUri() { + return getFile().toURI(); + } + + /** + * Get the source {@linkplain File file}. + * + * @return the source file; never {@code null} + */ + @Override + public final File getFile() { + return this.file; + } + + /** + * Get the {@link FilePosition}, if available. + */ + public final Optional getPosition() { + return Optional.ofNullable(this.filePosition); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FileSource that = (FileSource) o; + return Objects.equals(this.file, that.file) && Objects.equals(this.filePosition, that.filePosition); + } + + @Override + public int hashCode() { + return Objects.hash(this.file, this.filePosition); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("file", this.file) + .append("filePosition", this.filePosition) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java new file mode 100644 index 00000000..2a4c976d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.File; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestSource; + +/** + * File system based {@link TestSource}. + * + *

This interface uses {@link File} instead of {@link java.nio.file.Path} + * because the latter does not implement {@link java.io.Serializable}. + * + * @since 1.0 + */ +@API(status = STABLE, since = "1.0") +public interface FileSystemSource extends UriSource { + + /** + * Get the source file or directory. + * + * @return the source file or directory; never {@code null} + */ + File getFile(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java new file mode 100644 index 00000000..977bd4f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java @@ -0,0 +1,254 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; + +import java.lang.reflect.Method; +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestSource; + +/** + * Method based {@link org.junit.platform.engine.TestSource TestSource}. + * + *

This class stores the method name along with the names of its parameter + * types because {@link Method} does not implement {@link java.io.Serializable}. + * + * @since 1.0 + * @see org.junit.platform.engine.discovery.MethodSelector + */ +@API(status = STABLE, since = "1.0") +public class MethodSource implements TestSource { + + private static final long serialVersionUID = 1L; + + /** + * Create a new {@code MethodSource} using the supplied class name and + * method name. + * + * @param className the class name; must not be {@code null} or blank + * @param methodName the method name; must not be {@code null} or blank + */ + public static MethodSource from(String className, String methodName) { + return new MethodSource(className, methodName); + } + + /** + * Create a new {@code MethodSource} using the supplied class name, method + * name, and method parameter types. + * + * @param className the class name; must not be {@code null} or blank + * @param methodName the method name; must not be {@code null} or blank + * @param methodParameterTypes a comma-separated list of fully qualified + * class names representing the method parameter types + */ + public static MethodSource from(String className, String methodName, String methodParameterTypes) { + return new MethodSource(className, methodName, methodParameterTypes); + } + + /** + * Create a new {@code MethodSource} using the supplied class name, method + * name, and method parameter types. + * + * @param className the class name; must not be {@code null} or blank + * @param methodName the method name; must not be {@code null} or blank + * @param methodParameterTypes a varargs array of classes representing the + * method parameter types + * @since 1.5 + */ + @API(status = STABLE, since = "1.5") + public static MethodSource from(String className, String methodName, Class... methodParameterTypes) { + return new MethodSource(className, methodName, nullSafeToString(methodParameterTypes)); + } + + /** + * Create a new {@code MethodSource} using the supplied {@link Method method}. + * + * @param testMethod the Java method; must not be {@code null} + * @see #from(Class, Method) + */ + public static MethodSource from(Method testMethod) { + return new MethodSource(testMethod); + } + + /** + * Create a new {@code MethodSource} using the supplied + * {@link Class class} and {@link Method method}. + * + *

This method should be used in favor of {@link #from(Method)} if the + * test method is inherited from a superclass or present as an interface + * {@code default} method. + * + * @param testClass the Java class; must not be {@code null} + * @param testMethod the Java method; must not be {@code null} + * @since 1.3 + */ + @API(status = STABLE, since = "1.3") + public static MethodSource from(Class testClass, Method testMethod) { + return new MethodSource(testClass, testMethod); + } + + private final String className; + private final String methodName; + private final String methodParameterTypes; + private Class javaClass; + private transient Method javaMethod; + + private MethodSource(String className, String methodName) { + this(className, methodName, null); + } + + private MethodSource(String className, String methodName, String methodParameterTypes) { + Preconditions.notBlank(className, "Class name must not be null or blank"); + Preconditions.notBlank(methodName, "Method name must not be null or blank"); + this.className = className; + this.methodName = methodName; + this.methodParameterTypes = methodParameterTypes; + } + + private MethodSource(Method testMethod) { + this(Preconditions.notNull(testMethod, "Method must not be null").getDeclaringClass(), testMethod); + } + + /** + * @since 1.3 + */ + private MethodSource(Class testClass, Method testMethod) { + Preconditions.notNull(testClass, "Class must not be null"); + Preconditions.notNull(testMethod, "Method must not be null"); + this.className = testClass.getName(); + this.methodName = testMethod.getName(); + this.methodParameterTypes = nullSafeToString(testMethod.getParameterTypes()); + this.javaClass = testClass; + this.javaMethod = testMethod; + } + + /** + * Get the class name of this source. + */ + public String getClassName() { + return this.className; + } + + /** + * Get the method name of this source. + */ + public final String getMethodName() { + return this.methodName; + } + + /** + * Get the method parameter types of this source. + */ + public final String getMethodParameterTypes() { + return this.methodParameterTypes; + } + + /** + * Get the {@linkplain Class Java class} of this source. + * + *

If the {@link Class} was not provided, but only the name, this method + * attempts to lazily load the {@link Class} based on its name and throws a + * {@link PreconditionViolationException} if the class cannot be loaded. + * + * @since 1.7 + * @see #getClassName() + */ + @API(status = STABLE, since = "1.7") + public final Class getJavaClass() { + lazyLoadJavaClass(); + return this.javaClass; + } + + /** + * Get the {@linkplain Method Java method} of this source. + * + *

If the {@link Method} was not provided, but only the name, this method + * attempts to lazily load the {@code Method} based on its name and throws a + * {@link PreconditionViolationException} if the method cannot be loaded. + * + * @since 1.7 + * @see #getMethodName() + */ + @API(status = STABLE, since = "1.7") + public final Method getJavaMethod() { + lazyLoadJavaMethod(); + return this.javaMethod; + } + + private void lazyLoadJavaClass() { + if (this.javaClass == null) { + // @formatter:off + this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( + cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); + // @formatter:on + } + } + + private void lazyLoadJavaMethod() { + lazyLoadJavaClass(); + + if (this.javaMethod == null) { + if (StringUtils.isNotBlank(this.methodParameterTypes)) { + this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName, + this.methodParameterTypes).orElseThrow( + () -> new PreconditionViolationException(String.format( + "Could not find method with name [%s] and parameter types [%s] in class [%s].", + this.methodName, this.methodParameterTypes, this.javaClass.getName()))); + } + else { + this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName).orElseThrow( + () -> new PreconditionViolationException( + String.format("Could not find method with name [%s] in class [%s].", this.methodName, + this.javaClass.getName()))); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MethodSource that = (MethodSource) o; + return Objects.equals(this.className, that.className)// + && Objects.equals(this.methodName, that.methodName)// + && Objects.equals(this.methodParameterTypes, that.methodParameterTypes); + } + + @Override + public int hashCode() { + return Objects.hash(this.className, this.methodName, this.methodParameterTypes); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("className", this.className) + .append("methodName", this.methodName) + .append("methodParameterTypes", this.methodParameterTypes) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java new file mode 100644 index 00000000..b19dc3b3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Objects; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestSource; + +/** + * Package based {@link org.junit.platform.engine.TestSource}. + * + *

This class stores the package name because {@link Package} does not + * implement {@link java.io.Serializable}. + * + * @since 1.0 + * @see org.junit.platform.engine.discovery.PackageSelector + */ +@API(status = STABLE, since = "1.0") +public class PackageSource implements TestSource { + + private static final long serialVersionUID = 1L; + + /** + * Create a new {@code PackageSource} using the supplied Java {@link Package}. + * + * @param javaPackage the Java package; must not be {@code null} + */ + public static PackageSource from(Package javaPackage) { + return new PackageSource(javaPackage); + } + + /** + * Create a new {@code PackageSource} using the supplied {@code packageName}. + * + * @param packageName the package name; must not be {@code null} or blank + */ + public static PackageSource from(String packageName) { + return new PackageSource(packageName); + } + + private final String packageName; + + private PackageSource(Package javaPackage) { + this(Preconditions.notNull(javaPackage, "package must not be null").getName()); + } + + private PackageSource(String packageName) { + this.packageName = Preconditions.notBlank(packageName, "package name must not be null or blank"); + } + + /** + * Get the package name of this test source. + */ + public final String getPackageName() { + return this.packageName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PackageSource that = (PackageSource) o; + return Objects.equals(this.packageName, that.packageName); + } + + @Override + public int hashCode() { + return this.packageName.hashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("packageName", this.packageName).toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java new file mode 100644 index 00000000..ae9cfdd2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import java.net.URI; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; + +/** + * Collection of static utility methods for working with resources. + * + * @since 1.3 + */ +final class ResourceUtils { + + private ResourceUtils() { + /* no-op */ + } + + /** + * Strip the {@link URI#getQuery() query} component from the supplied + * {@link URI}. + * + * @param uri the {@code URI} from which to strip the query component + * @return a new {@code URI} with the query component removed, or the + * original {@code URI} unmodified if it does not have a query component + */ + static URI stripQueryComponent(URI uri) { + Preconditions.notNull(uri, "URI must not be null"); + + if (StringUtils.isBlank(uri.getQuery())) { + return uri; + } + + String uriAsString = uri.toString(); + return URI.create(uriAsString.substring(0, uriAsString.indexOf('?'))); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java new file mode 100644 index 00000000..fb6829e5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestSource; + +/** + * A {@link TestSource} that can be represented as a {@link URI}. + * + * @since 1.0 + * @see org.junit.platform.engine.discovery.UriSelector + */ +@API(status = STABLE, since = "1.0") +public interface UriSource extends TestSource { + + /** + * Get the {@link URI} that represents this source. + * + * @return the source {@code URI}; never {@code null} + */ + URI getUri(); + + /** + * Create a new {@code UriSource} using the supplied {@code URI}. + * + *

This implementation first attempts to resolve the supplied {@code URI} + * to a path-based {@code UriSource} in the local filesystem. If that fails + * for any reason, an instance of the default {@code UriSource} + * implementation storing the supplied {@code URI} as-is will be + * returned. + * + * @param uri the URI to use as the source; never {@code null} + * @return an appropriate {@code UriSource} for the supplied {@code URI} + * @since 1.3 + * @see org.junit.platform.engine.support.descriptor.FileSource + * @see org.junit.platform.engine.support.descriptor.DirectorySource + */ + static UriSource from(URI uri) { + Preconditions.notNull(uri, "URI must not be null"); + + try { + URI uriWithoutQuery = ResourceUtils.stripQueryComponent(uri); + Path path = Paths.get(uriWithoutQuery); + if (Files.isRegularFile(path)) { + return FileSource.from(path.toFile(), FilePosition.fromQuery(uri.getQuery()).orElse(null)); + } + if (Files.isDirectory(path)) { + return DirectorySource.from(path.toFile()); + } + } + catch (RuntimeException ex) { + LoggerFactory.getLogger(UriSource.class).debug(ex, () -> String.format( + "The supplied URI [%s] is not path-based. Falling back to default UriSource implementation.", uri)); + } + + // Store supplied URI as-is + return new DefaultUriSource(uri); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java new file mode 100644 index 00000000..9f063532 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java @@ -0,0 +1,7 @@ +/** + * {@link org.junit.platform.engine.TestDescriptor}-related support classes + * intended to be used by test engine implementations and clients of + * the launcher. + */ + +package org.junit.platform.engine.support.descriptor; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java new file mode 100644 index 00000000..886743da --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.discovery; + +import static java.util.stream.Collectors.toSet; +import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInClasspathRoot; +import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInModule; +import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInPackage; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.selectors; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; + +import java.util.List; +import java.util.function.Predicate; + +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageSelector; + +/** + * @since 1.5 + */ +class ClassContainerSelectorResolver implements SelectorResolver { + + private final Predicate> classFilter; + private final Predicate classNameFilter; + + ClassContainerSelectorResolver(Predicate> classFilter, Predicate classNameFilter) { + this.classFilter = classFilter; + this.classNameFilter = classNameFilter; + } + + @Override + public Resolution resolve(ClasspathRootSelector selector, Context context) { + return classSelectors(findAllClassesInClasspathRoot(selector.getClasspathRoot(), classFilter, classNameFilter)); + } + + @Override + public Resolution resolve(ModuleSelector selector, Context context) { + return classSelectors(findAllClassesInModule(selector.getModuleName(), classFilter, classNameFilter)); + } + + @Override + public Resolution resolve(PackageSelector selector, Context context) { + return classSelectors(findAllClassesInPackage(selector.getPackageName(), classFilter, classNameFilter)); + } + + private Resolution classSelectors(List> classes) { + if (classes.isEmpty()) { + return unresolved(); + } + return selectors(classes.stream().map(DiscoverySelectors::selectClass).collect(toSet())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java new file mode 100644 index 00000000..81c618f9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java @@ -0,0 +1,263 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.discovery; + +import static java.util.stream.Collectors.joining; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.SelectorResolutionResult.failed; +import static org.junit.platform.engine.SelectorResolutionResult.resolved; +import static org.junit.platform.engine.SelectorResolutionResult.unresolved; + +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.EngineDiscoveryListener; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.NestedClassSelector; +import org.junit.platform.engine.discovery.NestedMethodSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.discovery.UriSelector; +import org.junit.platform.engine.support.discovery.SelectorResolver.Context; +import org.junit.platform.engine.support.discovery.SelectorResolver.Match; +import org.junit.platform.engine.support.discovery.SelectorResolver.Resolution; + +/** + * @since 1.5 + */ +class EngineDiscoveryRequestResolution { + + private final EngineDiscoveryRequest request; + private final Context defaultContext; + private final List resolvers; + private final List visitors; + private final TestDescriptor engineDescriptor; + private final Map resolvedSelectors = new LinkedHashMap<>(); + private final Map resolvedUniqueIds = new LinkedHashMap<>(); + private final Queue remainingSelectors = new ArrayDeque<>(); + private final Map contextBySelector = new HashMap<>(); + + EngineDiscoveryRequestResolution(EngineDiscoveryRequest request, TestDescriptor engineDescriptor, + List resolvers, List visitors) { + this.request = request; + this.engineDescriptor = engineDescriptor; + this.resolvers = resolvers; + this.visitors = visitors; + this.defaultContext = new DefaultContext(null); + this.resolvedUniqueIds.put(engineDescriptor.getUniqueId(), Match.exact(engineDescriptor)); + } + + void run() { + remainingSelectors.addAll(request.getSelectorsByType(DiscoverySelector.class)); + while (!remainingSelectors.isEmpty()) { + resolveCompletely(remainingSelectors.poll()); + } + visitors.forEach(engineDescriptor::accept); + } + + private void resolveCompletely(DiscoverySelector selector) { + EngineDiscoveryListener discoveryListener = request.getDiscoveryListener(); + UniqueId engineId = engineDescriptor.getUniqueId(); + try { + Optional result = resolve(selector); + if (result.isPresent()) { + discoveryListener.selectorProcessed(engineId, selector, resolved()); + enqueueAdditionalSelectors(result.get()); + } + else { + discoveryListener.selectorProcessed(engineId, selector, unresolved()); + } + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + discoveryListener.selectorProcessed(engineId, selector, failed(t)); + } + } + + private void enqueueAdditionalSelectors(Resolution resolution) { + remainingSelectors.addAll(resolution.getSelectors()); + resolution.getMatches().stream().filter(Match::isExact).forEach(match -> { + Set childSelectors = match.expand(); + if (!childSelectors.isEmpty()) { + remainingSelectors.addAll(childSelectors); + DefaultContext context = new DefaultContext(match.getTestDescriptor()); + childSelectors.forEach(selector -> contextBySelector.put(selector, context)); + } + }); + } + + private Optional resolve(DiscoverySelector selector) { + if (resolvedSelectors.containsKey(selector)) { + return Optional.of(resolvedSelectors.get(selector)); + } + if (selector instanceof UniqueIdSelector) { + return resolveUniqueId((UniqueIdSelector) selector); + } + return resolve(selector, resolver -> { + Context context = getContext(selector); + if (selector instanceof ClasspathResourceSelector) { + return resolver.resolve((ClasspathResourceSelector) selector, context); + } + if (selector instanceof ClasspathRootSelector) { + return resolver.resolve((ClasspathRootSelector) selector, context); + } + if (selector instanceof ClassSelector) { + return resolver.resolve((ClassSelector) selector, context); + } + if (selector instanceof IterationSelector) { + return resolver.resolve((IterationSelector) selector, context); + } + if (selector instanceof NestedClassSelector) { + return resolver.resolve((NestedClassSelector) selector, context); + } + if (selector instanceof DirectorySelector) { + return resolver.resolve((DirectorySelector) selector, context); + } + if (selector instanceof FileSelector) { + return resolver.resolve((FileSelector) selector, context); + } + if (selector instanceof MethodSelector) { + return resolver.resolve((MethodSelector) selector, context); + } + if (selector instanceof NestedMethodSelector) { + return resolver.resolve((NestedMethodSelector) selector, context); + } + if (selector instanceof ModuleSelector) { + return resolver.resolve((ModuleSelector) selector, context); + } + if (selector instanceof PackageSelector) { + return resolver.resolve((PackageSelector) selector, context); + } + if (selector instanceof UriSelector) { + return resolver.resolve((UriSelector) selector, context); + } + return resolver.resolve(selector, context); + }); + } + + private Optional resolveUniqueId(UniqueIdSelector selector) { + UniqueId uniqueId = selector.getUniqueId(); + if (resolvedUniqueIds.containsKey(uniqueId)) { + return Optional.of(Resolution.match(resolvedUniqueIds.get(uniqueId))); + } + if (!uniqueId.hasPrefix(engineDescriptor.getUniqueId())) { + return Optional.empty(); + } + return resolve(selector, resolver -> resolver.resolve(selector, getContext(selector))); + } + + private Context getContext(DiscoverySelector selector) { + return contextBySelector.getOrDefault(selector, defaultContext); + } + + private Optional resolve(DiscoverySelector selector, + Function resolutionFunction) { + // @formatter:off + return resolvers.stream() + .map(resolutionFunction) + .filter(Resolution::isResolved) + .findFirst() + .map(resolution -> { + contextBySelector.remove(selector); + resolvedSelectors.put(selector, resolution); + resolution.getMatches() + .forEach(match -> resolvedUniqueIds.put(match.getTestDescriptor().getUniqueId(), match)); + return resolution; + }); + // @formatter:on + } + + private class DefaultContext implements Context { + + private final TestDescriptor parent; + + DefaultContext(TestDescriptor parent) { + this.parent = parent; + } + + @Override + public Optional addToParent(Function> creator) { + if (parent != null) { + return createAndAdd(parent, creator); + } + return createAndAdd(engineDescriptor, creator); + } + + @Override + public Optional addToParent(Supplier parentSelectorSupplier, + Function> creator) { + if (parent != null) { + return createAndAdd(parent, creator); + } + return resolve(parentSelectorSupplier.get()).flatMap(parent -> createAndAdd(parent, creator)); + } + + @Override + public Optional resolve(DiscoverySelector selector) { + // @formatter:off + return EngineDiscoveryRequestResolution.this.resolve(selector) + .map(Resolution::getMatches) + .flatMap(matches -> { + if (matches.size() > 1) { + String stringRepresentation = matches.stream() + .map(Match::getTestDescriptor) + .map(Objects::toString) + .collect(joining(", ")); + throw new JUnitException( + "Selector " + selector + " did not yield unique test descriptor: " + stringRepresentation); + } + if (matches.size() == 1) { + return Optional.of(getOnlyElement(matches).getTestDescriptor()); + } + return Optional.empty(); + }); + // @formatter:on + } + + @SuppressWarnings("unchecked") + private Optional createAndAdd(TestDescriptor parent, + Function> creator) { + Optional child = creator.apply(parent); + if (child.isPresent()) { + UniqueId uniqueId = child.get().getUniqueId(); + if (resolvedUniqueIds.containsKey(uniqueId)) { + return Optional.of((T) resolvedUniqueIds.get(uniqueId).getTestDescriptor()); + } + parent.addChild(child.get()); + } + return child; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java new file mode 100644 index 00000000..344f917c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java @@ -0,0 +1,293 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.discovery; + +import static java.util.stream.Collectors.toCollection; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageNameFilter; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.support.discovery.SelectorResolver.Match; +import org.junit.platform.engine.support.discovery.SelectorResolver.Resolution; + +/** + * Configurable test discovery implementation based on {@link SelectorResolver} + * and {@link TestDescriptor.Visitor} that can be reused by different + * {@link org.junit.platform.engine.TestEngine TestEngines}. + * + *

This resolver takes care of notifying registered + * {@link org.junit.platform.engine.EngineDiscoveryListener + * EngineDiscoveryListeners} about the results of processed + * {@link org.junit.platform.engine.DiscoverySelector DiscoverySelectors}. + * + * @param the type of the engine's descriptor + * @since 1.5 + * @see #builder() + * @see #resolve(EngineDiscoveryRequest, TestDescriptor) + */ +@API(status = EXPERIMENTAL, since = "1.5") +public class EngineDiscoveryRequestResolver { + + private final List, SelectorResolver>> resolverCreators; + private final List, TestDescriptor.Visitor>> visitorCreators; + + private EngineDiscoveryRequestResolver(List, SelectorResolver>> resolverCreators, + List, TestDescriptor.Visitor>> visitorCreators) { + this.resolverCreators = new ArrayList<>(resolverCreators); + this.visitorCreators = new ArrayList<>(visitorCreators); + } + + /** + * Resolve the supplied {@link EngineDiscoveryRequest} and collect the + * results into the supplied {@link TestDescriptor}. + * + *

The algorithm works as follows: + * + *

    + *
  1. Enqueue all selectors in the supplied + * {@linkplain EngineDiscoveryRequest request} to be resolved. + *
  2. + *
  3. + * While there are selectors to be resolved, get the next one. + * Otherwise, the resolution is finished. + *
      + *
    1. + * Iterate over all registered {@linkplain SelectorResolver + * resolvers} in the order they were registered in and find the + * first one that returns a {@linkplain Resolution resolution} + * other than {@link Resolution#unresolved() unresolved()}. + *
    2. + *
    3. + * If such a {@linkplain Resolution resolution} exists, enqueue + * its {@linkplain Resolution#getSelectors() selectors}. + *
    4. + *
    5. + * For each exact {@linkplain Match match} in the {@linkplain + * Resolution resolution}, {@linkplain Match#expand() expand} + * its children and enqueue them as well. + *
    6. + *
    + *
  4. + *
  5. + * Iterate over all registered {@linkplain TestDescriptor.Visitor + * visitors} and let the engine test descriptor {@linkplain + * TestDescriptor#accept(TestDescriptor.Visitor) accept} them. + *
  6. + *
+ * + * @param request the request to be resolved; never {@code null} + * @param engineDescriptor the engine's {@code TestDescriptor} to be used + * for adding direct children + * @see SelectorResolver + * @see TestDescriptor.Visitor + */ + public void resolve(EngineDiscoveryRequest request, T engineDescriptor) { + Preconditions.notNull(request, "request must not be null"); + Preconditions.notNull(engineDescriptor, "engineDescriptor must not be null"); + InitializationContext initializationContext = new DefaultInitializationContext<>(request, engineDescriptor); + List resolvers = instantiate(resolverCreators, initializationContext); + List visitors = instantiate(visitorCreators, initializationContext); + new EngineDiscoveryRequestResolution(request, engineDescriptor, resolvers, visitors).run(); + } + + private List instantiate(List, R>> creators, + InitializationContext context) { + return creators.stream().map(creator -> creator.apply(context)).collect(toCollection(ArrayList::new)); + } + + /** + * Create a new {@link Builder} for creating a {@link EngineDiscoveryRequestResolver}. + * + * @param the type of the engine's descriptor + * @return a new builder; never {@code null} + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Builder for {@link EngineDiscoveryRequestResolver}. + * + * @param the type of the engine's descriptor + * @since 1.5 + */ + @API(status = EXPERIMENTAL, since = "1.5") + public static class Builder { + + private final List, SelectorResolver>> resolverCreators = new ArrayList<>(); + private final List, TestDescriptor.Visitor>> visitorCreators = new ArrayList<>(); + + private Builder() { + } + + /** + * Add a predefined resolver that resolves {@link ClasspathRootSelector + * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and + * {@link PackageSelector PackageSelectors} into {@link ClassSelector + * ClassSelectors} by scanning for classes that satisfy the supplied + * predicate in the respective class containers to this builder. + * + * @param classFilter predicate the resolved classes must satisfy; never + * {@code null} + * @return this builder for method chaining + */ + public Builder addClassContainerSelectorResolver(Predicate> classFilter) { + Preconditions.notNull(classFilter, "classFilter must not be null"); + return addSelectorResolver( + context -> new ClassContainerSelectorResolver(classFilter, context.getClassNameFilter())); + } + + /** + * Add a context insensitive {@link SelectorResolver} to this builder. + * + * @param resolver the resolver to add; never {@code null} + * @return this builder for method chaining + */ + public Builder addSelectorResolver(SelectorResolver resolver) { + Preconditions.notNull(resolver, "resolver must not be null"); + return addSelectorResolver(context -> resolver); + } + + /** + * Add a context sensitive {@link SelectorResolver} to this builder. + * + * @param resolverCreator the function that will be called to create the + * {@link SelectorResolver} to be added. + * @return this builder for method chaining + * @see InitializationContext + */ + public Builder addSelectorResolver(Function, SelectorResolver> resolverCreator) { + resolverCreators.add(resolverCreator); + return this; + } + + /** + * Add a context sensitive {@link TestDescriptor.Visitor} to this + * builder. + * + * @param visitorCreator the function that will be called to create the + * {@link TestDescriptor.Visitor} to be added. + * @return this builder for method chaining + * @see InitializationContext + */ + public Builder addTestDescriptorVisitor( + Function, TestDescriptor.Visitor> visitorCreator) { + visitorCreators.add(visitorCreator); + return this; + } + + /** + * Build the {@link EngineDiscoveryRequestResolver} that has been + * configured via this builder. + */ + public EngineDiscoveryRequestResolver build() { + return new EngineDiscoveryRequestResolver<>(resolverCreators, visitorCreators); + } + + } + + /** + * The initialization context for creating {@linkplain SelectorResolver + * resolvers} and {@linkplain TestDescriptor.Visitor visitors} that depend + * on the {@link EngineDiscoveryRequest} to be resolved or the engine + * descriptor that will be used to collect the results. + * + * @since 1.5 + * @see Builder#addSelectorResolver(Function) + * @see Builder#addTestDescriptorVisitor(Function) + */ + @API(status = EXPERIMENTAL, since = "1.5") + public interface InitializationContext { + + /** + * Get the {@link EngineDiscoveryRequest} that is about to be resolved. + * + * @return the {@link EngineDiscoveryRequest}; never {@code null} + */ + EngineDiscoveryRequest getDiscoveryRequest(); + + /** + * Get the engine's {@link TestDescriptor} that will be used to collect + * the results. + * + * @return engine's {@link TestDescriptor}; never {@code null} + */ + T getEngineDescriptor(); + + /** + * Get the class name filter built from the {@link ClassNameFilter + * ClassNameFilters} and {@link PackageNameFilter PackageNameFilters} + * in the {@link EngineDiscoveryRequest} that is about to be resolved. + * + * @return the predicate for filtering the resolved class names; never + * {@code null} + */ + Predicate getClassNameFilter(); + + } + + private static class DefaultInitializationContext implements InitializationContext { + + private final EngineDiscoveryRequest request; + private final T engineDescriptor; + private final Predicate classNameFilter; + + DefaultInitializationContext(EngineDiscoveryRequest request, T engineDescriptor) { + this.request = request; + this.engineDescriptor = engineDescriptor; + this.classNameFilter = buildClassNamePredicate(request); + } + + /** + * Build a {@link Predicate} for fully qualified class names to be used for + * classpath scanning from an {@link EngineDiscoveryRequest}. + * + * @param request the request to build a predicate from + */ + private Predicate buildClassNamePredicate(EngineDiscoveryRequest request) { + List> filters = new ArrayList<>(); + filters.addAll(request.getFiltersByType(ClassNameFilter.class)); + filters.addAll(request.getFiltersByType(PackageNameFilter.class)); + return Filter.composeFilters(filters).toPredicate(); + } + + @Override + public EngineDiscoveryRequest getDiscoveryRequest() { + return request; + } + + @Override + public T getEngineDescriptor() { + return engineDescriptor; + } + + @Override + public Predicate getClassNameFilter() { + return classNameFilter; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java new file mode 100644 index 00000000..93bc2b1d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java @@ -0,0 +1,676 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.discovery; + +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.NestedClassSelector; +import org.junit.platform.engine.discovery.NestedMethodSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.discovery.UriSelector; + +/** + * A resolver that supports resolving one or multiple types of + * {@link DiscoverySelector DiscoverySelectors}. + * + *

An implementation of a {@code resolve()} method is typically comprised + * of the following steps: + * + *

    + *
  1. + * Check whether the selector is applicable for the current + * {@link org.junit.platform.engine.TestEngine} and the current + * {@link org.junit.platform.engine.EngineDiscoveryRequest} (e.g. + * for a test class: is it relevant for the current engine and does + * it pass all filters in the request?). + *
  2. + *
  3. + * If so, use the supplied {@link Context Context}, to add one or + * multiple {@link TestDescriptor TestDescriptors} to the designated + * parent (see {@link Context Context} for details) and return a + * {@linkplain Resolution#match(Match) match} or multiple {@linkplain + * Resolution#matches(Set) matches}. Alternatively, convert the supplied + * selector into one or multiple other + * {@linkplain Resolution#selectors(Set) selectors} (e.g. a {@link + * PackageSelector} into a set of {@link ClassSelector ClassSelectors}). + * Otherwise, return {@link Resolution#unresolved() unresolved()}. + *
  4. + *
+ * + * @since 1.5 + */ +@API(status = EXPERIMENTAL, since = "1.5") +public interface SelectorResolver { + + /** + * Resolve the supplied {@link ClasspathResourceSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(ClasspathResourceSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link ClasspathRootSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(ClasspathRootSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link ClassSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(ClassSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link NestedClassSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(NestedClassSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link DirectorySelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(DirectorySelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link FileSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(FileSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link MethodSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(MethodSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link NestedMethodSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(NestedMethodSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link ModuleSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(ModuleSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link PackageSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(PackageSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link UniqueIdSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(UniqueIdSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link UriSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + default Resolution resolve(UriSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link IterationSelector} using the supplied + * {@link Context Context}. + * + *

The default implementation delegates to {@link + * #resolve(DiscoverySelector, Context)}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see #resolve(DiscoverySelector, Context) + */ + @API(status = EXPERIMENTAL, since = "1.9") + default Resolution resolve(IterationSelector selector, Context context) { + return resolve((DiscoverySelector) selector, context); + } + + /** + * Resolve the supplied {@link DiscoverySelector} using the supplied + * {@link Context Context}. + * + *

This method is only called if none of the overloaded variants match. + * + *

The default implementation returns {@link Resolution#unresolved() + * unresolved()}. + * + * @param selector the selector to be resolved; never {@code null} + * @param context the context to be used for resolving the selector; never + * {@code null} + * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() + * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link + * Resolution#matches(Set) matches()}; never {@code null} + * @see Context + */ + default Resolution resolve(DiscoverySelector selector, Context context) { + return Resolution.unresolved(); + } + + /** + * The context for resolving a {@link DiscoverySelector} and adding it to + * the test tree. + * + *

The context is used to add resolved {@link TestDescriptor + * TestDescriptors} to the test tree if and only if the parent + * {@code TestDescriptor} could be found. Alternatively, a resolver may + * use the context to {@linkplain #resolve(DiscoverySelector) resolve} a + * certain {@code DiscoverySelector} into a {@code TestDescriptor} (e.g. for + * adding a filter and returning a {@linkplain Match#partial(TestDescriptor) + * partial match}). + * + * @since 1.5 + * @see SelectorResolver + */ + @API(status = EXPERIMENTAL, since = "1.5") + interface Context { + + /** + * Resolve the supplied {@link TestDescriptor}, if possible. + * + *

Calling this method has the same effect as returning a {@linkplain + * Match#partial(TestDescriptor) partial match} from a {@link + * SelectorResolver}: the children of the resulting {@link + * TestDescriptor} will only be resolved if a subsequent resolution + * finds an exact match that contains a {@code TestDescriptor} with the + * same {@linkplain TestDescriptor#getUniqueId() unique ID}. + * + * @param selector the selector to resolve + * @return the resolved {@code TestDescriptor}; never {@code null} but + * potentially empty + */ + Optional resolve(DiscoverySelector selector); + + /** + * Add a {@link TestDescriptor} to an unspecified parent, usually the + * engine descriptor, by applying the supplied {@code Function} to the + * new parent. + * + *

The parent will be the engine descriptor unless another parent has + * already been determined, i.e. if the selector that is being resolved + * is the result of {@linkplain Match#expand() expanding} a {@link + * Match}. + * + *

If the result of applying the {@code Function} is {@linkplain + * Optional#isPresent() present}, it will be added as a child of the + * parent {@code TestDescriptor} unless a descriptor with the same + * {@linkplain TestDescriptor#getUniqueId() unique ID} was added + * earlier. + * + * @param creator {@code Function} that will be called with the new + * parent to determine the new {@code TestDescriptor} to be added; must + * not return {@code null} + * @param the type of the new {@code TestDescriptor} + * @return the new {@code TestDescriptor} or the previously existing one + * with the same unique ID; never {@code null} but potentially empty + * @throws ClassCastException if the previously existing {@code + * TestDescriptor} is not an instance of {@code T} + */ + Optional addToParent(Function> creator); + + /** + * Add a {@link TestDescriptor} to a parent, specified by the {@link + * DiscoverySelector} returned by the supplied {@code Supplier}, by + * applying the supplied {@code Function} to the new parent. + * + *

Unless another parent has already been determined, i.e. if the + * selector that is being resolved is the result of {@linkplain + * Match#expand() expanding} a {@link Match}, the {@link + * DiscoverySelector} returned by the supplied {@code Supplier} will + * be used to determine the parent. If no parent is found, the supplied + * {@code Function} will not be called. If there are multiple potential + * parents, an exception will be thrown. Otherwise, the resolved + * {@code TestDescriptor} will be used as the parent and passed to the + * supplied {@code Function}. + * + *

If the result of applying the {@code Function} is {@linkplain + * Optional#isPresent() present}, it will be added as a child of the + * parent {@code TestDescriptor} unless a descriptor with the same + * {@linkplain TestDescriptor#getUniqueId() unique ID} was added + * earlier. + * + * @param creator {@code Function} that will be called with the new + * parent to determine the new {@code TestDescriptor} to be added; must + * not return {@code null} + * @param the type of the new {@code TestDescriptor} + * @return the new {@code TestDescriptor} or the previously existing one + * with the same unique ID; never {@code null} but potentially empty + * @throws ClassCastException if the previously existing {@code + * TestDescriptor} is not an instance of {@code T} + */ + Optional addToParent(Supplier parentSelectorSupplier, + Function> creator); + + } + + /** + * The result of an attempt to resolve a {@link DiscoverySelector}. + * + *

A resolution is either {@linkplain #unresolved unresolved}, contains a + * {@linkplain #match match} or multiple {@linkplain #matches}, or a set of + * {@linkplain #selectors selectors}. + * + * @since 1.5 + * @see SelectorResolver + */ + @API(status = EXPERIMENTAL, since = "1.5") + class Resolution { + + private static final Resolution UNRESOLVED = new Resolution(emptySet(), emptySet()); + + private final Set matches; + private final Set selectors; + + /** + * Factory for creating unresolved resolutions. + * + * @return an unresolved resolution; never {@code null} + */ + public static Resolution unresolved() { + return UNRESOLVED; + } + + /** + * Factory for creating a resolution that contains the supplied + * {@link Match Match}. + * + * @param match the resolved {@code Match}; never {@code null} + * @return an resolution that contains the supplied {@code Match}; never + * {@code null} + */ + public static Resolution match(Match match) { + return new Resolution(singleton(match), emptySet()); + } + + /** + * Factory for creating a resolution that contains the supplied + * {@link Match Matches}. + * + * @param matches the resolved {@code Matches}; never {@code null} or + * empty + * @return an resolution that contains the supplied {@code Matches}; + * never {@code null} + */ + public static Resolution matches(Set matches) { + Preconditions.containsNoNullElements(matches, "matches must not contain null elements"); + Preconditions.notEmpty(matches, "matches must not be empty"); + return new Resolution(matches, emptySet()); + } + + /** + * Factory for creating a resolution that contains the supplied + * {@link DiscoverySelector DiscoverySelectors}. + * + * @param selectors the resolved {@code DiscoverySelectors}; never + * {@code null} or empty + * @return an resolution that contains the supplied + * {@code DiscoverySelectors}; never {@code null} + */ + public static Resolution selectors(Set selectors) { + Preconditions.containsNoNullElements(selectors, "selectors must not contain null elements"); + Preconditions.notEmpty(selectors, "selectors must not be empty"); + return new Resolution(emptySet(), selectors); + } + + private Resolution(Set matches, Set selectors) { + this.matches = matches; + this.selectors = selectors; + } + + /** + * Whether this resolution contains matches or selectors. + * + * @return {@code true} if this resolution contains matches or selectors + */ + public boolean isResolved() { + return this != UNRESOLVED; + } + + /** + * Returns the matches contained by this resolution. + * + * @return the set of matches; never {@code null} but potentially empty + */ + public Set getMatches() { + return matches; + } + + /** + * Returns the selectors contained by this resolution. + * + * @return the set of selectors; never {@code null} but potentially empty + */ + public Set getSelectors() { + return selectors; + } + + } + + /** + * An exact or partial match for resolving a {@link DiscoverySelector} into + * a {@link TestDescriptor}. + * + *

A match is exact if the {@link DiscoverySelector} directly + * represents the resulting {@link TestDescriptor}, e.g. if a + * {@link ClassSelector} was resolved into the {@link TestDescriptor} that + * represents the test class. It is partial if the matching + * {@link TestDescriptor} does not directly correspond to the resolved + * {@link DiscoverySelector}, e.g. when resolving a {@link UniqueIdSelector} + * that represents a dynamic child of the resolved {@link TestDescriptor}. + * + *

In addition to the {@link TestDescriptor}, a match may contain a + * {@code Supplier} of {@link DiscoverySelector DiscoverySelectors} that may + * be used to discover the children of the {@link TestDescriptor}. The + * algorithm implemented by {@link EngineDiscoveryRequestResolver} + * {@linkplain #expand() expands} all exact matches immediately, i.e. it + * resolves all of their children. Partial matches will only be expanded in + * case a subsequent resolution finds an exact match that contains a {@link + * TestDescriptor} with the same {@linkplain TestDescriptor#getUniqueId() + * unique ID}. + * + * @since 1.5 + * @see SelectorResolver + * @see Resolution#match(Match) + * @see Resolution#matches(Set) + */ + @API(status = EXPERIMENTAL, since = "1.5") + class Match { + + private final TestDescriptor testDescriptor; + private final Supplier> childSelectorsSupplier; + private final Type type; + + /** + * Factory for creating an exact match without any children. + * + * @param testDescriptor the resolved {@code TestDescriptor}; never + * {@code null} + * @return a match that contains the supplied {@code TestDescriptor}; + * never {@code null} + */ + public static Match exact(TestDescriptor testDescriptor) { + return exact(testDescriptor, Collections::emptySet); + } + + /** + * Factory for creating an exact match with potential children. + * + * @param testDescriptor the resolved {@code TestDescriptor}; never + * {@code null} + * @param childSelectorsSupplier a {@code Supplier} of children + * selectors that will be resolved when this match is expanded; never + * {@code null} + * @return a match that contains the supplied {@code TestDescriptor}; + * never {@code null} + */ + public static Match exact(TestDescriptor testDescriptor, + Supplier> childSelectorsSupplier) { + return new Match(testDescriptor, childSelectorsSupplier, Type.EXACT); + } + + /** + * Factory for creating a partial match without any children. + * + * @param testDescriptor the resolved {@code TestDescriptor}; never + * {@code null} + * @return a match that contains the supplied {@code TestDescriptor}; + * never {@code null} + */ + public static Match partial(TestDescriptor testDescriptor) { + return partial(testDescriptor, Collections::emptySet); + } + + /** + * Factory for creating a partial match with potential children. + * + * @param testDescriptor the resolved {@code TestDescriptor}; never + * {@code null} + * @param childSelectorsSupplier a {@code Supplier} of children + * selectors that will be resolved when this match is expanded; never + * {@code null} + * @return a match that contains the supplied {@code TestDescriptor}; + * never {@code null} + */ + public static Match partial(TestDescriptor testDescriptor, + Supplier> childSelectorsSupplier) { + return new Match(testDescriptor, childSelectorsSupplier, Type.PARTIAL); + } + + private Match(TestDescriptor testDescriptor, Supplier> childSelectorsSupplier, + Type type) { + this.testDescriptor = Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); + this.childSelectorsSupplier = Preconditions.notNull(childSelectorsSupplier, + "childSelectorsSupplier must not be null"); + this.type = type; + } + + /** + * Whether this match is exact. + * + * @return {@code true} if this match is exact; {@code false} if it's + * partial + */ + public boolean isExact() { + return type == Type.EXACT; + } + + /** + * Get the contained {@link TestDescriptor}. + * + * @return the contained {@code TestDescriptor}; never {@code null} + */ + public TestDescriptor getTestDescriptor() { + return testDescriptor; + } + + /** + * Expand this match in order to resolve the children of the contained + * {@link TestDescriptor}. + * + * @return the set of {@code DiscoverySelectors} that represent the + * children of the contained {@code TestDescriptor}; never {@code null} + */ + public Set expand() { + return childSelectorsSupplier.get(); + } + + private enum Type { + EXACT, PARTIAL + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java new file mode 100644 index 00000000..1f0ea1ff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java @@ -0,0 +1,9 @@ +/** + * Configurable test discovery implementation that can be reused by different test engines. + * + * @see org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver#builder() + * @see org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver#resolve + * @see org.junit.platform.engine.support.discovery.SelectorResolver + */ + +package org.junit.platform.engine.support.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java new file mode 100644 index 00000000..9169ea77 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.filter; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.junit.platform.engine.Filter.composeFilters; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.PackageNameFilter; + +/** + * Support utility methods for classpath scanning. + * + * @since 1.0 + * @deprecated Please use {@link org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver} instead. + */ +@Deprecated +@API(status = DEPRECATED, since = "1.5") +public final class ClasspathScanningSupport { + + private ClasspathScanningSupport() { + /* no-op */ + } + + /** + * Build a {@link Predicate} for fully qualified class names to be used for + * classpath scanning from an {@link EngineDiscoveryRequest}. + * + * @param request the request to build a predicate from + * @deprecated Please use {@link org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver} instead. + */ + @Deprecated + public static Predicate buildClassNamePredicate(EngineDiscoveryRequest request) { + List> filters = new ArrayList<>(); + filters.addAll(request.getFiltersByType(ClassNameFilter.class)); + filters.addAll(request.getFiltersByType(PackageNameFilter.class)); + return composeFilters(filters).toPredicate(); + } + + /** + * Build a {@link ClassFilter} by combining the name predicate built by + * {@link #buildClassNamePredicate(EngineDiscoveryRequest)} and the passed-in + * class predicate. + * + * @param request the request to build a name predicate from + * @param classPredicate the class predicate + * @deprecated Please use {@link org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver} instead. + */ + @Deprecated + public static ClassFilter buildClassFilter(EngineDiscoveryRequest request, Predicate> classPredicate) { + return ClassFilter.of(buildClassNamePredicate(request), classPredicate); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java new file mode 100644 index 00000000..c074ecf0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.junit.platform.engine.Filter}-related support classes intended to be + * used by test engine implementations. + */ + +package org.junit.platform.engine.support.filter; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java new file mode 100644 index 00000000..41b08d5f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.locks.Lock; + +/** + * @since 1.3 + */ +class CompositeLock implements ResourceLock { + + private final List locks; + + CompositeLock(List locks) { + this.locks = locks; + } + + // for tests only + List getLocks() { + return locks; + } + + @Override + public ResourceLock acquire() throws InterruptedException { + ForkJoinPool.managedBlock(new CompositeLockManagedBlocker()); + return this; + } + + private void acquireAllLocks() throws InterruptedException { + List acquiredLocks = new ArrayList<>(locks.size()); + try { + for (Lock lock : locks) { + lock.lockInterruptibly(); + acquiredLocks.add(lock); + } + } + catch (InterruptedException e) { + release(acquiredLocks); + throw e; + } + } + + @Override + public void release() { + release(locks); + } + + private void release(List acquiredLocks) { + for (int i = acquiredLocks.size() - 1; i >= 0; i--) { + acquiredLocks.get(i).unlock(); + } + } + + private class CompositeLockManagedBlocker implements ForkJoinPool.ManagedBlocker { + + private boolean acquired; + + @Override + public boolean block() throws InterruptedException { + acquireAllLocks(); + acquired = true; + return true; + } + + @Override + public boolean isReleasable() { + return acquired; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java new file mode 100644 index 00000000..f00735a7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import java.util.concurrent.ForkJoinPool; +import java.util.function.Predicate; + +/** + * @since 1.3 + */ +class DefaultParallelExecutionConfiguration implements ParallelExecutionConfiguration { + + private final int parallelism; + private final int minimumRunnable; + private final int maxPoolSize; + private final int corePoolSize; + private final int keepAliveSeconds; + private final Predicate saturate; + + DefaultParallelExecutionConfiguration(int parallelism, int minimumRunnable, int maxPoolSize, int corePoolSize, + int keepAliveSeconds, Predicate saturate) { + this.parallelism = parallelism; + this.minimumRunnable = minimumRunnable; + this.maxPoolSize = maxPoolSize; + this.corePoolSize = corePoolSize; + this.keepAliveSeconds = keepAliveSeconds; + this.saturate = saturate; + } + + @Override + public int getParallelism() { + return parallelism; + } + + @Override + public int getMinimumRunnable() { + return minimumRunnable; + } + + @Override + public int getMaxPoolSize() { + return maxPoolSize; + } + + @Override + public int getCorePoolSize() { + return corePoolSize; + } + + @Override + public int getKeepAliveSeconds() { + return keepAliveSeconds; + } + + @Override + public Predicate getSaturatePredicate() { + return saturate; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java new file mode 100644 index 00000000..52f03cb6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -0,0 +1,177 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.math.BigDecimal; +import java.util.Locale; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * Default implementations of configuration strategies for parallel test + * execution. + * + * @since 1.3 + */ +@API(status = EXPERIMENTAL, since = "1.3") +public enum DefaultParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy { + + /** + * Uses the mandatory {@value CONFIG_FIXED_PARALLELISM_PROPERTY_NAME} configuration + * parameter as the desired parallelism. + */ + FIXED { + @Override + public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { + int parallelism = configurationParameters.get(CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, + Integer::valueOf).orElseThrow( + () -> new JUnitException(String.format("Configuration parameter '%s' must be set", + CONFIG_FIXED_PARALLELISM_PROPERTY_NAME))); + + int maxPoolSize = configurationParameters.get(CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, + Integer::valueOf).orElse(parallelism + 256); + + boolean saturate = configurationParameters.get(CONFIG_FIXED_SATURATE_PROPERTY_NAME, + Boolean::valueOf).orElse(true); + + return new DefaultParallelExecutionConfiguration(parallelism, parallelism, maxPoolSize, parallelism, + KEEP_ALIVE_SECONDS, __ -> saturate); + } + }, + + /** + * Computes the desired parallelism based on the number of available + * processors/cores multiplied by the {@value CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} + * configuration parameter. + */ + DYNAMIC { + @Override + public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { + BigDecimal factor = configurationParameters.get(CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME, + BigDecimal::new).orElse(BigDecimal.ONE); + + Preconditions.condition(factor.compareTo(BigDecimal.ZERO) > 0, + () -> String.format("Factor '%s' specified via configuration parameter '%s' must be greater than 0", + factor, CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME)); + + int parallelism = Math.max(1, + factor.multiply(BigDecimal.valueOf(Runtime.getRuntime().availableProcessors())).intValue()); + + return new DefaultParallelExecutionConfiguration(parallelism, parallelism, 256 + parallelism, parallelism, + KEEP_ALIVE_SECONDS, null); + } + }, + + /** + * Allows the specification of a custom {@link ParallelExecutionConfigurationStrategy} + * implementation via the mandatory {@value CONFIG_CUSTOM_CLASS_PROPERTY_NAME} + * configuration parameter to determine the desired configuration. + */ + CUSTOM { + @Override + public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { + String className = configurationParameters.get(CONFIG_CUSTOM_CLASS_PROPERTY_NAME).orElseThrow( + () -> new JUnitException(CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " must be set")); + return ReflectionUtils.tryToLoadClass(className) // + .andThenTry(strategyClass -> { + Preconditions.condition( + ParallelExecutionConfigurationStrategy.class.isAssignableFrom(strategyClass), + CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " does not implement " + + ParallelExecutionConfigurationStrategy.class); + return (ParallelExecutionConfigurationStrategy) ReflectionUtils.newInstance(strategyClass); + }) // + .andThenTry(strategy -> strategy.createConfiguration(configurationParameters)) // + .getOrThrow(cause -> new JUnitException( + "Could not create configuration for strategy class: " + className, cause)); + } + }; + + private static final int KEEP_ALIVE_SECONDS = 30; + + /** + * Property name used to determine the desired configuration strategy. + * + *

Value must be one of {@code dynamic}, {@code fixed}, or + * {@code custom}. + */ + public static final String CONFIG_STRATEGY_PROPERTY_NAME = "strategy"; + + /** + * Property name used to determine the desired parallelism for the + * {@link #FIXED} configuration strategy. + * + *

No default value; must be an integer. + * + * @see #FIXED + */ + public static final String CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = "fixed.parallelism"; + + /** + * Property name used to configure the maximum pool size of the underlying + * fork-join pool for the {@link #FIXED} configuration strategy. + * + *

Value must be an integer and greater than or equal to + * {@value #CONFIG_FIXED_PARALLELISM_PROPERTY_NAME}; defaults to + * {@code 256 + fixed.parallelism}. + * + * @since 1.10 + * @see #FIXED + */ + @API(status = EXPERIMENTAL, since = "1.10") + public static final String CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = "fixed.max-pool-size"; + + /** + * Property name used to disable saturation of the underlying fork-join pool + * for the {@link #FIXED} configuration strategy. + * + *

When set to {@code false} the underlying fork-join pool will reject + * additional tasks if all available workers are busy and the maximum + * pool-size would be exceeded. + *

Value must either {@code true} or {@code false}; defaults to {@code true}. + * + * @since 1.10 + * @see #FIXED + * @see #CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "1.10") + public static final String CONFIG_FIXED_SATURATE_PROPERTY_NAME = "fixed.saturate"; + + /** + * Property name of the factor used to determine the desired parallelism for the + * {@link #DYNAMIC} configuration strategy. + * + *

Value must be a decimal number; defaults to {@code 1}. + * + * @see #DYNAMIC + */ + public static final String CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = "dynamic.factor"; + + /** + * Property name used to specify the fully qualified class name of the + * {@link ParallelExecutionConfigurationStrategy} to be used by the + * {@link #CUSTOM} configuration strategy. + * + * @see #CUSTOM + */ + public static final String CONFIG_CUSTOM_CLASS_PROPERTY_NAME = "custom.class"; + + static ParallelExecutionConfigurationStrategy getStrategy(ConfigurationParameters configurationParameters) { + return valueOf( + configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME).orElse("dynamic").toUpperCase(Locale.ROOT)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java new file mode 100644 index 00000000..4a3748d9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import org.apiguardian.api.API; + +/** + * Marker interface for an execution context used by a concrete implementation + * of {@link HierarchicalTestEngine} and its collaborators. + * + * @since 1.0 + * @see HierarchicalTestEngine + */ +@API(status = MAINTAINED, since = "1.0") +public interface EngineExecutionContext { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java new file mode 100644 index 00000000..1674101c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Objects; +import java.util.concurrent.locks.ReadWriteLock; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; + +/** + * An exclusive resource identified by a key with a lock mode that is used to + * synchronize access to shared resources when executing nodes in parallel. + * + * @since 1.3 + * @see Node#getExecutionMode() + */ +@API(status = EXPERIMENTAL, since = "1.3") +public class ExclusiveResource { + + /** + * Key of the global resource lock that all direct children of the engine + * descriptor acquire in {@linkplain LockMode#READ read mode} by default: + * {@value} + * + *

If any node {@linkplain Node#getExclusiveResources() requires} an + * exclusive resource with the same key in + * {@linkplain LockMode#READ_WRITE read-write mode}, the lock will be + * coarsened to be acquired by the node's ancestor that is a direct child of + * the engine descriptor and all of the ancestor's descendants will be + * forced to run in the {@linkplain ExecutionMode#SAME_THREAD same thread}. + * + * @since 1.7 + */ + @API(status = EXPERIMENTAL, since = "1.7") + public static final String GLOBAL_KEY = "org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY"; + + static final ExclusiveResource GLOBAL_READ = new ExclusiveResource(GLOBAL_KEY, LockMode.READ); + static final ExclusiveResource GLOBAL_READ_WRITE = new ExclusiveResource(GLOBAL_KEY, LockMode.READ_WRITE); + + private final String key; + private final LockMode lockMode; + private int hash; + + /** + * Create a new {@code ExclusiveResource}. + * + * @param key the identifier of the resource; never {@code null} or blank + * @param lockMode the lock mode to use to synchronize access to the + * resource; never {@code null} + */ + public ExclusiveResource(String key, LockMode lockMode) { + this.key = Preconditions.notBlank(key, "key must not be blank"); + this.lockMode = Preconditions.notNull(lockMode, "lockMode must not be null"); + } + + /** + * Get the key of this resource. + */ + public String getKey() { + return key; + } + + /** + * Get the lock mode of this resource. + */ + public LockMode getLockMode() { + return lockMode; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExclusiveResource that = (ExclusiveResource) o; + return Objects.equals(key, that.key) && lockMode == that.lockMode; + } + + @Override + public int hashCode() { + int h = hash; + if (h == 0) { + h = hash = Objects.hash(key, lockMode); + } + return h; + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("key", key).append("lockMode", lockMode).toString(); + } + + /** + * {@code LockMode} translates to the respective {@link ReadWriteLock} + * locks. + * + * @implNote Enum order is important, since it can be used to sort locks, so + * the stronger mode has to be first. + */ + public enum LockMode { + + /** + * Require read and write access to the resource. + * + * @see ReadWriteLock#writeLock() + */ + READ_WRITE, + + /** + * Require only read access to the resource. + * + * @see ReadWriteLock#readLock() + */ + READ + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java new file mode 100644 index 00000000..84ef2561 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java @@ -0,0 +1,231 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.Constructor; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.Future; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * A {@link ForkJoinPool}-based + * {@linkplain HierarchicalTestExecutorService executor service} that executes + * {@linkplain TestTask test tasks} with the configured parallelism. + * + * @since 1.3 + * @see ForkJoinPool + * @see DefaultParallelExecutionConfigurationStrategy + */ +@API(status = EXPERIMENTAL, since = "1.3") +public class ForkJoinPoolHierarchicalTestExecutorService implements HierarchicalTestExecutorService { + + private final ForkJoinPool forkJoinPool; + private final int parallelism; + + /** + * Create a new {@code ForkJoinPoolHierarchicalTestExecutorService} based on + * the supplied {@link ConfigurationParameters}. + * + * @see DefaultParallelExecutionConfigurationStrategy + */ + public ForkJoinPoolHierarchicalTestExecutorService(ConfigurationParameters configurationParameters) { + this(createConfiguration(configurationParameters)); + } + + /** + * Create a new {@code ForkJoinPoolHierarchicalTestExecutorService} based on + * the supplied {@link ParallelExecutionConfiguration}. + * + * @since 1.7 + */ + @API(status = EXPERIMENTAL, since = "1.7") + public ForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration) { + forkJoinPool = createForkJoinPool(configuration); + parallelism = forkJoinPool.getParallelism(); + LoggerFactory.getLogger(getClass()).config(() -> "Using ForkJoinPool with parallelism of " + parallelism); + } + + private static ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.getStrategy( + configurationParameters); + return strategy.createConfiguration(configurationParameters); + } + + private ForkJoinPool createForkJoinPool(ParallelExecutionConfiguration configuration) { + ForkJoinWorkerThreadFactory threadFactory = new WorkerThreadFactory(); + // Try to use constructor available in Java >= 9 + Callable constructorInvocation = sinceJava9Constructor() // + .map(sinceJava9ConstructorInvocation(configuration, threadFactory)) + // Fallback for Java 8 + .orElse(sinceJava7ConstructorInvocation(configuration, threadFactory)); + return Try.call(constructorInvocation) // + .getOrThrow(cause -> new JUnitException("Failed to create ForkJoinPool", cause)); + } + + private static Optional> sinceJava9Constructor() { + return Try.call(() -> ForkJoinPool.class.getDeclaredConstructor(Integer.TYPE, ForkJoinWorkerThreadFactory.class, + UncaughtExceptionHandler.class, Boolean.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, Predicate.class, + Long.TYPE, TimeUnit.class)) // + .toOptional(); + } + + private static Function, Callable> sinceJava9ConstructorInvocation( + ParallelExecutionConfiguration configuration, ForkJoinWorkerThreadFactory threadFactory) { + return constructor -> () -> constructor.newInstance(configuration.getParallelism(), threadFactory, null, false, + configuration.getCorePoolSize(), configuration.getMaxPoolSize(), configuration.getMinimumRunnable(), + configuration.getSaturatePredicate(), configuration.getKeepAliveSeconds(), TimeUnit.SECONDS); + } + + private static Callable sinceJava7ConstructorInvocation(ParallelExecutionConfiguration configuration, + ForkJoinWorkerThreadFactory threadFactory) { + return () -> new ForkJoinPool(configuration.getParallelism(), threadFactory, null, false); + } + + @Override + public Future submit(TestTask testTask) { + ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); + if (!isAlreadyRunningInForkJoinPool()) { + // ensure we're running inside the ForkJoinPool so we + // can use ForkJoinTask API in invokeAll etc. + return forkJoinPool.submit(exclusiveTask); + } + // Limit the amount of queued work so we don't consume dynamic tests too eagerly + // by forking only if the current worker thread's queue length is below the + // desired parallelism. This optimistically assumes that the already queued tasks + // can be stolen by other workers and the new task requires about the same + // execution time as the already queued tasks. If the other workers are busy, + // the parallelism is already at its desired level. If all already queued tasks + // can be stolen by otherwise idle workers and the new task takes significantly + // longer, parallelism will drop. However, that only happens if the enclosing test + // task is the only one remaining which should rarely be the case. + if (testTask.getExecutionMode() == CONCURRENT && ForkJoinTask.getSurplusQueuedTaskCount() < parallelism) { + return exclusiveTask.fork(); + } + exclusiveTask.compute(); + return completedFuture(null); + } + + private boolean isAlreadyRunningInForkJoinPool() { + return ForkJoinTask.getPool() == forkJoinPool; + } + + @Override + public void invokeAll(List tasks) { + if (tasks.size() == 1) { + new ExclusiveTask(tasks.get(0)).compute(); + return; + } + Deque nonConcurrentTasks = new LinkedList<>(); + Deque concurrentTasksInReverseOrder = new LinkedList<>(); + forkConcurrentTasks(tasks, nonConcurrentTasks, concurrentTasksInReverseOrder); + executeNonConcurrentTasks(nonConcurrentTasks); + joinConcurrentTasksInReverseOrderToEnableWorkStealing(concurrentTasksInReverseOrder); + } + + private void forkConcurrentTasks(List tasks, Deque nonConcurrentTasks, + Deque concurrentTasksInReverseOrder) { + for (TestTask testTask : tasks) { + ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); + if (testTask.getExecutionMode() == CONCURRENT) { + exclusiveTask.fork(); + concurrentTasksInReverseOrder.addFirst(exclusiveTask); + } + else { + nonConcurrentTasks.add(exclusiveTask); + } + } + } + + private void executeNonConcurrentTasks(Deque nonConcurrentTasks) { + for (ExclusiveTask task : nonConcurrentTasks) { + task.compute(); + } + } + + private void joinConcurrentTasksInReverseOrderToEnableWorkStealing( + Deque concurrentTasksInReverseOrder) { + for (ExclusiveTask forkedTask : concurrentTasksInReverseOrder) { + forkedTask.join(); + } + } + + @Override + public void close() { + forkJoinPool.shutdownNow(); + } + + // this class cannot not be serialized because TestTask is not Serializable + @SuppressWarnings("serial") + static class ExclusiveTask extends RecursiveAction { + + private final TestTask testTask; + + ExclusiveTask(TestTask testTask) { + this.testTask = testTask; + } + + @SuppressWarnings("try") + @Override + public void compute() { + try (ResourceLock lock = testTask.getResourceLock().acquire()) { + testTask.execute(); + } + catch (InterruptedException e) { + ExceptionUtils.throwAsUncheckedException(e); + } + } + + } + + static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + + private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + return new WorkerThread(pool, contextClassLoader); + } + + } + + static class WorkerThread extends ForkJoinWorkerThread { + + WorkerThread(ForkJoinPool pool, ClassLoader contextClassLoader) { + super(pool); + setContextClassLoader(contextClassLoader); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java new file mode 100644 index 00000000..44ff65f3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestEngine; + +/** + * Abstract base class for all {@link TestEngine} implementations that wish + * to organize test suites hierarchically based on the {@link Node} abstraction. + * + * @param the type of {@code EngineExecutionContext} used by this engine + * @since 1.0 + * @see Node + */ +@API(status = MAINTAINED, since = "1.0") +public abstract class HierarchicalTestEngine implements TestEngine { + + public HierarchicalTestEngine() { + } + + /** + * Create an {@linkplain #createExecutorService(ExecutionRequest) executor + * service}; create an initial {@linkplain #createExecutionContext execution + * context}; execute the behavior of all {@linkplain Node nodes} in the + * hierarchy starting with the supplied {@code request}'s + * {@linkplain ExecutionRequest#getRootTestDescriptor() root} and notify + * its {@linkplain ExecutionRequest#getEngineExecutionListener() execution + * listener} of test execution events. + * + * @see Node + * @see #createExecutorService + * @see #createExecutionContext + */ + @Override + public final void execute(ExecutionRequest request) { + try (HierarchicalTestExecutorService executorService = createExecutorService(request)) { + C executionContext = createExecutionContext(request); + ThrowableCollector.Factory throwableCollectorFactory = createThrowableCollectorFactory(request); + new HierarchicalTestExecutor<>(request, executionContext, executorService, + throwableCollectorFactory).execute().get(); + } + catch (Exception exception) { + throw new JUnitException("Error executing tests for engine " + getId(), exception); + } + } + + /** + * Create the {@linkplain HierarchicalTestExecutorService executor service} + * to use for executing the supplied {@linkplain ExecutionRequest request}. + * + *

An engine may use the information in the supplied request + * such as the contained + * {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters} + * to decide what kind of service to return or how to configure it. + * + *

By default, this method returns an instance of + * {@link SameThreadHierarchicalTestExecutorService}. + * + * @param request the request about to be executed + * @since 1.3 + * @see ForkJoinPoolHierarchicalTestExecutorService + * @see SameThreadHierarchicalTestExecutorService + */ + @API(status = EXPERIMENTAL, since = "1.3") + protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) { + return new SameThreadHierarchicalTestExecutorService(); + } + + /** + * Create the {@linkplain ThrowableCollector.Factory factory} for creating + * {@link ThrowableCollector} instances used to handle exceptions that occur + * during execution of this engine's tests. + * + *

An engine may use the information in the supplied request + * such as the contained + * {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters} + * to decide what kind of factory to return or how to configure it. + * + *

By default, this method returns a factory that always creates instances of + * {@link OpenTest4JAwareThrowableCollector}. + * + * @param request the request about to be executed + * @since 1.3 + * @see OpenTest4JAwareThrowableCollector + * @see ThrowableCollector + */ + @API(status = EXPERIMENTAL, since = "1.3") + protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) { + return OpenTest4JAwareThrowableCollector::new; + } + + /** + * Create the initial execution context for executing the supplied + * {@linkplain ExecutionRequest request}. + * + * @param request the request about to be executed + * @return the initial context that will be passed to nodes in the hierarchy + */ + protected abstract C createExecutionContext(ExecutionRequest request); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java new file mode 100644 index 00000000..e1535dc1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import java.util.concurrent.Future; + +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; + +/** + * Implementation core of all {@link TestEngine TestEngines} that wish to + * use the {@link Node} abstraction as the driving principle for structuring + * and executing test suites. + * + *

A {@code HierarchicalTestExecutor} is instantiated by a concrete + * implementation of {@link HierarchicalTestEngine} and takes care of + * executing nodes in the hierarchy in the appropriate order as well as + * firing the necessary events in the {@link EngineExecutionListener}. + * + * @param the type of {@code EngineExecutionContext} used by the + * {@code HierarchicalTestEngine} + * @since 1.0 + */ +class HierarchicalTestExecutor { + + private final ExecutionRequest request; + private final C rootContext; + private final HierarchicalTestExecutorService executorService; + private final ThrowableCollector.Factory throwableCollectorFactory; + + HierarchicalTestExecutor(ExecutionRequest request, C rootContext, HierarchicalTestExecutorService executorService, + ThrowableCollector.Factory throwableCollectorFactory) { + this.request = request; + this.rootContext = rootContext; + this.executorService = executorService; + this.throwableCollectorFactory = throwableCollectorFactory; + } + + Future execute() { + TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor(); + EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); + NodeExecutionAdvisor executionAdvisor = new NodeTreeWalker().walk(rootTestDescriptor); + NodeTestTaskContext taskContext = new NodeTestTaskContext(executionListener, this.executorService, + this.throwableCollectorFactory, executionAdvisor); + NodeTestTask rootTestTask = new NodeTestTask<>(taskContext, rootTestDescriptor); + rootTestTask.setParentContext(this.rootContext); + return this.executorService.submit(rootTestTask); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java new file mode 100644 index 00000000..b4290a77 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.List; +import java.util.concurrent.Future; + +import org.apiguardian.api.API; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; + +/** + * A closeable service that executes {@linkplain TestTask test tasks}. + * + * @since 1.3 + * @see HierarchicalTestEngine#createExecutorService(ExecutionRequest) + * @see SameThreadHierarchicalTestExecutorService + * @see ForkJoinPoolHierarchicalTestExecutorService + */ +@API(status = EXPERIMENTAL, since = "1.3") +public interface HierarchicalTestExecutorService extends AutoCloseable { + + /** + * Submit the supplied {@linkplain TestTask test task} to be executed by + * this service. + * + *

Implementations may {@linkplain TestTask#execute() execute} the task + * asynchronously as long as its + * {@linkplain TestTask#getExecutionMode() execution mode} is + * {@linkplain ExecutionMode#CONCURRENT concurrent}. + * + *

Implementations must generally acquire and release the task's + * {@linkplain TestTask#getResourceLock() resource lock} before and after its + * execution unless they execute all tests in the same thread which + * upholds the same guarantees. + * + * @param testTask the test task to be executed + * @return a future that the caller can use to wait for the task's execution + * to be finished + * @see #invokeAll(List) + */ + Future submit(TestTask testTask); + + /** + * Invoke all supplied {@linkplain TestTask test tasks} and block until + * their execution has finished. + * + *

Implementations may {@linkplain TestTask#execute() execute} one or + * multiple of the supplied tasks in parallel as long as their + * {@linkplain TestTask#getExecutionMode() execution mode} is + * {@linkplain ExecutionMode#CONCURRENT concurrent}. + * + *

Implementations must generally acquire and release each task's + * {@linkplain TestTask#getResourceLock() resource lock} before and after its + * execution unless they execute all tests in the same thread which + * upholds the same guarantees. + * + * @param testTasks the test tasks to be executed + * @see #submit(TestTask) + */ + void invokeAll(List testTasks); + + /** + * Close this service and let it perform any required cleanup work. + * + *

For example, thread-based implementations should usually close their + * thread pools in this method. + */ + @Override + void close(); + + /** + * An executable task that represents a single test or container. + */ + interface TestTask { + + /** + * Get the {@linkplain ExecutionMode execution mode} of this task. + */ + ExecutionMode getExecutionMode(); + + /** + * Get the {@linkplain ResourceLock resource lock} of this task. + */ + ResourceLock getResourceLock(); + + /** + * Execute this task. + */ + void execute(); + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java new file mode 100644 index 00000000..1b5b37b0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; + +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * @since 1.3 + */ +class LockManager { + + private static final Comparator COMPARATOR // + = comparing(ExclusiveResource::getKey, globalKeyFirst().thenComparing(naturalOrder())) // + .thenComparing(ExclusiveResource::getLockMode); + + private static Comparator globalKeyFirst() { + return comparing(key -> !GLOBAL_KEY.equals(key)); + } + + private final Map locksByKey = new ConcurrentHashMap<>(); + + ResourceLock getLockForResources(Collection resources) { + if (resources.size() == 1) { + return getLockForResource(getOnlyElement(resources)); + } + List locks = getDistinctSortedLocks(resources); + return toResourceLock(locks); + } + + ResourceLock getLockForResource(ExclusiveResource resource) { + return new SingleLock(toLock(resource)); + } + + private List getDistinctSortedLocks(Collection resources) { + // @formatter:off + Map> resourcesByKey = resources.stream() + .sorted(COMPARATOR) + .distinct() + .collect(groupingBy(ExclusiveResource::getKey, LinkedHashMap::new, toList())); + + return resourcesByKey.values().stream() + .map(resourcesWithSameKey -> resourcesWithSameKey.get(0)) + .map(this::toLock) + .collect(toList()); + // @formatter:on + } + + private Lock toLock(ExclusiveResource resource) { + ReadWriteLock lock = this.locksByKey.computeIfAbsent(resource.getKey(), key -> new ReentrantReadWriteLock()); + return resource.getLockMode() == READ ? lock.readLock() : lock.writeLock(); + } + + private ResourceLock toResourceLock(List locks) { + switch (locks.size()) { + case 0: + return NopLock.INSTANCE; + case 1: + return new SingleLock(locks.get(0)); + default: + return new CompositeLock(locks); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java new file mode 100644 index 00000000..691d5140 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java @@ -0,0 +1,362 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static java.util.Collections.emptySet; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Future; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; + +/** + * A node within the execution hierarchy. + * + * @param the type of {@code EngineExecutionContext} used by the + * {@code HierarchicalTestEngine} + * @since 1.0 + * @see HierarchicalTestEngine + */ +@API(status = MAINTAINED, since = "1.0", consumers = "org.junit.platform.engine.support.hierarchical") +public interface Node { + + /** + * Prepare the supplied {@code context} prior to execution. + * + *

The default implementation returns the supplied {@code context} unmodified. + * + * @see #cleanUp(EngineExecutionContext) + */ + default C prepare(C context) throws Exception { + return context; + } + + /** + * Clean up the supplied {@code context} after execution. + * + *

The default implementation does nothing. + * + * @param context the context to execute in + * @since 1.1 + * @see #prepare(EngineExecutionContext) + */ + default void cleanUp(C context) throws Exception { + } + + /** + * Determine if the execution of the supplied {@code context} should be + * skipped. + * + *

The default implementation returns {@link SkipResult#doNotSkip()}. + */ + default SkipResult shouldBeSkipped(C context) throws Exception { + return SkipResult.doNotSkip(); + } + + /** + * Execute the before behavior of this node. + * + *

This method will be called once before {@linkplain #execute + * execution} of this node. + * + *

The default implementation returns the supplied {@code context} unmodified. + * + * @param context the context to execute in + * @return the new context to be used for children of this node; never + * {@code null} + * @see #execute(EngineExecutionContext, DynamicTestExecutor) + * @see #after(EngineExecutionContext) + */ + default C before(C context) throws Exception { + return context; + } + + /** + * Execute the behavior of this node. + * + *

Containers typically do not implement this method since the + * {@link HierarchicalTestEngine} handles execution of their children. + * + *

The supplied {@code dynamicTestExecutor} may be used to submit + * additional dynamic tests for immediate execution. + * + *

The default implementation returns the supplied {@code context} unmodified. + * + * @param context the context to execute in + * @param dynamicTestExecutor the executor to submit dynamic tests to + * @return the new context to be used for children of this node and for the + * after behavior of the parent of this node, if any + * @see #before + * @see #after + */ + default C execute(C context, DynamicTestExecutor dynamicTestExecutor) throws Exception { + return context; + } + + /** + * Execute the after behavior of this node. + * + *

This method will be called once after {@linkplain #execute + * execution} of this node. + * + *

The default implementation does nothing. + * + * @param context the context to execute in + * @see #before + * @see #execute + */ + default void after(C context) throws Exception { + } + + /** + * Wraps around the invocation of {@link #before(EngineExecutionContext)}, + * {@link #execute(EngineExecutionContext, DynamicTestExecutor)}, and + * {@link #after(EngineExecutionContext)}. + * + * @param context context the context to execute in + * @param invocation the wrapped invocation (must be invoked exactly once) + * @since 1.4 + */ + @API(status = EXPERIMENTAL, since = "1.4") + default void around(C context, Invocation invocation) throws Exception { + invocation.invoke(context); + } + + /** + * Callback invoked when the execution of this node has been skipped. + * + *

The default implementation does nothing. + * + * @param context the execution context + * @param testDescriptor the test descriptor that was skipped + * @param result the result of skipped execution + * @since 1.4 + */ + @API(status = EXPERIMENTAL, since = "1.4", consumers = "org.junit.platform.engine.support.hierarchical") + default void nodeSkipped(C context, TestDescriptor testDescriptor, SkipResult result) { + } + + /** + * Callback invoked when the execution of this node has finished. + * + *

The default implementation does nothing. + * + * @param context the execution context + * @param testDescriptor the test descriptor that was executed + * @param result the result of the execution + * @since 1.4 + */ + @API(status = EXPERIMENTAL, since = "1.4", consumers = "org.junit.platform.engine.support.hierarchical") + default void nodeFinished(C context, TestDescriptor testDescriptor, TestExecutionResult result) { + } + + /** + * Get the set of {@linkplain ExclusiveResource exclusive resources} + * required to execute this node. + * + *

The default implementation returns an empty set. + * + * @return the set of exclusive resources required by this node; never + * {@code null} but potentially empty + * @since 1.3 + * @see ExclusiveResource + */ + @API(status = EXPERIMENTAL, since = "1.3", consumers = "org.junit.platform.engine.support.hierarchical") + default Set getExclusiveResources() { + return emptySet(); + } + + /** + * Get the preferred of {@linkplain ExecutionMode execution mode} for + * parallel execution of this node. + * + *

The default implementation returns {@link ExecutionMode#CONCURRENT}. + * + * @return the preferred execution mode of this node; never {@code null} + * @since 1.3 + * @see ExecutionMode + */ + @API(status = EXPERIMENTAL, since = "1.3", consumers = "org.junit.platform.engine.support.hierarchical") + default ExecutionMode getExecutionMode() { + return ExecutionMode.CONCURRENT; + } + + /** + * The result of determining whether the execution of a given {@code context} + * should be skipped. + * + * @see Node#shouldBeSkipped(EngineExecutionContext) + */ + class SkipResult { + + private static final SkipResult alwaysExecuteSkipResult = new SkipResult(false, null); + + private final boolean skipped; + private final Optional reason; + + /** + * Factory for creating skipped results. + * + *

A context that is skipped will be not be executed. + * + * @param reason the reason that the context should be skipped, + * may be {@code null} + * @return a skipped {@code SkipResult} with the given reason + */ + public static SkipResult skip(String reason) { + return new SkipResult(true, reason); + } + + /** + * Factory for creating do not skip results. + * + *

A context that is not skipped will be executed as normal. + * + * @return a do not skip {@code SkipResult} + */ + public static SkipResult doNotSkip() { + return alwaysExecuteSkipResult; + } + + private SkipResult(boolean skipped, String reason) { + this.skipped = skipped; + this.reason = Optional.ofNullable(reason); + } + + /** + * Whether execution of the context should be skipped. + * + * @return {@code true} if the execution should be skipped + */ + public boolean isSkipped() { + return this.skipped; + } + + /** + * Get the reason that execution of the context should be skipped, + * if available. + */ + public Optional getReason() { + return this.reason; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("skipped", this.skipped) + .append("reason", this.reason.orElse("")) + .toString(); + // @formatter:on + } + + } + + /** + * Executor for additional, dynamic test descriptors discovered during + * execution of a {@link Node}. + * + *

The test descriptors will be executed by the same + * {@link HierarchicalTestExecutor} that executes the submitting node. + * + *

This interface is not intended to be implemented by clients. + * + * @see Node#execute(EngineExecutionContext, DynamicTestExecutor) + * @see HierarchicalTestExecutor + */ + interface DynamicTestExecutor { + + /** + * Submit a dynamic test descriptor for immediate execution. + * + * @param testDescriptor the test descriptor to be executed; never + * {@code null} + */ + void execute(TestDescriptor testDescriptor); + + /** + * Submit a dynamic test descriptor for immediate execution with a + * custom, potentially no-op, execution listener. + * + * @param testDescriptor the test descriptor to be executed; never + * {@code null} + * @param executionListener the executionListener to be notified; never + * {@code null} + * @return a future to cancel or wait for the execution + * @since 5.7 + * @see EngineExecutionListener#NOOP + */ + @API(status = EXPERIMENTAL, since = "5.7") + Future execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener); + + /** + * Block until all dynamic test descriptors submitted to this executor + * are finished. + * + *

This method is useful if the node needs to perform actions in its + * {@link #execute(EngineExecutionContext, DynamicTestExecutor)} method + * after all its dynamic children have finished. + * + * @throws InterruptedException if interrupted while waiting + */ + void awaitFinished() throws InterruptedException; + } + + /** + * Supported execution modes for parallel execution. + * + * @since 1.3 + * @see #SAME_THREAD + * @see #CONCURRENT + * @see Node#getExecutionMode() + */ + @API(status = EXPERIMENTAL, since = "1.3", consumers = "org.junit.platform.engine.support.hierarchical") + enum ExecutionMode { + + /** + * Force execution in same thread as the parent node. + * + * @see #CONCURRENT + */ + SAME_THREAD, + + /** + * Allow concurrent execution with any other node. + * + * @see #SAME_THREAD + */ + CONCURRENT + } + + /** + * Represents an invocation that runs with the supplied context. + * + * @param the type of {@code EngineExecutionContext} used by the {@code HierarchicalTestEngine} + * @since 1.4 + */ + @API(status = EXPERIMENTAL, since = "1.4") + interface Invocation { + + /** + * Invoke this invocation with the supplied context. + * + * @param context the context to invoke in + */ + void invoke(C context) throws Exception; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java new file mode 100644 index 00000000..0a860361 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; + +/** + * @since 1.3.1 + */ +class NodeExecutionAdvisor { + + private final Map forcedDescendantExecutionModeByTestDescriptor = new HashMap<>(); + private final Map resourceLocksByTestDescriptor = new HashMap<>(); + + void forceDescendantExecutionMode(TestDescriptor testDescriptor, ExecutionMode executionMode) { + forcedDescendantExecutionModeByTestDescriptor.put(testDescriptor, executionMode); + } + + void useResourceLock(TestDescriptor testDescriptor, ResourceLock resourceLock) { + resourceLocksByTestDescriptor.put(testDescriptor, resourceLock); + } + + Optional getForcedExecutionMode(TestDescriptor testDescriptor) { + return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor); + } + + private Optional lookupExecutionModeForcedByAncestor(TestDescriptor testDescriptor) { + ExecutionMode value = forcedDescendantExecutionModeByTestDescriptor.get(testDescriptor); + if (value != null) { + return Optional.of(value); + } + return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor); + } + + ResourceLock getResourceLock(TestDescriptor testDescriptor) { + return resourceLocksByTestDescriptor.getOrDefault(testDescriptor, NopLock.INSTANCE); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java new file mode 100644 index 00000000..34df1e0c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java @@ -0,0 +1,265 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toCollection; +import static org.junit.platform.engine.TestExecutionResult.failed; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService.TestTask; +import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; +import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; +import org.junit.platform.engine.support.hierarchical.Node.SkipResult; + +/** + * @since 1.3 + */ +class NodeTestTask implements TestTask { + + private static final Logger logger = LoggerFactory.getLogger(NodeTestTask.class); + private static final Runnable NOOP = () -> { + }; + + private final NodeTestTaskContext taskContext; + private final TestDescriptor testDescriptor; + private final Node node; + private final Runnable finalizer; + + private C parentContext; + private C context; + + private SkipResult skipResult; + private boolean started; + private ThrowableCollector throwableCollector; + + NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor) { + this(taskContext, testDescriptor, NOOP); + } + + NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor, Runnable finalizer) { + this.taskContext = taskContext; + this.testDescriptor = testDescriptor; + this.node = NodeUtils.asNode(testDescriptor); + this.finalizer = finalizer; + } + + @Override + public ResourceLock getResourceLock() { + return taskContext.getExecutionAdvisor().getResourceLock(testDescriptor); + } + + @Override + public ExecutionMode getExecutionMode() { + return taskContext.getExecutionAdvisor().getForcedExecutionMode(testDescriptor).orElse(node.getExecutionMode()); + } + + void setParentContext(C parentContext) { + this.parentContext = parentContext; + } + + @Override + public void execute() { + try { + throwableCollector = taskContext.getThrowableCollectorFactory().create(); + prepare(); + if (throwableCollector.isEmpty()) { + checkWhetherSkipped(); + } + if (throwableCollector.isEmpty() && !skipResult.isSkipped()) { + executeRecursively(); + } + if (context != null) { + cleanUp(); + } + reportCompletion(); + } + finally { + // Ensure that the 'interrupted status' flag for the current thread + // is cleared for reuse of the thread in subsequent task executions. + // See https://github.com/junit-team/junit5/issues/1688 + if (Thread.interrupted()) { + logger.debug(() -> String.format( + "Execution of TestDescriptor with display name [%s] " + + "and unique ID [%s] failed to clear the 'interrupted status' flag for the " + + "current thread. JUnit has cleared the flag, but you may wish to investigate " + + "why the flag was not cleared by user code.", + this.testDescriptor.getDisplayName(), this.testDescriptor.getUniqueId())); + } + finalizer.run(); + } + + // Clear reference to context to allow it to be garbage collected. + // See https://github.com/junit-team/junit5/issues/1578 + context = null; + } + + private void prepare() { + throwableCollector.execute(() -> context = node.prepare(parentContext)); + + // Clear reference to parent context to allow it to be garbage collected. + // See https://github.com/junit-team/junit5/issues/1578 + parentContext = null; + } + + private void checkWhetherSkipped() { + throwableCollector.execute(() -> skipResult = node.shouldBeSkipped(context)); + } + + private void executeRecursively() { + taskContext.getListener().executionStarted(testDescriptor); + started = true; + + throwableCollector.execute(() -> { + node.around(context, ctx -> { + context = ctx; + throwableCollector.execute(() -> { + // @formatter:off + List> children = testDescriptor.getChildren().stream() + .map(descriptor -> new NodeTestTask(taskContext, descriptor)) + .collect(toCollection(ArrayList::new)); + // @formatter:on + + context = node.before(context); + + final DynamicTestExecutor dynamicTestExecutor = new DefaultDynamicTestExecutor(); + context = node.execute(context, dynamicTestExecutor); + + if (!children.isEmpty()) { + children.forEach(child -> child.setParentContext(context)); + taskContext.getExecutorService().invokeAll(children); + } + + throwableCollector.execute(dynamicTestExecutor::awaitFinished); + }); + + throwableCollector.execute(() -> node.after(context)); + }); + }); + } + + private void cleanUp() { + throwableCollector.execute(() -> node.cleanUp(context)); + } + + private void reportCompletion() { + if (throwableCollector.isEmpty() && skipResult.isSkipped()) { + try { + node.nodeSkipped(context, testDescriptor, skipResult); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + logger.debug(throwable, + () -> String.format("Failed to invoke nodeSkipped() on Node %s", testDescriptor.getUniqueId())); + } + taskContext.getListener().executionSkipped(testDescriptor, skipResult.getReason().orElse("")); + return; + } + if (!started) { + // Call executionStarted first to comply with the contract of EngineExecutionListener. + taskContext.getListener().executionStarted(testDescriptor); + } + try { + node.nodeFinished(context, testDescriptor, throwableCollector.toTestExecutionResult()); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + logger.debug(throwable, + () -> String.format("Failed to invoke nodeFinished() on Node %s", testDescriptor.getUniqueId())); + } + taskContext.getListener().executionFinished(testDescriptor, throwableCollector.toTestExecutionResult()); + throwableCollector = null; + } + + private class DefaultDynamicTestExecutor implements DynamicTestExecutor { + private final Map unfinishedTasks = new ConcurrentHashMap<>(); + + @Override + public void execute(TestDescriptor testDescriptor) { + execute(testDescriptor, taskContext.getListener()); + } + + @Override + public Future execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener) { + Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); + Preconditions.notNull(executionListener, "executionListener must not be null"); + + executionListener.dynamicTestRegistered(testDescriptor); + Set exclusiveResources = NodeUtils.asNode(testDescriptor).getExclusiveResources(); + if (!exclusiveResources.isEmpty()) { + executionListener.executionStarted(testDescriptor); + String message = "Dynamic test descriptors must not declare exclusive resources: " + exclusiveResources; + executionListener.executionFinished(testDescriptor, failed(new JUnitException(message))); + return completedFuture(null); + } + else { + UniqueId uniqueId = testDescriptor.getUniqueId(); + NodeTestTask nodeTestTask = new NodeTestTask<>(taskContext.withListener(executionListener), + testDescriptor, () -> unfinishedTasks.remove(uniqueId)); + nodeTestTask.setParentContext(context); + unfinishedTasks.put(uniqueId, DynamicTaskState.unscheduled()); + Future future = taskContext.getExecutorService().submit(nodeTestTask); + unfinishedTasks.computeIfPresent(uniqueId, (__, state) -> DynamicTaskState.scheduled(future)); + return future; + } + } + + @Override + public void awaitFinished() throws InterruptedException { + for (DynamicTaskState state : unfinishedTasks.values()) { + try { + state.awaitFinished(); + } + catch (CancellationException ignore) { + // Futures returned by execute() may have been cancelled + } + catch (ExecutionException e) { + ExceptionUtils.throwAsUncheckedException(e.getCause()); + } + } + } + } + + @FunctionalInterface + private interface DynamicTaskState { + + DynamicTaskState UNSCHEDULED = () -> { + }; + + static DynamicTaskState unscheduled() { + return UNSCHEDULED; + } + + static DynamicTaskState scheduled(Future future) { + return future::get; + } + + void awaitFinished() throws CancellationException, ExecutionException, InterruptedException; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java new file mode 100644 index 00000000..35a34af8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import org.junit.platform.engine.EngineExecutionListener; + +/** + * @since 1.3.1 + */ +class NodeTestTaskContext { + + private final EngineExecutionListener listener; + private final HierarchicalTestExecutorService executorService; + private final ThrowableCollector.Factory throwableCollectorFactory; + private final NodeExecutionAdvisor executionAdvisor; + + public NodeTestTaskContext(EngineExecutionListener listener, HierarchicalTestExecutorService executorService, + ThrowableCollector.Factory throwableCollectorFactory, NodeExecutionAdvisor executionAdvisor) { + this.listener = listener; + this.executorService = executorService; + this.throwableCollectorFactory = throwableCollectorFactory; + this.executionAdvisor = executionAdvisor; + } + + NodeTestTaskContext withListener(EngineExecutionListener listener) { + if (this.listener == listener) { + return this; + } + return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor); + } + + EngineExecutionListener getListener() { + return listener; + } + + HierarchicalTestExecutorService getExecutorService() { + return executorService; + } + + ThrowableCollector.Factory getThrowableCollectorFactory() { + return throwableCollectorFactory; + } + + NodeExecutionAdvisor getExecutionAdvisor() { + return executionAdvisor; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java new file mode 100644 index 00000000..2bb59d50 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; +import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; + +/** + * @since 1.3 + */ +class NodeTreeWalker { + + private final LockManager lockManager; + private final ResourceLock globalReadLock; + private final ResourceLock globalReadWriteLock; + + NodeTreeWalker() { + this(new LockManager()); + } + + NodeTreeWalker(LockManager lockManager) { + this.lockManager = lockManager; + this.globalReadLock = lockManager.getLockForResource(GLOBAL_READ); + this.globalReadWriteLock = lockManager.getLockForResource(GLOBAL_READ_WRITE); + } + + NodeExecutionAdvisor walk(TestDescriptor rootDescriptor) { + Preconditions.condition(getExclusiveResources(rootDescriptor).isEmpty(), + "Engine descriptor must not declare exclusive resources"); + NodeExecutionAdvisor advisor = new NodeExecutionAdvisor(); + rootDescriptor.getChildren().forEach(child -> walk(child, child, advisor)); + return advisor; + } + + private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescriptor, + NodeExecutionAdvisor advisor) { + Set exclusiveResources = getExclusiveResources(testDescriptor); + if (exclusiveResources.isEmpty()) { + advisor.useResourceLock(testDescriptor, globalReadLock); + testDescriptor.getChildren().forEach(child -> walk(globalLockDescriptor, child, advisor)); + } + else { + Set allResources = new HashSet<>(exclusiveResources); + if (isReadOnly(allResources)) { + doForChildrenRecursively(testDescriptor, child -> allResources.addAll(getExclusiveResources(child))); + if (!isReadOnly(allResources)) { + forceDescendantExecutionModeRecursively(advisor, testDescriptor); + } + } + else { + advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD); + doForChildrenRecursively(testDescriptor, child -> { + allResources.addAll(getExclusiveResources(child)); + advisor.forceDescendantExecutionMode(child, SAME_THREAD); + }); + } + if (!globalLockDescriptor.equals(testDescriptor) && allResources.contains(GLOBAL_READ_WRITE)) { + forceDescendantExecutionModeRecursively(advisor, globalLockDescriptor); + advisor.useResourceLock(globalLockDescriptor, globalReadWriteLock); + } + if (globalLockDescriptor.equals(testDescriptor) && !allResources.contains(GLOBAL_READ_WRITE)) { + allResources.add(GLOBAL_READ); + } + advisor.useResourceLock(testDescriptor, lockManager.getLockForResources(allResources)); + } + } + + private void forceDescendantExecutionModeRecursively(NodeExecutionAdvisor advisor, TestDescriptor testDescriptor) { + advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD); + doForChildrenRecursively(testDescriptor, child -> advisor.forceDescendantExecutionMode(child, SAME_THREAD)); + } + + private boolean isReadOnly(Set exclusiveResources) { + return exclusiveResources.stream().allMatch( + exclusiveResource -> exclusiveResource.getLockMode() == ExclusiveResource.LockMode.READ); + } + + private Set getExclusiveResources(TestDescriptor testDescriptor) { + return NodeUtils.asNode(testDescriptor).getExclusiveResources(); + } + + private void doForChildrenRecursively(TestDescriptor parent, Consumer consumer) { + parent.getChildren().forEach(child -> { + consumer.accept(child); + doForChildrenRecursively(child, consumer); + }); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java new file mode 100644 index 00000000..b76ba240 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import org.junit.platform.engine.TestDescriptor; + +/** + * @since 1.3.1 + */ +final class NodeUtils { + + private NodeUtils() { + /* no-op */ + } + + @SuppressWarnings("unchecked") + static Node asNode(TestDescriptor testDescriptor) { + return (testDescriptor instanceof Node ? (Node) testDescriptor : noOpNode); + } + + @SuppressWarnings("rawtypes") + private static final Node noOpNode = new Node() { + }; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java new file mode 100644 index 00000000..df6f64fc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +/** + * No-op {@link ResourceLock} implementation. + * + * @since 1.3 + */ +class NopLock implements ResourceLock { + + static final ResourceLock INSTANCE = new NopLock(); + + private NopLock() { + } + + @Override + public ResourceLock acquire() { + return this; + } + + @Override + public void release() { + // nothing to do + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java new file mode 100644 index 00000000..53b9e216 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import org.apiguardian.api.API; +import org.opentest4j.TestAbortedException; + +/** + * Specialization of {@link ThrowableCollector} that treats instances of + * {@link TestAbortedException} as aborting. + * + * @since 1.3 + * @see ThrowableCollector + */ +@API(status = MAINTAINED, since = "1.3") +public class OpenTest4JAwareThrowableCollector extends ThrowableCollector { + + public OpenTest4JAwareThrowableCollector() { + super(TestAbortedException.class::isInstance); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java new file mode 100644 index 00000000..47078896 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.concurrent.ForkJoinPool; +import java.util.function.Predicate; + +import org.apiguardian.api.API; + +/** + * Configuration to use for parallel test execution. + * + *

Instances of this class are intended to be used to configure + * implementations of {@link HierarchicalTestExecutorService}. Such + * implementations may use all of the properties in this class or + * only a subset. + * + * @since 1.3 + * @see ForkJoinPoolHierarchicalTestExecutorService + * @see ParallelExecutionConfigurationStrategy + * @see DefaultParallelExecutionConfigurationStrategy + */ +@API(status = EXPERIMENTAL, since = "1.3") +public interface ParallelExecutionConfiguration { + + /** + * Get the parallelism to be used. + * + * @see ForkJoinPool#getParallelism() + */ + int getParallelism(); + + /** + * Get the minimum number of runnable threads to be used. + */ + int getMinimumRunnable(); + + /** + * Get the maximum thread pool size to be used. + */ + int getMaxPoolSize(); + + /** + * Get the core thread pool size to be used. + */ + int getCorePoolSize(); + + /** + * Get the number of seconds for which inactive threads should be kept alive + * before terminating them and shrinking the thread pool. + */ + int getKeepAliveSeconds(); + + /** + * Get the saturate predicate to be used for the execution's {@link ForkJoinPool}. + * @return the saturate predicate to be passed to the {@code ForkJoinPool} constructor; may be {@code null} + * @since 1.9 + * @see ForkJoinPool#ForkJoinPool(int, ForkJoinPool.ForkJoinWorkerThreadFactory, Thread.UncaughtExceptionHandler, + * boolean, int, int, int, Predicate, long, TimeUnit) + */ + @API(status = EXPERIMENTAL, since = "1.9") + default Predicate getSaturatePredicate() { + return null; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java new file mode 100644 index 00000000..181ae463 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * A strategy to use for configuring parallel test execution. + * + * @since 1.3 + * @see DefaultParallelExecutionConfigurationStrategy + */ +@API(status = EXPERIMENTAL, since = "1.3") +public interface ParallelExecutionConfigurationStrategy { + + /** + * Create a configuration for parallel test execution based on the supplied + * {@link ConfigurationParameters}. + */ + ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java new file mode 100644 index 00000000..9ec55b8f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * A lock for a one or more resources. + * + * @since 1.3 + * @see HierarchicalTestExecutorService.TestTask#getResourceLock() + */ +@API(status = EXPERIMENTAL, since = "1.3") +public interface ResourceLock extends AutoCloseable { + + /** + * Acquire this resource lock, potentially blocking. + * + * @return this lock so it can easily be used in a try-with-resources + * statement. + * @throws InterruptedException if the calling thread is interrupted + * while waiting to acquire this lock + */ + ResourceLock acquire() throws InterruptedException; + + /** + * Release this resource lock. + */ + void release(); + + @Override + default void close() { + release(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java new file mode 100644 index 00000000..c1844da9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.List; +import java.util.concurrent.Future; + +import org.apiguardian.api.API; + +/** + * A simple {@linkplain HierarchicalTestExecutorService executor service} that + * executes all {@linkplain TestTask test tasks} in the caller's thread. + * + * @since 1.3 + */ +@API(status = EXPERIMENTAL, since = "1.3") +public class SameThreadHierarchicalTestExecutorService implements HierarchicalTestExecutorService { + + public SameThreadHierarchicalTestExecutorService() { + } + + @Override + public Future submit(TestTask testTask) { + testTask.execute(); + return completedFuture(null); + } + + @Override + public void invokeAll(List tasks) { + tasks.forEach(TestTask::execute); + } + + @Override + public void close() { + // nothing to do + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java new file mode 100644 index 00000000..5d104e8d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.locks.Lock; + +/** + * @since 1.3 + */ +class SingleLock implements ResourceLock { + + private final Lock lock; + + SingleLock(Lock lock) { + this.lock = lock; + } + + // for tests only + Lock getLock() { + return lock; + } + + @Override + public ResourceLock acquire() throws InterruptedException { + ForkJoinPool.managedBlock(new SingleLockManagedBlocker()); + return this; + } + + @Override + public void release() { + lock.unlock(); + } + + private class SingleLockManagedBlocker implements ForkJoinPool.ManagedBlocker { + + private boolean acquired; + + @Override + public boolean block() throws InterruptedException { + lock.lockInterruptibly(); + acquired = true; + return true; + } + + @Override + public boolean isReleasable() { + return acquired || lock.tryLock(); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java new file mode 100644 index 00000000..6c87fb11 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.junit.platform.engine.TestExecutionResult.aborted; +import static org.junit.platform.engine.TestExecutionResult.failed; +import static org.junit.platform.engine.TestExecutionResult.successful; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestExecutionResult; +import org.opentest4j.TestAbortedException; + +/** + * {@code SingleTestExecutor} encapsulates the execution of a single test + * wrapped in an {@link Executable}. + * + * @since 1.0 + * @see #executeSafely(Executable) + * @deprecated Please use {@link ThrowableCollector#execute} and + * {@link ThrowableCollector#toTestExecutionResult} instead. + */ +@Deprecated +@API(status = DEPRECATED, since = "1.2") +public class SingleTestExecutor { + + /** + * Functional interface for a single test to be executed by + * {@link SingleTestExecutor}. + */ + @FunctionalInterface + public interface Executable { + + /** + * Execute the test. + * + * @throws TestAbortedException to signal aborted execution + * @throws Throwable to signal failure + */ + void execute() throws TestAbortedException, Throwable; + + } + + /** + * Execute the supplied {@link Executable} and return a + * {@link TestExecutionResult} based on the outcome. + * + *

If the {@code Executable} throws an unrecoverable exception + * — for example, an {@link OutOfMemoryError} — this method will + * rethrow it. + * + * @param executable the test to be executed + * @return {@linkplain TestExecutionResult#aborted aborted} if the + * {@code Executable} throws a {@link TestAbortedException}; + * {@linkplain TestExecutionResult#failed failed} if any other + * {@link Throwable} is thrown; and {@linkplain TestExecutionResult#successful + * successful} otherwise + */ + public TestExecutionResult executeSafely(Executable executable) { + try { + executable.execute(); + return successful(); + } + catch (TestAbortedException e) { + return aborted(e); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + return failed(t); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java new file mode 100644 index 00000000..5a1e9d56 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java @@ -0,0 +1,217 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.junit.platform.engine.TestExecutionResult.aborted; +import static org.junit.platform.engine.TestExecutionResult.failed; +import static org.junit.platform.engine.TestExecutionResult.successful; + +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestExecutionResult; + +/** + * Simple component that can be used to collect one or more instances of + * {@link Throwable}. + * + *

This class distinguishes between {@code Throwables} that abort + * and those that fail test execution. The latter take precedence over + * the former, i.e. if both types of {@code Throwables} were collected, the ones + * that abort execution are reported as + * {@linkplain Throwable#addSuppressed(Throwable) suppressed} {@code Throwables} + * of the first {@code Throwable} that failed execution. + * + * @since 1.3 + * @see OpenTest4JAwareThrowableCollector + */ +@API(status = MAINTAINED, since = "1.3") +public class ThrowableCollector { + + private final Predicate abortedExecutionPredicate; + + private Throwable throwable; + + /** + * Create a new {@code ThrowableCollector} that uses the supplied + * {@link Predicate} to determine whether a {@link Throwable} + * aborted or failed execution. + * + * @param abortedExecutionPredicate the predicate used to decide whether a + * {@code Throwable} aborted execution; never {@code null}. + */ + public ThrowableCollector(Predicate abortedExecutionPredicate) { + this.abortedExecutionPredicate = Preconditions.notNull(abortedExecutionPredicate, + "abortedExecutionPredicate must not be null"); + } + + /** + * Execute the supplied {@link Executable} and collect any {@link Throwable} + * thrown during the execution. + * + *

If the {@code Executable} throws an unrecoverable exception + * — for example, an {@link OutOfMemoryError} — this method will + * rethrow it. + * + * @param executable the {@code Executable} to execute + * @see #assertEmpty() + */ + public void execute(Executable executable) { + try { + executable.execute(); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + add(t); + } + } + + /** + * Add the supplied {@link Throwable} to this {@code ThrowableCollector}. + * + * @param t the {@code Throwable} to add + * @see #execute(Executable) + * @see #assertEmpty() + */ + private void add(Throwable t) { + Preconditions.notNull(t, "Throwable must not be null"); + + if (this.throwable == null) { + this.throwable = t; + } + else if (hasAbortedExecution(this.throwable) && !hasAbortedExecution(t)) { + t.addSuppressed(this.throwable); + this.throwable = t; + } + else if (throwable != t) { + // Jupiter does not throw the same Throwable from Node.after() anymore but other engines might + this.throwable.addSuppressed(t); + } + } + + /** + * Get the first {@link Throwable} collected by this + * {@code ThrowableCollector}. + * + *

If this collector is not empty, the first collected {@code Throwable} + * will be returned with any additional {@code Throwables} + * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the + * first {@code Throwable}. + * + *

If the first collected {@code Throwable} aborted execution + * and at least one later collected {@code Throwable} failed + * execution, the first failing {@code Throwable} will be returned + * with the previous aborting and any additional {@code Throwables} + * {@linkplain Throwable#addSuppressed(Throwable) suppressed} inside. + * + * @return the first collected {@code Throwable} or {@code null} if this + * {@code ThrowableCollector} is empty + * @see #isEmpty() + * @see #assertEmpty() + */ + public Throwable getThrowable() { + return this.throwable; + } + + /** + * Determine if this {@code ThrowableCollector} is empty (i.e., + * has not collected any {@code Throwables}). + */ + public boolean isEmpty() { + return (this.throwable == null); + } + + /** + * Determine if this {@code ThrowableCollector} is not empty (i.e., + * has collected at least one {@code Throwable}). + */ + public boolean isNotEmpty() { + return (this.throwable != null); + } + + /** + * Assert that this {@code ThrowableCollector} is empty (i.e., + * has not collected any {@code Throwables}). + * + *

If this collector is not empty, the first collected {@code Throwable} + * will be thrown with any additional {@code Throwables} + * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the + * first {@code Throwable}. Note, however, that the {@code Throwable} + * will not be wrapped. Rather, it will be + * {@linkplain ExceptionUtils#throwAsUncheckedException masked} + * as an unchecked exception. + * + * @see #getThrowable() + * @see ExceptionUtils#throwAsUncheckedException(Throwable) + */ + public void assertEmpty() { + if (!isEmpty()) { + ExceptionUtils.throwAsUncheckedException(this.throwable); + } + } + + /** + * Convert the collected {@link Throwable Throwables} into a {@link TestExecutionResult}. + * + * @return {@linkplain TestExecutionResult#aborted aborted} if the collected + * {@code Throwable} aborted execution; + * {@linkplain TestExecutionResult#failed failed} if it failed + * execution; and {@linkplain TestExecutionResult#successful successful} + * otherwise + * @since 1.6 + */ + @API(status = MAINTAINED, since = "1.6") + public TestExecutionResult toTestExecutionResult() { + if (isEmpty()) { + return successful(); + } + if (hasAbortedExecution(throwable)) { + return aborted(throwable); + } + return failed(throwable); + } + + private boolean hasAbortedExecution(Throwable t) { + return this.abortedExecutionPredicate.test(t); + } + + /** + * Functional interface for an executable block of code that may throw a + * {@link Throwable}. + */ + @FunctionalInterface + public interface Executable { + + /** + * Execute this executable, potentially throwing a {@link Throwable} + * that signals abortion or failure. + */ + void execute() throws Throwable; + + } + + /** + * Factory for {@code ThrowableCollector} instances. + */ + public interface Factory { + + /** + * Create a new instance of a {@code ThrowableCollector}. + */ + ThrowableCollector create(); + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java new file mode 100644 index 00000000..033745ba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java @@ -0,0 +1,8 @@ +/** + * Support classes and base implementation for any + * {@link org.junit.platform.engine.TestEngine} that wishes to organize test suites + * hierarchically based on the + * {@link org.junit.platform.engine.support.hierarchical.Node} abstraction. + */ + +package org.junit.platform.engine.support.hierarchical; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java new file mode 100644 index 00000000..0b6336c6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Public API for test engines. + * + *

Provides the {@linkplain org.junit.platform.engine.TestEngine} interface, test discovery + * and execution reporting support. + * + * @since 1.0 + */ +module org.junit.platform.engine { + requires static transitive org.apiguardian.api; + requires transitive org.junit.platform.commons; + requires transitive org.opentest4j; + + exports org.junit.platform.engine; + exports org.junit.platform.engine.discovery; + exports org.junit.platform.engine.reporting; + // exports org.junit.platform.engine.support; empty package + exports org.junit.platform.engine.support.config; + exports org.junit.platform.engine.support.descriptor; + exports org.junit.platform.engine.support.discovery; + exports org.junit.platform.engine.support.filter; + exports org.junit.platform.engine.support.hierarchical; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java new file mode 100644 index 00000000..8d3c74a7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import org.junit.platform.engine.ExecutionRequest; + +/** + * @since 1.0 + */ +public class DemoEngineExecutionContext implements EngineExecutionContext { + + public final ExecutionRequest request; + + public DemoEngineExecutionContext(ExecutionRequest request) { + this.request = request; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java new file mode 100644 index 00000000..09c0b622 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; + +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; + +/** + * @since 1.0 + */ +public class DemoHierarchicalContainerDescriptor extends AbstractTestDescriptor + implements Node { + + private final Runnable beforeBlock; + + public DemoHierarchicalContainerDescriptor(UniqueId uniqueId, String displayName, TestSource source, + Runnable beforeBlock) { + super(uniqueId, displayName, source); + this.beforeBlock = beforeBlock; + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public boolean mayRegisterTests() { + return true; + } + + @Override + public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { + return doNotSkip(); + } + + @Override + public DemoEngineExecutionContext before(DemoEngineExecutionContext context) { + if (this.beforeBlock != null) { + this.beforeBlock.run(); + } + return context; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java new file mode 100644 index 00000000..1d14b771 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; +import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip; + +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; + +/** + * @since 1.0 + */ +public class DemoHierarchicalEngineDescriptor extends EngineDescriptor implements Node { + + private String skippedReason; + private boolean skipped; + private Runnable beforeAllBehavior = () -> { + }; + + public DemoHierarchicalEngineDescriptor(UniqueId uniqueId) { + super(uniqueId, uniqueId.getEngineId().orElseThrow()); + } + + public void markSkipped(String reason) { + this.skipped = true; + this.skippedReason = reason; + } + + public void setBeforeAllBehavior(Runnable beforeAllBehavior) { + this.beforeAllBehavior = beforeAllBehavior; + } + + @Override + public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { + return skipped ? skip(skippedReason) : doNotSkip(); + } + + @Override + public DemoEngineExecutionContext before(DemoEngineExecutionContext context) { + beforeAllBehavior.run(); + return context; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java new file mode 100644 index 00000000..130aba4a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; +import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip; + +import java.util.function.BiConsumer; + +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; + +/** + * @since 1.0 + */ +public class DemoHierarchicalTestDescriptor extends AbstractTestDescriptor implements Node { + + private final BiConsumer executeBlock; + private String skippedReason; + private boolean skipped; + + public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, + BiConsumer executeBlock) { + this(uniqueId, displayName, null, executeBlock); + } + + public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, + BiConsumer executeBlock) { + super(uniqueId, displayName, source); + this.executeBlock = executeBlock; + } + + @Override + public Type getType() { + return this.executeBlock != null ? Type.TEST : Type.CONTAINER; + } + + public void markSkipped(String reason) { + this.skipped = true; + this.skippedReason = reason; + } + + @Override + public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { + return skipped ? skip(skippedReason) : doNotSkip(); + } + + @Override + public DemoEngineExecutionContext execute(DemoEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) { + if (this.executeBlock != null) { + this.executeBlock.accept(context, this); + } + return context; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java new file mode 100644 index 00000000..97f23ce1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; + +/** + * @since 1.0 + */ +public final class DemoHierarchicalTestEngine extends HierarchicalTestEngine { + + private final String engineId; + private final DemoHierarchicalEngineDescriptor engineDescriptor; + + public DemoHierarchicalTestEngine() { + this("dummy"); + } + + public DemoHierarchicalTestEngine(String engineId) { + this.engineId = engineId; + this.engineDescriptor = new DemoHierarchicalEngineDescriptor(UniqueId.forEngine(getId())); + } + + @Override + public String getId() { + return engineId; + } + + public DemoHierarchicalEngineDescriptor getEngineDescriptor() { + return engineDescriptor; + } + + public DemoHierarchicalTestDescriptor addTest(String uniqueName, Runnable executeBlock) { + return addTest(uniqueName, uniqueName, executeBlock); + } + + public DemoHierarchicalTestDescriptor addTest(String uniqueName, String displayName, Runnable executeBlock) { + return addChild(uniqueName, + uniqueId -> new DemoHierarchicalTestDescriptor(uniqueId, displayName, (c, t) -> executeBlock.run()), + "test"); + } + + public DemoHierarchicalTestDescriptor addTest(String uniqueName, String displayName, + BiConsumer executeBlock) { + return addChild(uniqueName, uniqueId -> new DemoHierarchicalTestDescriptor(uniqueId, displayName, executeBlock), + "test"); + } + + public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, TestSource source) { + return addContainer(uniqueName, displayName, source, null); + } + + public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, Runnable beforeBlock) { + return addContainer(uniqueName, uniqueName, null, beforeBlock); + } + + public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, TestSource source, + Runnable beforeBlock) { + + return addChild(uniqueName, + uniqueId -> new DemoHierarchicalContainerDescriptor(uniqueId, displayName, source, beforeBlock), + "container"); + } + + public > T addChild(String uniqueName, + Function creator, String segmentType) { + var uniqueId = engineDescriptor.getUniqueId().append(segmentType, uniqueName); + var child = creator.apply(uniqueId); + engineDescriptor.addChild(child); + return child; + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + return engineDescriptor; + } + + @Override + protected DemoEngineExecutionContext createExecutionContext(ExecutionRequest request) { + return new DemoEngineExecutionContext(request); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java new file mode 100644 index 00000000..49907eec --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.fakes; + +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; + +/** + * @since 1.4 + */ +public class TestDescriptorStub extends AbstractTestDescriptor { + + public TestDescriptorStub(UniqueId uniqueId, String displayName) { + super(uniqueId, displayName); + } + + @Override + public Type getType() { + return getChildren().isEmpty() ? Type.TEST : Type.CONTAINER; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java new file mode 100644 index 00000000..2cb30350 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.fakes; + +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; + +/** + * @since 1.4 + */ +public class TestEngineSpy implements TestEngine { + + public static final String ID = TestEngineSpy.class.getSimpleName(); + + public ExecutionRequest requestForExecution; + + @Override + public String getId() { + return ID; + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + var engineUniqueId = UniqueId.forEngine(ID); + var engineDescriptor = new TestDescriptorStub(engineUniqueId, ID); + var testDescriptor = new TestDescriptorStub(engineUniqueId.append("test", "test"), "test"); + engineDescriptor.addChild(testDescriptor); + return engineDescriptor; + } + + @Override + public void execute(ExecutionRequest request) { + this.requestForExecution = request; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java new file mode 100644 index 00000000..3ef71865 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.fakes; + +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; + +/** + * @since 1.4 + */ +public class TestEngineStub implements TestEngine { + + private final String id; + + public TestEngineStub() { + this(TestEngineStub.class.getSimpleName()); + } + + public TestEngineStub(String id) { + this.id = id; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + return new TestDescriptorStub(UniqueId.forEngine(getId()), getId()); + } + + @Override + public void execute(ExecutionRequest request) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts new file mode 100644 index 00000000..00198d86 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts @@ -0,0 +1,31 @@ +plugins { + `java-library-conventions` +} + +description = "JUnit Platform Flight Recorder Support" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformLauncher) + + compileOnlyApi(libs.apiguardian) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +javaLibrary { + // --release 8 does not support jdk.jfr even though it was backported + configureRelease = false +} + +tasks { + compileJava { + javaCompiler.set(project.javaToolchains.compilerFor { + languageVersion.set(JavaLanguageVersion.of(8)) + }) + } + compileModule { + options.release.set(11) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java new file mode 100644 index 00000000..d506a562 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java @@ -0,0 +1,103 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.jfr; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +import jdk.jfr.Category; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +import org.apiguardian.api.API; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.launcher.EngineDiscoveryResult; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * A {@link LauncherDiscoveryListener} that generates Java Flight Recorder + * events. + * + * @since 1.8 + * @see JEP 328: Flight Recorder + */ +@API(status = EXPERIMENTAL, since = "1.8") +public class FlightRecordingDiscoveryListener implements LauncherDiscoveryListener { + + private final AtomicReference launcherDiscoveryEvent = new AtomicReference<>(); + private final Map engineDiscoveryEvents = new ConcurrentHashMap<>(); + + @Override + public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { + LauncherDiscoveryEvent event = new LauncherDiscoveryEvent(); + event.selectors = request.getSelectorsByType(DiscoverySelector.class).size(); + event.filters = request.getFiltersByType(DiscoveryFilter.class).size(); + event.begin(); + launcherDiscoveryEvent.set(event); + } + + @Override + public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { + launcherDiscoveryEvent.getAndSet(null).commit(); + } + + @Override + public void engineDiscoveryStarted(org.junit.platform.engine.UniqueId engineId) { + EngineDiscoveryEvent event = new EngineDiscoveryEvent(); + event.uniqueId = engineId.toString(); + event.begin(); + engineDiscoveryEvents.put(engineId, event); + } + + @Override + public void engineDiscoveryFinished(org.junit.platform.engine.UniqueId engineId, EngineDiscoveryResult result) { + EngineDiscoveryEvent event = engineDiscoveryEvents.remove(engineId); + event.result = result.getStatus().toString(); + event.commit(); + } + + @Category({ "JUnit", "Discovery" }) + @StackTrace(false) + abstract static class DiscoveryEvent extends Event { + } + + @Label("Test Discovery") + @Category({ "JUnit", "Discovery" }) + @Name("org.junit.LauncherDiscovery") + static class LauncherDiscoveryEvent extends DiscoveryEvent { + + @Label("Number of selectors") + int selectors; + + @Label("Number of filters") + int filters; + } + + @Label("Engine Discovery") + @Category({ "JUnit", "Discovery" }) + @Name("org.junit.EngineDiscovery") + static class EngineDiscoveryEvent extends DiscoveryEvent { + + @UniqueId + @Label("Unique Id") + String uniqueId; + + @Label("Result") + String result; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java new file mode 100644 index 00000000..be666602 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java @@ -0,0 +1,162 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.jfr; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import jdk.jfr.Category; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * A {@link TestExecutionListener} that generates Java Flight Recorder + * events. + * + * @since 1.8 + * @see JEP 328: Flight Recorder + */ +@API(status = EXPERIMENTAL, since = "1.8") +public class FlightRecordingExecutionListener implements TestExecutionListener { + + private final AtomicReference testPlanExecutionEvent = new AtomicReference<>(); + private final Map testExecutionEvents = new ConcurrentHashMap<>(); + + @Override + public void testPlanExecutionStarted(TestPlan plan) { + TestPlanExecutionEvent event = new TestPlanExecutionEvent(); + event.containsTests = plan.containsTests(); + event.engineNames = plan.getRoots().stream().map(TestIdentifier::getDisplayName).collect( + Collectors.joining(", ")); + testPlanExecutionEvent.set(event); + event.begin(); + } + + @Override + public void testPlanExecutionFinished(TestPlan plan) { + testPlanExecutionEvent.getAndSet(null).commit(); + } + + @Override + public void executionSkipped(TestIdentifier test, String reason) { + SkippedTestEvent event = new SkippedTestEvent(); + event.initialize(test); + event.reason = reason; + event.commit(); + } + + @Override + public void executionStarted(TestIdentifier test) { + TestExecutionEvent event = new TestExecutionEvent(); + testExecutionEvents.put(test.getUniqueIdObject(), event); + event.initialize(test); + event.begin(); + } + + @Override + public void executionFinished(TestIdentifier test, TestExecutionResult result) { + Optional throwable = result.getThrowable(); + TestExecutionEvent event = testExecutionEvents.remove(test.getUniqueIdObject()); + event.end(); + event.result = result.getStatus().toString(); + event.exceptionClass = throwable.map(Throwable::getClass).orElse(null); + event.exceptionMessage = throwable.map(Throwable::getMessage).orElse(null); + event.commit(); + } + + @Override + public void reportingEntryPublished(TestIdentifier test, ReportEntry reportEntry) { + for (Map.Entry entry : reportEntry.getKeyValuePairs().entrySet()) { + ReportEntryEvent event = new ReportEntryEvent(); + event.uniqueId = test.getUniqueId(); + event.key = entry.getKey(); + event.value = entry.getValue(); + event.commit(); + } + } + + @Category({ "JUnit", "Execution" }) + @StackTrace(false) + abstract static class ExecutionEvent extends Event { + } + + @Label("Test Execution") + @Name("org.junit.TestPlanExecution") + static class TestPlanExecutionEvent extends ExecutionEvent { + @Label("Contains Tests") + boolean containsTests; + @Label("Engine Names") + String engineNames; + } + + abstract static class TestEvent extends ExecutionEvent { + @UniqueId + @Label("Unique Id") + String uniqueId; + @Label("Display Name") + String displayName; + @Label("Tags") + String tags; + @Label("Type") + String type; + + void initialize(TestIdentifier test) { + this.uniqueId = test.getUniqueId(); + this.displayName = test.getDisplayName(); + this.tags = test.getTags().isEmpty() ? null : test.getTags().toString(); + this.type = test.getType().name(); + } + } + + @Label("Skipped Test") + @Name("org.junit.SkippedTest") + static class SkippedTestEvent extends TestEvent { + @Label("Reason") + String reason; + } + + @Label("Test") + @Name("org.junit.TestExecution") + static class TestExecutionEvent extends TestEvent { + @Label("Result") + String result; + @Label("Exception Class") + Class exceptionClass; + @Label("Exception Message") + String exceptionMessage; + } + + @Label("Report Entry") + @Name("org.junit.ReportEntry") + static class ReportEntryEvent extends ExecutionEvent { + @UniqueId + @Label("Unique Id") + String uniqueId; + @Label("Key") + String key; + @Label("Value") + String value; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java new file mode 100644 index 00000000..653226e6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.jfr; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; +import jdk.jfr.Relational; + +@MetadataDefinition +@Relational +@Name("org.junit.UniqueId") +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@interface UniqueId { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java new file mode 100644 index 00000000..0a8c9813 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java @@ -0,0 +1,5 @@ +/** + * Java Flight Recorder support package. + */ + +package org.junit.platform.jfr; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener new file mode 100644 index 00000000..de4e472c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener @@ -0,0 +1 @@ +org.junit.platform.jfr.FlightRecordingDiscoveryListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 00000000..35571863 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1 @@ +org.junit.platform.jfr.FlightRecordingExecutionListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java new file mode 100644 index 00000000..61aad3ec --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Provides Java Flight Recorder events for the JUnit Platform. + * + *

The Flight Recording Listener module implements a + * {@link org.junit.platform.launcher.LauncherDiscoveryListener} and a + * {@link org.junit.platform.launcher.TestExecutionListener} that generate Java + * Flight Recorder (JFR) events. + * + * @see JEP 328: Flight Recorder + * @since 1.7 + */ +module org.junit.platform.jfr { + requires jdk.jfr; + requires static org.apiguardian.api; + requires org.junit.platform.engine; + requires org.junit.platform.launcher; + + provides org.junit.platform.launcher.LauncherDiscoveryListener + with org.junit.platform.jfr.FlightRecordingDiscoveryListener; + provides org.junit.platform.launcher.TestExecutionListener + with org.junit.platform.jfr.FlightRecordingExecutionListener; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts new file mode 100644 index 00000000..d51d0df3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts @@ -0,0 +1,28 @@ +plugins { + `java-library-conventions` + `java-test-fixtures` +} + +description = "JUnit Platform Launcher" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformEngine) + + compileOnlyApi(libs.apiguardian) + + osgiVerification(projects.junitJupiterEngine) +} + +tasks { + jar { + bundle { + bnd(""" + Provide-Capability:\ + org.junit.platform.launcher;\ + org.junit.platform.launcher='junit-platform-launcher';\ + version:Version="${'$'}{version_cleanup;${project.version}}" + """) + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java new file mode 100644 index 00000000..39c70372 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code EngineDiscoveryResult} encapsulates the result of test discovery by a + * {@link org.junit.platform.engine.TestEngine}. + * + *

A {@code EngineDiscoveryResult} consists of a mandatory + * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. + * + * @since 1.6 + */ +@API(status = EXPERIMENTAL, since = "1.6") +public class EngineDiscoveryResult { + + /** + * Status of test discovery by a + * {@link org.junit.platform.engine.TestEngine}. + */ + public enum Status { + + /** + * Indicates that test discovery was successful. + */ + SUCCESSFUL, + + /** + * Indicates that test discovery has failed. + */ + FAILED + + } + + private static final EngineDiscoveryResult SUCCESSFUL_RESULT = new EngineDiscoveryResult(Status.SUCCESSFUL, null); + + /** + * Create a {@code EngineDiscoveryResult} for a successful test + * discovery. + * @return the {@code EngineDiscoveryResult}; never {@code null} + */ + public static EngineDiscoveryResult successful() { + return SUCCESSFUL_RESULT; + } + + /** + * Create a {@code EngineDiscoveryResult} for a failed test + * discovery. + * + * @param throwable the throwable that caused the failed discovery; may be + * {@code null} + * @return the {@code EngineDiscoveryResult}; never {@code null} + */ + public static EngineDiscoveryResult failed(Throwable throwable) { + return new EngineDiscoveryResult(Status.FAILED, throwable); + } + + private final Status status; + private final Throwable throwable; + + private EngineDiscoveryResult(Status status, Throwable throwable) { + this.status = status; + this.throwable = throwable; + } + + /** + * Get the {@linkplain Status status} of this result. + * + * @return the status; never {@code null} + */ + public Status getStatus() { + return status; + } + + /** + * Get the throwable that caused this result, if available. + * + * @return an {@code Optional} containing the throwable; never {@code null} + * but potentially empty + */ + public Optional getThrowable() { + return Optional.ofNullable(throwable); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("status", status) + .append("throwable", throwable) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java new file mode 100644 index 00000000..6013abb2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java @@ -0,0 +1,172 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.engine.FilterResult.includedIf; + +import java.util.Arrays; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestEngine; + +/** + * An {@code EngineFilter} is applied to all {@link TestEngine TestEngines} + * before they are used. + * + *

Warning: be cautious when registering multiple competing + * {@link #includeEngines include} {@code EngineFilters} or multiple competing + * {@link #excludeEngines exclude} {@code EngineFilters} for the same discovery + * request since doing so will likely lead to undesirable results (i.e., zero + * engines being active). + * + * @since 1.0 + * @see #includeEngines(String...) + * @see #excludeEngines(String...) + * @see LauncherDiscoveryRequest + */ +@API(status = STABLE, since = "1.0") +public class EngineFilter implements Filter { + + /** + * Create a new include {@code EngineFilter} based on the + * supplied engine IDs. + * + *

Only {@code TestEngines} with matching engine IDs will be + * included within the test discovery and execution. + * + * @param engineIds the list of engine IDs to match against; never {@code null} + * or empty; individual IDs must also not be null or blank + * @see #includeEngines(String...) + */ + public static EngineFilter includeEngines(String... engineIds) { + return includeEngines(Arrays.asList(engineIds)); + } + + /** + * Create a new include {@code EngineFilter} based on the + * supplied engine IDs. + * + *

Only {@code TestEngines} with matching engine IDs will be + * included within the test discovery and execution. + * + * @param engineIds the list of engine IDs to match against; never {@code null} + * or empty; individual IDs must also not be null or blank + * @see #includeEngines(String...) + */ + public static EngineFilter includeEngines(List engineIds) { + return new EngineFilter(engineIds, Type.INCLUDE); + } + + /** + * Create a new exclude {@code EngineFilter} based on the + * supplied engine IDs. + * + *

{@code TestEngines} with matching engine IDs will be + * excluded from test discovery and execution. + * + * @param engineIds the list of engine IDs to match against; never {@code null} + * or empty; individual IDs must also not be null or blank + * @see #excludeEngines(List) + */ + public static EngineFilter excludeEngines(String... engineIds) { + return excludeEngines(Arrays.asList(engineIds)); + } + + /** + * Create a new exclude {@code EngineFilter} based on the + * supplied engine IDs. + * + *

{@code TestEngines} with matching engine IDs will be + * excluded from test discovery and execution. + * + * @param engineIds the list of engine IDs to match against; never {@code null} + * or empty; individual IDs must also not be null or blank + * @see #includeEngines(String...) + */ + public static EngineFilter excludeEngines(List engineIds) { + return new EngineFilter(engineIds, Type.EXCLUDE); + } + + private final List engineIds; + private final Type type; + + private EngineFilter(List engineIds, Type type) { + this.engineIds = validateAndTrim(engineIds); + this.type = type; + } + + @API(status = INTERNAL, since = "1.9") + public List getEngineIds() { + return engineIds; + } + + @API(status = INTERNAL, since = "1.9") + public boolean isIncludeFilter() { + return type == Type.INCLUDE; + } + + @Override + public FilterResult apply(TestEngine testEngine) { + Preconditions.notNull(testEngine, "TestEngine must not be null"); + String engineId = testEngine.getId(); + Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); + + if (this.type == Type.INCLUDE) { + return includedIf(this.engineIds.stream().anyMatch(engineId::equals), // + () -> String.format("Engine ID [%s] is in included list [%s]", engineId, this.engineIds), // + () -> String.format("Engine ID [%s] is not in included list [%s]", engineId, this.engineIds)); + } + else { + return includedIf(this.engineIds.stream().noneMatch(engineId::equals), // + () -> String.format("Engine ID [%s] is not in excluded list [%s]", engineId, this.engineIds), // + () -> String.format("Engine ID [%s] is in excluded list [%s]", engineId, this.engineIds)); + } + } + + @Override + public String toString() { + return String.format("%s that %s engines with IDs %s", getClass().getSimpleName(), this.type.verb, + this.engineIds); + } + + private static List validateAndTrim(List engineIds) { + Preconditions.notEmpty(engineIds, "engine ID list must not be null or empty"); + + // @formatter:off + return engineIds.stream() + .map(id -> Preconditions.notBlank(id, "engine ID must not be null or blank").trim()) + .distinct() + .collect(toList()); + // @formatter:on + } + + private enum Type { + + INCLUDE("includes"), + + EXCLUDE("excludes"); + + private final String verb; + + Type(String verb) { + this.verb = verb; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java new file mode 100644 index 00000000..2c3a96df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * The {@code Launcher} API is the main entry point for client code that + * wishes to discover and execute tests using one or more + * {@linkplain org.junit.platform.engine.TestEngine test engines}. + * + *

Implementations of this interface are responsible for determining + * the set of test engines to delegate to at runtime and for ensuring that + * each test engine has an + * {@linkplain org.junit.platform.engine.TestEngine#getId ID} that is unique + * among the registered test engines. For example, the default implementation + * returned by {@link org.junit.platform.launcher.core.LauncherFactory#create} + * dynamically discovers test engines via Java's {@link java.util.ServiceLoader + * ServiceLoader} mechanism. + * + *

Test discovery and execution require a {@link LauncherDiscoveryRequest} + * that is passed to all registered engines. Each engine decides which tests it + * can discover and execute according to the supplied request. + * + *

Prior to executing tests, clients of this interface should + * {@linkplain #registerTestExecutionListeners register} one or more + * {@link TestExecutionListener} instances in order to get feedback about the + * progress and results of test execution. Listeners will be notified of events + * in the order in which they were registered. The default implementation + * returned by {@link org.junit.platform.launcher.core.LauncherFactory#create} + * dynamically discovers test execution listeners via Java's + * {@link java.util.ServiceLoader ServiceLoader} mechanism. + * + * @since 1.0 + * @see LauncherDiscoveryRequest + * @see TestPlan + * @see TestExecutionListener + * @see org.junit.platform.launcher.core.LauncherFactory + * @see org.junit.platform.engine.TestEngine + */ +@API(status = STABLE, since = "1.0") +public interface Launcher { + + /** + * Register one or more listeners for test discovery. + * + * @param listeners the listeners to be notified of test discovery events; + * never {@code null} or empty + */ + @API(status = EXPERIMENTAL, since = "1.8") + void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners); + + /** + * Register one or more listeners for test execution. + * + * @param listeners the listeners to be notified of test execution events; + * never {@code null} or empty + */ + void registerTestExecutionListeners(TestExecutionListener... listeners); + + /** + * Discover tests and build a {@link TestPlan} according to the supplied + * {@link LauncherDiscoveryRequest} by querying all registered engines and + * collecting their results. + * + * @apiNote This method may be called to generate a preview of the test + * tree. The resulting {@link TestPlan} is unmodifiable and may be passed to + * {@link #execute(TestPlan, TestExecutionListener...)} for execution at + * most once. + * + * @param launcherDiscoveryRequest the launcher discovery request; never + * {@code null} + * @return an unmodifiable {@code TestPlan} that contains all resolved + * {@linkplain TestIdentifier identifiers} from all registered engines + */ + TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest); + + /** + * Execute a {@link TestPlan} which is built according to the supplied + * {@link LauncherDiscoveryRequest} by querying all registered engines and + * collecting their results, and notify + * {@linkplain #registerTestExecutionListeners registered listeners} about + * the progress and results of the execution. + * + *

Supplied test execution listeners are registered in addition to already + * registered listeners but only for the supplied launcher discovery request. + * + * @apiNote Calling this method will cause test discovery to be executed for + * all registered engines. If the same {@link LauncherDiscoveryRequest} was + * previously passed to {@link #discover(LauncherDiscoveryRequest)}, you + * should instead call {@link #execute(TestPlan, TestExecutionListener...)} + * and pass the already acquired {@link TestPlan} to avoid the potential + * performance degradation (e.g., classpath scanning) of running test + * discovery twice. + * + * @param launcherDiscoveryRequest the launcher discovery request; never {@code null} + * @param listeners additional test execution listeners; never {@code null} + */ + void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners); + + /** + * Execute the supplied {@link TestPlan} and notify + * {@linkplain #registerTestExecutionListeners registered listeners} about + * the progress and results of the execution. + * + *

Supplied test execution listeners are registered in addition to + * already registered listeners but only for the execution of the supplied + * test plan. + * + * @apiNote The supplied {@link TestPlan} must not have been executed + * previously. + * + * @param testPlan the test plan to execute; never {@code null} + * @param listeners additional test execution listeners; never {@code null} + * @since 1.4 + */ + @API(status = STABLE, since = "1.4") + void execute(TestPlan testPlan, TestExecutionListener... listeners); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java new file mode 100644 index 00000000..b4f5916f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -0,0 +1,142 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ClassNamePatternFilterUtils; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * Collection of constants related to {@link Launcher}. + * + * @since 1.3 + * @see org.junit.platform.engine.ConfigurationParameters + */ +@API(status = STABLE, since = "1.7") +public class LauncherConstants { + + /** + * Property name used to enable capturing output to {@link System#out}: + * {@value} + * + *

By default, output to {@link System#out} is not captured. + * + *

If enabled, the JUnit Platform captures the corresponding output and + * publishes it as a {@link ReportEntry} using the + * {@value #STDOUT_REPORT_ENTRY_KEY} key immediately before reporting the + * test identifier as finished. + * + * @see #STDOUT_REPORT_ENTRY_KEY + * @see ReportEntry + * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) + */ + public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; + + /** + * Property name used to enable capturing output to {@link System#err}: + * {@value} + * + *

By default, output to {@link System#err} is not captured. + * + *

If enabled, the JUnit Platform captures the corresponding output and + * publishes it as a {@link ReportEntry} using the + * {@value #STDERR_REPORT_ENTRY_KEY} key immediately before reporting the + * test identifier as finished. + * + * @see #STDERR_REPORT_ENTRY_KEY + * @see ReportEntry + * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) + */ + public static final String CAPTURE_STDERR_PROPERTY_NAME = "junit.platform.output.capture.stderr"; + + /** + * Property name used to configure the maximum number of bytes for buffering + * to use per thread and output type if output capturing is enabled: + * {@value} + * + *

Value must be an integer; defaults to {@value CAPTURE_MAX_BUFFER_DEFAULT}. + * + * @see #CAPTURE_MAX_BUFFER_DEFAULT + */ + public static final String CAPTURE_MAX_BUFFER_PROPERTY_NAME = "junit.platform.output.capture.maxBuffer"; + + /** + * Default maximum number of bytes for buffering to use per thread and + * output type if output capturing is enabled. + * + * @see #CAPTURE_MAX_BUFFER_PROPERTY_NAME + */ + public static final int CAPTURE_MAX_BUFFER_DEFAULT = 4 * 1024 * 1024; + + /** + * Key used to publish captured output to {@link System#out} as part of a + * {@link ReportEntry}: {@value} + */ + public static final String STDOUT_REPORT_ENTRY_KEY = "stdout"; + + /** + * Key used to publish captured output to {@link System#err} as part of a + * {@link ReportEntry}: {@value} + */ + public static final String STDERR_REPORT_ENTRY_KEY = "stderr"; + + /** + * Property name used to provide patterns for deactivating listeners registered + * via the {@link java.util.ServiceLoader ServiceLoader} mechanism: {@value} + * + *

Pattern Matching Syntax

+ * + *

If the property value consists solely of an asterisk ({@code *}), all + * listeners will be deactivated. Otherwise, the property value will be treated + * as a comma-separated list of patterns where each individual pattern will be + * matched against the fully qualified class name (FQCN) of each registered + * listener. Any dot ({@code .}) in a pattern will match against a dot ({@code .}) + * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match + * against one or more characters in a FQCN. All other characters in a pattern + * will be matched one-to-one against a FQCN. + * + *

Examples

+ * + *
    + *
  • {@code *}: deactivates all listeners. + *
  • {@code org.junit.*}: deactivates every listener under the {@code org.junit} + * base package and any of its subpackages. + *
  • {@code *.MyListener}: deactivates every listener whose simple class name is + * exactly {@code MyListener}. + *
  • {@code *System*, *Dev*}: deactivates every listener whose FQCN contains + * {@code System} or {@code Dev}. + *
  • {@code org.example.MyListener, org.example.TheirListener}: deactivates + * listeners whose FQCN is exactly {@code org.example.MyListener} or + * {@code org.example.TheirListener}. + *
+ * + * @see #DEACTIVATE_ALL_LISTENERS_PATTERN + * @see org.junit.platform.launcher.TestExecutionListener + */ + public static final String DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME = "junit.platform.execution.listeners.deactivate"; + + /** + * Wildcard pattern which signals that all listeners registered via the + * {@link java.util.ServiceLoader ServiceLoader} mechanism should be deactivated: + * {@value} + * + * @see #DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME + * @see org.junit.platform.launcher.TestExecutionListener + */ + public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; + + private LauncherConstants() { + /* no-op */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java new file mode 100644 index 00000000..90062041 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.platform.engine.EngineDiscoveryListener; +import org.junit.platform.engine.UniqueId; + +/** + * Register a concrete implementation of this interface with a + * {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} or + * {@link Launcher} to be notified of events that occur during test discovery. + * + *

All methods in this interface have empty default implementations. + * Concrete implementations may therefore override one or more of these methods + * to be notified of the selected events. + * + *

JUnit provides default implementations that are created via the factory + * methods in + * {@link org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners}. + * + *

The methods declared in this interface are called by the {@link Launcher} + * created via the {@link org.junit.platform.launcher.core.LauncherFactory} + * during test discovery. + * + * @since 1.6 + * @see org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners + * @see LauncherDiscoveryRequest#getDiscoveryListener() + * @see org.junit.platform.launcher.core.LauncherConfig.Builder#addLauncherDiscoveryListeners + */ +@API(status = EXPERIMENTAL, since = "1.6") +public interface LauncherDiscoveryListener extends EngineDiscoveryListener { + + /** + * No-op implementation of {@code LauncherDiscoveryListener} + */ + LauncherDiscoveryListener NOOP = new LauncherDiscoveryListener() { + }; + + /** + * Called when test discovery is about to be started. + * + * @param request the request for which discovery is being started + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + default void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { + } + + /** + * Called when test discovery has finished. + * + * @param request the request for which discovery has finished + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + default void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { + } + + /** + * Called when test discovery is about to be started for an engine. + * + * @param engineId the unique ID of the engine descriptor + */ + default void engineDiscoveryStarted(UniqueId engineId) { + } + + /** + * Called when test discovery has finished for an engine. + * + *

Exceptions thrown by implementations of this method will cause the + * complete test discovery to be aborted. + * + * @param engineId the unique ID of the engine descriptor + * @param result the discovery result of the supplied engine + * @see EngineDiscoveryResult + */ + default void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java new file mode 100644 index 00000000..1a91c9cc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.EngineDiscoveryRequest; + +/** + * {@code LauncherDiscoveryRequest} extends the {@link EngineDiscoveryRequest} API + * with additional filters that are applied by the {@link Launcher} itself. + * + *

Specifically, a {@code LauncherDiscoveryRequest} contains the following. + * + *

    + *
  • {@linkplain EngineFilter Engine Filters}: filters that are applied before + * each {@code TestEngine} is executed. All of them have to include an engine for it + * to contribute to the test plan.
  • + *
  • {@linkplain ConfigurationParameters Configuration Parameters}: configuration + * parameters that can be used to influence the discovery process
  • + *
  • {@linkplain DiscoverySelector Discovery Selectors}: components that select + * resources that a {@code TestEngine} can use to discover tests
  • + *
  • {@linkplain DiscoveryFilter Discovery Filters}: filters that should be applied + * by {@code TestEngines} during test discovery. All of them have to include a + * resource for it to end up in the test plan.
  • + *
  • {@linkplain PostDiscoveryFilter Post-Discovery Filters}: filters that will be + * applied by the {@code Launcher} after {@code TestEngines} have performed test + * discovery. All of them have to include a {@code TestDescriptor} for it to end up + * in the test plan.
  • + *
+ * + * @since 1.0 + * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder + * @see EngineDiscoveryRequest + * @see EngineFilter + * @see ConfigurationParameters + * @see DiscoverySelector + * @see DiscoveryFilter + * @see PostDiscoveryFilter + * @see #getEngineFilters() + * @see #getPostDiscoveryFilters() + */ +@API(status = STABLE, since = "1.0") +public interface LauncherDiscoveryRequest extends EngineDiscoveryRequest { + + /** + * Get the {@code EngineFilters} for this request. + * + *

The returned filters are to be combined using AND semantics, i.e. all + * of them have to include an engine for it to contribute to the test plan. + * + * @return the list of {@code EngineFilters} for this request; never + * {@code null} but potentially empty + */ + List getEngineFilters(); + + /** + * Get the {@code PostDiscoveryFilters} for this request. + * + *

The returned filters are to be combined using AND semantics, i.e. all + * of them have to include a {@code TestDescriptor} for it to end up in the + * test plan. + * + * @return the list of {@code PostDiscoveryFilters} for this request; never + * {@code null} but potentially empty + */ + List getPostDiscoveryFilters(); + + /** + * Get the {@link LauncherDiscoveryListener} for this request. + * + *

The default implementation returns a no-op listener that ignores all + * calls so that engines that call this methods can be used with an earlier + * version of the JUnit Platform that did not yet include it. + * + * @return the discovery listener; never {@code null} + * @since 1.6 + */ + @API(status = EXPERIMENTAL, since = "1.6") + @Override + default LauncherDiscoveryListener getDiscoveryListener() { + return LauncherDiscoveryListener.NOOP; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java new file mode 100644 index 00000000..b8e71c8d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.platform.launcher.core.LauncherFactory; + +/** + * The {@code LauncherSession} API is the main entry point for client code that + * wishes to repeatedly discover and execute tests using one + * or more {@linkplain org.junit.platform.engine.TestEngine test engines}. + * + * @since 1.8 + * @see Launcher + * @see LauncherSessionListener + * @see LauncherFactory + */ +@API(status = EXPERIMENTAL, since = "1.8") +public interface LauncherSession extends AutoCloseable { + + /** + * Get the {@link Launcher} associated with this session. + * + *

Any call to the launcher returned by this method after the session has + * been closed will throw an exception. + */ + Launcher getLauncher(); + + /** + * Close this session and notify all registered + * {@link LauncherSessionListener LauncherSessionListeners}. + * + * @apiNote The behavior of calling this method concurrently with any call + * to the {@link Launcher} returned by {@link #getLauncher()} is currently + * undefined. + */ + @Override + void close(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java new file mode 100644 index 00000000..96af1643 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.platform.launcher.core.LauncherConfig; +import org.junit.platform.launcher.core.LauncherFactory; + +/** + * Register an implementation of this interface to be notified when a + * {@link LauncherSession} is opened and closed. + * + *

A {@code LauncherSessionListener} can be registered programmatically with + * the {@link LauncherConfig.Builder#addLauncherSessionListeners LauncherConfig} + * passed to the + * {@link LauncherFactory#openSession(LauncherConfig) LauncherFactory} or + * automatically via Java's {@link java.util.ServiceLoader ServiceLoader} + * mechanism. + * + *

All methods in this class have empty default implementations. + * Subclasses may therefore override one or more of these methods to be notified + * of the selected events. + * + *

The methods declared in this interface are called by the {@link Launcher} + * or {@link LauncherSession} created via the {@link LauncherFactory}. + * + * @since 1.8 + * @see LauncherSession + * @see LauncherConfig.Builder#addLauncherSessionListeners + * @see LauncherFactory + */ +@API(status = EXPERIMENTAL, since = "1.8") +public interface LauncherSessionListener { + + /** + * No-op implementation of {@code LauncherSessionListener} + */ + LauncherSessionListener NOOP = new LauncherSessionListener() { + }; + + /** + * Called when a launcher session was opened. + * + * @param session the opened session + */ + default void launcherSessionOpened(LauncherSession session) { + } + + /** + * Called when a launcher session was closed. + * + * @param session the closed session + */ + default void launcherSessionClosed(LauncherSession session) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java new file mode 100644 index 00000000..7c31d55c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; + +/** + * A {@code PostDiscoveryFilter} is applied to {@link TestDescriptor TestDescriptors} + * after test discovery. + * + *

A {@code PostDiscoveryFilter} must not modify the + * {@link TestDescriptor TestDescriptors} it is applied to in any way. + * + *

{@link TestEngine TestEngines} must not apply + * {@code PostDiscoveryFilters} during the test discovery phase. + * + * @since 1.0 + * @see LauncherDiscoveryRequest + * @see TestEngine + */ +@API(status = STABLE, since = "1.0") +public interface PostDiscoveryFilter extends Filter { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java new file mode 100644 index 00000000..7389aa53 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java @@ -0,0 +1,185 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static java.util.Arrays.asList; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; + +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestTag; +import org.junit.platform.launcher.tagexpression.TagExpression; + +/** + * Factory methods for creating {@link PostDiscoveryFilter PostDiscoveryFilters} + * based on included and excluded tags or tag expressions. + * + *

Tag expressions are boolean expressions with the following allowed + * operators: {@code !} (not), {@code &} (and), and {@code |} (or). Parentheses + * can be used to adjust for operator precedence. Please refer to the + * JUnit 5 User Guide + * for usage examples. + * + *

Please note that a tag name is a valid tag expression. Thus, wherever a tag + * expression can be used, a single tag name can also be used. + * + * @since 1.0 + * @see #includeTags(String...) + * @see #excludeTags(String...) + * @see TestTag + */ +@API(status = STABLE, since = "1.0") +public final class TagFilter { + + private TagFilter() { + /* no-op */ + } + + /** + * Create an include filter based on the supplied tag expressions. + * + *

Containers and tests will only be executed if their tags match at + * least one of the supplied included tag expressions. + * + * @param tagExpressions the included tag expressions; never {@code null} or + * empty + * @throws PreconditionViolationException if the supplied tag expressions + * array is {@code null} or empty, or if any individual tag expression is + * not syntactically valid + * @see #includeTags(List) + * @see TestTag#isValid(String) + */ + public static PostDiscoveryFilter includeTags(String... tagExpressions) throws PreconditionViolationException { + Preconditions.notNull(tagExpressions, "array of tag expressions must not be null"); + return includeTags(asList(tagExpressions)); + } + + /** + * Create an include filter based on the supplied tag expressions. + * + *

Containers and tests will only be executed if their tags match at + * least one of the supplied included tag expressions. + * + * @param tagExpressions the included tag expressions; never {@code null} or + * empty + * @throws PreconditionViolationException if the supplied tag expressions + * array is {@code null} or empty, or if any individual tag expression is + * not syntactically valid + * @see #includeTags(String...) + * @see TestTag#isValid(String) + */ + public static PostDiscoveryFilter includeTags(List tagExpressions) throws PreconditionViolationException { + Preconditions.notEmpty(tagExpressions, "list of tag expressions must not be null or empty"); + return includeMatching(tagExpressions); + } + + /** + * Create an exclude filter based on the supplied tag expressions. + * + *

Containers and tests will only be executed if their tags do + * not match any of the supplied excluded tag expressions. + * + * @param tagExpressions the excluded tag expressions; never {@code null} or + * empty + * @throws PreconditionViolationException if the supplied tag expressions + * array is {@code null} or empty, or if any individual tag expression is + * not syntactically valid + * @see #excludeTags(List) + * @see TestTag#isValid(String) + */ + public static PostDiscoveryFilter excludeTags(String... tagExpressions) throws PreconditionViolationException { + Preconditions.notNull(tagExpressions, "array of tag expressions must not be null"); + return excludeTags(asList(tagExpressions)); + } + + /** + * Create an exclude filter based on the supplied tag expressions. + * + *

Containers and tests will only be executed if their tags do + * not match any of the supplied excluded tag expressions. + * + * @param tagExpressions the excluded tag expressions; never {@code null} or + * empty + * @throws PreconditionViolationException if the supplied tag expressions + * array is {@code null} or empty, or if any individual tag expression is + * not syntactically valid + * @see #excludeTags(String...) + * @see TestTag#isValid(String) + */ + public static PostDiscoveryFilter excludeTags(List tagExpressions) throws PreconditionViolationException { + Preconditions.notEmpty(tagExpressions, "list of tag expressions must not be null or empty"); + return excludeMatching(tagExpressions); + } + + private static PostDiscoveryFilter includeMatching(List tagExpressions) { + Supplier inclusionReason = () -> inclusionReasonExpressionSatisfy(tagExpressions); + Supplier exclusionReason = () -> exclusionReasonExpressionNotSatisfy(tagExpressions); + List parsedTagExpressions = parseAll(tagExpressions); + return descriptor -> { + Set tags = descriptor.getTags(); + boolean included = parsedTagExpressions.stream().anyMatch(expression -> expression.evaluate(tags)); + + return FilterResult.includedIf(included, inclusionReason, exclusionReason); + }; + } + + private static String inclusionReasonExpressionSatisfy(List tagExpressions) { + return String.format("included because tags match expression(s): [%s]", formatToString(tagExpressions)); + } + + private static String exclusionReasonExpressionNotSatisfy(List tagExpressions) { + return String.format("excluded because tags do not match tag expression(s): [%s]", + formatToString(tagExpressions)); + } + + private static PostDiscoveryFilter excludeMatching(List tagExpressions) { + Supplier inclusionReason = () -> inclusionReasonExpressionNotSatisfy(tagExpressions); + Supplier exclusionReason = () -> exclusionReasonExpressionSatisfy(tagExpressions); + List parsedTagExpressions = parseAll(tagExpressions); + return descriptor -> { + Set tags = descriptor.getTags(); + boolean included = parsedTagExpressions.stream().noneMatch(expression -> expression.evaluate(tags)); + + return FilterResult.includedIf(included, inclusionReason, exclusionReason); + }; + } + + private static String inclusionReasonExpressionNotSatisfy(List tagExpressions) { + return String.format("included because tags do not match expression(s): [%s]", formatToString(tagExpressions)); + } + + private static String exclusionReasonExpressionSatisfy(List tagExpressions) { + return String.format("excluded because tags match tag expression(s): [%s]", formatToString(tagExpressions)); + } + + private static String formatToString(List tagExpressions) { + return tagExpressions.stream().map(String::trim).sorted().collect(Collectors.joining(",")); + } + + private static List parseAll(List tagExpressions) { + return tagExpressions.stream().map(TagFilter::parse).collect(toUnmodifiableList()); + } + + private static TagExpression parse(String tagExpression) { + return TagExpression.parseFrom(tagExpression).tagExpressionOrThrow( + message -> new PreconditionViolationException( + "Unable to parse tag expression \"" + tagExpression + "\": " + message)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java new file mode 100644 index 00000000..fed40da4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java @@ -0,0 +1,175 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * Register a concrete implementation of this interface with a {@link Launcher} + * to be notified of events that occur during test execution. + * + *

All methods in this interface have empty default implementations. + * Concrete implementations may therefore override one or more of these methods + * to be notified of the selected events. + * + *

JUnit provides two example implementations. + * + *

    + *
  • {@link org.junit.platform.launcher.listeners.LoggingListener}
  • + *
  • {@link org.junit.platform.launcher.listeners.SummaryGeneratingListener}
  • + *
+ * + *

Contrary to JUnit 4, {@linkplain org.junit.platform.engine.TestEngine test engines} + * are supposed to report events not only for {@linkplain TestIdentifier identifiers} + * that represent executable leaves in the {@linkplain TestPlan test plan} but also + * for all intermediate containers. However, while both the JUnit Vintage and JUnit + * Jupiter engines comply with this contract, there is no way to guarantee this for + * third-party engines. + * + *

As of JUnit Platform 1.8, a {@code TestExecutionListener} can access + * {@linkplain org.junit.platform.engine.ConfigurationParameters configuration + * parameters} via the {@link TestPlan#getConfigurationParameters() + * getConfigurationParameters()} method in the {@code TestPlan} supplied to + * {@link #testPlanExecutionStarted(TestPlan)} and + * {@link #testPlanExecutionFinished(TestPlan)}. + * + *

Note on concurrency: {@link #testPlanExecutionStarted(TestPlan)} and + * {@link #testPlanExecutionFinished(TestPlan)} are always called from the same + * thread. It is safe to assume that there is at most one {@code TestPlan} + * instance at a time. All other methods could be called from different threads + * concurrently in case one or multiple test engines execute tests in parallel. + * + * @since 1.0 + * @see Launcher + * @see TestPlan + * @see TestIdentifier + */ +@API(status = STABLE, since = "1.0") +public interface TestExecutionListener { + + /** + * Called when the execution of the {@link TestPlan} has started, + * before any test has been executed. + * + *

Called from the same thread as {@link #testPlanExecutionFinished(TestPlan)}. + * + * @param testPlan describes the tree of tests about to be executed + */ + default void testPlanExecutionStarted(TestPlan testPlan) { + } + + /** + * Called when the execution of the {@link TestPlan} has finished, + * after all tests have been executed. + * + *

Called from the same thread as {@link #testPlanExecutionStarted(TestPlan)}. + * + * @param testPlan describes the tree of tests that have been executed + */ + default void testPlanExecutionFinished(TestPlan testPlan) { + } + + /** + * Called when a new, dynamic {@link TestIdentifier} has been registered. + * + *

A dynamic test is a test that is not known a-priori and + * therefore not contained in the original {@link TestPlan}. + * + * @param testIdentifier the identifier of the newly registered test + * or container + */ + default void dynamicTestRegistered(TestIdentifier testIdentifier) { + } + + /** + * Called when the execution of a leaf or subtree of the {@link TestPlan} + * has been skipped. + * + *

The {@link TestIdentifier} may represent a test or a container. In + * the case of a container, no listener methods will be called for any of + * its descendants. + * + *

A skipped test or subtree of tests will never be reported as + * {@linkplain #executionStarted started} or + * {@linkplain #executionFinished finished}. + * + * @param testIdentifier the identifier of the skipped test or container + * @param reason a human-readable message describing why the execution + * has been skipped + */ + default void executionSkipped(TestIdentifier testIdentifier, String reason) { + } + + /** + * Called when the execution of a leaf or subtree of the {@link TestPlan} + * is about to be started. + * + *

The {@link TestIdentifier} may represent a test or a container. + * + *

This method will only be called if the test or container has not + * been {@linkplain #executionSkipped skipped}. + * + *

This method will be called for a container {@code TestIdentifier} + * before {@linkplain #executionStarted starting} or + * {@linkplain #executionSkipped skipping} any of its children. + * + * @param testIdentifier the identifier of the started test or container + */ + default void executionStarted(TestIdentifier testIdentifier) { + } + + /** + * Called when the execution of a leaf or subtree of the {@link TestPlan} + * has finished, regardless of the outcome. + * + *

The {@link TestIdentifier} may represent a test or a container. + * + *

This method will only be called if the test or container has not + * been {@linkplain #executionSkipped skipped}. + * + *

This method will be called for a container {@code TestIdentifier} + * after all of its children have been + * {@linkplain #executionSkipped skipped} or have + * {@linkplain #executionFinished finished}. + * + *

The {@link TestExecutionResult} describes the result of the execution + * for the supplied {@code TestIdentifier}. The result does not include or + * aggregate the results of its children. For example, a container with a + * failing test will be reported as {@link Status#SUCCESSFUL SUCCESSFUL} even + * if one or more of its children are reported as {@link Status#FAILED FAILED}. + * + * @param testIdentifier the identifier of the finished test or container + * @param testExecutionResult the (unaggregated) result of the execution for + * the supplied {@code TestIdentifier} + * + * @see TestExecutionResult + */ + default void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + } + + /** + * Called when additional test reporting data has been published for + * the supplied {@link TestIdentifier}. + * + *

Can be called at any time during the execution of a test plan. + * + * @param testIdentifier describes the test or container to which the entry pertains + * @param entry the published {@code ReportEntry} + */ + default void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java new file mode 100644 index 00000000..20eaf356 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java @@ -0,0 +1,347 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.unmodifiableSet; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestDescriptor.Type; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; + +/** + * Immutable data transfer object that represents a test or container which is + * usually part of a {@link TestPlan}. + * + * @since 1.0 + * @see TestPlan + */ +@API(status = STABLE, since = "1.0") +public final class TestIdentifier implements Serializable { + + private static final long serialVersionUID = 1L; + private static final ObjectStreamField[] serialPersistentFields = ObjectStreamClass.lookup( + SerializedForm.class).getFields(); + + // These are effectively final but not technically due to late initialization when deserializing + private /* final */ UniqueId uniqueId; + private /* final */ UniqueId parentId; + private /* final */ String displayName; + private /* final */ String legacyReportingName; + private /* final */ TestSource source; + private /* final */ Set tags; + private /* final */ Type type; + + /** + * Factory for creating a new {@link TestIdentifier} from a {@link TestDescriptor}. + */ + @API(status = INTERNAL, since = "1.0") + public static TestIdentifier from(TestDescriptor testDescriptor) { + Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); + UniqueId uniqueId = testDescriptor.getUniqueId(); + String displayName = testDescriptor.getDisplayName(); + TestSource source = testDescriptor.getSource().orElse(null); + Set tags = testDescriptor.getTags(); + Type type = testDescriptor.getType(); + UniqueId parentId = testDescriptor.getParent().map(TestDescriptor::getUniqueId).orElse(null); + String legacyReportingName = testDescriptor.getLegacyReportingName(); + return new TestIdentifier(uniqueId, displayName, source, tags, type, parentId, legacyReportingName); + } + + private TestIdentifier(UniqueId uniqueId, String displayName, TestSource source, Set tags, Type type, + UniqueId parentId, String legacyReportingName) { + Preconditions.notNull(type, "TestDescriptor.Type must not be null"); + this.uniqueId = uniqueId; + this.parentId = parentId; + this.displayName = displayName; + this.source = source; + this.tags = copyOf(tags); + this.type = type; + this.legacyReportingName = legacyReportingName; + } + + private Set copyOf(Set tags) { + switch (tags.size()) { + case 0: + return emptySet(); + case 1: + return singleton(getOnlyElement(tags)); + default: + return new LinkedHashSet<>(tags); + } + } + + /** + * Get the unique ID of the represented test or container as a + * {@code String}. + * + *

Uniqueness must be guaranteed across an entire + * {@linkplain TestPlan test plan}, regardless of how many engines are used + * behind the scenes. + * + * @return the unique ID for this identifier; never {@code null} + */ + public String getUniqueId() { + return this.uniqueId.toString(); + } + + /** + * Get the unique ID of the represented test or container as a + * {@code UniqueId}. + * + *

Uniqueness must be guaranteed across an entire + * {@linkplain TestPlan test plan}, regardless of how many engines are used + * behind the scenes. + * + * @return the unique ID for this identifier; never {@code null} + * @since 5.8 + */ + @API(status = STABLE, since = "5.8") + public UniqueId getUniqueIdObject() { + return this.uniqueId; + } + + /** + * Get the unique ID of this identifier's parent as a {@code String}, if + * available. + * + *

An identifier without a parent is called a root. + * + * @return a container for the unique ID for this identifier's parent; + * never {@code null} though potentially empty + */ + public Optional getParentId() { + return getParentIdObject().map(UniqueId::toString); + } + + /** + * Get the unique ID of this identifier's parent as a {@code UniqueId}, if + * available. + * + *

An identifier without a parent is called a root. + * + * @return a container for the unique ID for this identifier's parent; + * never {@code null} though potentially empty + * @since 5.8 + */ + @API(status = STABLE, since = "5.8") + public Optional getParentIdObject() { + return Optional.ofNullable(this.parentId); + } + + /** + * Get the display name of the represented test or container. + * + *

A display name is a human-readable name for a test or + * container that is typically used for test reporting in IDEs and build + * tools. Display names may contain spaces, special characters, and emoji, + * and the format may be customized by {@link org.junit.platform.engine.TestEngine + * TestEngines} or potentially by end users as well. Consequently, display + * names should never be parsed; rather, they should be used for display + * purposes only. + * + * @return the display name for this identifier; never {@code null} or blank + * @see #getSource() + * @see org.junit.platform.engine.TestDescriptor#getDisplayName() + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * Get the name of this identifier in a format that is suitable for legacy + * reporting infrastructure — for example, for reporting systems built + * on the Ant-based XML reporting format for JUnit 4. + * + *

The default implementation delegates to {@link #getDisplayName()}. + * + * @return the legacy reporting name; never {@code null} or blank + * @see org.junit.platform.engine.TestDescriptor#getLegacyReportingName() + * @see org.junit.platform.reporting.legacy.LegacyReportingUtils + */ + @SuppressWarnings("JavadocReference") + public String getLegacyReportingName() { + return this.legacyReportingName; + } + + /** + * Get the underlying descriptor type. + * + * @return the underlying descriptor type; never {@code null} + */ + public Type getType() { + return type; + } + + /** + * Determine if this identifier represents a test. + * + * @return {@code true} if the underlying descriptor type represents a test, + * {@code false} otherwise + * @see Type#isTest() + */ + public boolean isTest() { + return getType().isTest(); + } + + /** + * Determine if this identifier represents a container. + * + * @return {@code true} if the underlying descriptor type represents a container, + * {@code false} otherwise + * @see Type#isContainer() + */ + public boolean isContainer() { + return getType().isContainer(); + } + + /** + * Get the {@linkplain TestSource source} of the represented test + * or container, if available. + * + * @see TestSource + */ + public Optional getSource() { + return Optional.ofNullable(this.source); + } + + /** + * Get the set of {@linkplain TestTag tags} associated with the represented + * test or container. + * + * @see TestTag + */ + public Set getTags() { + return unmodifiableSet(this.tags); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TestIdentifier) { + TestIdentifier that = (TestIdentifier) obj; + return Objects.equals(this.uniqueId, that.uniqueId); + } + return false; + } + + @Override + public int hashCode() { + return this.uniqueId.hashCode(); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("uniqueId", this.uniqueId) + .append("parentId", this.parentId) + .append("displayName", this.displayName) + .append("legacyReportingName", this.legacyReportingName) + .append("source", this.source) + .append("tags", this.tags) + .append("type", this.type) + .toString(); + // @formatter:on + } + + private void writeObject(ObjectOutputStream s) throws IOException { + SerializedForm serializedForm = new SerializedForm(this); + serializedForm.serialize(s); + } + + private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { + SerializedForm serializedForm = SerializedForm.deserialize(s); + uniqueId = UniqueId.parse(serializedForm.uniqueId); + displayName = serializedForm.displayName; + source = serializedForm.source; + tags = serializedForm.tags; + type = serializedForm.type; + parentId = UniqueId.parse(serializedForm.parentId); + legacyReportingName = serializedForm.legacyReportingName; + } + + /** + * Represents the serialized output of {@code TestIdentifier}. The fields on this + * class match the fields that {@code TestIdentifier} had prior to 5.8. + */ + private static class SerializedForm implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String uniqueId; + private final String parentId; + private final String displayName; + private final String legacyReportingName; + private final TestSource source; + @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (see TestIdentifier#copyOf()) + private final Set tags; + private final Type type; + + SerializedForm(TestIdentifier testIdentifier) { + this.uniqueId = testIdentifier.uniqueId.toString(); + this.parentId = testIdentifier.parentId.toString(); + this.displayName = testIdentifier.displayName; + this.legacyReportingName = testIdentifier.legacyReportingName; + this.source = testIdentifier.source; + this.tags = testIdentifier.tags; + this.type = testIdentifier.type; + } + + @SuppressWarnings("unchecked") + private SerializedForm(ObjectInputStream.GetField fields) throws IOException { + this.uniqueId = (String) fields.get("uniqueId", null); + this.parentId = (String) fields.get("parentId", null); + this.displayName = (String) fields.get("displayName", null); + this.legacyReportingName = (String) fields.get("legacyReportingName", null); + this.source = (TestSource) fields.get("source", null); + this.tags = (Set) fields.get("tags", null); + this.type = (Type) fields.get("type", null); + } + + void serialize(ObjectOutputStream s) throws IOException { + ObjectOutputStream.PutField fields = s.putFields(); + fields.put("uniqueId", uniqueId); + fields.put("parentId", parentId); + fields.put("displayName", displayName); + fields.put("legacyReportingName", legacyReportingName); + fields.put("source", source); + fields.put("tags", tags); + fields.put("type", type); + s.writeFields(); + } + + static SerializedForm deserialize(ObjectInputStream s) throws ClassNotFoundException, IOException { + ObjectInputStream.GetField fields = s.readFields(); + return new SerializedForm(fields); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java new file mode 100644 index 00000000..0557b865 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java @@ -0,0 +1,294 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static java.util.Collections.emptySet; +import static java.util.Collections.synchronizedSet; +import static java.util.Collections.unmodifiableSet; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestDescriptor.Visitor; +import org.junit.platform.engine.UniqueId; + +/** + * {@code TestPlan} describes the tree of tests and containers as discovered + * by a {@link Launcher}. + * + *

Tests and containers are represented by {@link TestIdentifier} instances. + * The complete set of identifiers comprises a tree-like structure. However, + * each identifier only stores the unique ID of its parent. This class provides + * a number of helpful methods to retrieve the + * {@linkplain #getParent(TestIdentifier) parent}, + * {@linkplain #getChildren(TestIdentifier) children}, and + * {@linkplain #getDescendants(TestIdentifier) descendants} of an identifier. + * + *

While the contained instances of {@link TestIdentifier} are immutable, + * instances of this class contain mutable state. For example, when a dynamic + * test is registered at runtime, it is added to the original test plan and + * reported to {@link TestExecutionListener} implementations. + * + *

This class is not intended to be extended by clients. + * + * @since 1.0 + * @see Launcher + * @see TestExecutionListener + */ +@API(status = STABLE, since = "1.0") +public class TestPlan { + + private final Set roots = synchronizedSet(new LinkedHashSet<>(4)); + + private final Map> children = new ConcurrentHashMap<>(32); + + private final Map allIdentifiers = new ConcurrentHashMap<>(32); + + private final boolean containsTests; + + private final ConfigurationParameters configurationParameters; + + /** + * Construct a new {@code TestPlan} from the supplied collection of + * {@link TestDescriptor TestDescriptors}. + * + *

Each supplied {@code TestDescriptor} is expected to be a descriptor + * for a {@link org.junit.platform.engine.TestEngine TestEngine}. + * + * @param engineDescriptors the engine test descriptors from which the test + * plan should be created; never {@code null} + * @param configurationParameters the {@code ConfigurationParameters} for + * this test plan; never {@code null} + * @return a new test plan + */ + @API(status = INTERNAL, since = "1.0") + public static TestPlan from(Collection engineDescriptors, + ConfigurationParameters configurationParameters) { + Preconditions.notNull(engineDescriptors, "Cannot create TestPlan from a null collection of TestDescriptors"); + Preconditions.notNull(configurationParameters, "Cannot create TestPlan from null ConfigurationParameters"); + TestPlan testPlan = new TestPlan(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests), + configurationParameters); + Visitor visitor = descriptor -> testPlan.addInternal(TestIdentifier.from(descriptor)); + engineDescriptors.forEach(engineDescriptor -> engineDescriptor.accept(visitor)); + return testPlan; + } + + @API(status = INTERNAL, since = "1.4") + protected TestPlan(boolean containsTests, ConfigurationParameters configurationParameters) { + this.containsTests = containsTests; + this.configurationParameters = configurationParameters; + } + + /** + * Add the supplied {@link TestIdentifier} to this test plan. + * + * @param testIdentifier the identifier to add; never {@code null} + * @deprecated Calling this method is no longer supported and will throw an + * exception. + * @throws JUnitException always + */ + @Deprecated + @API(status = DEPRECATED, since = "1.4") + public void add(@SuppressWarnings("unused") TestIdentifier testIdentifier) { + throw new JUnitException("Unsupported attempt to modify the TestPlan was detected. " + + "Please contact your IDE/tool vendor and request a fix or downgrade to JUnit 5.7.x (see https://github.com/junit-team/junit5/issues/1732 for details)."); + } + + @API(status = INTERNAL, since = "1.8") + public void addInternal(TestIdentifier testIdentifier) { + Preconditions.notNull(testIdentifier, "testIdentifier must not be null"); + allIdentifiers.put(testIdentifier.getUniqueIdObject(), testIdentifier); + + // Root identifiers. Typically, a test engine. + if (!testIdentifier.getParentIdObject().isPresent()) { + roots.add(testIdentifier); + return; + } + + // Identifiers without a parent in this test plan. Could be a test + // engine that is used in a suite. + UniqueId parentId = testIdentifier.getParentIdObject().get(); + if (!allIdentifiers.containsKey(parentId)) { + roots.add(testIdentifier); + return; + } + + Set directChildren = children.computeIfAbsent(parentId, + key -> synchronizedSet(new LinkedHashSet<>(16))); + directChildren.add(testIdentifier); + } + + /** + * Get the root {@link TestIdentifier TestIdentifiers} for this test plan. + * + * @return an unmodifiable set of the root identifiers + */ + public Set getRoots() { + return unmodifiableSet(roots); + } + + /** + * Get the parent of the supplied {@link TestIdentifier}. + * + * @param child the identifier to look up the parent for; never {@code null} + * @return an {@code Optional} containing the parent, if present + */ + public Optional getParent(TestIdentifier child) { + Preconditions.notNull(child, "child must not be null"); + return child.getParentIdObject().map(this::getTestIdentifier); + } + + /** + * Get the children of the supplied {@link TestIdentifier}. + * + * @param parent the identifier to look up the children for; never {@code null} + * @return an unmodifiable set of the parent's children, potentially empty + * @see #getChildren(UniqueId) + */ + public Set getChildren(TestIdentifier parent) { + Preconditions.notNull(parent, "parent must not be null"); + return getChildren(parent.getUniqueIdObject()); + } + + /** + * Get the children of the supplied unique ID. + * + * @param parentId the unique ID to look up the children for; never + * {@code null} or blank + * @return an unmodifiable set of the parent's children, potentially empty + * @see #getChildren(TestIdentifier) + * @deprecated Use {@link #getChildren(UniqueId)} + */ + @API(status = DEPRECATED, since = "1.10") + @Deprecated + public Set getChildren(String parentId) { + Preconditions.notBlank(parentId, "parent ID must not be null or blank"); + return getChildren(UniqueId.parse(parentId)); + } + + /** + * Get the children of the supplied unique ID. + * + * @param parentId the unique ID to look up the children for; never + * {@code null} + * @return an unmodifiable set of the parent's children, potentially empty + * @see #getChildren(TestIdentifier) + */ + @API(status = MAINTAINED, since = "1.10") + public Set getChildren(UniqueId parentId) { + return children.containsKey(parentId) ? unmodifiableSet(children.get(parentId)) : emptySet(); + } + + /** + * Get the {@link TestIdentifier} with the supplied unique ID. + * + * @param uniqueId the unique ID to look up the identifier for; never + * {@code null} or blank + * @return the identifier with the supplied unique ID; never {@code null} + * @throws PreconditionViolationException if no {@code TestIdentifier} + * with the supplied unique ID is present in this test plan + * @deprecated Use {@link #getTestIdentifier(UniqueId)} + */ + @API(status = DEPRECATED, since = "1.10") + @Deprecated + public TestIdentifier getTestIdentifier(String uniqueId) throws PreconditionViolationException { + Preconditions.notBlank(uniqueId, "unique ID must not be null or blank"); + return getTestIdentifier(UniqueId.parse(uniqueId)); + } + + /** + * Get the {@link TestIdentifier} with the supplied unique ID. + * + * @param uniqueId the unique ID to look up the identifier for; never + * {@code null} + * @return the identifier with the supplied unique ID; never {@code null} + * @throws PreconditionViolationException if no {@code TestIdentifier} + * with the supplied unique ID is present in this test plan + */ + @API(status = MAINTAINED, since = "1.10") + public TestIdentifier getTestIdentifier(UniqueId uniqueId) { + Preconditions.notNull(uniqueId, () -> "uniqueId must not be null"); + return Preconditions.notNull(allIdentifiers.get(uniqueId), + () -> "No TestIdentifier with unique ID [" + uniqueId + "] has been added to this TestPlan."); + } + + /** + * Count all {@link TestIdentifier TestIdentifiers} that satisfy the + * given {@linkplain Predicate predicate}. + * + * @param predicate a predicate which returns {@code true} for identifiers + * to be counted; never {@code null} + * @return the number of identifiers that satisfy the supplied predicate + */ + public long countTestIdentifiers(Predicate predicate) { + Preconditions.notNull(predicate, "Predicate must not be null"); + return allIdentifiers.values().stream().filter(predicate).count(); + } + + /** + * Get all descendants of the supplied {@link TestIdentifier} (i.e., + * all of its children and their children, recursively). + * + * @param parent the identifier to look up the descendants for; never {@code null} + * @return an unmodifiable set of the parent's descendants, potentially empty + */ + public Set getDescendants(TestIdentifier parent) { + Preconditions.notNull(parent, "parent must not be null"); + Set result = new LinkedHashSet<>(16); + Set children = getChildren(parent); + result.addAll(children); + for (TestIdentifier child : children) { + result.addAll(getDescendants(child)); + } + return unmodifiableSet(result); + } + + /** + * Return whether this test plan contains any tests. + * + *

A test plan contains tests, if at least one of the contained engine + * descriptors {@linkplain TestDescriptor#containsTests(TestDescriptor) + * contains tests}. + * + * @return {@code true} if this test plan contains tests + * @see TestDescriptor#containsTests(TestDescriptor) + */ + public boolean containsTests() { + return containsTests; + } + + /** + * Get the {@link ConfigurationParameters} for this test plan. + * + * @return the configuration parameters; never {@code null} + * @since 1.8 + */ + @API(status = MAINTAINED, since = "1.8") + public ConfigurationParameters getConfigurationParameters() { + return this.configurationParameters; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java new file mode 100644 index 00000000..fc3cf561 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; + +class CompositeEngineExecutionListener implements EngineExecutionListener { + + private static final Logger logger = LoggerFactory.getLogger(CompositeEngineExecutionListener.class); + + private final List engineExecutionListeners; + + CompositeEngineExecutionListener(List engineExecutionListeners) { + this.engineExecutionListeners = new ArrayList<>(engineExecutionListeners); + } + + @Override + public void dynamicTestRegistered(TestDescriptor testDescriptor) { + notifyEach(engineExecutionListeners, listener -> listener.dynamicTestRegistered(testDescriptor), + () -> "dynamicTestRegistered(" + testDescriptor + ")"); + } + + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + notifyEach(engineExecutionListeners, listener -> listener.executionSkipped(testDescriptor, reason), + () -> "executionSkipped(" + testDescriptor + ", " + reason + ")"); + } + + @Override + public void executionStarted(TestDescriptor testDescriptor) { + notifyEach(engineExecutionListeners, listener -> listener.executionStarted(testDescriptor), + () -> "executionStarted(" + testDescriptor + ")"); + } + + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + notifyEach(engineExecutionListeners, + listener -> listener.executionFinished(testDescriptor, testExecutionResult), + () -> "executionFinished(" + testDescriptor + ", " + testExecutionResult + ")"); + } + + @Override + public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + notifyEach(engineExecutionListeners, listener -> listener.reportingEntryPublished(testDescriptor, entry), + () -> "reportingEntryPublished(" + testDescriptor + ", " + entry + ")"); + } + + private static void notifyEach(List listeners, Consumer consumer, + Supplier description) { + listeners.forEach(listener -> { + try { + consumer.accept(listener); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + logger.warn(throwable, + () -> String.format("EngineExecutionListener [%s] threw exception for method: %s", + listener.getClass().getName(), description.get())); + } + }); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java new file mode 100644 index 00000000..a8c5d7ad --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.stream.Collectors.toList; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +class CompositeTestExecutionListener implements TestExecutionListener { + + private static final Logger logger = LoggerFactory.getLogger(CompositeTestExecutionListener.class); + + private final List testExecutionListeners; + private final List eagerTestExecutionListeners; + + CompositeTestExecutionListener(List testExecutionListeners) { + this.testExecutionListeners = new ArrayList<>(testExecutionListeners); + this.eagerTestExecutionListeners = this.testExecutionListeners.stream() // + .filter(EagerTestExecutionListener.class::isInstance) // + .map(EagerTestExecutionListener.class::cast) // + .collect(toList()); + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + notifyEach(testExecutionListeners, listener -> listener.dynamicTestRegistered(testIdentifier), + () -> "dynamicTestRegistered(" + testIdentifier + ")"); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + notifyEach(testExecutionListeners, listener -> listener.executionSkipped(testIdentifier, reason), + () -> "executionSkipped(" + testIdentifier + ", " + reason + ")"); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + notifyEach(eagerTestExecutionListeners, listener -> listener.executionJustStarted(testIdentifier), + () -> "executionJustStarted(" + testIdentifier + ")"); + notifyEach(testExecutionListeners, listener -> listener.executionStarted(testIdentifier), + () -> "executionStarted(" + testIdentifier + ")"); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + notifyEach(eagerTestExecutionListeners, + listener -> listener.executionJustFinished(testIdentifier, testExecutionResult), + () -> "executionJustFinished(" + testIdentifier + ", " + testExecutionResult + ")"); + notifyEach(testExecutionListeners, listener -> listener.executionFinished(testIdentifier, testExecutionResult), + () -> "executionFinished(" + testIdentifier + ", " + testExecutionResult + ")"); + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + notifyEach(testExecutionListeners, listener -> listener.testPlanExecutionStarted(testPlan), + () -> "testPlanExecutionStarted(" + testPlan + ")"); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + notifyEach(testExecutionListeners, listener -> listener.testPlanExecutionFinished(testPlan), + () -> "testPlanExecutionFinished(" + testPlan + ")"); + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + notifyEach(testExecutionListeners, listener -> listener.reportingEntryPublished(testIdentifier, entry), + () -> "reportingEntryPublished(" + testIdentifier + ", " + entry + ")"); + } + + private static void notifyEach(List listeners, Consumer consumer, + Supplier description) { + listeners.forEach(listener -> { + try { + consumer.accept(listener); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + logger.warn(throwable, () -> String.format("TestExecutionListener [%s] threw exception for method: %s", + listener.getClass().getName(), description.get())); + } + }); + } + + interface EagerTestExecutionListener extends TestExecutionListener { + default void executionJustStarted(TestIdentifier testIdentifier) { + } + + default void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java new file mode 100644 index 00000000..422f9592 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.Collections.unmodifiableList; +import static java.util.stream.Collectors.toList; + +import java.util.List; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.launcher.EngineFilter; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.PostDiscoveryFilter; + +/** + * {@code DefaultDiscoveryRequest} is the default implementation of the + * {@link EngineDiscoveryRequest} and {@link LauncherDiscoveryRequest} APIs. + * + * @since 1.0 + */ +final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { + + // Selectors provided to the engines to be used for discovering tests + private final List selectors; + + // Filters based on engines + private final List engineFilters; + + // Discovery filters are handed through to all engines to be applied during discovery. + private final List> discoveryFilters; + + // Descriptor filters are applied by the launcher itself after engines have performed discovery. + private final List postDiscoveryFilters; + + // Configuration parameters can be used to provide custom configuration to engines, e.g. for extensions + private final LauncherConfigurationParameters configurationParameters; + + // Listener for test discovery that may abort on errors. + private final LauncherDiscoveryListener discoveryListener; + + DefaultDiscoveryRequest(List selectors, List engineFilters, + List> discoveryFilters, List postDiscoveryFilters, + LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener) { + this.selectors = selectors; + this.engineFilters = engineFilters; + this.discoveryFilters = discoveryFilters; + this.postDiscoveryFilters = postDiscoveryFilters; + this.configurationParameters = configurationParameters; + this.discoveryListener = discoveryListener; + } + + @Override + public List getSelectorsByType(Class selectorType) { + Preconditions.notNull(selectorType, "selectorType must not be null"); + return this.selectors.stream().filter(selectorType::isInstance).map(selectorType::cast).collect(toList()); + } + + @Override + public List getEngineFilters() { + return unmodifiableList(this.engineFilters); + } + + @Override + public > List getFiltersByType(Class filterType) { + Preconditions.notNull(filterType, "filterType must not be null"); + return this.discoveryFilters.stream().filter(filterType::isInstance).map(filterType::cast).collect(toList()); + } + + @Override + public List getPostDiscoveryFilters() { + return unmodifiableList(this.postDiscoveryFilters); + } + + @Override + public ConfigurationParameters getConfigurationParameters() { + return this.configurationParameters; + } + + @Override + public LauncherDiscoveryListener getDiscoveryListener() { + return discoveryListener; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java new file mode 100644 index 00000000..3b0dc3e0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -0,0 +1,117 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.Collections.unmodifiableCollection; +import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY; +import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; + +import java.util.Collection; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +/** + * Default implementation of the {@link Launcher} API. + * + *

External clients can obtain an instance by invoking + * {@link LauncherFactory#create()}. + * + * @since 1.0 + * @see Launcher + * @see LauncherFactory + */ +class DefaultLauncher implements InternalLauncher { + + private final ListenerRegistry launcherDiscoveryListenerRegistry = ListenerRegistry.forLauncherDiscoveryListeners(); + private final ListenerRegistry testExecutionListenerRegistry = ListenerRegistry.forTestExecutionListeners(); + private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator( + testExecutionListenerRegistry); + private final EngineDiscoveryOrchestrator discoveryOrchestrator; + + /** + * Construct a new {@code DefaultLauncher} with the supplied test engines. + * + * @param testEngines the test engines to delegate to; never {@code null} or + * empty + * @param postDiscoveryFilters the additional post discovery filters for + * discovery requests; never {@code null} + */ + DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters) { + Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(), + () -> "Cannot create Launcher without at least one TestEngine; " + + "consider adding an engine implementation JAR to the classpath"); + Preconditions.notNull(postDiscoveryFilters, "PostDiscoveryFilter array must not be null"); + Preconditions.containsNoNullElements(postDiscoveryFilters, + "PostDiscoveryFilter array must not contain null elements"); + this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, + unmodifiableCollection(postDiscoveryFilters), launcherDiscoveryListenerRegistry); + } + + @Override + public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { + this.launcherDiscoveryListenerRegistry.addAll(listeners); + } + + @Override + public void registerTestExecutionListeners(TestExecutionListener... listeners) { + this.testExecutionListenerRegistry.addAll(listeners); + } + + @Override + public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { + Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); + return InternalTestPlan.from(discover(discoveryRequest, DISCOVERY)); + } + + @Override + public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { + Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); + Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); + Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); + execute(InternalTestPlan.from(discover(discoveryRequest, EXECUTION)), listeners); + } + + @Override + public void execute(TestPlan testPlan, TestExecutionListener... listeners) { + Preconditions.notNull(testPlan, "TestPlan must not be null"); + Preconditions.condition(testPlan instanceof InternalTestPlan, "TestPlan was not returned by this Launcher"); + Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); + Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); + execute((InternalTestPlan) testPlan, listeners); + } + + @Override + public ListenerRegistry getTestExecutionListenerRegistry() { + return testExecutionListenerRegistry; + } + + @Override + public ListenerRegistry getLauncherDiscoveryListenerRegistry() { + return launcherDiscoveryListenerRegistry; + } + + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, + EngineDiscoveryOrchestrator.Phase phase) { + return discoveryOrchestrator.discover(discoveryRequest, phase); + } + + private void execute(InternalTestPlan internalTestPlan, TestExecutionListener[] listeners) { + executionOrchestrator.execute(internalTestPlan, listeners); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java new file mode 100644 index 00000000..500cdde2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.Collections.unmodifiableCollection; + +import java.util.Collection; + +import org.junit.platform.engine.TestEngine; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.TestExecutionListener; + +/** + * Default implementation of the {@link LauncherConfig} API. + * + * @since 1.3 + */ +class DefaultLauncherConfig implements LauncherConfig { + + private final boolean testEngineAutoRegistrationEnabled; + private final boolean launcherSessionListenerAutoRegistrationEnabled; + private final boolean launcherDiscoveryListenerAutoRegistrationEnabled; + private final boolean testExecutionListenerAutoRegistrationEnabled; + private final boolean postDiscoveryFilterAutoRegistrationEnabled; + private final Collection additionalTestEngines; + private final Collection additionalLauncherSessionListeners; + private final Collection additionalLauncherDiscoveryListeners; + private final Collection additionalTestExecutionListeners; + private final Collection additionalPostDiscoveryFilters; + + DefaultLauncherConfig(boolean testEngineAutoRegistrationEnabled, + boolean launcherSessionListenerAutoRegistrationEnabled, + boolean launcherDiscoveryListenerAutoRegistrationEnabled, + boolean testExecutionListenerAutoRegistrationEnabled, boolean postDiscoveryFilterAutoRegistrationEnabled, + Collection additionalTestEngines, + Collection additionalLauncherSessionListeners, + Collection additionalLauncherDiscoveryListeners, + Collection additionalTestExecutionListeners, + Collection additionalPostDiscoveryFilters) { + this.launcherSessionListenerAutoRegistrationEnabled = launcherSessionListenerAutoRegistrationEnabled; + this.launcherDiscoveryListenerAutoRegistrationEnabled = launcherDiscoveryListenerAutoRegistrationEnabled; + this.testExecutionListenerAutoRegistrationEnabled = testExecutionListenerAutoRegistrationEnabled; + this.testEngineAutoRegistrationEnabled = testEngineAutoRegistrationEnabled; + this.postDiscoveryFilterAutoRegistrationEnabled = postDiscoveryFilterAutoRegistrationEnabled; + this.additionalTestEngines = unmodifiableCollection(additionalTestEngines); + this.additionalLauncherSessionListeners = unmodifiableCollection(additionalLauncherSessionListeners); + this.additionalLauncherDiscoveryListeners = unmodifiableCollection(additionalLauncherDiscoveryListeners); + this.additionalTestExecutionListeners = unmodifiableCollection(additionalTestExecutionListeners); + this.additionalPostDiscoveryFilters = unmodifiableCollection(additionalPostDiscoveryFilters); + } + + @Override + public boolean isTestEngineAutoRegistrationEnabled() { + return this.testEngineAutoRegistrationEnabled; + } + + @Override + public boolean isLauncherSessionListenerAutoRegistrationEnabled() { + return launcherSessionListenerAutoRegistrationEnabled; + } + + @Override + public boolean isLauncherDiscoveryListenerAutoRegistrationEnabled() { + return launcherDiscoveryListenerAutoRegistrationEnabled; + } + + @Override + public boolean isTestExecutionListenerAutoRegistrationEnabled() { + return this.testExecutionListenerAutoRegistrationEnabled; + } + + @Override + public boolean isPostDiscoveryFilterAutoRegistrationEnabled() { + return this.postDiscoveryFilterAutoRegistrationEnabled; + } + + @Override + public Collection getAdditionalTestEngines() { + return this.additionalTestEngines; + } + + @Override + public Collection getAdditionalLauncherSessionListeners() { + return additionalLauncherSessionListeners; + } + + @Override + public Collection getAdditionalLauncherDiscoveryListeners() { + return additionalLauncherDiscoveryListeners; + } + + @Override + public Collection getAdditionalTestExecutionListeners() { + return this.additionalTestExecutionListeners; + } + + @Override + public Collection getAdditionalPostDiscoveryFilters() { + return this.additionalPostDiscoveryFilters; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java new file mode 100644 index 00000000..79f6d815 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -0,0 +1,127 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.8 + */ +class DefaultLauncherSession implements LauncherSession { + + private final DelegatingLauncher launcher; + private final LauncherSessionListener listener; + + DefaultLauncherSession(Launcher launcher, LauncherSessionListener listener) { + this.launcher = new DelegatingLauncher(launcher); + this.listener = listener; + listener.launcherSessionOpened(this); + } + + @Override + public Launcher getLauncher() { + return launcher; + } + + LauncherSessionListener getListener() { + return listener; + } + + @Override + public void close() { + if (launcher.getDelegate() != ClosedLauncher.INSTANCE) { + launcher.setDelegate(ClosedLauncher.INSTANCE); + listener.launcherSessionClosed(this); + } + } + + private static class DelegatingLauncher implements Launcher { + + private Launcher delegate; + + DelegatingLauncher(Launcher delegate) { + this.delegate = delegate; + } + + public Launcher getDelegate() { + return delegate; + } + + public void setDelegate(Launcher delegate) { + this.delegate = delegate; + } + + @Override + public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { + delegate.registerLauncherDiscoveryListeners(listeners); + } + + @Override + public void registerTestExecutionListeners(TestExecutionListener... listeners) { + delegate.registerTestExecutionListeners(listeners); + } + + @Override + public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return delegate.discover(launcherDiscoveryRequest); + } + + @Override + public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { + delegate.execute(launcherDiscoveryRequest, listeners); + } + + @Override + public void execute(TestPlan testPlan, TestExecutionListener... listeners) { + delegate.execute(testPlan, listeners); + } + } + + private static class ClosedLauncher implements Launcher { + + static final ClosedLauncher INSTANCE = new ClosedLauncher(); + + private ClosedLauncher() { + } + + @Override + public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } + + @Override + public void registerTestExecutionListeners(TestExecutionListener... listeners) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } + + @Override + public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } + + @Override + public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } + + @Override + public void execute(TestPlan testPlan, TestExecutionListener... listeners) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java new file mode 100644 index 00000000..e2f41053 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * @since 1.6 + */ +class DelegatingEngineExecutionListener implements EngineExecutionListener { + + private final EngineExecutionListener delegate; + + DelegatingEngineExecutionListener(EngineExecutionListener delegate) { + this.delegate = delegate; + } + + @Override + public void dynamicTestRegistered(TestDescriptor testDescriptor) { + delegate.dynamicTestRegistered(testDescriptor); + } + + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + delegate.executionSkipped(testDescriptor, reason); + } + + @Override + public void executionStarted(TestDescriptor testDescriptor) { + delegate.executionStarted(testDescriptor); + } + + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + delegate.executionFinished(testDescriptor, testExecutionResult); + } + + @Override + public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + delegate.reportingEntryPublished(testDescriptor, entry); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java new file mode 100644 index 00000000..50a4df10 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.ClassSource; + +/** + * Represents an error thrown by a {@link org.junit.platform.engine.TestEngine} + * during discovery. + * + *

The contained {@link Throwable} will be reported as the cause of a test + * failure by the {@link DefaultLauncher} when execution is started for this + * engine. + * + * @since 1.6 + */ +class EngineDiscoveryErrorDescriptor extends AbstractTestDescriptor { + + private final Throwable cause; + + EngineDiscoveryErrorDescriptor(UniqueId uniqueId, TestEngine testEngine, Throwable cause) { + super(uniqueId, testEngine.getId(), ClassSource.from(testEngine.getClass())); + this.cause = cause; + } + + Throwable getCause() { + return cause; + } + + @Override + public Type getType() { + return Type.TEST; + } + + @Override + public void prune() { + // prevent pruning + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java new file mode 100644 index 00000000..4bb7f319 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -0,0 +1,234 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.stream.Collectors.joining; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.engine.Filter.composeFilters; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.EngineDiscoveryResult; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.PostDiscoveryFilter; + +/** + * Orchestrates test discovery using the configured test engines. + * + * @since 1.7 + */ +@API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) +public class EngineDiscoveryOrchestrator { + + private static final Logger logger = LoggerFactory.getLogger(EngineDiscoveryOrchestrator.class); + + private final EngineDiscoveryResultValidator discoveryResultValidator = new EngineDiscoveryResultValidator(); + private final Iterable testEngines; + private final Collection postDiscoveryFilters; + private final ListenerRegistry launcherDiscoveryListenerRegistry; + + public EngineDiscoveryOrchestrator(Iterable testEngines, + Collection postDiscoveryFilters) { + this(testEngines, postDiscoveryFilters, ListenerRegistry.forLauncherDiscoveryListeners()); + } + + EngineDiscoveryOrchestrator(Iterable testEngines, Collection postDiscoveryFilters, + ListenerRegistry launcherDiscoveryListenerRegistry) { + this.testEngines = EngineIdValidator.validate(testEngines); + this.postDiscoveryFilters = postDiscoveryFilters; + this.launcherDiscoveryListenerRegistry = launcherDiscoveryListenerRegistry; + } + + /** + * Discovers tests for the supplied request in the supplied phase using the + * configured test engines. + * + *

Applies {@linkplain org.junit.platform.launcher.EngineFilter engine + * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and + * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. + */ + public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase) { + Map result = discover(request, phase, UniqueId::forEngine); + return new LauncherDiscoveryResult(result, request.getConfigurationParameters()); + } + + /** + * Discovers tests for the supplied request in the supplied phase using the + * configured test engines to be used by the suite engine. + * + *

Applies {@linkplain org.junit.platform.launcher.EngineFilter engine + * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and + * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. + * + * Note: The test descriptors in the discovery result can safely be used as + * non-root descriptors. Engine-test descriptor entries are pruned from + * the returned result. As such execution by + * {@link EngineExecutionOrchestrator#execute(LauncherDiscoveryResult, EngineExecutionListener)} + * will not emit start or emit events for engines without tests. + */ + public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, UniqueId parentId) { + Map testEngines = discover(request, phase, parentId::appendEngine); + LauncherDiscoveryResult result = new LauncherDiscoveryResult(testEngines, request.getConfigurationParameters()); + return result.withRetainedEngines(TestDescriptor::containsTests); + } + + private Map discover(LauncherDiscoveryRequest request, Phase phase, + Function uniqueIdCreator) { + LauncherDiscoveryListener listener = getLauncherDiscoveryListener(request); + listener.launcherDiscoveryStarted(request); + try { + return discoverSafely(request, phase, listener, uniqueIdCreator); + } + finally { + listener.launcherDiscoveryFinished(request); + } + } + + private Map discoverSafely(LauncherDiscoveryRequest request, Phase phase, + LauncherDiscoveryListener listener, Function uniqueIdCreator) { + Map testEngineDescriptors = new LinkedHashMap<>(); + EngineFilterer engineFilterer = new EngineFilterer(request.getEngineFilters()); + + for (TestEngine testEngine : this.testEngines) { + boolean engineIsExcluded = engineFilterer.isExcluded(testEngine); + + if (engineIsExcluded) { + logger.debug(() -> String.format( + "Test discovery for engine '%s' was skipped due to an EngineFilter in %s phase.", + testEngine.getId(), phase)); + continue; + } + + logger.debug(() -> String.format("Discovering tests during Launcher %s phase in engine '%s'.", phase, + testEngine.getId())); + + TestDescriptor rootDescriptor = discoverEngineRoot(testEngine, request, listener, uniqueIdCreator); + testEngineDescriptors.put(testEngine, rootDescriptor); + } + + engineFilterer.performSanityChecks(); + + List filters = new LinkedList<>(postDiscoveryFilters); + filters.addAll(request.getPostDiscoveryFilters()); + + applyPostDiscoveryFilters(testEngineDescriptors, filters); + prune(testEngineDescriptors); + + return testEngineDescriptors; + } + + private TestDescriptor discoverEngineRoot(TestEngine testEngine, LauncherDiscoveryRequest request, + LauncherDiscoveryListener listener, Function uniqueIdCreator) { + UniqueId uniqueEngineId = uniqueIdCreator.apply(testEngine.getId()); + try { + listener.engineDiscoveryStarted(uniqueEngineId); + TestDescriptor engineRoot = testEngine.discover(request, uniqueEngineId); + discoveryResultValidator.validate(testEngine, engineRoot); + listener.engineDiscoveryFinished(uniqueEngineId, EngineDiscoveryResult.successful()); + return engineRoot; + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + String message = String.format("TestEngine with ID '%s' failed to discover tests", testEngine.getId()); + JUnitException cause = new JUnitException(message, throwable); + listener.engineDiscoveryFinished(uniqueEngineId, EngineDiscoveryResult.failed(cause)); + return new EngineDiscoveryErrorDescriptor(uniqueEngineId, testEngine, cause); + } + } + + LauncherDiscoveryListener getLauncherDiscoveryListener(LauncherDiscoveryRequest discoveryRequest) { + return ListenerRegistry.copyOf(launcherDiscoveryListenerRegistry) // + .add(discoveryRequest.getDiscoveryListener()) // + .getCompositeListener(); + } + + private void applyPostDiscoveryFilters(Map testEngineDescriptors, + List filters) { + Filter postDiscoveryFilter = composeFilters(filters); + Map> excludedTestDescriptorsByReason = new LinkedHashMap<>(); + TestDescriptor.Visitor removeExcludedTestDescriptors = descriptor -> { + FilterResult filterResult = postDiscoveryFilter.apply(descriptor); + if (!descriptor.isRoot() && isExcluded(descriptor, filterResult)) { + populateExclusionReasonInMap(filterResult.getReason(), descriptor, excludedTestDescriptorsByReason); + descriptor.removeFromHierarchy(); + } + }; + acceptInAllTestEngines(testEngineDescriptors, removeExcludedTestDescriptors); + logTestDescriptorExclusionReasons(excludedTestDescriptorsByReason); + } + + private void populateExclusionReasonInMap(Optional reason, TestDescriptor testDescriptor, + Map> excludedTestDescriptorsByReason) { + excludedTestDescriptorsByReason.computeIfAbsent(reason.orElse("Unknown"), list -> new LinkedList<>()).add( + testDescriptor); + } + + private void logTestDescriptorExclusionReasons(Map> excludedTestDescriptorsByReason) { + excludedTestDescriptorsByReason.forEach((exclusionReason, testDescriptors) -> { + String displayNames = testDescriptors.stream().map(TestDescriptor::getDisplayName).collect(joining(", ")); + long containerCount = testDescriptors.stream().filter(TestDescriptor::isContainer).count(); + long methodCount = testDescriptors.stream().filter(TestDescriptor::isTest).count(); + logger.config(() -> String.format("%d containers and %d tests were %s", containerCount, methodCount, + exclusionReason)); + logger.debug( + () -> String.format("The following containers and tests were %s: %s", exclusionReason, displayNames)); + }); + } + + /** + * Prune all branches in the tree of {@link TestDescriptor TestDescriptors} + * that do not have executable tests. + * + *

If a {@link TestEngine} ends up with no {@code TestDescriptors} after + * pruning, it will not be removed. + */ + private void prune(Map testEngineDescriptors) { + acceptInAllTestEngines(testEngineDescriptors, TestDescriptor::prune); + } + + private boolean isExcluded(TestDescriptor descriptor, FilterResult filterResult) { + return descriptor.getChildren().isEmpty() && filterResult.excluded(); + } + + private void acceptInAllTestEngines(Map testEngineDescriptors, + TestDescriptor.Visitor visitor) { + testEngineDescriptors.values().forEach(descriptor -> descriptor.accept(visitor)); + } + + public enum Phase { + DISCOVERY, EXECUTION; + + @Override + public String toString() { + return name().toLowerCase(Locale.ENGLISH); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java new file mode 100644 index 00000000..a5441ae6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; + +/** + * Perform common validation checks on the result from the `discover()` method. + * + * @since 1.3 + */ +class EngineDiscoveryResultValidator { + + /** + * Perform common validation checks. + * + * @throws org.junit.platform.commons.PreconditionViolationException if any check fails + */ + void validate(TestEngine testEngine, TestDescriptor root) { + Preconditions.notNull(root, + () -> String.format( + "The discover() method for TestEngine with ID '%s' must return a non-null root TestDescriptor.", + testEngine.getId())); + Preconditions.condition(isAcyclic(root), + () -> String.format("The discover() method for TestEngine with ID '%s' returned a cyclic graph.", + testEngine.getId())); + } + + /** + * @return {@code true} if the tree does not contain a cycle; else {@code false}. + */ + boolean isAcyclic(TestDescriptor root) { + Set visited = new HashSet<>(); + visited.add(root.getUniqueId()); + Queue queue = new ArrayDeque<>(); + queue.add(root); + while (!queue.isEmpty()) { + for (TestDescriptor child : queue.remove().getChildren()) { + if (!visited.add(child.getUniqueId())) { + return false; // id already known: cycle detected! + } + if (child.isContainer()) { + queue.add(child); + } + } + } + return true; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java new file mode 100644 index 00000000..35e174e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.launcher.core.ListenerRegistry.forEngineExecutionListeners; + +import java.util.Optional; +import java.util.function.Consumer; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +/** + * Orchestrates test execution using the configured test engines. + * + * @since 1.7 + */ +@API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) +public class EngineExecutionOrchestrator { + + private final ListenerRegistry listenerRegistry; + + public EngineExecutionOrchestrator() { + this(ListenerRegistry.forTestExecutionListeners()); + } + + EngineExecutionOrchestrator(ListenerRegistry listenerRegistry) { + this.listenerRegistry = listenerRegistry; + } + + void execute(InternalTestPlan internalTestPlan, TestExecutionListener... listeners) { + ConfigurationParameters configurationParameters = internalTestPlan.getConfigurationParameters(); + ListenerRegistry testExecutionListenerListeners = buildListenerRegistryForExecution( + listeners); + withInterceptedStreams(configurationParameters, testExecutionListenerListeners, + testExecutionListener -> execute(internalTestPlan, EngineExecutionListener.NOOP, testExecutionListener)); + } + + /** + * Executes tests for the supplied {@linkplain LauncherDiscoveryResult + * discoveryResult} and notifies the supplied {@linkplain + * EngineExecutionListener engineExecutionListener} and + * {@linkplain TestExecutionListener testExecutionListener} of execution + * events. + */ + @API(status = INTERNAL, since = "1.9", consumers = { "org.junit.platform.suite.engine" }) + public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener, + TestExecutionListener testExecutionListener) { + Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); + Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); + Preconditions.notNull(testExecutionListener, "testExecutionListener must not be null"); + + InternalTestPlan internalTestPlan = InternalTestPlan.from(discoveryResult); + execute(internalTestPlan, engineExecutionListener, testExecutionListener); + } + + private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener parentEngineExecutionListener, + TestExecutionListener testExecutionListener) { + internalTestPlan.markStarted(); + + // Do not directly pass the internal test plan to test execution listeners. + // Hyrum's Law indicates that someone will eventually come to depend on it. + TestPlan testPlan = internalTestPlan.getDelegate(); + LauncherDiscoveryResult discoveryResult = internalTestPlan.getDiscoveryResult(); + + ListenerRegistry engineExecutionListenerRegistry = forEngineExecutionListeners(); + engineExecutionListenerRegistry.add(new ExecutionListenerAdapter(testPlan, testExecutionListener)); + engineExecutionListenerRegistry.add(parentEngineExecutionListener); + + testExecutionListener.testPlanExecutionStarted(testPlan); + execute(discoveryResult, engineExecutionListenerRegistry.getCompositeListener()); + testExecutionListener.testPlanExecutionFinished(testPlan); + } + + private void withInterceptedStreams(ConfigurationParameters configurationParameters, + ListenerRegistry listenerRegistry, Consumer action) { + + TestExecutionListener testExecutionListener = listenerRegistry.getCompositeListener(); + Optional streamInterceptingTestExecutionListener = StreamInterceptingTestExecutionListener.create( + configurationParameters, testExecutionListener::reportingEntryPublished); + streamInterceptingTestExecutionListener.ifPresent(listenerRegistry::add); + try { + action.accept(listenerRegistry.getCompositeListener()); + } + finally { + streamInterceptingTestExecutionListener.ifPresent(StreamInterceptingTestExecutionListener::unregister); + } + } + + /** + * Executes tests for the supplied {@linkplain LauncherDiscoveryResult + * discovery results} and notifies the supplied {@linkplain + * EngineExecutionListener listener} of execution events. + */ + @API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit" }) + public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener) { + Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); + Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); + + for (TestEngine testEngine : discoveryResult.getTestEngines()) { + TestDescriptor engineDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); + if (engineDescriptor instanceof EngineDiscoveryErrorDescriptor) { + engineExecutionListener.executionStarted(engineDescriptor); + engineExecutionListener.executionFinished(engineDescriptor, + TestExecutionResult.failed(((EngineDiscoveryErrorDescriptor) engineDescriptor).getCause())); + } + else { + execute(engineDescriptor, engineExecutionListener, discoveryResult.getConfigurationParameters(), + testEngine); + } + } + } + + private ListenerRegistry buildListenerRegistryForExecution( + TestExecutionListener... listeners) { + if (listeners.length == 0) { + return this.listenerRegistry; + } + return ListenerRegistry.copyOf(this.listenerRegistry).addAll(listeners); + } + + private void execute(TestDescriptor engineDescriptor, EngineExecutionListener listener, + ConfigurationParameters configurationParameters, TestEngine testEngine) { + + OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, + engineDescriptor); + try { + testEngine.execute(new ExecutionRequest(engineDescriptor, delayingListener, configurationParameters)); + delayingListener.reportEngineOutcome(); + } + catch (Throwable throwable) { + UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); + delayingListener.reportEngineFailure(new JUnitException( + String.format("TestEngine with ID '%s' failed to execute tests", testEngine.getId()), throwable)); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java new file mode 100644 index 00000000..04f0d24f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toCollection; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.launcher.EngineFilter; + +class EngineFilterer { + + private static final Logger LOGGER = LoggerFactory.getLogger(EngineFilterer.class); + + private final List engineFilters; + + private final Map checkedTestEngines = new HashMap<>(); + + EngineFilterer(List engineFilters) { + this.engineFilters = engineFilters; + } + + boolean isExcluded(TestEngine testEngine) { + boolean excluded = engineFilters.stream() // + .map(engineFilter -> engineFilter.apply(testEngine)) // + .anyMatch(FilterResult::excluded); + checkedTestEngines.put(testEngine, excluded); + return excluded; + } + + void performSanityChecks() { + checkNoUnmatchedIncludeFilter(); + warnIfAllEnginesExcluded(); + } + + private void warnIfAllEnginesExcluded() { + if (!checkedTestEngines.isEmpty() && checkedTestEngines.values().stream().allMatch(excluded -> excluded)) { + LOGGER.warn(() -> "All TestEngines were excluded by EngineFilters.\n" + getStateDescription()); + } + } + + private void checkNoUnmatchedIncludeFilter() { + SortedSet unmatchedEngineIdsOfIncludeFilters = getUnmatchedEngineIdsOfIncludeFilters(); + if (!unmatchedEngineIdsOfIncludeFilters.isEmpty()) { + throw new JUnitException("No TestEngine ID matched the following include EngineFilters: " + + unmatchedEngineIdsOfIncludeFilters + ".\n" // + + "Please fix/remove the filter or add the engine.\n" // + + getStateDescription()); + } + } + + private SortedSet getUnmatchedEngineIdsOfIncludeFilters() { + return engineFilters.stream() // + .filter(EngineFilter::isIncludeFilter) // + .filter(engineFilter -> checkedTestEngines.keySet().stream() // + .map(engineFilter::apply) // + .noneMatch(FilterResult::included)) // + .flatMap(engineFilter -> engineFilter.getEngineIds().stream()) // + .collect(toCollection(TreeSet::new)); + } + + private String getStateDescription() { + return getRegisteredEnginesDescription() + "\n" + getRegisteredFiltersDescription(); + } + + private String getRegisteredEnginesDescription() { + return TestEngineFormatter.format("Registered TestEngines", checkedTestEngines.keySet()); + } + + private String getRegisteredFiltersDescription() { + return "Registered EngineFilters:" + engineFilters.stream() // + .map(Objects::toString) // + .collect(joining("\n- ", "\n- ", "")); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java new file mode 100644 index 00000000..adf3739b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.TestEngine; + +/** + * @since 1.7 + */ +class EngineIdValidator { + + private static final Logger logger = LoggerFactory.getLogger(EngineIdValidator.class); + + private EngineIdValidator() { + } + + static Iterable validate(Iterable testEngines) { + Set ids = new HashSet<>(); + for (TestEngine testEngine : testEngines) { + // check usage of reserved id prefix + if (!validateReservedIds(testEngine)) { + logger.warn(() -> String.format( + "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID: '%s'", + testEngine.getId())); + } + + // check uniqueness + if (!ids.add(testEngine.getId())) { + throw new JUnitException(String.format( + "Cannot create Launcher for multiple engines with the same ID '%s'.", testEngine.getId())); + } + } + return testEngines; + } + + // https://github.com/junit-team/junit5/issues/1557 + private static boolean validateReservedIds(TestEngine testEngine) { + String engineId = testEngine.getId(); + if (!engineId.startsWith("junit-")) { + return true; + } + if (engineId.equals("junit-jupiter")) { + validateWellKnownClassName(testEngine, "org.junit.jupiter.engine.JupiterTestEngine"); + return true; + } + if (engineId.equals("junit-vintage")) { + validateWellKnownClassName(testEngine, "org.junit.vintage.engine.VintageTestEngine"); + return true; + } + if (engineId.equals("junit-platform-suite")) { + validateWellKnownClassName(testEngine, "org.junit.platform.suite.engine.SuiteTestEngine"); + return true; + } + return false; + } + + private static void validateWellKnownClassName(TestEngine testEngine, String expectedClassName) { + String actualClassName = testEngine.getClass().getName(); + if (actualClassName.equals(expectedClassName)) { + return; + } + throw new JUnitException( + String.format("Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.", + actualClassName, testEngine.getId())); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java new file mode 100644 index 00000000..f2d87e4b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * An {@code ExecutionListenerAdapter} adapts a {@link TestPlan} and a corresponding + * {@link TestExecutionListener} to the {@link EngineExecutionListener} API. + * + * @since 1.0 + */ +class ExecutionListenerAdapter implements EngineExecutionListener { + + private final TestPlan testPlan; + private final TestExecutionListener testExecutionListener; + + ExecutionListenerAdapter(TestPlan testPlan, TestExecutionListener testExecutionListener) { + this.testPlan = testPlan; + this.testExecutionListener = testExecutionListener; + } + + @Override + public void dynamicTestRegistered(TestDescriptor testDescriptor) { + TestIdentifier testIdentifier = TestIdentifier.from(testDescriptor); + this.testPlan.addInternal(testIdentifier); + this.testExecutionListener.dynamicTestRegistered(testIdentifier); + } + + @Override + public void executionStarted(TestDescriptor testDescriptor) { + this.testExecutionListener.executionStarted(getTestIdentifier(testDescriptor)); + } + + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + this.testExecutionListener.executionSkipped(getTestIdentifier(testDescriptor), reason); + } + + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + this.testExecutionListener.executionFinished(getTestIdentifier(testDescriptor), testExecutionResult); + } + + @Override + public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + this.testExecutionListener.reportingEntryPublished(getTestIdentifier(testDescriptor), entry); + } + + private TestIdentifier getTestIdentifier(TestDescriptor testDescriptor) { + return this.testPlan.getTestIdentifier(testDescriptor.getUniqueId()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java new file mode 100644 index 00000000..453be693 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.TestExecutionListener; + +/** + * @since 1.8 + */ +interface InternalLauncher extends Launcher { + + ListenerRegistry getTestExecutionListenerRegistry(); + + ListenerRegistry getLauncherDiscoveryListenerRegistry(); +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java new file mode 100644 index 00000000..20a15571 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; + +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.4 + */ +class InternalTestPlan extends TestPlan { + + private final AtomicBoolean executionStarted = new AtomicBoolean(false); + private final LauncherDiscoveryResult discoveryResult; + private final TestPlan delegate; + + static InternalTestPlan from(LauncherDiscoveryResult discoveryResult) { + TestPlan delegate = TestPlan.from(discoveryResult.getEngineTestDescriptors(), + discoveryResult.getConfigurationParameters()); + return new InternalTestPlan(discoveryResult, delegate); + } + + private InternalTestPlan(LauncherDiscoveryResult discoveryResult, TestPlan delegate) { + super(delegate.containsTests(), delegate.getConfigurationParameters()); + this.discoveryResult = discoveryResult; + this.delegate = delegate; + } + + void markStarted() { + if (!executionStarted.compareAndSet(false, true)) { + throw new PreconditionViolationException("TestPlan must only be executed once"); + } + } + + LauncherDiscoveryResult getDiscoveryResult() { + return discoveryResult; + } + + public TestPlan getDelegate() { + return delegate; + } + + @Override + @SuppressWarnings("deprecation") + public void add(TestIdentifier testIdentifier) { + delegate.add(testIdentifier); + } + + @Override + public void addInternal(TestIdentifier testIdentifier) { + delegate.addInternal(testIdentifier); + } + + @Override + public Set getRoots() { + return delegate.getRoots(); + } + + @Override + public Optional getParent(TestIdentifier child) { + return delegate.getParent(child); + } + + @Override + public Set getChildren(TestIdentifier parent) { + return delegate.getChildren(parent); + } + + @SuppressWarnings("deprecation") + @Override + public Set getChildren(String parentId) { + return delegate.getChildren(parentId); + } + + @Override + public Set getChildren(UniqueId parentId) { + return delegate.getChildren(parentId); + } + + @SuppressWarnings("deprecation") + @Override + public TestIdentifier getTestIdentifier(String uniqueId) throws PreconditionViolationException { + return delegate.getTestIdentifier(uniqueId); + } + + @Override + public TestIdentifier getTestIdentifier(UniqueId uniqueId) { + return delegate.getTestIdentifier(uniqueId); + } + + @Override + public long countTestIdentifiers(Predicate predicate) { + return delegate.countTestIdentifiers(predicate); + } + + @Override + public Set getDescendants(TestIdentifier parent) { + return delegate.getDescendants(parent); + } + + @Override + public boolean containsTests() { + return delegate.containsTests(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java new file mode 100644 index 00000000..0a622b16 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java @@ -0,0 +1,365 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.TestExecutionListener; + +/** + * {@code LauncherConfig} defines the configuration API for creating + * {@link Launcher} instances via the {@link LauncherFactory}. + * + *

Example

+ * + *
+ * LauncherConfig launcherConfig = LauncherConfig.builder()
+ *   .enableTestEngineAutoRegistration(false)
+ *   .enableTestExecutionListenerAutoRegistration(false)
+ *   .addTestEngines(new CustomTestEngine())
+ *   .addTestExecutionListeners(new CustomTestExecutionListener())
+ *   .build();
+ *
+ * Launcher launcher = LauncherFactory.create(launcherConfig);
+ * LauncherDiscoveryRequest discoveryRequest = ...
+ * launcher.execute(discoveryRequest);
+ * 
+ * + * @since 1.3 + * @see #builder() + * @see Launcher + * @see LauncherFactory + */ +@API(status = STABLE, since = "1.7") +public interface LauncherConfig { + + /** + * The default {@code LauncherConfig} which uses automatic registration for + * test engines, supported listeners, and post-discovery filters. + */ + LauncherConfig DEFAULT = builder().build(); + + /** + * Determine if test engines should be discovered at runtime using the + * {@link java.util.ServiceLoader ServiceLoader} mechanism and + * automatically registered. + * + * @return {@code true} if test engines should be automatically registered + */ + boolean isTestEngineAutoRegistrationEnabled(); + + /** + * Determine if launcher session listeners should be discovered at runtime + * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and + * automatically registered. + * + * @return {@code true} if launcher session listeners should be + * automatically registered + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + boolean isLauncherSessionListenerAutoRegistrationEnabled(); + + /** + * Determine if launcher discovery listeners should be discovered at runtime + * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and + * automatically registered. + * + * @return {@code true} if launcher discovery listeners should be + * automatically registered + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + boolean isLauncherDiscoveryListenerAutoRegistrationEnabled(); + + /** + * Determine if test execution listeners should be discovered at runtime + * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and + * automatically registered. + * + * @return {@code true} if test execution listeners should be automatically + * registered + */ + boolean isTestExecutionListenerAutoRegistrationEnabled(); + + /** + * Determine if post discovery filters should be discovered at runtime + * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and + * automatically registered. + * + * @return {@code true} if post discovery filters should be automatically + * registered + * @since 1.7 + */ + @API(status = EXPERIMENTAL, since = "1.7") + boolean isPostDiscoveryFilterAutoRegistrationEnabled(); + + /** + * Get the collection of additional test engines that should be added to + * the {@link Launcher}. + * + * @return the collection of additional test engines; never {@code null} but + * potentially empty + */ + Collection getAdditionalTestEngines(); + + /** + * Get the collection of additional launcher session listeners that should + * be added to the {@link Launcher}. + * + * @return the collection of additional launcher session listeners; never + * {@code null} but potentially empty + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + Collection getAdditionalLauncherSessionListeners(); + + /** + * Get the collection of additional launcher discovery listeners that should + * be added to the {@link Launcher}. + * + * @return the collection of additional launcher discovery listeners; never + * {@code null} but potentially empty + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + Collection getAdditionalLauncherDiscoveryListeners(); + + /** + * Get the collection of additional test execution listeners that should be + * added to the {@link Launcher}. + * + * @return the collection of additional test execution listeners; never + * {@code null} but potentially empty + */ + Collection getAdditionalTestExecutionListeners(); + + /** + * Get the collection of additional post discovery filters that should be + * added to the {@link Launcher}. + * + * @return the collection of additional post discovery filters; never + * {@code null} but potentially empty + * @since 1.7 + */ + @API(status = EXPERIMENTAL, since = "1.7") + Collection getAdditionalPostDiscoveryFilters(); + + /** + * Create a new {@link LauncherConfig.Builder}. + * + * @return a new builder; never {@code null} + */ + static Builder builder() { + return new Builder(); + } + + /** + * Builder API for {@link LauncherConfig}. + */ + class Builder { + + private boolean engineAutoRegistrationEnabled = true; + private boolean launcherSessionListenerAutoRegistrationEnabled = true; + private boolean launcherDiscoveryListenerAutoRegistrationEnabled = true; + private boolean testExecutionListenerAutoRegistrationEnabled = true; + private boolean postDiscoveryFilterAutoRegistrationEnabled = true; + private final Collection engines = new LinkedHashSet<>(); + private final Collection sessionListeners = new LinkedHashSet<>(); + private final Collection discoveryListeners = new LinkedHashSet<>(); + private final Collection executionListeners = new LinkedHashSet<>(); + private final Collection postDiscoveryFilters = new LinkedHashSet<>(); + + private Builder() { + /* no-op */ + } + + /** + * Configure the auto-registration flag for launcher session + * listeners. + * + *

Defaults to {@code true}. + * + * @param enabled {@code true} if launcher session listeners should be + * automatically registered + * @return this builder for method chaining + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + public Builder enableLauncherSessionListenerAutoRegistration(boolean enabled) { + this.launcherSessionListenerAutoRegistrationEnabled = enabled; + return this; + } + + /** + * Configure the auto-registration flag for launcher discovery + * listeners. + * + *

Defaults to {@code true}. + * + * @param enabled {@code true} if launcher discovery listeners should be + * automatically registered + * @return this builder for method chaining + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + public Builder enableLauncherDiscoveryListenerAutoRegistration(boolean enabled) { + this.launcherDiscoveryListenerAutoRegistrationEnabled = enabled; + return this; + } + + /** + * Configure the auto-registration flag for test execution listeners. + * + *

Defaults to {@code true}. + * + * @param enabled {@code true} if test execution listeners should be + * automatically registered + * @return this builder for method chaining + */ + public Builder enableTestExecutionListenerAutoRegistration(boolean enabled) { + this.testExecutionListenerAutoRegistrationEnabled = enabled; + return this; + } + + /** + * Configure the auto-registration flag for test engines. + * + *

Defaults to {@code true}. + * + * @param enabled {@code true} if test engines should be automatically + * registered + * @return this builder for method chaining + */ + public Builder enableTestEngineAutoRegistration(boolean enabled) { + this.engineAutoRegistrationEnabled = enabled; + return this; + } + + /** + * Configure the auto-registration flag for post discovery filters. + * + *

Defaults to {@code true}. + * + * @param enabled {@code true} if post discovery filters should be automatically + * registered + * @return this builder for method chaining + * @since 1.7 + */ + @API(status = EXPERIMENTAL, since = "1.7") + public Builder enablePostDiscoveryFilterAutoRegistration(boolean enabled) { + this.postDiscoveryFilterAutoRegistrationEnabled = enabled; + return this; + } + + /** + * Add all of the supplied test engines to the configuration. + * + * @param engines additional test engines to register; never {@code null} + * or containing {@code null} + * @return this builder for method chaining + */ + public Builder addTestEngines(TestEngine... engines) { + Preconditions.notNull(engines, "TestEngine array must not be null"); + Preconditions.containsNoNullElements(engines, "TestEngine array must not contain null elements"); + Collections.addAll(this.engines, engines); + return this; + } + + /** + * Add all of the supplied launcher session listeners to the configuration. + * + * @param listeners additional launcher session listeners to register; + * never {@code null} or containing {@code null} + * @return this builder for method chaining + */ + public Builder addLauncherSessionListeners(LauncherSessionListener... listeners) { + Preconditions.notNull(listeners, "LauncherSessionListener array must not be null"); + Preconditions.containsNoNullElements(listeners, + "LauncherSessionListener array must not contain null elements"); + Collections.addAll(this.sessionListeners, listeners); + return this; + } + + /** + * Add all of the supplied launcher discovery listeners to the configuration. + * + * @param listeners additional launcher discovery listeners to register; + * never {@code null} or containing {@code null} + * @return this builder for method chaining + */ + public Builder addLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { + Preconditions.notNull(listeners, "LauncherDiscoveryListener array must not be null"); + Preconditions.containsNoNullElements(listeners, + "LauncherDiscoveryListener array must not contain null elements"); + Collections.addAll(this.discoveryListeners, listeners); + return this; + } + + /** + * Add all of the supplied test execution listeners to the configuration. + * + * @param listeners additional test execution listeners to register; + * never {@code null} or containing {@code null} + * @return this builder for method chaining + */ + public Builder addTestExecutionListeners(TestExecutionListener... listeners) { + Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); + Preconditions.containsNoNullElements(listeners, + "TestExecutionListener array must not contain null elements"); + Collections.addAll(this.executionListeners, listeners); + return this; + } + + /** + * Add all of the supplied {@code filters} to the configuration. + * + * @param filters additional post discovery filters to register; + * never {@code null} or containing {@code null} + * @return this builder for method chaining + * @since 1.7 + */ + @API(status = EXPERIMENTAL, since = "1.7") + public Builder addPostDiscoveryFilters(PostDiscoveryFilter... filters) { + Preconditions.notNull(filters, "PostDiscoveryFilter array must not be null"); + Preconditions.containsNoNullElements(filters, "PostDiscoveryFilter array must not contain null elements"); + Collections.addAll(this.postDiscoveryFilters, filters); + return this; + } + + /** + * Build the {@link LauncherConfig} that has been configured via this + * builder. + */ + public LauncherConfig build() { + return new DefaultLauncherConfig(this.engineAutoRegistrationEnabled, + this.launcherSessionListenerAutoRegistrationEnabled, + this.launcherDiscoveryListenerAutoRegistrationEnabled, + this.testExecutionListenerAutoRegistrationEnabled, this.postDiscoveryFilterAutoRegistrationEnabled, + this.engines, this.sessionListeners, this.discoveryListeners, this.executionListeners, + this.postDiscoveryFilters); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java new file mode 100644 index 00000000..5d6b95ed --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java @@ -0,0 +1,290 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * @since 1.0 + */ +class LauncherConfigurationParameters implements ConfigurationParameters { + + private static final Logger logger = LoggerFactory.getLogger(LauncherConfigurationParameters.class); + + static Builder builder() { + return new Builder(); + } + + private final List providers; + + private LauncherConfigurationParameters(List providers) { + this.providers = providers; + } + + @Override + public Optional get(String key) { + return Optional.ofNullable(getProperty(key)); + } + + @Override + public Optional getBoolean(String key) { + return get(key).map(Boolean::parseBoolean); + } + + @Override + @SuppressWarnings("deprecation") + public int size() { + return providers.stream() // + .mapToInt(ParameterProvider::size) // + .sum(); + } + + @Override + public Set keySet() { + return providers.stream().map(ParameterProvider::keySet).flatMap(Collection::stream).collect( + Collectors.toSet()); + } + + private String getProperty(String key) { + Preconditions.notBlank(key, "key must not be null or blank"); + return providers.stream() // + .map(parameterProvider -> parameterProvider.getValue(key)) // + .filter(Objects::nonNull) // + .findFirst() // + .orElse(null); + } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("lookups", providers) // + .toString(); + } + + static final class Builder { + + private final Map explicitParameters = new HashMap<>(); + private boolean implicitProvidersEnabled = true; + private String configFileName = ConfigurationParameters.CONFIG_FILE_NAME; + private ConfigurationParameters parentConfigurationParameters; + + private Builder() { + } + + Builder explicitParameters(Map parameters) { + Preconditions.notNull(parameters, "configuration parameters must not be null"); + explicitParameters.putAll(parameters); + return this; + } + + Builder enableImplicitProviders(boolean enabled) { + this.implicitProvidersEnabled = enabled; + return this; + } + + Builder configFileName(String configFileName) { + Preconditions.notBlank(configFileName, "configFileName must not be null or blank"); + this.configFileName = configFileName; + return this; + } + + Builder parentConfigurationParameters(ConfigurationParameters parameters) { + Preconditions.notNull(parameters, "parent configuration parameters must not be null"); + this.parentConfigurationParameters = parameters; + return this; + } + + LauncherConfigurationParameters build() { + List parameterProviders = new ArrayList<>(); + if (!explicitParameters.isEmpty()) { + parameterProviders.add(ParameterProvider.explicit(explicitParameters)); + } + + if (parentConfigurationParameters != null) { + parameterProviders.add(ParameterProvider.inherited(parentConfigurationParameters)); + } + + if (implicitProvidersEnabled) { + parameterProviders.add(ParameterProvider.systemProperties()); + parameterProviders.add(ParameterProvider.propertiesFile(configFileName)); + } + return new LauncherConfigurationParameters(parameterProviders); + } + } + + private interface ParameterProvider { + + String getValue(String key); + + default int size() { + return 0; + } + + Set keySet(); + + static ParameterProvider explicit(Map configParams) { + return new ParameterProvider() { + @Override + public String getValue(String key) { + return configParams.get(key); + } + + @Override + public int size() { + return configParams.size(); + } + + @Override + public Set keySet() { + return configParams.keySet(); + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder("explicit"); + configParams.forEach(builder::append); + return builder.toString(); + } + }; + } + + static ParameterProvider systemProperties() { + return new ParameterProvider() { + @Override + public String getValue(String key) { + try { + return System.getProperty(key); + } + catch (Exception ignore) { + return null; + } + } + + @Override + public Set keySet() { + return System.getProperties().stringPropertyNames(); + } + + @Override + public String toString() { + return "systemProperties [...]"; + } + }; + } + + static ParameterProvider propertiesFile(String configFileName) { + Preconditions.notBlank(configFileName, "configFileName must not be null or blank"); + Properties properties = loadClasspathResource(configFileName.trim()); + return new ParameterProvider() { + @Override + public String getValue(String key) { + return properties.getProperty(key); + } + + @Override + public Set keySet() { + return properties.stringPropertyNames(); + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder("propertiesFile"); + properties.stringPropertyNames().forEach(key -> builder.append(key, getValue(key))); + return builder.toString(); + } + }; + } + + static ParameterProvider inherited(ConfigurationParameters configParams) { + return new ParameterProvider() { + @Override + public String getValue(String key) { + return configParams.get(key).orElse(null); + } + + @Override + @SuppressWarnings("deprecation") + public int size() { + return configParams.size(); + } + + @Override + public Set keySet() { + return configParams.keySet(); + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder("inherited"); + builder.append("parent", configParams); + return builder.toString(); + } + }; + } + + } + + private static Properties loadClasspathResource(String configFileName) { + Properties props = new Properties(); + + try { + ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader(); + Set resources = new LinkedHashSet<>(Collections.list(classLoader.getResources(configFileName))); + + if (!resources.isEmpty()) { + if (resources.size() > 1) { + logger.warn(() -> String.format( + "Discovered %d '%s' configuration files in the classpath; only the first will be used.", + resources.size(), configFileName)); + } + + URL configFileUrl = resources.iterator().next(); // same as List#get(0) + logger.config(() -> String.format( + "Loading JUnit Platform configuration parameters from classpath resource [%s].", configFileUrl)); + URLConnection urlConnection = configFileUrl.openConnection(); + urlConnection.setUseCaches(false); + try (InputStream inputStream = urlConnection.getInputStream()) { + props.load(inputStream); + } + } + } + catch (Exception ex) { + logger.warn(ex, + () -> String.format( + "Failed to load JUnit Platform configuration parameters from classpath resource [%s].", + configFileName)); + } + + return props; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java new file mode 100644 index 00000000..24be659b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -0,0 +1,332 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.Filter; +import org.junit.platform.launcher.EngineFilter; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.core.LauncherConfigurationParameters.Builder; +import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; + +/** + * The {@code LauncherDiscoveryRequestBuilder} provides a light-weight DSL for + * generating a {@link LauncherDiscoveryRequest}. + * + *

Example

+ * + *
+ * import static org.junit.platform.engine.discovery.DiscoverySelectors.*;
+ * import static org.junit.platform.engine.discovery.ClassNameFilter.*;
+ * import static org.junit.platform.launcher.EngineFilter.*;
+ * import static org.junit.platform.launcher.TagFilter.*;
+ *
+ * // ...
+ *
+ *   LauncherDiscoveryRequestBuilder.request()
+ *     .selectors(
+ *        selectPackage("org.example.user"),
+ *        selectClass("org.example.payment.PaymentTests"),
+ *        selectClass(ShippingTests.class),
+ *        selectMethod("org.example.order.OrderTests#test1"),
+ *        selectMethod("org.example.order.OrderTests#test2()"),
+ *        selectMethod("org.example.order.OrderTests#test3(java.lang.String)"),
+ *        selectMethod("org.example.order.OrderTests", "test4"),
+ *        selectMethod(OrderTests.class, "test5"),
+ *        selectMethod(OrderTests.class, testMethod),
+ *        selectClasspathRoots(Collections.singleton(new File("/my/local/path1"))),
+ *        selectUniqueId("unique-id-1"),
+ *        selectUniqueId("unique-id-2")
+ *     )
+ *     .filters(
+ *        includeEngines("junit-jupiter", "spek"),
+ *        // excludeEngines("junit-vintage"),
+ *        includeTags("fast"),
+ *        // excludeTags("slow"),
+ *        includeClassNamePatterns(".*Test[s]?")
+ *        // includeClassNamePatterns("org\.example\.tests.*")
+ *     )
+ *     .configurationParameter("key1", "value1")
+ *     .configurationParameters(configParameterMap)
+ *     .build();
+ * 
+ * + * @since 1.0 + * @see org.junit.platform.engine.discovery.DiscoverySelectors + * @see org.junit.platform.engine.discovery.ClassNameFilter + * @see org.junit.platform.launcher.EngineFilter + * @see org.junit.platform.launcher.TagFilter + */ +@API(status = STABLE, since = "1.0") +public final class LauncherDiscoveryRequestBuilder { + + /** + * Property name used to set the default discovery listener that is added to all : {@value} + * + *

Supported Values

+ * + *

Supported values are {@code "logging"} and {@code "abortOnFailure"}. + * + *

If not specified, the default is {@value #DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE}. + */ + public static final String DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME = "junit.platform.discovery.listener.default"; + + private static final String DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE = "abortOnFailure"; + + private final List selectors = new ArrayList<>(); + private final List engineFilters = new ArrayList<>(); + private final List> discoveryFilters = new ArrayList<>(); + private final List postDiscoveryFilters = new ArrayList<>(); + private final Map configurationParameters = new HashMap<>(); + private final List discoveryListeners = new ArrayList<>(); + private boolean implicitConfigurationParametersEnabled = true; + private ConfigurationParameters parentConfigurationParameters; + + /** + * Create a new {@code LauncherDiscoveryRequestBuilder}. + * + * @return a new builder + */ + public static LauncherDiscoveryRequestBuilder request() { + return new LauncherDiscoveryRequestBuilder(); + } + + /** + * @deprecated Use {@link #request()} + */ + @API(status = DEPRECATED, since = "1.8") + @Deprecated + public LauncherDiscoveryRequestBuilder() { + } + + /** + * Add all of the supplied {@code selectors} to the request. + * + * @param selectors the {@code DiscoverySelectors} to add; never {@code null} + * @return this builder for method chaining + */ + public LauncherDiscoveryRequestBuilder selectors(DiscoverySelector... selectors) { + Preconditions.notNull(selectors, "selectors array must not be null"); + selectors(Arrays.asList(selectors)); + return this; + } + + /** + * Add all of the supplied {@code selectors} to the request. + * + * @param selectors the {@code DiscoverySelectors} to add; never {@code null} + * @return this builder for method chaining + */ + public LauncherDiscoveryRequestBuilder selectors(List selectors) { + Preconditions.notNull(selectors, "selectors list must not be null"); + Preconditions.containsNoNullElements(selectors, "individual selectors must not be null"); + this.selectors.addAll(selectors); + return this; + } + + /** + * Add all of the supplied {@code filters} to the request. + * + *

The {@code filters} are combined using AND semantics, i.e. all of them + * have to include a resource for it to end up in the test plan. + * + *

Warning: be cautious when registering multiple competing + * {@link EngineFilter#includeEngines include} {@code EngineFilters} or multiple + * competing {@link EngineFilter#excludeEngines exclude} {@code EngineFilters} + * for the same discovery request since doing so will likely lead to + * undesirable results (i.e., zero engines being active). + * + * @param filters the {@code Filter}s to add; never {@code null} + * @return this builder for method chaining + */ + public LauncherDiscoveryRequestBuilder filters(Filter... filters) { + Preconditions.notNull(filters, "filters array must not be null"); + Preconditions.containsNoNullElements(filters, "individual filters must not be null"); + Arrays.stream(filters).forEach(this::storeFilter); + return this; + } + + /** + * Add the supplied configuration parameter to the request. + * + * @param key the configuration parameter key under which to store the + * value; never {@code null} or blank + * @param value the value to store + * @return this builder for method chaining + */ + public LauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { + Preconditions.notBlank(key, "configuration parameter key must not be null or blank"); + this.configurationParameters.put(key, value); + return this; + } + + /** + * Add all of the supplied configuration parameters to the request. + * + * @param configurationParameters the map of configuration parameters to add; + * never {@code null} + * @return this builder for method chaining + * @see #configurationParameter(String, String) + */ + public LauncherDiscoveryRequestBuilder configurationParameters(Map configurationParameters) { + Preconditions.notNull(configurationParameters, "configuration parameters map must not be null"); + configurationParameters.forEach(this::configurationParameter); + return this; + } + + /** + * Set the parent configuration parameters to use for the request. + * + *

Any explicit configuration parameters configured via + * {@link #configurationParameter(String, String)} or + * {@link #configurationParameters(Map)} takes precedence over the supplied + * configuration parameters. + * + * @param configurationParameters the parent instance to be used for looking + * up configuration parameters that have not been explicitly configured; + * never {@code null} + * @since 1.8 + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + */ + @API(status = EXPERIMENTAL, since = "1.8") + public LauncherDiscoveryRequestBuilder parentConfigurationParameters( + ConfigurationParameters configurationParameters) { + Preconditions.notNull(configurationParameters, "parent configuration parameters must not be null"); + this.parentConfigurationParameters = configurationParameters; + return this; + } + + /** + * Add all of the supplied discovery listeners to the request. + * + *

In addition to the {@linkplain LauncherDiscoveryListener listeners} + * registered using this method, this builder will add a default listener + * to this request that can be specified using the + * {@value LauncherDiscoveryRequestBuilder#DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME} + * configuration parameter. + * + * @param listeners the {@code LauncherDiscoveryListeners} to add; never + * {@code null} + * @return this builder for method chaining + * @since 1.6 + * @see LauncherDiscoveryListener + * @see LauncherDiscoveryListeners + * @see LauncherDiscoveryRequestBuilder#DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "1.6") + public LauncherDiscoveryRequestBuilder listeners(LauncherDiscoveryListener... listeners) { + Preconditions.notNull(listeners, "discovery listener array must not be null"); + Preconditions.containsNoNullElements(listeners, "individual discovery listeners must not be null"); + this.discoveryListeners.addAll(Arrays.asList(listeners)); + return this; + } + + /** + * Configure whether implicit configuration parameters should be considered. + * + *

By default, in addition to those parameters that are passed explicitly + * to this builder, configuration parameters are read from system properties + * and from the {@code junit-platform.properties} classpath resource. + * Passing {@code false} to this method, disables the latter two sources so + * that only explicit configuration parameters are taken into account. + * + * @since 1.7 + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + */ + @API(status = EXPERIMENTAL, since = "1.7") + public LauncherDiscoveryRequestBuilder enableImplicitConfigurationParameters(boolean enabled) { + this.implicitConfigurationParametersEnabled = enabled; + return this; + } + + private void storeFilter(Filter filter) { + if (filter instanceof EngineFilter) { + this.engineFilters.add((EngineFilter) filter); + } + else if (filter instanceof PostDiscoveryFilter) { + this.postDiscoveryFilters.add((PostDiscoveryFilter) filter); + } + else if (filter instanceof DiscoveryFilter) { + this.discoveryFilters.add((DiscoveryFilter) filter); + } + else { + throw new PreconditionViolationException( + String.format("Filter [%s] must implement %s, %s, or %s.", filter, EngineFilter.class.getSimpleName(), + PostDiscoveryFilter.class.getSimpleName(), DiscoveryFilter.class.getSimpleName())); + } + } + + /** + * Build the {@link LauncherDiscoveryRequest} that has been configured via + * this builder. + */ + public LauncherDiscoveryRequest build() { + LauncherConfigurationParameters launcherConfigurationParameters = buildLauncherConfigurationParameters(); + LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); + return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, + this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener); + } + + private LauncherConfigurationParameters buildLauncherConfigurationParameters() { + Builder builder = LauncherConfigurationParameters.builder() // + .explicitParameters(this.configurationParameters) // + .enableImplicitProviders(this.implicitConfigurationParametersEnabled); + + if (parentConfigurationParameters != null) { + builder.parentConfigurationParameters(this.parentConfigurationParameters); + } + + return builder.build(); + } + + private LauncherDiscoveryListener getLauncherDiscoveryListener(ConfigurationParameters configurationParameters) { + LauncherDiscoveryListener defaultDiscoveryListener = getDefaultLauncherDiscoveryListener( + configurationParameters); + if (discoveryListeners.isEmpty()) { + return defaultDiscoveryListener; + } + if (discoveryListeners.contains(defaultDiscoveryListener)) { + return LauncherDiscoveryListeners.composite(discoveryListeners); + } + List allDiscoveryListeners = new ArrayList<>(discoveryListeners.size() + 1); + allDiscoveryListeners.addAll(discoveryListeners); + allDiscoveryListeners.add(defaultDiscoveryListener); + return LauncherDiscoveryListeners.composite(allDiscoveryListeners); + } + + private LauncherDiscoveryListener getDefaultLauncherDiscoveryListener( + ConfigurationParameters configurationParameters) { + String value = configurationParameters.get(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME) // + .orElse(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE); + return LauncherDiscoveryListeners.fromConfigurationParameter( + DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, value); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java new file mode 100644 index 00000000..9e6d3e7a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collectors.toMap; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; + +/** + * Represents the result of test discovery of the configured + * {@linkplain TestEngine test engines}. + * + * @since 1.7 + */ +@API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) +public class LauncherDiscoveryResult { + + private final Map testEngineDescriptors; + private final ConfigurationParameters configurationParameters; + + LauncherDiscoveryResult(Map testEngineDescriptors, + ConfigurationParameters configurationParameters) { + this.testEngineDescriptors = unmodifiableMap(new LinkedHashMap<>(testEngineDescriptors)); + this.configurationParameters = configurationParameters; + } + + public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { + return this.testEngineDescriptors.get(testEngine); + } + + ConfigurationParameters getConfigurationParameters() { + return configurationParameters; + } + + public Collection getTestEngines() { + return this.testEngineDescriptors.keySet(); + } + + Collection getEngineTestDescriptors() { + return this.testEngineDescriptors.values(); + } + + public LauncherDiscoveryResult withRetainedEngines(Predicate predicate) { + Map prunedTestEngineDescriptors = retainEngines(predicate); + if (prunedTestEngineDescriptors.size() < testEngineDescriptors.size()) { + return new LauncherDiscoveryResult(prunedTestEngineDescriptors, configurationParameters); + } + return this; + } + + private Map retainEngines(Predicate predicate) { + // @formatter:off + return testEngineDescriptors.entrySet() + .stream() + .filter(entry -> predicate.test(entry.getValue())) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java new file mode 100644 index 00000000..3ac0b4c8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java @@ -0,0 +1,195 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.launcher.LauncherConstants.DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ClassNamePatternFilterUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.TestExecutionListener; + +/** + * Factory for creating {@link Launcher} instances by invoking {@link #create()} + * or {@link #create(LauncherConfig)}. + * + *

By default, test engines are discovered at runtime using the + * {@link java.util.ServiceLoader ServiceLoader} mechanism. For that purpose, a + * text file named {@code META-INF/services/org.junit.platform.engine.TestEngine} + * has to be added to the engine's JAR file in which the fully qualified name + * of the implementation class of the {@link org.junit.platform.engine.TestEngine} + * interface is declared. + * + *

By default, test execution listeners are discovered at runtime via the + * {@link java.util.ServiceLoader ServiceLoader} mechanism and are + * automatically registered with the {@link Launcher} created by this factory. + * Users may register additional listeners using the + * {@link Launcher#registerTestExecutionListeners(TestExecutionListener...)} + * method on the created launcher instance. + * + *

For full control over automatic registration and programmatic registration + * of test engines and listeners, supply an instance of {@link LauncherConfig} + * to {@link #create(LauncherConfig)}. + * + * @since 1.0 + * @see Launcher + * @see LauncherConfig + */ +@API(status = STABLE, since = "1.0") +public class LauncherFactory { + + private static final ServiceLoaderRegistry SERVICE_LOADER_REGISTRY = new ServiceLoaderRegistry(); + + private LauncherFactory() { + /* no-op */ + } + + /** + * Factory method for opening a new {@link LauncherSession} using the + * {@linkplain LauncherConfig#DEFAULT default} {@link LauncherConfig}. + * + * @throws PreconditionViolationException if no test engines are detected + * @since 1.8 + * @see #openSession(LauncherConfig) + */ + @API(status = EXPERIMENTAL, since = "1.8") + public static LauncherSession openSession() throws PreconditionViolationException { + return openSession(LauncherConfig.DEFAULT); + } + + /** + * Factory method for opening a new {@link LauncherSession} using the + * supplied {@link LauncherConfig}. + * + * @param config the configuration for the session and the launcher; never + * {@code null} + * @throws PreconditionViolationException if the supplied configuration is + * {@code null}, or if no test engines are detected + * @since 1.8 + * @see #openSession() + */ + @API(status = EXPERIMENTAL, since = "1.8") + public static LauncherSession openSession(LauncherConfig config) throws PreconditionViolationException { + return new DefaultLauncherSession(createDefaultLauncher(config), createLauncherSessionListener(config)); + } + + /** + * Factory method for creating a new {@link Launcher} using the + * {@linkplain LauncherConfig#DEFAULT default} {@link LauncherConfig}. + * + * @throws PreconditionViolationException if no test engines are detected + * @see #create(LauncherConfig) + */ + public static Launcher create() throws PreconditionViolationException { + return create(LauncherConfig.DEFAULT); + } + + /** + * Factory method for creating a new {@link Launcher} using the supplied + * {@link LauncherConfig}. + * + * @param config the configuration for the launcher; never {@code null} + * @throws PreconditionViolationException if the supplied configuration is + * {@code null}, or if no test engines are detected + * registered + * @since 1.3 + * @see #create() + */ + @API(status = EXPERIMENTAL, since = "1.3") + public static Launcher create(LauncherConfig config) throws PreconditionViolationException { + return new SessionPerRequestLauncher(createDefaultLauncher(config), createLauncherSessionListener(config)); + } + + private static DefaultLauncher createDefaultLauncher(LauncherConfig config) { + Preconditions.notNull(config, "LauncherConfig must not be null"); + + Set engines = collectTestEngines(config); + List filters = collectPostDiscoveryFilters(config); + + DefaultLauncher launcher = new DefaultLauncher(engines, filters); + + registerLauncherDiscoveryListeners(config, launcher); + registerTestExecutionListeners(config, launcher); + + return launcher; + } + + private static Set collectTestEngines(LauncherConfig config) { + Set engines = new LinkedHashSet<>(); + if (config.isTestEngineAutoRegistrationEnabled()) { + new ServiceLoaderTestEngineRegistry().loadTestEngines().forEach(engines::add); + } + engines.addAll(config.getAdditionalTestEngines()); + return engines; + } + + private static LauncherSessionListener createLauncherSessionListener(LauncherConfig config) { + ListenerRegistry listenerRegistry = ListenerRegistry.forLauncherSessionListeners(); + if (config.isLauncherSessionListenerAutoRegistrationEnabled()) { + SERVICE_LOADER_REGISTRY.load(LauncherSessionListener.class).forEach(listenerRegistry::add); + } + config.getAdditionalLauncherSessionListeners().forEach(listenerRegistry::add); + return listenerRegistry.getCompositeListener(); + } + + private static List collectPostDiscoveryFilters(LauncherConfig config) { + List filters = new ArrayList<>(); + if (config.isPostDiscoveryFilterAutoRegistrationEnabled()) { + SERVICE_LOADER_REGISTRY.load(PostDiscoveryFilter.class).forEach(filters::add); + } + filters.addAll(config.getAdditionalPostDiscoveryFilters()); + return filters; + } + + private static void registerLauncherDiscoveryListeners(LauncherConfig config, Launcher launcher) { + if (config.isLauncherDiscoveryListenerAutoRegistrationEnabled()) { + SERVICE_LOADER_REGISTRY.load(LauncherDiscoveryListener.class).forEach( + launcher::registerLauncherDiscoveryListeners); + } + config.getAdditionalLauncherDiscoveryListeners().forEach(launcher::registerLauncherDiscoveryListeners); + } + + private static void registerTestExecutionListeners(LauncherConfig config, Launcher launcher) { + if (config.isTestExecutionListenerAutoRegistrationEnabled()) { + loadAndFilterTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); + } + config.getAdditionalTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); + } + + private static Stream loadAndFilterTestExecutionListeners() { + Iterable listeners = SERVICE_LOADER_REGISTRY.load(TestExecutionListener.class); + ConfigurationParameters configurationParameters = LauncherConfigurationParameters.builder().build(); + String deactivatedListenersPattern = configurationParameters.get( + DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME).orElse(null); + // @formatter:off + return StreamSupport.stream(listeners.spliterator(), false) + .filter(ClassNamePatternFilterUtils.excludeMatchingClasses(deactivatedListenersPattern)); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java new file mode 100644 index 00000000..1fab26cd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; +import org.junit.platform.launcher.listeners.session.LauncherSessionListeners; + +class ListenerRegistry { + + private final Function, T> compositeListenerFactory; + + static ListenerRegistry forLauncherSessionListeners() { + return create(LauncherSessionListeners::composite); + } + + static ListenerRegistry forLauncherDiscoveryListeners() { + return create(LauncherDiscoveryListeners::composite); + } + + static ListenerRegistry forTestExecutionListeners() { + return create(CompositeTestExecutionListener::new); + } + + static ListenerRegistry forEngineExecutionListeners() { + return create(CompositeEngineExecutionListener::new); + } + + static ListenerRegistry create(Function, T> compositeListenerFactory) { + return new ListenerRegistry<>(compositeListenerFactory); + } + + static ListenerRegistry copyOf(ListenerRegistry source) { + ListenerRegistry registry = new ListenerRegistry<>(source.compositeListenerFactory); + if (!source.listeners.isEmpty()) { + registry.addAll(source.listeners); + } + return registry; + } + + private final ArrayList listeners = new ArrayList<>(); + + private ListenerRegistry(Function, T> compositeListenerFactory) { + this.compositeListenerFactory = compositeListenerFactory; + } + + ListenerRegistry add(T listener) { + Preconditions.notNull(listener, "listener must not be null"); + this.listeners.add(listener); + return this; + } + + @SafeVarargs + @SuppressWarnings("varargs") + final ListenerRegistry addAll(T... listeners) { + Preconditions.notEmpty(listeners, "listeners array must not be null or empty"); + return addAll(Arrays.asList(listeners)); + } + + ListenerRegistry addAll(Collection listeners) { + Preconditions.notEmpty(listeners, "listeners collection must not be null or empty"); + Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); + this.listeners.addAll(listeners); + return this; + } + + T getCompositeListener() { + this.listeners.trimToSize(); + return compositeListenerFactory.apply(this.listeners); + } + + List getListeners() { + return Collections.unmodifiableList(this.listeners); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java new file mode 100644 index 00000000..9b55df04 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; + +/** + * Delays reporting of engine skipped/finished events so that exceptions thrown + * by engines can be reported to listeners. + * + * @since 1.6 + */ +class OutcomeDelayingEngineExecutionListener extends DelegatingEngineExecutionListener { + + private final TestDescriptor engineDescriptor; + + private volatile boolean engineStarted; + private volatile Outcome outcome; + private volatile String skipReason; + private volatile TestExecutionResult executionResult; + + OutcomeDelayingEngineExecutionListener(EngineExecutionListener delegate, TestDescriptor engineDescriptor) { + super(delegate); + this.engineDescriptor = engineDescriptor; + } + + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + if (testDescriptor == engineDescriptor) { + outcome = Outcome.SKIPPED; + skipReason = reason; + } + else { + super.executionSkipped(testDescriptor, reason); + } + } + + @Override + public void executionStarted(TestDescriptor testDescriptor) { + if (testDescriptor == engineDescriptor) { + engineStarted = true; + } + super.executionStarted(testDescriptor); + } + + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult executionResult) { + if (testDescriptor == engineDescriptor) { + outcome = Outcome.FINISHED; + this.executionResult = executionResult; + } + else { + super.executionFinished(testDescriptor, executionResult); + } + } + + void reportEngineOutcome() { + if (outcome == Outcome.FINISHED) { + super.executionFinished(engineDescriptor, executionResult); + } + else if (outcome == Outcome.SKIPPED) { + super.executionSkipped(engineDescriptor, skipReason); + } + } + + void reportEngineFailure(Throwable throwable) { + if (!engineStarted) { + super.executionStarted(engineDescriptor); + } + if (executionResult != null && executionResult.getThrowable().isPresent()) { + throwable.addSuppressed(executionResult.getThrowable().get()); + } + super.executionFinished(engineDescriptor, TestExecutionResult.failed(throwable)); + } + + private enum Outcome { + SKIPPED, FINISHED + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java new file mode 100644 index 00000000..47ccd2f2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; + +import java.util.ServiceLoader; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassLoaderUtils; + +/** + * @since 1.8 + */ +class ServiceLoaderRegistry { + + private static final Logger logger = LoggerFactory.getLogger(ServiceLoaderRegistry.class); + + Iterable load(Class serviceProviderClass) { + Iterable listeners = ServiceLoader.load(serviceProviderClass, ClassLoaderUtils.getDefaultClassLoader()); + logger.config(() -> "Loaded " + serviceProviderClass.getSimpleName() + " instances: " + + stream(listeners.spliterator(), false).map(Object::toString).collect(toList())); + return listeners; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java new file mode 100644 index 00000000..00233a6b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.ServiceLoader; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.engine.TestEngine; + +/** + * @since 1.0 + */ +@API(status = INTERNAL, since = "1.0", consumers = "org.junit.platform.suite.engine") +public final class ServiceLoaderTestEngineRegistry { + + public ServiceLoaderTestEngineRegistry() { + } + + private static final Logger logger = LoggerFactory.getLogger(ServiceLoaderTestEngineRegistry.class); + + public Iterable loadTestEngines() { + Iterable testEngines = ServiceLoader.load(TestEngine.class, + ClassLoaderUtils.getDefaultClassLoader()); + logger.config(() -> TestEngineFormatter.format("Discovered TestEngines", testEngines)); + return testEngines; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java new file mode 100644 index 00000000..9c0b7dd4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.8 + */ +class SessionPerRequestLauncher implements InternalLauncher { + + private final InternalLauncher delegate; + private final LauncherSessionListener sessionListener; + + SessionPerRequestLauncher(InternalLauncher delegate, LauncherSessionListener sessionListener) { + this.delegate = delegate; + this.sessionListener = sessionListener; + } + + @Override + public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { + delegate.registerLauncherDiscoveryListeners(listeners); + } + + @Override + public void registerTestExecutionListeners(TestExecutionListener... listeners) { + delegate.registerTestExecutionListeners(listeners); + } + + @Override + public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { + try (LauncherSession session = createSession()) { + return session.getLauncher().discover(launcherDiscoveryRequest); + } + } + + @Override + public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { + try (LauncherSession session = createSession()) { + session.getLauncher().execute(launcherDiscoveryRequest, listeners); + } + } + + @Override + public void execute(TestPlan testPlan, TestExecutionListener... listeners) { + try (LauncherSession session = createSession()) { + session.getLauncher().execute(testPlan, listeners); + } + } + + @Override + public ListenerRegistry getTestExecutionListenerRegistry() { + return delegate.getTestExecutionListenerRegistry(); + } + + @Override + public ListenerRegistry getLauncherDiscoveryListenerRegistry() { + return delegate.getLauncherDiscoveryListenerRegistry(); + } + + private LauncherSession createSession() { + return new DefaultLauncherSession(delegate, sessionListener); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java new file mode 100644 index 00000000..be0e6a41 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java @@ -0,0 +1,99 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_DEFAULT; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; +import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.core.CompositeTestExecutionListener.EagerTestExecutionListener; + +/** + * @since 1.3 + */ +class StreamInterceptingTestExecutionListener implements EagerTestExecutionListener { + + private final Optional stdoutInterceptor; + private final Optional stderrInterceptor; + private final BiConsumer reporter; + + static Optional create(ConfigurationParameters configurationParameters, + BiConsumer reporter) { + + boolean captureStdout = configurationParameters.getBoolean(CAPTURE_STDOUT_PROPERTY_NAME).orElse(false); + boolean captureStderr = configurationParameters.getBoolean(CAPTURE_STDERR_PROPERTY_NAME).orElse(false); + if (!captureStdout && !captureStderr) { + return Optional.empty(); + } + + int maxSize = configurationParameters.get(CAPTURE_MAX_BUFFER_PROPERTY_NAME, Integer::valueOf) // + .orElse(CAPTURE_MAX_BUFFER_DEFAULT); + + Optional stdoutInterceptor = captureStdout ? StreamInterceptor.registerStdout(maxSize) + : Optional.empty(); + Optional stderrInterceptor = captureStderr ? StreamInterceptor.registerStderr(maxSize) + : Optional.empty(); + + if ((!stdoutInterceptor.isPresent() && captureStdout) || (!stderrInterceptor.isPresent() && captureStderr)) { + stdoutInterceptor.ifPresent(StreamInterceptor::unregister); + stderrInterceptor.ifPresent(StreamInterceptor::unregister); + return Optional.empty(); + } + return Optional.of(new StreamInterceptingTestExecutionListener(stdoutInterceptor, stderrInterceptor, reporter)); + } + + private StreamInterceptingTestExecutionListener(Optional stdoutInterceptor, + Optional stderrInterceptor, BiConsumer reporter) { + this.stdoutInterceptor = stdoutInterceptor; + this.stderrInterceptor = stderrInterceptor; + this.reporter = reporter; + } + + void unregister() { + stdoutInterceptor.ifPresent(StreamInterceptor::unregister); + stderrInterceptor.ifPresent(StreamInterceptor::unregister); + } + + @Override + public void executionJustStarted(TestIdentifier testIdentifier) { + stdoutInterceptor.ifPresent(StreamInterceptor::capture); + stderrInterceptor.ifPresent(StreamInterceptor::capture); + } + + @Override + public void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + Map map = new HashMap<>(); + String out = stdoutInterceptor.map(StreamInterceptor::consume).orElse(""); + if (StringUtils.isNotBlank(out)) { + map.put(STDOUT_REPORT_ENTRY_KEY, out); + } + String err = stderrInterceptor.map(StreamInterceptor::consume).orElse(""); + if (StringUtils.isNotBlank(err)) { + map.put(STDERR_REPORT_ENTRY_KEY, err); + } + if (!map.isEmpty()) { + reporter.accept(testIdentifier, ReportEntry.from(map)); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java new file mode 100644 index 00000000..aa72890f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * @since 1.3 + */ +class StreamInterceptor extends PrintStream { + + private final PrintStream originalStream; + private final Consumer unregisterAction; + private final int maxNumberOfBytesPerThread; + + private final ThreadLocal output = ThreadLocal.withInitial( + RewindableByteArrayOutputStream::new); + + static Optional registerStdout(int maxNumberOfBytesPerThread) { + return register(System.out, System::setOut, maxNumberOfBytesPerThread); + } + + static Optional registerStderr(int maxNumberOfBytesPerThread) { + return register(System.err, System::setErr, maxNumberOfBytesPerThread); + } + + static Optional register(PrintStream originalStream, Consumer streamSetter, + int maxNumberOfBytesPerThread) { + if (originalStream instanceof StreamInterceptor) { + return Optional.empty(); + } + StreamInterceptor interceptor = new StreamInterceptor(originalStream, streamSetter, maxNumberOfBytesPerThread); + streamSetter.accept(interceptor); + return Optional.of(interceptor); + } + + private StreamInterceptor(PrintStream originalStream, Consumer unregisterAction, + int maxNumberOfBytesPerThread) { + super(originalStream); + this.originalStream = originalStream; + this.unregisterAction = unregisterAction; + this.maxNumberOfBytesPerThread = maxNumberOfBytesPerThread; + } + + void capture() { + output.get().mark(); + } + + String consume() { + return output.get().rewind(); + } + + void unregister() { + unregisterAction.accept(originalStream); + } + + @Override + public void write(int b) { + RewindableByteArrayOutputStream out = output.get(); + if (out.isMarked() && out.size() < maxNumberOfBytesPerThread) { + out.write(b); + } + super.write(b); + } + + @Override + public void write(byte[] b) { + write(b, 0, b.length); + } + + @Override + public void write(byte[] buf, int off, int len) { + RewindableByteArrayOutputStream out = output.get(); + if (out.isMarked()) { + int actualLength = Math.max(0, Math.min(len, maxNumberOfBytesPerThread - out.size())); + if (actualLength > 0) { + out.write(buf, off, actualLength); + } + } + super.write(buf, off, len); + } + + static class RewindableByteArrayOutputStream extends ByteArrayOutputStream { + + private final Deque markedPositions = new ArrayDeque<>(); + + boolean isMarked() { + return !markedPositions.isEmpty(); + } + + void mark() { + markedPositions.addFirst(count); + } + + String rewind() { + Integer position = markedPositions.pollFirst(); + if (position == null || position == count) { + return ""; + } + int length = count - position; + count -= length; + return new String(buf, position, length); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java new file mode 100644 index 00000000..0c5beba2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.lang.String.join; +import static java.util.stream.Collectors.joining; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.commons.util.CollectionUtils; +import org.junit.platform.engine.TestEngine; + +class TestEngineFormatter { + + private TestEngineFormatter() { + } + + @SuppressWarnings("unchecked") + static String format(String title, Iterable testEngines) { + return format(title, (Stream) CollectionUtils.toStream(testEngines)); + } + + private static String format(String title, Stream testEngines) { + String details = testEngines // + .map(engine -> String.format("- %s (%s)", engine.getId(), join(", ", computeAttributes(engine)))) // + .collect(joining("\n")); + return title + ":" + (details.isEmpty() ? " " : "\n" + details); + } + + private static List computeAttributes(TestEngine engine) { + List attributes = new ArrayList<>(4); + engine.getGroupId().ifPresent(groupId -> attributes.add("group ID: " + groupId)); + engine.getArtifactId().ifPresent(artifactId -> attributes.add("artifact ID: " + artifactId)); + engine.getVersion().ifPresent(version -> attributes.add("version: " + version)); + ClassLoaderUtils.getLocation(engine).ifPresent(location -> attributes.add("location: " + location)); + return attributes; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java new file mode 100644 index 00000000..779b81d6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java @@ -0,0 +1,8 @@ +/** + * Core support classes for the {@link org.junit.platform.launcher.Launcher Launcher} + * including the {@link org.junit.platform.launcher.core.LauncherFactory LauncherFactory} + * and the {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder + * LauncherDiscoveryRequestBuilder}. + */ + +package org.junit.platform.launcher.core; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java new file mode 100644 index 00000000..4ab5097a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.apiguardian.api.API.Status.DEPRECATED; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * Utility methods for dealing with legacy reporting infrastructure, such as + * reporting systems built on the Ant-based XML reporting format for JUnit 4. + * + * @since 1.0.3 + * @deprecated Use {@link org.junit.platform.reporting.legacy.LegacyReportingUtils} + * instead. + */ +@Deprecated +@SuppressWarnings("JavadocReference") +@API(status = DEPRECATED, since = "1.6") +public class LegacyReportingUtils { + + private LegacyReportingUtils() { + /* no-op */ + } + + /** + * Get the class name for the supplied {@link TestIdentifier} using the + * supplied {@link TestPlan}. + * + *

This implementation attempts to find the closest test identifier with + * a {@link ClassSource} by traversing the hierarchy upwards towards the + * root starting with the supplied test identifier. In case no such source + * is found, it falls back to using the parent's + * {@linkplain TestIdentifier#getLegacyReportingName legacy reporting name}. + * + * @param testPlan the test plan that contains the {@code TestIdentifier}; + * never {@code null} + * @param testIdentifier the identifier to determine the class name for; + * never {@code null} + * @see TestIdentifier#getLegacyReportingName + */ + public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifier) { + Preconditions.notNull(testPlan, "testPlan must not be null"); + Preconditions.notNull(testIdentifier, "testIdentifier must not be null"); + for (TestIdentifier current = testIdentifier; current != null; current = getParent(testPlan, current)) { + ClassSource source = getClassSource(current); + if (source != null) { + return source.getClassName(); + } + } + return getParentLegacyReportingName(testPlan, testIdentifier); + } + + private static TestIdentifier getParent(TestPlan testPlan, TestIdentifier testIdentifier) { + return testPlan.getParent(testIdentifier).orElse(null); + } + + private static ClassSource getClassSource(TestIdentifier current) { + // @formatter:off + return current.getSource() + .filter(ClassSource.class::isInstance) + .map(ClassSource.class::cast) + .orElse(null); + // @formatter:on + } + + private static String getParentLegacyReportingName(TestPlan testPlan, TestIdentifier testIdentifier) { + // @formatter:off + return testPlan.getParent(testIdentifier) + .map(TestIdentifier::getLegacyReportingName) + .orElse(""); + // @formatter:on + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java new file mode 100644 index 00000000..949d3657 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java @@ -0,0 +1,130 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * Simple {@link TestExecutionListener} for logging informational messages + * for all events via a {@link BiConsumer} that consumes {@code Throwable} + * and {@code Supplier}. + * + * @since 1.0 + * @see #forJavaUtilLogging() + * @see #forJavaUtilLogging(Level) + * @see LoggingListener#LoggingListener(BiConsumer) + */ +@API(status = MAINTAINED, since = "1.0") +public class LoggingListener implements TestExecutionListener { + + /** + * Create a {@code LoggingListener} which delegates to a + * {@link java.util.logging.Logger} using a log level of + * {@link Level#FINE FINE}. + * + * @see #forJavaUtilLogging(Level) + * @see #forBiConsumer(BiConsumer) + */ + public static LoggingListener forJavaUtilLogging() { + return forJavaUtilLogging(Level.FINE); + } + + /** + * Create a {@code LoggingListener} which delegates to a + * {@link java.util.logging.Logger} using the supplied + * {@linkplain Level log level}. + * + * @param logLevel the log level to use; never {@code null} + * @see #forJavaUtilLogging() + * @see #forBiConsumer(BiConsumer) + */ + public static LoggingListener forJavaUtilLogging(Level logLevel) { + Preconditions.notNull(logLevel, "logLevel must not be null"); + Logger logger = Logger.getLogger(LoggingListener.class.getName()); + return new LoggingListener((t, messageSupplier) -> logger.log(logLevel, t, messageSupplier)); + } + + /** + * Create a {@code LoggingListener} which delegates to the supplied + * {@link BiConsumer} for consumption of logging messages. + * + *

The {@code BiConsumer's} arguments are a {@link Throwable} (potentially + * {@code null}) and a {@link Supplier} (never {@code null}) for the log + * message. + * + * @param logger a logger implemented as a {@code BiConsumer}; + * never {@code null} + * + * @see #forJavaUtilLogging() + * @see #forJavaUtilLogging(Level) + */ + public static LoggingListener forBiConsumer(BiConsumer> logger) { + return new LoggingListener(logger); + } + + private final BiConsumer> logger; + + private LoggingListener(BiConsumer> logger) { + this.logger = Preconditions.notNull(logger, "logger must not be null"); + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + log("TestPlan Execution Started: %s", testPlan); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + log("TestPlan Execution Finished: %s", testPlan); + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + log("Dynamic Test Registered: %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + log("Execution Started: %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + log("Execution Skipped: %s - %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), reason); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + logWithThrowable("Execution Finished: %s - %s - %s", testExecutionResult.getThrowable().orElse(null), + testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), testExecutionResult); + } + + private void log(String message, Object... args) { + logWithThrowable(message, null, args); + } + + private void logWithThrowable(String message, Throwable t, Object... args) { + this.logger.accept(t, () -> String.format(message, args)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java new file mode 100644 index 00000000..96f34144 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java @@ -0,0 +1,308 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static java.lang.String.join; +import static java.util.Collections.synchronizedList; + +import java.io.PrintWriter; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * Mutable, internal implementation of the {@link TestExecutionSummary} API. + * + * @since 1.0 + */ +class MutableTestExecutionSummary implements TestExecutionSummary { + + private static final String TAB = " "; + private static final String DOUBLE_TAB = TAB + TAB; + private static final int DEFAULT_MAX_STACKTRACE_LINES = 10; + + private static final String CAUSED_BY = "Caused by: "; + private static final String SUPPRESSED = "Suppressed: "; + private static final String CIRCULAR = "Circular reference: "; + + final AtomicLong containersFound = new AtomicLong(); + final AtomicLong containersStarted = new AtomicLong(); + final AtomicLong containersSkipped = new AtomicLong(); + final AtomicLong containersAborted = new AtomicLong(); + final AtomicLong containersSucceeded = new AtomicLong(); + final AtomicLong containersFailed = new AtomicLong(); + + final AtomicLong testsFound = new AtomicLong(); + final AtomicLong testsStarted = new AtomicLong(); + final AtomicLong testsSkipped = new AtomicLong(); + final AtomicLong testsAborted = new AtomicLong(); + final AtomicLong testsSucceeded = new AtomicLong(); + final AtomicLong testsFailed = new AtomicLong(); + + private final TestPlan testPlan; + private final List failures = synchronizedList(new ArrayList<>()); + private final long timeStarted; + private final long timeStartedNanos; + long timeFinished; + long timeFinishedNanos; + + MutableTestExecutionSummary(TestPlan testPlan) { + this.testPlan = testPlan; + this.containersFound.set(testPlan.countTestIdentifiers(TestIdentifier::isContainer)); + this.testsFound.set(testPlan.countTestIdentifiers(TestIdentifier::isTest)); + this.timeStarted = System.currentTimeMillis(); + this.timeStartedNanos = System.nanoTime(); + } + + void addFailure(TestIdentifier testIdentifier, Throwable throwable) { + this.failures.add(new DefaultFailure(testIdentifier, throwable)); + } + + @Override + public long getTimeStarted() { + return this.timeStarted; + } + + @Override + public long getTimeFinished() { + return this.timeFinished; + } + + @Override + public long getTotalFailureCount() { + return getTestsFailedCount() + getContainersFailedCount(); + } + + @Override + public long getContainersFoundCount() { + return this.containersFound.get(); + } + + @Override + public long getContainersStartedCount() { + return this.containersStarted.get(); + } + + @Override + public long getContainersSkippedCount() { + return this.containersSkipped.get(); + } + + @Override + public long getContainersAbortedCount() { + return this.containersAborted.get(); + } + + @Override + public long getContainersSucceededCount() { + return this.containersSucceeded.get(); + } + + @Override + public long getContainersFailedCount() { + return this.containersFailed.get(); + } + + @Override + public long getTestsFoundCount() { + return this.testsFound.get(); + } + + @Override + public long getTestsStartedCount() { + return this.testsStarted.get(); + } + + @Override + public long getTestsSkippedCount() { + return this.testsSkipped.get(); + } + + @Override + public long getTestsAbortedCount() { + return this.testsAborted.get(); + } + + @Override + public long getTestsSucceededCount() { + return this.testsSucceeded.get(); + } + + @Override + public long getTestsFailedCount() { + return this.testsFailed.get(); + } + + @Override + public void printTo(PrintWriter writer) { + // @formatter:off + writer.printf("%nTest run finished after %d ms%n" + + + "[%10d containers found ]%n" + + "[%10d containers skipped ]%n" + + "[%10d containers started ]%n" + + "[%10d containers aborted ]%n" + + "[%10d containers successful ]%n" + + "[%10d containers failed ]%n" + + + "[%10d tests found ]%n" + + "[%10d tests skipped ]%n" + + "[%10d tests started ]%n" + + "[%10d tests aborted ]%n" + + "[%10d tests successful ]%n" + + "[%10d tests failed ]%n" + + "%n", + + Duration.ofNanos(this.timeFinishedNanos - this.timeStartedNanos).toMillis(), + + getContainersFoundCount(), + getContainersSkippedCount(), + getContainersStartedCount(), + getContainersAbortedCount(), + getContainersSucceededCount(), + getContainersFailedCount(), + + getTestsFoundCount(), + getTestsSkippedCount(), + getTestsStartedCount(), + getTestsAbortedCount(), + getTestsSucceededCount(), + getTestsFailedCount() + ); + // @formatter:on + + writer.flush(); + } + + @Override + public void printFailuresTo(PrintWriter writer) { + printFailuresTo(writer, DEFAULT_MAX_STACKTRACE_LINES); + } + + @Override + public void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { + Preconditions.notNull(writer, "PrintWriter must not be null"); + Preconditions.condition(maxStackTraceLines >= 0, "maxStackTraceLines must be a positive number"); + + if (getTotalFailureCount() > 0) { + writer.printf("%nFailures (%d):%n", getTotalFailureCount()); + this.failures.forEach(failure -> { + writer.printf("%s%s%n", TAB, describeTest(failure.getTestIdentifier())); + printSource(writer, failure.getTestIdentifier()); + writer.printf("%s=> %s%n", DOUBLE_TAB, failure.getException()); + printStackTrace(writer, failure.getException(), maxStackTraceLines); + }); + writer.flush(); + } + } + + @Override + public List getFailures() { + return Collections.unmodifiableList(failures); + } + + private String describeTest(TestIdentifier testIdentifier) { + List descriptionParts = new ArrayList<>(); + collectTestDescription(testIdentifier, descriptionParts); + return join(":", descriptionParts); + } + + private void collectTestDescription(TestIdentifier identifier, List descriptionParts) { + descriptionParts.add(0, identifier.getDisplayName()); + this.testPlan.getParent(identifier).ifPresent(parent -> collectTestDescription(parent, descriptionParts)); + } + + private void printSource(PrintWriter writer, TestIdentifier testIdentifier) { + testIdentifier.getSource().ifPresent(source -> writer.printf("%s%s%n", DOUBLE_TAB, source)); + } + + private void printStackTrace(PrintWriter writer, Throwable throwable, int max) { + if (throwable.getCause() != null + || (throwable.getSuppressed() != null && throwable.getSuppressed().length > 0)) { + max = max / 2; + } + printStackTrace(writer, new StackTraceElement[] {}, throwable, "", DOUBLE_TAB + " ", new HashSet<>(), max); + writer.flush(); + } + + private void printStackTrace(PrintWriter writer, StackTraceElement[] parentTrace, Throwable throwable, + String caption, String indentation, Set seenThrowables, int max) { + if (seenThrowables.contains(throwable)) { + writer.printf("%s%s[%s%s]%n", indentation, TAB, CIRCULAR, throwable); + return; + } + seenThrowables.add(throwable); + + StackTraceElement[] trace = throwable.getStackTrace(); + if (parentTrace != null && parentTrace.length > 0) { + writer.printf("%s%s%s%n", indentation, caption, throwable); + } + int duplicates = numberOfCommonFrames(trace, parentTrace); + int numDistinctFrames = trace.length - duplicates; + int numDisplayLines = Math.min(numDistinctFrames, max); + for (int i = 0; i < numDisplayLines; i++) { + writer.printf("%s%s%s%n", indentation, TAB, trace[i]); + } + if (trace.length > max || duplicates != 0) { + writer.printf("%s%s%s%n", indentation, TAB, "[...]"); + } + + for (Throwable suppressed : throwable.getSuppressed()) { + printStackTrace(writer, trace, suppressed, SUPPRESSED, indentation + TAB, seenThrowables, max); + } + if (throwable.getCause() != null) { + printStackTrace(writer, trace, throwable.getCause(), CAUSED_BY, indentation, seenThrowables, max); + } + } + + private int numberOfCommonFrames(StackTraceElement[] currentTrace, StackTraceElement[] parentTrace) { + int currentIndex = currentTrace.length - 1; + for (int parentIndex = parentTrace.length - 1; currentIndex >= 0 + && parentIndex >= 0; currentIndex--, parentIndex--) { + if (!currentTrace[currentIndex].equals(parentTrace[parentIndex])) { + break; + } + } + return currentTrace.length - 1 - currentIndex; + } + + private static class DefaultFailure implements Failure { + + private static final long serialVersionUID = 1L; + + private final TestIdentifier testIdentifier; + private final Throwable exception; + + DefaultFailure(TestIdentifier testIdentifier, Throwable exception) { + this.testIdentifier = testIdentifier; + this.exception = exception; + } + + @Override + public TestIdentifier getTestIdentifier() { + return testIdentifier; + } + + @Override + public Throwable getException() { + return exception; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java new file mode 100644 index 00000000..14080b1f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.util.Optional; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.StringUtils; + +@API(status = INTERNAL, since = "1.9") +public class OutputDir { + + public static OutputDir create(Optional customDir) { + try { + return createSafely(customDir, () -> Paths.get(".").toAbsolutePath()); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to create output dir", e); + } + } + + /** + * Package private for testing purposes. + */ + static OutputDir createSafely(Optional customDir, Supplier currentWorkingDir) throws IOException { + Path cwd = currentWorkingDir.get(); + Path outputDir; + + if (customDir.isPresent() && StringUtils.isNotBlank(customDir.get())) { + outputDir = cwd.resolve(customDir.get()); + } + else if (Files.exists(cwd.resolve("pom.xml"))) { + outputDir = cwd.resolve("target"); + } + else if (containsFilesWithExtensions(cwd, ".gradle", ".gradle.kts")) { + outputDir = cwd.resolve("build"); + } + else { + outputDir = cwd; + } + + if (!Files.exists(outputDir)) { + Files.createDirectories(outputDir); + } + + return new OutputDir(outputDir); + } + + private final Path path; + + private OutputDir(Path path) { + this.path = path; + } + + public Path toPath() { + return path; + } + + public Path createFile(String prefix, String extension) throws UncheckedIOException { + String filename = String.format("%s-%d.%s", prefix, Math.abs(new SecureRandom().nextLong()), extension); + Path outputFile = path.resolve(filename); + + try { + if (Files.exists(outputFile)) { + Files.delete(outputFile); + } + return Files.createFile(outputFile); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to create output file: " + outputFile, e); + } + } + + /** + * Determine if the supplied directory contains files with any of the + * supplied extensions. + */ + private static boolean containsFilesWithExtensions(Path dir, String... extensions) throws IOException { + return Files.find(dir, 1, // + (path, basicFileAttributes) -> { + if (basicFileAttributes.isRegularFile()) { + for (String extension : extensions) { + if (path.getFileName().toString().endsWith(extension)) { + return true; + } + } + } + return false; + }) // + .findFirst() // + .isPresent(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java new file mode 100644 index 00000000..a161fc50 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static java.util.stream.Stream.concat; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * Simple {@link TestExecutionListener} that generates a + * {@linkplain TestExecutionSummary summary} of the test execution. + * + * @since 1.0 + * @see #getSummary() + */ +@API(status = MAINTAINED, since = "1.0") +public class SummaryGeneratingListener implements TestExecutionListener { + + private TestPlan testPlan; + private MutableTestExecutionSummary summary; + + public SummaryGeneratingListener() { + + } + + /** + * Get the summary generated by this listener. + */ + public TestExecutionSummary getSummary() { + return this.summary; + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + this.testPlan = testPlan; + this.summary = new MutableTestExecutionSummary(testPlan); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + this.summary.timeFinished = System.currentTimeMillis(); + this.summary.timeFinishedNanos = System.nanoTime(); + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + if (testIdentifier.isContainer()) { + this.summary.containersFound.incrementAndGet(); + } + if (testIdentifier.isTest()) { + this.summary.testsFound.incrementAndGet(); + } + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + // @formatter:off + long skippedContainers = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) + .filter(TestIdentifier::isContainer) + .count(); + long skippedTests = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) + .filter(TestIdentifier::isTest) + .count(); + // @formatter:on + this.summary.containersSkipped.addAndGet(skippedContainers); + this.summary.testsSkipped.addAndGet(skippedTests); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + if (testIdentifier.isContainer()) { + this.summary.containersStarted.incrementAndGet(); + } + if (testIdentifier.isTest()) { + this.summary.testsStarted.incrementAndGet(); + } + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + + switch (testExecutionResult.getStatus()) { + + case SUCCESSFUL: { + if (testIdentifier.isContainer()) { + this.summary.containersSucceeded.incrementAndGet(); + } + if (testIdentifier.isTest()) { + this.summary.testsSucceeded.incrementAndGet(); + } + break; + } + + case ABORTED: { + if (testIdentifier.isContainer()) { + this.summary.containersAborted.incrementAndGet(); + } + if (testIdentifier.isTest()) { + this.summary.testsAborted.incrementAndGet(); + } + break; + } + + case FAILED: { + if (testIdentifier.isContainer()) { + this.summary.containersFailed.incrementAndGet(); + } + if (testIdentifier.isTest()) { + this.summary.testsFailed.incrementAndGet(); + } + testExecutionResult.getThrowable().ifPresent( + throwable -> this.summary.addFailure(testIdentifier, throwable)); + break; + } + + default: + throw new PreconditionViolationException( + "Unsupported execution status:" + testExecutionResult.getStatus()); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java new file mode 100644 index 00000000..3a996d01 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java @@ -0,0 +1,184 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.launcher.TestIdentifier; + +/** + * Summary of test plan execution. + * + * @since 1.0 + * @see SummaryGeneratingListener + */ +@API(status = MAINTAINED, since = "1.0") +public interface TestExecutionSummary { + + /** + * Get the timestamp (in milliseconds) when the test plan started. + */ + long getTimeStarted(); + + /** + * Get the timestamp (in milliseconds) when the test plan finished. + */ + long getTimeFinished(); + + /** + * Get the total number of {@linkplain #getContainersFailedCount failed + * containers} and {@linkplain #getTestsFailedCount failed tests}. + * + * @see #getTestsFailedCount() + * @see #getContainersFailedCount() + */ + long getTotalFailureCount(); + + /** + * Get the number of containers found. + */ + long getContainersFoundCount(); + + /** + * Get the number of containers started. + */ + long getContainersStartedCount(); + + /** + * Get the number of containers skipped. + */ + long getContainersSkippedCount(); + + /** + * Get the number of containers aborted. + */ + long getContainersAbortedCount(); + + /** + * Get the number of containers that succeeded. + */ + long getContainersSucceededCount(); + + /** + * Get the number of containers that failed. + * + * @see #getTestsFailedCount() + * @see #getTotalFailureCount() + */ + long getContainersFailedCount(); + + /** + * Get the number of tests found. + */ + long getTestsFoundCount(); + + /** + * Get the number of tests started. + */ + long getTestsStartedCount(); + + /** + * Get the number of tests skipped. + */ + long getTestsSkippedCount(); + + /** + * Get the number of tests aborted. + */ + long getTestsAbortedCount(); + + /** + * Get the number of tests that succeeded. + */ + long getTestsSucceededCount(); + + /** + * Get the number of tests that failed. + * + * @see #getContainersFailedCount() + * @see #getTotalFailureCount() + */ + long getTestsFailedCount(); + + /** + * Print this summary to the supplied {@link PrintWriter}. + * + *

This method does not print failure messages. + * + * @see #printFailuresTo(PrintWriter) + */ + void printTo(PrintWriter writer); + + /** + * Print failed containers and tests, including sources and exception + * messages, to the supplied {@link PrintWriter}. + * + * @param writer the {@code PrintWriter} to which to print; never {@code null} + * @see #printTo(PrintWriter) + * @see #printFailuresTo(PrintWriter, int) + */ + void printFailuresTo(PrintWriter writer); + + /** + * Print failed containers and tests, including sources and exception + * messages, to the supplied {@link PrintWriter}. + * + *

The maximum number of lines to print for exception stack traces (if any) + * can be specified via the {@code maxStackTraceLines} argument. + * + *

By default, this method delegates to {@link #printFailuresTo(PrintWriter)}, + * effectively ignoring the {@code maxStackTraceLines} argument. Concrete + * implementations of this interface should therefore override this default + * method in order to honor the {@code maxStackTraceLines} argument. + * + * @param writer the {@code PrintWriter} to which to print; never {@code null} + * @param maxStackTraceLines the maximum number of lines to print for exception + * stack traces; must be a positive value + * @since 1.6 + * @see #printTo(PrintWriter) + * @see #printFailuresTo(PrintWriter) + */ + @API(status = MAINTAINED, since = "1.6") + default void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { + printFailuresTo(writer); + } + + /** + * Get an immutable list of the failures of the test plan execution. + */ + List getFailures(); + + /** + * Failure of a test or container. + */ + interface Failure extends Serializable { + + /** + * Get the identifier of the failed test or container. + * + * @return the {@link TestIdentifier} for this failure; never {@code null} + */ + TestIdentifier getTestIdentifier(); + + /** + * Get the {@link Throwable} causing the failure. + * + * @return the {@link Throwable} for this failure; never {@code null} + */ + Throwable getException(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java new file mode 100644 index 00000000..379dccdb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java @@ -0,0 +1,190 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * {@code UniqueIdTrackingListener} is a {@link TestExecutionListener} that tracks + * the {@linkplain TestIdentifier#getUniqueId() unique IDs} of all + * {@linkplain TestIdentifier#isTest() tests} that were + * {@linkplain #executionFinished executed} during the execution of the + * {@link TestPlan} and generates a file containing the unique IDs once execution + * of the {@code TestPlan} has {@linkplain #testPlanExecutionFinished(TestPlan) + * finished}. + * + *

Tests are tracked regardless of their {@link TestExecutionResult} or whether + * they were skipped, and the unique IDs are written to an output file, one ID + * per line, encoding using UTF-8. + * + *

The output file can be used to execute the same set of tests again without + * having to query the user configuration for the test plan and without having to + * perform test discovery again. This can be useful for test environments such as + * within a native image — for example, a GraalVM native image — in + * order to rerun the exact same tests from a standard JVM test run within a + * native image. + * + *

Configuration and Defaults

+ * + *

The {@code OUTPUT_DIR} is the directory in which this listener generates + * the output file. The exact path of the generated file is + * {@code OUTPUT_DIR/OUTPUT_FILE_PREFIX-.txt}, where + * {@code } is a pseudo-random number. The inclusion of a random + * number in the file name ensures that multiple concurrently executing test + * plans do not overwrite each other's results. + * + *

The value of the {@code OUTPUT_FILE_PREFIX} defaults to + * {@link #DEFAULT_OUTPUT_FILE_PREFIX}, but a custom prefix can be set via the + * {@link #OUTPUT_FILE_PREFIX_PROPERTY_NAME} configuration property. + * + *

The {@code OUTPUT_DIR} can be set to a custom directory via the + * {@link #OUTPUT_DIR_PROPERTY_NAME} configuration property. Otherwise the + * following algorithm is used to select a default output directory. + * + *

    + *
  • If the current working directory of the Java process contains a file named + * {@code pom.xml}, the output directory will be {@code ./target}, following the + * conventions of Maven.
  • + *
  • If the current working directory of the Java process contains a file with + * the extension {@code .gradle} or {@code .gradle.kts}, the output directory + * will be {@code ./build}, following the conventions of Gradle.
  • + *
  • Otherwise, the current working directory of the Java process will be used + * as the output directory.
  • + *
+ * + *

For example, in a project using Gradle as the build tool, the file generated + * by this listener would be {@code ./build/junit-platform-unique-ids-.txt} + * by default. + * + *

Configuration properties can be set via JVM system properties, via a + * {@code junit-platform.properties} file in the root of the classpath, or as + * JUnit Platform {@linkplain ConfigurationParameters configuration parameters}. + * + * @since 1.8 + */ +@API(status = EXPERIMENTAL, since = "1.8") +public class UniqueIdTrackingListener implements TestExecutionListener { + + /** + * Property name used to enable the {@code UniqueIdTrackingListener}: {@value} + * + *

The {@code UniqueIdTrackingListener} is registered automatically via + * Java's {@link java.util.ServiceLoader} mechanism but disabled by default. + * + *

Set the value of this property to {@code true} to enable this listener. + */ + public static final String LISTENER_ENABLED_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.enabled"; + + /** + * Property name used to set the path to the output directory for the file + * generated by the {@code UniqueIdTrackingListener}: {@value} + * + *

For details on the default output directory, see the + * {@linkplain UniqueIdTrackingListener class-level Javadoc}. + */ + public static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.output.dir"; + + /** + * Property name used to set the prefix for the name of the file generated + * by the {@code UniqueIdTrackingListener}: {@value} + * + *

Defaults to {@link #DEFAULT_OUTPUT_FILE_PREFIX}. + */ + public static final String OUTPUT_FILE_PREFIX_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.output.file.prefix"; + + /** + * The default prefix for the name of the file generated by the + * {@code UniqueIdTrackingListener}: {@value} + * + * @see #OUTPUT_FILE_PREFIX_PROPERTY_NAME + */ + public static final String DEFAULT_OUTPUT_FILE_PREFIX = "junit-platform-unique-ids"; + + private final Logger logger = LoggerFactory.getLogger(UniqueIdTrackingListener.class); + + private final List uniqueIds = new ArrayList<>(); + + private boolean enabled; + + public UniqueIdTrackingListener() { + // to avoid missing-explicit-ctor warning + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + this.enabled = testPlan.getConfigurationParameters().getBoolean(LISTENER_ENABLED_PROPERTY_NAME).orElse(false); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + trackTestUid(testIdentifier); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + trackTestUid(testIdentifier); + } + + private void trackTestUid(TestIdentifier testIdentifier) { + if (this.enabled && testIdentifier.isTest()) { + this.uniqueIds.add(testIdentifier.getUniqueId()); + } + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + if (this.enabled) { + Path outputFile; + try { + outputFile = createOutputFile(testPlan.getConfigurationParameters()); + } + catch (Exception ex) { + logger.error(ex, () -> "Failed to create output file"); + // Abort since we cannot generate the file. + return; + } + + logger.debug(() -> "Writing unique IDs to output file " + outputFile.toAbsolutePath()); + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputFile, StandardCharsets.UTF_8))) { + this.uniqueIds.forEach(writer::println); + writer.flush(); + } + catch (IOException ex) { + logger.error(ex, () -> "Failed to write unique IDs to output file " + outputFile.toAbsolutePath()); + } + } + } + + private Path createOutputFile(ConfigurationParameters configurationParameters) { + String prefix = configurationParameters.get(OUTPUT_FILE_PREFIX_PROPERTY_NAME) // + .orElse(DEFAULT_OUTPUT_FILE_PREFIX); + return OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)) // + .createFile(prefix, "txt"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java new file mode 100644 index 00000000..b41816c4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.discovery; + +import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; +import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.SelectorResolutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.launcher.EngineDiscoveryResult; +import org.junit.platform.launcher.LauncherDiscoveryListener; + +/** + * @since 1.6 + * @see LauncherDiscoveryListeners#abortOnFailure() + */ +class AbortOnFailureLauncherDiscoveryListener implements LauncherDiscoveryListener { + + @Override + public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { + result.getThrowable().ifPresent(ExceptionUtils::throwAsUncheckedException); + } + + @Override + public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { + if (result.getStatus() == FAILED) { + throw new JUnitException(selector + " resolution failed", result.getThrowable().orElse(null)); + } + if (result.getStatus() == UNRESOLVED && selector instanceof UniqueIdSelector) { + UniqueId uniqueId = ((UniqueIdSelector) selector).getUniqueId(); + if (uniqueId.hasPrefix(engineId)) { + throw new JUnitException(selector + " could not be resolved"); + } + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + return getClass() == obj.getClass(); + } + + @Override + public int hashCode() { + return 0; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java new file mode 100644 index 00000000..14846966 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.discovery; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.SelectorResolutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.EngineDiscoveryResult; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * @since 1.6 + * @see LauncherDiscoveryListeners#composite(List) + */ +class CompositeLauncherDiscoveryListener implements LauncherDiscoveryListener { + + private final List listeners; + + CompositeLauncherDiscoveryListener(List listeners) { + this.listeners = Collections.unmodifiableList(new ArrayList<>(listeners)); + } + + @Override + public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { + listeners.forEach(delegate -> delegate.launcherDiscoveryStarted(request)); + } + + @Override + public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { + listeners.forEach(delegate -> delegate.launcherDiscoveryFinished(request)); + } + + @Override + public void engineDiscoveryStarted(UniqueId engineId) { + listeners.forEach(delegate -> delegate.engineDiscoveryStarted(engineId)); + } + + @Override + public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { + listeners.forEach(delegate -> delegate.engineDiscoveryFinished(engineId, result)); + } + + @Override + public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { + listeners.forEach(delegate -> delegate.selectorProcessed(engineId, selector, result)); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java new file mode 100644 index 00000000..c64f28fc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.discovery; + +import static java.util.stream.Collectors.joining; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.SelectorResolutionResult.Status; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.launcher.LauncherDiscoveryListener; + +/** + * Collection of {@code static} factory methods for creating + * {@link LauncherDiscoveryListener LauncherDiscoveryListeners}. + * + * @since 1.6 + */ +@API(status = EXPERIMENTAL, since = "1.6") +public class LauncherDiscoveryListeners { + + private LauncherDiscoveryListeners() { + } + + /** + * Create a {@link LauncherDiscoveryListener} that aborts test discovery on + * failures. + * + *

The following events are considered failures: + * + *

    + *
  • + * a {@linkplain Status#FAILED failed} resolution result. + *
  • + *
  • + * an {@linkplain Status#FAILED unresolved} resolution result for a + * {@link UniqueIdSelector} that starts with the engine's unique ID. + *
  • + *
  • + * any recoverable {@link Throwable} thrown by + * {@link TestEngine#discover}. + *
  • + *
+ */ + public static LauncherDiscoveryListener abortOnFailure() { + return new AbortOnFailureLauncherDiscoveryListener(); + } + + /** + * Create a {@link LauncherDiscoveryListener} that logs test discovery + * events based on their severity. + * + *

For example, failures during test discovery are logged as errors. + */ + public static LauncherDiscoveryListener logging() { + return new LoggingLauncherDiscoveryListener(); + } + + @API(status = INTERNAL, since = "1.6") + public static LauncherDiscoveryListener composite(List listeners) { + Preconditions.notNull(listeners, "listeners must not be null"); + Preconditions.containsNoNullElements(listeners, "listeners must not contain any null elements"); + if (listeners.isEmpty()) { + return LauncherDiscoveryListener.NOOP; + } + if (listeners.size() == 1) { + return listeners.get(0); + } + return new CompositeLauncherDiscoveryListener(listeners); + } + + @API(status = INTERNAL, since = "1.6") + public static LauncherDiscoveryListener fromConfigurationParameter(String key, String value) { + return Arrays.stream(LauncherDiscoveryListenerType.values()) // + .filter(type -> type.parameterValue.equalsIgnoreCase(value)) // + .findFirst() // + .map(type -> type.creator.get()) // + .orElseThrow(() -> newInvalidConfigurationParameterException(key, value)); + } + + private static JUnitException newInvalidConfigurationParameterException(String key, String value) { + String allowedValues = Arrays.stream(LauncherDiscoveryListenerType.values()) // + .map(type -> type.parameterValue) // + .collect(joining("', '", "'", "'")); + return new JUnitException("Invalid value of configuration parameter '" + key + "': " // + + value + " (allowed are " + allowedValues + ")"); + } + + private enum LauncherDiscoveryListenerType { + + LOGGING("logging", LauncherDiscoveryListeners::logging), + + ABORT_ON_FAILURE("abortOnFailure", LauncherDiscoveryListeners::abortOnFailure); + + private final String parameterValue; + private final Supplier creator; + + LauncherDiscoveryListenerType(String parameterValue, Supplier creator) { + this.parameterValue = parameterValue; + this.creator = creator; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java new file mode 100644 index 00000000..b3a674d5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.discovery; + +import static org.junit.platform.launcher.EngineDiscoveryResult.Status.FAILED; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.SelectorResolutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.launcher.EngineDiscoveryResult; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * @since 1.6 + * @see LauncherDiscoveryListeners#logging() + */ +class LoggingLauncherDiscoveryListener implements LauncherDiscoveryListener { + + private static final Logger logger = LoggerFactory.getLogger(LoggingLauncherDiscoveryListener.class); + + @Override + public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { + logger.trace(() -> "Test discovery started"); + } + + @Override + public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { + logger.trace(() -> "Test discovery finished"); + } + + @Override + public void engineDiscoveryStarted(UniqueId engineId) { + logger.trace(() -> "Engine " + engineId + " has started discovering tests"); + } + + @Override + public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { + if (result.getStatus() == FAILED) { + Optional failure = result.getThrowable(); + if (failure.isPresent()) { + logger.error(failure.get().getCause(), () -> failure.get().getMessage()); + } + else { + logger.error(() -> "Engine " + engineId + " failed to discover tests"); + } + } + else { + logger.trace(() -> "Engine " + engineId + " has finished discovering tests"); + } + } + + @Override + public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { + switch (result.getStatus()) { + case RESOLVED: + logger.debug(() -> selector + " was resolved by " + engineId); + break; + case FAILED: + logger.error(result.getThrowable().orElse(null), + () -> "Resolution of " + selector + " by " + engineId + " failed"); + break; + case UNRESOLVED: + Consumer> loggingConsumer = logger::debug; + if (selector instanceof UniqueIdSelector) { + UniqueId uniqueId = ((UniqueIdSelector) selector).getUniqueId(); + if (uniqueId.hasPrefix(engineId)) { + loggingConsumer = logger::warn; + } + } + loggingConsumer.accept(() -> selector + " could not be resolved by " + engineId); + break; + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + return getClass() == obj.getClass(); + } + + @Override + public int hashCode() { + return 1; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java new file mode 100644 index 00000000..371df34d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java @@ -0,0 +1,9 @@ +/** + * Common {@link org.junit.platform.launcher.LauncherDiscoveryListener} + * implementations and factory methods. + * + * @see org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners + * @since 1.6 + */ + +package org.junit.platform.launcher.listeners.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java new file mode 100644 index 00000000..d8a0a06e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java @@ -0,0 +1,7 @@ +/** + * Common {@link org.junit.platform.launcher.TestExecutionListener + * TestExecutionListener} implementations and related support classes for + * the {@link org.junit.platform.launcher.Launcher Launcher}. + */ + +package org.junit.platform.launcher.listeners; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java new file mode 100644 index 00000000..a79f93f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.session; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; + +/** + * @since 1.8 + * @see LauncherSessionListeners#composite(List) + */ +class CompositeLauncherSessionListener implements LauncherSessionListener { + + private final List listeners; + + CompositeLauncherSessionListener(List listeners) { + this.listeners = Collections.unmodifiableList(new ArrayList<>(listeners)); + } + + @Override + public void launcherSessionOpened(LauncherSession session) { + listeners.forEach(delegate -> delegate.launcherSessionOpened(session)); + } + + @Override + public void launcherSessionClosed(LauncherSession session) { + listeners.forEach(delegate -> delegate.launcherSessionClosed(session)); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java new file mode 100644 index 00000000..f200e01a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.session; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.launcher.LauncherSessionListener; + +/** + * Collection of {@code static} factory methods for creating + * {@link LauncherSessionListener LauncherSessionListeners}. + * + * @since 1.8 + */ +@API(status = INTERNAL, since = "1.8") +public class LauncherSessionListeners { + + private LauncherSessionListeners() { + } + + public static LauncherSessionListener composite(List listeners) { + Preconditions.notNull(listeners, "listeners must not be null"); + Preconditions.containsNoNullElements(listeners, "listeners must not contain any null elements"); + if (listeners.isEmpty()) { + return LauncherSessionListener.NOOP; + } + if (listeners.size() == 1) { + return listeners.get(0); + } + return new CompositeLauncherSessionListener(listeners); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java new file mode 100644 index 00000000..97541a15 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java @@ -0,0 +1,9 @@ +/** + * Common {@link org.junit.platform.launcher.LauncherSessionListener} + * implementations and factory methods. + * + * @see org.junit.platform.launcher.listeners.session.LauncherSessionListeners + * @since 1.8 + */ + +package org.junit.platform.launcher.listeners.session; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java new file mode 100644 index 00000000..4005a842 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java @@ -0,0 +1,7 @@ +/** + * Public API for configuring and launching test plans. + * + *

This API is typically used by IDEs and build tools. + */ + +package org.junit.platform.launcher; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java new file mode 100644 index 00000000..c49df5f6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * @since 1.1 + */ +class DequeStack implements Stack { + + private final Deque deque = new ArrayDeque<>(); + + @Override + public void push(T t) { + deque.addFirst(t); + } + + @Override + public T peek() { + return deque.peek(); + } + + @Override + public T pop() { + return deque.pollFirst(); + } + + @Override + public boolean isEmpty() { + return deque.isEmpty(); + } + + @Override + public int size() { + return deque.size(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java new file mode 100644 index 00000000..936b53c5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Left; +import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOperatorBetween; +import static org.junit.platform.launcher.tagexpression.ParseStatus.missingRhsOperand; +import static org.junit.platform.launcher.tagexpression.ParseStatus.problemParsing; +import static org.junit.platform.launcher.tagexpression.ParseStatus.success; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * @since 1.1 + */ +class Operator { + + enum Associativity { + Left, Right + } + + interface TagExpressionCreator { + ParseStatus createExpressionAndAddTo(Stack> expressions, Token operatorToken); + } + + static Operator nullaryOperator(String representation, int precedence) { + return new Operator(representation, precedence, 0, null, (expressions, operatorToken) -> success()); + } + + static Operator unaryOperator(String representation, int precedence, Associativity associativity, + Function unaryExpression) { + + return new Operator(representation, precedence, 1, associativity, (expressions, operatorToken) -> { + TokenWith rhs = expressions.pop(); + if (operatorToken.isLeftOf(rhs.token)) { + Token combinedToken = operatorToken.concatenate(rhs.token); + expressions.push(new TokenWith<>(combinedToken, unaryExpression.apply(rhs.element))); + return success(); + } + return missingRhsOperand(operatorToken, representation); + }); + } + + static Operator binaryOperator(String representation, int precedence, Associativity associativity, + BiFunction binaryExpression) { + + return new Operator(representation, precedence, 2, associativity, (expressions, operatorToken) -> { + TokenWith rhs = expressions.pop(); + TokenWith lhs = expressions.pop(); + Token lhsToken = lhs.token; + if (lhsToken.isLeftOf(operatorToken) && operatorToken.isLeftOf(rhs.token)) { + Token combinedToken = lhsToken.concatenate(operatorToken).concatenate(rhs.token); + expressions.push(new TokenWith<>(combinedToken, binaryExpression.apply(lhs.element, rhs.element))); + return success(); + } + if (rhs.token.isLeftOf(operatorToken)) { + return missingRhsOperand(operatorToken, representation); + } + if (operatorToken.isLeftOf(lhsToken)) { + return missingOperatorBetween(lhs, rhs); + } + return problemParsing(operatorToken, representation); + }); + } + + private final String representation; + private final int precedence; + private final int arity; + private final Associativity associativity; + private final TagExpressionCreator tagExpressionCreator; + + private Operator(String representation, int precedence, int arity, Associativity associativity, + TagExpressionCreator tagExpressionCreator) { + + this.representation = representation; + this.precedence = precedence; + this.arity = arity; + this.associativity = associativity; + this.tagExpressionCreator = tagExpressionCreator; + } + + boolean represents(String token) { + return representation.equals(token); + } + + String representation() { + return representation; + } + + boolean hasLowerPrecedenceThan(Operator operator) { + return this.precedence < operator.precedence; + } + + boolean hasSamePrecedenceAs(Operator operator) { + return this.precedence == operator.precedence; + } + + boolean isLeftAssociative() { + return Left == associativity; + } + + ParseStatus createAndAddExpressionTo(Stack> expressions, Token operatorToken) { + if (expressions.size() < arity) { + String message = createMissingOperandMessage(expressions, operatorToken); + return ParseStatus.errorAt(operatorToken, representation, message); + } + return tagExpressionCreator.createExpressionAndAddTo(expressions, operatorToken); + } + + private String createMissingOperandMessage(Stack> expressions, Token operatorToken) { + if (1 == arity) { + return missingOneOperand(associativity == Left ? "lhs" : "rhs"); + } + + if (2 == arity) { + int mismatch = arity - expressions.size(); + if (2 == mismatch) { + return "missing lhs and rhs operand"; + } + return missingOneOperand(operatorToken.isLeftOf(expressions.peek().token) ? "lhs" : "rhs"); + } + return "missing operand"; + } + + private String missingOneOperand(String side) { + return "missing " + side + " operand"; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java new file mode 100644 index 00000000..a8685088 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Left; +import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Right; + +import java.util.Map; +import java.util.stream.Stream; + +/** + * @since 1.1 + */ +class Operators { + + private static final Operator Not = Operator.unaryOperator("!", 3, Right, TagExpressions::not); + private static final Operator And = Operator.binaryOperator("&", 2, Left, TagExpressions::and); + private static final Operator Or = Operator.binaryOperator("|", 1, Left, TagExpressions::or); + + private final Map representationToOperator = Stream.of(Not, And, Or).collect( + toMap(Operator::representation, identity())); + + boolean isOperator(String token) { + return representationToOperator.containsKey(token); + } + + Operator operatorFor(String token) { + return representationToOperator.get(token); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java new file mode 100644 index 00000000..1c0a1346 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Optional; +import java.util.function.Function; + +import org.apiguardian.api.API; + +/** + * The result of attempting to parse a {@link TagExpression}. + * + *

An instance of this type either contains a successfully parsed + * {@link TagExpression} or an error message describing the parse + * error. + * + * @since 1.1 + * @see TagExpression#parseFrom(String) + */ +@API(status = INTERNAL, since = "1.1") +public interface ParseResult { + + /** + * Return the parsed {@link TagExpression} or throw an exception with the + * contained parse error. + * + * @param exceptionCreator will be called with the error message in case + * this parse result contains a parse error; never {@code null}. + */ + default TagExpression tagExpressionOrThrow(Function exceptionCreator) { + if (errorMessage().isPresent()) { + throw exceptionCreator.apply(errorMessage().get()); + } + return tagExpression().get(); + } + + /** + * Return the contained parse error message, if any. + */ + default Optional errorMessage() { + return Optional.empty(); + } + + /** + * Return the contained {@link TagExpression}, if any. + */ + default Optional tagExpression() { + return Optional.empty(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java new file mode 100644 index 00000000..ceb19b32 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import java.util.Optional; + +/** + * @since 1.1 + */ +class ParseResults { + + private ParseResults() { + /* no-op */ + } + + static ParseResult success(TagExpression tagExpression) { + return new ParseResult() { + @Override + public Optional tagExpression() { + return Optional.of(tagExpression); + } + }; + } + + static ParseResult error(String errorMessage) { + return new ParseResult() { + @Override + public Optional errorMessage() { + return Optional.of(errorMessage); + } + }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java new file mode 100644 index 00000000..52f8fd15 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import java.util.function.Supplier; + +/** + * @since 1.1 + */ +class ParseStatus { + + static ParseStatus success() { + return error(null); + } + + static ParseStatus problemParsing(Token token, String representation) { + return errorAt(token, representation, "problem parsing"); + } + + static ParseStatus missingOpeningParenthesis(Token token, String representation) { + return errorAt(token, representation, "missing opening parenthesis"); + } + + static ParseStatus missingClosingParenthesis(Token token, String representation) { + return errorAt(token, representation, "missing closing parenthesis"); + } + + static ParseStatus missingRhsOperand(Token token, String representation) { + return errorAt(token, representation, "missing rhs operand"); + } + + static ParseStatus errorAt(Token token, String operatorRepresentation, String message) { + return error( + message + " for '" + operatorRepresentation + "' at index " + format(token.trimmedTokenStartIndex())); + } + + static ParseStatus missingOperatorBetween(TokenWith lhs, TokenWith rhs) { + String lhsString = "'" + lhs.element.toString() + "' at index " + format(lhs.token.lastCharacterIndex()); + String rhsString = "'" + rhs.element.toString() + "' at index " + format(rhs.token.trimmedTokenStartIndex()); + return error("missing operator between " + lhsString + " and " + rhsString); + } + + static ParseStatus emptyTagExpression() { + return error("empty tag expression"); + } + + private static String format(int indexInTagExpression) { + return "<" + indexInTagExpression + ">"; + } + + private static ParseStatus error(String errorMessage) { + return new ParseStatus(errorMessage); + } + + final String errorMessage; + + private ParseStatus(String errorMessage) { + this.errorMessage = errorMessage; + } + + public ParseStatus process(Supplier step) { + if (isSuccess()) { + return step.get(); + } + return this; + } + + public boolean isError() { + return !isSuccess(); + } + + private boolean isSuccess() { + return errorMessage == null; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java new file mode 100644 index 00000000..c3b3000a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import java.util.List; + +/** + * @since 1.1 + */ +class Parser { + + private final Tokenizer tokenizer = new Tokenizer(); + + ParseResult parse(String infixTagExpression) { + return constructExpressionFrom(tokensDerivedFrom(infixTagExpression)); + } + + private List tokensDerivedFrom(String infixTagExpression) { + return tokenizer.tokenize(infixTagExpression); + } + + private ParseResult constructExpressionFrom(List tokens) { + return new ShuntingYard(tokens).execute(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java new file mode 100644 index 00000000..063574ff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java @@ -0,0 +1,164 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static java.lang.Integer.MIN_VALUE; +import static org.junit.platform.launcher.tagexpression.Operator.nullaryOperator; +import static org.junit.platform.launcher.tagexpression.ParseStatus.emptyTagExpression; +import static org.junit.platform.launcher.tagexpression.ParseStatus.missingClosingParenthesis; +import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOpeningParenthesis; +import static org.junit.platform.launcher.tagexpression.ParseStatus.success; +import static org.junit.platform.launcher.tagexpression.TagExpressions.any; +import static org.junit.platform.launcher.tagexpression.TagExpressions.none; +import static org.junit.platform.launcher.tagexpression.TagExpressions.tag; + +import java.util.List; + +/** + * This is based on a modified version of the + * + * Shunting-yard algorithm. + * + * @since 1.1 + */ +class ShuntingYard { + + private static final Operator RightParenthesis = nullaryOperator(")", -1); + private static final Operator LeftParenthesis = nullaryOperator("(", -2); + private static final Operator Sentinel = nullaryOperator("sentinel", MIN_VALUE); + private static final Token SentinelToken = new Token(-1, ""); + + private final Operators validOperators = new Operators(); + private final Stack> expressions = new DequeStack<>(); + private final Stack> operators = new DequeStack<>(); + private final List tokens; + + ShuntingYard(List tokens) { + this.tokens = tokens; + pushOperatorAt(SentinelToken, Sentinel); + } + + public ParseResult execute() { + // @formatter:off + ParseStatus parseStatus = processTokens() + .process(this::consumeRemainingOperators) + .process(this::ensureOnlySingleExpressionRemains); + // @formatter:on + if (parseStatus.isError()) { + return ParseResults.error(parseStatus.errorMessage); + } + return ParseResults.success(expressions.pop().element); + } + + private ParseStatus processTokens() { + ParseStatus parseStatus = success(); + for (Token token : tokens) { + parseStatus = parseStatus.process(() -> process(token)); + } + return parseStatus; + } + + private ParseStatus process(Token token) { + String trimmed = token.string(); + if (LeftParenthesis.represents(trimmed)) { + pushOperatorAt(token, LeftParenthesis); + return success(); + } + if (RightParenthesis.represents(trimmed)) { + return findMatchingLeftParenthesis(token); + } + if (validOperators.isOperator(trimmed)) { + Operator operator = validOperators.operatorFor(trimmed); + return findOperands(token, operator); + } + pushExpressionAt(token, convertLeafTokenToExpression(trimmed)); + return success(); + } + + private TagExpression convertLeafTokenToExpression(String trimmed) { + if ("any()".equalsIgnoreCase(trimmed)) { + return any(); + } + if ("none()".equalsIgnoreCase(trimmed)) { + return none(); + } + return tag(trimmed); + } + + private ParseStatus findMatchingLeftParenthesis(Token token) { + while (!operators.isEmpty()) { + TokenWith tokenWithWithOperator = operators.pop(); + Operator operator = tokenWithWithOperator.element; + if (LeftParenthesis.equals(operator)) { + return success(); + } + ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token); + if (parseStatus.isError()) { + return parseStatus; + } + } + return missingOpeningParenthesis(token, RightParenthesis.representation()); + } + + private ParseStatus findOperands(Token token, Operator currentOperator) { + while (currentOperator.hasLowerPrecedenceThan(previousOperator()) + || currentOperator.hasSamePrecedenceAs(previousOperator()) && currentOperator.isLeftAssociative()) { + TokenWith tokenWithWithOperator = operators.pop(); + ParseStatus parseStatus = tokenWithWithOperator.element.createAndAddExpressionTo(expressions, + tokenWithWithOperator.token); + if (parseStatus.isError()) { + return parseStatus; + } + } + pushOperatorAt(token, currentOperator); + return success(); + } + + private Operator previousOperator() { + return operators.peek().element; + } + + private void pushExpressionAt(Token token, TagExpression tagExpression) { + expressions.push(new TokenWith<>(token, tagExpression)); + } + + private void pushOperatorAt(Token token, Operator operator) { + operators.push(new TokenWith<>(token, operator)); + } + + private ParseStatus consumeRemainingOperators() { + while (!operators.isEmpty()) { + TokenWith tokenWithWithOperator = operators.pop(); + Operator operator = tokenWithWithOperator.element; + if (LeftParenthesis.equals(operator)) { + return missingClosingParenthesis(tokenWithWithOperator.token, operator.representation()); + } + ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token); + if (parseStatus.isError()) { + return parseStatus; + } + } + return success(); + } + + private ParseStatus ensureOnlySingleExpressionRemains() { + if (expressions.size() == 1) { + return success(); + } + if (expressions.isEmpty()) { + return emptyTagExpression(); + } + TokenWith rhs = expressions.pop(); + TokenWith lhs = expressions.pop(); + return ParseStatus.missingOperatorBetween(lhs, rhs); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java new file mode 100644 index 00000000..ab7d8b73 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +/** + * @since 1.1 + */ +interface Stack { + + void push(T t); + + T peek(); + + T pop(); + + boolean isEmpty(); + + int size(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java new file mode 100644 index 00000000..823191bc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Collection; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestTag; + +/** + * A tag expression can be evaluated against a collection of + * {@linkplain TestTag tags} to determine if they match the expression. + * + * @since 1.1 + */ +@API(status = INTERNAL, since = "1.1") +public interface TagExpression { + + /** + * Attempt to parse a {@link TagExpression} from the supplied tag + * expression string. + * + * @param infixTagExpression the tag expression string to parse; never {@code null}. + * @see ParseResult + */ + @API(status = INTERNAL, since = "1.1") + static ParseResult parseFrom(String infixTagExpression) { + return new Parser().parse(infixTagExpression); + } + + /** + * Evaluate this tag expression against the supplied collection of + * {@linkplain TestTag tags}. + * + * @param tags the tags this tag expression is to be evaluated against + * @return {@code true}, if the tags match this tag expression; {@code false}, otherwise + */ + boolean evaluate(Collection tags); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java new file mode 100644 index 00000000..a9f8e04a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import java.util.Collection; + +import org.junit.platform.engine.TestTag; + +/** + * @since 1.1 + */ +class TagExpressions { + + static TagExpression tag(String tag) { + TestTag testTag = TestTag.create(tag); + return new TagExpression() { + @Override + public boolean evaluate(Collection tags) { + return tags.contains(testTag); + } + + @Override + public String toString() { + return testTag.getName(); + } + }; + } + + static TagExpression none() { + return new TagExpression() { + @Override + public boolean evaluate(Collection tags) { + return tags.isEmpty(); + } + + @Override + public String toString() { + return "none()"; + } + }; + } + + static TagExpression any() { + return new TagExpression() { + @Override + public boolean evaluate(Collection tags) { + return !tags.isEmpty(); + } + + @Override + public String toString() { + return "any()"; + } + }; + } + + static TagExpression not(TagExpression toNegate) { + return new TagExpression() { + @Override + public boolean evaluate(Collection tags) { + return !toNegate.evaluate(tags); + } + + @Override + public String toString() { + return "!" + toNegate + ""; + } + }; + } + + static TagExpression and(TagExpression lhs, TagExpression rhs) { + return new TagExpression() { + @Override + public boolean evaluate(Collection tags) { + return lhs.evaluate(tags) && rhs.evaluate(tags); + } + + @Override + public String toString() { + return "(" + lhs + " & " + rhs + ")"; + } + }; + } + + static TagExpression or(TagExpression lhs, TagExpression rhs) { + return new TagExpression() { + @Override + public boolean evaluate(Collection tags) { + return lhs.evaluate(tags) || rhs.evaluate(tags); + } + + @Override + public String toString() { + return "(" + lhs + " | " + rhs + ")"; + } + }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java new file mode 100644 index 00000000..f06a60ca --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +/** + * @since 1.1 + */ +class Token { + + final int startIndex; + final String rawString; + + Token(int startIndex, String rawString) { + this.startIndex = startIndex; + this.rawString = rawString; + } + + String string() { + return rawString.trim(); + } + + public int trimmedTokenStartIndex() { + return startIndex + rawString.indexOf(string()); + } + + public boolean isLeftOf(Token token) { + return lastCharacterIndex() < token.startIndex; + } + + public int lastCharacterIndex() { + return endIndexExclusive() - 1; + } + + public int endIndexExclusive() { + return startIndex + rawString.length(); + } + + public Token concatenate(Token rightOfThis) { + String concatenatedRawString = this.rawString + rightOfThis.rawString; + return new Token(startIndex, concatenatedRawString); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java new file mode 100644 index 00000000..a580df87 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +/** + * @since 1.1 + */ +class TokenWith { + + final Token token; + final T element; + + TokenWith(Token token, T element) { + this.token = token; + this.element = element; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java new file mode 100644 index 00000000..65d13c11 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static java.util.Collections.emptyList; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @since 1.1 + */ +class Tokenizer { + + private static final Pattern PATTERN = Pattern.compile("\\s*(?:(?:(?:any|none)\\(\\))|[()!|&]|(?:[^\\s()!|&]+))", + CASE_INSENSITIVE); + + List tokenize(String infixTagExpression) { + if (infixTagExpression == null) { + return emptyList(); + } + List parts = new ArrayList<>(); + Matcher matcher = PATTERN.matcher(infixTagExpression); + while (matcher.find()) { + parts.add(new Token(matcher.start(), matcher.group())); + } + return parts; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java new file mode 100644 index 00000000..53e2a7fa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java @@ -0,0 +1,5 @@ +/** + * The tag expression language parser and related support classes. + */ + +package org.junit.platform.launcher.tagexpression; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 00000000..85f17ac2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1 @@ +org.junit.platform.launcher.listeners.UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java new file mode 100644 index 00000000..9501caff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Public API for configuring and launching test plans. + * + *

This API is typically used by IDEs and build tools. + * + * @since 1.0 + * @uses org.junit.platform.engine.TestEngine + * @uses org.junit.platform.launcher.LauncherDiscoveryListener + * @uses org.junit.platform.launcher.PostDiscoveryFilter + * @uses org.junit.platform.launcher.TestExecutionListener + */ +module org.junit.platform.launcher { + requires transitive java.logging; + requires static transitive org.apiguardian.api; + requires transitive org.junit.platform.commons; + requires transitive org.junit.platform.engine; + + exports org.junit.platform.launcher; + exports org.junit.platform.launcher.core; + exports org.junit.platform.launcher.listeners; + exports org.junit.platform.launcher.listeners.discovery; + + uses org.junit.platform.engine.TestEngine; + uses org.junit.platform.launcher.LauncherDiscoveryListener; + uses org.junit.platform.launcher.LauncherSessionListener; + uses org.junit.platform.launcher.PostDiscoveryFilter; + uses org.junit.platform.launcher.TestExecutionListener; + + provides org.junit.platform.launcher.TestExecutionListener + with org.junit.platform.launcher.listeners.UniqueIdTrackingListener; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java new file mode 100644 index 00000000..4444022d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.Map; + +import org.junit.platform.engine.ConfigurationParameters; + +public class ConfigurationParametersFactoryForTests { + + private ConfigurationParametersFactoryForTests() { + } + + public static ConfigurationParameters create(Map configParams) { + return LauncherConfigurationParameters.builder() // + .explicitParameters(configParams) // + .enableImplicitProviders(false) // + .build(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java new file mode 100644 index 00000000..93002b81 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import org.junit.platform.engine.TestEngine; +import org.junit.platform.launcher.Launcher; + +/** + * @since 1.0 + */ +public class LauncherFactoryForTestingPurposesOnly { + + public static Launcher createLauncher(TestEngine... engines) { + return LauncherFactory.create(createLauncherConfigBuilderWithDisabledServiceLoading() // + .addTestEngines(engines) // + .build()); + } + + public static LauncherConfig.Builder createLauncherConfigBuilderWithDisabledServiceLoading() { + return LauncherConfig.builder() // + .enableTestEngineAutoRegistration(false) // + .enableLauncherDiscoveryListenerAutoRegistration(false) // + .enableTestExecutionListenerAutoRegistration(false) // + .enablePostDiscoveryFilterAutoRegistration(false) // + .enableLauncherSessionListenerAutoRegistration(false); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md new file mode 100644 index 00000000..6aec502b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md @@ -0,0 +1,194 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts new file mode 100644 index 00000000..144c3fbe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts @@ -0,0 +1,28 @@ +plugins { + `java-library-conventions` + `shadow-conventions` +} + +description = "JUnit Platform Reporting" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformLauncher) + + compileOnlyApi(libs.apiguardian) + + shadowed(libs.openTestReporting.events) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + shadowJar { + relocate("org.opentest4j.reporting", "org.junit.platform.reporting.shadow.org.opentest4j.reporting") + from(projectDir) { + include("LICENSE-open-test-reporting.md") + into("META-INF") + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java new file mode 100644 index 00000000..fd8968f4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import org.apiguardian.api.API; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * Utility methods for dealing with legacy reporting infrastructure, such as + * reporting systems built on the Ant-based XML reporting format for JUnit 4. + * + * This class was formerly from {@code junit-platform-launcher} + * in {@link org.junit.platform.launcher.listeners} package. + * + * @since 1.0.3 + */ +@API(status = MAINTAINED, since = "1.6") +public final class LegacyReportingUtils { + + private LegacyReportingUtils() { + /* no-op */ + } + + /** + * Get the class name for the supplied {@link TestIdentifier} using the + * supplied {@link TestPlan}. + * + *

This implementation attempts to find the closest test identifier with + * a {@link ClassSource} by traversing the hierarchy upwards towards the + * root starting with the supplied test identifier. In case no such source + * is found, it falls back to using the parent's + * {@linkplain TestIdentifier#getLegacyReportingName legacy reporting name}. + * + * @param testPlan the test plan that contains the {@code TestIdentifier}; + * never {@code null} + * @param testIdentifier the identifier to determine the class name for; + * never {@code null} + * @see TestIdentifier#getLegacyReportingName + */ + @SuppressWarnings("deprecation") + public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifier) { + return org.junit.platform.launcher.listeners.LegacyReportingUtils.getClassName(testPlan, testIdentifier); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java new file mode 100644 index 00000000..f7a50ee1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java @@ -0,0 +1,5 @@ +/** + * Support for legacy reporting formats. + */ + +package org.junit.platform.reporting.legacy; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java new file mode 100644 index 00000000..10d90288 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Clock; + +import javax.xml.stream.XMLStreamException; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * {@code LegacyXmlReportGeneratingListener} is a {@link TestExecutionListener} that + * generates a separate XML report for each {@linkplain TestPlan#getRoots() root} + * in the {@link TestPlan}. + * + *

Note that the generated XML format is compatible with the legacy + * de facto standard for JUnit 4 based test reports that was made popular by the + * Ant build system. + * + * @since 1.4 + * @see org.junit.platform.launcher.listeners.LoggingListener + * @see org.junit.platform.launcher.listeners.SummaryGeneratingListener + */ +@API(status = STABLE, since = "1.7") +public class LegacyXmlReportGeneratingListener implements TestExecutionListener { + + private final Path reportsDir; + private final PrintWriter out; + private final Clock clock; + + private XmlReportData reportData; + + public LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out) { + this(reportsDir, out, Clock.systemDefaultZone()); + } + + // For tests only + LegacyXmlReportGeneratingListener(String reportsDir, PrintWriter out, Clock clock) { + this(Paths.get(reportsDir), out, clock); + } + + private LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out, Clock clock) { + this.reportsDir = reportsDir; + this.out = out; + this.clock = clock; + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + this.reportData = new XmlReportData(testPlan, clock); + try { + Files.createDirectories(this.reportsDir); + } + catch (IOException e) { + printException("Could not create reports directory: " + this.reportsDir, e); + } + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + this.reportData = null; + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + this.reportData.markSkipped(testIdentifier, reason); + writeXmlReportInCaseOfRoot(testIdentifier); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + this.reportData.markStarted(testIdentifier); + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + this.reportData.addReportEntry(testIdentifier, entry); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult result) { + this.reportData.markFinished(testIdentifier, result); + writeXmlReportInCaseOfRoot(testIdentifier); + } + + private void writeXmlReportInCaseOfRoot(TestIdentifier testIdentifier) { + if (isRoot(testIdentifier)) { + String rootName = testIdentifier.getUniqueIdObject().getSegments().get(0).getValue(); + writeXmlReportSafely(testIdentifier, rootName); + } + } + + private void writeXmlReportSafely(TestIdentifier testIdentifier, String rootName) { + Path xmlFile = this.reportsDir.resolve("TEST-" + rootName + ".xml"); + try (Writer fileWriter = Files.newBufferedWriter(xmlFile)) { + new XmlReportWriter(this.reportData).writeXmlReport(testIdentifier, fileWriter); + } + catch (XMLStreamException | IOException e) { + printException("Could not write XML report: " + xmlFile, e); + } + } + + private boolean isRoot(TestIdentifier testIdentifier) { + return !testIdentifier.getParentIdObject().isPresent(); + } + + private void printException(String message, Exception exception) { + out.println(message); + exception.printStackTrace(out); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java new file mode 100644 index 00000000..1f799353 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java @@ -0,0 +1,143 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.4 + */ +class XmlReportData { + + private static final int MILLIS_PER_SECOND = 1000; + + private final Map finishedTests = new ConcurrentHashMap<>(); + private final Map skippedTests = new ConcurrentHashMap<>(); + private final Map startInstants = new ConcurrentHashMap<>(); + private final Map endInstants = new ConcurrentHashMap<>(); + private final Map> reportEntries = new ConcurrentHashMap<>(); + + private final TestPlan testPlan; + private final Clock clock; + + XmlReportData(TestPlan testPlan, Clock clock) { + this.testPlan = testPlan; + this.clock = clock; + } + + TestPlan getTestPlan() { + return this.testPlan; + } + + Clock getClock() { + return this.clock; + } + + void markSkipped(TestIdentifier testIdentifier, String reason) { + this.skippedTests.put(testIdentifier, reason == null ? "" : reason); + } + + void markStarted(TestIdentifier testIdentifier) { + this.startInstants.put(testIdentifier, this.clock.instant()); + } + + void markFinished(TestIdentifier testIdentifier, TestExecutionResult result) { + this.endInstants.put(testIdentifier, this.clock.instant()); + if (result.getStatus() == ABORTED) { + String reason = result.getThrowable().map(ExceptionUtils::readStackTrace).orElse(""); + this.skippedTests.put(testIdentifier, reason); + } + else { + this.finishedTests.put(testIdentifier, result); + } + } + + void addReportEntry(TestIdentifier testIdentifier, ReportEntry entry) { + List entries = this.reportEntries.computeIfAbsent(testIdentifier, key -> new ArrayList<>()); + entries.add(entry); + } + + boolean wasSkipped(TestIdentifier testIdentifier) { + return findSkippedAncestor(testIdentifier).isPresent(); + } + + double getDurationInSeconds(TestIdentifier testIdentifier) { + Instant startInstant = this.startInstants.getOrDefault(testIdentifier, Instant.EPOCH); + Instant endInstant = this.endInstants.getOrDefault(testIdentifier, startInstant); + return Duration.between(startInstant, endInstant).toMillis() / (double) MILLIS_PER_SECOND; + } + + String getSkipReason(TestIdentifier testIdentifier) { + return findSkippedAncestor(testIdentifier).map(skippedTestIdentifier -> { + String reason = this.skippedTests.get(skippedTestIdentifier); + if (!testIdentifier.equals(skippedTestIdentifier)) { + reason = "parent was skipped: " + reason; + } + return reason; + }).orElse(null); + } + + List getResults(TestIdentifier testIdentifier) { + return getAncestors(testIdentifier).stream() // + .map(this.finishedTests::get) // + .filter(Objects::nonNull) // + .collect(toList()); + } + + List getReportEntries(TestIdentifier testIdentifier) { + return this.reportEntries.getOrDefault(testIdentifier, emptyList()); + } + + private Optional findSkippedAncestor(TestIdentifier testIdentifier) { + return findAncestor(testIdentifier, this.skippedTests::containsKey); + } + + private Optional findAncestor(TestIdentifier testIdentifier, Predicate predicate) { + Optional current = Optional.of(testIdentifier); + while (current.isPresent()) { + if (predicate.test(current.get())) { + return current; + } + current = this.testPlan.getParent(current.get()); + } + return Optional.empty(); + } + + private List getAncestors(TestIdentifier testIdentifier) { + TestIdentifier current = testIdentifier; + List ancestors = new ArrayList<>(); + while (current != null) { + ancestors.add(current); + current = this.testPlan.getParent(current).orElse(null); + } + return ancestors; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java new file mode 100644 index 00000000..fd9dd98d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java @@ -0,0 +1,417 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import static java.text.MessageFormat.format; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; +import static java.util.Collections.emptyList; +import static java.util.Comparator.naturalOrder; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; +import static org.junit.platform.commons.util.StringUtils.isNotBlank; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; +import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; +import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.ERROR; +import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.FAILURE; +import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SKIPPED; +import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SUCCESS; + +import java.io.Writer; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.NumberFormat; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Properties; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.reporting.legacy.LegacyReportingUtils; +import org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type; + +/** + * {@code XmlReportWriter} writes an XML report whose format is compatible + * with the de facto standard for JUnit 4 based test reports that was made + * popular by the Ant build system. + * + * @since 1.4 + */ +class XmlReportWriter { + + // Using zero-width assertions in the split pattern simplifies the splitting process: All split parts + // (including the first and last one) can be used directly, without having to re-add separator characters. + private static final Pattern CDATA_SPLIT_PATTERN = Pattern.compile("(?<=]])(?=>)"); + + private final XmlReportData reportData; + + XmlReportWriter(XmlReportData reportData) { + this.reportData = reportData; + } + + void writeXmlReport(TestIdentifier rootDescriptor, Writer out) throws XMLStreamException { + TestPlan testPlan = this.reportData.getTestPlan(); + Map tests = testPlan.getDescendants(rootDescriptor) // + .stream() // + .filter(testIdentifier -> shouldInclude(testPlan, testIdentifier)) // + .collect(toMap(identity(), this::toAggregatedResult)); // + writeXmlReport(rootDescriptor, tests, out); + } + + private AggregatedTestResult toAggregatedResult(TestIdentifier testIdentifier) { + if (this.reportData.wasSkipped(testIdentifier)) { + return AggregatedTestResult.skipped(); + } + return AggregatedTestResult.nonSkipped(this.reportData.getResults(testIdentifier)); + } + + private boolean shouldInclude(TestPlan testPlan, TestIdentifier testIdentifier) { + return testIdentifier.isTest() || testPlan.getChildren(testIdentifier).isEmpty(); + } + + private void writeXmlReport(TestIdentifier testIdentifier, Map tests, + Writer out) throws XMLStreamException { + + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + XMLStreamWriter xmlWriter = factory.createXMLStreamWriter(out); + xmlWriter.writeStartDocument("UTF-8", "1.0"); + newLine(xmlWriter); + writeTestsuite(testIdentifier, tests, xmlWriter); + xmlWriter.writeEndDocument(); + xmlWriter.flush(); + xmlWriter.close(); + } + + private void writeTestsuite(TestIdentifier testIdentifier, Map tests, + XMLStreamWriter writer) throws XMLStreamException { + + // NumberFormat is not thread-safe. Thus, we instantiate it here and pass it to + // writeTestcase instead of using a constant + NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); + + writer.writeStartElement("testsuite"); + + writeSuiteAttributes(testIdentifier, tests.values(), numberFormat, writer); + + newLine(writer); + writeSystemProperties(writer); + + for (Entry entry : tests.entrySet()) { + writeTestcase(entry.getKey(), entry.getValue(), numberFormat, writer); + } + + writeOutputElement("system-out", formatNonStandardAttributesAsString(testIdentifier), writer); + + writer.writeEndElement(); + newLine(writer); + } + + private void writeSuiteAttributes(TestIdentifier testIdentifier, Collection testResults, + NumberFormat numberFormat, XMLStreamWriter writer) throws XMLStreamException { + + writeAttributeSafely(writer, "name", testIdentifier.getDisplayName()); + writeTestCounts(testResults, writer); + writeAttributeSafely(writer, "time", getTime(testIdentifier, numberFormat)); + writeAttributeSafely(writer, "hostname", getHostname().orElse("")); + writeAttributeSafely(writer, "timestamp", ISO_LOCAL_DATE_TIME.format(getCurrentDateTime())); + } + + private void writeTestCounts(Collection testResults, XMLStreamWriter writer) + throws XMLStreamException { + Map counts = testResults.stream().map(it -> it.type).collect(groupingBy(identity(), counting())); + long total = counts.values().stream().mapToLong(Long::longValue).sum(); + writeAttributeSafely(writer, "tests", String.valueOf(total)); + writeAttributeSafely(writer, "skipped", counts.getOrDefault(SKIPPED, 0L).toString()); + writeAttributeSafely(writer, "failures", counts.getOrDefault(FAILURE, 0L).toString()); + writeAttributeSafely(writer, "errors", counts.getOrDefault(ERROR, 0L).toString()); + } + + private void writeSystemProperties(XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement("properties"); + newLine(writer); + Properties systemProperties = System.getProperties(); + for (String propertyName : new TreeSet<>(systemProperties.stringPropertyNames())) { + writer.writeEmptyElement("property"); + writeAttributeSafely(writer, "name", propertyName); + writeAttributeSafely(writer, "value", systemProperties.getProperty(propertyName)); + newLine(writer); + } + writer.writeEndElement(); + newLine(writer); + } + + private void writeTestcase(TestIdentifier testIdentifier, AggregatedTestResult testResult, + NumberFormat numberFormat, XMLStreamWriter writer) throws XMLStreamException { + + writer.writeStartElement("testcase"); + + writeAttributeSafely(writer, "name", getName(testIdentifier)); + writeAttributeSafely(writer, "classname", getClassName(testIdentifier)); + writeAttributeSafely(writer, "time", getTime(testIdentifier, numberFormat)); + newLine(writer); + + writeSkippedOrErrorOrFailureElement(testIdentifier, testResult, writer); + + List systemOutElements = new ArrayList<>(); + List systemErrElements = new ArrayList<>(); + systemOutElements.add(formatNonStandardAttributesAsString(testIdentifier)); + collectReportEntries(testIdentifier, systemOutElements, systemErrElements); + writeOutputElements("system-out", systemOutElements, writer); + writeOutputElements("system-err", systemErrElements, writer); + + writer.writeEndElement(); + newLine(writer); + } + + private String getName(TestIdentifier testIdentifier) { + return testIdentifier.getLegacyReportingName(); + } + + private String getClassName(TestIdentifier testIdentifier) { + return LegacyReportingUtils.getClassName(this.reportData.getTestPlan(), testIdentifier); + } + + private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, AggregatedTestResult testResult, + XMLStreamWriter writer) throws XMLStreamException { + + if (testResult.type == SKIPPED) { + writeSkippedElement(this.reportData.getSkipReason(testIdentifier), writer); + } + else { + Map>> throwablesByType = testResult.getThrowablesByType(); + for (Type type : EnumSet.of(FAILURE, ERROR)) { + for (Optional throwable : throwablesByType.getOrDefault(type, emptyList())) { + writeErrorOrFailureElement(type, throwable.orElse(null), writer); + } + } + } + } + + private void writeSkippedElement(String reason, XMLStreamWriter writer) throws XMLStreamException { + if (isNotBlank(reason)) { + writer.writeStartElement("skipped"); + writeCDataSafely(writer, reason); + writer.writeEndElement(); + } + else { + writer.writeEmptyElement("skipped"); + } + newLine(writer); + } + + private void writeErrorOrFailureElement(Type type, Throwable throwable, XMLStreamWriter writer) + throws XMLStreamException { + + String elementName = type == FAILURE ? "failure" : "error"; + if (throwable != null) { + writer.writeStartElement(elementName); + writeFailureAttributesAndContent(throwable, writer); + writer.writeEndElement(); + } + else { + writer.writeEmptyElement(elementName); + } + newLine(writer); + } + + private void writeFailureAttributesAndContent(Throwable throwable, XMLStreamWriter writer) + throws XMLStreamException { + + if (throwable.getMessage() != null) { + writeAttributeSafely(writer, "message", throwable.getMessage()); + } + writeAttributeSafely(writer, "type", throwable.getClass().getName()); + writeCDataSafely(writer, readStackTrace(throwable)); + } + + private void collectReportEntries(TestIdentifier testIdentifier, List systemOutElements, + List systemErrElements) { + List entries = this.reportData.getReportEntries(testIdentifier); + if (!entries.isEmpty()) { + List systemOutElementsForCapturedOutput = new ArrayList<>(); + StringBuilder formattedReportEntries = new StringBuilder(); + for (int i = 0; i < entries.size(); i++) { + ReportEntry reportEntry = entries.get(i); + Map keyValuePairs = new LinkedHashMap<>(reportEntry.getKeyValuePairs()); + removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDOUT_REPORT_ENTRY_KEY, + systemOutElementsForCapturedOutput); + removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDERR_REPORT_ENTRY_KEY, systemErrElements); + if (!keyValuePairs.isEmpty()) { + buildReportEntryDescription(reportEntry.getTimestamp(), keyValuePairs, i + 1, + formattedReportEntries); + } + } + systemOutElements.add(formattedReportEntries.toString().trim()); + systemOutElements.addAll(systemOutElementsForCapturedOutput); + } + } + + private void removeIfPresentAndAddAsSeparateElement(Map keyValuePairs, String key, + List elements) { + String value = keyValuePairs.remove(key); + if (value != null) { + elements.add(value); + } + } + + private void buildReportEntryDescription(LocalDateTime timestamp, Map keyValuePairs, + int entryNumber, StringBuilder result) { + result.append( + format("Report Entry #{0} (timestamp: {1})\n", entryNumber, ISO_LOCAL_DATE_TIME.format(timestamp))); + keyValuePairs.forEach((key, value) -> result.append(format("\t- {0}: {1}\n", key, value))); + } + + private String getTime(TestIdentifier testIdentifier, NumberFormat numberFormat) { + return numberFormat.format(this.reportData.getDurationInSeconds(testIdentifier)); + } + + private Optional getHostname() { + try { + return Optional.ofNullable(InetAddress.getLocalHost().getHostName()); + } + catch (UnknownHostException e) { + return Optional.empty(); + } + } + + private LocalDateTime getCurrentDateTime() { + return LocalDateTime.now(this.reportData.getClock()).withNano(0); + } + + private String formatNonStandardAttributesAsString(TestIdentifier testIdentifier) { + return "unique-id: " + testIdentifier.getUniqueId() // + + "\ndisplay-name: " + testIdentifier.getDisplayName(); + } + + private void writeOutputElements(String elementName, List elements, XMLStreamWriter writer) + throws XMLStreamException { + for (String content : elements) { + writeOutputElement(elementName, content, writer); + } + } + + private void writeOutputElement(String elementName, String content, XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement(elementName); + writeCDataSafely(writer, "\n" + content + "\n"); + writer.writeEndElement(); + newLine(writer); + } + + private void writeAttributeSafely(XMLStreamWriter writer, String name, String value) throws XMLStreamException { + writer.writeAttribute(name, escapeIllegalChars(value)); + } + + private void writeCDataSafely(XMLStreamWriter writer, String data) throws XMLStreamException { + for (String safeDataPart : CDATA_SPLIT_PATTERN.split(escapeIllegalChars(data))) { + writer.writeCData(safeDataPart); + } + } + + static String escapeIllegalChars(String text) { + if (text.codePoints().allMatch(XmlReportWriter::isAllowedXmlCharacter)) { + return text; + } + StringBuilder result = new StringBuilder(text.length() * 2); + text.codePoints().forEach(codePoint -> { + if (isAllowedXmlCharacter(codePoint)) { + result.appendCodePoint(codePoint); + } + else { // use a Character Reference (cf. https://www.w3.org/TR/xml/#NT-CharRef) + result.append("&#").append(codePoint).append(';'); + } + }); + return result.toString(); + } + + private static boolean isAllowedXmlCharacter(int codePoint) { + // source: https://www.w3.org/TR/xml/#charsets + return codePoint == 0x9 // + || codePoint == 0xA // + || codePoint == 0xD // + || (codePoint >= 0x20 && codePoint <= 0xD7FF) // + || (codePoint >= 0xE000 && codePoint <= 0xFFFD) // + || (codePoint >= 0x10000 && codePoint <= 0x10FFFF); + } + + private void newLine(XMLStreamWriter xmlWriter) throws XMLStreamException { + xmlWriter.writeCharacters("\n"); + } + + private static boolean isFailure(TestExecutionResult result) { + Optional throwable = result.getThrowable(); + return throwable.isPresent() && throwable.get() instanceof AssertionError; + } + + static class AggregatedTestResult { + + private static final AggregatedTestResult SKIPPED_RESULT = new AggregatedTestResult(SKIPPED, emptyList()); + + public static AggregatedTestResult skipped() { + return SKIPPED_RESULT; + } + + public static AggregatedTestResult nonSkipped(List executionResults) { + Type type = executionResults.stream() // + .map(Type::from) // + .max(naturalOrder()) // + .orElse(SUCCESS); + return new AggregatedTestResult(type, executionResults); + } + + private final Type type; + private final List executionResults; + + private AggregatedTestResult(Type type, List executionResults) { + this.type = type; + this.executionResults = executionResults; + } + + public Map>> getThrowablesByType() { + return executionResults.stream() // + .collect(groupingBy(Type::from, mapping(TestExecutionResult::getThrowable, toList()))); + } + + enum Type { + + SUCCESS, SKIPPED, FAILURE, ERROR; + + private static Type from(TestExecutionResult executionResult) { + if (executionResult.getStatus() == FAILED) { + return isFailure(executionResult) ? FAILURE : ERROR; + } + return SUCCESS; + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java new file mode 100644 index 00000000..d41e891b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java @@ -0,0 +1,7 @@ +/** + * Support for generating XML reports using a format which is compatible with + * the de facto standard for JUnit 4 based test reports that was made popular + * by the Ant build system. + */ + +package org.junit.platform.reporting.legacy.xml; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java new file mode 100644 index 00000000..382404bd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.open.xml; + +import org.junit.platform.engine.TestDescriptor; +import org.opentest4j.reporting.events.api.Factory; +import org.opentest4j.reporting.schema.Namespace; + +class JUnitFactory { + + static Namespace NAMESPACE = Namespace.of("https://schemas.junit.org/open-test-reporting"); + + private JUnitFactory() { + } + + static Factory uniqueId(String uniqueId) { + return context -> new UniqueId(context, uniqueId); + } + + static Factory legacyReportingName(String legacyReportingName) { + return context -> new LegacyReportingName(context, legacyReportingName); + } + + static Factory type(TestDescriptor.Type type) { + return context -> new Type(context, type); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java new file mode 100644 index 00000000..a81214a7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.open.xml; + +import org.opentest4j.reporting.events.api.ChildElement; +import org.opentest4j.reporting.events.api.Context; +import org.opentest4j.reporting.events.core.Metadata; +import org.opentest4j.reporting.schema.QualifiedName; + +class LegacyReportingName extends ChildElement { + + static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "legacyReportingName"); + + LegacyReportingName(Context context, String value) { + super(context, ELEMENT); + withContent(value); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java new file mode 100644 index 00000000..05c45288 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java @@ -0,0 +1,266 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.open.xml; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.junit.platform.reporting.open.xml.JUnitFactory.legacyReportingName; +import static org.junit.platform.reporting.open.xml.JUnitFactory.type; +import static org.junit.platform.reporting.open.xml.JUnitFactory.uniqueId; +import static org.opentest4j.reporting.events.core.CoreFactory.attachments; +import static org.opentest4j.reporting.events.core.CoreFactory.cpuCores; +import static org.opentest4j.reporting.events.core.CoreFactory.data; +import static org.opentest4j.reporting.events.core.CoreFactory.directorySource; +import static org.opentest4j.reporting.events.core.CoreFactory.fileSource; +import static org.opentest4j.reporting.events.core.CoreFactory.hostName; +import static org.opentest4j.reporting.events.core.CoreFactory.infrastructure; +import static org.opentest4j.reporting.events.core.CoreFactory.metadata; +import static org.opentest4j.reporting.events.core.CoreFactory.operatingSystem; +import static org.opentest4j.reporting.events.core.CoreFactory.reason; +import static org.opentest4j.reporting.events.core.CoreFactory.result; +import static org.opentest4j.reporting.events.core.CoreFactory.sources; +import static org.opentest4j.reporting.events.core.CoreFactory.tag; +import static org.opentest4j.reporting.events.core.CoreFactory.tags; +import static org.opentest4j.reporting.events.core.CoreFactory.uriSource; +import static org.opentest4j.reporting.events.core.CoreFactory.userName; +import static org.opentest4j.reporting.events.java.JavaFactory.classSource; +import static org.opentest4j.reporting.events.java.JavaFactory.classpathResourceSource; +import static org.opentest4j.reporting.events.java.JavaFactory.fileEncoding; +import static org.opentest4j.reporting.events.java.JavaFactory.heapSize; +import static org.opentest4j.reporting.events.java.JavaFactory.javaVersion; +import static org.opentest4j.reporting.events.java.JavaFactory.methodSource; +import static org.opentest4j.reporting.events.java.JavaFactory.packageSource; +import static org.opentest4j.reporting.events.java.JavaFactory.throwable; +import static org.opentest4j.reporting.events.root.RootFactory.finished; +import static org.opentest4j.reporting.events.root.RootFactory.reported; +import static org.opentest4j.reporting.events.root.RootFactory.started; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; +import org.junit.platform.engine.support.descriptor.CompositeTestSource; +import org.junit.platform.engine.support.descriptor.DirectorySource; +import org.junit.platform.engine.support.descriptor.FileSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.engine.support.descriptor.PackageSource; +import org.junit.platform.engine.support.descriptor.UriSource; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.listeners.OutputDir; +import org.opentest4j.reporting.events.api.DocumentWriter; +import org.opentest4j.reporting.events.api.NamespaceRegistry; +import org.opentest4j.reporting.events.core.Result; +import org.opentest4j.reporting.events.core.Sources; +import org.opentest4j.reporting.events.root.Events; +import org.opentest4j.reporting.schema.Namespace; + +/** + * Open Test Reporting events XML generating test execution listener. + * + * @since 1.9 + */ +@API(status = EXPERIMENTAL, since = "1.9") +public class OpenTestReportGeneratingListener implements TestExecutionListener { + + static final String ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.enabled"; + static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; + + private final AtomicInteger idCounter = new AtomicInteger(); + private final Map inProgressIds = new ConcurrentHashMap<>(); + private DocumentWriter eventsFileWriter = DocumentWriter.noop(); + + public OpenTestReportGeneratingListener() { + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + ConfigurationParameters config = testPlan.getConfigurationParameters(); + if (isEnabled(config)) { + NamespaceRegistry namespaceRegistry = NamespaceRegistry.builder(Namespace.REPORTING_CORE) // + .add("e", Namespace.REPORTING_EVENTS) // + .add("java", Namespace.REPORTING_JAVA) // + .add("junit", JUnitFactory.NAMESPACE, + "https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd") // + .build(); + Path eventsXml = OutputDir.create(config.get(OUTPUT_DIR_PROPERTY_NAME)) // + .createFile("junit-platform-events", "xml"); + try { + eventsFileWriter = Events.createDocumentWriter(namespaceRegistry, eventsXml); + reportInfrastructure(); + } + catch (Exception e) { + throw new JUnitException("Failed to initialize XML events file: " + eventsXml, e); + } + } + } + + private Boolean isEnabled(ConfigurationParameters config) { + return config.getBoolean(ENABLED_PROPERTY_NAME).orElse(false); + } + + private void reportInfrastructure() { + eventsFileWriter.append(infrastructure(), infrastructure -> { + try { + String hostName = InetAddress.getLocalHost().getHostName(); + infrastructure.append(hostName(hostName)); + } + catch (UnknownHostException ignored) { + } + infrastructure // + .append(userName(System.getProperty("user.name"))) // + .append(operatingSystem(System.getProperty("os.name"))) // + .append(cpuCores(Runtime.getRuntime().availableProcessors())) // + .append(javaVersion(System.getProperty("java.version"))) // + .append(fileEncoding(System.getProperty("file.encoding"))) // + .append(heapSize(), heapSize -> heapSize.withMax(Runtime.getRuntime().maxMemory())); + }); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + try { + eventsFileWriter.close(); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to close XML events file", e); + } + finally { + eventsFileWriter = DocumentWriter.noop(); + } + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + String id = String.valueOf(idCounter.incrementAndGet()); + reportStarted(testIdentifier, id); + eventsFileWriter.append(finished(id, Instant.now()), // + finished -> finished.append(result(Result.Status.SKIPPED), result -> { + if (StringUtils.isNotBlank(reason)) { + result.append(reason(reason)); + } + })); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + String id = String.valueOf(idCounter.incrementAndGet()); + inProgressIds.put(testIdentifier.getUniqueIdObject(), id); + reportStarted(testIdentifier, id); + } + + private void reportStarted(TestIdentifier testIdentifier, String id) { + eventsFileWriter.append(started(id, Instant.now(), testIdentifier.getDisplayName()), started -> { + testIdentifier.getParentIdObject().ifPresent(parentId -> started.withParentId(inProgressIds.get(parentId))); + started.append(metadata(), metadata -> { + if (!testIdentifier.getTags().isEmpty()) { + metadata.append(tags(), tags -> // + testIdentifier.getTags().forEach(tag -> tags.append(tag(tag.getName())))); + } + metadata.append(uniqueId(testIdentifier.getUniqueId())) // + .append(legacyReportingName(testIdentifier.getLegacyReportingName())) // + .append(type(testIdentifier.getType())); + }); + testIdentifier.getSource().ifPresent( + source -> started.append(sources(), sources -> addTestSource(source, sources))); + }); + } + + private void addTestSource(TestSource source, Sources sources) { + if (source instanceof CompositeTestSource) { + ((CompositeTestSource) source).getSources().forEach(it -> addTestSource(it, sources)); + } + else if (source instanceof ClassSource) { + ClassSource classSource = (ClassSource) source; + sources.append(classSource(classSource.getClassName()), // + element -> classSource.getPosition().ifPresent( + filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); + } + else if (source instanceof MethodSource) { + MethodSource methodSource = (MethodSource) source; + sources.append(methodSource(methodSource.getClassName(), methodSource.getMethodName()), element -> { + String methodParameterTypes = methodSource.getMethodParameterTypes(); + if (methodParameterTypes != null) { + element.withMethodParameterTypes(methodParameterTypes); + } + }); + } + else if (source instanceof ClasspathResourceSource) { + ClasspathResourceSource classpathResourceSource = (ClasspathResourceSource) source; + sources.append(classpathResourceSource(classpathResourceSource.getClasspathResourceName()), // + element -> classpathResourceSource.getPosition().ifPresent( + filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); + } + else if (source instanceof PackageSource) { + sources.append(packageSource(((PackageSource) source).getPackageName())); + } + else if (source instanceof FileSource) { + FileSource fileSource = (FileSource) source; + sources.append(fileSource(fileSource.getFile()), // + element -> fileSource.getPosition().ifPresent( + filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); + } + else if (source instanceof DirectorySource) { + sources.append(directorySource(((DirectorySource) source).getFile())); + } + else if (source instanceof UriSource) { + sources.append(uriSource(((UriSource) source).getUri())); + } + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + String id = inProgressIds.get(testIdentifier.getUniqueIdObject()); + eventsFileWriter.append(reported(id, Instant.now()), // + reported -> reported.append(attachments(), attachments -> attachments.append(data(), data -> { + data.withTime(entry.getTimestamp()); + entry.getKeyValuePairs().forEach(data::addEntry); + }))); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + String id = inProgressIds.remove(testIdentifier.getUniqueIdObject()); + eventsFileWriter.append(finished(id, Instant.now()), // + finished -> finished.append(result(convertStatus(testExecutionResult.getStatus())), // + result -> testExecutionResult.getThrowable() // + .ifPresent(throwable -> result.append(throwable(throwable))))); + } + + private Result.Status convertStatus(TestExecutionResult.Status status) { + switch (status) { + case FAILED: + return Result.Status.FAILED; + case SUCCESSFUL: + return Result.Status.SUCCESSFUL; + case ABORTED: + return Result.Status.ABORTED; + } + throw new JUnitException("Unhandled status: " + status); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java new file mode 100644 index 00000000..49f8d32c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.open.xml; + +import org.junit.platform.engine.TestDescriptor; +import org.opentest4j.reporting.events.api.ChildElement; +import org.opentest4j.reporting.events.api.Context; +import org.opentest4j.reporting.events.core.Metadata; +import org.opentest4j.reporting.schema.QualifiedName; + +class Type extends ChildElement { + + static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "type"); + + Type(Context context, TestDescriptor.Type type) { + super(context, ELEMENT); + withContent(type.toString()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java new file mode 100644 index 00000000..4d3ef7b6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.open.xml; + +import org.opentest4j.reporting.events.api.ChildElement; +import org.opentest4j.reporting.events.api.Context; +import org.opentest4j.reporting.events.core.Metadata; +import org.opentest4j.reporting.schema.QualifiedName; + +class UniqueId extends ChildElement { + + static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "uniqueId"); + + UniqueId(Context context, String value) { + super(context, ELEMENT); + withContent(value); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java new file mode 100644 index 00000000..5befd7af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java @@ -0,0 +1,5 @@ +/** + * Support for generating Open Test Reporting compatible XML event reports. + */ + +package org.junit.platform.reporting.open.xml; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java new file mode 100644 index 00000000..d7c60324 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java @@ -0,0 +1,5 @@ +/** + * JUnit Platform Reporting. + */ + +package org.junit.platform.reporting; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 00000000..d48ed597 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1 @@ +org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml new file mode 100644 index 00000000..057d295a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd new file mode 100644 index 00000000..b8456f0d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java new file mode 100644 index 00000000..66c74947 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Defines the JUnit Platform Reporting API. + * + * @since 1.4 + */ +module org.junit.platform.reporting { + requires java.xml; + requires static transitive org.apiguardian.api; + requires org.junit.platform.commons; + requires transitive org.junit.platform.engine; + requires transitive org.junit.platform.launcher; + + // exports org.junit.platform.reporting; empty package + exports org.junit.platform.reporting.legacy; + exports org.junit.platform.reporting.legacy.xml; + exports org.junit.platform.reporting.open.xml; + + provides org.junit.platform.launcher.TestExecutionListener + with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts new file mode 100644 index 00000000..78205ece --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts @@ -0,0 +1,36 @@ +plugins { + `java-library-conventions` + `junit4-compatibility` +} + +description = "JUnit Platform Runner" + +dependencies { + api(platform(projects.junitBom)) + api(libs.junit4) + api(projects.junitPlatformLauncher) + api(projects.junitPlatformSuiteApi) + + compileOnlyApi(libs.apiguardian) + + implementation(projects.junitPlatformSuiteCommons) + + testImplementation(testFixtures(projects.junitPlatformEngine)) + testImplementation(testFixtures(projects.junitPlatformLauncher)) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} + +tasks.jar { + bundle { + bnd(""" + # Import JUnit4 packages with a version + Import-Package: \ + ${extra["importAPIGuardian"]},\ + org.junit.platform.commons.logging;status=INTERNAL,\ + org.junit.runner.*;version="[${libs.versions.junit4Min.get()},5)",\ + * + """) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java new file mode 100644 index 00000000..bbc87847 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java @@ -0,0 +1,201 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.runner; + +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.ExcludeClassNamePatterns; +import org.junit.platform.suite.api.ExcludeEngines; +import org.junit.platform.suite.api.ExcludePackages; +import org.junit.platform.suite.api.ExcludeTags; +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.IncludePackages; +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.SelectDirectories; +import org.junit.platform.suite.api.SelectFile; +import org.junit.platform.suite.api.SelectModules; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.SelectUris; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; +import org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.notification.RunNotifier; + +/** + * JUnit 4 based {@link Runner} which runs tests on the JUnit Platform in a + * JUnit 4 environment. + * + *

Annotating a class with {@code @RunWith(JUnitPlatform.class)} allows it + * to be run with IDEs and build systems that support JUnit 4 but do not yet + * support the JUnit Platform directly. + * + *

Please note that test classes and suites annotated with + * {@code @RunWith(JUnitPlatform.class)} cannot be executed directly on + * the JUnit Platform (or as a "JUnit 5" test as documented in some IDEs). Such + * classes and suites can only be executed using JUnit 4 infrastructure. + * + *

Consult the various annotations in the {@code org.junit.platform.suite.api} + * package for configuration options. + * + *

If you do not use any configuration annotations from the + * {@code org.junit.platform.suite.api} package, you can use this runner on a + * test class whose programming model is supported on the JUnit Platform — + * for example, a JUnit Jupiter test class. Note, however, that any test class + * run with this runner must be {@code public} in order to be picked up by IDEs + * and build tools. + * + *

When used on a class that serves as a test suite and the + * {@link IncludeClassNamePatterns @IncludeClassNamePatterns} annotation is not + * present, the default include pattern + * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} + * will be used in order to avoid loading classes unnecessarily (see {@link + * org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN + * ClassNameFilter#STANDARD_INCLUDE_PATTERN}). + * + * @since 1.0 + * @see SelectClasses + * @see SelectClasspathResource + * @see SelectDirectories + * @see SelectFile + * @see SelectModules + * @see SelectPackages + * @see SelectUris + * @see IncludeClassNamePatterns + * @see ExcludeClassNamePatterns + * @see IncludeEngines + * @see ExcludeEngines + * @see IncludePackages + * @see ExcludePackages + * @see IncludeTags + * @see ExcludeTags + * @see SuiteDisplayName + * @see org.junit.platform.suite.api.UseTechnicalNames UseTechnicalNames + * @see ConfigurationParameter + * @deprecated since 1.8, in favor of the {@link Suite @Suite} support provided by + * the {@code junit-platform-suite-engine} module; to be removed in JUnit Platform 2.0 + */ +@API(status = DEPRECATED, since = "1.8") +@Deprecated +public class JUnitPlatform extends Runner implements Filterable { + + // @formatter:off + private static final List> IMPLICIT_SUITE_ANNOTATIONS = Arrays.asList( + SelectClasses.class, + SelectClasspathResource.class, + SelectDirectories.class, + SelectFile.class, + SelectFile.class, + SelectModules.class, + SelectPackages.class, + SelectUris.class + ); + // @formatter:on + + private final Class testClass; + private final Launcher launcher; + + private JUnitPlatformTestTree testTree; + + public JUnitPlatform(Class testClass) { + this(testClass, LauncherFactory.create()); + } + + // For testing only + JUnitPlatform(Class testClass, Launcher launcher) { + this.launcher = launcher; + this.testClass = testClass; + this.testTree = generateTestTree(createDiscoveryRequest()); + } + + @Override + public Description getDescription() { + return this.testTree.getSuiteDescription(); + } + + @Override + public void run(RunNotifier notifier) { + this.launcher.execute(this.testTree.getTestPlan(), new JUnitPlatformRunnerListener(this.testTree, notifier)); + } + + private JUnitPlatformTestTree generateTestTree(LauncherDiscoveryRequest discoveryRequest) { + TestPlan testPlan = this.launcher.discover(discoveryRequest); + return new JUnitPlatformTestTree(testPlan, this.testClass); + } + + private LauncherDiscoveryRequest createDiscoveryRequest() { + SuiteLauncherDiscoveryRequestBuilder requestBuilder = request(); + // Allows @RunWith(JUnitPlatform.class) to be added to any test case + boolean isSuite = isSuite(); + if (!isSuite) { + requestBuilder.selectors(selectClass(this.testClass)); + } + + // @formatter:off + return requestBuilder + .filterStandardClassNamePatterns(isSuite) + .suite(this.testClass) + .build(); + // @formatter:on + } + + private boolean isSuite() { + // @formatter:off + return IMPLICIT_SUITE_ANNOTATIONS.stream() + .anyMatch(annotation -> isAnnotated(this.testClass, annotation)); + // @formatter:on + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + Set filteredIdentifiers = this.testTree.getFilteredLeaves(filter); + if (filteredIdentifiers.isEmpty()) { + throw new NoTestsRemainException(); + } + this.testTree = generateTestTree(createDiscoveryRequestForUniqueIds(filteredIdentifiers)); + } + + private LauncherDiscoveryRequest createDiscoveryRequestForUniqueIds(Set testIdentifiers) { + // @formatter:off + List selectors = testIdentifiers.stream() + .map(TestIdentifier::getUniqueIdObject) + .map(DiscoverySelectors::selectUniqueId) + .collect(toList()); + // @formatter:on + return request().selectors(selectors).build(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java new file mode 100644 index 00000000..03911718 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.runner; + +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; + +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; + +/** + * @since 1.0 + */ +class JUnitPlatformRunnerListener implements TestExecutionListener { + + private final JUnitPlatformTestTree testTree; + private final RunNotifier notifier; + + JUnitPlatformRunnerListener(JUnitPlatformTestTree testTree, RunNotifier notifier) { + this.testTree = testTree; + this.notifier = notifier; + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + UniqueId parentId = testIdentifier.getParentIdObject().get(); + testTree.addDynamicDescription(testIdentifier, parentId); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + if (testIdentifier.isTest()) { + fireTestIgnored(testIdentifier); + } + else { + testTree.getTestsInSubtree(testIdentifier).forEach(this::fireTestIgnored); + } + } + + private void fireTestIgnored(TestIdentifier testIdentifier) { + Description description = findJUnit4Description(testIdentifier); + this.notifier.fireTestIgnored(description); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + Description description = findJUnit4Description(testIdentifier); + if (description.isTest()) { + this.notifier.fireTestStarted(description); + } + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + Description description = findJUnit4Description(testIdentifier); + Status status = testExecutionResult.getStatus(); + if (status == ABORTED) { + this.notifier.fireTestAssumptionFailed(toFailure(testExecutionResult, description)); + } + else if (status == FAILED) { + this.notifier.fireTestFailure(toFailure(testExecutionResult, description)); + } + if (description.isTest()) { + this.notifier.fireTestFinished(description); + } + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + System.out.println(entry); + } + + private Failure toFailure(TestExecutionResult testExecutionResult, Description description) { + return new Failure(description, testExecutionResult.getThrowable().orElse(null)); + } + + private Description findJUnit4Description(TestIdentifier testIdentifier) { + return this.testTree.getDescription(testIdentifier); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java new file mode 100644 index 00000000..cb32dfeb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java @@ -0,0 +1,167 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.runner; + +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toSet; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.suite.api.SuiteDisplayName; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +/** + * @since 1.0 + */ +class JUnitPlatformTestTree { + + private final Map descriptions = new HashMap<>(); + private final TestPlan testPlan; + private final Function nameExtractor; + private final Description suiteDescription; + + JUnitPlatformTestTree(TestPlan testPlan, Class testClass) { + this.testPlan = testPlan; + this.nameExtractor = useTechnicalNames(testClass) ? this::getTechnicalName : TestIdentifier::getDisplayName; + this.suiteDescription = generateSuiteDescription(testPlan, testClass); + } + + public TestPlan getTestPlan() { + return testPlan; + } + + @SuppressWarnings("deprecation") + private static boolean useTechnicalNames(Class testClass) { + return testClass.isAnnotationPresent(org.junit.platform.suite.api.UseTechnicalNames.class); + } + + Description getSuiteDescription() { + return this.suiteDescription; + } + + Description getDescription(TestIdentifier identifier) { + return this.descriptions.get(identifier); + } + + private Description generateSuiteDescription(TestPlan testPlan, Class testClass) { + String displayName = useTechnicalNames(testClass) ? testClass.getName() : getSuiteDisplayName(testClass); + Description suiteDescription = Description.createSuiteDescription(displayName); + buildDescriptionTree(suiteDescription, testPlan); + return suiteDescription; + } + + private String getSuiteDisplayName(Class testClass) { + // @formatter:off + return AnnotationUtils.findAnnotation(testClass, SuiteDisplayName.class) + .map(SuiteDisplayName::value) + .filter(StringUtils::isNotBlank) + .orElse(testClass.getName()); + // @formatter:on + } + + private void buildDescriptionTree(Description suiteDescription, TestPlan testPlan) { + testPlan.getRoots().forEach(testIdentifier -> buildDescription(testIdentifier, suiteDescription, testPlan)); + } + + void addDynamicDescription(TestIdentifier newIdentifier, UniqueId parentId) { + Description parent = getDescription(this.testPlan.getTestIdentifier(parentId)); + buildDescription(newIdentifier, parent, this.testPlan); + } + + private void buildDescription(TestIdentifier identifier, Description parent, TestPlan testPlan) { + Description newDescription = createJUnit4Description(identifier, testPlan); + parent.addChild(newDescription); + this.descriptions.put(identifier, newDescription); + testPlan.getChildren(identifier).forEach( + testIdentifier -> buildDescription(testIdentifier, newDescription, testPlan)); + } + + private Description createJUnit4Description(TestIdentifier identifier, TestPlan testPlan) { + String name = nameExtractor.apply(identifier); + if (identifier.isTest()) { + String containerName = testPlan.getParent(identifier).map(nameExtractor).orElse(""); + return Description.createTestDescription(containerName, name, identifier.getUniqueIdObject()); + } + return Description.createSuiteDescription(name, identifier.getUniqueIdObject()); + } + + private String getTechnicalName(TestIdentifier testIdentifier) { + Optional optionalSource = testIdentifier.getSource(); + if (optionalSource.isPresent()) { + TestSource source = optionalSource.get(); + if (source instanceof ClassSource) { + return ((ClassSource) source).getJavaClass().getName(); + } + else if (source instanceof MethodSource) { + MethodSource methodSource = (MethodSource) source; + String methodParameterTypes = methodSource.getMethodParameterTypes(); + if (StringUtils.isBlank(methodParameterTypes)) { + return methodSource.getMethodName(); + } + return String.format("%s(%s)", methodSource.getMethodName(), methodParameterTypes); + } + } + + // Else fall back to display name + return testIdentifier.getDisplayName(); + } + + Set getTestsInSubtree(TestIdentifier ancestor) { + // @formatter:off + return testPlan.getDescendants(ancestor).stream() + .filter(TestIdentifier::isTest) + .collect(toCollection(LinkedHashSet::new)); + // @formatter:on + } + + Set getFilteredLeaves(Filter filter) { + Set identifiers = applyFilterToDescriptions(filter); + return removeNonLeafIdentifiers(identifiers); + } + + private Set removeNonLeafIdentifiers(Set identifiers) { + return identifiers.stream().filter(isALeaf(identifiers)).collect(toSet()); + } + + private Predicate isALeaf(Set identifiers) { + return testIdentifier -> { + Set descendants = testPlan.getDescendants(testIdentifier); + return identifiers.stream().noneMatch(descendants::contains); + }; + } + + private Set applyFilterToDescriptions(Filter filter) { + // @formatter:off + return descriptions.entrySet() + .stream() + .filter(entry -> filter.shouldRun(entry.getValue())) + .map(Entry::getKey) + .collect(toSet()); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java new file mode 100644 index 00000000..ef778ce2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java @@ -0,0 +1,6 @@ +/** + * {@code Runner} and annotations for configuring and executing tests on the + * JUnit Platform in a JUnit 4 environment. + */ + +package org.junit.platform.runner; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java new file mode 100644 index 00000000..47edeb10 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * {@code Runner} and annotations for configuring and executing tests on the + * JUnit Platform in a JUnit 4 environment. + * + * @since 1.0 + */ +module org.junit.platform.runner { + requires transitive junit; // 4 + requires static transitive org.apiguardian.api; + requires transitive org.junit.platform.launcher; + requires transitive org.junit.platform.suite.api; + requires org.junit.platform.suite.commons; + + exports org.junit.platform.runner; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts new file mode 100644 index 00000000..7be1b0a2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts @@ -0,0 +1,15 @@ +plugins { + `java-library-conventions` +} + +description = "JUnit Platform Suite API" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformCommons) + + compileOnlyApi(libs.apiguardian) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java new file mode 100644 index 00000000..0ab3693b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @ConfigurationParameter} is a {@linkplain Repeatable repeatable} + * annotation that specifies a configuration {@link #key key} and + * {@link #value value} pair to be added to the discovery request when running + * a test suite on the JUnit Platform. + * + * @since 1.8 + * @see DisableParentConfigurationParameters + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder#configurationParameter(String, String) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +@Repeatable(ConfigurationParameters.class) +public @interface ConfigurationParameter { + + /** + * The configuration parameter key under which to add the {@link #value() value} + * to the discovery request; never {@code null} or blank. + */ + String key(); + + /** + * The value to add to the discovery request for the specified {@link #key() key}. + */ + String value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java new file mode 100644 index 00000000..0f12270a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @ConfigurationParameters} is a container for one or more + * {@link ConfigurationParameter @ConfigurationParameter} declarations. + * + *

Note, however, that use of the {@code @ConfigurationParameters} container + * is completely optional since {@code @ConfigurationParameter} is a + * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. + * + * @since 1.8 + * @see ConfigurationParameter + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +public @interface ConfigurationParameters { + + /** + * An array of one or more {@link ConfigurationParameter @ConfigurationParameter} + * declarations. + */ + ConfigurationParameter[] value(); +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java new file mode 100644 index 00000000..d1eed0ee --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * Disable parent configuration parameters. + * + *

By default a suite discovers tests using the configuration parameters + * explicitly configured via {@link ConfigurationParameter @ConfigurationParameter} + * and the configuration parameters from the discovery request that was used to + * discover the suite. + * + *

Annotating a suite with this annotation disables the latter source so + * that only explicit configuration parameters are taken into account. + * + * @since 1.8 + * @see ConfigurationParameter + * @see Suite + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +public @interface DisableParentConfigurationParameters { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java new file mode 100644 index 00000000..3aa18b8b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ExcludeClassNamePatterns} specifies regular expressions that are used + * to match against fully qualified class names when running a test suite on the + * JUnit Platform. + * + *

The patterns are combined using OR semantics: if the fully qualified name + * of a class matches against at least one of the patterns, the class will be + * excluded from the test plan. + * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.ClassNameFilter#excludeClassNamePatterns + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface ExcludeClassNamePatterns { + + /** + * Regular expressions used to match against fully qualified class names. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java new file mode 100644 index 00000000..e65acb5c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ExcludeEngines} specifies the {@linkplain #value IDs} of + * {@link org.junit.platform.engine.TestEngine TestEngines} to be excluded + * when running a test suite on the JUnit Platform. + * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.launcher.EngineFilter#excludeEngines + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface ExcludeEngines { + + /** + * One or more TestEngine IDs to be excluded from the test plan. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java new file mode 100644 index 00000000..22b7736c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ExcludePackages} specifies the {@linkplain #value packages} to be + * excluded when running a test suite on the JUnit Platform. + + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.PackageNameFilter#excludePackageNames(String...) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface ExcludePackages { + + /** + * One or more packages to exclude. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java new file mode 100644 index 00000000..9d55090e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @ExcludeTags} specifies the + * {@linkplain #value tags or tag expressions} to be excluded when running a + * test suite on the JUnit Platform. + * + *

Tag Expressions

+ * + *

Tag expressions are boolean expressions with the following allowed + * operators: {@code !} (not), {@code &} (and) and {@code |} (or). Parentheses + * can be used to adjust for operator precedence. Please refer to the + * JUnit 5 User Guide + * for usage examples. + * + *

Syntax Rules for Tags

+ *
    + *
  • A tag must not be blank.
  • + *
  • A trimmed tag must not contain whitespace.
  • + *
  • A trimmed tag must not contain ISO control characters.
  • + *
  • A trimmed tag must not contain reserved characters.
  • + *
+ * + *

Reserved characters that are not permissible as part of a tag name. + * + *

    + *
  • {@code ","}
  • + *
  • {@code "("}
  • + *
  • {@code ")"}
  • + *
  • {@code "&"}
  • + *
  • {@code "|"}
  • + *
  • {@code "!"}
  • + *
+ * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.launcher.TagFilter#excludeTags + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface ExcludeTags { + + /** + * One or more tags to exclude. + * + *

Note: each tag will be {@linkplain String#trim() trimmed} and + * validated according to the Syntax Rules for Tags (see + * {@linkplain ExcludeTags class-level Javadoc} for details). + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java new file mode 100644 index 00000000..5d75fedb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @IncludeClassNamePatterns} specifies regular expressions that are used + * to match against fully qualified class names when running a test suite on the + * JUnit Platform. + * + *

The patterns are combined using OR semantics: if the fully qualified name + * of a class matches against at least one of the patterns, the class will be + * included in the test plan. + * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN + * @see org.junit.platform.engine.discovery.ClassNameFilter#includeClassNamePatterns + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface IncludeClassNamePatterns { + + /** + * Regular expressions used to match against fully qualified class names. + * + *

The default pattern matches against classes whose names either begin + * with {@code Test} or end with {@code Test} or {@code Tests} (in any package). + */ + // Implementation notes: + // - Test.* :: "Test" prefix for classes in default package + // - .+[.$]Test.* :: "Test" prefix for top-level and nested classes in a named package + // - .*Tests? :: "Test" and "Tests" suffixes in any package + String[] value() default "^(Test.*|.+[.$]Test.*|.*Tests?)$"; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java new file mode 100644 index 00000000..4117947f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @IncludeEngines} specifies the {@linkplain #value IDs} of + * {@link org.junit.platform.engine.TestEngine TestEngines} to be included + * when running a test suite on the JUnit Platform. + * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.launcher.EngineFilter#includeEngines + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface IncludeEngines { + + /** + * One or more TestEngine IDs to be included in the test plan. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java new file mode 100644 index 00000000..7635d1dc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @IncludePackages} specifies the {@linkplain #value packages} to be + * included when running a test suite on the JUnit Platform. + * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.PackageNameFilter#includePackageNames(String...) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface IncludePackages { + + /** + * One or more packages to include. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java new file mode 100644 index 00000000..744dc75a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @IncludeTags} specifies the + * {@linkplain #value tags or tag expressions} to be included when running a + * test suite on the JUnit Platform. + * + *

Tag Expressions

+ * + *

Tag expressions are boolean expressions with the following allowed + * operators: {@code !} (not), {@code &} (and) and {@code |} (or). Parentheses + * can be used to adjust for operator precedence. Please refer to the + * JUnit 5 User Guide + * for usage examples. + * + *

Syntax Rules for Tags

+ *
    + *
  • A tag must not be blank.
  • + *
  • A trimmed tag must not contain whitespace.
  • + *
  • A trimmed tag must not contain ISO control characters.
  • + *
  • A trimmed tag must not contain reserved characters.
  • + *
+ * + *

Reserved characters that are not permissible as part of a tag name. + * + *

    + *
  • {@code ","}
  • + *
  • {@code "("}
  • + *
  • {@code ")"}
  • + *
  • {@code "&"}
  • + *
  • {@code "|"}
  • + *
  • {@code "!"}
  • + *
+ * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.launcher.TagFilter#includeTags + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface IncludeTags { + + /** + * One or more tags to include. + * + *

Note: each tag will be {@linkplain String#trim() trimmed} and + * validated according to the Syntax Rules for Tags (see + * {@linkplain IncludeTags class-level Javadoc} for details). + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java new file mode 100644 index 00000000..9d0a8d3c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @SelectClasses} specifies the classes to select when running + * a test suite on the JUnit Platform. + * + * @since 1.0 + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectClass(Class) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface SelectClasses { + + /** + * One or more classes to select. + */ + Class[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java new file mode 100644 index 00000000..64d6cc3d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @SelectClasspathResource} is a {@linkplain Repeatable repeatable} + * annotation that specifies a classpath resource to select when running + * a test suite on the JUnit Platform. + * + * @since 1.8 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectClasspathResource(String, org.junit.platform.engine.discovery.FilePosition) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +@Repeatable(SelectClasspathResources.class) +public @interface SelectClasspathResource { + + /** + * The name of the classpath resource to select. + */ + String value(); + + /** + * The line number within the classpath resource; ignored if not greater than + * zero. + */ + int line() default 0; + + /** + * The column number within the classpath resource; ignored if the line number + * is ignored or if not greater than zero. + */ + int column() default 0; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java new file mode 100644 index 00000000..a8860b9c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @SelectClasspathResources} is a container for one or more + * {@link SelectClasspathResource @SelectClasspathResource} declarations. + * + *

Note, however, that use of the {@code @SelectClasspathResources} container is + * completely optional since {@code @SelectClasspathResource} is a + * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. + * + * @since 1.8 + * @see SelectClasspathResource + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +public @interface SelectClasspathResources { + + /** + * An array of one or more {@link SelectClasspathResource @SelectClasspathResource} + * declarations. + */ + SelectClasspathResource[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java new file mode 100644 index 00000000..30d32a53 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @SelectDirectories} specifies the directories to select when + * running a test suite on the JUnit Platform. + * + * @since 1.8 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectDirectory(String) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +public @interface SelectDirectories { + + /** + * One or more directories to select. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java new file mode 100644 index 00000000..88be9454 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @SelectFile} is a {@linkplain Repeatable repeatable} annotation that + * specifies a file to select when running a test suite on the JUnit + * Platform. + * + * @since 1.8 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectFile(String, org.junit.platform.engine.discovery.FilePosition) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +@Repeatable(SelectFiles.class) +public @interface SelectFile { + + /** + * The file to select. + */ + String value(); + + /** + * The line number within the file; ignored if not greater than zero. + */ + int line() default 0; + + /** + * The column number within the file; ignored if the line number is ignored + * or if not greater than zero. + */ + int column() default 0; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java new file mode 100644 index 00000000..4f4c8fd4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @SelectFiles} is a container for one or more + * {@link SelectFile @SelectFile} declarations. + * + *

Note, however, that use of the {@code @SelectFiles} container is + * completely optional since {@code @SelectFile} is a + * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. + * + * @since 1.8 + * @see SelectFile + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +public @interface SelectFiles { + + /** + * An array of one or more {@link SelectFile @SelectFile} declarations. + */ + SelectFile[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java new file mode 100644 index 00000000..12f90de7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Set; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @SelectModules} specifies the modules to select when running + * a test suite on the JUnit Platform. + * + * @since 1.8 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectModules(Set) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +public @interface SelectModules { + + /** + * One or more modules to select. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java new file mode 100644 index 00000000..c2d5eaff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @SelectPackages} specifies the names of packages to select + * when running a test suite on the JUnit Platform. + * + * @since 1.0 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectPackage(String) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = MAINTAINED, since = "1.0") +public @interface SelectPackages { + + /** + * One or more fully qualified package names to select. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java new file mode 100644 index 00000000..82512f2f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * {@code @SelectUris} specifies the URIs to select when running a test + * suite on the JUnit Platform. + * + * @since 1.8 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectUri(String) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +public @interface SelectUris { + + /** + * One or more URIs to select. + */ + String[] value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java new file mode 100644 index 00000000..739d6582 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.junit.platform.commons.annotation.Testable; + +/** + * {@code @Suite} marks a class as a test suite on the JUnit Platform. + * + *

Selector and filter annotations are used to control the contents of the + * suite. Additionally configuration can be passed to the suite via the + * configuration annotations. + * + *

When the {@link IncludeClassNamePatterns @IncludeClassNamePatterns} + * annotation is not present, the default include pattern + * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} + * will be used in order to avoid loading classes unnecessarily (see {@link + * org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN + * ClassNameFilter#STANDARD_INCLUDE_PATTERN}). + * + *

By default a suite discovers tests using the configuration parameters + * explicitly configured by {@link ConfigurationParameter @ConfigurationParameter} + * and the configuration parameters from the discovery request that discovered + * the suite. Annotating a suite with + * {@link DisableParentConfigurationParameters @DisableParentConfigurationParameters} + * annotation disables the latter as a source of parameters so that only explicit + * configuration parameters are taken into account. + * + * @since 1.8 + * @see SelectClasses + * @see SelectClasspathResource + * @see SelectDirectories + * @see SelectFile + * @see SelectModules + * @see SelectPackages + * @see SelectUris + * @see IncludeClassNamePatterns + * @see ExcludeClassNamePatterns + * @see IncludeEngines + * @see ExcludeEngines + * @see IncludePackages + * @see ExcludePackages + * @see IncludeTags + * @see ExcludeTags + * @see SuiteDisplayName + * @see ConfigurationParameter + * @see DisableParentConfigurationParameters + * @see org.junit.platform.launcher.LauncherDiscoveryRequest + * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder + * @see org.junit.platform.launcher.Launcher + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = Status.EXPERIMENTAL, since = "1.8") +@Testable +public @interface Suite { + + /** + * Fail suite if no tests were discovered. + * + * @since 1.9 + */ + @API(status = Status.EXPERIMENTAL, since = "1.9") + boolean failIfNoTests() default true; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java new file mode 100644 index 00000000..3cd11cb9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @SuiteDisplayName} is used to declare a {@linkplain #value custom + * display name} for the annotated test class that is executed as a test suite + * on the JUnit Platform. + * + *

Display names are typically used for test reporting in IDEs and build + * tools and may contain spaces, special characters, and even emoji. + * + *

JUnit 4 Suite Support

+ *

Test suites can be run on the JUnit Platform in a JUnit 4 environment via + * {@code @RunWith(JUnitPlatform.class)}. + * + *

JUnit 5 Suite Support

+ *

Test suites can be run on the JUnit Platform in a JUnit 5 environment via + * the {@code junit-platform-suite-engine} module. + * + * @since 1.1 + * @see Suite + * @see org.junit.platform.runner.JUnitPlatform + */ +@Retention(RUNTIME) +@Target(TYPE) +@Documented +@API(status = MAINTAINED, since = "1.1") +public @interface SuiteDisplayName { + + /** + * Custom display name for the annotated class. + * + * @return a custom display name; never blank or consisting solely of + * whitespace + */ + String value(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java new file mode 100644 index 00000000..12b11808 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.api; + +import static org.apiguardian.api.API.Status.DEPRECATED; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @UseTechnicalNames} specifies that technical names should be + * used instead of display names when running a test suite on the + * JUnit Platform. + * + *

By default, display names will be used for test artifacts in + * reports and graphical displays in IDEs; however, when a JUnit Platform test + * suite is executed with a build tool such as Gradle or Maven, the generated + * test report may need to include the technical names of test + * artifacts — for example, fully qualified class names — instead + * of shorter display names like the simple name of a test class or a + * custom display name containing special characters. + * + *

Note that the presence of {@code @UseTechnicalNames} overrides any custom + * display name configured for the suite via {@link SuiteDisplayName @SuiteDisplayName}. + * + *

JUnit 4 Suite Support

+ *

Test suites can be run on the JUnit Platform in a JUnit 4 environment via + * {@code @RunWith(JUnitPlatform.class)}. + * + * @since 1.0 + * @see org.junit.platform.runner.JUnitPlatform + * @deprecated since 1.8, in favor of the {@link Suite @Suite} support provided by + * the {@code junit-platform-suite-engine} module; to be removed in JUnit Platform 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +@API(status = DEPRECATED, since = "1.8") +@Deprecated +public @interface UseTechnicalNames { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java new file mode 100644 index 00000000..f5cd9bc5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java @@ -0,0 +1,13 @@ +/** + * Annotations for configuring a test suite on the JUnit Platform. + * + *

JUnit 4 Suite Support

+ *

Test suites can be run on the JUnit Platform in a JUnit 4 environment via + * {@code @RunWith(JUnitPlatform.class)} with the {@code junit-platform-runner}. + * + *

JUnit 5 Suite Support

+ *

Test suites can be run on the JUnit Platform in a JUnit 5 environment via + * {@link Suite @Suite} with the {@code junit-platform-suite-engine}. + */ + +package org.junit.platform.suite.api; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java new file mode 100644 index 00000000..9af8daea --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Annotations for configuring a test suite on the JUnit Platform. + * + * @since 1.0 + */ +module org.junit.platform.suite.api { + requires static transitive org.apiguardian.api; + requires transitive org.junit.platform.commons; + + exports org.junit.platform.suite.api; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts new file mode 100644 index 00000000..8f578b0b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `java-library-conventions` +} + +description = "JUnit Platform Suite Commons" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformLauncher) + + compileOnlyApi(libs.apiguardian) + + implementation(projects.junitPlatformEngine) + implementation(projects.junitPlatformSuiteApi) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java new file mode 100644 index 00000000..99dffdaf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.commons; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.FilePosition; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UriSelector; + +/** + * @since 1.8 + */ +class AdditionalDiscoverySelectors { + + static List selectUris(String... uris) { + Preconditions.notNull(uris, "URI list must not be null"); + Preconditions.containsNoNullElements(uris, "Individual URIs must not be null"); + + // @formatter:off + return uniqueStreamOf(uris) + .filter(StringUtils::isNotBlank) + .map(DiscoverySelectors::selectUri) + .collect(Collectors.toList()); + // @formatter:on + } + + static List selectDirectories(String... paths) { + Preconditions.notNull(paths, "Directory paths must not be null"); + Preconditions.containsNoNullElements(paths, "Individual directory paths must not be null"); + + // @formatter:off + return uniqueStreamOf(paths) + .filter(StringUtils::isNotBlank) + .map(DiscoverySelectors::selectDirectory) + .collect(Collectors.toList()); + // @formatter:on + } + + static List selectPackages(String... packageNames) { + Preconditions.notNull(packageNames, "Package names must not be null"); + Preconditions.containsNoNullElements(packageNames, "Individual package names must not be null"); + + // @formatter:off + return uniqueStreamOf(packageNames) + .map(DiscoverySelectors::selectPackage) + .collect(Collectors.toList()); + // @formatter:on + } + + static List selectClasses(Class... classes) { + Preconditions.notNull(classes, "classes must not be null"); + Preconditions.containsNoNullElements(classes, "Individual classes must not be null"); + + // @formatter:off + return uniqueStreamOf(classes) + .map(DiscoverySelectors::selectClass) + .collect(Collectors.toList()); + // @formatter:on + } + + static List selectModules(String... moduleNames) { + Preconditions.notNull(moduleNames, "Module names must not be null"); + Preconditions.containsNoNullElements(moduleNames, "Individual module names must not be null"); + + return DiscoverySelectors.selectModules(uniqueStreamOf(moduleNames).collect(Collectors.toSet())); + } + + static FileSelector selectFile(String path, int line, int column) { + Preconditions.notBlank(path, "File path must not be null or blank"); + + if (line <= 0) { + return DiscoverySelectors.selectFile(path); + } + if (column <= 0) { + return DiscoverySelectors.selectFile(path, FilePosition.from(line)); + } + return DiscoverySelectors.selectFile(path, FilePosition.from(line, column)); + } + + static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, int line, int column) { + Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); + + if (line <= 0) { + return DiscoverySelectors.selectClasspathResource(classpathResourceName); + } + if (column <= 0) { + return DiscoverySelectors.selectClasspathResource(classpathResourceName, FilePosition.from(line)); + } + return DiscoverySelectors.selectClasspathResource(classpathResourceName, FilePosition.from(line, column)); + } + + private static Stream uniqueStreamOf(T[] packageNames) { + return Arrays.stream(packageNames).distinct(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java new file mode 100644 index 00000000..fabe8891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java @@ -0,0 +1,234 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.commons; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; +import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectClasspathResource; +import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectFile; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.PackageNameFilter; +import org.junit.platform.launcher.EngineFilter; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TagFilter; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.DisableParentConfigurationParameters; +import org.junit.platform.suite.api.ExcludeClassNamePatterns; +import org.junit.platform.suite.api.ExcludeEngines; +import org.junit.platform.suite.api.ExcludePackages; +import org.junit.platform.suite.api.ExcludeTags; +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.IncludePackages; +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.SelectDirectories; +import org.junit.platform.suite.api.SelectFile; +import org.junit.platform.suite.api.SelectModules; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.SelectUris; + +/** + * @since 1.8 + */ +@API(status = Status.INTERNAL, since = "1.8", consumers = { "org.junit.platform.suite.engine", + "org.junit.platform.runner" }) +public final class SuiteLauncherDiscoveryRequestBuilder { + + private final LauncherDiscoveryRequestBuilder delegate = LauncherDiscoveryRequestBuilder.request(); + private final List selectedClassNames = new ArrayList<>(); + private boolean includeClassNamePatternsUsed; + private boolean filterStandardClassNamePatterns = false; + private ConfigurationParameters parentConfigurationParameters; + private boolean enableParentConfigurationParameters = true; + + private SuiteLauncherDiscoveryRequestBuilder() { + } + + public static SuiteLauncherDiscoveryRequestBuilder request() { + return new SuiteLauncherDiscoveryRequestBuilder(); + } + + public SuiteLauncherDiscoveryRequestBuilder filterStandardClassNamePatterns( + boolean filterStandardClassNamePatterns) { + this.filterStandardClassNamePatterns = filterStandardClassNamePatterns; + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder selectors(DiscoverySelector... selectors) { + delegate.selectors(selectors); + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder selectors(List selectors) { + delegate.selectors(selectors); + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder filters(Filter... filters) { + delegate.filters(filters); + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { + delegate.configurationParameter(key, value); + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder configurationParameters(Map configurationParameters) { + delegate.configurationParameters(configurationParameters); + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder parentConfigurationParameters( + ConfigurationParameters parentConfigurationParameters) { + this.parentConfigurationParameters = parentConfigurationParameters; + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder enableImplicitConfigurationParameters(boolean enabled) { + delegate.enableImplicitConfigurationParameters(enabled); + return this; + } + + public SuiteLauncherDiscoveryRequestBuilder suite(Class suiteClass) { + Preconditions.notNull(suiteClass, "Suite class must not be null"); + + // Annotations in alphabetical order (except @SelectClasses) + // @formatter:off + findRepeatableAnnotations(suiteClass, ConfigurationParameter.class) + .forEach(configuration -> configurationParameter(configuration.key(), configuration.value())); + findAnnotation(suiteClass, DisableParentConfigurationParameters.class) + .ifPresent(__ -> enableParentConfigurationParameters = false); + findAnnotationValues(suiteClass, ExcludeClassNamePatterns.class, ExcludeClassNamePatterns::value) + .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) + .map(ClassNameFilter::excludeClassNamePatterns) + .ifPresent(this::filters); + findAnnotationValues(suiteClass, ExcludeEngines.class, ExcludeEngines::value) + .map(EngineFilter::excludeEngines) + .ifPresent(this::filters); + findAnnotationValues(suiteClass, ExcludePackages.class, ExcludePackages::value) + .map(PackageNameFilter::excludePackageNames) + .ifPresent(this::filters); + findAnnotationValues(suiteClass, ExcludeTags.class, ExcludeTags::value) + .map(TagFilter::excludeTags) + .ifPresent(this::filters); + // Process @SelectClasses before @IncludeClassNamePatterns, since the names + // of selected classes get automatically added to the include filter. + findAnnotationValues(suiteClass, SelectClasses.class, SelectClasses::value) + .map(this::selectClasses) + .ifPresent(this::selectors); + findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value) + .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) + .map(this::createIncludeClassNameFilter) + .ifPresent(filters -> { + includeClassNamePatternsUsed = true; + filters(filters); + }); + findAnnotationValues(suiteClass, IncludeEngines.class, IncludeEngines::value) + .map(EngineFilter::includeEngines) + .ifPresent(this::filters); + findAnnotationValues(suiteClass, IncludePackages.class, IncludePackages::value) + .map(PackageNameFilter::includePackageNames) + .ifPresent(this::filters); + findAnnotationValues(suiteClass, IncludeTags.class, IncludeTags::value) + .map(TagFilter::includeTags) + .ifPresent(this::filters); + findRepeatableAnnotations(suiteClass, SelectClasspathResource.class) + .stream() + .map(annotation -> selectClasspathResource(annotation.value(), annotation.line(), annotation.column())) + .forEach(this::selectors); + findAnnotationValues(suiteClass, SelectDirectories.class, SelectDirectories::value) + .map(AdditionalDiscoverySelectors::selectDirectories) + .ifPresent(this::selectors); + findRepeatableAnnotations(suiteClass, SelectFile.class) + .stream() + .map(annotation -> selectFile(annotation.value(), annotation.line(), annotation.column())) + .forEach(this::selectors); + findAnnotationValues(suiteClass, SelectModules.class, SelectModules::value) + .map(AdditionalDiscoverySelectors::selectModules) + .ifPresent(this::selectors); + findAnnotationValues(suiteClass, SelectUris.class, SelectUris::value) + .map(AdditionalDiscoverySelectors::selectUris) + .ifPresent(this::selectors); + findAnnotationValues(suiteClass, SelectPackages.class, SelectPackages::value) + .map(AdditionalDiscoverySelectors::selectPackages) + .ifPresent(this::selectors); + // @formatter:on + return this; + } + + public LauncherDiscoveryRequest build() { + if (filterStandardClassNamePatterns && !includeClassNamePatternsUsed) { + delegate.filters(createIncludeClassNameFilter(STANDARD_INCLUDE_PATTERN)); + } + + if (enableParentConfigurationParameters && parentConfigurationParameters != null) { + delegate.parentConfigurationParameters(parentConfigurationParameters); + } + + return delegate.build(); + } + + private List selectClasses(Class... classes) { + Arrays.stream(classes).map(Class::getName).distinct().forEach(this.selectedClassNames::add); + return AdditionalDiscoverySelectors.selectClasses(classes); + } + + private ClassNameFilter createIncludeClassNameFilter(String... patterns) { + String[] combinedPatterns = Stream.concat(// + this.selectedClassNames.stream().map(Pattern::quote), // + Arrays.stream(patterns)// + ).toArray(String[]::new); + return ClassNameFilter.includeClassNamePatterns(combinedPatterns); + } + + private static Optional findAnnotationValues(AnnotatedElement element, + Class annotationType, Function valueExtractor) { + return findAnnotation(element, annotationType).map(valueExtractor).filter(values -> values.length > 0); + } + + private static Optional trimmed(String[] patterns) { + if (patterns.length == 0) { + return Optional.empty(); + } + // @formatter:off + return Optional.of(Arrays.stream(patterns) + .filter(StringUtils::isNotBlank) + .map(String::trim) + .toArray(String[]::new)); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java new file mode 100644 index 00000000..3eeb6350 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java @@ -0,0 +1,11 @@ +/** + * Common support utilities for executing test suites on the JUnit Platform. + * + *

DISCLAIMER

+ * + *

Any API annotated with {@code @API(status = INTERNAL)} is intended solely + * for usage within the JUnit framework itself. Any usage of internal + * APIs by external parties is not supported! + */ + +package org.junit.platform.suite.commons; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java new file mode 100644 index 00000000..b810efc7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Common support utilities for declarative test suite executors. + * + * @since 1.8 + */ +module org.junit.platform.suite.commons { + requires static transitive org.apiguardian.api; + requires org.junit.platform.suite.api; + requires org.junit.platform.commons; + requires org.junit.platform.engine; + requires transitive org.junit.platform.launcher; + + exports org.junit.platform.suite.commons to + org.junit.platform.suite.engine, + org.junit.platform.runner; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts new file mode 100644 index 00000000..12e7f674 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `java-library-conventions` +} + +description = "JUnit Platform Suite Engine" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformEngine) + api(projects.junitPlatformSuiteApi) + + compileOnlyApi(libs.apiguardian) + + implementation(projects.junitPlatformSuiteCommons) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java new file mode 100644 index 00000000..441dc3f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; + +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.UniqueId.Segment; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.support.discovery.SelectorResolver; + +/** + * @since 1.8 + */ +final class ClassSelectorResolver implements SelectorResolver { + + private static final Logger log = LoggerFactory.getLogger(ClassSelectorResolver.class); + + private static final IsSuiteClass isSuiteClass = new IsSuiteClass(); + + private final Predicate classNameFilter; + private final SuiteEngineDescriptor suiteEngineDescriptor; + private final ConfigurationParameters configurationParameters; + + ClassSelectorResolver(Predicate classNameFilter, SuiteEngineDescriptor suiteEngineDescriptor, + ConfigurationParameters configurationParameters) { + this.classNameFilter = classNameFilter; + this.suiteEngineDescriptor = suiteEngineDescriptor; + this.configurationParameters = configurationParameters; + } + + @Override + public Resolution resolve(ClassSelector selector, Context context) { + Class testClass = selector.getJavaClass(); + if (isSuiteClass.test(testClass)) { + if (classNameFilter.test(testClass.getName())) { + // @formatter:off + Optional suiteWithDiscoveryRequest = context + .addToParent(parent -> newSuiteDescriptor(testClass, parent)) + .map(suite -> suite.addDiscoveryRequestFrom(testClass)); + return toResolution(suiteWithDiscoveryRequest); + // @formatter:on + } + } + return unresolved(); + } + + @Override + public Resolution resolve(UniqueIdSelector selector, Context context) { + UniqueId uniqueId = selector.getUniqueId(); + UniqueId engineId = suiteEngineDescriptor.getUniqueId(); + List resolvedSegments = engineId.getSegments(); + // @formatter:off + return uniqueId.getSegments() + .stream() + .skip(resolvedSegments.size()) + .findFirst() + .filter(suiteSegment -> SuiteTestDescriptor.SEGMENT_TYPE.equals(suiteSegment.getType())) + .flatMap(ClassSelectorResolver::tryLoadSuiteClass) + .filter(isSuiteClass) + .map(suiteClass -> context + .addToParent(parent -> newSuiteDescriptor(suiteClass, parent)) + .map(suite -> uniqueId.equals(suite.getUniqueId()) + // The uniqueId selector either targeted a class annotated with @Suite; + ? suite.addDiscoveryRequestFrom(suiteClass) + // or a specific test in that suite + : suite.addDiscoveryRequestFrom(uniqueId))) + .map(ClassSelectorResolver::toResolution) + .orElseGet(Resolution::unresolved); + // @formatter:on + } + + private static Optional> tryLoadSuiteClass(UniqueId.Segment segment) { + return ReflectionUtils.tryToLoadClass(segment.getValue()).toOptional(); + } + + private static Resolution toResolution(Optional suite) { + return suite.map(Match::exact).map(Resolution::match).orElseGet(Resolution::unresolved); + } + + private Optional newSuiteDescriptor(Class suiteClass, TestDescriptor parent) { + UniqueId id = parent.getUniqueId().append(SuiteTestDescriptor.SEGMENT_TYPE, suiteClass.getName()); + if (containsCycle(id)) { + log.config(() -> createConfigContainsCycleMessage(suiteClass, id)); + return Optional.empty(); + } + + return Optional.of(new SuiteTestDescriptor(id, suiteClass, configurationParameters)); + } + + private static boolean containsCycle(UniqueId id) { + List segments = id.getSegments(); + List engineAndSuiteSegment = segments.subList(segments.size() - 2, segments.size()); + List ancestorSegments = segments.subList(0, segments.size() - 2); + for (int i = 0; i < ancestorSegments.size() - 1; i++) { + List candidate = ancestorSegments.subList(i, i + 2); + if (engineAndSuiteSegment.equals(candidate)) { + return true; + } + } + return false; + } + + private static String createConfigContainsCycleMessage(Class suiteClass, UniqueId suiteId) { + return String.format( + "The suite configuration of [%s] resulted in a cycle [%s] and will not be discovered a second time.", + suiteClass.getName(), suiteId); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java new file mode 100644 index 00000000..f9c20e5a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; + +/** + * @since 1.8 + */ +final class DiscoverySelectorResolver { + + // @formatter:off + private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() + .addClassContainerSelectorResolver(new IsSuiteClass()) + .addSelectorResolver(context -> new ClassSelectorResolver( + context.getClassNameFilter(), + context.getEngineDescriptor(), + context.getDiscoveryRequest().getConfigurationParameters())) + .build(); + // @formatter:on + + private static void discoverSuites(SuiteEngineDescriptor engineDescriptor) { + // @formatter:off + engineDescriptor.getChildren().stream() + .map(SuiteTestDescriptor.class::cast) + .forEach(SuiteTestDescriptor::discover); + // @formatter:on + } + + void resolveSelectors(EngineDiscoveryRequest request, SuiteEngineDescriptor engineDescriptor) { + resolver.resolve(request, engineDescriptor); + discoverSuites(engineDescriptor); + engineDescriptor.accept(TestDescriptor::prune); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java new file mode 100644 index 00000000..9e6816af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; +import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; +import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; + +import java.util.function.Predicate; + +/** + * @since 1.8 + */ +final class IsPotentialTestContainer implements Predicate> { + + @Override + public boolean test(Class candidate) { + // Please do not collapse the following into a single statement. + if (isPrivate(candidate)) { + return false; + } + if (isAbstract(candidate)) { + return false; + } + if (candidate.isLocalClass()) { + return false; + } + if (candidate.isAnonymousClass()) { + return false; + } + return !isInnerClass(candidate); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java new file mode 100644 index 00000000..f336c03f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import java.util.function.Predicate; + +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.suite.api.Suite; + +/** + * @since 1.8 + */ +final class IsSuiteClass implements Predicate> { + + private static final IsPotentialTestContainer isPotentialTestContainer = new IsPotentialTestContainer(); + + @Override + public boolean test(Class testClass) { + return isPotentialTestContainer.test(testClass) && hasSuiteAnnotation(testClass); + } + + private boolean hasSuiteAnnotation(Class testClass) { + return AnnotationSupport.isAnnotated(testClass, Suite.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java new file mode 100644 index 00000000..966cfcbe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import org.junit.platform.commons.JUnitException; + +class NoTestsDiscoveredException extends JUnitException { + + private static final long serialVersionUID = 1L; + + NoTestsDiscoveredException(Class suiteClass) { + super(String.format("Suite [%s] did not discover any tests", suiteClass.getName())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java new file mode 100644 index 00000000..3c351b2e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; + +/** + * @since 1.8 + */ +final class SuiteEngineDescriptor extends EngineDescriptor { + + static final String ENGINE_ID = "junit-platform-suite"; + + SuiteEngineDescriptor(UniqueId uniqueId) { + super(uniqueId, "JUnit Platform Suite"); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java new file mode 100644 index 00000000..af776ef8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import static java.util.Collections.emptyList; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; +import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase; +import org.junit.platform.launcher.core.EngineExecutionOrchestrator; +import org.junit.platform.launcher.core.LauncherDiscoveryResult; +import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +/** + * @since 1.8 + */ +class SuiteLauncher { + + private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator(); + private final EngineDiscoveryOrchestrator discoveryOrchestrator; + + static SuiteLauncher create() { + Set engines = new LinkedHashSet<>(); + new ServiceLoaderTestEngineRegistry().loadTestEngines().forEach(engines::add); + return new SuiteLauncher(engines); + } + + private SuiteLauncher(Set testEngines) { + Preconditions.condition(hasTestEngineOtherThanSuiteEngine(testEngines), + () -> "Cannot create SuiteLauncher without at least one other TestEngine; " + + "consider adding an engine implementation JAR to the classpath"); + this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, emptyList()); + } + + private boolean hasTestEngineOtherThanSuiteEngine(Set testEngines) { + return testEngines.stream().anyMatch(testEngine -> !SuiteEngineDescriptor.ENGINE_ID.equals(testEngine.getId())); + } + + LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, UniqueId parentId) { + return discoveryOrchestrator.discover(discoveryRequest, Phase.DISCOVERY, parentId); + } + + TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, + EngineExecutionListener parentEngineExecutionListener) { + SummaryGeneratingListener listener = new SummaryGeneratingListener(); + executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener); + return listener.getSummary(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java new file mode 100644 index 00000000..52cb4e11 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java @@ -0,0 +1,147 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.LauncherDiscoveryResult; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; +import org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder; + +/** + * {@link TestDescriptor} for tests based on the JUnit Platform Suite API. + * + *

Default Display Names

+ * + *

The default display name is the simple name of the class. + * + * @since 1.8 + * @see SuiteDisplayName + */ +final class SuiteTestDescriptor extends AbstractTestDescriptor { + + static final String SEGMENT_TYPE = "suite"; + + private final SuiteLauncherDiscoveryRequestBuilder discoveryRequestBuilder = request(); + private final ConfigurationParameters configurationParameters; + private final Boolean failIfNoTests; + private final Class suiteClass; + + private LauncherDiscoveryResult launcherDiscoveryResult; + private SuiteLauncher launcher; + + SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters) { + super(id, getSuiteDisplayName(suiteClass), ClassSource.from(suiteClass)); + this.configurationParameters = configurationParameters; + this.failIfNoTests = getFailIfNoTests(suiteClass); + this.suiteClass = suiteClass; + } + + private static Boolean getFailIfNoTests(Class suiteClass) { + // @formatter:off + return findAnnotation(suiteClass, Suite.class) + .map(Suite::failIfNoTests) + .orElseThrow(() -> new JUnitException(String.format("Suite [%s] was not annotated with @Suite", suiteClass.getName()))); + // @formatter:on + } + + SuiteTestDescriptor addDiscoveryRequestFrom(Class suiteClass) { + Preconditions.condition(launcherDiscoveryResult == null, + "discovery request can not be modified after discovery"); + discoveryRequestBuilder.suite(suiteClass); + return this; + } + + SuiteTestDescriptor addDiscoveryRequestFrom(UniqueId uniqueId) { + Preconditions.condition(launcherDiscoveryResult == null, + "discovery request can not be modified after discovery"); + discoveryRequestBuilder.selectors(DiscoverySelectors.selectUniqueId(uniqueId)); + return this; + } + + void discover() { + if (launcherDiscoveryResult != null) { + return; + } + + // @formatter:off + LauncherDiscoveryRequest request = discoveryRequestBuilder + .filterStandardClassNamePatterns(true) + .enableImplicitConfigurationParameters(false) + .parentConfigurationParameters(configurationParameters) + .build(); + // @formatter:on + this.launcher = SuiteLauncher.create(); + this.launcherDiscoveryResult = launcher.discover(request, getUniqueId()); + // @formatter:off + launcherDiscoveryResult.getTestEngines() + .stream() + .map(testEngine -> launcherDiscoveryResult.getEngineTestDescriptor(testEngine)) + .forEach(this::addChild); + // @formatter:on + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + + private static String getSuiteDisplayName(Class testClass) { + // @formatter:off + return findAnnotation(testClass, SuiteDisplayName.class) + .map(SuiteDisplayName::value) + .filter(StringUtils::isNotBlank) + .orElse(testClass.getSimpleName()); + // @formatter:on + } + + void execute(EngineExecutionListener parentEngineExecutionListener) { + parentEngineExecutionListener.executionStarted(this); + // #2838: The discovery result from a suite may have been filtered by + // post discovery filters from the launcher. The discovery result should + // be pruned accordingly + LauncherDiscoveryResult discoveryResult = this.launcherDiscoveryResult.withRetainedEngines( + getChildren()::contains); + TestExecutionSummary summary = launcher.execute(discoveryResult, parentEngineExecutionListener); + parentEngineExecutionListener.executionFinished(this, computeTestExecutionResult(summary)); + } + + private TestExecutionResult computeTestExecutionResult(TestExecutionSummary summary) { + if (failIfNoTests && summary.getTestsFoundCount() == 0) { + return TestExecutionResult.failed(new NoTestsDiscoveredException(suiteClass)); + } + return TestExecutionResult.successful(); + } + + @Override + public boolean mayRegisterTests() { + // While a suite will not register new tests after discovery, we pretend + // it does. This allows the suite to fail if not tests were discovered. + // If not, the empty suite would be pruned. + return true; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java new file mode 100644 index 00000000..96b35339 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; + +/** + * The JUnit Platform Suite {@link org.junit.platform.engine.TestEngine TestEngine}. + * + * @since 1.8 + */ +@API(status = INTERNAL, since = "1.8") +public final class SuiteTestEngine implements TestEngine { + + @Override + public String getId() { + return SuiteEngineDescriptor.ENGINE_ID; + } + + /** + * Returns {@code org.junit.platform} as the group ID. + */ + @Override + public Optional getGroupId() { + return Optional.of("org.junit.platform"); + } + + /** + * Returns {@code junit-platform-suite-engine} as the artifact ID. + */ + @Override + public Optional getArtifactId() { + return Optional.of("junit-platform-suite-engine"); + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + SuiteEngineDescriptor engineDescriptor = new SuiteEngineDescriptor(uniqueId); + new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); + return engineDescriptor; + } + + @Override + public void execute(ExecutionRequest request) { + SuiteEngineDescriptor suiteEngineDescriptor = (SuiteEngineDescriptor) request.getRootTestDescriptor(); + EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); + + engineExecutionListener.executionStarted(suiteEngineDescriptor); + + // @formatter:off + suiteEngineDescriptor.getChildren() + .stream() + .map(SuiteTestDescriptor.class::cast) + .forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener)); + // @formatter:on + engineExecutionListener.executionFinished(suiteEngineDescriptor, TestExecutionResult.successful()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java new file mode 100644 index 00000000..fa4237d3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java @@ -0,0 +1,5 @@ +/** + * Core package for the JUnit Platform Suite test engine. + */ + +package org.junit.platform.suite.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine new file mode 100644 index 00000000..5832d4fc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine @@ -0,0 +1 @@ +org.junit.platform.suite.engine.SuiteTestEngine diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java new file mode 100644 index 00000000..f98845cb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Provides a {@linkplain org.junit.platform.engine.TestEngine} for running + * declarative test suites. + * + * @since 1.8 + * @provides org.junit.platform.engine.TestEngine + */ +module org.junit.platform.suite.engine { + requires static org.apiguardian.api; + requires org.junit.platform.suite.api; + requires org.junit.platform.suite.commons; + requires org.junit.platform.commons; + requires org.junit.platform.engine; + requires org.junit.platform.launcher; + + provides org.junit.platform.engine.TestEngine + with org.junit.platform.suite.engine.SuiteTestEngine; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts new file mode 100644 index 00000000..8615483d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts @@ -0,0 +1,14 @@ +plugins { + `java-library-conventions` +} + +description = "JUnit Platform Suite (Aggregator)" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformSuiteApi) + implementation(projects.junitPlatformSuiteEngine) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java new file mode 100644 index 00000000..74a575d7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Aggregates all JUnit Platform Suite modules. + * + * @since 1.8 + */ +module org.junit.platform.suite { + requires transitive org.junit.platform.suite.api; + requires transitive org.junit.platform.suite.engine; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md new file mode 100644 index 00000000..a32decd8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md @@ -0,0 +1,98 @@ +Eclipse Public License - v 2.0 +============================== + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +### 1. Definitions + +“Contribution” means: +* **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and +* **b)** in the case of each subsequent Contributor: + * **i)** changes to the Program, and + * **ii)** additions to the Program; +where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. + +“Contributor” means any person or entity that Distributes the Program. + +“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +“Program” means the Contributions Distributed in accordance with this Agreement. + +“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. + +“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. + +“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. + +“Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy. + +“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. + +“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. + +### 2. Grant of Rights + +**a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. + +**b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. + +**c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. + +**d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. + +**e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). + +### 3. Requirements + +**3.1** If a Contributor Distributes the Program in any form, then: + +* **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and + +* **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: + * **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; + * **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; + * **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and + * **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. + +**3.2** When the Program is Distributed as Source Code: + +* **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and +* **b)** a copy of this Agreement must be included with each copy of the Program. + +**3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. + +### 4. Commercial Distribution + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +### 5. No Warranty + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +### 6. Disclaimer of Liability + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +### 7. General + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. + +#### Exhibit A - Form of Secondary Licenses Notice + +> “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” + +Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. + +If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts new file mode 100644 index 00000000..6a574bcf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts @@ -0,0 +1,17 @@ +plugins { + `java-library-conventions` +} + +description = "JUnit Platform Test Kit" + +dependencies { + api(platform(projects.junitBom)) + api(libs.assertj) + api(libs.opentest4j) + api(projects.junitPlatformLauncher) + + compileOnlyApi(libs.apiguardian) + + osgiVerification(projects.junitJupiterEngine) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java new file mode 100644 index 00000000..74015e88 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.opentest4j.AssertionFailedError; +import org.opentest4j.MultipleFailuresError; + +/** + * {@code Assertions} is a collection of selected assertion utility methods + * from JUnit Jupiter for use within the JUnit Platform Test Kit. + * + * @since 1.4 + */ +class Assertions { + + @FunctionalInterface + interface Executable { + void execute() throws Throwable; + } + + static void assertAll(String heading, Stream executables) { + Preconditions.notNull(executables, "executables stream must not be null"); + + List failures = executables // + .map(executable -> { + Preconditions.notNull(executable, "individual executables must not be null"); + try { + executable.execute(); + return null; + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + return t; + } + }) // + .filter(Objects::nonNull) // + .collect(Collectors.toList()); + + if (!failures.isEmpty()) { + MultipleFailuresError multipleFailuresError = new MultipleFailuresError(heading, failures); + failures.forEach(multipleFailuresError::addSuppressed); + throw multipleFailuresError; + } + } + + static void assertEquals(long expected, long actual, String message) { + if (expected != actual) { + failNotEqual(expected, actual, message); + } + } + + private static void failNotEqual(long expected, long actual, String message) { + fail(format(expected, actual, message), expected, actual); + } + + private static void fail(String message, Object expected, Object actual) { + throw new AssertionFailedError(message, expected, actual); + } + + private static String format(long expected, long actual, String message) { + return buildPrefix(message) + formatValues(expected, actual); + } + + private static String buildPrefix(String message) { + return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); + } + + private static String formatValues(long expected, long actual) { + return String.format("expected: <%d> but was: <%d>", expected, actual); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java new file mode 100644 index 00000000..bce1db1c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java @@ -0,0 +1,106 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.junit.platform.testkit.engine.Event.byTestDescriptor; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; + +/** + * {@code EngineExecutionResults} provides a fluent API for processing the + * results of executing a test plan on the JUnit Platform for a given + * {@link org.junit.platform.engine.TestEngine TestEngine}. + * + * @since 1.4 + * @see #allEvents() + * @see #containerEvents() + * @see #testEvents() + * @see ExecutionRecorder + * @see Events + * @see Executions + */ +@API(status = MAINTAINED, since = "1.7") +public class EngineExecutionResults { + + private final Events allEvents; + private final Events testEvents; + private final Events containerEvents; + + /** + * Construct {@link EngineExecutionResults} from the supplied list of recorded + * {@linkplain Event events}. + * + * @param events the list of events; never {@code null} or + * containing {@code null} elements + */ + EngineExecutionResults(List events) { + Preconditions.notNull(events, "Event list must not be null"); + Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); + + this.allEvents = new Events(events, "All"); + this.testEvents = new Events(filterEvents(events, TestDescriptor::isTest), "Test"); + this.containerEvents = new Events(filterEvents(events, TestDescriptor::isContainer), "Container"); + } + + /** + * Get all recorded events. + * + * @since 1.6 + * @see #containerEvents() + * @see #testEvents() + */ + public Events allEvents() { + return this.allEvents; + } + + /** + * Get recorded events for containers. + * + *

In this context, the word "container" applies to {@link TestDescriptor + * TestDescriptors} that return {@code true} from {@link TestDescriptor#isContainer()}. + * + * @since 1.6 + * @see #allEvents() + * @see #testEvents() + */ + public Events containerEvents() { + return this.containerEvents; + } + + /** + * Get recorded events for tests. + * + *

In this context, the word "test" applies to {@link TestDescriptor + * TestDescriptors} that return {@code true} from {@link TestDescriptor#isTest()}. + * + * @since 1.6 + * @see #allEvents() + * @see #containerEvents() + */ + public Events testEvents() { + return this.testEvents; + } + + /** + * Filter the supplied list of events using the supplied predicate. + */ + private static Stream filterEvents(List events, Predicate predicate) { + return events.stream().filter(byTestDescriptor(predicate)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java new file mode 100644 index 00000000..195c223f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java @@ -0,0 +1,446 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; + +import java.util.Map; +import java.util.ServiceLoader; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.CollectionUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; +import org.junit.platform.launcher.core.EngineExecutionOrchestrator; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherDiscoveryResult; +import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; + +/** + * {@code EngineTestKit} provides support for executing a test plan for a given + * {@link TestEngine} and then accessing the results via + * {@linkplain EngineExecutionResults a fluent API} to verify the expected results. + * + * @since 1.4 + * @see #engine(String) + * @see #engine(TestEngine) + * @see #execute(String, LauncherDiscoveryRequest) + * @see #execute(TestEngine, LauncherDiscoveryRequest) + * @see EngineExecutionResults + */ +@API(status = MAINTAINED, since = "1.7") +public final class EngineTestKit { + + /** + * Create an execution {@link Builder} for the {@link TestEngine} with the + * supplied ID. + * + *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} + * mechanism, analogous to the manner in which test engines are loaded in + * the JUnit Platform Launcher API. + * + *

Example Usage

+ * + *
+	 * EngineTestKit
+	 *     .engine("junit-jupiter")
+	 *     .selectors(selectClass(MyTests.class))
+	 *     .execute()
+	 *     .testEvents()
+	 *     .assertStatistics(stats -> stats.started(2).finished(2));
+	 * 
+ * + * @param engineId the ID of the {@code TestEngine} to use; must not be + * {@code null} or blank + * @return the engine execution {@code Builder} + * @throws PreconditionViolationException if the supplied ID is {@code null} + * or blank, or if the {@code TestEngine} with the supplied ID + * cannot be loaded + * @see #engine(TestEngine) + * @see #execute(String, LauncherDiscoveryRequest) + * @see #execute(TestEngine, LauncherDiscoveryRequest) + */ + public static Builder engine(String engineId) { + Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); + return engine(loadTestEngine(engineId.trim())); + } + + /** + * Create an execution {@link Builder} for the supplied {@link TestEngine}. + * + *

Example Usage

+ * + *
+	 * EngineTestKit
+	 *     .engine(new MyTestEngine())
+	 *     .selectors(selectClass(MyTests.class))
+	 *     .execute()
+	 *     .testEvents()
+	 *     .assertStatistics(stats -> stats.started(2).finished(2));
+	 * 
+ * + * @param testEngine the {@code TestEngine} to use; must not be {@code null} + * @return the engine execution {@code Builder} + * @throws PreconditionViolationException if the {@code TestEngine} is + * {@code null} + * @see #engine(String) + * @see #execute(String, LauncherDiscoveryRequest) + * @see #execute(TestEngine, LauncherDiscoveryRequest) + */ + public static Builder engine(TestEngine testEngine) { + Preconditions.notNull(testEngine, "TestEngine must not be null"); + return new Builder(testEngine); + } + + /** + * Execute tests for the given {@link EngineDiscoveryRequest} using the + * {@link TestEngine} with the supplied ID. + * + *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} + * mechanism, analogous to the manner in which test engines are loaded in + * the JUnit Platform Launcher API. + * + *

Note that {@link org.junit.platform.launcher.LauncherDiscoveryRequest} + * from the {@code junit-platform-launcher} module is a subtype of + * {@code EngineDiscoveryRequest}. It is therefore quite convenient to make + * use of the DSL provided in + * {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} + * to build an appropriate discovery request to supply to this method. As + * an alternative, consider using {@link #engine(String)} for a more fluent + * API. + * + * @param engineId the ID of the {@code TestEngine} to use; must not be + * {@code null} or blank + * @param discoveryRequest the {@code EngineDiscoveryRequest} to use + * @return the results of the execution + * @throws PreconditionViolationException for invalid arguments or if the + * {@code TestEngine} with the supplied ID cannot be loaded + * @see #execute(String, LauncherDiscoveryRequest) + * @see #engine(String) + * @see #engine(TestEngine) + * @deprecated Please use {@link #execute(String, LauncherDiscoveryRequest)} + * instead. + */ + @Deprecated + @API(status = DEPRECATED, since = "1.7") + public static EngineExecutionResults execute(String engineId, EngineDiscoveryRequest discoveryRequest) { + Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); + return execute(loadTestEngine(engineId.trim()), discoveryRequest); + } + + /** + * Execute tests for the given {@link LauncherDiscoveryRequest} using the + * {@link TestEngine} with the supplied ID. + * + *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} + * mechanism, analogous to the manner in which test engines are loaded in + * the JUnit Platform Launcher API. + * + *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} + * provides a convenient way to build an appropriate discovery request to + * supply to this method. As an alternative, consider using + * {@link #engine(TestEngine)} for a more fluent API. + * + * @param engineId the ID of the {@code TestEngine} to use; must not be + * {@code null} or blank + * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use + * @return the results of the execution + * @throws PreconditionViolationException for invalid arguments or if the + * {@code TestEngine} with the supplied ID cannot be loaded + * @since 1.7 + * @see #execute(TestEngine, LauncherDiscoveryRequest) + * @see #engine(String) + * @see #engine(TestEngine) + */ + public static EngineExecutionResults execute(String engineId, LauncherDiscoveryRequest discoveryRequest) { + Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); + return execute(loadTestEngine(engineId.trim()), discoveryRequest); + } + + /** + * Execute tests for the given {@link EngineDiscoveryRequest} using the + * supplied {@link TestEngine}. + * + *

Note that {@link org.junit.platform.launcher.LauncherDiscoveryRequest} + * from the {@code junit-platform-launcher} module is a subtype of + * {@code EngineDiscoveryRequest}. It is therefore quite convenient to make + * use of the DSL provided in + * {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} + * to build an appropriate discovery request to supply to this method. As + * an alternative, consider using {@link #engine(TestEngine)} for a more fluent + * API. + * + * @param testEngine the {@code TestEngine} to use; must not be {@code null} + * @param discoveryRequest the {@code EngineDiscoveryRequest} to use; must + * not be {@code null} + * @return the recorded {@code EngineExecutionResults} + * @throws PreconditionViolationException for invalid arguments + * @see #execute(TestEngine, LauncherDiscoveryRequest) + * @see #engine(String) + * @see #engine(TestEngine) + * @deprecated Please use {@link #execute(TestEngine, LauncherDiscoveryRequest)} + * instead. + */ + @Deprecated + @API(status = DEPRECATED, since = "1.7") + public static EngineExecutionResults execute(TestEngine testEngine, EngineDiscoveryRequest discoveryRequest) { + Preconditions.notNull(testEngine, "TestEngine must not be null"); + Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); + + ExecutionRecorder executionRecorder = new ExecutionRecorder(); + executeDirectly(testEngine, discoveryRequest, executionRecorder); + return executionRecorder.getExecutionResults(); + } + + /** + * Execute tests for the given {@link LauncherDiscoveryRequest} using the + * supplied {@link TestEngine}. + * + *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} + * provides a convenient way to build an appropriate discovery request to + * supply to this method. As an alternative, consider using + * {@link #engine(TestEngine)} for a more fluent API. + * + * @param testEngine the {@code TestEngine} to use; must not be {@code null} + * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use; must + * not be {@code null} + * @return the recorded {@code EngineExecutionResults} + * @throws PreconditionViolationException for invalid arguments + * @since 1.7 + * @see #execute(String, LauncherDiscoveryRequest) + * @see #engine(String) + * @see #engine(TestEngine) + */ + public static EngineExecutionResults execute(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { + Preconditions.notNull(testEngine, "TestEngine must not be null"); + Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); + + ExecutionRecorder executionRecorder = new ExecutionRecorder(); + executeUsingLauncherOrchestration(testEngine, discoveryRequest, executionRecorder); + return executionRecorder.getExecutionResults(); + } + + private static void executeDirectly(TestEngine testEngine, EngineDiscoveryRequest discoveryRequest, + EngineExecutionListener listener) { + UniqueId engineUniqueId = UniqueId.forEngine(testEngine.getId()); + TestDescriptor engineTestDescriptor = testEngine.discover(discoveryRequest, engineUniqueId); + ExecutionRequest request = new ExecutionRequest(engineTestDescriptor, listener, + discoveryRequest.getConfigurationParameters()); + testEngine.execute(request); + } + + private static void executeUsingLauncherOrchestration(TestEngine testEngine, + LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { + LauncherDiscoveryResult discoveryResult = new EngineDiscoveryOrchestrator(singleton(testEngine), + emptySet()).discover(discoveryRequest, EXECUTION); + TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); + Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); + new EngineExecutionOrchestrator().execute(discoveryResult, listener); + } + + @SuppressWarnings("unchecked") + private static TestEngine loadTestEngine(String engineId) { + Iterable testEngines = new ServiceLoaderTestEngineRegistry().loadTestEngines(); + return ((Stream) CollectionUtils.toStream(testEngines)) // + .filter((TestEngine engine) -> engineId.equals(engine.getId()))// + .findFirst()// + .orElseThrow(() -> new PreconditionViolationException( + String.format("Failed to load TestEngine with ID [%s]", engineId))); + } + + private EngineTestKit() { + /* no-op */ + } + + // ------------------------------------------------------------------------- + + /** + * {@link TestEngine} execution builder. + * + *

See {@link EngineTestKit#engine(String)} and + * {@link EngineTestKit#engine(TestEngine)} for example usage. + * + * @since 1.4 + * @see #selectors(DiscoverySelector...) + * @see #filters(Filter...) + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + * @see #execute() + */ + public static final class Builder { + + private final LauncherDiscoveryRequestBuilder requestBuilder = LauncherDiscoveryRequestBuilder.request() // + .enableImplicitConfigurationParameters(false); + private final TestEngine testEngine; + + private Builder(TestEngine testEngine) { + this.testEngine = testEngine; + } + + /** + * Add all of the supplied {@linkplain DiscoverySelector discovery selectors}. + * + *

Built-in discovery selectors can be created via the static factory + * methods in {@link org.junit.platform.engine.discovery.DiscoverySelectors}. + * + * @param selectors the discovery selectors to add; never {@code null} + * @return this builder for method chaining + * @see #filters(Filter...) + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + * @see #execute() + */ + public Builder selectors(DiscoverySelector... selectors) { + this.requestBuilder.selectors(selectors); + return this; + } + + /** + * Add all of the supplied {@linkplain DiscoveryFilter discovery filters}. + * + *

Built-in discovery filters can be created via the static factory + * methods in {@link org.junit.platform.engine.discovery.ClassNameFilter} + * and {@link org.junit.platform.engine.discovery.PackageNameFilter}. + * + * @param filters the discovery filters to add; never {@code null} + * @return this builder for method chaining + * @see #filters(Filter...) + * @see #selectors(DiscoverySelector...) + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + * @see #execute() + * @deprecated Please use {@link #filters(Filter...)} instead. + */ + @Deprecated + @API(status = DEPRECATED, since = "1.7") + public Builder filters(DiscoveryFilter... filters) { + this.requestBuilder.filters(filters); + return this; + } + + /** + * Add all of the supplied {@linkplain Filter filters}. + * + *

Built-in discovery filters can be created via the static factory + * methods in {@link org.junit.platform.engine.discovery.ClassNameFilter} + * and {@link org.junit.platform.engine.discovery.PackageNameFilter}. + * + *

Built-in post-discovery filters can be created via the static + * factory methods in {@link org.junit.platform.launcher.TagFilter}. + * + * @param filters the filters to add; never {@code null} + * @return this builder for method chaining + * @since 1.7 + * @see #selectors(DiscoverySelector...) + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + * @see #execute() + */ + @API(status = EXPERIMENTAL, since = "1.7") + public Builder filters(Filter... filters) { + this.requestBuilder.filters(filters); + return this; + } + + /** + * Add the supplied configuration parameter. + * + * @param key the configuration parameter key under which to store the + * value; never {@code null} or blank + * @param value the value to store + * @return this builder for method chaining + * @see #selectors(DiscoverySelector...) + * @see #filters(Filter...) + * @see #configurationParameters(Map) + * @see #execute() + * @see org.junit.platform.engine.ConfigurationParameters + */ + public Builder configurationParameter(String key, String value) { + this.requestBuilder.configurationParameter(key, value); + return this; + } + + /** + * Add all of the supplied configuration parameters. + * + * @param configurationParameters the map of configuration parameters to add; + * never {@code null} + * @return this builder for method chaining + * @see #selectors(DiscoverySelector...) + * @see #filters(Filter...) + * @see #configurationParameter(String, String) + * @see #execute() + * @see org.junit.platform.engine.ConfigurationParameters + */ + public Builder configurationParameters(Map configurationParameters) { + this.requestBuilder.configurationParameters(configurationParameters); + return this; + } + + /** + * Configure whether implicit configuration parameters should be + * considered. + * + *

By default, only configuration parameters that are passed + * explicitly to this builder are taken into account. Passing + * {@code true} to this method, enables additionally reading + * configuration parameters from implicit sources, i.e. system + * properties and the {@code junit-platform.properties} classpath + * resource. + * + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + */ + @API(status = EXPERIMENTAL, since = "1.7") + public Builder enableImplicitConfigurationParameters(boolean enabled) { + this.requestBuilder.enableImplicitConfigurationParameters(enabled); + return this; + } + + /** + * Execute tests for the configured {@link TestEngine}, + * {@linkplain DiscoverySelector discovery selectors}, + * {@linkplain DiscoveryFilter discovery filters}, and + * configuration parameters. + * + * @return the recorded {@code EngineExecutionResults} + * @see #selectors(DiscoverySelector...) + * @see #filters(Filter...) + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + */ + public EngineExecutionResults execute() { + LauncherDiscoveryRequest request = this.requestBuilder.build(); + ExecutionRecorder executionRecorder = new ExecutionRecorder(); + EngineTestKit.executeUsingLauncherOrchestration(this.testEngine, request, executionRecorder); + return executionRecorder.getExecutionResults(); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java new file mode 100644 index 00000000..63c69409 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java @@ -0,0 +1,260 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.junit.platform.commons.util.FunctionUtils.where; + +import java.time.Instant; +import java.util.Optional; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * {@code Event} represents a single event fired during execution of + * a test plan on the JUnit Platform. + * + * @since 1.4 + * @see EventType + */ +@API(status = MAINTAINED, since = "1.7") +public class Event { + + // --- Factories ----------------------------------------------------------- + + /** + * Create an {@code Event} for a reporting entry published for the + * supplied {@link TestDescriptor} and {@link ReportEntry}. + * + * @param testDescriptor the {@code TestDescriptor} associated with the event; + * never {@code null} + * @param entry the {@code ReportEntry} that was published; never {@code null} + * @return the newly created {@code Event} + * @see EventType#REPORTING_ENTRY_PUBLISHED + */ + public static Event reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + Preconditions.notNull(entry, "ReportEntry must not be null"); + return new Event(EventType.REPORTING_ENTRY_PUBLISHED, testDescriptor, entry); + } + + /** + * Create an {@code Event} for the dynamic registration of the + * supplied {@link TestDescriptor}. + * + * @param testDescriptor the {@code TestDescriptor} associated with the event; + * never {@code null} + * @return the newly created {@code Event} + * @see EventType#DYNAMIC_TEST_REGISTERED + */ + public static Event dynamicTestRegistered(TestDescriptor testDescriptor) { + return new Event(EventType.DYNAMIC_TEST_REGISTERED, testDescriptor, null); + } + + /** + * Create a skipped {@code Event} for the supplied + * {@link TestDescriptor} and {@code reason}. + * + * @param testDescriptor the {@code TestDescriptor} associated with the event; + * never {@code null} + * @param reason the reason the execution was skipped; may be {@code null} + * @return the newly created {@code Event} + * @see EventType#SKIPPED + */ + public static Event executionSkipped(TestDescriptor testDescriptor, String reason) { + return new Event(EventType.SKIPPED, testDescriptor, reason); + } + + /** + * Create a started {@code Event} for the supplied + * {@link TestDescriptor}. + * + * @param testDescriptor the {@code TestDescriptor} associated with the + * event; never {@code null} + * @return the newly created {@code Event} + * @see EventType#STARTED + */ + public static Event executionStarted(TestDescriptor testDescriptor) { + return new Event(EventType.STARTED, testDescriptor, null); + } + + /** + * Create a finished {@code Event} for the supplied + * {@link TestDescriptor} and {@link TestExecutionResult}. + * + * @param testDescriptor the {@code TestDescriptor} associated with the + * event; never {@code null} + * @param result the {@code TestExecutionResult} for the supplied + * {@code TestDescriptor}; never {@code null} + * @return the newly created {@code Event} + * @see EventType#FINISHED + */ + public static Event executionFinished(TestDescriptor testDescriptor, TestExecutionResult result) { + Preconditions.notNull(result, "Event of type FINISHED cannot have a null TestExecutionResult"); + return new Event(EventType.FINISHED, testDescriptor, result); + } + + // --- Predicates ---------------------------------------------------------- + + /** + * Create a {@link Predicate} for {@linkplain Event events} whose payload + * types match the supplied {@code payloadType} and whose payloads match the + * supplied {@code payloadPredicate}. + * + * @param payloadType the required payload type + * @param payloadPredicate a {@code Predicate} to match against payloads + * @return the resulting {@code Predicate} + */ + public static Predicate byPayload(Class payloadType, Predicate payloadPredicate) { + return event -> event.getPayload(payloadType).filter(payloadPredicate).isPresent(); + } + + /** + * Create a {@link Predicate} for {@linkplain Event events} whose + * {@linkplain EventType event types} match the supplied {@code type}. + * + * @param type the type to match against + * @return the resulting {@code Predicate} + */ + public static Predicate byType(EventType type) { + return event -> event.type.equals(type); + } + + /** + * Create a {@link Predicate} for {@linkplain Event events} whose + * {@link TestDescriptor TestDescriptors} match the supplied + * {@code testDescriptorPredicate}. + * + * @param testDescriptorPredicate a {@code Predicate} to match against test + * descriptors + * @return the resulting {@link Predicate} + */ + public static Predicate byTestDescriptor(Predicate testDescriptorPredicate) { + return where(Event::getTestDescriptor, testDescriptorPredicate); + } + + // ------------------------------------------------------------------------- + + private final Instant timestamp = Instant.now(); + private final EventType type; + private final TestDescriptor testDescriptor; + private final Object payload; + + /** + * Construct an {@code Event} with the supplied arguments. + * + * @param type the type of the event; never {@code null} + * @param testDescriptor the {@code TestDescriptor} associated with the event; + * never {@code null} + * @param payload the generic payload associated with the event; may be {@code null} + */ + private Event(EventType type, TestDescriptor testDescriptor, Object payload) { + this.type = Preconditions.notNull(type, "EventType must not be null"); + this.testDescriptor = Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); + this.payload = payload; + } + + /** + * Get the type of this {@code Event}. + * + * @return the event type; never {@code null} + * @see EventType + */ + public EventType getType() { + return this.type; + } + + /** + * Get the {@link TestDescriptor} associated with this {@code Event}. + * + * @return the {@code TestDescriptor}; never {@code null} + */ + public TestDescriptor getTestDescriptor() { + return this.testDescriptor; + } + + /** + * Get the {@link Instant} when this {@code Event} occurred. + * + * @return the {@code Instant} when this {@code Event} occurred; + * never {@code null} + */ + public Instant getTimestamp() { + return this.timestamp; + } + + /** + * Get the payload, if available. + * + * @return an {@code Optional} containing the payload; never {@code null} + * but potentially empty + * @see #getPayload(Class) + * @see #getRequiredPayload(Class) + */ + public Optional getPayload() { + return Optional.ofNullable(this.payload); + } + + /** + * Get the payload of the expected type, if available. + * + *

This is a convenience method that automatically casts the payload to + * the expected type. If the payload is not present or is not of the expected + * type, this method will return {@link Optional#empty()}. + * + * @param payloadType the expected payload type; never {@code null} + * @return an {@code Optional} containing the payload; never {@code null} + * but potentially empty + * @see #getPayload() + * @see #getRequiredPayload(Class) + */ + public Optional getPayload(Class payloadType) { + Preconditions.notNull(payloadType, "Payload type must not be null"); + return getPayload().filter(payloadType::isInstance).map(payloadType::cast); + } + + /** + * Get the payload of the required type. + * + *

This is a convenience method that automatically casts the payload to + * the required type. If the payload is not present or is not of the expected + * type, this method will throw an {@link IllegalArgumentException}. + * + * @param payloadType the required payload type; never {@code null} + * @return the payload + * @throws IllegalArgumentException if the payload is of a different type + * or is not present + * @see #getPayload() + * @see #getPayload(Class) + */ + public T getRequiredPayload(Class payloadType) throws IllegalArgumentException { + return getPayload(payloadType).orElseThrow(// + () -> new IllegalArgumentException("Event does not contain a payload of type " + payloadType.getName())); + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("type", this.type) + .append("testDescriptor", this.testDescriptor) + .append("timestamp", this.timestamp) + .append("payload", this.payload) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java new file mode 100644 index 00000000..f1aa4734 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java @@ -0,0 +1,473 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static java.util.function.Predicate.isEqual; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.assertj.core.api.Assertions.allOf; +import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; +import static org.junit.platform.testkit.engine.Event.byPayload; +import static org.junit.platform.testkit.engine.Event.byTestDescriptor; +import static org.junit.platform.testkit.engine.Event.byType; +import static org.junit.platform.testkit.engine.EventType.DYNAMIC_TEST_REGISTERED; +import static org.junit.platform.testkit.engine.EventType.FINISHED; +import static org.junit.platform.testkit.engine.EventType.SKIPPED; +import static org.junit.platform.testkit.engine.EventType.STARTED; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.assertj.core.api.Condition; +import org.assertj.core.description.Description; +import org.assertj.core.description.JoinDescription; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; + +/** + * Collection of AssertJ {@linkplain Condition conditions} for {@link Event}. + * + * @since 1.4 + * @see TestExecutionResultConditions + */ +@API(status = MAINTAINED, since = "1.7") +public final class EventConditions { + + private EventConditions() { + /* no-op */ + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event} matches all of the supplied conditions. + */ + @SafeVarargs + @SuppressWarnings("varargs") + public static Condition event(Condition... conditions) { + return allOf(conditions); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * an instance of {@link EngineDescriptor}. + */ + public static Condition engine() { + return new Condition<>(byTestDescriptor(EngineDescriptor.class::isInstance), "is an engine"); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isTest() test} and its + * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied + * {@link String}. + * + * @see #test() + * @see #uniqueIdSubstring(String) + */ + public static Condition test(String uniqueIdSubstring) { + return test(uniqueIdSubstring(uniqueIdSubstring)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isTest() test}, its + * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied + * {@link String}, and its {@linkplain TestDescriptor#getDisplayName() + * display name} equals the supplied {@link String}. + * + * @see #test() + * @see #test(Condition) + * @see #uniqueIdSubstring(String) + * @see #displayName(String) + */ + public static Condition test(String uniqueIdSubstring, String displayName) { + return allOf(test(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event} matches the supplied {@code Condition} and its + * {@linkplain Event#getTestDescriptor() test descriptor} is a + * {@linkplain TestDescriptor#isTest() test}. + * + *

For example, {@code test(displayName("my display name"))} can be used + * to match against a test with the given display name. + * + * @since 1.8 + * @see #test(String) + * @see #test(String, String) + * @see #displayName(String) + */ + @API(status = MAINTAINED, since = "1.8") + public static Condition test(Condition condition) { + return allOf(test(), condition); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isTest() test}. + */ + public static Condition test() { + return new Condition<>(byTestDescriptor(TestDescriptor::isTest), "is a test"); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isContainer() container} and its + * {@linkplain TestDescriptor#getUniqueId() unique id} contains the + * fully-qualified name of the supplied {@link Class}. + */ + public static Condition container(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + return container(clazz.getName()); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isContainer() container} and its + * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied + * {@link String}. + */ + public static Condition container(String uniqueIdSubstring) { + return container(uniqueIdSubstring(uniqueIdSubstring)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event} matches the supplied {@code Condition} and its + * {@linkplain Event#getTestDescriptor() test descriptor} is a + * {@linkplain TestDescriptor#isContainer() container}. + */ + public static Condition container(Condition condition) { + return allOf(container(), condition); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isContainer() container}. + */ + public static Condition container() { + return new Condition<>(byTestDescriptor(TestDescriptor::isContainer), "is a container"); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event} matches the supplied {@code Condition}, its + * {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isContainer() container}, and its + * {@linkplain TestDescriptor#getUniqueId() unique id} contains the + * simple names of the supplied {@link Class} and all of its + * {@linkplain Class#getEnclosingClass() enclosing classes}. + * + *

For example, {@code nestedContainer(MyNestedTests.class, displayName("my display name"))} + * can be used to match against a nested container with the given display name. + * + *

Please note that this method does not differentiate between static + * nested classes and non-static member classes (e.g., inner classes). + * + * @since 1.8 + * @see #nestedContainer(Class) + */ + @API(status = MAINTAINED, since = "1.8") + public static Condition nestedContainer(Class clazz, Condition condition) { + return allOf(nestedContainer(clazz), condition); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is + * a {@linkplain TestDescriptor#isContainer() container} and its + * {@linkplain TestDescriptor#getUniqueId() unique id} contains the + * simple names of the supplied {@link Class} and all of its + * {@linkplain Class#getEnclosingClass() enclosing classes}. + * + *

Please note that this method does not differentiate between static + * nested classes and non-static member classes (e.g., inner classes). + * + * @see #nestedContainer(Class, Condition) + */ + public static Condition nestedContainer(Class clazz) { + Preconditions.notNull(clazz, "Class must not be null"); + Preconditions.notNull(clazz.getEnclosingClass(), () -> clazz.getName() + " must be a nested class"); + + List classNames = new ArrayList<>(); + for (Class current = clazz; current != null; current = current.getEnclosingClass()) { + classNames.add(0, current.getSimpleName()); + } + + return allOf(container(), uniqueIdSubstrings(classNames)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#DYNAMIC_TEST_REGISTERED} and its + * {@linkplain TestDescriptor#getUniqueId() unique id} contains the + * supplied {@link String}. + */ + public static Condition dynamicTestRegistered(String uniqueIdSubstring) { + return dynamicTestRegistered(uniqueIdSubstring(uniqueIdSubstring)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#DYNAMIC_TEST_REGISTERED} and it matches the supplied + * {@code Condition}. + */ + public static Condition dynamicTestRegistered(Condition condition) { + return allOf(type(DYNAMIC_TEST_REGISTERED), condition); + } + + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getUniqueId() unique id} of an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} + * contains the supplied {@link String}. + */ + public static Condition uniqueIdSubstring(String uniqueIdSubstring) { + Predicate predicate = segment -> { + String text = segment.getType() + ":" + segment.getValue(); + return text.contains(uniqueIdSubstring); + }; + + return new Condition<>( + byTestDescriptor( + where(TestDescriptor::getUniqueId, uniqueId -> uniqueId.getSegments().stream().anyMatch(predicate))), + "descriptor with uniqueId substring '%s'", uniqueIdSubstring); + } + + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getUniqueId() unique id} of an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} + * contains all of the supplied strings. + * + * @since 1.6 + */ + public static Condition uniqueIdSubstrings(String... uniqueIdSubstrings) { + return uniqueIdSubstrings(Arrays.asList(uniqueIdSubstrings)); + } + + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getUniqueId() unique id} of an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} + * contains all of the supplied strings. + * + * @since 1.6 + */ + public static Condition uniqueIdSubstrings(List uniqueIdSubstrings) { + // The following worked with AssertJ 3.13.2 + // return allOf(uniqueIdSubstrings.stream().map(EventConditions::uniqueIdSubstring).collect(toList())); + + // Workaround for a regression in AssertJ 3.14.0 that loses the individual descriptions + // when multiple conditions are supplied as an Iterable instead of as an array. + // The underlying cause is that org.assertj.core.condition.Join.Join(Condition...) + // tracks all descriptions; whereas, + // org.assertj.core.condition.Join.Join(Iterable>) + // does not track all descriptions. + List> conditions = uniqueIdSubstrings.stream()// + .map(EventConditions::uniqueIdSubstring)// + .collect(toList()); + List descriptions = conditions.stream().map(Condition::description).collect(toList()); + return allOf(conditions).describedAs(new JoinDescription("all of :[", "]", descriptions)); + } + + /** + * Create a new {@link Condition} that matches if and only if the + * {@linkplain TestDescriptor#getDisplayName() display name} of an + * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} + * is equal to the supplied {@link String}. + */ + public static Condition displayName(String displayName) { + return new Condition<>(byTestDescriptor(where(TestDescriptor::getDisplayName, isEqual(displayName))), + "descriptor with display name '%s'", displayName); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#SKIPPED} and the + * {@linkplain Event#getPayload() reason} is equal to the supplied + * {@link String}. + * + * @see #reason(String) + */ + public static Condition skippedWithReason(String expectedReason) { + return allOf(type(SKIPPED), reason(expectedReason)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#SKIPPED} and the + * {@linkplain Event#getPayload() reason} matches the supplied + * {@link Predicate}. + * + * @see #reason(Predicate) + */ + public static Condition skippedWithReason(Predicate predicate) { + return allOf(type(SKIPPED), reason(predicate)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#STARTED}. + */ + public static Condition started() { + return type(STARTED); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#FINISHED} and its + * {@linkplain Event#getPayload() result} has a + * {@linkplain TestExecutionResult#getStatus() status} of + * {@link TestExecutionResult.Status#ABORTED ABORTED} as well as a + * {@linkplain TestExecutionResult#getThrowable() cause} that matches all of + * the supplied conditions. + */ + @SafeVarargs + public static Condition abortedWithReason(Condition... conditions) { + return finishedWithCause(ABORTED, conditions); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#FINISHED} and its + * {@linkplain Event#getPayload() result} has a + * {@linkplain TestExecutionResult#getStatus() status} of + * {@link TestExecutionResult.Status#FAILED FAILED} as well as a + * {@linkplain TestExecutionResult#getThrowable() cause} that matches all of + * the supplied {@code Conditions}. + */ + @SafeVarargs + public static Condition finishedWithFailure(Condition... conditions) { + return finishedWithCause(FAILED, conditions); + } + + @SuppressWarnings("unchecked") + private static Condition finishedWithCause(Status expectedStatus, Condition... conditions) { + List> list = Arrays.asList(TestExecutionResultConditions.status(expectedStatus), + TestExecutionResultConditions.throwable(conditions)); + + return finished(allOf(list)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#FINISHED} and its + * {@linkplain Event#getPayload() result} has a + * {@linkplain TestExecutionResult#getStatus() status} of + * {@link TestExecutionResult.Status#FAILED FAILED}. + */ + public static Condition finishedWithFailure() { + return finished(TestExecutionResultConditions.status(FAILED)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#FINISHED} and its + * {@linkplain Event#getPayload() result} has a + * {@linkplain TestExecutionResult#getStatus() status} of + * {@link TestExecutionResult.Status#SUCCESSFUL SUCCESSFUL}. + */ + public static Condition finishedSuccessfully() { + return finished(TestExecutionResultConditions.status(SUCCESSFUL)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is + * {@link EventType#FINISHED} and its + * {@linkplain Event#getPayload() payload} is an instance of + * {@link TestExecutionResult} that matches the supplied {@code Condition}. + */ + public static Condition finished(Condition resultCondition) { + return allOf(type(FINISHED), result(resultCondition)); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getType() type} is equal to the + * supplied {@link EventType}. + */ + public static Condition type(EventType expectedType) { + return new Condition<>(byType(expectedType), "type is %s", expectedType); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of + * {@link TestExecutionResult} that matches the supplied {@code Condition}. + */ + public static Condition result(Condition condition) { + return new Condition<>(byPayload(TestExecutionResult.class, condition::matches), "event with result where %s", + condition); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of + * {@link String} that is equal to the supplied value. + */ + public static Condition reason(String expectedReason) { + return new Condition<>(byPayload(String.class, isEqual(expectedReason)), "event with reason '%s'", + expectedReason); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of + * {@link String} that matches the supplied {@link Predicate}. + */ + public static Condition reason(Predicate predicate) { + return new Condition<>(byPayload(String.class, predicate), "event with custom reason predicate"); + } + + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of + * {@link ReportEntry} that contains the supplied key-value pairs. + */ + @API(status = EXPERIMENTAL, since = "1.7") + public static Condition reportEntry(Map keyValuePairs) { + return new Condition<>(byPayload(ReportEntry.class, it -> it.getKeyValuePairs().equals(keyValuePairs)), + "event for report entry with key-value pairs %s", keyValuePairs); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java new file mode 100644 index 00000000..fda7d1d8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java @@ -0,0 +1,142 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.junit.platform.testkit.engine.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.testkit.engine.Assertions.Executable; + +/** + * {@code EventStatistics} provides a fluent API for asserting statistics + * for {@linkplain Event events}. + * + *

{@code EventStatistics} is used in conjunction with + * {@link Events#assertStatistics(java.util.function.Consumer)} as in the + * following example. + * + *

{@code events.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));} + * + * @since 1.4 + * @see Event + * @see Events + */ +@API(status = MAINTAINED, since = "1.7") +public class EventStatistics { + + private final List executables = new ArrayList<>(); + private final Events events; + + EventStatistics(Events events, String category) { + this.events = events; + } + + void assertAll() { + Assertions.assertAll(this.events.getCategory() + " Event Statistics", this.executables.stream()); + } + + // ------------------------------------------------------------------------- + + /** + * Specify the number of expected skipped events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics skipped(long expected) { + this.executables.add(() -> assertEquals(expected, this.events.skipped().count(), "skipped")); + return this; + } + + /** + * Specify the number of expected started events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics started(long expected) { + this.executables.add(() -> assertEquals(expected, this.events.started().count(), "started")); + return this; + } + + /** + * Specify the number of expected finished events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics finished(long expected) { + this.executables.add(() -> assertEquals(expected, this.events.finished().count(), "finished")); + return this; + } + + /** + * Specify the number of expected aborted events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics aborted(long expected) { + this.executables.add(() -> assertEquals(expected, this.events.aborted().count(), "aborted")); + return this; + } + + /** + * Specify the number of expected succeeded events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics succeeded(long expected) { + this.executables.add(() -> assertEquals(expected, this.events.succeeded().count(), "succeeded")); + return this; + } + + /** + * Specify the number of expected failed events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics failed(long expected) { + this.executables.add(() -> assertEquals(expected, this.events.failed().count(), "failed")); + return this; + } + + /** + * Specify the number of expected reporting entry publication events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics reportingEntryPublished(long expected) { + this.executables.add( + () -> assertEquals(expected, this.events.reportingEntryPublished().count(), "reporting entry published")); + return this; + } + + /** + * Specify the number of expected dynamic registration events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + */ + public EventStatistics dynamicallyRegistered(long expected) { + this.executables.add( + () -> assertEquals(expected, this.events.dynamicallyRegistered().count(), "dynamically registered")); + return this; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java new file mode 100644 index 00000000..1fed5b9b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * Enumeration of the different possible {@link Event} types. + * + * @since 1.4 + * @see Event + */ +@API(status = MAINTAINED, since = "1.7") +public enum EventType { + + /** + * Signals that a {@link TestDescriptor} has been dynamically registered. + * + * @see org.junit.platform.engine.EngineExecutionListener#dynamicTestRegistered(TestDescriptor) + */ + DYNAMIC_TEST_REGISTERED, + + /** + * Signals that the execution of a {@link TestDescriptor} has been skipped. + * + * @see org.junit.platform.engine.EngineExecutionListener#executionSkipped(TestDescriptor, String) + */ + SKIPPED, + + /** + * Signals that the execution of a {@link TestDescriptor} has started. + * + * @see org.junit.platform.engine.EngineExecutionListener#executionStarted(TestDescriptor) + */ + STARTED, + + /** + * Signals that the execution of a {@link TestDescriptor} has finished, + * regardless of the outcome. + * + * @see org.junit.platform.engine.EngineExecutionListener#executionFinished(TestDescriptor, TestExecutionResult) + */ + FINISHED, + + /** + * Signals that a {@link TestDescriptor} published a reporting entry. + * + * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished(TestDescriptor, ReportEntry) + */ + REPORTING_ENTRY_PUBLISHED; + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java new file mode 100644 index 00000000..01481ac3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java @@ -0,0 +1,473 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static java.util.Collections.sort; +import static java.util.function.Predicate.isEqual; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.testkit.engine.Event.byPayload; +import static org.junit.platform.testkit.engine.Event.byType; + +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; +import org.assertj.core.api.ListAssert; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.data.Index; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestExecutionResult.Status; +import org.opentest4j.AssertionFailedError; + +/** + * {@code Events} is a facade that provides a fluent API for working with + * {@linkplain Event events}. + * + * @since 1.4 + */ +@API(status = MAINTAINED, since = "1.7") +public final class Events { + + private final List events; + private final String category; + + Events(Stream events, String category) { + this(Preconditions.notNull(events, "Event stream must not be null").collect(toList()), category); + } + + Events(List events, String category) { + Preconditions.notNull(events, "Event list must not be null"); + Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); + + this.events = Collections.unmodifiableList(events); + this.category = category; + } + + String getCategory() { + return this.category; + } + + // --- Accessors ----------------------------------------------------------- + + /** + * Get the {@linkplain Event events} as a {@link List}. + * + * @return the list of events; never {@code null} + * @see #stream() + */ + public List list() { + return this.events; + } + + /** + * Get the {@linkplain Event events} as a {@link Stream}. + * + * @return the stream of events; never {@code null} + * @see #list() + */ + public Stream stream() { + return this.events.stream(); + } + + /** + * Shortcut for {@code events.stream().map(mapper)}. + * + * @param mapper a {@code Function} to apply to each event; never {@code null} + * @return the mapped stream of events; never {@code null} + * @see #stream() + * @see Stream#map(Function) + */ + public Stream map(Function mapper) { + Preconditions.notNull(mapper, "Mapping function must not be null"); + return stream().map(mapper); + } + + /** + * Shortcut for {@code events.stream().filter(predicate)}. + * + * @param predicate a {@code Predicate} to apply to each event to decide if + * it should be included in the filtered stream; never {@code null} + * @return the filtered stream of events; never {@code null} + * @see #stream() + * @see Stream#filter(Predicate) + */ + public Stream filter(Predicate predicate) { + Preconditions.notNull(predicate, "Filter predicate must not be null"); + return stream().filter(predicate); + } + + /** + * Get the {@link Executions} for the current set of {@linkplain Event events}. + * + * @return an instance of {@code Executions} for the current set of events; + * never {@code null} + */ + public Executions executions() { + return new Executions(this.events, this.category); + } + + // --- Statistics ---------------------------------------------------------- + + /** + * Get the number of {@linkplain Event events} contained in this {@code Events} + * object. + */ + public long count() { + return this.events.size(); + } + + // --- Built-in Filters ---------------------------------------------------- + + /** + * Get the skipped {@link Events} contained in this {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events skipped() { + return new Events(eventsByType(EventType.SKIPPED), this.category + " Skipped"); + } + + /** + * Get the started {@link Events} contained in this {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events started() { + return new Events(eventsByType(EventType.STARTED), this.category + " Started"); + } + + /** + * Get the finished {@link Events} contained in this {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events finished() { + return new Events(eventsByType(EventType.FINISHED), this.category + " Finished"); + } + + /** + * Get the aborted {@link Events} contained in this {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events aborted() { + return new Events(finishedEventsByStatus(Status.ABORTED), this.category + " Aborted"); + } + + /** + * Get the succeeded {@link Events} contained in this {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events succeeded() { + return new Events(finishedEventsByStatus(Status.SUCCESSFUL), this.category + " Successful"); + } + + /** + * Get the failed {@link Events} contained in this {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events failed() { + return new Events(finishedEventsByStatus(Status.FAILED), this.category + " Failed"); + } + + /** + * Get the reporting entry publication {@link Events} contained in this + * {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events reportingEntryPublished() { + return new Events(eventsByType(EventType.REPORTING_ENTRY_PUBLISHED), + this.category + " Reporting Entry Published"); + } + + /** + * Get the dynamic registration {@link Events} contained in this + * {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + */ + public Events dynamicallyRegistered() { + return new Events(eventsByType(EventType.DYNAMIC_TEST_REGISTERED), this.category + " Dynamically Registered"); + } + + // --- Assertions ---------------------------------------------------------- + + /** + * Assert statistics for the {@linkplain Event events} contained in this + * {@code Events} object. + * + *

Example

+ * + *

{@code events.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));} + * + * @param statisticsConsumer a {@link Consumer} of {@link EventStatistics}; + * never {@code null} + * @return this {@code Events} object for method chaining; never {@code null} + */ + public Events assertStatistics(Consumer statisticsConsumer) { + Preconditions.notNull(statisticsConsumer, "Consumer must not be null"); + EventStatistics eventStatistics = new EventStatistics(this, this.category); + statisticsConsumer.accept(eventStatistics); + eventStatistics.assertAll(); + return this; + } + + /** + * Assert that all {@linkplain Event events} contained in this {@code Events} + * object exactly match the provided conditions. + * + *

Conditions can be imported statically from {@link EventConditions} + * and {@link TestExecutionResultConditions}. + * + *

Example

+ * + *
+	 * executionResults.testEvents().assertEventsMatchExactly(
+	 *     event(test("exampleTestMethod"), started()),
+	 *     event(test("exampleTestMethod"), finishedSuccessfully())
+	 * );
+	 * 
+ * + * @param conditions the conditions to match against; never {@code null} + * @see #assertEventsMatchLoosely(Condition...) + * @see #assertEventsMatchLooselyInOrder(Condition...) + * @see EventConditions + * @see TestExecutionResultConditions + */ + @SafeVarargs + public final void assertEventsMatchExactly(Condition... conditions) { + Preconditions.notNull(conditions, "conditions must not be null"); + assertEventsMatchExactly(this.events, conditions); + } + + /** + * Assert that all provided conditions are matched by an {@linkplain Event event} + * contained in this {@code Events} object, regardless of order. + * + *

Note that this method performs a partial match. Thus, some events may + * not match any of the provided conditions. + * + *

Conditions can be imported statically from {@link EventConditions} + * and {@link TestExecutionResultConditions}. + * + *

Example

+ * + *
+	 * executionResults.testEvents().assertEventsMatchLoosely(
+	 *     event(test("exampleTestMethod"), started()),
+	 *     event(test("exampleTestMethod"), finishedSuccessfully())
+	 * );
+	 * 
+ * + * @param conditions the conditions to match against; never {@code null} + * @since 1.7 + * @see #assertEventsMatchExactly(Condition...) + * @see #assertEventsMatchLooselyInOrder(Condition...) + * @see EventConditions + * @see TestExecutionResultConditions + */ + @SafeVarargs + @SuppressWarnings("varargs") + public final void assertEventsMatchLoosely(Condition... conditions) { + Preconditions.notNull(conditions, "conditions must not be null"); + Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements"); + assertEventsMatchLoosely(this.events, conditions); + } + + /** + * Assert that all provided conditions are matched by an {@linkplain Event event} + * contained in this {@code Events} object. + * + *

Note that this method performs a partial match. Thus, some events may + * not match any of the provided conditions; however, the conditions provided + * must be in the correct order. + * + *

Conditions can be imported statically from {@link EventConditions} + * and {@link TestExecutionResultConditions}. + * + *

Example

+ * + *
+	 * executionResults.testEvents().assertEventsMatchLooselyInOrder(
+	 *     event(test("exampleTestMethod"), started()),
+	 *     event(test("exampleTestMethod"), finishedSuccessfully())
+	 * );
+	 * 
+ * + * @param conditions the conditions to match against; never {@code null} + * @since 1.7 + * @see #assertEventsMatchExactly(Condition...) + * @see #assertEventsMatchLoosely(Condition...) + * @see EventConditions + * @see TestExecutionResultConditions + */ + @SafeVarargs + @SuppressWarnings("varargs") + public final void assertEventsMatchLooselyInOrder(Condition... conditions) { + Preconditions.notNull(conditions, "conditions must not be null"); + Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements"); + assertEventsMatchLooselyInOrder(this.events, conditions); + } + + /** + * Shortcut for {@code org.assertj.core.api.Assertions.assertThat(events.list())}. + * + * @return an instance of {@link ListAssert} for events; never {@code null} + * @see org.assertj.core.api.Assertions#assertThat(List) + * @see org.assertj.core.api.ListAssert + */ + public ListAssert assertThatEvents() { + return org.assertj.core.api.Assertions.assertThat(list()); + } + + // --- Diagnostics --------------------------------------------------------- + + /** + * Print all events to {@link System#out}. + * + * @return this {@code Events} object for method chaining; never {@code null} + */ + public Events debug() { + debug(System.out); + return this; + } + + /** + * Print all events to the supplied {@link OutputStream}. + * + * @param out the {@code OutputStream} to print to; never {@code null} + * @return this {@code Events} object for method chaining; never {@code null} + */ + public Events debug(OutputStream out) { + Preconditions.notNull(out, "OutputStream must not be null"); + debug(new PrintWriter(out, true)); + return this; + } + + /** + * Print all events to the supplied {@link Writer}. + * + * @param writer the {@code Writer} to print to; never {@code null} + * @return this {@code Events} object for method chaining; never {@code null} + */ + public Events debug(Writer writer) { + Preconditions.notNull(writer, "Writer must not be null"); + debug(new PrintWriter(writer, true)); + return this; + } + + private Events debug(PrintWriter printWriter) { + printWriter.println(this.category + " Events:"); + this.events.forEach(event -> printWriter.printf("\t%s%n", event)); + return this; + } + + // --- Internals ----------------------------------------------------------- + + private Stream eventsByType(EventType type) { + Preconditions.notNull(type, "EventType must not be null"); + return stream().filter(byType(type)); + } + + private Stream finishedEventsByStatus(Status status) { + Preconditions.notNull(status, "Status must not be null"); + return eventsByType(EventType.FINISHED)// + .filter(byPayload(TestExecutionResult.class, where(TestExecutionResult::getStatus, isEqual(status)))); + } + + @SafeVarargs + private static void assertEventsMatchExactly(List events, Condition... conditions) { + Assertions.assertThat(events).hasSize(conditions.length); + + SoftAssertions softly = new SoftAssertions(); + for (int i = 0; i < conditions.length; i++) { + softly.assertThat(events).has(conditions[i], Index.atIndex(i)); + } + softly.assertAll(); + } + + @SafeVarargs + private static void assertEventsMatchLoosely(List events, Condition... conditions) { + SoftAssertions softly = new SoftAssertions(); + for (Condition condition : conditions) { + checkCondition(events, softly, condition); + } + softly.assertAll(); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static void assertEventsMatchLooselyInOrder(List events, Condition... conditions) { + Assertions.assertThat(conditions).hasSizeLessThanOrEqualTo(events.size()); + SoftAssertions softly = new SoftAssertions(); + + // @formatter:off + List indices = Arrays.stream(conditions) + .map(condition -> findEvent(events, softly, condition)) + .filter(Objects::nonNull) + .map(events::indexOf) + .collect(toList()); + // @formatter:on + + if (isNotInIncreasingOrder(indices)) { + throw new AssertionFailedError("Conditions are not in the correct order."); + } + + softly.assertAll(); + } + + private static boolean isNotInIncreasingOrder(List indices) { + List copy = new ArrayList<>(indices); + sort(copy); + + return !indices.equals(copy); + } + + private static void checkCondition(List events, SoftAssertions softly, Condition condition) { + if (events.stream().noneMatch(condition::matches)) { + softly.fail("Condition did not match any event: " + condition); + } + } + + private static Event findEvent(List events, SoftAssertions softly, Condition condition) { + // @formatter:off + Optional matchedEvent = events.stream() + .filter(condition::matches) + .findFirst(); + // @formatter:on + + if (!matchedEvent.isPresent()) { + softly.fail("Condition did not match any event: " + condition); + } + + return matchedEvent.orElse(null); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java new file mode 100644 index 00000000..be14554e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.time.Duration; +import java.time.Instant; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; + +/** + * {@code Execution} encapsulates metadata for the execution of a single + * {@link TestDescriptor}. + * + * @since 1.4 + */ +@API(status = MAINTAINED, since = "1.7") +public class Execution { + + // --- Factories ----------------------------------------------------------- + + /** + * Create a new instance of an {@code Execution} that finished with the + * provided {@link TestExecutionResult}. + * + * @param testDescriptor the {@code TestDescriptor} that finished; + * never {@code null} + * @param startInstant the {@code Instant} that the {@code Execution} started; + * never {@code null} + * @param endInstant the {@code Instant} that the {@code Execution} completed; + * never {@code null} + * @param executionResult the {@code TestExecutionResult} of the finished + * {@code TestDescriptor}; never {@code null} + * @return the newly created {@code Execution} instance; never {@code null} + */ + public static Execution finished(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, + TestExecutionResult executionResult) { + + return new Execution(testDescriptor, startInstant, endInstant, TerminationInfo.executed(executionResult)); + } + + /** + * Create a new instance of an {@code Execution} that was skipped with the + * provided {@code skipReason}. + * + * @param testDescriptor the {@code TestDescriptor} that finished; + * never {@code null} + * @param startInstant the {@code Instant} that the {@code Execution} started; + * never {@code null} + * @param endInstant the {@code Instant} that the {@code Execution} completed; + * never {@code null} + * @param skipReason the reason the {@code TestDescriptor} was skipped; + * may be {@code null} + * @return the newly created {@code Execution} instance; never {@code null} + */ + public static Execution skipped(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, + String skipReason) { + + return new Execution(testDescriptor, startInstant, endInstant, TerminationInfo.skipped(skipReason)); + } + + // ------------------------------------------------------------------------- + + private final TestDescriptor testDescriptor; + private final Instant startInstant; + private final Instant endInstant; + private final Duration duration; + private final TerminationInfo terminationInfo; + + private Execution(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, + TerminationInfo terminationInfo) { + + Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); + Preconditions.notNull(startInstant, "Start Instant must not be null"); + Preconditions.notNull(endInstant, "End Instant must not be null"); + Preconditions.notNull(terminationInfo, "TerminationInfo must not be null"); + + this.testDescriptor = testDescriptor; + this.startInstant = startInstant; + this.endInstant = endInstant; + this.duration = Duration.between(startInstant, endInstant); + this.terminationInfo = terminationInfo; + } + + /** + * Get the {@link TestDescriptor} for this {@code Execution}. + * + * @return the {@code TestDescriptor} for this {@code Execution} + */ + public TestDescriptor getTestDescriptor() { + return this.testDescriptor; + } + + /** + * Get the start {@link Instant} of this {@code Execution}. + * + * @return the start {@code Instant} of this {@code Execution} + */ + public Instant getStartInstant() { + return this.startInstant; + } + + /** + * Get the end {@link Instant} of this {@code Execution}. + * + * @return the end {@code Instant} of this {@code Execution} + */ + public Instant getEndInstant() { + return this.endInstant; + } + + /** + * Get the {@link Duration} of this {@code Execution}. + * + * @return the {@code Duration} of this {@code Execution} + */ + public Duration getDuration() { + return this.duration; + } + + /** + * Get the {@link TerminationInfo} for this {@code Execution}. + * + * @return the {@code TerminationInfo} for this {@code Execution} + */ + public TerminationInfo getTerminationInfo() { + return this.terminationInfo; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("testDescriptor", this.testDescriptor) + .append("startInstant", this.startInstant) + .append("endInstant", this.endInstant) + .append("duration", this.duration) + .append("terminationInfo", this.terminationInfo) + .toString(); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java new file mode 100644 index 00000000..b11a1e44 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apiguardian.api.API; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.ReportEntry; + +/** + * {@code ExecutionRecorder} is an {@link EngineExecutionListener} that records + * data from every event that occurs during the engine execution lifecycle and + * provides functionality for retrieving execution state via + * {@link EngineExecutionResults}. + * + * @since 1.4 + * @see EngineExecutionResults + * @see Event + * @see Execution + */ +@API(status = MAINTAINED, since = "1.7") +public class ExecutionRecorder implements EngineExecutionListener { + + private final List events = new CopyOnWriteArrayList<>(); + + /** + * Record an {@link Event} for a dynamically registered container + * or test. + */ + @Override + public void dynamicTestRegistered(TestDescriptor testDescriptor) { + this.events.add(Event.dynamicTestRegistered(testDescriptor)); + } + + /** + * Record an {@link Event} for a container or test that was skipped. + */ + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + this.events.add(Event.executionSkipped(testDescriptor, reason)); + } + + /** + * Record an {@link Event} for a container or test that started. + */ + @Override + public void executionStarted(TestDescriptor testDescriptor) { + this.events.add(Event.executionStarted(testDescriptor)); + } + + /** + * Record an {@link Event} for a container or test that completed + * with the provided {@link TestExecutionResult}. + */ + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + this.events.add(Event.executionFinished(testDescriptor, testExecutionResult)); + } + + /** + * Record an {@link Event} for a published {@link ReportEntry}. + */ + @Override + public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + this.events.add(Event.reportingEntryPublished(testDescriptor, entry)); + } + + /** + * Get the state of the engine's execution in the form of {@link EngineExecutionResults}. + * + * @return the {@code EngineExecutionResults} containing all current state information + */ + public EngineExecutionResults getExecutionResults() { + return new EngineExecutionResults(this.events); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java new file mode 100644 index 00000000..6ef7a89e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java @@ -0,0 +1,302 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.assertj.core.api.ListAssert; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestExecutionResult.Status; + +/** + * {@code Executions} is a facade that provides a fluent API for working with + * {@linkplain Execution executions}. + * + * @since 1.4 + */ +@API(status = MAINTAINED, since = "1.7") +public final class Executions { + + private final List executions; + private final String category; + + private Executions(Stream executions, String category) { + Preconditions.notNull(executions, "Execution stream must not be null"); + + this.executions = Collections.unmodifiableList(executions.collect(toList())); + this.category = category; + } + + Executions(List events, String category) { + Preconditions.notNull(events, "Event list must not be null"); + Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); + + this.executions = Collections.unmodifiableList(createExecutions(events)); + this.category = category; + } + + // --- Accessors ----------------------------------------------------------- + + /** + * Get the {@linkplain Execution executions} as a {@link List}. + * + * @return the list of executions; never {@code null} + * @see #stream() + */ + public List list() { + return this.executions; + } + + /** + * Get the {@linkplain Execution executions} as a {@link Stream}. + * + * @return the stream of executions; never {@code null} + * @see #list() + */ + public Stream stream() { + return this.executions.stream(); + } + + /** + * Shortcut for {@code executions.stream().map(mapper)}. + * + * @param mapper a {@code Function} to apply to each execution; + * never {@code null} + * @return the mapped stream of executions; never {@code null} + * @see #stream() + * @see Stream#map(Function) + */ + public Stream map(Function mapper) { + Preconditions.notNull(mapper, "Mapping function must not be null"); + return stream().map(mapper); + } + + /** + * Shortcut for {@code executions.stream().filter(predicate)}. + * + * @param predicate a {@code Predicate} to apply to each execution to decide + * if it should be included in the filtered stream; never {@code null} + * @return the filtered stream of executions; never {@code null} + * @see #stream() + * @see Stream#filter(Predicate) + */ + public Stream filter(Predicate predicate) { + Preconditions.notNull(predicate, "Filter predicate must not be null"); + return stream().filter(predicate); + } + + // --- Statistics ---------------------------------------------------------- + + /** + * Get the number of {@linkplain Execution executions} contained in this + * {@code Executions} object. + */ + public long count() { + return this.executions.size(); + } + + // --- Built-in Filters ---------------------------------------------------- + + /** + * Get the skipped {@link Executions} contained in this {@code Executions} object. + * + * @return the filtered {@code Executions}; never {@code null} + */ + public Executions skipped() { + return new Executions(executionsByTerminationInfo(TerminationInfo::skipped), this.category + " Skipped"); + } + + /** + * Get the started {@link Executions} contained in this {@code Executions} object. + * + * @return the filtered {@code Executions}; never {@code null} + */ + public Executions started() { + return new Executions(executionsByTerminationInfo(TerminationInfo::notSkipped), this.category + " Started"); + } + + /** + * Get the finished {@link Executions} contained in this {@code Executions} object. + * + * @return the filtered {@code Executions}; never {@code null} + */ + public Executions finished() { + return new Executions(finishedExecutions(), this.category + " Finished"); + } + + /** + * Get the aborted {@link Executions} contained in this {@code Executions} object. + * + * @return the filtered {@code Executions}; never {@code null} + */ + public Executions aborted() { + return new Executions(finishedExecutionsByStatus(Status.ABORTED), this.category + " Aborted"); + } + + /** + * Get the succeeded {@link Executions} contained in this {@code Executions} object. + * + * @return the filtered {@code Executions}; never {@code null} + */ + public Executions succeeded() { + return new Executions(finishedExecutionsByStatus(Status.SUCCESSFUL), this.category + " Successful"); + } + + /** + * Get the failed {@link Executions} contained in this {@code Executions} object. + * + * @return the filtered {@code Executions}; never {@code null} + */ + public Executions failed() { + return new Executions(finishedExecutionsByStatus(Status.FAILED), this.category + " Failed"); + } + + // --- Assertions ---------------------------------------------------------- + + /** + * Shortcut for {@code org.assertj.core.api.Assertions.assertThat(executions.list())}. + * + * @return an instance of {@link ListAssert} for executions; never {@code null} + * @see org.assertj.core.api.Assertions#assertThat(List) + * @see org.assertj.core.api.ListAssert + */ + public ListAssert assertThatExecutions() { + return org.assertj.core.api.Assertions.assertThat(list()); + } + + // --- Diagnostics --------------------------------------------------------- + + /** + * Print all executions to {@link System#out}. + * + * @return this {@code Executions} object for method chaining; never {@code null} + */ + public Executions debug() { + debug(System.out); + return this; + } + + /** + * Print all executions to the supplied {@link OutputStream}. + * + * @param out the {@code OutputStream} to print to; never {@code null} + * @return this {@code Executions} object for method chaining; never {@code null} + */ + public Executions debug(OutputStream out) { + Preconditions.notNull(out, "OutputStream must not be null"); + debug(new PrintWriter(out, true)); + return this; + } + + /** + * Print all executions to the supplied {@link Writer}. + * + * @param writer the {@code Writer} to print to; never {@code null} + * @return this {@code Executions} object for method chaining; never {@code null} + */ + public Executions debug(Writer writer) { + Preconditions.notNull(writer, "Writer must not be null"); + debug(new PrintWriter(writer, true)); + return this; + } + + private Executions debug(PrintWriter printWriter) { + printWriter.println(this.category + " Executions:"); + this.executions.forEach(execution -> printWriter.printf("\t%s%n", execution)); + return this; + } + + // --- Internals ----------------------------------------------------------- + + private Stream finishedExecutions() { + return executionsByTerminationInfo(TerminationInfo::executed); + } + + private Stream finishedExecutionsByStatus(Status status) { + Preconditions.notNull(status, "Status must not be null"); + return finishedExecutions()// + .filter(execution -> execution.getTerminationInfo().getExecutionResult().getStatus() == status); + } + + private Stream executionsByTerminationInfo(Predicate predicate) { + return filter(execution -> predicate.test(execution.getTerminationInfo())); + } + + /** + * Create executions from the supplied list of events. + */ + private static List createExecutions(List events) { + List executions = new ArrayList<>(); + Map executionStarts = new HashMap<>(); + + for (Event event : events) { + switch (event.getType()) { + case STARTED: { + executionStarts.put(event.getTestDescriptor(), event.getTimestamp()); + break; + } + case SKIPPED: { + // Based on the Javadoc for EngineExecutionListener.executionSkipped(...), + // a skipped descriptor must never be reported as started or finished, + // but just in case a TestEngine does not adhere to that contract, we + // make an attempt to remove any tracked "started" event. + executionStarts.remove(event.getTestDescriptor()); + + // Use the same timestamp for both the start and end Instant values. + Instant timestamp = event.getTimestamp(); + + Execution skippedEvent = Execution.skipped(event.getTestDescriptor(), timestamp, timestamp, + event.getRequiredPayload(String.class)); + executions.add(skippedEvent); + break; + } + case FINISHED: { + Instant startInstant = executionStarts.remove(event.getTestDescriptor()); + Instant endInstant = event.getTimestamp(); + + // If "started" events have already been filtered out, it is no + // longer possible to create a legitimate Execution for the current + // descriptor. So we effectively ignore it in that case. + if (startInstant != null) { + Execution finishedEvent = Execution.finished(event.getTestDescriptor(), startInstant, + endInstant, event.getRequiredPayload(TestExecutionResult.class)); + executions.add(finishedEvent); + } + break; + } + default: { + // Ignore other events + break; + } + } + } + + return executions; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java new file mode 100644 index 00000000..e3875c8c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java @@ -0,0 +1,147 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; +import org.junit.platform.engine.TestExecutionResult; + +/** + * {@code TerminationInfo} is a union type that allows propagation of terminated + * container/test state, supporting either the reason if the container/test + * was skipped or the {@link TestExecutionResult} if the container/test was executed. + * + * @since 1.4 + * @see Execution#getTerminationInfo() + */ +@API(status = MAINTAINED, since = "1.7") +public class TerminationInfo { + + // --- Factories ----------------------------------------------------------- + + /** + * Create a skipped {@code TerminationInfo} instance for the + * supplied reason. + * + * @param reason the reason the execution was skipped; may be {@code null} + * @return the created {@code TerminationInfo}; never {@code null} + * @see #executed(TestExecutionResult) + */ + public static TerminationInfo skipped(String reason) { + return new TerminationInfo(true, reason, null); + } + + /** + * Create an executed {@code TerminationInfo} instance for the + * supplied {@link TestExecutionResult}. + * + * @param testExecutionResult the result of the execution; never {@code null} + * @return the created {@code TerminationInfo}; never {@code null} + * @see #skipped(String) + */ + public static TerminationInfo executed(TestExecutionResult testExecutionResult) { + Preconditions.notNull(testExecutionResult, "TestExecutionResult must not be null"); + return new TerminationInfo(false, null, testExecutionResult); + } + + // ------------------------------------------------------------------------- + + private final boolean skipped; + private final String skipReason; + private final TestExecutionResult testExecutionResult; + + private TerminationInfo(boolean skipped, String skipReason, TestExecutionResult testExecutionResult) { + boolean executed = (testExecutionResult != null); + Preconditions.condition((skipped ^ executed), + "TerminationInfo must represent either a skipped execution or a TestExecutionResult but not both"); + + this.skipped = skipped; + this.skipReason = skipReason; + this.testExecutionResult = testExecutionResult; + } + + /** + * Determine if this {@code TerminationInfo} represents a skipped execution. + * + * @return {@code true} if this this {@code TerminationInfo} represents a + * skipped execution + */ + public boolean skipped() { + return this.skipped; + } + + /** + * Determine if this {@code TerminationInfo} does not represent a skipped + * execution. + * + * @return {@code true} if this this {@code TerminationInfo} does not + * represent a skipped execution + */ + public boolean notSkipped() { + return !skipped(); + } + + /** + * Determine if this {@code TerminationInfo} represents a completed execution. + * + * @return {@code true} if this this {@code TerminationInfo} represents a + * completed execution + */ + public boolean executed() { + return (this.testExecutionResult != null); + } + + /** + * Get the reason the execution was skipped. + * + * @return the reason the execution was skipped + * @throws UnsupportedOperationException if this {@code TerminationInfo} + * does not represent a skipped execution + */ + public String getSkipReason() throws UnsupportedOperationException { + if (skipped()) { + return this.skipReason; + } + // else + throw new UnsupportedOperationException("No skip reason contained in this TerminationInfo"); + } + + /** + * Get the {@link TestExecutionResult} for the completed execution. + * + * @return the result of the completed execution + * @throws UnsupportedOperationException if this {@code TerminationInfo} + * does not represent a completed execution + */ + public TestExecutionResult getExecutionResult() throws UnsupportedOperationException { + if (executed()) { + return this.testExecutionResult; + } + // else + throw new UnsupportedOperationException("No TestExecutionResult contained in this TerminationInfo"); + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + if (skipped()) { + builder.append("skipped", true).append("reason", this.skipReason); + } + else { + builder.append("executed", true).append("result", this.testExecutionResult); + } + return builder.toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java new file mode 100644 index 00000000..21ff73e7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java @@ -0,0 +1,145 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static java.util.function.Predicate.isEqual; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.MAINTAINED; +import static org.junit.platform.commons.util.FunctionUtils.where; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestExecutionResult.Status; + +/** + * Collection of AssertJ {@linkplain Condition conditions} for + * {@link TestExecutionResult}. + * + * @since 1.4 + * @see EventConditions + */ +@API(status = MAINTAINED, since = "1.7") +public final class TestExecutionResultConditions { + + private TestExecutionResultConditions() { + /* no-op */ + } + + /** + * Create a new {@link Condition} that matches if and only if a + * {@link TestExecutionResult}'s {@linkplain TestExecutionResult#getStatus() + * status} is equal to the supplied {@link Status Status}. + */ + public static Condition status(Status expectedStatus) { + return new Condition<>(where(TestExecutionResult::getStatus, isEqual(expectedStatus)), "status is %s", + expectedStatus); + } + + /** + * Create a new {@link Condition} that matches if and only if a + * {@link TestExecutionResult}'s + * {@linkplain TestExecutionResult#getThrowable() throwable} matches all + * supplied conditions. + */ + @SafeVarargs + @SuppressWarnings("varargs") + public static Condition throwable(Condition... conditions) { + List> list = Arrays.stream(conditions)// + .map(TestExecutionResultConditions::throwable)// + .collect(toList()); + + return Assertions.allOf(list); + } + + /** + * Create a new {@link Condition} that matches if and only if a + * {@link Throwable}'s {@linkplain Throwable#getCause() cause} matches all + * supplied conditions. + */ + @SafeVarargs + @SuppressWarnings("varargs") + public static Condition cause(Condition... conditions) { + List> list = Arrays.stream(conditions)// + .map(TestExecutionResultConditions::cause)// + .collect(toList()); + + return Assertions.allOf(list); + } + + /** + * Create a new {@link Condition} that matches if and only if a + * {@link Throwable}'s {@linkplain Throwable#getSuppressed() suppressed + * throwable} at the supplied index matches all supplied conditions. + */ + @SafeVarargs + @SuppressWarnings("varargs") + public static Condition suppressed(int index, Condition... conditions) { + List> list = Arrays.stream(conditions)// + .map(condition -> suppressed(index, condition))// + .collect(toList()); + + return Assertions.allOf(list); + } + + /** + * Create a new {@link Condition} that matches if and only if a + * {@link Throwable} is an {@linkplain Class#isInstance(Object) instance of} + * the supplied {@link Class}. + */ + public static Condition instanceOf(Class expectedType) { + return new Condition<>(expectedType::isInstance, "instance of %s", expectedType.getName()); + } + + /** + * Create a new {@link Condition} that matches if and only if a + * {@link Throwable}'s {@linkplain Throwable#getMessage() message} is equal + * to the supplied {@link String}. + */ + public static Condition message(String expectedMessage) { + return new Condition<>(where(Throwable::getMessage, isEqual(expectedMessage)), "message is '%s'", + expectedMessage); + } + + /** + * Create a new {@link Condition} that matches if and only if a + * {@link Throwable}'s {@linkplain Throwable#getMessage() message} matches + * the supplied {@link Predicate}. + */ + public static Condition message(Predicate expectedMessagePredicate) { + return new Condition<>(where(Throwable::getMessage, expectedMessagePredicate), "message matches predicate"); + } + + private static Condition throwable(Condition condition) { + return new Condition<>( + where(TestExecutionResult::getThrowable, + throwable -> throwable.isPresent() && condition.matches(throwable.get())), + "throwable matches %s", condition); + } + + private static Condition cause(Condition condition) { + return new Condition<>(throwable -> condition.matches(throwable.getCause()), "throwable cause matches %s", + condition); + } + + private static Condition suppressed(int index, Condition condition) { + return new Condition<>( + throwable -> throwable.getSuppressed().length > index + && condition.matches(throwable.getSuppressed()[index]), + "suppressed throwable at index %d matches %s", index, condition); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java new file mode 100644 index 00000000..c50fec3e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java @@ -0,0 +1,6 @@ +/** + * Test Kit for testing the execution of a {@link org.junit.platform.engine.TestEngine} + * running on the JUnit Platform. + */ + +package org.junit.platform.testkit.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java new file mode 100644 index 00000000..7a10b625 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java @@ -0,0 +1,5 @@ +/** + * Test Kit for the JUnit Platform. + */ + +package org.junit.platform.testkit; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java new file mode 100644 index 00000000..c0ae3181 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Defines the Test Kit API for the JUnit Platform. + * + * @since 1.4 + * @uses org.junit.platform.engine.TestEngine + */ +module org.junit.platform.testkit { + requires static transitive org.apiguardian.api; + requires transitive org.assertj.core; + requires org.junit.platform.commons; + requires transitive org.junit.platform.engine; + requires org.junit.platform.launcher; + requires transitive org.opentest4j; + + // exports org.junit.platform.testkit; empty package + exports org.junit.platform.testkit.engine; + + uses org.junit.platform.engine.TestEngine; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts new file mode 100644 index 00000000..b92fffa5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts @@ -0,0 +1,85 @@ +plugins { + `java-library-conventions` + `junit4-compatibility` + `testing-conventions` + `java-test-fixtures` + groovy +} + +description = "JUnit Vintage Engine" + +dependencies { + api(platform(projects.junitBom)) + api(projects.junitPlatformEngine) + api(libs.junit4) + + compileOnlyApi(libs.apiguardian) + + testFixturesApi(platform(libs.groovy2.bom)) + testFixturesApi(libs.spock1) + testFixturesImplementation(projects.junitPlatformRunner) + + testImplementation(projects.junitPlatformLauncher) + testImplementation(projects.junitPlatformSuiteEngine) + testImplementation(projects.junitPlatformTestkit) + + osgiVerification(projects.junitPlatformLauncher) +} + +tasks { + compileTestFixturesGroovy { + javaLauncher.set(project.javaToolchains.launcherFor { + // Groovy 2.x (used for Spock tests) does not support current JDKs + languageVersion.set(JavaLanguageVersion.of(8)) + }) + } + jar { + bundle { + val junit4Min = libs.versions.junit4Min.get() + bnd(""" + # Import JUnit4 packages with a version + Import-Package: \ + ${extra["importAPIGuardian"]},\ + junit.runner;version="[${junit4Min},5)",\ + org.junit;version="[${junit4Min},5)",\ + org.junit.experimental.categories;version="[${junit4Min},5)",\ + org.junit.internal.builders;version="[${junit4Min},5)",\ + org.junit.platform.commons.logging;status=INTERNAL,\ + org.junit.runner.*;version="[${junit4Min},5)",\ + org.junit.runners.model;version="[${junit4Min},5)",\ + * + + Provide-Capability:\ + org.junit.platform.engine;\ + org.junit.platform.engine='junit-vintage';\ + version:Version="${'$'}{version_cleanup;${project.version}}" + Require-Capability:\ + org.junit.platform.launcher;\ + filter:='(&(org.junit.platform.launcher=junit-platform-launcher)(version>=${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}}})))';\ + effective:=active + """) + } + } + val testWithoutJUnit4 by registering(Test::class) { + (options as JUnitPlatformOptions).apply { + includeTags("missing-junit4") + } + setIncludes(listOf("**/JUnit4VersionCheckTests.class")) + classpath = classpath.filter { + !it.name.startsWith("junit-4") + } + } + withType().matching { it.name != testWithoutJUnit4.name }.configureEach { + (options as JUnitPlatformOptions).apply { + excludeTags("missing-junit4") + } + } + withType().configureEach { + // Workaround for Groovy 2.5 + jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") + jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED") + } + check { + dependsOn(testWithoutJUnit4) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java new file mode 100644 index 00000000..8c5f1f27 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; + +import java.math.BigDecimal; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import junit.runner.Version; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * @since 5.4 + */ +class JUnit4VersionCheck { + + private static final Pattern versionPattern = Pattern.compile("^(\\d+\\.\\d+).*"); + private static final BigDecimal minVersion = new BigDecimal("4.12"); + + static void checkSupported() { + try { + checkSupported(Version::id); + } + catch (NoClassDefFoundError e) { + throw new JUnitException( + "Invalid class/module path: junit-vintage-engine is present but junit:junit is not. " + + "Please either remove junit-vintage-engine or add junit:junit, or " + + "alternatively use an excludeEngines(\"" + ENGINE_ID + "\") filter."); + } + } + + static void checkSupported(Supplier versionSupplier) { + String versionString = readVersion(versionSupplier); + BigDecimal version = parseVersion(versionString); + if (version.compareTo(minVersion) < 0) { + throw new JUnitException("Unsupported version of junit:junit: " + versionString + + ". Please upgrade to version " + minVersion + " or later."); + } + } + + static BigDecimal parseVersion(String versionString) { + try { + Matcher matcher = versionPattern.matcher(versionString); + if (matcher.matches()) { + return new BigDecimal(matcher.group(1)); + } + } + catch (Exception e) { + throw new JUnitException("Failed to parse version of junit:junit: " + versionString, e); + } + throw new JUnitException("Failed to parse version of junit:junit: " + versionString); + } + + private static String readVersion(Supplier versionSupplier) { + try { + return versionSupplier.get(); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + throw new JUnitException("Failed to read version of junit:junit", t); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java new file mode 100644 index 00000000..fb52f79b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; + +import java.util.Iterator; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.VintageEngineDescriptor; +import org.junit.vintage.engine.discovery.VintageDiscoverer; +import org.junit.vintage.engine.execution.RunnerExecutor; + +/** + * The JUnit Vintage {@link TestEngine}. + * + * @since 4.12 + */ +@API(status = INTERNAL, since = "4.12") +public final class VintageTestEngine implements TestEngine { + + @Override + public String getId() { + return ENGINE_ID; + } + + /** + * Returns {@code org.junit.vintage} as the group ID. + */ + @Override + public Optional getGroupId() { + return Optional.of("org.junit.vintage"); + } + + /** + * Returns {@code junit-vintage-engine} as the artifact ID. + */ + @Override + public Optional getArtifactId() { + return Optional.of("junit-vintage-engine"); + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + JUnit4VersionCheck.checkSupported(); + return new VintageDiscoverer().discover(discoveryRequest, uniqueId); + } + + @Override + public void execute(ExecutionRequest request) { + EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); + VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); + engineExecutionListener.executionStarted(engineDescriptor); + executeAllChildren(engineDescriptor, engineExecutionListener); + engineExecutionListener.executionFinished(engineDescriptor, successful()); + } + + private void executeAllChildren(VintageEngineDescriptor engineDescriptor, + EngineExecutionListener engineExecutionListener) { + RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener); + for (Iterator iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) { + runnerExecutor.execute((RunnerTestDescriptor) iterator.next()); + iterator.remove(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java new file mode 100644 index 00000000..3cc89c7e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import org.apiguardian.api.API; +import org.junit.runner.Description; + +@API(status = API.Status.INTERNAL, since = "5.8") +public class DescriptionUtils { + + private DescriptionUtils() { + } + + public static String getMethodName(Description description) { + String displayName = description.getDisplayName(); + int i = displayName.indexOf('('); + if (i >= 0) { + int j = displayName.lastIndexOf('('); + if (i == j) { + char lastChar = displayName.charAt(displayName.length() - 1); + if (lastChar == ')') { + return displayName.substring(0, i); + } + } + } + return description.getMethodName(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java new file mode 100644 index 00000000..5bf9bf71 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static java.util.stream.Collectors.joining; + +import java.util.Collection; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +/** + * @since 5.4 + */ +class OrFilter extends Filter { + + private final Collection filters; + + OrFilter(Collection filters) { + this.filters = Preconditions.notEmpty(filters, "filters must not be empty"); + } + + @Override + public boolean shouldRun(Description description) { + return filters.stream().anyMatch(filter -> filter.shouldRun(description)); + } + + @Override + public String describe() { + return filters.stream().map(Filter::describe).collect(joining(" OR ")); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java new file mode 100644 index 00000000..d81b64f1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.runner.Runner; + +@API(status = INTERNAL, since = "5.4") +public interface RunnerDecorator { + + Runner getDecoratedRunner(); + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java new file mode 100644 index 00000000..bb9037c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import org.junit.runner.Request; +import org.junit.runner.Runner; + +/** + * @since 4.12 + */ +class RunnerRequest extends Request { + + private final Runner runner; + + RunnerRequest(Runner runner) { + this.runner = runner; + } + + @Override + public Runner getRunner() { + return runner; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java new file mode 100644 index 00000000..0ad3c5f9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java @@ -0,0 +1,185 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static java.util.Collections.singletonList; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.runner.Description; +import org.junit.runner.Request; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; + +/** + * @since 4.12 + */ +@API(status = INTERNAL, since = "4.12") +public class RunnerTestDescriptor extends VintageTestDescriptor { + + private static final Logger logger = LoggerFactory.getLogger(RunnerTestDescriptor.class); + + private final Set rejectedExclusions = new HashSet<>(); + private Runner runner; + private boolean wasFiltered; + private List filters = new ArrayList<>(); + + public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner) { + super(uniqueId, runner.getDescription(), testClass.getSimpleName(), ClassSource.from(testClass)); + this.runner = runner; + } + + @Override + public String getLegacyReportingName() { + return getSource().map(source -> ((ClassSource) source).getClassName()) // + .orElseThrow(() -> new JUnitException("source should have been present")); + } + + public Request toRequest() { + return new RunnerRequest(this.runner); + } + + @Override + protected boolean tryToExcludeFromRunner(Description description) { + boolean excluded = tryToFilterRunner(description); + if (excluded) { + wasFiltered = true; + } + else { + rejectedExclusions.add(description); + } + return excluded; + } + + private boolean tryToFilterRunner(Description description) { + if (runner instanceof Filterable) { + ExcludeDescriptionFilter filter = new ExcludeDescriptionFilter(description); + try { + ((Filterable) runner).filter(filter); + } + catch (NoTestsRemainException ignore) { + // it's safe to ignore this exception because childless TestDescriptors will get pruned + } + return filter.wasSuccessful(); + } + return false; + } + + @Override + protected boolean canBeRemovedFromHierarchy() { + return true; + } + + @Override + public void prune() { + if (wasFiltered) { + // filtering the runner may render intermediate Descriptions obsolete + // (e.g. test classes without any remaining children in a suite) + pruneDescriptorsForObsoleteDescriptions(singletonList(runner.getDescription())); + } + if (rejectedExclusions.isEmpty()) { + super.prune(); + } + else if (rejectedExclusions.containsAll(getDescription().getChildren())) { + // since the Runner was asked to remove all of its direct children, + // it's safe to remove it entirely + removeFromHierarchy(); + } + else { + logIncompleteFiltering(); + } + } + + private void logIncompleteFiltering() { + if (runner instanceof Filterable) { + logger.warn(() -> "Runner " + getRunnerToReport().getClass().getName() // + + " (used on class " + getLegacyReportingName() + ") was not able to satisfy all filter requests."); + } + else { + warnAboutUnfilterableRunner(); + } + } + + private void warnAboutUnfilterableRunner() { + logger.warn(() -> "Runner " + getRunnerToReport().getClass().getName() // + + " (used on class " + getLegacyReportingName() + ") does not support filtering" // + + " and will therefore be run completely."); + } + + public Optional> getFilters() { + return Optional.ofNullable(filters); + } + + public void clearFilters() { + this.filters = null; + } + + public void applyFilters(Consumer childrenCreator) { + if (filters != null && !filters.isEmpty()) { + if (runner instanceof Filterable) { + this.runner = toRequest().filterWith(new OrFilter(filters)).getRunner(); + this.description = runner.getDescription(); + this.children.clear(); + childrenCreator.accept(this); + } + else { + warnAboutUnfilterableRunner(); + } + } + clearFilters(); + } + + private Runner getRunnerToReport() { + return (runner instanceof RunnerDecorator) ? ((RunnerDecorator) runner).getDecoratedRunner() : runner; + } + + private static class ExcludeDescriptionFilter extends Filter { + + private final Description description; + private boolean successful; + + ExcludeDescriptionFilter(Description description) { + this.description = description; + } + + @Override + public boolean shouldRun(Description description) { + if (this.description.equals(description)) { + successful = true; + return false; + } + return true; + } + + @Override + public String describe() { + return "exclude " + description; + } + + boolean wasSuccessful() { + return successful; + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java new file mode 100644 index 00000000..e5b327a2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static java.util.Collections.synchronizedMap; +import static java.util.function.Predicate.isEqual; +import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apiguardian.api.API; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.util.LruCache; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.runner.Description; + +/** + * @since 5.6 + */ +@API(status = INTERNAL, since = "5.6") +public class TestSourceProvider { + + @SuppressWarnings("serial") + private static final TestSource NULL_SOURCE = new TestSource() { + }; + + private final Map testSourceCache = new ConcurrentHashMap<>(); + private final Map, List> methodsCache = synchronizedMap(new LruCache<>(31)); + + public TestSource findTestSource(Description description) { + TestSource testSource = testSourceCache.computeIfAbsent(description, this::computeTestSource); + return testSource == NULL_SOURCE ? null : testSource; + } + + private TestSource computeTestSource(Description description) { + Class testClass = description.getTestClass(); + if (testClass != null) { + String methodName = DescriptionUtils.getMethodName(description); + if (methodName != null) { + Method method = findMethod(testClass, sanitizeMethodName(methodName)); + if (method != null) { + return MethodSource.from(testClass, method); + } + } + return ClassSource.from(testClass); + } + return NULL_SOURCE; + } + + private String sanitizeMethodName(String methodName) { + if (methodName.contains("[") && methodName.endsWith("]")) { + // special case for parameterized tests + return methodName.substring(0, methodName.indexOf("[")); + } + return methodName; + } + + private Method findMethod(Class testClass, String methodName) { + List methods = methodsCache.computeIfAbsent(testClass, clazz -> findMethods(clazz, m -> true)).stream() // + .filter(where(Method::getName, isEqual(methodName))) // + .collect(toList()); + if (methods.isEmpty()) { + return null; + } + if (methods.size() == 1) { + return methods.get(0); + } + methods = methods.stream().filter(ModifierSupport::isPublic).collect(toList()); + if (methods.size() == 1) { + return methods.get(0); + } + return null; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java new file mode 100644 index 00000000..9e0450fa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; + +/** + * @since 5.6 + */ +@API(status = INTERNAL, since = "5.6") +public class VintageEngineDescriptor extends EngineDescriptor { + + public VintageEngineDescriptor(UniqueId uniqueId) { + super(uniqueId, "JUnit Vintage"); + } + + public Set getModifiableChildren() { + return children; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java new file mode 100644 index 00000000..d8a64ba7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static java.util.Arrays.stream; +import static java.util.function.Predicate.isEqual; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.StringUtils.isNotBlank; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.experimental.categories.Category; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.runner.Description; + +/** + * @since 4.12 + */ +@API(status = INTERNAL, since = "4.12") +public class VintageTestDescriptor extends AbstractTestDescriptor { + + public static final String ENGINE_ID = "junit-vintage"; + public static final String SEGMENT_TYPE_RUNNER = "runner"; + public static final String SEGMENT_TYPE_TEST = "test"; + public static final String SEGMENT_TYPE_DYNAMIC = "dynamic"; + + protected Description description; + + public VintageTestDescriptor(UniqueId uniqueId, Description description, TestSource source) { + this(uniqueId, description, generateDisplayName(description), source); + } + + VintageTestDescriptor(UniqueId uniqueId, Description description, String displayName, TestSource source) { + super(uniqueId, displayName, source); + this.description = description; + } + + private static String generateDisplayName(Description description) { + String methodName = DescriptionUtils.getMethodName(description); + return isNotBlank(methodName) ? methodName : description.getDisplayName(); + } + + public Description getDescription() { + return description; + } + + @Override + public String getLegacyReportingName() { + String methodName = DescriptionUtils.getMethodName(description); + if (methodName == null) { + String className = description.getClassName(); + if (isNotBlank(className)) { + return className; + } + } + return super.getLegacyReportingName(); + } + + @Override + public Type getType() { + return description.isTest() ? Type.TEST : Type.CONTAINER; + } + + @Override + public Set getTags() { + Set tags = new LinkedHashSet<>(); + addTagsFromParent(tags); + addCategoriesAsTags(tags); + return tags; + } + + @Override + public void removeFromHierarchy() { + if (canBeRemovedFromHierarchy()) { + super.removeFromHierarchy(); + } + } + + protected boolean canBeRemovedFromHierarchy() { + return tryToExcludeFromRunner(this.description); + } + + protected boolean tryToExcludeFromRunner(Description description) { + // @formatter:off + return getParent().map(VintageTestDescriptor.class::cast) + .map(parent -> parent.tryToExcludeFromRunner(description)) + .orElse(false); + // @formatter:on + } + + void pruneDescriptorsForObsoleteDescriptions(List newSiblingDescriptions) { + Optional newDescription = newSiblingDescriptions.stream().filter(isEqual(description)).findAny(); + if (newDescription.isPresent()) { + List newChildren = newDescription.get().getChildren(); + new ArrayList<>(children).stream().map(VintageTestDescriptor.class::cast).forEach( + childDescriptor -> childDescriptor.pruneDescriptorsForObsoleteDescriptions(newChildren)); + } + else { + super.removeFromHierarchy(); + } + } + + private void addTagsFromParent(Set tags) { + getParent().map(TestDescriptor::getTags).ifPresent(tags::addAll); + } + + private void addCategoriesAsTags(Set tags) { + Category annotation = description.getAnnotation(Category.class); + if (annotation != null) { + // @formatter:off + stream(annotation.value()) + .map(ReflectionUtils::getAllAssignmentCompatibleClasses) + .flatMap(Collection::stream) + .distinct() + .map(Class::getName) + .map(TestTag::create) + .forEachOrdered(tags::add); + // @formatter:on + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java new file mode 100644 index 00000000..bef751cb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java @@ -0,0 +1,5 @@ +/** + * Test descriptors used within the JUnit Vintage test engine. + */ + +package org.junit.vintage.engine.descriptor; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java new file mode 100644 index 00000000..a2d1e7d4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static java.util.Collections.emptySet; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; + +import java.util.Optional; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.UniqueId.Segment; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.support.discovery.SelectorResolver; +import org.junit.runner.Runner; +import org.junit.runners.model.RunnerBuilder; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; + +/** + * @since 4.12 + */ +class ClassSelectorResolver implements SelectorResolver { + + private static final RunnerBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder(); + + private final ClassFilter classFilter; + + ClassSelectorResolver(ClassFilter classFilter) { + this.classFilter = classFilter; + } + + @Override + public Resolution resolve(ClassSelector selector, Context context) { + return resolveTestClass(selector.getJavaClass(), context); + } + + @Override + public Resolution resolve(UniqueIdSelector selector, Context context) { + Segment lastSegment = selector.getUniqueId().getLastSegment(); + if (SEGMENT_TYPE_RUNNER.equals(lastSegment.getType())) { + String testClassName = lastSegment.getValue(); + Class testClass = ReflectionUtils.tryToLoadClass(testClassName)// + .getOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); + return resolveTestClass(testClass, context); + } + return unresolved(); + } + + private Resolution resolveTestClass(Class testClass, Context context) { + if (!classFilter.test(testClass)) { + return unresolved(); + } + Runner runner = RUNNER_BUILDER.safeRunnerForClass(testClass); + if (runner == null) { + return unresolved(); + } + return context.addToParent(parent -> Optional.of(createRunnerTestDescriptor(parent, testClass, runner))).map( + runnerTestDescriptor -> Match.exact(runnerTestDescriptor, () -> { + runnerTestDescriptor.clearFilters(); + return emptySet(); + })).map(Resolution::match).orElse(unresolved()); + } + + private RunnerTestDescriptor createRunnerTestDescriptor(TestDescriptor parent, Class testClass, Runner runner) { + UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); + return new RunnerTestDescriptor(uniqueId, testClass, runner); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java new file mode 100644 index 00000000..0da94f68 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java @@ -0,0 +1,151 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import java.lang.reflect.Method; +import java.util.function.Predicate; + +import org.junit.Ignore; +import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; +import org.junit.internal.builders.AnnotatedBuilder; +import org.junit.internal.builders.IgnoredBuilder; +import org.junit.internal.builders.IgnoredClassRunner; +import org.junit.internal.builders.JUnit4Builder; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filterable; +import org.junit.runners.model.RunnerBuilder; + +/** + * Customization of {@link AllDefaultPossibilitiesBuilder} from JUnit 4 to + * ignore certain classes that would otherwise be reported as errors or cause + * infinite recursion. + * + * @since 4.12 + * @see DefensiveAnnotatedBuilder + * @see DefensiveJUnit4Builder + * @see IgnoredClassRunner + */ +class DefensiveAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder { + + private static final Logger logger = LoggerFactory.getLogger(DefensiveAllDefaultPossibilitiesBuilder.class); + + private final AnnotatedBuilder annotatedBuilder; + private final JUnit4Builder junit4Builder; + private final IgnoredBuilder ignoredBuilder; + + @SuppressWarnings("deprecation") + DefensiveAllDefaultPossibilitiesBuilder() { + super(true); + annotatedBuilder = new DefensiveAnnotatedBuilder(this); + junit4Builder = new DefensiveJUnit4Builder(); + ignoredBuilder = new NullIgnoredBuilder(); + } + + @Override + public Runner runnerForClass(Class testClass) throws Throwable { + Runner runner = super.runnerForClass(testClass); + if (testClass.getAnnotation(Ignore.class) != null) { + if (runner == null) { + return new IgnoredClassRunner(testClass); + } + return decorateIgnoredTestClass(runner); + } + return runner; + } + + /** + * Instead of checking for the {@link Ignore} annotation and returning an + * {@link IgnoredClassRunner} from {@link IgnoredBuilder}, we've let the + * super class determine the regular runner that would have been used if + * {@link Ignore} hadn't been present. Now, we decorate the runner to + * override its runtime behavior (i.e. skip execution) but return its + * regular {@link org.junit.runner.Description}. + */ + private Runner decorateIgnoredTestClass(Runner runner) { + if (runner instanceof Filterable) { + return new FilterableIgnoringRunnerDecorator(runner); + } + return new IgnoringRunnerDecorator(runner); + } + + @Override + protected AnnotatedBuilder annotatedBuilder() { + return annotatedBuilder; + } + + @Override + protected JUnit4Builder junit4Builder() { + return junit4Builder; + } + + @Override + protected IgnoredBuilder ignoredBuilder() { + return ignoredBuilder; + } + + /** + * Customization of {@link AnnotatedBuilder} that ignores classes annotated + * with {@code @RunWith(JUnitPlatform.class)} to avoid infinite recursion. + */ + private static class DefensiveAnnotatedBuilder extends AnnotatedBuilder { + + DefensiveAnnotatedBuilder(RunnerBuilder suiteBuilder) { + super(suiteBuilder); + } + + @Override + public Runner buildRunner(Class runnerClass, Class testClass) throws Exception { + // Referenced by name because it might not be available at runtime. + if ("org.junit.platform.runner.JUnitPlatform".equals(runnerClass.getName())) { + logger.warn(() -> "Ignoring test class using JUnitPlatform runner: " + testClass.getName()); + return null; + } + return super.buildRunner(runnerClass, testClass); + } + } + + /** + * Customization of {@link JUnit4Builder} that ignores classes that do not + * contain any test methods in order not to report errors for them. + */ + private static class DefensiveJUnit4Builder extends JUnit4Builder { + + private static final Predicate isPotentialJUnit4TestMethod = new IsPotentialJUnit4TestMethod(); + + @Override + public Runner runnerForClass(Class testClass) throws Throwable { + if (containsTestMethods(testClass)) { + return super.runnerForClass(testClass); + } + return null; + } + + private boolean containsTestMethods(Class testClass) { + return ReflectionUtils.isMethodPresent(testClass, isPotentialJUnit4TestMethod); + } + } + + /** + * Customization of {@link IgnoredBuilder} that always returns {@code null}. + * + * @since 5.1 + */ + private static class NullIgnoredBuilder extends IgnoredBuilder { + @Override + public Runner runnerForClass(Class testClass) { + // don't ignore entire test classes just yet + return null; + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java new file mode 100644 index 00000000..636ef255 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; + +/** + * {@link Filterable} {@link IgnoringRunnerDecorator}. + * + * @since 5.1 + */ +class FilterableIgnoringRunnerDecorator extends IgnoringRunnerDecorator implements Filterable { + + FilterableIgnoringRunnerDecorator(Runner runner) { + super(runner); + Preconditions.condition(runner instanceof Filterable, + () -> "Runner must be an instance of Filterable: " + runner.getClass().getName()); + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + ((Filterable) runner).filter(filter); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java new file mode 100644 index 00000000..3b5597e5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.vintage.engine.descriptor.RunnerDecorator; + +/** + * Decorator for Runners that will be ignored completely. + * + *

Contrary to {@link org.junit.internal.builders.IgnoredClassRunner}, this + * runner returns a complete description including all children. + * + * @since 5.1 + */ +class IgnoringRunnerDecorator extends Runner implements RunnerDecorator { + + protected final Runner runner; + + IgnoringRunnerDecorator(Runner runner) { + this.runner = Preconditions.notNull(runner, "Runner must not be null"); + } + + @Override + public Description getDescription() { + return runner.getDescription(); + } + + @Override + public void run(RunNotifier notifier) { + notifier.fireTestIgnored(getDescription()); + } + + @Override + public Runner getDecoratedRunner() { + return runner; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java new file mode 100644 index 00000000..91b9b183 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; +import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; +import static org.junit.platform.commons.util.ReflectionUtils.isPublic; + +import java.util.function.Predicate; + +import org.apiguardian.api.API; + +/** + * @since 4.12 + */ +@API(status = INTERNAL, since = "5.8", consumers = "org.junit.vintage.**") +public class IsPotentialJUnit4TestClass implements Predicate> { + + @Override + public boolean test(Class candidate) { + // Do not collapse into a single return statement. + if (!isPublic(candidate)) { + return false; + } + if (isAbstract(candidate)) { + return false; + } + if (isInnerClass(candidate)) { + return false; + } + + return true; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java new file mode 100644 index 00000000..d382a606 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import java.lang.reflect.Method; +import java.util.function.Predicate; + +import org.junit.Test; + +/** + * @since 4.12 + */ +class IsPotentialJUnit4TestMethod implements Predicate { + + @Override + public boolean test(Method method) { + // Don't use AnnotationUtils.isAnnotated since JUnit 4 does not support + // meta-annotations + return method.isAnnotationPresent(Test.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java new file mode 100644 index 00000000..724e9a02 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; + +import java.util.Optional; +import java.util.function.Function; + +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.support.discovery.SelectorResolver; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; +import org.junit.vintage.engine.descriptor.DescriptionUtils; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; + +/** + * @since 4.12 + */ +class MethodSelectorResolver implements SelectorResolver { + + @Override + public Resolution resolve(MethodSelector selector, Context context) { + Class testClass = selector.getJavaClass(); + return resolveParentAndAddFilter(context, selectClass(testClass), parent -> toMethodFilter(selector)); + } + + @Override + public Resolution resolve(UniqueIdSelector selector, Context context) { + for (UniqueId current = selector.getUniqueId(); !current.getSegments().isEmpty(); current = current.removeLastSegment()) { + if (SEGMENT_TYPE_RUNNER.equals(current.getLastSegment().getType())) { + return resolveParentAndAddFilter(context, selectUniqueId(current), + parent -> toUniqueIdFilter(parent, selector.getUniqueId())); + } + } + return unresolved(); + } + + private Resolution resolveParentAndAddFilter(Context context, DiscoverySelector selector, + Function filterCreator) { + return context.resolve(selector).flatMap(parent -> addFilter(parent, filterCreator)).map( + this::toResolution).orElse(unresolved()); + } + + private Optional addFilter(TestDescriptor parent, + Function filterCreator) { + if (parent instanceof RunnerTestDescriptor) { + RunnerTestDescriptor runnerTestDescriptor = (RunnerTestDescriptor) parent; + runnerTestDescriptor.getFilters().ifPresent( + filters -> filters.add(filterCreator.apply(runnerTestDescriptor))); + return Optional.of(runnerTestDescriptor); + } + return Optional.empty(); + } + + private Resolution toResolution(RunnerTestDescriptor parent) { + return Resolution.match(Match.partial(parent)); + } + + private Filter toMethodFilter(MethodSelector methodSelector) { + Class testClass = methodSelector.getJavaClass(); + String methodName = methodSelector.getMethodName(); + return matchMethodDescription(Description.createTestDescription(testClass, methodName)); + } + + private Filter toUniqueIdFilter(RunnerTestDescriptor runnerTestDescriptor, UniqueId uniqueId) { + return new UniqueIdFilter(runnerTestDescriptor, uniqueId); + } + + /** + * The method {@link Filter#matchMethodDescription(Description)} returns a + * filter that does not account for the case when the description is for a + * {@link org.junit.runners.Parameterized} runner. + */ + private static Filter matchMethodDescription(final Description desiredDescription) { + String desiredMethodName = DescriptionUtils.getMethodName(desiredDescription); + return new Filter() { + + @Override + public boolean shouldRun(Description description) { + if (description.isTest()) { + return desiredDescription.equals(description) || isParameterizedMethod(description); + } + + // explicitly check if any children want to run + for (Description each : description.getChildren()) { + if (shouldRun(each)) { + return true; + } + } + return false; + } + + private boolean isParameterizedMethod(Description description) { + String methodName = DescriptionUtils.getMethodName(description); + return methodName.startsWith(desiredMethodName + "["); + } + + @Override + public String describe() { + return String.format("Method %s", desiredDescription.getDisplayName()); + } + }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java new file mode 100644 index 00000000..b9948537 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toCollection; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.IntFunction; + +import org.junit.platform.engine.UniqueId; +import org.junit.runner.Description; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.TestSourceProvider; +import org.junit.vintage.engine.descriptor.VintageTestDescriptor; +import org.junit.vintage.engine.support.UniqueIdReader; +import org.junit.vintage.engine.support.UniqueIdStringifier; + +/** + * @since 5.5 + */ +class RunnerTestDescriptorPostProcessor { + + private final UniqueIdReader uniqueIdReader = new UniqueIdReader(); + private final UniqueIdStringifier uniqueIdStringifier = new UniqueIdStringifier(); + private final TestSourceProvider testSourceProvider = new TestSourceProvider(); + + void applyFiltersAndCreateDescendants(RunnerTestDescriptor runnerTestDescriptor) { + addChildrenRecursively(runnerTestDescriptor); + runnerTestDescriptor.applyFilters(this::addChildrenRecursively); + } + + private void addChildrenRecursively(VintageTestDescriptor parent) { + if (parent.getDescription().isTest()) { + return; + } + List children = parent.getDescription().getChildren(); + // Use LinkedHashMap to preserve order, ArrayList for fast access by index + Map> childrenByUniqueId = children.stream().collect( + groupingBy(uniqueIdReader.andThen(uniqueIdStringifier), LinkedHashMap::new, toCollection(ArrayList::new))); + for (Entry> entry : childrenByUniqueId.entrySet()) { + String uniqueId = entry.getKey(); + List childrenWithSameUniqueId = entry.getValue(); + IntFunction uniqueIdGenerator = determineUniqueIdGenerator(uniqueId, childrenWithSameUniqueId); + for (int index = 0; index < childrenWithSameUniqueId.size(); index++) { + String reallyUniqueId = uniqueIdGenerator.apply(index); + Description description = childrenWithSameUniqueId.get(index); + UniqueId id = parent.getUniqueId().append(VintageTestDescriptor.SEGMENT_TYPE_TEST, reallyUniqueId); + VintageTestDescriptor child = new VintageTestDescriptor(id, description, + testSourceProvider.findTestSource(description)); + parent.addChild(child); + addChildrenRecursively(child); + } + } + } + + private IntFunction determineUniqueIdGenerator(String uniqueId, + List childrenWithSameUniqueId) { + if (childrenWithSameUniqueId.size() == 1) { + return index -> uniqueId; + } + return index -> uniqueId + "[" + index + "]"; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java new file mode 100644 index 00000000..24685c55 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static java.util.stream.Collectors.toSet; + +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.Optional; +import java.util.Set; + +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.VintageTestDescriptor; + +/** + * @since 4.12 + */ +class UniqueIdFilter extends Filter { + + private final RunnerTestDescriptor runnerTestDescriptor; + private final UniqueId uniqueId; + + private Deque path; + private Set descendants; + + UniqueIdFilter(RunnerTestDescriptor runnerTestDescriptor, UniqueId uniqueId) { + this.runnerTestDescriptor = runnerTestDescriptor; + this.uniqueId = uniqueId; + } + + private void ensureInitialized() { + if (descendants == null) { + Optional identifiedTestDescriptor = runnerTestDescriptor.findByUniqueId(uniqueId); + descendants = determineDescendants(identifiedTestDescriptor); + path = determinePath(runnerTestDescriptor, identifiedTestDescriptor); + } + } + + private Deque determinePath(RunnerTestDescriptor runnerTestDescriptor, + Optional identifiedTestDescriptor) { + Deque path = new ArrayDeque<>(); + Optional current = identifiedTestDescriptor; + while (current.isPresent() && !current.get().equals(runnerTestDescriptor)) { + path.addFirst(((VintageTestDescriptor) current.get()).getDescription()); + current = current.get().getParent(); + } + return path; + } + + private Set determineDescendants(Optional identifiedTestDescriptor) { + // @formatter:off + return identifiedTestDescriptor.map( + testDescriptor -> testDescriptor + .getDescendants() + .stream() + .map(VintageTestDescriptor.class::cast) + .map(VintageTestDescriptor::getDescription) + .collect(toSet())) + .orElseGet(Collections::emptySet); + // @formatter:on + } + + @Override + public boolean shouldRun(Description description) { + ensureInitialized(); + return path.contains(description) || descendants.contains(description); + } + + @Override + public String describe() { + return "Unique ID " + uniqueId; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java new file mode 100644 index 00000000..427526bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.VintageEngineDescriptor; + +/** + * @since 4.12 + */ +@API(status = INTERNAL, since = "4.12") +public class VintageDiscoverer { + + private static final IsPotentialJUnit4TestClass isPotentialJUnit4TestClass = new IsPotentialJUnit4TestClass(); + + // @formatter:off + private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() + .addClassContainerSelectorResolver(isPotentialJUnit4TestClass) + .addSelectorResolver(context -> new ClassSelectorResolver(ClassFilter.of(context.getClassNameFilter(), isPotentialJUnit4TestClass))) + .addSelectorResolver(new MethodSelectorResolver()) + .build(); + // @formatter:on + + public VintageEngineDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + VintageEngineDescriptor engineDescriptor = new VintageEngineDescriptor(uniqueId); + resolver.resolve(discoveryRequest, engineDescriptor); + RunnerTestDescriptorPostProcessor postProcessor = new RunnerTestDescriptorPostProcessor(); + for (TestDescriptor testDescriptor : engineDescriptor.getChildren()) { + RunnerTestDescriptor runnerTestDescriptor = (RunnerTestDescriptor) testDescriptor; + postProcessor.applyFiltersAndCreateDescendants(runnerTestDescriptor); + } + return engineDescriptor; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java new file mode 100644 index 00000000..a22ae393 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java @@ -0,0 +1,5 @@ +/** + * Internal classes for test discovery within the JUnit Vintage test engine. + */ + +package org.junit.vintage.engine.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java new file mode 100644 index 00000000..949cc436 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java @@ -0,0 +1,18 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.execution; + +/** + * @since 5.4.1 + */ +enum EventType { + REPORTED, SYNTHETIC +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java new file mode 100644 index 00000000..9cefb8fa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java @@ -0,0 +1,251 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.execution; + +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_DYNAMIC; + +import java.util.Optional; +import java.util.function.Function; + +import org.junit.Ignore; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.TestSourceProvider; +import org.junit.vintage.engine.descriptor.VintageTestDescriptor; +import org.junit.vintage.engine.support.UniqueIdReader; +import org.junit.vintage.engine.support.UniqueIdStringifier; + +/** + * @since 4.12 + */ +class RunListenerAdapter extends RunListener { + + private final TestRun testRun; + private final EngineExecutionListener listener; + private final TestSourceProvider testSourceProvider; + private final Function uniqueIdExtractor; + + RunListenerAdapter(TestRun testRun, EngineExecutionListener listener, TestSourceProvider testSourceProvider) { + this.testRun = testRun; + this.listener = listener; + this.testSourceProvider = testSourceProvider; + this.uniqueIdExtractor = new UniqueIdReader().andThen(new UniqueIdStringifier()); + } + + @Override + public void testRunStarted(Description description) { + if (description.isSuite() && description.getAnnotation(Ignore.class) == null) { + fireExecutionStarted(testRun.getRunnerTestDescriptor(), EventType.REPORTED); + } + } + + @Override + public void testSuiteStarted(Description description) { + RunnerTestDescriptor runnerTestDescriptor = testRun.getRunnerTestDescriptor(); + // runnerTestDescriptor is reported in testRunStarted + if (!runnerTestDescriptor.getDescription().equals(description)) { + testStarted(lookupOrRegisterNextTestDescriptor(description), EventType.REPORTED); + } + } + + @Override + public void testIgnored(Description description) { + testIgnored(lookupOrRegisterNextTestDescriptor(description), determineReasonForIgnoredTest(description)); + } + + @Override + public void testStarted(Description description) { + testStarted(lookupOrRegisterNextTestDescriptor(description), EventType.REPORTED); + } + + @Override + public void testAssumptionFailure(Failure failure) { + handleFailure(failure, TestExecutionResult::aborted); + } + + @Override + public void testFailure(Failure failure) { + handleFailure(failure, TestExecutionResult::failed); + } + + @Override + public void testFinished(Description description) { + testFinished(lookupOrRegisterCurrentTestDescriptor(description)); + } + + @Override + public void testSuiteFinished(Description description) { + RunnerTestDescriptor runnerTestDescriptor = testRun.getRunnerTestDescriptor(); + // runnerTestDescriptor is reported in testRunFinished + if (!runnerTestDescriptor.getDescription().equals(description)) { + reportContainerFinished(lookupOrRegisterCurrentTestDescriptor(description)); + } + } + + @Override + public void testRunFinished(Result result) { + reportContainerFinished(testRun.getRunnerTestDescriptor()); + } + + private void reportContainerFinished(TestDescriptor containerTestDescriptor) { + if (testRun.isNotSkipped(containerTestDescriptor)) { + if (testRun.isNotStarted(containerTestDescriptor)) { + fireExecutionStarted(containerTestDescriptor, EventType.SYNTHETIC); + } + testRun.getInProgressTestDescriptorsWithSyntheticStartEvents().stream() // + .filter(this::canFinish) // + .forEach(this::fireExecutionFinished); + if (testRun.isNotFinished(containerTestDescriptor)) { + fireExecutionFinished(containerTestDescriptor); + } + } + } + + private TestDescriptor lookupOrRegisterNextTestDescriptor(Description description) { + return lookupOrRegisterTestDescriptor(description, testRun::lookupNextTestDescriptor); + } + + private TestDescriptor lookupOrRegisterCurrentTestDescriptor(Description description) { + return lookupOrRegisterTestDescriptor(description, testRun::lookupCurrentTestDescriptor); + } + + private TestDescriptor lookupOrRegisterTestDescriptor(Description description, + Function> lookup) { + return lookup.apply(description).orElseGet(() -> registerDynamicTestDescriptor(description, lookup)); + } + + private VintageTestDescriptor registerDynamicTestDescriptor(Description description, + Function> lookup) { + // workaround for dynamic children as used by Spock's Runner + TestDescriptor parent = findParent(description, lookup); + UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_DYNAMIC, uniqueIdExtractor.apply(description)); + VintageTestDescriptor dynamicDescriptor = new VintageTestDescriptor(uniqueId, description, + testSourceProvider.findTestSource(description)); + parent.addChild(dynamicDescriptor); + testRun.registerDynamicTest(dynamicDescriptor); + dynamicTestRegistered(dynamicDescriptor); + return dynamicDescriptor; + } + + private TestDescriptor findParent(Description description, + Function> lookup) { + // @formatter:off + return Optional.ofNullable(description.getTestClass()) + .map(Description::createSuiteDescription) + .flatMap(lookup) + .orElseGet(testRun::getRunnerTestDescriptor); + // @formatter:on + } + + private void handleFailure(Failure failure, Function resultCreator) { + handleFailure(failure, resultCreator, lookupOrRegisterCurrentTestDescriptor(failure.getDescription())); + } + + private void handleFailure(Failure failure, Function resultCreator, + TestDescriptor testDescriptor) { + TestExecutionResult result = resultCreator.apply(failure.getException()); + testRun.storeResult(testDescriptor, result); + if (testRun.isNotStarted(testDescriptor)) { + testStarted(testDescriptor, EventType.SYNTHETIC); + } + if (testRun.isNotFinished(testDescriptor) && testDescriptor.isContainer() + && testRun.hasSyntheticStartEvent(testDescriptor) + && testRun.isDescendantOfRunnerTestDescriptor(testDescriptor)) { + testFinished(testDescriptor); + } + } + + private void testIgnored(TestDescriptor testDescriptor, String reason) { + fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents(testDescriptor); + fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); + fireExecutionSkipped(testDescriptor, reason); + } + + private String determineReasonForIgnoredTest(Description description) { + Ignore ignoreAnnotation = description.getAnnotation(Ignore.class); + return Optional.ofNullable(ignoreAnnotation).map(Ignore::value).orElse(""); + } + + private void dynamicTestRegistered(TestDescriptor testDescriptor) { + fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); + listener.dynamicTestRegistered(testDescriptor); + } + + private void testStarted(TestDescriptor testDescriptor, EventType eventType) { + fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents(testDescriptor); + fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); + fireExecutionStarted(testDescriptor, eventType); + } + + private void fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents( + TestDescriptor testDescriptor) { + testRun.getInProgressTestDescriptorsWithSyntheticStartEvents().stream() // + .filter(it -> !isAncestor(it, testDescriptor) && canFinish(it)) // + .forEach(this::fireExecutionFinished); + } + + private boolean isAncestor(TestDescriptor candidate, TestDescriptor testDescriptor) { + Optional parent = testDescriptor.getParent(); + if (!parent.isPresent()) { + return false; + } + if (parent.get().equals(candidate)) { + return true; + } + return isAncestor(candidate, parent.get()); + } + + private void testFinished(TestDescriptor descriptor) { + fireExecutionFinished(descriptor); + } + + private void fireExecutionStartedIncludingUnstartedAncestors(Optional parent) { + if (parent.isPresent() && canStart(parent.get())) { + fireExecutionStartedIncludingUnstartedAncestors(parent.get().getParent()); + fireExecutionStarted(parent.get(), EventType.SYNTHETIC); + } + } + + private boolean canStart(TestDescriptor testDescriptor) { + return testRun.isNotStarted(testDescriptor) // + && (testDescriptor.equals(testRun.getRunnerTestDescriptor()) + || testRun.isDescendantOfRunnerTestDescriptor(testDescriptor)); + } + + private boolean canFinish(TestDescriptor testDescriptor) { + return testRun.isNotFinished(testDescriptor) // + && testRun.isDescendantOfRunnerTestDescriptor(testDescriptor) + && testRun.areAllFinishedOrSkipped(testDescriptor.getChildren()); + } + + private void fireExecutionSkipped(TestDescriptor testDescriptor, String reason) { + testRun.markSkipped(testDescriptor); + listener.executionSkipped(testDescriptor, reason); + } + + private void fireExecutionStarted(TestDescriptor testDescriptor, EventType eventType) { + testRun.markStarted(testDescriptor, eventType); + listener.executionStarted(testDescriptor); + } + + private void fireExecutionFinished(TestDescriptor testDescriptor) { + testRun.markFinished(testDescriptor); + listener.executionFinished(testDescriptor, testRun.getStoredResultOrSuccessful(testDescriptor)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java new file mode 100644 index 00000000..23fad4de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.engine.TestExecutionResult.failed; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.runner.JUnitCore; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.TestSourceProvider; + +/** + * @since 4.12 + */ +@API(status = INTERNAL, since = "4.12") +public class RunnerExecutor { + + private final EngineExecutionListener engineExecutionListener; + private final TestSourceProvider testSourceProvider = new TestSourceProvider(); + + public RunnerExecutor(EngineExecutionListener engineExecutionListener) { + this.engineExecutionListener = engineExecutionListener; + } + + public void execute(RunnerTestDescriptor runnerTestDescriptor) { + TestRun testRun = new TestRun(runnerTestDescriptor); + JUnitCore core = new JUnitCore(); + core.addListener(new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider)); + try { + core.run(runnerTestDescriptor.toRequest()); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + reportUnexpectedFailure(testRun, runnerTestDescriptor, failed(t)); + } + } + + private void reportUnexpectedFailure(TestRun testRun, RunnerTestDescriptor runnerTestDescriptor, + TestExecutionResult result) { + if (testRun.isNotStarted(runnerTestDescriptor)) { + engineExecutionListener.executionStarted(runnerTestDescriptor); + } + engineExecutionListener.executionFinished(runnerTestDescriptor, result); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java new file mode 100644 index 00000000..1e46e0e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java @@ -0,0 +1,276 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.execution; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Stream.concat; +import static org.junit.platform.engine.TestExecutionResult.failed; +import static org.junit.platform.engine.TestExecutionResult.successful; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.runner.Description; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.VintageTestDescriptor; +import org.opentest4j.MultipleFailuresError; + +/** + * @since 4.12 + */ +class TestRun { + + private final RunnerTestDescriptor runnerTestDescriptor; + private final Set runnerDescendants; + private final Map descriptionToDescriptors; + private final Map> executionResults = new LinkedHashMap<>(); + private final Set skippedDescriptors = new LinkedHashSet<>(); + private final Set startedDescriptors = new HashSet<>(); + private final Map inProgressDescriptors = new LinkedHashMap<>(); + private final Set finishedDescriptors = new LinkedHashSet<>(); + private final ThreadLocal> inProgressDescriptorsByStartingThread = ThreadLocal.withInitial( + ArrayDeque::new); + + TestRun(RunnerTestDescriptor runnerTestDescriptor) { + this.runnerTestDescriptor = runnerTestDescriptor; + runnerDescendants = new LinkedHashSet<>(runnerTestDescriptor.getDescendants()); + // @formatter:off + descriptionToDescriptors = concat(Stream.of(runnerTestDescriptor), runnerDescendants.stream()) + .map(VintageTestDescriptor.class::cast) + .collect(toMap(VintageTestDescriptor::getDescription, VintageDescriptors::new, VintageDescriptors::merge, HashMap::new)); + // @formatter:on + } + + void registerDynamicTest(VintageTestDescriptor testDescriptor) { + descriptionToDescriptors.computeIfAbsent(testDescriptor.getDescription(), __ -> new VintageDescriptors()).add( + testDescriptor); + runnerDescendants.add(testDescriptor); + } + + RunnerTestDescriptor getRunnerTestDescriptor() { + return runnerTestDescriptor; + } + + Collection getInProgressTestDescriptorsWithSyntheticStartEvents() { + List result = inProgressDescriptors.entrySet().stream() // + .filter(entry -> entry.getValue().equals(EventType.SYNTHETIC)) // + .map(Entry::getKey) // + .collect(toCollection(ArrayList::new)); + Collections.reverse(result); + return result; + } + + boolean isDescendantOfRunnerTestDescriptor(TestDescriptor testDescriptor) { + return runnerDescendants.contains(testDescriptor); + } + + boolean hasSyntheticStartEvent(TestDescriptor testDescriptor) { + return inProgressDescriptors.get(testDescriptor) == EventType.SYNTHETIC; + } + + Optional lookupNextTestDescriptor(Description description) { + return lookupUnambiguouslyOrApplyFallback(description, VintageDescriptors::getNextUnstarted); + } + + Optional lookupCurrentTestDescriptor(Description description) { + return lookupUnambiguouslyOrApplyFallback(description, __ -> { + VintageTestDescriptor lastStarted = inProgressDescriptorsByStartingThread.get().peekLast(); + if (lastStarted != null && description.equals(lastStarted.getDescription())) { + return Optional.of(lastStarted); + } + return Optional.empty(); + }); + } + + private Optional lookupUnambiguouslyOrApplyFallback(Description description, + Function> fallback) { + VintageDescriptors vintageDescriptors = descriptionToDescriptors.getOrDefault(description, + VintageDescriptors.NONE); + Optional result = vintageDescriptors.getUnambiguously(description); + if (!result.isPresent()) { + result = fallback.apply(vintageDescriptors); + } + return result; + } + + void markSkipped(TestDescriptor testDescriptor) { + skippedDescriptors.add(testDescriptor); + if (testDescriptor instanceof VintageTestDescriptor) { + VintageTestDescriptor vintageDescriptor = (VintageTestDescriptor) testDescriptor; + descriptionToDescriptors.get(vintageDescriptor.getDescription()).incrementSkippedOrStarted(); + } + } + + boolean isNotSkipped(TestDescriptor testDescriptor) { + return !isSkipped(testDescriptor); + } + + boolean isSkipped(TestDescriptor testDescriptor) { + return skippedDescriptors.contains(testDescriptor); + } + + void markStarted(TestDescriptor testDescriptor, EventType eventType) { + inProgressDescriptors.put(testDescriptor, eventType); + startedDescriptors.add(testDescriptor); + if (testDescriptor instanceof VintageTestDescriptor) { + VintageTestDescriptor vintageDescriptor = (VintageTestDescriptor) testDescriptor; + inProgressDescriptorsByStartingThread.get().addLast(vintageDescriptor); + descriptionToDescriptors.get(vintageDescriptor.getDescription()).incrementSkippedOrStarted(); + } + } + + boolean isNotStarted(TestDescriptor testDescriptor) { + return !startedDescriptors.contains(testDescriptor); + } + + void markFinished(TestDescriptor testDescriptor) { + inProgressDescriptors.remove(testDescriptor); + finishedDescriptors.add(testDescriptor); + if (testDescriptor instanceof VintageTestDescriptor) { + VintageTestDescriptor descriptor = (VintageTestDescriptor) testDescriptor; + inProgressDescriptorsByStartingThread.get().removeLastOccurrence(descriptor); + } + } + + boolean isNotFinished(TestDescriptor testDescriptor) { + return !isFinished(testDescriptor); + } + + boolean isFinished(TestDescriptor testDescriptor) { + return finishedDescriptors.contains(testDescriptor); + } + + boolean areAllFinishedOrSkipped(Set testDescriptors) { + return testDescriptors.stream().allMatch(this::isFinishedOrSkipped); + } + + boolean isFinishedOrSkipped(TestDescriptor testDescriptor) { + return isFinished(testDescriptor) || isSkipped(testDescriptor); + } + + void storeResult(TestDescriptor testDescriptor, TestExecutionResult result) { + List testExecutionResults = executionResults.computeIfAbsent(testDescriptor, + key -> new ArrayList<>()); + testExecutionResults.add(result); + } + + TestExecutionResult getStoredResultOrSuccessful(TestDescriptor testDescriptor) { + List testExecutionResults = executionResults.get(testDescriptor); + + if (testExecutionResults == null) { + return successful(); + } + if (testExecutionResults.size() == 1) { + return testExecutionResults.get(0); + } + // @formatter:off + List failures = testExecutionResults + .stream() + .map(TestExecutionResult::getThrowable) + .map(Optional::get) + .collect(toList()); + // @formatter:on + MultipleFailuresError multipleFailuresError = new MultipleFailuresError("", failures); + failures.forEach(multipleFailuresError::addSuppressed); + return failed(multipleFailuresError); + } + + private static class VintageDescriptors { + + private static final VintageDescriptors NONE = new VintageDescriptors(emptyList()); + + private final List descriptors; + private int skippedOrStartedCount; + + static VintageDescriptors merge(VintageDescriptors a, VintageDescriptors b) { + List mergedDescriptors = new ArrayList<>( + a.descriptors.size() + b.descriptors.size()); + mergedDescriptors.addAll(a.descriptors); + mergedDescriptors.addAll(b.descriptors); + return new VintageDescriptors(mergedDescriptors); + } + + VintageDescriptors(VintageTestDescriptor vintageTestDescriptor) { + this(); + add(vintageTestDescriptor); + } + + VintageDescriptors() { + this(new ArrayList<>(1)); + } + + VintageDescriptors(List descriptors) { + this.descriptors = descriptors; + } + + void add(VintageTestDescriptor descriptor) { + descriptors.add(descriptor); + } + + /** + * Returns the {@link TestDescriptor} that represents the specified + * {@link Description}. + * + *

There are edge cases where multiple {@link Description Descriptions} + * with the same {@code uniqueId} exist, e.g. when using overloaded methods + * to define {@linkplain org.junit.experimental.theories.Theory theories}. + * In this case, we try to find the correct {@link TestDescriptor} by + * checking for object identity on the {@link Description} it represents. + * + * @param description the {@code Description} to look up + */ + Optional getUnambiguously(Description description) { + if (descriptors.isEmpty()) { + return Optional.empty(); + } + if (descriptors.size() == 1) { + return Optional.of(descriptors.get(0)); + } + // @formatter:off + return descriptors.stream() + .filter(testDescriptor -> description == testDescriptor.getDescription()) + .findFirst(); + // @formatter:on + } + + public void incrementSkippedOrStarted() { + skippedOrStartedCount++; + } + + public Optional getNextUnstarted() { + if (skippedOrStartedCount < descriptors.size()) { + return Optional.of(descriptors.get(skippedOrStartedCount)); + } + return Optional.empty(); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java new file mode 100644 index 00000000..43e48e1d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java @@ -0,0 +1,5 @@ +/** + * Internal classes for test execution within the JUnit Vintage test engine. + */ + +package org.junit.vintage.engine.execution; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java new file mode 100644 index 00000000..af7d60d3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java @@ -0,0 +1,5 @@ +/** + * Core package for the JUnit Vintage test engine. + */ + +package org.junit.vintage.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java new file mode 100644 index 00000000..35a90736 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.support; + +import static java.lang.String.format; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; + +import java.io.Serializable; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.runner.Description; + +/** + * @since 4.12 + */ +@API(status = INTERNAL, since = "4.12") +public class UniqueIdReader implements Function { + + private static final Logger logger = LoggerFactory.getLogger(UniqueIdReader.class); + + private final String fieldName; + + public UniqueIdReader() { + this("fUniqueId"); + } + + // For tests only + UniqueIdReader(String fieldName) { + this.fieldName = fieldName; + } + + @Override + public Serializable apply(Description description) { + // @formatter:off + return tryToReadFieldValue(Description.class, fieldName, description) + .andThenTry(Serializable.class::cast) + .ifFailure(cause -> logger.warn(cause, () -> + format("Could not read unique ID for Description; using display name instead: %s", description))) + .toOptional() + .orElseGet(description::getDisplayName); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java new file mode 100644 index 00000000..6c2846e1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.support; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.NumberFormat; +import java.util.Base64; +import java.util.Locale; +import java.util.function.Function; + +import org.apiguardian.api.API; + +/** + * @since 4.12 + */ +@API(status = INTERNAL, since = "4.12") +public class UniqueIdStringifier implements Function { + + static final Charset CHARSET = StandardCharsets.UTF_8; + + @Override + public String apply(Serializable uniqueId) { + if (uniqueId instanceof CharSequence) { + return uniqueId.toString(); + } + if (uniqueId instanceof Number) { + return NumberFormat.getInstance(Locale.US).format(uniqueId); + } + return encodeBase64(serialize(uniqueId)); + } + + private byte[] serialize(Serializable uniqueId) { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + try (ObjectOutputStream out = new ObjectOutputStream(byteStream)) { + out.writeObject(uniqueId); + } + catch (IOException e) { + return uniqueId.toString().getBytes(CHARSET); + } + return byteStream.toByteArray(); + } + + private String encodeBase64(byte[] bytes) { + return new String(Base64.getEncoder().encode(bytes), CHARSET); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java new file mode 100644 index 00000000..bb5a291a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java @@ -0,0 +1,6 @@ +/** + * Internal support classes for test discovery and execution within the JUnit + * Vintage test engine. + */ + +package org.junit.vintage.engine.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine new file mode 100644 index 00000000..97ee0b95 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine @@ -0,0 +1 @@ +org.junit.vintage.engine.VintageTestEngine \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java new file mode 100644 index 00000000..859744fc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +/** + * Provides a {@linkplain org.junit.platform.engine.TestEngine} for running + * JUnit 3 and 4 based tests on the platform. + * + * @since 4.12 + * @provides org.junit.platform.engine.TestEngine The {@code VintageTestEngine} + * runs JUnit 3 and 4 based tests on the platform. + */ +module org.junit.vintage.engine { + requires junit; // 4 + requires static org.apiguardian.api; + requires org.junit.platform.engine; + + provides org.junit.platform.engine.TestEngine + with org.junit.vintage.engine.VintageTestEngine; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java new file mode 100644 index 00000000..bb533dbc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.vintage.engine.samples.junit4.JUnit4ParameterizedTestCase; + +/** + * @since 4.12 + */ +class JUnit4ParameterizedTests { + + private final Map callCounts = new HashMap<>(); + + @Test + void selectingWholeParameterizedClassRunsTestsWithAllValues() { + executeTests(selectClass(JUnit4ParameterizedTestCase.class)); + + Map expectedCallCounts = new HashMap<>(); + expectedCallCounts.put(SUCCESSFUL, 3); + expectedCallCounts.put(FAILED, 9); + + assertEquals(expectedCallCounts, callCounts); + } + + @Test + void selectingOneTestFromParameterizedClassRunsWithAllValues() { + executeTests(selectMethod(JUnit4ParameterizedTestCase.class, "test1")); + + assertEquals(Map.of(FAILED, 3), callCounts); + } + + private void executeTests(DiscoverySelector selector) { + var launcher = LauncherFactory.create(); + launcher.registerTestExecutionListeners(new StatusTrackingListener()); + + // @formatter:off + launcher.execute( + request() + .selectors(selector) + .filters(includeEngines("junit-vintage")) + .build() + ); + // @formatter:on + } + + private class StatusTrackingListener implements TestExecutionListener { + + @Override + public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { + if (identifier.isTest()) { + callCounts.merge(result.getStatus(), 1, Integer::sum); + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java new file mode 100644 index 00000000..4df0e86a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.JUnitException; + +/** + * @since 5.4 + */ +class JUnit4VersionCheckTests { + + /** + * @since 5.7 + */ + @Test + void handlesParsingSupportedVersionIdWithStandardVersionFormat() { + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12")); + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13")); + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13.1")); + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13.2")); + } + + /** + * @since 5.7 + */ + @Test + void handlesParsingSupportedVersionIdWithCustomizedVersionFormat() { + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12-patch_1")); + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0")); + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0.1")); + assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0.patch-042")); + } + + @Test + void throwsExceptionForUnsupportedVersion() { + var exception = assertThrows(JUnitException.class, () -> JUnit4VersionCheck.checkSupported(() -> "4.11")); + + assertEquals("Unsupported version of junit:junit: 4.11. Please upgrade to version 4.12 or later.", + exception.getMessage()); + } + + @Test + void handlesErrorsReadingVersion() { + Error error = new NoClassDefFoundError(); + + var exception = assertThrows(JUnitException.class, () -> JUnit4VersionCheck.checkSupported(() -> { + throw error; + })); + + assertEquals("Failed to read version of junit:junit", exception.getMessage()); + assertSame(error, exception.getCause()); + } + + @Test + void handlesErrorsParsingVersion() { + var exception = assertThrows(JUnitException.class, + () -> JUnit4VersionCheck.checkSupported(() -> "not a version")); + + assertEquals("Failed to parse version of junit:junit: not a version", exception.getMessage()); + } + + @Test + @Tag("missing-junit4") + void handlesMissingJUnit() { + var exception = assertThrows(JUnitException.class, JUnit4VersionCheck::checkSupported); + + assertEquals("Invalid class/module path: junit-vintage-engine is present but junit:junit is not. " + + "Please either remove junit-vintage-engine or add junit:junit, or alternatively use " + + "an excludeEngines(\"junit-vintage\") filter.", + exception.getMessage()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java new file mode 100644 index 00000000..cb12669a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java @@ -0,0 +1,288 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.includedIf; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.TagFilter.excludeTags; +import static org.junit.platform.launcher.TagFilter.includeTags; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.internal.runners.SuiteMethod; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.runners.Suite; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit4.Categories; +import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithFilterableChildRunner; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithTwoTestCases; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithNotFilterableRunner; +import org.junit.vintage.engine.samples.junit4.NotFilterableRunner; +import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; + +/** + * @since 5.1 + */ +class VintageLauncherIntegrationTests { + + @Test + void executesOnlyTaggedMethodOfRegularTestClass() { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters(includeTags(Categories.Failing.class.getName())); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(2); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), "failingTest"); + } + + @Test + void executesIncludedTaggedMethodOfNestedTestClass() { + Class testClass = EnclosedJUnit4TestCase.class; + Class nestedTestClass = EnclosedJUnit4TestCase.NestedClass.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters(includeTags(Categories.Failing.class.getName())); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), nestedTestClass.getName(), + "failingTest"); + } + + @Test + void executesOnlyNotExcludedTaggedMethodOfNestedTestClass() { + Class testClass = EnclosedJUnit4TestCase.class; + Class nestedTestClass = EnclosedJUnit4TestCase.NestedClass.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters(excludeTags(Categories.Failing.class.getName())); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), nestedTestClass.getName(), + "successfulTest"); + } + + @Test + void removesWholeSubtree() { + Class testClass = EnclosedJUnit4TestCase.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters(excludeTags(Categories.Plain.class.getName())); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage"); + } + + @Test + void removesCompleteClassIfNoMethodHasMatchingTags() { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters(includeTags("wrong-tag")); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactly("JUnit Vintage"); + } + + @Test + void removesCompleteClassIfItHasExcludedTag() { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters(excludeTags(Categories.Plain.class.getName())); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactly("JUnit Vintage"); + } + + @TrackLogRecords + @Test + void executesAllTestsForNotFilterableRunner(LogRecordListener logRecordListener) { + Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters((PostDiscoveryFilter) descriptor -> includedIf(descriptor.getDisplayName().contains("#1"))); + + var testPlan = discover(request); + logRecordListener.clear(); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), "Test #0", "Test #1"); + assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // + .containsExactly( + "Runner " + NotFilterableRunner.class.getName() + " (used on class " + testClass.getName() + ")" // + + " does not support filtering and will therefore be run completely."); + } + + @TrackLogRecords + @Test + void executesAllTestsForNotFilterableChildRunnerOfSuite(LogRecordListener logRecordListener) { + Class suiteClass = JUnit4SuiteOfSuiteWithFilterableChildRunner.class; + Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; + var request = request() // + .selectors(selectClass(suiteClass)) // + .filters((PostDiscoveryFilter) descriptor -> includedIf(descriptor.getDisplayName().contains("#1"))); + + var testPlan = discover(request); + logRecordListener.clear(); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(4); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), "Test #0", + "Test #1"); + assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // + .containsExactly("Runner " + Suite.class.getName() + " (used on class " + suiteClass.getName() + ")" // + + " was not able to satisfy all filter requests."); + } + + @TrackLogRecords + @Test + void executesAllTestsWhenFilterDidNotExcludeTestForJUnit3Suite(LogRecordListener logRecordListener) { + Class suiteClass = JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class; + Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; + var request = request() // + .selectors(selectClass(suiteClass)) // + .filters((PostDiscoveryFilter) descriptor -> excluded("not today")); + + var testPlan = discover(request); + logRecordListener.clear(); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), "test"); + assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // + .containsExactly( + "Runner " + SuiteMethod.class.getName() + " (used on class " + suiteClass.getName() + ")" // + + " was not able to satisfy all filter requests."); + } + + @Test + void executesOnlyTaggedMethodsForSuite() { + Class suiteClass = JUnit4SuiteWithTwoTestCases.class; + Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; + var request = request() // + .selectors(selectClass(suiteClass)) // + .filters(includeTags(Categories.Successful.class.getName())); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), + "successfulTest"); + } + + @Test + void removesCompleteClassWithNotFilterableRunnerIfItHasExcludedTag() { + Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters(excludeTags(Categories.Successful.class.getName())); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactly("JUnit Vintage"); + } + + @Test + void filtersOutAllDescendantsOfParameterizedTestCase() { + Class testClass = ParameterizedTestCase.class; + var request = request() // + .selectors(selectClass(testClass)) // + .filters((PostDiscoveryFilter) descriptor -> excluded("excluded")); + + var testPlan = discover(request); + assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); + + var results = execute(request); + assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // + .containsExactly("JUnit Vintage"); + } + + private TestPlan discover(LauncherDiscoveryRequestBuilder requestBuilder) { + var launcher = LauncherFactory.create(); + return launcher.discover(toRequest(requestBuilder)); + } + + private Map execute(LauncherDiscoveryRequestBuilder requestBuilder) { + Map results = new LinkedHashMap<>(); + var request = toRequest(requestBuilder); + var launcher = LauncherFactory.create(); + launcher.execute(request, new TestExecutionListener() { + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + results.put(testIdentifier, testExecutionResult); + } + }); + return results; + } + + private LauncherDiscoveryRequest toRequest(LauncherDiscoveryRequestBuilder requestBuilder) { + return requestBuilder.filters(includeEngines(ENGINE_ID)).build(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java new file mode 100644 index 00000000..e78df198 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Basic assertions regarding {@link org.junit.platform.engine.TestEngine} + * functionality in JUnit Vintage. + * + * @since 4.12 + */ +class VintageTestEngineBasicTests { + + private final VintageTestEngine vintage = new VintageTestEngine(); + + @Test + void id() { + assertEquals("junit-vintage", vintage.getId()); + } + + @Test + void groupId() { + assertEquals("org.junit.vintage", vintage.getGroupId().get()); + } + + @Test + void artifactId() { + assertEquals("junit-vintage-engine", vintage.getArtifactId().get()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java new file mode 100644 index 00000000..b89470cf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java @@ -0,0 +1,803 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static java.text.MessageFormat.format; +import static java.util.function.Predicate.isEqual; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.runner.manipulation.Filter; +import org.junit.vintage.engine.samples.PlainOldJavaClassWithoutAnyTestsTestCase; +import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit4.Categories.Failing; +import org.junit.vintage.engine.samples.junit4.Categories.Plain; +import org.junit.vintage.engine.samples.junit4.Categories.Skipped; +import org.junit.vintage.engine.samples.junit4.Categories.SkippedWithReason; +import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; +import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithTwoTestCases; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithDistinguishableOverloadedMethod; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithIndistinguishableOverloadedMethod; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithNotFilterableRunner; +import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleInheritedTestWhichFails; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichIsIgnored; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; +import org.junit.vintage.engine.samples.junit4.SingleFailingTheoryTestCase; +import org.junit.vintage.engine.samples.junit4.TestCaseRunWithJUnitPlatformRunner; + +/** + * @since 4.12 + */ +class VintageTestEngineDiscoveryTests { + + VintageTestEngine engine = new VintageTestEngine(); + + @Test + void resolvesSimpleJUnit4TestClass() throws Exception { + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesIgnoredJUnit4TestClass() throws Exception { + Class testClass = IgnoredJUnit4TestCase.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + assertThat(runnerDescriptor.getChildren()).hasSize(2); + List children = new ArrayList<>(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(children.get(0), testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + assertTestMethodDescriptor(children.get(1), testClass, "succeedingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesEmptyIgnoredTestClass() { + Class testClass = EmptyIgnoredTestCase.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertFalse(runnerDescriptor.isContainer()); + assertTrue(runnerDescriptor.isTest()); + assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); + assertThat(runnerDescriptor.getChildren()).isEmpty(); + } + + @Test + void resolvesJUnit4TestClassWithCustomRunner() throws Exception { + Class testClass = SingleFailingTheoryTestCase.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(childDescriptor, testClass, "theory", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesJUnit3TestCase() throws Exception { + Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(childDescriptor, testClass, "test", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesJUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails() throws Exception { + Class suiteClass = JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class; + Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; + var discoveryRequest = discoveryRequestForClass(suiteClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(suiteDescriptor, suiteClass); + assertThat(suiteDescriptor.getDisplayName()).describedAs("display name") // + .startsWith(suiteClass.getSimpleName()); + assertThat(suiteDescriptor.getLegacyReportingName()).describedAs("legacy reporting name") // + .isEqualTo(suiteClass.getName()); + + var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); + assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertTestMethodDescriptor(testMethodDescriptor, testClass, "test", + VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); + } + + @Test + void resolvesJUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored() throws Exception { + Class suiteClass = JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; + var discoveryRequest = discoveryRequestForClass(suiteClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(suiteDescriptor, suiteClass); + + var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); + assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertTestMethodDescriptor(testMethodDescriptor, testClass, "ignoredTest", + VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); + } + + @Test + void resolvesJUnit4TestCaseWithIndistinguishableOverloadedMethod() { + Class testClass = JUnit4TestCaseWithIndistinguishableOverloadedMethod.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); + assertThat(testMethodDescriptors).hasSize(2); + + var testMethodDescriptor = testMethodDescriptors.get(0); + assertEquals("theory", testMethodDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "theory", "0"), + testMethodDescriptor.getUniqueId()); + assertClassSource(testClass, testMethodDescriptor); + + testMethodDescriptor = testMethodDescriptors.get(1); + assertEquals("theory", testMethodDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "theory", "1"), + testMethodDescriptor.getUniqueId()); + assertClassSource(testClass, testMethodDescriptor); + } + + @Test + void resolvesJUnit4TestCaseWithDistinguishableOverloadedMethod() throws Exception { + Class testClass = JUnit4TestCaseWithDistinguishableOverloadedMethod.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); + + var testMethodDescriptor = getOnlyElement(testMethodDescriptors); + assertEquals("test", testMethodDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "test"), testMethodDescriptor.getUniqueId()); + assertMethodSource(testClass.getMethod("test"), testMethodDescriptor); + } + + @Test + void doesNotResolvePlainOldJavaClassesWithoutAnyTest() { + assertYieldsNoDescriptors(PlainOldJavaClassWithoutAnyTestsTestCase.class); + } + + @Test + void doesNotResolveClassRunWithJUnitPlatform() { + assertYieldsNoDescriptors(TestCaseRunWithJUnitPlatformRunner.class); + } + + @Test + void resolvesClasspathSelector() throws Exception { + var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); + var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).build(); + var engineDescriptor = discoverTests(discoveryRequest); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()) + .contains(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()) + .doesNotContain(PlainOldJavaClassWithoutAnyTestsTestCase.class.getSimpleName()); + // @formatter:on + } + + @Test + void resolvesClasspathSelectorForJarFile() throws Exception { + var jarUrl = getClass().getResource("/vintage-testjar.jar"); + var jarFile = Paths.get(jarUrl.toURI()); + + var originalClassLoader = Thread.currentThread().getContextClassLoader(); + try (var classLoader = new URLClassLoader(new URL[] { jarUrl })) { + Thread.currentThread().setContextClassLoader(classLoader); + + var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(jarFile))).build(); + var engineDescriptor = discoverTests(discoveryRequest); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .containsExactly("JUnit4Test"); + // @formatter:on + } + finally { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + + @Test + void resolvesApplyingClassNameFilters() throws Exception { + var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); + + var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).filters( + includeClassNamePatterns(".*JUnit4.*"), includeClassNamePatterns(".*Plain.*")).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()) + .doesNotContain(JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getSimpleName()) + .doesNotContain(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()); + // @formatter:on + } + + @Test + void resolvesApplyingPackageNameFilters() throws Exception { + var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); + + var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).filters( + includePackageNames("org"), includePackageNames("org.junit")).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()); + // @formatter:on + } + + @Test + void resolvesPackageSelectorForJUnit4SamplesPackage() { + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + + var discoveryRequest = request().selectors(selectPackage(testClass.getPackage().getName())).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .contains(testClass.getSimpleName()) + .doesNotContain(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()); + // @formatter:on + } + + @Test + void resolvesPackageSelectorForJUnit3SamplesPackage() { + Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; + + var discoveryRequest = request().selectors(selectPackage(testClass.getPackage().getName())).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .contains(testClass.getSimpleName()) + .doesNotContain(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()); + // @formatter:on + } + + @Test + void resolvesClassesWithInheritedMethods() throws Exception { + Class superclass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + Class testClass = PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); + assertClassSource(testClass, runnerDescriptor); + + var testDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertEquals("failingTest", testDescriptor.getDisplayName()); + assertMethodSource(testClass, superclass.getMethod("failingTest"), testDescriptor); + } + + @Test + void resolvesCategoriesIntoTags() { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = discoveryRequestForClass(testClass); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(runnerDescriptor.getTags()).containsOnly(TestTag.create(Plain.class.getName())); + + var failingTest = findChildByDisplayName(runnerDescriptor, "failingTest"); + assertThat(failingTest.getTags()).containsOnly(// + TestTag.create(Plain.class.getName()), // + TestTag.create(Failing.class.getName())); + + var ignoredWithoutReason = findChildByDisplayName(runnerDescriptor, "ignoredTest1_withoutReason"); + assertThat(ignoredWithoutReason.getTags()).containsOnly(// + TestTag.create(Plain.class.getName()), // + TestTag.create(Skipped.class.getName())); + + var ignoredWithReason = findChildByDisplayName(runnerDescriptor, "ignoredTest2_withReason"); + assertThat(ignoredWithReason.getTags()).containsOnly(// + TestTag.create(Plain.class.getName()), // + TestTag.create(Skipped.class.getName()), // + TestTag.create(SkippedWithReason.class.getName())); + } + + @Test + void resolvesMethodSelectorForSingleMethod() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest"))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesMethodOfIgnoredJUnit4TestClass() throws Exception { + Class testClass = IgnoredJUnit4TestCase.class; + var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest"))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesMethodSelectorForTwoMethodsOfSameClass() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest")), + selectMethod(testClass, testClass.getMethod("successfulTest"))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); + assertThat(testMethodDescriptors).hasSize(2); + + var failingTest = testMethodDescriptors.get(0); + assertTestMethodDescriptor(failingTest, testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + + var successfulTest = testMethodDescriptors.get(1); + assertTestMethodDescriptor(successfulTest, testClass, "successfulTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesUniqueIdSelectorForSingleMethod() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors( + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest"))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void resolvesUniqueIdSelectorForSingleClass() { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors( + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + assertThat(runnerDescriptor.getChildren()).hasSize(5); + } + + @Test + void resolvesUniqueIdSelectorOfSingleClassWithinSuite() throws Exception { + Class suiteClass = JUnit4SuiteWithTwoTestCases.class; + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + var discoveryRequest = request().selectors( + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(suiteDescriptor, suiteClass); + + var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); + assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertTestMethodDescriptor(testMethodDescriptor, testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); + } + + @Test + void resolvesUniqueIdSelectorOfSingleMethodWithinSuite() throws Exception { + Class suiteClass = JUnit4SuiteWithTwoTestCases.class; + Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; + var discoveryRequest = request().selectors(selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod( + VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass), testClass, "successfulTest"))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(suiteDescriptor, suiteClass); + + var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); + assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertTestMethodDescriptor(testMethodDescriptor, testClass, "successfulTest", + VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); + } + + @Test + void resolvesMultipleUniqueIdSelectorsForMethodsOfSameClass() throws Exception { + Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; + var discoveryRequest = request().selectors( + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "successfulTest")), + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest"))).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); + assertThat(testMethodDescriptors).hasSize(2); + assertTestMethodDescriptor(testMethodDescriptors.get(0), testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + assertTestMethodDescriptor(testMethodDescriptors.get(1), testClass, "successfulTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void doesNotResolveMissingUniqueIdSelectorForSingleClass() { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors( + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass) + "/[test:doesNotExist]")).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var testDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertInitializationError(testDescriptor, Filter.class, testClass); + } + + @Test + void ignoresMoreFineGrainedSelectorsWhenClassIsSelectedAsWell() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors( // + selectMethod(testClass, testClass.getMethod("failingTest")), // + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "abortedTest")), selectClass(testClass) // + ).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + assertThat(runnerDescriptor.getChildren()).hasSize(5); + } + + @Test + void resolvesCombinationOfMethodAndUniqueIdSelector() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors( // + selectMethod(testClass, testClass.getMethod("failingTest")), // + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "abortedTest") // + )).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); + assertThat(testMethodDescriptors).hasSize(2); + assertTestMethodDescriptor(testMethodDescriptors.get(0), testClass, "abortedTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + assertTestMethodDescriptor(testMethodDescriptors.get(1), testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void ignoresRedundantSelector() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + var discoveryRequest = request().selectors( // + selectMethod(testClass, testClass.getMethod("failingTest")), // + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest") // + )).build(); + + var engineDescriptor = discoverTests(discoveryRequest); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var testMethodDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertTestMethodDescriptor(testMethodDescriptor, testClass, "failingTest", + VintageUniqueIdBuilder.uniqueIdForClass(testClass)); + } + + @Test + void doesNotResolveMethodOfClassNotAcceptedByClassNameFilter() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + // @formatter:off + var request = request() + .selectors(selectMethod(testClass, testClass.getMethod("failingTest"))) + .filters(includeClassNamePatterns("Foo")) + .build(); + // @formatter:on + + assertYieldsNoDescriptors(request); + } + + @Test + void doesNotResolveMethodOfClassNotAcceptedByPackageNameFilter() throws Exception { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + // @formatter:off + var request = request() + .selectors(selectMethod(testClass, testClass.getMethod("failingTest"))) + .filters(includePackageNames("com.acme")) + .build(); + // @formatter:on + + assertYieldsNoDescriptors(request); + } + + @Test + void resolvesClassForMethodSelectorForClassWithNonFilterableRunner() { + Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; + // @formatter:off + var request = request() + .selectors(selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "Test #0"))) + .build(); + // @formatter:on + + var engineDescriptor = discoverTests(request); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); + assertThat(runnerDescriptor.getChildren()).isNotEmpty(); + } + + @Test + void usesCustomUniqueIdsAndDisplayNamesWhenPresent() { + Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class; + var request = request().selectors(selectClass(suiteClass)).build(); + + var engineDescriptor = discoverTests(request); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, suiteClass); + + var testClassDescriptor = getOnlyElement(runnerDescriptor.getChildren()); + assertEquals("(TestClass)", testClassDescriptor.getDisplayName()); + + var childDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + + var prefix = VintageUniqueIdBuilder.uniqueIdForClass(suiteClass); + assertThat(childDescriptor.getUniqueId().toString()).startsWith(prefix.toString()); + assertEquals("(TestMethod)", childDescriptor.getDisplayName()); + + var customUniqueIdValue = childDescriptor.getUniqueId().getSegments().get(2).getType(); + assertNotNull(Base64.getDecoder().decode(customUniqueIdValue.getBytes(StandardCharsets.UTF_8)), + "is a valid Base64 encoding scheme"); + } + + @Test + void resolvesTestSourceForParameterizedTests() throws Exception { + Class testClass = ParameterizedTestCase.class; + var request = request().selectors(selectClass(testClass)).build(); + + var engineDescriptor = discoverTests(request); + + var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertRunnerTestDescriptor(runnerDescriptor, testClass); + + var fooParentDescriptor = findChildByDisplayName(runnerDescriptor, "[foo]"); + assertTrue(fooParentDescriptor.isContainer()); + assertFalse(fooParentDescriptor.isTest()); + assertThat(fooParentDescriptor.getSource()).isEmpty(); + + var testMethodDescriptor = getOnlyElement(fooParentDescriptor.getChildren()); + assertEquals("test[foo]", testMethodDescriptor.getDisplayName()); + assertTrue(testMethodDescriptor.isTest()); + assertFalse(testMethodDescriptor.isContainer()); + assertMethodSource(testClass.getMethod("test"), testMethodDescriptor); + } + + private TestDescriptor findChildByDisplayName(TestDescriptor runnerDescriptor, String displayName) { + // @formatter:off + var children = runnerDescriptor.getChildren(); + return children + .stream() + .filter(where(TestDescriptor::getDisplayName, isEqual(displayName))) + .findAny() + .orElseThrow(() -> + new AssertionError(format("No child with display name \"{0}\" in {1}", displayName, children))); + // @formatter:on + } + + private TestDescriptor discoverTests(LauncherDiscoveryRequest discoveryRequest) { + return engine.discover(discoveryRequest, UniqueId.forEngine(engine.getId())); + } + + private Path getClasspathRoot(Class testClass) throws Exception { + var location = testClass.getProtectionDomain().getCodeSource().getLocation(); + return Paths.get(location.toURI()); + } + + private void assertYieldsNoDescriptors(Class testClass) { + var request = discoveryRequestForClass(testClass); + + assertYieldsNoDescriptors(request); + } + + private void assertYieldsNoDescriptors(LauncherDiscoveryRequest request) { + var engineDescriptor = discoverTests(request); + + assertThat(engineDescriptor.getChildren()).isEmpty(); + } + + private static void assertRunnerTestDescriptor(TestDescriptor runnerDescriptor, Class testClass) { + assertTrue(runnerDescriptor.isContainer()); + assertFalse(runnerDescriptor.isTest()); + assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); + assertClassSource(testClass, runnerDescriptor); + } + + private static void assertTestMethodDescriptor(TestDescriptor testMethodDescriptor, Class testClass, + String methodName, UniqueId uniqueContainerId) throws Exception { + assertTrue(testMethodDescriptor.isTest()); + assertFalse(testMethodDescriptor.isContainer()); + assertEquals(methodName, testMethodDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(uniqueContainerId, testClass, methodName), + testMethodDescriptor.getUniqueId()); + assertThat(testMethodDescriptor.getChildren()).isEmpty(); + assertMethodSource(testClass.getMethod(methodName), testMethodDescriptor); + } + + private static void assertContainerTestDescriptor(TestDescriptor containerDescriptor, Class suiteClass, + Class testClass) { + assertTrue(containerDescriptor.isContainer()); + assertFalse(containerDescriptor.isTest()); + assertEquals(testClass.getName(), containerDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass), + containerDescriptor.getUniqueId()); + assertClassSource(testClass, containerDescriptor); + } + + private static void assertInitializationError(TestDescriptor testDescriptor, Class failingClass, + Class testClass) { + assertTrue(testDescriptor.isTest()); + assertFalse(testDescriptor.isContainer()); + assertEquals("initializationError", testDescriptor.getDisplayName()); + assertEquals(VintageUniqueIdBuilder.uniqueIdForErrorInClass(testClass, failingClass), + testDescriptor.getUniqueId()); + assertThat(testDescriptor.getChildren()).isEmpty(); + assertClassSource(failingClass, testDescriptor); + } + + private static void assertClassSource(Class expectedClass, TestDescriptor testDescriptor) { + assertThat(testDescriptor.getSource()).containsInstanceOf(ClassSource.class); + var classSource = (ClassSource) testDescriptor.getSource().get(); + assertThat(classSource.getJavaClass()).isEqualTo(expectedClass); + } + + private static void assertMethodSource(Method expectedMethod, TestDescriptor testDescriptor) { + assertMethodSource(expectedMethod.getDeclaringClass(), expectedMethod, testDescriptor); + } + + private static void assertMethodSource(Class expectedClass, Method expectedMethod, + TestDescriptor testDescriptor) { + assertThat(testDescriptor.getSource()).containsInstanceOf(MethodSource.class); + var methodSource = (MethodSource) testDescriptor.getSource().get(); + assertThat(methodSource.getClassName()).isEqualTo(expectedClass.getName()); + assertThat(methodSource.getMethodName()).isEqualTo(expectedMethod.getName()); + assertThat(methodSource.getMethodParameterTypes()).isEqualTo( + ClassUtils.nullSafeToString(expectedMethod.getParameterTypes())); + } + + private static LauncherDiscoveryRequest discoveryRequestForClass(Class testClass) { + return request().selectors(selectClass(testClass)).build(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java new file mode 100644 index 00000000..66af815a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -0,0 +1,923 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.junit.runner.Description.createSuiteDescription; +import static org.junit.runner.Description.createTestDescription; + +import java.math.BigDecimal; + +import junit.runner.Version; + +import org.assertj.core.api.Condition; +import org.junit.AssumptionViolatedException; +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.junit.vintage.engine.samples.junit3.JUnit3ParallelSuiteWithSubsuites; +import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites; +import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase; +import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; +import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.EnclosedWithParameterizedChildrenJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.IgnoredParameterizedTestCase; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithExceptionThrowingRunner; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithIgnoredJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit3SuiteWithSingleTestCase; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished; +import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithAssumptionFailureInBeforeClass; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorCollectorStoringMultipleFailures; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorInAfterClass; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorInBeforeClass; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithExceptionThrowingRunner; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithIndistinguishableOverloadedMethod; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames; +import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions; +import org.junit.vintage.engine.samples.junit4.MalformedJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; +import org.junit.vintage.engine.samples.junit4.ParameterizedTimingTestCase; +import org.junit.vintage.engine.samples.junit4.ParameterizedWithAfterParamFailureTestCase; +import org.junit.vintage.engine.samples.junit4.ParameterizedWithBeforeParamFailureTestCase; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithLifecycleMethods; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichIsIgnored; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; +import org.junit.vintage.engine.samples.spock.SpockTestCaseWithUnrolledAndRegularFeatureMethods; +import org.opentest4j.MultipleFailuresError; + +/** + * @since 4.12 + */ +class VintageTestEngineExecutionTests { + + @Test + void executesPlainJUnit4TestCaseWithSingleTestWhichFails() { + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("failingTest"), started()), // + event(test("failingTest"), + finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesPlainJUnit4TestCaseWithTwoTests() { + Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("failingTest"), started()), // + event(test("failingTest"), + finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // + event(test("successfulTest"), started()), // + event(test("successfulTest"), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesPlainJUnit4TestCaseWithFiveTests() { + Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("abortedTest"), started()), // + event(test("abortedTest"), + abortedWithReason(instanceOf(AssumptionViolatedException.class), + message("this test should be aborted"))), // + event(test("failingTest"), started()), // + event(test("failingTest"), + finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // + event(test("ignoredTest1_withoutReason"), skippedWithReason("")), // + event(test("ignoredTest2_withReason"), skippedWithReason("a custom reason")), // + event(test("successfulTest"), started()), // + event(test("successfulTest"), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesEnclosedJUnit4TestCase() { + Class testClass = EnclosedJUnit4TestCase.class; + Class nestedClass = EnclosedJUnit4TestCase.NestedClass.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(nestedClass), started()), // + event(test("successfulTest"), started()), // + event(test("successfulTest"), finishedSuccessfully()), // + event(test("failingTest"), started()), // + event(test("failingTest"), + finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // + event(container(nestedClass), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesEnclosedWithParameterizedChildrenJUnit4TestCase() { + Class testClass = EnclosedWithParameterizedChildrenJUnit4TestCase.class; + String commonNestedClassPrefix = EnclosedWithParameterizedChildrenJUnit4TestCase.class.getName() + + "$NestedTestCase"; + + execute(testClass).allEvents().debug().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(commonNestedClassPrefix), started()), // + event(container("[0]"), started()), // + event(test("test[0]"), started()), // + event(test("test[0]"), finishedSuccessfully()), // + event(container("[0]"), finishedSuccessfully()), // + event(container("[1]"), started()), // + event(test("test[1]"), started()), // + event(test("test[1]"), finishedSuccessfully()), // + event(container("[1]"), finishedSuccessfully()), // + event(container(commonNestedClassPrefix), finishedSuccessfully()), // + event(container(commonNestedClassPrefix), started()), // + event(container("[0]"), started()), // + event(test("test[0]"), started()), // + event(test("test[0]"), finishedSuccessfully()), // + event(container("[0]"), finishedSuccessfully()), // + event(container("[1]"), started()), // + event(test("test[1]"), started()), // + event(test("test[1]"), finishedSuccessfully()), // + event(container("[1]"), finishedSuccessfully()), // + event(container(commonNestedClassPrefix), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteWithJUnit3SuiteWithSingleTestCase() { + Class junit4SuiteClass = JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.class; + Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; + + execute(junit4SuiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(junit4SuiteClass), started()), // + event(container("TestSuite with 1 tests"), started()), // + event(container(testClass), started()), // + event(test("test"), started()), // + event(test("test"), + finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // + event(container(testClass), finishedSuccessfully()), // + event(container("TestSuite with 1 tests"), finishedSuccessfully()), // + event(container(junit4SuiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesMalformedJUnit4TestCase() { + Class testClass = MalformedJUnit4TestCase.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("initializationError"), started()), // + event(test("initializationError"), + finishedWithFailure(message(it -> it.contains("Method nonPublicTest() should be public")))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithErrorInBeforeClass() { + Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass() { + Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; + Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; + + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // + event(container(suiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass() { + Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; + Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; + Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; + + execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteOfSuiteClass), started()), // + event(container(suiteClass), started()), // + event(container(testClass), started()), // + event(container(testClass), + finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // + event(container(suiteClass), finishedSuccessfully()), // + event(container(suiteOfSuiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithAssumptionFailureInBeforeClass() { + Class testClass = JUnit4TestCaseWithAssumptionFailureInBeforeClass.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), + abortedWithReason(instanceOf(AssumptionViolatedException.class), message("assumption violated"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass() { + Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class; + Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class; + Class testClass = JUnit4TestCaseWithAssumptionFailureInBeforeClass.class; + + execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteOfSuiteClass), started()), // + event(container(suiteClass), started()), // + event(container(testClass), started()), // + event(container(testClass), + abortedWithReason(instanceOf(AssumptionViolatedException.class), message("assumption violated"))), // + event(container(suiteClass), finishedSuccessfully()), // + event(container(suiteOfSuiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithErrorInAfterClass() { + Class testClass = JUnit4TestCaseWithErrorInAfterClass.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("failingTest"), started()), // + event(test("failingTest"), + finishedWithFailure(instanceOf(AssertionError.class), message("expected to fail"))), // + event(test("succeedingTest"), started()), // + event(test("succeedingTest"), finishedSuccessfully()), // + event(container(testClass), + finishedWithFailure(instanceOf(AssertionError.class), message("error in @AfterClass"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithOverloadedMethod() { + Class testClass = JUnit4TestCaseWithIndistinguishableOverloadedMethod.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[0]"), + started()), // + event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[0]"), + finishedWithFailure()), // + event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[1]"), + started()), // + event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[1]"), + finishedWithFailure()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesIgnoredJUnit4TestCase() { + Class testClass = IgnoredJUnit4TestCase.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), skippedWithReason("complete class is ignored")), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesEmptyIgnoredTestClass() { + Class testClass = EmptyIgnoredTestCase.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(test(testClass.getName()), skippedWithReason("empty")), // + event(engine(), finishedSuccessfully())); + } + + @Test + void reportsExecutionEventsAroundLifecycleMethods() { + Class testClass = PlainJUnit4TestCaseWithLifecycleMethods.class; + PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.clear(); + + var listener = new EngineExecutionListener() { + + @Override + public void executionStarted(TestDescriptor testDescriptor) { + PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( + "executionStarted:" + testDescriptor.getDisplayName()); + } + + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( + "executionFinished:" + testDescriptor.getDisplayName()); + } + + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( + "executionSkipped:" + testDescriptor.getDisplayName()); + } + + @Override + public void dynamicTestRegistered(TestDescriptor testDescriptor) { + } + + @Override + public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + } + }; + + execute(testClass, listener); + + // @formatter:off + assertThat(PlainJUnit4TestCaseWithLifecycleMethods.EVENTS).containsExactly( + "executionStarted:JUnit Vintage", + "executionStarted:" + testClass.getSimpleName(), + "beforeClass", + "executionStarted:failingTest", + "before", + "failingTest", + "after", + "executionFinished:failingTest", + "executionSkipped:skippedTest", + "executionStarted:succeedingTest", + "before", + "succeedingTest", + "after", + "executionFinished:succeedingTest", + "afterClass", + "executionFinished:" + testClass.getSimpleName(), + "executionFinished:JUnit Vintage" + ); + // @formatter:on + } + + @Test + void executesJUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored() { + Class suiteClass = JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; + + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), started()), // + event(container(testClass), started()), // + event(test("ignoredTest"), skippedWithReason("ignored test")), // + event(container(testClass), finishedSuccessfully()), // + event(container(suiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase() { + Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.class; + Class suiteClass = JUnit4SuiteWithIgnoredJUnit4TestCase.class; + Class testClass = IgnoredJUnit4TestCase.class; + + execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteOfSuiteClass), started()), // + event(container(suiteClass), started()), // + event(container(testClass), skippedWithReason("complete class is ignored")), // + event(container(suiteClass), finishedSuccessfully()), // + event(container(suiteOfSuiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesParameterizedTestCase() { + Class testClass = ParameterizedTestCase.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container("[foo]"), started()), // + event(test("test[foo]"), started()), // + event(test("test[foo]"), finishedSuccessfully()), // + event(container("[foo]"), finishedSuccessfully()), // + event(container("[bar]"), started()), // + event(test("test[bar]"), started()), // + event(test("test[bar]"), + finishedWithFailure(instanceOf(AssertionError.class), message("expected:<[foo]> but was:<[bar]>"))), // + event(container("[bar]"), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesIgnoredParameterizedTestCase() { + Class testClass = IgnoredParameterizedTestCase.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container("[foo]"), started()), // + event(test("test[foo]"), skippedWithReason("")), // + event(container("[foo]"), finishedSuccessfully()), // + event(container("[bar]"), started()), // + event(test("test[bar]"), skippedWithReason("")), // + event(container("[bar]"), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesParameterizedTimingTestCase() { + assumeTrue(atLeastJUnit4_13(), "@BeforeParam and @AfterParam were introduced in JUnit 4.13"); + + Class testClass = ParameterizedTimingTestCase.class; + + var events = execute(testClass).allEvents().debug(); + + var firstParamStartedEvent = events.filter(event(container("[foo]"), started())::matches).findFirst() // + .orElseThrow(() -> new AssertionError("No start event for [foo]")); + var firstParamFinishedEvent = events.filter( + event(container("[foo]"), finishedSuccessfully())::matches).findFirst() // + .orElseThrow(() -> new AssertionError("No finish event for [foo]")); + var secondParamStartedEvent = events.filter(event(container("[bar]"), started())::matches).findFirst() // + .orElseThrow(() -> new AssertionError("No start event for [bar]")); + var secondParamFinishedEvent = events.filter( + event(container("[bar]"), finishedSuccessfully())::matches).findFirst() // + .orElseThrow(() -> new AssertionError("No finish event for [bar]")); + + assertThat(ParameterizedTimingTestCase.EVENTS.get("beforeParam(foo)")).isAfterOrEqualTo( + firstParamStartedEvent.getTimestamp()); + assertThat(ParameterizedTimingTestCase.EVENTS.get("afterParam(foo)")).isBeforeOrEqualTo( + firstParamFinishedEvent.getTimestamp()); + assertThat(ParameterizedTimingTestCase.EVENTS.get("beforeParam(bar)")).isAfterOrEqualTo( + secondParamStartedEvent.getTimestamp()); + assertThat(ParameterizedTimingTestCase.EVENTS.get("afterParam(bar)")).isBeforeOrEqualTo( + secondParamFinishedEvent.getTimestamp()); + } + + @Test + void executesParameterizedWithAfterParamFailureTestCase() { + assumeTrue(atLeastJUnit4_13(), "@AfterParam was introduced in JUnit 4.13"); + + Class testClass = ParameterizedWithAfterParamFailureTestCase.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container("[foo]"), started()), // + event(test("test[foo]"), started()), // + event(test("test[foo]"), finishedSuccessfully()), // + event(container("[foo]"), finishedWithFailure(instanceOf(AssertionError.class))), // + event(container("[bar]"), started()), // + event(test("test[bar]"), started()), // + event(test("test[bar]"), + finishedWithFailure(instanceOf(AssertionError.class), message("expected:<[foo]> but was:<[bar]>"))), // + event(container("[bar]"), finishedWithFailure(instanceOf(AssertionError.class))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesParameterizedWithBeforeParamFailureTestCase() { + assumeTrue(atLeastJUnit4_13(), "@BeforeParam was introduced in JUnit 4.13"); + + Class testClass = ParameterizedWithBeforeParamFailureTestCase.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container("[foo]"), started()), // + event(container("[foo]"), finishedWithFailure(instanceOf(AssertionError.class))), // + event(container("[bar]"), started()), // + event(container("[bar]"), finishedWithFailure(instanceOf(AssertionError.class))), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithExceptionThrowingRunner() { + Class testClass = JUnit4TestCaseWithExceptionThrowingRunner.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(test(testClass.getName()), started()), // + event(test(testClass.getName()), finishedWithFailure()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteWithExceptionThrowingRunner() { + Class testClass = JUnit4SuiteWithExceptionThrowingRunner.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container(testClass), finishedWithFailure()), // + event(engine(), finishedSuccessfully())); + } + + public static class DynamicSuiteRunner extends Runner { + + private final Class testClass; + + public DynamicSuiteRunner(Class testClass) { + this.testClass = testClass; + } + + @Override + public Description getDescription() { + return createSuiteDescription(testClass); + } + + @Override + public void run(RunNotifier notifier) { + var dynamicDescription = createTestDescription(testClass, "dynamicTest"); + notifier.fireTestStarted(dynamicDescription); + notifier.fireTestFinished(dynamicDescription); + } + + } + + @RunWith(DynamicSuiteRunner.class) + public static class DynamicTestClass { + } + + @Test + void reportsDynamicTestsForUnknownDescriptions() { + Class testClass = DynamicTestClass.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(test(testClass.getName()), started()), // + event(dynamicTestRegistered("dynamicTest")), // + event(test("dynamicTest"), started()), // + event(test("dynamicTest"), finishedSuccessfully()), // + event(test(testClass.getName()), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + public static class DynamicAndStaticChildrenRunner extends Runner { + + private final Class testClass; + + public DynamicAndStaticChildrenRunner(Class testClass) { + this.testClass = testClass; + } + + @Override + public Description getDescription() { + var suiteDescription = createSuiteDescription(testClass); + suiteDescription.addChild(createTestDescription(testClass, "staticTest")); + return suiteDescription; + } + + @Override + public void run(RunNotifier notifier) { + var staticDescription = getDescription().getChildren().get(0); + notifier.fireTestStarted(staticDescription); + notifier.fireTestFinished(staticDescription); + var dynamicDescription = createTestDescription(testClass, "dynamicTest"); + notifier.fireTestStarted(dynamicDescription); + notifier.fireTestFinished(dynamicDescription); + } + + } + + @RunWith(DynamicAndStaticChildrenRunner.class) + public static class DynamicAndStaticTestClass { + } + + @RunWith(Suite.class) + @SuiteClasses(DynamicAndStaticTestClass.class) + public static class SuiteWithDynamicAndStaticTestClass { + } + + @Test + void reportsIntermediateContainersFinishedAfterTheirDynamicChildren() { + Class suiteClass = SuiteWithDynamicAndStaticTestClass.class; + Class testClass = DynamicAndStaticTestClass.class; + + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass.getName()), started()), // + event(container(testClass.getName()), started()), // + event(test("staticTest"), started()), // + event(test("staticTest"), finishedSuccessfully()), // + event(dynamicTestRegistered("dynamicTest")), // + event(test("dynamicTest"), started()), // + event(test("dynamicTest"), finishedSuccessfully()), // + event(container(testClass.getName()), finishedSuccessfully()), // + event(container(suiteClass.getName()), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + public static class MisbehavingChildlessRunner extends Runner { + + private final Class testClass; + + public MisbehavingChildlessRunner(Class testClass) { + this.testClass = testClass; + } + + @Override + public Description getDescription() { + return createSuiteDescription(testClass); + } + + @Override + public void run(RunNotifier notifier) { + notifier.fireTestStarted(createTestDescription(testClass, "doesNotExist")); + } + + } + + @RunWith(MisbehavingChildlessRunner.class) + public static class MisbehavingChildTestClass { + + } + + @Test + void ignoreEventsForUnknownDescriptionsByMisbehavingChildlessRunner() { + Class testClass = MisbehavingChildTestClass.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(test(testClass.getName()), started()), // + event(dynamicTestRegistered("doesNotExist")), // + event(test("doesNotExist"), started()), // + event(test(testClass.getName()), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithRunnerWithCustomUniqueIds() { + Class testClass = JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(uniqueIdSubstring(testClass.getName()), started()), // + event(uniqueIdSubstring(testClass.getName()), finishedWithFailure()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithErrorCollectorStoringMultipleFailures() { + Class testClass = JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("example"), started()), // + event(test("example"), // + finishedWithFailure(// + instanceOf(MultipleFailuresError.class), // + new Condition<>(throwable -> ((MultipleFailuresError) throwable).getFailures().size() == 3, + "MultipleFailuresError must contain 3 failures"), // + new Condition<>(throwable -> ((MultipleFailuresError) throwable).getSuppressed().length == 3, + "MultipleFailuresError must contain 3 suppressed exceptions")// + )), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished() { + Class testClass = JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; + + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("testWithMissingEvents"), started()), // + event(test("testWithMissingEvents"), finishedWithFailure()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished() { + Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; + Class firstTestClass = JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; + Class secondTestClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), started()), // + event(container(firstTestClass), started()), // + event(test("testWithMissingEvents"), started()), // + event(test("testWithMissingEvents"), finishedWithFailure()), // + event(container(firstTestClass), finishedSuccessfully()), // + event(container(secondTestClass), started()), // + event(test("failingTest"), started()), // + event(test("failingTest"), finishedWithFailure()), // + event(container(secondTestClass), finishedSuccessfully()), // + event(container(suiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesCompletelyDynamicTestCaseDiscoveredByUniqueId() { + Class testClass = CompletelyDynamicTestCase.class; + var request = LauncherDiscoveryRequestBuilder.request().selectors( + selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass))).build(); + + execute(request).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(displayName(testClass.getSimpleName()), started()), // + event(dynamicTestRegistered("Test #0")), // + event(test("Test #0"), started()), // + event(test("Test #0"), finishedSuccessfully()), // + event(displayName(testClass.getSimpleName()), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit3ParallelSuiteWithSubsuites() { + var suiteClass = JUnit3ParallelSuiteWithSubsuites.class; + var results = execute(suiteClass); + results.containerEvents() // + .assertStatistics(stats -> stats.started(4).dynamicallyRegistered(0).finished(4).succeeded(4)) // + .assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), started()), // + event(container("Case"), started()), // + event(container("Case")), // + event(container("Case")), // + event(container("Case"), finishedSuccessfully()), // + event(container(suiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + results.testEvents() // + .assertStatistics(stats -> stats.started(2).dynamicallyRegistered(0).finished(2).succeeded(2)) // + .assertEventsMatchExactly( // + event(test("hello"), started()), // + event(test("hello")), // + event(test("hello")), // + event(test("hello"), finishedSuccessfully())); + } + + @Test + void executesJUnit3SuiteWithSubsuites() { + var suiteClass = JUnit3SuiteWithSubsuites.class; + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), started()), // + event(container("Case1"), started()), // + event(test("hello"), started()), // + event(test("hello"), finishedSuccessfully()), // + event(container("Case1"), finishedSuccessfully()), // + event(container("Case2"), started()), // + event(test("hello"), started()), // + event(test("hello"), finishedSuccessfully()), // + event(container("Case2"), finishedSuccessfully()), // + event(container(suiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions() { + Class testClass = JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.class; + execute(testClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container("1st"), started()), // + event(test("0"), skippedWithReason(__ -> true)), // + event(test("1"), started()), // + event(test("1"), finishedSuccessfully()), // + event(container("1st"), finishedSuccessfully()), // + event(container("2nd"), started()), // + event(test("0"), skippedWithReason(__ -> true)), // + event(test("1"), started()), // + event(test("1"), finishedSuccessfully()), // + event(container("2nd"), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesUnrolledSpockFeatureMethod() { + Class testClass = SpockTestCaseWithUnrolledAndRegularFeatureMethods.class; + var request = LauncherDiscoveryRequestBuilder.request().selectors( + selectMethod(testClass, "unrolled feature for #input")).build(); + execute(request).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(uniqueIdSubstring(testClass.getName()), started()), // + event(dynamicTestRegistered("unrolled feature for 23")), // + event(test("unrolled feature for 23"), started()), // + event(test("unrolled feature for 23"), finishedWithFailure()), // + event(dynamicTestRegistered("unrolled feature for 42")), // + event(test("unrolled feature for 42"), started()), // + event(test("unrolled feature for 42"), finishedSuccessfully()), // + event(uniqueIdSubstring(testClass.getName()), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesRegularSpockFeatureMethod() { + Class testClass = SpockTestCaseWithUnrolledAndRegularFeatureMethods.class; + var request = LauncherDiscoveryRequestBuilder.request().selectors(selectMethod(testClass, "regular")).build(); + execute(request).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("regular"), started()), // + event(test("regular"), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + private static EngineExecutionResults execute(Class testClass) { + return execute(request(testClass)); + } + + private static EngineExecutionResults execute(LauncherDiscoveryRequest request) { + return EngineTestKit.execute(new VintageTestEngine(), request); + } + + private static void execute(Class testClass, EngineExecutionListener listener) { + TestEngine testEngine = new VintageTestEngine(); + var discoveryRequest = request(testClass); + var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId())); + testEngine.execute( + new ExecutionRequest(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters())); + } + + private static LauncherDiscoveryRequest request(Class testClass) { + return LauncherDiscoveryRequestBuilder.request().selectors(selectClass(testClass)).build(); + } + + private static boolean atLeastJUnit4_13() { + return JUnit4VersionCheck.parseVersion(Version.id()).compareTo(new BigDecimal("4.13")) >= 0; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java new file mode 100644 index 00000000..62f9f6c8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * Test suite for the {@link VintageTestEngine}. + * + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @since 4.12 + */ +@Suite +@SelectPackages("org.junit.vintage.engine") +@IncludeClassNamePatterns(".*Tests?") +@IncludeEngines("junit-jupiter") +class VintageTestEngineTestSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java new file mode 100644 index 00000000..88fdd915 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import org.junit.platform.engine.UniqueId; +import org.junit.vintage.engine.descriptor.VintageTestDescriptor; + +/** + * Test data builder for building unique IDs for the {@link VintageTestEngine}. + * + * Used to decouple tests from concrete unique ID strings. + * + * @since 4.12 + */ +public class VintageUniqueIdBuilder { + + public static UniqueId uniqueIdForErrorInClass(Class clazz, Class failingClass) { + return uniqueIdForClasses(clazz).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, + "initializationError(" + failingClass.getName() + ")"); + } + + public static UniqueId uniqueIdForClass(Class clazz) { + return uniqueIdForClasses(clazz); + } + + public static UniqueId uniqueIdForClasses(Class clazz, Class... clazzes) { + var uniqueId = uniqueIdForClass(clazz.getName()); + for (var each : clazzes) { + uniqueId = uniqueId.append(VintageTestDescriptor.SEGMENT_TYPE_TEST, each.getName()); + } + return uniqueId; + } + + public static UniqueId uniqueIdForClass(String fullyQualifiedClassName) { + var containerId = engineId(); + return containerId.append(VintageTestDescriptor.SEGMENT_TYPE_RUNNER, fullyQualifiedClassName); + } + + public static UniqueId uniqueIdForMethod(Class testClass, String methodName) { + return uniqueIdForClass(testClass).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, + methodValue(testClass, methodName)); + } + + private static String methodValue(Class testClass, String methodName) { + return methodName + "(" + testClass.getName() + ")"; + } + + public static UniqueId uniqueIdForMethod(Class testClass, String methodName, String index) { + return uniqueIdForClass(testClass).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, + methodValue(testClass, methodName) + "[" + index + "]"); + } + + public static UniqueId uniqueIdForMethod(UniqueId containerId, Class testClass, String methodName) { + return containerId.append(VintageTestDescriptor.SEGMENT_TYPE_TEST, methodValue(testClass, methodName)); + } + + public static UniqueId engineId() { + return UniqueId.forEngine(VintageTestDescriptor.ENGINE_ID); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java new file mode 100644 index 00000000..10247cb7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.stream.Stream; + +import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.runner.Description; +import org.junit.vintage.engine.discovery.IsPotentialJUnit4TestClass; + +class DescriptionUtilsTests { + + @SuppressWarnings("deprecation") + AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true); + + @TestFactory + Stream computedMethodNameCorrectly() { + var classFilter = ClassFilter.of(new IsPotentialJUnit4TestClass()); + var testClasses = ReflectionUtils.findAllClassesInPackage("org.junit.vintage.engine.samples", classFilter); + return testClasses.stream().flatMap(this::toDynamicTests); + } + + private Stream toDynamicTests(Class testClass) { + try { + var runner = builder.runnerForClass(testClass); + return toDynamicTests(Stream.of(runner.getDescription())); + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + Stream toDynamicTests(Stream children) { + return children.map(description -> description.isTest() // + ? toDynamicTest(description, "child: " + description.toString()) // + : dynamicContainer("class: " + description.toString(), Stream.concat( // + Stream.of(toDynamicTest(description, "self")), // + toDynamicTests(description.getChildren().stream())))); + } + + private DynamicTest toDynamicTest(Description description, String displayName) { + return dynamicTest(displayName, + () -> assertEquals(description.getMethodName(), DescriptionUtils.getMethodName(description))); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java new file mode 100644 index 00000000..572b4e7f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +/** + * @since 5.5 + */ +class OrFilterTests { + + @Test + void exceptionWithoutAnyFilters() { + var actual = assertThrows(PreconditionViolationException.class, () -> new OrFilter(Set.of())); + assertEquals("filters must not be empty", actual.getMessage()); + } + + @Test + void evaluatesSingleFilter() { + var filter = mockFilter("foo", true); + + var orFilter = new OrFilter(Set.of(filter)); + + assertEquals("foo", orFilter.describe()); + + var description = Description.createTestDescription(getClass(), "evaluatesSingleFilter"); + assertTrue(orFilter.shouldRun(description)); + + verify(filter).shouldRun(same(description)); + } + + @Test + void evaluatesMultipleFilters() { + var filter1 = mockFilter("foo", false); + var filter2 = mockFilter("bar", true); + + var orFilter = new OrFilter(List.of(filter1, filter2)); + + assertEquals("foo OR bar", orFilter.describe()); + + var description = Description.createTestDescription(getClass(), "evaluatesMultipleFilters"); + assertTrue(orFilter.shouldRun(description)); + + verify(filter1).shouldRun(same(description)); + verify(filter2).shouldRun(same(description)); + } + + private Filter mockFilter(String description, boolean result) { + var filter = mock(Filter.class); + when(filter.describe()).thenReturn(description); + when(filter.shouldRun(any())).thenReturn(result); + return filter; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java new file mode 100644 index 00000000..8211f759 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.runner.Description; +import org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase; + +/** + * @since 5.6 + */ +class TestSourceProviderTests { + + @Test + void findsInheritedMethod() { + var description = Description.createTestDescription(ConcreteJUnit4TestCase.class, "theTest"); + + var source = new TestSourceProvider().findTestSource(description); + assertThat(source).isInstanceOf(MethodSource.class); + + var methodSource = (MethodSource) source; + assertEquals(ConcreteJUnit4TestCase.class.getName(), methodSource.getClassName()); + assertEquals("theTest", methodSource.getMethodName()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java new file mode 100644 index 00000000..58be5a83 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.descriptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.UniqueId; +import org.junit.runner.Description; +import org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase; + +class VintageTestDescriptorTests { + + private static final UniqueId uniqueId = UniqueId.forEngine("vintage"); + + @Test + void legacyReportingNameUsesClassName() { + var description = Description.createSuiteDescription(ConcreteJUnit4TestCase.class); + var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); + + assertEquals("org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase", + testDescriptor.getLegacyReportingName()); + } + + @Test + void legacyReportingNameUsesMethodName() { + var description = Description.createTestDescription(ConcreteJUnit4TestCase.class, "legacyTest"); + var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); + + assertEquals("legacyTest", testDescriptor.getLegacyReportingName()); + } + + @Test + void legacyReportingNameFallbackToDisplayName() { + var suiteName = "Legacy Suite"; + var description = Description.createSuiteDescription(suiteName); + var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); + + assertEquals(testDescriptor.getDisplayName(), testDescriptor.getLegacyReportingName()); + assertEquals(suiteName, testDescriptor.getLegacyReportingName()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java new file mode 100644 index 00000000..442daa53 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class IsPotentialJUnit4TestClassTests { + + private final IsPotentialJUnit4TestClass isPotentialJUnit4TestClass = new IsPotentialJUnit4TestClass(); + + @Test + void staticMemberClass() { + assertTrue(isPotentialJUnit4TestClass.test(Foo.class)); + } + + public static class Foo { + } + + @Test + void nonPublicClass() { + assertFalse(isPotentialJUnit4TestClass.test(Bar.class)); + } + + static class Bar { + } + + @Test + void abstractClass() { + assertFalse(isPotentialJUnit4TestClass.test(Baz.class)); + } + + public static abstract class Baz { + } + + @Test + void anonymousClass() { + var foo = new Foo() { + }; + + assertFalse(isPotentialJUnit4TestClass.test(foo.getClass())); + } + + public class FooBaz { + } + + @Test + void publicInnerClass() { + assertFalse(isPotentialJUnit4TestClass.test(FooBaz.class)); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java new file mode 100644 index 00000000..6b64d1af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.mockito.Mockito.mock; + +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.vintage.engine.VintageUniqueIdBuilder; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; +import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCaseWithNotFilterableRunner; +import org.junit.vintage.engine.samples.junit4.NotFilterableRunner; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; + +/** + * Tests for {@link RunnerTestDescriptorPostProcessor}. + * + * @since 5.5 + */ +@TrackLogRecords +class RunnerTestDescriptorPostProcessorTests { + + @Test + void doesNotLogAnythingForFilterableRunner(LogRecordListener listener) { + resolve(selectMethod(PlainJUnit4TestCaseWithFiveTestMethods.class, "successfulTest")); + + assertThat(listener.stream(RunnerTestDescriptor.class)).isEmpty(); + } + + @Test + void doesNotLogAnythingForNonFilterableRunnerIfNoFiltersAreToBeApplied(LogRecordListener listener) { + resolve(selectClass(IgnoredJUnit4TestCase.class)); + + assertThat(listener.stream(RunnerTestDescriptor.class)).isEmpty(); + } + + @Test + void logsWarningOnNonFilterableRunner(LogRecordListener listener) { + Class testClass = IgnoredJUnit4TestCaseWithNotFilterableRunner.class; + + resolve(selectMethod(testClass, "someTest")); + + // @formatter:off + assertThat(listener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) + .containsOnlyOnce("Runner " + NotFilterableRunner.class.getName() + + " (used on class " + testClass.getName() + ") does not support filtering" + + " and will therefore be run completely."); + // @formatter:on + } + + private void resolve(DiscoverySelector selector) { + var request = LauncherDiscoveryRequestBuilder.request().selectors(selector).listeners( + mock(LauncherDiscoveryListener.class)).build(); + TestDescriptor engineDescriptor = new VintageDiscoverer().discover(request, VintageUniqueIdBuilder.engineId()); + var runnerTestDescriptor = (RunnerTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); + new RunnerTestDescriptorPostProcessor().applyFiltersAndCreateDescendants(runnerTestDescriptor); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java new file mode 100644 index 00000000..754c34b0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; +import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.vintage.engine.VintageUniqueIdBuilder.engineId; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.SelectorResolutionResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.PackageNameFilter; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.vintage.engine.VintageUniqueIdBuilder; +import org.junit.vintage.engine.samples.junit3.AbstractJUnit3TestCase; +import org.junit.vintage.engine.samples.junit4.AbstractJunit4TestCaseWithConstructorParameter; +import org.mockito.ArgumentCaptor; + +/** + * Tests for {@link VintageDiscoverer}. + * + * @since 4.12 + */ +class VintageDiscovererTests { + + @Test + void classNameFilterExcludesClass() { + // @formatter:off + EngineDiscoveryRequest request = request() + .selectors(selectClass(Foo.class), selectClass(Bar.class)) + .filters(ClassNameFilter.includeClassNamePatterns(".*Foo")) + .build(); + // @formatter:on + + var testDescriptor = discover(request); + + assertThat(testDescriptor.getChildren()).hasSize(1); + assertThat(getOnlyElement(testDescriptor.getChildren()).getUniqueId().toString()).contains(Foo.class.getName()); + } + + @Test + void packageNameFilterExcludesClasses() { + // @formatter:off + EngineDiscoveryRequest request = request() + .selectors(selectClass(Foo.class), selectClass(Bar.class)) + .filters(PackageNameFilter.excludePackageNames("org.junit.vintage.engine.discovery")) + .build(); + // @formatter:on + + var testDescriptor = discover(request); + + assertThat(testDescriptor.getChildren()).isEmpty(); + } + + @Test + void doesNotResolveAbstractJUnit3Classes() { + doesNotResolve(selectClass(AbstractJUnit3TestCase.class)); + } + + @Test + void doesNotResolveAbstractJUnit4Classes() { + doesNotResolve(selectClass(AbstractJunit4TestCaseWithConstructorParameter.class)); + } + + @Test + void failsToResolveUnloadableTestClass() { + var uniqueId = VintageUniqueIdBuilder.uniqueIdForClass("foo.bar.UnknownClass"); + + doesNotResolve(selectUniqueId(uniqueId), result -> { + assertThat(result.getStatus()).isEqualTo(FAILED); + assertThat(result.getThrowable().get()).hasMessageContaining("Unknown class"); + }); + } + + @Test + void ignoresUniqueIdsOfOtherEngines() { + doesNotResolve(selectUniqueId(UniqueId.forEngine("someEngine"))); + } + + private void doesNotResolve(DiscoverySelector selector) { + doesNotResolve(selector, result -> assertThat(result.getStatus()).isEqualTo(UNRESOLVED)); + } + + private void doesNotResolve(DiscoverySelector selector, Consumer resultCheck) { + var discoveryListener = mock(LauncherDiscoveryListener.class); + var request = request() // + .selectors(selector) // + .listeners(discoveryListener) // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .build(); + + var testDescriptor = discover(request); + + assertThat(testDescriptor.getChildren()).isEmpty(); + var resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); + verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-vintage")), eq(selector), + resultCaptor.capture()); + resultCheck.accept(resultCaptor.getValue()); + } + + private TestDescriptor discover(EngineDiscoveryRequest request) { + return new VintageDiscoverer().discover(request, engineId()); + } + + public static class Foo { + + @org.junit.Test + public void test() { + } + + } + + public static class Bar { + + @org.junit.Test + public void test() { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java new file mode 100644 index 00000000..69272fd5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.runner.Description.createTestDescription; +import static org.junit.vintage.engine.VintageUniqueIdBuilder.engineId; +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_DYNAMIC; +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; + +import org.junit.jupiter.api.Test; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; +import org.junit.vintage.engine.descriptor.VintageTestDescriptor; +import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; + +/** + * @since 4.12 + */ +class TestRunTests { + + @Test + void returnsEmptyOptionalForUnknownDescriptions() throws Exception { + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); + var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); + var unknownDescription = createTestDescription(testClass, "dynamicTest"); + + var testRun = new TestRun(runnerTestDescriptor); + var testDescriptor = testRun.lookupNextTestDescriptor(unknownDescription); + + assertThat(testDescriptor).isEmpty(); + } + + @Test + void registersDynamicTestDescriptors() throws Exception { + Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; + var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); + var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); + var dynamicTestId = runnerId.append(SEGMENT_TYPE_DYNAMIC, "dynamicTest"); + var dynamicDescription = createTestDescription(testClass, "dynamicTest"); + var dynamicTestDescriptor = new VintageTestDescriptor(dynamicTestId, dynamicDescription, null); + + var testRun = new TestRun(runnerTestDescriptor); + testRun.registerDynamicTest(dynamicTestDescriptor); + + assertThat(testRun.lookupNextTestDescriptor(dynamicDescription)).contains(dynamicTestDescriptor); + assertTrue(testRun.isDescendantOfRunnerTestDescriptor(dynamicTestDescriptor)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java new file mode 100644 index 00000000..3ea9cca2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.runner.Description.createTestDescription; + +import java.util.logging.Level; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; + +/** + * Tests for {@link UniqueIdReader}. + * + * @since 4.12 + */ +@TrackLogRecords +class UniqueIdReaderTests { + + @Test + void readsUniqueId(LogRecordListener listener) { + var description = createTestDescription("ClassName", "methodName", "uniqueId"); + + var uniqueId = new UniqueIdReader().apply(description); + + assertEquals("uniqueId", uniqueId); + assertThat(listener.stream(UniqueIdReader.class)).isEmpty(); + } + + @Test + void returnsDisplayNameWhenUniqueIdCannotBeRead(LogRecordListener listener) { + var description = createTestDescription("ClassName", "methodName", "uniqueId"); + assertEquals("methodName(ClassName)", description.getDisplayName()); + + var uniqueId = new UniqueIdReader("wrongFieldName").apply(description); + + assertEquals(description.getDisplayName(), uniqueId); + + var logRecord = listener.stream(UniqueIdReader.class, Level.WARNING).findFirst(); + assertThat(logRecord).isPresent(); + assertThat(logRecord.get().getMessage()).isEqualTo( + "Could not read unique ID for Description; using display name instead: " + description.getDisplayName()); + assertThat(logRecord.get().getThrown()).isInstanceOf(NoSuchFieldException.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java new file mode 100644 index 00000000..6ec8e41a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.Base64; + +import org.junit.jupiter.api.Test; + +/** + * @since 4.12 + */ +class UniqueIdStringifierTests { + + @Test + void returnsReadableStringForKnownTypes() { + var stringifier = new UniqueIdStringifier(); + + assertEquals("foo", stringifier.apply("foo")); + assertEquals("42", stringifier.apply(42)); + assertEquals("42", stringifier.apply(42L)); + assertEquals("42.23", stringifier.apply(42.23d)); + } + + @Test + void serializesUnknownTypes() throws Exception { + var stringifier = new UniqueIdStringifier(); + + var serialized = stringifier.apply(new MyCustomId(42)); + + var deserializedObject = deserialize(decodeBase64(serialized)); + assertThat(deserializedObject).isInstanceOf(MyCustomId.class); + assertEquals(42, ((MyCustomId) deserializedObject).getValue()); + } + + @Test + void usesToStringWhenSerializationFails() { + var stringifier = new UniqueIdStringifier(); + var serialized = stringifier.apply(new ClassWithErroneousSerialization()); + + var deserializedString = new String(decodeBase64(serialized), UniqueIdStringifier.CHARSET); + + assertEquals("value from toString()", deserializedString); + } + + private byte[] decodeBase64(String value) { + return Base64.getDecoder().decode(value.getBytes(UniqueIdStringifier.CHARSET)); + } + + private Object deserialize(byte[] bytes) throws Exception { + try (var inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes))) { + return inputStream.readObject(); + } + } + + private static class MyCustomId implements Serializable { + + private static final long serialVersionUID = 1L; + + private final int value; + + MyCustomId(int value) { + this.value = value; + } + + int getValue() { + return value; + } + + } + + private static class ClassWithErroneousSerialization implements Serializable { + + private static final long serialVersionUID = 1L; + + Object writeReplace() throws ObjectStreamException { + throw new InvalidObjectException("failed on purpose"); + } + + @Override + public String toString() { + return "value from toString()"; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..063e4159 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy new file mode 100644 index 00000000..8abfe8d4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy @@ -0,0 +1,20 @@ +package org.junit.vintage.engine.samples.spock + +import spock.lang.Specification +import spock.lang.Unroll + +class SpockTestCaseWithUnrolledAndRegularFeatureMethods extends Specification { + + @Unroll + def "unrolled feature for #input"() { + expect: + input == 42 + where: + input << [23, 42] + } + + def "regular"() { + expect: + true + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java new file mode 100644 index 00000000..cad6044a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples; + +/** + * @since 4.12 + */ +public class PlainOldJavaClassWithoutAnyTestsTestCase { + + public void doSomething() { + // no-op + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java new file mode 100644 index 00000000..ec7f01e9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit3; + +import junit.framework.TestCase; + +import org.junit.Assert; + +/** + * @since 4.12 + */ +public abstract class AbstractJUnit3TestCase extends TestCase { + + public void test() { + Assert.fail("this test should not be run"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java new file mode 100644 index 00000000..0171e86e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit3; + +import junit.extensions.ActiveTestSuite; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class JUnit3ParallelSuiteWithSubsuites extends TestCase { + private final String arg; + + public JUnit3ParallelSuiteWithSubsuites(String name, String arg) { + super(name); + this.arg = arg; + } + + public void hello() { + assertNotNull(arg); + } + + public static TestSuite suite() { + TestSuite root = new ActiveTestSuite("allTests"); + var case1 = new TestSuite("Case1"); + case1.addTest(new JUnit3ParallelSuiteWithSubsuites("hello", "world")); + root.addTest(case1); + var case2 = new TestSuite("Case2"); + case2.addTest(new JUnit3ParallelSuiteWithSubsuites("hello", "WORLD")); + root.addTest(case2); + return root; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java new file mode 100644 index 00000000..a253e2f4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit3; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * @since 4.12 + */ +public class JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails extends TestCase { + + public static junit.framework.Test suite() { + var suite = new TestSuite(); + suite.addTestSuite(PlainJUnit3TestCaseWithSingleTestWhichFails.class); + return suite; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java new file mode 100644 index 00000000..cb070d77 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit3; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class JUnit3SuiteWithSubsuites extends TestCase { + private final String arg; + + public JUnit3SuiteWithSubsuites(String name, String arg) { + super(name); + this.arg = arg; + } + + public void hello() { + assertNotNull(arg); + } + + public static TestSuite suite() { + var root = new TestSuite("allTests"); + var case1 = new TestSuite("Case1"); + case1.addTest(new JUnit3SuiteWithSubsuites("hello", "world")); + root.addTest(case1); + var case2 = new TestSuite("Case2"); + case2.addTest(new JUnit3SuiteWithSubsuites("hello", "WORLD")); + root.addTest(case2); + return root; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java new file mode 100644 index 00000000..17918c65 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit3; + +import junit.framework.TestCase; + +import org.junit.Assert; + +/** + * @since 4.12 + */ +public class PlainJUnit3TestCaseWithSingleTestWhichFails extends TestCase { + + public void test() { + Assert.fail("this test should fail"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java new file mode 100644 index 00000000..6c18d8fb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.Test; + +public abstract class AbstractJUnit4TestCase { + + @Test + public void theTest() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java new file mode 100644 index 00000000..b25e1180 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.Test; + +public abstract class AbstractJunit4TestCaseWithConstructorParameter { + + public AbstractJunit4TestCaseWithConstructorParameter(int parameter) { + + } + + @Test + public void test() { + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java new file mode 100644 index 00000000..9cc57948 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +/** + * @since 4.12 + */ +public class Categories { + + public interface Plain { + } + + public interface Failing { + } + + public interface Skipped { + } + + public interface SkippedWithReason extends Skipped { + } + + public interface Successful { + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java new file mode 100644 index 00000000..2331a56b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; + +/** + * Simulates a Spock 1.x test with only {@code @Unroll} feature methods. + */ +@RunWith(DynamicRunner.class) +@ChildCount(1) +public class CompletelyDynamicTestCase { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java new file mode 100644 index 00000000..e659b595 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java @@ -0,0 +1,14 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +public class ConcreteJUnit4TestCase extends AbstractJUnit4TestCase { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java new file mode 100644 index 00000000..47400e94 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static java.util.stream.IntStream.range; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; + +/** + * @since 5.1 + */ +abstract class ConfigurableRunner extends Runner { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface ChildCount { + + int value(); + + } + + protected final Class testClass; + protected final List filteredChildren = new ArrayList<>(); + + ConfigurableRunner(Class testClass) { + this.testClass = testClass; + var childCountAnnotation = testClass.getAnnotation(ChildCount.class); + int childCount = Optional.ofNullable(childCountAnnotation).map(ChildCount::value).orElse(0); + // @formatter:off + range(0, childCount) + .mapToObj(index -> Description.createTestDescription(testClass, "Test #" + index)) + .forEach(filteredChildren::add); + // @formatter:on + } + + @Override + public Description getDescription() { + var suiteDescription = Description.createSuiteDescription(testClass); + filteredChildren.forEach(suiteDescription::addChild); + return suiteDescription; + } + + @Override + public void run(RunNotifier notifier) { + filteredChildren.forEach(child -> { + notifier.fireTestStarted(child); + notifier.fireTestFinished(child); + }); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java new file mode 100644 index 00000000..20f3238a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; + +public class DynamicRunner extends ConfigurableRunner implements Filterable { + + public DynamicRunner(Class testClass) { + super(testClass); + } + + @Override + public Description getDescription() { + return Description.createSuiteDescription(testClass); + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + filteredChildren.removeIf(each -> !filter.shouldRun(each)); + if (filteredChildren.isEmpty()) { + throw new NoTestsRemainException(); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java new file mode 100644 index 00000000..69f53e2d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java @@ -0,0 +1,17 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.Ignore; + +@Ignore("empty") +public class EmptyIgnoredTestCase { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java new file mode 100644 index 00000000..dec70cc9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +/** + * @since 4.12 + */ +@RunWith(Enclosed.class) +public class EnclosedJUnit4TestCase { + + @Category(Categories.Plain.class) + public static class NestedClass { + + @Test + @Category(Categories.Failing.class) + public void failingTest() { + fail("this test should fail"); + } + + @Test + public void successfulTest() { + assertEquals(3, 1 + 2); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java new file mode 100644 index 00000000..dce4f50c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// Source: https://github.com/junit-team/junit5/issues/3083 +@RunWith(Enclosed.class) +public class EnclosedWithParameterizedChildrenJUnit4TestCase { + + @RunWith(Parameterized.class) + public static class NestedTestCase1 { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[] { 1, 2 }, new Object[] { 3, 4 }); + } + + @SuppressWarnings("unused") + public NestedTestCase1(final int a, final int b) { + } + + @Test + public void test() { + } + } + + @RunWith(Parameterized.class) + public static class NestedTestCase2 { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[] { 1, 2 }, new Object[] { 3, 4 }); + } + + @SuppressWarnings("unused") + public NestedTestCase2(final int a, final int b) { + } + + @Test + public void test() { + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java new file mode 100644 index 00000000..146d659c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.notification.RunNotifier; + +/** + * @since 4.12 + */ +public class ExceptionThrowingRunner extends ConfigurableRunner { + + public ExceptionThrowingRunner(Class testClass) { + super(testClass); + } + + @Override + public void run(RunNotifier notifier) { + throw new RuntimeException("Simulated exception in custom runner for " + testClass.getName()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java new file mode 100644 index 00000000..19d53de5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; +import static org.junit.runners.MethodSorters.NAME_ASCENDING; + +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; + +/** + * @since 4.12 + */ +@Ignore("complete class is ignored") +@FixMethodOrder(NAME_ASCENDING) +public class IgnoredJUnit4TestCase { + + @Test + public void failingTest() { + fail("this test is discovered, but skipped"); + } + + @Test + public void succeedingTest() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java new file mode 100644 index 00000000..dd19ad0a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.Ignore; + +/** + * @since 5.1 + */ +@Ignore +public class IgnoredJUnit4TestCaseWithNotFilterableRunner extends JUnit4TestCaseWithNotFilterableRunner { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java new file mode 100644 index 00000000..0c127b5b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * @since 5.4.1 + */ +@RunWith(Parameterized.class) +public class IgnoredParameterizedTestCase { + + @Parameters(name = "{0}") + public static Iterable parameters() { + return List.of("foo", "bar"); + } + + @Parameter + public String value; + + @Test + @Ignore + public void test() { + // never called + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java new file mode 100644 index 00000000..705f2b89 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * Test case used in {@link JUnit4ParameterizedTests}. + * + * @since 4.12 + */ +@RunWith(Parameterized.class) +public class JUnit4ParameterizedTestCase { + + @Parameters + public static Object[] data() { + return new Object[] { 1, 2, 3 }; + } + + public JUnit4ParameterizedTestCase(int i) { + } + + @Test + public void test1() { + fail("this test should fail"); + } + + @Test + public void endingIn_test1() { + fail("this test should fail"); + } + + @Test + public void test1_atTheBeginning() { + fail("this test should fail"); + } + + @Test + public void test2() { + /* always succeeds */ + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java new file mode 100644 index 00000000..5b8a1284 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 5.1 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit4TestCaseWithNotFilterableRunner.class) +public class JUnit4SuiteOfSuiteWithFilterableChildRunner { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java new file mode 100644 index 00000000..04c4aed2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit4SuiteWithIgnoredJUnit4TestCase.class) +public class JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java new file mode 100644 index 00000000..271cda18 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class) +public class JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java new file mode 100644 index 00000000..66b0273f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class) +public class JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java new file mode 100644 index 00000000..01dd3aeb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; + +/** + * @since 4.12 + */ +@RunWith(ExceptionThrowingRunner.class) +@ChildCount(1) +public class JUnit4SuiteWithExceptionThrowingRunner { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java new file mode 100644 index 00000000..c1f1279a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(IgnoredJUnit4TestCase.class) +public class JUnit4SuiteWithIgnoredJUnit4TestCase { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java new file mode 100644 index 00000000..aa4c4094 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class) +public class JUnit4SuiteWithJUnit3SuiteWithSingleTestCase { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java new file mode 100644 index 00000000..6f2e3673 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit4TestCaseWithAssumptionFailureInBeforeClass.class) +public class JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java new file mode 100644 index 00000000..56bc1bd2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit4TestCaseWithErrorInBeforeClass.class) +public class JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java new file mode 100644 index 00000000..7e4dc1b0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class, + PlainJUnit4TestCaseWithSingleTestWhichFails.class }) +public class JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java new file mode 100644 index 00000000..b784ffde --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 5.6.2 + */ +@RunWith(Suite.class) +@SuiteClasses(JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class) +public class JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java new file mode 100644 index 00000000..a6e65202 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses(PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class) +public class JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java new file mode 100644 index 00000000..4cf0eed1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @since 4.12 + */ +@RunWith(Suite.class) +@SuiteClasses({ PlainJUnit4TestCaseWithTwoTestMethods.class, PlainJUnit4TestCaseWithSingleTestWhichFails.class }) +public class JUnit4SuiteWithTwoTestCases { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java new file mode 100644 index 00000000..f06d3fe8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.AssumptionViolatedException; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * @since 4.12 + */ +public class JUnit4TestCaseWithAssumptionFailureInBeforeClass { + + @BeforeClass + public static void failingBeforeClass() { + throw new AssumptionViolatedException("assumption violated"); + } + + @Test + public void test() { + fail("this should never be called"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java new file mode 100644 index 00000000..10f77db2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.experimental.theories.Theories; +import org.junit.runner.RunWith; + +/** + * @since 5.5 + */ +@RunWith(Theories.class) +public class JUnit4TestCaseWithDistinguishableOverloadedMethod { + + @Test + public void test() { + test("foo"); + } + + @SuppressWarnings("SameParameterValue") + private void test(String message) { + fail(message); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java new file mode 100644 index 00000000..090e0401 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.StringContains.containsString; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +public class JUnit4TestCaseWithErrorCollectorStoringMultipleFailures { + @Rule + public ErrorCollector collector = new ErrorCollector(); + + @Test + public void example() { + collector.addError(new Throwable("first thing went wrong")); + collector.addError(new Throwable("second thing went wrong")); + collector.checkThat(getResult(), not(containsString("ERROR!"))); + // all lines will run, and then a combined failure logged at the end. + } + + private String getResult() { + return "This is an ERROR!"; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java new file mode 100644 index 00000000..2cb44e60 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; +import static org.junit.runners.MethodSorters.NAME_ASCENDING; + +import org.junit.AfterClass; +import org.junit.FixMethodOrder; +import org.junit.Test; + +/** + * @since 4.12 + */ +@FixMethodOrder(NAME_ASCENDING) +public class JUnit4TestCaseWithErrorInAfterClass { + + @AfterClass + public static void failingAfterClass() { + fail("error in @AfterClass"); + } + + @Test + public void failingTest() { + fail("expected to fail"); + } + + @Test + public void succeedingTest() { + // no-op + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java new file mode 100644 index 00000000..dd3afe5e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * @since 4.12 + */ +public class JUnit4TestCaseWithErrorInBeforeClass { + + @BeforeClass + public static void failingBeforeClass() { + fail("something went wrong"); + } + + @Test + public void test() { + fail("this should never be called"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java new file mode 100644 index 00000000..44329675 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.RunWith; +import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; + +/** + * @since 4.12 + */ +@RunWith(ExceptionThrowingRunner.class) +@ChildCount(0) +public class JUnit4TestCaseWithExceptionThrowingRunner { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java new file mode 100644 index 00000000..b1816d2a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(RunnerThatOnlyReportsFailures.class) +public class JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished { + @Test + public void testWithMissingEvents() { + fail("boom"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java new file mode 100644 index 00000000..6d4099db --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.experimental.theories.DataPoint; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; + +/** + * @since 4.12 + */ +@RunWith(Theories.class) +public class JUnit4TestCaseWithIndistinguishableOverloadedMethod { + + @DataPoint + public static int MAGIC_NUMBER = 42; + + @Theory + public void theory(int i) { + fail("failing theory with single parameter"); + } + + @Theory + public void theory(int i, int j) { + fail("failing theory with two parameters"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java new file mode 100644 index 00000000..9ece2415 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; + +/** + * @since 5.1 + */ +@RunWith(NotFilterableRunner.class) +@ChildCount(2) +@Category(Categories.Successful.class) +public class JUnit4TestCaseWithNotFilterableRunner { + + @Test + public void someTest() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java new file mode 100644 index 00000000..8906ce68 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @since 4.12 + */ +@Label("(TestClass)") +@RunWith(RunnerWithCustomUniqueIdsAndDisplayNames.class) +public class JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames { + + @Test + @Label("(TestMethod)") + public void test() { + Assert.fail(); + } + +} + +@Retention(RUNTIME) +@interface Label { + String value(); +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java new file mode 100644 index 00000000..aa039daf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.junit.runner.notification.RunNotifier; + +@RunWith(JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.Runner.class) +public class JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions { + public static class Runner extends org.junit.runner.Runner { + + private final Class testClass; + + public Runner(Class testClass) { + this.testClass = testClass; + } + + @Override + public Description getDescription() { + var suiteDescription = Description.createSuiteDescription(testClass); + suiteDescription.addChild(getContainerDescription("1st")); + suiteDescription.addChild(getContainerDescription("2nd")); + return suiteDescription; + } + + private Description getContainerDescription(String name) { + var parent = Description.createSuiteDescription(name); + parent.addChild(getLeafDescription()); + parent.addChild(getLeafDescription()); + return parent; + } + + private Description getLeafDescription() { + return Description.createTestDescription(testClass, "leaf"); + } + + @Override + public void run(RunNotifier notifier) { + for (var i = 0; i < 2; i++) { + notifier.fireTestIgnored(getLeafDescription()); + notifier.fireTestStarted(getLeafDescription()); + notifier.fireTestFinished(getLeafDescription()); + } + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java new file mode 100644 index 00000000..87480a21 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.Test; + +/** + * @since 4.12 + */ +public class MalformedJUnit4TestCase { + + @Test + @SuppressWarnings("TestMethodWithIncorrectSignature") // intentionally not public + void nonPublicTest() { + fail("this should never be called"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java new file mode 100644 index 00000000..4eb6bea4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +/** + * @since 5.1 + */ +public class NotFilterableRunner extends ConfigurableRunner { + + public NotFilterableRunner(Class testClass) { + super(testClass); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java new file mode 100644 index 00000000..ec094427 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * @since 4.12 + */ +@RunWith(Parameterized.class) +public class ParameterizedTestCase { + + @Parameters(name = "{0}") + public static Iterable parameters() { + return List.of("foo", "bar"); + } + + @Parameter + public String value; + + @Test + public void test() { + assertEquals("foo", value); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java new file mode 100644 index 00000000..a9905cbe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.assertEquals; + +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.AfterParam; +import org.junit.runners.Parameterized.BeforeParam; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * @since 5.9 + */ +@RunWith(Parameterized.class) +public class ParameterizedTimingTestCase { + + public static Map EVENTS = new LinkedHashMap<>(); + + @BeforeClass + public static void beforeClass() throws Exception { + EVENTS.clear(); + } + + @BeforeParam + public static void beforeParam(String param) throws Exception { + EVENTS.put("beforeParam(" + param + ")", Instant.now()); + Thread.sleep(100); + } + + @AfterParam + public static void afterParam(String param) throws Exception { + Thread.sleep(100); + System.out.println("ParameterizedTimingTestCase.afterParam"); + EVENTS.put("afterParam(" + param + ")", Instant.now()); + } + + @Parameters(name = "{0}") + public static Iterable parameters() { + return List.of("foo", "bar"); + } + + @Parameter + public String value; + + @Test + public void test() { + assertEquals("foo", value); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java new file mode 100644 index 00000000..a6852f99 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.AfterParam; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * @since 5.9 + */ +@RunWith(Parameterized.class) +public class ParameterizedWithAfterParamFailureTestCase { + + @AfterParam + public static void afterParam() { + fail(); + } + + @Parameters(name = "{0}") + public static Iterable parameters() { + return List.of("foo", "bar"); + } + + @Parameter + public String value; + + @Test + public void test() { + assertEquals("foo", value); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java new file mode 100644 index 00000000..515f8d4e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.BeforeParam; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * @since 5.9 + */ +@RunWith(Parameterized.class) +public class ParameterizedWithBeforeParamFailureTestCase { + + @BeforeParam + public static void beforeParam() { + fail(); + } + + @Parameters(name = "{0}") + public static Iterable parameters() { + return List.of("foo", "bar"); + } + + @Parameter + public String value; + + @Test + public void test() { + assertEquals("foo", value); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java new file mode 100644 index 00000000..08fe68db --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; +import static org.junit.runners.MethodSorters.NAME_ASCENDING; + +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.vintage.engine.samples.junit4.Categories.Failing; +import org.junit.vintage.engine.samples.junit4.Categories.Plain; +import org.junit.vintage.engine.samples.junit4.Categories.Skipped; +import org.junit.vintage.engine.samples.junit4.Categories.SkippedWithReason; + +/** + * @since 4.12 + */ +@FixMethodOrder(NAME_ASCENDING) +@Category(Plain.class) +public class PlainJUnit4TestCaseWithFiveTestMethods { + + @Test + public void abortedTest() { + assumeFalse("this test should be aborted", true); + } + + @Test + @Category(Failing.class) + public void failingTest() { + fail("this test should fail"); + } + + @Test + @Ignore + @Category(Skipped.class) + public void ignoredTest1_withoutReason() { + fail("this should never be called"); + } + + @Test + @Ignore("a custom reason") + @Category(SkippedWithReason.class) + public void ignoredTest2_withReason() { + fail("this should never be called"); + } + + @Test + public void successfulTest() { + assertEquals(3, 1 + 2); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java new file mode 100644 index 00000000..c629a217 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class PlainJUnit4TestCaseWithLifecycleMethods { + + public static final List EVENTS = new ArrayList<>(); + + @BeforeClass + public static void beforeClass() { + EVENTS.add("beforeClass"); + } + + @Before + public void before() { + EVENTS.add("before"); + } + + @Test + public void failingTest() { + EVENTS.add("failingTest"); + fail(); + } + + @Test + @Ignore("skipped") + public void skippedTest() { + EVENTS.add("this should never ever be executed because the test is skipped"); + } + + @Test + public void succeedingTest() { + EVENTS.add("succeedingTest"); + } + + @After + public void after() { + EVENTS.add("after"); + } + + @AfterClass + public static void afterClass() { + EVENTS.add("afterClass"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java new file mode 100644 index 00000000..6ac3601b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java @@ -0,0 +1,17 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +/** + * @since 4.12 + */ +public class PlainJUnit4TestCaseWithSingleInheritedTestWhichFails extends PlainJUnit4TestCaseWithSingleTestWhichFails { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java new file mode 100644 index 00000000..de6ac2b1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.fail; + +import org.junit.Test; + +/** + * @since 4.12 + */ +public class PlainJUnit4TestCaseWithSingleTestWhichFails { + + @Test + public void failingTest() { + fail("this test should fail"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java new file mode 100644 index 00000000..fb80eb65 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * @since 4.12 + */ +public class PlainJUnit4TestCaseWithSingleTestWhichIsIgnored { + + @Test + @Ignore("ignored test") + public void ignoredTest() { + Assert.fail("this should not be called"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java new file mode 100644 index 00000000..ffd2cdc0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.runners.MethodSorters.NAME_ASCENDING; + +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * @since 4.12 + */ +@FixMethodOrder(NAME_ASCENDING) +public class PlainJUnit4TestCaseWithTwoTestMethods { + + @Test + public void failingTest() { + fail("this test should fail"); + } + + @Test + @Category(Categories.Successful.class) + public void successfulTest() { + assertEquals(3, 1 + 2); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java new file mode 100644 index 00000000..dd94df32 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; + +public class RunnerThatOnlyReportsFailures extends BlockJUnit4ClassRunner { + public RunnerThatOnlyReportsFailures(Class klass) throws InitializationError { + super(klass); + } + + @Override + protected void runChild(FrameworkMethod method, RunNotifier notifier) { + var statement = methodBlock(method); + try { + statement.evaluate(); + } + catch (Throwable e) { + notifier.fireTestFailure(new Failure(describeChild(method), e)); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java new file mode 100644 index 00000000..1cf2140d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static org.junit.runner.Description.createTestDescription; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Objects; +import java.util.function.Supplier; + +import org.junit.runner.Description; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.Annotatable; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; + +/** + * @since 4.12 + */ +public class RunnerWithCustomUniqueIdsAndDisplayNames extends BlockJUnit4ClassRunner { + + public RunnerWithCustomUniqueIdsAndDisplayNames(Class klass) throws InitializationError { + super(klass); + } + + @Override + protected String getName() { + return getLabel(getTestClass(), super::getName); + } + + @Override + protected Description describeChild(FrameworkMethod method) { + var testName = testName(method); + return createTestDescription(getTestClass().getJavaClass().getName(), testName, new CustomUniqueId(testName)); + } + + @Override + protected String testName(FrameworkMethod method) { + return getLabel(method, () -> super.testName(method)); + } + + private String getLabel(Annotatable element, Supplier fallback) { + var label = element.getAnnotation(Label.class); + return label == null ? fallback.get() : label.value(); + } + + private static class CustomUniqueId implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private final String testName; + + public CustomUniqueId(String testName) { + this.testName = testName; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CustomUniqueId) { + var that = (CustomUniqueId) obj; + return Objects.equals(this.testName, that.testName); + } + return false; + } + + @Override + public int hashCode() { + return testName.hashCode(); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java new file mode 100644 index 00000000..1718b50a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.Assert; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; + +/** + * @since 4.12 + */ +@RunWith(Theories.class) +public class SingleFailingTheoryTestCase { + + @Theory + public void theory() { + Assert.fail("this theory should fail"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java new file mode 100644 index 00000000..e4074c2f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import org.junit.platform.runner.JUnitPlatform; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.runner.RunWith; + +/** + * @since 4.12 + */ +@SuppressWarnings("deprecation") +@RunWith(JUnitPlatform.class) +@SelectClasses(PlainJUnit4TestCaseWithSingleTestWhichFails.class) +public class TestCaseRunWithJUnitPlatformRunner { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts new file mode 100644 index 00000000..cca78db6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts @@ -0,0 +1,96 @@ +import org.gradle.api.tasks.PathSensitivity.NONE +import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.internal.os.OperatingSystem + +plugins { + `java-library-conventions` + `junit4-compatibility` + `testing-conventions` + id("me.champeau.jmh") +} + +dependencies { + // --- Things we are testing -------------------------------------------------- + testImplementation(projects.junitPlatformCommons) + testImplementation(projects.junitPlatformConsole) + testImplementation(projects.junitPlatformEngine) + testImplementation(projects.junitPlatformJfr) + testImplementation(projects.junitPlatformLauncher) + testImplementation(projects.junitPlatformSuiteCommons) + testImplementation(projects.junitPlatformSuiteEngine) + + // --- Things we are testing with --------------------------------------------- + testImplementation(projects.junitPlatformRunner) + testImplementation(projects.junitPlatformTestkit) + testImplementation(testFixtures(projects.junitPlatformCommons)) + testImplementation(testFixtures(projects.junitPlatformEngine)) + testImplementation(testFixtures(projects.junitPlatformLauncher)) + testImplementation(projects.junitJupiterEngine) + testImplementation(libs.apiguardian) + testImplementation(libs.jfrunit) { + exclude(group = "org.junit.vintage") + } + testImplementation(libs.joox) + testImplementation(libs.openTestReporting.tooling) + testImplementation(libs.bundles.xmlunit) + + // --- Test run-time dependencies --------------------------------------------- + testRuntimeOnly(projects.junitVintageEngine) + testRuntimeOnly(libs.groovy4) { + because("`ReflectionUtilsTests.findNestedClassesWithInvalidNestedClassFile` needs it") + } + + // --- https://openjdk.java.net/projects/code-tools/jmh/ ----------------------- + jmh(libs.jmh.core) + jmh(projects.junitJupiterApi) + jmh(libs.junit4) + jmhAnnotationProcessor(libs.jmh.generator.annprocess) +} + +jmh { + jmhVersion.set(libs.versions.jmh) + + duplicateClassesStrategy.set(DuplicatesStrategy.WARN) + fork.set(1) + warmupIterations.set(1) + iterations.set(5) +} + +tasks { + withType().configureEach { + useJUnitPlatform { + excludeTags("exclude") + } + jvmArgs("-Xmx1g") + distribution { + // Retry in a new JVM on Windows to improve chances of successful retries when + // cached resources are used (e.g. in ClasspathScannerTests) + retryInSameJvm.set(!OperatingSystem.current().isWindows) + } + } + test { + // Additional inputs for remote execution with Test Distribution + inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) + inputs.file(buildFile).withPathSensitivity(NONE) // for UniqueIdTrackingListenerIntegrationTests + } + test_4_12 { + useJUnitPlatform { + includeTags("junit4") + } + } + checkstyleJmh { // use same style rules as defined for tests + configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") + } +} + +eclipse { + classpath { + plusConfigurations.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) + } +} + +idea { + module { + scopes["PROVIDED"]!!["plus"]!!.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java new file mode 100644 index 00000000..97136ecd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.jmh; + +import org.junit.Assert; +import org.junit.jupiter.api.Assertions; +import org.openjdk.jmh.annotations.Benchmark; + +/** + * JMH benchmarks for assertions. + * + * @since 5.1 + */ +public class AssertionBenchmarks { + + @Benchmark + public void junit4_assertTrue_boolean() { + Assert.assertTrue(true); + } + + @Benchmark + public void junitJupiter_assertTrue_boolean() { + Assertions.assertTrue(true); + } + + @Benchmark + public void junit4_assertTrue_String_boolean() { + Assert.assertTrue("message", true); + } + + @Benchmark + public void junitJupiter_assertTrue_boolean_String() { + Assertions.assertTrue(true, "message"); + } + + @Benchmark + public void junitJupiter_assertTrue_boolean_Supplier() { + Assertions.assertTrue(true, () -> "message"); + } + + @Benchmark + public void junitJupiter_assertTrue_BooleanSupplier_String() { + Assertions.assertTrue(() -> true, "message"); + } + + @Benchmark + public void junitJupiter_assertTrue_BooleanSupplier_Supplier() { + Assertions.assertTrue(() -> true, () -> "message"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java new file mode 100644 index 00000000..8018c935 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java @@ -0,0 +1,28 @@ + +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Simple test case that is used to verify proper support for classpath scanning + * within the default package. + * + * @since 1.0 + */ +@Disabled("Only used reflectively by other tests") +class DefaultPackageTestCase { + + @Test + void test() { + // do nothing + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java new file mode 100644 index 00000000..714869b6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class OSTests { + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = { " ", "\t" }) + void blankOsNameYieldsNull(String name) { + assertNull(OS.parse(name)); + } + + @ParameterizedTest + @ValueSource(strings = { "!", "?", "✨", "MINIX" }) + void unknownOsNameYieldsOTHER(String name) { + assertEquals(OS.OTHER, OS.parse(name)); + } + + @Nested + class ValidNames { + @ParameterizedTest + @ValueSource(strings = { "AIX", "Aix", "LaIxOS" }) + void aix(String name) { + assertEquals(OS.AIX, OS.parse(name)); + } + + @ParameterizedTest + @ValueSource(strings = { "FREEBSD", "FreeBSD" }) + void freebsd(String name) { + assertEquals(OS.FREEBSD, OS.parse(name)); + } + + @ParameterizedTest + @ValueSource(strings = { "LINUX", "Linux" }) + void linux(String name) { + assertEquals(OS.LINUX, OS.parse(name)); + } + + @ParameterizedTest + @ValueSource(strings = { "MAC", "mac" }) + void mac(String name) { + assertEquals(OS.MAC, OS.parse(name)); + } + + @ParameterizedTest + @ValueSource(strings = { "OPENBSD", "OpenBSD" }) + void openbsd(String name) { + assertEquals(OS.OPENBSD, OS.parse(name)); + } + + @ParameterizedTest + @ValueSource(strings = { "SOLARIS", "SunOS" }) + void solaris(String name) { + assertEquals(OS.SOLARIS, OS.parse(name)); + } + + @ParameterizedTest + @ValueSource(strings = { "WINDOW", "Microsoft Windows [Version 10.?]" }) + void windows(String name) { + assertEquals(OS.WINDOWS, OS.parse(name)); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java new file mode 100644 index 00000000..dd2df97c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +public class Heavyweight implements ParameterResolver, BeforeEachCallback { + + @Override + public void beforeEach(ExtensionContext context) { + context.getStore(ExtensionContext.Namespace.GLOBAL).put("once", new CloseableOnlyOnceResource()); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) { + return Resource.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) { + var engineContext = context.getRoot(); + var store = engineContext.getStore(ExtensionContext.Namespace.GLOBAL); + var resource = store.getOrComputeIfAbsent(ResourceValue.class); + resource.usages.incrementAndGet(); + return resource; + } + + interface Resource { + String ID = "org.junit.jupiter.extensions.Heavyweight.Resource"; + + int usages(); + } + + /** + * Demo resource class. + * + *

The class implements interface {@link CloseableResource} + * and interface {@link AutoCloseable} to show and ensure that a single + * {@link ResourceValue#close()} method implementation is needed to comply + * with both interfaces. + */ + static class ResourceValue implements Resource, CloseableResource, AutoCloseable { + + static final AtomicInteger creations = new AtomicInteger(); + private final AtomicInteger usages = new AtomicInteger(); + + @SuppressWarnings("unused") // used via reflection + ResourceValue() { + // Open long-living resources here. + assertEquals(1, creations.incrementAndGet(), "Singleton pattern failure!"); + } + + @Override + public void close() { + // Close resources here. + assertEquals(9, usages.get(), "Usage counter mismatch!"); + } + + @Override + public int usages() { + return usages.get(); + } + } + + private static class CloseableOnlyOnceResource implements CloseableResource { + + private final AtomicBoolean closed = new AtomicBoolean(); + + @Override + public void close() { + assertTrue(closed.compareAndSet(false, true), "already closed!"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java new file mode 100644 index 00000000..2d316392 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Unit tests for {@link org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource} + * stored values. + * + * @since 1.1 + */ +class HeavyweightTests { + + @AfterAll + static void afterAll() { + Heavyweight.ResourceValue.creations.set(0); + } + + @Nested + @TestInstance(PER_CLASS) + @ExtendWith(Heavyweight.class) + @ResourceLock(Heavyweight.Resource.ID) + class Alpha { + + private int mark; + + @BeforeAll + void setMark(Heavyweight.Resource resource) { + assertTrue(resource.usages() > 0); + mark = resource.usages(); + } + + @TestFactory + Stream alpha1(Heavyweight.Resource resource) { + return Stream.of(dynamicTest("foo", () -> assertTrue(resource.usages() > 1))); + } + + @Test + void alpha2(Heavyweight.Resource resource) { + assertTrue(resource.usages() > 1); + } + + @Test + void alpha3(Heavyweight.Resource resource) { + assertTrue(resource.usages() > 1); + } + + @AfterAll + void checkMark(Heavyweight.Resource resource) { + assertEquals(mark, resource.usages() - 4); + } + + } + + @Nested + @TestInstance(PER_CLASS) + @ExtendWith(Heavyweight.class) + @ResourceLock(Heavyweight.Resource.ID) + class Beta { + + private int mark; + + @BeforeAll + void beforeAll(Heavyweight.Resource resource) { + assertTrue(resource.usages() > 0); + mark = resource.usages(); + } + + @BeforeEach + void beforeEach(Heavyweight.Resource resource) { + assertTrue(resource.usages() > 1); + } + + @Test + void beta(Heavyweight.Resource resource) { + assertTrue(resource.usages() > 2); + } + + @AfterAll + void afterAll(Heavyweight.Resource resource) { + assertEquals(mark, resource.usages() - 3); + } + + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java new file mode 100644 index 00000000..8fdd06ae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Abstract base class for unit tests that wish to test + * {@link Object#equals(Object)} and {@link Object#hashCode()}. + * + * @since 1.3 + */ +public abstract class AbstractEqualsAndHashCodeTests { + + protected final void assertEqualsAndHashCode(T equal1, T equal2, T different) { + assertThat(equal1).isNotNull(); + assertThat(equal2).isNotNull(); + assertThat(different).isNotNull(); + + assertThat(equal1).isNotSameAs(equal2); + assertThat(equal1).isNotEqualTo(null); + assertThat(equal1).isNotEqualTo(new Object()); + assertThat(equal1).isNotEqualTo(different); + assertThat(different).isNotEqualTo(equal1); + assertThat(different).isNotEqualTo(equal2); + assertThat(equal1.hashCode()).isNotEqualTo(different.hashCode()); + + assertThat(equal1).isEqualTo(equal1); + assertThat(equal1).isEqualTo(equal2); + assertThat(equal2).isEqualTo(equal1); + assertThat(equal1.hashCode()).isEqualTo(equal2.hashCode()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java new file mode 100644 index 00000000..48633071 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * Test suite for the JUnit Platform. + * + *

Logging Configuration

+ * + *

In order for our log4j2 configuration to be used in an IDE, you must + * set the following system property before running any tests — for + * example, in Run Configurations in Eclipse. + * + *

+ * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
+ * 
+ * + * @since 1.0 + */ +@Suite +@SelectPackages("org.junit.platform") +@IncludeClassNamePatterns(".*Tests?") +@IncludeEngines("junit-jupiter") +class JUnitPlatformTestSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java new file mode 100644 index 00000000..fc3e8f13 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; + +/** + * Test cases for default implementations of {@link org.junit.platform.engine.TestEngine}. + * + *

Note: the package {@code org.junit.platform} this class resides in is + * chosen on purpose. If it was in {@code org.junit.platform.engine} the default + * implementation will pick up values defined by the real Jupiter test engine. + * + * @since 1.1 + */ +class TestEngineTests { + + @Test + void defaults() { + TestEngine engine = new DefaultEngine(); + assertEquals(Optional.empty(), engine.getGroupId()); + assertEquals(Optional.empty(), engine.getArtifactId()); + assertEquals(Optional.of("DEVELOPMENT"), engine.getVersion()); + } + + private static class DefaultEngine implements TestEngine { + + @Override + public String getId() { + return getClass().getSimpleName(); + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + throw new UnsupportedOperationException("discover"); + } + + @Override + public void execute(ExecutionRequest request) { + throw new UnsupportedOperationException("execute"); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java new file mode 100644 index 00000000..611a30bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.annotation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +/** + * Integration tests that indirectly verify that the {@link Testable @Testable} + * annotation may be declared on any element type. + * + * @since 1.7 + */ +class TestableAnnotationTests { + + @Test + void testMethod() { + assertNotNull(new TestableEverywhere().toString()); + } + + @Testable + static class TestableEverywhere { + + @Testable + final int field = 0; + + @Testable + TestableEverywhere() { + } + + @Testable + void test(@Testable int parameter) { + @Testable + @SuppressWarnings("unused") + var var = "var"; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java new file mode 100644 index 00000000..52d466ff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.function; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.JUnitException; + +public class TryTests { + + @Test + void successfulTriesCanBeTransformed() throws Exception { + var success = Try.success("foo"); + + assertThat(success.get()).isEqualTo("foo"); + assertThat(success.getOrThrow(RuntimeException::new)).isEqualTo("foo"); + assertThat(success.toOptional()).contains("foo"); + + assertThat(success.andThen(v -> { + assertThat(v).isEqualTo("foo"); + return Try.success("bar"); + }).get()).isEqualTo("bar"); + assertThat(success.andThenTry(v -> { + assertThat(v).isEqualTo("foo"); + return "bar"; + }).get()).isEqualTo("bar"); + + assertThat(success.orElse(() -> fail("should not be called"))).isSameAs(success); + assertThat(success.orElseTry(() -> fail("should not be called"))).isSameAs(success); + + var value = new AtomicReference(); + assertThat(success.ifSuccess(value::set)).isSameAs(success); + assertThat(value.get()).isEqualTo("foo"); + assertThat(success.ifFailure(cause -> fail("should not be called"))).isSameAs(success); + } + + @Test + void failedTriesCanBeTransformed() throws Exception { + var cause = new JUnitException("foo"); + var failure = Try.failure(cause); + + assertThat(assertThrows(JUnitException.class, failure::get)).isSameAs(cause); + assertThat(assertThrows(RuntimeException.class, () -> failure.getOrThrow(RuntimeException::new))).isInstanceOf( + RuntimeException.class).hasCause(cause); + assertThat(failure.toOptional()).isEmpty(); + + assertThat(failure.andThen(v -> fail("should not be called"))).isSameAs(failure); + assertThat(failure.andThenTry(v -> fail("should not be called"))).isSameAs(failure); + + assertThat(failure.orElse(() -> Try.success("bar")).get()).isEqualTo("bar"); + assertThat(failure.orElseTry(() -> "bar").get()).isEqualTo("bar"); + + assertThat(failure.ifSuccess(v -> fail("should not be called"))).isSameAs(failure); + var exception = new AtomicReference(); + assertThat(failure.ifFailure(exception::set)).isSameAs(failure); + assertThat(exception.get()).isSameAs(cause); + } + + @Test + void successfulTriesCanStoreNull() throws Exception { + var success = Try.success(null); + assertThat(success.get()).isNull(); + assertThat(success.getOrThrow(RuntimeException::new)).isNull(); + assertThat(success.toOptional()).isEmpty(); + } + + @Test + void triesWithSameContentAreEqual() { + var cause = new Exception(); + Callable failingCallable = () -> { + throw cause; + }; + + var success = Try.call(() -> "foo"); + assertThat(success).isEqualTo(success).hasSameHashCodeAs(success); + assertThat(success).isEqualTo(Try.success("foo")); + assertThat(success).isNotEqualTo(Try.failure(cause)); + + var failure = Try.call(failingCallable); + assertThat(failure).isEqualTo(failure).hasSameHashCodeAs(failure); + assertThat(failure).isNotEqualTo(Try.success("foo")); + assertThat(failure).isEqualTo(Try.failure(cause)); + } + + @Test + void methodPreconditionsAreChecked() { + assertThrows(JUnitException.class, () -> Try.call(null)); + + var success = Try.success("foo"); + assertThrows(JUnitException.class, () -> success.andThen(null)); + assertThrows(JUnitException.class, () -> success.andThenTry(null)); + assertThrows(JUnitException.class, () -> success.ifSuccess(null)); + + var failure = Try.failure(new Exception()); + assertThrows(JUnitException.class, () -> failure.orElse(null)); + assertThrows(JUnitException.class, () -> failure.orElseTry(null)); + assertThrows(JUnitException.class, () -> failure.ifFailure(null)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java new file mode 100644 index 00000000..8675d038 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java @@ -0,0 +1,325 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Optional; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 1.0 + */ +class AnnotationSupportTests { + + @Test + void isAnnotatedPreconditions() { + var optional = Optional.of(Probe.class); + assertPreconditionViolationException("annotationType", () -> AnnotationSupport.isAnnotated(optional, null)); + assertPreconditionViolationException("annotationType", () -> AnnotationSupport.isAnnotated(Probe.class, null)); + } + + @Test + void isAnnotatedDelegates() { + var element = Probe.class; + var optional = Optional.of(element); + + assertEquals(AnnotationUtils.isAnnotated(optional, Tag.class), + AnnotationSupport.isAnnotated(optional, Tag.class)); + assertEquals(AnnotationUtils.isAnnotated(optional, Override.class), + AnnotationSupport.isAnnotated(optional, Override.class)); + + assertEquals(AnnotationUtils.isAnnotated(element, Tag.class), + AnnotationSupport.isAnnotated(element, Tag.class)); + assertEquals(AnnotationUtils.isAnnotated(element, Override.class), + AnnotationSupport.isAnnotated(element, Override.class)); + } + + @Test + void findAnnotationOnElementPreconditions() { + var optional = Optional.of(Probe.class); + assertPreconditionViolationException("annotationType", () -> AnnotationSupport.findAnnotation(optional, null)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotation(Probe.class, null)); + } + + @Test + void findAnnotationOnElementDelegates() { + var element = Probe.class; + var optional = Optional.of(element); + + assertEquals(AnnotationUtils.findAnnotation(optional, Tag.class), + AnnotationSupport.findAnnotation(optional, Tag.class)); + assertEquals(AnnotationUtils.findAnnotation(optional, Override.class), + AnnotationSupport.findAnnotation(optional, Override.class)); + + assertEquals(AnnotationUtils.findAnnotation(element, Tag.class), + AnnotationSupport.findAnnotation(element, Tag.class)); + assertEquals(AnnotationUtils.findAnnotation(element, Override.class), + AnnotationSupport.findAnnotation(element, Override.class)); + } + + @Test + void findAnnotationOnClassPreconditions() { + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotation(Probe.class, null, SearchOption.INCLUDE_ENCLOSING_CLASSES)); + assertPreconditionViolationException("SearchOption", + () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, null)); + } + + @Test + void findAnnotationOnClassDelegates() { + Class clazz = Probe.class; + assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false), + AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT)); + assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, true), + AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); + assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, false), + AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.DEFAULT)); + assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, true), + AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); + + clazz = Probe.InnerClass.class; + assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false), + AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT)); + assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, true), + AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); + assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, false), + AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.DEFAULT)); + assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, true), + AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); + } + + @Test + void findPublicAnnotatedFieldsPreconditions() { + assertPreconditionViolationException("Class", + () -> AnnotationSupport.findPublicAnnotatedFields(null, String.class, FieldMarker.class)); + assertPreconditionViolationException("fieldType", + () -> AnnotationSupport.findPublicAnnotatedFields(Probe.class, null, FieldMarker.class)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findPublicAnnotatedFields(Probe.class, String.class, null)); + } + + @Test + void findPublicAnnotatedFieldsDelegates() { + assertEquals(AnnotationUtils.findPublicAnnotatedFields(Probe.class, String.class, FieldMarker.class), + AnnotationSupport.findPublicAnnotatedFields(Probe.class, String.class, FieldMarker.class)); + assertEquals(AnnotationUtils.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class), + AnnotationSupport.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class)); + } + + @Test + void findAnnotatedMethodsPreconditions() { + assertPreconditionViolationException("Class", + () -> AnnotationSupport.findAnnotatedMethods(null, Tag.class, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotatedMethods(Probe.class, null, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("HierarchyTraversalMode", + () -> AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, null)); + } + + @Test + void findAnnotatedMethodsDelegates() { + assertEquals( + AnnotationUtils.findAnnotatedMethods(Probe.class, Tag.class, + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), + AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, HierarchyTraversalMode.TOP_DOWN)); + assertEquals( + AnnotationUtils.findAnnotatedMethods(Probe.class, Tag.class, + ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), + AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, HierarchyTraversalMode.BOTTOM_UP)); + + assertEquals( + AnnotationUtils.findAnnotatedMethods(Probe.class, Override.class, + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), + AnnotationSupport.findAnnotatedMethods(Probe.class, Override.class, HierarchyTraversalMode.TOP_DOWN)); + assertEquals( + AnnotationUtils.findAnnotatedMethods(Probe.class, Override.class, + ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), + AnnotationSupport.findAnnotatedMethods(Probe.class, Override.class, HierarchyTraversalMode.BOTTOM_UP)); + } + + @Test + void findRepeatableAnnotationsDelegates() throws Throwable { + var bMethod = Probe.class.getDeclaredMethod("bMethod"); + assertEquals(AnnotationUtils.findRepeatableAnnotations(bMethod, Tag.class), + AnnotationSupport.findRepeatableAnnotations(bMethod, Tag.class)); + Object expected = assertThrows(PreconditionViolationException.class, + () -> AnnotationUtils.findRepeatableAnnotations(bMethod, Override.class)); + Object actual = assertThrows(PreconditionViolationException.class, + () -> AnnotationSupport.findRepeatableAnnotations(bMethod, Override.class)); + assertSame(expected.getClass(), actual.getClass(), "expected same exception class"); + assertEquals(expected.toString(), actual.toString(), "expected equal exception toString representation"); + } + + @Test + void findRepeatableAnnotationsPreconditions() { + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findRepeatableAnnotations(Probe.class, null)); + } + + @Test + void findAnnotatedFieldsDelegates() { + assertEquals(AnnotationUtils.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true), + AnnotationSupport.findAnnotatedFields(Probe.class, FieldMarker.class)); + assertEquals(AnnotationUtils.findAnnotatedFields(Probe.class, Override.class, f -> true), + AnnotationSupport.findAnnotatedFields(Probe.class, Override.class)); + + assertEquals( + AnnotationUtils.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true, + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), + AnnotationSupport.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true, + HierarchyTraversalMode.TOP_DOWN)); + assertEquals( + AnnotationUtils.findAnnotatedFields(Probe.class, Override.class, f -> true, + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), + AnnotationSupport.findAnnotatedFields(Probe.class, Override.class, f -> true, + HierarchyTraversalMode.TOP_DOWN)); + } + + @Test + void findAnnotatedFieldsPreconditions() { + assertPreconditionViolationException("Class", + () -> AnnotationSupport.findAnnotatedFields(null, FieldMarker.class)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotatedFields(Probe.class, null)); + + assertPreconditionViolationException("Class", () -> AnnotationSupport.findAnnotatedFields(null, Override.class, + f -> true, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotatedFields(Probe.class, null, f -> true, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("HierarchyTraversalMode", + () -> AnnotationSupport.findAnnotatedFields(Probe.class, Override.class, f -> true, null)); + } + + @Test + void findAnnotatedFieldValuesForNonStaticFields() { + var instance = new Fields(); + + assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, Tag.class)).isEmpty(); + + assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class))// + .containsExactlyInAnyOrder("i1", "i2"); + } + + @Test + void findAnnotatedFieldValuesForStaticFields() { + assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, Tag.class)).isEmpty(); + + assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class))// + .containsExactlyInAnyOrder("s1", "s2"); + } + + @Test + void findAnnotatedFieldValuesForNonStaticFieldsByType() { + var instance = new Fields(); + + assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class, Number.class)).isEmpty(); + + assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class, String.class))// + .containsExactlyInAnyOrder("i1", "i2"); + } + + @Test + void findAnnotatedFieldValuesForStaticFieldsByType() { + assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class, Number.class)).isEmpty(); + + assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class, String.class))// + .containsExactlyInAnyOrder("s1", "s2"); + } + + @Test + void findAnnotatedFieldValuesPreconditions() { + assertPreconditionViolationException("instance", + () -> AnnotationSupport.findAnnotatedFieldValues((Object) null, FieldMarker.class)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotatedFieldValues(this, null)); + + assertPreconditionViolationException("Class", + () -> AnnotationSupport.findAnnotatedFieldValues(null, FieldMarker.class)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, null)); + + assertPreconditionViolationException("instance", + () -> AnnotationSupport.findAnnotatedFieldValues((Object) null, FieldMarker.class, Number.class)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotatedFieldValues(this, null, Number.class)); + assertPreconditionViolationException("fieldType", + () -> AnnotationSupport.findAnnotatedFieldValues(this, FieldMarker.class, null)); + + assertPreconditionViolationException("Class", + () -> AnnotationSupport.findAnnotatedFieldValues(null, FieldMarker.class, Number.class)); + assertPreconditionViolationException("annotationType", + () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, null, Number.class)); + assertPreconditionViolationException("fieldType", + () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, FieldMarker.class, null)); + } + + // ------------------------------------------------------------------------- + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @interface FieldMarker { + } + + @Tag("class-tag") + static class Probe { + + @FieldMarker + public static String publicAnnotatedStaticField = "static"; + + @FieldMarker + public String publicAnnotatedInstanceField = "instance"; + + @Tag("method-tag") + void aMethod() { + } + + @Tag("method-tag-1") + @Tag("method-tag-2") + void bMethod() { + } + + class InnerClass { + } + + } + + static class Fields { + + @FieldMarker + static String staticField1 = "s1"; + + @FieldMarker + static String staticField2 = "s2"; + + @FieldMarker + String instanceField1 = "i1"; + + @FieldMarker + String instanceField2 = "i2"; + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java new file mode 100644 index 00000000..05352fa9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; + +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ClassUtils; + +/** + * @since 1.1 + */ +class ClassSupportTests { + + @Test + void nullSafeToStringPreconditions() { + Function, ? extends String> mapper = null; + assertPreconditionViolationException("Mapping function", + () -> ClassSupport.nullSafeToString(mapper, String.class, List.class)); + } + + @Test + void nullSafeToStringDelegates() { + assertEquals(ClassUtils.nullSafeToString(String.class, List.class), + ClassSupport.nullSafeToString(String.class, List.class)); + + Function, String> classToStringMapper = aClass -> aClass.getSimpleName() + "-Test"; + assertEquals(ClassUtils.nullSafeToString(classToStringMapper, String.class, List.class), + ClassSupport.nullSafeToString(classToStringMapper, String.class, List.class)); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java new file mode 100644 index 00000000..9bc39a1a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java @@ -0,0 +1,243 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link ModifierSupport}. + * + * @since 1.4 + */ +class ModifierSupportTests { + + @Test + void isPublicPreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isPublic((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isPublic((Member) null)); + } + + @Classes + void isPublicDelegates(Class clazz) { + assertEquals(ReflectionUtils.isPublic(clazz), ModifierSupport.isPublic(clazz)); + } + + @Methods + void isPublicDelegates(Method method) { + assertEquals(ReflectionUtils.isPublic(method), ModifierSupport.isPublic(method)); + } + + @Test + void isPrivatePreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isPrivate((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isPrivate((Member) null)); + } + + @Classes + void isPrivateDelegates(Class clazz) { + assertEquals(ReflectionUtils.isPrivate(clazz), ModifierSupport.isPrivate(clazz)); + } + + @Methods + void isPrivateDelegates(Method method) { + assertEquals(ReflectionUtils.isPrivate(method), ModifierSupport.isPrivate(method)); + } + + @Test + void isNotPrivatePreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isNotPrivate((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isNotPrivate((Member) null)); + } + + @Classes + void isNotPrivateDelegates(Class clazz) { + assertEquals(ReflectionUtils.isNotPrivate(clazz), ModifierSupport.isNotPrivate(clazz)); + } + + @Methods + void isNotPrivateDelegates(Method method) { + assertEquals(ReflectionUtils.isNotPrivate(method), ModifierSupport.isNotPrivate(method)); + } + + @Test + void isAbstractPreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isAbstract((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isAbstract((Member) null)); + } + + @Classes + void isAbstractDelegates(Class clazz) { + assertEquals(ReflectionUtils.isAbstract(clazz), ModifierSupport.isAbstract(clazz)); + } + + @Methods + void isAbstractDelegates(Method method) { + assertEquals(ReflectionUtils.isAbstract(method), ModifierSupport.isAbstract(method)); + } + + @Test + void isStaticPreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isStatic((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isStatic((Member) null)); + } + + @Classes + void isStaticDelegates(Class clazz) { + assertEquals(ReflectionUtils.isStatic(clazz), ModifierSupport.isStatic(clazz)); + } + + @Methods + void isStaticDelegates(Method method) { + assertEquals(ReflectionUtils.isStatic(method), ModifierSupport.isStatic(method)); + } + + @Test + void isNotStaticPreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isNotStatic((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isNotStatic((Member) null)); + } + + @Classes + void isNotStaticDelegates(Class clazz) { + assertEquals(ReflectionUtils.isNotStatic(clazz), ModifierSupport.isNotStatic(clazz)); + } + + @Methods + void isNotStaticDelegates(Method method) { + assertEquals(ReflectionUtils.isNotStatic(method), ModifierSupport.isNotStatic(method)); + } + + @Test + void isFinalPreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isFinal((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isFinal((Member) null)); + } + + @Classes + void isFinalDelegates(Class clazz) { + assertEquals(ReflectionUtils.isFinal(clazz), ModifierSupport.isFinal(clazz)); + } + + @Methods + void isFinalDelegates(Method method) { + assertEquals(ReflectionUtils.isFinal(method), ModifierSupport.isFinal(method)); + } + + @Test + void isNotFinalPreconditions() { + assertPreconditionViolationException("Class", () -> ModifierSupport.isNotFinal((Class) null)); + assertPreconditionViolationException("Member", () -> ModifierSupport.isNotFinal((Member) null)); + } + + @Classes + void isNotFinalDelegates(Class clazz) { + assertEquals(ReflectionUtils.isNotFinal(clazz), ModifierSupport.isNotFinal(clazz)); + } + + @Methods + void isNotFinalDelegates(Method method) { + assertEquals(ReflectionUtils.isNotFinal(method), ModifierSupport.isNotFinal(method)); + } + + // ------------------------------------------------------------------------- + + // Intentionally non-static + public class PublicClass { + + public void publicMethod() { + } + } + + private class PrivateClass { + + @SuppressWarnings("unused") + private void privateMethod() { + } + } + + protected class ProtectedClass { + + @SuppressWarnings("unused") + protected void protectedMethod() { + } + } + + class PackageVisibleClass { + + @SuppressWarnings("unused") + void packageVisibleMethod() { + } + } + + abstract static class AbstractClass { + + abstract void abstractMethod(); + } + + static class StaticClass { + + static void staticMethod() { + } + } + + final class FinalClass { + + final void finalMethod() { + } + } + + // ------------------------------------------------------------------------- + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ParameterizedTest + @ValueSource(classes = { PublicClass.class, PrivateClass.class, ProtectedClass.class, PackageVisibleClass.class, + AbstractClass.class, StaticClass.class, FinalClass.class }) + @interface Classes { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ParameterizedTest + @MethodSource("methods") + @interface Methods { + } + + static Stream methods() throws Exception { + // @formatter:off + return Stream.of( + PublicClass.class.getMethod("publicMethod"), + PrivateClass.class.getDeclaredMethod("privateMethod"), + ProtectedClass.class.getDeclaredMethod("protectedMethod"), + PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"), + AbstractClass.class.getDeclaredMethod("abstractMethod"), + StaticClass.class.getDeclaredMethod("staticMethod"), + FinalClass.class.getDeclaredMethod("finalMethod") + ); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java new file mode 100644 index 00000000..bd7a3fe1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 1.4 + */ +class PreconditionAssertions { + + static void assertPreconditionViolationException(String name, Executable executable) { + var exception = assertThrows(PreconditionViolationException.class, executable); + assertEquals(name + " must not be null", exception.getMessage()); + } + + static void assertPreconditionViolationExceptionForString(String name, Executable executable) { + var exception = assertThrows(PreconditionViolationException.class, executable); + assertEquals(name + " must not be null or blank", exception.getMessage()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java new file mode 100644 index 00000000..f14ff26e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java @@ -0,0 +1,303 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; +import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationExceptionForString; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 1.0 + */ +class ReflectionSupportTests { + + private static final Predicate> allTypes = type -> true; + private static final Predicate allNames = name -> true; + private static final Predicate allMethods = name -> true; + private static final Predicate allFields = name -> true; + + static final String staticField = "static"; + final String instanceField = "instance"; + + @Test + @SuppressWarnings("deprecation") + void loadClassDelegates() { + assertEquals(ReflectionUtils.loadClass("-"), ReflectionSupport.loadClass("-")); + assertEquals(ReflectionUtils.loadClass("A"), ReflectionSupport.loadClass("A")); + assertEquals(ReflectionUtils.loadClass("java.nio.Bits"), ReflectionSupport.loadClass("java.nio.Bits")); + } + + @Test + @SuppressWarnings("deprecation") + void loadClassPreconditions() { + assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.loadClass(null)); + assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.loadClass("")); + } + + @Test + void tryToLoadClassDelegates() { + assertEquals(ReflectionUtils.tryToLoadClass("-").toOptional(), + ReflectionSupport.tryToLoadClass("-").toOptional()); + assertEquals(ReflectionUtils.tryToLoadClass("A").toOptional(), + ReflectionSupport.tryToLoadClass("A").toOptional()); + assertEquals(ReflectionUtils.tryToLoadClass("java.nio.Bits"), + ReflectionSupport.tryToLoadClass("java.nio.Bits")); + } + + @Test + void tryToLoadClassPreconditions() { + assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.tryToLoadClass(null)); + assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.tryToLoadClass("")); + } + + @TestFactory + List findAllClassesInClasspathRootDelegates() throws Throwable { + List tests = new ArrayList<>(); + List paths = new ArrayList<>(); + paths.add(Path.of(".").toRealPath()); + paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); + for (var path : paths) { + var root = path.toUri(); + var displayName = root.getPath(); + if (displayName.length() > 42) { + displayName = "..." + displayName.substring(displayName.length() - 42); + } + tests.add(DynamicTest.dynamicTest(displayName, + () -> assertEquals(ReflectionUtils.findAllClassesInClasspathRoot(root, allTypes, allNames), + ReflectionSupport.findAllClassesInClasspathRoot(root, allTypes, allNames)))); + } + return tests; + } + + @Test + void findAllClassesInClasspathRootPreconditions() { + var path = Path.of(".").toUri(); + assertPreconditionViolationException("root", + () -> ReflectionSupport.findAllClassesInClasspathRoot(null, allTypes, allNames)); + assertPreconditionViolationException("class predicate", + () -> ReflectionSupport.findAllClassesInClasspathRoot(path, null, allNames)); + assertPreconditionViolationException("name predicate", + () -> ReflectionSupport.findAllClassesInClasspathRoot(path, allTypes, null)); + } + + @Test + void findAllClassesInPackageDelegates() { + assertNotEquals(0, ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames).size()); + assertEquals(ReflectionUtils.findAllClassesInPackage("org.junit", allTypes, allNames), + ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames)); + } + + @Test + void findAllClassesInPackagePreconditions() { + assertPreconditionViolationExceptionForString("basePackageName", + () -> ReflectionSupport.findAllClassesInPackage(null, allTypes, allNames)); + assertPreconditionViolationException("class predicate", + () -> ReflectionSupport.findAllClassesInPackage("org.junit", null, allNames)); + assertPreconditionViolationException("name predicate", + () -> ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, null)); + } + + @Test + void findAllClassesInModuleDelegates() { + assertEquals(ReflectionUtils.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames), + ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames)); + } + + @Test + void findAllClassesInModulePreconditions() { + var exception = assertThrows(PreconditionViolationException.class, + () -> ReflectionSupport.findAllClassesInModule(null, allTypes, allNames)); + assertEquals("Module name must not be null or empty", exception.getMessage()); + assertPreconditionViolationException("class predicate", + () -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", null, allNames)); + assertPreconditionViolationException("name predicate", + () -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, null)); + } + + @Test + void newInstanceDelegates() { + assertEquals(ReflectionUtils.newInstance(String.class, "foo"), + ReflectionSupport.newInstance(String.class, "foo")); + } + + @Test + void newInstancePreconditions() { + assertPreconditionViolationException("Class", () -> ReflectionSupport.newInstance(null)); + assertPreconditionViolationException("Argument array", + () -> ReflectionSupport.newInstance(String.class, (Object[]) null)); + assertPreconditionViolationException("Individual arguments", + () -> ReflectionSupport.newInstance(String.class, new Object[] { null })); + } + + @Test + void invokeMethodDelegates() throws Exception { + var method = Boolean.class.getMethod("valueOf", String.class); + assertEquals(ReflectionUtils.invokeMethod(method, null, "true"), + ReflectionSupport.invokeMethod(method, null, "true")); + } + + @Test + void invokeMethodPreconditions() throws Exception { + assertPreconditionViolationException("Method", () -> ReflectionSupport.invokeMethod(null, null, "true")); + + var method = Boolean.class.getMethod("toString"); + var exception = assertThrows(PreconditionViolationException.class, + () -> ReflectionSupport.invokeMethod(method, null)); + assertEquals("Cannot invoke non-static method [" + method.toGenericString() + "] on a null target.", + exception.getMessage()); + } + + @Test + void findFieldsDelegates() { + assertEquals( + ReflectionUtils.findFields(ReflectionSupportTests.class, allFields, + ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), + ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.BOTTOM_UP)); + assertEquals( + ReflectionUtils.findFields(ReflectionSupportTests.class, allFields, + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), + ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.TOP_DOWN)); + } + + @Test + void findFieldsPreconditions() { + assertPreconditionViolationException("Class", + () -> ReflectionSupport.findFields(null, allFields, HierarchyTraversalMode.BOTTOM_UP)); + assertPreconditionViolationException("Class", + () -> ReflectionSupport.findFields(null, allFields, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("Predicate", + () -> ReflectionSupport.findFields(ReflectionSupportTests.class, null, HierarchyTraversalMode.BOTTOM_UP)); + assertPreconditionViolationException("Predicate", + () -> ReflectionSupport.findFields(ReflectionSupportTests.class, null, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("HierarchyTraversalMode", + () -> ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, null)); + } + + @Test + void tryToReadFieldValueDelegates() throws Exception { + var staticField = getClass().getDeclaredField("staticField"); + assertEquals(ReflectionUtils.tryToReadFieldValue(staticField, null), + ReflectionSupport.tryToReadFieldValue(staticField, null)); + + var instanceField = getClass().getDeclaredField("instanceField"); + assertEquals(ReflectionUtils.tryToReadFieldValue(instanceField, this), + ReflectionSupport.tryToReadFieldValue(instanceField, this)); + } + + @Test + void tryToReadFieldValuePreconditions() throws Exception { + assertPreconditionViolationException("Field", () -> ReflectionSupport.tryToReadFieldValue(null, this)); + + var instanceField = getClass().getDeclaredField("instanceField"); + Exception exception = assertThrows(PreconditionViolationException.class, + () -> ReflectionSupport.tryToReadFieldValue(instanceField, null)); + assertThat(exception)// + .hasMessageStartingWith("Cannot read non-static field")// + .hasMessageEndingWith("on a null instance."); + } + + @Test + void findMethodDelegates() { + assertEquals(ReflectionUtils.findMethod(Boolean.class, "valueOf", String.class.getName()), + ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class.getName())); + + assertEquals(ReflectionUtils.findMethod(Boolean.class, "valueOf", String.class), + ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class)); + } + + @Test + void findMethodPreconditions() { + assertPreconditionViolationException("Class", + () -> ReflectionSupport.findMethod(null, "valueOf", String.class.getName())); + assertPreconditionViolationExceptionForString("Method name", + () -> ReflectionSupport.findMethod(Boolean.class, "", String.class.getName())); + assertPreconditionViolationExceptionForString("Method name", + () -> ReflectionSupport.findMethod(Boolean.class, " ", String.class.getName())); + + assertPreconditionViolationException("Class", + () -> ReflectionSupport.findMethod(null, "valueOf", String.class)); + assertPreconditionViolationExceptionForString("Method name", + () -> ReflectionSupport.findMethod(Boolean.class, "", String.class)); + assertPreconditionViolationExceptionForString("Method name", + () -> ReflectionSupport.findMethod(Boolean.class, " ", String.class)); + assertPreconditionViolationException("Parameter types array", + () -> ReflectionSupport.findMethod(Boolean.class, "valueOf", (Class[]) null)); + assertPreconditionViolationException("Individual parameter types", + () -> ReflectionSupport.findMethod(Boolean.class, "valueOf", new Class[] { null })); + } + + @Test + void findMethodsDelegates() { + assertEquals( + ReflectionUtils.findMethods(ReflectionSupportTests.class, allMethods, + ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), + ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.BOTTOM_UP)); + assertEquals( + ReflectionUtils.findMethods(ReflectionSupportTests.class, allMethods, + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), + ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.TOP_DOWN)); + } + + @Test + void findMethodsPreconditions() { + assertPreconditionViolationException("Class", + () -> ReflectionSupport.findMethods(null, allMethods, HierarchyTraversalMode.BOTTOM_UP)); + assertPreconditionViolationException("Class", + () -> ReflectionSupport.findMethods(null, allMethods, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("Predicate", + () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, null, HierarchyTraversalMode.BOTTOM_UP)); + assertPreconditionViolationException("Predicate", + () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, null, HierarchyTraversalMode.TOP_DOWN)); + assertPreconditionViolationException("HierarchyTraversalMode", + () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, null)); + } + + @Test + void findNestedClassesDelegates() { + assertEquals(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic), + ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)); + } + + @Test + void findNestedClassesPreconditions() { + assertPreconditionViolationException("Class", + () -> ReflectionSupport.findNestedClasses(null, ReflectionUtils::isStatic)); + assertPreconditionViolationException("Predicate", + () -> ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, null)); + } + + static class ClassWithNestedClasses { + + class Nested1 { + } + + static class Nested2 { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java new file mode 100644 index 00000000..47849101 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java @@ -0,0 +1,967 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedMethods; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.util.AnnotationUtils.findPublicAnnotatedFields; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link AnnotationUtils}. + * + * @since 1.0 + */ +class AnnotationUtilsTests { + + private static final Predicate isStringField = field -> field.getType() == String.class; + + @Test + void findAnnotationForNullOptional() { + assertThat(findAnnotation((Optional) null, Annotation1.class)).isEmpty(); + } + + @Test + void findAnnotationForEmptyOptional() { + assertThat(findAnnotation(Optional.empty(), Annotation1.class)).isEmpty(); + } + + @Test + void findAnnotationForNullAnnotatedElement() { + assertThat(findAnnotation((AnnotatedElement) null, Annotation1.class)).isEmpty(); + } + + @Test + void findAnnotationOnClassWithoutAnnotation() { + assertThat(findAnnotation(Annotation1Class.class, Annotation2.class)).isNotPresent(); + } + + @Test + void findAnnotationIndirectlyPresentOnOptionalClass() { + Optional> optional = Optional.of(SubInheritedAnnotationClass.class); + assertThat(findAnnotation(optional, InheritedAnnotation.class)).isPresent(); + } + + @Test + void findAnnotationIndirectlyPresentOnClass() { + assertThat(findAnnotation(SubInheritedAnnotationClass.class, InheritedAnnotation.class)).isPresent(); + } + + /** + * Test for https://github.com/junit-team/junit5/issues/1133 + */ + @Test + void findInheritedAnnotationMetaPresentOnNonInheritedComposedAnnotationPresentOnSuperclass() { + assertThat(findAnnotation(SubNonInheritedCompositionOfInheritedAnnotationClass.class, + InheritedAnnotation.class)).isPresent(); + } + + @Test + void findAnnotationDirectlyPresentOnClass() { + assertThat(findAnnotation(Annotation1Class.class, Annotation1.class)).isPresent(); + } + + @Test + void findAnnotationMetaPresentOnClass() { + assertThat(findAnnotation(ComposedAnnotationClass.class, Annotation1.class)).isPresent(); + } + + /** + * Note: there is no findAnnotationIndirectlyMetaPresentOnMethod + * counterpart because the {@code @Inherited} annotation has no effect if + * the annotation type is used to annotate anything other than a class. + * + * @see Inherited + */ + @Test + void findAnnotationIndirectlyMetaPresentOnClass() { + assertThat(findAnnotation(SubInheritedComposedAnnotationClass.class, Annotation1.class)).isPresent(); + } + + @Test + void findAnnotationDirectlyPresentOnImplementedInterface() { + assertThat(findAnnotation(TestingTraitClass.class, Annotation1.class)).isPresent(); + } + + @Test + void findAnnotationMetaPresentOnImplementedInterface() { + assertThat(findAnnotation(ComposedTestingTraitClass.class, Annotation1.class)).isPresent(); + } + + @Test + void findAnnotationDirectlyPresentOnMethod() throws Exception { + var method = Annotation2Class.class.getDeclaredMethod("method"); + assertThat(findAnnotation(method, Annotation1.class)).isPresent(); + } + + @Test + void findAnnotationMetaPresentOnMethod() throws Exception { + var method = ComposedAnnotationClass.class.getDeclaredMethod("method"); + assertThat(findAnnotation(method, Annotation1.class)).isPresent(); + } + + @Test + void findAnnotationMetaPresentOnOptionalMethod() throws Exception { + var method = ComposedAnnotationClass.class.getDeclaredMethod("method"); + assertThat(findAnnotation(Optional.of(method), Annotation1.class)).isPresent(); + } + + @Test + void findAnnotationDirectlyPresentOnEnclosingClass() throws Exception { + Class clazz = Annotation1Class.InnerClass.class; + assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); + assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); + + clazz = Annotation1Class.InnerClass.InnerInnerClass.class; + assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); + assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); + + clazz = Annotation1Class.NestedClass.class; + assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); + assertThat(findAnnotation(clazz, Annotation1.class, true)).isNotPresent(); + } + + @Test + void findAnnotationMetaPresentOnEnclosingClass() throws Exception { + Class clazz = ComposedAnnotationClass.InnerClass.class; + assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); + assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); + + clazz = ComposedAnnotationClass.InnerClass.InnerInnerClass.class; + assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); + assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); + } + + @Test + void isAnnotatedForClassWithoutAnnotation() { + assertFalse(isAnnotated(Annotation1Class.class, Annotation2.class)); + } + + @Test + void isAnnotatedWhenIndirectlyPresentOnClass() { + assertTrue(isAnnotated(SubInheritedAnnotationClass.class, InheritedAnnotation.class)); + } + + @Test + void isAnnotatedWhenDirectlyPresentOnClass() { + assertTrue(isAnnotated(Annotation1Class.class, Annotation1.class)); + } + + @Test + void isAnnotatedWhenMetaPresentOnClass() { + assertTrue(isAnnotated(ComposedAnnotationClass.class, Annotation1.class)); + } + + @Test + void isAnnotatedWhenDirectlyPresentOnMethod() throws Exception { + assertTrue(isAnnotated(Annotation2Class.class.getDeclaredMethod("method"), Annotation1.class)); + } + + @Test + void isAnnotatedWhenMetaPresentOnMethod() throws Exception { + assertTrue(isAnnotated(ComposedAnnotationClass.class.getDeclaredMethod("method"), Annotation1.class)); + } + + @Test + void findRepeatableAnnotationsForNotRepeatableAnnotation() { + var exception = assertThrows(PreconditionViolationException.class, + () -> findRepeatableAnnotations(getClass(), Inherited.class)); + + assertThat(exception.getMessage()).isEqualTo(Inherited.class.getName() + " must be @Repeatable"); + } + + @Test + void findRepeatableAnnotationsForNullOptionalAnnotatedElement() { + assertThat(findRepeatableAnnotations((Optional) null, Tag.class)).isEmpty(); + } + + @Test + void findRepeatableAnnotationsForEmptyOptionalAnnotatedElement() { + assertThat(findRepeatableAnnotations(Optional.empty(), Tag.class)).isEmpty(); + } + + @Test + void findRepeatableAnnotationsForNullAnnotatedElement() { + assertThat(findRepeatableAnnotations((AnnotatedElement) null, Tag.class)).isEmpty(); + } + + @Test + void findRepeatableAnnotationsWithSingleTag() { + assertTagsFound(SingleTaggedClass.class, "a"); + } + + @Test + void findRepeatableAnnotationsWithSingleComposedTag() { + assertTagsFound(SingleComposedTaggedClass.class, "fast"); + } + + @Test + void findRepeatableAnnotationsWithSingleComposedTagOnImplementedInterface() { + assertTagsFound(TaggedInterfaceClass.class, "fast"); + } + + @Test + void findRepeatableAnnotationsWithLocalComposedTagAndComposedTagOnImplementedInterface() { + assertTagsFound(LocalTagOnTaggedInterfaceClass.class, "fast", "smoke"); + } + + @Test + void findRepeatableAnnotationsWithMultipleTags() { + assertTagsFound(MultiTaggedClass.class, "a", "b", "c"); + } + + @Test + void findRepeatableAnnotationsWithMultipleComposedTags() { + assertTagsFound(MultiComposedTaggedClass.class, "fast", "smoke"); + assertTagsFound(FastAndSmokyTaggedClass.class, "fast", "smoke"); + } + + @Test + void findRepeatableAnnotationsWithContainer() { + assertTagsFound(ContainerTaggedClass.class, "a", "b", "c", "d"); + } + + @Test + void findRepeatableAnnotationsWithComposedTagBeforeContainer() { + assertTagsFound(ContainerAfterComposedTaggedClass.class, "fast", "a", "b", "c"); + } + + private void assertTagsFound(Class clazz, String... tags) { + assertEquals(List.of(tags), + findRepeatableAnnotations(clazz, Tag.class).stream().map(Tag::value).collect(toList()), + () -> "Tags found for class " + clazz.getName()); + } + + @Test + void findInheritedRepeatableAnnotationsWithSingleAnnotationOnSuperclass() { + assertExtensionsFound(SingleExtensionClass.class, "a"); + assertExtensionsFound(SubSingleExtensionClass.class, "a"); + } + + @Test + void findInheritedRepeatableAnnotationsWithMultipleAnnotationsOnSuperclass() { + assertExtensionsFound(MultiExtensionClass.class, "a", "b", "c"); + assertExtensionsFound(SubMultiExtensionClass.class, "a", "b", "c", "x", "y", "z"); + } + + @Test + void findInheritedRepeatableAnnotationsWithContainerAnnotationOnSuperclass() { + assertExtensionsFound(ContainerExtensionClass.class, "a", "b", "c"); + assertExtensionsFound(SubContainerExtensionClass.class, "a", "b", "c", "x"); + } + + @Test + void findInheritedRepeatableAnnotationsWithSingleComposedAnnotation() { + assertExtensionsFound(SingleComposedExtensionClass.class, "foo"); + } + + /** + * @since 1.5 + */ + @Test + void findInheritedRepeatableAnnotationsWithComposedAnnotationsInNestedContainer() { + assertExtensionsFound(MultipleFoos1.class, "foo"); + assertExtensionsFound(MultipleFoos2.class, "foo"); + } + + @Test + void findInheritedRepeatableAnnotationsWithSingleComposedAnnotationOnSuperclass() { + assertExtensionsFound(SubSingleComposedExtensionClass.class, "foo"); + } + + @Test + void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotations() { + assertExtensionsFound(MultiComposedExtensionClass.class, "foo", "bar"); + } + + @Test + void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotationsOnSuperclass() { + assertExtensionsFound(SubMultiComposedExtensionClass.class, "foo", "bar"); + } + + @Test + void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotationsOnSuperclassAndLocalContainerAndComposed() { + assertExtensionsFound(ContainerPlusSubMultiComposedExtensionClass.class, "foo", "bar", "x", "y", "z"); + } + + private void assertExtensionsFound(Class clazz, String... tags) { + assertEquals(List.of(tags), + findRepeatableAnnotations(clazz, ExtendWith.class).stream().map(ExtendWith::value).collect(toList()), + () -> "Extensions found for class " + clazz.getName()); + } + + @Test + void findAnnotatedMethodsForNullClass() { + assertThrows(PreconditionViolationException.class, + () -> findAnnotatedMethods(null, Annotation1.class, TOP_DOWN)); + } + + @Test + void findAnnotatedMethodsForNullAnnotationType() { + assertThrows(PreconditionViolationException.class, + () -> findAnnotatedMethods(ClassWithAnnotatedMethods.class, null, TOP_DOWN)); + } + + @Test + void findAnnotatedMethodsForAnnotationThatIsNotPresent() { + assertThat(findAnnotatedMethods(ClassWithAnnotatedMethods.class, Fast.class, TOP_DOWN)).isEmpty(); + } + + @Test + void findAnnotatedMethodsForAnnotationOnMethodsInClassUsingHierarchyDownMode() throws Exception { + var method2 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method2"); + var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); + + var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation2.class, TOP_DOWN); + + assertThat(methods).containsOnly(method2, method3); + } + + @Test + void findAnnotatedMethodsForAnnotationOnMethodsInClassHierarchyUsingHierarchyUpMode() throws Exception { + var method1 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method1"); + var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); + var superMethod = SuperclassWithAnnotatedMethod.class.getDeclaredMethod("superMethod"); + + var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation1.class, BOTTOM_UP); + + assertEquals(3, methods.size()); + assertThat(methods.subList(0, 2)).containsOnly(method1, method3); + assertEquals(superMethod, methods.get(2)); + } + + @Test + void findAnnotatedMethodsForAnnotationUsedInClassAndSuperclassHierarchyDown() throws Exception { + var method1 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method1"); + var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); + var superMethod = SuperclassWithAnnotatedMethod.class.getDeclaredMethod("superMethod"); + + var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation1.class, TOP_DOWN); + + assertEquals(3, methods.size()); + assertEquals(superMethod, methods.get(0)); + assertThat(methods.subList(1, 3)).containsOnly(method1, method3); + } + + @Test + void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception { + var interfaceMethod = InterfaceWithAnnotatedDefaultMethod.class.getDeclaredMethod("interfaceMethod"); + + var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation3.class, BOTTOM_UP); + + assertThat(methods).containsExactly(interfaceMethod); + } + + // === findAnnotatedFields() =============================================== + + @Test + void findAnnotatedFieldsForNullClass() { + assertThrows(PreconditionViolationException.class, + () -> findAnnotatedFields(null, Annotation1.class, isStringField, TOP_DOWN)); + } + + @Test + void findAnnotatedFieldsForNullAnnotationType() { + assertThrows(PreconditionViolationException.class, + () -> findAnnotatedFields(ClassWithAnnotatedFields.class, null, isStringField, TOP_DOWN)); + } + + @Test + void findAnnotatedFieldsForNullPredicate() { + assertThrows(PreconditionViolationException.class, + () -> findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, null, TOP_DOWN)); + } + + @Test + void findAnnotatedFieldsForAnnotationThatIsNotPresent() { + assertThat(findAnnotatedFields(ClassWithAnnotatedFields.class, Fast.class, isStringField, TOP_DOWN)).isEmpty(); + } + + @Test + void findAnnotatedFieldsForAnnotationOnFieldsInClassUsingHierarchyDownMode() throws Exception { + var field2 = ClassWithAnnotatedFields.class.getDeclaredField("field2"); + var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); + + var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation2.class, isStringField, TOP_DOWN); + + assertThat(fields).containsOnly(field2, field3); + } + + @Test + void findAnnotatedFieldsForAnnotationOnFieldsInClassHierarchyUsingHierarchyUpMode() throws Exception { + var field1 = ClassWithAnnotatedFields.class.getDeclaredField("field1"); + var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); + var superField = SuperclassWithAnnotatedField.class.getDeclaredField("superField"); + + var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, isStringField, BOTTOM_UP); + + assertEquals(3, fields.size()); + assertThat(fields.subList(0, 2)).containsOnly(field1, field3); + assertEquals(superField, fields.get(2)); + } + + @Test + void findAnnotatedFieldsForAnnotationUsedInClassAndSuperclassHierarchyDown() throws Exception { + var field1 = ClassWithAnnotatedFields.class.getDeclaredField("field1"); + var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); + var superField = SuperclassWithAnnotatedField.class.getDeclaredField("superField"); + + var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, isStringField, TOP_DOWN); + + assertEquals(3, fields.size()); + assertEquals(superField, fields.get(0)); + assertThat(fields.subList(1, 3)).containsOnly(field1, field3); + } + + @Test + void findAnnotatedFieldsForAnnotationUsedInInterface() throws Exception { + var interfaceField = InterfaceWithAnnotatedField.class.getDeclaredField("interfaceField"); + + var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation3.class, isStringField, BOTTOM_UP); + + assertThat(fields).containsExactly(interfaceField); + } + + @Test + void findAnnotatedFieldsForShadowedFields() throws Exception { + Class clazz = ClassWithShadowedAnnotatedFields.class; + var interfaceField = clazz.getDeclaredField("interfaceField"); + var superField = clazz.getDeclaredField("superField"); + var field1 = clazz.getDeclaredField("field1"); + var field2 = clazz.getDeclaredField("field2"); + var field3 = clazz.getDeclaredField("field3"); + + assertThat(findShadowingAnnotatedFields(Annotation1.class)).containsExactly(superField, field1, field3); + assertThat(findShadowingAnnotatedFields(Annotation2.class)).containsExactly(field2, field3); + assertThat(findShadowingAnnotatedFields(Annotation3.class)).containsExactly(interfaceField); + } + + private List findShadowingAnnotatedFields(Class annotationType) { + return findAnnotatedFields(ClassWithShadowedAnnotatedFields.class, annotationType, isStringField); + } + + // === findPublicAnnotatedFields() ========================================= + + @Test + void findPublicAnnotatedFieldsForNullClass() { + assertThrows(PreconditionViolationException.class, + () -> findPublicAnnotatedFields(null, String.class, Annotation1.class)); + } + + @Test + void findPublicAnnotatedFieldsForNullFieldType() { + assertThrows(PreconditionViolationException.class, + () -> findPublicAnnotatedFields(getClass(), null, Annotation1.class)); + } + + @Test + void findPublicAnnotatedFieldsForNullAnnotationType() { + assertThrows(PreconditionViolationException.class, + () -> findPublicAnnotatedFields(getClass(), String.class, null)); + } + + @Test + void findPublicAnnotatedFieldsForPrivateField() { + var fields = findPublicAnnotatedFields(getClass(), Boolean.class, Annotation1.class); + assertNotNull(fields); + assertEquals(0, fields.size()); + } + + @Test + void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldOfWrongFieldType() { + var fields = findPublicAnnotatedFields(getClass(), BigDecimal.class, Annotation1.class); + assertNotNull(fields); + assertEquals(0, fields.size()); + } + + @Test + void findPublicAnnotatedFieldsForDirectlyAnnotatedField() { + var fields = findPublicAnnotatedFields(getClass(), String.class, Annotation1.class); + assertNotNull(fields); + assertIterableEquals(List.of("directlyAnnotatedField"), asNames(fields)); + } + + @Test + void findPublicAnnotatedFieldsForMetaAnnotatedField() { + var fields = findPublicAnnotatedFields(getClass(), Number.class, Annotation1.class); + assertNotNull(fields); + assertEquals(1, fields.size()); + assertIterableEquals(List.of("metaAnnotatedField"), asNames(fields)); + } + + @Test + void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldInInterface() { + var fields = findPublicAnnotatedFields(InterfaceWithAnnotatedFields.class, String.class, Annotation1.class); + assertNotNull(fields); + assertIterableEquals(List.of("foo"), asNames(fields)); + } + + @Test + void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldsInClassAndInterface() { + var fields = findPublicAnnotatedFields(ClassWithPublicAnnotatedFieldsFromInterface.class, String.class, + Annotation1.class); + assertNotNull(fields); + assertThat(asNames(fields)).containsExactlyInAnyOrder("foo", "bar"); + } + + private List asNames(List fields) { + return fields.stream().map(Field::getName).collect(toList()); + } + + // ------------------------------------------------------------------------- + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface AnnotationWithDefaultValue { + + String value() default "default"; + } + + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Annotation1 { + } + + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Annotation2 { + } + + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @interface Annotation3 { + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface InheritedAnnotation { + } + + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + @Annotation1 + @interface ComposedAnnotation { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Annotation1 + @Inherited + @interface InheritedComposedAnnotation { + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @InheritedAnnotation + // DO NOT make this @Inherited. + @interface NonInheritedCompositionOfInheritedAnnotation { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + // DO NOT make this @Inherited. + @interface Tags { + + Tag[] value(); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(Tags.class) + // DO NOT make this @Inherited. + @interface Tag { + + String value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Tag("fast") + @interface Fast { + } + + @Retention(RetentionPolicy.RUNTIME) + @Tag("smoke") + @interface Smoke { + } + + @Retention(RetentionPolicy.RUNTIME) + @Fast + @Smoke + @interface FastAndSmoky { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface Extensions { + + ExtendWith[] value(); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @Repeatable(Extensions.class) + @interface ExtendWith { + + String value(); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @ExtendWith("foo") + @Repeatable(FooExtensions.class) + @interface ExtendWithFoo { + + String info() default ""; + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface FooExtensions { + + ExtendWithFoo[] value(); + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + // Intentionally NOT @Inherited in order to ensure that the algorithm for + // findRepeatableAnnotations() in fact lives up to the claims in the + // Javadoc regarding searching for repeatable annotations with implicit + // "inheritance" if the repeatable annotation is @Inherited but the + // custom composed annotation is not @Inherited. + // @Inherited + @ExtendWith("bar") + @interface ExtendWithBar { + } + + @AnnotationWithDefaultValue + static class AnnotationWithDefaultValueClass { + } + + @Annotation1 + static class Annotation1Class { + class InnerClass { + class InnerInnerClass { + } + } + static class NestedClass { + } + } + + @Annotation2 + static class Annotation2Class { + + @Annotation1 + void method() { + } + } + + @InheritedAnnotation + static class InheritedAnnotationClass { + } + + static class SubInheritedAnnotationClass extends InheritedAnnotationClass { + } + + @ComposedAnnotation + static class ComposedAnnotationClass { + + @ComposedAnnotation + void method() { + } + + class InnerClass { + class InnerInnerClass { + } + } + } + + @InheritedComposedAnnotation + static class InheritedComposedAnnotationClass { + + @InheritedComposedAnnotation + void method() { + } + } + + static class SubInheritedComposedAnnotationClass extends InheritedComposedAnnotationClass { + } + + @NonInheritedCompositionOfInheritedAnnotation + static class NonInheritedCompositionOfInheritedAnnotationClass { + } + + static class SubNonInheritedCompositionOfInheritedAnnotationClass + extends NonInheritedCompositionOfInheritedAnnotationClass { + } + + @Annotation1 + interface TestingTrait { + } + + static class TestingTraitClass implements TestingTrait { + } + + @ComposedAnnotation + interface ComposedTestingTrait { + } + + static class ComposedTestingTraitClass implements ComposedTestingTrait { + } + + @Tag("a") + static class SingleTaggedClass { + } + + @Fast + static class SingleComposedTaggedClass { + } + + @Tag("a") + @Tag("b") + @Tag("c") + static class MultiTaggedClass { + } + + @Fast + @Smoke + static class MultiComposedTaggedClass { + } + + @FastAndSmoky + static class FastAndSmokyTaggedClass { + } + + @Fast + interface TaggedInterface { + } + + static class TaggedInterfaceClass implements TaggedInterface { + } + + @Smoke + static class LocalTagOnTaggedInterfaceClass implements TaggedInterface { + } + + @Tags({ @Tag("a"), @Tag("b"), @Tag("c") }) + @Tag("d") + static class ContainerTaggedClass { + } + + @Fast + @Tags({ @Tag("a"), @Tag("b"), @Tag("c") }) + static class ContainerAfterComposedTaggedClass { + } + + @ExtendWith("a") + static class SingleExtensionClass { + } + + static class SubSingleExtensionClass extends SingleExtensionClass { + } + + @ExtendWith("a") + @ExtendWith("b") + @ExtendWith("c") + static class MultiExtensionClass { + } + + @ExtendWith("x") + @ExtendWith("y") + @ExtendWith("b") // duplicates parent + @ExtendWith("z") + @ExtendWith("a") // duplicates parent + static class SubMultiExtensionClass extends MultiExtensionClass { + } + + @Extensions({ @ExtendWith("a"), @ExtendWith("b"), @ExtendWith("c"), @ExtendWith("a") }) + static class ContainerExtensionClass { + } + + @ExtendWith("x") + static class SubContainerExtensionClass extends ContainerExtensionClass { + } + + @ExtendWithFoo + static class SingleComposedExtensionClass { + } + + @ExtendWithFoo(info = "A") + @ExtendWithFoo(info = "B") + static class MultipleFoos1 { + } + + @FooExtensions({ @ExtendWithFoo(info = "A"), @ExtendWithFoo(info = "B") }) + static class MultipleFoos2 { + } + + static class SubSingleComposedExtensionClass extends SingleComposedExtensionClass { + } + + @ExtendWithFoo + @ExtendWithBar + static class MultiComposedExtensionClass { + } + + static class SubMultiComposedExtensionClass extends MultiComposedExtensionClass { + } + + @ExtendWith("x") + @Extensions({ @ExtendWith("y"), @ExtendWith("z") }) + @ExtendWithBar + static class ContainerPlusSubMultiComposedExtensionClass extends MultiComposedExtensionClass { + } + + interface InterfaceWithAnnotatedDefaultMethod { + + @Annotation3 + default void interfaceMethod() { + } + } + + static class SuperclassWithAnnotatedMethod { + + @Annotation1 + void superMethod() { + } + } + + static class ClassWithAnnotatedMethods extends SuperclassWithAnnotatedMethod + implements InterfaceWithAnnotatedDefaultMethod { + + @Annotation1 + void method1() { + } + + @Annotation2 + void method2() { + } + + @Annotation1 + @Annotation2 + void method3() { + } + } + + // ------------------------------------------------------------------------- + + interface InterfaceWithAnnotatedField { + + @Annotation3 + String interfaceField = "interface"; + } + + static class SuperclassWithAnnotatedField { + + @Annotation1 + String superField = "super"; + } + + static class ClassWithAnnotatedFields extends SuperclassWithAnnotatedField implements InterfaceWithAnnotatedField { + + @Annotation1 + protected Object field0 = "?"; + + @Annotation1 + protected String field1 = "foo"; + + @Annotation2 + protected String field2 = "bar"; + + @Annotation1 + @Annotation2 + protected String field3 = "baz"; + + } + + static class ClassWithShadowedAnnotatedFields extends ClassWithAnnotatedFields { + + @Annotation3 + String interfaceField = "interface-shadow"; + + @Annotation1 + String superField = "super-shadow"; + + @Annotation1 + protected String field1 = "foo-shadow"; + + @Annotation2 + protected String field2 = "bar-shadow"; + + @Annotation1 + @Annotation2 + protected String field3 = "baz-shadow"; + + } + + // ------------------------------------------------------------------------- + + @Annotation1 + private Boolean privateDirectlyAnnotatedField; + + @Annotation1 + public String directlyAnnotatedField; + + @ComposedAnnotation + public Integer metaAnnotatedField; + + interface InterfaceWithAnnotatedFields { + + @Annotation1 + String foo = "bar"; + + @Annotation1 + boolean wrongType = false; + } + + class ClassWithPublicAnnotatedFieldsFromInterface implements InterfaceWithAnnotatedFields { + + @Annotation1 + public String bar = "baz"; + + @Annotation1 + public boolean notAString = true; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java new file mode 100644 index 00000000..87d793a0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link ClassLoaderUtils}. + * + * @since 1.0 + */ +class ClassLoaderUtilsTests { + + @Test + void getDefaultClassLoaderWithExplicitContextClassLoader() { + var original = Thread.currentThread().getContextClassLoader(); + var mock = mock(ClassLoader.class); + Thread.currentThread().setContextClassLoader(mock); + try { + assertSame(mock, ClassLoaderUtils.getDefaultClassLoader()); + } + finally { + Thread.currentThread().setContextClassLoader(original); + } + } + + @Test + void getDefaultClassLoaderWithNullContextClassLoader() { + var original = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(null); + try { + assertSame(ClassLoader.getSystemClassLoader(), ClassLoaderUtils.getDefaultClassLoader()); + } + finally { + Thread.currentThread().setContextClassLoader(original); + } + } + + @Test + void getLocationFromNullFails() { + var exception = assertThrows(PreconditionViolationException.class, () -> ClassLoaderUtils.getLocation(null)); + assertEquals("object must not be null", exception.getMessage()); + } + + @Test + void getLocationFromVariousObjectsArePresent() { + assertTrue(ClassLoaderUtils.getLocation(void.class).isPresent()); + assertTrue(ClassLoaderUtils.getLocation(byte.class).isPresent()); + assertTrue(ClassLoaderUtils.getLocation(this).isPresent()); + assertTrue(ClassLoaderUtils.getLocation("").isPresent()); + assertTrue(ClassLoaderUtils.getLocation(0).isPresent()); + assertTrue(ClassLoaderUtils.getLocation(Thread.State.RUNNABLE).isPresent()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java new file mode 100644 index 00000000..7f0d6a7f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.util.classes.AExecutionConditionClass; +import org.junit.platform.commons.util.classes.ATestExecutionListenerClass; +import org.junit.platform.commons.util.classes.AVanillaEmpty; +import org.junit.platform.commons.util.classes.BExecutionConditionClass; +import org.junit.platform.commons.util.classes.BTestExecutionListenerClass; +import org.junit.platform.commons.util.classes.BVanillaEmpty; +import org.junit.platform.launcher.TestExecutionListener; + +/** + * Unit tests for {@link ClassNamePatternFilterUtils}. + * + * @since 1.7 + */ +@TestInstance(Lifecycle.PER_CLASS) +class ClassNamePatternFilterUtilsTests { + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AExecutionConditionClass, BExecutionConditionClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysEnabledConditions(String pattern) { + List executionConditions = List.of(new AExecutionConditionClass(), + new BExecutionConditionClass()); + assertThat(executionConditions).filteredOn( + ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isNotEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AExecutionConditionClass, *BExecutionConditionClass", + "*ExecutionConditionClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysDisabledConditions(String pattern) { + List executionConditions = List.of(new AExecutionConditionClass(), + new BExecutionConditionClass()); + assertThat(executionConditions).filteredOn( + ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "ATestExecutionListenerClass, BTestExecutionListenerClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysEnabledListeners(String pattern) { + List executionConditions = List.of(new ATestExecutionListenerClass(), + new BTestExecutionListenerClass()); + assertThat(executionConditions).filteredOn( + ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isNotEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*ATestExecutionListenerClass, *BTestExecutionListenerClass", + "*TestExecutionListenerClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysDisabledListeners(String pattern) { + List executionConditions = List.of(new ATestExecutionListenerClass(), + new BTestExecutionListenerClass()); + assertThat(executionConditions).filteredOn( + ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AVanillaEmpty, BVanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void alwaysEnabledClass(String pattern) { + var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); + assertThat(executionConditions).filteredOn( + ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isNotEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AVanillaEmpty, *BVanillaEmpty", + "*VanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void alwaysDisabledClass(String pattern) { + var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); + assertThat(executionConditions).filteredOn( + ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isEmpty(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java new file mode 100644 index 00000000..015e60b4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link ClassUtils}. + * + * @since 1.0 + */ +class ClassUtilsTests { + + @Test + void nullSafeToStringWithDefaultMapper() { + assertEquals("", nullSafeToString((Class[]) null)); + assertEquals("", nullSafeToString()); + assertEquals("java.lang.String", nullSafeToString(String.class)); + assertEquals("java.lang.String, java.lang.Integer", nullSafeToString(String.class, Integer.class)); + assertEquals("java.lang.String, null, java.lang.Integer", nullSafeToString(String.class, null, Integer.class)); + } + + @Test + void nullSafeToStringWithCustomMapper() { + assertEquals("", nullSafeToString(Class::getSimpleName, (Class[]) null)); + assertEquals("", nullSafeToString(Class::getSimpleName)); + assertEquals("String", nullSafeToString(Class::getSimpleName, String.class)); + assertEquals("String, Integer", nullSafeToString(Class::getSimpleName, String.class, Integer.class)); + assertEquals("String, null, Integer", + nullSafeToString(Class::getSimpleName, String.class, null, Integer.class)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java new file mode 100644 index 00000000..69c2a8f7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java @@ -0,0 +1,411 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; + +import java.io.IOException; +import java.lang.module.ModuleFinder; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.spi.ToolProvider; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.logging.LogRecordListener; + +/** + * Unit tests for {@link ClasspathScanner}. + * + * @since 1.0 + */ +@TrackLogRecords +class ClasspathScannerTests { + + private static final ClassFilter allClasses = ClassFilter.of(type -> true); + + private final List> loadedClasses = new ArrayList<>(); + + private final BiFunction>> trackingClassLoader = (name, + classLoader) -> ReflectionUtils.tryToLoadClass(name, classLoader).ifSuccess(loadedClasses::add); + + private final ClasspathScanner classpathScanner = new ClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, + trackingClassLoader); + + @Test + void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccursWithNullDetailedMessage( + LogRecordListener listener) throws Exception { + + Predicate> malformedClassNameSimulationFilter = clazz -> { + if (clazz.getSimpleName().equals(ClassForMalformedClassNameSimulation.class.getSimpleName())) { + throw new InternalError(); + } + return true; + }; + + assertClassesScannedWhenExceptionIsThrown(malformedClassNameSimulationFilter); + assertDebugMessageLogged(listener, "Failed to load java.lang.Class for path .+ during classpath scanning."); + } + + @Test + void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccurs(LogRecordListener listener) + throws Exception { + + Predicate> malformedClassNameSimulationFilter = clazz -> { + if (clazz.getSimpleName().equals(ClassForMalformedClassNameSimulation.class.getSimpleName())) { + throw new InternalError("Malformed class name"); + } + return true; + }; + + assertClassesScannedWhenExceptionIsThrown(malformedClassNameSimulationFilter); + assertDebugMessageLogged(listener, "The java.lang.Class loaded from path .+ has a malformed class name .+"); + } + + @Test + void scanForClassesInClasspathRootWhenOtherInternalErrorOccurs(LogRecordListener listener) throws Exception { + Predicate> otherInternalErrorSimulationFilter = clazz -> { + if (clazz.getSimpleName().equals(ClassForOtherInternalErrorSimulation.class.getSimpleName())) { + throw new InternalError("other internal error"); + } + return true; + }; + + assertClassesScannedWhenExceptionIsThrown(otherInternalErrorSimulationFilter); + assertDebugMessageLogged(listener, "Failed to load java.lang.Class for path .+ during classpath scanning."); + } + + @Test + void scanForClassesInClasspathRootWhenGenericRuntimeExceptionOccurs(LogRecordListener listener) throws Exception { + Predicate> runtimeExceptionSimulationFilter = clazz -> { + if (clazz.getSimpleName().equals(ClassForGenericRuntimeExceptionSimulation.class.getSimpleName())) { + throw new RuntimeException("a generic exception"); + } + return true; + }; + + assertClassesScannedWhenExceptionIsThrown(runtimeExceptionSimulationFilter); + assertDebugMessageLogged(listener, "Failed to load java.lang.Class for path .+ during classpath scanning."); + } + + private void assertClassesScannedWhenExceptionIsThrown(Predicate> filter) throws Exception { + var classFilter = ClassFilter.of(filter); + var classes = this.classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter); + assertThat(classes.size()).isGreaterThanOrEqualTo(150); + } + + private void assertDebugMessageLogged(LogRecordListener listener, String regex) { + // @formatter:off + assertThat(listener.stream(ClasspathScanner.class, Level.FINE) + .map(LogRecord::getMessage) + .filter(m -> m.matches(regex)) + ).hasSize(1); + // @formatter:on + } + + @Test + void scanForClassesInClasspathRootWhenOutOfMemoryErrorOccurs() { + Predicate> outOfMemoryErrorSimulationFilter = clazz -> { + if (clazz.getSimpleName().equals(ClassForOutOfMemoryErrorSimulation.class.getSimpleName())) { + throw new OutOfMemoryError(); + } + return true; + }; + var classFilter = ClassFilter.of(outOfMemoryErrorSimulationFilter); + + assertThrows(OutOfMemoryError.class, + () -> this.classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter)); + } + + @Test + void scanForClassesInClasspathRootWithinJarFile() throws Exception { + scanForClassesInClasspathRootWithinJarFile("/jartest.jar"); + } + + @Test + void scanForClassesInClasspathRootWithinJarWithSpacesInPath() throws Exception { + scanForClassesInClasspathRootWithinJarFile("/folder with spaces/jar test with spaces.jar"); + } + + private void scanForClassesInClasspathRootWithinJarFile(String resourceName) throws Exception { + var jarfile = getClass().getResource(resourceName); + + try (var classLoader = new URLClassLoader(new URL[] { jarfile })) { + var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + + var classes = classpathScanner.scanForClassesInClasspathRoot(jarfile.toURI(), allClasses); + var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); + assertThat(classNames).hasSize(3) // + .contains("org.junit.platform.jartest.notincluded.NotIncluded", + "org.junit.platform.jartest.included.recursive.RecursivelyIncluded", + "org.junit.platform.jartest.included.Included"); + } + } + + @Test + void scanForClassesInPackage() { + var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); + assertThat(classes.size()).isGreaterThanOrEqualTo(20); + assertTrue(classes.contains(NestedClassToBeFound.class)); + assertTrue(classes.contains(MemberClassToBeFound.class)); + } + + @Test + // #2500 + void scanForClassesInPackageWithinModulesSharingNamePrefix(@TempDir Path temp) throws Exception { + var moduleSourcePath = Path.of(getClass().getResource("/modules-2500/").toURI()).toString(); + run("javac", "--module", "foo,foo.bar", "--module-source-path", moduleSourcePath, "-d", temp.toString()); + + checkModules2500(ModuleFinder.of(temp)); // exploded modules + + var foo = temp.resolve("foo.jar"); + var bar = temp.resolve("foo.bar.jar"); + run("jar", "--create", "--file", foo.toString(), "-C", temp.resolve("foo").toString(), "."); + run("jar", "--create", "--file", bar.toString(), "-C", temp.resolve("foo.bar").toString(), "."); + + checkModules2500(ModuleFinder.of(foo, bar)); // jarred modules + + System.gc(); // required on Windows in order to release JAR file handles + } + + private static int run(String tool, String... args) { + return ToolProvider.findFirst(tool).orElseThrow().run(System.out, System.err, args); + } + + private void checkModules2500(ModuleFinder finder) { + var root = "foo.bar"; + var before = ModuleFinder.of(); + var boot = ModuleLayer.boot(); + var configuration = boot.configuration().resolve(before, finder, Set.of(root)); + var parent = ClassLoader.getPlatformClassLoader(); + var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer(); + + var classpathScanner = new ClasspathScanner(() -> layer.findLoader(root), ReflectionUtils::tryToLoadClass); + { + var classes = classpathScanner.scanForClassesInPackage("foo", allClasses); + var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); + assertThat(classNames).hasSize(2).contains("foo.Foo", "foo.bar.FooBar"); + } + { + var classes = classpathScanner.scanForClassesInPackage("foo.bar", allClasses); + var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); + assertThat(classNames).hasSize(1).contains("foo.bar.FooBar"); + } + } + + @Test + void findAllClassesInPackageWithinJarFileConcurrently() throws Exception { + var jarFile = getClass().getResource("/jartest.jar"); + var jarUri = URI.create("jar:" + jarFile); + + try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { + var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + + var results = executeConcurrently(10, + () -> classpathScanner.scanForClassesInPackage("org.junit.platform.jartest.included", allClasses)); + + assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), + "FileSystem should be closed"); + + results.forEach(classes -> { + assertThat(classes).hasSize(2); + var classNames = classes.stream().map(Class::getSimpleName).toList(); + assertTrue(classNames.contains("Included")); + assertTrue(classNames.contains("RecursivelyIncluded")); + }); + } + } + + @Test + void scanForClassesInDefaultPackage() { + var classFilter = ClassFilter.of(this::inDefaultPackage); + var classes = classpathScanner.scanForClassesInPackage("", classFilter); + + assertThat(classes.size()).as("number of classes found in default package").isGreaterThanOrEqualTo(1); + assertTrue(classes.stream().allMatch(this::inDefaultPackage)); + assertTrue(classes.stream().anyMatch(clazz -> "DefaultPackageTestCase".equals(clazz.getName()))); + } + + @Test + void scanForClassesInPackageWithFilter() { + var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); + var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", thisClassOnly); + assertSame(ClasspathScannerTests.class, classes.get(0)); + } + + @Test + void scanForClassesInPackageForNullBasePackage() { + assertThrows(PreconditionViolationException.class, + () -> classpathScanner.scanForClassesInPackage(null, allClasses)); + } + + @Test + void scanForClassesInPackageForWhitespaceBasePackage() { + assertThrows(PreconditionViolationException.class, + () -> classpathScanner.scanForClassesInPackage(" ", allClasses)); + } + + @Test + void scanForClassesInPackageForNullClassFilter() { + assertThrows(PreconditionViolationException.class, + () -> classpathScanner.scanForClassesInPackage("org.junit.platform.commons", null)); + } + + @Test + void scanForClassesInPackageWhenIOExceptionOccurs() { + var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); + var classes = scanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); + assertThat(classes).isEmpty(); + } + + @Test + void scanForClassesInPackageOnlyLoadsClassesThatAreIncludedByTheClassNameFilter() { + Predicate classNameFilter = name -> ClasspathScannerTests.class.getName().equals(name); + var classFilter = ClassFilter.of(classNameFilter, type -> true); + + classpathScanner.scanForClassesInPackage("org.junit.platform.commons", classFilter); + + assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); + } + + @Test + void findAllClassesInClasspathRoot() throws Exception { + var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); + var root = getTestClasspathRoot(); + var classes = classpathScanner.scanForClassesInClasspathRoot(root, thisClassOnly); + assertSame(ClasspathScannerTests.class, classes.get(0)); + } + + @Test + void findAllClassesInDefaultPackageInClasspathRoot() throws Exception { + var classFilter = ClassFilter.of(this::inDefaultPackage); + var classes = classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter); + + assertEquals(1, classes.size(), "number of classes found in default package"); + var testClass = classes.get(0); + assertTrue(inDefaultPackage(testClass)); + assertEquals("DefaultPackageTestCase", testClass.getName()); + } + + @Test + void doesNotLoopInfinitelyWithCircularSymlinks(@TempDir Path tempDir) throws Exception { + + // Abort if running on Microsoft Windows since we are testing symbolic links + assumeFalse(System.getProperty("os.name").toLowerCase().contains("win")); + + var directory = Files.createDirectory(tempDir.resolve("directory")); + var symlink1 = Files.createSymbolicLink(tempDir.resolve("symlink1"), directory); + Files.createSymbolicLink(directory.resolve("symlink2"), symlink1); + + var classes = classpathScanner.scanForClassesInClasspathRoot(symlink1.toUri(), allClasses); + + assertThat(classes).isEmpty(); + } + + private boolean inDefaultPackage(Class clazz) { + // OpenJDK returns NULL for the default package. + var pkg = clazz.getPackage(); + return pkg == null || "".equals(clazz.getPackage().getName()); + } + + @Test + void findAllClassesInClasspathRootWithFilter() throws Exception { + var root = getTestClasspathRoot(); + var classes = classpathScanner.scanForClassesInClasspathRoot(root, allClasses); + + assertThat(classes.size()).isGreaterThanOrEqualTo(20); + assertTrue(classes.contains(ClasspathScannerTests.class)); + } + + @Test + void findAllClassesInClasspathRootForNullRoot() { + assertThrows(PreconditionViolationException.class, + () -> classpathScanner.scanForClassesInClasspathRoot(null, allClasses)); + } + + @Test + void findAllClassesInClasspathRootForNonExistingRoot() { + assertThrows(PreconditionViolationException.class, + () -> classpathScanner.scanForClassesInClasspathRoot(Paths.get("does_not_exist").toUri(), allClasses)); + } + + @Test + void findAllClassesInClasspathRootForNullClassFilter() { + assertThrows(PreconditionViolationException.class, + () -> classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), null)); + } + + @Test + void onlyLoadsClassesInClasspathRootThatAreIncludedByTheClassNameFilter() throws Exception { + var classFilter = ClassFilter.of(name -> ClasspathScannerTests.class.getName().equals(name), type -> true); + var root = getTestClasspathRoot(); + + classpathScanner.scanForClassesInClasspathRoot(root, classFilter); + + assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); + } + + private URI getTestClasspathRoot() throws Exception { + var location = getClass().getProtectionDomain().getCodeSource().getLocation(); + return location.toURI(); + } + + class MemberClassToBeFound { + } + + static class NestedClassToBeFound { + } + + static class ClassForMalformedClassNameSimulation { + } + + static class ClassForOtherInternalErrorSimulation { + } + + static class ClassForGenericRuntimeExceptionSimulation { + } + + static class ClassForOutOfMemoryErrorSimulation { + } + + private static class ThrowingClassLoader extends ClassLoader { + + @Override + public Enumeration getResources(String name) throws IOException { + throw new IOException("Demo I/O error"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java new file mode 100644 index 00000000..f2c4745c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java @@ -0,0 +1,280 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link CollectionUtils}. + * + * @since 1.0 + */ +class CollectionUtilsTests { + + @Test + void getOnlyElementWithNullCollection() { + var exception = assertThrows(PreconditionViolationException.class, () -> CollectionUtils.getOnlyElement(null)); + assertEquals("collection must not be null", exception.getMessage()); + } + + @Test + void getOnlyElementWithEmptyCollection() { + var exception = assertThrows(PreconditionViolationException.class, + () -> CollectionUtils.getOnlyElement(Set.of())); + assertEquals("collection must contain exactly one element: []", exception.getMessage()); + } + + @Test + void getOnlyElementWithSingleElementCollection() { + var expected = new Object(); + var actual = CollectionUtils.getOnlyElement(Set.of(expected)); + assertSame(expected, actual); + } + + @Test + void getOnlyElementWithMultiElementCollection() { + var exception = assertThrows(PreconditionViolationException.class, + () -> CollectionUtils.getOnlyElement(List.of("foo", "bar"))); + assertEquals("collection must contain exactly one element: [foo, bar]", exception.getMessage()); + } + + @Test + void toUnmodifiableListThrowsOnMutation() { + var numbers = Stream.of(1).collect(toUnmodifiableList()); + assertThrows(UnsupportedOperationException.class, numbers::clear); + } + + @ParameterizedTest + @ValueSource(classes = { // + Stream.class, // + DoubleStream.class, // + IntStream.class, // + LongStream.class, // + Collection.class, // + Iterable.class, // + Iterator.class, // + Object[].class, // + String[].class, // + int[].class, // + double[].class, // + char[].class // + }) + void isConvertibleToStreamForSupportedTypes(Class type) { + assertThat(CollectionUtils.isConvertibleToStream(type)).isTrue(); + } + + @ParameterizedTest + @MethodSource("objectsConvertibleToStreams") + void isConvertibleToStreamForSupportedTypesFromObjects(Object object) { + assertThat(CollectionUtils.isConvertibleToStream(object.getClass())).isTrue(); + } + + static Stream objectsConvertibleToStreams() { + return Stream.of(// + Stream.of("cat", "dog"), // + DoubleStream.of(42.3), // + IntStream.of(99), // + LongStream.of(100000000), // + Set.of(1, 2, 3), // + Arguments.of((Object) new Object[] { 9, 8, 7 }), // + new int[] { 5, 10, 15 }// + ); + } + + @ParameterizedTest + @ValueSource(classes = { // + void.class, // + Void.class, // + Object.class, // + Integer.class, // + String.class, // + int.class, // + boolean.class // + }) + void isConvertibleToStreamForUnsupportedTypes(Class type) { + assertThat(CollectionUtils.isConvertibleToStream(type)).isFalse(); + } + + @Test + void isConvertibleToStreamForNull() { + assertThat(CollectionUtils.isConvertibleToStream(null)).isFalse(); + } + + @Test + void toStreamWithNull() { + Exception exception = assertThrows(PreconditionViolationException.class, () -> CollectionUtils.toStream(null)); + + assertThat(exception).hasMessage("Object must not be null"); + } + + @Test + void toStreamWithUnsupportedObjectType() { + Exception exception = assertThrows(PreconditionViolationException.class, + () -> CollectionUtils.toStream("unknown")); + + assertThat(exception).hasMessage("Cannot convert instance of java.lang.String into a Stream: unknown"); + } + + @Test + void toStreamWithExistingStream() { + var input = Stream.of("foo"); + + var result = CollectionUtils.toStream(input); + + assertThat(result).isSameAs(input); + } + + @Test + @SuppressWarnings("unchecked") + void toStreamWithDoubleStream() { + var input = DoubleStream.of(42.23); + + var result = (Stream) CollectionUtils.toStream(input); + + assertThat(result).containsExactly(42.23); + } + + @Test + @SuppressWarnings("unchecked") + void toStreamWithIntStream() { + var input = IntStream.of(23, 42); + + var result = (Stream) CollectionUtils.toStream(input); + + assertThat(result).containsExactly(23, 42); + } + + @Test + @SuppressWarnings("unchecked") + void toStreamWithLongStream() { + var input = LongStream.of(23L, 42L); + + var result = (Stream) CollectionUtils.toStream(input); + + assertThat(result).containsExactly(23L, 42L); + } + + @Test + @SuppressWarnings({ "unchecked", "serial" }) + void toStreamWithCollection() { + var collectionStreamClosed = new AtomicBoolean(false); + Collection input = new ArrayList<>() { + + { + add("foo"); + add("bar"); + } + + @Override + public Stream stream() { + return super.stream().onClose(() -> collectionStreamClosed.set(true)); + } + }; + + try (var stream = (Stream) CollectionUtils.toStream(input)) { + var result = stream.collect(toList()); + assertThat(result).containsExactly("foo", "bar"); + } + + assertThat(collectionStreamClosed.get()).describedAs("collectionStreamClosed").isTrue(); + } + + @Test + @SuppressWarnings("unchecked") + void toStreamWithIterable() { + + Iterable input = () -> List.of("foo", "bar").iterator(); + + var result = (Stream) CollectionUtils.toStream(input); + + assertThat(result).containsExactly("foo", "bar"); + } + + @Test + @SuppressWarnings("unchecked") + void toStreamWithIterator() { + var input = List.of("foo", "bar").iterator(); + + var result = (Stream) CollectionUtils.toStream(input); + + assertThat(result).containsExactly("foo", "bar"); + } + + @Test + @SuppressWarnings("unchecked") + void toStreamWithArray() { + var result = (Stream) CollectionUtils.toStream(new String[] { "foo", "bar" }); + + assertThat(result).containsExactly("foo", "bar"); + } + + @TestFactory + Stream toStreamWithPrimitiveArrays() { + //@formatter:off + return Stream.of( + dynamicTest("boolean[]", + () -> toStreamWithPrimitiveArray(new boolean[] { true, false })), + dynamicTest("byte[]", + () -> toStreamWithPrimitiveArray(new byte[] { 0, Byte.MIN_VALUE, Byte.MAX_VALUE })), + dynamicTest("char[]", + () -> toStreamWithPrimitiveArray(new char[] { 0, Character.MIN_VALUE, Character.MAX_VALUE })), + dynamicTest("double[]", + () -> toStreamWithPrimitiveArray(new double[] { 0, Double.MIN_VALUE, Double.MAX_VALUE })), + dynamicTest("float[]", + () -> toStreamWithPrimitiveArray(new float[] { 0, Float.MIN_VALUE, Float.MAX_VALUE })), + dynamicTest("int[]", + () -> toStreamWithPrimitiveArray(new int[] { 0, Integer.MIN_VALUE, Integer.MAX_VALUE })), + dynamicTest("long[]", + () -> toStreamWithPrimitiveArray(new long[] { 0, Long.MIN_VALUE, Long.MAX_VALUE })), + dynamicTest("short[]", + () -> toStreamWithPrimitiveArray(new short[] { 0, Short.MIN_VALUE, Short.MAX_VALUE })) + ); + //@formatter:on + } + + private void toStreamWithPrimitiveArray(Object primitiveArray) { + assertTrue(primitiveArray.getClass().isArray()); + assertTrue(primitiveArray.getClass().getComponentType().isPrimitive()); + var result = CollectionUtils.toStream(primitiveArray).toArray(); + for (var i = 0; i < result.length; i++) { + assertEquals(Array.get(primitiveArray, i), result[i]); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java new file mode 100644 index 00000000..b3750f52 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; +import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link ExceptionUtils}. + * + * @since 1.0 + */ +class ExceptionUtilsTests { + + @Test + void throwAsUncheckedExceptionWithNullException() { + assertThrows(PreconditionViolationException.class, () -> throwAsUncheckedException(null)); + } + + @Test + void throwAsUncheckedExceptionWithCheckedException() { + assertThrows(IOException.class, () -> throwAsUncheckedException(new IOException())); + } + + @Test + void throwAsUncheckedExceptionWithUncheckedException() { + assertThrows(RuntimeException.class, () -> throwAsUncheckedException(new NumberFormatException())); + } + + @Test + void readStackTraceForNullThrowable() { + assertThrows(PreconditionViolationException.class, () -> readStackTrace(null)); + } + + @Test + void readStackTraceForLocalJUnitException() { + try { + throw new JUnitException("expected"); + } + catch (JUnitException e) { + // @formatter:off + assertThat(readStackTrace(e)) + .startsWith(JUnitException.class.getName() + ": expected") + .contains("at " + ExceptionUtilsTests.class.getName()); + // @formatter:on + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java new file mode 100644 index 00000000..a543c652 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.function.Predicate.isEqual; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link FunctionUtils}. + * + * @since 1.0 + */ +class FunctionUtilsTests { + + @Test + void whereWithNullFunction() { + var exception = assertThrows(PreconditionViolationException.class, () -> FunctionUtils.where(null, o -> true)); + assertEquals("function must not be null", exception.getMessage()); + } + + @Test + void whereWithNullPredicate() { + var exception = assertThrows(PreconditionViolationException.class, () -> FunctionUtils.where(o -> o, null)); + assertEquals("predicate must not be null", exception.getMessage()); + } + + @Test + void whereWithChecksPredicateAgainstResultOfFunction() { + var combinedPredicate = FunctionUtils.where(String::length, isEqual(3)); + assertFalse(combinedPredicate.test("fo")); + assertTrue(combinedPredicate.test("foo")); + assertFalse(combinedPredicate.test("fooo")); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java new file mode 100644 index 00000000..7d7d6168 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.6 + */ +class LruCacheTests { + + @Test + void evictsEldestEntryWhenMaxSizeIsReached() { + var cache = new LruCache(1); + + cache.put(0, 0); + cache.put(1, 1); + + assertThat(cache) // + .doesNotContain(entry(0, 0)) // + .hasSize(1); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java new file mode 100644 index 00000000..5b8a42b5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link ModuleUtils}. + * + * @since 1.1 + */ +class ModuleUtilsTests { + + @Test + void isJavaPlatformModuleSystemAvailable() { + boolean expected; + try { + Class.forName("java.lang.Module"); + expected = true; + } + catch (ClassNotFoundException e) { + expected = false; + } + assertEquals(expected, ModuleUtils.isJavaPlatformModuleSystemAvailable()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java new file mode 100644 index 00000000..009ba2c5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.PreconditionViolationException; +import org.opentest4j.ValueWrapper; + +/** + * Unit tests for {@link PackageUtils}. + * + * @since 1.0 + */ +class PackageUtilsTests { + + @Test + void getAttributeWithNullType() { + var exception = assertThrows(PreconditionViolationException.class, + () -> PackageUtils.getAttribute(null, p -> "any")); + assertEquals("type must not be null", exception.getMessage()); + } + + @Test + void getAttributeWithNullFunction() { + var exception = assertThrows(PreconditionViolationException.class, + () -> PackageUtils.getAttribute(getClass(), (Function) null)); + assertEquals("function must not be null", exception.getMessage()); + } + + @Test + void getAttributeWithFunctionReturningNullIsEmpty() { + assertFalse(PackageUtils.getAttribute(ValueWrapper.class, p -> null).isPresent()); + } + + @Test + void getAttributeFromDefaultPackageMemberIsEmpty() throws Exception { + var classInDefaultPackage = ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get(); + assertFalse(PackageUtils.getAttribute(classInDefaultPackage, Package::getSpecificationTitle).isPresent()); + } + + @TestFactory + List attributesFromValueWrapperClassArePresent() { + return List.of( // + dynamicTest("getName", isPresent(Package::getName)), + dynamicTest("getImplementationTitle", isPresent(Package::getImplementationTitle)), + dynamicTest("getImplementationVendor", isPresent(Package::getImplementationVendor)), + dynamicTest("getImplementationVersion", isPresent(Package::getImplementationVersion)), + dynamicTest("getSpecificationTitle", isPresent(Package::getSpecificationTitle)), + dynamicTest("getSpecificationVendor", isPresent(Package::getSpecificationVendor)), + dynamicTest("getSpecificationVersion", isPresent(Package::getSpecificationVersion)) // + ); + } + + private Executable isPresent(Function function) { + return () -> assertTrue(PackageUtils.getAttribute(ValueWrapper.class, function).isPresent()); + } + + @Test + void getAttributeWithNullTypeAndName() { + var exception = assertThrows(PreconditionViolationException.class, + () -> PackageUtils.getAttribute(null, "foo")); + assertEquals("type must not be null", exception.getMessage()); + } + + @Test + void getAttributeWithNullName() { + var exception = assertThrows(PreconditionViolationException.class, + () -> PackageUtils.getAttribute(getClass(), (String) null)); + assertEquals("name must not be blank", exception.getMessage()); + } + + @Test + void getAttributeWithEmptyName() { + var exception = assertThrows(PreconditionViolationException.class, + () -> PackageUtils.getAttribute(getClass(), "")); + assertEquals("name must not be blank", exception.getMessage()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java new file mode 100644 index 00000000..b525437b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java @@ -0,0 +1,268 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.util.Preconditions.condition; +import static org.junit.platform.commons.util.Preconditions.containsNoNullElements; +import static org.junit.platform.commons.util.Preconditions.notBlank; +import static org.junit.platform.commons.util.Preconditions.notEmpty; +import static org.junit.platform.commons.util.Preconditions.notNull; + +import java.util.Collection; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link Preconditions}. + * + * @since 1.0 + */ +class PreconditionsTests { + + @Test + void notNullPassesForNonNullObject() { + var object = new Object(); + var nonNullObject = notNull(object, "message"); + assertSame(object, nonNullObject); + } + + @Test + void notNullThrowsForNullObject() { + var message = "argument is null"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notNull(null, message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notNullThrowsForNullObjectAndMessageSupplier() { + var message = "argument is null"; + Object object = null; + + var exception = assertThrows(PreconditionViolationException.class, () -> notNull(object, () -> message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notEmptyPassesForNonEmptyArray() { + var array = new String[] { "a", "b", "c" }; + var nonEmptyArray = notEmpty(array, () -> "should not fail"); + assertSame(array, nonEmptyArray); + } + + @Test + void notEmptyPassesForNonEmptyCollection() { + Collection collection = List.of("a", "b", "c"); + var nonEmptyCollection = notEmpty(collection, () -> "should not fail"); + assertSame(collection, nonEmptyCollection); + } + + @Test + void notEmptyPassesForArrayWithNullElements() { + notEmpty(new String[] { null }, "message"); + } + + @Test + void notEmptyPassesForCollectionWithNullElements() { + notEmpty(singletonList(null), "message"); + } + + @Test + void notEmptyThrowsForNullArray() { + var message = "array is empty"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notEmpty((Object[]) null, message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notEmptyThrowsForNullCollection() { + var message = "collection is empty"; + + var exception = assertThrows(PreconditionViolationException.class, + () -> notEmpty((Collection) null, message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notEmptyThrowsForEmptyArray() { + var message = "array is empty"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notEmpty(new Object[0], message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notEmptyThrowsForEmptyCollection() { + var message = "collection is empty"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notEmpty(List.of(), message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void containsNoNullElementsPassesForArrayThatIsNullOrEmpty() { + containsNoNullElements((Object[]) null, "array is null"); + containsNoNullElements((Object[]) null, () -> "array is null"); + + containsNoNullElements(new Object[0], "array is empty"); + containsNoNullElements(new Object[0], () -> "array is empty"); + } + + @Test + void containsNoNullElementsPassesForCollectionThatIsNullOrEmpty() { + containsNoNullElements((List) null, "collection is null"); + containsNoNullElements(List.of(), "collection is empty"); + + containsNoNullElements((List) null, () -> "collection is null"); + containsNoNullElements(List.of(), () -> "collection is empty"); + } + + @Test + void containsNoNullElementsPassesForArrayContainingNonNullElements() { + var input = new String[] { "a", "b", "c" }; + var output = containsNoNullElements(input, "message"); + assertSame(input, output); + } + + @Test + void containsNoNullElementsPassesForCollectionContainingNonNullElements() { + var input = List.of("a", "b", "c"); + var output = containsNoNullElements(input, "message"); + assertSame(input, output); + + output = containsNoNullElements(input, () -> "message"); + assertSame(input, output); + } + + @Test + void containsNoNullElementsThrowsForArrayContainingNullElements() { + var message = "array contains null elements"; + Object[] array = { new Object(), null, new Object() }; + + var exception = assertThrows(PreconditionViolationException.class, + () -> containsNoNullElements(array, message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void containsNoNullElementsThrowsForCollectionContainingNullElements() { + var message = "collection contains null elements"; + + var exception = assertThrows(PreconditionViolationException.class, + () -> containsNoNullElements(singletonList(null), message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notBlankPassesForNonBlankString() { + var string = "abc"; + var nonBlankString = notBlank(string, "message"); + assertSame(string, nonBlankString); + } + + @Test + void notBlankThrowsForNullString() { + var message = "string shouldn't be blank"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(null, message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notBlankThrowsForNullStringWithMessageSupplier() { + var message = "string shouldn't be blank"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(null, () -> message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notBlankThrowsForEmptyString() { + var message = "string shouldn't be blank"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notBlank("", message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notBlankThrowsForEmptyStringWithMessageSupplier() { + var message = "string shouldn't be blank"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notBlank("", () -> message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notBlankThrowsForBlankString() { + var message = "string shouldn't be blank"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(" ", message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void notBlankThrowsForBlankStringWithMessageSupplier() { + var message = "string shouldn't be blank"; + + var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(" ", () -> message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void conditionPassesForTruePredicate() { + condition(true, "error message"); + } + + @Test + void conditionPassesForTruePredicateWithMessageSupplier() { + condition(true, () -> "error message"); + } + + @Test + void conditionThrowsForFalsePredicate() { + var message = "condition does not hold"; + + var exception = assertThrows(PreconditionViolationException.class, () -> condition(false, message)); + + assertEquals(message, exception.getMessage()); + } + + @Test + void conditionThrowsForFalsePredicateWithMessageSupplier() { + var message = "condition does not hold"; + + var exception = assertThrows(PreconditionViolationException.class, () -> condition(false, () -> message)); + + assertEquals(message, exception.getMessage()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java new file mode 100644 index 00000000..d667bce7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -0,0 +1,1906 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.time.Duration.ofMillis; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.function.Try.success; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.util.ReflectionUtils.findMethod; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; +import static org.junit.platform.commons.util.ReflectionUtils.readFieldValue; +import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.ReflectionUtilsTests.ClassWithNestedClasses.Nested1; +import org.junit.platform.commons.util.ReflectionUtilsTests.ClassWithNestedClasses.Nested2; +import org.junit.platform.commons.util.ReflectionUtilsTests.ClassWithNestedClasses.Nested3; +import org.junit.platform.commons.util.ReflectionUtilsTests.Interface45.Nested5; +import org.junit.platform.commons.util.ReflectionUtilsTests.InterfaceWithNestedClass.Nested4; +import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerClass; +import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerClass.RecursiveInnerInnerClass; +import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerSiblingClass; +import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.RecursiveInnerClass; +import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedClass; +import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedSiblingClass; +import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClassImplementingInterface.InnerClassImplementingInterface; + +/** + * Unit tests for {@link ReflectionUtils}. + * + * @since 1.0 + */ +class ReflectionUtilsTests { + + private static final Predicate isFooMethod = method -> method.getName().equals("foo"); + private static final Predicate methodContains1 = method -> method.getName().contains("1"); + private static final Predicate methodContains2 = method -> method.getName().contains("2"); + private static final Predicate methodContains4 = method -> method.getName().contains("4"); + private static final Predicate methodContains5 = method -> method.getName().contains("5"); + + @Test + void isPublic() throws Exception { + assertTrue(ReflectionUtils.isPublic(PublicClass.class)); + assertTrue(ReflectionUtils.isPublic(PublicClass.class.getMethod("publicMethod"))); + + assertFalse(ReflectionUtils.isPublic(PrivateClass.class)); + assertFalse(ReflectionUtils.isPublic(PrivateClass.class.getDeclaredMethod("privateMethod"))); + assertFalse(ReflectionUtils.isPublic(ProtectedClass.class)); + assertFalse(ReflectionUtils.isPublic(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); + assertFalse(ReflectionUtils.isPublic(PackageVisibleClass.class)); + assertFalse(ReflectionUtils.isPublic(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); + } + + @Test + void isPrivate() throws Exception { + assertTrue(ReflectionUtils.isPrivate(PrivateClass.class)); + assertTrue(ReflectionUtils.isPrivate(PrivateClass.class.getDeclaredMethod("privateMethod"))); + + assertFalse(ReflectionUtils.isPrivate(PublicClass.class)); + assertFalse(ReflectionUtils.isPrivate(PublicClass.class.getMethod("publicMethod"))); + assertFalse(ReflectionUtils.isPrivate(ProtectedClass.class)); + assertFalse(ReflectionUtils.isPrivate(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); + assertFalse(ReflectionUtils.isPrivate(PackageVisibleClass.class)); + assertFalse(ReflectionUtils.isPrivate(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); + } + + @Test + void isNotPrivate() throws Exception { + assertTrue(ReflectionUtils.isNotPrivate(PublicClass.class)); + assertTrue(ReflectionUtils.isNotPrivate(PublicClass.class.getDeclaredMethod("publicMethod"))); + assertTrue(ReflectionUtils.isNotPrivate(ProtectedClass.class)); + assertTrue(ReflectionUtils.isNotPrivate(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); + assertTrue(ReflectionUtils.isNotPrivate(PackageVisibleClass.class)); + assertTrue(ReflectionUtils.isNotPrivate(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); + + assertFalse(ReflectionUtils.isNotPrivate(PrivateClass.class.getDeclaredMethod("privateMethod"))); + } + + @Test + void isAbstract() throws Exception { + assertTrue(ReflectionUtils.isAbstract(AbstractClass.class)); + assertTrue(ReflectionUtils.isAbstract(AbstractClass.class.getDeclaredMethod("abstractMethod"))); + + assertFalse(ReflectionUtils.isAbstract(PublicClass.class)); + assertFalse(ReflectionUtils.isAbstract(PublicClass.class.getDeclaredMethod("publicMethod"))); + } + + @Test + void isStatic() throws Exception { + assertTrue(ReflectionUtils.isStatic(StaticClass.class)); + assertTrue(ReflectionUtils.isStatic(StaticClass.class.getDeclaredMethod("staticMethod"))); + + assertFalse(ReflectionUtils.isStatic(PublicClass.class)); + assertFalse(ReflectionUtils.isStatic(PublicClass.class.getDeclaredMethod("publicMethod"))); + } + + @Test + void isNotStatic() throws Exception { + assertTrue(ReflectionUtils.isNotStatic(PublicClass.class)); + assertTrue(ReflectionUtils.isNotStatic(PublicClass.class.getDeclaredMethod("publicMethod"))); + + assertFalse(ReflectionUtils.isNotStatic(StaticClass.class)); + assertFalse(ReflectionUtils.isNotStatic(StaticClass.class.getDeclaredMethod("staticMethod"))); + } + + @Test + void isFinal() throws Exception { + assertTrue(ReflectionUtils.isFinal(FinalClass.class)); + assertTrue(ReflectionUtils.isFinal(FinalClass.class.getDeclaredMethod("finalMethod"))); + + assertFalse(ReflectionUtils.isFinal(PublicClass.class)); + assertFalse(ReflectionUtils.isFinal(PublicClass.class.getDeclaredMethod("publicMethod"))); + } + + @Test + void isNotFinal() throws Exception { + assertTrue(ReflectionUtils.isNotFinal(PublicClass.class)); + assertTrue(ReflectionUtils.isNotFinal(PublicClass.class.getDeclaredMethod("publicMethod"))); + + assertFalse(ReflectionUtils.isNotFinal(FinalClass.class)); + assertFalse(ReflectionUtils.isNotFinal(FinalClass.class.getDeclaredMethod("finalMethod"))); + } + + @Test + void returnsVoid() throws Exception { + Class clazz = ClassWithVoidAndNonVoidMethods.class; + assertTrue(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("voidMethod"))); + + assertFalse(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("methodReturningVoidReference"))); + assertFalse(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("methodReturningObject"))); + assertFalse(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("methodReturningPrimitive"))); + } + + @Test + void getAllAssignmentCompatibleClassesWithNullClass() { + assertThrows(PreconditionViolationException.class, + () -> ReflectionUtils.getAllAssignmentCompatibleClasses(null)); + } + + @Test + void getAllAssignmentCompatibleClasses() { + var superclasses = ReflectionUtils.getAllAssignmentCompatibleClasses(B.class); + assertThat(superclasses).containsExactly(B.class, InterfaceC.class, InterfaceA.class, InterfaceB.class, A.class, + InterfaceD.class, Object.class); + assertTrue(superclasses.stream().allMatch(clazz -> clazz.isAssignableFrom(B.class))); + } + + @Test + void newInstance() { + // @formatter:off + assertThat(ReflectionUtils.newInstance(C.class, "one", "two")).isNotNull(); + assertThat(ReflectionUtils.newInstance(C.class)).isNotNull(); + + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, "one", null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, null, "two")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, null, null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, ((Object[]) null))); + + var exception = assertThrows(RuntimeException.class, () -> ReflectionUtils.newInstance(Exploder.class)); + assertThat(exception).hasMessage("boom"); + // @formatter:on + } + + @Test + @SuppressWarnings("deprecation") + void readFieldValueOfNonexistentStaticField() { + assertThat(readFieldValue(MyClass.class, "doesNotExist", null)).isNotPresent(); + assertThat(readFieldValue(MySubClass.class, "staticField", null)).isNotPresent(); + } + + @Test + void tryToReadFieldValueOfNonexistentStaticField() { + assertThrows(NoSuchFieldException.class, () -> tryToReadFieldValue(MyClass.class, "doesNotExist", null).get()); + assertThrows(NoSuchFieldException.class, + () -> tryToReadFieldValue(MySubClass.class, "staticField", null).get()); + } + + @Test + @SuppressWarnings("deprecation") + void readFieldValueOfNonexistentInstanceField() { + assertThat(readFieldValue(MyClass.class, "doesNotExist", new MyClass(42))).isNotPresent(); + assertThat(readFieldValue(MyClass.class, "doesNotExist", new MySubClass(42))).isNotPresent(); + } + + @Test + void tryToReadFieldValueOfNonexistentInstanceField() { + assertThrows(NoSuchFieldException.class, + () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MyClass(42)).get()); + assertThrows(NoSuchFieldException.class, + () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MySubClass(42)).get()); + } + + @Test + @SuppressWarnings("deprecation") + void readFieldValueOfExistingStaticField() throws Exception { + assertThat(readFieldValue(MyClass.class, "staticField", null)).contains(42); + + var field = MyClass.class.getDeclaredField("staticField"); + assertThat(readFieldValue(field)).contains(42); + assertThat(readFieldValue(field, null)).contains(42); + } + + @Test + void tryToReadFieldValueOfExistingStaticField() throws Exception { + assertThat(tryToReadFieldValue(MyClass.class, "staticField", null).get()).isEqualTo(42); + + var field = MyClass.class.getDeclaredField("staticField"); + assertThat(tryToReadFieldValue(field).get()).isEqualTo(42); + assertThat(tryToReadFieldValue(field, null).get()).isEqualTo(42); + } + + @Test + @SuppressWarnings("deprecation") + void readFieldValueOfExistingInstanceField() throws Exception { + var instance = new MyClass(42); + assertThat(readFieldValue(MyClass.class, "instanceField", instance)).contains(42); + + var field = MyClass.class.getDeclaredField("instanceField"); + assertThat(readFieldValue(field, instance)).contains(42); + } + + @Test + @SuppressWarnings("deprecation") + void attemptToReadFieldValueOfExistingInstanceFieldAsStaticField() throws Exception { + var field = MyClass.class.getDeclaredField("instanceField"); + Exception exception = assertThrows(PreconditionViolationException.class, () -> readFieldValue(field, null)); + assertThat(exception)// + .hasMessageStartingWith("Cannot read non-static field")// + .hasMessageEndingWith("on a null instance."); + } + + @Test + void tryToReadFieldValueOfExistingInstanceField() throws Exception { + var instance = new MyClass(42); + assertThat(tryToReadFieldValue(MyClass.class, "instanceField", instance).get()).isEqualTo(42); + + var field = MyClass.class.getDeclaredField("instanceField"); + assertThat(tryToReadFieldValue(field, instance).get()).isEqualTo(42); + assertThrows(PreconditionViolationException.class, () -> tryToReadFieldValue(field, null).get()); + } + + @Nested + class IsClassAssignableToClassTests { + + @Test + void isAssignableToForNullSourceType() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> ReflectionUtils.isAssignableTo(null, getClass()))// + .withMessage("source type must not be null"); + } + + @Test + void isAssignableToForPrimitiveSourceType() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> ReflectionUtils.isAssignableTo(int.class, Integer.class))// + .withMessage("source type must not be a primitive type"); + } + + @Test + void isAssignableToForNullTargetType() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> ReflectionUtils.isAssignableTo(getClass(), null))// + .withMessage("target type must not be null"); + } + + @Test + void isAssignableTo() { + // Reference Types + assertTrue(ReflectionUtils.isAssignableTo(String.class, Object.class)); + assertTrue(ReflectionUtils.isAssignableTo(String.class, CharSequence.class)); + assertTrue(ReflectionUtils.isAssignableTo(String.class, String.class)); + assertTrue(ReflectionUtils.isAssignableTo(Integer.class, Number.class)); + assertTrue(ReflectionUtils.isAssignableTo(Integer.class, Integer.class)); + + assertFalse(ReflectionUtils.isAssignableTo(Object.class, String.class)); + assertFalse(ReflectionUtils.isAssignableTo(CharSequence.class, String.class)); + assertFalse(ReflectionUtils.isAssignableTo(Number.class, Integer.class)); + + // Arrays + assertTrue(ReflectionUtils.isAssignableTo(int[].class, int[].class)); + assertTrue(ReflectionUtils.isAssignableTo(double[].class, double[].class)); + assertTrue(ReflectionUtils.isAssignableTo(double[].class, Object.class)); + assertTrue(ReflectionUtils.isAssignableTo(String[].class, Object.class)); + assertTrue(ReflectionUtils.isAssignableTo(String[].class, Object[].class)); + assertTrue(ReflectionUtils.isAssignableTo(String[].class, String[].class)); + + // Wrappers to Primitives + assertTrue(ReflectionUtils.isAssignableTo(Integer.class, int.class)); + assertTrue(ReflectionUtils.isAssignableTo(Boolean.class, boolean.class)); + + // Widening Conversions from Wrappers to Primitives + assertTrue(ReflectionUtils.isAssignableTo(Integer.class, long.class)); + assertTrue(ReflectionUtils.isAssignableTo(Float.class, double.class)); + assertTrue(ReflectionUtils.isAssignableTo(Byte.class, double.class)); + + // Widening Conversions from Wrappers to Wrappers (not supported by Java) + assertFalse(ReflectionUtils.isAssignableTo(Integer.class, Long.class)); + assertFalse(ReflectionUtils.isAssignableTo(Float.class, Double.class)); + assertFalse(ReflectionUtils.isAssignableTo(Byte.class, Double.class)); + + // Narrowing Conversions + assertFalse(ReflectionUtils.isAssignableTo(Integer.class, char.class)); + assertFalse(ReflectionUtils.isAssignableTo(Long.class, byte.class)); + assertFalse(ReflectionUtils.isAssignableTo(Long.class, int.class)); + } + + } + + @Nested + class IsObjectAssignableToClassTests { + + @Test + void isAssignableToForNullClass() { + assertThrows(PreconditionViolationException.class, + () -> ReflectionUtils.isAssignableTo(new Object(), null)); + } + + @Test + void isAssignableTo() { + // Reference Types + assertTrue(ReflectionUtils.isAssignableTo("string", String.class)); + assertTrue(ReflectionUtils.isAssignableTo("string", CharSequence.class)); + assertTrue(ReflectionUtils.isAssignableTo("string", Object.class)); + + assertFalse(ReflectionUtils.isAssignableTo(new Object(), String.class)); + assertFalse(ReflectionUtils.isAssignableTo(Integer.valueOf("1"), StringBuilder.class)); + assertFalse(ReflectionUtils.isAssignableTo(new StringBuilder(), String.class)); + + // Arrays + assertTrue(ReflectionUtils.isAssignableTo(new int[0], int[].class)); + assertTrue(ReflectionUtils.isAssignableTo(new double[0], Object.class)); + assertTrue(ReflectionUtils.isAssignableTo(new String[0], String[].class)); + assertTrue(ReflectionUtils.isAssignableTo(new String[0], Object.class)); + + // Primitive Types + assertTrue(ReflectionUtils.isAssignableTo(1, int.class)); + assertTrue(ReflectionUtils.isAssignableTo(Long.valueOf("1"), long.class)); + assertTrue(ReflectionUtils.isAssignableTo(Boolean.TRUE, boolean.class)); + + // Widening Conversions to Primitives + assertTrue(ReflectionUtils.isAssignableTo(1, long.class)); + assertTrue(ReflectionUtils.isAssignableTo(1f, double.class)); + assertTrue(ReflectionUtils.isAssignableTo((byte) 1, double.class)); + + // Widening Conversions to Wrappers (not supported by Java) + assertFalse(ReflectionUtils.isAssignableTo(1, Long.class)); + assertFalse(ReflectionUtils.isAssignableTo(1f, Double.class)); + assertFalse(ReflectionUtils.isAssignableTo((byte) 1, Double.class)); + + // Narrowing Conversions + assertFalse(ReflectionUtils.isAssignableTo(1, char.class)); + assertFalse(ReflectionUtils.isAssignableTo(1L, byte.class)); + assertFalse(ReflectionUtils.isAssignableTo(1L, int.class)); + } + + @Test + void isAssignableToForNullObject() { + assertTrue(ReflectionUtils.isAssignableTo((Object) null, Object.class)); + assertTrue(ReflectionUtils.isAssignableTo((Object) null, String.class)); + assertTrue(ReflectionUtils.isAssignableTo((Object) null, Long.class)); + assertTrue(ReflectionUtils.isAssignableTo((Object) null, Character[].class)); + } + + @Test + void isAssignableToForNullObjectAndPrimitive() { + assertFalse(ReflectionUtils.isAssignableTo((Object) null, byte.class)); + assertFalse(ReflectionUtils.isAssignableTo((Object) null, int.class)); + assertFalse(ReflectionUtils.isAssignableTo((Object) null, long.class)); + assertFalse(ReflectionUtils.isAssignableTo((Object) null, boolean.class)); + } + + } + + @Test + void wideningConversion() { + // byte + assertTrue(ReflectionUtils.isWideningConversion(byte.class, short.class)); + assertTrue(ReflectionUtils.isWideningConversion(byte.class, int.class)); + assertTrue(ReflectionUtils.isWideningConversion(byte.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(byte.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(byte.class, double.class)); + // Byte + assertTrue(ReflectionUtils.isWideningConversion(Byte.class, short.class)); + assertTrue(ReflectionUtils.isWideningConversion(Byte.class, int.class)); + assertTrue(ReflectionUtils.isWideningConversion(Byte.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(Byte.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(Byte.class, double.class)); + + // short + assertTrue(ReflectionUtils.isWideningConversion(short.class, int.class)); + assertTrue(ReflectionUtils.isWideningConversion(short.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(short.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(short.class, double.class)); + // Short + assertTrue(ReflectionUtils.isWideningConversion(Short.class, int.class)); + assertTrue(ReflectionUtils.isWideningConversion(Short.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(Short.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(Short.class, double.class)); + + // char + assertTrue(ReflectionUtils.isWideningConversion(char.class, int.class)); + assertTrue(ReflectionUtils.isWideningConversion(char.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(char.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(char.class, double.class)); + // Character + assertTrue(ReflectionUtils.isWideningConversion(Character.class, int.class)); + assertTrue(ReflectionUtils.isWideningConversion(Character.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(Character.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(Character.class, double.class)); + + // int + assertTrue(ReflectionUtils.isWideningConversion(int.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(int.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(int.class, double.class)); + // Integer + assertTrue(ReflectionUtils.isWideningConversion(Integer.class, long.class)); + assertTrue(ReflectionUtils.isWideningConversion(Integer.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(Integer.class, double.class)); + + // long + assertTrue(ReflectionUtils.isWideningConversion(long.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(long.class, double.class)); + // Long + assertTrue(ReflectionUtils.isWideningConversion(Long.class, float.class)); + assertTrue(ReflectionUtils.isWideningConversion(Long.class, double.class)); + + // float + assertTrue(ReflectionUtils.isWideningConversion(float.class, double.class)); + // Float + assertTrue(ReflectionUtils.isWideningConversion(Float.class, double.class)); + + // double and Double --> nothing to test + + // Unsupported + assertFalse(ReflectionUtils.isWideningConversion(int.class, byte.class)); // narrowing + assertFalse(ReflectionUtils.isWideningConversion(float.class, int.class)); // narrowing + assertFalse(ReflectionUtils.isWideningConversion(int.class, int.class)); // direct match + assertFalse(ReflectionUtils.isWideningConversion(String.class, int.class)); // neither a primitive nor a wrapper + } + + @Test + void invokeMethodPreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> invokeMethod(null, new Object())); + assertThrows(PreconditionViolationException.class, () -> invokeMethod(Object.class.getMethod("hashCode"), null)); + // @formatter:on + } + + @Test + void invokePublicMethod() throws Exception { + var tracker = new InvocationTracker(); + invokeMethod(InvocationTracker.class.getDeclaredMethod("publicMethod"), tracker); + assertTrue(tracker.publicMethodInvoked); + } + + @Test + void invokePrivateMethod() throws Exception { + var tracker = new InvocationTracker(); + invokeMethod(InvocationTracker.class.getDeclaredMethod("privateMethod"), tracker); + assertTrue(tracker.privateMethodInvoked); + } + + @Test + void invokePublicStaticMethod() throws Exception { + invokeMethod(InvocationTracker.class.getDeclaredMethod("publicStaticMethod"), null); + assertTrue(InvocationTracker.publicStaticMethodInvoked); + } + + @Test + void invokePrivateStaticMethod() throws Exception { + invokeMethod(InvocationTracker.class.getDeclaredMethod("privateStaticMethod"), null); + assertTrue(InvocationTracker.privateStaticMethodInvoked); + } + + @Test + void tryToLoadClassPreconditions() { + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass("")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(" ")); + + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(null, null)); + assertThrows(PreconditionViolationException.class, + () -> ReflectionUtils.tryToLoadClass(getClass().getName(), null)); + } + + @Test + @SuppressWarnings("deprecation") + void loadClassWhenClassNotFoundException() { + assertThat(ReflectionUtils.loadClass("foo.bar.EnigmaClassThatDoesNotExist")).isEmpty(); + } + + @Test + void tryToLoadClassWhenClassNotFoundException() { + assertThrows(ClassNotFoundException.class, + () -> ReflectionUtils.tryToLoadClass("foo.bar.EnigmaClassThatDoesNotExist").get()); + } + + @Test + void tryToLoadClassFailsWithinReasonableTimeForInsanelyLargeAndInvalidMultidimensionalPrimitiveArrayName() { + // Create a class name of the form int[][][]...[][][]X + String className = IntStream.rangeClosed(1, 20_000)// + .mapToObj(i -> "[]")// + .collect(joining("", "int", "X")); + + // The following should ideally fail in less than 50ms. So we just make + // sure it fails in less than 500ms in order to (hopefully) allow the + // test to pass on CI servers with limited resources. + assertTimeoutPreemptively(ofMillis(500), + () -> assertThrows(ClassNotFoundException.class, () -> ReflectionUtils.tryToLoadClass(className).get())); + } + + @Test + @SuppressWarnings("deprecation") + void loadClass() { + var optional = ReflectionUtils.loadClass(Integer.class.getName()); + assertThat(optional).contains(Integer.class); + } + + @Test + void tryToLoadClass() { + assertThat(ReflectionUtils.tryToLoadClass(Integer.class.getName())).isEqualTo(success(Integer.class)); + } + + @Test + void tryToLoadClassTrimsClassName() { + assertThat(ReflectionUtils.tryToLoadClass(" " + Integer.class.getName() + "\t")).isEqualTo( + success(Integer.class)); + } + + @Test + void tryToLoadClassForPrimitive() { + assertThat(ReflectionUtils.tryToLoadClass(int.class.getName())).isEqualTo(success(int.class)); + } + + @Test + void tryToLoadClassForPrimitiveArray() { + assertThat(ReflectionUtils.tryToLoadClass(int[].class.getName())).isEqualTo(success(int[].class)); + } + + @Test + void tryToLoadClassForPrimitiveArrayUsingSourceCodeSyntax() { + assertThat(ReflectionUtils.tryToLoadClass("int[]")).isEqualTo(success(int[].class)); + } + + @Test + void tryToLoadClassForObjectArray() { + assertThat(ReflectionUtils.tryToLoadClass(String[].class.getName())).isEqualTo(success(String[].class)); + } + + @Test + void tryToLoadClassForObjectArrayUsingSourceCodeSyntax() { + assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[]")).isEqualTo(success(String[].class)); + } + + @Test + void tryToLoadClassForTwoDimensionalPrimitiveArray() { + assertThat(ReflectionUtils.tryToLoadClass(int[][].class.getName())).isEqualTo(success(int[][].class)); + } + + @Test + void tryToLoadClassForTwoDimensionaldimensionalPrimitiveArrayUsingSourceCodeSyntax() { + assertThat(ReflectionUtils.tryToLoadClass("int[][]")).isEqualTo(success(int[][].class)); + } + + @Test + void tryToLoadClassForMultidimensionalPrimitiveArray() { + assertThat(ReflectionUtils.tryToLoadClass(int[][][][][].class.getName())).isEqualTo( + success(int[][][][][].class)); + } + + @Test + void tryToLoadClassForMultidimensionalPrimitiveArrayUsingSourceCodeSyntax() { + assertThat(ReflectionUtils.tryToLoadClass("int[][][][][]")).isEqualTo(success(int[][][][][].class)); + } + + @Test + void tryToLoadClassForMultidimensionalObjectArray() { + assertThat(ReflectionUtils.tryToLoadClass(String[][][][][].class.getName())).isEqualTo( + success(String[][][][][].class)); + } + + @Test + void tryToLoadClassForMultidimensionalObjectArrayUsingSourceCodeSyntax() { + assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[][][][][]")).isEqualTo( + success(String[][][][][].class)); + } + + @Test + void getFullyQualifiedMethodNamePreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getFullyQualifiedMethodName(null, null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getFullyQualifiedMethodName(null, "testMethod")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getFullyQualifiedMethodName(Object.class, null)); + // @formatter:on + } + + @Test + void getFullyQualifiedMethodNameForMethodWithoutParameters() { + assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "toString"))// + .isEqualTo("java.lang.Object#toString()"); + } + + @Test + void getFullyQualifiedMethodNameForMethodWithNullParameters() { + assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "toString", (Class[]) null))// + .isEqualTo("java.lang.Object#toString()"); + } + + @Test + void getFullyQualifiedMethodNameForMethodWithSingleParameter() { + assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "equals", Object.class))// + .isEqualTo("java.lang.Object#equals(java.lang.Object)"); + } + + @Test + void getFullyQualifiedMethodNameForMethodWithMultipleParameters() { + // @formatter:off + assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "testMethod", int.class, Object.class))// + .isEqualTo("java.lang.Object#testMethod(int, java.lang.Object)"); + // @formatter:on + } + + @Test + void parseFullyQualifiedMethodNamePreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName(null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName(" ")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("java.lang.Object#")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("#equals")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("#")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("java.lang.Object")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("equals")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("()")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("(int, java.lang.Object)")); + // @formatter:on + } + + @Test + void parseFullyQualifiedMethodNameForMethodWithoutParameters() { + assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method()"))// + .containsExactly("com.example.Test", "method", ""); + } + + @Test + void parseFullyQualifiedMethodNameForMethodWithSingleParameter() { + assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method(java.lang.Object)"))// + .containsExactly("com.example.Test", "method", "java.lang.Object"); + } + + @Test + void parseFullyQualifiedMethodNameForMethodWithMultipleParameters() { + assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method(int, java.lang.Object)"))// + .containsExactly("com.example.Test", "method", "int, java.lang.Object"); + } + + @Test + @SuppressWarnings("deprecation") + void getOutermostInstancePreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(null, null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(null, Object.class)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(new Object(), null)); + // @formatter:on + } + + @Test + @SuppressWarnings("deprecation") + void getOutermostInstance() { + var firstClass = new FirstClass(); + var secondClass = firstClass.new SecondClass(); + var thirdClass = secondClass.new ThirdClass(); + + assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.SecondClass.ThirdClass.class))// + .contains(thirdClass); + assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.SecondClass.class))// + .contains(secondClass); + assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.class)).contains(firstClass); + assertThat(ReflectionUtils.getOutermostInstance(thirdClass, String.class)).isEmpty(); + } + + @Test + void getAllClasspathRootDirectories(@TempDir Path tempDirectory) throws Exception { + var root1 = tempDirectory.resolve("root1").toAbsolutePath(); + var root2 = tempDirectory.resolve("root2").toAbsolutePath(); + var testClassPath = root1 + File.pathSeparator + root2; + + var originalClassPath = System.setProperty("java.class.path", testClassPath); + try { + createDirectories(root1, root2); + + assertThat(ReflectionUtils.getAllClasspathRootDirectories()).containsOnly(root1, root2); + } + finally { + System.setProperty("java.class.path", originalClassPath); + } + } + + @Test + void findNestedClassesPreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.findNestedClasses(null, null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.findNestedClasses(null, clazz -> true)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.findNestedClasses(FirstClass.class, null)); + // @formatter:on + } + + @Test + void findNestedClasses() { + // @formatter:off + assertThat(findNestedClasses(Object.class)).isEmpty(); + + assertThat(findNestedClasses(ClassWithNestedClasses.class)) + .containsOnly(Nested1.class, Nested2.class, Nested3.class); + + assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, clazz -> clazz.getName().contains("1"))) + .containsExactly(Nested1.class); + + assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)) + .containsExactly(Nested3.class); + + assertThat(findNestedClasses(ClassExtendingClassWithNestedClasses.class)) + .containsOnly(Nested1.class, Nested2.class, Nested3.class, Nested4.class, Nested5.class); + + assertThat(findNestedClasses(ClassWithNestedClasses.Nested1.class)).isEmpty(); + // @formatter:on + } + + /** + * @since 1.6 + */ + @Test + void findNestedClassesWithSeeminglyRecursiveHierarchies() { + assertThat(findNestedClasses(AbstractOuterClass.class))// + .containsExactly(AbstractOuterClass.InnerClass.class); + + // OuterClass contains recursive hierarchies, but the non-matching + // predicate should prevent cycle detection. + // See https://github.com/junit-team/junit5/issues/2249 + assertThat(ReflectionUtils.findNestedClasses(OuterClass.class, clazz -> false)).isEmpty(); + // RecursiveInnerInnerClass is part of a recursive hierarchy, but the non-matching + // predicate should prevent cycle detection. + assertThat(ReflectionUtils.findNestedClasses(RecursiveInnerInnerClass.class, clazz -> false)).isEmpty(); + + // Sibling types don't actually result in cycles. + assertThat(findNestedClasses(StaticNestedSiblingClass.class))// + .containsExactly(AbstractOuterClass.InnerClass.class); + assertThat(findNestedClasses(InnerSiblingClass.class))// + .containsExactly(AbstractOuterClass.InnerClass.class); + + // Interfaces with static nested classes + assertThat(findNestedClasses(OuterClassImplementingInterface.class))// + .containsExactly(InnerClassImplementingInterface.class, Nested4.class); + assertThat(findNestedClasses(InnerClassImplementingInterface.class))// + .containsExactly(Nested4.class); + } + + /** + * @since 1.6 + */ + @Test + void findNestedClassesWithRecursiveHierarchies() { + Runnable runnable1 = () -> assertNestedCycle(OuterClass.class, InnerClass.class, OuterClass.class); + Runnable runnable2 = () -> assertNestedCycle(StaticNestedClass.class, InnerClass.class, OuterClass.class); + Runnable runnable3 = () -> assertNestedCycle(RecursiveInnerClass.class, OuterClass.class); + Runnable runnable4 = () -> assertNestedCycle(RecursiveInnerInnerClass.class, OuterClass.class); + Runnable runnable5 = () -> assertNestedCycle(InnerClass.class, RecursiveInnerInnerClass.class, + OuterClass.class); + Stream.of(runnable1, runnable1, runnable1, runnable2, runnable2, runnable2, runnable3, runnable3, runnable3, + runnable4, runnable4, runnable4, runnable5, runnable5, runnable5).parallel().forEach(Runnable::run); + } + + private static List> findNestedClasses(Class clazz) { + return ReflectionUtils.findNestedClasses(clazz, c -> true); + } + + private void assertNestedCycle(Class from, Class to) { + assertNestedCycle(from, from, to); + } + + private void assertNestedCycle(Class start, Class from, Class to) { + assertThatExceptionOfType(JUnitException.class)// + .as("expected cycle from %s to %s", from.getSimpleName(), to.getSimpleName())// + .isThrownBy(() -> findNestedClasses(start))// + .withMessageMatching(String.format("Detected cycle in inner class hierarchy between .+%s and .+%s", + from.getSimpleName(), to.getSimpleName())); + } + + /** + * @since 1.3 + */ + @Test + @TrackLogRecords + void findNestedClassesWithInvalidNestedClassFile(LogRecordListener listener) throws Exception { + var jarUrl = getClass().getResource("/gh-1436-invalid-nested-class-file.jar"); + + try (var classLoader = new URLClassLoader(new URL[] { jarUrl })) { + var fqcn = "tests.NestedInterfaceGroovyTests"; + var classWithInvalidNestedClassFile = classLoader.loadClass(fqcn); + + assertEquals(fqcn, classWithInvalidNestedClassFile.getName()); + var noClassDefFoundError = assertThrows(NoClassDefFoundError.class, + classWithInvalidNestedClassFile::getDeclaredClasses); + assertEquals("tests/NestedInterfaceGroovyTests$NestedInterface$1", noClassDefFoundError.getMessage()); + + assertThat(findNestedClasses(classWithInvalidNestedClassFile)).isEmpty(); + // @formatter:off + var logMessage = listener.stream(ReflectionUtils.class, Level.FINE) + .findFirst() + .map(LogRecord::getMessage) + .orElse("didn't find log record"); + // @formatter:on + assertThat(logMessage).isEqualTo("Failed to retrieve declared classes for " + fqcn); + } + } + + @Test + void getDeclaredConstructorPreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getDeclaredConstructor(null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getDeclaredConstructor(ClassWithTwoConstructors.class)); + // @formatter:on + } + + @Test + void getDeclaredConstructor() { + Constructor constructor = ReflectionUtils.getDeclaredConstructor(getClass()); + assertNotNull(constructor); + assertEquals(getClass(), constructor.getDeclaringClass()); + + constructor = ReflectionUtils.getDeclaredConstructor(ClassWithOneCustomConstructor.class); + assertNotNull(constructor); + assertEquals(ClassWithOneCustomConstructor.class, constructor.getDeclaringClass()); + assertEquals(String.class, constructor.getParameterTypes()[0]); + } + + @Test + void tryToGetMethodPreconditions() { + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(null, null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(String.class, null)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(null, "hashCode")); + } + + @Test + void tryToGetMethod() throws Exception { + assertThat(ReflectionUtils.tryToGetMethod(Object.class, "hashCode").get()).isEqualTo( + Object.class.getMethod("hashCode")); + assertThat(ReflectionUtils.tryToGetMethod(String.class, "charAt", int.class).get())// + .isEqualTo(String.class.getMethod("charAt", int.class)); + + assertThat(ReflectionUtils.tryToGetMethod(Path.class, "subpath", int.class, int.class).get())// + .isEqualTo(Path.class.getMethod("subpath", int.class, int.class)); + assertThat(ReflectionUtils.tryToGetMethod(String.class, "chars").get()).isEqualTo( + String.class.getMethod("chars")); + + assertThat(ReflectionUtils.tryToGetMethod(String.class, "noSuchMethod").toOptional()).isEmpty(); + assertThat(ReflectionUtils.tryToGetMethod(Object.class, "clone", int.class).toOptional()).isEmpty(); + } + + @Test + void isMethodPresentPreconditions() { + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isMethodPresent(null, m -> true)); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isMethodPresent(getClass(), null)); + } + + @Test + void isMethodPresent() { + Predicate isMethod1 = method -> (method.getName().equals("method1") + && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == String.class); + + assertThat(ReflectionUtils.isMethodPresent(MethodShadowingChild.class, isMethod1)).isTrue(); + + assertThat(ReflectionUtils.isMethodPresent(getClass(), isMethod1)).isFalse(); + } + + @Test + void findMethodByParameterTypesPreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> findMethod(null, null)); + assertThrows(PreconditionViolationException.class, () -> findMethod(null, "method")); + + RuntimeException exception = assertThrows(PreconditionViolationException.class, () -> findMethod(String.class, null)); + assertThat(exception).hasMessage("Method name must not be null or blank"); + + exception = assertThrows(PreconditionViolationException.class, () -> findMethod(String.class, " ")); + assertThat(exception).hasMessage("Method name must not be null or blank"); + + exception = assertThrows(PreconditionViolationException.class, () -> findMethod(Files.class, "copy", (Class[]) null)); + assertThat(exception).hasMessage("Parameter types array must not be null"); + + exception = assertThrows(PreconditionViolationException.class, () -> findMethod(Files.class, "copy", (Class) null)); + assertThat(exception).hasMessage("Individual parameter types must not be null"); + + exception = assertThrows(PreconditionViolationException.class, () -> findMethod(Files.class, "copy", new Class[] { Path.class, null })); + assertThat(exception).hasMessage("Individual parameter types must not be null"); + // @formatter:on + } + + @Test + void findMethodByParameterTypes() throws Exception { + assertThat(findMethod(Object.class, "noSuchMethod")).isEmpty(); + assertThat(findMethod(String.class, "noSuchMethod")).isEmpty(); + + assertThat(findMethod(String.class, "chars")).contains(String.class.getMethod("chars")); + assertThat(findMethod(Files.class, "copy", Path.class, OutputStream.class))// + .contains(Files.class.getMethod("copy", Path.class, OutputStream.class)); + + assertThat(findMethod(MethodShadowingChild.class, "method1", String.class))// + .contains(MethodShadowingChild.class.getMethod("method1", String.class)); + } + + @Test + void findMethodByParameterTypesInGenericInterface() { + Class ifc = InterfaceWithGenericDefaultMethod.class; + var method = findMethod(ifc, "foo", Number.class); + assertThat(method).isNotEmpty(); + assertThat(method.get().getName()).isEqualTo("foo"); + } + + /** + * @see #findMethodByParameterTypesWithOverloadedMethodNextToGenericDefaultMethod() + */ + @Test + void findMethodByParameterTypesInGenericInterfaceViaParameterizedSubclass() { + Class clazz = InterfaceWithGenericDefaultMethodImpl.class; + var method = findMethod(clazz, "foo", Long.class); + assertThat(method).isNotEmpty(); + assertThat(method.get().getName()).isEqualTo("foo"); + + // One might expect or desire that the signature for the generic foo(N) + // default method would be "foo(java.lang.Long)" when looked up via the + // concrete parameterized class, but it apparently is only _visible_ as + // "foo(java.lang.Number)" via reflection. Hence the following assertion + // checks for java.lang.Number instead of java.lang.Long. + assertThat(method.get().getParameterTypes()[0]).isEqualTo(Number.class); + } + + /** + * This test is identical to + * {@link #findMethodByParameterTypesInGenericInterfaceViaParameterizedSubclass()}, + * except that this test attempts to find the overloaded + * {@link InterfaceWithGenericDefaultMethodImpl#foo(Double)} method instead of + * the {@link InterfaceWithGenericDefaultMethod#foo(Number)} default method. + */ + @Test + void findMethodByParameterTypesWithOverloadedMethodNextToGenericDefaultMethod() { + Class clazz = InterfaceWithGenericDefaultMethodImpl.class; + Class parameterType = Double.class; + var method = findMethod(clazz, "foo", parameterType); + assertThat(method).isNotEmpty(); + assertThat(method.get().getName()).isEqualTo("foo"); + assertThat(method.get().getParameterTypes()[0]).isEqualTo(parameterType); + } + + @Test + void findMethodByParameterNamesWithPrimitiveArrayParameter() throws Exception { + assertFindMethodByParameterNames("methodWithPrimitiveArray", int[].class); + } + + @Test + void findMethodByParameterNamesWithTwoDimensionalPrimitiveArrayParameter() throws Exception { + assertFindMethodByParameterNames("methodWithTwoDimensionalPrimitiveArray", int[][].class); + } + + @Test + void findMethodByParameterNamesWithMultidimensionalPrimitiveArrayParameter() throws Exception { + assertFindMethodByParameterNames("methodWithMultidimensionalPrimitiveArray", int[][][][][].class); + } + + @Test + void findMethodByParameterNamesWithObjectArrayParameter() throws Exception { + assertFindMethodByParameterNames("methodWithObjectArray", String[].class); + } + + @Test + void findMethodByParameterNamesWithMultidimensionalObjectArrayParameter() throws Exception { + assertFindMethodByParameterNames("methodWithMultidimensionalObjectArray", Double[][][][][].class); + } + + @Test + void findMethodByParameterNamesWithParameterizedMapParameter() throws Exception { + var methodName = "methodWithParameterizedMap"; + + // standard, supported use case + assertFindMethodByParameterNames(methodName, Map.class); + + // generic type info in parameter list + var method = getClass().getDeclaredMethod(methodName, Map.class); + var genericParameterTypeName = method.getGenericParameterTypes()[0].getTypeName(); + var exception = assertThrows(JUnitException.class, + () -> findMethod(getClass(), methodName, genericParameterTypeName)); + + assertThat(exception).hasMessageStartingWith("Failed to load parameter type [java.util.Map parameterType) + throws NoSuchMethodException { + + var method = getClass().getDeclaredMethod(methodName, parameterType); + var optional = findMethod(getClass(), methodName, parameterType.getName()); + assertThat(optional).contains(method); + } + + @Test + void findMethodsPreconditions() { + // @formatter:off + assertThrows(PreconditionViolationException.class, () -> findMethods(null, null)); + assertThrows(PreconditionViolationException.class, () -> findMethods(null, clazz -> true)); + assertThrows(PreconditionViolationException.class, () -> findMethods(String.class, null)); + + assertThrows(PreconditionViolationException.class, () -> findMethods(null, null, null)); + assertThrows(PreconditionViolationException.class, () -> findMethods(null, clazz -> true, BOTTOM_UP)); + assertThrows(PreconditionViolationException.class, () -> findMethods(String.class, null, BOTTOM_UP)); + assertThrows(PreconditionViolationException.class, () -> findMethods(String.class, clazz -> true, null)); + // @formatter:on + } + + @Test + void findMethodsInInterface() { + assertOneFooMethodIn(InterfaceWithOneDeclaredMethod.class); + assertOneFooMethodIn(InterfaceWithDefaultMethod.class); + assertOneFooMethodIn(InterfaceWithDefaultMethodImpl.class); + assertOneFooMethodIn(InterfaceWithStaticMethod.class); + assertOneFooMethodIn(InterfaceWithStaticMethodImpl.class); + } + + private static void assertOneFooMethodIn(Class clazz) { + assertThat(findMethods(clazz, isFooMethod)).hasSize(1); + } + + /** + * @since 1.9.1 + * @see https://github.com/junit-team/junit5/issues/2993 + */ + @Test + void findMethodsFindsDistinctMethodsDeclaredInMultipleInterfaces() { + Predicate isStringsMethod = method -> method.getName().equals("strings"); + assertThat(findMethods(DoubleInheritedInterfaceMethodTestCase.class, isStringsMethod)).hasSize(1); + } + + @Test + void findMethodsInObject() { + var methods = findMethods(Object.class, method -> true); + assertNotNull(methods); + assertTrue(methods.size() > 10); + } + + @Test + void findMethodsInVoid() { + assertThat(findMethods(void.class, method -> true)).isEmpty(); + assertThat(findMethods(Void.class, method -> true)).isEmpty(); + } + + @Test + void findMethodsInPrimitive() { + assertThat(findMethods(int.class, method -> true)).isEmpty(); + } + + @Test + void findMethodsInArrays() { + assertThat(findMethods(int[].class, method -> true)).isEmpty(); + assertThat(findMethods(Integer[].class, method -> true)).isEmpty(); + } + + @Test + void findMethodsIgnoresSyntheticMethods() { + assertTrue(stream(ClassWithSyntheticMethod.class.getDeclaredMethods()).anyMatch(Method::isSynthetic), + "ClassWithSyntheticMethod must actually contain at least one synthetic method."); + + var methods = findMethods(ClassWithSyntheticMethod.class, method -> true); + assertThat(methods).isEmpty(); + } + + @Test + void findMethodsUsingHierarchyUpMode() throws Exception { + assertThat(findMethods(ChildClass.class, method -> method.getName().contains("method"), BOTTOM_UP))// + .containsExactly(ChildClass.class.getMethod("method4"), ParentClass.class.getMethod("method3"), + GrandparentInterface.class.getMethod("method2"), GrandparentClass.class.getMethod("method1")); + + assertThat(findMethods(ChildClass.class, method -> method.getName().contains("other"), BOTTOM_UP))// + .containsExactly(ChildClass.class.getMethod("otherMethod3"), + ParentClass.class.getMethod("otherMethod2"), GrandparentClass.class.getMethod("otherMethod1")); + + assertThat(findMethods(ChildClass.class, method -> method.getName().equals("method2"), BOTTOM_UP))// + .containsExactly(ParentClass.class.getMethod("method2")); + + assertThat(findMethods(ChildClass.class, method -> method.getName().equals("wrongName"), BOTTOM_UP)).isEmpty(); + + assertThat(findMethods(ParentClass.class, method -> method.getName().contains("method"), BOTTOM_UP))// + .containsExactly(ParentClass.class.getMethod("method3"), + GrandparentInterface.class.getMethod("method2"), GrandparentClass.class.getMethod("method1")); + } + + @Test + void findMethodsUsingHierarchyDownMode() throws Exception { + assertThat(findMethods(ChildClass.class, method -> method.getName().contains("method"), TOP_DOWN))// + .containsExactly(GrandparentClass.class.getMethod("method1"), + GrandparentInterface.class.getMethod("method2"), ParentClass.class.getMethod("method3"), + ChildClass.class.getMethod("method4")); + + assertThat(findMethods(ChildClass.class, method -> method.getName().contains("other"), TOP_DOWN))// + .containsExactly(GrandparentClass.class.getMethod("otherMethod1"), + ParentClass.class.getMethod("otherMethod2"), ChildClass.class.getMethod("otherMethod3")); + + assertThat(findMethods(ChildClass.class, method -> method.getName().equals("method2"), TOP_DOWN))// + .containsExactly(ParentClass.class.getMethod("method2")); + + assertThat(findMethods(ChildClass.class, method -> method.getName().equals("wrongName"), TOP_DOWN)).isEmpty(); + + assertThat(findMethods(ParentClass.class, method -> method.getName().contains("method"), TOP_DOWN))// + .containsExactly(GrandparentClass.class.getMethod("method1"), + GrandparentInterface.class.getMethod("method2"), ParentClass.class.getMethod("method3")); + } + + @Test + void findMethodsWithShadowingUsingHierarchyUpMode() throws Exception { + assertThat(findMethods(MethodShadowingChild.class, methodContains1, BOTTOM_UP))// + .containsExactly(MethodShadowingChild.class.getMethod("method1", String.class)); + + assertThat(findMethods(MethodShadowingChild.class, methodContains2, BOTTOM_UP))// + .containsExactly(MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), + MethodShadowingInterface.class.getMethod("method2", int.class, int.class)); + + assertThat(findMethods(MethodShadowingChild.class, methodContains4, BOTTOM_UP))// + .containsExactly(MethodShadowingChild.class.getMethod("method4", boolean.class)); + + assertThat(findMethods(MethodShadowingChild.class, methodContains5, BOTTOM_UP))// + .containsExactly(MethodShadowingChild.class.getMethod("method5", Long.class), + MethodShadowingParent.class.getMethod("method5", String.class)); + + var methods = findMethods(MethodShadowingChild.class, method -> true, BOTTOM_UP); + assertEquals(6, methods.size()); + assertThat(methods.subList(0, 3)).containsOnly(MethodShadowingChild.class.getMethod("method4", boolean.class), + MethodShadowingChild.class.getMethod("method1", String.class), + MethodShadowingChild.class.getMethod("method5", Long.class)); + assertThat(methods.subList(3, 5)).containsOnly( + MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), + MethodShadowingParent.class.getMethod("method5", String.class)); + assertEquals(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), methods.get(5)); + } + + @Test + void findMethodsWithShadowingUsingHierarchyDownMode() throws Exception { + assertThat(findMethods(MethodShadowingChild.class, methodContains1, TOP_DOWN))// + .containsExactly(MethodShadowingChild.class.getMethod("method1", String.class)); + + assertThat(findMethods(MethodShadowingChild.class, methodContains2, TOP_DOWN))// + .containsExactly(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), + MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class)); + + assertThat(findMethods(MethodShadowingChild.class, methodContains4, TOP_DOWN))// + .containsExactly(MethodShadowingChild.class.getMethod("method4", boolean.class)); + + assertThat(findMethods(MethodShadowingChild.class, methodContains5, TOP_DOWN))// + .containsExactly(MethodShadowingParent.class.getMethod("method5", String.class), + MethodShadowingChild.class.getMethod("method5", Long.class)); + + var methods = findMethods(MethodShadowingChild.class, method -> true, TOP_DOWN); + assertEquals(6, methods.size()); + assertEquals(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), methods.get(0)); + assertThat(methods.subList(1, 3)).containsOnly( + MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), + MethodShadowingParent.class.getMethod("method5", String.class)); + assertThat(methods.subList(3, 6)).containsOnly(MethodShadowingChild.class.getMethod("method4", boolean.class), + MethodShadowingChild.class.getMethod("method1", String.class), + MethodShadowingChild.class.getMethod("method5", Long.class)); + } + + @Test + void findMethodsWithStaticHidingUsingHierarchyUpMode() throws Exception { + Class ifc = StaticMethodHidingInterface.class; + Class parent = StaticMethodHidingParent.class; + Class child = StaticMethodHidingChild.class; + + var ifcMethod2 = ifc.getDeclaredMethod("method2", int.class, int.class); + var childMethod1 = child.getDeclaredMethod("method1", String.class); + var childMethod4 = child.getDeclaredMethod("method4", boolean.class); + var childMethod5 = child.getDeclaredMethod("method5", Long.class); + var parentMethod2 = parent.getDeclaredMethod("method2", int.class, int.class, int.class); + var parentMethod5 = parent.getDeclaredMethod("method5", String.class); + + assertThat(findMethods(child, methodContains1, BOTTOM_UP)).containsExactly(childMethod1); + assertThat(findMethods(child, methodContains2, BOTTOM_UP)).containsExactly(parentMethod2, ifcMethod2); + assertThat(findMethods(child, methodContains4, BOTTOM_UP)).containsExactly(childMethod4); + assertThat(findMethods(child, methodContains5, BOTTOM_UP)).containsExactly(childMethod5, parentMethod5); + + var methods = findMethods(child, method -> true, BOTTOM_UP); + assertEquals(6, methods.size()); + assertThat(methods.subList(0, 3)).containsOnly(childMethod1, childMethod4, childMethod5); + assertThat(methods.subList(3, 5)).containsOnly(parentMethod2, parentMethod5); + assertEquals(ifcMethod2, methods.get(5)); + } + + @Test + void findMethodsWithStaticHidingUsingHierarchyDownMode() throws Exception { + Class ifc = StaticMethodHidingInterface.class; + Class parent = StaticMethodHidingParent.class; + Class child = StaticMethodHidingChild.class; + + var ifcMethod2 = ifc.getDeclaredMethod("method2", int.class, int.class); + var childMethod1 = child.getDeclaredMethod("method1", String.class); + var childMethod4 = child.getDeclaredMethod("method4", boolean.class); + var childMethod5 = child.getDeclaredMethod("method5", Long.class); + var parentMethod2 = parent.getDeclaredMethod("method2", int.class, int.class, int.class); + var parentMethod5 = parent.getDeclaredMethod("method5", String.class); + + assertThat(findMethods(child, methodContains1, TOP_DOWN)).containsExactly(childMethod1); + assertThat(findMethods(child, methodContains2, TOP_DOWN)).containsExactly(ifcMethod2, parentMethod2); + assertThat(findMethods(child, methodContains4, TOP_DOWN)).containsExactly(childMethod4); + assertThat(findMethods(child, methodContains5, TOP_DOWN)).containsExactly(parentMethod5, childMethod5); + + var methods = findMethods(child, method -> true, TOP_DOWN); + assertEquals(6, methods.size()); + assertEquals(ifcMethod2, methods.get(0)); + assertThat(methods.subList(1, 3)).containsOnly(parentMethod2, parentMethod5); + assertThat(methods.subList(3, 6)).containsOnly(childMethod1, childMethod4, childMethod5); + } + + @Test + void findMethodsReturnsAllOverloadedMethodsThatAreNotShadowed() { + Class clazz = InterfaceWithGenericDefaultMethodImpl.class; + + // Search for all foo(*) methods. + var methods = findMethods(clazz, isFooMethod); + + // One might expect or desire that the signature for the generic foo(N) + // default method would be "foo(java.lang.Long)" when looked up via the + // concrete parameterized class, but it apparently is only _visible_ as + // "foo(java.lang.Number)" via reflection. + assertThat(signaturesOf(methods)).containsExactly("foo(java.lang.Number)", "foo(java.lang.Double)"); + } + + @Test + void findMethodsDoesNotReturnOverriddenDefaultMethods() { + Class clazz = InterfaceWithOverriddenGenericDefaultMethodImpl.class; + + // Search for all foo(*) methods. + var methods = findMethods(clazz, isFooMethod); + var signatures = signaturesOf(methods); + + // Although the subsequent assertion covers this case as well, this + // assertion is in place to provide a more informative failure message. + assertThat(signatures).as("overridden default method should not be in results").doesNotContain( + "foo(java.lang.Number)"); + assertThat(signatures).containsExactly("foo(java.lang.Long)", "foo(java.lang.Double)"); + } + + private static List signaturesOf(List methods) { + // @formatter:off + return methods.stream() + .map(m -> String.format("%s(%s)", m.getName(), ClassUtils.nullSafeToString(m.getParameterTypes()))) + .collect(toList()); + // @formatter:on + } + + @Test + void findMethodsIgnoresBridgeMethods() throws Exception { + assertFalse(Modifier.isPublic(PublicChildClass.class.getSuperclass().getModifiers())); + assertTrue(Modifier.isPublic(PublicChildClass.class.getModifiers())); + assertTrue(PublicChildClass.class.getDeclaredMethod("method1").isBridge()); + assertTrue(PublicChildClass.class.getDeclaredMethod("method3").isBridge()); + + var methods = findMethods(PublicChildClass.class, method -> true); + var signatures = signaturesOf(methods); + assertThat(signatures).containsOnly("method1()", "method2()", "method3()", "otherMethod1()", "otherMethod2()"); + assertEquals(0, methods.stream().filter(Method::isBridge).count()); + } + + @Test + void isGeneric() { + for (var method : Generic.class.getMethods()) { + assertTrue(ReflectionUtils.isGeneric(method)); + } + for (var method : PublicClass.class.getMethods()) { + assertFalse(ReflectionUtils.isGeneric(method)); + } + } + + @Test + void readFieldValuesPreconditions() { + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.readFieldValues(null, new Object())); + assertThrows(PreconditionViolationException.class, + () -> ReflectionUtils.readFieldValues(new ArrayList<>(), new Object(), null)); + } + + @Test + void readFieldValuesFromInstance() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, f -> true, TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields()); + + assertThat(values).containsExactly("enigma", 3.14, "text", 2.5, null, 42, "constant", 99); + } + + @Test + void readFieldValuesFromClass() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, ReflectionUtils::isStatic, TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, null); + + assertThat(values).containsExactly(2.5, "constant", 99); + } + + @Test + void readFieldValuesFromInstanceWithTypeFilterForString() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class), TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(String.class)); + + assertThat(values).containsExactly("enigma", "text", null, "constant"); + } + + @Test + void readFieldValuesFromClassWithTypeFilterForString() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class).and(ReflectionUtils::isStatic), + TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, null, isA(String.class)); + + assertThat(values).containsExactly("constant"); + } + + @Test + void readFieldValuesFromInstanceWithTypeFilterForInteger() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(int.class), TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(int.class)); + + assertThat(values).containsExactly(42); + } + + @Test + void readFieldValuesFromClassWithTypeFilterForInteger() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, + isA(Integer.class).and(ReflectionUtils::isStatic), TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, null, isA(Integer.class)); + + assertThat(values).containsExactly(99); + } + + @Test + void readFieldValuesFromInstanceWithTypeFilterForDouble() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(double.class), TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(double.class)); + + assertThat(values).containsExactly(3.14); + } + + @Test + void readFieldValuesFromClassWithTypeFilterForDouble() { + var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(Double.class).and(ReflectionUtils::isStatic), + TOP_DOWN); + + var values = ReflectionUtils.readFieldValues(fields, null, isA(Double.class)); + + assertThat(values).containsExactly(2.5); + } + + private Predicate isA(Class type) { + return f -> f.getType().isAssignableFrom(type); + } + + private static void createDirectories(Path... paths) throws IOException { + for (var path : paths) { + Files.createDirectory(path); + } + } + + // ------------------------------------------------------------------------- + + void methodWithPrimitiveArray(int[] nums) { + } + + void methodWithTwoDimensionalPrimitiveArray(int[][] grid) { + } + + void methodWithMultidimensionalPrimitiveArray(int[][][][][] grid) { + } + + void methodWithObjectArray(String[] info) { + } + + void methodWithTwoDimensionalObjectArray(String[][] info) { + } + + void methodWithMultidimensionalObjectArray(Double[][][][][] data) { + } + + void methodWithParameterizedMap(Map map) { + } + + interface StringsInterface1 { + static Stream strings() { + return Stream.of("abc", "def"); + } + } + + interface StringsInterface2 extends StringsInterface1 { + } + + /** + * Inherits strings() from interfaces StringsInterface1 and StringsInterface2. + */ + static class DoubleInheritedInterfaceMethodTestCase implements StringsInterface1, StringsInterface2 { + } + + interface Generic { + + X foo(); + + Y foo(X x, Y y); + + Z foo(Z[][] zees); + + int foo(T t); + + T foo(int i); + } + + class ClassWithSyntheticMethod { + + // The following lambda expression results in a synthetic method in the + // compiled byte code. + Comparable synthetic = number -> 0; + } + + interface InterfaceWithOneDeclaredMethod { + + void foo(); + } + + interface InterfaceWithDefaultMethod { + + default void foo() { + } + } + + static class InterfaceWithDefaultMethodImpl implements InterfaceWithDefaultMethod { + } + + interface InterfaceWithGenericDefaultMethod { + + default void foo(N number) { + } + } + + static class InterfaceWithGenericDefaultMethodImpl implements InterfaceWithGenericDefaultMethod { + + void foo(Double number) { + } + } + + static class InterfaceWithOverriddenGenericDefaultMethodImpl implements InterfaceWithGenericDefaultMethod { + + @Override + public void foo(Long number) { + } + + void foo(Double number) { + } + } + + interface InterfaceWithStaticMethod { + + static void foo() { + } + } + + static class InterfaceWithStaticMethodImpl implements InterfaceWithStaticMethod { + } + + interface InterfaceA { + } + + interface InterfaceB { + } + + interface InterfaceC extends InterfaceA, InterfaceB { + } + + interface InterfaceD { + } + + static class A implements InterfaceA, InterfaceD { + } + + static class B extends A implements InterfaceC { + } + + static class C { + + C() { + } + + C(String a, String b) { + } + + } + + static class Exploder { + + Exploder() { + throw new RuntimeException("boom"); + } + + } + + static class MyClass { + + static final int staticField = 42; + + final int instanceField; + + MyClass(int value) { + this.instanceField = value; + } + } + + static class MySubClass extends MyClass { + + MySubClass(int value) { + super(value); + } + } + + // Intentionally non-static + public class PublicClass { + + public void publicMethod() { + } + + public void method(String str, Integer num) { + } + + public void method(String[] strings, Integer[] nums) { + } + + public void method(boolean b, char c) { + } + + public void method(char[] characters, int[] nums) { + } + } + + private class PrivateClass { + + @SuppressWarnings("unused") + private void privateMethod() { + } + } + + protected class ProtectedClass { + + @SuppressWarnings("unused") + protected void protectedMethod() { + } + } + + class PackageVisibleClass { + + @SuppressWarnings("unused") + void packageVisibleMethod() { + } + } + + final class FinalClass { + + @SuppressWarnings("unused") + final void finalMethod() { + } + } + + abstract static class AbstractClass { + + abstract void abstractMethod(); + } + + static class StaticClass { + + static void staticMethod() { + } + } + + static class ClassWithVoidAndNonVoidMethods { + + void voidMethod() { + } + + Void methodReturningVoidReference() { + return null; + } + + String methodReturningObject() { + return ""; + } + + int methodReturningPrimitive() { + return 0; + } + + } + + static class InvocationTracker { + + static boolean publicStaticMethodInvoked; + static boolean privateStaticMethodInvoked; + + boolean publicMethodInvoked; + boolean privateMethodInvoked; + + public static void publicStaticMethod() { + publicStaticMethodInvoked = true; + } + + @SuppressWarnings("unused") + private static void privateStaticMethod() { + privateStaticMethodInvoked = true; + } + + public void publicMethod() { + publicMethodInvoked = true; + } + + @SuppressWarnings("unused") + private void privateMethod() { + privateMethodInvoked = true; + } + } + + static class FirstClass { + + class SecondClass { + + class ThirdClass { + } + } + } + + static class ClassWithNestedClasses { + + class Nested1 { + } + + class Nested2 { + } + + static class Nested3 { + } + } + + interface InterfaceWithNestedClass { + + class Nested4 { + } + } + + interface Interface45 extends InterfaceWithNestedClass { + + class Nested5 { + } + } + + static class ClassExtendingClassWithNestedClasses extends ClassWithNestedClasses implements Interface45 { + } + + abstract static class AbstractOuterClass { + + class InnerClass { + } + } + + static class OuterClass extends AbstractOuterClass { + + // sibling of OuterClass due to common super type + static class StaticNestedSiblingClass extends AbstractOuterClass { + } + + // sibling of OuterClass due to common super type + class InnerSiblingClass extends AbstractOuterClass { + } + + static class StaticNestedClass extends OuterClass { + } + + class RecursiveInnerClass extends OuterClass { + } + + class InnerClass { + class RecursiveInnerInnerClass extends OuterClass { + } + } + } + + static class OuterClassImplementingInterface implements InterfaceWithNestedClass { + + class InnerClassImplementingInterface implements InterfaceWithNestedClass { + } + } + + static class GrandparentClass { + + public void method1() { + } + + public void otherMethod1() { + } + } + + interface GrandparentInterface { + + default void method2() { + } + } + + static class ParentClass extends GrandparentClass implements GrandparentInterface { + + public void method3() { + } + + public void otherMethod2() { + } + } + + static class ChildClass extends ParentClass { + + public void method4() { + } + + public void otherMethod3() { + } + } + + interface MethodShadowingInterface { + + default void method1(String string) { + } + + default void method2(int i, int j) { + } + } + + static class MethodShadowingParent implements MethodShadowingInterface { + + @Override + public void method1(String string) { + } + + public void method2(int i, int j, int k) { + } + + public void method4(boolean flag) { + } + + public void method5(String string) { + } + } + + static class MethodShadowingChild extends MethodShadowingParent { + + @Override + public void method1(String string) { + } + + @Override + public void method4(boolean flag) { + } + + public void method5(Long i) { + } + } + + interface StaticMethodHidingInterface { + + static void method1(String string) { + } + + static void method2(int i, int j) { + } + } + + static class StaticMethodHidingParent implements StaticMethodHidingInterface { + + static void method1(String string) { + } + + static void method2(int i, int j, int k) { + } + + static void method4(boolean flag) { + } + + static void method5(String string) { + } + } + + static class StaticMethodHidingChild extends StaticMethodHidingParent { + + static void method1(String string) { + } + + static void method4(boolean flag) { + } + + static void method5(Long i) { + } + } + + // "public" modifier is necessary here, so that the compiler creates a bridge method. + public static class PublicChildClass extends ParentClass { + + @Override + public void otherMethod1() { + } + + @Override + public void otherMethod2() { + } + } + + public static class ClassWithFields { + + public static final String CONST = "constant"; + + public static final Integer CONST_INTEGER = 99; + + public static final Double CONST_DOUBLE = 2.5; + + public final String stringField = "text"; + + @SuppressWarnings("unused") + private final String privateStringField = "enigma"; + + final String nullStringField = null; + + public final int integerField = 42; + + public final double doubleField = 3.14; + + } + + @SuppressWarnings("unused") + private static class ClassWithOneCustomConstructor { + + ClassWithOneCustomConstructor(String str) { + } + } + + @SuppressWarnings("unused") + private static class ClassWithTwoConstructors { + + ClassWithTwoConstructors() { + } + + ClassWithTwoConstructors(String str) { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java new file mode 100644 index 00000000..8e8bb060 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.commons.util.ReflectionUtils.findMethod; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("TypeParameterExplicitlyExtendsObject") +class ReflectionUtilsWithGenericTypeHierarchiesTests { + @Test + @Disabled("Describes a new case that does not yet yield the expected result.") + void findsMethodsIndependentlyFromOrderOfImplementationsOfInterfaces() { + + class AB implements InterfaceDouble, InterfaceGenericNumber { + } + + class BA implements InterfaceGenericNumber, InterfaceDouble { + } + + var methodAB = findMethod(AB.class, "foo", Double.class).orElseThrow(); + var methodBA = findMethod(BA.class, "foo", Double.class).orElseThrow(); + + assertEquals(methodAB, methodBA); + } + + @Test + void findMoreSpecificMethodFromAbstractImplementationOverDefaultInterfaceMethod() { + class A implements InterfaceGenericNumber { + @Override + public void foo(Long parameter) { + } + } + + var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); + + assertEquals(A.class, foo.getDeclaringClass()); + } + + @Test + @Disabled("Describes a new case that does not yet yield the expected result.") + void findMoreSpecificMethodFromOverriddenImplementationOfGenericInterfaceMethod() { + class A implements InterfaceGenericNumber { + @Override + public void foo(Number parameter) { + } + } + + var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); + + assertEquals(A.class, foo.getDeclaringClass()); + } + + @Test + @Disabled("Describes a new case that does not yet yield the expected result.") + void findMoreSpecificMethodFromImplementationOverDefaultInterfaceMethodAndGenericClassExtension() { + + class AParent { + @SuppressWarnings("unused") + public void foo(Number parameter) { + } + } + + class A extends AParent implements InterfaceGenericNumber { + @Override + public void foo(Number parameter) { + } + } + + var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); + + assertEquals(A.class, foo.getDeclaringClass()); + } + + @Test + @Disabled("Expected behaviour is not clear yet.") + void unclearPrecedenceOfImplementationsInParentClassAndInterfaceDefault() { + + class AParent { + public void foo(@SuppressWarnings("unused") Number parameter) { + } + } + + class A extends AParent implements InterfaceGenericNumber { + } + + var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); + + // ???????? + assertEquals(A.class, foo.getDeclaringClass()); + assertEquals(AParent.class, foo.getDeclaringClass()); + assertEquals(InterfaceGenericNumber.class, foo.getDeclaringClass()); + } + + @Test + @Disabled("Describes cases where current implementation returns unexpected value") + public void findMethodWithMostSpecificParameterTypeInHierarchy() { + // Searched Parameter Type is more specific + assertSpecificFooMethodFound(ClassImplementingInterfaceWithInvertedHierarchy.class, + InterfaceWithGenericNumberParameter.class, Double.class); + assertSpecificFooMethodFound(ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, + ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, Double.class); + assertSpecificFooMethodFound(ClassImplementingGenericAndMoreSpecificInterface.class, + InterfaceWithGenericNumberParameter.class, Double.class); + assertSpecificFooMethodFound(ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface.class, + ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface.class, Double.class); + + // Exact Type Match + assertSpecificFooMethodFound(ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, + ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, Number.class); + } + + private void assertSpecificFooMethodFound(Class classToSearchIn, Class classWithMostSpecificMethod, + Class parameterType) { + var foo = findMethod(classToSearchIn, "foo", parameterType).orElseThrow(); + assertDeclaringClass(foo, classWithMostSpecificMethod); + } + + private void assertDeclaringClass(Method method, Class expectedClass) { + assertEquals(expectedClass, method.getDeclaringClass()); + } + + interface InterfaceDouble { + default void foo(@SuppressWarnings("unused") Double parameter) { + } + } + + interface InterfaceGenericNumber { + default void foo(@SuppressWarnings("unused") T parameter) { + } + } + + public interface InterfaceWithGenericObjectParameter { + default void foo(@SuppressWarnings("unused") T a) { + } + } + + public interface InterfaceWithGenericNumberParameter { + default void foo(@SuppressWarnings("unused") T a) { + } + } + + public interface InterfaceExtendingNumberInterfaceWithGenericObjectMethod + extends InterfaceWithGenericNumberParameter { + default void foo(@SuppressWarnings("unused") T a) { + } + } + + public static class ClassImplementingGenericInterfaceWithMoreSpecificMethod + implements InterfaceWithGenericObjectParameter { + public void foo(@SuppressWarnings("unused") Number a) { + } + } + + public static class ClassImplementingGenericAndMoreSpecificInterface + implements InterfaceWithGenericObjectParameter, InterfaceWithGenericNumberParameter { + } + + public static class ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface + implements InterfaceWithGenericObjectParameter, InterfaceWithGenericNumberParameter { + + @Override + public void foo(@SuppressWarnings("unused") T a) { + } + } + + public static class ClassImplementingInterfaceWithInvertedHierarchy + implements InterfaceExtendingNumberInterfaceWithGenericObjectMethod { + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java new file mode 100644 index 00000000..4174ab46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link RuntimeUtils}. + * + * @since 1.6 + */ +class RuntimeUtilsTests { + + @Test + void jmxIsAvailableAndInputArgumentsAreReturned() { + var optionalArguments = RuntimeUtils.getInputArguments(); + assertTrue(optionalArguments.isPresent(), "JMX not available or something else happened..."); + var arguments = optionalArguments.get(); + assertNotNull(arguments); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java new file mode 100644 index 00000000..f6989b4e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class SerializationUtils { + + public static Object deserialize(byte[] bytes) throws Exception { + try (var in = new ObjectInputStream(new ByteArrayInputStream(bytes))) { + return in.readObject(); + } + } + + public static byte[] serialize(Object object) throws Exception { + try (var byteArrayOutputStream = new ByteArrayOutputStream(); + var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) { + objectOutputStream.writeObject(object); + objectOutputStream.flush(); + return byteArrayOutputStream.toByteArray(); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java new file mode 100644 index 00000000..d74b1d52 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.StringUtils.containsIsoControlCharacter; +import static org.junit.platform.commons.util.StringUtils.containsWhitespace; +import static org.junit.platform.commons.util.StringUtils.doesNotContainIsoControlCharacter; +import static org.junit.platform.commons.util.StringUtils.doesNotContainWhitespace; +import static org.junit.platform.commons.util.StringUtils.isBlank; +import static org.junit.platform.commons.util.StringUtils.isNotBlank; +import static org.junit.platform.commons.util.StringUtils.nullSafeToString; +import static org.junit.platform.commons.util.StringUtils.replaceIsoControlCharacters; +import static org.junit.platform.commons.util.StringUtils.replaceWhitespaceCharacters; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link StringUtils}. + * + * @since 1.0 + */ +class StringUtilsTests { + + @Test + void blankness() { + // @formatter:off + assertAll("Blankness", + () -> assertTrue(isBlank(null)), + () -> assertTrue(isBlank("")), + () -> assertTrue(isBlank(" \t\n\r")), + () -> assertTrue(isNotBlank(".")) + ); + // @formatter:on + } + + @Test + void whitespace() { + // @formatter:off + assertAll("Whitespace", + () -> shouldContainWhitespace(" "), + () -> shouldContainWhitespace("\u005Ct"), // horizontal tab + () -> shouldContainWhitespace("\t"), + () -> shouldContainWhitespace("\u005Cn"), // line feed + () -> shouldContainWhitespace("\n"), + () -> shouldContainWhitespace("\u005Cf"), // form feed + () -> shouldContainWhitespace("\f"), + () -> shouldContainWhitespace("\u005Cr"), // carriage return + () -> shouldContainWhitespace("\r"), + () -> shouldContainWhitespace("hello world"), + () -> shouldNotContainWhitespace(null), + () -> shouldNotContainWhitespace(""), + () -> shouldNotContainWhitespace("hello-world"), + () -> shouldNotContainWhitespace("0123456789"), + () -> shouldNotContainWhitespace("$-_=+!@.,") + ); + // @formatter:on + } + + @Test + void controlCharacters() { + // @formatter:off + assertAll("ISO Control Characters", + () -> shouldContainIsoControlCharacter("\u005Ct"), // horizontal tab + () -> shouldContainIsoControlCharacter("\t"), + () -> shouldContainIsoControlCharacter("\u005Cn"), // line feed + () -> shouldContainIsoControlCharacter("\n"), + () -> shouldContainIsoControlCharacter("\u005Cf"), // form feed + () -> shouldContainIsoControlCharacter("\f"), + () -> shouldContainIsoControlCharacter("\u005Cr"), // carriage return + () -> shouldContainIsoControlCharacter("\r"), + () -> shouldNotContainIsoControlCharacter(null), + () -> shouldNotContainIsoControlCharacter(""), + () -> shouldNotContainIsoControlCharacter("hello-world"), + () -> shouldNotContainIsoControlCharacter("0123456789"), + () -> shouldNotContainIsoControlCharacter("$-_=+!@.,"), + () -> shouldNotContainIsoControlCharacter(" "), + () -> shouldNotContainIsoControlCharacter("hello world") + ); + // @formatter:on + } + + @Test + void replaceControlCharacters() { + assertNull(replaceIsoControlCharacters(null, "")); + assertEquals("", replaceIsoControlCharacters("", ".")); + assertEquals("", replaceIsoControlCharacters("\t\n\r", "")); + assertEquals("...", replaceIsoControlCharacters("\t\n\r", ".")); + assertEquals("...", replaceIsoControlCharacters("\u005Ct\u005Cn\u005Cr", ".")); + assertEquals("abc", replaceIsoControlCharacters("abc", "?")); + assertEquals("...", replaceIsoControlCharacters("...", "?")); + + assertThrows(PreconditionViolationException.class, () -> replaceIsoControlCharacters("", null)); + } + + @Test + void replaceWhitespaces() { + assertNull(replaceWhitespaceCharacters(null, "")); + assertEquals("", replaceWhitespaceCharacters("", ".")); + assertEquals("", replaceWhitespaceCharacters("\t\n\r", "")); + assertEquals("...", replaceWhitespaceCharacters("\t\n\r", ".")); + assertEquals("...", replaceWhitespaceCharacters("\u005Ct\u005Cn\u005Cr", ".")); + assertEquals("abc", replaceWhitespaceCharacters("abc", "?")); + assertEquals("...", replaceWhitespaceCharacters("...", "?")); + assertEquals(" ", replaceWhitespaceCharacters(" ", " ")); + assertEquals(" ", replaceWhitespaceCharacters("\u000B", " ")); + assertEquals(" ", replaceWhitespaceCharacters("\f", " ")); + + assertThrows(PreconditionViolationException.class, () -> replaceWhitespaceCharacters("", null)); + } + + @Test + void nullSafeToStringChecks() { + assertEquals("null", nullSafeToString(null)); + assertEquals("", nullSafeToString("")); + assertEquals("\t", nullSafeToString("\t")); + assertEquals("foo", nullSafeToString("foo")); + assertEquals("3.14", nullSafeToString(Double.valueOf("3.14"))); + assertEquals("[1, 2, 3]", nullSafeToString(new int[] { 1, 2, 3 })); + assertEquals("[a, b, c]", nullSafeToString(new char[] { 'a', 'b', 'c' })); + assertEquals("[foo, bar]", nullSafeToString(new String[] { "foo", "bar" })); + assertEquals("[34, 42]", nullSafeToString(new Integer[] { 34, 42 })); + assertEquals("[[2, 4], [3, 9]]", nullSafeToString(new Integer[][] { { 2, 4 }, { 3, 9 } })); + } + + @Test + void nullSafeToStringForObjectWhoseToStringImplementationReturnsNull() { + assertEquals("null", nullSafeToString(new ToStringReturnsNull())); + } + + @Test + void nullSafeToStringForObjectWhoseToStringImplementationThrowsAnException() { + assertThat(nullSafeToString(new ToStringThrowsException()))// + .startsWith(ToStringThrowsException.class.getName() + "@"); + } + + private void shouldContainWhitespace(String str) { + assertTrue(containsWhitespace(str), () -> String.format("'%s' should contain whitespace", str)); + assertFalse(doesNotContainWhitespace(str), () -> String.format("'%s' should contain whitespace", str)); + } + + private void shouldNotContainWhitespace(String str) { + assertTrue(doesNotContainWhitespace(str), () -> String.format("'%s' should not contain whitespace", str)); + assertFalse(containsWhitespace(str), () -> String.format("'%s' should not contain whitespace", str)); + } + + private void shouldContainIsoControlCharacter(String str) { + assertTrue(containsIsoControlCharacter(str), + () -> String.format("'%s' should contain ISO control character", str)); + assertFalse(doesNotContainIsoControlCharacter(str), + () -> String.format("'%s' should contain ISO control character", str)); + } + + private void shouldNotContainIsoControlCharacter(String str) { + assertTrue(doesNotContainIsoControlCharacter(str), + () -> String.format("'%s' should not contain ISO control character", str)); + assertFalse(containsIsoControlCharacter(str), + () -> String.format("'%s' should not contain ISO control character", str)); + } + + private static class ToStringReturnsNull { + + @Override + public String toString() { + return null; + } + } + + private static class ToStringThrowsException { + + @Override + public String toString() { + throw new RuntimeException("Boom!"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java new file mode 100644 index 00000000..9802f344 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link ToStringBuilder}. + * + * @since 1.0 + */ +class ToStringBuilderTests { + + @Test + void withNullObject() { + assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Object) null)); + } + + @Test + void withNullClass() { + assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Class) null)); + } + + @Test + void appendWithIllegalName() { + var builder = new ToStringBuilder(""); + + assertThrows(PreconditionViolationException.class, () -> builder.append(null, "")); + assertThrows(PreconditionViolationException.class, () -> builder.append("", "")); + assertThrows(PreconditionViolationException.class, () -> builder.append(" ", "")); + } + + @Test + void withZeroFields() { + assertEquals("RoleModel []", new ToStringBuilder(new RoleModel()).toString()); + assertEquals("RoleModel []", new ToStringBuilder(RoleModel.class).toString()); + } + + @Test + void withOneField() { + assertEquals("RoleModel [name = 'Dilbert']", + new ToStringBuilder(new RoleModel()).append("name", "Dilbert").toString()); + } + + @Test + void withNullField() { + assertEquals("RoleModel [name = null]", new ToStringBuilder(new RoleModel()).append("name", null).toString()); + } + + @Test + void withTwoFields() { + assertEquals("RoleModel [name = 'Dilbert', age = 42]", + new ToStringBuilder(new RoleModel()).append("name", "Dilbert").append("age", 42).toString()); + } + + @Test + void withIntegerArrayField() { + assertEquals("RoleModel [magic numbers = [1, 42, 99]]", + new ToStringBuilder(new RoleModel()).append("magic numbers", new Integer[] { 1, 42, 99 }).toString()); + } + + @Test + void withIntArrayField() { + assertEquals("RoleModel [magic numbers = [1, 42, 23]]", + new ToStringBuilder(new RoleModel()).append("magic numbers", new int[] { 1, 42, 23 }).toString()); + } + + @Test + void withCharArrayField() { + assertEquals("RoleModel [magic characters = [a, b]]", + new ToStringBuilder(new RoleModel()).append("magic characters", new char[] { 'a', 'b' }).toString()); + } + + @Test + void withPrimitiveBooleanArrayField() { + assertEquals("RoleModel [booleans = [true, false, true]]", + new ToStringBuilder(new RoleModel()).append("booleans", new boolean[] { true, false, true }).toString()); + } + + @Test + void withShortArrayField() { + assertEquals("RoleModel [values = [23, 42]]", + new ToStringBuilder(new RoleModel()).append("values", new short[] { 23, 42 }).toString()); + } + + @Test + void withByteArrayField() { + assertEquals("RoleModel [values = [23, 42]]", + new ToStringBuilder(new RoleModel()).append("values", new byte[] { 23, 42 }).toString()); + } + + @Test + void withPrimitiveLongArrayField() { + assertEquals("RoleModel [values = [23, 42]]", + new ToStringBuilder(new RoleModel()).append("values", new long[] { 23, 42 }).toString()); + } + + @Test + void withPrimitiveFloatArrayField() { + assertEquals("RoleModel [values = [23.45, 17.13]]", + new ToStringBuilder(new RoleModel()).append("values", new float[] { 23.45f, 17.13f }).toString()); + } + + @Test + void withPrimitiveDoubleArrayField() { + assertEquals("RoleModel [values = [23.45, 17.13]]", + new ToStringBuilder(new RoleModel()).append("values", new double[] { 23.45d, 17.13d }).toString()); + } + + @Test + @SuppressWarnings("serial") + void withMapField() { + // @formatter:off + Map map = new LinkedHashMap<>() {{ + put("foo", 42); + put("bar", "enigma"); + }}; + // @formatter:on + assertEquals("RoleModel [mystery map = {foo=42, bar=enigma}]", + new ToStringBuilder(new RoleModel()).append("mystery map", map).toString()); + } + + @Test + void withDemoImplementation() { + var roleModel = new RoleModel("Dilbert", 42); + assertEquals("RoleModel [name = 'Dilbert', age = 42]", roleModel.toString()); + } + + static class RoleModel { + + String name; + int age; + + RoleModel() { + } + + RoleModel(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("name", this.name) + .append("age", this.age) + .toString(); + // @formatter:on + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java new file mode 100644 index 00000000..d9d4b2ff --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.classes; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * @since 5.7 + */ +public class AExecutionConditionClass implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return ConditionEvaluationResult.enabled("for Class Name Filter Tests"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java new file mode 100644 index 00000000..99ad33e0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.classes; + +import org.junit.platform.launcher.TestExecutionListener; + +/** + * @since 5.7 + */ +public class ATestExecutionListenerClass implements TestExecutionListener { + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java new file mode 100644 index 00000000..bda71e43 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java @@ -0,0 +1,18 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.classes; + +/** + * @since 5.7 + */ +public class AVanillaEmpty { + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java new file mode 100644 index 00000000..a06b3300 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.classes; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * @since 5.7 + */ +public class BExecutionConditionClass implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return ConditionEvaluationResult.enabled("for Class Name Filter Tests"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java new file mode 100644 index 00000000..f8e9f5f2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.classes; + +import org.junit.platform.launcher.TestExecutionListener; + +/** + * @since 5.7 + */ +public class BTestExecutionListenerClass implements TestExecutionListener { + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java new file mode 100644 index 00000000..0ddbb172 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java @@ -0,0 +1,18 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.classes; + +/** + * @since 5.7 + */ +public class BVanillaEmpty { + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java new file mode 100644 index 00000000..1ed5017d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java @@ -0,0 +1,251 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.commons.util.ReflectionUtils.getFullyQualifiedMethodName; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.console.options.Details; +import org.junit.platform.console.options.Theme; +import org.opentest4j.TestAbortedException; + +/** + * @since 1.0 + */ +class ConsoleDetailsTests { + + @TestFactory + @DisplayName("Basic tests and annotations usage") + List basic() { + return scanContainerClassAndCreateDynamicTests(BasicTestCase.class); + } + + @TestFactory + @DisplayName("Skipped and disabled tests") + List skipped() { + return scanContainerClassAndCreateDynamicTests(SkipTestCase.class); + } + + @TestFactory + @DisplayName("Failed tests") + List failed() { + return scanContainerClassAndCreateDynamicTests(FailTestCase.class); + } + + @TestFactory + @DisplayName("Tests publishing report entries") + List reports() { + return scanContainerClassAndCreateDynamicTests(ReportTestCase.class); + } + + private List scanContainerClassAndCreateDynamicTests(Class containerClass) { + var containerName = containerClass.getSimpleName().replace("TestCase", ""); + // String containerName = containerClass.getSimpleName(); + List nodes = new ArrayList<>(); + Map> map = new EnumMap<>(Details.class); + for (var method : findMethods(containerClass, m -> m.isAnnotationPresent(Test.class))) { + var methodName = method.getName(); + var types = method.getParameterTypes(); + for (var details : Details.values()) { + var tests = map.computeIfAbsent(details, key -> new ArrayList<>()); + for (var theme : Theme.values()) { + var caption = containerName + "-" + methodName + "-" + details + "-" + theme; + String[] args = { // + "--include-engine", "junit-jupiter", // + "--details", details.name(), // + "--details-theme", theme.name(), // + "--disable-ansi-colors", // + "--disable-banner", // + "--include-classname", containerClass.getCanonicalName(), // + "--select-method", getFullyQualifiedMethodName(containerClass, methodName, types) // + }; + var displayName = methodName + "() " + theme.name(); + var dirName = "console/details/" + containerName.toLowerCase(); + var outName = caption + ".out.txt"; + var runner = new Runner(dirName, outName, args); + var source = toUri(dirName, outName).orElse(null); + tests.add(dynamicTest(displayName, source, runner)); + } + } + } + var source = new File("src/test/resources/console/details").toURI(); + map.forEach((details, tests) -> nodes.add(dynamicContainer(details.name(), source, tests.stream()))); + return nodes; + } + + @DisplayName("Basic") + static class BasicTestCase { + + @Test + void empty() { + } + + @Test + @DisplayName(".oO fancy display name Oo.") + void changeDisplayName() { + } + + } + + @DisplayName("Skip") + static class SkipTestCase { + + @Test + @Disabled("single line skip reason") + void skipWithSingleLineReason() { + } + + @Test + @Disabled("multi\nline\nfail\nmessage") + void skipWithMultiLineMessage() { + } + + } + + @DisplayName("Fail") + static class FailTestCase { + + @Test + void failWithSingleLineMessage() { + fail("single line fail message"); + } + + @Test + void failWithMultiLineMessage() { + fail("multi\nline\nfail\nmessage"); + } + + } + + @DisplayName("Report") + static class ReportTestCase { + + @Test + void reportSingleMessage(TestReporter reporter) { + reporter.publishEntry("foo"); + } + + @Test + void reportMultipleMessages(TestReporter reporter) { + reporter.publishEntry("foo"); + reporter.publishEntry("bar"); + } + + @Test + void reportSingleEntryWithSingleMapping(TestReporter reporter) { + reporter.publishEntry("foo", "bar"); + } + + @Test + void reportMultiEntriesWithSingleMapping(TestReporter reporter) { + reporter.publishEntry("foo", "bar"); + reporter.publishEntry("far", "boo"); + } + + @Test + void reportMultiEntriesWithMultiMappings(TestReporter reporter) { + Map values = new LinkedHashMap<>(); + values.put("user name", "dk38"); + values.put("award year", "1974"); + reporter.publishEntry(values); + reporter.publishEntry("single", "mapping"); + Map more = new LinkedHashMap<>(); + more.put("user name", "st77"); + more.put("award year", "1977"); + more.put("last seen", "2001"); + reporter.publishEntry(more); + } + + } + + private static class Runner implements Executable { + + private final String dirName; + private final String outName; + private final String[] args; + + private Runner(String dirName, String outName, String... args) { + this.dirName = dirName; + this.outName = outName; + this.args = args; + } + + @Override + public void execute() throws Throwable { + var wrapper = new ConsoleLauncherWrapper(); + var result = wrapper.execute(Optional.empty(), args); + + var optionalUri = toUri(dirName, outName); + if (optionalUri.isEmpty()) { + if (Boolean.getBoolean("org.junit.platform.console.ConsoleDetailsTests.writeResultOut")) { + // do not use Files.createTempDirectory(prefix) as we want one folder for one container + var temp = Paths.get(System.getProperty("java.io.tmpdir"), dirName.replace('/', '-')); + Files.createDirectories(temp); + var path = Files.writeString(temp.resolve(outName), result.out); + throw new TestAbortedException( + format("resource `%s` not found\nwrote console stdout to: %s/%s", dirName, outName, path)); + } + fail("could not load resource named `" + dirName + "/" + outName + "`"); + } + + var path = Paths.get(optionalUri.get()); + assumeTrue(Files.exists(path), "path does not exist: " + path); + assumeTrue(Files.isReadable(path), "can not read: " + path); + + var expectedLines = Files.readAllLines(path, UTF_8); + var actualLines = List.of(result.out.split("\\R")); + + assertLinesMatch(expectedLines, actualLines); + } + } + + static Optional toUri(String dirName, String outName) { + var resourceName = dirName + "/" + outName; + var url = ConsoleDetailsTests.class.getClassLoader().getResource(resourceName); + if (url == null) { + return Optional.empty(); + } + try { + return Optional.of(url.toURI()); + } + catch (URISyntaxException e) { + return Optional.empty(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java new file mode 100644 index 00000000..1d37cfc9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +/** + * @since 1.3 + */ +class ConsoleLauncherExecutionResultTests { + + private final CommandLineOptions options = new CommandLineOptions(); + private final TestExecutionSummary summary = mock(); + + @Test + void hasStatusCode0ForNoTotalFailures() { + when(summary.getTotalFailureCount()).thenReturn(0L); + + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); + + assertThat(exitCode).isEqualTo(0); + } + + @Test + void hasStatusCode1ForForAnyFailure() { + when(summary.getTotalFailureCount()).thenReturn(1L); + + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); + + assertThat(exitCode).isEqualTo(1); + } + + /** + * @since 1.3 + */ + @Test + void hasStatusCode2ForNoTestsAndHasOptionFailIfNoTestsFound() { + options.setFailIfNoTests(true); + when(summary.getTestsFoundCount()).thenReturn(0L); + + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); + + assertThat(exitCode).isEqualTo(2); + } + + /** + * @since 1.3 + */ + @Test + void hasStatusCode0ForTestsAndHasOptionFailIfNoTestsFound() { + options.setFailIfNoTests(true); + when(summary.getTestsFoundCount()).thenReturn(1L); + when(summary.getTotalFailureCount()).thenReturn(0L); + + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); + + assertThat(exitCode).isEqualTo(0); + } + + /** + * @since 1.3 + */ + @Test + void hasStatusCode0ForNoTestsAndNotFailIfNoTestsFound() { + options.setFailIfNoTests(false); + when(summary.getTestsFoundCount()).thenReturn(0L); + + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); + + assertThat(exitCode).isEqualTo(0); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java new file mode 100644 index 00000000..b8ccb37f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.StringUtils.isBlank; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.0 + */ +class ConsoleLauncherIntegrationTests { + + @Test + void executeWithoutArgumentsFailsAndPrintsHelpInformation() { + var result = new ConsoleLauncherWrapper().execute(-1); + assertAll("empty args array results in display of help information and an exception stacktrace", // + () -> assertTrue(result.out.contains("help information")), // + () -> assertTrue(result.err.contains("No arguments were supplied to the ConsoleLauncher")) // + ); + } + + @Test + void executeWithoutExcludeClassnameOptionDoesNotExcludeClassesAndMustIncludeAllClassesMatchingTheStandardClassnamePattern() { + String[] args = { "-e", "junit-jupiter", "-p", "org.junit.platform.console.subpackage" }; + assertEquals(9, new ConsoleLauncherWrapper().execute(args).getTestsFoundCount()); + } + + @Test + void executeWithExcludeClassnameOptionExcludesClasses() { + String[] args = { "-e", "junit-jupiter", "-p", "org.junit.platform.console.subpackage", "--exclude-classname", + "^org\\.junit\\.platform\\.console\\.subpackage\\..*" }; + var result = new ConsoleLauncherWrapper().execute(args); + assertAll("all subpackage test classes are excluded by the class name filter", // + () -> assertArrayEquals(args, result.args), // + () -> assertEquals(0, result.code), // + () -> assertEquals(0, result.getTestsFoundCount()), // + () -> assertTrue(isBlank(result.err)) // + ); + } + + @Test + void executeSelectingModuleNames() { + String[] args1 = { "-e", "junit-jupiter", "-o", "java.base" }; + assertEquals(0, new ConsoleLauncherWrapper().execute(args1).getTestsFoundCount()); + String[] args2 = { "-e", "junit-jupiter", "--select-module", "java.base" }; + assertEquals(0, new ConsoleLauncherWrapper().execute(args2).getTestsFoundCount()); + } + + @Test + void executeScanModules() { + String[] args1 = { "-e", "junit-jupiter", "--scan-modules" }; + assertEquals(0, new ConsoleLauncherWrapper().execute(args1).getTestsFoundCount()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java new file mode 100644 index 00000000..c22e94e1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.console.options.CommandLineOptionsParser; + +/** + * @since 1.0 + */ +class ConsoleLauncherTests { + + private final StringWriter stringWriter = new StringWriter(); + private final PrintWriter printSink = new PrintWriter(stringWriter); + + @Test + void displayHelp() { + var options = new CommandLineOptions(); + options.setDisplayHelp(true); + + var commandLineOptionsParser = mock(CommandLineOptionsParser.class); + when(commandLineOptionsParser.parse(any())).thenReturn(options); + + var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); + var exitCode = consoleLauncher.execute("--help").getExitCode(); + + assertEquals(0, exitCode); + verify(commandLineOptionsParser).parse("--help"); + } + + @Test + void displayBanner() { + var options = new CommandLineOptions(); + options.setBannerDisabled(false); + options.setDisplayHelp(true); + + var commandLineOptionsParser = mock(CommandLineOptionsParser.class); + when(commandLineOptionsParser.parse(any())).thenReturn(options); + + var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); + var exitCode = consoleLauncher.execute("--help").getExitCode(); + + assertEquals(0, exitCode); + assertLinesMatch( + List.of("", "Thanks for using JUnit! Support its development at https://junit.org/sponsoring", ""), + stringWriter.toString().lines().collect(Collectors.toList())); + } + + @Test + void disableBanner() { + var options = new CommandLineOptions(); + options.setBannerDisabled(true); + options.setDisplayHelp(true); + + var commandLineOptionsParser = mock(CommandLineOptionsParser.class); + when(commandLineOptionsParser.parse(any())).thenReturn(options); + + var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); + var exitCode = consoleLauncher.execute("--help", "--disable-banner").getExitCode(); + + assertEquals(0, exitCode); + assertLinesMatch(List.of(), stringWriter.toString().lines().collect(Collectors.toList())); + } + + @Test + void executeWithUnknownCommandLineOption() { + var commandLineOptionsParser = mock(CommandLineOptionsParser.class); + when(commandLineOptionsParser.parse(any())).thenReturn(new CommandLineOptions()); + + var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); + var exitCode = consoleLauncher.execute("--all").getExitCode(); + + assertEquals(-1, exitCode); + verify(commandLineOptionsParser).parse("--all"); + } + + @Test + void executeWithSupportedCommandLineOption() { + var commandLineOptionsParser = mock(CommandLineOptionsParser.class); + when(commandLineOptionsParser.parse(any())).thenReturn(new CommandLineOptions()); + + var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); + var exitCode = consoleLauncher.execute("--scan-classpath").getExitCode(); + + assertEquals(-1, exitCode); + verify(commandLineOptionsParser).parse("--scan-classpath"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java new file mode 100644 index 00000000..1740d079 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.StringUtils.isBlank; +import static org.junit.platform.commons.util.StringUtils.isNotBlank; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Optional; + +import org.junit.platform.console.options.CommandLineOptionsParser; +import org.junit.platform.console.options.PicocliCommandLineOptionsParser; + +/** + * @since 1.0 + */ +class ConsoleLauncherWrapper { + + private final StringWriter out = new StringWriter(); + private final StringWriter err = new StringWriter(); + private final ConsoleLauncher consoleLauncher; + + ConsoleLauncherWrapper() { + this(new PicocliCommandLineOptionsParser()); + } + + private ConsoleLauncherWrapper(CommandLineOptionsParser parser) { + var outWriter = new PrintWriter(out, false); + var errWriter = new PrintWriter(err, false); + this.consoleLauncher = new ConsoleLauncher(parser, outWriter, errWriter); + + } + + public ConsoleLauncherWrapperResult execute(String... args) { + return execute(0, args); + } + + public ConsoleLauncherWrapperResult execute(int expectedExitCode, String... args) { + return execute(Optional.of(expectedExitCode), args); + } + + public ConsoleLauncherWrapperResult execute(Optional expectedCode, String... args) { + var result = consoleLauncher.execute(args); + var code = result.getExitCode(); + var outText = out.toString(); + var errText = err.toString(); + if (expectedCode.isPresent()) { + int expectedValue = expectedCode.get(); + assertAll("wrapped execution failed:\n" + outText + "\n", // + () -> assertEquals(expectedValue, code, "ConsoleLauncher execute code mismatch!"), // + () -> assertTrue(expectedValue == 0 ? isBlank(errText) : isNotBlank(errText)) // + ); + } + return new ConsoleLauncherWrapperResult(args, outText, errText, result); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java new file mode 100644 index 00000000..bad59d3f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console; + +import java.io.PrintWriter; +import java.util.List; + +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +/** + * @since 1.0 + */ +class ConsoleLauncherWrapperResult implements TestExecutionSummary { + + final String[] args; + final String out; + final String err; + final int code; + private final TestExecutionSummary summary; + + ConsoleLauncherWrapperResult(String[] args, String out, String err, ConsoleLauncherExecutionResult result) { + this.args = args; + this.out = out; + this.err = err; + this.code = result.getExitCode(); + this.summary = result.getTestExecutionSummary().orElse(null); + } + + private void checkTestExecutionSummaryState() { + if (summary == null) { + throw new IllegalStateException("TestExecutionSummary not assigned. Exit code is: " + code); + } + } + + @Override + public long getTimeStarted() { + checkTestExecutionSummaryState(); + return summary.getTimeStarted(); + } + + @Override + public long getTimeFinished() { + checkTestExecutionSummaryState(); + return summary.getTimeFinished(); + } + + @Override + public long getTotalFailureCount() { + checkTestExecutionSummaryState(); + return summary.getTotalFailureCount(); + } + + @Override + public long getContainersFoundCount() { + checkTestExecutionSummaryState(); + return summary.getContainersFoundCount(); + } + + @Override + public long getContainersStartedCount() { + checkTestExecutionSummaryState(); + return summary.getContainersStartedCount(); + } + + @Override + public long getContainersSkippedCount() { + checkTestExecutionSummaryState(); + return summary.getContainersSkippedCount(); + } + + @Override + public long getContainersAbortedCount() { + checkTestExecutionSummaryState(); + return summary.getContainersAbortedCount(); + } + + @Override + public long getContainersSucceededCount() { + checkTestExecutionSummaryState(); + return summary.getContainersSucceededCount(); + } + + @Override + public long getContainersFailedCount() { + checkTestExecutionSummaryState(); + return summary.getContainersFailedCount(); + } + + @Override + public long getTestsFoundCount() { + checkTestExecutionSummaryState(); + return summary.getTestsFoundCount(); + } + + @Override + public long getTestsStartedCount() { + checkTestExecutionSummaryState(); + return summary.getTestsStartedCount(); + } + + @Override + public long getTestsSkippedCount() { + checkTestExecutionSummaryState(); + return summary.getTestsSkippedCount(); + } + + @Override + public long getTestsAbortedCount() { + checkTestExecutionSummaryState(); + return summary.getTestsAbortedCount(); + } + + @Override + public long getTestsSucceededCount() { + checkTestExecutionSummaryState(); + return summary.getTestsSucceededCount(); + } + + @Override + public long getTestsFailedCount() { + checkTestExecutionSummaryState(); + return summary.getTestsFailedCount(); + } + + @Override + public void printTo(PrintWriter writer) { + checkTestExecutionSummaryState(); + summary.printTo(writer); + } + + @Override + public void printFailuresTo(PrintWriter writer) { + checkTestExecutionSummaryState(); + summary.printFailuresTo(writer); + } + + @Override + public void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { + checkTestExecutionSummaryState(); + summary.printFailuresTo(writer, maxStackTraceLines); + } + + @Override + public List getFailures() { + checkTestExecutionSummaryState(); + return summary.getFailures(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java new file mode 100644 index 00000000..09e807eb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.Charset; + +import org.junit.jupiter.api.Test; + +class ConsoleUtilsTests { + + @Test + void consoleCharsetReportedByConsoleUtilsIsEitherNativeCharsetOrDefaultCharset() { + var console = System.console(); + var expected = console != null ? console.charset() : Charset.defaultCharset(); + assertEquals(expected, ConsoleUtils.charset()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java new file mode 100644 index 00000000..ed793dcf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java @@ -0,0 +1,655 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.platform.commons.JUnitException; + +/** + * @since 1.0 + */ +class PicocliCommandLineOptionsParserTests { + + @Test + void parseNoArguments() { + String[] noArguments = {}; + var options = createParser().parse(noArguments); + + // @formatter:off + assertAll( + () -> assertFalse(options.isAnsiColorOutputDisabled()), + () -> assertFalse(options.isDisplayHelp()), + () -> assertEquals(CommandLineOptions.DEFAULT_DETAILS, options.getDetails()), + () -> assertFalse(options.isScanClasspath()), + () -> assertEquals(List.of(STANDARD_INCLUDE_PATTERN), options.getIncludedClassNamePatterns()), + () -> assertEquals(List.of(), options.getExcludedClassNamePatterns()), + () -> assertEquals(List.of(), options.getIncludedPackages()), + () -> assertEquals(List.of(), options.getExcludedPackages()), + () -> assertEquals(List.of(), options.getIncludedTagExpressions()), + () -> assertEquals(List.of(), options.getExcludedTagExpressions()), + () -> assertEquals(List.of(), options.getAdditionalClasspathEntries()), + () -> assertEquals(Optional.empty(), options.getReportsDir()), + () -> assertEquals(List.of(), options.getSelectedUris()), + () -> assertEquals(List.of(), options.getSelectedFiles()), + () -> assertEquals(List.of(), options.getSelectedDirectories()), + () -> assertEquals(List.of(), options.getSelectedModules()), + () -> assertEquals(List.of(), options.getSelectedPackages()), + () -> assertEquals(List.of(), options.getSelectedMethods()), + () -> assertEquals(List.of(), options.getSelectedClasspathEntries()), + () -> assertEquals(Map.of(), options.getConfigurationParameters()) + ); + // @formatter:on + } + + @Test + void parseSwitches() { + // @formatter:off + assertAll( + () -> assertParses("disable ansi", CommandLineOptions::isAnsiColorOutputDisabled, "--disable-ansi-colors"), + () -> assertParses("help", CommandLineOptions::isDisplayHelp, "-h", "--help"), + () -> assertParses("scan class path", CommandLineOptions::isScanClasspath, "--scan-class-path") + ); + // @formatter:on + } + + @ParameterizedTest + @EnumSource + void parseValidDetails(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(Details.VERBOSE, type.parseArgLine("--details verbose").getDetails()), + () -> assertEquals(Details.TREE, type.parseArgLine("--details tree").getDetails()), + () -> assertEquals(Details.FLAT, type.parseArgLine("--details flat").getDetails()), + () -> assertEquals(Details.NONE, type.parseArgLine("--details NONE").getDetails()), + () -> assertEquals(Details.NONE, type.parseArgLine("--details none").getDetails()), + () -> assertEquals(Details.NONE, type.parseArgLine("--details None").getDetails()) + ); + // @formatter:on + } + + @Test + void parseInvalidDetails() { + assertOptionWithMissingRequiredArgumentThrowsException("--details"); + } + + @ParameterizedTest + @EnumSource + void parseValidDetailsTheme(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(Theme.ASCII, type.parseArgLine("--details-theme ascii").getTheme()), + () -> assertEquals(Theme.ASCII, type.parseArgLine("--details-theme ASCII").getTheme()), + () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme unicode").getTheme()), + () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme UNICODE").getTheme()), + () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme uniCode").getTheme()) + ); + // @formatter:on + } + + @Test + void parseInvalidDetailsTheme() { + assertOptionWithMissingRequiredArgumentThrowsException("--details-theme"); + } + + @ParameterizedTest + @EnumSource + void parseValidIncludeClassNamePatterns(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(".*Test"), type.parseArgLine("-n .*Test").getIncludedClassNamePatterns()), + () -> assertEquals(List.of(".*Test", ".*Tests"), type.parseArgLine("--include-classname .*Test --include-classname .*Tests").getIncludedClassNamePatterns()), + () -> assertEquals(List.of(".*Test"), type.parseArgLine("--include-classname=.*Test").getIncludedClassNamePatterns()) + ); + // @formatter:on + } + + @ParameterizedTest + @EnumSource + void parseValidExcludeClassNamePatterns(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(".*Test"), type.parseArgLine("-N .*Test").getExcludedClassNamePatterns()), + () -> assertEquals(List.of(".*Test", ".*Tests"), type.parseArgLine("--exclude-classname .*Test --exclude-classname .*Tests").getExcludedClassNamePatterns()), + () -> assertEquals(List.of(".*Test"), type.parseArgLine("--exclude-classname=.*Test").getExcludedClassNamePatterns()) + ); + // @formatter:on + } + + @Test + void usesDefaultClassNamePatternWithoutExplicitArgument() throws Exception { + assertEquals(List.of(STANDARD_INCLUDE_PATTERN), ArgsType.args.parseArgLine("").getIncludedClassNamePatterns()); + } + + @Test + void parseInvalidIncludeClassNamePatterns() { + assertOptionWithMissingRequiredArgumentThrowsException("-n", "--include-classname"); + } + + @Test + void parseInvalidExcludeClassNamePatterns() { + assertOptionWithMissingRequiredArgumentThrowsException("-N", "--exclude-classname"); + } + + @ParameterizedTest + @EnumSource + void parseValidIncludedPackages(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of("org.junit.included"), + type.parseArgLine("--include-package org.junit.included").getIncludedPackages()), + () -> assertEquals(List.of("org.junit.included"), + type.parseArgLine("--include-package=org.junit.included").getIncludedPackages()), + () -> assertEquals(List.of("org.junit.included1", "org.junit.included2"), + type.parseArgLine("--include-package org.junit.included1 --include-package org.junit.included2").getIncludedPackages()) + ); + // @formatter:on + } + + @ParameterizedTest + @EnumSource + void parseValidExcludedPackages(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of("org.junit.excluded"), + type.parseArgLine("--exclude-package org.junit.excluded").getExcludedPackages()), + () -> assertEquals(List.of("org.junit.excluded"), + type.parseArgLine("--exclude-package=org.junit.excluded").getExcludedPackages()), + () -> assertEquals(List.of("org.junit.excluded1", "org.junit.excluded2"), + type.parseArgLine("--exclude-package org.junit.excluded1 --exclude-package org.junit.excluded2").getExcludedPackages()) + ); + // @formatter:on + } + + @ParameterizedTest + @EnumSource + void parseValidIncludedTags(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of("fast"), type.parseArgLine("-t fast").getIncludedTagExpressions()), + () -> assertEquals(List.of("fast"), type.parseArgLine("--include-tag fast").getIncludedTagExpressions()), + () -> assertEquals(List.of("fast"), type.parseArgLine("--include-tag=fast").getIncludedTagExpressions()), + () -> assertEquals(List.of("fast", "slow"), type.parseArgLine("-t fast -t slow").getIncludedTagExpressions()) + ); + // @formatter:on + } + + @Test + void parseInvalidIncludedTags() { + assertOptionWithMissingRequiredArgumentThrowsException("-t", "--include-tag"); + } + + @ParameterizedTest + @EnumSource + void parseValidExcludedTags(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of("fast"), type.parseArgLine("-T fast").getExcludedTagExpressions()), + () -> assertEquals(List.of("fast"), type.parseArgLine("--exclude-tag fast").getExcludedTagExpressions()), + () -> assertEquals(List.of("fast"), type.parseArgLine("--exclude-tag=fast").getExcludedTagExpressions()), + () -> assertEquals(List.of("fast", "slow"), type.parseArgLine("-T fast -T slow").getExcludedTagExpressions()) + ); + // @formatter:on + } + + @Test + void parseInvalidExcludedTags() { + assertOptionWithMissingRequiredArgumentThrowsException("-T", "--exclude-tag"); + } + + @ParameterizedTest + @EnumSource + void parseValidIncludedEngines(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of("junit-jupiter"), type.parseArgLine("-e junit-jupiter").getIncludedEngines()), + () -> assertEquals(List.of("junit-vintage"), type.parseArgLine("--include-engine junit-vintage").getIncludedEngines()), + () -> assertEquals(List.of(), type.parseArgLine("").getIncludedEngines()) + ); + // @formatter:on + } + + @Test + void parseInvalidIncludedEngines() { + assertOptionWithMissingRequiredArgumentThrowsException("-e", "--include-engine"); + } + + @ParameterizedTest + @EnumSource + void parseValidExcludedEngines(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of("junit-jupiter"), type.parseArgLine("-E junit-jupiter").getExcludedEngines()), + () -> assertEquals(List.of("junit-vintage"), type.parseArgLine("--exclude-engine junit-vintage").getExcludedEngines()), + () -> assertEquals(List.of(), type.parseArgLine("").getExcludedEngines()) + ); + // @formatter:on + } + + @Test + void parseInvalidExcludedEngines() { + assertOptionWithMissingRequiredArgumentThrowsException("-E", "--exclude-engine"); + } + + @ParameterizedTest + @EnumSource + void parseValidAdditionalClasspathEntries(ArgsType type) { + var dir = Paths.get("."); + // @formatter:off + assertAll( + () -> assertEquals(List.of(dir), type.parseArgLine("-cp .").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("--cp .").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("-classpath .").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("-classpath=.").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("--classpath .").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("--classpath=.").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("--class-path .").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("--class-path=.").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir, Path.of("lib/some.jar")), type.parseArgLine("-cp . -cp lib/some.jar").getAdditionalClasspathEntries()), + () -> assertEquals(List.of(dir, Path.of("lib/some.jar")), type.parseArgLine("-cp ." + File.pathSeparator + "lib/some.jar").getAdditionalClasspathEntries()) + ); + // @formatter:on + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void parseValidAndAbsoluteAdditionalClasspathEntries() throws Exception { + ArgsType type = ArgsType.args; + assertEquals(List.of(Path.of("C:\\a.jar")), type.parseArgLine("-cp C:\\a.jar").getAdditionalClasspathEntries()); + assertEquals(List.of(Path.of("C:\\foo.jar"), Path.of("D:\\bar.jar")), + type.parseArgLine("-cp C:\\foo.jar;D:\\bar.jar").getAdditionalClasspathEntries()); + } + + @Test + void parseInvalidAdditionalClasspathEntries() { + assertOptionWithMissingRequiredArgumentThrowsException("-cp", "--classpath", "--class-path"); + } + + @ParameterizedTest + @EnumSource + void parseValidXmlReportsDirs(ArgsType type) { + var dir = Paths.get("build", "test-results"); + // @formatter:off + assertAll( + () -> assertEquals(Optional.of(dir), type.parseArgLine("--reports-dir build/test-results").getReportsDir()), + () -> assertEquals(Optional.of(dir), type.parseArgLine("--reports-dir=build/test-results").getReportsDir()) + ); + // @formatter:on + } + + @Test + void parseInvalidXmlReportsDirs() { + assertOptionWithMissingRequiredArgumentThrowsException("--reports-dir"); + } + + @ParameterizedTest + @EnumSource + void parseValidUriSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-u file:///foo.txt").getSelectedUris()), + () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--u file:///foo.txt").getSelectedUris()), + () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-select-uri file:///foo.txt").getSelectedUris()), + () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-select-uri=file:///foo.txt").getSelectedUris()), + () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri file:///foo.txt").getSelectedUris()), + () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri=file:///foo.txt").getSelectedUris()), + () -> assertEquals(List.of(selectUri("file:///foo.txt"), selectUri("https://example")), type.parseArgLine("-u file:///foo.txt -u https://example").getSelectedUris()) + ); + // @formatter:on + } + + @Test + void parseInvalidUriSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-u", "--select-uri", "-u unknown-scheme:"); + } + + @ParameterizedTest + @EnumSource + void parseValidFileSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-f foo.txt").getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--f foo.txt").getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-select-file foo.txt").getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-select-file=foo.txt").getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file foo.txt").getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file=foo.txt").getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt"), selectFile("bar.csv")), type.parseArgLine("-f foo.txt -f bar.csv").getSelectedFiles()) + ); + // @formatter:on + } + + @Test + void parseInvalidFileSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-f", "--select-file"); + } + + @ParameterizedTest + @EnumSource + void parseValidDirectorySelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-d foo/bar").getSelectedDirectories()), + () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--d foo/bar").getSelectedDirectories()), + () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-select-directory foo/bar").getSelectedDirectories()), + () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-select-directory=foo/bar").getSelectedDirectories()), + () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory foo/bar").getSelectedDirectories()), + () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory=foo/bar").getSelectedDirectories()), + () -> assertEquals(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux")), type.parseArgLine("-d foo/bar -d bar/qux").getSelectedDirectories()) + ); + // @formatter:on + } + + @Test + void parseInvalidDirectorySelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-d", "--select-directory"); + } + + @ParameterizedTest + @EnumSource + void parseValidModuleSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-o com.acme.foo").getSelectedModules()), + () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--o com.acme.foo").getSelectedModules()), + () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-select-module com.acme.foo").getSelectedModules()), + () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-select-module=com.acme.foo").getSelectedModules()), + () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module com.acme.foo").getSelectedModules()), + () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module=com.acme.foo").getSelectedModules()), + () -> assertEquals(List.of(selectModule("com.acme.foo"), selectModule("com.example.bar")), type.parseArgLine("-o com.acme.foo -o com.example.bar").getSelectedModules()) + ); + // @formatter:on + } + + @Test + void parseInvalidModuleSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-o", "--select-module"); + } + + @ParameterizedTest + @EnumSource + void parseValidPackageSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-p com.acme.foo").getSelectedPackages()), + () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--p com.acme.foo").getSelectedPackages()), + () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-select-package com.acme.foo").getSelectedPackages()), + () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-select-package=com.acme.foo").getSelectedPackages()), + () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package com.acme.foo").getSelectedPackages()), + () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package=com.acme.foo").getSelectedPackages()), + () -> assertEquals(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar")), type.parseArgLine("-p com.acme.foo -p com.example.bar").getSelectedPackages()) + ); + // @formatter:on + } + + @Test + void parseInvalidPackageSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-p", "--select-package"); + } + + @ParameterizedTest + @EnumSource + void parseValidClassSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-c com.acme.Foo").getSelectedClasses()), + () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--c com.acme.Foo").getSelectedClasses()), + () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-select-class com.acme.Foo").getSelectedClasses()), + () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-select-class=com.acme.Foo").getSelectedClasses()), + () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class com.acme.Foo").getSelectedClasses()), + () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class=com.acme.Foo").getSelectedClasses()), + () -> assertEquals(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar")), type.parseArgLine("-c com.acme.Foo -c com.example.Bar").getSelectedClasses()) + ); + // @formatter:on + } + + @Test + void parseInvalidClassSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-c", "--select-class"); + } + + @ParameterizedTest + @EnumSource + void parseValidMethodSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("-m com.acme.Foo#m()").getSelectedMethods()), + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--m com.acme.Foo#m()").getSelectedMethods()), + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("-select-method com.acme.Foo#m()").getSelectedMethods()), + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("-select-method=com.acme.Foo#m()").getSelectedMethods()), + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method com.acme.Foo#m()").getSelectedMethods()), + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method=com.acme.Foo#m()").getSelectedMethods()), + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)")), + type.parseArgLine("-m com.acme.Foo#m() -m com.example.Bar#method(java.lang.Object)").getSelectedMethods()) + ); + // @formatter:on + } + + @Test + void parseInvalidMethodSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-m", "--select-method"); + } + + @ParameterizedTest + @EnumSource + void parseValidClasspathResourceSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-r /foo.csv").getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--r /foo.csv").getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-select-resource /foo.csv").getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-select-resource=/foo.csv").getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource /foo.csv").getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource=/foo.csv").getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv"), selectClasspathResource("bar.json")), type.parseArgLine("-r /foo.csv -r bar.json").getSelectedClasspathResources()) + ); + // @formatter:on + } + + @Test + void parseInvalidClasspathResourceSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-r", "--select-resource"); + } + + @ParameterizedTest + @EnumSource + void parseValidIterationSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectIteration(selectClasspathResource("/foo.csv"), 0)), type.parseArgLine("-i resource:/foo.csv[0]").getSelectedIterations()), + () -> assertEquals(List.of(selectIteration(selectMethod("com.acme.Foo#m()"), 1, 2)), type.parseArgLine("--i method:com.acme.Foo#m()[1..2]").getSelectedIterations()), + () -> assertEquals(List.of(selectIteration(selectClass("com.acme.Foo"), 0, 2)), type.parseArgLine("-select-iteration class:com.acme.Foo[0,2]").getSelectedIterations()), + () -> assertEquals(List.of(selectIteration(selectPackage("com.acme.foo"), 3)), type.parseArgLine("-select-iteration=package:com.acme.foo[3]").getSelectedIterations()), + () -> assertEquals(List.of(selectIteration(selectModule("com.acme.foo"), 0, 1, 2, 4, 5, 6)), type.parseArgLine("--select-iteration module:com.acme.foo[0..2,4..6]").getSelectedIterations()), + () -> assertEquals(List.of(selectIteration(selectDirectory("foo/bar"), 1, 5)), type.parseArgLine("--select-iteration=directory:foo/bar[1,5]").getSelectedIterations()), + () -> assertEquals(List.of(selectIteration(selectFile("foo.txt"), 6), selectIteration(selectUri("file:///foo.txt"), 7)), type.parseArgLine("-i file:foo.txt[6] -i uri:file:///foo.txt[7]").getSelectedIterations()) + ); + // @formatter:on + } + + @Test + void parseInvalidIterationSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("-i", "--select-iteration"); + } + + @ParameterizedTest + @EnumSource + void parseClasspathScanningEntries(ArgsType type) { + var dir = Paths.get("."); + // @formatter:off + assertAll( + () -> assertTrue(type.parseArgLine("--scan-class-path").isScanClasspath()), + () -> assertEquals(List.of(), type.parseArgLine("--scan-class-path").getSelectedClasspathEntries()), + () -> assertTrue(type.parseArgLine("--scan-classpath").isScanClasspath()), + () -> assertEquals(List.of(), type.parseArgLine("--scan-classpath").getSelectedClasspathEntries()), + () -> assertTrue(type.parseArgLine("--scan-class-path .").isScanClasspath()), + () -> assertEquals(List.of(dir), type.parseArgLine("--scan-class-path .").getSelectedClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("--scan-class-path=.").getSelectedClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("-scan-class-path .").getSelectedClasspathEntries()), + () -> assertEquals(List.of(dir), type.parseArgLine("-scan-class-path=.").getSelectedClasspathEntries()), + () -> assertEquals(List.of(dir, Paths.get("src/test/java")), type.parseArgLine("--scan-class-path . --scan-class-path src/test/java").getSelectedClasspathEntries()), + () -> assertEquals(List.of(dir, Paths.get("src/test/java")), type.parseArgLine("--scan-class-path ." + File.pathSeparator + "src/test/java").getSelectedClasspathEntries()) + ); + // @formatter:on + } + + @ParameterizedTest + @EnumSource + void parseValidConfigurationParameters(ArgsType type) { + // @formatter:off + assertAll( + () -> assertThat(type.parseArgLine("-config foo=bar").getConfigurationParameters()) + .containsOnly(entry("foo", "bar")), + () -> assertThat(type.parseArgLine("-config=foo=bar").getConfigurationParameters()) + .containsOnly(entry("foo", "bar")), + () -> assertThat(type.parseArgLine("--config foo=bar").getConfigurationParameters()) + .containsOnly(entry("foo", "bar")), + () -> assertThat(type.parseArgLine("--config=foo=bar").getConfigurationParameters()) + .containsOnly(entry("foo", "bar")), + () -> assertThat(type.parseArgLine("--config foo=bar --config baz=qux").getConfigurationParameters()) + .containsExactly(entry("foo", "bar"), entry("baz", "qux")) + ); + // @formatter:on + } + + @Test + void parseInvalidConfigurationParameters() { + assertOptionWithMissingRequiredArgumentThrowsException("-config", "--config"); + } + + @ParameterizedTest + @EnumSource + void parseInvalidConfigurationParametersWithDuplicateKey(ArgsType type) { + Exception e = assertThrows(JUnitException.class, () -> type.parseArgLine("--config foo=bar --config foo=baz")); + + assertThat(e.getMessage()).isEqualTo( + "Error parsing command-line arguments: Duplicate key 'foo' for values 'bar' and 'baz'."); + assertThat(e.getCause().getMessage()).isEqualTo("Duplicate key 'foo' for values 'bar' and 'baz'."); + } + + @Test + void printHelpOutputsHelpOption() { + var writer = new StringWriter(); + + createParser().printHelp(writer, true); + + assertThat(writer.toString()).contains("--help"); + } + + @Test + void printHelpPreservesOriginalIOException() { + var writer = new Writer() { + + @SuppressWarnings("NullableProblems") + @Override + public void write(char[] buffer, int off, int len) throws IOException { + throw new IOException("Something went wrong"); + } + + @Override + public void flush() { + } + + @Override + public void close() { + } + }; + + var parser = createParser(); + var exception = assertThrows(RuntimeException.class, () -> parser.printHelp(writer, true)); + + assertThat(exception).hasCauseInstanceOf(IOException.class); + assertThat(exception.getCause()).hasMessage("Something went wrong"); + } + + private void assertOptionWithMissingRequiredArgumentThrowsException(String... options) { + assertAll(Stream.of(options).map( + opt -> () -> assertThrows(JUnitException.class, () -> ArgsType.args.parseArgLine(opt)))); + } + + private void assertParses(String name, Predicate property, String... argLines) { + Stream.of(argLines).forEach(argLine -> { + CommandLineOptions options = null; + try { + options = ArgsType.args.parseArgLine(argLine); + } + catch (IOException e) { + fail(e); + } + assertTrue(property.test(options), () -> name + " should be enabled by: " + argLine); + }); + } + + enum ArgsType { + args { + CommandLineOptions parseArgLine(String argLine) { + return createParser().parse(split(argLine)); + } + }, + atFile { + CommandLineOptions parseArgLine(String argLine) throws IOException { + var atFile = Files.createTempFile("junit-launcher-args", ".txt"); + try { + Files.write(atFile, List.of(split(argLine))); + return createParser().parse("@" + atFile); + } + finally { + Files.deleteIfExists(atFile); + } + } + }; + abstract CommandLineOptions parseArgLine(String argLine) throws IOException; + + private static String[] split(String argLine) { + return "".equals(argLine) ? new String[0] : argLine.split("\\s+"); + } + } + + private static CommandLineOptionsParser createParser() { + return new PicocliCommandLineOptionsParser(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java new file mode 100644 index 00000000..8c96fed4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; + +class ThemeTests { + + @Test + void givenUtf8ShouldReturnUnicode() { + assertEquals(Theme.UNICODE, Theme.valueOf(StandardCharsets.UTF_8)); + } + + @Test + void givenAnythingElseShouldReturnAscii() { + assertAll("All character sets that are not UTF-8 should return Theme.ASCII", () -> { + assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.ISO_8859_1)); + assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.US_ASCII)); + assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.UTF_16)); + }); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java new file mode 100644 index 00000000..b8181cae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named *Test in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class ContainerForInnerTest { + + @Nested + class NestedTest { + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java new file mode 100644 index 00000000..410e9d8b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named *Tests in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class ContainerForInnerTests { + + @Nested + class NestedTests { + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java new file mode 100644 index 00000000..8143890b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named *Test in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class ContainerForStaticNestedTest { + + static class NestedTest { + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java new file mode 100644 index 00000000..c1338725 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named *Tests in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class ContainerForStaticNestedTests { + + static class NestedTests { + + @Test + void test() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java new file mode 100644 index 00000000..56bccb3e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named *Test in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class SecondTest { + + @Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java new file mode 100644 index 00000000..bcecdc12 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named Test in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class Test { + + @org.junit.jupiter.api.Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java new file mode 100644 index 00000000..d554e0b2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named Test* in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class Test1 { + + @Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java new file mode 100644 index 00000000..52142a32 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named Tests in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class Tests { + + @Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java new file mode 100644 index 00000000..9de75239 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import org.junit.jupiter.api.Test; + +// Even though our "example test classes" should be named *TestCase +// according to team policy, this class must be named *Tests in order +// for ConsoleLauncherIntegrationTests to pass, but that's not a +// problem since the test method here can never fail. +class ThirdTests { + + @Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java new file mode 100644 index 00000000..3a0909c3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java @@ -0,0 +1,207 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +import java.io.PrintWriter; +import java.io.StringReader; +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.platform.console.options.Theme; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * Unit tests for {@link ColorPalette}. + * + * @since 1.9 + */ +class ColorPaletteTests { + + @Nested + class LoadFromPropertiesTests { + + @Test + void singleOverride() { + String properties = """ + SUCCESSFUL = 35;1 + """; + ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); + + String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); + + assertEquals("\u001B[35;1mtext\u001B[0m", actual); + } + + @Test + void keysAreCaseInsensitive() { + String properties = """ + suCcESSfuL = 35;1 + """; + ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); + + String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); + + assertEquals("\u001B[35;1mtext\u001B[0m", actual); + } + + @Test + void junkKeysAreIgnored() { + String properties = """ + SUCCESSFUL = 35;1 + JUNK = 1;31;40 + """; + ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); + + String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); + + assertEquals("\u001B[35;1mtext\u001B[0m", actual); + } + + @Test + void multipleOverrides() { + String properties = """ + SUCCESSFUL = 35;1 + FAILED = 33;4 + """; + ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); + + String successful = colorPalette.paint(Style.SUCCESSFUL, "text"); + String failed = colorPalette.paint(Style.FAILED, "text"); + + assertEquals("\u001B[35;1mtext\u001B[0m", successful); + assertEquals("\u001B[33;4mtext\u001B[0m", failed); + } + + @Test + void unspecifiedStylesAreDefault() { + String properties = """ + SUCCESSFUL = 35;1 + """; + ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); + + String actual = colorPalette.paint(Style.FAILED, "text"); + + assertEquals("\u001B[31mtext\u001B[0m", actual); + } + + @Test + void cannotOverrideNone() { + String properties = """ + NONE = 35;1 + """; + StringReader reader = new StringReader(properties); + + assertThrows(IllegalArgumentException.class, () -> new ColorPalette(reader)); + } + } + + /** + * TODO Actually assert something in these "demo" tests and stop printing to SYSOUT. + */ + @Nested + class DemonstratePalettesTests { + + @Test + void verbose_default() { + PrintWriter out = new PrintWriter(System.out); + TestExecutionListener listener = new VerboseTreePrintingListener(out, ColorPalette.DEFAULT, 16, + Theme.ASCII); + + demoTestRun(listener); + + assertDoesNotThrow(out::flush); + } + + @Test + void verbose_single_color() { + PrintWriter out = new PrintWriter(System.out); + TestExecutionListener listener = new VerboseTreePrintingListener(out, ColorPalette.SINGLE_COLOR, 16, + Theme.ASCII); + + demoTestRun(listener); + + assertDoesNotThrow(out::flush); + } + + @Test + void simple_default() { + PrintWriter out = new PrintWriter(System.out); + TestExecutionListener listener = new TreePrintingListener(out, ColorPalette.DEFAULT, Theme.ASCII); + + demoTestRun(listener); + + assertDoesNotThrow(out::flush); + } + + @Test + void simple_single_color() { + PrintWriter out = new PrintWriter(System.out); + TestExecutionListener listener = new TreePrintingListener(out, ColorPalette.SINGLE_COLOR, Theme.ASCII); + + demoTestRun(listener); + + assertDoesNotThrow(out::flush); + } + + @Test + void flat_default() { + PrintWriter out = new PrintWriter(System.out); + TestExecutionListener listener = new FlatPrintingListener(out, ColorPalette.DEFAULT); + + demoTestRun(listener); + + assertDoesNotThrow(out::flush); + } + + @Test + void flat_single_color() { + PrintWriter out = new PrintWriter(System.out); + TestExecutionListener listener = new FlatPrintingListener(out, ColorPalette.SINGLE_COLOR); + + demoTestRun(listener); + + assertDoesNotThrow(out::flush); + } + + private void demoTestRun(TestExecutionListener listener) { + TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "My Test"); + TestPlan testPlan = TestPlan.from(List.of(testDescriptor), mock()); + listener.testPlanExecutionStarted(testPlan); + listener.executionStarted(TestIdentifier.from(testDescriptor)); + listener.executionFinished(TestIdentifier.from(testDescriptor), TestExecutionResult.successful()); + listener.dynamicTestRegistered(TestIdentifier.from(testDescriptor)); + listener.executionStarted(TestIdentifier.from(testDescriptor)); + listener.executionFinished(TestIdentifier.from(testDescriptor), + TestExecutionResult.failed(new Exception())); + listener.executionStarted(TestIdentifier.from(testDescriptor)); + listener.executionFinished(TestIdentifier.from(testDescriptor), + TestExecutionResult.aborted(new Exception())); + listener.reportingEntryPublished(TestIdentifier.from(testDescriptor), ReportEntry.from("Key", "Value")); + listener.executionSkipped(TestIdentifier.from(testDescriptor), "to demonstrate skipping"); + listener.testPlanExecutionFinished(testPlan); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java new file mode 100644 index 00000000..02c270af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java @@ -0,0 +1,183 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Paths; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.platform.console.ConsoleLauncherExecutionResult; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.console.options.Details; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; + +/** + * @since 1.0 + */ +class ConsoleTestExecutorTests { + + private static final Runnable FAILING_BLOCK = () -> fail("should fail"); + private static final Runnable SUCCEEDING_TEST = () -> { + }; + + private final StringWriter stringWriter = new StringWriter(); + private final CommandLineOptions options = new CommandLineOptions(); + private DemoHierarchicalTestEngine dummyTestEngine = new DemoHierarchicalTestEngine(); + + { + options.setScanClasspath(true); + } + + @Test + void printsSummary() throws Exception { + dummyTestEngine.addTest("succeedingTest", SUCCEEDING_TEST); + dummyTestEngine.addTest("failingTest", FAILING_BLOCK); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + task.execute(new PrintWriter(stringWriter)); + + assertThat(stringWriter.toString()).contains("Test run finished after", "2 tests found", "0 tests skipped", + "2 tests started", "0 tests aborted", "1 tests successful", "1 tests failed"); + } + + @Test + void printsDetailsIfTheyAreNotHidden() throws Exception { + options.setDetails(Details.FLAT); + + dummyTestEngine.addTest("failingTest", FAILING_BLOCK); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + task.execute(new PrintWriter(stringWriter)); + + assertThat(stringWriter.toString()).contains("Test execution started."); + } + + @Test + void printsNoDetailsIfTheyAreHidden() throws Exception { + options.setDetails(Details.NONE); + + dummyTestEngine.addTest("failingTest", FAILING_BLOCK); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + task.execute(new PrintWriter(stringWriter)); + + assertThat(stringWriter.toString()).doesNotContain("Test execution started."); + } + + @Test + void printsFailuresEvenIfDetailsAreHidden() throws Exception { + options.setDetails(Details.NONE); + + dummyTestEngine.addTest("failingTest", FAILING_BLOCK); + dummyTestEngine.addContainer("failingContainer", FAILING_BLOCK); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + task.execute(new PrintWriter(stringWriter)); + + assertThat(stringWriter.toString()).contains("Failures (2)", "failingTest", "failingContainer"); + } + + @Test + void hasStatusCode0ForSucceedingTest() throws Exception { + dummyTestEngine.addTest("succeedingTest", SUCCEEDING_TEST); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); + + assertThat(exitCode).isEqualTo(0); + } + + @Test + void hasStatusCode1ForFailingTest() throws Exception { + dummyTestEngine.addTest("failingTest", FAILING_BLOCK); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); + + assertThat(exitCode).isEqualTo(1); + } + + @Test + void hasStatusCode1ForFailingContainer() throws Exception { + dummyTestEngine.addContainer("failingContainer", FAILING_BLOCK); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); + + assertThat(exitCode).isEqualTo(1); + } + + /** + * @since 1.3 + */ + @Test + void hasStatusCode2ForNoTestsAndHasOptionFailIfNoTestsFound() throws Exception { + options.setFailIfNoTests(true); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); + + assertThat(exitCode).isEqualTo(2); + } + + /** + * @since 1.3 + */ + @Test + void hasStatusCode0ForNoTestsAndNotFailIfNoTestsFound() throws Exception { + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); + + assertThat(exitCode).isEqualTo(0); + } + + @Test + void usesCustomClassLoaderIfAdditionalClassPathEntriesArePresent() throws Exception { + options.setAdditionalClasspathEntries(List.of(Paths.get("."))); + + var oldClassLoader = getDefaultClassLoader(); + dummyTestEngine.addTest("failingTest", + () -> assertSame(oldClassLoader, getDefaultClassLoader(), "should fail")); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + task.execute(new PrintWriter(stringWriter)); + + assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); + } + + @Test + void usesSameClassLoaderIfNoAdditionalClassPathEntriesArePresent() throws Exception { + options.setAdditionalClasspathEntries(List.of()); + + var oldClassLoader = getDefaultClassLoader(); + dummyTestEngine.addTest("failingTest", + () -> assertNotSame(oldClassLoader, getDefaultClassLoader(), "should fail")); + + var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); + task.execute(new PrintWriter(stringWriter)); + + assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); + } + + private PrintWriter dummyWriter() { + return new PrintWriter(new StringWriter()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java new file mode 100644 index 00000000..ef53d1fe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.0 + */ +class CustomContextClassLoaderExecutorTests { + + @Test + void invokeWithoutCustomClassLoaderDoesNotSetClassLoader() throws Exception { + var originalClassLoader = Thread.currentThread().getContextClassLoader(); + var executor = new CustomContextClassLoaderExecutor(Optional.empty()); + + int result = executor.invoke(() -> { + assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); + return 42; + }); + + assertEquals(42, result); + assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); + } + + @Test + void invokeWithCustomClassLoaderSetsCustomAndResetsToOriginal() throws Exception { + var originalClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader customClassLoader = URLClassLoader.newInstance(new URL[0]); + var executor = new CustomContextClassLoaderExecutor(Optional.of(customClassLoader)); + + int result = executor.invoke(() -> { + assertSame(customClassLoader, Thread.currentThread().getContextClassLoader()); + return 23; + }); + + assertEquals(23, result); + assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); + } + + @Test + void invokeWithCustomClassLoaderAndEnsureItIsClosedAfterUsage() throws Exception { + var closed = new AtomicBoolean(false); + ClassLoader localClassLoader = new URLClassLoader(new URL[0]) { + @Override + public void close() throws IOException { + closed.set(true); + super.close(); + } + }; + var executor = new CustomContextClassLoaderExecutor(Optional.of(localClassLoader)); + + int result = executor.invoke(() -> 4711); + + assertEquals(4711, result); + assertTrue(closed.get()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java new file mode 100644 index 00000000..3456eb8f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java @@ -0,0 +1,339 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; + +import java.io.File; +import java.net.URI; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.console.options.CommandLineOptions; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.IterationSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.PackageNameFilter; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UriSelector; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * @since 1.0 + */ +class DiscoveryRequestCreatorTests { + + private final CommandLineOptions options = new CommandLineOptions(); + + @Test + void convertsScanClasspathOptionWithoutExplicitRootDirectories() { + options.setScanClasspath(true); + + var request = convert(); + + var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); + // @formatter:off + assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) + .hasAtLeastOneElementOfType(URI.class) + .doesNotContainNull(); + // @formatter:on + } + + @Test + void convertsScanClasspathOptionWithExplicitRootDirectories() { + options.setScanClasspath(true); + options.setSelectedClasspathEntries(List.of(Paths.get("."), Paths.get(".."))); + + var request = convert(); + + var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); + // @formatter:off + assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) + .containsExactly(new File(".").toURI(), new File("..").toURI()); + // @formatter:on + } + + @Test + void convertsScanClasspathOptionWithAdditionalClasspathEntries() { + options.setScanClasspath(true); + options.setAdditionalClasspathEntries(List.of(Paths.get("."), Paths.get(".."))); + + var request = convert(); + + var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); + // @formatter:off + assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) + .contains(new File(".").toURI(), new File("..").toURI()); + // @formatter:on + } + + @Test + void doesNotSupportScanClasspathAndExplicitSelectors() { + options.setScanClasspath(true); + options.setSelectedClasses(List.of(selectClass("SomeTest"))); + + Throwable cause = assertThrows(PreconditionViolationException.class, this::convert); + + assertThat(cause).hasMessageContaining("not supported"); + } + + @Test + void convertsDefaultIncludeClassNamePatternOption() { + options.setScanClasspath(true); + + var request = convert(); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).hasSize(1); + assertExcludes(filters.get(0), STANDARD_INCLUDE_PATTERN); + } + + @Test + void convertsExplicitIncludeClassNamePatternOption() { + options.setScanClasspath(true); + options.setIncludedClassNamePatterns(List.of("Foo.*Bar", "Bar.*Foo")); + + var request = convert(); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).hasSize(1); + assertIncludes(filters.get(0), "Foo.*Bar"); + assertIncludes(filters.get(0), "Bar.*Foo"); + } + + @Test + void includeSelectedClassesAndMethodsRegardlessOfClassNamePatterns() { + options.setSelectedClasses(List.of(selectClass("SomeTest"))); + options.setSelectedMethods(List.of(selectMethod("com.acme.Foo#m()"))); + options.setSelectedIterations(List.of(selectIteration(selectMethod("com.acme.Bar#n()"), 0))); + options.setIncludedClassNamePatterns(List.of("Foo.*Bar")); + + var request = convert(); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).hasSize(1); + assertIncludes(filters.get(0), "SomeTest"); + assertIncludes(filters.get(0), "com.acme.Foo"); + assertIncludes(filters.get(0), "com.acme.Bar"); + assertIncludes(filters.get(0), "Foo.*Bar"); + } + + @Test + void convertsExcludeClassNamePatternOption() { + options.setScanClasspath(true); + options.setExcludedClassNamePatterns(List.of("Foo.*Bar", "Bar.*Foo")); + + var request = convert(); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).hasSize(2); + assertExcludes(filters.get(1), "Foo.*Bar"); + assertExcludes(filters.get(1), "Bar.*Foo"); + } + + @Test + void convertsPackageOptions() { + options.setScanClasspath(true); + options.setIncludedPackages(List.of("org.junit.included1", "org.junit.included2", "org.junit.included3")); + options.setExcludedPackages(List.of("org.junit.excluded1")); + + var request = convert(); + var packageNameFilters = request.getFiltersByType(PackageNameFilter.class); + + assertThat(packageNameFilters).hasSize(2); + assertIncludes(packageNameFilters.get(0), "org.junit.included1"); + assertIncludes(packageNameFilters.get(0), "org.junit.included2"); + assertIncludes(packageNameFilters.get(0), "org.junit.included3"); + assertExcludes(packageNameFilters.get(1), "org.junit.excluded1"); + } + + @Test + void convertsTagOptions() { + options.setScanClasspath(true); + options.setIncludedTagExpressions(List.of("fast", "medium", "slow")); + options.setExcludedTagExpressions(List.of("slow")); + + var request = convert(); + var postDiscoveryFilters = request.getPostDiscoveryFilters(); + + assertThat(postDiscoveryFilters).hasSize(2); + assertThat(postDiscoveryFilters.get(0).toString()).contains("TagFilter"); + assertThat(postDiscoveryFilters.get(1).toString()).contains("TagFilter"); + } + + @Test + void convertsEngineOptions() { + options.setScanClasspath(true); + options.setIncludedEngines(List.of("engine1", "engine2", "engine3")); + options.setExcludedEngines(List.of("engine2")); + + var request = convert(); + var engineFilters = request.getEngineFilters(); + + assertThat(engineFilters).hasSize(2); + assertThat(engineFilters.get(0).toString()).contains("includes", "[engine1, engine2, engine3]"); + assertThat(engineFilters.get(1).toString()).contains("excludes", "[engine2]"); + } + + @Test + void propagatesUriSelectors() { + options.setSelectedUris(List.of(selectUri("a"), selectUri("b"))); + + var request = convert(); + var uriSelectors = request.getSelectorsByType(UriSelector.class); + + assertThat(uriSelectors).extracting(UriSelector::getUri).containsExactly(URI.create("a"), URI.create("b")); + } + + @Test + void propagatesFileSelectors() { + options.setSelectedFiles(List.of(selectFile("foo.txt"), selectFile("bar.csv"))); + + var request = convert(); + var fileSelectors = request.getSelectorsByType(FileSelector.class); + + assertThat(fileSelectors).extracting(FileSelector::getRawPath).containsExactly("foo.txt", "bar.csv"); + } + + @Test + void propagatesDirectorySelectors() { + options.setSelectedDirectories(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux"))); + + var request = convert(); + var directorySelectors = request.getSelectorsByType(DirectorySelector.class); + + assertThat(directorySelectors).extracting(DirectorySelector::getRawPath).containsExactly("foo/bar", "bar/qux"); + } + + @Test + void propagatesPackageSelectors() { + options.setSelectedPackages(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar"))); + + var request = convert(); + var packageSelectors = request.getSelectorsByType(PackageSelector.class); + + assertThat(packageSelectors).extracting(PackageSelector::getPackageName).containsExactly("com.acme.foo", + "com.example.bar"); + } + + @Test + void propagatesClassSelectors() { + options.setSelectedClasses(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar"))); + + var request = convert(); + var classSelectors = request.getSelectorsByType(ClassSelector.class); + + assertThat(classSelectors).extracting(ClassSelector::getClassName).containsExactly("com.acme.Foo", + "com.example.Bar"); + } + + @Test + void propagatesMethodSelectors() { + options.setSelectedMethods( + List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)"))); + + var request = convert(); + var methodSelectors = request.getSelectorsByType(MethodSelector.class); + + assertThat(methodSelectors).hasSize(2); + assertThat(methodSelectors.get(0).getClassName()).isEqualTo("com.acme.Foo"); + assertThat(methodSelectors.get(0).getMethodName()).isEqualTo("m"); + assertThat(methodSelectors.get(0).getMethodParameterTypes()).isEqualTo(""); + assertThat(methodSelectors.get(1).getClassName()).isEqualTo("com.example.Bar"); + assertThat(methodSelectors.get(1).getMethodName()).isEqualTo("method"); + assertThat(methodSelectors.get(1).getMethodParameterTypes()).isEqualTo("java.lang.Object"); + } + + @Test + void propagatesClasspathResourceSelectors() { + options.setSelectedClasspathResources( + List.of(selectClasspathResource("foo.csv"), selectClasspathResource("com/example/bar.json"))); + + var request = convert(); + var classpathResourceSelectors = request.getSelectorsByType(ClasspathResourceSelector.class); + + assertThat(classpathResourceSelectors).extracting( + ClasspathResourceSelector::getClasspathResourceName).containsExactly("foo.csv", "com/example/bar.json"); + } + + @Test + void propagatesIterationSelectors() { + var methodSelector = selectMethod("com.acme.Foo#m()"); + var classSelector = selectClass("com.example.Bar"); + options.setSelectedIterations(List.of(selectIteration(methodSelector, 1), selectIteration(classSelector, 2))); + + var request = convert(); + var iterationSelectors = request.getSelectorsByType(IterationSelector.class); + + assertThat(iterationSelectors).hasSize(2); + assertThat(iterationSelectors.get(0).getParentSelector()).isEqualTo(methodSelector); + assertThat(iterationSelectors.get(0).getIterationIndices()).containsExactly(1); + assertThat(iterationSelectors.get(1).getParentSelector()).isEqualTo(classSelector); + assertThat(iterationSelectors.get(1).getIterationIndices()).containsExactly(2); + } + + @Test + @SuppressWarnings("deprecation") + void convertsConfigurationParameters() { + options.setScanClasspath(true); + options.setConfigurationParameters(mapOf(entry("foo", "bar"), entry("baz", "true"))); + + var request = convert(); + var configurationParameters = request.getConfigurationParameters(); + + assertThat(configurationParameters.size()).isEqualTo(2); + assertThat(configurationParameters.get("foo")).contains("bar"); + assertThat(configurationParameters.getBoolean("baz")).contains(true); + } + + private LauncherDiscoveryRequest convert() { + var creator = new DiscoveryRequestCreator(); + return creator.toDiscoveryRequest(options); + } + + private void assertIncludes(Filter filter, String included) { + assertThat(filter.apply(included).included()).isTrue(); + } + + private void assertExcludes(Filter filter, String excluded) { + assertThat(filter.apply(excluded).excluded()).isTrue(); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static Map mapOf(Entry... entries) { + return Stream.of(entries).collect(toMap(Entry::getKey, Entry::getValue)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java new file mode 100644 index 00000000..a79bd43f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.console.tasks.FlatPrintingListener.INDENTATION; +import static org.junit.platform.engine.TestExecutionResult.failed; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.assertj.core.util.Maps; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestIdentifier; + +/** + * @since 1.0 + */ +class FlatPrintingListenerTests { + + private static final String EOL = System.lineSeparator(); + + @Test + void executionSkipped() { + var stringWriter = new StringWriter(); + listener(stringWriter).executionSkipped(newTestIdentifier(), "Test" + EOL + "disabled"); + var lines = lines(stringWriter); + + assertEquals(3, lines.length); + assertAll("lines in the output", // + () -> assertEquals("Skipped: demo-test ([engine:demo-engine])", lines[0]), // + () -> assertEquals(INDENTATION + "=> Reason: Test", lines[1]), // + () -> assertEquals(INDENTATION + "disabled", lines[2])); + } + + @Test + void reportingEntryPublished() { + var stringWriter = new StringWriter(); + listener(stringWriter).reportingEntryPublished(newTestIdentifier(), ReportEntry.from("foo", "bar")); + var lines = lines(stringWriter); + + assertEquals(2, lines.length); + assertAll("lines in the output", // + () -> assertEquals("Reported: demo-test ([engine:demo-engine])", lines[0]), // + () -> assertTrue(lines[1].startsWith(INDENTATION + "=> Reported values: ReportEntry [timestamp =")), // + () -> assertTrue(lines[1].endsWith(", foo = 'bar']"))); + } + + @Test + void executionFinishedWithFailure() { + var stringWriter = new StringWriter(); + listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("Boom!"))); + var lines = lines(stringWriter); + + assertAll("lines in the output", // + () -> assertEquals("Finished: demo-test ([engine:demo-engine])", lines[0]), // + () -> assertEquals(INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", lines[1])); + } + + @Nested + class ColorPaletteTests { + + @Nested + class DefaultColorPaletteTests { + + @Test + void executionSkipped() { + var stringWriter = new StringWriter(); + new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).executionSkipped( + newTestIdentifier(), "Test" + EOL + "disabled"); + var lines = lines(stringWriter); + + assertEquals(3, lines.length); + assertAll("lines in the output", // + () -> assertEquals("\u001B[35mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // + () -> assertEquals("\u001B[35m" + INDENTATION + "=> Reason: Test", lines[1]), // + () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); + } + + @Test + void reportingEntryPublished() { + var stringWriter = new StringWriter(); + new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).reportingEntryPublished( + newTestIdentifier(), ReportEntry.from("foo", "bar")); + var lines = lines(stringWriter); + + assertEquals(2, lines.length); + assertAll("lines in the output", // + () -> assertEquals("\u001B[37mReported: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // + () -> assertTrue(lines[1].startsWith( + "\u001B[37m" + INDENTATION + "=> Reported values: ReportEntry [timestamp =")), // + () -> assertTrue(lines[1].endsWith(", foo = 'bar']\u001B[0m"))); + } + + @Test + void executionFinishedWithFailure() { + var stringWriter = new StringWriter(); + new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).executionFinished( + newTestIdentifier(), failed(new AssertionError("Boom!"))); + var lines = lines(stringWriter); + + assertAll("lines in the output", // + () -> assertEquals("\u001B[31mFinished: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // + () -> assertEquals("\u001B[31m" + INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", + lines[1]), + () -> assertTrue(lines[lines.length - 1].endsWith("\u001B[0m"))); + } + + } + + @Nested + class ColorPaletteOverrideTests { + + @Test + void overridingSkipped() { + var stringWriter = new StringWriter(); + ColorPalette colorPalette = new ColorPalette(Maps.newHashMap(Style.SKIPPED, "36;7")); + new FlatPrintingListener(new PrintWriter(stringWriter), colorPalette).executionSkipped( + newTestIdentifier(), "Test" + EOL + "disabled"); + var lines = lines(stringWriter); + + assertEquals(3, lines.length); + assertAll("lines in the output", // + () -> assertEquals("\u001B[36;7mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // + () -> assertEquals("\u001B[36;7m" + INDENTATION + "=> Reason: Test", lines[1]), // + () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); + } + + @Test + void overridingUnusedStyle() { + var stringWriter = new StringWriter(); + ColorPalette colorPalette = new ColorPalette(Maps.newHashMap(Style.FAILED, "36;7")); + new FlatPrintingListener(new PrintWriter(stringWriter), colorPalette).executionSkipped( + newTestIdentifier(), "Test" + EOL + "disabled"); + var lines = lines(stringWriter); + + assertEquals(3, lines.length); + assertAll("lines in the output", // + () -> assertEquals("\u001B[35mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // + () -> assertEquals("\u001B[35m" + INDENTATION + "=> Reason: Test", lines[1]), // + () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); + } + + } + + } + + private FlatPrintingListener listener(StringWriter stringWriter) { + return new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.NONE); + } + + private static TestIdentifier newTestIdentifier() { + var testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "demo-test"); + return TestIdentifier.from(testDescriptor); + } + + private String[] lines(StringWriter stringWriter) { + return stringWriter.toString().split(EOL); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java new file mode 100644 index 00000000..a1256833 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.reporting.ReportEntry; + +class TreeNodeTests { + + private static final int NUM_THREADS = 2; + private static final int ITEMS_PER_THREAD = 1000; + + @Test + void caption() { + assertEquals("", TreeNode.createCaption("")); + assertEquals("[@@]", TreeNode.createCaption("[@@]")); + assertEquals("[@ @]", TreeNode.createCaption("[@ @]")); + assertEquals("[@ @]", TreeNode.createCaption("[@\u000B@]")); + assertEquals("[@ @]", TreeNode.createCaption("[@\t@]")); + assertEquals("[@ @]", TreeNode.createCaption("[@\t\n@]")); + assertEquals("[@ @]", TreeNode.createCaption("[@\t\n\r@]")); + assertEquals("[@ @]", TreeNode.createCaption("[@\t\n\r\f@]")); + assertEquals("@".repeat(80) + "...", TreeNode.createCaption("@".repeat(1000) + "!")); + } + + @Test + void childrenCanBeAddedConcurrently() throws Exception { + var treeNode = new TreeNode("root"); + + runConcurrently(() -> { + for (long i = 0; i < ITEMS_PER_THREAD; i++) { + treeNode.addChild(new TreeNode(String.valueOf(i))); + } + }); + + assertThat(treeNode.children.size()).isEqualTo(NUM_THREADS * ITEMS_PER_THREAD); + } + + @Test + void reportEntriesCanBeAddedConcurrently() throws Exception { + var treeNode = new TreeNode("root"); + + runConcurrently(() -> { + for (long i = 0; i < ITEMS_PER_THREAD; i++) { + treeNode.addReportEntry(ReportEntry.from("index", String.valueOf(i))); + } + }); + + assertThat(treeNode.reports.size()).isEqualTo(NUM_THREADS * ITEMS_PER_THREAD); + } + + private void runConcurrently(Runnable action) throws InterruptedException { + ExecutorService executor = new ThreadPoolExecutor(NUM_THREADS, NUM_THREADS, 10, SECONDS, + new ArrayBlockingQueue<>(NUM_THREADS)); + try { + var barrier = new CyclicBarrier(NUM_THREADS); + for (long i = 0; i < NUM_THREADS; i++) { + executor.submit(() -> { + await(barrier); + action.run(); + }); + } + } + finally { + executor.shutdown(); + var terminated = executor.awaitTermination(10, SECONDS); + assertTrue(terminated, "Executor was not terminated"); + } + } + + private void await(CyclicBarrier barrier) { + try { + barrier.await(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java new file mode 100644 index 00000000..f7d59827 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.platform.engine.TestExecutionResult.aborted; +import static org.junit.platform.engine.TestExecutionResult.failed; +import static org.junit.platform.engine.TestExecutionResult.successful; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.platform.console.options.Theme; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestIdentifier; + +class TreePrinterTests { + + private final Charset charset = StandardCharsets.UTF_8; + private final ByteArrayOutputStream stream = new ByteArrayOutputStream(1000); + private final PrintWriter out = new PrintWriter(new OutputStreamWriter(stream, charset)); + + private List actual() { + try { + out.flush(); + return List.of(stream.toString(charset.name()).split("\\R")); + } + catch (UnsupportedEncodingException e) { + throw new AssertionError(charset.name() + " is an unsupported encoding?!", e); + } + } + + @Test + void emptyTree() { + new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(new TreeNode("")); + assertIterableEquals(List.of("╷"), actual()); + } + + @Test + void emptyEngines() { + var root = new TreeNode(""); + root.addChild(new TreeNode(identifier("e-0", "engine zero"), "none")); + root.addChild(new TreeNode(identifier("e-1", "engine one")).setResult(successful())); + root.addChild(new TreeNode(identifier("e-2", "engine two")).setResult(failed(null))); + root.addChild(new TreeNode(identifier("e-3", "engine three")).setResult(aborted(null))); + new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); + assertIterableEquals( // + List.of( // + "╷", // + "├─ engine zero ↷ none", // + "├─ engine one ✔", // + "├─ engine two ✘", // + "└─ engine three ■"), // + actual()); + } + + @Test + // https://github.com/junit-team/junit5/issues/786 + void printNodeHandlesNullMessageThrowableGracefully() { + var result = TestExecutionResult.failed(new NullPointerException()); + var node = new TreeNode(identifier("NPE", "test()")).setResult(result); + new TreePrinter(out, Theme.ASCII, ColorPalette.NONE).print(node); + assertLinesMatch(List.of(".", "+-- test() [X] java.lang.NullPointerException"), actual()); + } + + @Test + // https://github.com/junit-team/junit5/issues/1531 + void reportsAreTabbedCorrectly() { + var root = new TreeNode(""); + var e1 = new TreeNode(identifier("e-1", "engine one")).setResult(successful()); + e1.addReportEntry(ReportEntry.from("key", "e-1")); + root.addChild(e1); + + var c1 = new TreeNode(identifier("c-1", "class one")).setResult(successful()); + c1.addReportEntry(ReportEntry.from("key", "c-1")); + e1.addChild(c1); + + var m1 = new TreeNode(identifier("m-1", "method one")).setResult(successful()); + m1.addReportEntry(ReportEntry.from("key", "m-1")); + c1.addChild(m1); + + var m2 = new TreeNode(identifier("m-2", "method two")).setResult(successful()); + m2.addReportEntry(ReportEntry.from("key", "m-2")); + c1.addChild(m2); + + new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); + assertLinesMatch(List.of( // + "╷", // + "└─ engine one ✔", // + " │ ....-..-..T..:...* key = `e-1`", // + " └─ class one ✔", // + " │ ....-..-..T..:...* key = `c-1`", // + " ├─ method one ✔", // + " │ ....-..-..T..:...* key = `m-1`", // + " └─ method two ✔", // + " ....-..-..T..:...* key = `m-2`" // + ), // + actual()); + } + + private static TestIdentifier identifier(String id, String displayName) { + var descriptor = new TestDescriptorStub(UniqueId.forEngine(id), displayName); + return TestIdentifier.from(descriptor); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java new file mode 100644 index 00000000..2d140433 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.platform.engine.TestExecutionResult.failed; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.platform.console.options.Theme; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestIdentifier; + +/** + * @since 1.3.2 + */ +class VerboseTreeListenerTests { + + private static final String EOL = System.lineSeparator(); + + @Test + void executionSkipped() { + var stringWriter = new StringWriter(); + listener(stringWriter).executionSkipped(newTestIdentifier(), "Test" + EOL + "disabled"); + var lines = lines(stringWriter); + + assertLinesMatch(List.of( // + "+-- %c ool test", // + " tags: []", // + " uniqueId: [engine:demo-engine]", // + " parent: []", // + " reason: Test", // + " disabled", // + " status: [S] SKIPPED"), List.of(lines)); + } + + @Test + void reportingEntryPublished() { + var stringWriter = new StringWriter(); + listener(stringWriter).reportingEntryPublished(newTestIdentifier(), ReportEntry.from("foo", "bar")); + var lines = lines(stringWriter); + + assertLinesMatch(List.of(" reports: ReportEntry \\[timestamp = .+, foo = 'bar'\\]"), List.of(lines)); + } + + @Test + void executionFinishedWithFailure() { + var stringWriter = new StringWriter(); + listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("Boom!"))); + var lines = lines(stringWriter); + + assertLinesMatch(List.of(" caught: java.lang.AssertionError: Boom!", // + ">> STACKTRACE >>", // + " duration: \\d+ ms", // + " status: [X] FAILED"), List.of(lines)); + } + + @Test + void failureMessageWithFormatSpecifier() { + var stringWriter = new StringWriter(); + listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("%crash"))); + var lines = lines(stringWriter); + + assertLinesMatch(List.of(" caught: java.lang.AssertionError: %crash", // + ">> STACKTRACE >>", // + " duration: \\d+ ms", // + " status: [X] FAILED"), List.of(lines)); + } + + private VerboseTreePrintingListener listener(StringWriter stringWriter) { + return new VerboseTreePrintingListener(new PrintWriter(stringWriter), ColorPalette.NONE, 16, Theme.ASCII); + } + + private static TestIdentifier newTestIdentifier() { + var testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "%c ool test"); + return TestIdentifier.from(testDescriptor); + } + + private String[] lines(StringWriter stringWriter) { + return stringWriter.toString().split(EOL); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java new file mode 100644 index 00000000..422d86df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.included; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.launcher.FilterStub; + +/** + * Unit tests for {@link Filter#composeFilters}. + * + * {@link Filter#composeFilters} will delegate to {@linkplain CompositeFilter} under the hood. + * + * @since 1.0 + */ +class FilterCompositionTests { + + @Test + void composingNoFiltersCreatesFilterThatIncludesEverything() { + var composedFilter = Filter.composeFilters(); + + assertTrue(composedFilter.apply(String.class).included()); + assertTrue(composedFilter.toPredicate().test(String.class)); + assertTrue(composedFilter.apply(Object.class).included()); + assertTrue(composedFilter.toPredicate().test(Object.class)); + } + + @Test + void composingSingleFilterWillReturnTheOriginalOne() { + Filter singleFilter = ClassNameFilter.includeClassNamePatterns(".*ring.*"); + var composed = Filter.composeFilters(singleFilter); + assertSame(singleFilter, composed); + } + + @Test + void composingMultipleFiltersIsAConjunctionOfFilters() { + Filter firstFilter = ClassNameFilter.includeClassNamePatterns(".*ring.*"); + Filter secondFilter = ClassNameFilter.includeClassNamePatterns(".*Join.*"); + + var composed = Filter.composeFilters(firstFilter, secondFilter); + + assertFalse(composed.apply("java.lang.String").included()); + assertFalse(composed.toPredicate().test("java.lang.String")); + assertTrue(composed.apply("java.util.StringJoiner").included()); + assertTrue(composed.toPredicate().test("java.util.StringJoiner")); + } + + @Test + void aFilterComposedOfMultipleFiltersHasReadableDescription() { + Filter firstFilter = new FilterStub<>(o -> excluded("wrong"), () -> "1st"); + Filter secondFilter = new FilterStub<>(o -> included("right"), () -> "2nd"); + + var composed = Filter.composeFilters(firstFilter, secondFilter); + + assertFalse(composed.apply(String.class).included()); + assertEquals("(1st) and (2nd)", composed.toString()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java new file mode 100644 index 00000000..a98e1ec3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.platform.fakes.TestDescriptorStub; + +/** + * @since 1.0 + */ +class TestDescriptorTests { + + @Test + void isRootWithoutParent() { + TestDescriptor root = new TestDescriptorStub(UniqueId.root("root", "id"), "id"); + + assertTrue(root.isRoot()); + } + + @Test + void isRootWithParent() { + TestDescriptor child = new TestDescriptorStub(UniqueId.root("child", "child"), "child"); + child.setParent(new TestDescriptorStub(UniqueId.root("root", "root"), "root")); + + assertFalse(child.isRoot()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java new file mode 100644 index 00000000..d334b2f9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link TestTag}. + * + * @since 1.0 + */ +class TestTagTests { + + @Test + void validSyntax() { + // @formatter:off + assertAll("Valid Tag Syntax", + () -> yep("fast"), + () -> yep("super_fast"), + () -> yep("unit-test"), + () -> yep("integration.test"), + () -> yep("org.example.CustomTagClass"), + () -> yep(" surrounded-by-whitespace\t\n"), + () -> nope(null), + () -> nope(""), + () -> nope(" "), + () -> nope("\t"), + () -> nope("\f"), + () -> nope("\r"), + () -> nope("\n"), + () -> nope("custom tag"), // internal space + () -> nope(","), // comma + () -> nope("("), // opening parenthesis + () -> nope(")"), // closing parenthesis + () -> nope("&"), // boolean AND + () -> nope("|"), // boolean OR + () -> nope("!") // boolean NOT + ); + // @formatter:on + } + + @Test + void factory() { + assertEquals("foo", TestTag.create("foo").getName()); + assertEquals("foo.tag", TestTag.create("foo.tag").getName()); + assertEquals("foo-tag", TestTag.create("foo-tag").getName()); + assertEquals("foo-tag", TestTag.create(" foo-tag ").getName()); + assertEquals("foo-tag", TestTag.create("\t foo-tag \n").getName()); + } + + @Test + void factoryPreconditions() { + assertSyntaxViolation(null); + assertSyntaxViolation(""); + assertSyntaxViolation(" "); + assertSyntaxViolation("X\tX"); + assertSyntaxViolation("X\nX"); + assertSyntaxViolation("XXX\u005CtXXX"); + } + + @Test + void tagEqualsOtherTagWithSameName() { + assertEquals(TestTag.create("fast"), TestTag.create("fast")); + assertEquals(TestTag.create("fast").hashCode(), TestTag.create("fast").hashCode()); + assertNotEquals(null, TestTag.create("fast")); + assertNotEquals(TestTag.create("fast"), null); + } + + @Test + void toStringPrintsName() { + assertEquals("fast", TestTag.create("fast").toString()); + } + + private static void yep(String tag) { + assertTrue(TestTag.isValid(tag), () -> String.format("'%s' should be a valid tag", tag)); + } + + private static void nope(String tag) { + assertFalse(TestTag.isValid(tag), () -> String.format("'%s' should not be a valid tag", tag)); + } + + private void assertSyntaxViolation(String tag) { + var exception = assertThrows(PreconditionViolationException.class, () -> TestTag.create(tag)); + assertThat(exception).hasMessageStartingWith("Tag name"); + assertThat(exception).hasMessageEndingWith("must be syntactically valid"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java new file mode 100644 index 00000000..6913a7dc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.UniqueId.Segment; + +/** + * @since 1.0 + */ +class UniqueIdFormatTests { + + @Nested + class Formatting { + + private final UniqueId engineId = UniqueId.root("engine", "junit-jupiter"); + + private final UniqueIdFormat format = UniqueIdFormat.getDefault(); + + @Test + void engineIdOnly() { + assertEquals("[engine:junit-jupiter]", engineId.toString()); + assertEquals(format.format(engineId), engineId.toString()); + } + + @Test + void withTwoSegments() { + var classId = engineId.append("class", "org.junit.MyClass"); + assertEquals("[engine:junit-jupiter]/[class:org.junit.MyClass]", classId.toString()); + assertEquals(format.format(classId), classId.toString()); + } + + @Test + void withManySegments() { + var uniqueId = engineId.append("t1", "v1").append("t2", "v2").append("t3", "v3"); + assertEquals("[engine:junit-jupiter]/[t1:v1]/[t2:v2]/[t3:v3]", uniqueId.toString()); + assertEquals(format.format(uniqueId), uniqueId.toString()); + } + + } + + @Nested + class ParsingWithDefaultFormat implements ParsingTestTrait { + + private final UniqueIdFormat format = UniqueIdFormat.getDefault(); + + @Override + public UniqueIdFormat getFormat() { + return this.format; + } + + @Override + public String getEngineUid() { + return "[engine:junit-jupiter]"; + } + + @Override + public String getMethodUid() { + return "[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"; + } + + } + + @Nested + class ParsingWithCustomFormat implements ParsingTestTrait { + + private final UniqueIdFormat format = new UniqueIdFormat('{', '=', '}', ','); + + @Override + public UniqueIdFormat getFormat() { + return this.format; + } + + @Override + public String getEngineUid() { + return "{engine=junit-jupiter}"; + } + + @Override + public String getMethodUid() { + return "{engine=junit-jupiter},{class=MyClass},{method=myMethod}"; + } + + } + + // ------------------------------------------------------------------------- + + private static void assertSegment(Segment segment, String expectedType, String expectedValue) { + assertEquals(expectedType, segment.getType(), "segment type"); + assertEquals(expectedValue, segment.getValue(), "segment value"); + } + + interface ParsingTestTrait { + + UniqueIdFormat getFormat(); + + String getEngineUid(); + + String getMethodUid(); + + @Test + default void parseMalformedUid() { + Throwable throwable = assertThrows(JUnitException.class, () -> getFormat().parse("malformed UID")); + assertTrue(throwable.getMessage().contains("malformed UID")); + } + + @Test + default void parseEngineUid() { + var parsedId = getFormat().parse(getEngineUid()); + assertSegment(parsedId.getSegments().get(0), "engine", "junit-jupiter"); + assertEquals(getEngineUid(), getFormat().format(parsedId)); + assertEquals(getEngineUid(), parsedId.toString()); + } + + @Test + default void parseMethodUid() { + var parsedId = getFormat().parse(getMethodUid()); + assertSegment(parsedId.getSegments().get(0), "engine", "junit-jupiter"); + assertSegment(parsedId.getSegments().get(1), "class", "MyClass"); + assertSegment(parsedId.getSegments().get(2), "method", "myMethod"); + assertEquals(getMethodUid(), getFormat().format(parsedId)); + assertEquals(getMethodUid(), parsedId.toString()); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java new file mode 100644 index 00000000..9c87d261 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java @@ -0,0 +1,299 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.UniqueId.Segment; + +/** + * Unit tests for {@link UniqueId}. + * + * @since 1.0 + * @see org.junit.jupiter.engine.execution.UniqueIdParsingForArrayParameterIntegrationTests + */ +class UniqueIdTests { + + private static final String ENGINE_ID = "junit-jupiter"; + private static final String SUITE_ENGINE_ID = "junit-platform-suite"; + + @Nested + class Creation { + + @Test + void uniqueIdCanBeCreatedFromEngineId() { + var uniqueId = UniqueId.forEngine(ENGINE_ID); + + assertEquals("[engine:junit-jupiter]", uniqueId.toString()); + assertSegment(uniqueId.getSegments().get(0), "engine", "junit-jupiter"); + } + + @Test + void engineIdCanBeAppended() { + var suiteEngineId = UniqueId.forEngine(SUITE_ENGINE_ID); + var uniqueId = suiteEngineId.appendEngine(ENGINE_ID); + assertSegment(uniqueId.getSegments().get(1), "engine", "junit-jupiter"); + } + + @Test + void retrievingOptionalEngineId() { + var uniqueIdWithEngine = UniqueId.forEngine(ENGINE_ID); + assertThat(uniqueIdWithEngine.getEngineId()).contains("junit-jupiter"); + + var uniqueIdWithoutEngine = UniqueId.root("root", "avalue"); + assertEquals(Optional.empty(), uniqueIdWithoutEngine.getEngineId()); + } + + @Test + void uniqueIdCanBeCreatedFromTypeAndValue() { + var uniqueId = UniqueId.root("aType", "aValue"); + + assertEquals("[aType:aValue]", uniqueId.toString()); + assertSegment(uniqueId.getSegments().get(0), "aType", "aValue"); + } + + @Test + void rootSegmentCanBeRetrieved() { + var uniqueId = UniqueId.root("aType", "aValue"); + + assertThat(uniqueId.getRoot()).contains(new Segment("aType", "aValue")); + } + + @Test + void appendingOneSegment() { + var engineId = UniqueId.root("engine", ENGINE_ID); + var classId = engineId.append("class", "org.junit.MyClass"); + + assertThat(classId.getSegments()).hasSize(2); + assertSegment(classId.getSegments().get(0), "engine", ENGINE_ID); + assertSegment(classId.getSegments().get(1), "class", "org.junit.MyClass"); + } + + @Test + void appendingSegmentLeavesOriginalUnchanged() { + var uniqueId = UniqueId.root("engine", ENGINE_ID); + uniqueId.append("class", "org.junit.MyClass"); + + assertThat(uniqueId.getSegments()).hasSize(1); + assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); + } + + @Test + void appendingSeveralSegments() { + var engineId = UniqueId.root("engine", ENGINE_ID); + var uniqueId = engineId.append("t1", "v1").append("t2", "v2").append("t3", "v3"); + + assertThat(uniqueId.getSegments()).hasSize(4); + assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); + assertSegment(uniqueId.getSegments().get(1), "t1", "v1"); + assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); + assertSegment(uniqueId.getSegments().get(3), "t3", "v3"); + } + + @Test + void appendingSegmentInstance() { + var uniqueId = UniqueId.forEngine(ENGINE_ID).append("t1", "v1"); + + uniqueId = uniqueId.append(new Segment("t2", "v2")); + + assertThat(uniqueId.getSegments()).hasSize(3); + assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); + assertSegment(uniqueId.getSegments().get(1), "t1", "v1"); + assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); + } + + @Test + void appendingNullIsNotAllowed() { + var uniqueId = UniqueId.forEngine(ENGINE_ID); + + assertThrows(PreconditionViolationException.class, () -> uniqueId.append(null)); + assertThrows(PreconditionViolationException.class, () -> uniqueId.append(null, "foo")); + assertThrows(PreconditionViolationException.class, () -> uniqueId.append("foo", null)); + } + + } + + @Nested + class ParsingAndFormatting { + + @Test + void ensureDefaultUniqueIdFormatIsUsedForParsing() { + var uniqueIdString = "[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"; + var parsedDirectly = UniqueId.parse(uniqueIdString); + var parsedViaFormat = UniqueIdFormat.getDefault().parse(uniqueIdString); + assertEquals(parsedViaFormat, parsedDirectly); + } + + @Test + void ensureDefaultUniqueIdFormatIsUsedForFormatting() { + var parsedDirectly = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); + assertEquals("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]", parsedDirectly.toString()); + } + + @Test + void ensureDefaultUniqueIdFormatDecodingEncodesSegmentParts() { + var segment = UniqueId.parse("[%5B+%25+%5D):(%3A+%2B+%2F]").getSegments().get(0); + assertEquals("[ % ])", segment.getType()); + assertEquals("(: + /", segment.getValue()); + } + + @Test + void ensureDefaultUniqueIdFormatCanHandleAllCharacters() { + for (char c = 0; c < Character.MAX_VALUE; c++) { + var value = "foo " + c + " bar"; + var uniqueId = UniqueId.parse(UniqueId.root("type", value).toString()); + var segment = uniqueId.getSegments().get(0); + assertEquals(value, segment.getValue()); + } + } + + @ParameterizedTest + @ValueSource(strings = { "[a:b]", "[a:b]/[a:b]", "[a$b:b()]", "[a:b(%5BI)]", "[%5B%5D:%3A%2F]" }) + void ensureDefaultToStringAndParsingIsIdempotent(String expected) { + assertEquals(expected, UniqueId.parse(expected).toString()); + } + } + + @Nested + class EqualsContract { + + @Test + void sameEnginesAreEqual() { + var id1 = UniqueId.root("engine", "junit-jupiter"); + var id2 = UniqueId.root("engine", "junit-jupiter"); + + assertEquals(id2, id1); + assertEquals(id1, id2); + assertEquals(id1.hashCode(), id2.hashCode()); + } + + @Test + void differentEnginesAreNotEqual() { + var id1 = UniqueId.root("engine", "junit-vintage"); + var id2 = UniqueId.root("engine", "junit-jupiter"); + + assertNotEquals(id2, id1); + assertNotEquals(id1, id2); + } + + @Test + void uniqueIdWithSameSegmentsAreEqual() { + var id1 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); + var id2 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); + + assertEquals(id2, id1); + assertEquals(id1, id2); + assertEquals(id1.hashCode(), id2.hashCode()); + } + + @Test + void differentOrderOfSegmentsAreNotEqual() { + var id1 = UniqueId.root("engine", "junit-jupiter").append("t2", "v2").append("t1", "v1"); + var id2 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); + + assertNotEquals(id2, id1); + assertNotEquals(id1, id2); + } + + @Test + void additionalSegmentMakesItNotEqual() { + var id1 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1"); + var id2 = id1.append("t2", "v2"); + + assertNotEquals(id2, id1); + assertNotEquals(id1, id2); + } + } + + @Nested + class Prefixing { + + @Test + void nullIsNotAPrefix() { + var id = UniqueId.forEngine(ENGINE_ID); + + assertThrows(PreconditionViolationException.class, () -> id.hasPrefix(null)); + } + + @Test + void uniqueIdIsPrefixForItself() { + var id = UniqueId.forEngine(ENGINE_ID).append("t1", "v1").append("t2", "v2"); + + assertTrue(id.hasPrefix(id)); + } + + @Test + void uniqueIdIsPrefixForUniqueIdWithAdditionalSegments() { + var id1 = UniqueId.forEngine(ENGINE_ID); + var id2 = id1.append("t1", "v1"); + var id3 = id2.append("t2", "v2"); + + assertFalse(id1.hasPrefix(id2)); + assertFalse(id1.hasPrefix(id3)); + assertTrue(id2.hasPrefix(id1)); + assertFalse(id2.hasPrefix(id3)); + assertTrue(id3.hasPrefix(id1)); + assertTrue(id3.hasPrefix(id2)); + } + + @Test + void completelyUnrelatedUniqueIdsAreNotPrefixesForEachOther() { + var id1 = UniqueId.forEngine("foo"); + var id2 = UniqueId.forEngine("bar"); + + assertFalse(id1.hasPrefix(id2)); + assertFalse(id2.hasPrefix(id1)); + } + + } + + @Nested + class LastSegment { + + @Test + void returnsLastSegment() { + var uniqueId = UniqueId.forEngine("foo"); + assertSame(uniqueId.getSegments().get(0), uniqueId.getLastSegment()); + + uniqueId = UniqueId.forEngine("foo").append("type", "bar"); + assertSame(uniqueId.getSegments().get(1), uniqueId.getLastSegment()); + } + + @Test + void removesLastSegment() { + var uniqueId = UniqueId.forEngine("foo"); + assertThrows(PreconditionViolationException.class, uniqueId::removeLastSegment); + + var newUniqueId = uniqueId.append("type", "bar").removeLastSegment(); + assertEquals(uniqueId, newUniqueId); + } + + } + + private void assertSegment(Segment segment, String expectedType, String expectedValue) { + assertEquals(expectedType, segment.getType(), "segment type"); + assertEquals(expectedValue, segment.getValue(), "segment value"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java new file mode 100644 index 00000000..622f0041 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 1.0 + */ +class ClassNameFilterTests { + + @Test + void includeClassNamePatternsChecksPreconditions() { + assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns((String[]) null)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns(new String[0])) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns(new String[] { null })) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not contain null elements"); + } + + @Test + void includeClassNamePatternsWithSinglePattern() { + var regex = "^java\\.lang\\..*"; + + var filter = ClassNameFilter.includeClassNamePatterns(regex); + + assertThat(filter).hasToString( + "IncludeClassNameFilter that includes class names that match one of the following regular expressions: '" + + regex + "'"); + + assertTrue(filter.apply("java.lang.String").included()); + assertTrue(filter.toPredicate().test("java.lang.String")); + assertThat(filter.apply("java.lang.String").getReason()).contains( + "Class name [java.lang.String] matches included pattern: '" + regex + "'"); + + assertFalse(filter.apply("java.time.Instant").included()); + assertFalse(filter.toPredicate().test("java.time.Instant")); + assertThat(filter.apply("java.time.Instant").getReason()).contains( + "Class name [java.time.Instant] does not match any included pattern: '" + regex + "'"); + } + + @Test + void includeClassNamePatternsWithMultiplePatterns() { + var firstRegex = "^java\\.lang\\..*"; + var secondRegex = "^java\\.util\\..*"; + + var filter = ClassNameFilter.includeClassNamePatterns(firstRegex, secondRegex); + + assertThat(filter).hasToString( + "IncludeClassNameFilter that includes class names that match one of the following regular expressions: '" + + firstRegex + "' OR '" + secondRegex + "'"); + + assertTrue(filter.apply("java.lang.String").included()); + assertTrue(filter.toPredicate().test("java.lang.String")); + assertThat(filter.apply("java.lang.String").getReason()).contains( + "Class name [java.lang.String] matches included pattern: '" + firstRegex + "'"); + + assertTrue(filter.apply("java.util.Collection").included()); + assertTrue(filter.toPredicate().test("java.util.Collection")); + assertThat(filter.apply("java.util.Collection").getReason()).contains( + "Class name [java.util.Collection] matches included pattern: '" + secondRegex + "'"); + + assertFalse(filter.apply("java.time.Instant").included()); + assertFalse(filter.toPredicate().test("java.time.Instant")); + assertThat(filter.apply("java.time.Instant").getReason()).contains( + "Class name [java.time.Instant] does not match any included pattern: '" + firstRegex + "' OR '" + + secondRegex + "'"); + } + + @Test + void excludeClassNamePatternsChecksPreconditions() { + assertThatThrownBy(() -> ClassNameFilter.excludeClassNamePatterns((String[]) null)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> ClassNameFilter.excludeClassNamePatterns(new String[0])) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> ClassNameFilter.excludeClassNamePatterns(new String[] { null })) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not contain null elements"); + } + + @Test + void excludeClassNamePatternsWithSinglePattern() { + var regex = "^java\\.lang\\..*"; + + var filter = ClassNameFilter.excludeClassNamePatterns(regex); + + assertThat(filter).hasToString( + "ExcludeClassNameFilter that excludes class names that match one of the following regular expressions: '" + + regex + "'"); + + assertTrue(filter.apply("java.lang.String").excluded()); + assertFalse(filter.toPredicate().test("java.lang.String")); + + assertThat(filter.apply("java.lang.String").getReason()).contains( + "Class name [java.lang.String] matches excluded pattern: '" + regex + "'"); + + assertTrue(filter.apply("java.time.Instant").included()); + assertTrue(filter.toPredicate().test("java.time.Instant")); + assertThat(filter.apply("java.time.Instant").getReason()).contains( + "Class name [java.time.Instant] does not match any excluded pattern: '" + regex + "'"); + } + + @Test + void excludeClassNamePatternsWithMultiplePatterns() { + var firstRegex = "^java\\.lang\\..*"; + var secondRegex = "^java\\.util\\..*"; + + var filter = ClassNameFilter.excludeClassNamePatterns(firstRegex, secondRegex); + + assertThat(filter).hasToString( + "ExcludeClassNameFilter that excludes class names that match one of the following regular expressions: '" + + firstRegex + "' OR '" + secondRegex + "'"); + + assertTrue(filter.apply("java.lang.String").excluded()); + assertFalse(filter.toPredicate().test("java.lang.String")); + assertThat(filter.apply("java.lang.String").getReason()).contains( + "Class name [java.lang.String] matches excluded pattern: '" + firstRegex + "'"); + + assertTrue(filter.apply("java.util.Collection").excluded()); + assertFalse(filter.toPredicate().test("java.util.Collection")); + assertThat(filter.apply("java.util.Collection").getReason()).contains( + "Class name [java.util.Collection] matches excluded pattern: '" + secondRegex + "'"); + + assertFalse(filter.apply("java.time.Instant").excluded()); + assertTrue(filter.toPredicate().test("java.time.Instant")); + assertThat(filter.apply("java.time.Instant").getReason()).contains( + "Class name [java.time.Instant] does not match any excluded pattern: '" + firstRegex + "' OR '" + + secondRegex + "'"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java new file mode 100644 index 00000000..6da64efd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link ClassSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class ClassSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new ClassSelector("org.example.TestClass"); + var selector2 = new ClassSelector("org.example.TestClass"); + var selector3 = new ClassSelector("org.example.X"); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + + @Test + void preservesOriginalExceptionWhenTryingToLoadClass() { + var selector = new ClassSelector("org.example.TestClass"); + + var e = assertThrows(PreconditionViolationException.class, selector::getJavaClass); + + assertThat(e).hasMessage("Could not load class with name: org.example.TestClass").hasCauseInstanceOf( + ClassNotFoundException.class); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java new file mode 100644 index 00000000..17559826 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; + +/** + * Unit tests for {@link ClasspathResourceSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class ClasspathResourceSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new ClasspathResourceSelector("/foo/bar.txt", null); + var selector2 = new ClasspathResourceSelector("/foo/bar.txt", null); + var selector3 = new ClasspathResourceSelector("/foo/X.txt", null); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + + @Test + void equalsAndHashCodeWithFilePosition() { + var selector1 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(1)); + var selector2 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(1)); + var selector3 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(2)); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java new file mode 100644 index 00000000..3f23e8aa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import java.net.URI; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; + +/** + * Unit tests for {@link ClasspathRootSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class ClasspathRootSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() throws Exception { + var selector1 = new ClasspathRootSelector(new URI("file://example/path")); + var selector2 = new ClasspathRootSelector(new URI("file://example/path")); + var selector3 = new ClasspathRootSelector(new URI("file://example/foo")); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java new file mode 100644 index 00000000..59f6522b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; + +/** + * Unit tests for {@link DirectorySelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class DirectorySelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new DirectorySelector("/example/foo"); + var selector2 = new DirectorySelector("/example/foo"); + var selector3 = new DirectorySelector("/example/bar"); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java new file mode 100644 index 00000000..db65c530 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java @@ -0,0 +1,852 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static java.lang.String.join; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModules; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link DiscoverySelectors}. + * + * @since 1.0 + */ +class DiscoverySelectorsTests { + + @Test + void selectUriByName() { + assertViolatesPrecondition(() -> selectUri((String) null)); + assertViolatesPrecondition(() -> selectUri(" ")); + assertViolatesPrecondition(() -> selectUri("foo:")); + + var uri = "https://junit.org"; + + var selector = selectUri(uri); + assertEquals(uri, selector.getUri().toString()); + } + + @Test + void selectUriByURI() throws Exception { + assertViolatesPrecondition(() -> selectUri((URI) null)); + assertViolatesPrecondition(() -> selectUri(" ")); + + var uri = new URI("https://junit.org"); + + var selector = selectUri(uri); + assertEquals(uri, selector.getUri()); + } + + @Test + void selectFileByName() { + assertViolatesPrecondition(() -> selectFile((String) null)); + assertViolatesPrecondition(() -> selectFile(" ")); + + var path = "src/test/resources/do_not_delete_me.txt"; + + var selector = selectFile(path); + assertEquals(path, selector.getRawPath()); + assertEquals(new File(path), selector.getFile()); + assertEquals(Paths.get(path), selector.getPath()); + } + + @Test + void selectFileByNameAndPosition() { + var filePosition = FilePosition.from(12, 34); + assertViolatesPrecondition(() -> selectFile((String) null, filePosition)); + assertViolatesPrecondition(() -> selectFile(" ", filePosition)); + + var path = "src/test/resources/do_not_delete_me.txt"; + + var selector = selectFile(path, filePosition); + assertEquals(path, selector.getRawPath()); + assertEquals(new File(path), selector.getFile()); + assertEquals(Paths.get(path), selector.getPath()); + assertEquals(filePosition, selector.getPosition().get()); + } + + @Test + void selectFileByFileReference() throws Exception { + assertViolatesPrecondition(() -> selectFile((File) null)); + assertViolatesPrecondition(() -> selectFile(new File("bogus/nonexistent.txt"))); + + var currentDir = new File(".").getCanonicalFile(); + var relativeDir = new File("..", currentDir.getName()); + var file = new File(relativeDir, "src/test/resources/do_not_delete_me.txt"); + var path = file.getCanonicalFile().getPath(); + + var selector = selectFile(file); + assertEquals(path, selector.getRawPath()); + assertEquals(file.getCanonicalFile(), selector.getFile()); + assertEquals(Paths.get(path), selector.getPath()); + } + + @Test + void selectFileByFileReferenceAndPosition() throws Exception { + var filePosition = FilePosition.from(12, 34); + assertViolatesPrecondition(() -> selectFile((File) null, filePosition)); + assertViolatesPrecondition(() -> selectFile(new File("bogus/nonexistent.txt"), filePosition)); + + var currentDir = new File(".").getCanonicalFile(); + var relativeDir = new File("..", currentDir.getName()); + var file = new File(relativeDir, "src/test/resources/do_not_delete_me.txt"); + var path = file.getCanonicalFile().getPath(); + + var selector = selectFile(file, filePosition); + assertEquals(path, selector.getRawPath()); + assertEquals(file.getCanonicalFile(), selector.getFile()); + assertEquals(Paths.get(path), selector.getPath()); + assertEquals(FilePosition.from(12, 34), selector.getPosition().get()); + } + + @Test + void selectDirectoryByName() { + assertViolatesPrecondition(() -> selectDirectory((String) null)); + assertViolatesPrecondition(() -> selectDirectory(" ")); + + var path = "src/test/resources"; + + var selector = selectDirectory(path); + assertEquals(path, selector.getRawPath()); + assertEquals(new File(path), selector.getDirectory()); + assertEquals(Paths.get(path), selector.getPath()); + } + + @Test + void selectDirectoryByFileReference() throws Exception { + assertViolatesPrecondition(() -> selectDirectory((File) null)); + assertViolatesPrecondition(() -> selectDirectory(new File("bogus/nonexistent"))); + + var currentDir = new File(".").getCanonicalFile(); + var relativeDir = new File("..", currentDir.getName()); + var directory = new File(relativeDir, "src/test/resources"); + var path = directory.getCanonicalFile().getPath(); + + var selector = selectDirectory(directory); + assertEquals(path, selector.getRawPath()); + assertEquals(directory.getCanonicalFile(), selector.getDirectory()); + assertEquals(Paths.get(path), selector.getPath()); + } + + @Test + void selectClasspathResources() { + assertViolatesPrecondition(() -> selectClasspathResource(null)); + assertViolatesPrecondition(() -> selectClasspathResource("")); + assertViolatesPrecondition(() -> selectClasspathResource(" ")); + assertViolatesPrecondition(() -> selectClasspathResource("\t")); + + // with unnecessary "/" prefix + var selector = selectClasspathResource("/foo/bar/spec.xml"); + assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); + + // standard use case + selector = selectClasspathResource("A/B/C/spec.json"); + assertEquals("A/B/C/spec.json", selector.getClasspathResourceName()); + } + + @Test + void selectClasspathResourcesWithFilePosition() { + var filePosition = FilePosition.from(12, 34); + assertViolatesPrecondition(() -> selectClasspathResource(null, filePosition)); + assertViolatesPrecondition(() -> selectClasspathResource("", filePosition)); + assertViolatesPrecondition(() -> selectClasspathResource(" ", filePosition)); + assertViolatesPrecondition(() -> selectClasspathResource("\t", filePosition)); + + // with unnecessary "/" prefix + var selector = selectClasspathResource("/foo/bar/spec.xml", filePosition); + assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); + assertEquals(FilePosition.from(12, 34), selector.getPosition().get()); + + // standard use case + selector = selectClasspathResource("A/B/C/spec.json", filePosition); + assertEquals("A/B/C/spec.json", selector.getClasspathResourceName()); + assertEquals(filePosition, selector.getPosition().get()); + } + + @Test + void selectModuleByName() { + var selector = selectModule("java.base"); + assertEquals("java.base", selector.getModuleName()); + } + + @Test + void selectModuleByNamePreconditions() { + assertViolatesPrecondition(() -> selectModule(null)); + assertViolatesPrecondition(() -> selectModule("")); + assertViolatesPrecondition(() -> selectModule(" ")); + } + + @Test + void selectModulesByNames() { + var selectors = selectModules(Set.of("a", "b")); + var names = selectors.stream().map(ModuleSelector::getModuleName).collect(Collectors.toList()); + assertThat(names).containsExactlyInAnyOrder("b", "a"); + } + + @Test + void selectModulesByNamesPreconditions() { + assertViolatesPrecondition(() -> selectModules(null)); + assertViolatesPrecondition(() -> selectModules(Set.of("a", " "))); + } + + @Test + void selectPackageByName() { + var selector = selectPackage(getClass().getPackage().getName()); + assertEquals(getClass().getPackage().getName(), selector.getPackageName()); + } + + @Test + void selectClassByName() { + var selector = selectClass(getClass().getName()); + assertEquals(getClass(), selector.getJavaClass()); + } + + @Test + void selectMethodByClassNameAndMethodNamePreconditions() { + assertViolatesPrecondition(() -> selectMethod("TestClass", null)); + assertViolatesPrecondition(() -> selectMethod("TestClass", "")); + assertViolatesPrecondition(() -> selectMethod("TestClass", " ")); + assertViolatesPrecondition(() -> selectMethod((String) null, "method")); + assertViolatesPrecondition(() -> selectMethod("", "method")); + assertViolatesPrecondition(() -> selectMethod(" ", "method")); + } + + @Test + void selectMethodByClassNameMethodNameAndMethodParameterTypesPreconditions() { + assertViolatesPrecondition(() -> selectMethod("TestClass", null, "int")); + assertViolatesPrecondition(() -> selectMethod("TestClass", "", "int")); + assertViolatesPrecondition(() -> selectMethod("TestClass", " ", "int")); + assertViolatesPrecondition(() -> selectMethod((String) null, "method", "int")); + assertViolatesPrecondition(() -> selectMethod("", "method", "int")); + assertViolatesPrecondition(() -> selectMethod(" ", "method", "int")); + assertViolatesPrecondition(() -> selectMethod("TestClass", "method", null)); + } + + @Test + void selectMethodByClassAndMethodNamePreconditions() { + assertViolatesPrecondition(() -> selectMethod(getClass(), (String) null)); + assertViolatesPrecondition(() -> selectMethod(getClass(), "")); + assertViolatesPrecondition(() -> selectMethod(getClass(), " ")); + assertViolatesPrecondition(() -> selectMethod((Class) null, "method")); + assertViolatesPrecondition(() -> selectMethod("", "method")); + assertViolatesPrecondition(() -> selectMethod(" ", "method")); + } + + @Test + void selectMethodByClassMethodNameAndMethodParameterTypesPreconditions() { + assertViolatesPrecondition(() -> selectMethod((Class) null, "method", "int")); + assertViolatesPrecondition(() -> selectMethod(getClass(), null, "int")); + assertViolatesPrecondition(() -> selectMethod(getClass(), "", "int")); + assertViolatesPrecondition(() -> selectMethod(getClass(), " ", "int")); + assertViolatesPrecondition(() -> selectMethod(getClass(), "method", null)); + } + + @Test + void selectMethodByClassAndMethodPreconditions() { + var method = getClass().getDeclaredMethods()[0]; + assertViolatesPrecondition(() -> selectMethod(null, method)); + assertViolatesPrecondition(() -> selectMethod(getClass(), (Method) null)); + } + + @ParameterizedTest(name = "FQMN: ''{0}''") + @MethodSource("invalidFullyQualifiedMethodNames") + void selectMethodByFullyQualifiedNamePreconditions(String fqmn, String message) { + Exception exception = assertThrows(PreconditionViolationException.class, () -> selectMethod(fqmn)); + assertThat(exception).hasMessageContaining(message); + } + + static Stream invalidFullyQualifiedMethodNames() { + // @formatter:off + return Stream.of( + arguments(null, "must not be null or blank"), + arguments("", "must not be null or blank"), + arguments(" ", "must not be null or blank"), + arguments("com.example", "not a valid fully qualified method name"), + arguments("com.example.Foo", "not a valid fully qualified method name"), + arguments("method", "not a valid fully qualified method name"), + arguments("#method", "not a valid fully qualified method name"), + arguments("#method()", "not a valid fully qualified method name"), + arguments("#method(int)", "not a valid fully qualified method name"), + arguments("java.lang.String#", "not a valid fully qualified method name") + ); + // @formatter:on + } + + @Test + void selectMethodByFullyQualifiedName() throws Exception { + Class clazz = getClass(); + var method = clazz.getDeclaredMethod("myTest"); + assertSelectMethodByFullyQualifiedName(clazz, method); + } + + @Test + void selectMethodByFullyQualifiedNameForDefaultMethodInInterface() throws Exception { + Class clazz = TestCaseWithDefaultMethod.class; + var method = clazz.getMethod("myTest"); + assertSelectMethodByFullyQualifiedName(clazz, method); + } + + @Test + void selectMethodByFullyQualifiedNameWithPrimitiveParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", int.class); + assertSelectMethodByFullyQualifiedName(getClass(), method, int.class, "int"); + } + + @Test + void selectMethodByFullyQualifiedNameWithPrimitiveParameterUsingSourceCodeSyntax() throws Exception { + var method = getClass().getDeclaredMethod("myTest", int.class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "int", "int"); + } + + @Test + void selectMethodByFullyQualifiedNameWithObjectParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", String.class); + assertSelectMethodByFullyQualifiedName(getClass(), method, String.class, String.class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithObjectParameterUsingSourceCodeSyntax() throws Exception { + var method = getClass().getDeclaredMethod("myTest", String.class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.String", String.class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithPrimitiveArrayParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", int[].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, int[].class, int[].class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithPrimitiveArrayParameterUsingSourceCodeSyntax() throws Exception { + var method = getClass().getDeclaredMethod("myTest", int[].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "int[]", "int[]"); + } + + @Test + void selectMethodByFullyQualifiedNameWithObjectArrayParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", String[].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, String[].class, String[].class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithObjectArrayParameterUsingSourceCodeSyntax() throws Exception { + var method = getClass().getDeclaredMethod("myTest", String[].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.String[]", "java.lang.String[]"); + } + + @Test + void selectMethodByFullyQualifiedNameWithTwoDimensionalPrimitiveArrayParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", int[][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, int[][].class, int[][].class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithTwoDimensionalPrimitiveArrayParameterUsingSourceCodeSyntax() + throws Exception { + var method = getClass().getDeclaredMethod("myTest", int[][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "int[][]", "int[][]"); + } + + @Test + void selectMethodByFullyQualifiedNameWithTwoDimensionalObjectArrayParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", String[][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, String[][].class, String[][].class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithTwoDimensionalObjectArrayParameterUsingSourceCodeSyntax() + throws Exception { + var method = getClass().getDeclaredMethod("myTest", String[][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.String[][]", "java.lang.String[][]"); + } + + @Test + void selectMethodByFullyQualifiedNameWithMultidimensionalPrimitiveArrayParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", int[][][][][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, int[][][][][].class, int[][][][][].class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithMultidimensionalPrimitiveArrayParameterUsingSourceCodeSyntax() + throws Exception { + + var method = getClass().getDeclaredMethod("myTest", int[][][][][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "int[][][][][]", "int[][][][][]"); + } + + @Test + void selectMethodByFullyQualifiedNameWithMultidimensionalObjectArrayParameter() throws Exception { + var method = getClass().getDeclaredMethod("myTest", Double[][][][][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, Double[][][][][].class, + Double[][][][][].class.getName()); + } + + @Test + void selectMethodByFullyQualifiedNameWithMultidimensionalObjectArrayParameterUsingSourceCodeSyntax() + throws Exception { + + var method = getClass().getDeclaredMethod("myTest", Double[][][][][].class); + assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.Double[][][][][]", + "java.lang.Double[][][][][]"); + } + + @Test + void selectMethodByFullyQualifiedNameEndingInOpeningParenthesis() { + var className = "org.example.MyClass"; + // The following bizarre method name is not permissible in Java source + // code; however, it's permitted by the JVM -- for example, in Groovy + // or Kotlin source code using back ticks. + var methodName = ")--("; + var fqmn = className + "#" + methodName; + + var selector = selectMethod(fqmn); + assertEquals(className, selector.getClassName()); + assertEquals(methodName, selector.getMethodName()); + assertEquals("", selector.getMethodParameterTypes()); + } + + /** + * Inspired by Spock specifications. + */ + @Test + void selectMethodByFullyQualifiedNameContainingHashtags() { + var className = "org.example.CalculatorSpec"; + var methodName = "#a plus #b equals #c"; + var fqmn = className + "#" + methodName; + + var selector = selectMethod(fqmn); + assertEquals(className, selector.getClassName()); + assertEquals(methodName, selector.getMethodName()); + assertEquals("", selector.getMethodParameterTypes()); + } + + /** + * Inspired by Spock specifications. + */ + @Test + void selectMethodByFullyQualifiedNameContainingHashtagsAndWithParameterList() { + var className = "org.example.CalculatorSpec"; + var methodName = "#a plus #b equals #c"; + var methodParameters = "int, int, int"; + var fqmn = String.format("%s#%s(%s)", className, methodName, methodParameters); + + var selector = selectMethod(fqmn); + assertEquals(className, selector.getClassName()); + assertEquals(methodName, selector.getMethodName()); + assertEquals(methodParameters, selector.getMethodParameterTypes()); + } + + /** + * Inspired by Kotlin tests. + */ + @Test + void selectMethodByFullyQualifiedNameContainingParentheses() { + var className = "org.example.KotlinTestCase"; + var methodName = "🦆 ~|~test with a really, (really) terrible name & that needs to be changed!~|~"; + var fqmn = className + "#" + methodName; + + var selector = selectMethod(fqmn); + + assertEquals(className, selector.getClassName()); + assertEquals(methodName, selector.getMethodName()); + assertEquals("", selector.getMethodParameterTypes()); + } + + /** + * Inspired by Kotlin tests. + */ + @Test + void selectMethodByFullyQualifiedNameEndingWithParentheses() { + var className = "org.example.KotlinTestCase"; + var methodName = "test name ends with parentheses()"; + var fqmn = className + "#" + methodName + "()"; + + var selector = selectMethod(fqmn); + + assertEquals(className, selector.getClassName()); + assertEquals(methodName, selector.getMethodName()); + assertEquals("", selector.getMethodParameterTypes()); + } + + /** + * Inspired by Kotlin tests. + */ + @Test + void selectMethodByFullyQualifiedNameEndingWithParenthesesAndWithParameterList() { + var className = "org.example.KotlinTestCase"; + var methodName = "test name ends with parentheses()"; + var methodParameters = "int, int, int"; + var fqmn = String.format("%s#%s(%s)", className, methodName, methodParameters); + + var selector = selectMethod(fqmn); + + assertEquals(className, selector.getClassName()); + assertEquals(methodName, selector.getMethodName()); + assertEquals(methodParameters, selector.getMethodParameterTypes()); + } + + private void assertSelectMethodByFullyQualifiedName(Class clazz, Method method) { + var selector = selectMethod(fqmn(clazz, method.getName())); + assertEquals(method, selector.getJavaMethod()); + assertEquals(clazz, selector.getJavaClass()); + assertEquals(clazz.getName(), selector.getClassName()); + assertEquals(method.getName(), selector.getMethodName()); + assertEquals("", selector.getMethodParameterTypes()); + } + + private void assertSelectMethodByFullyQualifiedName(Class clazz, Method method, Class parameterType, + String expectedParameterTypes) { + + var selector = selectMethod(fqmn(parameterType)); + assertEquals(method, selector.getJavaMethod()); + assertEquals(clazz, selector.getJavaClass()); + assertEquals(clazz.getName(), selector.getClassName()); + assertEquals(method.getName(), selector.getMethodName()); + assertEquals(expectedParameterTypes, selector.getMethodParameterTypes()); + } + + private void assertSelectMethodByFullyQualifiedName(Class clazz, Method method, String parameterName, + String expectedParameterTypes) { + + var selector = selectMethod(fqmnWithParamNames(parameterName)); + assertEquals(method, selector.getJavaMethod()); + assertEquals(clazz, selector.getJavaClass()); + assertEquals(clazz.getName(), selector.getClassName()); + assertEquals(method.getName(), selector.getMethodName()); + assertEquals(expectedParameterTypes, selector.getMethodParameterTypes()); + } + + @Test + void selectMethodByClassAndMethodName() throws Exception { + var method = getClass().getDeclaredMethod("myTest"); + + var selector = selectMethod(getClass(), "myTest"); + assertEquals(getClass(), selector.getJavaClass()); + assertEquals(getClass().getName(), selector.getClassName()); + assertEquals(method, selector.getJavaMethod()); + assertEquals("myTest", selector.getMethodName()); + assertEquals("", selector.getMethodParameterTypes()); + } + + @Test + void selectMethodByClassAndMethodNameWithParameterTypes() throws Exception { + var method = getClass().getDeclaredMethod("myTest", String.class); + + var selector = selectMethod(getClass(), "myTest", String.class.getName()); + assertEquals(getClass(), selector.getJavaClass()); + assertEquals(getClass().getName(), selector.getClassName()); + assertEquals(method, selector.getJavaMethod()); + assertEquals("myTest", selector.getMethodName()); + assertEquals(String.class.getName(), selector.getMethodParameterTypes()); + } + + @Test + void selectMethodWithParametersByMethodReference() throws Exception { + var method = getClass().getDeclaredMethod("myTest", String.class); + + var selector = selectMethod(getClass(), method); + assertEquals(method, selector.getJavaMethod()); + assertEquals(getClass(), selector.getJavaClass()); + assertEquals(getClass().getName(), selector.getClassName()); + assertEquals("myTest", selector.getMethodName()); + assertEquals(String.class.getName(), selector.getMethodParameterTypes()); + } + + @Test + void selectClassByNameForSpockSpec() { + var className = "org.example.CalculatorSpec"; + var selector = selectClass(className); + assertEquals(className, selector.getClassName()); + } + + @Test + void selectMethodByClassAndNameForSpockSpec() { + var className = "org.example.CalculatorSpec"; + var methodName = "#a plus #b equals #c"; + + var selector = selectMethod(className, methodName); + assertEquals(className, selector.getClassName()); + assertEquals(methodName, selector.getMethodName()); + assertEquals("", selector.getMethodParameterTypes()); + } + + @Test + void selectClasspathRootsWithNonExistingDirectory() { + var selectors = selectClasspathRoots(Set.of(Paths.get("some", "local", "path"))); + + assertThat(selectors).isEmpty(); + } + + @Test + void selectClasspathRootsWithNonExistingJarFile() { + var selectors = selectClasspathRoots(Set.of(Paths.get("some.jar"))); + + assertThat(selectors).isEmpty(); + } + + @Test + void selectClasspathRootsWithExistingDirectory(@TempDir Path tempDir) { + var selectors = selectClasspathRoots(Set.of(tempDir)); + + assertThat(selectors).extracting(ClasspathRootSelector::getClasspathRoot).containsExactly(tempDir.toUri()); + } + + @Test + void selectClasspathRootsWithExistingJarFile() throws Exception { + var jarUri = getClass().getResource("/jartest.jar").toURI(); + var jarFile = Paths.get(jarUri); + + var selectors = selectClasspathRoots(Set.of(jarFile)); + + assertThat(selectors).extracting(ClasspathRootSelector::getClasspathRoot).containsExactly(jarUri); + } + + @Nested + class NestedClassAndMethodSelectors { + + private final String enclosingClassName = getClass().getName() + "$ClassWithNestedInnerClass"; + private final String nestedClassName = getClass().getName() + "$AbstractClassWithNestedInnerClass$NestedClass"; + private final String doubleNestedClassName = nestedClassName + "$DoubleNestedClass"; + private final String methodName = "nestedTest"; + + @Test + void selectNestedClassByClassNames() { + var selector = selectNestedClass(List.of(enclosingClassName), nestedClassName); + + assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); + assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); + + assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); + assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); + } + + @Test + void selectDoubleNestedClassByClassNames() { + var selector = selectNestedClass(List.of(enclosingClassName, nestedClassName), doubleNestedClassName); + + assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class, + AbstractClassWithNestedInnerClass.NestedClass.class); + assertThat(selector.getNestedClass()).isEqualTo( + AbstractClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class); + + assertThat(selector.getEnclosingClassNames()).containsExactly(enclosingClassName, nestedClassName); + assertThat(selector.getNestedClassName()).isEqualTo(doubleNestedClassName); + } + + @Test + void selectNestedClassPreconditions() { + assertViolatesPrecondition(() -> selectNestedClass(null, "ClassName")); + assertViolatesPrecondition(() -> selectNestedClass(List.of(), "ClassName")); + assertViolatesPrecondition(() -> selectNestedClass(List.of("ClassName"), null)); + assertViolatesPrecondition(() -> selectNestedClass(List.of("ClassName"), "")); + assertViolatesPrecondition(() -> selectNestedClass(List.of("ClassName"), " ")); + } + + @Test + void selectNestedMethodByEnclosingClassNamesAndMethodName() throws Exception { + var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName); + + assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); + assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); + assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName)); + + assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); + assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); + assertThat(selector.getMethodName()).isEqualTo(methodName); + } + + @Test + void selectNestedMethodByEnclosingClassesAndMethodName() throws Exception { + var selector = selectNestedMethod(List.of(ClassWithNestedInnerClass.class), + AbstractClassWithNestedInnerClass.NestedClass.class, methodName); + + assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); + assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); + assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName)); + + assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); + assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); + assertThat(selector.getMethodName()).isEqualTo(methodName); + } + + @Test + void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypes() throws Exception { + var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName, + String.class.getName()); + + assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); + assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); + assertThat(selector.getMethod()).isEqualTo( + selector.getNestedClass().getDeclaredMethod(methodName, String.class)); + + assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); + assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); + assertThat(selector.getMethodName()).isEqualTo(methodName); + } + + @Test + void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Exception { + var doubleNestedMethodName = "doubleNestedTest"; + var selector = selectNestedMethod(List.of(enclosingClassName, nestedClassName), doubleNestedClassName, + doubleNestedMethodName); + + assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class, + AbstractClassWithNestedInnerClass.NestedClass.class); + assertThat(selector.getNestedClass()).isEqualTo( + AbstractClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class); + assertThat(selector.getMethod()).isEqualTo( + selector.getNestedClass().getDeclaredMethod(doubleNestedMethodName)); + + assertThat(selector.getEnclosingClassNames()).containsExactly(enclosingClassName, nestedClassName); + assertThat(selector.getNestedClassName()).isEqualTo(doubleNestedClassName); + assertThat(selector.getMethodName()).isEqualTo(doubleNestedMethodName); + } + + @Test + void selectNestedMethodPreconditions() { + assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName")); + assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName", "int")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName", "int")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName", "int")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName", "int")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null)); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, "int")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ", "int")); + assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", null)); + } + + abstract class AbstractClassWithNestedInnerClass { + + @Nested + class NestedClass { + + @Test + void nestedTest() { + } + + @Test + void nestedTest(String parameter) { + } + + @Nested + class DoubleNestedClass { + + @Test + void doubleNestedTest() { + } + + } + + } + + } + + class ClassWithNestedInnerClass extends AbstractClassWithNestedInnerClass { + } + + class OtherClassWithNestedInnerClass extends AbstractClassWithNestedInnerClass { + } + + } + + // ------------------------------------------------------------------------- + + private void assertViolatesPrecondition(Executable precondition) { + assertThrows(PreconditionViolationException.class, precondition); + } + + private static String fqmn(Class... params) { + return fqmn(DiscoverySelectorsTests.class, "myTest", params); + } + + private static String fqmn(Class clazz, String methodName, Class... params) { + return ReflectionUtils.getFullyQualifiedMethodName(clazz, methodName, params); + } + + private static String fqmnWithParamNames(String... params) { + return String.format("%s#%s(%s)", DiscoverySelectorsTests.class.getName(), "myTest", join(", ", params)); + } + interface TestInterface { + + @Test + default void myTest() { + } + + } + private static class TestCaseWithDefaultMethod implements TestInterface { + + } + + void myTest() { + } + + void myTest(int num) { + } + + void myTest(int[] nums) { + } + + void myTest(int[][] grid) { + } + + void myTest(int[][][][][] grid) { + } + + void myTest(String info) { + } + + void myTest(String[] info) { + } + + void myTest(String[][] info) { + } + + void myTest(Double[][][][][] data) { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java new file mode 100644 index 00000000..bd5afe94 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.AbstractEqualsAndHashCodeTests; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link FilePosition}. + * + * @since 1.7 + */ +@DisplayName("FilePosition unit tests") +class FilePositionTests extends AbstractEqualsAndHashCodeTests { + + @Test + @DisplayName("factory method preconditions") + void preconditions() { + assertThrows(PreconditionViolationException.class, () -> FilePosition.from(-1)); + assertThrows(PreconditionViolationException.class, () -> FilePosition.from(0, -1)); + } + + @Test + @DisplayName("create FilePosition from factory method with line number") + void filePositionFromLine() { + var filePosition = FilePosition.from(42); + + assertThat(filePosition.getLine()).isEqualTo(42); + assertThat(filePosition.getColumn()).isEmpty(); + } + + @Test + @DisplayName("create FilePosition from factory method with line number and column number") + void filePositionFromLineAndColumn() { + var filePosition = FilePosition.from(42, 99); + + assertThat(filePosition.getLine()).isEqualTo(42); + assertThat(filePosition.getColumn()).contains(99); + } + + /** + * @since 1.3 + */ + @ParameterizedTest + @MethodSource + void filePositionFromQuery(String query, int expectedLine, int expectedColumn) { + var optionalFilePosition = FilePosition.fromQuery(query); + + if (optionalFilePosition.isPresent()) { + var filePosition = optionalFilePosition.get(); + + assertThat(filePosition.getLine()).isEqualTo(expectedLine); + assertThat(filePosition.getColumn().orElse(-1)).isEqualTo(expectedColumn); + } + else { + assertEquals(-1, expectedColumn); + assertEquals(-1, expectedLine); + } + } + + @SuppressWarnings("unused") + static Stream filePositionFromQuery() { + return Stream.of( // + arguments(null, -1, -1), // + arguments("?!", -1, -1), // + arguments("line=ZZ", -1, -1), // + arguments("line=42", 42, -1), // + arguments("line=42&column=99", 42, 99), // + arguments("line=42&column=ZZ", 42, -1), // + arguments("line=42&abc=xyz&column=99", 42, 99), // + arguments("1=3&foo=X&line=42&abc=xyz&column=99&enigma=393939", 42, 99), // + // First one wins: + arguments("line=42&line=555", 42, -1), // + arguments("line=42&line=555&column=99&column=555", 42, 99) // + ); + } + + @Test + @DisplayName("equals() and hashCode() with column number cached by Integer.valueOf()") + void equalsAndHashCode() { + var same = FilePosition.from(42, 99); + var sameSame = FilePosition.from(42, 99); + var different = FilePosition.from(1, 2); + + assertEqualsAndHashCode(same, sameSame, different); + } + + @Test + @DisplayName("equals() and hashCode() with column number not cached by Integer.valueOf()") + void equalsAndHashCodeWithColumnNumberNotCachedByJavaLangIntegerDotValueOf() { + var same = FilePosition.from(42, 99999); + var sameSame = FilePosition.from(42, 99999); + var different = FilePosition.from(1, 2); + + assertEqualsAndHashCode(same, sameSame, different); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java new file mode 100644 index 00000000..2d4b47d1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; + +/** + * Unit tests for {@link FileSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class FileSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new FileSelector("/example/foo.txt", null); + var selector2 = new FileSelector("/example/foo.txt", null); + var selector3 = new FileSelector("/example/bar.txt", null); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + + @Test + void equalsAndHashCodeWithFilePosition() { + var selector1 = new FileSelector("/example/foo.txt", FilePosition.from(1)); + var selector2 = new FileSelector("/example/foo.txt", FilePosition.from(1)); + var selector3 = new FileSelector("/example/bar.txt", FilePosition.from(2)); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java new file mode 100644 index 00000000..4f6b5412 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link MethodSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class MethodSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new MethodSelector("TestClass", "method", "int, boolean"); + var selector2 = new MethodSelector("TestClass", "method", "int, boolean"); + + assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "method", "int")); + assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "method")); + assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "X", "int, boolean")); + assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "X")); + assertEqualsAndHashCode(selector1, selector2, new MethodSelector("X", "method", "int, boolean")); + assertEqualsAndHashCode(selector1, selector2, new MethodSelector("X", "method")); + } + + @Test + void preservesOriginalExceptionWhenTryingToLoadClass() { + var selector = new MethodSelector("TestClass", "method", "int, boolean"); + + var e = assertThrows(PreconditionViolationException.class, selector::getJavaClass); + + assertThat(e).hasMessage("Could not load class with name: TestClass").hasCauseInstanceOf( + ClassNotFoundException.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java new file mode 100644 index 00000000..a637a6c7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; + +/** + * Unit tests for {@link ModuleSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class ModuleSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new ModuleSelector("foo-api"); + var selector2 = new ModuleSelector("foo-api"); + var selector3 = new ModuleSelector("bar-impl"); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java new file mode 100644 index 00000000..c58b24b5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link NestedClassSelector}. + * + * @since 1.6 + * @see DiscoverySelectorsTests + */ +class NestedClassSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), + "org.example.NestedTestClass"); + var selector2 = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), + "org.example.NestedTestClass"); + var selector3 = new NestedClassSelector(List.of("org.example.X"), "org.example.Y"); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + + @Test + void preservesOriginalExceptionWhenTryingToLoadEnclosingClasses() { + var selector = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), + "org.example.NestedTestClass"); + + var exception = assertThrows(PreconditionViolationException.class, selector::getEnclosingClasses); + + assertThat(exception).hasMessage("Could not load class with name: org.example.EnclosingTestClass") // + .hasCauseInstanceOf(ClassNotFoundException.class); + } + + @Test + void preservesOriginalExceptionWhenTryingToLoadNestedClass() { + var selector = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), + "org.example.NestedTestClass"); + + var exception = assertThrows(PreconditionViolationException.class, selector::getNestedClass); + + assertThat(exception).hasMessage("Could not load class with name: org.example.NestedTestClass") // + .hasCauseInstanceOf(ClassNotFoundException.class); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java new file mode 100644 index 00000000..2c547172 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link NestedMethodSelector}. + * + * @since 1.6 + * @see DiscoverySelectorsTests + */ +class NestedMethodSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", + "int, boolean"); + var selector2 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", + "int, boolean"); + + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("X"), "NestedTestClass", "method", "int, boolean")); + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("X"), "NestedTestClass", "method")); + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int")); + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method")); + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X", "int, boolean")); + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X")); + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("EnclosingClass"), "X", "method", "int, boolean")); + assertEqualsAndHashCode(selector1, selector2, + new NestedMethodSelector(List.of("EnclosingClass"), "X", "method")); + } + + @Test + void preservesOriginalExceptionWhenTryingToLoadEnclosingClass() { + var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); + + var exception = assertThrows(PreconditionViolationException.class, selector::getEnclosingClasses); + + assertThat(exception).hasMessage("Could not load class with name: EnclosingClass") // + .hasCauseInstanceOf(ClassNotFoundException.class); + } + + @Test + void preservesOriginalExceptionWhenTryingToLoadNestedClass() { + var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); + + var exception = assertThrows(PreconditionViolationException.class, selector::getNestedClass); + + assertThat(exception).hasMessage("Could not load class with name: NestedTestClass") // + .hasCauseInstanceOf(ClassNotFoundException.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java new file mode 100644 index 00000000..68c20f76 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 1.0 + */ +class PackageNameFilterTests { + + @Test + void includePackageChecksPreconditions() { + assertThatThrownBy(() -> PackageNameFilter.includePackageNames((String[]) null)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("packageNames array must not be null or empty"); + assertThatThrownBy(() -> PackageNameFilter.includePackageNames(new String[0])) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("packageNames array must not be null or empty"); + assertThatThrownBy(() -> PackageNameFilter.includePackageNames(new String[] { null })) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("packageNames array must not contain null elements"); + } + + @Test + void includePackageWithMultiplePackages() { + var includedPackage1 = "java.lang"; + var includedPackage2 = "java.util"; + var filter = PackageNameFilter.includePackageNames(includedPackage1, includedPackage2); + + assertThat(filter).hasToString( + "IncludePackageNameFilter that includes packages whose names are either equal to or start with one of the following: '" + + includedPackage1 + "' OR '" + includedPackage2 + "'"); + + assertTrue(filter.apply("java.lang.String").included()); + assertTrue(filter.toPredicate().test("java.lang.String")); + assertThat(filter.apply("java.lang.String").getReason()).contains( + "Package name [java.lang.String] matches included name: '" + includedPackage1 + "'"); + + assertTrue(filter.apply("java.util.Collection").included()); + assertTrue(filter.toPredicate().test("java.util.Collection")); + assertThat(filter.apply("java.util.Collection").getReason()).contains( + "Package name [java.util.Collection] matches included name: '" + includedPackage2 + "'"); + + assertTrue(filter.apply("java.util.function.Consumer").included()); + assertTrue(filter.toPredicate().test("java.util.function.Consumer")); + assertThat(filter.apply("java.util.function.Consumer").getReason()).contains( + "Package name [java.util.function.Consumer] matches included name: '" + includedPackage2 + "'"); + + assertFalse(filter.apply("java.time.Instant").included()); + assertFalse(filter.toPredicate().test("java.time.Instant")); + assertThat(filter.apply("java.time.Instant").getReason()).contains( + "Package name [java.time.Instant] does not match any included names: '" + includedPackage1 + "' OR '" + + includedPackage2 + "'"); + + assertFalse(filter.apply("java.language.Test").included()); + assertFalse(filter.toPredicate().test("java.language.Test")); + assertThat(filter.apply("java.language.Test").getReason()).contains( + "Package name [java.language.Test] does not match any included names: '" + includedPackage1 + "' OR '" + + includedPackage2 + "'"); + } + + @Test + void excludePackageChecksPreconditions() { + assertThatThrownBy(() -> PackageNameFilter.excludePackageNames((String[]) null)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("packageNames must not be null or empty"); + assertThatThrownBy(() -> PackageNameFilter.excludePackageNames(new String[0])) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("packageNames must not be null or empty"); + assertThatThrownBy(() -> PackageNameFilter.excludePackageNames(new String[] { null })) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("packageNames must not contain null elements"); + } + + @Test + void excludePackageWithMultiplePackages() { + var excludedPackage1 = "java.lang"; + var excludedPackage2 = "java.util"; + var filter = PackageNameFilter.excludePackageNames(excludedPackage1, excludedPackage2); + + assertThat(filter).hasToString( + "ExcludePackageNameFilter that excludes packages whose names are either equal to or start with one of the following: '" + + excludedPackage1 + "' OR '" + excludedPackage2 + "'"); + + assertTrue(filter.apply("java.lang.String").excluded()); + assertFalse(filter.toPredicate().test("java.lang.String")); + assertThat(filter.apply("java.lang.String").getReason()).contains( + "Package name [java.lang.String] matches excluded name: '" + excludedPackage1 + "'"); + + assertTrue(filter.apply("java.util.Collection").excluded()); + assertFalse(filter.toPredicate().test("java.util.Collection")); + assertThat(filter.apply("java.util.Collection").getReason()).contains( + "Package name [java.util.Collection] matches excluded name: '" + excludedPackage2 + "'"); + + assertTrue(filter.apply("java.util.function.Consumer").excluded()); + assertFalse(filter.toPredicate().test("java.util.function.Consumer")); + assertThat(filter.apply("java.util.function.Consumer").getReason()).contains( + "Package name [java.util.function.Consumer] matches excluded name: '" + excludedPackage2 + "'"); + + assertTrue(filter.apply("java.time.Instant").included()); + assertTrue(filter.toPredicate().test("java.time.Instant")); + assertThat(filter.apply("java.time.Instant").getReason()).contains( + "Package name [java.time.Instant] does not match any excluded names: '" + excludedPackage1 + "' OR '" + + excludedPackage2 + "'"); + + assertTrue(filter.apply("java.language.Test").included()); + assertTrue(filter.toPredicate().test("java.language.Test")); + assertThat(filter.apply("java.language.Test").getReason()).contains( + "Package name [java.language.Test] does not match any excluded names: '" + excludedPackage1 + "' OR '" + + excludedPackage2 + "'"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java new file mode 100644 index 00000000..3de77a7b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; + +/** + * Unit tests for {@link PackageSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class PackageSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var selector1 = new PackageSelector("org.example.foo"); + var selector2 = new PackageSelector("org.example.foo"); + var selector3 = new PackageSelector("org.example.bar"); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java new file mode 100644 index 00000000..13084e5f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; +import org.junit.platform.engine.UniqueId; + +/** + * Unit tests for {@link UniqueIdSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class UniqueIdSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() { + var testEngine = UniqueId.forEngine("test-engine"); + var selector1 = new UniqueIdSelector(testEngine.append("test-class", "org.example.TestClass")); + var selector2 = new UniqueIdSelector(testEngine.append("test-class", "org.example.TestClass")); + var selector3 = new UniqueIdSelector(testEngine.append("test-class", "org.example.FooBar")); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java new file mode 100644 index 00000000..1405c8e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.discovery; + +import java.net.URI; + +import org.junit.jupiter.api.Test; +import org.junit.platform.AbstractEqualsAndHashCodeTests; + +/** + * Unit tests for {@link UriSelector}. + * + * @since 1.3 + * @see DiscoverySelectorsTests + */ +class UriSelectorTests extends AbstractEqualsAndHashCodeTests { + + @Test + void equalsAndHashCode() throws Exception { + var selector1 = new UriSelector(new URI("https://junit.org")); + var selector2 = new UriSelector(new URI("https://junit.org")); + var selector3 = new UriSelector(new URI("https://example.org")); + + assertEqualsAndHashCode(selector1, selector2, selector3); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java new file mode 100644 index 00000000..5875c349 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.ConfigurationParameters; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link PrefixedConfigurationParameters}. + * + * @since 1.3 + */ +@ExtendWith(MockitoExtension.class) +class PrefixedConfigurationParametersTests { + + @Mock + private ConfigurationParameters delegate; + + @Test + void preconditions() { + assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(null, "example.")); + assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(delegate, null)); + assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(delegate, "")); + assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(delegate, " ")); + } + + @Test + void delegatesGetCalls() { + when(delegate.get(any())).thenReturn(Optional.of("result")); + var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); + + assertThat(parameters.get("qux")).contains("result"); + + verify(delegate).get("foo.bar.qux"); + } + + @Test + void delegatesGetBooleanCalls() { + when(delegate.getBoolean(any())).thenReturn(Optional.of(true)); + var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); + + assertThat(parameters.getBoolean("qux")).contains(true); + + verify(delegate).getBoolean("foo.bar.qux"); + } + + @Test + void delegatesGetWithTransformerCalls() { + when(delegate.get(any(), any())).thenReturn(Optional.of("QUX")); + var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); + + Function transformer = String::toUpperCase; + assertThat(parameters.get("qux", transformer)).contains("QUX"); + + verify(delegate).get("foo.bar.qux", transformer); + } + + @Test + @SuppressWarnings("deprecation") + void delegatesSizeCalls() { + when(delegate.size()).thenReturn(42); + var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); + + assertThat(parameters.size()).isEqualTo(42); + + verify(delegate).size(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java new file mode 100644 index 00000000..abbc0f1e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java @@ -0,0 +1,162 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; + +/** + * Unit tests for {@link AbstractTestDescriptor} and {@link EngineDescriptor}. + * + * @since 1.0 + */ +class AbstractTestDescriptorTests { + + private EngineDescriptor engineDescriptor; + + @BeforeEach + void initTree() { + engineDescriptor = new EngineDescriptor(UniqueId.forEngine("testEngine"), "testEngine"); + var group1 = new GroupDescriptor(UniqueId.root("group", "group1")); + engineDescriptor.addChild(group1); + var group2 = new GroupDescriptor(UniqueId.root("group", "group2")); + engineDescriptor.addChild(group2); + var group11 = new GroupDescriptor(UniqueId.root("group", "group1-1")); + group1.addChild(group11); + + group1.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf1-1"))); + group1.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf1-2"))); + + group2.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf2-1"))); + + group11.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf11-1"))); + } + + @Test + void removeRootFromHierarchyFails() { + var e = assertThrows(JUnitException.class, () -> engineDescriptor.removeFromHierarchy()); + assertTrue(e.toString().contains("cannot remove the root of a hierarchy")); + } + + @Test + void removeFromHierarchyClearsParentFromAllChildren() { + var group = engineDescriptor.getChildren().iterator().next(); + assertSame(engineDescriptor, group.getParent().orElseThrow(Error::new)); + assertTrue(group.getChildren().stream().allMatch(d -> d.getParent().orElseThrow(Error::new) == group)); + + var formerChildren = group.getChildren(); + group.removeFromHierarchy(); + + assertFalse(group.getParent().isPresent()); + assertTrue(group.getChildren().isEmpty()); + assertTrue(formerChildren.stream().noneMatch(d -> d.getParent().isPresent())); + } + + @Test + void setParentToOtherInstance() { + TestDescriptor newEngine = new EngineDescriptor(UniqueId.forEngine("newEngine"), "newEngine"); + var group = engineDescriptor.getChildren().iterator().next(); + assertSame(engineDescriptor, group.getParent().orElseThrow(Error::new)); + group.setParent(newEngine); + assertSame(newEngine, group.getParent().orElseThrow(Error::new)); + } + + @Test + void setParentToNull() { + var group = engineDescriptor.getChildren().iterator().next(); + assertTrue(group.getParent().isPresent()); + group.setParent(null); + assertFalse(group.getParent().isPresent()); + } + + @Test + void visitAllNodes() { + List visited = new ArrayList<>(); + engineDescriptor.accept(visited::add); + + assertEquals(8, visited.size()); + } + + @Test + void pruneLeaf() { + TestDescriptor.Visitor visitor = descriptor -> { + if (descriptor.getUniqueId().equals(UniqueId.root("leaf", "leaf1-1"))) + descriptor.removeFromHierarchy(); + }; + engineDescriptor.accept(visitor); + + List visited = new ArrayList<>(); + engineDescriptor.accept(descriptor -> visited.add(descriptor.getUniqueId())); + + assertEquals(7, visited.size()); + assertTrue(visited.contains(UniqueId.root("group", "group1"))); + assertFalse(visited.contains(UniqueId.root("leaf", "leaf1-1"))); + } + + @Test + void pruneGroup() { + final var countVisited = new AtomicInteger(); + TestDescriptor.Visitor visitor = descriptor -> { + if (descriptor.getUniqueId().equals(UniqueId.root("group", "group1"))) + descriptor.removeFromHierarchy(); + countVisited.incrementAndGet(); + }; + engineDescriptor.accept(visitor); + + assertEquals(4, countVisited.get(), "Children of pruned element are not visited"); + + List visited = new ArrayList<>(); + engineDescriptor.accept(descriptor -> visited.add(descriptor.getUniqueId())); + + assertEquals(3, visited.size()); + assertFalse(visited.contains(UniqueId.root("group", "group1"))); + } + +} + +class GroupDescriptor extends AbstractTestDescriptor { + + GroupDescriptor(UniqueId uniqueId) { + super(uniqueId, "group: " + uniqueId); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + +} + +class LeafDescriptor extends AbstractTestDescriptor { + + LeafDescriptor(UniqueId uniqueId) { + super(uniqueId, "leaf: " + uniqueId); + } + + @Override + public Type getType() { + return Type.TEST; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java new file mode 100644 index 00000000..50dc4a1a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.AbstractEqualsAndHashCodeTests; +import org.junit.platform.engine.TestSource; + +/** + * Abstract base class for unit tests involving {@link TestSource TestSources} + * and {@link FilePosition FilePositions}. + * + * @since 1.0 + */ +abstract class AbstractTestSourceTests extends AbstractEqualsAndHashCodeTests { + + abstract Stream createSerializableInstances() throws Exception; + + @TestFactory + Stream assertToString() throws Exception { + return createSerializableInstances() // + .map(instance -> dynamicTest(instance.toString(), () -> assertToString(instance))); + } + + private void assertToString(Object instance) { + assertNotNull(instance); + assertTrue(instance.toString().startsWith(instance.getClass().getSimpleName())); + } + + @TestFactory + Stream assertSerializable() throws Exception { + return createSerializableInstances() // + .map(instance -> dynamicTest(instance.toString(), () -> assertSerializable(instance))); + } + + private void assertSerializable(T instance) { + try { + Class type = instance.getClass(); + var serialized = serialize(instance); + var deserialized = deserialize(serialized); + + assertTrue(type.isAssignableFrom(deserialized.getClass())); + assertEquals(instance, deserialized); + } + catch (Exception e) { + fail("assertSerializable failed: " + instance, e); + } + } + + private byte[] serialize(Object obj) throws Exception { + var b = new ByteArrayOutputStream(); + var o = new ObjectOutputStream(b); + o.writeObject(obj); + return b.toByteArray(); + } + + private Object deserialize(byte[] bytes) throws Exception { + var b = new ByteArrayInputStream(bytes); + var o = new ObjectInputStream(b); + return o.readObject(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java new file mode 100644 index 00000000..2d5b6ead --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java @@ -0,0 +1,181 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link ClassSource}. + * + * @since 1.0 + */ +class ClassSourceTests extends AbstractTestSourceTests { + + @Override + Stream createSerializableInstances() { + return Stream.of( // + ClassSource.from("class.source"), // + ClassSource.from("class.and.position", FilePosition.from(1, 2)), // + ClassSource.from(getClass()), // + ClassSource.from(getClass(), FilePosition.from(1, 2)) // + ); + } + + @Test + void preconditions() { + assertThrows(PreconditionViolationException.class, () -> ClassSource.from((String) null)); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from(" ")); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from((String) null, null)); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from(" ", null)); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from((Class) null)); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from((Class) null, null)); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from((URI) null)); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from(new URI("badscheme:/com.foo.Bar"))); + assertThrows(PreconditionViolationException.class, () -> ClassSource.from(new URI("class:?line=1"))); + } + + @Test + void classSourceFromName() { + var testClassName = "com.unknown.mypackage.ClassByName"; + var source = ClassSource.from(testClassName); + + assertThat(source.getClassName()).isEqualTo(testClassName); + assertThat(source.getPosition()).isEmpty(); + + var exception = assertThrows(PreconditionViolationException.class, source::getJavaClass); + assertThat(exception).hasMessage("Could not load class with name: " + testClassName); + } + + @Test + void classSourceFromNameAndFilePosition() { + var testClassName = "com.unknown.mypackage.ClassByName"; + var position = FilePosition.from(42, 23); + var source = ClassSource.from(testClassName, position); + + assertThat(source.getClassName()).isEqualTo(testClassName); + assertThat(source.getPosition()).isNotEmpty(); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void classSourceFromReference() { + var testClass = getClass(); + var source = ClassSource.from(testClass); + + assertThat(source.getJavaClass()).isEqualTo(testClass); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void classSourceFromReferenceAndFilePosition() { + var testClass = getClass(); + var position = FilePosition.from(42, 23); + var source = ClassSource.from(testClass, position); + + assertThat(source.getJavaClass()).isEqualTo(testClass); + assertThat(source.getPosition()).isNotEmpty(); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void classSourceFromUri() throws URISyntaxException { + var source = ClassSource.from(new URI("class:java.lang.Object")); + + assertThat(source.getJavaClass()).isEqualTo(Object.class); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void classSourceFromUriWithLineNumber() throws URISyntaxException { + var position = FilePosition.from(42); + var source = ClassSource.from(new URI("class:java.lang.Object?line=42")); + + assertThat(source.getJavaClass()).isEqualTo(Object.class); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void classSourceFromUriWithLineAndColumnNumbers() throws URISyntaxException { + var position = FilePosition.from(42, 23); + var source = ClassSource.from(new URI("class:java.lang.Object?line=42&foo=bar&column=23")); + + assertThat(source.getJavaClass()).isEqualTo(Object.class); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void classSourceFromUriWithEmptyQuery() throws URISyntaxException { + var source = ClassSource.from(new URI("class:java.lang.Object?")); + + assertThat(source.getJavaClass()).isEqualTo(Object.class); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void classSourceFromUriWithUnsupportedParametersInQuery() throws URISyntaxException { + var source = ClassSource.from(new URI("class:java.lang.Object?foo=42&bar")); + + assertThat(source.getJavaClass()).isEqualTo(Object.class); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void equalsAndHashCodeForClassSourceFromName() { + var name1 = String.class.getName(); + var name2 = Number.class.getName(); + + assertEqualsAndHashCode(ClassSource.from(name1), ClassSource.from(name1), ClassSource.from(name2)); + } + + @Test + void equalsAndHashCodeForClassSourceFromNameAndFilePosition() { + var name1 = String.class.getName(); + var name2 = Number.class.getName(); + var position1 = FilePosition.from(42, 23); + var position2 = FilePosition.from(1, 2); + + assertEqualsAndHashCode(ClassSource.from(name1, position1), ClassSource.from(name1, position1), + ClassSource.from(name2, position1)); + assertEqualsAndHashCode(ClassSource.from(name1, position1), ClassSource.from(name1, position1), + ClassSource.from(name1, position2)); + } + + @Test + void equalsAndHashCodeForClassSourceFromReference() { + var class1 = String.class; + var class2 = Number.class; + + assertEqualsAndHashCode(ClassSource.from(class1), ClassSource.from(class1), ClassSource.from(class2)); + } + + @Test + void equalsAndHashCodeForClassSourceFromReferenceAndFilePosition() { + var class1 = String.class; + var class2 = Number.class; + var position1 = FilePosition.from(42, 23); + var position2 = FilePosition.from(1, 2); + + assertEqualsAndHashCode(ClassSource.from(class1, position1), ClassSource.from(class1, position1), + ClassSource.from(class2, position1)); + assertEqualsAndHashCode(ClassSource.from(class1, position1), ClassSource.from(class1, position1), + ClassSource.from(class1, position2)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java new file mode 100644 index 00000000..cb1f75bd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; + +import java.net.URI; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link ClasspathResourceSource}. + * + * @since 1.0 + */ +class ClasspathResourceSourceTests extends AbstractTestSourceTests { + + private static final String FOO_RESOURCE = "test/foo.xml"; + private static final String BAR_RESOURCE = "/config/bar.json"; + + private static final URI FOO_RESOURCE_URI = URI.create(CLASSPATH_SCHEME + ":/" + FOO_RESOURCE); + + @Override + Stream createSerializableInstances() { + return Stream.of(ClasspathResourceSource.from(FOO_RESOURCE)); + } + + @Test + void preconditions() { + assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from((String) null)); + assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from("")); + assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from(" ")); + + assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from((URI) null)); + assertThrows(PreconditionViolationException.class, + () -> ClasspathResourceSource.from(URI.create("file:/foo.txt"))); + } + + @Test + void resourceWithoutPosition() { + var source = ClasspathResourceSource.from(FOO_RESOURCE); + + assertThat(source).isNotNull(); + assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void resourceWithLeadingSlashWithoutPosition() { + var source = ClasspathResourceSource.from("/" + FOO_RESOURCE); + + assertThat(source).isNotNull(); + assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void resourceWithPosition() { + var position = FilePosition.from(42, 23); + var source = ClasspathResourceSource.from(FOO_RESOURCE, position); + + assertThat(source).isNotNull(); + assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void resourceFromUriWithoutPosition() { + var source = ClasspathResourceSource.from(FOO_RESOURCE_URI); + + assertThat(source).isNotNull(); + assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void resourceFromUriWithLineNumber() { + var position = FilePosition.from(42); + var uri = URI.create(FOO_RESOURCE_URI + "?line=42"); + var source = ClasspathResourceSource.from(uri); + + assertThat(source).isNotNull(); + assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void resourceFromUriWithLineAndColumnNumbers() { + var position = FilePosition.from(42, 23); + var uri = URI.create(FOO_RESOURCE_URI + "?line=42&foo=bar&column=23"); + var source = ClasspathResourceSource.from(uri); + + assertThat(source).isNotNull(); + assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void equalsAndHashCode() { + assertEqualsAndHashCode(ClasspathResourceSource.from(FOO_RESOURCE), ClasspathResourceSource.from(FOO_RESOURCE), + ClasspathResourceSource.from(BAR_RESOURCE)); + + var position = FilePosition.from(42, 23); + assertEqualsAndHashCode(ClasspathResourceSource.from(FOO_RESOURCE, position), + ClasspathResourceSource.from(FOO_RESOURCE, position), ClasspathResourceSource.from(BAR_RESOURCE, position)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java new file mode 100644 index 00000000..6984fa14 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link CompositeTestSource}. + * + * @since 1.0 + */ +class CompositeTestSourceTests extends AbstractTestSourceTests { + + @Override + Stream createSerializableInstances() { + var fileSource = FileSource.from(new File("sample.instance")); + var classSource = ClassSource.from(getClass()); + var sources = List.of(fileSource, classSource); + return Stream.of(CompositeTestSource.from(sources)); + } + + @Test + void createCompositeTestSourceFromNullList() { + assertThrows(PreconditionViolationException.class, () -> CompositeTestSource.from(null)); + } + + @Test + void createCompositeTestSourceFromEmptyList() { + assertThrows(PreconditionViolationException.class, () -> CompositeTestSource.from(List.of())); + } + + @Test + void createCompositeTestSourceFromClassAndFileSources() { + var fileSource = FileSource.from(new File("example.test")); + var classSource = ClassSource.from(getClass()); + var sources = new ArrayList<>(List.of(fileSource, classSource)); + var compositeTestSource = CompositeTestSource.from(sources); + + assertThat(compositeTestSource.getSources().size()).isEqualTo(2); + assertThat(compositeTestSource.getSources()).contains(fileSource, classSource); + + // Ensure the supplied sources list was defensively copied. + sources.remove(1); + assertThat(compositeTestSource.getSources().size()).isEqualTo(2); + + // Ensure the returned sources list is immutable. + assertThrows(UnsupportedOperationException.class, () -> compositeTestSource.getSources().add(fileSource)); + } + + @Test + void equalsAndHashCode() { + var sources1 = List.of(ClassSource.from(Number.class)); + var sources2 = List.of(ClassSource.from(String.class)); + assertEqualsAndHashCode(CompositeTestSource.from(sources1), CompositeTestSource.from(sources1), + CompositeTestSource.from(sources2)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java new file mode 100644 index 00000000..bb488afb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URI; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DefaultUriSource}. + * + * @since 1.3 + */ +class DefaultUriSourceTests extends AbstractTestSourceTests { + + @Override + Stream createSerializableInstances() { + return Stream.of(new DefaultUriSource(URI.create("sample://instance"))); + } + + @Test + void nullSourceUriYieldsException() { + assertThrows(PreconditionViolationException.class, () -> new DefaultUriSource(null)); + } + + @Test + void getterReturnsSameUriInstanceAsSuppliedToTheConstructor() throws Exception { + var expected = new URI("foo.txt"); + var actual = new DefaultUriSource(expected).getUri(); + assertSame(expected, actual); + } + + @Test + void equalsAndHashCode() throws Exception { + var uri1 = new URI("foo.txt"); + var uri2 = new URI("bar.txt"); + assertEqualsAndHashCode(new DefaultUriSource(uri1), new DefaultUriSource(uri1), new DefaultUriSource(uri2)); + } + + @Test + void testToString() { + var actual = new DefaultUriSource(URI.create("foo.txt")).toString(); + assertEquals("DefaultUriSource [uri = foo.txt]", actual); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java new file mode 100644 index 00000000..8531561c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static java.util.stream.Collectors.toCollection; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Tag; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; + +/** + * @since 1.0 + */ +public class DemoClassTestDescriptor extends AbstractTestDescriptor { + + private static final Logger logger = LoggerFactory.getLogger(DemoClassTestDescriptor.class); + + private final Class testClass; + + public DemoClassTestDescriptor(UniqueId uniqueId, Class testClass) { + super(uniqueId, Preconditions.notNull(testClass, "Class must not be null").getSimpleName(), + ClassSource.from(testClass)); + this.testClass = testClass; + } + + @Override + public Set getTags() { + // Copied from org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.getTags(AnnotatedElement) + // @formatter:off + return findRepeatableAnnotations(this.testClass, Tag.class).stream() + .map(Tag::value) + .filter(tag -> { + var isValid = TestTag.isValid(tag); + if (!isValid) { + // TODO [#242] Replace logging with precondition check once we have a proper mechanism for + // handling validation exceptions during the TestEngine discovery phase. + // + // As an alternative to a precondition check here, we could catch any + // PreconditionViolationException thrown by TestTag::create. + logger.warn(() -> String.format( + "Configuration error: invalid tag syntax in @Tag(\"%s\") declaration on [%s]. Tag will be ignored.", + tag, this.testClass)); + } + return isValid; + }) + .map(TestTag::create) + .collect(toCollection(LinkedHashSet::new)); + // @formatter:on + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java new file mode 100644 index 00000000..9b73b713 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static java.util.stream.Collectors.toCollection; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; + +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Tag; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; + +/** + * @since 1.0 + */ +public class DemoMethodTestDescriptor extends AbstractTestDescriptor { + + private static final Logger logger = LoggerFactory.getLogger(DemoMethodTestDescriptor.class); + + private final Class testClass; + private final Method testMethod; + + public DemoMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod) { + super(uniqueId, + String.format("%s(%s)", Preconditions.notNull(testMethod, "Method must not be null").getName(), + ClassUtils.nullSafeToString(Class::getSimpleName, testMethod.getParameterTypes())), + MethodSource.from(testMethod)); + + this.testClass = Preconditions.notNull(testClass, "Class must not be null"); + this.testMethod = testMethod; + } + + @Override + public Set getTags() { + // Copied from org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.getTags(AnnotatedElement) + // @formatter:off + Set methodTags = findRepeatableAnnotations(this.testMethod, Tag.class).stream() + .map(Tag::value) + .filter(tag -> { + var isValid = TestTag.isValid(tag); + if (!isValid) { + // TODO [#242] Replace logging with precondition check once we have a proper mechanism for + // handling validation exceptions during the TestEngine discovery phase. + // + // As an alternative to a precondition check here, we could catch any + // PreconditionViolationException thrown by TestTag::create. + logger.warn(() -> String.format( + "Configuration error: invalid tag syntax in @Tag(\"%s\") declaration on [%s]. Tag will be ignored.", + tag, this.testMethod)); + } + return isValid; + }) + .map(TestTag::create) + .collect(toCollection(LinkedHashSet::new)); + // @formatter:on + + getParent().ifPresent(parentDescriptor -> methodTags.addAll(parentDescriptor.getTags())); + return methodTags; + } + + public final Class getTestClass() { + return this.testClass; + } + + public final Method getTestMethod() { + return this.testMethod; + } + + @Override + public Type getType() { + return Type.TEST; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java new file mode 100644 index 00000000..94318468 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link FilePosition}. + * + * @since 1.0 + */ +@DisplayName("FilePosition unit tests") +class FilePositionTests extends AbstractTestSourceTests { + + @Override + Stream createSerializableInstances() { + return Stream.of(FilePosition.from(42, 99)); + } + + @Test + @DisplayName("factory method preconditions") + void preconditions() { + assertThrows(PreconditionViolationException.class, () -> FilePosition.from(-1)); + assertThrows(PreconditionViolationException.class, () -> FilePosition.from(0, -1)); + } + + @Test + @DisplayName("create FilePosition from factory method with line number") + void filePositionFromLine() { + var filePosition = FilePosition.from(42); + + assertThat(filePosition.getLine()).isEqualTo(42); + assertThat(filePosition.getColumn()).isEmpty(); + } + + @Test + @DisplayName("create FilePosition from factory method with line number and column number") + void filePositionFromLineAndColumn() { + var filePosition = FilePosition.from(42, 99); + + assertThat(filePosition.getLine()).isEqualTo(42); + assertThat(filePosition.getColumn()).contains(99); + } + + /** + * @since 1.3 + */ + @ParameterizedTest + @MethodSource + void filePositionFromQuery(String query, int expectedLine, int expectedColumn) { + var optionalFilePosition = FilePosition.fromQuery(query); + + if (optionalFilePosition.isPresent()) { + var filePosition = optionalFilePosition.get(); + + assertThat(filePosition.getLine()).isEqualTo(expectedLine); + assertThat(filePosition.getColumn().orElse(-1)).isEqualTo(expectedColumn); + } + else { + assertEquals(-1, expectedColumn); + assertEquals(-1, expectedLine); + } + } + + @SuppressWarnings("unused") + static Stream filePositionFromQuery() { + return Stream.of( // + arguments(null, -1, -1), // + arguments("?!", -1, -1), // + arguments("line=ZZ", -1, -1), // + arguments("line=42", 42, -1), // + arguments("line=42&column=99", 42, 99), // + arguments("line=42&column=ZZ", 42, -1), // + arguments("line=42&abc=xyz&column=99", 42, 99), // + arguments("1=3&foo=X&line=42&abc=xyz&column=99&enigma=393939", 42, 99), // + // First one wins: + arguments("line=42&line=555", 42, -1), // + arguments("line=42&line=555&column=99&column=555", 42, 99) // + ); + } + + @Test + @DisplayName("equals() and hashCode() with column number cached by Integer.valueOf()") + void equalsAndHashCode() { + var same = FilePosition.from(42, 99); + var sameSame = FilePosition.from(42, 99); + var different = FilePosition.from(1, 2); + + assertEqualsAndHashCode(same, sameSame, different); + } + + @Test + @DisplayName("equals() and hashCode() with column number not cached by Integer.valueOf()") + void equalsAndHashCodeWithColumnNumberNotCachedByJavaLangIntegerDotValueOf() { + var same = FilePosition.from(42, 99999); + var sameSame = FilePosition.from(42, 99999); + var different = FilePosition.from(1, 2); + + assertEqualsAndHashCode(same, sameSame, different); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java new file mode 100644 index 00000000..f89d127a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.File; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link FileSource} and {@link DirectorySource}. + * + * @since 1.0 + */ +class FileSystemSourceTests extends AbstractTestSourceTests { + + @Override + Stream createSerializableInstances() { + return Stream.of( // + FileSource.from(new File("file.source")), // + FileSource.from(new File("file.and.position"), FilePosition.from(42, 23))); + } + + @Test + void nullSourceFileOrDirectoryYieldsException() { + assertThrows(PreconditionViolationException.class, () -> FileSource.from(null)); + } + + @Test + void directory() throws Exception { + var canonicalDir = new File(".").getCanonicalFile(); + var relativeDir = new File("..", canonicalDir.getName()); + + var source = DirectorySource.from(relativeDir); + + assertThat(source.getUri()).isEqualTo(canonicalDir.toURI()); + assertThat(source.getFile()).isEqualTo(canonicalDir); + } + + @Test + void fileWithoutPosition() throws Exception { + var canonicalDir = new File(".").getCanonicalFile(); + var relativeDir = new File("..", canonicalDir.getName()); + var relativeFile = new File(relativeDir, "test.txt"); + var canonicalFile = relativeFile.getCanonicalFile(); + + var source = FileSource.from(relativeFile); + + assertThat(source.getUri()).isEqualTo(canonicalFile.toURI()); + assertThat(source.getFile()).isEqualTo(canonicalFile); + assertThat(source.getPosition()).isEmpty(); + } + + @Test + void fileWithPosition() { + var file = new File("test.txt"); + var position = FilePosition.from(42, 23); + var source = FileSource.from(file, position); + + assertThat(source.getUri()).isEqualTo(file.getAbsoluteFile().toURI()); + assertThat(source.getFile()).isEqualTo(file.getAbsoluteFile()); + assertThat(source.getPosition()).hasValue(position); + } + + @Test + void equalsAndHashCodeForFileSource() { + var file1 = new File("foo.txt"); + var file2 = new File("bar.txt"); + assertEqualsAndHashCode(FileSource.from(file1), FileSource.from(file1), FileSource.from(file2)); + + var position = FilePosition.from(42, 23); + assertEqualsAndHashCode(FileSource.from(file1, position), FileSource.from(file1, position), + FileSource.from(file2, position)); + } + + @Test + void equalsAndHashCodeForDirectorySource() { + var dir1 = new File("."); + var dir2 = new File(".."); + assertEqualsAndHashCode(DirectorySource.from(dir1), DirectorySource.from(dir1), DirectorySource.from(dir2)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java new file mode 100644 index 00000000..44c7f064 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java @@ -0,0 +1,293 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link MethodSource}. + * + * @since 1.0 + */ +class MethodSourceTests extends AbstractTestSourceTests { + + @Override + Stream createSerializableInstances() throws Exception { + return Stream.of( // + MethodSource.from(getMethod("method1")), // + MethodSource.from(getMethod("method2")) // + ); + } + + @Test + void methodSource() throws Exception { + var testMethod = getMethod("method1"); + var source = MethodSource.from(testMethod); + + assertThat(source.getClassName()).isEqualTo(getClass().getName()); + assertThat(source.getMethodName()).isEqualTo(testMethod.getName()); + assertThat(source.getMethodParameterTypes()).isEqualTo(String.class.getName()); + assertThat(source.getJavaClass()).isEqualTo(getClass()); + assertThat(source.getJavaMethod()).isEqualTo(testMethod); + } + + @Test + void equalsAndHashCodeForMethodSource() throws Exception { + var method1 = getMethod("method1"); + var method2 = getMethod("method2"); + assertEqualsAndHashCode(MethodSource.from(method1), MethodSource.from(method1), MethodSource.from(method2)); + } + + @Test + void instantiatingWithNullNamesShouldThrowPreconditionViolationException() { + assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", null)); + assertThrows(PreconditionViolationException.class, () -> MethodSource.from(null, "foo")); + } + + @Test + void instantiatingWithEmptyNamesShouldThrowPreconditionViolationException() { + assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", "")); + assertThrows(PreconditionViolationException.class, () -> MethodSource.from("", "foo")); + } + + @Test + void instantiatingWithBlankNamesShouldThrowPreconditionViolationException() { + assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", " ")); + assertThrows(PreconditionViolationException.class, () -> MethodSource.from(" ", "foo")); + } + + @Test + void instantiationWithNullMethodShouldThrowPreconditionViolationException() { + assertThrows(PreconditionViolationException.class, () -> MethodSource.from(null)); + } + + @Test + void instantiationWithNullClassOrMethodShouldThrowPreconditionViolationException() { + assertThrows(PreconditionViolationException.class, + () -> MethodSource.from(null, String.class.getDeclaredMethod("getBytes"))); + assertThrows(PreconditionViolationException.class, () -> MethodSource.from(String.class, null)); + } + + @Test + void instantiationWithClassAndMethodShouldResultInACorrectObject() throws Exception { + var source = MethodSource.from(String.class, + String.class.getDeclaredMethod("lastIndexOf", String.class, int.class)); + assertEquals(String.class.getName(), source.getClassName()); + assertEquals("lastIndexOf", source.getMethodName()); + assertEquals("java.lang.String, int", source.getMethodParameterTypes()); + } + + @Test + void instantiationWithClassAndMethodAsStringAndParamsAsClassVarargsShouldResultInACorrectObject() { + var source = MethodSource.from(String.class.getName(), "lastIndexOf", String.class, int.class); + assertEquals(String.class.getName(), source.getClassName()); + assertEquals("lastIndexOf", source.getMethodName()); + assertEquals("java.lang.String, int", source.getMethodParameterTypes()); + } + + @Test + void twoEqualMethodsShouldHaveEqualMethodSourceObjects() { + assertEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass1", "testMethod1")); + } + + @Test + void twoUnequalMethodsShouldHaveUnequalMethodSourceObjects() { + assertNotEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass2", "testMethod1")); + } + + @Test + void twoUnequalMethodsInTheSameClassShouldHaveUnequalMethodSourceObjects() { + assertNotEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass1", "testMethod2")); + } + + @Test + void twoEqualMethodSourceObjectsShouldHaveEqualHashCodes() { + assertEquals(MethodSource.from("TestClass1", "testMethod1").hashCode(), + MethodSource.from("TestClass1", "testMethod1").hashCode()); + } + + @Test + void twoEqualMethodsWithEqualParametersShouldHaveEqualMethodSourceObjects() { + assertEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), + MethodSource.from("TestClass1", "testMethod1", "int, String")); + } + + @Test + void twoUnequalMethodsWithEqualParametersShouldHaveUnequalMethodSourceObjects() { + assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), + MethodSource.from("TestClass1", "testMethod2", "int, String")); + } + + @Test + void twoEqualMethodsWithUnequalParametersShouldHaveUnequalMethodSourceObjects() { + assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), + MethodSource.from("TestClass1", "testMethod1", "float, int, String")); + } + + @Test + void twoEqualMethodsWithEqualParametersShouldHaveEqualMethodSourceHashCodes() { + assertEquals(MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode(), + MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode()); + } + + @Test + void twoEqualMethodsWithUnequalParametersShouldHaveUnequalMethodSourceHashCodes() { + assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode(), + MethodSource.from("TestClass1", "testMethod1", "float, int, String").hashCode()); + } + + @Test + void aReflectedMethodsClassNameShouldBeConsistent() throws Exception { + var m = String.class.getDeclaredMethod("valueOf", int.class); + + assertEquals("java.lang.String", MethodSource.from(m).getClassName()); + } + + @Test + void aReflectedMethodsMethodNameShouldBeConsistent() throws Exception { + var m = String.class.getDeclaredMethod("valueOf", int.class); + + assertEquals("valueOf", MethodSource.from(m).getMethodName()); + } + + @Test + void aReflectedMethodsParameterTypesShouldBeConsistent() throws Exception { + var m = String.class.getDeclaredMethod("valueOf", float.class); + + assertEquals("float", MethodSource.from(m).getMethodParameterTypes()); + } + + @Test + void twoEqualReflectedMethodsShouldHaveEqualMethodSourceObjects() throws Exception { + var m1 = String.class.getDeclaredMethod("valueOf", int.class); + var m2 = String.class.getDeclaredMethod("valueOf", int.class); + + assertEquals(MethodSource.from(m1), MethodSource.from(m2)); + } + + @Test + void twoEqualReflectedMethodsShouldHaveEqualMethodSourceHashCodes() throws Exception { + var m1 = String.class.getDeclaredMethod("valueOf", int.class); + var m2 = String.class.getDeclaredMethod("valueOf", int.class); + + assertEquals(MethodSource.from(m1).hashCode(), MethodSource.from(m2).hashCode()); + } + + @Test + void twoUnequalReflectedMethodsShouldNotHaveEqualMethodSourceObjects() throws Exception { + var m1 = String.class.getDeclaredMethod("valueOf", int.class); + var m2 = Byte.class.getDeclaredMethod("byteValue"); + + assertNotEquals(MethodSource.from(m1), MethodSource.from(m2)); + } + + @Test + void twoUnequalReflectedMethodsShouldNotHaveEqualMethodSourceHashCodes() throws Exception { + var m1 = String.class.getDeclaredMethod("valueOf", int.class); + var m2 = Byte.class.getDeclaredMethod("byteValue"); + + assertNotEquals(MethodSource.from(m1).hashCode(), MethodSource.from(m2).hashCode()); + } + + @Test + void getJavaClassFromString() { + var source = MethodSource.from(getClass().getName(), "method1"); + + assertThat(source.getJavaClass()).isEqualTo(getClass()); + } + + @Test + void getJavaClassShouldThrowExceptionIfClassNotFound() { + var source = MethodSource.from(getClass().getName() + "X", "method1"); + + assertThrows(PreconditionViolationException.class, source::getJavaClass); + } + + @Test + void getJavaMethodShouldReturnGivenMethodIfOverloadExists() throws Exception { + var testMethod = getMethod("method3"); + var source = MethodSource.from(testMethod); + + assertThat(source.getJavaMethod()).isEqualTo(testMethod); + } + + @Test + void getJavaMethodFromStringShouldFindVoidMethod() throws Exception { + var testMethod = getClass().getDeclaredMethod("methodVoid"); + var source = MethodSource.from(getClass().getName(), testMethod.getName()); + + assertThat(source.getJavaMethod()).isEqualTo(testMethod); + } + + @Test + void getJavaMethodFromStringShouldFindMethodWithParameter() throws Exception { + var testMethod = getClass().getDeclaredMethod("method3", Integer.TYPE); + var source = MethodSource.from(getClass().getName(), testMethod.getName(), testMethod.getParameterTypes()); + + assertThat(source.getJavaMethod()).isEqualTo(testMethod); + } + + @Test + void getJavaMethodFromStringShouldThrowExceptionIfParameterTypesAreNotSupplied() { + var source = MethodSource.from(getClass().getName(), "method3"); + + assertThrows(PreconditionViolationException.class, source::getJavaMethod); + } + + @Test + void getJavaMethodFromStringShouldThrowExceptionIfParameterTypesDoNotMatch() { + var source = MethodSource.from(getClass().getName(), "method3", Double.TYPE); + + assertThrows(PreconditionViolationException.class, source::getJavaMethod); + } + + @Test + void getJavaMethodFromStringShouldThrowExceptionIfMethodDoesNotExist() { + var source = MethodSource.from(getClass().getName(), "methodX"); + + assertThrows(PreconditionViolationException.class, source::getJavaMethod); + } + + private Method getMethod(String name) throws Exception { + return getClass().getDeclaredMethod(name, String.class); + } + + @SuppressWarnings("unused") + void method1(String text) { + } + + @SuppressWarnings("unused") + void method2(String text) { + } + + @SuppressWarnings("unused") + void method3(String text) { + } + + @SuppressWarnings("unused") + void method3(int number) { + } + + @SuppressWarnings("unused") + void methodVoid() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java new file mode 100644 index 00000000..b7217e6d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.Serializable; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link PackageSource}. + * + * @since 1.0 + */ +class PackageSourceTests extends AbstractTestSourceTests { + + @Override + Stream createSerializableInstances() { + return Stream.of(PackageSource.from("package.source")); + } + + @Test + void packageSourceFromNullPackageName() { + assertThrows(PreconditionViolationException.class, () -> PackageSource.from((String) null)); + } + + @Test + void packageSourceFromEmptyPackageName() { + assertThrows(PreconditionViolationException.class, () -> PackageSource.from(" ")); + } + + @Test + void packageSourceFromNullPackageReference() { + assertThrows(PreconditionViolationException.class, () -> PackageSource.from((Package) null)); + } + + @Test + void packageSourceFromPackageName() { + var testPackage = getClass().getPackage().getName(); + var source = PackageSource.from(testPackage); + + assertThat(source.getPackageName()).isEqualTo(testPackage); + } + + @Test + void packageSourceFromPackageReference() { + var testPackage = getClass().getPackage(); + var source = PackageSource.from(testPackage); + + assertThat(source.getPackageName()).isEqualTo(testPackage.getName()); + } + + @Test + void equalsAndHashCodeForPackageSource() { + var pkg1 = getClass().getPackage(); + var pkg2 = String.class.getPackage(); + assertEqualsAndHashCode(PackageSource.from(pkg1), PackageSource.from(pkg1), PackageSource.from(pkg2)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java new file mode 100644 index 00000000..8f1ddee3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.Lock; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +/** + * @since 1.3 + */ +class CompositeLockTests { + + @Test + @SuppressWarnings("resource") + void acquiresAllLocksInOrder() throws Exception { + var lock1 = mock(Lock.class); + var lock2 = mock(Lock.class); + + new CompositeLock(List.of(lock1, lock2)).acquire(); + + var inOrder = inOrder(lock1, lock2); + inOrder.verify(lock1).lockInterruptibly(); + inOrder.verify(lock2).lockInterruptibly(); + } + + @Test + @SuppressWarnings("resource") + void releasesAllLocksInReverseOrder() throws Exception { + var lock1 = mock(Lock.class); + var lock2 = mock(Lock.class); + + new CompositeLock(List.of(lock1, lock2)).acquire().close(); + + var inOrder = inOrder(lock1, lock2); + inOrder.verify(lock2).unlock(); + inOrder.verify(lock1).unlock(); + } + + @Test + @SuppressWarnings("resource") + void releasesLocksInReverseOrderWhenInterruptedDuringAcquire() throws Exception { + var firstTwoLocksWereLocked = new CountDownLatch(2); + var firstLock = mockLock("firstLock", firstTwoLocksWereLocked::countDown); + var secondLock = mockLock("secondLock", firstTwoLocksWereLocked::countDown); + var unavailableLock = mockLock("unavailableLock", new CountDownLatch(1)::await); + + var thread = new Thread(() -> { + try { + new CompositeLock(List.of(firstLock, secondLock, unavailableLock)).acquire(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + thread.start(); + firstTwoLocksWereLocked.await(); + thread.interrupt(); + thread.join(); + + var inOrder = inOrder(firstLock, secondLock); + inOrder.verify(secondLock).unlock(); + inOrder.verify(firstLock).unlock(); + verify(unavailableLock, never()).unlock(); + } + + private Lock mockLock(String name, Executable lockAction) throws InterruptedException { + var lock = mock(Lock.class, name); + doAnswer(invocation -> { + lockAction.execute(); + return null; + }).when(lock).lockInterruptibly(); + return lock; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java new file mode 100644 index 00000000..b6cb6422 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java @@ -0,0 +1,201 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * @since 1.3 + */ +class DefaultParallelExecutionConfigurationStrategyTests { + + private ConfigurationParameters configParams = mock(); + + @BeforeEach + void setUp() { + when(configParams.get(any(), any())).thenCallRealMethod(); + } + + @Test + void fixedStrategyCreatesValidConfiguration() { + when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("42")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; + var configuration = strategy.createConfiguration(configParams); + + assertThat(configuration.getParallelism()).isEqualTo(42); + assertThat(configuration.getCorePoolSize()).isEqualTo(42); + assertThat(configuration.getMinimumRunnable()).isEqualTo(42); + assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + 42); + assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); + assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); + } + + @Test + void fixedSaturateStrategyCreatesValidConfiguration() { + when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("42")); + when(configParams.get("fixed.max-pool-size")).thenReturn(Optional.of("42")); + when(configParams.get("fixed.saturate")).thenReturn(Optional.of("false")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; + var configuration = strategy.createConfiguration(configParams); + assertThat(configuration.getParallelism()).isEqualTo(42); + assertThat(configuration.getMaxPoolSize()).isEqualTo(42); + assertThat(configuration.getSaturatePredicate().test(null)).isFalse(); + } + + @Test + void dynamicStrategyCreatesValidConfiguration() { + when(configParams.get("dynamic.factor")).thenReturn(Optional.of("2.0")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; + var configuration = strategy.createConfiguration(configParams); + + var availableProcessors = Runtime.getRuntime().availableProcessors(); + assertThat(configuration.getParallelism()).isEqualTo(availableProcessors * 2); + assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors * 2); + assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2); + assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + (availableProcessors * 2)); + assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); + assertThat(configuration.getSaturatePredicate()).isNull(); + } + + @Test + void customStrategyCreatesValidConfiguration() { + when(configParams.get("custom.class")).thenReturn( + Optional.of(CustomParallelExecutionConfigurationStrategy.class.getName())); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; + var configuration = strategy.createConfiguration(configParams); + + assertThat(configuration.getParallelism()).isEqualTo(1); + assertThat(configuration.getCorePoolSize()).isEqualTo(4); + assertThat(configuration.getMinimumRunnable()).isEqualTo(2); + assertThat(configuration.getMaxPoolSize()).isEqualTo(3); + assertThat(configuration.getKeepAliveSeconds()).isEqualTo(5); + assertThat(configuration.getSaturatePredicate()).isNotNull(); + assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); + } + + @ParameterizedTest + @EnumSource + void createsStrategyFromConfigParam(DefaultParallelExecutionConfigurationStrategy strategy) { + when(configParams.get("strategy")).thenReturn(Optional.of(strategy.name().toLowerCase())); + + assertThat(DefaultParallelExecutionConfigurationStrategy.getStrategy(configParams)).isSameAs(strategy); + } + + @Test + void fixedStrategyThrowsExceptionWhenPropertyIsNotPresent() { + when(configParams.get("fixed.parallelism")).thenReturn(Optional.empty()); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; + assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); + } + + @Test + void fixedStrategyThrowsExceptionWhenPropertyIsNotAnInteger() { + when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("foo")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; + assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); + } + + @Test + void dynamicStrategyUsesDefaultWhenPropertyIsNotPresent() { + when(configParams.get("dynamic.factor")).thenReturn(Optional.empty()); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; + var configuration = strategy.createConfiguration(configParams); + + var availableProcessors = Runtime.getRuntime().availableProcessors(); + assertThat(configuration.getParallelism()).isEqualTo(availableProcessors); + assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors); + assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors); + assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + availableProcessors); + assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); + } + + @Test + void dynamicStrategyThrowsExceptionWhenPropertyIsNotAnInteger() { + when(configParams.get("dynamic.factor")).thenReturn(Optional.of("foo")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; + assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); + } + + @Test + void dynamicStrategyThrowsExceptionWhenFactorIsZero() { + when(configParams.get("dynamic.factor")).thenReturn(Optional.of("0")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; + assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); + } + + @Test + void dynamicStrategyThrowsExceptionWhenFactorIsNegative() { + when(configParams.get("dynamic.factor")).thenReturn(Optional.of("-1")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; + assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); + } + + @Test + void dynamicStrategyUsesAtLeastParallelismOfOneWhenPropertyIsTooSmall() { + when(configParams.get("dynamic.factor")).thenReturn(Optional.of("0.00000000001")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; + var configuration = strategy.createConfiguration(configParams); + + assertThat(configuration.getParallelism()).isEqualTo(1); + assertThat(configuration.getCorePoolSize()).isEqualTo(1); + assertThat(configuration.getMinimumRunnable()).isEqualTo(1); + assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + 1); + assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); + } + + @Test + void customStrategyThrowsExceptionWhenPropertyIsNotPresent() { + when(configParams.get("custom.class")).thenReturn(Optional.empty()); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; + assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); + } + + @Test + void customStrategyThrowsExceptionWhenClassDoesNotExist() { + when(configParams.get("custom.class")).thenReturn(Optional.of("com.acme.ClassDoesNotExist")); + + ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; + assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); + } + + static class CustomParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy { + @Override + public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { + return new DefaultParallelExecutionConfiguration(1, 2, 3, 4, 5, __ -> true); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java new file mode 100644 index 00000000..868386f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.commons.JUnitException; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ForkJoinPoolHierarchicalTestExecutorServiceTests { + + @Mock + ParallelExecutionConfiguration configuration; + + @Test + void exceptionsFromInvalidConfigurationAreNotSwallowed() { + when(configuration.getParallelism()).thenReturn(2); + when(configuration.getMaxPoolSize()).thenReturn(1); // invalid, should be > parallelism + when(configuration.getCorePoolSize()).thenReturn(1); + when(configuration.getMinimumRunnable()).thenReturn(1); + when(configuration.getSaturatePredicate()).thenReturn(__ -> true); + when(configuration.getKeepAliveSeconds()).thenReturn(0); + + JUnitException exception = assertThrows(JUnitException.class, + () -> new ForkJoinPoolHierarchicalTestExecutorService(configuration)); + assertThat(exception).hasMessage("Failed to create ForkJoinPool"); + assertThat(exception).rootCause().isInstanceOf(IllegalArgumentException.class); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java new file mode 100644 index 00000000..80d46102 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java @@ -0,0 +1,753 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; +import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; +import org.junit.platform.launcher.core.ConfigurationParametersFactoryForTests; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; +import org.opentest4j.TestAbortedException; + +/** + * Micro-tests that verify behavior of {@link HierarchicalTestExecutor}. + * + * @since 1.0 + */ +@ExtendWith(MockitoExtension.class) +class HierarchicalTestExecutorTests { + + @Spy + MyContainer root = new MyContainer(UniqueId.root("container", "root")); + + @Mock + EngineExecutionListener listener; + + MyEngineExecutionContext rootContext = new MyEngineExecutionContext(); + HierarchicalTestExecutor executor; + + @BeforeEach + void init() { + executor = createExecutor(new SameThreadHierarchicalTestExecutorService()); + } + + private HierarchicalTestExecutor createExecutor( + HierarchicalTestExecutorService executorService) { + var request = new ExecutionRequest(root, listener, null); + return new HierarchicalTestExecutor<>(request, rootContext, executorService, + OpenTest4JAwareThrowableCollector::new); + } + + @Test + void emptyRootDescriptor() throws Exception { + + var inOrder = inOrder(listener, root); + + executor.execute(); + + var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(rootContext); + inOrder.verify(root).after(rootContext); + inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); + + assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); + } + + @Test + void rootDescriptorWithOneChildContainer() throws Exception { + + var child = spy(new MyContainer(UniqueId.root("container", "child container"))); + root.addChild(child); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(child).prepare(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(child).before(rootContext); + inOrder.verify(child).after(rootContext); + inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); + } + + @Test + void rootDescriptorWithOneChildLeaf() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); + root.addChild(child); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(child).prepare(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(child).execute(eq(rootContext), any()); + inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + assertThat(aTestExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); + } + + @Test + void skippingAContainer() throws Exception { + + var child = spy(new MyContainer(UniqueId.root("container", "child container"))); + when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); + root.addChild(child); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + inOrder.verify(listener).executionStarted(root); + inOrder.verify(child).prepare(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(child).cleanUp(rootContext); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + verify(listener, never()).executionStarted(child); + verify(child, never()).execute(any(), any()); + verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); + } + + @Test + void skippingALeaf() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); + when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); + root.addChild(child); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + inOrder.verify(listener).executionStarted(root); + inOrder.verify(child).prepare(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(child).cleanUp(rootContext); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + verify(listener, never()).executionStarted(child); + verify(child, never()).execute(any(), any()); + verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); + } + + @Test + void exceptionInShouldBeSkipped() throws Exception { + + var child = spy(new MyContainer(UniqueId.root("container", "child container"))); + var anException = new RuntimeException("in skip"); + when(child.shouldBeSkipped(rootContext)).thenThrow(anException); + root.addChild(child); + + var inOrder = inOrder(listener, child); + + executor.execute(); + + var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(child).prepare(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(child).cleanUp(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + verify(child, never()).execute(any(), any()); + + assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anException); + } + + @Test + void exceptionInContainerBeforeAll() throws Exception { + + var child = spy(new MyContainer(UniqueId.root("container", "child container"))); + root.addChild(child); + var anException = new RuntimeException("in test"); + when(root.before(rootContext)).thenThrow(anException); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(rootContext); + inOrder.verify(root).after(rootContext); + inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); + + assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(anException); + + verify(child, never()).execute(any(), any()); + } + + @Test + void exceptionInContainerAfterAllAndCleanUp() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); + root.addChild(child); + var afterException = new RuntimeException("in after()"); + doThrow(afterException).when(root).after(rootContext); + var cleanUpException = new RuntimeException("in cleanUp()"); + doThrow(cleanUpException).when(root).cleanUp(rootContext); + + executor.execute(); + + var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + var inOrder = inOrder(listener, root, child); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(child).execute(eq(rootContext), any()); + inOrder.verify(listener).executionFinished(eq(child), any(TestExecutionResult.class)); + inOrder.verify(root).after(rootContext); + inOrder.verify(root).cleanUp(rootContext); + inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); + inOrder.verifyNoMoreInteractions(); + + assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(afterException); + assertThat(afterException.getSuppressed()).containsExactly(cleanUpException); + } + + @Test + void exceptionInPrepare() throws Exception { + var prepareException = new RuntimeException("in prepare()"); + doThrow(prepareException).when(root).prepare(rootContext); + + executor.execute(); + + var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + var inOrder = inOrder(listener, root); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); + inOrder.verifyNoMoreInteractions(); + + assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(prepareException); + assertThat(prepareException.getSuppressed()).isEmpty(); + } + + @Test + void exceptionInCleanUp() throws Exception { + var cleanUpException = new RuntimeException("in cleanUp()"); + doThrow(cleanUpException).when(root).cleanUp(rootContext); + + executor.execute(); + + var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + var inOrder = inOrder(listener, root); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).execute(eq(rootContext), any()); + inOrder.verify(root).after(rootContext); + inOrder.verify(root).cleanUp(rootContext); + inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); + inOrder.verifyNoMoreInteractions(); + + assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(cleanUpException); + assertThat(cleanUpException.getSuppressed()).isEmpty(); + } + + @Test + void exceptionInShouldBeSkippedAndCleanUp() throws Exception { + var shouldBeSkippedException = new RuntimeException("in prepare()"); + doThrow(shouldBeSkippedException).when(root).shouldBeSkipped(rootContext); + var cleanUpException = new RuntimeException("in cleanUp()"); + doThrow(cleanUpException).when(root).cleanUp(rootContext); + + executor.execute(); + + var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + var inOrder = inOrder(listener, root); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).shouldBeSkipped(rootContext); + inOrder.verify(root).cleanUp(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); + inOrder.verifyNoMoreInteractions(); + + assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(shouldBeSkippedException); + assertThat(shouldBeSkippedException.getSuppressed()).containsExactly(cleanUpException); + } + + @Test + void exceptionInLeafExecute() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); + var anException = new RuntimeException("in test"); + when(child.execute(eq(rootContext), any())).thenThrow(anException); + root.addChild(child); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(child).execute(eq(rootContext), any()); + inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); + inOrder.verify(root).after(rootContext); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anException); + } + + @Test + void abortInRootBeforeAll() throws Exception { + + var child = spy(new MyContainer(UniqueId.root("container", "child container"))); + root.addChild(child); + var anAbortedException = new TestAbortedException("in BeforeAll"); + when(root.before(rootContext)).thenThrow(anAbortedException); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(rootContext); + inOrder.verify(root).after(rootContext); + inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); + + assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); + assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); + + verify(child, never()).execute(any(), any()); + } + + @Test + void abortInChildContainerBeforeAll() throws Exception { + + var child = spy(new MyContainer(UniqueId.root("container", "child container"))); + root.addChild(child); + var anAbortedException = new TestAbortedException("in BeforeAll"); + when(child.before(rootContext)).thenThrow(anAbortedException); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(child).before(rootContext); + inOrder.verify(child).after(rootContext); + inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); + inOrder.verify(root).after(rootContext); + + assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); + assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); + + verify(child, never()).execute(any(), any()); + } + + @Test + void abortInLeafExecute() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); + var anAbortedException = new TestAbortedException("in test"); + when(child.execute(eq(rootContext), any())).thenThrow(anAbortedException); + root.addChild(child); + + var inOrder = inOrder(listener, root, child); + + executor.execute(); + + var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(child).execute(eq(rootContext), any()); + inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); + inOrder.verify(root).after(rootContext); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); + assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); + } + + @Test + void executesDynamicTestDescriptors() throws Exception { + + var leafUniqueId = UniqueId.root("leaf", "child leaf"); + var child = spy(new MyLeaf(leafUniqueId)); + var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); + + when(child.execute(any(), any())).thenAnswer(execute(dynamicTestDescriptor)); + root.addChild(child); + + var inOrder = inOrder(listener, root, child, dynamicTestDescriptor); + + executor.execute(); + + var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(child).prepare(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(child).execute(eq(rootContext), any()); + inOrder.verify(listener).dynamicTestRegistered(dynamicTestDescriptor); + inOrder.verify(dynamicTestDescriptor).prepare(rootContext); + inOrder.verify(dynamicTestDescriptor).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(dynamicTestDescriptor); + inOrder.verify(dynamicTestDescriptor).execute(eq(rootContext), any()); + inOrder.verify(listener).executionFinished(eq(dynamicTestDescriptor), aTestExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + assertThat(aTestExecutionResult.getAllValues()).extracting(TestExecutionResult::getStatus).containsExactly( + SUCCESSFUL, SUCCESSFUL); + } + + @Test + void executesDynamicTestDescriptorsUsingContainerAndTestType() throws Exception { + + var child = spy(new MyContainerAndTestTestCase(root.getUniqueId().append("c&t", "child"))); + var dynamicContainerAndTest = spy( + new MyContainerAndTestTestCase(child.getUniqueId().append("c&t", "dynamicContainerAndTest"))); + var dynamicLeaf = spy(new MyLeaf(dynamicContainerAndTest.getUniqueId().append("test", "dynamicLeaf"))); + + root.addChild(child); + when(child.execute(any(), any())).thenAnswer(execute(dynamicContainerAndTest)); + when(dynamicContainerAndTest.execute(any(), any())).thenAnswer(execute(dynamicLeaf)); + when(dynamicLeaf.execute(any(), any())).thenAnswer(invocation -> { + throw new AssertionError("test fails"); + }); + + var inOrder = inOrder(listener, root, child, dynamicContainerAndTest, dynamicLeaf); + + executor.execute(); + + var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(listener).executionStarted(root); + + inOrder.verify(child).prepare(rootContext); + inOrder.verify(child).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(child); + inOrder.verify(child).execute(eq(rootContext), any()); + + inOrder.verify(listener).dynamicTestRegistered(dynamicContainerAndTest); + inOrder.verify(dynamicContainerAndTest).prepare(rootContext); + inOrder.verify(dynamicContainerAndTest).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(dynamicContainerAndTest); + inOrder.verify(dynamicContainerAndTest).execute(eq(rootContext), any()); + + inOrder.verify(listener).dynamicTestRegistered(dynamicLeaf); + inOrder.verify(dynamicLeaf).prepare(rootContext); + inOrder.verify(dynamicLeaf).shouldBeSkipped(rootContext); + inOrder.verify(listener).executionStarted(dynamicLeaf); + inOrder.verify(dynamicLeaf).execute(eq(rootContext), any()); + + inOrder.verify(listener).executionFinished(eq(dynamicLeaf), aTestExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(dynamicContainerAndTest), aTestExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); + inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); + + assertThat(aTestExecutionResult.getAllValues()).extracting(TestExecutionResult::getStatus).containsExactly( + FAILED, SUCCESSFUL, SUCCESSFUL); + } + + @Test + void executesDynamicTestDescriptorsWithCustomListener() { + + var leafUniqueId = UniqueId.root("leaf", "child leaf"); + var child = spy(new MyLeaf(leafUniqueId)); + var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); + root.addChild(child); + + var anotherListener = mock(EngineExecutionListener.class); + when(child.execute(any(), any())).thenAnswer( + useDynamicTestExecutor(executor -> executor.execute(dynamicTestDescriptor, anotherListener))); + + executor.execute(); + + var inOrder = inOrder(listener, anotherListener, root, child, dynamicTestDescriptor); + inOrder.verify(anotherListener).dynamicTestRegistered(dynamicTestDescriptor); + inOrder.verify(anotherListener).executionStarted(dynamicTestDescriptor); + inOrder.verify(dynamicTestDescriptor).execute(eq(rootContext), any()); + inOrder.verify(dynamicTestDescriptor).nodeFinished(rootContext, dynamicTestDescriptor, successful()); + inOrder.verify(anotherListener).executionFinished(dynamicTestDescriptor, successful()); + } + + @Test + void canAbortExecutionOfDynamicChild() throws Exception { + + var leafUniqueId = UniqueId.root("leaf", "child leaf"); + var child = spy(new MyLeaf(leafUniqueId)); + var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); + root.addChild(child); + + var startedLatch = new CountDownLatch(1); + var interrupted = new CompletableFuture(); + + when(child.execute(any(), any())).thenAnswer(useDynamicTestExecutor(executor -> { + var future = executor.execute(dynamicTestDescriptor, EngineExecutionListener.NOOP); + startedLatch.await(); + future.cancel(true); + executor.awaitFinished(); + })); + when(dynamicTestDescriptor.execute(any(), any())).thenAnswer(invocation -> { + startedLatch.countDown(); + try { + new CountDownLatch(1).await(); // block until interrupted + interrupted.complete(false); + return null; + } + catch (InterruptedException e) { + interrupted.complete(true); + throw e; + } + }); + + var parameters = ConfigurationParametersFactoryForTests.create(Map.of(// + DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME, "fixed", // + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, "2")); + + try (var executorService = new ForkJoinPoolHierarchicalTestExecutorService(parameters)) { + createExecutor(executorService).execute().get(); + } + + verify(listener).executionFinished(child, successful()); + assertTrue(interrupted.get(), "dynamic node was interrupted"); + } + + private Answer execute(TestDescriptor dynamicChild) { + return useDynamicTestExecutor(executor -> executor.execute(dynamicChild)); + } + + private Answer useDynamicTestExecutor(ThrowingConsumer action) { + return invocation -> { + DynamicTestExecutor dynamicTestExecutor = invocation.getArgument(1); + action.accept(dynamicTestExecutor); + return invocation.getArgument(0); + }; + } + + /** + * Verifies support for unrecoverable exceptions. + */ + @Test + void outOfMemoryErrorInShouldBeSkipped() throws Exception { + var child = spy(new MyContainer(UniqueId.root("container", "child container"))); + var outOfMemoryError = new OutOfMemoryError("in skip"); + when(child.shouldBeSkipped(rootContext)).thenThrow(outOfMemoryError); + root.addChild(child); + + Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); + assertSame(outOfMemoryError, actualException); + } + + /** + * Verifies support for unrecoverable exceptions. + */ + @Test + void outOfMemoryErrorInLeafExecution() { + var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); + var outOfMemoryError = new OutOfMemoryError("in test"); + when(child.execute(eq(rootContext), any())).thenThrow(outOfMemoryError); + root.addChild(child); + + Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); + assertSame(outOfMemoryError, actualException); + } + + @Test + void exceptionInAfterDoesNotHideEarlierException() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); + Exception exceptionInExecute = new RuntimeException("execute"); + Exception exceptionInAfter = new RuntimeException("after"); + doThrow(exceptionInExecute).when(child).execute(eq(rootContext), any()); + doThrow(exceptionInAfter).when(child).after(eq(rootContext)); + root.addChild(child); + + var inOrder = inOrder(listener, child); + + executor.execute(); + + var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(child).execute(eq(rootContext), any()); + inOrder.verify(child).after(eq(rootContext)); + inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); + + assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(childExecutionResult.getValue().getThrowable().get()).isSameAs( + exceptionInExecute).hasSuppressedException(exceptionInAfter); + } + + @Test + void dynamicTestDescriptorsMustNotDeclareExclusiveResources() { + + var leafUniqueId = UniqueId.root("leaf", "child leaf"); + var child = spy(new MyLeaf(leafUniqueId)); + var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); + when(dynamicTestDescriptor.getExclusiveResources()).thenReturn( + Set.of(new ExclusiveResource("foo", LockMode.READ))); + + when(child.execute(any(), any())).thenAnswer(execute(dynamicTestDescriptor)); + root.addChild(child); + + executor.execute(); + + var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + verify(listener).executionStarted(dynamicTestDescriptor); + verify(listener).executionFinished(eq(dynamicTestDescriptor), aTestExecutionResult.capture()); + + var executionResult = aTestExecutionResult.getValue(); + assertThat(executionResult.getStatus()).isEqualTo(FAILED); + assertThat(executionResult.getThrowable()).isPresent(); + assertThat(executionResult.getThrowable().get()).hasMessageContaining( + "Dynamic test descriptors must not declare exclusive resources"); + } + + @Test + void exceptionInAfterIsReportedInsteadOfEarlierTestAbortedException() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); + Exception exceptionInExecute = new TestAbortedException("execute"); + Exception exceptionInAfter = new RuntimeException("after"); + doThrow(exceptionInExecute).when(child).execute(eq(rootContext), any()); + doThrow(exceptionInAfter).when(child).after(eq(rootContext)); + root.addChild(child); + + var inOrder = inOrder(listener, child); + + executor.execute(); + + var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + inOrder.verify(child).execute(eq(rootContext), any()); + inOrder.verify(child).after(eq(rootContext)); + inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); + + assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); + assertThat(childExecutionResult.getValue().getThrowable().get()).isSameAs( + exceptionInAfter).hasSuppressedException(exceptionInExecute); + } + + // ------------------------------------------------------------------- + + private static class MyEngineExecutionContext implements EngineExecutionContext { + } + + private static class MyContainer extends AbstractTestDescriptor implements Node { + + MyContainer(UniqueId uniqueId) { + super(uniqueId, uniqueId.toString()); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + } + + private static class MyLeaf extends AbstractTestDescriptor implements Node { + + MyLeaf(UniqueId uniqueId) { + super(uniqueId, uniqueId.toString()); + } + + @Override + public MyEngineExecutionContext execute(MyEngineExecutionContext context, + DynamicTestExecutor dynamicTestExecutor) { + return context; + } + + @Override + public Type getType() { + return Type.TEST; + } + } + + private static class MyContainerAndTestTestCase extends AbstractTestDescriptor + implements Node { + + MyContainerAndTestTestCase(UniqueId uniqueId) { + super(uniqueId, uniqueId.toString()); + } + + @Override + public Type getType() { + return Type.CONTAINER_AND_TEST; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java new file mode 100644 index 00000000..342978f0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; + +/** + * @since 1.3 + */ +class LockManagerTests { + + private LockManager lockManager = new LockManager(); + + @Test + void returnsNopLockWithoutExclusiveResources() { + Collection resources = Set.of(); + + var locks = getLocks(resources, NopLock.class); + + assertThat(locks).isEmpty(); + } + + @Test + void returnsSingleLockForSingleExclusiveResource() { + Collection resources = Set.of(new ExclusiveResource("foo", READ)); + + var locks = getLocks(resources, SingleLock.class); + + assertThat(locks).hasSize(1); + assertThat(locks.get(0)).isInstanceOf(ReadLock.class); + } + + @Test + void returnsCompositeLockForMultipleDifferentExclusiveResources() { + Collection resources = List.of( // + new ExclusiveResource("a", READ), // + new ExclusiveResource("b", READ_WRITE)); + + var locks = getLocks(resources, CompositeLock.class); + + assertThat(locks).hasSize(2); + assertThat(locks.get(0)).isInstanceOf(ReadLock.class); + assertThat(locks.get(1)).isInstanceOf(WriteLock.class); + } + + @Test + void reusesSameLockForExclusiveResourceWithSameKey() { + Collection resources = Set.of(new ExclusiveResource("foo", READ)); + + var locks1 = getLocks(resources, SingleLock.class); + var locks2 = getLocks(resources, SingleLock.class); + + assertThat(locks1).hasSize(1); + assertThat(locks2).hasSize(1); + assertThat(locks1.get(0)).isSameAs(locks2.get(0)); + } + + @Test + void returnsWriteLockForExclusiveResourceWithBothLockModes() { + Collection resources = List.of( // + new ExclusiveResource("bar", READ), // + new ExclusiveResource("foo", READ), // + new ExclusiveResource("foo", READ_WRITE), // + new ExclusiveResource("bar", READ_WRITE)); + + var locks = getLocks(resources, CompositeLock.class); + + assertThat(locks).hasSize(2); + assertThat(locks.get(0)).isInstanceOf(WriteLock.class); + assertThat(locks.get(1)).isInstanceOf(WriteLock.class); + } + + @ParameterizedTest + @EnumSource + void globalLockComesFirst(LockMode globalLockMode) { + Collection resources = List.of( // + new ExclusiveResource("___foo", READ), // + new ExclusiveResource("foo", READ_WRITE), // + new ExclusiveResource(GLOBAL_KEY, globalLockMode), // + new ExclusiveResource("bar", READ_WRITE)); + + var locks = getLocks(resources, CompositeLock.class); + + assertThat(locks).hasSize(4); + assertThat(locks.get(0)).isEqualTo(getSingleLock(GLOBAL_KEY, globalLockMode)); + assertThat(locks.get(1)).isEqualTo(getSingleLock("___foo", READ)); + assertThat(locks.get(2)).isEqualTo(getSingleLock("bar", READ_WRITE)); + assertThat(locks.get(3)).isEqualTo(getSingleLock("foo", READ_WRITE)); + } + + private Lock getSingleLock(String globalResourceLockKey, LockMode read) { + return getLocks(Set.of(new ExclusiveResource(globalResourceLockKey, read)), SingleLock.class).get(0); + } + + private List getLocks(Collection resources, Class type) { + var lock = lockManager.getLockForResources(resources); + assertThat(lock).isInstanceOf(type); + return ResourceLockSupport.getLocks(lock); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java new file mode 100644 index 00000000..ef706b91 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +/** + * Integration tests intended to verify that memory leaks do not + * exist with regard to the "context" held by {@link NodeTestTask}. + * + * @since 5.3.1 + * @see GitHub issue #1578 + */ +// Explicitly specifying Lifecycle.PER_METHOD to be certain that the +// test instance state is recreated for every test method executed. +@TestInstance(Lifecycle.PER_METHOD) +class MemoryLeakTests { + + // Allocate 500 MB of memory per test method. + // + // If the test instance is garbage collected, this should not cause any + // problems for the JUnit 5 build; however, if the instances of this test + // class are NOT garbage collected, we should run out of memory pretty + // quickly since the instances of this test class would consume 5GB of + // heap space. + final byte[] state = new byte[524_288_000]; + + @Test + void test01() { + } + + @Test + void test02() { + } + + @Test + void test03() { + } + + @Test + void test04() { + } + + @Test + void test05() { + } + + @Test + void test06() { + } + + @Test + void test07() { + } + + @Test + void test08() { + } + + @Test + void test09() { + } + + @Test + void test10() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java new file mode 100644 index 00000000..e7813801 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java @@ -0,0 +1,257 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE; +import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.function.Function; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; + +/** + * @since 1.3 + */ +class NodeTreeWalkerIntegrationTests { + + LockManager lockManager = new LockManager(); + NodeTreeWalker nodeTreeWalker = new NodeTreeWalker(lockManager); + + @Test + void pullUpExclusiveChildResourcesToTestClass() { + var engineDescriptor = discover(TestCaseWithResourceLock.class); + + var advisor = nodeTreeWalker.walk(engineDescriptor); + + var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"), getReadWriteLock("b"))); + assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); + assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); + } + + @Test + void setsForceExecutionModeForChildrenWithWriteLocksOnClass() { + var engineDescriptor = discover(TestCaseWithResourceWriteLockOnClass.class); + + var advisor = nodeTreeWalker.walk(engineDescriptor); + + var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"))); + assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); + assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); + } + + @Test + void doesntSetForceExecutionModeForChildrenWithReadLocksOnClass() { + var engineDescriptor = discover(TestCaseWithResourceReadLockOnClass.class); + + var advisor = nodeTreeWalker.walk(engineDescriptor); + + var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a"))); + assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); + assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); + } + + @Test + void setsForceExecutionModeForChildrenWithReadLocksOnClassAndWriteLockOnTest() { + var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndWriteClockOnTestCase.class); + + var advisor = nodeTreeWalker.walk(engineDescriptor); + + var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"))); + assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); + assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); + } + + @Test + void doesntSetForceExecutionModeForChildrenWithReadLocksOnClassAndReadLockOnTest() { + var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase.class); + + var advisor = nodeTreeWalker.walk(engineDescriptor); + + var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a"), getReadLock("b"))); + assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); + + var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); + assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); + } + + @Test + void leavesResourceLockOnTestMethodWhenClassDoesNotUseResource() { + var engineDescriptor = discover(TestCaseWithoutResourceLock.class); + + var advisor = nodeTreeWalker.walk(engineDescriptor); + + var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ))); + assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); + + assertThat(testClassDescriptor.getChildren()).hasSize(2); + var children = testClassDescriptor.getChildren().iterator(); + var testMethodDescriptor = children.next(); + assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getReadWriteLock("a"))); + assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); + + var nestedTestClassDescriptor = children.next(); + assertThat(advisor.getResourceLock(nestedTestClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getReadWriteLock("b"), getReadWriteLock("c"))); + assertThat(advisor.getForcedExecutionMode(nestedTestClassDescriptor)).isEmpty(); + + var nestedTestMethodDescriptor = getOnlyElement(nestedTestClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(nestedTestMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); + assertThat(advisor.getForcedExecutionMode(nestedTestMethodDescriptor)).contains(SAME_THREAD); + } + + @Test + void coarsensGlobalLockToEngineDescriptorChild() { + var engineDescriptor = discover(TestCaseWithGlobalLockRequiringChild.class); + + var advisor = nodeTreeWalker.walk(engineDescriptor); + + var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ_WRITE))); + assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); + + var nestedTestClassDescriptor = getOnlyElement(testClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(nestedTestClassDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ))); + assertThat(advisor.getForcedExecutionMode(nestedTestClassDescriptor)).contains(SAME_THREAD); + + var testMethodDescriptor = getOnlyElement(nestedTestClassDescriptor.getChildren()); + assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()) // + .isEqualTo(List.of(getLock(GLOBAL_READ_WRITE))); + assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); + } + + private static Function> allLocks() { + return ResourceLockSupport::getLocks; + } + + private Lock getReadWriteLock(String key) { + return getLock(new ExclusiveResource(key, READ_WRITE)); + } + + private Lock getReadLock(String key) { + return getLock(new ExclusiveResource(key, READ)); + } + + private Lock getLock(ExclusiveResource exclusiveResource) { + return getOnlyElement(ResourceLockSupport.getLocks(lockManager.getLockForResource(exclusiveResource))); + } + + private TestDescriptor discover(Class testClass) { + var discoveryRequest = request().selectors(selectClass(testClass)).build(); + return new JupiterTestEngine().discover(discoveryRequest, UniqueId.forEngine("junit-jupiter")); + } + + @ResourceLock("a") + static class TestCaseWithResourceLock { + @Test + @ResourceLock("b") + void test() { + } + } + + static class TestCaseWithoutResourceLock { + @Test + @ResourceLock("a") + void test() { + } + + @Nested + @ResourceLock("c") + class NestedTestCaseWithResourceLock { + @Test + @ResourceLock("b") + void test() { + } + } + } + + static class TestCaseWithGlobalLockRequiringChild { + @Nested + class NestedTestCaseWithResourceLock { + @Test + @ResourceLock(ExclusiveResource.GLOBAL_KEY) + void test() { + } + } + } + + @ResourceLock("a") + static class TestCaseWithResourceWriteLockOnClass { + @Test + void test() { + } + } + + @ResourceLock(value = "a", mode = ResourceAccessMode.READ) + static class TestCaseWithResourceReadLockOnClass { + @Test + void test() { + } + } + + @ResourceLock(value = "a", mode = ResourceAccessMode.READ) + static class TestCaseWithResourceReadLockOnClassAndWriteClockOnTestCase { + @Test + @ResourceLock("a") + void test() { + } + } + + @ResourceLock(value = "a", mode = ResourceAccessMode.READ) + static class TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase { + @Test + @ResourceLock(value = "b", mode = ResourceAccessMode.READ) + void test() { + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java new file mode 100644 index 00000000..ca743337 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java @@ -0,0 +1,845 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +import static org.junit.jupiter.engine.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.DEFAULT_PARALLEL_EXECUTION_MODE; +import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.EventConditions.type; +import static org.junit.platform.testkit.engine.EventType.REPORTING_ENTRY_PUBLISHED; + +import java.net.URL; +import java.net.URLClassLoader; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.MethodOrderer.MethodName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Event; + +/** + * @since 1.3 + */ +class ParallelExecutionIntegrationTests { + + @Test + void successfulParallelTest(TestReporter reporter) { + var events = executeConcurrently(3, SuccessfulParallelTestCase.class); + + var startedTimestamps = getTimestampsFor(events, event(test(), started())); + var finishedTimestamps = getTimestampsFor(events, event(test(), finishedSuccessfully())); + reporter.publishEntry("startedTimestamps", startedTimestamps.toString()); + reporter.publishEntry("finishedTimestamps", finishedTimestamps.toString()); + + assertThat(startedTimestamps).hasSize(3); + assertThat(finishedTimestamps).hasSize(3); + assertThat(startedTimestamps).allMatch(startTimestamp -> finishedTimestamps.stream().noneMatch( + finishedTimestamp -> finishedTimestamp.isBefore(startTimestamp))); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); + } + + @Test + void failingTestWithoutLock() { + var events = executeConcurrently(3, FailingWithoutLockTestCase.class); + assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).hasSize(2); + } + + @Test + void successfulTestWithMethodLock() { + var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); + } + + @Test + void successfulTestWithClassLock() { + var events = executeConcurrently(3, SuccessfulWithClassLockTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + } + + @Test + void testCaseWithFactory() { + var events = executeConcurrently(3, TestCaseWithTestFactory.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + } + + @Test + void customContextClassLoader() { + var currentThread = Thread.currentThread(); + var currentLoader = currentThread.getContextClassLoader(); + var smilingLoader = new URLClassLoader("(-:", new URL[0], ClassLoader.getSystemClassLoader()); + currentThread.setContextClassLoader(smilingLoader); + try { + var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); + assertThat(ThreadReporter.getLoaderNames(events)).containsExactly("(-:"); + } + finally { + currentThread.setContextClassLoader(currentLoader); + } + } + + @RepeatedTest(10) + void mixingClassAndMethodLevelLocks() { + var events = executeConcurrently(4, TestCaseWithSortedLocks.class, TestCaseWithUnsortedLocks.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); + assertThat(ThreadReporter.getThreadNames(events).count()).isLessThanOrEqualTo(2); + } + + @RepeatedTest(10) + void locksOnNestedTests() { + var events = executeConcurrently(3, TestCaseWithNestedLocks.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + } + + @Test + void afterHooksAreCalledAfterConcurrentDynamicTestsAreFinished() { + var events = executeConcurrently(3, ConcurrentDynamicTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(1); + var timestampedEvents = ConcurrentDynamicTestCase.events; + assertThat(timestampedEvents.get("afterEach")).isAfterOrEqualTo(timestampedEvents.get("dynamicTestFinished")); + } + + /** + * @since 1.4 + * @see gh-1688 + */ + @Test + void threadInterruptedByUserCode() { + var events = executeConcurrently(3, InterruptedThreadTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(4); + } + + @Test + void executesTestTemplatesWithResourceLocksInSameThread() { + var events = executeConcurrently(2, ConcurrentTemplateTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(10); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + } + + @Test + void executesClassesInParallelIfEnabledViaConfigurationParameter() { + ParallelClassesTestCase.GLOBAL_BARRIER.reset(); + + var configParams = Map.of(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "concurrent"); + var results = executeWithFixedParallelism(3, configParams, ParallelClassesTestCaseA.class, + ParallelClassesTestCaseB.class, ParallelClassesTestCaseC.class); + + results.testEvents().assertStatistics(stats -> stats.succeeded(9)); + assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSize(3); + var testClassA = findFirstTestDescriptor(results, container(ParallelClassesTestCaseA.class)); + assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(1); + var testClassB = findFirstTestDescriptor(results, container(ParallelClassesTestCaseB.class)); + assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(1); + var testClassC = findFirstTestDescriptor(results, container(ParallelClassesTestCaseC.class)); + assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(1); + } + + @Test + void executesMethodsInParallelIfEnabledViaConfigurationParameter() { + ParallelMethodsTestCase.barriersPerClass.clear(); + + var configParams = Map.of( // + DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent", // + DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "same_thread"); + var results = executeWithFixedParallelism(3, configParams, ParallelMethodsTestCaseA.class, + ParallelMethodsTestCaseB.class, ParallelMethodsTestCaseC.class); + + results.testEvents().assertStatistics(stats -> stats.succeeded(9)); + assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSizeGreaterThanOrEqualTo(3); + var testClassA = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseA.class)); + assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(3); + var testClassB = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseB.class)); + assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(3); + var testClassC = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseC.class)); + assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(3); + } + + @Test + void canRunTestsIsolatedFromEachOther() { + var events = executeConcurrently(2, IsolatedTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + } + + @Test + void canRunTestsIsolatedFromEachOtherWithNestedCases() { + var events = executeConcurrently(4, NestedIsolatedTestCase.class); + + assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + } + + @Test + void canRunTestsIsolatedFromEachOtherAcrossClasses() { + var events = executeConcurrently(4, IndependentClasses.A.class, IndependentClasses.B.class); + + assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + } + + @RepeatedTest(10) + void canRunTestsIsolatedFromEachOtherAcrossClassesWithOtherResourceLocks() { + var events = executeConcurrently(4, IndependentClasses.B.class, IndependentClasses.C.class); + + assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + } + + @Isolated("testing") + static class IsolatedTestCase { + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(2); + } + + @Test + void a() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void b() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + } + + static class NestedIsolatedTestCase { + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(6); + } + + @Test + void a() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void b() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + + @Nested + class Inner { + + @Test + void a() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void b() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + + @Nested + @Isolated + class InnerInner { + + @Test + void a() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void b() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + } + } + } + + static class IndependentClasses { + static AtomicInteger sharedResource = new AtomicInteger(); + static CountDownLatch countDownLatch = new CountDownLatch(4); + + static class A { + @Test + void a() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void b() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + } + + @Isolated + static class B { + @Test + void a() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void b() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + } + + @ResourceLock("other") + static class C { + @Test + void a() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void b() throws Exception { + storeAndBlockAndCheck(sharedResource, countDownLatch); + } + } + } + + private List getEventsOfChildren(EngineExecutionResults results, TestDescriptor container) { + return results.testEvents().filter( + event -> event.getTestDescriptor().getParent().orElseThrow().equals(container)).collect(toList()); + } + + private TestDescriptor findFirstTestDescriptor(EngineExecutionResults results, Condition condition) { + return results.allEvents().filter(condition::matches).map(Event::getTestDescriptor).findFirst().orElseThrow(); + } + + private List getTimestampsFor(List events, Condition condition) { + // @formatter:off + return events.stream() + .filter(condition::matches) + .map(Event::getTimestamp) + .collect(toList()); + // @formatter:on + } + + private List executeConcurrently(int parallelism, Class... testClasses) { + return executeWithFixedParallelism(parallelism, Map.of(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent"), + testClasses).allEvents().list(); + } + + private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map configParams, + Class... testClasses) { + // @formatter:off + var discoveryRequest = request() + .selectors(Arrays.stream(testClasses).map(DiscoverySelectors::selectClass).collect(toList())) + .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, String.valueOf(true)) + .configurationParameter(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") + .configurationParameter(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, String.valueOf(parallelism)) + .configurationParameters(configParams) + .build(); + // @formatter:on + return EngineTestKit.execute("junit-jupiter", discoveryRequest); + } + + // ------------------------------------------------------------------------- + + @ExtendWith(ThreadReporter.class) + static class SuccessfulParallelTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(3); + } + + @Test + void firstTest() throws Exception { + incrementAndBlock(sharedResource, countDownLatch); + } + + @Test + void secondTest() throws Exception { + incrementAndBlock(sharedResource, countDownLatch); + } + + @Test + void thirdTest() throws Exception { + incrementAndBlock(sharedResource, countDownLatch); + } + } + + @ExtendWith(ThreadReporter.class) + static class FailingWithoutLockTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(3); + } + + @Test + void firstTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void secondTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void thirdTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + + @ExtendWith(ThreadReporter.class) + static class SuccessfulWithMethodLockTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(3); + } + + @Test + @ResourceLock("sharedResource") + void firstTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + @ResourceLock("sharedResource") + void secondTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + @ResourceLock("sharedResource") + void thirdTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + + @ExtendWith(ThreadReporter.class) + @ResourceLock("sharedResource") + static class SuccessfulWithClassLockTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(3); + } + + @Test + void firstTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void secondTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + void thirdTest() throws Exception { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + + static class TestCaseWithTestFactory { + @TestFactory + @Execution(SAME_THREAD) + Stream testFactory(TestReporter testReporter) { + var sharedResource = new AtomicInteger(0); + var countDownLatch = new CountDownLatch(3); + return IntStream.range(0, 3).mapToObj(i -> dynamicTest("test " + i, () -> { + incrementBlockAndCheck(sharedResource, countDownLatch); + testReporter.publishEntry("thread", Thread.currentThread().getName()); + })); + } + } + + private static final ReentrantLock A = new ReentrantLock(); + private static final ReentrantLock B = new ReentrantLock(); + + @ExtendWith(ThreadReporter.class) + @ResourceLock("A") + static class TestCaseWithSortedLocks { + @ResourceLock("B") + @Test + void firstTest() { + assertTrue(A.tryLock()); + assertTrue(B.tryLock()); + } + + @Execution(CONCURRENT) + @ResourceLock("B") + @Test + void secondTest() { + assertTrue(A.tryLock()); + assertTrue(B.tryLock()); + } + + @ResourceLock("B") + @Test + void thirdTest() { + assertTrue(A.tryLock()); + assertTrue(B.tryLock()); + } + + @AfterEach + void unlock() { + B.unlock(); + A.unlock(); + } + } + + @ExtendWith(ThreadReporter.class) + @ResourceLock("B") + static class TestCaseWithUnsortedLocks { + @ResourceLock("A") + @Test + void firstTest() { + assertTrue(B.tryLock()); + assertTrue(A.tryLock()); + } + + @Execution(CONCURRENT) + @ResourceLock("A") + @Test + void secondTest() { + assertTrue(B.tryLock()); + assertTrue(A.tryLock()); + } + + @ResourceLock("A") + @Test + void thirdTest() { + assertTrue(B.tryLock()); + assertTrue(A.tryLock()); + } + + @AfterEach + void unlock() { + A.unlock(); + B.unlock(); + } + } + + @ExtendWith(ThreadReporter.class) + @ResourceLock("A") + static class TestCaseWithNestedLocks { + + @ResourceLock("B") + @Test + void firstTest() { + assertTrue(A.tryLock()); + assertTrue(B.tryLock()); + } + + @Execution(CONCURRENT) + @ResourceLock("B") + @Test + void secondTest() { + assertTrue(A.tryLock()); + assertTrue(B.tryLock()); + } + + @Test + void thirdTest() { + assertTrue(A.tryLock()); + assertTrue(B.tryLock()); + } + + @AfterEach + void unlock() { + A.unlock(); + B.unlock(); + } + + @Nested + @ResourceLock("B") + class B { + + @ResourceLock("A") + @Test + void firstTest() { + assertTrue(B.tryLock()); + assertTrue(A.tryLock()); + } + + @ResourceLock("A") + @Test + void secondTest() { + assertTrue(B.tryLock()); + assertTrue(A.tryLock()); + } + + @Test + void thirdTest() { + assertTrue(B.tryLock()); + assertTrue(A.tryLock()); + } + } + } + + @Execution(CONCURRENT) + static class ConcurrentDynamicTestCase { + static Map events; + + @BeforeAll + static void beforeAll() { + events = new ConcurrentHashMap<>(); + } + + @AfterEach + void afterEach() { + events.put("afterEach", Instant.now()); + } + + @TestFactory + DynamicTest testFactory() { + return dynamicTest("slow", () -> { + Thread.sleep(100); + events.put("dynamicTestFinished", Instant.now()); + }); + } + } + + @TestMethodOrder(MethodName.class) + static class InterruptedThreadTestCase { + + @Test + void test1() { + Thread.currentThread().interrupt(); + } + + @Test + void test2() throws InterruptedException { + Thread.sleep(10); + } + + @Test + void test3() { + Thread.currentThread().interrupt(); + } + + @Test + void test4() throws InterruptedException { + Thread.sleep(10); + } + + } + + @Execution(CONCURRENT) + @ExtendWith(ThreadReporter.class) + static class ConcurrentTemplateTestCase { + @RepeatedTest(10) + @ResourceLock("a") + void repeatedTest() throws Exception { + Thread.sleep(100); + } + } + + @ExtendWith(ThreadReporter.class) + static abstract class BarrierTestCase { + + @Test + void test1() throws Exception { + getBarrier().await(); + } + + @Test + void test2() throws Exception { + getBarrier().await(); + } + + @Test + void test3() throws Exception { + getBarrier().await(); + } + + abstract CyclicBarrier getBarrier(); + + } + + static class ParallelMethodsTestCase extends BarrierTestCase { + + static final Map, CyclicBarrier> barriersPerClass = new ConcurrentHashMap<>(); + + @Override + CyclicBarrier getBarrier() { + return barriersPerClass.computeIfAbsent(this.getClass(), key -> new CyclicBarrier(3)); + } + } + + static class ParallelClassesTestCase extends BarrierTestCase { + + static final CyclicBarrier GLOBAL_BARRIER = new CyclicBarrier(3); + + @Override + CyclicBarrier getBarrier() { + return GLOBAL_BARRIER; + } + + } + + static class ParallelClassesTestCaseA extends ParallelClassesTestCase { + } + + static class ParallelClassesTestCaseB extends ParallelClassesTestCase { + } + + static class ParallelClassesTestCaseC extends ParallelClassesTestCase { + } + + static class ParallelMethodsTestCaseA extends ParallelMethodsTestCase { + } + + static class ParallelMethodsTestCaseB extends ParallelMethodsTestCase { + } + + static class ParallelMethodsTestCaseC extends ParallelMethodsTestCase { + } + + private static void incrementBlockAndCheck(AtomicInteger sharedResource, CountDownLatch countDownLatch) + throws InterruptedException { + var value = incrementAndBlock(sharedResource, countDownLatch); + assertEquals(value, sharedResource.get()); + } + + private static int incrementAndBlock(AtomicInteger sharedResource, CountDownLatch countDownLatch) + throws InterruptedException { + var value = sharedResource.incrementAndGet(); + countDownLatch.countDown(); + countDownLatch.await(estimateSimulatedTestDurationInMiliseconds(), MILLISECONDS); + return value; + } + + private static void storeAndBlockAndCheck(AtomicInteger sharedResource, CountDownLatch countDownLatch) + throws InterruptedException { + var value = sharedResource.get(); + countDownLatch.countDown(); + countDownLatch.await(estimateSimulatedTestDurationInMiliseconds(), MILLISECONDS); + assertEquals(value, sharedResource.get()); + } + + /** + * To simulate tests running in parallel tests will modify a shared + * resource, simulate work by waiting, then check if the shared resource was + * not modified by any other thread. + * + * Depending on system performance the simulation of work needs to be longer + * on slower systems to ensure tests can run in parallel. + * + * Currently CI is known to be slow. + */ + private static long estimateSimulatedTestDurationInMiliseconds() { + var runningInCi = Boolean.valueOf(System.getenv("CI")); + return runningInCi ? 1000 : 100; + } + + static class ThreadReporter implements AfterTestExecutionCallback { + + private static Stream getLoaderNames(List events) { + return getValues(events, "loader"); + } + + private static Stream getThreadNames(List events) { + return getValues(events, "thread"); + } + + private static Stream getValues(List events, String key) { + // @formatter:off + return events.stream() + .filter(type(REPORTING_ENTRY_PUBLISHED)::matches) + .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) + .map(ReportEntry::getKeyValuePairs) + .filter(keyValuePairs -> keyValuePairs.containsKey(key)) + .map(keyValuePairs -> keyValuePairs.get(key)) + .distinct(); + // @formatter:on + } + + @Override + public void afterTestExecution(ExtensionContext context) { + context.publishReportEntry("thread", Thread.currentThread().getName()); + context.publishReportEntry("loader", Thread.currentThread().getContextClassLoader().getName()); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java new file mode 100644 index 00000000..59f02ffa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import java.util.List; +import java.util.concurrent.locks.Lock; + +class ResourceLockSupport { + + static List getLocks(ResourceLock resourceLock) { + if (resourceLock instanceof NopLock) { + return List.of(); + } + if (resourceLock instanceof SingleLock) { + return List.of(((SingleLock) resourceLock).getLock()); + } + return ((CompositeLock) resourceLock).getLocks(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java new file mode 100644 index 00000000..9a17de1a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.MethodOrderer.MethodName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.testkit.engine.EngineTestKit; + +/** + * @since 1.4 + */ +class SameThreadExecutionIntegrationTests { + + /** + * @see gh-1688 + */ + @Test + @TrackLogRecords + void threadInterruptedByUserCode(LogRecordListener listener) { + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(InterruptedThreadTestCase.class))// + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.succeeded(4)); + + assertThat(firstDebugLogRecord(listener).getMessage()).matches( + "Execution of TestDescriptor with display name .+test1.+ and " + + "unique ID .+ failed to clear the 'interrupted status' flag " + + "for the current thread. JUnit has cleared the flag, but you " + + "may wish to investigate why the flag was not cleared by user code."); + } + + private LogRecord firstDebugLogRecord(LogRecordListener listener) throws AssertionError { + return listener.stream(NodeTestTask.class, Level.FINE).findFirst().orElseThrow( + () -> new AssertionError("Failed to find debug log record")); + } + + // ------------------------------------------------------------------------- + + @TestMethodOrder(MethodName.class) + static class InterruptedThreadTestCase { + + @Test + void test1() { + Thread.currentThread().interrupt(); + } + + @Test + void test2() throws InterruptedException { + Thread.sleep(10); + } + + @Test + void test3() { + Thread.currentThread().interrupt(); + } + + @Test + void test4() throws InterruptedException { + Thread.sleep(10); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java new file mode 100644 index 00000000..382a82fc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.locks.ReentrantLock; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.3 + */ +class SingleLockTests { + + @Test + @SuppressWarnings("resource") + void acquire() throws Exception { + var lock = new ReentrantLock(); + + new SingleLock(lock).acquire(); + + assertTrue(lock.isLocked()); + } + + @Test + @SuppressWarnings("resource") + void release() throws Exception { + var lock = new ReentrantLock(); + + new SingleLock(lock).acquire().close(); + + assertFalse(lock.isLocked()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java new file mode 100644 index 00000000..2160099a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; + +/** + * @since 1.0 + */ +@SuppressWarnings("deprecation") +class SingleTestExecutorTests { + + @Test + void executeSafelySuccessful() { + var result = new SingleTestExecutor().executeSafely(() -> { + }); + + assertEquals(SUCCESSFUL, result.getStatus()); + assertEquals(Optional.empty(), result.getThrowable()); + } + + @Test + void executeSafelyAborted() { + var testAbortedException = new TestAbortedException("assumption violated"); + + var result = new SingleTestExecutor().executeSafely(() -> { + throw testAbortedException; + }); + + assertEquals(ABORTED, result.getStatus()); + assertSame(testAbortedException, result.getThrowable().get()); + } + + @Test + void executeSafelyFailed() { + var assertionError = new AssertionError("assumption violated"); + + var result = new SingleTestExecutor().executeSafely(() -> { + throw assertionError; + }); + + assertEquals(FAILED, result.getStatus()); + assertSame(assertionError, result.getThrowable().get()); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java new file mode 100644 index 00000000..868e4804 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; +import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.6 + */ +class ThrowableCollectorTests { + + @Test + void successfulExecution() { + var collector = new ThrowableCollector(x -> true); + collector.execute(() -> { + }); + var result = collector.toTestExecutionResult(); + + assertEquals(SUCCESSFUL, result.getStatus()); + assertEquals(Optional.empty(), result.getThrowable()); + } + + @Test + void abortedExecution() { + var customAbort = new CustomAbort(); + + var collector = new ThrowableCollector(CustomAbort.class::isInstance); + collector.execute(() -> { + throw customAbort; + }); + var result = collector.toTestExecutionResult(); + + assertEquals(ABORTED, result.getStatus()); + assertSame(customAbort, result.getThrowable().get()); + } + + @Test + void failedExecution() { + var assertionError = new AssertionError("assertion violated"); + + var collector = new ThrowableCollector(CustomAbort.class::isInstance); + collector.execute(() -> { + throw assertionError; + }); + var result = collector.toTestExecutionResult(); + + assertEquals(FAILED, result.getStatus()); + assertSame(assertionError, result.getThrowable().get()); + } + + private static class CustomAbort extends Error { + private static final long serialVersionUID = 1L; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java new file mode 100644 index 00000000..9dfdc786 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.jfr; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.moditect.jfrunit.ExpectedEvent.event; +import static org.moditect.jfrunit.JfrEventsAssert.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly; +import org.moditect.jfrunit.EnableEvent; +import org.moditect.jfrunit.JfrEventTest; +import org.moditect.jfrunit.JfrEvents; + +@JfrEventTest +public class FlightRecordingDiscoveryListenerIntegrationTests { + + public JfrEvents jfrEvents = new JfrEvents(); + + @Test + @EnableEvent("org.junit.*") + void reportsEvents() { + var launcher = LauncherFactoryForTestingPurposesOnly.createLauncher(new JupiterTestEngine()); + var request = request() // + .selectors(selectClass(FlightRecordingDiscoveryListenerIntegrationTests.class)) // + .listeners(new FlightRecordingDiscoveryListener()) // + .build(); + + launcher.discover(request); + jfrEvents.awaitEvents(); + + assertThat(jfrEvents) // + .contains(event("org.junit.LauncherDiscovery") // + // TODO JfrUnit does not yey support checking int values + // .with("selectors", 1) // + // .with("filters", 0) // + ) // + .contains(event("org.junit.EngineDiscovery") // + .with("uniqueId", "[engine:junit-jupiter]")); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java new file mode 100644 index 00000000..d75732ba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.jfr; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.moditect.jfrunit.ExpectedEvent.event; +import static org.moditect.jfrunit.JfrEventsAssert.assertThat; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly; +import org.moditect.jfrunit.EnableEvent; +import org.moditect.jfrunit.JfrEventTest; +import org.moditect.jfrunit.JfrEvents; + +@JfrEventTest +public class FlightRecordingExecutionListenerIntegrationTests { + + public JfrEvents jfrEvents = new JfrEvents(); + + @Test + @EnableEvent("org.junit.*") + void reportsEvents() { + var launcher = LauncherFactoryForTestingPurposesOnly.createLauncher(new JupiterTestEngine()); + var request = request() // + .selectors(selectClass(TestCase.class)) // + .build(); + + launcher.execute(request, new FlightRecordingExecutionListener()); + jfrEvents.awaitEvents(); + + assertThat(jfrEvents) // + .contains(event("org.junit.TestPlanExecution") // + .with("engineNames", "JUnit Jupiter")) // + .contains(event("org.junit.TestExecution") // + .with("displayName", "JUnit Jupiter") // + .with("type", "CONTAINER")) // + .contains(event("org.junit.TestExecution") // + .with("displayName", FlightRecordingExecutionListenerIntegrationTests.class.getSimpleName() + + "$" + TestCase.class.getSimpleName()) // + .with("type", "CONTAINER")) // + .contains(event("org.junit.TestExecution") // + .with("displayName", "test(TestReporter)") // + .with("type", "TEST") // + .with("result", "SUCCESSFUL")) // + .contains(event("org.junit.ReportEntry") // + .with("key", "message") // + .with("value", "Hello JFR!")) // + .contains(event("org.junit.SkippedTest") // + .with("displayName", "skipped()") // + .with("type", "TEST") // + .with("reason", "for demonstration purposes")); + } + + static class TestCase { + @Test + void test(TestReporter reporter) { + reporter.publishEntry("message", "Hello JFR!"); + } + + @Test + @Disabled("for demonstration purposes") + void skipped() { + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java new file mode 100644 index 00000000..1ca8e6b4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.FilterResult; + +/** + * @since 1.0 + */ +public class DiscoveryFilterStub extends FilterStub implements DiscoveryFilter { + + public DiscoveryFilterStub(String toString) { + super(toString); + } + + public DiscoveryFilterStub(Function function, Supplier toString) { + super(function, toString); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java new file mode 100644 index 00000000..04c7eab0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.FilterResult; + +/** + * @since 1.0 + */ +public class FilterStub implements Filter { + + private final Function function; + private final Supplier toString; + + public FilterStub(String toString) { + this(o -> FilterResult.included("always"), () -> toString); + } + + public FilterStub(Function function, Supplier toString) { + this.function = function; + this.toString = toString; + } + + @Override + public FilterResult apply(T object) { + return function.apply(object); + } + + @Override + public String toString() { + return toString.get(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java new file mode 100644 index 00000000..d16a7bbd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; + +/** + * @since 1.0 + */ +public class PostDiscoveryFilterStub extends FilterStub implements PostDiscoveryFilter { + + public PostDiscoveryFilterStub(String toString) { + super(toString); + } + + public PostDiscoveryFilterStub(Function function, Supplier toString) { + super(function, toString); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java new file mode 100644 index 00000000..14d966ee --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java @@ -0,0 +1,226 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.launcher.TagFilter.excludeTags; +import static org.junit.platform.launcher.TagFilter.includeTags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.DemoClassTestDescriptor; + +/** + * Unit tests for {@link TagFilter}. + * + *

NOTE: part of the behavior of these tests regarding tags is + * influenced by the implementation of {@link DemoClassTestDescriptor#getTags()} + * rather than any concrete test engine. + * + * @since 1.0 + */ +class TagFilterTests { + + private static final TestDescriptor classWithTag1 = classTestDescriptor("class1", ClassWithTag1.class); + private static final TestDescriptor classWithTag1AndSurroundingWhitespace = classTestDescriptor( + "class1-surrounding-whitespace", ClassWithTag1AndSurroundingWhitespace.class); + private static final TestDescriptor classWithTag2 = classTestDescriptor("class2", ClassWithTag2.class); + private static final TestDescriptor classWithBothTags = classTestDescriptor("class12", ClassWithBothTags.class); + private static final TestDescriptor classWithDifferentTags = classTestDescriptor("classX", + ClassWithDifferentTags.class); + private static final TestDescriptor classWithNoTags = classTestDescriptor("class", ClassWithNoTags.class); + + @Test + void includeTagsWithInvalidSyntax() { + // @formatter:off + assertAll( + () -> assertSyntaxViolationForIncludes(null), + () -> assertSyntaxViolationForIncludes(""), + () -> assertSyntaxViolationForIncludes(" "), + () -> assertSyntaxViolationForIncludes("foo bar") + ); + // @formatter:on + } + + private void assertSyntaxViolationForIncludes(String tag) { + var exception = assertThrows(PreconditionViolationException.class, () -> includeTags(tag)); + assertThat(exception).hasMessageStartingWith("Unable to parse tag expression"); + } + + @Test + void excludeTagsWithInvalidSyntax() { + // @formatter:off + assertAll( + () -> assertSyntaxViolationForExcludes(null), + () -> assertSyntaxViolationForExcludes(""), + () -> assertSyntaxViolationForExcludes(" "), + () -> assertSyntaxViolationForExcludes("foo bar") + ); + // @formatter:on + } + + private void assertSyntaxViolationForExcludes(String tag) { + var exception = assertThrows(PreconditionViolationException.class, () -> excludeTags(tag)); + assertThat(exception).hasMessageStartingWith("Unable to parse tag expression"); + } + + @Test + void includeSingleTag() { + includeSingleTag(includeTags("tag1")); + } + + @Test + void includeSingleTagAndWhitespace() { + includeSingleTag(includeTags("\t \n tag1 ")); + } + + @Test + void includeMultipleTags() { + var filter = includeTags("tag1", " tag2 "); + + assertTrue(filter.apply(classWithBothTags).included()); + assertTrue(filter.apply(classWithTag1).included()); + assertTrue(filter.apply(classWithTag1AndSurroundingWhitespace).included()); + assertTrue(filter.apply(classWithTag2).included()); + + assertTrue(filter.apply(classWithDifferentTags).excluded()); + assertTrue(filter.apply(classWithNoTags).excluded()); + } + + @Test + void excludeSingleTag() { + excludeSingleTag(excludeTags("tag1")); + } + + @Test + void excludeSingleTagAndWhitespace() { + excludeSingleTag(excludeTags("\t \n tag1 ")); + } + + @Test + void excludeMultipleTags() { + var filter = excludeTags("tag1", " tag2 "); + + var exclusionReason = "excluded because tags match tag expression(s): [tag1,tag2]"; + assertExcluded(filter.apply(classWithTag1), exclusionReason); + assertExcluded(filter.apply(classWithTag1AndSurroundingWhitespace), exclusionReason); + assertExcluded(filter.apply(classWithBothTags), exclusionReason); + assertExcluded(filter.apply(classWithTag2), exclusionReason); + + var inclusionReason = "included because tags do not match expression(s): [tag1,tag2]"; + assertIncluded(filter.apply(classWithDifferentTags), inclusionReason); + assertIncluded(filter.apply(classWithNoTags), inclusionReason); + } + + @Test + void rejectSingleUnparsableTagExpressions() { + var brokenTagExpression = "tag & "; + RuntimeException expected = assertThrows(PreconditionViolationException.class, + () -> TagFilter.includeTags(brokenTagExpression)); + assertThat(expected).hasMessageStartingWith("Unable to parse tag expression \"" + brokenTagExpression + "\""); + } + + @Test + void rejectUnparsableTagExpressionFromArray() { + var brokenTagExpression = "tag & "; + RuntimeException expected = assertThrows(PreconditionViolationException.class, + () -> TagFilter.excludeTags(brokenTagExpression, "foo", "bar")); + assertThat(expected).hasMessageStartingWith("Unable to parse tag expression \"" + brokenTagExpression + "\""); + } + + private void includeSingleTag(PostDiscoveryFilter filter) { + var inclusionReason = "included because tags match expression(s): [tag1]"; + assertIncluded(filter.apply(classWithTag1), inclusionReason); + assertIncluded(filter.apply(classWithTag1AndSurroundingWhitespace), inclusionReason); + assertIncluded(filter.apply(classWithBothTags), inclusionReason); + + var exclusionReason = "excluded because tags do not match tag expression(s): [tag1]"; + assertExcluded(filter.apply(classWithTag2), exclusionReason); + assertExcluded(filter.apply(classWithDifferentTags), exclusionReason); + assertExcluded(filter.apply(classWithNoTags), exclusionReason); + } + + private void excludeSingleTag(PostDiscoveryFilter filter) { + var exclusionReason = "excluded because tags match tag expression(s): [tag1]"; + assertExcluded(filter.apply(classWithTag1), exclusionReason); + assertExcluded(filter.apply(classWithTag1AndSurroundingWhitespace), exclusionReason); + assertExcluded(filter.apply(classWithBothTags), exclusionReason); + + var inclusionReason = "included because tags do not match expression(s): [tag1]"; + assertIncluded(filter.apply(classWithTag2), inclusionReason); + assertIncluded(filter.apply(classWithDifferentTags), inclusionReason); + assertIncluded(filter.apply(classWithNoTags), inclusionReason); + } + + private void assertIncluded(FilterResult filterResult, String expectedReason) { + assertTrue(filterResult.included()); + assertThat(filterResult.getReason()).isPresent().contains(expectedReason); + } + + private void assertExcluded(FilterResult filterResult, String expectedReason) { + assertTrue(filterResult.excluded()); + assertThat(filterResult.getReason()).isPresent().contains(expectedReason); + } + + // ------------------------------------------------------------------------- + + @Retention(RetentionPolicy.RUNTIME) + @Tag("tag1") + private @interface Tag1 { + } + + @Retention(RetentionPolicy.RUNTIME) + @Tag("tag2") + private @interface Tag2 { + } + + @Tag1 + private static class ClassWithTag1 { + } + + @Tag(" tag1 \t ") + private static class ClassWithTag1AndSurroundingWhitespace { + } + + @Tag2 + private static class ClassWithTag2 { + } + + @Tag1 + @Tag2 + private static class ClassWithBothTags { + } + + @Tag("foo") + @Tag("bar") + private static class ClassWithDifferentTags { + } + + @Tag(" ") // intentionally "blank" + private static class ClassWithNoTags { + } + + private static TestDescriptor classTestDescriptor(String uniqueId, Class testClass) { + var rootUniqueId = UniqueId.root("class", uniqueId); + return new DemoClassTestDescriptor(rootUniqueId, testClass); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java new file mode 100644 index 00000000..fd9809c1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.TagFilter.includeTags; +import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.doubleTaggedWasExecuted; +import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.tag1WasExecuted; +import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.tag2WasExecuted; +import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.unTaggedWasExecuted; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.testkit.engine.EngineTestKit; + +class TagIntegrationTests { + + @BeforeEach + void init() { + tag1WasExecuted = false; + tag2WasExecuted = false; + unTaggedWasExecuted = false; + doubleTaggedWasExecuted = false; + } + + @Test + void includingWrongTagExecutesNothing() { + executeTaggedTestCase(includeTags("whatever")); + + assertFalse(tag1WasExecuted); + assertFalse(tag2WasExecuted); + assertFalse(doubleTaggedWasExecuted); + assertFalse(unTaggedWasExecuted); + } + + @Test + void includingSuitableTagExecutesTaggedTestOnly() { + executeTaggedTestCase(includeTags("tag1")); + + assertTrue(tag1WasExecuted); + assertFalse(tag2WasExecuted); + assertTrue(doubleTaggedWasExecuted); + assertFalse(unTaggedWasExecuted); + } + + @ParameterizedTest + @ValueSource(strings = { "any()", "!none()" }) + void includingTheAnyKeywordExecutesAllTaggedTests(String tagExpression) { + executeTaggedTestCase(includeTags(tagExpression)); + + assertTrue(tag1WasExecuted); + assertTrue(tag2WasExecuted); + assertTrue(doubleTaggedWasExecuted); + assertFalse(unTaggedWasExecuted); + } + + @ParameterizedTest + @ValueSource(strings = { "none()", "!any()" }) + void includingTheNoneKeywordExecutesAllUntaggedTests(String tagExpression) { + executeTaggedTestCase(includeTags(tagExpression)); + + assertFalse(tag1WasExecuted); + assertFalse(tag2WasExecuted); + assertFalse(doubleTaggedWasExecuted); + assertTrue(unTaggedWasExecuted); + } + + private void executeTaggedTestCase(PostDiscoveryFilter filter) { + EngineTestKit.engine("junit-jupiter") // + .selectors(selectClass(TaggedTestCase.class)) // + .filters(filter) // + .execute(); + } + + static class TaggedTestCase { + + static boolean tag1WasExecuted = false; + static boolean tag2WasExecuted = false; + static boolean unTaggedWasExecuted = false; + static boolean doubleTaggedWasExecuted = false; + + @Test + @Tag("tag1") + void tagged1() { + tag1WasExecuted = true; + } + + @Test + @Tag("tag2") + void tagged2() { + tag2WasExecuted = true; + } + + @Test + @Tag("tag1") + @Tag("tag2") + void doubleTagged() { + doubleTaggedWasExecuted = true; + } + + @Test + void unTagged() { + unTaggedWasExecuted = true; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java new file mode 100644 index 00000000..8d007f45 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.SerializationUtils.deserialize; +import static org.junit.platform.commons.util.SerializationUtils.serialize; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.fakes.TestDescriptorStub; + +/** + * @since 1.0 + */ +class TestIdentifierTests { + + @Test + void inheritsIdAndNamesFromDescriptor() { + TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.root("aType", "uniqueId"), "displayName"); + var testIdentifier = TestIdentifier.from(testDescriptor); + + assertEquals("[aType:uniqueId]", testIdentifier.getUniqueId()); + assertEquals("displayName", testIdentifier.getDisplayName()); + } + + @Test + void inheritsTypeFromDescriptor() { + TestDescriptor descriptor = new TestDescriptorStub(UniqueId.root("aType", "uniqueId"), "displayName"); + var identifier = TestIdentifier.from(descriptor); + assertEquals(TestDescriptor.Type.TEST, identifier.getType()); + assertTrue(identifier.isTest()); + assertFalse(identifier.isContainer()); + + descriptor.addChild(new TestDescriptorStub(UniqueId.root("aChild", "uniqueId"), "displayName")); + identifier = TestIdentifier.from(descriptor); + assertEquals(TestDescriptor.Type.CONTAINER, identifier.getType()); + assertFalse(identifier.isTest()); + assertTrue(identifier.isContainer()); + } + + @Test + void currentVersionCanBeSerializedAndDeserialized() throws Exception { + var originalIdentifier = createOriginalTestIdentifier(); + var deserializedIdentifier = (TestIdentifier) deserialize(serialize(originalIdentifier)); + assertDeepEquals(originalIdentifier, deserializedIdentifier); + } + + @Test + void initialVersionCanBeDeserialized() throws Exception { + try (var inputStream = getClass().getResourceAsStream("/serialized-test-identifier")) { + var bytes = inputStream.readAllBytes(); + var deserializedIdentifier = (TestIdentifier) deserialize(bytes); + assertDeepEquals(createOriginalTestIdentifier(), deserializedIdentifier); + } + } + + private static void assertDeepEquals(TestIdentifier first, TestIdentifier second) { + assertEquals(first, second); + assertEquals(first.getUniqueId(), second.getUniqueId()); + assertEquals(first.getUniqueIdObject(), second.getUniqueIdObject()); + assertEquals(first.getDisplayName(), second.getDisplayName()); + assertEquals(first.getLegacyReportingName(), second.getLegacyReportingName()); + assertEquals(first.getSource(), second.getSource()); + assertEquals(first.getTags(), second.getTags()); + assertEquals(first.getType(), second.getType()); + assertEquals(first.getParentId(), second.getParentId()); + assertEquals(first.getParentIdObject(), second.getParentIdObject()); + } + + private static TestIdentifier createOriginalTestIdentifier() { + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); + var uniqueId = engineDescriptor.getUniqueId().append("child", "child"); + var testSource = ClassSource.from(TestIdentifierTests.class); + var testDescriptor = new AbstractTestDescriptor(uniqueId, "displayName", testSource) { + + @Override + public Type getType() { + return Type.TEST; + } + + @Override + public String getLegacyReportingName() { + return "reportingName"; + } + + @Override + public Set getTags() { + return Set.of(TestTag.create("aTag")); + } + }; + engineDescriptor.addChild(testDescriptor); + return TestIdentifier.from(testDescriptor); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java new file mode 100644 index 00000000..489f63ca --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +public class TestLauncherDiscoveryListener implements LauncherDiscoveryListener { + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + return getClass() == obj.getClass(); + } + + @Override + public int hashCode() { + return 1; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java new file mode 100644 index 00000000..02a39507 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +public class TestLauncherSessionListener implements LauncherSessionListener { + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + return getClass() == obj.getClass(); + } + + @Override + public int hashCode() { + return 1; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java new file mode 100644 index 00000000..866e085a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; + +class TestPlanTests { + + private final ConfigurationParameters configParams = mock(); + + private EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("foo"), "Foo"); + + @Test + void doesNotContainTestsForEmptyContainers() { + engineDescriptor.addChild( + new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { + @Override + public Type getType() { + return Type.CONTAINER; + } + }); + + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + assertThat(testPlan.containsTests()).as("contains tests").isFalse(); + } + + @Test + void containsTestsForTests() { + engineDescriptor.addChild( + new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { + @Override + public Type getType() { + return Type.TEST; + } + }); + + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + assertThat(testPlan.containsTests()).as("contains tests").isTrue(); + } + + @Test + void containsTestsForContainersThatMayRegisterTests() { + engineDescriptor.addChild( + new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public boolean mayRegisterTests() { + return true; + } + }); + + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + assertThat(testPlan.containsTests()).as("contains tests").isTrue(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java new file mode 100644 index 00000000..5ba0aada --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; + +public class TestPostDiscoveryTagFilter implements PostDiscoveryFilter { + @Override + public FilterResult apply(final TestDescriptor object) { + var include = object.getTags().stream().map(TestTag::getName).anyMatch("test-post-discovery"::equals); + return FilterResult.includedIf(include); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java new file mode 100644 index 00000000..b6ff9bac --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; + +@TrackLogRecords +class CompositeEngineExecutionListenerTests { + + private final List listeners = new ArrayList<>( + List.of(new ThrowingEngineExecutionListener())); + + @Test + void shouldNotThrowExceptionButLogIfDynamicTestRegisteredListenerMethodFails(LogRecordListener logRecordListener) { + var testDescriptor = getSampleMethodTestDescriptor(); + + compositeTestExecutionListener().dynamicTestRegistered(testDescriptor); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, + "dynamicTestRegistered"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionStartedListenerMethodFails(LogRecordListener logRecordListener) { + var testDescriptor = getSampleMethodTestDescriptor(); + + compositeTestExecutionListener().executionStarted(testDescriptor); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, "executionStarted"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionSkippedListenerMethodFails(LogRecordListener logRecordListener) { + var testDescriptor = getSampleMethodTestDescriptor(); + + compositeTestExecutionListener().executionSkipped(testDescriptor, "deliberately skipped container"); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, "executionSkipped"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionFinishedListenerMethodFails(LogRecordListener logRecordListener) { + var testDescriptor = getSampleMethodTestDescriptor(); + + compositeTestExecutionListener().executionFinished(testDescriptor, mock(TestExecutionResult.class)); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, + "executionFinished"); + } + + @Test + void shouldNotThrowExceptionButLogIfReportingEntryPublishedListenerMethodFails( + LogRecordListener logRecordListener) { + var testDescriptor = getSampleMethodTestDescriptor(); + + compositeTestExecutionListener().reportingEntryPublished(testDescriptor, ReportEntry.from("one", "two")); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, + "reportingEntryPublished"); + } + + @Test + void shouldThrowOutOfMemoryExceptionAndStopListenerWithoutLog(LogRecordListener logRecordListener) { + listeners.clear(); + listeners.add(new EngineExecutionListener() { + @Override + public void executionStarted(TestDescriptor testDescriptor) { + throw new OutOfMemoryError(); + } + }); + var testDescriptor = getSampleMethodTestDescriptor(); + assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(testDescriptor)).isInstanceOf( + OutOfMemoryError.class); + + assertNotLogs(logRecordListener); + } + + private EngineExecutionListener compositeTestExecutionListener() { + return new CompositeEngineExecutionListener(listeners); + } + + private LogRecord firstWarnLogRecord(LogRecordListener logRecordListener) throws AssertionError { + return logRecordListener.stream(CompositeEngineExecutionListener.class, Level.WARNING).findFirst().orElseThrow( + () -> new AssertionError("Failed to find error log record")); + } + + private void assertNotLogs(LogRecordListener logRecordListener) throws AssertionError { + assertThat(logRecordListener.stream(CompositeEngineExecutionListener.class, Level.WARNING).count()).isZero(); + } + + private TestDescriptor getSampleMethodTestDescriptor() { + return getDemoMethodTestDescriptor(); + } + + private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListener, Class listenerClass, + String methodName) { + assertThat(firstWarnLogRecord(logRecordListener).getMessage()).startsWith( + "EngineExecutionListener [" + listenerClass.getName() + "] threw exception for method: " + methodName); + } + + private DemoMethodTestDescriptor getDemoMethodTestDescriptor() { + var method = ReflectionUtils.findMethod(this.getClass(), "getDemoMethodTestDescriptor", + new Class[0]).orElseThrow(); + return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), this.getClass(), method); + } + + private static class ThrowingEngineExecutionListener implements EngineExecutionListener { + + @Override + public void dynamicTestRegistered(TestDescriptor testDescriptor) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void executionStarted(TestDescriptor testDescriptor) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void executionSkipped(TestDescriptor testDescriptor, String reason) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { + throw new RuntimeException("failed to invoke listener"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java new file mode 100644 index 00000000..18751347 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java @@ -0,0 +1,246 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.CompositeTestExecutionListener.EagerTestExecutionListener; + +@TrackLogRecords +class CompositeTestExecutionListenerTests { + + private final List listeners = new ArrayList<>(List.of(new ThrowingTestExecutionListener())); + + @Test + void shouldNotThrowExceptionButLogIfDynamicTestRegisteredListenerMethodFails(LogRecordListener logRecordListener) { + var testIdentifier = getSampleMethodTestIdentifier(); + + compositeTestExecutionListener().dynamicTestRegistered(testIdentifier); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, + "dynamicTestRegistered"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionStartedListenerMethodFails(LogRecordListener logRecordListener) { + var testIdentifier = getSampleMethodTestIdentifier(); + + compositeTestExecutionListener().executionStarted(testIdentifier); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionStarted"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionSkippedListenerMethodFails(LogRecordListener logRecordListener) { + var testIdentifier = getSampleMethodTestIdentifier(); + + compositeTestExecutionListener().executionSkipped(testIdentifier, "deliberately skipped container"); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionSkipped"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionFinishedListenerMethodFails(LogRecordListener logRecordListener) { + var testIdentifier = getSampleMethodTestIdentifier(); + + compositeTestExecutionListener().executionFinished(testIdentifier, mock(TestExecutionResult.class)); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionFinished"); + } + + @Test + void shouldNotThrowExceptionButLogIfReportingEntryPublishedListenerMethodFails( + LogRecordListener logRecordListener) { + var testIdentifier = getSampleMethodTestIdentifier(); + + compositeTestExecutionListener().reportingEntryPublished(testIdentifier, ReportEntry.from("one", "two")); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, + "reportingEntryPublished"); + } + + @Test + void shouldNotThrowExceptionButLogIfTesPlanExecutionStartedListenerMethodFails( + LogRecordListener logRecordListener) { + var testDescriptor = getDemoMethodTestDescriptor(); + + compositeTestExecutionListener().testPlanExecutionStarted(TestPlan.from(Set.of(testDescriptor), mock())); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, + "testPlanExecutionStarted"); + } + + @Test + void shouldNotThrowExceptionButLogIfTesPlanExecutionFinishedListenerMethodFails( + LogRecordListener logRecordListener) { + var testDescriptor = getDemoMethodTestDescriptor(); + + compositeTestExecutionListener().testPlanExecutionFinished(TestPlan.from(Set.of(testDescriptor), mock())); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, + "testPlanExecutionFinished"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionJustStartedEagerTestListenerMethodFails( + LogRecordListener logRecordListener) { + listeners.add(new ThrowingEagerTestExecutionListener()); + + var testIdentifier = getSampleMethodTestIdentifier(); + compositeTestExecutionListener().executionStarted(testIdentifier); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingEagerTestExecutionListener.class, + "executionJustStarted"); + } + + @Test + void shouldNotThrowExceptionButLogIfExecutionJustFinishedEagerTestListenerMethodFails( + LogRecordListener logRecordListener) { + listeners.add(new ThrowingEagerTestExecutionListener()); + + var testIdentifier = getSampleMethodTestIdentifier(); + compositeTestExecutionListener().executionFinished(testIdentifier, mock(TestExecutionResult.class)); + + assertThatTestListenerErrorLogged(logRecordListener, ThrowingEagerTestExecutionListener.class, + "executionJustFinished"); + } + + @Test + void shouldThrowOutOfMemoryExceptionAndStopListenerWithoutLog(LogRecordListener logRecordListener) { + listeners.clear(); + listeners.add(new TestExecutionListener() { + @Override + public void executionStarted(TestIdentifier testIdentifier) { + throw new OutOfMemoryError(); + } + }); + var testIdentifier = getSampleMethodTestIdentifier(); + assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(testIdentifier)).isInstanceOf( + OutOfMemoryError.class); + + assertNotLogs(logRecordListener); + } + + @Test + void shouldThrowOutOfMemoryExceptionAndStopEagerListenerWithoutLog(LogRecordListener logRecordListener) { + listeners.add(new EagerTestExecutionListener() { + @Override + public void executionJustStarted(TestIdentifier testIdentifier) { + throw new OutOfMemoryError(); + } + }); + var testIdentifier = getSampleMethodTestIdentifier(); + assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(testIdentifier)).isInstanceOf( + OutOfMemoryError.class); + + assertNotLogs(logRecordListener); + } + + private TestExecutionListener compositeTestExecutionListener() { + return new CompositeTestExecutionListener(listeners); + } + + private LogRecord firstWarnLogRecord(LogRecordListener logRecordListener) throws AssertionError { + return logRecordListener.stream(CompositeTestExecutionListener.class, Level.WARNING).findFirst().orElseThrow( + () -> new AssertionError("Failed to find error log record")); + } + + private void assertNotLogs(LogRecordListener logRecordListener) throws AssertionError { + assertThat(logRecordListener.stream(CompositeTestExecutionListener.class, Level.WARNING).count()).isZero(); + } + + private TestIdentifier getSampleMethodTestIdentifier() { + var demoMethodTestDescriptor = getDemoMethodTestDescriptor(); + return TestIdentifier.from(demoMethodTestDescriptor); + } + + private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListener, Class listenerClass, + String methodName) { + assertThat(firstWarnLogRecord(logRecordListener).getMessage()).startsWith( + "TestExecutionListener [" + listenerClass.getName() + "] threw exception for method: " + methodName); + } + + private DemoMethodTestDescriptor getDemoMethodTestDescriptor() { + var method = ReflectionUtils.findMethod(this.getClass(), "getDemoMethodTestDescriptor", + new Class[0]).orElseThrow(); + return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), this.getClass(), method); + } + + private static class ThrowingEagerTestExecutionListener extends ThrowingTestExecutionListener + implements EagerTestExecutionListener { + @Override + public void executionJustStarted(TestIdentifier testIdentifier) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + throw new RuntimeException("failed to invoke listener"); + } + } + + private static class ThrowingTestExecutionListener implements TestExecutionListener { + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + throw new RuntimeException("failed to invoke listener"); + } + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + throw new RuntimeException("failed to invoke listener"); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java new file mode 100644 index 00000000..85732038 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java @@ -0,0 +1,194 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.logging.Level.WARNING; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.EngineFilter.excludeEngines; +import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; +import org.junit.platform.launcher.LauncherDiscoveryRequest; + +/** + * @since 1.0 + */ +class DefaultLauncherEngineFilterTests { + + private static final Runnable noOp = () -> { + }; + + @Test + void launcherWillNotExecuteEnginesIfNotIncludedByAnEngineFilter() { + var firstEngine = new DemoHierarchicalTestEngine("first"); + TestDescriptor test1 = firstEngine.addTest("test1", noOp); + var secondEngine = new DemoHierarchicalTestEngine("second"); + TestDescriptor test2 = secondEngine.addTest("test2", noOp); + + var launcher = createLauncher(firstEngine, secondEngine); + + // @formatter:off + var testPlan = launcher.discover( + request() + .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) + .filters(includeEngines("first")) + .build()); + // @formatter:on + + assertThat(testPlan.getRoots()).hasSize(1); + var rootIdentifier = testPlan.getRoots().iterator().next(); + assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); + assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); + } + + @Test + void launcherWillExecuteAllEnginesExplicitlyIncludedViaSingleEngineFilter() { + var firstEngine = new DemoHierarchicalTestEngine("first"); + TestDescriptor test1 = firstEngine.addTest("test1", noOp); + var secondEngine = new DemoHierarchicalTestEngine("second"); + TestDescriptor test2 = secondEngine.addTest("test2", noOp); + + var launcher = createLauncher(firstEngine, secondEngine); + + // @formatter:off + var testPlan = launcher.discover( + request() + .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) + .filters(includeEngines("first", "second")) + .build()); + // @formatter:on + + assertThat(testPlan.getRoots()).hasSize(2); + } + + @Test + void launcherWillNotExecuteEnginesExplicitlyIncludedViaMultipleCompetingEngineFilters() { + var firstEngine = new DemoHierarchicalTestEngine("first"); + TestDescriptor test1 = firstEngine.addTest("test1", noOp); + var secondEngine = new DemoHierarchicalTestEngine("second"); + TestDescriptor test2 = secondEngine.addTest("test2", noOp); + + var launcher = createLauncher(firstEngine, secondEngine); + + // @formatter:off + var testPlan = launcher.discover( + request() + .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) + .filters(includeEngines("first"), includeEngines("second")) + .build()); + // @formatter:on + + assertThat(testPlan.getRoots()).isEmpty(); + } + + @Test + void launcherWillNotExecuteEnginesExplicitlyExcludedByAnEngineFilter() { + var firstEngine = new DemoHierarchicalTestEngine("first"); + TestDescriptor test1 = firstEngine.addTest("test1", noOp); + var secondEngine = new DemoHierarchicalTestEngine("second"); + TestDescriptor test2 = secondEngine.addTest("test2", noOp); + + var launcher = createLauncher(firstEngine, secondEngine); + + // @formatter:off + var testPlan = launcher.discover( + request() + .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) + .filters(excludeEngines("second")) + .build()); + // @formatter:on + + assertThat(testPlan.getRoots()).hasSize(1); + var rootIdentifier = testPlan.getRoots().iterator().next(); + assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); + assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); + } + + @Test + void launcherWillExecuteEnginesHonoringBothIncludeAndExcludeEngineFilters() { + var firstEngine = new DemoHierarchicalTestEngine("first"); + TestDescriptor test1 = firstEngine.addTest("test1", noOp); + var secondEngine = new DemoHierarchicalTestEngine("second"); + TestDescriptor test2 = secondEngine.addTest("test2", noOp); + var thirdEngine = new DemoHierarchicalTestEngine("third"); + TestDescriptor test3 = thirdEngine.addTest("test3", noOp); + + var launcher = createLauncher(firstEngine, secondEngine, thirdEngine); + + // @formatter:off + var testPlan = launcher.discover( + request() + .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId()), selectUniqueId(test3.getUniqueId())) + .filters(includeEngines("first", "second"), excludeEngines("second")) + .build()); + // @formatter:on + + assertThat(testPlan.getRoots()).hasSize(1); + var rootIdentifier = testPlan.getRoots().iterator().next(); + assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); + assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); + } + + @Test + @TrackLogRecords + void launcherThrowsExceptionWhenNoEngineMatchesIncludeEngineFilter(LogRecordListener log) { + var engine = new DemoHierarchicalTestEngine("first"); + TestDescriptor test1 = engine.addTest("test1", noOp); + LauncherDiscoveryRequest request = request() // + .selectors(selectUniqueId(test1.getUniqueId())) // + .filters(includeEngines("second")) // + .build(); + + var launcher = createLauncher(engine); + var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); + + assertThat(exception.getMessage()) // + .startsWith("No TestEngine ID matched the following include EngineFilters: [second].") // + .contains("Please fix/remove the filter or add the engine.") // + .contains("Registered TestEngines:\n- first (") // + .endsWith("Registered EngineFilters:\n- EngineFilter that includes engines with IDs [second]"); + assertThat(log.stream(WARNING)).isEmpty(); + } + + @Test + @TrackLogRecords + void launcherWillLogWarningWhenAllEnginesWereExcluded(LogRecordListener log) { + var engine = new DemoHierarchicalTestEngine("first"); + TestDescriptor test = engine.addTest("test1", noOp); + + var launcher = createLauncher(engine); + + // @formatter:off + var testPlan = launcher.discover( + request() + .selectors(selectUniqueId(test.getUniqueId())) + .filters(excludeEngines("first")) + .build()); + // @formatter:on + + assertThat(testPlan.getRoots()).isEmpty(); + assertThat(log.stream(WARNING)).hasSize(1); + assertThat(log.stream(WARNING).findAny().orElseThrow().getMessage()) // + .startsWith("All TestEngines were excluded by EngineFilters.") // + .contains("Registered TestEngines:\n- first (") // + .endsWith("Registered EngineFilters:\n- EngineFilter that excludes engines with IDs [first]"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java new file mode 100644 index 00000000..dce04470 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java @@ -0,0 +1,623 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.fakes.TestEngineSpy; +import org.junit.platform.fakes.TestEngineStub; +import org.junit.platform.launcher.EngineDiscoveryResult; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.PostDiscoveryFilterStub; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.mockito.ArgumentCaptor; + +/** + * @since 1.0 + */ +class DefaultLauncherTests { + + private static final String FOO = DefaultLauncherTests.class.getSimpleName() + ".foo"; + private static final String BAR = DefaultLauncherTests.class.getSimpleName() + ".bar"; + + private static final Runnable noOp = () -> { + }; + + @Test + void constructLauncherWithoutAnyEngines() { + Throwable exception = assertThrows(PreconditionViolationException.class, + LauncherFactoryForTestingPurposesOnly::createLauncher); + + assertThat(exception).hasMessageContaining("Cannot create Launcher without at least one TestEngine"); + } + + @Test + void constructLauncherWithMultipleTestEnginesWithDuplicateIds() { + var exception = assertThrows(JUnitException.class, + () -> createLauncher(new DemoHierarchicalTestEngine("dummy id"), + new DemoHierarchicalTestEngine("dummy id"))); + + assertThat(exception).hasMessageContaining("multiple engines with the same ID"); + } + + @Test + void discoverEmptyTestPlanWithEngineWithoutAnyTests() { + var launcher = createLauncher(new DemoHierarchicalTestEngine()); + + var testPlan = launcher.discover(request().build()); + + assertThat(testPlan.getRoots()).hasSize(1); + } + + @Test + void discoverTestPlanForEngineThatReturnsNullForItsRootDescriptor() { + TestEngine engine = new TestEngineStub("some-engine-id") { + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + return null; + } + }; + + var discoveryListener = mock(LauncherDiscoveryListener.class); + var testPlan = createLauncher(engine).discover(request() // + .listeners(discoveryListener) // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .build()); + assertThat(testPlan.getRoots()).hasSize(1); + assertDiscoveryFailed(engine, discoveryListener); + } + + @ParameterizedTest + @ValueSource(classes = { Error.class, RuntimeException.class }) + void discoverErrorTestDescriptorForEngineThatThrowsInDiscoveryPhase(Class throwableClass) { + TestEngine engine = new TestEngineStub("my-engine-id") { + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + try { + var constructor = throwableClass.getDeclaredConstructor(String.class); + throw ExceptionUtils.throwAsUncheckedException(constructor.newInstance("ignored")); + } + catch (Exception ignored) { + return null; + } + } + }; + + var launcher = createLauncher(engine); + var discoveryListener = mock(LauncherDiscoveryListener.class); + var request = request() // + .listeners(discoveryListener) // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .build(); + var testPlan = launcher.discover(request); + + assertThat(testPlan.getRoots()).hasSize(1); + var engineIdentifier = getOnlyElement(testPlan.getRoots()); + assertThat(getOnlyElement(testPlan.getRoots()).getDisplayName()).isEqualTo("my-engine-id"); + verify(discoveryListener).launcherDiscoveryStarted(request); + verify(discoveryListener).launcherDiscoveryFinished(request); + assertDiscoveryFailed(engine, discoveryListener); + + var listener = mock(TestExecutionListener.class); + launcher.execute(testPlan, listener); + + var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + verify(listener).executionStarted(engineIdentifier); + verify(listener).executionFinished(eq(engineIdentifier), testExecutionResult.capture()); + assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); + assertThat(testExecutionResult.getValue().getThrowable().get()) // + .hasMessage("TestEngine with ID 'my-engine-id' failed to discover tests"); + } + + private void assertDiscoveryFailed(TestEngine testEngine, LauncherDiscoveryListener discoveryListener) { + var engineId = testEngine.getId(); + var failureCaptor = ArgumentCaptor.forClass(EngineDiscoveryResult.class); + verify(discoveryListener).engineDiscoveryFinished(eq(UniqueId.forEngine(engineId)), failureCaptor.capture()); + var result = failureCaptor.getValue(); + assertThat(result.getStatus()).isEqualTo(EngineDiscoveryResult.Status.FAILED); + assertThat(result.getThrowable()).isPresent(); + assertThat(result.getThrowable().get()).hasMessage( + "TestEngine with ID '" + engineId + "' failed to discover tests"); + } + + @Test + void reportsEngineExecutionFailuresWithoutPriorEvents() { + var rootCause = new RuntimeException("something went horribly wrong"); + var engine = new TestEngineStub() { + @Override + public void execute(ExecutionRequest request) { + throw rootCause; + } + }; + + var listener = mock(TestExecutionListener.class); + createLauncher(engine).execute(request().build(), listener); + + var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + verify(listener).executionStarted(any()); + verify(listener).executionFinished(any(), testExecutionResult.capture()); + assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); + assertThat(testExecutionResult.getValue().getThrowable().get()) // + .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // + .hasCauseReference(rootCause); + } + + @Test + void reportsEngineExecutionFailuresForSkippedEngine() { + var rootCause = new RuntimeException("something went horribly wrong"); + var engine = new TestEngineStub() { + @Override + public void execute(ExecutionRequest request) { + var engineDescriptor = request.getRootTestDescriptor(); + request.getEngineExecutionListener().executionSkipped(engineDescriptor, "not today"); + throw rootCause; + } + }; + + var listener = mock(TestExecutionListener.class); + createLauncher(engine).execute(request().build(), listener); + + var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + verify(listener).executionStarted(any()); + verify(listener).executionFinished(any(), testExecutionResult.capture()); + assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); + assertThat(testExecutionResult.getValue().getThrowable().get()) // + .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // + .hasCauseReference(rootCause); + } + + @Test + void reportsEngineExecutionFailuresForStartedEngine() { + var rootCause = new RuntimeException("something went horribly wrong"); + var engine = new TestEngineStub() { + @Override + public void execute(ExecutionRequest request) { + var engineDescriptor = request.getRootTestDescriptor(); + request.getEngineExecutionListener().executionStarted(engineDescriptor); + throw rootCause; + } + }; + + var listener = mock(TestExecutionListener.class); + createLauncher(engine).execute(request().build(), listener); + + var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + verify(listener).executionStarted(any()); + verify(listener).executionFinished(any(), testExecutionResult.capture()); + assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); + assertThat(testExecutionResult.getValue().getThrowable().get()) // + .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // + .hasCauseReference(rootCause); + } + + @Test + void reportsEngineExecutionFailuresForSuccessfullyFinishedEngine() { + var rootCause = new RuntimeException("something went horribly wrong"); + var engine = new TestEngineStub() { + @Override + public void execute(ExecutionRequest request) { + var engineDescriptor = request.getRootTestDescriptor(); + request.getEngineExecutionListener().executionStarted(engineDescriptor); + request.getEngineExecutionListener().executionFinished(engineDescriptor, + TestExecutionResult.successful()); + throw rootCause; + } + }; + + var listener = mock(TestExecutionListener.class); + createLauncher(engine).execute(request().build(), listener); + + var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + verify(listener).executionStarted(any()); + verify(listener).executionFinished(any(), testExecutionResult.capture()); + assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); + assertThat(testExecutionResult.getValue().getThrowable().get()) // + .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // + .hasCauseReference(rootCause); + } + + @Test + void reportsEngineExecutionFailuresForFailedFinishedEngine() { + var rootCause = new RuntimeException("something went horribly wrong"); + var originalFailure = new RuntimeException("suppressed"); + var engine = new TestEngineStub() { + @Override + public void execute(ExecutionRequest request) { + var engineDescriptor = request.getRootTestDescriptor(); + var listener = request.getEngineExecutionListener(); + listener.executionStarted(engineDescriptor); + listener.executionFinished(engineDescriptor, TestExecutionResult.failed(originalFailure)); + throw rootCause; + } + }; + + var listener = mock(TestExecutionListener.class); + createLauncher(engine).execute(request().build(), listener); + + var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); + verify(listener).executionStarted(any()); + verify(listener).executionFinished(any(), testExecutionResult.capture()); + assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); + assertThat(testExecutionResult.getValue().getThrowable().get()) // + .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // + .hasCauseReference(rootCause) // + .hasSuppressedException(originalFailure); + } + + @Test + void reportsSkippedEngines() { + var engine = new TestEngineStub() { + @Override + public void execute(ExecutionRequest request) { + var engineDescriptor = request.getRootTestDescriptor(); + request.getEngineExecutionListener().executionSkipped(engineDescriptor, "not today"); + } + }; + + var listener = mock(TestExecutionListener.class); + createLauncher(engine).execute(request().build(), listener); + + verify(listener).executionSkipped(any(TestIdentifier.class), eq("not today")); + verify(listener, times(0)).executionStarted(any()); + verify(listener, times(0)).executionFinished(any(), any()); + } + + @Test + void reportsFinishedEngines() { + var engine = new TestEngineStub() { + @Override + public void execute(ExecutionRequest request) { + var engineDescriptor = request.getRootTestDescriptor(); + var listener = request.getEngineExecutionListener(); + listener.executionStarted(engineDescriptor); + listener.executionFinished(engineDescriptor, TestExecutionResult.successful()); + } + }; + + var listener = mock(TestExecutionListener.class); + createLauncher(engine).execute(request().build(), listener); + + verify(listener).executionStarted(any()); + verify(listener).executionFinished(any(), eq(TestExecutionResult.successful())); + } + + @Test + void discoverTestPlanForSingleEngine() { + var engine = new DemoHierarchicalTestEngine("myEngine"); + engine.addTest("test1", noOp); + engine.addTest("test2", noOp); + + var launcher = createLauncher(engine); + + var testPlan = launcher.discover(request().selectors(selectPackage("any")).build()); + + assertThat(testPlan.getRoots()).hasSize(1); + var rootIdentifier = testPlan.getRoots().iterator().next(); + assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(2); + assertThat(testPlan.getChildren(UniqueId.parse("[engine:myEngine]"))).hasSize(2); + } + + @Test + void discoverTestPlanForMultipleEngines() { + var firstEngine = new DemoHierarchicalTestEngine("engine1"); + TestDescriptor test1 = firstEngine.addTest("test1", noOp); + var secondEngine = new DemoHierarchicalTestEngine("engine2"); + TestDescriptor test2 = secondEngine.addTest("test2", noOp); + + var launcher = createLauncher(firstEngine, secondEngine); + + var testPlan = launcher.discover( + request().selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())).build()); + + assertThat(testPlan.getRoots()).hasSize(2); + assertThat(testPlan.getChildren(UniqueId.forEngine("engine1"))).hasSize(1); + assertThat(testPlan.getChildren(UniqueId.forEngine("engine2"))).hasSize(1); + } + + @Test + void launcherAppliesPostDiscoveryFilters() { + var engine = new DemoHierarchicalTestEngine("myEngine"); + var test1 = engine.addTest("test1", noOp); + engine.addTest("test2", noOp); + + var launcher = createLauncher(engine); + + PostDiscoveryFilter includeWithUniqueIdContainsTest = new PostDiscoveryFilterStub( + descriptor -> FilterResult.includedIf(descriptor.getUniqueId().toString().contains("test")), + () -> "filter1"); + PostDiscoveryFilter includeWithUniqueIdContains1 = new PostDiscoveryFilterStub( + descriptor -> FilterResult.includedIf(descriptor.getUniqueId().toString().contains("1")), () -> "filter2"); + + var testPlan = launcher.discover( // + request() // + .selectors(selectPackage("any")) // + .filters(includeWithUniqueIdContainsTest, includeWithUniqueIdContains1) // + .build()); + + assertThat(testPlan.getChildren(UniqueId.forEngine("myEngine"))).hasSize(1); + assertThat(testPlan.getTestIdentifier(test1.getUniqueId())).isNotNull(); + } + + @Test + @SuppressWarnings("deprecation") + void withoutConfigurationParameters_LauncherPassesEmptyConfigurationParametersIntoTheExecutionRequest() { + var engine = new TestEngineSpy(); + + var launcher = createLauncher(engine); + launcher.execute(request().build()); + + var configurationParameters = engine.requestForExecution.getConfigurationParameters(); + assertThat(configurationParameters.get("key").isPresent()).isFalse(); + assertThat(configurationParameters.size()).isEqualTo(0); + } + + @Test + @SuppressWarnings("deprecation") + void withConfigurationParameters_LauncherPassesPopulatedConfigurationParametersIntoTheExecutionRequest() { + var engine = new TestEngineSpy(); + + var launcher = createLauncher(engine); + launcher.execute(request().configurationParameter("key", "value").build()); + + var configurationParameters = engine.requestForExecution.getConfigurationParameters(); + assertThat(configurationParameters.size()).isEqualTo(1); + assertThat(configurationParameters.get("key").isPresent()).isTrue(); + assertThat(configurationParameters.get("key").get()).isEqualTo("value"); + } + + @Test + @SuppressWarnings("deprecation") + void withoutConfigurationParameters_LookupFallsBackToSystemProperty() { + System.setProperty(FOO, BAR); + + try { + var engine = new TestEngineSpy(); + + var launcher = createLauncher(engine); + launcher.execute(request().build()); + + var configurationParameters = engine.requestForExecution.getConfigurationParameters(); + assertThat(configurationParameters.size()).isEqualTo(0); + var optionalFoo = configurationParameters.get(FOO); + assertTrue(optionalFoo.isPresent(), "foo should have been picked up via system property"); + assertEquals(BAR, optionalFoo.get(), "foo property"); + } + finally { + System.clearProperty(FOO); + } + } + + @Test + void withAdditionalListener() { + var engine = new TestEngineSpy(); + var listener = new SummaryGeneratingListener(); + + var launcher = createLauncher(engine); + launcher.execute(request().build(), listener); + + assertThat(listener.getSummary()).isNotNull(); + assertThat(listener.getSummary().getContainersFoundCount()).isEqualTo(1); + assertThat(listener.getSummary().getTestsFoundCount()).isEqualTo(1); + } + + @Test + void prunesTestDescriptorsAfterApplyingPostDiscoveryFilters() { + var engine = new TestEngineSpy() { + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + super.discover(discoveryRequest, uniqueId); + var engineDescriptor = new TestDescriptorStub(uniqueId, uniqueId.toString()); + var containerDescriptor = new TestDescriptorStub(uniqueId.append("container", "a"), "container") { + + @Override + public Type getType() { + return Type.CONTAINER; + } + }; + containerDescriptor.addChild( + new TestDescriptorStub(containerDescriptor.getUniqueId().append("test", "b"), "test")); + engineDescriptor.addChild(containerDescriptor); + return engineDescriptor; + } + }; + + var launcher = createLauncher(engine); + var testPlan = launcher.discover(request().filters( + (PostDiscoveryFilter) testDescriptor -> FilterResult.includedIf(testDescriptor.isContainer())).build()); + + assertThat(testPlan.getRoots()).hasSize(1); + var engineIdentifier = getOnlyElement(testPlan.getRoots()); + assertThat(testPlan.getChildren(engineIdentifier)).isEmpty(); + } + + @Test + void reportsDynamicTestDescriptorsCorrectly() { + var engineId = UniqueId.forEngine(TestEngineSpy.ID); + var containerAndTestId = engineId.append("c&t", "c&t"); + var dynamicTestId = containerAndTestId.append("test", "test"); + + var engine = new TestEngineSpy() { + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + super.discover(discoveryRequest, uniqueId); + var engineDescriptor = new TestDescriptorStub(uniqueId, uniqueId.toString()); + engineDescriptor.addChild(new TestDescriptorStub(containerAndTestId, "c&t") { + + @Override + public Type getType() { + return Type.CONTAINER_AND_TEST; + } + }); + return engineDescriptor; + } + + @Override + public void execute(ExecutionRequest request) { + super.execute(request); + var listener = request.getEngineExecutionListener(); + + listener.executionStarted(request.getRootTestDescriptor()); + var containerAndTest = getOnlyElement(request.getRootTestDescriptor().getChildren()); + listener.executionStarted(containerAndTest); + + var dynamicTest = new TestDescriptorStub(dynamicTestId, "test"); + dynamicTest.setParent(containerAndTest); + listener.dynamicTestRegistered(dynamicTest); + listener.executionStarted(dynamicTest); + listener.executionFinished(dynamicTest, successful()); + + listener.executionFinished(containerAndTest, successful()); + listener.executionFinished(request.getRootTestDescriptor(), successful()); + } + }; + + var launcher = createLauncher(engine); + var listener = mock(TestExecutionListener.class); + launcher.execute(request().build(), listener); + + var inOrder = inOrder(listener); + var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); + inOrder.verify(listener).testPlanExecutionStarted(testPlanArgumentCaptor.capture()); + + var testPlan = testPlanArgumentCaptor.getValue(); + var engineTestIdentifier = testPlan.getTestIdentifier(engineId); + var containerAndTestIdentifier = testPlan.getTestIdentifier(containerAndTestId); + var dynamicTestIdentifier = testPlan.getTestIdentifier(dynamicTestId); + assertThat(engineTestIdentifier.getParentIdObject()).isEmpty(); + assertThat(containerAndTestIdentifier.getParentIdObject()).contains(engineId); + assertThat(dynamicTestIdentifier.getParentIdObject()).contains(containerAndTestId); + + inOrder.verify(listener).executionStarted(engineTestIdentifier); + inOrder.verify(listener).executionStarted(containerAndTestIdentifier); + inOrder.verify(listener).dynamicTestRegistered(dynamicTestIdentifier); + inOrder.verify(listener).executionStarted(dynamicTestIdentifier); + inOrder.verify(listener).executionFinished(dynamicTestIdentifier, successful()); + inOrder.verify(listener).executionFinished(containerAndTestIdentifier, successful()); + inOrder.verify(listener).executionFinished(engineTestIdentifier, successful()); + inOrder.verify(listener).testPlanExecutionFinished(same(testPlan)); + } + + @Test + void launcherCanExecuteTestPlanExactlyOnce() { + var engine = mock(TestEngine.class); + when(engine.getId()).thenReturn("some-engine"); + when(engine.discover(any(), any())).thenAnswer(invocation -> { + UniqueId uniqueId = invocation.getArgument(1); + return new EngineDescriptor(uniqueId, uniqueId.toString()); + }); + + var launcher = createLauncher(engine); + var testPlan = launcher.discover(request().build()); + verify(engine, times(1)).discover(any(), any()); + + launcher.execute(testPlan); + verify(engine, times(1)).execute(any()); + + var e = assertThrows(PreconditionViolationException.class, () -> launcher.execute(testPlan)); + assertEquals(e.getMessage(), "TestPlan must only be executed once"); + } + + @Test + @SuppressWarnings("deprecation") + void testPlanThrowsExceptionWhenModified() { + TestEngine engine = new TestEngineSpy(); + var launcher = createLauncher(engine); + var testPlan = launcher.discover(request().build()); + var engineIdentifier = getOnlyElement(testPlan.getRoots()); + var engineUniqueId = engineIdentifier.getUniqueIdObject(); + assertThat(testPlan.getChildren(engineIdentifier)).hasSize(1); + + var addedIdentifier = TestIdentifier.from( + new TestDescriptorStub(engineUniqueId.append("test", "test2"), "test2")); + + var exception = assertThrows(JUnitException.class, () -> testPlan.add(addedIdentifier)); + assertThat(exception).hasMessage("Unsupported attempt to modify the TestPlan was detected. " + + "Please contact your IDE/tool vendor and request a fix or downgrade to JUnit 5.7.x (see https://github.com/junit-team/junit5/issues/1732 for details)."); + assertThat(testPlan.getChildren(engineIdentifier)).hasSize(1).doesNotContain(addedIdentifier); + } + + @Test + @TrackLogRecords + void thirdPartyEngineUsingReservedEngineIdPrefixEmitsWarning(LogRecordListener listener) { + var id = "junit-using-reserved-prefix"; + createLauncher(new TestEngineStub(id)); + assertThat(listener.stream(EngineIdValidator.class, Level.WARNING).map(LogRecord::getMessage)) // + .containsExactly( + "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID: '" + + id + "'"); + } + + @Test + void thirdPartyEngineClaimingToBeJupiterResultsInException() { + assertImposter("junit-jupiter"); + } + + @Test + void thirdPartyEngineClaimingToBeVintageResultsInException() { + assertImposter("junit-vintage"); + } + + private void assertImposter(String id) { + TestEngine impostor = new TestEngineStub(id); + Exception exception = assertThrows(JUnitException.class, () -> createLauncher(impostor)); + assertThat(exception).hasMessage( + "Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.", + impostor.getClass().getName(), id); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java new file mode 100644 index 00000000..e9421a24 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.fakes.TestDescriptorStub; + +/** + * @since 1.3 + */ +class EngineDiscoveryResultValidatorTests { + + private final EngineDiscoveryResultValidator validator = new EngineDiscoveryResultValidator(); + + @Test + void detectCycleWithDoubleRoot() { + var root = new TestDescriptorStub(UniqueId.forEngine("root"), "root"); + assertTrue(validator.isAcyclic(root)); + + root.addChild(root); + assertFalse(validator.isAcyclic(root)); + } + + @Test + void detectCycleWithDoubleGroup() { + var rootId = UniqueId.forEngine("root"); + var root = new TestDescriptorStub(rootId, "root"); + TestDescriptor group1 = new TestDescriptorStub(rootId.append("group", "1"), "1"); + TestDescriptor group2 = new TestDescriptorStub(rootId.append("group", "2"), "2"); + root.addChild(group1); + root.addChild(group2); + assertTrue(validator.isAcyclic(root)); + + group2.addChild(group1); + assertFalse(validator.isAcyclic(root)); + } + + @Test + void detectCycleWithDoubleTest() { + var rootId = UniqueId.forEngine("root"); + var root = new TestDescriptorStub(rootId, "root"); + TestDescriptor group1 = new TestDescriptorStub(rootId.append("group", "1"), "1"); + TestDescriptor group2 = new TestDescriptorStub(rootId.append("group", "2"), "2"); + root.addChild(group1); + root.addChild(group2); + TestDescriptor test1 = new TestDescriptorStub(rootId.append("test", "1"), "1-1"); + TestDescriptor test2 = new TestDescriptorStub(rootId.append("test", "2"), "2-2"); + group1.addChild(test1); + group2.addChild(test2); + assertTrue(validator.isAcyclic(root)); + + group2.addChild(test1); + assertFalse(validator.isAcyclic(root)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java new file mode 100644 index 00000000..c120c5c7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; + +/** + * @since 1.0 + */ +// TODO Test other adapter methods. +class ExecutionListenerAdapterTests { + + @Test + void testReportingEntryPublished() { + var testDescriptor = getSampleMethodTestDescriptor(); + + var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(), testDescriptor), mock()); + var testPlan = InternalTestPlan.from(discoveryResult); + var testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId()); + + //not yet spyable with mockito? -> https://github.com/mockito/mockito/issues/146 + var testExecutionListener = new MockTestExecutionListener(); + var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); + + var entry = ReportEntry.from("one", "two"); + executionListenerAdapter.reportingEntryPublished(testDescriptor, entry); + + assertThat(testExecutionListener.entry).isEqualTo(entry); + assertThat(testExecutionListener.testIdentifier).isEqualTo(testIdentifier); + } + + private TestDescriptor getSampleMethodTestDescriptor() { + var localMethodNamedNothing = ReflectionUtils.findMethod(this.getClass(), "nothing", new Class[0]).get(); + return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), this.getClass(), + localMethodNamedNothing); + } + + //for reflection purposes only + void nothing() { + } + + static class MockTestExecutionListener implements TestExecutionListener { + + public TestIdentifier testIdentifier; + public ReportEntry entry; + + @Override + public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { + this.testIdentifier = testIdentifier; + this.entry = entry; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java new file mode 100644 index 00000000..63182980 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.fakes.TestEngineStub; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TestExecutionListener; + +/** + * Unit tests for {@link LauncherConfig} and {@link LauncherConfig.Builder}. + * + * @since 1.3 + */ +class LauncherConfigTests { + + @Test + void preconditions() { + assertThrows(PreconditionViolationException.class, + () -> LauncherConfig.builder().addTestEngines((TestEngine[]) null)); + assertThrows(PreconditionViolationException.class, + () -> LauncherConfig.builder().addTestExecutionListeners((TestExecutionListener[]) null)); + + TestEngine engine = new TestEngineStub(); + var listener = new TestExecutionListener() { + }; + assertThrows(PreconditionViolationException.class, + () -> LauncherConfig.builder().addTestEngines(engine, engine, null)); + assertThrows(PreconditionViolationException.class, + () -> LauncherConfig.builder().addTestExecutionListeners(listener, listener, null)); + } + + @Test + void defaultConfig() { + var config = LauncherConfig.DEFAULT; + + assertTrue(config.isTestEngineAutoRegistrationEnabled(), + "Test engine auto-registration should be enabled by default"); + assertTrue(config.isLauncherDiscoveryListenerAutoRegistrationEnabled(), + "Launcher discovery listener auto-registration should be enabled by default"); + assertTrue(config.isTestExecutionListenerAutoRegistrationEnabled(), + "Test execution listener auto-registration should be enabled by default"); + assertTrue(config.isPostDiscoveryFilterAutoRegistrationEnabled(), + "Post-discovery filter auto-registration should be enabled by default"); + + assertThat(config.getAdditionalTestEngines()).isEmpty(); + + assertThat(config.getAdditionalTestExecutionListeners()).isEmpty(); + } + + @Test + void disableTestEngineAutoRegistration() { + var config = LauncherConfig.builder().enableTestEngineAutoRegistration(false).build(); + + assertFalse(config.isTestEngineAutoRegistrationEnabled()); + } + + @Test + void disableLauncherDiscoveryListenerAutoRegistration() { + var config = LauncherConfig.builder().enableLauncherDiscoveryListenerAutoRegistration(false).build(); + + assertFalse(config.isLauncherDiscoveryListenerAutoRegistrationEnabled()); + } + + @Test + void disableTestExecutionListenerAutoRegistration() { + var config = LauncherConfig.builder().enableTestExecutionListenerAutoRegistration(false).build(); + + assertFalse(config.isTestExecutionListenerAutoRegistrationEnabled()); + } + + @Test + void disablePostDiscoveryFilterAutoRegistration() { + var config = LauncherConfig.builder().enablePostDiscoveryFilterAutoRegistration(false).build(); + + assertFalse(config.isPostDiscoveryFilterAutoRegistrationEnabled()); + } + + @Test + void addTestEngines() { + TestEngine first = new TestEngineStub(); + TestEngine second = new TestEngineStub(); + + var config = LauncherConfig.builder().addTestEngines(first, second).build(); + + assertThat(config.getAdditionalTestEngines()).containsOnly(first, second); + } + + @Test + void addLauncherSessionListeners() { + var first = new LauncherSessionListener() { + }; + var second = new LauncherSessionListener() { + }; + + var config = LauncherConfig.builder().addLauncherSessionListeners(first, second).build(); + + assertThat(config.getAdditionalLauncherSessionListeners()).containsOnly(first, second); + } + + @Test + void addLauncherDiscoveryListeners() { + var first = new LauncherDiscoveryListener() { + }; + var second = new LauncherDiscoveryListener() { + }; + + var config = LauncherConfig.builder().addLauncherDiscoveryListeners(first, second).build(); + + assertThat(config.getAdditionalLauncherDiscoveryListeners()).containsOnly(first, second); + } + + @Test + void addTestExecutionListeners() { + var first = new TestExecutionListener() { + }; + var second = new TestExecutionListener() { + }; + + var config = LauncherConfig.builder().addTestExecutionListeners(first, second).build(); + + assertThat(config.getAdditionalTestExecutionListeners()).containsOnly(first, second); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java new file mode 100644 index 00000000..31154cfa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java @@ -0,0 +1,236 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; + +/** + * Unit tests for {@link LauncherConfigurationParameters}. + * + * @since 1.0 + */ +@SuppressWarnings("deprecation") +class LauncherConfigurationParametersTests { + + private static final String CONFIG_FILE_NAME = "test-junit-platform.properties"; + private static final String KEY = LauncherConfigurationParametersTests.class.getName(); + private static final String INHERITED_PARAM = "parent config param"; + private static final String CONFIG_PARAM = "explicit config param"; + private static final String CONFIG_FILE = "from config file"; + private static final String SYSTEM_PROPERTY = "system property"; + + @BeforeEach + @AfterEach + void reset() { + System.clearProperty(KEY); + } + + @Test + void constructorPreconditions() { + assertThrows(PreconditionViolationException.class, () -> fromMap(null)); + assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), null)); + assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), "")); + assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), " ")); + } + + @Test + void getPreconditions() { + ConfigurationParameters configParams = fromMap(Map.of()); + assertThrows(PreconditionViolationException.class, () -> configParams.get(null)); + assertThrows(PreconditionViolationException.class, () -> configParams.get("")); + assertThrows(PreconditionViolationException.class, () -> configParams.get(" ")); + } + + @Test + void noConfigParams() { + ConfigurationParameters configParams = fromMap(Map.of()); + assertThat(configParams.size()).isEqualTo(0); + assertThat(configParams.get(KEY)).isEmpty(); + assertThat(configParams.keySet()).doesNotContain(KEY); + assertThat(configParams.toString()).doesNotContain(KEY); + } + + @Test + void explicitConfigParam() { + ConfigurationParameters configParams = fromMap(Map.of(KEY, CONFIG_PARAM)); + assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(CONFIG_PARAM); + } + + @Test + void systemProperty() { + System.setProperty(KEY, SYSTEM_PROPERTY); + ConfigurationParameters configParams = fromMap(Map.of()); + assertThat(configParams.get(KEY)).contains(SYSTEM_PROPERTY); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).doesNotContain(KEY); + } + + @Test + void configFile() { + ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); + assertThat(configParams.get(KEY)).contains(CONFIG_FILE); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(CONFIG_FILE); + } + + @Test + void inherited() { + ConfigurationParameters configParams = fromMapAndParent( // + Map.of(), // + Map.of(KEY, INHERITED_PARAM)); + assertThat(configParams.get(KEY)).contains(INHERITED_PARAM); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(KEY); + } + + @Test + void explicitConfigParamOverridesSystemProperty() { + System.setProperty(KEY, SYSTEM_PROPERTY); + ConfigurationParameters configParams = fromMap(Map.of(KEY, CONFIG_PARAM)); + assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(CONFIG_PARAM); + } + + @Test + void explicitConfigParamOverridesConfigFile() { + ConfigurationParameters configParams = fromMapAndFile(Map.of(KEY, CONFIG_PARAM), CONFIG_FILE_NAME); + assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(CONFIG_PARAM); + } + + @Test + void explicitConfigParamOverridesInheritedProperty() { + System.setProperty(KEY, SYSTEM_PROPERTY); + ConfigurationParameters configParams = fromMapAndParent( // + Map.of(KEY, CONFIG_PARAM), // + Map.of(KEY, INHERITED_PARAM)); + assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(CONFIG_PARAM); + } + + @Test + void systemPropertyOverridesConfigFile() { + System.setProperty(KEY, SYSTEM_PROPERTY); + ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); + assertThat(configParams.get(KEY)).contains(SYSTEM_PROPERTY); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(CONFIG_FILE); + } + + @Test + void inheritedPropertyOverridesSystemProperty() { + System.setProperty(KEY, SYSTEM_PROPERTY); + ConfigurationParameters configParams = fromMapAndParent(Map.of(), Map.of(KEY, INHERITED_PARAM)); + assertThat(configParams.get(KEY)).contains(INHERITED_PARAM); + assertThat(configParams.keySet()).contains(KEY); + assertThat(configParams.toString()).contains(KEY); + } + + @Test + void getValueInExtensionContext() { + var request = LauncherDiscoveryRequestBuilder.request() // + .configurationParameter("thing", "one else!") // + .selectors(DiscoverySelectors.selectClass(Something.class)).build(); + var summary = new SummaryGeneratingListener(); + LauncherFactory.create().execute(request, summary); + assertEquals(0, summary.getSummary().getTestsFailedCount()); + } + + @Test + void getWithSuccessfulTransformer() { + ConfigurationParameters configParams = fromMap(Map.of(KEY, "42")); + assertThat(configParams.get(KEY, Integer::valueOf)).contains(42); + } + + @Test + void getWithErroneousTransformer() { + ConfigurationParameters configParams = fromMap(Map.of(KEY, "42")); + var exception = assertThrows(JUnitException.class, () -> configParams.get(KEY, input -> { + throw new RuntimeException("foo"); + })); + assertThat(exception).hasMessageContaining( + "Failed to transform configuration parameter with key '" + KEY + "' and initial value '42'"); + } + + @Test + void ignoresSystemPropertyAndConfigFileWhenImplicitLookupsAreDisabled() { + System.setProperty(KEY, SYSTEM_PROPERTY); + ConfigurationParameters configParams = LauncherConfigurationParameters.builder() // + .enableImplicitProviders(false) // + .build(); + assertThat(configParams.get(KEY)).isEmpty(); + } + + private static LauncherConfigurationParameters fromMap(Map map) { + return LauncherConfigurationParameters.builder().explicitParameters(map).build(); + } + + private static LauncherConfigurationParameters fromMapAndFile(Map map, String configFileName) { + return LauncherConfigurationParameters.builder() // + .explicitParameters(map) // + .configFileName(configFileName) // + .build(); + } + + private static LauncherConfigurationParameters fromMapAndParent(Map map, + Map inherited) { + var parameters = LauncherConfigurationParameters.builder() // + .explicitParameters(inherited) // + .build(); + + return LauncherConfigurationParameters.builder() // + .explicitParameters(map) // + .parentConfigurationParameters(parameters) // + .build(); + } + + private static class Mutator implements TestInstancePostProcessor { + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { + var value = context.getConfigurationParameter("thing").orElse("thing"); + Something.class.getField("thing").set(testInstance, value); + } + } + + @ExtendWith(Mutator.class) + static class Something { + + // `public` is needed for simple "Class#getField(String)" to work + public String thing = "body."; + + @Test + void some() { + assertEquals("Someone else!", "Some" + thing); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java new file mode 100644 index 00000000..f75ee101 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java @@ -0,0 +1,382 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.abortOnFailure; +import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.logging; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.MethodSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.fakes.TestEngineStub; +import org.junit.platform.launcher.DiscoveryFilterStub; +import org.junit.platform.launcher.PostDiscoveryFilterStub; + +/** + * @since 1.0 + */ +class LauncherDiscoveryRequestBuilderTests { + + @Nested + class DiscoverySelectionTests { + + @Test + void modulesAreStoredInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .selectors( + selectModule("java.base") + ).build(); + // @formatter:on + + var packageSelectors = discoveryRequest.getSelectorsByType(ModuleSelector.class).stream().map( + ModuleSelector::getModuleName).collect(toList()); + assertThat(packageSelectors).contains("java.base"); + } + + @Test + void packagesAreStoredInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .selectors( + selectPackage("org.junit.platform.engine") + ).build(); + // @formatter:on + + var packageSelectors = discoveryRequest.getSelectorsByType(PackageSelector.class).stream().map( + PackageSelector::getPackageName).collect(toList()); + assertThat(packageSelectors).contains("org.junit.platform.engine"); + } + + @Test + void classesAreStoredInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .selectors( + selectClass(LauncherDiscoveryRequestBuilderTests.class.getName()), + selectClass(SampleTestClass.class) + ) + .build(); + // @formatter:on + + List> classes = discoveryRequest.getSelectorsByType(ClassSelector.class).stream().map( + ClassSelector::getJavaClass).collect(toList()); + assertThat(classes).contains(SampleTestClass.class, LauncherDiscoveryRequestBuilderTests.class); + } + + @Test + void methodsByFullyQualifiedNameAreStoredInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .selectors(selectMethod(fullyQualifiedMethodName())) + .build(); + // @formatter:on + + var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); + assertThat(methodSelectors).hasSize(1); + + var methodSelector = methodSelectors.get(0); + assertThat(methodSelector.getJavaClass()).isEqualTo(LauncherDiscoveryRequestBuilderTests.class); + assertThat(methodSelector.getJavaMethod()).isEqualTo(fullyQualifiedMethod()); + } + + @Test + void methodsByNameAreStoredInDiscoveryRequest() throws Exception { + Class testClass = SampleTestClass.class; + var testMethod = testClass.getDeclaredMethod("test"); + + // @formatter:off + var discoveryRequest = request() + .selectors(selectMethod(SampleTestClass.class.getName(), "test")) + .build(); + // @formatter:on + + var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); + assertThat(methodSelectors).hasSize(1); + + var methodSelector = methodSelectors.get(0); + assertThat(methodSelector.getJavaClass()).isEqualTo(testClass); + assertThat(methodSelector.getJavaMethod()).isEqualTo(testMethod); + } + + @Test + void methodsByClassAreStoredInDiscoveryRequest() throws Exception { + Class testClass = SampleTestClass.class; + var testMethod = testClass.getDeclaredMethod("test"); + + // @formatter:off + var discoveryRequest = (DefaultDiscoveryRequest) request() + .selectors( + selectMethod(testClass, "test") + ).build(); + // @formatter:on + + var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); + assertThat(methodSelectors).hasSize(1); + + var methodSelector = methodSelectors.get(0); + assertThat(methodSelector.getJavaClass()).isEqualTo(testClass); + assertThat(methodSelector.getJavaMethod()).isEqualTo(testMethod); + } + + @Test + void uniqueIdsAreStoredInDiscoveryRequest() { + var id1 = UniqueId.forEngine("engine").append("foo", "id1"); + var id2 = UniqueId.forEngine("engine").append("foo", "id2"); + + // @formatter:off + var discoveryRequest = request() + .selectors( + selectUniqueId(id1), + selectUniqueId(id2) + ).build(); + // @formatter:on + + var uniqueIds = discoveryRequest.getSelectorsByType(UniqueIdSelector.class).stream().map( + UniqueIdSelector::getUniqueId).map(Object::toString).collect(toList()); + + assertThat(uniqueIds).contains(id1.toString(), id2.toString()); + } + } + + @Nested + class DiscoveryFilterTests { + + @Test + void engineFiltersAreStoredInDiscoveryRequest() { + TestEngine engine1 = new TestEngineStub("engine1"); + TestEngine engine2 = new TestEngineStub("engine2"); + TestEngine engine3 = new TestEngineStub("engine3"); + + // @formatter:off + var discoveryRequest = request() + .filters(includeEngines(engine1.getId(), engine2.getId())) + .build(); + // @formatter:on + + var filters = discoveryRequest.getEngineFilters(); + assertThat(filters).hasSize(1); + var engineFilter = filters.get(0); + assertTrue(engineFilter.apply(engine1).included()); + assertTrue(engineFilter.apply(engine2).included()); + assertTrue(engineFilter.apply(engine3).excluded()); + } + + @Test + void discoveryFiltersAreStoredInDiscoveryRequest() { + var filter1 = new DiscoveryFilterStub<>("filter1"); + var filter2 = new DiscoveryFilterStub<>("filter2"); + // @formatter:off + var discoveryRequest = request() + .filters(filter1, filter2) + .build(); + // @formatter:on + + var filters = discoveryRequest.getFiltersByType(DiscoveryFilter.class); + assertThat(filters).containsOnly(filter1, filter2); + } + + @Test + void postDiscoveryFiltersAreStoredInDiscoveryRequest() { + var postFilter1 = new PostDiscoveryFilterStub("postFilter1"); + var postFilter2 = new PostDiscoveryFilterStub("postFilter2"); + // @formatter:off + var discoveryRequest = request() + .filters(postFilter1, postFilter2) + .build(); + // @formatter:on + + var filters = discoveryRequest.getPostDiscoveryFilters(); + assertThat(filters).containsOnly(postFilter1, postFilter2); + } + + @Test + void exceptionForIllegalFilterClass() { + Exception exception = assertThrows(PreconditionViolationException.class, + () -> request().filters(o -> excluded("reason"))); + + assertThat(exception).hasMessageStartingWith("Filter"); + assertThat(exception).hasMessageEndingWith( + "must implement EngineFilter, PostDiscoveryFilter, or DiscoveryFilter."); + } + } + + @Nested + class DiscoveryConfigurationParameterTests { + + @Test + void withoutConfigurationParametersSet_NoConfigurationParametersAreStoredInDiscoveryRequest() { + var discoveryRequest = request().build(); + + var configParams = discoveryRequest.getConfigurationParameters(); + assertThat(configParams.get("key")).isNotPresent(); + } + + @Test + void configurationParameterAddedDirectly_isStoredInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .configurationParameter("key", "value") + .build(); + // @formatter:on + + var configParams = discoveryRequest.getConfigurationParameters(); + assertThat(configParams.get("key")).contains("value"); + } + + @Test + void configurationParameterAddedDirectlyTwice_overridesPreviousValueInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .configurationParameter("key", "value") + .configurationParameter("key", "value-new") + .build(); + // @formatter:on + + var configParams = discoveryRequest.getConfigurationParameters(); + assertThat(configParams.get("key")).contains("value-new"); + } + + @Test + void multipleConfigurationParametersAddedDirectly_areStoredInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .configurationParameter("key1", "value1") + .configurationParameter("key2", "value2") + .build(); + // @formatter:on + + var configParams = discoveryRequest.getConfigurationParameters(); + assertThat(configParams.get("key1")).contains("value1"); + assertThat(configParams.get("key2")).contains("value2"); + } + + @Test + void configurationParameterAddedByMap_isStoredInDiscoveryRequest() { + // @formatter:off + var discoveryRequest = request() + .configurationParameters(Map.of("key", "value")) + .build(); + // @formatter:on + + var configParams = discoveryRequest.getConfigurationParameters(); + assertThat(configParams.get("key")).contains("value"); + } + + @Test + void multipleConfigurationParametersAddedByMap_areStoredInDiscoveryRequest() { + Map configurationParams = new HashMap<>(); + configurationParams.put("key1", "value1"); + configurationParams.put("key2", "value2"); + + // @formatter:off + var discoveryRequest = request() + .configurationParameters(configurationParams) + .build(); + // @formatter:on + + var configParams = discoveryRequest.getConfigurationParameters(); + assertThat(configParams.get("key1")).contains("value1"); + assertThat(configParams.get("key2")).contains("value2"); + } + } + + @Nested + class DiscoveryListenerTests { + + @Test + void usesAbortOnFailureByDefault() { + var request = request().build(); + + assertThat(request.getDiscoveryListener()).isEqualTo(abortOnFailure()); + } + + @Test + void onlyAddsAbortOnFailureOnce() { + var request = request() // + .listeners(abortOnFailure()) // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "abortOnFailure") // + .build(); + + assertThat(request.getDiscoveryListener()).isEqualTo(abortOnFailure()); + } + + @Test + void onlyAddsLoggingOnce() { + var request = request() // + .listeners(logging()) // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .build(); + + assertThat(request.getDiscoveryListener()).isEqualTo(logging()); + } + + @Test + void createsCompositeForMultipleListeners() { + var request = request() // + .listeners(logging(), abortOnFailure()) // + .build(); + + assertThat(request.getDiscoveryListener().getClass().getSimpleName()).startsWith("Composite"); + } + + } + + private static class SampleTestClass { + + @Test + void test() { + } + } + + private static String fullyQualifiedMethodName() { + return LauncherDiscoveryRequestBuilderTests.class.getName() + "#" + fullyQualifiedMethod().getName(); + } + + private static Method fullyQualifiedMethod() { + try { + return LauncherDiscoveryRequestBuilderTests.class.getDeclaredMethod("myTest"); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + void myTest() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java new file mode 100644 index 00000000..d33d8895 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java @@ -0,0 +1,255 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.net.URL; +import java.net.URLClassLoader; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryListener; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSessionListener; +import org.junit.platform.launcher.TagFilter; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestLauncherDiscoveryListener; +import org.junit.platform.launcher.TestLauncherSessionListener; +import org.junit.platform.launcher.listeners.AnotherUnusedTestExecutionListener; +import org.junit.platform.launcher.listeners.NoopTestExecutionListener; +import org.junit.platform.launcher.listeners.UnusedTestExecutionListener; + +/** + * @since 1.0 + */ +class LauncherFactoryTests { + + @Test + void preconditions() { + assertThrows(PreconditionViolationException.class, () -> LauncherFactory.create(null)); + } + + @Test + void noopTestExecutionListenerIsLoadedViaServiceApi() { + withTestServices(() -> { + var launcher = (InternalLauncher) LauncherFactory.create(); + var listeners = launcher.getTestExecutionListenerRegistry().getListeners(); + var listener = listeners.stream().filter(NoopTestExecutionListener.class::isInstance).findFirst(); + assertThat(listener).isPresent(); + }); + } + + @Test + void unusedTestExecutionListenerIsNotLoadedViaServiceApi() { + withTestServices(() -> { + var launcher = (InternalLauncher) LauncherFactory.create(); + var listeners = launcher.getTestExecutionListenerRegistry().getListeners(); + + assertThat(listeners).filteredOn(AnotherUnusedTestExecutionListener.class::isInstance).isEmpty(); + assertThat(listeners).filteredOn(UnusedTestExecutionListener.class::isInstance).isEmpty(); + assertThat(listeners).filteredOn(NoopTestExecutionListener.class::isInstance).isNotEmpty(); + }); + } + + @Test + void create() { + var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); + + var testPlan = LauncherFactory.create().discover(discoveryRequest); + var roots = testPlan.getRoots(); + assertThat(roots).hasSize(3); + + // @formatter:off + var ids = roots.stream() + .map(TestIdentifier::getUniqueId) + .collect(toList()); + // @formatter:on + + assertThat(ids).containsOnly("[engine:junit-vintage]", "[engine:junit-jupiter]", + "[engine:junit-platform-suite]"); + } + + @Test + void createWithConfig() { + var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); + + var config = LauncherConfig.builder()// + .enableTestEngineAutoRegistration(false)// + .addTestEngines(new JupiterTestEngine())// + .build(); + + var testPlan = LauncherFactory.create(config).discover(discoveryRequest); + var roots = testPlan.getRoots(); + assertThat(roots).hasSize(1); + + // @formatter:off + var ids = roots.stream() + .map(TestIdentifier::getUniqueId) + .collect(toList()); + // @formatter:on + + assertThat(ids).containsOnly("[engine:junit-jupiter]"); + } + + @Test + void createWithPostDiscoveryFilters() { + var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); + + var config = LauncherConfig.builder()// + .addPostDiscoveryFilters(TagFilter.includeTags("test-post-discovery")).build(); + + var testPlan = LauncherFactory.create(config).discover(discoveryRequest); + final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); + assertThat(vintage).isEmpty(); + + final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); + assertThat(jupiter).hasSize(1); + } + + @Test + void applyPostDiscoveryFiltersViaServiceApi() { + final var current = Thread.currentThread().getContextClassLoader(); + try { + var url = getClass().getClassLoader().getResource("testservices/"); + var classLoader = new URLClassLoader(new URL[] { url }, current); + Thread.currentThread().setContextClassLoader(classLoader); + + var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); + + var config = LauncherConfig.builder()// + .build(); + + var testPlan = LauncherFactory.create(config).discover(discoveryRequest); + final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); + assertThat(vintage).isEmpty(); + + final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); + assertThat(jupiter).hasSize(1); + } + finally { + Thread.currentThread().setContextClassLoader(current); + } + } + + @Test + void notApplyIfDisabledPostDiscoveryFiltersViaServiceApi() { + final var current = Thread.currentThread().getContextClassLoader(); + try { + var url = getClass().getClassLoader().getResource("testservices/"); + var classLoader = new URLClassLoader(new URL[] { url }, current); + Thread.currentThread().setContextClassLoader(classLoader); + + var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); + + var config = LauncherConfig.builder()// + .enablePostDiscoveryFilterAutoRegistration(false).build(); + + var testPlan = LauncherFactory.create(config).discover(discoveryRequest); + final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); + assertThat(vintage).hasSize(1); + + final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); + assertThat(jupiter).hasSize(1); + } + finally { + Thread.currentThread().setContextClassLoader(current); + } + } + + @Test + void doesNotDiscoverLauncherDiscoverRequestListenerViaServiceApiWhenDisabled() { + withTestServices(() -> { + var launcher = (InternalLauncher) LauncherFactory.create( + LauncherConfig.builder().enableLauncherDiscoveryListenerAutoRegistration(false).build()); + var launcherDiscoveryListener = launcher.getLauncherDiscoveryListenerRegistry().getCompositeListener(); + + assertThat(launcherDiscoveryListener).isSameAs(LauncherDiscoveryListener.NOOP); + }); + } + + @Test + void discoversLauncherDiscoverRequestListenerViaServiceApiByDefault() { + withTestServices(() -> { + var launcher = (InternalLauncher) LauncherFactory.create(); + var launcherDiscoveryListener = launcher.getLauncherDiscoveryListenerRegistry().getCompositeListener(); + + assertThat(launcherDiscoveryListener.getClass().getSimpleName()).startsWith("Composite"); + assertThat(launcherDiscoveryListener).extracting("listeners").asList() // + .contains(new TestLauncherDiscoveryListener()); + }); + } + + @Test + void doesNotDiscoverLauncherSessionListenerViaServiceApiWhenDisabled() { + withTestServices(() -> { + var session = (DefaultLauncherSession) LauncherFactory.openSession( + LauncherConfig.builder().enableLauncherSessionListenerAutoRegistration(false).build()); + + assertThat(session.getListener()).isSameAs(LauncherSessionListener.NOOP); + }); + } + + @Test + void discoversLauncherSessionListenerViaServiceApiByDefault() { + withTestServices(() -> { + var session = (DefaultLauncherSession) LauncherFactory.openSession(); + + assertThat(session.getListener()).isEqualTo(new TestLauncherSessionListener()); + }); + } + + private static void withTestServices(Runnable runnable) { + var current = Thread.currentThread().getContextClassLoader(); + try { + var url = LauncherFactoryTests.class.getClassLoader().getResource("testservices/"); + var classLoader = new URLClassLoader(new URL[] { url }, current); + Thread.currentThread().setContextClassLoader(classLoader); + runnable.run(); + } + finally { + Thread.currentThread().setContextClassLoader(current); + } + } + + private LauncherDiscoveryRequest createLauncherDiscoveryRequestForBothStandardEngineExampleClasses() { + // @formatter:off + return request() + .selectors(selectClass(JUnit4Example.class)) + .selectors(selectClass(JUnit5Example.class)) + .build(); + // @formatter:on + } + + public static class JUnit4Example { + + @org.junit.Test + public void testJ4() { + } + + } + + static class JUnit5Example { + + @Tag("test-post-discovery") + @Test + void testJ5() { + } + + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java new file mode 100644 index 00000000..9cc2063d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncherConfigBuilderWithDisabledServiceLoading; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.fakes.TestEngineStub; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.mockito.ArgumentCaptor; + +class LauncherSessionTests { + + LauncherSessionListener firstSessionListener = mock(LauncherSessionListener.class); + LauncherSessionListener secondSessionListener = mock(LauncherSessionListener.class); + LauncherConfig launcherConfig = createLauncherConfigBuilderWithDisabledServiceLoading() // + .addLauncherSessionListeners(firstSessionListener, secondSessionListener) // + .addTestEngines(new TestEngineStub()) // + .build(); + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request().build(); + + @Test + void callsRegisteredListenersWhenLauncherIsUsedDirectly() { + var launcher = LauncherFactory.create(launcherConfig); + + var testPlan = launcher.discover(request); + + var inOrder = inOrder(firstSessionListener, secondSessionListener); + var launcherSession = ArgumentCaptor.forClass(LauncherSession.class); + inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); + inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); + inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); + inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); + + launcher.execute(testPlan); + + inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); + inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); + inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); + inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); + + launcher.execute(request); + + inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); + inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); + inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); + inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); + } + + @Test + void callsRegisteredListenersWhenLauncherIsUsedViaSession() { + @SuppressWarnings("resource") + var session = LauncherFactory.openSession(launcherConfig); + var launcher = session.getLauncher(); + + var inOrder = inOrder(firstSessionListener, secondSessionListener); + inOrder.verify(firstSessionListener).launcherSessionOpened(session); + inOrder.verify(secondSessionListener).launcherSessionOpened(session); + verifyNoMoreInteractions(firstSessionListener, secondSessionListener); + + var testPlan = launcher.discover(request); + + verifyNoMoreInteractions(firstSessionListener, secondSessionListener); + + launcher.execute(testPlan); + + verifyNoMoreInteractions(firstSessionListener, secondSessionListener); + + launcher.execute(request); + + verifyNoMoreInteractions(firstSessionListener, secondSessionListener); + + session.close(); + + inOrder.verify(firstSessionListener).launcherSessionClosed(session); + inOrder.verify(secondSessionListener).launcherSessionClosed(session); + verifyNoMoreInteractions(firstSessionListener, secondSessionListener); + } + + @Test + void closedSessionCannotBeUsed() { + @SuppressWarnings("resource") + var session = LauncherFactory.openSession(launcherConfig); + var launcher = session.getLauncher(); + var testPlan = launcher.discover(request); + + session.close(); + + assertThrows(PreconditionViolationException.class, () -> launcher.discover(request)); + assertThrows(PreconditionViolationException.class, () -> launcher.execute(testPlan)); + assertThrows(PreconditionViolationException.class, () -> launcher.execute(request)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java new file mode 100644 index 00000000..664076d2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +public class ListenerRegistryTests { + + @Test + void registerWithNullArray() { + var registry = ListenerRegistry.create(l -> l.get(0)); + + var exception = assertThrows(PreconditionViolationException.class, () -> registry.addAll((Object[]) null)); + + assertThat(exception).hasMessageContaining("listeners array must not be null or empty"); + } + + @Test + void registerWithEmptyArray() { + var registry = ListenerRegistry.create(l -> l.get(0)); + + var exception = assertThrows(PreconditionViolationException.class, registry::addAll); + + assertThat(exception).hasMessageContaining("listeners array must not be null or empty"); + } + + @Test + void registerWithArrayContainingNullElements() { + var registry = ListenerRegistry.create(l -> l.get(0)); + + var exception = assertThrows(PreconditionViolationException.class, + () -> registry.addAll(new Object[] { null })); + + assertThat(exception).hasMessageContaining("individual listeners must not be null"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java new file mode 100644 index 00000000..cd70637e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; +import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; +import org.junit.platform.launcher.LauncherConstants; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.mockito.ArgumentCaptor; + +/** + * @since 1.3 + */ +class StreamInterceptingTestExecutionListenerIntegrationTests { + + @ParameterizedTest(name = "{0}") + @MethodSource("systemStreams") + @ExtendWith(HiddenSystemOutAndErr.class) + void interceptsStream(String configParam, Supplier printStreamSupplier, String reportKey) { + var engine = new DemoHierarchicalTestEngine("engine"); + TestDescriptor test = engine.addTest("test", () -> printStreamSupplier.get().print("4567890")); + var listener = mock(TestExecutionListener.class); + doAnswer(invocation -> { + TestIdentifier testIdentifier = invocation.getArgument(0); + if (testIdentifier.getUniqueIdObject().equals(test.getUniqueId())) { + printStreamSupplier.get().print("123"); + } + return null; + }).when(listener).executionStarted(any()); + + var launcher = createLauncher(engine); + var discoveryRequest = request()// + .selectors(selectUniqueId(test.getUniqueId()))// + .configurationParameter(configParam, String.valueOf(true))// + .configurationParameter(LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME, String.valueOf(5))// + .build(); + launcher.execute(discoveryRequest, listener); + + var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); + var inOrder = inOrder(listener); + inOrder.verify(listener).testPlanExecutionStarted(testPlanArgumentCaptor.capture()); + var testPlan = testPlanArgumentCaptor.getValue(); + var testIdentifier = testPlan.getTestIdentifier(test.getUniqueId()); + + var reportEntryArgumentCaptor = ArgumentCaptor.forClass(ReportEntry.class); + inOrder.verify(listener).reportingEntryPublished(same(testIdentifier), reportEntryArgumentCaptor.capture()); + inOrder.verify(listener).executionFinished(testIdentifier, successful()); + var reportEntry = reportEntryArgumentCaptor.getValue(); + + assertThat(reportEntry.getKeyValuePairs()).containsExactly(entry(reportKey, "12345")); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("systemStreams") + @ExtendWith(HiddenSystemOutAndErr.class) + void doesNotInterceptStreamWhenAlreadyBeingIntercepted(String configParam, + Supplier printStreamSupplier) { + var engine = new DemoHierarchicalTestEngine("engine"); + TestDescriptor test = engine.addTest("test", () -> printStreamSupplier.get().print("1234567890")); + + assertThat(StreamInterceptor.registerStdout(1)).isPresent(); + assertThat(StreamInterceptor.registerStderr(1)).isPresent(); + + var launcher = createLauncher(engine); + var discoveryRequest = request()// + .selectors(selectUniqueId(test.getUniqueId()))// + .configurationParameter(configParam, String.valueOf(true))// + .build(); + var listener = mock(TestExecutionListener.class); + launcher.execute(discoveryRequest, listener); + + verify(listener, never()).reportingEntryPublished(any(), any()); + } + + @SuppressWarnings("unused") // used via @MethodSource("systemStreams") + private static Stream systemStreams() { + return Stream.of(// + streamType(CAPTURE_STDOUT_PROPERTY_NAME, () -> System.out, STDOUT_REPORT_ENTRY_KEY), // + streamType(CAPTURE_STDERR_PROPERTY_NAME, () -> System.err, STDERR_REPORT_ENTRY_KEY)); + } + + private static Arguments streamType(String configParam, Supplier printStreamSupplier, + String reportKey) { + return arguments(configParam, printStreamSupplier, reportKey); + } + + static class HiddenSystemOutAndErr implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + private static final Namespace NAMESPACE = Namespace.create(HiddenSystemOutAndErr.class); + + @Override + public void beforeTestExecution(ExtensionContext context) { + var store = context.getStore(NAMESPACE); + store.put("out", System.out); + store.put("err", System.err); + System.setOut(new PrintStream(new ByteArrayOutputStream())); + System.setErr(new PrintStream(new ByteArrayOutputStream())); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + var store = context.getStore(NAMESPACE); + System.setOut(store.get("out", PrintStream.class)); + System.setErr(store.get("err", PrintStream.class)); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java new file mode 100644 index 00000000..c3ba0f96 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.3 + */ +class StreamInterceptorTests { + + private ByteArrayOutputStream originalOut = new ByteArrayOutputStream(); + private PrintStream targetStream = new PrintStream(originalOut); + + @Test + void interceptsWriteOperationsToStreamPerThread() { + var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + 3).orElseThrow(RuntimeException::new); + // @formatter:off + IntStream.range(0, 1000) + .parallel() + .peek(i -> targetStream.println(i)) + .mapToObj(String::valueOf) + .peek(i -> streamInterceptor.capture()) + .peek(i -> targetStream.println(i)) + .forEach(i -> assertEquals(i, streamInterceptor.consume().trim())); + // @formatter:on + } + + @Test + void unregisterRestoresOriginalStream() { + var originalStream = targetStream; + + var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + 3).orElseThrow(RuntimeException::new); + assertSame(streamInterceptor, targetStream); + + streamInterceptor.unregister(); + assertSame(originalStream, targetStream); + } + + @Test + void writeForwardsOperationsToOriginalStream() throws IOException { + var originalStream = targetStream; + + StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 2).orElseThrow( + RuntimeException::new); + assertNotSame(originalStream, targetStream); + + targetStream.write('a'); + targetStream.write("b".getBytes()); + targetStream.write("c".getBytes(), 0, 1); + assertEquals("abc", originalOut.toString()); + } + + @Test + void handlesNestedCaptures() { + var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + 100).orElseThrow(RuntimeException::new); + + String outermost, inner, innermost; + + streamInterceptor.capture(); + streamInterceptor.print("before outermost - "); + { + streamInterceptor.capture(); + streamInterceptor.print("before inner - "); + { + streamInterceptor.capture(); + streamInterceptor.print("innermost"); + innermost = streamInterceptor.consume(); + } + streamInterceptor.print("after inner"); + inner = streamInterceptor.consume(); + } + streamInterceptor.print("after outermost"); + outermost = streamInterceptor.consume(); + + assertAll(// + () -> assertEquals("before outermost - after outermost", outermost), // + () -> assertEquals("before inner - after inner", inner), // + () -> assertEquals("innermost", innermost) // + ); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java new file mode 100644 index 00000000..c3d6cfac --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java @@ -0,0 +1,17 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import org.junit.platform.launcher.TestExecutionListener; + +public class AnotherUnusedTestExecutionListener implements TestExecutionListener { + // empty on purpose +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java new file mode 100644 index 00000000..d471e6e7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import org.junit.platform.launcher.TestExecutionListener; + +/** + * @since 1.0 + */ +public class NoopTestExecutionListener implements TestExecutionListener { + // empty on purpose +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java new file mode 100644 index 00000000..a876c5bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +class OutputDirTests { + + @Test + void getOutputDirUsesCustomOutputDir() throws Exception { + String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + Path outputDir = OutputDir.create(Optional.of(customDir)).toPath(); + assertThat(Files.isSameFile(Paths.get(customDir), outputDir)).isTrue(); + assertThat(outputDir).exists(); + } + + @Test + void getOutputDirFallsBackToCurrentWorkingDir() throws Exception { + String cwd = "src/test/resources/listeners/uidtracking"; + String expected = cwd; + + assertOutputDirIsDetected(cwd, expected); + } + + @Test + void getOutputDirDetectsMavenPom() throws Exception { + String cwd = "src/test/resources/listeners/uidtracking/maven"; + String expected = cwd + "/target"; + + assertOutputDirIsDetected(cwd, expected); + } + + @Test + void getOutputDirDetectsGradleGroovyDefaultBuildScript() throws Exception { + String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy"; + String expected = cwd + "/build"; + + assertOutputDirIsDetected(cwd, expected); + } + + @Test + void getOutputDirDetectsGradleGroovyCustomBuildScript() throws Exception { + String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy/sub-project"; + String expected = cwd + "/build"; + + assertOutputDirIsDetected(cwd, expected); + } + + @Test + void getOutputDirDetectsGradleKotlinDefaultBuildScript() throws Exception { + String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin"; + String expected = cwd + "/build"; + + assertOutputDirIsDetected(cwd, expected); + } + + @Test + void getOutputDirDetectsGradleKotlinCustomBuildScript() throws Exception { + String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project"; + String expected = cwd + "/build"; + + assertOutputDirIsDetected(cwd, expected); + } + + private void assertOutputDirIsDetected(String cwd, String expected) throws IOException { + Path outputDir = OutputDir.createSafely(Optional.empty(), () -> Paths.get(cwd)).toPath(); + assertThat(Files.isSameFile(Paths.get(expected), outputDir)).isTrue(); + assertThat(outputDir).exists(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java new file mode 100644 index 00000000..7c947bf1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java @@ -0,0 +1,277 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; +import static org.mockito.Mockito.mock; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0 + */ +class SummaryGenerationTests { + + private final SummaryGeneratingListener listener = new SummaryGeneratingListener(); + private final TestPlan testPlan = TestPlan.from(List.of(), mock()); + + @Test + void emptyReport() { + listener.testPlanExecutionStarted(testPlan); + listener.testPlanExecutionFinished(testPlan); + + assertEquals(0, listener.getSummary().getTestsFailedCount()); + + var summaryString = summaryAsString(); + assertAll("summary", // + () -> assertTrue(summaryString.contains("Test run finished after"), "test run"), // + + () -> assertTrue(summaryString.contains("0 containers found"), "containers found"), // + () -> assertTrue(summaryString.contains("0 containers skipped"), "containers skipped"), // + () -> assertTrue(summaryString.contains("0 containers started"), "containers started"), // + () -> assertTrue(summaryString.contains("0 containers aborted"), "containers aborted"), // + () -> assertTrue(summaryString.contains("0 containers successful"), "containers successful"), // + () -> assertTrue(summaryString.contains("0 containers failed"), "containers failed"), // + + () -> assertTrue(summaryString.contains("0 tests found"), "tests found"), // + () -> assertTrue(summaryString.contains("0 tests skipped"), "tests skipped"), // + () -> assertTrue(summaryString.contains("0 tests started"), "tests started"), // + () -> assertTrue(summaryString.contains("0 tests aborted"), "tests aborted"), // + () -> assertTrue(summaryString.contains("0 tests successful"), "tests successful"), // + () -> assertTrue(summaryString.contains("0 tests failed"), "tests failed") // + ); + + assertEquals("", failuresAsString()); + } + + @Test + void reportingCorrectCounts() { + var successfulContainer = createContainerIdentifier("c1"); + var failedContainer = createContainerIdentifier("c2"); + var abortedContainer = createContainerIdentifier("c3"); + var skippedContainer = createContainerIdentifier("c4"); + + var successfulTest = createTestIdentifier("t1"); + var failedTest = createTestIdentifier("t2"); + var abortedTest = createTestIdentifier("t3"); + var skippedTest = createTestIdentifier("t4"); + + listener.testPlanExecutionStarted(testPlan); + + listener.executionSkipped(skippedContainer, "skipped"); + listener.executionSkipped(skippedTest, "skipped"); + + listener.executionStarted(successfulContainer); + listener.executionFinished(successfulContainer, TestExecutionResult.successful()); + + listener.executionStarted(successfulTest); + listener.executionFinished(successfulTest, TestExecutionResult.successful()); + + listener.executionStarted(failedContainer); + listener.executionFinished(failedContainer, TestExecutionResult.failed(new RuntimeException("failed"))); + + listener.executionStarted(failedTest); + listener.executionFinished(failedTest, TestExecutionResult.failed(new RuntimeException("failed"))); + + listener.executionStarted(abortedContainer); + listener.executionFinished(abortedContainer, TestExecutionResult.aborted(new RuntimeException("aborted"))); + + listener.executionStarted(abortedTest); + listener.executionFinished(abortedTest, TestExecutionResult.aborted(new RuntimeException("aborted"))); + + listener.testPlanExecutionFinished(testPlan); + + var summaryString = summaryAsString(); + try { + assertAll("summary", // + () -> assertTrue(summaryString.contains("4 containers found"), "containers found"), // + () -> assertTrue(summaryString.contains("1 containers skipped"), "containers skipped"), // + () -> assertTrue(summaryString.contains("3 containers started"), "containers started"), // + () -> assertTrue(summaryString.contains("1 containers aborted"), "containers aborted"), // + () -> assertTrue(summaryString.contains("1 containers successful"), "containers successful"), // + () -> assertTrue(summaryString.contains("1 containers failed"), "containers failed"), // + + () -> assertTrue(summaryString.contains("4 tests found"), "tests found"), // + () -> assertTrue(summaryString.contains("1 tests skipped"), "tests skipped"), // + () -> assertTrue(summaryString.contains("3 tests started"), "tests started"), // + () -> assertTrue(summaryString.contains("1 tests aborted"), "tests aborted"), // + () -> assertTrue(summaryString.contains("1 tests successful"), "tests successful"), // + () -> assertTrue(summaryString.contains("1 tests failed"), "tests failed") // + ); + } + catch (AssertionError error) { + System.err.println(summaryString); + throw error; + } + } + + @Test + void canGetListOfFailures() { + var failedException = new RuntimeException("Pow!"); + var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "1"), "failingTest") { + + @Override + public Optional getSource() { + return Optional.of(ClassSource.from(Object.class)); + } + }; + var failingTest = TestIdentifier.from(testDescriptor); + listener.testPlanExecutionStarted(testPlan); + listener.executionStarted(failingTest); + listener.executionFinished(failingTest, TestExecutionResult.failed(failedException)); + listener.testPlanExecutionFinished(testPlan); + final var failures = listener.getSummary().getFailures(); + assertThat(failures).hasSize(1); + assertThat(failures.get(0).getException()).isEqualTo(failedException); + assertThat(failures.get(0).getTestIdentifier()).isEqualTo(failingTest); + } + + @Test + void reportingCorrectFailures() { + var iaeCausedBy = new IllegalArgumentException("Illegal Argument Exception"); + var failedException = new RuntimeException("Runtime Exception", iaeCausedBy); + var npeSuppressed = new NullPointerException("Null Pointer Exception"); + failedException.addSuppressed(npeSuppressed); + + var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { + + @Override + public Optional getSource() { + return Optional.of(ClassSource.from(Object.class)); + } + }; + var failed = TestIdentifier.from(testDescriptor); + var aborted = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("root", "3"), "abortedTest")); + + listener.testPlanExecutionStarted(testPlan); + listener.executionStarted(failed); + listener.executionFinished(failed, TestExecutionResult.failed(failedException)); + listener.executionStarted(aborted); + listener.executionFinished(aborted, TestExecutionResult.aborted(new RuntimeException("aborted"))); + listener.testPlanExecutionFinished(testPlan); + + // An aborted test is not a failure + assertEquals(1, listener.getSummary().getTestsFailedCount()); + + var failuresString = failuresAsString(); + assertAll("failures", // + () -> assertTrue(failuresString.contains("Failures (1)"), "test failures"), // + () -> assertTrue(failuresString.contains(Object.class.getName()), "source"), // + () -> assertTrue(failuresString.contains("failingTest"), "display name"), // + () -> assertTrue(failuresString.contains("=> " + failedException), "main exception"), // + () -> assertTrue(failuresString.contains("Caused by: " + iaeCausedBy), "Caused by exception"), // + () -> assertTrue(failuresString.contains("Suppressed: " + npeSuppressed), "Suppressed exception") // + ); + } + + @Test + public void reportingCircularFailure() { + var iaeCausedBy = new IllegalArgumentException("Illegal Argument Exception"); + var failedException = new RuntimeException("Runtime Exception", iaeCausedBy); + var npeSuppressed = new NullPointerException("Null Pointer Exception"); + failedException.addSuppressed(npeSuppressed); + npeSuppressed.addSuppressed(iaeCausedBy); + + var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { + + @Override + public Optional getSource() { + return Optional.of(ClassSource.from(Object.class)); + } + }; + var failed = TestIdentifier.from(testDescriptor); + + listener.testPlanExecutionStarted(testPlan); + listener.executionStarted(failed); + listener.executionFinished(failed, TestExecutionResult.failed(failedException)); + listener.testPlanExecutionFinished(testPlan); + + assertEquals(1, listener.getSummary().getTestsFailedCount()); + + var failuresString = failuresAsString(); + assertAll("failures", // + () -> assertTrue(failuresString.contains("Suppressed: " + npeSuppressed), "Suppressed exception"), // + () -> assertTrue(failuresString.contains("Circular reference: " + iaeCausedBy), "Circular reference"), // + () -> assertFalse(failuresString.contains("Caused by: "), + "'Caused by: ' omitted because of Circular reference") // + ); + } + + @RepeatedTest(10) + void reportingConcurrentlyFinishedTests() throws Exception { + var numThreads = 250; + var testIdentifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { + @Override + public Optional getSource() { + return Optional.of(ClassSource.from(Object.class)); + } + }); + var result = TestExecutionResult.failed(new RuntimeException()); + + listener.testPlanExecutionStarted(testPlan); + executeConcurrently(numThreads, () -> { + listener.executionStarted(testIdentifier); + listener.executionFinished(testIdentifier, result); + }); + listener.testPlanExecutionFinished(testPlan); + + assertThat(listener.getSummary().getFailures()).hasSize(numThreads); + } + + private TestIdentifier createTestIdentifier(String uniqueId) { + var identifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("test", uniqueId), uniqueId)); + testPlan.addInternal(identifier); + return identifier; + } + + private TestIdentifier createContainerIdentifier(String uniqueId) { + var identifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("container", uniqueId), uniqueId) { + + @Override + public Type getType() { + return Type.CONTAINER; + } + }); + testPlan.addInternal(identifier); + return identifier; + } + + private String summaryAsString() { + var summaryWriter = new StringWriter(); + listener.getSummary().printTo(new PrintWriter(summaryWriter)); + return summaryWriter.toString(); + } + + private String failuresAsString() { + var failuresWriter = new StringWriter(); + listener.getSummary().printFailuresTo(new PrintWriter(failuresWriter)); + return failuresWriter.toString(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java new file mode 100644 index 00000000..86943a9c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java @@ -0,0 +1,343 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.platform.commons.util.FunctionUtils.where; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX; +import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.LISTENER_ENABLED_PROPERTY_NAME; +import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME; +import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME; +import static org.junit.platform.testkit.engine.Event.byTestDescriptor; +import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.stream.Stream; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Event; +import org.opentest4j.AssertionFailedError; +import org.opentest4j.TestAbortedException; + +/** + * Integration tests for the {@link UniqueIdTrackingListener}. + * + * @since 1.8 + */ +class UniqueIdTrackingListenerIntegrationTests { + + private static final String passingTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:passingTest()]"; + private static final String skippedTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:skippedTest()]"; + private static final String abortedTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:abortedTest()]"; + private static final String failingTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:failingTest()]"; + private static final String dynamicTest1 = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[test-factory:dynamicTests()]/[dynamic-test:#1]"; + private static final String dynamicTest2 = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[test-factory:dynamicTests()]/[dynamic-test:#2]"; + private static final String testA = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase2]/[method:testA()]"; + private static final String testB = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase2]/[method:testB()]"; + private static final String testC = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase3]/[method:testC()]"; + private static final String testD = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase3]/[method:testD()]"; + private static final String testE = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase4]/[method:testE()]"; + private static final String testF = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase4]/[method:testF()]"; + + private static final String[] expectedUniqueIds = { passingTest, skippedTest, abortedTest, failingTest, + dynamicTest1, dynamicTest2, testA, testB }; + + private static final String[] expectedConcurrentUniqueIds = { testA, testB, testC, testD, testE, testF }; + + @Test + void confirmExpectedUniqueIdsViaEngineTestKit() { + // @formatter:off + EngineTestKit.engine("junit-jupiter") + .selectors(selectClasses()) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(7).skipped(1).aborted(1).succeeded(5).failed(1)) + .assertEventsMatchLoosely( + event(test(uniqueId(passingTest)), finishedSuccessfully()), + event(test(uniqueId(abortedTest)), abortedWithReason(instanceOf(TestAbortedException.class))), + event(test(uniqueId(failingTest)), finishedWithFailure(instanceOf(AssertionFailedError.class))), + event(test(uniqueId(dynamicTest1)), finishedSuccessfully()), + event(test(uniqueId(dynamicTest2)), finishedSuccessfully()), + event(test(uniqueId(testA)), finishedSuccessfully()), + event(test(uniqueId(testB)), finishedSuccessfully()) + ); + // @formatter:on + } + + private Condition uniqueId(String uniqueId) { + return new Condition<>( + byTestDescriptor(where(TestDescriptor::getUniqueId, uid -> uid.toString().equals(uniqueId))), + "descriptor with uniqueId '%s'", uniqueId); + } + + @Test + void listenerIsRegisteredButDisabledByDefault() throws Exception { + long numListenersRegistered = ServiceLoader.load(TestExecutionListener.class).stream()// + .filter(provider -> UniqueIdTrackingListener.class.equals(provider.type()))// + .count(); + assertThat(numListenersRegistered).isEqualTo(1); + + String outputDir = "build"; + String prefix = DEFAULT_OUTPUT_FILE_PREFIX; + + deleteFiles(outputDir, prefix); + + try { + List actualUniqueIds = executeTests(Map.of()); + + // Sanity check using the results of our local TestExecutionListener + assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); + + // Check that files were not generated by the UniqueIdTrackingListener + assertThat(findFiles(outputDir, prefix)).isEmpty(); + } + finally { + deleteFiles(outputDir, prefix); + } + } + + @Test + void verifyUniqueIdsAreTrackedWithDefaults() throws Exception { + verifyUniqueIdsAreTracked("build", DEFAULT_OUTPUT_FILE_PREFIX, Map.of()); + } + + @Test + void verifyUniqueIdsAreTrackedWithCustomOutputFile() throws Exception { + String customPrefix = "test_ids"; + verifyUniqueIdsAreTracked("build", customPrefix, Map.of(OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); + } + + @Test + void verifyUniqueIdsAreTrackedWithCustomOutputDir() throws Exception { + String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + verifyUniqueIdsAreTracked(customDir, DEFAULT_OUTPUT_FILE_PREFIX, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir)); + } + + @Test + void verifyUniqueIdsAreTrackedWithCustomOutputFileAndCustomOutputDir() throws Exception { + String customPrefix = "test_ids"; + String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + + verifyUniqueIdsAreTracked(customDir, customPrefix, + Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir, OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); + } + + private void verifyUniqueIdsAreTracked(String outputDir, String prefix, Map configurationParameters) + throws IOException { + + configurationParameters = new HashMap<>(configurationParameters); + configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); + + deleteFiles(outputDir, prefix); + + try { + List actualUniqueIds = executeTests(configurationParameters); + + // Sanity check using the results of our local TestExecutionListener + assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); + + // Check contents of the file (or files) generated by the UniqueIdTrackingListener + assertThat(readAllFiles(outputDir, prefix)).containsExactlyInAnyOrder(expectedUniqueIds); + } + finally { + deleteFiles(outputDir, prefix); + } + } + + @Test + void verifyUniqueIdsAreTrackedWithConcurrentlyExecutingTestPlans() throws Exception { + String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + String prefix = DEFAULT_OUTPUT_FILE_PREFIX; + + Map configurationParameters = new HashMap<>(); + configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); + configurationParameters.put(OUTPUT_DIR_PROPERTY_NAME, customDir); + + deleteFiles(customDir, prefix); + + try { + Stream.of(TestCase2.class, TestCase3.class, TestCase4.class).parallel()// + .forEach(clazz -> executeTests(configurationParameters, selectClass(clazz))); + + // 3 output files should have been generated. + assertThat(findFiles(customDir, prefix)).hasSize(3); + + // Check contents of the file (or files) generated by the UniqueIdTrackingListener + assertThat(readAllFiles(customDir, prefix)).containsExactlyInAnyOrder(expectedConcurrentUniqueIds); + } + finally { + deleteFiles(customDir, prefix); + } + } + + private static List executeTests(Map configurationParameters) { + return executeTests(configurationParameters, selectClasses()); + } + + private static List executeTests(Map configurationParameters, + ClassSelector... classSelectors) { + List uniqueIds = new ArrayList<>(); + LauncherDiscoveryRequest request = request()// + .selectors(classSelectors)// + .filters(includeEngines("junit-jupiter"))// + .configurationParameters(configurationParameters)// + .build(); + LauncherFactory.create().execute(request, new TestExecutionListener() { + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + if (testIdentifier.isTest()) { + uniqueIds.add(testIdentifier.getUniqueId()); + } + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + if (testIdentifier.isTest()) { + uniqueIds.add(testIdentifier.getUniqueId()); + } + } + }); + return uniqueIds; + } + + private static ClassSelector[] selectClasses() { + return new ClassSelector[] { selectClass(TestCase1.class), selectClass(TestCase2.class) }; + } + + private static Stream findFiles(String dir, String prefix) throws IOException { + Path outputDir = Paths.get(dir); + if (!Files.exists(outputDir)) { + return Stream.empty(); + } + return Files.find(outputDir, 1, // + (path, basicFileAttributes) -> (basicFileAttributes.isRegularFile() + && path.getFileName().toString().startsWith(prefix))); + } + + private void deleteFiles(String outputDir, String prefix) throws IOException { + findFiles(outputDir, prefix).forEach(file -> { + try { + Files.deleteIfExists(file); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + } + + private Stream readAllFiles(String outputDir, String prefix) throws IOException { + return findFiles(outputDir, prefix).map(outputFile -> { + try { + return Files.readAllLines(outputFile); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }).flatMap(List::stream); + } + + // ------------------------------------------------------------------------- + + static class TestCase1 { + + @Test + void passingTest() { + } + + @Test + @Disabled("testing") + void skippedTest() { + } + + @Test + void abortedTest() { + assumeTrue(false); + } + + @Test + void failingTest() { + fail(); + } + + @TestFactory + Stream dynamicTests() { + return Stream.of("cat", "dog").map(text -> dynamicTest(text, () -> assertEquals(3, text.length()))); + } + } + + static class TestCase2 { + + @Test + void testA() { + } + + @Test + void testB() { + } + } + + static class TestCase3 { + + @Test + void testC() { + } + + @Test + void testD() { + } + } + + static class TestCase4 { + + @Test + void testE() { + } + + @Test + void testF() { + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java new file mode 100644 index 00000000..cbf7e6f9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java @@ -0,0 +1,17 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners; + +import org.junit.platform.launcher.TestExecutionListener; + +public class UnusedTestExecutionListener implements TestExecutionListener { + // empty on purpose +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java new file mode 100644 index 00000000..0ee89659 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.abortOnFailure; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.fakes.TestEngineStub; + +class AbortOnFailureLauncherDiscoveryListenerTests extends AbstractLauncherDiscoveryListenerTests { + + @Test + void abortsDiscoveryOnUnresolvedUniqueIdSelectorWithEnginePrefix() { + var engine = createEngineThatCannotResolveAnything("some-engine"); + var request = request() // + .listeners(abortOnFailure()) // + .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .build(); + var launcher = createLauncher(engine); + + var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); + assertThat(exception).hasMessage("TestEngine with ID 'some-engine' failed to discover tests"); + assertThat(exception.getCause()).hasMessage( + "UniqueIdSelector [uniqueId = [engine:some-engine]] could not be resolved"); + } + + @Test + void doesNotAbortDiscoveryOnUnresolvedUniqueIdSelectorWithoutEnginePrefix() { + var engine = createEngineThatCannotResolveAnything("some-engine"); + var request = request() // + .listeners(abortOnFailure()) // + .selectors(selectUniqueId(UniqueId.forEngine("some-other-engine"))) // + .build(); + var launcher = createLauncher(engine); + + assertDoesNotThrow(() -> launcher.discover(request)); + } + + @Test + void abortsDiscoveryOnSelectorResolutionFailure() { + var rootCause = new RuntimeException(); + var engine = createEngineThatFailsToResolveAnything("some-engine", rootCause); + var request = request() // + .listeners(abortOnFailure()) // + .selectors(selectClass(Object.class)) // + .build(); + var launcher = createLauncher(engine); + + var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); + assertThat(exception).hasMessage("TestEngine with ID 'some-engine' failed to discover tests"); + assertThat(exception.getCause()) // + .hasMessageEndingWith("resolution failed") // + .hasCauseReference(rootCause); + } + + @Test + void abortsDiscoveryOnEngineDiscoveryFailure() { + var rootCause = new RuntimeException(); + var engine = new TestEngineStub("some-engine") { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + throw rootCause; + } + }; + var request = request() // + .listeners(abortOnFailure()) // + .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .build(); + var launcher = createLauncher(engine); + + var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); + assertThat(exception) // + .hasMessage("TestEngine with ID 'some-engine' failed to discover tests") // + .hasCauseReference(rootCause); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java new file mode 100644 index 00000000..1666f66f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.discovery; + +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.SelectorResolutionResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.fakes.TestEngineStub; + +abstract class AbstractLauncherDiscoveryListenerTests { + + protected TestEngineStub createEngineThatCannotResolveAnything(String engineId) { + return new TestEngineStub(engineId) { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + discoveryRequest.getSelectorsByType(DiscoverySelector.class) // + .forEach(selector -> discoveryRequest.getDiscoveryListener().selectorProcessed(uniqueId, + selector, SelectorResolutionResult.unresolved())); + return new EngineDescriptor(uniqueId, "Some Engine"); + } + }; + } + + protected TestEngineStub createEngineThatFailsToResolveAnything(String engineId, RuntimeException rootCause) { + return new TestEngineStub(engineId) { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + discoveryRequest.getSelectorsByType(DiscoverySelector.class) // + .forEach(selector -> discoveryRequest.getDiscoveryListener().selectorProcessed(uniqueId, + selector, SelectorResolutionResult.failed(rootCause))); + return new EngineDescriptor(uniqueId, "Some Engine"); + } + }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java new file mode 100644 index 00000000..4cdecfa2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.listeners.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; + +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.fakes.TestEngineStub; + +@TrackLogRecords +public class LoggingLauncherDiscoveryListenerTests extends AbstractLauncherDiscoveryListenerTests { + + @Test + void logsWarningOnUnresolvedUniqueIdSelectorWithEnginePrefix(LogRecordListener log) { + var engine = createEngineThatCannotResolveAnything("some-engine"); + var request = request() // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .build(); + var launcher = createLauncher(engine); + + launcher.discover(request); + + assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.WARNING)) // + .extracting(LogRecord::getMessage) // + .containsExactly( + "UniqueIdSelector [uniqueId = [engine:some-engine]] could not be resolved by [engine:some-engine]"); + } + + @Test + void logsDebugMessageOnUnresolvedUniqueIdSelectorWithoutEnginePrefix(LogRecordListener log) { + var engine = createEngineThatCannotResolveAnything("some-engine"); + var request = request() // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .selectors(selectUniqueId(UniqueId.forEngine("some-other-engine"))) // + .build(); + var launcher = createLauncher(engine); + + launcher.discover(request); + + assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.FINE)) // + .extracting(LogRecord::getMessage) // + .containsExactly( + "UniqueIdSelector [uniqueId = [engine:some-other-engine]] could not be resolved by [engine:some-engine]"); + } + + @Test + void logsErrorOnSelectorResolutionFailure(LogRecordListener log) { + var rootCause = new RuntimeException(); + var engine = createEngineThatFailsToResolveAnything("some-engine", rootCause); + var request = request() // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .selectors(selectClass(Object.class)) // + .build(); + var launcher = createLauncher(engine); + + launcher.discover(request); + + assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.SEVERE)) // + .extracting(LogRecord::getMessage) // + .containsExactly( + "Resolution of ClassSelector [className = 'java.lang.Object'] by [engine:some-engine] failed"); + } + + @Test + void logsErrorOnEngineDiscoveryFailure(LogRecordListener log) { + var rootCause = new RuntimeException(); + var engine = new TestEngineStub("some-engine") { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + throw rootCause; + } + }; + var request = request() // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .build(); + var launcher = createLauncher(engine); + + launcher.discover(request); + + var logRecord = log.stream(LoggingLauncherDiscoveryListener.class, Level.SEVERE).findFirst().get(); + assertThat(logRecord.getMessage()).isEqualTo("TestEngine with ID 'some-engine' failed to discover tests"); + assertThat(logRecord.getThrown()).isSameAs(rootCause); + } + + @Test + void logsTraceMessageOnStartAndEnd(LogRecordListener log) { + var engine = new TestEngineStub("some-engine"); + var request = request() // + .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // + .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .build(); + var launcher = createLauncher(engine); + + launcher.discover(request); + + assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.FINER)) // + .extracting(LogRecord::getMessage) // + .containsExactly( // + "Test discovery started", // + "Engine [engine:some-engine] has started discovering tests", // + "Engine [engine:some-engine] has finished discovering tests", // + "Test discovery finished"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java new file mode 100644 index 00000000..3691dce2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ParserErrorTests { + + private final Parser parser = new Parser(); + + @Test + void cantParseExpressionFromNull() { + assertThat(parseErrorFromParsing(null)).contains("empty tag expression"); + } + + @Test + void emptyExpression() { + assertThat(parseErrorFromParsing("")).contains("empty tag expression"); + } + + @Test + void missingClosingParenthesis() { + assertThat(parseErrorFromParsing("(")).contains("missing closing parenthesis for '(' at index <0>"); + assertThat(parseErrorFromParsing("( foo & bar")).contains("missing closing parenthesis for '(' at index <0>"); + } + + @Test + void missingOpeningParenthesis() { + assertThat(parseErrorFromParsing(")")).contains("missing opening parenthesis for ')' at index <0>"); + assertThat(parseErrorFromParsing(" foo | bar)")).contains("missing opening parenthesis for ')' at index <10>"); + } + + @Test + void partialUnaryOperator() { + assertThat(parseErrorFromParsing("!")).contains("missing rhs operand for '!' at index <0>"); + } + + @Test + void partialBinaryOperator() { + assertThat(parseErrorFromParsing("& foo")).contains("missing lhs operand for '&' at index <0>"); + assertThat(parseErrorFromParsing("foo |")).contains("missing rhs operand for '|' at index <4>"); + } + + @ParameterizedTest + @MethodSource("data") + void acceptanceTests(String tagExpression, String parseError) { + assertThat(parseErrorFromParsing(tagExpression)).contains(parseError); + } + + @SuppressWarnings("unused") + private static Stream data() { + // @formatter:off + return Stream.of( + arguments("&", "missing lhs and rhs operand for '&' at index <0>"), + arguments("|", "missing lhs and rhs operand for '|' at index <0>"), + arguments("| |", "missing lhs and rhs operand for '|' at index <0>"), + arguments("!", "missing rhs operand for '!' at index <0>"), + arguments("foo bar", "missing operator between 'foo' at index <2> and 'bar' at index <4>"), + arguments("foo bar |", "missing rhs operand for '|' at index <8>"), + arguments("foo bar | baz", "missing operator between 'foo' at index <2> and '(bar | baz)' at index <4>"), + arguments("foo bar &", "missing rhs operand for '&' at index <8>"), + arguments("foo & (bar !)", "missing rhs operand for '!' at index <11>"), + arguments("( foo & bar ) )", "missing opening parenthesis for ')' at index <14>"), + arguments("( ( foo & bar )", "missing closing parenthesis for '(' at index <0>"), + + arguments("foo & (bar baz) |", "missing operator between 'bar' at index <9> and 'baz' at index <11>"), + + arguments("foo & (bar baz) &", "missing operator between 'bar' at index <9> and 'baz' at index <11>"), + arguments("foo & (bar |baz) &", "missing rhs operand for '&' at index <17>"), + + arguments("foo | (bar baz) &", "missing rhs operand for '&' at index <16>"), + arguments("foo | (bar baz) &quux", "missing operator between 'bar' at index <9> and '(baz & quux)' at index <11>"), + + arguments("foo & |", "missing rhs operand for '&' at index <4>"), + arguments("foo !& bar", "missing rhs operand for '!' at index <4>"), + arguments("foo !| bar", "missing rhs operand for '!' at index <4>") + ); + // @formatter:on + } + + private String parseErrorFromParsing(String tagExpression) { + try { + var parseResult = parser.parse(tagExpression); + parseResult.tagExpressionOrThrow(RuntimeException::new); + return null; + } + catch (RuntimeException ex) { + return ex.getMessage(); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java new file mode 100644 index 00000000..4b15ba0f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ParserTests { + + private final Parser parser = new Parser(); + + @Test + void notHasHigherPrecedenceThanAnd() { + assertThat(tagExpressionParsedFrom("! foo & bar")).hasToString("(!foo & bar)"); + } + + @Test + void andHasHigherPrecedenceThanOr() { + assertThat(tagExpressionParsedFrom("foo | bar & baz")).hasToString("(foo | (bar & baz))"); + } + + @Test + void notIsRightAssociative() { + assertThat(tagExpressionParsedFrom("foo &! bar")).hasToString("(foo & !bar)"); + } + + @Test + void andIsLeftAssociative() { + assertThat(tagExpressionParsedFrom("foo & bar & baz")).hasToString("((foo & bar) & baz)"); + } + + @Test + void orIsLeftAssociative() { + assertThat(tagExpressionParsedFrom("foo | bar | baz")).hasToString("((foo | bar) | baz)"); + } + + @ParameterizedTest + @MethodSource("data") + void acceptanceTests(String tagExpression, String expression) { + assertThat(tagExpressionParsedFrom(tagExpression)).hasToString(expression); + } + + @SuppressWarnings("unused") + private static Stream data() { + // @formatter:off + return Stream.of( + arguments("foo", "foo"), + arguments("! foo", "!foo"), + arguments("foo & bar", "(foo & bar)"), + arguments("foo | bar", "(foo | bar)"), + arguments("( ! foo & bar | baz)", "((!foo & bar) | baz)"), + arguments("(foo & bar ) | baz & quux", "((foo & bar) | (baz & quux))"), + arguments("! foo | bar & ! baz | ! quux | quuz & corge", "(((!foo | (bar & !baz)) | !quux) | (quuz & corge))"), + arguments("(foo & bar ) | baz & quux", "((foo & bar) | (baz & quux))"), + arguments("foo | bar & baz|quux", "((foo | (bar & baz)) | quux)"), + arguments("any()", "any()"), + arguments("! none()", "!none()") + ); + // @formatter:on + } + + private TagExpression tagExpressionParsedFrom(String tagExpression) { + return parser.parse(tagExpression).tagExpressionOrThrow( + (error) -> new RuntimeException("[" + tagExpression + "] should be parsable")); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java new file mode 100644 index 00000000..852ed6af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.engine.TestTag.create; +import static org.junit.platform.launcher.tagexpression.TagExpressions.and; +import static org.junit.platform.launcher.tagexpression.TagExpressions.any; +import static org.junit.platform.launcher.tagexpression.TagExpressions.none; +import static org.junit.platform.launcher.tagexpression.TagExpressions.not; +import static org.junit.platform.launcher.tagexpression.TagExpressions.or; +import static org.junit.platform.launcher.tagexpression.TagExpressions.tag; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.TestTag; + +class TagExpressionsTests { + + private static final TagExpression True = tags -> true; + private static final TagExpression False = tags -> false; + + @Test + void tagIsJustATestTag() { + assertThat(tag("foo")).hasToString("foo"); + } + + @Test + void rejectInvalidTestTags() { + RuntimeException expected = assertThrows(PreconditionViolationException.class, + () -> tag("tags with spaces are not allowed")); + assertThat(expected).hasMessageContaining("tags with spaces are not allowed"); + } + + @Test + void tagEvaluation() { + var tagExpression = tag("foo"); + + assertThat(tagExpression.evaluate(Set.of(create("foo")))).isTrue(); + assertThat(tagExpression.evaluate(Set.of(create("not_foo")))).isFalse(); + } + + @Test + void justConcatenateNot() { + assertThat(not(tag("foo"))).hasToString("!foo"); + assertThat(not(and(tag("foo"), tag("bar")))).hasToString("!(foo & bar)"); + assertThat(not(or(tag("foo"), tag("bar")))).hasToString("!(foo | bar)"); + } + + @Test + void notEvaluation() { + assertThat(not(True).evaluate(Set.of())).isFalse(); + assertThat(not(False).evaluate(Set.of())).isTrue(); + } + + @Test + void encloseAndWithParenthesis() { + assertThat(and(tag("foo"), tag("bar"))).hasToString("(foo & bar)"); + } + + @Test + void andEvaluation() { + assertThat(and(True, True).evaluate(Set.of())).isTrue(); + assertThat(and(True, False).evaluate(Set.of())).isFalse(); + assertThat(and(False, onEvaluateThrow()).evaluate(Set.of())).isFalse(); + } + + @Test + void encloseOrWithParenthesis() { + assertThat(or(tag("foo"), tag("bar"))).hasToString("(foo | bar)"); + } + + @Test + void orEvaluation() { + assertThat(or(False, False).evaluate(Set.of())).isFalse(); + assertThat(or(True, onEvaluateThrow()).evaluate(Set.of())).isTrue(); + assertThat(or(False, True).evaluate(Set.of())).isTrue(); + } + + @Test + void anyEvaluation() { + assertThat(any().evaluate(Set.of())).isFalse(); + assertThat(any().evaluate(Set.of(TestTag.create("foo")))).isTrue(); + } + + @Test + void noneEvaluation() { + assertThat(none().evaluate(Set.of())).isTrue(); + assertThat(none().evaluate(Set.of(TestTag.create("foo")))).isFalse(); + } + + private TagExpression onEvaluateThrow() { + return tags -> { + throw new RuntimeException("should not be evaluated"); + }; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java new file mode 100644 index 00000000..387d2986 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.Test; + +class TokenTests { + + @Test + void startIndexOfTokenString() { + assertThat(new Token(0, "!").trimmedTokenStartIndex()).isEqualTo(0); + assertThat(new Token(0, " !").trimmedTokenStartIndex()).isEqualTo(2); + assertThat(new Token(7, "!").trimmedTokenStartIndex()).isEqualTo(7); + } + + @Test + void endIndexExclusive() { + assertThat(new Token(0, "!").endIndexExclusive()).isEqualTo(1); + assertThat(new Token(0, " !").endIndexExclusive()).isEqualTo(3); + assertThat(new Token(7, "!").endIndexExclusive()).isEqualTo(8); + } + + @Test + void lastCharacterIndex() { + assertThat(new Token(0, "!").lastCharacterIndex()).isEqualTo(0); + assertThat(new Token(0, " !").lastCharacterIndex()).isEqualTo(2); + assertThat(new Token(7, "!").lastCharacterIndex()).isEqualTo(7); + } + + @Test + void concatenateTwoTokens() { + var tokens = new Tokenizer().tokenize(" ! foo"); + var one = tokens.get(0); + var two = tokens.get(1); + var joined = one.concatenate(two); + assertThat(joined.rawString).isEqualTo(" ! foo"); + assertThat(joined.startIndex).isEqualTo(0); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java new file mode 100644 index 00000000..94aa3044 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.tagexpression; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +class TokenizerTests { + + @Test + void nullContainsNoTokens() { + assertThat(tokenStringsExtractedFrom(null)).isEmpty(); + } + + @Test + void removeLeadingAndTrailingSpaces() { + assertThat(tokenStringsExtractedFrom(" tag ")).containsExactly("tag"); + } + + @Test + void notIsAReservedKeyword() { + assertThat(tokenStringsExtractedFrom("! tag")).containsExactly("!", "tag"); + assertThat(tokenStringsExtractedFrom("!tag")).containsExactly("!", "tag"); + } + + @Test + void andIsAReservedKeyword() { + assertThat(tokenStringsExtractedFrom("one & two")).containsExactly("one", "&", "two"); + assertThat(tokenStringsExtractedFrom("one&two")).containsExactly("one", "&", "two"); + } + + @Test + void orIsAReservedKeyword() { + assertThat(tokenStringsExtractedFrom("one | two")).containsExactly("one", "|", "two"); + assertThat(tokenStringsExtractedFrom("one|two")).containsExactly("one", "|", "two"); + } + + @Test + void anyAndNoneAreReservedKeywords() { + assertThat(tokenStringsExtractedFrom("!(any())")).containsExactly("!", "(", "any()", ")"); + assertThat(tokenStringsExtractedFrom("!(none())")).containsExactly("!", "(", "none()", ")"); + } + + @Test + void discoverBrackets() { + assertThat(tokenStringsExtractedFrom("()")).containsExactly("(", ")"); + assertThat(tokenStringsExtractedFrom("(tag)")).containsExactly("(", "tag", ")"); + assertThat(tokenStringsExtractedFrom("( tag )")).containsExactly("(", "tag", ")"); + assertThat(tokenStringsExtractedFrom("( foo &bar)| (baz& qux )")).containsExactly("(", "foo", "&", "bar", ")", + "|", "(", "baz", "&", "qux", ")"); + } + + @Test + void extractRawStringWithSpaceCharactersBeforeTheToken() { + assertThat(rawStringsExtractedFrom("(")).containsExactly("("); + assertThat(rawStringsExtractedFrom(" (")).containsExactly(" ("); + assertThat(rawStringsExtractedFrom(" ( foo ")).containsExactly(" (", " foo"); + assertThat(rawStringsExtractedFrom("(( (( (")).containsExactly("(", "(", " (", "(", " ("); + } + + @Test + void extractStartPositionOfRawString() { + assertThat(startIndicesExtractedFrom("(")).containsExactly(0); + assertThat(startIndicesExtractedFrom(" ( (")).containsExactly(0, 3); + assertThat(startIndicesExtractedFrom("foo &!bar")).containsExactly(0, 3, 5, 6); + } + + private Stream startIndicesExtractedFrom(String expression) { + return tokensExtractedFrom(expression).map(token -> token.startIndex); + } + + private Stream rawStringsExtractedFrom(String expression) { + return tokensExtractedFrom(expression).map(token -> token.rawString); + } + + private List tokenStringsExtractedFrom(String expression) { + return tokensExtractedFrom(expression).map(Token::string).collect(toList()); + } + + private Stream tokensExtractedFrom(String expression) { + return new Tokenizer().tokenize(expression).stream(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java new file mode 100644 index 00000000..2cf36b30 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0.3 + */ +class LegacyReportingUtilsTests { + + private TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("foo"), "Foo"); + + @Test + void legacyReportingClassNameForTestIdentifierWithoutClassSourceIsParentLegacyReportingName() { + var uniqueId = engineDescriptor.getUniqueId().append("child", "bar"); + var testDescriptor = createTestDescriptor(uniqueId, "Bar", null); + engineDescriptor.addChild(testDescriptor); + + assertThat(getClassName(engineDescriptor.getUniqueId())).isEqualTo(""); + assertThat(getClassName(uniqueId)).isEqualTo("Foo"); + + assertThat(getClassNameFromOldLocation(engineDescriptor.getUniqueId())).isEqualTo(""); + assertThat(getClassNameFromOldLocation(uniqueId)).isEqualTo("Foo"); + } + + @Test + void legacyReportingClassNameForDescendantOfTestIdentifierWithClassSourceIsClassName() { + var classUniqueId = engineDescriptor.getUniqueId().append("class", "class"); + var classDescriptor = createTestDescriptor(classUniqueId, "Class", + ClassSource.from(LegacyReportingUtilsTests.class)); + engineDescriptor.addChild(classDescriptor); + + var subUniqueId = classUniqueId.append("sub", "baz"); + var subDescriptor = createTestDescriptor(subUniqueId, "Baz", null); + classDescriptor.addChild(subDescriptor); + + var subSubUniqueId = subUniqueId.append("subsub", "qux"); + var subSubDescriptor = createTestDescriptor(subSubUniqueId, "Qux", null); + subDescriptor.addChild(subSubDescriptor); + + assertThat(getClassName(engineDescriptor.getUniqueId())).isEqualTo(""); + assertThat(getClassName(classUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); + assertThat(getClassName(subUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); + assertThat(getClassName(subSubUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); + + assertThat(getClassNameFromOldLocation(engineDescriptor.getUniqueId())).isEqualTo(""); + assertThat(getClassNameFromOldLocation(classUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); + assertThat(getClassNameFromOldLocation(subUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); + assertThat(getClassNameFromOldLocation(subSubUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); + } + + private String getClassName(UniqueId uniqueId) { + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + return LegacyReportingUtils.getClassName(testPlan, testPlan.getTestIdentifier(uniqueId)); + } + + @SuppressWarnings("deprecation") + private String getClassNameFromOldLocation(UniqueId uniqueId) { + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + return org.junit.platform.launcher.listeners.LegacyReportingUtils.getClassName(testPlan, + testPlan.getTestIdentifier(uniqueId)); + } + + private TestDescriptor createTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) { + return new AbstractTestDescriptor(uniqueId, displayName, source) { + @Override + public Type getType() { + return Type.CONTAINER_AND_TEST; + } + }; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java new file mode 100644 index 00000000..d46dad4a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; + +/** + * @since 1.0 + */ +final class IncrementingClock extends Clock { + + private final Duration duration; + private final ZoneId zone; + + private int counter; + + IncrementingClock(int start, Duration duration) { + this(start, duration, ZoneId.systemDefault()); + } + + private IncrementingClock(int start, Duration duration, ZoneId zone) { + this.counter = start; + this.duration = duration; + this.zone = zone; + } + + @Override + public Instant instant() { + return Instant.EPOCH.plus(duration.multipliedBy(counter++)); + } + + @Override + public Clock withZone(ZoneId zone) { + return new IncrementingClock(counter, duration, zone); + } + + @Override + public ZoneId getZone() { + return zone; + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java new file mode 100644 index 00000000..1ae505f8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java @@ -0,0 +1,444 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.joox.JOOX.$; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; +import static org.mockito.Mockito.mock; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.Year; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.joox.Match; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.engine.support.hierarchical.DemoEngineExecutionContext; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalContainerDescriptor; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestDescriptor; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.opentest4j.AssertionFailedError; + +/** + * Tests for {@link LegacyXmlReportGeneratingListener}. + * + * @since 1.0 + */ +class LegacyXmlReportGeneratingListenerTests { + + @TempDir + Path tempDirectory; + + @Test + void writesFileForSingleSucceedingTest() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("succeedingTest", "display<-->Name 😎", () -> { + }); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("name")).isEqualTo("dummy"); + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); + assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); + assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); + assertThat(testsuite.child("system-out").text()) // + .containsSubsequence("unique-id: [engine:dummy]", "display-name: dummy"); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("display<-->Name 😎"); + assertThat(testcase.attr("classname")).isEqualTo("dummy"); + assertThat(testcase.child("system-out").text()) // + .containsSubsequence("unique-id: [engine:dummy]/[test:succeedingTest]", + "display-name: display<-->Name 😎"); + + assertThat(testsuite.find("skipped")).isEmpty(); + assertThat(testsuite.find("failure")).isEmpty(); + assertThat(testsuite.find("error")).isEmpty(); + } + + @Test + void writesFileForSingleFailingTest() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("failingTest", () -> fail("expected to fail")); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); + assertThat(testsuite.attr("failures", int.class)).isEqualTo(1); + assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("failingTest"); + + var failure = testcase.child("failure"); + assertThat(failure.attr("message")).isEqualTo("expected to fail"); + assertThat(failure.attr("type")).isEqualTo(AssertionFailedError.class.getName()); + assertThat(failure.text()).containsSubsequence("AssertionFailedError: expected to fail", "\tat"); + + assertThat(testsuite.find("skipped")).isEmpty(); + assertThat(testsuite.find("error")).isEmpty(); + } + + @Test + void writesFileForSingleErroneousTest() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("failingTest", () -> { + throw new RuntimeException("error occurred"); + }); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); + assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); + assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("failingTest"); + + var error = testcase.child("error"); + assertThat(error.attr("message")).isEqualTo("error occurred"); + assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); + assertThat(error.text()).containsSubsequence("RuntimeException: error occurred", "\tat"); + + assertThat(testsuite.find("skipped")).isEmpty(); + assertThat(testsuite.find("failure")).isEmpty(); + } + + @Test + void writesFileForSingleSkippedTest() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + var testDescriptor = engine.addTest("skippedTest", () -> fail("never called")); + testDescriptor.markSkipped("should be skipped"); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); + assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); + assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("skippedTest"); + assertThat(testcase.child("skipped").text()).isEqualTo("should be skipped"); + + assertThat(testsuite.find("failure")).isEmpty(); + assertThat(testsuite.find("error")).isEmpty(); + } + + @SuppressWarnings("ConstantConditions") + @Test + void writesFileForSingleAbortedTest() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("abortedTest", () -> assumeFalse(true, "deliberately aborted")); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); + assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); + assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("abortedTest"); + assertThat(testcase.child("skipped").text()) // + .containsSubsequence("TestAbortedException: ", "deliberately aborted", "at "); + + assertThat(testsuite.find("failure")).isEmpty(); + assertThat(testsuite.find("error")).isEmpty(); + } + + @Test + void measuresTimesInSeconds() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("firstTest", () -> { + }); + engine.addTest("secondTest", () -> { + }); + + executeTests(engine, new IncrementingClock(0, Duration.ofMillis(333))); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + // start end + // ----------- ---------- ----------- + // engine 0 (1) 1,665 (6) + // firstTest 333 (2) 666 (3) + // secondTest 999 (4) 1,332 (5) + + assertThat(testsuite.attr("time", double.class)) // + .isEqualTo(1.665); + assertThat(testsuite.children("testcase").matchAttr("name", "firstTest").attr("time", double.class)) // + .isEqualTo(0.333); + assertThat(testsuite.children("testcase").matchAttr("name", "secondTest").attr("time", double.class)) // + .isEqualTo(0.333); + } + + @Test + void testWithImmeasurableTimeIsOutputCorrectly() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("test", () -> { + }); + + executeTests(engine, Clock.fixed(Instant.EPOCH, ZoneId.systemDefault())); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.child("testcase").attr("time")).isEqualTo("0"); + } + + @Test + void writesFileForSkippedContainer() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("test", () -> fail("never called")); + engine.getEngineDescriptor().markSkipped("should be skipped"); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("test"); + assertThat(testcase.child("skipped").text()).isEqualTo("parent was skipped: should be skipped"); + } + + @Test + void writesFileForFailingContainer() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("test", () -> fail("never called")); + engine.getEngineDescriptor().setBeforeAllBehavior(() -> fail("failure before all tests")); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("failures", int.class)).isEqualTo(1); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("test"); + + var failure = testcase.child("failure"); + assertThat(failure.attr("message")).isEqualTo("failure before all tests"); + assertThat(failure.attr("type")).isEqualTo(AssertionFailedError.class.getName()); + assertThat(failure.text()).containsSubsequence("AssertionFailedError: failure before all tests", "\tat"); + } + + @Test + void writesFileForFailingContainerWithoutTest() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addContainer("failingContainer", () -> { + throw new RuntimeException("boom"); + }); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("failingContainer"); + assertThat(testcase.attr("classname")).isEqualTo("dummy"); + + var error = testcase.child("error"); + assertThat(error.attr("message")).isEqualTo("boom"); + assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); + assertThat(error.text()).containsSubsequence("RuntimeException: boom", "\tat"); + } + + @Test + void writesFileForContainerFailingAfterTest() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + + var container = engine.addChild("failingContainer", + uniqueId -> new DemoHierarchicalContainerDescriptor(uniqueId, "failingContainer", null, null) { + @Override + public void after(DemoEngineExecutionContext context) { + throw new RuntimeException("boom"); + } + }, "child"); + container.addChild(new DemoHierarchicalTestDescriptor(container.getUniqueId().append("test", "someTest"), + "someTest", (c, t) -> { + })); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + + assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); + assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); + + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("someTest"); + assertThat(testcase.attr("classname")).isEqualTo("failingContainer"); + + var error = testcase.child("error"); + assertThat(error.attr("message")).isEqualTo("boom"); + assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); + assertThat(error.text()).containsSubsequence("RuntimeException: boom", "\tat"); + } + + @Test + void writesSystemProperties() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("test", () -> { + }); + + executeTests(engine); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + var properties = testsuite.child("properties").children("property"); + assertThat(properties.matchAttr("name", "file\\.separator").attr("value")).isEqualTo(File.separator); + assertThat(properties.matchAttr("name", "path\\.separator").attr("value")).isEqualTo(File.pathSeparator); + } + + @Test + void writesHostNameAndTimestamp() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("test", () -> { + }); + + var now = LocalDateTime.parse("2016-01-28T14:02:59.123"); + var zone = ZoneId.systemDefault(); + + executeTests(engine, Clock.fixed(ZonedDateTime.of(now, zone).toInstant(), zone)); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); + assertThat(testsuite.attr("hostname")).isEqualTo(InetAddress.getLocalHost().getHostName()); + assertThat(testsuite.attr("timestamp")).isEqualTo("2016-01-28T14:02:59"); + } + + @Test + void printsExceptionWhenReportsDirCannotBeCreated() throws Exception { + var reportsDir = tempDirectory.resolve("dummy.txt"); + Files.write(reportsDir, Set.of("content")); + + var out = new StringWriter(); + var listener = new LegacyXmlReportGeneratingListener(reportsDir, new PrintWriter(out)); + + listener.testPlanExecutionStarted(TestPlan.from(Set.of(), mock())); + + assertThat(out.toString()).containsSubsequence("Could not create reports directory", + "FileAlreadyExistsException", "at "); + } + + @Test + void printsExceptionWhenReportCouldNotBeWritten() throws Exception { + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); + + var xmlFile = tempDirectory.resolve("TEST-engine.xml"); + Files.createDirectories(xmlFile); + + var out = new StringWriter(); + var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); + + listener.testPlanExecutionStarted(TestPlan.from(Set.of(engineDescriptor), mock())); + listener.executionFinished(TestIdentifier.from(engineDescriptor), successful()); + + assertThat(out.toString()).containsSubsequence("Could not write XML report", "Exception", "at "); + } + + @Test + void writesReportEntriesToSystemOutElement() throws Exception { + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); + var childUniqueId = UniqueId.root("child", "test"); + engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + + var out = new StringWriter(); + var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); + + listener.testPlanExecutionStarted(testPlan); + var testIdentifier = testPlan.getTestIdentifier(childUniqueId); + listener.executionStarted(testIdentifier); + listener.reportingEntryPublished(testIdentifier, ReportEntry.from("foo", "bar")); + Map map = new LinkedHashMap<>(); + map.put("bar", "baz"); + map.put("qux", "foo"); + listener.reportingEntryPublished(testIdentifier, ReportEntry.from(map)); + listener.executionFinished(testIdentifier, successful()); + listener.executionFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); + + var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-engine.xml")); + + assertThat(String.join("\n", testsuite.child("testcase").children("system-out").texts())) // + .containsSubsequence( // + "Report Entry #1 (timestamp: " + Year.now(), "- foo: bar\n", + "Report Entry #2 (timestamp: " + Year.now(), "- bar: baz\n", "- qux: foo\n"); + } + + private void executeTests(TestEngine engine) { + executeTests(engine, Clock.systemDefaultZone()); + } + + private void executeTests(TestEngine engine, Clock clock) { + var out = new PrintWriter(new StringWriter()); + var reportListener = new LegacyXmlReportGeneratingListener(tempDirectory.toString(), out, clock); + var launcher = createLauncher(engine); + launcher.registerTestExecutionListeners(reportListener); + launcher.execute(request().selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))).build()); + } + + private Match readValidXmlFile(Path xmlFile) throws Exception { + assertTrue(Files.exists(xmlFile), () -> "File does not exist: " + xmlFile); + try (var reader = Files.newBufferedReader(xmlFile)) { + var xml = $(reader); + assertValidAccordingToJenkinsSchema(xml.document()); + return xml; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java new file mode 100644 index 00000000..22b3d7d0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import static org.junit.jupiter.api.Assertions.fail; + +import javax.xml.XMLConstants; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * @since 1.0 + */ +class XmlReportAssertions { + + static void assertValidAccordingToJenkinsSchema(Document document) throws Exception { + try { + // Schema is thread-safe, Validator is not + var validator = CachedSchema.JENKINS.newValidator(); + validator.validate(new DOMSource(document)); + } + catch (SAXException e) { + fail("Invalid XML document: " + document, e); + } + } + + private enum CachedSchema { + + JENKINS("/jenkins-junit.xsd"); + + private final Schema schema; + + CachedSchema(String resourcePath) { + var schemaFile = LegacyXmlReportGeneratingListener.class.getResource(resourcePath); + var schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + try { + this.schema = schemaFactory.newSchema(schemaFile); + } + catch (SAXException e) { + throw new RuntimeException("Failed to create schema using " + schemaFile, e); + } + } + + Validator newValidator() { + return schema.newValidator(); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java new file mode 100644 index 00000000..fb88bc9f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.TestExecutionResult.failed; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.mockito.Mockito.mock; + +import java.time.Clock; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0 + */ +class XmlReportDataTests { + + private final ConfigurationParameters configParams = mock(); + + @Test + void resultsOfTestIdentifierWithoutAnyReportedEventsAreEmpty() { + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); + var childUniqueId = UniqueId.root("child", "test"); + engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); + + assertThat(results).isEmpty(); + } + + @Test + void resultsOfTestIdentifierWithoutReportedEventsContainsOnlyFailureOfAncestor() { + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); + var childUniqueId = UniqueId.root("child", "test"); + engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + var failureOfAncestor = failed(new RuntimeException("failed!")); + reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), failureOfAncestor); + + var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); + + assertThat(results).containsExactly(failureOfAncestor); + } + + @Test + void resultsOfTestIdentifierWithoutReportedEventsContainsOnlySuccessOfAncestor() { + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); + var childUniqueId = UniqueId.root("child", "test"); + engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); + + var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); + + assertThat(results).containsExactly(successful()); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java new file mode 100644 index 00000000..8487afb1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java @@ -0,0 +1,280 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.legacy.xml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.joox.JOOX.$; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.TestExecutionResult.failed; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; +import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; +import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; +import static org.mockito.Mockito.mock; + +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.time.Clock; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.joox.Match; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 1.0 + */ +class XmlReportWriterTests { + + private final ConfigurationParameters configParams = mock(); + + private EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); + + @Test + void writesTestsuiteElementsWithoutTestcaseElementsWithoutAnyTests() throws Exception { + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + + var testsuite = writeXmlReport(testPlan, reportData); + + assertValidAccordingToJenkinsSchema(testsuite.document()); + assertThat(testsuite.document().getDocumentElement().getTagName()).isEqualTo("testsuite"); + assertThat(testsuite.attr("name")).isEqualTo("Engine"); + assertThat(testsuite.attr("tests", int.class)).isEqualTo(0); + assertThat(testsuite.find("testcase")).isEmpty(); + } + + @Test + void writesReportEntry() throws Exception { + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); + engineDescriptor.addChild(testDescriptor); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from("myKey", "myValue")); + reportData.markFinished(testPlan.getTestIdentifier(uniqueId), successful()); + + var testsuite = writeXmlReport(testPlan, reportData); + + assertValidAccordingToJenkinsSchema(testsuite.document()); + assertThat(String.join("\n", testsuite.find("system-out").texts())) // + .containsSubsequence("Report Entry #1 (timestamp: ", "- myKey: myValue"); + } + + @Test + void writesCapturedOutput() throws Exception { + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); + engineDescriptor.addChild(testDescriptor); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + var reportEntry = ReportEntry.from(Map.of( // + STDOUT_REPORT_ENTRY_KEY, "normal output", // + STDERR_REPORT_ENTRY_KEY, "error output", // + "foo", "bar")); + reportData.addReportEntry(TestIdentifier.from(testDescriptor), reportEntry); + reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from(Map.of("baz", "qux"))); + reportData.markFinished(testPlan.getTestIdentifier(uniqueId), successful()); + + var testsuite = writeXmlReport(testPlan, reportData); + + assertValidAccordingToJenkinsSchema(testsuite.document()); + assertThat(testsuite.find("system-out").text(0)) // + .containsSubsequence("unique-id: ", "test:test", "display-name: successfulTest"); + assertThat(testsuite.find("system-out").text(1)) // + .containsSubsequence("Report Entry #1 (timestamp: ", "- foo: bar", "Report Entry #2 (timestamp: ", + "- baz: qux"); + assertThat(testsuite.find("system-out").text(2).trim()) // + .isEqualTo("normal output"); + assertThat(testsuite.find("system-err").text().trim()) // + .isEqualTo("error output"); + } + + @Test + void writesEmptySkippedElementForSkippedTestWithoutReason() throws Exception { + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "skippedTest")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + reportData.markSkipped(testPlan.getTestIdentifier(uniqueId), null); + + var testsuite = writeXmlReport(testPlan, reportData); + + assertValidAccordingToJenkinsSchema(testsuite.document()); + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("skippedTest"); + var skipped = testcase.child("skipped"); + assertThat(skipped.size()).isEqualTo(1); + assertThat(skipped.children()).isEmpty(); + } + + @Test + void writesEmptyErrorElementForFailedTestWithoutCause() throws Exception { + engineDescriptor = new EngineDescriptor(UniqueId.forEngine("myEngineId"), "Fancy Engine") { + @Override + public String getLegacyReportingName() { + return "myEngine"; + } + }; + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "some fancy name") { + @Override + public String getLegacyReportingName() { + return "failedTest"; + } + }); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(null)); + + var testsuite = writeXmlReport(testPlan, reportData); + + assertValidAccordingToJenkinsSchema(testsuite.document()); + var testcase = testsuite.child("testcase"); + assertThat(testcase.attr("name")).isEqualTo("failedTest"); + assertThat(testcase.attr("classname")).isEqualTo("myEngine"); + var error = testcase.child("error"); + assertThat(error.size()).isEqualTo(1); + assertThat(error.children()).isEmpty(); + } + + @Test + void omitsMessageAttributeForFailedTestWithThrowableWithoutMessage() throws Exception { + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "failedTest")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(new NullPointerException())); + + var testsuite = writeXmlReport(testPlan, reportData); + + assertValidAccordingToJenkinsSchema(testsuite.document()); + var error = testsuite.find("error"); + assertThat(error.attr("type")).isEqualTo("java.lang.NullPointerException"); + assertThat(error.attr("message")).isNull(); + } + + @Test + void writesValidXmlEvenIfExceptionMessageContainsCData() throws Exception { + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + var assertionError = new AssertionError(""); + reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); + + var testsuite = writeXmlReport(testPlan, reportData); + + assertValidAccordingToJenkinsSchema(testsuite.document()); + assertThat(testsuite.find("failure").attr("message")).isEqualTo(""); + } + + @Test + void escapesInvalidCharactersInSystemPropertiesAndExceptionMessages() throws Exception { + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + var assertionError = new AssertionError("expected: but was: "); + reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); + + System.setProperty("foo.bar", "\1"); + Match testsuite; + try { + testsuite = writeXmlReport(testPlan, reportData); + } + finally { + System.getProperties().remove("foo.bar"); + } + + assertValidAccordingToJenkinsSchema(testsuite.document()); + assertThat(testsuite.find("property").matchAttr("name", "foo\\.bar").attr("value")) // + .isEqualTo(""); + var failure = testsuite.find("failure"); + assertThat(failure.attr("message")) // + .isEqualTo("expected: but was: "); + assertThat(failure.text()) // + .contains("AssertionError: expected: but was: "); + } + + @Test + void doesNotReopenCDataWithinCDataContent() throws Exception { + var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); + engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + + var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); + var assertionError = new AssertionError(""); + reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); + Writer assertingWriter = new StringWriter() { + + @SuppressWarnings("NullableProblems") + @Override + public void write(char[] buffer, int off, int len) { + assertThat(new String(buffer, off, len)).doesNotContain("]]> stringPairs() { + return Stream.of( // + arguments("\0", "�"), // + arguments("\1", ""), // + arguments("\t", "\t"), // + arguments("\r", "\r"), // + arguments("\n", "\n"), // + arguments("\u001f", ""), // + arguments("\u0020", "\u0020"), // + arguments("foo!", "foo!"), // + arguments("\uD801\uDC00", "\uD801\uDC00") // + ); + } + + private Match writeXmlReport(TestPlan testPlan, XmlReportData reportData) throws Exception { + var out = new StringWriter(); + writeXmlReport(testPlan, reportData, out); + return $(new StringReader(out.toString())); + } + + private void writeXmlReport(TestPlan testPlan, XmlReportData reportData, Writer out) throws Exception { + new XmlReportWriter(reportData).writeXmlReport(getOnlyElement(testPlan.getRoots()), out); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java new file mode 100644 index 00000000..99398dbc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.open.xml; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME; +import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.OUTPUT_DIR_PROPERTY_NAME; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; +import org.opentest4j.reporting.tooling.validator.DefaultValidator; +import org.opentest4j.reporting.tooling.validator.ValidationResult; +import org.xmlunit.assertj3.XmlAssert; +import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; + +/** + * Tests for {@link OpenTestReportGeneratingListener}. + * + * @since 1.9 + */ +public class OpenTestReportGeneratingListenerTests { + + @TempDir(cleanup = ON_SUCCESS) + Path tempDirectory; + + @Test + void writesValidXmlReport() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("failingTest", "display<-->Name 😎", (context, descriptor) -> { + var listener = context.request.getEngineExecutionListener(); + listener.reportingEntryPublished(descriptor, ReportEntry.from("key", "value")); + fail("failure message"); + }); + + executeTests(engine); + + var xmlFile = findXmlReport(); + assertThat(validate(xmlFile)).isEmpty(); + + var expected = """ + + + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + + + + + [engine:dummy] + dummy + CONTAINER + + + + + [engine:dummy]/[test:failingTest] + display<-->Name 😎 + TEST + + + + + + value + + + + + + + ${xmlunit.matchesRegex(org\\.opentest4j\\.AssertionFailedError: failure message)} + + + + + + + + """; + + XmlAssert.assertThat(xmlFile).and(expected) // + .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // + .ignoreWhitespace() // + .areIdentical(); + } + + private ValidationResult validate(Path xmlFile) throws URISyntaxException { + var catalogUri = requireNonNull(getClass().getResource("catalog.xml")).toURI(); + return new DefaultValidator(catalogUri).validate(xmlFile); + } + + private void executeTests(TestEngine engine) { + var build = request() // + .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .configurationParameter(ENABLED_PROPERTY_NAME, String.valueOf(true)) // + .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, tempDirectory.toString()) // + .build(); + createLauncher(engine).execute(build, new OpenTestReportGeneratingListener()); + } + + private Path findXmlReport() throws IOException { + try (var stream = Files.list(tempDirectory)) { + return stream.findAny().orElseThrow(AssertionError::new); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java new file mode 100644 index 00000000..a515e6dd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java @@ -0,0 +1,901 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.runner; + +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; +import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.junit.runner.Description.createSuiteDescription; +import static org.junit.runner.Description.createTestDescription; +import static org.junit.runner.manipulation.Filter.matchMethodDescription; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.Filter; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.PackageNameFilter; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestDescriptor; +import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.fakes.TestEngineStub; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.suite.api.ExcludeClassNamePatterns; +import org.junit.platform.suite.api.ExcludeEngines; +import org.junit.platform.suite.api.ExcludePackages; +import org.junit.platform.suite.api.ExcludeTags; +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.IncludePackages; +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.SuiteDisplayName; +import org.junit.platform.suite.api.UseTechnicalNames; +import org.junit.runner.Description; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.mockito.ArgumentCaptor; + +/** + * Tests for the {@link JUnitPlatform} runner. + * + * @since 1.0 + */ +@Tag("junit4") +@SuppressWarnings("deprecation") +class JUnitPlatformRunnerTests { + + @Nested + class Discovery { + + @Test + void requestsClassSelectorForAnnotatedClassWhenNoAdditionalAnnotationsArePresent() { + + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var selectors = request.getSelectorsByType(ClassSelector.class); + assertThat(selectors).hasSize(1); + var classSelector = getOnlyElement(selectors); + assertEquals(TestCase.class, classSelector.getJavaClass()); + } + + @Test + void requestsClassSelectorsWhenSelectClassesAnnotationIsPresent() { + + @SelectClasses({ Short.class, Byte.class }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var selectors = request.getSelectorsByType(ClassSelector.class); + assertThat(selectors).hasSize(2); + assertEquals(Short.class, selectors.get(0).getJavaClass()); + assertEquals(Byte.class, selectors.get(1).getJavaClass()); + } + + @Test + void updatesIncludeClassNameFilterWhenSelectClassesAnnotationIsPresent() { + + @SelectClasses({ Short.class, Byte.class }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).hasSize(1); + + var filter = filters.get(0); + + // Excluded by default + assertExcludes(filter, "example.MyClass"); + assertExcludes(filter, "example.MyTestClass"); + assertExcludes(filter, "example.Short"); + assertExcludes(filter, "example.Byte"); + + // Included due to ClassNameFilter.STANDARD_INCLUDE_PATTERN + assertIncludes(filter, "TestClass"); + assertIncludes(filter, "example.TestClass"); + assertIncludes(filter, "example.MyTests"); + assertIncludes(filter, "example.MyTest"); + + // Included due to @SelectClasses({ Short.class, Byte.class }) + assertIncludes(filter, Short.class.getName()); + assertIncludes(filter, Byte.class.getName()); + } + + @Test + void requestsPackageSelectorsWhenPackagesAnnotationIsPresent() { + + @SelectPackages({ "foo", "bar" }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var selectors = request.getSelectorsByType(PackageSelector.class); + assertThat(selectors).hasSize(2); + assertEquals("foo", selectors.get(0).getPackageName()); + assertEquals("bar", selectors.get(1).getPackageName()); + } + + @Test + void addsPackageFiltersToRequestWhenIncludePackageAnnotationIsPresent() { + + @IncludePackages({ "includedpackage1", "includedpackage2" }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(PackageNameFilter.class); + assertThat(filters).hasSize(1); + + var filter = filters.get(0); + assertIncludes(filter, "includedpackage1.TestClass"); + assertIncludes(filter, "includedpackage2.TestClass"); + assertExcludes(filter, "excludedpackage1.TestClass"); + } + + @Test + void addsPackageFiltersToRequestWhenExcludePackageAnnotationIsPresent() { + + @ExcludePackages({ "excludedpackage1", "excludedpackage2" }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(PackageNameFilter.class); + assertThat(filters).hasSize(1); + + var filter = filters.get(0); + assertIncludes(filter, "includedpackage1.TestClass"); + assertExcludes(filter, "excludedpackage1.TestClass"); + assertExcludes(filter, "excludedpackage2.TestClass"); + } + + @Test + void addsTagFilterToRequestWhenIncludeTagsAnnotationIsPresent() { + + @IncludeTags({ "foo", "bar" }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getPostDiscoveryFilters(); + assertThat(filters).hasSize(1); + + var filter = filters.get(0); + assertIncludes(filter, testDescriptorWithTags("foo")); + assertIncludes(filter, testDescriptorWithTags("bar")); + assertExcludes(filter, testDescriptorWithTags("baz")); + } + + @Test + void addsTagFilterToRequestWhenExcludeTagsAnnotationIsPresent() { + + @ExcludeTags({ "foo", "bar" }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getPostDiscoveryFilters(); + assertThat(filters).hasSize(1); + + var filter = filters.get(0); + assertExcludes(filter, testDescriptorWithTags("foo")); + assertExcludes(filter, testDescriptorWithTags("bar")); + assertIncludes(filter, testDescriptorWithTags("baz")); + } + + @Test + void includeTagsAcceptsTagExpressions() { + + @IncludeTags("foo & !bar") + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getPostDiscoveryFilters(); + assertThat(filters).hasSize(1); + + var filter = filters.get(0); + assertIncludes(filter, testDescriptorWithTags("foo")); + assertIncludes(filter, testDescriptorWithTags("foo", "any_other_tag")); + assertExcludes(filter, testDescriptorWithTags("foo", "bar")); + assertExcludes(filter, testDescriptorWithTags("bar")); + assertExcludes(filter, testDescriptorWithTags("bar", "any_other_tag")); + } + + @Test + void excludeTagsAcceptsTagExpressions() { + + @ExcludeTags("foo & !bar") + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getPostDiscoveryFilters(); + assertThat(filters).hasSize(1); + + var filter = filters.get(0); + assertExcludes(filter, testDescriptorWithTags("foo")); + assertExcludes(filter, testDescriptorWithTags("foo", "any_other_tag")); + assertIncludes(filter, testDescriptorWithTags("foo", "bar")); + assertIncludes(filter, testDescriptorWithTags("bar")); + assertIncludes(filter, testDescriptorWithTags("bar", "any_other_tag")); + } + + @Test + void addsEngineFiltersToRequestWhenIncludeEnginesOrExcludeEnginesAnnotationsArePresent() { + + @IncludeEngines({ "foo", "bar", "baz" }) + @ExcludeEngines({ "bar", "quux" }) + class TestCase { + } + + TestEngine fooEngine = new TestEngineStub("foo"); + TestEngine barEngine = new TestEngineStub("bar"); + TestEngine bazEngine = new TestEngineStub("baz"); + TestEngine quuxEngine = new TestEngineStub("quux"); + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getEngineFilters(); + assertThat(filters).hasSize(2); + + var includeFilter = filters.get(1); + assertIncludes(includeFilter, fooEngine); + assertIncludes(includeFilter, barEngine); + assertIncludes(includeFilter, bazEngine); + assertExcludes(includeFilter, quuxEngine); + + var excludeFilter = filters.get(0); + assertIncludes(excludeFilter, fooEngine); + assertExcludes(excludeFilter, barEngine); + assertIncludes(excludeFilter, bazEngine); + assertExcludes(excludeFilter, quuxEngine); + } + + @Test + void addsDefaultClassNameFilterToRequestWhenFilterClassNameAnnotationIsNotPresentOnTestSuite() { + + @SelectPackages("foo") + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains(STANDARD_INCLUDE_PATTERN); + } + + @Test + void addsDefaultClassNameFilterToRequestWhenFilterClassNameAnnotationIsNotPresentOnTestClass() { + + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).isEmpty(); + } + + @Test + void addsSingleExplicitClassNameFilterToRequestWhenIncludeClassNamePatternsAnnotationIsPresent() { + + @IncludeClassNamePatterns(".*Foo") + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains(".*Foo"); + } + + @Test + void addsSingleClassNameFilterToRequestWhenExcludeClassNamePatternsAnnotationIsPresent() { + + @ExcludeClassNamePatterns(".*Foo") + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains(".*Foo"); + } + + @Test + void addsMultipleExplicitClassNameFilterToRequestWhenIncludeClassNamePatternsAnnotationIsPresent() { + + @IncludeClassNamePatterns({ ".*Foo", "Bar.*" }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains(".*Foo", "Bar.*"); + } + + @Test + void addsMultipleClassNameFilterToRequestWhenExcludeClassNamePatternsAnnotationIsPresent() { + + @ExcludeClassNamePatterns({ ".*Foo", "Bar.*" }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains(".*Foo", "Bar.*"); + } + + @Test + void usesStandardIncludePatternWhenIncludeClassNamePatternsAnnotationIsPresentWithoutArguments() { + + @IncludeClassNamePatterns + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains(STANDARD_INCLUDE_PATTERN); + } + + @Test + void doesNotAddClassNameFilterWhenIncludeClassNamePatternsAnnotationIsPresentWithEmptyArguments() { + + @IncludeClassNamePatterns({}) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).isEmpty(); + } + + @Test + void doesNotAddClassNameFilterWhenExcludeClassNamePatternsAnnotationIsPresentWithEmptyArguments() { + + @ExcludeClassNamePatterns({}) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(filters).isEmpty(); + } + + @Test + void trimsArgumentsOfIncludeClassNamePatternsAnnotation() { + + @IncludeClassNamePatterns({ " foo", "bar " }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains("'foo'", "'bar'"); + } + + @Test + void trimsArgumentsOfExcludeClassNamePatternsAnnotation() { + + @ExcludeClassNamePatterns({ " foo", "bar " }) + class TestCase { + } + + var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); + + var filters = request.getFiltersByType(ClassNameFilter.class); + assertThat(getOnlyElement(filters).toString()).contains("'foo'", "'bar'"); + } + + @Test + void convertsTestIdentifiersIntoDescriptions() { + + TestDescriptor container1 = new TestDescriptorStub(UniqueId.root("root", "container1"), "container1"); + container1.addChild(new TestDescriptorStub(UniqueId.root("root", "test1"), "test1")); + TestDescriptor container2 = new TestDescriptorStub(UniqueId.root("root", "container2"), "container2"); + container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2a"), "test2a")); + container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2b"), "test2b")); + var testPlan = TestPlan.from(List.of(container1, container2), mock()); + + var launcher = mock(Launcher.class); + when(launcher.discover(any())).thenReturn(testPlan); + + var runner = new JUnitPlatform(TestClass.class, launcher); + + var runnerDescription = runner.getDescription(); + assertEquals(createSuiteDescription(TestClass.class), runnerDescription); + + List containerDescriptions = runnerDescription.getChildren(); + assertThat(containerDescriptions).hasSize(2); + assertEquals(suiteDescription("[root:container1]"), containerDescriptions.get(0)); + assertEquals(suiteDescription("[root:container2]"), containerDescriptions.get(1)); + + List testDescriptions = containerDescriptions.get(0).getChildren(); + assertEquals(testDescription("[root:test1]"), getOnlyElement(testDescriptions)); + + testDescriptions = containerDescriptions.get(1).getChildren(); + assertThat(testDescriptions).hasSize(2); + assertEquals(testDescription("[root:test2a]"), testDescriptions.get(0)); + assertEquals(testDescription("[root:test2b]"), testDescriptions.get(1)); + } + + private static void assertIncludes(Filter filter, T included) { + assertThat(filter.apply(included).included()).isTrue(); + } + + private static void assertExcludes(Filter filter, T excluded) { + assertThat(filter.apply(excluded).excluded()).isTrue(); + } + + } + + @Nested + class Filtering { + + private final ConfigurationParameters configParams = mock(); + + @Test + void appliesFilter() throws Exception { + + TestDescriptor originalParent1 = new TestDescriptorStub(UniqueId.root("root", "parent1"), "parent1"); + originalParent1.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf1"), "leaf1")); + TestDescriptor originalParent2 = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); + originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2a"), "leaf2a")); + originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); + var fullTestPlan = TestPlan.from(List.of(originalParent1, originalParent2), configParams); + + TestDescriptor filteredParent = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); + filteredParent.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); + var filteredTestPlan = TestPlan.from(Set.of(filteredParent), configParams); + + var launcher = mock(Launcher.class); + var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); + when(launcher.discover(captor.capture())).thenReturn(fullTestPlan).thenReturn(filteredTestPlan); + + var runner = new JUnitPlatform(TestClass.class, launcher); + runner.filter(matchMethodDescription(testDescription("[root:leaf2b]"))); + + var lastDiscoveryRequest = captor.getValue(); + var uniqueIdSelectors = lastDiscoveryRequest.getSelectorsByType(UniqueIdSelector.class); + assertEquals("[root:leaf2b]", getOnlyElement(uniqueIdSelectors).getUniqueId().toString()); + + var parentDescription = getOnlyElement(runner.getDescription().getChildren()); + assertEquals(suiteDescription("[root:parent2]"), parentDescription); + + var testDescription = getOnlyElement(parentDescription.getChildren()); + assertEquals(testDescription("[root:leaf2b]"), testDescription); + } + + @Test + void throwsNoTestsRemainExceptionWhenNoTestIdentifierMatchesFilter() { + var testPlan = TestPlan.from(Set.of(new TestDescriptorStub(UniqueId.root("root", "test"), "test")), + configParams); + + var launcher = mock(Launcher.class); + when(launcher.discover(any())).thenReturn(testPlan); + + var runner = new JUnitPlatform(TestClass.class, launcher); + + assertThrows(NoTestsRemainException.class, + () -> runner.filter(matchMethodDescription(suiteDescription("[root:doesNotExist]")))); + } + + } + + @Nested + class Execution { + + @Test + void notifiesRunListenerOfTestExecution() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + engine.addTest("failingTest", () -> fail("expected to fail")); + engine.addTest("succeedingTest", () -> { + }); + engine.addTest("abortedTest", () -> assumeFalse(true)); + engine.addTest("skippedTest", () -> fail("never called")).markSkipped("should be skipped"); + + var runListener = mock(RunListener.class); + + var notifier = new RunNotifier(); + notifier.addListener(runListener); + new JUnitPlatform(TestClass.class, createLauncher(engine)).run(notifier); + + var inOrder = inOrder(runListener); + + inOrder.verify(runListener).testStarted(testDescription("[engine:dummy]/[test:failingTest]")); + inOrder.verify(runListener).testFailure(any()); + inOrder.verify(runListener).testFinished(testDescription("[engine:dummy]/[test:failingTest]")); + + inOrder.verify(runListener).testStarted(testDescription("[engine:dummy]/[test:succeedingTest]")); + inOrder.verify(runListener).testFinished(testDescription("[engine:dummy]/[test:succeedingTest]")); + + inOrder.verify(runListener).testStarted(testDescription("[engine:dummy]/[test:abortedTest]")); + inOrder.verify(runListener).testAssumptionFailure(any()); + inOrder.verify(runListener).testFinished(testDescription("[engine:dummy]/[test:abortedTest]")); + + inOrder.verify(runListener).testIgnored(testDescription("[engine:dummy]/[test:skippedTest]")); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + void supportsDynamicTestRegistration() throws Exception { + var runListener = mock(RunListener.class); + var notifier = new RunNotifier(); + // notifier.addListener(new LoggingRunListener()); + notifier.addListener(runListener); + new JUnitPlatform(TestClass.class, createLauncher(new DynamicTestEngine())).run(notifier); + + var inOrder = inOrder(runListener); + + inOrder.verify(runListener).testStarted(testDescription("[engine:dynamic]/[container:1]/[test:1]")); + inOrder.verify(runListener).testFinished(testDescription("[engine:dynamic]/[container:1]/[test:1]")); + + inOrder.verify(runListener).testStarted(testDescription("[engine:dynamic]/[container:1]/[test:2]")); + inOrder.verify(runListener).testFinished(testDescription("[engine:dynamic]/[container:1]/[test:2]")); + + inOrder.verify(runListener).testStarted(testDescription("[engine:dynamic]/[container:1]/[test:3]")); + inOrder.verify(runListener).testFinished(testDescription("[engine:dynamic]/[container:1]/[test:3]")); + + inOrder.verify(runListener).testStarted( + testDescription("[engine:dynamic]/[container:1]/[test:3]/[test:3a]")); + inOrder.verify(runListener).testFinished( + testDescription("[engine:dynamic]/[container:1]/[test:3]/[test:3a]")); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + void reportsIgnoredEventsForLeavesWhenContainerIsSkipped() throws Exception { + var uniqueEngineId = UniqueId.forEngine("engine"); + TestDescriptor engineDescriptor = new EngineDescriptor(uniqueEngineId, "engine"); + TestDescriptor container = new TestDescriptorStub(UniqueId.root("root", "container"), "container"); + container.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf"), "leaf")); + engineDescriptor.addChild(container); + + var engine = mock(TestEngine.class); + when(engine.getId()).thenReturn("engine"); + when(engine.discover(any(), eq(uniqueEngineId))).thenReturn(engineDescriptor); + doAnswer(invocation -> { + ExecutionRequest request = invocation.getArgument(0); + var listener = request.getEngineExecutionListener(); + listener.executionStarted(engineDescriptor); + listener.executionSkipped(container, "deliberately skipped container"); + listener.executionFinished(engineDescriptor, successful()); + return null; + }).when(engine).execute(any()); + + var runListener = mock(RunListener.class); + + var notifier = new RunNotifier(); + notifier.addListener(runListener); + new JUnitPlatform(TestClass.class, createLauncher(engine)).run(notifier); + + verify(runListener).testIgnored(testDescription("[root:leaf]")); + verifyNoMoreInteractions(runListener); + } + + } + + @Nested + class Descriptions { + + @Test + @DisplayName("Suite with default display name") + void descriptionForTestSuiteWithDefaultDisplayName() { + Class testClass = TestSuiteWithDefaultDisplayName.class; + var platformRunner = new JUnitPlatform(testClass, + createLauncher(new DemoHierarchicalTestEngine("suite names"))); + + assertEquals(testClass.getName(), platformRunner.getDescription().getDisplayName()); + } + + @Test + @DisplayName("Suite with @SuiteDisplayName") + void descriptionForTestSuiteWithCustomDisplayName() { + var platformRunner = new JUnitPlatform(TestSuiteWithCustomDisplayName.class, + createLauncher(new DemoHierarchicalTestEngine("suite names"))); + + assertEquals("Sweeeeeeet Name!", platformRunner.getDescription().getDisplayName()); + } + + @Test + @DisplayName("Suite with @SuiteDisplayName and @UseTechnicalNames") + void descriptionForTestSuiteWithCustomDisplayNameAndTechnicalNames() { + Class testClass = TestSuiteWithCustomDisplayNameAndTechnicalNames.class; + var platformRunner = new JUnitPlatform(testClass, + createLauncher(new DemoHierarchicalTestEngine("suite names"))); + + assertEquals(testClass.getName(), platformRunner.getDescription().getDisplayName()); + } + + @Test + void descriptionForJavaMethodAndClassSources() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + var failingTest = getClass().getDeclaredMethod("failingTest"); + var containerDescriptor = engine.addContainer("uniqueContainerName", "containerDisplayName", + ClassSource.from(getClass())); + containerDescriptor.addChild( + new DemoHierarchicalTestDescriptor(containerDescriptor.getUniqueId().append("test", "failingTest"), + "testDisplayName", MethodSource.from(failingTest), (c, t) -> { + })); + + var platformRunner = new JUnitPlatform(TestClass.class, createLauncher(engine)); + + List children = platformRunner.getDescription().getChildren(); + assertEquals(1, children.size()); + var engineDescription = children.get(0); + assertEquals("dummy", engineDescription.getDisplayName()); + + var containerDescription = getOnlyElement(engineDescription.getChildren()); + var testDescription = getOnlyElement(containerDescription.getChildren()); + + // @formatter:off + assertAll( + () -> assertEquals("dummy", engineDescription.getDisplayName(), "engine display name"), + () -> assertEquals("dummy", engineDescription.getClassName(), "engine class name"), + () -> assertNull(engineDescription.getMethodName(), "engine method name"), + () -> assertEquals("containerDisplayName", containerDescription.getDisplayName(), "container display name"), + () -> assertEquals("containerDisplayName", containerDescription.getClassName(), "container class name"), + () -> assertNull(containerDescription.getMethodName(), "container method name"), + () -> assertEquals("testDisplayName(containerDisplayName)", testDescription.getDisplayName(), "test display name"), + () -> assertEquals("containerDisplayName", testDescription.getClassName(), "test class name"), + () -> assertEquals("testDisplayName", testDescription.getMethodName(), "test method name") + ); + // @formatter:on + } + + @Test + void descriptionForJavaMethodAndClassSourcesUsingTechnicalNames() throws Exception { + var engine = new DemoHierarchicalTestEngine("dummy"); + var failingTest = getClass().getDeclaredMethod("failingTest"); + var containerDescriptor = engine.addContainer("uniqueContainerName", "containerDisplayName", + ClassSource.from(getClass())); + containerDescriptor.addChild( + new DemoHierarchicalTestDescriptor(containerDescriptor.getUniqueId().append("test", "failingTest"), + "testDisplayName", MethodSource.from(failingTest), (c, t) -> { + })); + + var platformRunner = new JUnitPlatform(TestClassWithTechnicalNames.class, createLauncher(engine)); + + List children = platformRunner.getDescription().getChildren(); + assertEquals(1, children.size()); + var engineDescription = children.get(0); + assertEquals("dummy", engineDescription.getDisplayName()); + + var containerDescription = getOnlyElement(engineDescription.getChildren()); + var testDescription = getOnlyElement(containerDescription.getChildren()); + + // @formatter:off + assertAll( + () -> assertEquals("dummy", engineDescription.getDisplayName(), "engine display name"), + () -> assertEquals("dummy", engineDescription.getClassName(), "engine class name"), + () -> assertNull(engineDescription.getMethodName(), "engine method name"), + () -> assertEquals(getClass().getName(), containerDescription.getDisplayName(), "container display name"), + () -> assertEquals(getClass().getName(), containerDescription.getClassName(), "container class name"), + () -> assertNull(containerDescription.getMethodName(), "container method name"), + () -> assertEquals("failingTest(" + getClass().getName() + ")", testDescription.getDisplayName(), "test display name"), + () -> assertEquals(getClass().getName(), testDescription.getClassName(), "test class name"), + () -> assertEquals("failingTest", testDescription.getMethodName(), "test method name") + ); + // @formatter:on + } + + void failingTest() { + // not actually invoked + } + + } + + // ------------------------------------------------------------------------- + + private static Description suiteDescription(String uniqueId) { + return createSuiteDescription(uniqueId, UniqueId.parse(uniqueId)); + } + + private static Description testDescription(String uniqueId) { + return createTestDescription(uniqueId, uniqueId, UniqueId.parse(uniqueId)); + } + + private TestDescriptor testDescriptorWithTags(String... tag) { + var testDescriptor = mock(TestDescriptor.class); + var tags = Arrays.stream(tag).map(TestTag::create).collect(toSet()); + when(testDescriptor.getTags()).thenReturn(tags); + return testDescriptor; + } + + private LauncherDiscoveryRequest instantiateRunnerAndCaptureGeneratedRequest(Class testClass) { + var launcher = mock(Launcher.class); + var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); + when(launcher.discover(captor.capture())).thenReturn(TestPlan.from(Set.of(), mock())); + + new JUnitPlatform(testClass, launcher); + + return captor.getValue(); + } + + private static class TestClass { + } + + @UseTechnicalNames + private static class TestClassWithTechnicalNames { + } + + private static class TestSuiteWithDefaultDisplayName { + } + + @SuiteDisplayName("Sweeeeeeet Name!") + private static class TestSuiteWithCustomDisplayName { + } + + @SuiteDisplayName("Sweeeeeeet Name!") + @UseTechnicalNames + private static class TestSuiteWithCustomDisplayNameAndTechnicalNames { + } + + private static class DynamicTestEngine implements TestEngine { + + @Override + public String getId() { + return "dynamic"; + } + + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + return new EngineDescriptor(uniqueId, "Dynamic Engine"); + } + + @Override + public void execute(ExecutionRequest request) { + var engineExecutionListener = request.getEngineExecutionListener(); + var root = request.getRootTestDescriptor(); + + TestDescriptor container = new DemoContainerTestDescriptor(root.getUniqueId().append("container", "1"), + "container #1"); + root.addChild(container); + + engineExecutionListener.dynamicTestRegistered(container); + engineExecutionListener.executionStarted(container); + + var containerUid = container.getUniqueId(); + + TestDescriptor dynamicTest1 = new DemoTestTestDescriptor(containerUid.append("test", "1"), + "dynamic test #1"); + container.addChild(dynamicTest1); + engineExecutionListener.dynamicTestRegistered(dynamicTest1); + engineExecutionListener.executionStarted(dynamicTest1); + engineExecutionListener.executionFinished(dynamicTest1, TestExecutionResult.successful()); + + TestDescriptor dynamicTest2 = new DemoTestTestDescriptor(containerUid.append("test", "2"), + "dynamic test #2"); + container.addChild(dynamicTest2); + engineExecutionListener.dynamicTestRegistered(dynamicTest2); + engineExecutionListener.executionStarted(dynamicTest2); + engineExecutionListener.executionFinished(dynamicTest2, TestExecutionResult.successful()); + + TestDescriptor dynamicTest3 = new DemoContainerAndTestTestDescriptor(containerUid.append("test", "3"), + "dynamic test #3"); + container.addChild(dynamicTest3); + engineExecutionListener.dynamicTestRegistered(dynamicTest3); + engineExecutionListener.executionStarted(dynamicTest3); + engineExecutionListener.executionFinished(dynamicTest3, TestExecutionResult.successful()); + + TestDescriptor dynamicTest3a = new DemoTestTestDescriptor(dynamicTest3.getUniqueId().append("test", "3a"), + "dynamic test #3a"); + dynamicTest3.addChild(dynamicTest3a); + engineExecutionListener.dynamicTestRegistered(dynamicTest3a); + engineExecutionListener.executionStarted(dynamicTest3a); + engineExecutionListener.executionFinished(dynamicTest3a, TestExecutionResult.successful()); + + engineExecutionListener.executionFinished(container, TestExecutionResult.successful()); + } + + } + + private static class DemoContainerTestDescriptor extends AbstractTestDescriptor { + + DemoContainerTestDescriptor(UniqueId uniqueId, String displayName) { + super(uniqueId, displayName); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + } + + private static class DemoTestTestDescriptor extends AbstractTestDescriptor { + + DemoTestTestDescriptor(UniqueId uniqueId, String displayName) { + super(uniqueId, displayName); + } + + @Override + public Type getType() { + return Type.TEST; + } + } + + private static class DemoContainerAndTestTestDescriptor extends AbstractTestDescriptor { + + DemoContainerAndTestTestDescriptor(UniqueId uniqueId, String displayName) { + super(uniqueId, displayName); + } + + @Override + public Type getType() { + return Type.CONTAINER_AND_TEST; + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java new file mode 100644 index 00000000..7a985b60 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java @@ -0,0 +1,458 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.commons; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.URI; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClassNameFilter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.DirectorySelector; +import org.junit.platform.engine.discovery.FilePosition; +import org.junit.platform.engine.discovery.FileSelector; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageNameFilter; +import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UriSelector; +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; +import org.junit.platform.launcher.EngineFilter; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.DisableParentConfigurationParameters; +import org.junit.platform.suite.api.ExcludeClassNamePatterns; +import org.junit.platform.suite.api.ExcludeEngines; +import org.junit.platform.suite.api.ExcludePackages; +import org.junit.platform.suite.api.ExcludeTags; +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.IncludePackages; +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.SelectDirectories; +import org.junit.platform.suite.api.SelectFile; +import org.junit.platform.suite.api.SelectModules; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.SelectUris; + +class SuiteLauncherDiscoveryRequestBuilderTests { + + SuiteLauncherDiscoveryRequestBuilder builder = SuiteLauncherDiscoveryRequestBuilder.request(); + + @Test + void configurationParameter() { + @ConfigurationParameter(key = "com.example", value = "*") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + ConfigurationParameters configuration = request.getConfigurationParameters(); + Optional parameter = configuration.get("com.example"); + assertEquals(Optional.of("*"), parameter); + } + + @Test + void excludeClassNamePatterns() { + class TestCase { + } + @ExcludeClassNamePatterns("^.*TestCase$") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getFiltersByType(ClassNameFilter.class); + assertTrue(exactlyOne(filters).apply(TestCase.class.getName()).excluded()); + } + + @Test + void excludeEngines() { + @ExcludeEngines("junit-jupiter") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getEngineFilters(); + assertTrue(exactlyOne(filters).apply(new JupiterTestEngine()).excluded()); + } + + @Test + void excludePackages() { + @ExcludePackages("com.example.testcases") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getFiltersByType(PackageNameFilter.class); + assertTrue(exactlyOne(filters).apply("com.example.testcases").excluded()); + } + + @Test + void excludeTags() { + @ExcludeTags("test-tag") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getPostDiscoveryFilters(); + TestDescriptor testDescriptor = new StubAbstractTestDescriptor(); + assertTrue(exactlyOne(filters).apply(testDescriptor).excluded()); + } + + @Test + void includeClassNamePatterns() { + class TestCase { + } + @IncludeClassNamePatterns("^.*TestCase$") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getFiltersByType(ClassNameFilter.class); + assertTrue(exactlyOne(filters).apply(TestCase.class.getName()).included()); + assertTrue(exactlyOne(filters).apply(Suite.class.getName()).excluded()); + } + + @Test + void filtersOnStandardClassNamePatternsWhenIncludeClassNamePatternsIsOmitted() { + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + assertTrue(request.getFiltersByType(ClassNameFilter.class).isEmpty()); + } + + @Test + void filtersOnStandardClassNamePatternsWhenIncludeClassNamePatternsIsOmittedUnlessDisabled() { + class ExampleTest { + } + class Suite { + } + + // @formatter:off + LauncherDiscoveryRequest request = builder + .filterStandardClassNamePatterns(true) + .suite(Suite.class) + .build(); + // @formatter:on + List filters = request.getFiltersByType(ClassNameFilter.class); + assertTrue(exactlyOne(filters).apply(ExampleTest.class.getName()).included()); + assertTrue(exactlyOne(filters).apply(Suite.class.getName()).excluded()); + } + + @Test + void includeEngines() { + @IncludeEngines("junit-jupiter") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getEngineFilters(); + assertTrue(exactlyOne(filters).apply(new JupiterTestEngine()).included()); + } + + @Test + void includePackages() { + @IncludePackages("com.example.testcases") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getFiltersByType(PackageNameFilter.class); + assertTrue(exactlyOne(filters).apply("com.example.testcases").included()); + } + + @Test + void includeTags() { + @IncludeTags("test-tag") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List filters = request.getPostDiscoveryFilters(); + TestDescriptor testDescriptor = new StubAbstractTestDescriptor(); + assertTrue(exactlyOne(filters).apply(testDescriptor).included()); + } + + @Test + void selectClasses() { + class TestCase { + } + @SelectClasses(TestCase.class) + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(ClassSelector.class); + assertFalse(selectors.isEmpty()); + assertEquals(TestCase.class, exactlyOne(selectors).getJavaClass()); + } + + @Test + void selectClasspathResource() { + @SelectClasspathResource("com.example.testcases") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); + assertEquals("com.example.testcases", exactlyOne(selectors).getClasspathResourceName()); + } + + @Test + void selectClasspathResourcePosition() { + @SelectClasspathResource(value = "com.example.testcases", line = 42) + @SelectClasspathResource(value = "com.example.testcases", line = 14, column = 15) + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); + assertEquals(Optional.of(FilePosition.from(42)), selectors.get(0).getPosition()); + assertEquals(Optional.of(FilePosition.from(14, 15)), selectors.get(1).getPosition()); + } + + @Test + void ignoreClasspathResourcePosition() { + @SelectClasspathResource(value = "com.example.testcases", line = -1) + @SelectClasspathResource(value = "com.example.testcases", column = 12) + @SelectClasspathResource(value = "com.example.testcases", line = 42, column = -12) + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); + assertEquals(Optional.empty(), selectors.get(0).getPosition()); + assertEquals(Optional.empty(), selectors.get(1).getPosition()); + assertEquals(Optional.of(FilePosition.from(42)), selectors.get(2).getPosition()); + } + + @Test + void selectDirectories() { + @SelectDirectories("path/to/root") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(DirectorySelector.class); + assertEquals(Paths.get("path/to/root"), exactlyOne(selectors).getPath()); + } + + @Test + void selectDirectoriesFiltersEmptyPaths() { + @SelectDirectories("") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + assertTrue(request.getSelectorsByType(DirectorySelector.class).isEmpty()); + } + + @Test + void selectFile() { + @SelectFile("path/to/root") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(FileSelector.class); + assertEquals(Paths.get("path/to/root"), exactlyOne(selectors).getPath()); + } + + @Test + void selectFilePosition() { + @SelectFile(value = "path/to/root", line = 42) + @SelectFile(value = "path/to/root", line = 14, column = 15) + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(FileSelector.class); + assertEquals(Optional.of(FilePosition.from(42)), selectors.get(0).getPosition()); + assertEquals(Optional.of(FilePosition.from(14, 15)), selectors.get(1).getPosition()); + } + + @Test + void ignoreInvalidFilePosition() { + @SelectFile(value = "path/to/root", line = -1) + @SelectFile(value = "path/to/root", column = 12) + @SelectFile(value = "path/to/root", line = 42, column = -12) + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(FileSelector.class); + assertEquals(Optional.empty(), selectors.get(0).getPosition()); + assertEquals(Optional.empty(), selectors.get(1).getPosition()); + assertEquals(Optional.of(FilePosition.from(42)), selectors.get(2).getPosition()); + } + + @Test + void selectModules() { + @SelectModules("com.example.testcases") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(ModuleSelector.class); + assertEquals("com.example.testcases", exactlyOne(selectors).getModuleName()); + } + + @Test + void selectUris() { + @SelectUris("path/to/root") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(UriSelector.class); + assertEquals(URI.create("path/to/root"), exactlyOne(selectors).getUri()); + } + + @Test + void selectUrisFiltersEmptyUris() { + @SelectUris("") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + assertTrue(request.getSelectorsByType(UriSelector.class).isEmpty()); + } + + @Test + void selectPackages() { + @SelectPackages("com.example.testcases") + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List selectors = request.getSelectorsByType(PackageSelector.class); + assertEquals("com.example.testcases", exactlyOne(selectors).getPackageName()); + } + + @SelectPackages("com.example.testcases") + @Retention(RetentionPolicy.RUNTIME) + @interface Meta { + } + + @Test + void metaAnnotations() { + @Meta + class Suite { + } + + LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); + List pSelectors = request.getSelectorsByType(PackageSelector.class); + assertEquals("com.example.testcases", exactlyOne(pSelectors).getPackageName()); + } + + @Test + void enableParentConfigurationParametersByDefault() { + class Suite { + + } + // @formatter:off + var configuration = new ParentConfigurationParameters("parent", "parent parameters were used"); + var request = builder.suite(Suite.class) + .parentConfigurationParameters(configuration) + .build(); + // @formatter:on + var configurationParameters = request.getConfigurationParameters(); + assertEquals(Optional.of("parent parameters were used"), configurationParameters.get("parent")); + } + + @Test + void disableParentConfigurationParameters() { + @DisableParentConfigurationParameters + class Suite { + + } + // @formatter:off + var configuration = new ParentConfigurationParameters("parent", "parent parameters were used"); + var request = builder.suite(Suite.class) + .parentConfigurationParameters(configuration) + .build(); + // @formatter:on + var configurationParameters = request.getConfigurationParameters(); + assertEquals(Optional.empty(), configurationParameters.get("parent")); + } + + private static T exactlyOne(List list) { + assertEquals(1, list.size()); + return list.get(0); + } + + private static class StubAbstractTestDescriptor extends AbstractTestDescriptor { + public StubAbstractTestDescriptor() { + super(UniqueId.forEngine("test"), "stub"); + } + + @Override + public Type getType() { + return Type.CONTAINER; + } + + @Override + public Set getTags() { + return Collections.singleton(TestTag.create("test-tag")); + } + + } + + private static class ParentConfigurationParameters implements ConfigurationParameters { + private final Map map; + + public ParentConfigurationParameters(String key, String value) { + this.map = Map.of(key, value); + } + + @Override + public Optional get(String key) { + return Optional.ofNullable(map.get(key)); + } + + @Override + public Optional getBoolean(String key) { + return Optional.empty(); + } + + @Override + @SuppressWarnings("deprecation") + public int size() { + return map.size(); + } + + @Override + public Set keySet() { + return null; + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java new file mode 100644 index 00000000..4c44d1c0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java @@ -0,0 +1,395 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.TagFilter.excludeTags; +import static org.junit.platform.suite.engine.SuiteEngineDescriptor.ENGINE_ID; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.DynamicTestsTestCase; +import org.junit.platform.suite.engine.testcases.JUnit4TestsTestCase; +import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; +import org.junit.platform.suite.engine.testcases.TaggedTestTestCase; +import org.junit.platform.suite.engine.testsuites.AbstractSuite; +import org.junit.platform.suite.engine.testsuites.CyclicSuite; +import org.junit.platform.suite.engine.testsuites.DynamicSuite; +import org.junit.platform.suite.engine.testsuites.EmptyCyclicSuite; +import org.junit.platform.suite.engine.testsuites.EmptyDynamicTestSuite; +import org.junit.platform.suite.engine.testsuites.EmptyDynamicTestWithFailIfNoTestFalseSuite; +import org.junit.platform.suite.engine.testsuites.EmptyTestCaseSuite; +import org.junit.platform.suite.engine.testsuites.EmptyTestCaseWithFailIfNoTestFalseSuite; +import org.junit.platform.suite.engine.testsuites.MultiEngineSuite; +import org.junit.platform.suite.engine.testsuites.MultipleSuite; +import org.junit.platform.suite.engine.testsuites.NestedSuite; +import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; +import org.junit.platform.suite.engine.testsuites.SuiteDisplayNameSuite; +import org.junit.platform.suite.engine.testsuites.SuiteSuite; +import org.junit.platform.suite.engine.testsuites.ThreePartCyclicSuite; +import org.junit.platform.testkit.engine.EngineTestKit; + +/** + * @since 1.8 + */ +class SuiteEngineTests { + + @Test + void selectClasses() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(SelectClassesSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void suiteDisplayName() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(SuiteDisplayNameSuite.class)) + .execute() + .allEvents() + .assertThatEvents() + .haveExactly(1, event(container(displayName("Suite Display Name")), finishedSuccessfully())); + // @formatter:on + } + + @Test + void abstractSuiteIsNotExecuted() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(AbstractSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .isEmpty(); + // @formatter:on + } + + @Test + void privateSuiteIsNotExecuted() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(PrivateSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .isEmpty(); + // @formatter:on + } + + @Suite + @SelectClasses(SingleTestTestCase.class) + private static class PrivateSuite { + + } + + @Test + void innerSuiteIsNotExecuted() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(InnerSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .isEmpty(); + // @formatter:on + } + + @SuppressWarnings("InnerClassMayBeStatic") + @Suite + @SelectClasses(SingleTestTestCase.class) + private class InnerSuite { + + } + + @Test + void nestedSuiteIsNotExecuted() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(NestedSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .isEmpty(); + // @formatter:on + } + + @Test + void dynamicSuite() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(DynamicSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(2, event(test(DynamicTestsTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void suiteSuite() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(SuiteSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(1, event(test(SuiteSuite.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void selectClassesByUniqueId() { + // @formatter:off + UniqueId uniqId = UniqueId.forEngine(ENGINE_ID) + .append(SuiteTestDescriptor.SEGMENT_TYPE, SelectClassesSuite.class.getName()); + EngineTestKit.engine(ENGINE_ID) + .selectors(selectUniqueId(uniqId)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void selectMethodInTestPlanByUniqueId() { + // @formatter:off + UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) + .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) + .append("engine", JupiterEngineDescriptor.ENGINE_ID) + .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) + .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + EngineTestKit.engine(ENGINE_ID) + .selectors(selectUniqueId(uniqueId)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(1, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void selectSuiteByUniqueId() { + // @formatter:off + UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) + .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()); + + EngineTestKit.engine(ENGINE_ID) + .selectors(selectUniqueId(uniqueId)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) + .haveExactly(2, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void selectMethodAndSuiteInTestPlanByUniqueId() { + // @formatter:off + UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) + .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) + .append("engine", JupiterEngineDescriptor.ENGINE_ID) + .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) + .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + EngineTestKit.engine(ENGINE_ID) + .selectors(selectUniqueId(uniqueId)) + .selectors(selectClass(SelectClassesSuite.class)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void selectMethodsInTestPlanByUniqueId() { + // @formatter:off + UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) + .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) + .append("engine", JupiterEngineDescriptor.ENGINE_ID) + .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) + .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + UniqueId uniqueId2 = UniqueId.forEngine(ENGINE_ID) + .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) + .append("engine", JupiterEngineDescriptor.ENGINE_ID) + .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) + .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test2()"); + + EngineTestKit.engine(ENGINE_ID) + .selectors(selectUniqueId(uniqueId)) + .selectors(selectUniqueId(uniqueId2)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) + .haveExactly(2, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void postDiscoveryCanRemoveTestDescriptorsInSuite() { + // @formatter:off + PostDiscoveryFilter postDiscoveryFilter = testDescriptor -> testDescriptor.getSource() + .filter(MethodSource.class::isInstance) + .map(MethodSource.class::cast) + .filter(classSource -> SingleTestTestCase.class.equals(classSource.getJavaClass())) + .map(classSource -> FilterResult.excluded("Was a test in SimpleTest")) + .orElseGet(() -> FilterResult.included("Was not a test in SimpleTest")); + + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(SelectClassesSuite.class)) + .filters(postDiscoveryFilter) + .execute() + .testEvents() + .assertThatEvents() + .isEmpty(); + // @formatter:on + } + + @Test + void emptySuiteFails() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(EmptyTestCaseSuite.class)) + .execute() + .containerEvents() + .assertThatEvents() + .haveExactly(1, event(container(EmptyTestCaseSuite.class), finishedWithFailure(instanceOf(NoTestsDiscoveredException.class)))); + // @formatter:on + } + + @Test + void emptySuitePassesWhenFailIfNoTestIsFalse() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(EmptyTestCaseWithFailIfNoTestFalseSuite.class)) + .execute() + .containerEvents() + .assertThatEvents() + .haveExactly(1, event(engine(), finishedSuccessfully())); + // @formatter:on + } + + @Test + void emptyDynamicSuiteFails() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(EmptyDynamicTestSuite.class)) + .execute() + .containerEvents() + .assertThatEvents() + .haveExactly(1, event(container(EmptyDynamicTestSuite.class), finishedWithFailure(instanceOf(NoTestsDiscoveredException.class)))); + // @formatter:on + } + + @Test + void emptyDynamicSuitePassesWhenFailIfNoTestIsFalse() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(EmptyDynamicTestWithFailIfNoTestFalseSuite.class)) + .execute() + .allEvents() + .assertThatEvents() + .haveAtLeastOne(event(container(EmptyDynamicTestWithFailIfNoTestFalseSuite.class), finishedSuccessfully())); + // @formatter:on + } + + @Test + void pruneAfterPostDiscoveryFilters() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(MultiEngineSuite.class)) + .filters(excludeTags("excluded")) + .execute() + .allEvents() + .assertThatEvents() + .haveExactly(1, event(test(JUnit4TestsTestCase.class.getName()), finishedSuccessfully())) + .doNotHave(test(TaggedTestTestCase.class.getName())) + .doNotHave(container("junit-jupiter")); + // @formatter:on + } + + @Test + void cyclicSuite() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(CyclicSuite.class)) + .execute() + .allEvents() + .assertThatEvents() + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + + @Test + void emptyCyclicSuite() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(EmptyCyclicSuite.class)) + .execute() + .allEvents() + .assertThatEvents() + .haveExactly(1, event(container(EmptyCyclicSuite.class), finishedWithFailure(message( + "Suite [org.junit.platform.suite.engine.testsuites.EmptyCyclicSuite] did not discover any tests" + )))); + // @formatter:on + } + + @Test + void threePartCyclicSuite() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(ThreePartCyclicSuite.PartA.class)) + .execute() + .allEvents() + .assertThatEvents() + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java new file mode 100644 index 00000000..627de28d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine; + +import static java.util.Collections.emptySet; +import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; +import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; + +/** + * @since 1.8 + */ +class SuiteTestDescriptorTests { + @Suite + static class TestSuite { + + } + UniqueId engineId = UniqueId.forEngine(SuiteEngineDescriptor.ENGINE_ID); + UniqueId suiteId = engineId.append(SuiteTestDescriptor.SEGMENT_TYPE, "test"); + UniqueId jupiterEngineId = suiteId.append("engine", JupiterEngineDescriptor.ENGINE_ID); + UniqueId testClassId = jupiterEngineId.append(ClassTestDescriptor.SEGMENT_TYPE, SingleTestTestCase.class.getName()); + UniqueId methodId = testClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + + ConfigurationParameters configurationParameters = new EmptyConfigurationParameters(); + SuiteTestDescriptor suite = new SuiteTestDescriptor(suiteId, TestSuite.class, configurationParameters); + + @Test + void suiteIsEmptyBeforeDiscovery() { + suite.addDiscoveryRequestFrom(SelectClassesSuite.class); + assertEquals(emptySet(), suite.getChildren()); + } + + @Test + void suiteDiscoversTestsFromClass() { + suite.addDiscoveryRequestFrom(SelectClassesSuite.class); + suite.discover(); + assertEquals(Set.of(jupiterEngineId, testClassId, methodId), + suite.getDescendants().stream().map(TestDescriptor::getUniqueId).collect(toSet())); + } + + @Test + void suitDiscoversTestsFromUniqueId() { + suite.addDiscoveryRequestFrom(methodId); + suite.discover(); + assertEquals(Set.of(jupiterEngineId, testClassId, methodId), + suite.getDescendants().stream().map(TestDescriptor::getUniqueId).collect(toSet())); + } + + @Test + void discoveryPlanCanNotBeModifiedAfterDiscovery() { + suite.addDiscoveryRequestFrom(SelectClassesSuite.class); + suite.discover(); + assertAll(() -> { + PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, + () -> suite.addDiscoveryRequestFrom(SelectClassesSuite.class)); + assertEquals("discovery request can not be modified after discovery", exception.getMessage()); + + }, () -> { + PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, + () -> suite.addDiscoveryRequestFrom(methodId)); + assertEquals("discovery request can not be modified after discovery", exception.getMessage()); + }); + } + + @Test + void suiteMayRegisterTests() { + assertTrue(suite.mayRegisterTests()); + } + + private static class EmptyConfigurationParameters implements ConfigurationParameters { + @Override + public Optional get(String key) { + return Optional.empty(); + } + + @Override + public Optional getBoolean(String key) { + return Optional.empty(); + } + + @Override + @SuppressWarnings("deprecation") + public int size() { + return 0; + } + + @Override + public Set keySet() { + return Collections.emptySet(); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java new file mode 100644 index 00000000..7972eee2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testcases; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +/** + * @since 1.8 + */ +public class DynamicTestsTestCase { + + @TestFactory + Stream dynamicTests() { + return Stream.of(// + dynamicTest("Add test", () -> assertEquals(2, Math.addExact(1, 1))), + dynamicTest("Multiply Test", () -> assertEquals(4, Math.multiplyExact(2, 2)))// + ); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java new file mode 100644 index 00000000..be17ec51 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testcases; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +/** + * @since 1.9 + */ +public class EmptyDynamicTestsTestCase { + + @TestFactory + Stream dynamicTests() { + return Stream.empty(); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java new file mode 100644 index 00000000..1251644a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java @@ -0,0 +1,18 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testcases; + +/** + * @since 1.9 + */ +public class EmptyTestTestCase { + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java new file mode 100644 index 00000000..208d48bb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testcases; + +import org.junit.Test; + +public class JUnit4TestsTestCase { + @Test + public void aTest() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java new file mode 100644 index 00000000..41ddbbbb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testcases; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.8 + */ +public class MultipleTestsTestCase { + + @Test + void test() { + } + + @Test + void test2() { + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java new file mode 100644 index 00000000..bb6116eb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testcases; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.8 + */ +public class SingleTestTestCase { + + @Test + void test() { + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java new file mode 100644 index 00000000..0ecc4d9c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testcases; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +public class TaggedTestTestCase { + @Test + @Tag("excluded") + public void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java new file mode 100644 index 00000000..c648e904 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; + +/** + * @since 1.8 + */ +@Suite +@SelectClasses(SingleTestTestCase.class) +public abstract class AbstractSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java new file mode 100644 index 00000000..df2636d5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; + +/** + * @since 1.8 + */ +@Suite +@IncludeClassNamePatterns(".*") +@SelectClasses({ CyclicSuite.class, SingleTestTestCase.class }) +public class CyclicSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java new file mode 100644 index 00000000..525498e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.DynamicTestsTestCase; + +/** + * @since 1.8 + */ +@Suite +@SelectClasses(DynamicTestsTestCase.class) +public class DynamicSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java new file mode 100644 index 00000000..38554f36 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +/** + * @since 1.9.2 + */ +@Suite +@IncludeClassNamePatterns(".*") +@SelectClasses(EmptyCyclicSuite.class) +public class EmptyCyclicSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java new file mode 100644 index 00000000..b5790485 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.EmptyDynamicTestsTestCase; + +/** + * @since 1.9 + */ +@Suite +@SelectClasses(EmptyDynamicTestsTestCase.class) +public class EmptyDynamicTestSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java new file mode 100644 index 00000000..d1f87c65 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.EmptyDynamicTestsTestCase; + +/** + * @since 1.9 + */ +@Suite(failIfNoTests = false) +@SelectClasses(EmptyDynamicTestsTestCase.class) +public class EmptyDynamicTestWithFailIfNoTestFalseSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java new file mode 100644 index 00000000..55b72d39 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.EmptyTestTestCase; + +/** + * @since 1.9 + */ +@Suite +@SelectClasses(EmptyTestTestCase.class) +public class EmptyTestCaseSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java new file mode 100644 index 00000000..f1d5bf56 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.EmptyTestTestCase; + +/** + * @since 1.9 + */ +@Suite(failIfNoTests = false) +@SelectClasses(EmptyTestTestCase.class) +public class EmptyTestCaseWithFailIfNoTestFalseSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java new file mode 100644 index 00000000..ac12777b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.JUnit4TestsTestCase; +import org.junit.platform.suite.engine.testcases.TaggedTestTestCase; + +@Suite +@SelectClasses({ JUnit4TestsTestCase.class, TaggedTestTestCase.class }) +public class MultiEngineSuite { + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java new file mode 100644 index 00000000..c05368dd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; + +/** + * @since 1.8 + */ +@Suite +@SelectClasses(MultipleTestsTestCase.class) +public class MultipleSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java new file mode 100644 index 00000000..7d07a866 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; + +/** + * @since 1.8 + */ +public class NestedSuite { + + @Suite + @SelectClasses(SingleTestTestCase.class) + static class Jupiter { + } + + @Suite + @SelectClasses(MultipleTestsTestCase.class) + static class Tagged { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java new file mode 100644 index 00000000..1d9df39e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; + +/** + * @since 1.8 + */ +@Suite +@SelectClasses(SingleTestTestCase.class) +public class SelectClassesSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java new file mode 100644 index 00000000..436e822d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; + +/** + * @since 1.8 + */ +@Suite +@SelectClasses(SingleTestTestCase.class) +@SuiteDisplayName("Suite Display Name") +public class SuiteDisplayNameSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java new file mode 100644 index 00000000..205c71c8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +/** + * @since 1.8 + */ +@Suite +@IncludeClassNamePatterns(".*") +@SelectClasses(SelectClassesSuite.class) +public class SuiteSuite { +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java new file mode 100644 index 00000000..febbba58 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.suite.engine.testsuites; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.testcases.SingleTestTestCase; + +/** + * @since 1.9.2 + */ +public class ThreePartCyclicSuite { + + @Suite + @IncludeClassNamePatterns(".*") + @SelectClasses({ PartB.class }) + public static class PartA { + + } + + @Suite + @IncludeClassNamePatterns(".*") + @SelectClasses({ PartC.class }) + public static class PartB { + + } + + @Suite + @IncludeClassNamePatterns(".*") + @SelectClasses({ PartB.class, SingleTestTestCase.class }) + public static class PartC { + + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java new file mode 100644 index 00000000..61c18d5e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import java.util.Optional; +import java.util.function.UnaryOperator; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.platform.engine.reporting.ReportEntry; + +class EngineTestKitTests { + + private static final String KEY = EngineTestKitTests.class.getName(); + + @BeforeEach + void setSystemProperty() { + System.setProperty(KEY, "from system property"); + } + + @AfterEach + void resetSystemProperty() { + System.clearProperty(KEY); + } + + @Test + void ignoresImplicitConfigurationParametersByDefault() { + var value = executeExampleTestCaseAndCollectValue(builder -> builder); + + assertThat(value).isEmpty(); + } + + @ParameterizedTest + @CsvSource({ "true, from system property", "false," }) + void usesImplicitConfigurationParametersWhenEnabled(boolean enabled, String expectedValue) { + var value = executeExampleTestCaseAndCollectValue( + builder -> builder.enableImplicitConfigurationParameters(enabled)); + + assertThat(value).isEqualTo(Optional.ofNullable(expectedValue)); + } + + private Optional executeExampleTestCaseAndCollectValue(UnaryOperator configuration) { + return configuration.apply(EngineTestKit.engine("junit-jupiter")) // + .selectors(selectClass(ExampleTestCase.class)) // + .execute() // + .allEvents() // + .reportingEntryPublished() // + .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) // + .map(ReportEntry::getKeyValuePairs) // + .map(entries -> entries.get(KEY)) // + .findFirst(); + } + + static class ExampleTestCase { + + @RegisterExtension + BeforeEachCallback callback = context -> context.getConfigurationParameter(KEY) // + .ifPresent(value -> context.publishReportEntry(KEY, value)); + + @Test + void test() { + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java new file mode 100644 index 00000000..aeda1baa --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java @@ -0,0 +1,287 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.started; + +import java.util.List; + +import org.assertj.core.error.AssertJMultipleFailuresError; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.opentest4j.AssertionFailedError; + +class EventsTests { + + TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("e1"), "engine"); + + List list = List.of(Event.executionStarted(engineDescriptor), + Event.executionSkipped(engineDescriptor, "reason1"), Event.executionSkipped(engineDescriptor, "reason2"), + Event.executionFinished(engineDescriptor, TestExecutionResult.successful())); + + Events events = new Events(list, "test"); + + @Test + @DisplayName("assertEventsMatchExactly: all events in order -> match") + void assertEventsMatchExactlyMatchesAllEventsInOrder() { + events.assertEventsMatchExactly( // + event(engine(), started()), // + event(engine(), skippedWithReason("reason1")), // + event(engine(), skippedWithReason("reason2")), // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: all events in order -> match") + void assertEventsMatchLooselyMatchesAllEventsInOrder() { + events.assertEventsMatchLoosely( // + event(engine(), started()), // + event(engine(), skippedWithReason("reason1")), // + event(engine(), skippedWithReason("reason2")), // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: all events in wrong order -> match") + void assertEventsMatchLooselyMatchesAllEventsInWrongOrder() { + events.assertEventsMatchLoosely( // + event(engine(), skippedWithReason("reason2")), // + event(engine(), finishedSuccessfully()), // + event(engine(), skippedWithReason("reason1")), // + event(engine(), started()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: tailing subset -> match") + void assertEventsMatchLooselyMatchesATailingSubset() { + events.assertEventsMatchLoosely( // + event(engine(), skippedWithReason("reason1")), // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: starting subset -> match") + void assertEventsMatchLooselyMatchesAStartingSubset() { + events.assertEventsMatchLoosely( // + event(engine(), started()), // + event(engine(), skippedWithReason("reason1")) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: subset in wrong order -> match") + void assertEventsMatchLooselyMatchesASubsetInWrongOrder() { + events.assertEventsMatchLoosely( // + event(engine(), skippedWithReason("reason1")), // + event(engine(), started()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: only last event -> match") + void assertEventsMatchLooselyMatchesTheLastEventAlone() { + events.assertEventsMatchLoosely( // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: only first event -> match") + void assertEventsMatchLooselyMatchesTheFirstEventAlone() { + events.assertEventsMatchLoosely( // + event(engine(), started()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLoosely: only bad events -> fails") + void assertEventsMatchLooselyWithBadConditionsOnlyFails() { + Executable willFail = () -> events.assertEventsMatchLoosely( // + event(engine(), finishedWithFailure()), // + event(engine(), skippedWithReason("other")) // + ); + + var error = assertThrows(AssertJMultipleFailuresError.class, willFail); + + var failures = error.getFailures(); + assertEquals(2, failures.size()); + assertEquals(AssertionError.class, failures.get(0).getClass()); + assertEquals(AssertionError.class, failures.get(1).getClass()); + } + + @Test + @DisplayName("assertEventsMatchLoosely: one matching and one bad event -> fails") + void assertEventsMatchLooselyWithOneMatchingAndOneBadConditionFailsPartly() { + Executable willFail = () -> events.assertEventsMatchLoosely( // + event(engine(), started()), // + event(engine(), finishedWithFailure()) // + ); + + var error = assertThrows(AssertJMultipleFailuresError.class, willFail); + + var failures = error.getFailures(); + assertEquals(1, failures.size()); + assertEquals(AssertionError.class, failures.get(0).getClass()); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: all events in order -> match") + void assertEventsMatchLooselyInOrderMatchesAllEventsInOrder() { + events.assertEventsMatchLooselyInOrder( // + event(engine(), started()), // + event(engine(), skippedWithReason("reason1")), // + event(engine(), skippedWithReason("reason2")), // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: all events in wrong order -> fail") + void assertEventsMatchLooselyInOrderWithAllEventsInWrongOrderFails() { + Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // + event(engine(), skippedWithReason("reason2")), // + event(engine(), finishedSuccessfully()), // + event(engine(), skippedWithReason("reason1")), // + event(engine(), started()) // + ); + + var error = assertThrows(AssertionFailedError.class, willFail); + assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: tailing subset in order -> match") + void assertEventsMatchLooselyInOrderMatchesATailingSubset() { + events.assertEventsMatchLooselyInOrder( // + event(engine(), skippedWithReason("reason1")), // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: starting subset in order -> match") + void assertEventsMatchLooselyInOrderMatchesAStartingSubset() { + events.assertEventsMatchLooselyInOrder( // + event(engine(), started()), // + event(engine(), skippedWithReason("reason1")) // + ); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: subset in wrong order -> fail") + void assertEventsMatchLooselyInOrderWithASubsetInWrongOrderFails() { + Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // + event(engine(), skippedWithReason("reason1")), // + event(engine(), started()) // + ); + + var error = assertThrows(AssertionFailedError.class, willFail); + assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: last event alone -> match") + void assertEventsMatchLooselyInOrderMatchesTheLastEventAlone() { + events.assertEventsMatchLooselyInOrder( // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: first event alone -> match") + void assertEventsMatchLooselyInOrderMatchesTheFirstEventAlone() { + events.assertEventsMatchLooselyInOrder( // + event(engine(), started()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: bad events only -> fail") + void assertEventsMatchLooselyInOrderWithBadConditionsOnlyFails() { + Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // + event(engine(), finishedWithFailure()), // + event(engine(), skippedWithReason("other")) // + ); + + var error = assertThrows(AssertJMultipleFailuresError.class, willFail); + + var failures = error.getFailures(); + assertEquals(2, failures.size()); + assertEquals(AssertionError.class, failures.get(0).getClass()); + assertEquals(AssertionError.class, failures.get(1).getClass()); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: one matching event and one bad event -> fail") + void assertEventsMatchLooselyInOrderWithOneMatchingAndOneBadConditionFailsPartly() { + Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // + event(engine(), started()), // + event(engine(), finishedWithFailure()) // + ); + + var error = assertThrows(AssertJMultipleFailuresError.class, willFail); + assertEquals(1, error.getFailures().size()); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: first and last event in order -> match") + void assertEventsMatchLooselyInOrderMatchesFirstAndLastEventInOrder() { + events.assertEventsMatchLooselyInOrder( // + event(engine(), started()), // + event(engine(), finishedSuccessfully()) // + ); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: second and last event in bad order -> fail") + void assertEventsMatchLooselyInOrderWithSecondAndLastEventInBadOrderFails() { + Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // + event(engine(), finishedSuccessfully()), // + event(engine(), skippedWithReason("reason1")) // + ); + + var error = assertThrows(AssertionFailedError.class, willFail); + assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); + } + + @Test + @DisplayName("assertEventsMatchLooselyInOrder: too many events -> fail") + void assertEventsMatchLooselyInOrderWithTooManyEventsFails() { + Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // + event(engine(), finishedSuccessfully()), // + event(engine(), finishedSuccessfully()), // + event(engine(), finishedSuccessfully()), // + event(engine(), finishedSuccessfully()), // + event(engine(), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + + var error = assertThrows(AssertionError.class, willFail); + assertTrue(error.getMessage().endsWith("to be less than or equal to 4 but was 6")); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java new file mode 100644 index 00000000..ea609b60 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link Executions}. + * + * @since 1.4 + */ +class ExecutionsIntegrationTests { + + @Test + void executionsFromSkippedTestEvents() { + var testEvents = getTestEvents(); + + // We expect 1 for both of the following cases, since an Execution can + // be created for a "skipped event even if "started" and "finished" events + // are filtered out. + assertThat(testEvents.executions().skipped().count()).isEqualTo(1); + assertThat(testEvents.skipped().executions().count()).isEqualTo(1); + } + + @Test + void executionsFromStartedTestEvents() { + var testEvents = getTestEvents(); + + // We expect 3 if the executions are created BEFORE filtering out "finished" events. + assertThat(testEvents.executions().started().count()).isEqualTo(3); + // We expect 0 if the executions are created AFTER filtering out "finished" events. + assertThat(testEvents.started().executions().count()).isEqualTo(0); + } + + @Test + void executionsFromFinishedTestEvents() { + var testEvents = getTestEvents(); + + // We expect 3 if the executions are created BEFORE filtering out "started" events. + assertThat(testEvents.executions().finished().count()).isEqualTo(3); + // We expect 0 if the executions are created AFTER filtering out "started" events. + assertThat(testEvents.finished().executions().count()).isEqualTo(0); + } + + @Test + void executionsFromSucceededTestEvents() { + var testEvents = getTestEvents(); + + // We expect 1 if the executions are created BEFORE filtering out "finished" events. + assertThat(testEvents.executions().succeeded().count()).isEqualTo(1); + // We expect 0 if the executions are created AFTER filtering out "finished" events. + assertThat(testEvents.succeeded().executions().count()).isEqualTo(0); + } + + @Test + void executionsFromAbortedTestEvents() { + var testEvents = getTestEvents(); + + // We expect 1 if the executions are created BEFORE filtering out "started" events. + assertThat(testEvents.executions().aborted().count()).isEqualTo(1); + // We expect 0 if the executions are created AFTER filtering out "started" events. + assertThat(testEvents.aborted().executions().count()).isEqualTo(0); + } + + @Test + void executionsFromFailedTestEvents() { + var testEvents = getTestEvents(); + + // We expect 1 if the executions are created BEFORE filtering out "started" events. + assertThat(testEvents.executions().failed().count()).isEqualTo(1); + // We expect 0 if the executions are created AFTER filtering out "started" events. + assertThat(testEvents.failed().executions().count()).isEqualTo(0); + } + + private Events getTestEvents() { + return EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(ExampleTestCase.class))// + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); + } + + static class ExampleTestCase { + + @Test + @Disabled + void skippedTest() { + } + + @Test + void succeedingTest() { + } + + @Test + void abortedTest() { + assumeTrue(false); + } + + @Test + void failingTest() { + fail("Boom!"); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java new file mode 100644 index 00000000..c16b2e26 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.testkit.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.engine; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.nestedContainer; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.testkit.engine.NestedContainerEventConditionTests.ATestCase.BTestCase; +import org.junit.platform.testkit.engine.NestedContainerEventConditionTests.ATestCase.BTestCase.CTestCase; + +/** + * @since 1.6 + */ +class NestedContainerEventConditionTests { + + @Test + void preconditions() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> nestedContainer(null))// + .withMessage("Class must not be null"); + + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(() -> nestedContainer(NestedContainerEventConditionTests.class))// + .withMessage(NestedContainerEventConditionTests.class.getName() + " must be a nested class"); + } + + @Test + void nestedContainerChecksSuppliedClassAndAllEnclosingClasses() { + var uniqueId = UniqueId.root("top-level", getClass().getName())// + .append("nested", ATestCase.class.getSimpleName())// + .append("nested", BTestCase.class.getSimpleName())// + .append("nested", CTestCase.class.getSimpleName()); + var event = createEvent(uniqueId); + + var condition = nestedContainer(HashMap.Entry.class); + assertThat(condition.matches(event)).isFalse(); + assertThat(condition.toString()).contains(// + "is a container", "with uniqueId substring 'Map'", "with uniqueId substring 'Entry'"); + + condition = nestedContainer(ATestCase.BTestCase.CTestCase.class); + assertThat(condition.matches(event)).isTrue(); + assertThat(condition.toString()).contains(// + "is a container", "with uniqueId substring 'NestedContainerEventConditionTests'", + "with uniqueId substring 'ATestCase'", "with uniqueId substring 'BTestCase'", + "with uniqueId substring 'CTestCase'"); + } + + private Event createEvent(UniqueId uniqueId) { + var testDescriptor = mock(TestDescriptor.class); + when(testDescriptor.isContainer()).thenReturn(true); + when(testDescriptor.getUniqueId()).thenReturn(uniqueId); + + var event = mock(Event.class); + when(event.getTestDescriptor()).thenReturn(testDescriptor); + return event; + } + + @Test + void usingNestedContainerCorrectly() { + assertDoesNotThrow(() -> container(ATestCase.class)); + assertDoesNotThrow(() -> nestedContainer(ATestCase.class)); + + assertDoesNotThrow(() -> container(ATestCase.BTestCase.class)); + assertDoesNotThrow(() -> nestedContainer(ATestCase.BTestCase.class)); + + assertDoesNotThrow(() -> container(ATestCase.BTestCase.CTestCase.class)); + assertDoesNotThrow(() -> nestedContainer(ATestCase.BTestCase.CTestCase.class)); + + assertDoesNotThrow(() -> container(NestedContainerEventConditionTests.class)); + } + + @Test + void eventConditionsForMultipleLevelsOfNestedClasses() { + // @formatter:off + EngineTestKit.engine("junit-jupiter") + .selectors(selectClass(ATestCase.class)) + .execute() + .allEvents() + .assertEventsMatchExactly( + event(engine(), started()), + event(container(ATestCase.class), started()), + event(test("test_a"), started()), + event(test("test_a"), finishedSuccessfully()), + event(nestedContainer(ATestCase.BTestCase.class, displayName("Test case B")), started()), + event(test("test_b"), started()), + event(test("test_b"), finishedSuccessfully()), + event(nestedContainer(ATestCase.BTestCase.CTestCase.class), started()), + event(test("test_c"), started()), + event(test("test_c"), finishedSuccessfully()), + event(nestedContainer(ATestCase.BTestCase.CTestCase.class), finishedSuccessfully()), + event(nestedContainer(ATestCase.BTestCase.class, displayName("Test case B")), finishedSuccessfully()), + event(container(ATestCase.class), finishedSuccessfully()), + event(engine(), finishedSuccessfully()) + ); + // @formatter:on + } + + static class ATestCase { + + @Test + void test_a() { + } + + @Nested + @DisplayName("Test case B") + class BTestCase { + @Test + void test_b() { + } + + @Nested + class CTestCase { + @Test + void test_c() { + } + } + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt new file mode 100644 index 00000000..b559d040 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt @@ -0,0 +1,22 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Started: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) +Finished: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) +Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt new file mode 100644 index 00000000..b559d040 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt @@ -0,0 +1,22 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Started: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) +Finished: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) +Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt new file mode 100644 index 00000000..1a826306 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt @@ -0,0 +1,18 @@ +. +'-- JUnit Jupiter [OK] + '-- Basic [OK] + '-- .oO fancy display name Oo. [OK] + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt new file mode 100644 index 00000000..de935d05 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt @@ -0,0 +1,18 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Basic ✔ + └─ .oO fancy display name Oo. ✔ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt new file mode 100644 index 00000000..f401e486 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt @@ -0,0 +1,28 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Basic +| | +-- .oO fancy display name Oo. +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'changeDisplayName', methodParameterTypes = ''] +\| \| \| duration: [\d]+ ms +| | | status: [OK] SUCCESSFUL +\| '-- Basic finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt new file mode 100644 index 00000000..021c28c3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt @@ -0,0 +1,28 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Basic +│ │ ├─ .oO fancy display name Oo. +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'changeDisplayName', methodParameterTypes = ''] +│ │ │ duration: [\d]+ ms +│ │ │ status: ✔ SUCCESSFUL +│ └─ Basic finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt new file mode 100644 index 00000000..b51dd812 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt @@ -0,0 +1,22 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Started: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) +Finished: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) +Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt new file mode 100644 index 00000000..b51dd812 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt @@ -0,0 +1,22 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Started: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) +Finished: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) +Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt new file mode 100644 index 00000000..72b9ffb1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt @@ -0,0 +1,18 @@ +. +'-- JUnit Jupiter [OK] + '-- Basic [OK] + '-- empty() [OK] + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt new file mode 100644 index 00000000..3eb81451 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt @@ -0,0 +1,18 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Basic ✔ + └─ empty() ✔ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt new file mode 100644 index 00000000..7d2c448c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt @@ -0,0 +1,28 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Basic +| | +-- empty() +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'empty', methodParameterTypes = ''] +\| \| \| duration: [\d]+ ms +| | | status: [OK] SUCCESSFUL +\| '-- Basic finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt new file mode 100644 index 00000000..36a4ec35 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt @@ -0,0 +1,28 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Basic +│ │ ├─ empty() +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'empty', methodParameterTypes = ''] +│ │ │ duration: [\d]+ ms +│ │ │ status: ✔ SUCCESSFUL +│ └─ Basic finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt new file mode 100644 index 00000000..db92d1c7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt @@ -0,0 +1,27 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Started: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) +Finished: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) + => Exception: org.opentest4j.AssertionFailedError: multi + line + fail + message +>> S T A C K T R A C E >> +Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt new file mode 100644 index 00000000..db92d1c7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt @@ -0,0 +1,27 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Started: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) +Finished: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) + => Exception: org.opentest4j.AssertionFailedError: multi + line + fail + message +>> S T A C K T R A C E >> +Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt new file mode 100644 index 00000000..e55bd31b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt @@ -0,0 +1,23 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithMultiLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: multi +line +fail +message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt new file mode 100644 index 00000000..e55bd31b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt @@ -0,0 +1,23 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithMultiLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: multi +line +fail +message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt new file mode 100644 index 00000000..e55bd31b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt @@ -0,0 +1,23 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithMultiLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: multi +line +fail +message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt new file mode 100644 index 00000000..e55bd31b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt @@ -0,0 +1,23 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithMultiLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: multi +line +fail +message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt new file mode 100644 index 00000000..2342df65 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt @@ -0,0 +1,30 @@ +. +'-- JUnit Jupiter [OK] + '-- Fail [OK] + '-- failWithMultiLineMessage() [X] multi + line + fail + message + +Failures (1): + JUnit Jupiter:Fail:failWithMultiLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: multi +line +fail +message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt new file mode 100644 index 00000000..5e557707 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt @@ -0,0 +1,30 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Fail ✔ + └─ failWithMultiLineMessage() ✘ multi + line + fail + message + +Failures (1): + JUnit Jupiter:Fail:failWithMultiLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: multi +line +fail +message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt new file mode 100644 index 00000000..62bcc4f6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt @@ -0,0 +1,33 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Fail +| | +-- failWithMultiLineMessage() +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] +| | | caught: org.opentest4j.AssertionFailedError: multi +| | | line +| | | fail +| | | message +>> S T A C K T R A C E >> +\| \| \| duration: [\d]+ ms +| | | status: [X] FAILED +\| '-- Fail finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt new file mode 100644 index 00000000..5c087c57 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt @@ -0,0 +1,33 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Fail +│ │ ├─ failWithMultiLineMessage() +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] +│ │ │ caught: org.opentest4j.AssertionFailedError: multi +│ │ │ line +│ │ │ fail +│ │ │ message +>> S T A C K T R A C E >> +│ │ │ duration: [\d]+ ms +│ │ │ status: ✘ FAILED +│ └─ Fail finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt new file mode 100644 index 00000000..04752703 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt @@ -0,0 +1,24 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Started: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) +Finished: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) + => Exception: org.opentest4j.AssertionFailedError: single line fail message +>> S T A C K T R A C E >> +Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt new file mode 100644 index 00000000..04752703 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt @@ -0,0 +1,24 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Started: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) +Finished: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) + => Exception: org.opentest4j.AssertionFailedError: single line fail message +>> S T A C K T R A C E >> +Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt new file mode 100644 index 00000000..c734ea9f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt @@ -0,0 +1,20 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithSingleLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: single line fail message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt new file mode 100644 index 00000000..c734ea9f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt @@ -0,0 +1,20 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithSingleLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: single line fail message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt new file mode 100644 index 00000000..c734ea9f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt @@ -0,0 +1,20 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithSingleLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: single line fail message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt new file mode 100644 index 00000000..c734ea9f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt @@ -0,0 +1,20 @@ + +Failures (1): + JUnit Jupiter:Fail:failWithSingleLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: single line fail message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt new file mode 100644 index 00000000..256a14db --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt @@ -0,0 +1,24 @@ +. +'-- JUnit Jupiter [OK] + '-- Fail [OK] + '-- failWithSingleLineMessage() [X] single line fail message + +Failures (1): + JUnit Jupiter:Fail:failWithSingleLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: single line fail message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt new file mode 100644 index 00000000..d1e99da9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt @@ -0,0 +1,24 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Fail ✔ + └─ failWithSingleLineMessage() ✘ single line fail message + +Failures (1): + JUnit Jupiter:Fail:failWithSingleLineMessage() + MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] + => org.opentest4j.AssertionFailedError: single line fail message +>> STACKTRACE >> + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt new file mode 100644 index 00000000..6b69d602 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt @@ -0,0 +1,30 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Fail +| | +-- failWithSingleLineMessage() +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] +| | | caught: org.opentest4j.AssertionFailedError: single line fail message +>> S T A C K T R A C E >> +\| \| \| duration: [\d]+ ms +| | | status: [X] FAILED +\| '-- Fail finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt new file mode 100644 index 00000000..6dc5cca6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt @@ -0,0 +1,30 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Fail +│ │ ├─ failWithSingleLineMessage() +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] +│ │ │ caught: org.opentest4j.AssertionFailedError: single line fail message +>> S T A C K T R A C E >> +│ │ │ duration: [\d]+ ms +│ │ │ status: ✘ FAILED +│ └─ Fail finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt new file mode 100644 index 00000000..588f1409 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt @@ -0,0 +1,28 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) +Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] +Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] +Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] +Finished: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt new file mode 100644 index 00000000..588f1409 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt @@ -0,0 +1,28 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) +Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] +Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] +Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] +Finished: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt new file mode 100644 index 00000000..177e716f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt @@ -0,0 +1,26 @@ +. +'-- JUnit Jupiter [OK] + '-- Report [OK] + '-- reportMultiEntriesWithMultiMappings(TestReporter) [OK] + ....-..-..T..:...* + user name = `dk38` + award year = `1974` + ....-..-..T..:...* single = `mapping` + ....-..-..T..:...* + user name = `st77` + award year = `1977` + last seen = `2001` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt new file mode 100644 index 00000000..ab028e5f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt @@ -0,0 +1,26 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Report ✔ + └─ reportMultiEntriesWithMultiMappings(TestReporter) ✔ + ....-..-..T..:...* + user name = `dk38` + award year = `1974` + ....-..-..T..:...* single = `mapping` + ....-..-..T..:...* + user name = `st77` + award year = `1977` + last seen = `2001` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt new file mode 100644 index 00000000..d67edf08 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt @@ -0,0 +1,31 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Report +| | +-- reportMultiEntriesWithMultiMappings(TestReporter) +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithMultiMappings', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] +\| \| \| duration: [\d]+ ms +| | | status: [OK] SUCCESSFUL +\| '-- Report finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt new file mode 100644 index 00000000..be34c7a2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt @@ -0,0 +1,31 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Report +│ │ ├─ reportMultiEntriesWithMultiMappings(TestReporter) +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithMultiMappings', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] +│ │ │ duration: [\d]+ ms +│ │ │ status: ✔ SUCCESSFUL +│ └─ Report finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt new file mode 100644 index 00000000..99357843 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt @@ -0,0 +1,26 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] +Finished: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt new file mode 100644 index 00000000..99357843 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt @@ -0,0 +1,26 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] +Finished: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt new file mode 100644 index 00000000..a7b2f6a4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt @@ -0,0 +1,20 @@ +. +'-- JUnit Jupiter [OK] + '-- Report [OK] + '-- reportMultiEntriesWithSingleMapping(TestReporter) [OK] + ....-..-..T..:...* foo = `bar` + ....-..-..T..:...* far = `boo` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt new file mode 100644 index 00000000..49d82d1c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt @@ -0,0 +1,20 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Report ✔ + └─ reportMultiEntriesWithSingleMapping(TestReporter) ✔ + ....-..-..T..:...* foo = `bar` + ....-..-..T..:...* far = `boo` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt new file mode 100644 index 00000000..7cb17e6f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt @@ -0,0 +1,30 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Report +| | +-- reportMultiEntriesWithSingleMapping(TestReporter) +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] +\| \| \| duration: [\d]+ ms +| | | status: [OK] SUCCESSFUL +\| '-- Report finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt new file mode 100644 index 00000000..55dccea9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt @@ -0,0 +1,30 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Report +│ │ ├─ reportMultiEntriesWithSingleMapping(TestReporter) +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] +│ │ │ duration: [\d]+ ms +│ │ │ status: ✔ SUCCESSFUL +│ └─ Report finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt new file mode 100644 index 00000000..94111e48 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt @@ -0,0 +1,26 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) +Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] +Finished: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt new file mode 100644 index 00000000..94111e48 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt @@ -0,0 +1,26 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) +Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] +Finished: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt new file mode 100644 index 00000000..792a952d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt @@ -0,0 +1,20 @@ +. +'-- JUnit Jupiter [OK] + '-- Report [OK] + '-- reportMultipleMessages(TestReporter) [OK] + ....-..-..T..:...* value = `foo` + ....-..-..T..:...* value = `bar` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt new file mode 100644 index 00000000..b211a265 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt @@ -0,0 +1,20 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Report ✔ + └─ reportMultipleMessages(TestReporter) ✔ + ....-..-..T..:...* value = `foo` + ....-..-..T..:...* value = `bar` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt new file mode 100644 index 00000000..38022210 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt @@ -0,0 +1,30 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Report +| | +-- reportMultipleMessages(TestReporter) +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultipleMessages', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] +\| \| \| duration: [\d]+ ms +| | | status: [OK] SUCCESSFUL +\| '-- Report finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt new file mode 100644 index 00000000..a281ac28 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt @@ -0,0 +1,30 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Report +│ │ ├─ reportMultipleMessages(TestReporter) +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultipleMessages', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] +│ │ │ duration: [\d]+ ms +│ │ │ status: ✔ SUCCESSFUL +│ └─ Report finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt new file mode 100644 index 00000000..58c3d7c7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt @@ -0,0 +1,24 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Reported: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +Finished: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt new file mode 100644 index 00000000..58c3d7c7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt @@ -0,0 +1,24 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Reported: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +Finished: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt new file mode 100644 index 00000000..f6670d86 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt @@ -0,0 +1,19 @@ +. +'-- JUnit Jupiter [OK] + '-- Report [OK] + '-- reportSingleEntryWithSingleMapping(TestReporter) [OK] + ....-..-..T..:...* foo = `bar` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt new file mode 100644 index 00000000..15319f4d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt @@ -0,0 +1,19 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Report ✔ + └─ reportSingleEntryWithSingleMapping(TestReporter) ✔ + ....-..-..T..:...* foo = `bar` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt new file mode 100644 index 00000000..f5432406 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt @@ -0,0 +1,29 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Report +| | +-- reportSingleEntryWithSingleMapping(TestReporter) +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleEntryWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +\| \| \| duration: [\d]+ ms +| | | status: [OK] SUCCESSFUL +\| '-- Report finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt new file mode 100644 index 00000000..3a592fab --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt @@ -0,0 +1,29 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Report +│ │ ├─ reportSingleEntryWithSingleMapping(TestReporter) +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleEntryWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] +│ │ │ duration: [\d]+ ms +│ │ │ status: ✔ SUCCESSFUL +│ └─ Report finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt new file mode 100644 index 00000000..a5397dc6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt @@ -0,0 +1,24 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) +Reported: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +Finished: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt new file mode 100644 index 00000000..a5397dc6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt @@ -0,0 +1,24 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Started: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) +Reported: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) + => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +Finished: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) +Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt new file mode 100644 index 00000000..03825a46 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt new file mode 100644 index 00000000..06123735 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt @@ -0,0 +1,19 @@ +. +'-- JUnit Jupiter [OK] + '-- Report [OK] + '-- reportSingleMessage(TestReporter) [OK] + ....-..-..T..:...* value = `foo` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt new file mode 100644 index 00000000..485ea73f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt @@ -0,0 +1,19 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Report ✔ + └─ reportSingleMessage(TestReporter) ✔ + ....-..-..T..:...* value = `foo` + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt new file mode 100644 index 00000000..3cfe0069 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt @@ -0,0 +1,29 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Report +| | +-- reportSingleMessage(TestReporter) +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleMessage', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +\| \| \| duration: [\d]+ ms +| | | status: [OK] SUCCESSFUL +\| '-- Report finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt new file mode 100644 index 00000000..0539900d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt @@ -0,0 +1,29 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Report +│ │ ├─ reportSingleMessage(TestReporter) +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleMessage', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] +│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] +│ │ │ duration: [\d]+ ms +│ │ │ status: ✔ SUCCESSFUL +│ └─ Report finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 0 tests skipped ] +[ 1 tests started ] +[ 0 tests aborted ] +[ 1 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt new file mode 100644 index 00000000..04e6ca20 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt @@ -0,0 +1,25 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Skipped: skipWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()]) + => Reason: multi + line + fail + message +Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt new file mode 100644 index 00000000..04e6ca20 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt @@ -0,0 +1,25 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Skipped: skipWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()]) + => Reason: multi + line + fail + message +Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt new file mode 100644 index 00000000..c5e5579e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt new file mode 100644 index 00000000..c5e5579e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt new file mode 100644 index 00000000..ae8bb3e5 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt @@ -0,0 +1,21 @@ +. +'-- JUnit Jupiter [OK] + '-- Skip [OK] + '-- skipWithMultiLineMessage() [S] multi + line + fail + message + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt new file mode 100644 index 00000000..d9c1d79c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt @@ -0,0 +1,21 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Skip ✔ + └─ skipWithMultiLineMessage() ↷ multi + line + fail + message + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt new file mode 100644 index 00000000..e63b0b52 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt @@ -0,0 +1,31 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Skip +| | +-- skipWithMultiLineMessage() +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithMultiLineMessage', methodParameterTypes = ''] +| | | reason: multi +| | | line +| | | fail +| | | message +| | | status: [S] SKIPPED +\| '-- Skip finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt new file mode 100644 index 00000000..ebf4caf9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt @@ -0,0 +1,31 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Skip +│ │ ├─ skipWithMultiLineMessage() +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithMultiLineMessage', methodParameterTypes = ''] +│ │ │ reason: multi +│ │ │ line +│ │ │ fail +│ │ │ message +│ │ │ status: ↷ SKIPPED +│ └─ Skip finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt new file mode 100644 index 00000000..d603b695 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt @@ -0,0 +1,22 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Skipped: skipWithSingleLineReason() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()]) + => Reason: single line skip reason +Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt new file mode 100644 index 00000000..d603b695 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt @@ -0,0 +1,22 @@ +Test execution started. Number of static tests: 1 +Started: JUnit Jupiter ([engine:junit-jupiter]) +Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Skipped: skipWithSingleLineReason() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()]) + => Reason: single line skip reason +Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) +Finished: JUnit Jupiter ([engine:junit-jupiter]) +Test execution finished. + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt new file mode 100644 index 00000000..c5e5579e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt new file mode 100644 index 00000000..c5e5579e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt @@ -0,0 +1,14 @@ + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt new file mode 100644 index 00000000..7e2b8a40 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt @@ -0,0 +1,18 @@ +. +'-- JUnit Jupiter [OK] + '-- Skip [OK] + '-- skipWithSingleLineReason() [S] single line skip reason + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt new file mode 100644 index 00000000..df6349dc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt @@ -0,0 +1,18 @@ +╷ +└─ JUnit Jupiter ✔ + └─ Skip ✔ + └─ skipWithSingleLineReason() ↷ single line skip reason + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt new file mode 100644 index 00000000..b24577c6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt @@ -0,0 +1,28 @@ +Test plan execution started. Number of static tests: 1 +. ++-- JUnit Jupiter +| +-- Skip +| | +-- skipWithSingleLineReason() +| | | tags: [] +| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()] +| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] +| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithSingleLineReason', methodParameterTypes = ''] +| | | reason: single line skip reason +| | | status: [S] SKIPPED +\| '-- Skip finished after [\d]+ ms\. +'-- JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt new file mode 100644 index 00000000..01537a1c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt @@ -0,0 +1,28 @@ +Test plan execution started. Number of static tests: 1 +╷ +├─ JUnit Jupiter +│ ├─ Skip +│ │ ├─ skipWithSingleLineReason() +│ │ │ tags: [] +│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()] +│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] +│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithSingleLineReason', methodParameterTypes = ''] +│ │ │ reason: single line skip reason +│ │ │ status: ↷ SKIPPED +│ └─ Skip finished after [\d]+ ms\. +└─ JUnit Jupiter finished after [\d]+ ms\. +Test plan execution finished. Number of all tests: 1 + +Test run finished after [\d]+ ms +[ 2 containers found ] +[ 0 containers skipped ] +[ 2 containers started ] +[ 0 containers aborted ] +[ 2 containers successful ] +[ 0 containers failed ] +[ 1 tests found ] +[ 1 tests skipped ] +[ 0 tests started ] +[ 0 tests aborted ] +[ 0 tests successful ] +[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt new file mode 100644 index 00000000..91c5d9e7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt @@ -0,0 +1,3 @@ +I am used by tests, so... + +Do NOT delete me! \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd new file mode 100644 index 00000000..9ee9cea4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle new file mode 100644 index 00000000..a4185e83 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle @@ -0,0 +1 @@ +// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle new file mode 100644 index 00000000..a4185e83 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle @@ -0,0 +1 @@ +// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts new file mode 100644 index 00000000..a4185e83 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts @@ -0,0 +1 @@ +// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts new file mode 100644 index 00000000..a4185e83 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts @@ -0,0 +1 @@ +// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml new file mode 100644 index 00000000..1a462ec9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml @@ -0,0 +1 @@ + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..653e2017 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java new file mode 100644 index 00000000..0b855e5d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java @@ -0,0 +1,3 @@ +package foo.bar; + +public class FooBar {} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java new file mode 100644 index 00000000..d216af08 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java @@ -0,0 +1,3 @@ +open module foo.bar { + requires foo; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java new file mode 100644 index 00000000..7f566baf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java @@ -0,0 +1,3 @@ +package foo; + +public class Foo {} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java new file mode 100644 index 00000000..43084978 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java @@ -0,0 +1,3 @@ +module foo { + exports foo; +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/serialized-test-identifier b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/serialized-test-identifier new file mode 100644 index 0000000000000000000000000000000000000000..614b53bb6557e251e93c145414c5f3efa2dd0f53 GIT binary patch literal 1079 zcmb7Dv2N5r5S_amkca?DkccRPgcK+u*lj3Kt^kn)5y?V`a~h%`E%u&o!`|B2U5C3S zx`rAlQfT-EdJw-rMFkC?kUA~1YX@7WBM~lad1vO$dv9hxAHxNsVI|cG+0B)(B$Lc` zQ{5+$fPARuLr?iot`ea*b!<9555ZfO zCC0+FqH%78q#bU3V3;8gJqKu=n@Eeyrg}NV0xX(AY!)lz zK&ZDoe#T4>`~Q+ZUmg8kK@it}zRD`&-`3wBef;|6!IV?=ZWhJOJ$mdkLIY86Pw=(e z4(DUY5}%66o@7g%-WNBN7Q^`9K*RmBQO5SVM1%#;_^M~@ZYkMNdA}ZXEy9gXXklq= OujzKw6Ea@)@0~x1PiaX2 literal 0 HcmV?d00001 diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties new file mode 100644 index 00000000..1ba78cae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties @@ -0,0 +1 @@ +org.junit.platform.launcher.core.LauncherConfigurationParametersTests = from config file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener new file mode 100644 index 00000000..3c8c2d50 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener @@ -0,0 +1 @@ +org.junit.platform.launcher.TestLauncherDiscoveryListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 00000000..a41b4e59 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1 @@ +org.junit.platform.launcher.TestLauncherSessionListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter new file mode 100644 index 00000000..c7c86539 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter @@ -0,0 +1 @@ +org.junit.platform.launcher.TestPostDiscoveryTagFilter diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 00000000..4bb9bbe7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1,3 @@ +org.junit.platform.launcher.listeners.NoopTestExecutionListener +org.junit.platform.launcher.listeners.UnusedTestExecutionListener +org.junit.platform.launcher.listeners.AnotherUnusedTestExecutionListener \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties new file mode 100644 index 00000000..7b87d3ca --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties @@ -0,0 +1 @@ +junit.platform.execution.listeners.deactivate=org.junit.*.launcher.listeners.Unused*,org.junit.*.launcher.listeners.AnotherUnused* \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts new file mode 100644 index 00000000..f594af84 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -0,0 +1,174 @@ +import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.jvm.toolchain.internal.NoToolchainAvailableException + +plugins { + `kotlin-library-conventions` + `testing-conventions` +} + +javaLibrary { + mainJavaVersion = JavaVersion.VERSION_11 +} + +spotless { + java { + target(files(project.java.sourceSets.map { it.allJava }), "projects/**/*.java") + } + kotlin { + target("projects/**/*.kt") + } + format("projects") { + target("projects/**/*.gradle.kts", "projects/**/*.md") + trimTrailingWhitespace() + endWithNewline() + } +} + +val thirdPartyJars by configurations.creating +val antJars by configurations.creating +val mavenDistribution by configurations.creating + +dependencies { + implementation(libs.bartholdy) { + because("manage external tool installations") + } + implementation(libs.commons.io) { + because("moving/deleting directory trees") + } + + testImplementation(libs.archunit) { + because("checking the architecture of JUnit 5") + } + testImplementation(libs.apiguardian) { + because("we validate that public classes are annotated") + } + testImplementation(libs.groovy4) { + because("it provides convenience methods to handle process output") + } + testImplementation(libs.bnd) { + because("parsing OSGi metadata") + } + testRuntimeOnly(libs.slf4j.julBinding) { + because("provide appropriate SLF4J binding") + } + testImplementation(libs.ant) { + because("we reference Ant's main class") + } + testImplementation(libs.bundles.xmlunit) + + thirdPartyJars(libs.junit4) + thirdPartyJars(libs.assertj) + thirdPartyJars(libs.apiguardian) + thirdPartyJars(libs.hamcrest) + thirdPartyJars(libs.opentest4j) + + antJars(platform(projects.junitBom)) + antJars(libs.bundles.ant) + antJars(projects.junitPlatformConsoleStandalone) + antJars(projects.junitPlatformLauncher) + antJars(projects.junitPlatformReporting) + + mavenDistribution(libs.maven) { + artifact { + classifier = "bin" + type = "zip" + isTransitive = false + } + } +} + +val unzipMavenDistribution by tasks.registering(Sync::class) { + from(zipTree(mavenDistribution.elements.map { it.single() })) + into(layout.buildDirectory.dir("maven-distribution")) +} + +tasks.test { + // Opt-out via system property: '-Dplatform.tooling.support.tests.enabled=false' + enabled = System.getProperty("platform.tooling.support.tests.enabled")?.toBoolean() ?: true + + // The following if-block is necessary since Gradle will otherwise + // always publish all mavenizedProjects even if this "test" task + // is not executed. + if (enabled) { + + // All maven-aware projects must be installed, i.e. published to the local repository + val mavenizedProjects: List by rootProject + val tempRepoName: String by rootProject + + (mavenizedProjects + projects.junitBom.dependencyProject) + .map { project -> project.tasks.named("publishAllPublicationsTo${tempRepoName.capitalize()}Repository") } + .forEach { dependsOn(it) } + } + + val tempRepoDir: File by rootProject + jvmArgumentProviders += MavenRepo(tempRepoDir) + jvmArgumentProviders += JarPath(thirdPartyJars) + jvmArgumentProviders += JarPath(antJars) + jvmArgumentProviders += MavenDistribution(project, unzipMavenDistribution) + + (options as JUnitPlatformOptions).apply { + includeEngines("archunit") + } + + inputs.apply { + dir("projects").withPathSensitivity(RELATIVE) + file("${rootDir}/gradle.properties") + file("${rootDir}/settings.gradle.kts") + file("${rootDir}/gradlew") + file("${rootDir}/gradlew.bat") + dir("${rootDir}/gradle/wrapper").withPathSensitivity(RELATIVE) + dir("${rootDir}/documentation/src/main").withPathSensitivity(RELATIVE) + dir("${rootDir}/documentation/src/test").withPathSensitivity(RELATIVE) + } + + distribution { + requirements.add("jdk=8") + localOnly { + includeClasses.add("platform.tooling.support.tests.GraalVmStarterTests") // GraalVM is not installed on Test Distribution agents + } + } + jvmArgumentProviders += JavaHomeDir(project, 8) +} + +class MavenRepo(@get:InputDirectory @get:PathSensitive(RELATIVE) val repoDir: File) : CommandLineArgumentProvider { + override fun asArguments() = listOf("-Dmaven.repo=$repoDir") +} + +class JavaHomeDir(project: Project, @Input val version: Int) : CommandLineArgumentProvider { + @Internal + val passToolchain = project.providers.gradleProperty("enableTestDistribution").map(String::toBoolean).orElse(false).map { !it } + + @Internal + val javaLauncher: Property = project.objects.property() + .value(project.provider { + try { + project.javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(version)) + }.get() + } catch (e: NoToolchainAvailableException) { + null + } + }) + + override fun asArguments(): List { + if (passToolchain.get()) { + val metadata = javaLauncher.map { it.metadata } + val javaHome = metadata.map { it.installationPath.asFile.absolutePath }.orNull + return javaHome?.let { listOf("-Djava.home.$version=$it") } ?: emptyList() + } + return emptyList() + } +} + +class JarPath(@Classpath val configuration: Configuration, @Input val key: String = configuration.name) : CommandLineArgumentProvider { + override fun asArguments() = listOf("-D${key}=${configuration.asPath}") +} + +class MavenDistribution(project: Project, sourceTask: TaskProvider<*>) : CommandLineArgumentProvider { + @InputDirectory + @PathSensitive(RELATIVE) + val mavenDistribution: DirectoryProperty = project.objects.directoryProperty() + .value(project.layout.dir(sourceTask.map { it.outputs.files.singleFile.listFiles()!!.single() })) + + override fun asArguments() = listOf("-DmavenDistribution=${mavenDistribution.get().asFile.absolutePath}") +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml new file mode 100644 index 00000000..6c805ceb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java new file mode 100644 index 00000000..6feca6bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java new file mode 100644 index 00000000..695282ba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class CalculatorTests { + + @Test + @DisplayName("1 + 1 = 2") + void addsTwoNumbers() { + Calculator calculator = new Calculator(); + assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); + } + + @ParameterizedTest(name = "{0} + {1} = {2}") + @CsvSource({ // + "0, 1, 1", // + "1, 2, 3", // + "49, 51, 100", // + "1, 100, 101" // + }) + void add(int first, int second, int expectedResult) { + Calculator calculator = new Calculator(); + assertEquals(expectedResult, calculator.add(first, second), + () -> first + " + " + second + " should equal " + expectedResult); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts new file mode 100644 index 00000000..edd8bd7b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + java + id("org.graalvm.buildtools.native") +} + +val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") +val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") + +repositories { + maven { url = uri(file(System.getProperty("maven.repo"))) } + mavenCentral() +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") +} + +tasks.test { + useJUnitPlatform() + + val outputDir = reports.junitXml.outputLocation + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties new file mode 100644 index 00000000..83d0ce01 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=GRAALVM_HOME diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts new file mode 100644 index 00000000..c928064e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts @@ -0,0 +1,11 @@ +pluginManagement { + plugins { + id("org.graalvm.buildtools.native") version "0.9.13" + } + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = "graalvm-starter" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java new file mode 100644 index 00000000..6feca6bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java new file mode 100644 index 00000000..695282ba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class CalculatorTests { + + @Test + @DisplayName("1 + 1 = 2") + void addsTwoNumbers() { + Calculator calculator = new Calculator(); + assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); + } + + @ParameterizedTest(name = "{0} + {1} = {2}") + @CsvSource({ // + "0, 1, 1", // + "1, 2, 3", // + "49, 51, 100", // + "1, 100, 101" // + }) + void add(int first, int second, int expectedResult) { + Calculator calculator = new Calculator(); + assertEquals(expectedResult, calculator.add(first, second), + () -> first + " + " + second + " should equal " + expectedResult); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts new file mode 100644 index 00000000..c79b5ff7 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts @@ -0,0 +1,33 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.2.71" +} + +repositories { + maven { url = uri(file(System.getProperty("maven.repo"))) } + mavenCentral() +} + +// grab jupiter version from system environment +val jupiterVersion = System.getenv("JUNIT_JUPITER_VERSION") + +dependencies { + testImplementation(kotlin("stdlib-jdk8")) + testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") +} + +tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "1.8" + apiVersion = "1.1" + languageVersion = "1.1" + } +} + +tasks.test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..5028f28f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew new file mode 100644 index 00000000..83f2acfd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat new file mode 100644 index 00000000..9618d8d9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts new file mode 100644 index 00000000..7a052453 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "gradle-kotlin-extensions" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt new file mode 100644 index 00000000..0de75b1f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package com.example.project + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.fail +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.aggregator.ArgumentsAccessor +import org.junit.jupiter.params.aggregator.get +import org.junit.jupiter.params.provider.CsvSource +import org.opentest4j.AssertionFailedError + +class ExtensionFunctionsTests { + + @Test + fun `assertDoesNotThrow() and assertAll`() { + assertDoesNotThrow { + assertAll(setOf {}) + } + assertDoesNotThrow("message") { + assertAll("header", setOf {}) + } + assertDoesNotThrow({ "message" }) { + assertAll({}) + assertAll("header", {}) + } + } + + @Test + fun `fail() and assertThrows`() { + assertThrows { + fail("message") + } + assertThrows("message") { + fail { "message" } + } + assertThrows({ "message" }) { + fail(IllegalArgumentException()) + } + } + + @ParameterizedTest + @CsvSource("1") + fun accessor(accessor: ArgumentsAccessor) { + val value = accessor.get(0) + assertEquals(1, value) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts new file mode 100644 index 00000000..022b1d3a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + java +} + +// grab jupiter version from system environment +val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") +val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") +val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") + +// emit default file encoding to a file +file("file.encoding.txt").writeText(System.getProperty("file.encoding")) + +// emit more Java runtime information +file("java.runtime.txt").writeText(""" +java.version=${System.getProperty("java.version")} +""") + +// emit versions of JUnit groups +file("junit.versions.txt").writeText(""" +jupiterVersion=$jupiterVersion +vintageVersion=$vintageVersion +platformVersion=$platformVersion +""") + +repositories { + maven { url = uri(file(System.getProperty("maven.repo"))) } + mavenCentral() +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter-api:$jupiterVersion") { + exclude(group = "org.junit.jupiter", module = "junit-jupiter-engine") + } +} + +tasks.test { + useJUnitPlatform() +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts new file mode 100644 index 00000000..c68450bb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "gradle-missing-engine" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java new file mode 100644 index 00000000..86d922c2 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java @@ -0,0 +1,22 @@ + +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class FooTests { + + @Test + void test() { + fail("This test must not be executed!"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts new file mode 100644 index 00000000..dfc4a31a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + java +} + +// grab jupiter version from system environment +val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") +val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") +val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") + +repositories { + maven { url = uri(file(System.getProperty("maven.repo"))) } + mavenCentral() +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") +} + +tasks.test { + useJUnitPlatform() + + testLogging { + events("passed", "skipped", "failed") + } + + reports { + html.isEnabled = true + } + + val outputDir = reports.junitXml.outputLocation + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ) + } + + doFirst { + println("Using Java version: ${JavaVersion.current()}") + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts new file mode 100644 index 00000000..48194085 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "gradle-starter" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java new file mode 100644 index 00000000..6feca6bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java new file mode 100644 index 00000000..695282ba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class CalculatorTests { + + @Test + @DisplayName("1 + 1 = 2") + void addsTwoNumbers() { + Calculator calculator = new Calculator(); + assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); + } + + @ParameterizedTest(name = "{0} + {1} = {2}") + @CsvSource({ // + "0, 1, 1", // + "1, 2, 3", // + "49, 51, 100", // + "1, 100, 101" // + }) + void add(int first, int second, int expectedResult) { + Calculator calculator = new Calculator(); + assertEquals(expectedResult, calculator.add(first, second), + () -> first + " + " + second + " should equal " + expectedResult); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt new file mode 100644 index 00000000..0b4810a6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt @@ -0,0 +1,12 @@ +org.junit.jupiter.api@${jupiterVersion} jar:file:.+/junit-jupiter-api-\d.+\.jar..module-info\.class +exports org.junit.jupiter.api +exports org.junit.jupiter.api.condition +exports org.junit.jupiter.api.extension +exports org.junit.jupiter.api.function +exports org.junit.jupiter.api.io +exports org.junit.jupiter.api.parallel +requires java.base mandated +requires org.apiguardian.api static transitive +requires org.junit.platform.commons transitive +requires org.opentest4j transitive +qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt new file mode 100644 index 00000000..5c637457 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt @@ -0,0 +1,11 @@ +org.junit.jupiter.engine@${jupiterVersion} jar:file:.+/junit-jupiter-engine-\d.+\.jar..module-info\.class +requires java.base mandated +requires org.apiguardian.api static +requires org.junit.jupiter.api +requires org.junit.platform.commons +requires org.junit.platform.engine +requires org.opentest4j +uses org.junit.jupiter.api.extension.Extension +provides org.junit.platform.engine.TestEngine with org.junit.jupiter.engine.JupiterTestEngine +qualified opens org.junit.jupiter.engine.extension to org.junit.platform.commons +contains org.junit.jupiter.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt new file mode 100644 index 00000000..74712571 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt @@ -0,0 +1,11 @@ +org.junit.jupiter.migrationsupport@${jupiterVersion} jar:file:.+/junit-jupiter-migrationsupport-\d.+\.jar..module-info\.class +exports org.junit.jupiter.migrationsupport +exports org.junit.jupiter.migrationsupport.conditions +exports org.junit.jupiter.migrationsupport.rules +exports org.junit.jupiter.migrationsupport.rules.adapter +exports org.junit.jupiter.migrationsupport.rules.member +requires java.base mandated +requires junit transitive +requires org.apiguardian.api static transitive +requires org.junit.jupiter.api transitive +requires org.junit.platform.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt new file mode 100644 index 00000000..05e8b8ac --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt @@ -0,0 +1,13 @@ +org.junit.jupiter.params@${jupiterVersion} jar:file:.+/junit-jupiter-params-\d.+\.jar..module-info\.class +exports org.junit.jupiter.params +exports org.junit.jupiter.params.aggregator +exports org.junit.jupiter.params.converter +exports org.junit.jupiter.params.provider +exports org.junit.jupiter.params.support +requires java.base mandated +requires org.apiguardian.api static transitive +requires org.junit.jupiter.api transitive +requires org.junit.platform.commons transitive +qualified opens org.junit.jupiter.params to org.junit.platform.commons +qualified opens org.junit.jupiter.params.converter to org.junit.platform.commons +qualified opens org.junit.jupiter.params.provider to org.junit.platform.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt new file mode 100644 index 00000000..76834239 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt @@ -0,0 +1,5 @@ +org.junit.jupiter@${jupiterVersion} jar:file:.+/junit-jupiter-\d.+\.jar..module-info\.class +requires java.base mandated +requires org.junit.jupiter.api transitive +requires org.junit.jupiter.engine transitive +requires org.junit.jupiter.params transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt new file mode 100644 index 00000000..5c0c9b44 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -0,0 +1,11 @@ +org.junit.platform.commons@${platformVersion} jar:file:.+/junit-platform-commons-\d.+\.jar..module-info\.class +exports org.junit.platform.commons +exports org.junit.platform.commons.annotation +exports org.junit.platform.commons.function +exports org.junit.platform.commons.support +requires java.base mandated +requires java.logging +requires java.management +requires org.apiguardian.api static transitive +qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine +qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.commons org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt new file mode 100644 index 00000000..4324e45e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt @@ -0,0 +1,13 @@ +org.junit.platform.console@${platformVersion} jar:file:.+/junit-platform-console-\d.+\.jar..module-info\.class +requires java.base mandated +requires org.apiguardian.api static +requires org.junit.platform.commons +requires org.junit.platform.engine +requires org.junit.platform.launcher +requires org.junit.platform.reporting +provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider +contains org.junit.platform.console +contains org.junit.platform.console.options +contains org.junit.platform.console.shadow.picocli +contains org.junit.platform.console.tasks +main-class org.junit.platform.console.ConsoleLauncher diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt new file mode 100644 index 00000000..d6f69626 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt @@ -0,0 +1,13 @@ +org.junit.platform.engine@${platformVersion} jar:file:.+/junit-platform-engine-\d.+\.jar..module-info\.class +exports org.junit.platform.engine +exports org.junit.platform.engine.discovery +exports org.junit.platform.engine.reporting +exports org.junit.platform.engine.support.config +exports org.junit.platform.engine.support.descriptor +exports org.junit.platform.engine.support.discovery +exports org.junit.platform.engine.support.filter +exports org.junit.platform.engine.support.hierarchical +requires java.base mandated +requires org.apiguardian.api static transitive +requires org.junit.platform.commons transitive +requires org.opentest4j transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt new file mode 100644 index 00000000..10bec3de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt @@ -0,0 +1,9 @@ +org.junit.platform.jfr@${platformVersion} jar:file:.+/junit-platform-jfr-\d.+\.jar..module-info\.class +requires java.base mandated +requires jdk.jfr +requires org.apiguardian.api static +requires org.junit.platform.engine +requires org.junit.platform.launcher +provides org.junit.platform.launcher.LauncherDiscoveryListener with org.junit.platform.jfr.FlightRecordingDiscoveryListener +provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.jfr.FlightRecordingExecutionListener +contains org.junit.platform.jfr diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt new file mode 100644 index 00000000..1461169b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt @@ -0,0 +1,16 @@ +org.junit.platform.launcher@${platformVersion} jar:file:.+/junit-platform-launcher-\d.+\.jar..module-info\.class +exports org.junit.platform.launcher +exports org.junit.platform.launcher.core +exports org.junit.platform.launcher.listeners +exports org.junit.platform.launcher.listeners.discovery +requires java.base mandated +requires java.logging transitive +requires org.apiguardian.api static transitive +requires org.junit.platform.commons transitive +requires org.junit.platform.engine transitive +uses org.junit.platform.engine.TestEngine +uses org.junit.platform.launcher.LauncherDiscoveryListener +uses org.junit.platform.launcher.LauncherSessionListener +uses org.junit.platform.launcher.PostDiscoveryFilter +uses org.junit.platform.launcher.TestExecutionListener +provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.launcher.listeners.UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt new file mode 100644 index 00000000..e9dbd208 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt @@ -0,0 +1,11 @@ +org.junit.platform.reporting@${platformVersion} jar:file:.+/junit-platform-reporting-\d.+\.jar..module-info\.class +exports org.junit.platform.reporting.legacy +exports org.junit.platform.reporting.legacy.xml +exports org.junit.platform.reporting.open.xml +requires java.base mandated +requires java.xml +requires org.apiguardian.api static transitive +requires org.junit.platform.commons +requires org.junit.platform.engine transitive +requires org.junit.platform.launcher transitive +provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt new file mode 100644 index 00000000..ee4ae179 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt @@ -0,0 +1,8 @@ +org.junit.platform.runner@${platformVersion} jar:file:.+/junit-platform-runner-\d.+\.jar..module-info\.class +exports org.junit.platform.runner +requires java.base mandated +requires junit transitive +requires org.apiguardian.api static transitive +requires org.junit.platform.launcher transitive +requires org.junit.platform.suite.api transitive +requires org.junit.platform.suite.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt new file mode 100644 index 00000000..5e3da71a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt @@ -0,0 +1,5 @@ +org.junit.platform.suite.api@${platformVersion} jar:file:.+/junit-platform-suite-api-\d.+\.jar..module-info\.class +exports org.junit.platform.suite.api +requires java.base mandated +requires org.apiguardian.api static transitive +requires org.junit.platform.commons transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt new file mode 100644 index 00000000..493962b6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt @@ -0,0 +1,8 @@ +org.junit.platform.suite.commons@${platformVersion} jar:file:.+/junit-platform-suite-commons-\d.+\.jar..module-info\.class +requires java.base mandated +requires org.apiguardian.api static transitive +requires org.junit.platform.commons +requires org.junit.platform.engine +requires org.junit.platform.launcher transitive +requires org.junit.platform.suite.api +qualified exports org.junit.platform.suite.commons to org.junit.platform.runner org.junit.platform.suite.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt new file mode 100644 index 00000000..4a569458 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt @@ -0,0 +1,10 @@ +org.junit.platform.suite.engine@${platformVersion} jar:file:.+/junit-platform-suite-engine-\d.+\.jar..module-info\.class +requires java.base mandated +requires org.apiguardian.api static +requires org.junit.platform.commons +requires org.junit.platform.engine +requires org.junit.platform.launcher +requires org.junit.platform.suite.api +requires org.junit.platform.suite.commons +provides org.junit.platform.engine.TestEngine with org.junit.platform.suite.engine.SuiteTestEngine +contains org.junit.platform.suite.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt new file mode 100644 index 00000000..d6114f72 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt @@ -0,0 +1,4 @@ +org.junit.platform.suite@${platformVersion} jar:file:.+/junit-platform-suite-\d.+\.jar..module-info\.class +requires java.base mandated +requires org.junit.platform.suite.api transitive +requires org.junit.platform.suite.engine transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt new file mode 100644 index 00000000..2ad3e8d1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt @@ -0,0 +1,10 @@ +org.junit.platform.testkit@${platformVersion} jar:file:.+/junit-platform-testkit-\d.+\.jar..module-info\.class +exports org.junit.platform.testkit.engine +requires java.base mandated +requires org.apiguardian.api static transitive +requires org.assertj.core transitive +requires org.junit.platform.commons +requires org.junit.platform.engine transitive +requires org.junit.platform.launcher +requires org.opentest4j transitive +uses org.junit.platform.engine.TestEngine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt new file mode 100644 index 00000000..854b4981 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt @@ -0,0 +1,7 @@ +org.junit.vintage.engine@${vintageVersion} jar:file:.+/junit-vintage-engine-\d.+\.jar..module-info\.class +requires java.base mandated +requires junit +requires org.apiguardian.api static +requires org.junit.platform.engine +provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine +contains org.junit.vintage.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml new file mode 100644 index 00000000..7123e267 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + platform.tooling.support.tests + java-versions + 1.0-SNAPSHOT + + + UTF-8 + ${env.JUNIT_JUPITER_VERSION} + ${env.JUNIT_PLATFORM_VERSION} + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + org.junit.platform + junit-platform-commons + ${junit.platform.version} + test + + + + + + + + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + maven-surefire-plugin + 2.22.2 + + + + + + + + local-temp + file://${maven.repo} + + true + + + true + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java new file mode 100644 index 00000000..53a15919 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java @@ -0,0 +1,37 @@ + +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.junit.platform.commons.util.ModuleUtils; + +class JUnitPlatformCommonsTests { + + @Test + @EnabledOnJre(JRE.JAVA_8) + void onJava8() { + assertFalse(ModuleUtils.isJavaPlatformModuleSystemAvailable()); + assertFalse(ModuleUtils.getModuleName(Object.class).isPresent()); + } + + @Test + @DisabledOnJre(JRE.JAVA_8) + void onJava9OrHigher() { + assertTrue(ModuleUtils.isJavaPlatformModuleSystemAvailable()); + assertEquals("java.base", ModuleUtils.getModuleName(Object.class).orElseThrow(Error::new)); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml new file mode 100644 index 00000000..9ec72ae3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + com.example + maven-starter + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + ${maven.compiler.source} + ${env.JUNIT_JUPITER_VERSION} + ${env.JUNIT_PLATFORM_VERSION} + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + org.junit.platform + junit-platform-reporting + ${junit.platform.version} + test + + + + + + + maven-compiler-plugin + 3.8.1 + + + maven-surefire-plugin + 2.22.2 + + + + junit.platform.reporting.open.xml.enabled = true + junit.platform.reporting.output.dir = target/surefire-reports + + + + + + org.codehaus.gmaven + groovy-maven-plugin + 2.1.1 + + + validate + + execute + + + + println("Using Java version: ${java.version}") + + + + + + + + + + + local-temp + file://${maven.repo} + + true + + + true + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java new file mode 100644 index 00000000..6feca6bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java new file mode 100644 index 00000000..695282ba --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class CalculatorTests { + + @Test + @DisplayName("1 + 1 = 2") + void addsTwoNumbers() { + Calculator calculator = new Calculator(); + assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); + } + + @ParameterizedTest(name = "{0} + {1} = {2}") + @CsvSource({ // + "0, 1, 1", // + "1, 2, 3", // + "49, 51, 100", // + "1, 100, 101" // + }) + void add(int first, int second, int expectedResult) { + Calculator calculator = new Calculator(); + assertEquals(expectedResult, calculator.add(first, second), + () -> first + " + " + second + " should equal " + expectedResult); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml new file mode 100644 index 00000000..2eda3ba4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + com.example + maven-surefire-compatibility + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + ${maven.compiler.source} + ${env.JUNIT_JUPITER_VERSION} + ${env.JUNIT_PLATFORM_VERSION} + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + + + + + maven-compiler-plugin + 3.8.1 + + + maven-surefire-plugin + ${surefire.version} + + + + junit.platform.listeners.uid.tracking.enabled = true + + + + + + + + + + local-temp + file://${maven.repo} + + true + + + true + + + + + + + manual-platform-dependency + + + org.junit.platform + junit-platform-launcher + ${junit.platform.version} + test + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java new file mode 100644 index 00000000..a695ded1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class DummyTests { + + @Test + void test() { + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml new file mode 100644 index 00000000..0904feb9 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + platform.tooling.support.tests + multi-release-jar + 1.0-SNAPSHOT + + + UTF-8 + ${env.JUNIT_JUPITER_VERSION} + ${env.JUNIT_VINTAGE_VERSION} + ${env.JUNIT_PLATFORM_VERSION} + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + org.junit.platform + junit-platform-reporting + ${junit.platform.version} + test + + + + + + + + maven-compiler-plugin + 3.8.1 + + 11 + + + + + + + de.sormuras.junit + junit-platform-maven-plugin + 1.1.5 + true + + JAVA + + true + + + + + + + + + local-temp + file://${maven.repo} + + true + + + true + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java new file mode 100644 index 00000000..31115397 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package integration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.junit.jupiter.api.MethodOrderer.Alphanumeric; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherFactory; + +@TestMethodOrder(Alphanumeric.class) +class JupiterIntegrationTests { + + @Test + void packageName() { + assertEquals("integration", getClass().getPackageName()); + } + + @Test + void moduleIsNamed() { + assumeTrue(getClass().getModule().isNamed(), "not running on the module-path"); + assertEquals("integration", getClass().getModule().getName()); + } + + @Test + void resolve() { + var selector = DiscoverySelectors.selectClass(getClass()); + var testPlan = LauncherFactory.create().discover( + request().selectors(selector).filters(includeEngines("junit-jupiter")).build()); + + var engine = testPlan.getRoots().iterator().next(); + + assertEquals(1, testPlan.getChildren(engine).size()); // JupiterIntegrationTests.class + assertEquals(3, testPlan.getChildren(testPlan.getChildren(engine).iterator().next()).size()); // 3 test methods + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java new file mode 100644 index 00000000..50cecc69 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package integration; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.module.ModuleDescriptor; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.util.ModuleUtils; + +/** + * Unit tests for {@link ModuleUtils}. + * + * @since 1.1 + */ +class ModuleUtilsTests { + + @Test + void javaPlatformModuleSystemIsAvailable() { + assertTrue(ModuleUtils.isJavaPlatformModuleSystemAvailable()); + } + + @Test + void findAllNonSystemBootModuleNames() { + Set moduleNames = ModuleUtils.findAllNonSystemBootModuleNames(); + + assertTrue(moduleNames.isEmpty()); + } + + @Test + void findAllClassesInModule() { + ClassFilter modular = ClassFilter.of(name -> name.contains("Module"), type -> true); + List> classes = ModuleUtils.findAllClassesInModule("java.base", modular); + assertFalse(classes.isEmpty()); + assertTrue(classes.contains(Module.class)); + assertTrue(classes.contains(ModuleDescriptor.class)); + } + + @Test + void preconditions() { + Class expected = PreconditionViolationException.class; + assertThrows(expected, () -> ModuleUtils.getModuleName(null)); + assertThrows(expected, () -> ModuleUtils.getModuleVersion(null)); + assertThrows(expected, () -> ModuleUtils.findAllClassesInModule(null, null)); + assertThrows(expected, () -> ModuleUtils.findAllClassesInModule("", null)); + assertThrows(expected, () -> ModuleUtils.findAllClassesInModule(" ", null)); + assertThrows(expected, () -> ModuleUtils.findAllClassesInModule("java.base", null)); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt new file mode 100644 index 00000000..46cdad9e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt @@ -0,0 +1,18 @@ +.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines +.+ Discovered TestEngines: +- junit-jupiter .+ +- junit-vintage .+ +- junit-platform-suite .+ +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded PostDiscoveryFilter instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded LauncherDiscoveryListener instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded TestExecutionListener instances: .+ +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded LauncherSessionListener instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines +.+ Discovered TestEngines: +- junit-jupiter .+ +- junit-vintage .+ +- junit-platform-suite .+ diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt new file mode 100644 index 00000000..24338dbc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt @@ -0,0 +1,19 @@ +>> JAVA VERSION + TREE >> + +Failures (2): +>> STACKTRACE >> + +Test run finished after \d+ ms +[ 11 containers found ] +[ 0 containers skipped ] +[ 11 containers started ] +[ 0 containers aborted ] +[ 11 containers successful ] +[ 0 containers failed ] +[ 10 tests found ] +[ 2 tests skipped ] +[ 8 tests started ] +[ 1 tests aborted ] +[ 5 tests successful ] +[ 2 tests failed ] + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties new file mode 100644 index 00000000..f17006de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties @@ -0,0 +1,3 @@ +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level=CONFIG +org.junit.level=CONFIG diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java new file mode 100644 index 00000000..04505d93 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package standalone; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class JupiterIntegration { + + @Test + void successful() { + } + + @Test + @Disabled("integration-test-disabled") + void disabled() { + } + + @Test + void abort() { + Assumptions.assumeTrue(false, "integration-test-abort"); + } + + @Test + void fail() { + Assertions.fail("integration-test-fail"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java new file mode 100644 index 00000000..270b9a16 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package standalone; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class JupiterParamsIntegration { + + @ParameterizedTest(name = "[{index}] argument={0}") + @ValueSource(strings = "test") + void parameterizedTest(String argument) { + assertEquals("test", argument); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java new file mode 100644 index 00000000..a06fc90f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package standalone; + +import org.junit.jupiter.api.Test; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses(SuiteIntegration.SingleTestContainer.class) +class SuiteIntegration { + + static class SingleTestContainer { + @Test + void successful() { + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java new file mode 100644 index 00000000..3fb28863 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package standalone; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.runners.MethodSorters.NAME_ASCENDING; + +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; + +@FixMethodOrder(NAME_ASCENDING) +public class VintageIntegration { + + @Test + @Ignore("integr4tion test") + public void ignored() { + fail("this test should be ignored"); + } + + @Test + public void succ3ssful() { + assertEquals(3, 1 + 2); + } + + @Test + public void f4il() { + fail("f4iled"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts new file mode 100644 index 00000000..4d691459 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts @@ -0,0 +1,33 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + +plugins { + java +} + +repositories { + maven { url = uri(file(System.getProperty("maven.repo"))) } + mavenCentral() +} + +dependencies { + val junit4Version = System.getProperty("junit4Version", "4.12") + testImplementation("junit:junit:$junit4Version") + + val vintageVersion = System.getenv("JUNIT_VINTAGE_VERSION") ?: "5.3.2" + testImplementation("org.junit.vintage:junit-vintage-engine:$vintageVersion") { + exclude(group = "junit") + because("we want to override it to test against different versions") + } +} + +tasks.test { + useJUnitPlatform() + + testLogging { + events = setOf(TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.FAILED) + afterSuite(KotlinClosure2({ _, result -> + result.exception?.printStackTrace(System.out) + })) + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml new file mode 100644 index 00000000..47a0f145 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + com.example + vintage + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + ${maven.compiler.source} + ${env.JUNIT_VINTAGE_VERSION} + + + + + org.junit.vintage + junit-vintage-engine + ${vintageVersion} + test + + + junit + junit + + + + + junit + junit + ${junit4Version} + test + + + + + + + maven-compiler-plugin + 3.8.1 + + + maven-surefire-plugin + 2.22.2 + + + + + + + local-temp + file://${maven.repo} + + true + + + true + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts new file mode 100644 index 00000000..f073959d --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "vintage" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java new file mode 100644 index 00000000..186bbf2e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.vintage; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class VintageTest { + @Test + public void success() { + // pass + } + + @Test + public void failure() { + fail("expected to fail"); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java new file mode 100644 index 00000000..517e4e5f --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java @@ -0,0 +1,158 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @since 1.3 + */ +public class Helper { + + public static final Duration TOOL_TIMEOUT = Duration.ofMinutes(3); + + private static final Path ROOT = Paths.get(".."); + private static final Path GRADLE_PROPERTIES = ROOT.resolve("gradle.properties"); + private static final Path SETTINGS_GRADLE = ROOT.resolve("settings.gradle.kts"); + + private static final Properties gradleProperties = new Properties(); + + static { + try { + gradleProperties.load(Files.newInputStream(GRADLE_PROPERTIES)); + } + catch (Exception e) { + throw new AssertionError("loading gradle.properties failed", e); + } + } + + public static String version(String module) { + if (module.startsWith("junit-jupiter")) { + return gradleProperties.getProperty("version"); + } + if (module.startsWith("junit-platform")) { + return gradleProperties.getProperty("platformVersion"); + } + if (module.startsWith("junit-vintage")) { + return gradleProperties.getProperty("vintageVersion"); + } + throw new AssertionError("Unknown module: " + module); + } + + static String groupId(String artifactId) { + if (artifactId.startsWith("junit-jupiter")) { + return "org.junit.jupiter"; + } + if (artifactId.startsWith("junit-platform")) { + return "org.junit.platform"; + } + if (artifactId.startsWith("junit-vintage")) { + return "org.junit.vintage"; + } + return "org.junit"; + } + + public static String replaceVersionPlaceholders(String line) { + line = line.replace("${jupiterVersion}", version("junit-jupiter")); + line = line.replace("${vintageVersion}", version("junit-vintage")); + line = line.replace("${platformVersion}", version("junit-platform")); + return line; + } + + public static List loadModuleDirectoryNames() { + var moduleLinePattern = Pattern.compile("include\\(\"(.+)\"\\)"); + try (var stream = Files.lines(SETTINGS_GRADLE) // + .map(moduleLinePattern::matcher) // + .filter(Matcher::matches) // + .map(matcher -> matcher.group(1)) // + .filter(name -> name.startsWith("junit-")) // + .filter(name -> !name.equals("junit-bom")) // + .filter(name -> !name.equals("junit-platform-console-standalone"))) { + return stream.collect(Collectors.toList()); + } + catch (Exception e) { + throw new AssertionError("loading module directory names failed: " + SETTINGS_GRADLE); + } + } + + static JarFile createJarFile(String module) { + var path = MavenRepo.jar(module); + try { + return new JarFile(path.toFile()); + } + catch (IOException e) { + throw new UncheckedIOException("Creating JarFile for '" + path + "' failed.", e); + } + } + + public static List loadJarFiles() { + return loadModuleDirectoryNames().stream().map(Helper::createJarFile).collect(Collectors.toList()); + } + + public static Optional getJavaHome(String version) { + // First, try various system sources... + var sources = Stream.of( // + System.getProperty("java.home." + version), // + System.getProperty("java." + version), // + System.getProperty("jdk.home." + version), // + System.getProperty("jdk." + version), // + System.getenv("JAVA_HOME_" + version), // + System.getenv("JAVA_" + version), // + System.getenv("JDK" + version) // + ); + return sources.filter(Objects::nonNull).findFirst().map(Path::of); + } + + /** Load, here copy, modular jar files to the given target directory. */ + public static void loadAllJUnitModules(Path target) throws Exception { + for (var module : loadModuleDirectoryNames()) { + var jar = MavenRepo.jar(module); + Files.copy(jar, target.resolve(jar.getFileName())); + } + } + + /** Walk directory tree structure. */ + public static List treeWalk(Path root) { + var lines = new ArrayList(); + treeWalk(root, lines::add); + return lines; + } + + /** Walk directory tree structure. */ + public static void treeWalk(Path root, Consumer out) { + try (var stream = Files.walk(root)) { + stream.map(root::relativize) // + .map(path -> path.toString().replace('\\', '/')) // + .sorted().filter(Predicate.not(String::isEmpty)) // + .forEach(out); + } + catch (Exception e) { + throw new Error("Walking tree failed: " + root, e); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java new file mode 100644 index 00000000..8764aea0 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class MavenRepo { + + private MavenRepo() { + } + + public static Path dir() { + var candidates = Stream.of(Path.of("../build/repo")); + var candidate = candidates.filter(Files::isDirectory).findFirst().orElse(Path.of("build/repo")); + return Path.of(System.getProperty("maven.repo", candidate.toString())); + } + + public static Path jar(String artifactId) { + return artifact(artifactId, fileName -> fileName.endsWith(".jar") // + && !fileName.endsWith("-sources.jar") // + && !fileName.endsWith("-javadoc.jar")); + } + + public static Path gradleModuleMetadata(String artifactId) { + return artifact(artifactId, fileName -> fileName.endsWith(".module")); + } + + public static Path pom(String artifactId) { + return artifact(artifactId, fileName -> fileName.endsWith(".pom")); + } + + private static Path artifact(String artifactId, Predicate fileNamePredicate) { + var parentDir = dir() // + .resolve(Helper.groupId(artifactId).replace('.', File.separatorChar)) // + .resolve(artifactId) // + .resolve(Helper.version(artifactId)); + try (var files = Files.list(parentDir)) { + return files.filter( + file -> fileNamePredicate.test(file.getFileName().toString())).findFirst().orElseThrow(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java new file mode 100644 index 00000000..03e2cf13 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java @@ -0,0 +1,183 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support; + +import java.io.FileFilter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import de.sormuras.bartholdy.Configuration; +import de.sormuras.bartholdy.Result; +import de.sormuras.bartholdy.Tool; +import de.sormuras.bartholdy.tool.Maven; + +import org.apache.commons.io.FileUtils; + +/** + * @since 1.3 + */ +public class Request { + + public static final Path PROJECTS = Paths.get("projects"); + private static final Path TOOLS = Paths.get("build", "test-tools"); + public static final Path WORKSPACE = Paths.get("build", "test-workspace"); + + public static Builder builder() { + return new Builder(); + } + + public static Maven maven() { + return new Maven(Path.of(System.getProperty("mavenDistribution"))); + } + + private Tool tool; + private String project; + private String workspace; + private List arguments = new ArrayList<>(); + private Map environment = new HashMap<>(); + private FileFilter copyProjectToWorkspaceFileFilter; + private Duration timeout = Duration.ofMinutes(1); + + public String getProject() { + return project; + } + + public FileFilter getCopyProjectToWorkspaceFileFilter() { + return copyProjectToWorkspaceFileFilter; + } + + public String getWorkspace() { + return workspace; + } + + public Map getEnvironment() { + return environment; + } + + public List getArguments() { + return arguments; + } + + public Duration getTimeout() { + return timeout; + } + + public Result run() { + return run(true); + } + + public Result run(boolean cleanWorkspace) { + try { + // sanity check + if (!Files.isDirectory(PROJECTS)) { + var cwd = Paths.get(".").normalize().toAbsolutePath(); + throw new IllegalStateException("Directory " + PROJECTS + " not found in: " + cwd); + } + + Files.createDirectories(TOOLS); + Files.createDirectories(WORKSPACE); + + var workspace = WORKSPACE.resolve(getWorkspace()); + if (cleanWorkspace) { + FileUtils.deleteQuietly(workspace.toFile()); + var project = PROJECTS.resolve(getProject()); + if (Files.isDirectory(project)) { + var filter = getCopyProjectToWorkspaceFileFilter(); + FileUtils.copyDirectory(project.toFile(), workspace.toFile(), filter); + } + } + + var configuration = Configuration.builder(); + configuration.setArguments(getArguments()); + configuration.setWorkingDirectory(workspace); + configuration.setTimeout(getTimeout()); + configuration.getEnvironment().putAll(getEnvironment()); + + var result = tool.run(configuration.build()); + System.out.println(result.getOutput("out")); + System.err.println(result.getOutput("err")); + return result; + } + catch (Exception e) { + throw new IllegalStateException("run failed", e); + } + } + + public static class Builder { + + private final Request request = new Request(); + + public Request build() { + if (request.project == null) { + throw new IllegalStateException("project must not be null"); + } + if (request.workspace == null) { + request.workspace = request.project; + } + buildEnvironment(request.environment); + request.arguments = List.copyOf(request.arguments); + request.environment = Map.copyOf(request.environment); + return request; + } + + private void buildEnvironment(Map environment) { + environment.computeIfAbsent("JUNIT_JUPITER_VERSION", key -> Helper.version("junit-jupiter")); + environment.computeIfAbsent("JUNIT_VINTAGE_VERSION", key -> Helper.version("junit-vintage")); + environment.computeIfAbsent("JUNIT_PLATFORM_VERSION", key -> Helper.version("junit-platform")); + } + + public Builder setTool(Tool tool) { + request.tool = tool; + return this; + } + + public Builder setJavaHome(Path javaHome) { + return putEnvironment("JAVA_HOME", javaHome.normalize().toAbsolutePath().toString()); + } + + public Builder setProject(String project) { + request.project = project; + return this; + } + + public Builder setProjectToWorkspaceCopyFileFilter(FileFilter copyProjectToWorkspaceFileFilter) { + request.copyProjectToWorkspaceFileFilter = copyProjectToWorkspaceFileFilter; + return this; + } + + public Builder setWorkspace(String workspace) { + request.workspace = workspace; + return this; + } + + public Builder addArguments(Object... arguments) { + Stream.of(arguments).map(Object::toString).forEach(request.arguments::add); + return this; + } + + public Builder putEnvironment(String key, String value) { + request.environment.put(key, value); + return this; + } + + public Builder setTimeout(Duration timeout) { + request.timeout = timeout; + return this; + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java new file mode 100644 index 00000000..02b256e4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class ThirdPartyJars { + + private ThirdPartyJars() { + } + + public static void copy(Path targetDir, String group, String artifact) throws Exception { + Path source = Stream.of(System.getProperty("thirdPartyJars").split(File.pathSeparator)) // + .filter(it -> it.replace(File.separator, "/").contains("/" + group + "/" + artifact + "/")) // + .map(Path::of) // + .findFirst() // + .orElseThrow(() -> new AssertionError("Failed to find JAR file for " + group + ":" + artifact)); + Files.copy(source, targetDir.resolve(source.getFileName()), REPLACE_EXISTING); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java new file mode 100644 index 00000000..feb8172b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java @@ -0,0 +1,7 @@ +/** + * Tooling support helper. + * + * @since 1.3 + */ + +package platform.tooling.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java new file mode 100644 index 00000000..fad952bf --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.nio.file.Files; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class HelperTests { + + @Test + void loadModuleDirectoryNames() { + assertLinesMatch(List.of( // + "junit-jupiter", // + "junit-jupiter-api", // + "junit-jupiter-engine", // + "junit-jupiter-migrationsupport", // + "junit-jupiter-params", // + "junit-platform-commons", // + "junit-platform-console", // + "junit-platform-engine", // + "junit-platform-jfr", // + "junit-platform-launcher", // + "junit-platform-reporting", // + "junit-platform-runner", // + "junit-platform-suite", // + "junit-platform-suite-api", // + "junit-platform-suite-commons", // + "junit-platform-suite-engine", // + "junit-platform-testkit", // + "junit-vintage-engine"// + ), Helper.loadModuleDirectoryNames()); + } + + @Test + void version() { + assertNotNull(Helper.version("junit-jupiter")); + assertNotNull(Helper.version("junit-vintage")); + assertNotNull(Helper.version("junit-platform")); + } + + @Test + void nonExistingJdkVersionYieldsAnEmptyOptional() { + assertEquals(Optional.empty(), Helper.getJavaHome("does not exist")); + } + + @ParameterizedTest + @ValueSource(ints = 8) + void checkJavaHome(int version) { + var home = Helper.getJavaHome(String.valueOf(version)); + assumeTrue(home.isPresent(), "No 'jdk' element found in Maven toolchain for: " + version); + assertTrue(Files.isDirectory(home.get())); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java new file mode 100644 index 00000000..4125ae2c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; + +import java.util.List; + +import de.sormuras.bartholdy.tool.Java; + +import org.apache.tools.ant.Main; +import org.junit.jupiter.api.Test; +import platform.tooling.support.Request; + +/** + * @since 1.3 + */ +class AntStarterTests { + + @Test + void ant_starter() { + var request = Request.builder() // + .setTool(new Java()) // + .setProject("ant-starter") // + .addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) // + .addArguments("-verbose") // + .build(); + + var result = request.run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assertEquals(0, result.getExitCode()); + assertEquals("", result.getOutput("err"), "error log isn't empty"); + assertLinesMatch(List.of(">> HEAD >>", // + "test.junit.launcher:", // + ">>>>", // + "\\[junitlauncher\\] Tests run: 5, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // + ">>>>", // + "test.console.launcher:", // + ">>>>", // + " \\[java\\] Test run finished after [\\d]+ ms", // + ">>>>", // + " \\[java\\] \\[ 5 tests successful \\]", // + " \\[java\\] \\[ 0 tests failed \\]", // + ">> TAIL >>"), // + result.getOutputLines("out")); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-report"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java new file mode 100644 index 00000000..2b26d889 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static com.tngtech.archunit.base.DescribedPredicate.describe; +import static com.tngtech.archunit.base.DescribedPredicate.not; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAnyPackage; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; +import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; +import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; +import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; +import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static platform.tooling.support.Helper.loadJarFiles; + +import java.util.Set; +import java.util.stream.Collectors; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.Location; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.junit.LocationProvider; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.library.GeneralCodingRules; + +import org.apiguardian.api.API; + +@AnalyzeClasses(locations = ArchUnitTests.AllJars.class) +class ArchUnitTests { + + @ArchTest + private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // + .that(have(modifier(PUBLIC))) // + .and(TOP_LEVEL_CLASSES) // + .and(not(ANONYMOUS_CLASSES)) // + .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // + .and(not(describe("are shadowed", resideInAnyPackage("..shadow..")))) // + .should().beAnnotatedWith(API.class); + + @ArchTest + void allAreIn(JavaClasses classes) { + // about 928 classes found in all jars + assertTrue(classes.size() > 800, "expected more than 800 classes, got: " + classes.size()); + } + + @ArchTest + void freeOfCycles(JavaClasses classes) { + slices().matching("org.junit.(*)..").should().beFreeOfCycles().check(classes); + } + + @ArchTest + void avoidJavaUtilLogging(JavaClasses classes) { + // LoggerFactory.java:80 -> sets field LoggerFactory$DelegatingLogger.julLogger + var subset = classes.that(are(not(name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger")))); + GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(subset); + } + + @ArchTest + void avoidThrowingGenericExceptions(JavaClasses classes) { + // LoggerFactory.java:155 -> new Throwable() + var subset = classes.that(are(not( + name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger").or(nameContaining(".shadow."))))); + GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS.check(subset); + } + + @ArchTest + void avoidAccessingStandardStreams(JavaClasses classes) { + // ConsoleLauncher, StreamInterceptor, Picocli et al... + var subset = classes // + .that(are(not(name("org.junit.platform.console.ConsoleLauncher")))) // + .that(are(not(name("org.junit.platform.launcher.core.StreamInterceptor")))) // + .that(are(not(name("org.junit.platform.runner.JUnitPlatformRunnerListener")))) // + .that(are(not(name("org.junit.platform.testkit.engine.Events")))) // + .that(are(not(name("org.junit.platform.testkit.engine.Executions")))) // + .that(are(not(resideInAPackage("org.junit.platform.console.shadow.picocli")))); + GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(subset); + } + + static class AllJars implements LocationProvider { + + @Override + public Set get(Class testClass) { + return loadJarFiles().stream().map(Location::of).collect(Collectors.toSet()); + } + + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java new file mode 100644 index 00000000..463dd064 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; + +import java.nio.file.Paths; +import java.time.Duration; + +import de.sormuras.bartholdy.tool.GradleWrapper; + +import org.junit.jupiter.api.Test; + +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.9.1 + */ +class GraalVmStarterTests { + + @Test + void runsTestsInNativeImage() { + var request = Request.builder() // + .setTool(new GradleWrapper(Paths.get(".."))) // + .setProject("graalvm-starter") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace") // + .setTimeout(Duration.ofMinutes(5)) // + .build(); + + var result = request.run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assumeFalse( + result.getOutputLines("err").stream().anyMatch(line -> line.contains("No compatible toolchains found")), + "Abort test if GraalVM is not installed"); + + assertEquals(0, result.getExitCode()); + assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java new file mode 100644 index 00000000..2b5411be --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; + +import de.sormuras.bartholdy.tool.GradleWrapper; + +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.3 + */ +class GradleKotlinExtensionsTests { + + @Test + void gradle_wrapper() { + var result = Request.builder() // + .setTool(new GradleWrapper(Request.PROJECTS.resolve("gradle-kotlin-extensions"))) // + .setProject("gradle-kotlin-extensions") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("build", "--no-daemon", "--stacktrace") // + .setTimeout(TOOL_TIMEOUT) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .build() // + .run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assertEquals(0, result.getExitCode(), "result=" + result); + assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java new file mode 100644 index 00000000..c82360af --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; + +import java.nio.file.Paths; +import java.util.List; + +import de.sormuras.bartholdy.Tool; +import de.sormuras.bartholdy.tool.GradleWrapper; + +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.3 + */ +class GradleMissingEngineTests { + + @Test + void gradle_wrapper() { + test(new GradleWrapper(Paths.get(".."))); + } + + private void test(Tool gradle) { + var project = "gradle-missing-engine"; + var result = Request.builder() // + .setProject(project) // + .setWorkspace(project + "-wrapper") // + .setTool(gradle) // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("build", "--no-daemon", "--debug", "--stacktrace") // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setTimeout(TOOL_TIMEOUT).build() // + .run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assertEquals(1, result.getExitCode()); + assertLinesMatch(List.of( // + ">> HEAD >>", // + ".+DEBUG.+Cannot create Launcher without at least one TestEngine.+", // + ">> TAIL >>"), // + result.getOutputLines("out")); + assertLinesMatch(List.of( // + ">> HEAD >>", // + ".+ERROR.+FAILURE: Build failed with an exception.", // + ">> TAIL >>"), // + result.getOutputLines("err")); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java new file mode 100644 index 00000000..008e4bfb --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java @@ -0,0 +1,165 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertLinesMatch; + +import java.nio.file.Files; +import java.util.List; + +import org.junit.jupiter.api.Test; +import platform.tooling.support.MavenRepo; + +/** + * @since 1.6 + */ +class GradleModuleFileTests { + + @Test + void jupiterAggregatorGradleModuleMetadataVariants() throws Exception { + var expected = List.of(">> HEAD >>", // + "{", // + " \"formatVersion\": \"1.1\",", // + " \"component\": {", // + " \"group\": \"org.junit.jupiter\",", // + " \"module\": \"junit-jupiter\",", // + ">> VERSION >>", // + " \"attributes\": {", // + ">> STATUS >>", // + " }", // + " },", // + ">> CREATED_BY >>", // + " \"variants\": [", // + " {", // + " \"name\": \"apiElements\",", // + " \"attributes\": {", // + " \"org.gradle.category\": \"library\",", // + " \"org.gradle.dependency.bundling\": \"external\",", // + " \"org.gradle.jvm.version\": 8,", // + " \"org.gradle.libraryelements\": \"jar\",", // + " \"org.gradle.usage\": \"java-api\"", // + " },", // + " \"dependencies\": [", // + " {", // + " \"group\": \"org.junit\",", // + " \"module\": \"junit-bom\",", // + " \"version\": {", // + ">> VERSION >>", // + " },", // + " \"attributes\": {", // + " \"org.gradle.category\": \"platform\"", // + " },", // + " \"endorseStrictVersions\": true", // + " },", // + " {", // + " \"group\": \"org.junit.jupiter\",", // + " \"module\": \"junit-jupiter-api\",", // + " \"version\": {", // + ">> VERSION >>", // + " }", // + " },", // + " {", // + " \"group\": \"org.junit.jupiter\",", // + " \"module\": \"junit-jupiter-params\",", // + " \"version\": {", // + ">> VERSION >>", // + " }", // + " }", // + " ],", // + " \"files\": [", // + " {", // + ">> JAR_FILE_DETAILS >>", // + " }", // + " ]", // + " },", // + " {", // + " \"name\": \"runtimeElements\",", // + " \"attributes\": {", // + " \"org.gradle.category\": \"library\",", // + " \"org.gradle.dependency.bundling\": \"external\",", // + " \"org.gradle.jvm.version\": 8,", // + " \"org.gradle.libraryelements\": \"jar\",", // + " \"org.gradle.usage\": \"java-runtime\"", // + " },", // + " \"dependencies\": [", // + " {", // + " \"group\": \"org.junit\",", // + " \"module\": \"junit-bom\",", // + " \"version\": {", // + ">> VERSION >>", // + " },", // + " \"attributes\": {", // + " \"org.gradle.category\": \"platform\"", // + " },", // + " \"endorseStrictVersions\": true", // + " },", // + " {", // + " \"group\": \"org.junit.jupiter\",", // + " \"module\": \"junit-jupiter-api\",", // + " \"version\": {", // + ">> VERSION >>", // + " }", // + " },", // + " {", // + " \"group\": \"org.junit.jupiter\",", // + " \"module\": \"junit-jupiter-params\",", // + " \"version\": {", // + ">> VERSION >>", // + " }", // + " },", // + " {", // + " \"group\": \"org.junit.jupiter\",", // + " \"module\": \"junit-jupiter-engine\",", // + " \"version\": {", // + ">> VERSION >>", // + " }", // + " }", // + " ],", // + " \"files\": [", // + " {", // + ">> JAR_FILE_DETAILS >>", // + " }", // + " ]", // + " },", // + " {", // + " \"name\": \"javadocElements\",", // + " \"attributes\": {", // + " \"org.gradle.category\": \"documentation\",", // + " \"org.gradle.dependency.bundling\": \"external\",", // + " \"org.gradle.docstype\": \"javadoc\",", // + " \"org.gradle.usage\": \"java-runtime\"", // + " },", // + " \"files\": [", // + " {", // + ">> JAR_FILE_DETAILS >>", // + " }", // + " ]", // + " },", // + " {", // + " \"name\": \"sourcesElements\",", // + " \"attributes\": {", // + " \"org.gradle.category\": \"documentation\",", // + " \"org.gradle.dependency.bundling\": \"external\",", // + " \"org.gradle.docstype\": \"sources\",", // + " \"org.gradle.usage\": \"java-runtime\"", // + " },", // + " \"files\": [", // + " {", // + ">> JAR_FILE_DETAILS >>", // + " }", // + " ]", // + " }", // + " ]", // + "}"); + + assertLinesMatch(expected, Files.readAllLines(MavenRepo.gradleModuleMetadata("junit-jupiter"))); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java new file mode 100644 index 00000000..69d884cc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; + +import java.nio.file.Paths; + +import de.sormuras.bartholdy.tool.GradleWrapper; + +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.3 + */ +class GradleStarterTests { + + @Test + void gradle_wrapper() { + var request = Request.builder() // + .setTool(new GradleWrapper(Paths.get(".."))) // + .setProject("gradle-starter") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("build", "--no-daemon", "--stacktrace") // + .setTimeout(TOOL_TIMEOUT) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .build(); + + var result = request.run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assertEquals(0, result.getExitCode()); + assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java new file mode 100644 index 00000000..ee2d7fae --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.FileInputStream; +import java.nio.file.Files; +import java.util.jar.JarInputStream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import platform.tooling.support.MavenRepo; + +/** + * @since 1.8 + */ +class JarContainsManifestFirstTests { + + @ParameterizedTest + @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") + void manifestFirst(String module) throws Exception { + var modulePath = MavenRepo.jar(module); + + if (Files.notExists(modulePath)) { + fail("No such file: " + modulePath); + } + + // JarInputStream expects the META-INF/MANIFEST.MF to be at the start of the JAR archive + try (final JarInputStream jarInputStream = new JarInputStream(new FileInputStream(modulePath.toFile()))) { + assertNotNull(jarInputStream.getManifest(), "MANIFEST.MF should be available via JarInputStream"); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java new file mode 100644 index 00000000..aeb3f7bd --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.module.ModuleFinder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +import de.sormuras.bartholdy.jdk.Jar; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.3 + */ +class JarDescribeModuleTests { + + @ParameterizedTest + @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") + void describeModule(String module) throws Exception { + var modulePath = MavenRepo.jar(module); + var result = Request.builder() // + .setTool(new Jar()) // + .setProject("jar-describe-module") // + .setProjectToWorkspaceCopyFileFilter(file -> file.getName().startsWith(module)) // + .setWorkspace("jar-describe-module/" + module) // + .addArguments("--describe-module", "--file", modulePath) // + .build() // + .run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assertEquals(0, result.getExitCode()); + assertEquals("", result.getOutput("err"), "error log isn't empty"); + var expected = Paths.get("build", "test-workspace", "jar-describe-module", module, module + ".expected.txt"); + if (Files.notExists(expected)) { + result.getOutputLines("out").forEach(System.err::println); + fail("No such file: " + expected); + } + var expectedLines = Files.lines(expected).map(Helper::replaceVersionPlaceholders).collect(Collectors.toList()); + var origin = Path.of("projects", "jar-describe-module", module + ".expected.txt").toUri(); + assertLinesMatch(expectedLines, result.getOutputLines("out"), () -> String.format("%s\nError", origin)); + } + + @ParameterizedTest + @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") + void packageNamesStartWithNameOfTheModule(String module) { + var modulePath = MavenRepo.jar(module); + var moduleDescriptor = ModuleFinder.of(modulePath).findAll().iterator().next().descriptor(); + var moduleName = moduleDescriptor.name(); + for (var packageName : moduleDescriptor.packages()) { + assertTrue(packageName.startsWith(moduleName)); + } + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java new file mode 100644 index 00000000..dce1cfd6 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; + +import java.nio.file.Path; +import java.util.List; + +import de.sormuras.bartholdy.tool.Java; + +import org.junit.jupiter.api.Test; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.4 + */ +class JavaVersionsTests { + + @Test + void java_8() { + var java8Home = Helper.getJavaHome("8"); + assumeTrue(java8Home.isPresent(), "Java 8 installation directory not found!"); + var actualLines = execute("8", java8Home.get()); + + assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); + } + + @Test + void java_default() { + var actualLines = execute("default", new Java().getHome()); + + assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); + } + + List execute(String version, Path javaHome) { + var result = Request.builder() // + .setTool(Request.maven()) // + .setProject("java-versions") // + .setWorkspace("java-versions-" + version) // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("--update-snapshots", "--batch-mode", "verify") // + .setTimeout(TOOL_TIMEOUT) // + .setJavaHome(javaHome) // + .build().run(); + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + assertEquals(0, result.getExitCode()); + assertEquals("", result.getOutput("err")); + return result.getOutputLines("out"); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java new file mode 100644 index 00000000..4fabbb75 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static aQute.bnd.osgi.Constants.VERSION_ATTRIBUTE; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.jar.Attributes; + +import aQute.bnd.osgi.Domain; +import aQute.bnd.osgi.Jar; +import aQute.bnd.version.MavenVersion; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.osgi.framework.Version; +import org.osgi.framework.VersionRange; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; + +/** + * @since 1.5 + */ +class ManifestTests { + + @ParameterizedTest + @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") + void manifestEntriesAdhereToConventions(String module) throws Exception { + var version = Helper.version(module); + var jarFile = MavenRepo.jar(module).toFile(); + try (var jar = new Jar(jarFile)) { + var manifest = jar.getManifest(); + var attributes = manifest.getMainAttributes(); + assertValue(attributes, "Built-By", "JUnit Team"); + assertValue(attributes, "Specification-Title", module); + assertValue(attributes, "Specification-Version", specificationVersion(version)); + assertValue(attributes, "Specification-Vendor", "junit.org"); + assertValue(attributes, "Implementation-Title", module); + assertValue(attributes, "Implementation-Version", version); + assertValue(attributes, "Implementation-Vendor", "junit.org"); + assertValue(attributes, "Automatic-Module-Name", null); + assertValue(attributes, "Bundle-ManifestVersion", "2"); + assertValue(attributes, "Bundle-SymbolicName", module); + assertValue(attributes, "Bundle-Version", + MavenVersion.parseMavenString(version).getOSGiVersion().toString()); + switch (module) { + case "junit-platform-commons" -> assertValue(attributes, "Multi-Release", "true"); + case "junit-platform-console" -> assertValue(attributes, "Main-Class", + "org.junit.platform.console.ConsoleLauncher"); + } + var domain = Domain.domain(manifest); + domain.getExportPackage().forEach((pkg, attrs) -> { + final var stringVersion = attrs.get(VERSION_ATTRIBUTE); + assertNotNull(stringVersion); + assertDoesNotThrow(() -> new Version(stringVersion)); + }); + domain.getImportPackage().forEach((pkg, attrs) -> { + final var stringVersionRange = attrs.get(VERSION_ATTRIBUTE); + if (stringVersionRange == null) { + return; + } + assertDoesNotThrow(() -> new VersionRange(stringVersionRange)); + }); + } + } + + private static String specificationVersion(String version) { + var dash = version.indexOf('-'); + if (dash < 0) { + return version; + } + return version.substring(0, dash); + } + + private static void assertValue(Attributes attributes, String name, String expected) { + var actual = attributes.getValue(name); + assertEquals(expected, actual, + String.format("Manifest attribute %s expected to be %s, but is: %s", name, expected, actual)); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java new file mode 100644 index 00000000..5b5b6f42 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertLinesMatch; + +import java.nio.file.Files; +import java.util.List; + +import org.junit.jupiter.api.Test; +import platform.tooling.support.MavenRepo; + +/** + * @since 1.4 + */ +class MavenPomFileTests { + + @Test + void jupiterAggregatorPomDependencies() throws Exception { + + var expected = List.of(">> HEAD >>", // + " ", // + " ", // + " ", // + " org.junit", // + " junit-bom", // + ">> VERSION >>", // + " pom", // + " import", // + " ", // + " ", // + " ", // + " ", // + " ", // + " org.junit.jupiter", // + " junit-jupiter-api", // + ">> VERSION >>", // + " compile", // + " ", // + " ", // + " org.junit.jupiter", // + " junit-jupiter-params", // + ">> VERSION >>", // + " compile", // + " ", // + " ", // + " org.junit.jupiter", // + " junit-jupiter-engine", // + ">> VERSION >>", // + " runtime", // + " ", // + " ", // + ">> TAIL >>"); + + assertLinesMatch(expected, Files.readAllLines(MavenRepo.pom("junit-jupiter"))); + } + + @Test + void jupiterAggregatorGradleMetadataMarker() throws Exception { + + var expected = List.of(">> HEAD >>", // + " ", // + ">> TAIL >>"); + + assertLinesMatch(expected, Files.readAllLines(MavenRepo.pom("junit-jupiter"))); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java new file mode 100644 index 00000000..26a7d371 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; + +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.3 + */ +class MavenStarterTests { + + @Test + void verifyMavenStarterProject() { + var request = Request.builder() // + .setTool(Request.maven()) // + .setProject("maven-starter") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("--update-snapshots", "--batch-mode", "verify") // + .setTimeout(TOOL_TIMEOUT) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .build(); + + var result = request.run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assertEquals(0, result.getExitCode()); + assertEquals("", result.getOutput("err")); + assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); + assertTrue(result.getOutputLines("out").contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); + assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target/surefire-reports"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java new file mode 100644 index 00000000..f346969b --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; + +import java.io.IOException; +import java.nio.file.Files; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.9.2 + */ +class MavenSurefireCompatibilityTests { + + @ParameterizedTest + @CsvSource(delimiter = '|', nullValues = "", textBlock = """ + 2.22.2 | --activate-profiles=manual-platform-dependency + 3.0.0-M4 | + """) + void testMavenSurefireCompatibilityProject(String surefireVersion, String extraArg) throws IOException { + var extraArgs = extraArg == null ? new Object[0] : new Object[] { extraArg }; + var request = Request.builder() // + .setTool(Request.maven()) // + .setProject("maven-surefire-compatibility") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("-Dsurefire.version=" + surefireVersion) // + .addArguments("--update-snapshots", "--batch-mode", "test") // + .addArguments(extraArgs) // + .setTimeout(TOOL_TIMEOUT) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .build(); + + var result = request.run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assertEquals(0, result.getExitCode()); + assertEquals("", result.getOutput("err")); + + var output = result.getOutputLines("out"); + assertTrue(output.contains("[INFO] BUILD SUCCESS")); + assertTrue(output.contains("[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0")); + + var targetDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target"); + try (var stream = Files.list(targetDir)) { + assertThat(stream.filter(file -> file.getFileName().toString().startsWith("junit-platform-unique-ids"))) // + .hasSize(1); + } + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java new file mode 100644 index 00000000..3c2fc9fe --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java @@ -0,0 +1,203 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.spi.ToolProvider; + +import org.codehaus.groovy.runtime.ProcessGroovyMethods; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import platform.tooling.support.Helper; +import platform.tooling.support.ThirdPartyJars; + +/** + * @since 1.5 + */ +class ModularUserGuideTests { + + private static final String DOCUMENTATION_MODULE_DESCRIPTOR = """ + open module documentation { + exports example.testkit; // just here to ensure documentation example sources are compiled + + requires org.junit.jupiter.api; + requires org.junit.jupiter.migrationsupport; + requires org.junit.jupiter.params; + + requires org.junit.platform.engine; + requires org.junit.platform.reporting; + requires org.junit.platform.runner; + requires org.junit.platform.testkit; + + // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit + requires net.bytebuddy; + + requires java.desktop; + requires java.logging; + requires java.scripting; + requires jdk.httpserver; + + provides org.junit.platform.launcher.LauncherSessionListener + with example.session.GlobalSetupTeardownListener; + } + """; + + private static List compile(Path temp, Writer out, Writer err) throws Exception { + var documentation = Files.createDirectories(temp.resolve("src/documentation")); + Files.writeString(documentation.resolve("module-info.java"), DOCUMENTATION_MODULE_DESCRIPTOR); + + var args = new ArrayList(); + args.add("-Xlint"); // enable all default warnings + args.add("-proc:none"); // disable annotation processing + args.add("-cp"); + args.add(""); // set empty class path, otherwise system property "java.class.path" is read + + args.add("-d"); + args.add(temp.resolve("destination").toString()); + + var lib = Files.createDirectories(temp.resolve("lib")); + ThirdPartyJars.copy(lib, "junit", "junit"); + ThirdPartyJars.copy(lib, "org.assertj", "assertj-core"); + // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit + ThirdPartyJars.copy(lib, "net.bytebuddy", "byte-buddy"); + ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); + ThirdPartyJars.copy(lib, "org.hamcrest", "hamcrest"); + ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); + Helper.loadAllJUnitModules(lib); + args.add("--module-path"); + args.add(lib.toString()); + + args.add("--patch-module"); + args.add("documentation=" + Path.of("../documentation/src/main/java") + File.pathSeparator + + Path.of("../documentation/src/test/java")); + + args.add("--module-source-path"); + args.add(temp.resolve("src").toString()); + + args.add(documentation.resolve("module-info.java").toString()); + try (var walk = Files.walk(Path.of("../documentation/src/test/java"))) { + walk.map(Path::toString) // + .filter(s -> s.endsWith(".java")) // + // TypeError: systemProperty.get is not a function ?!?! + .filter(s -> !s.endsWith("ConditionalTestExecutionDemo.java")) // + // Don't include command-line tools that "require io.github.classgraph" + .filter(s -> !s.contains("tools")).forEach(args::add); + } + + var javac = ToolProvider.findFirst("javac").orElseThrow(); + var code = javac.run(new PrintWriter(out), new PrintWriter(err), args.toArray(String[]::new)); + + assertEquals(0, code, err.toString()); + assertTrue(out.toString().isBlank(), out.toString()); + return args; + } + + private static void junit(Path temp, Writer out, Writer err) throws Exception { + var command = new ArrayList(); + var projectDir = Path.of("../documentation"); + command.add(Path.of(System.getProperty("java.home"), "bin", "java").toString()); + + command.add("-XX:StartFlightRecording:filename=" + temp.resolve("user-guide.jfr")); + + command.add("--show-version"); + command.add("--show-module-resolution"); + + command.add("--module-path"); + command.add(String.join(File.pathSeparator, // + temp.resolve("destination").toString(), // + temp.resolve("lib").toString() // + )); + + command.add("--add-modules"); + command.add("documentation"); + + // TODO This `patch-module` should work! Why doesn't it? + // command.add("--patch-module"); + // command.add("documentation=../documentation/src/test/resources/"); + Files.copy(projectDir.resolve("src/test/resources/two-column.csv"), + temp.resolve("destination/documentation/two-column.csv")); + + command.add("--module"); + command.add("org.junit.platform.console"); + + command.add("--scan-modules"); + + command.add("--config"); + command.add("enableHttpServer=true"); + + command.add("--fail-if-no-tests"); + command.add("--include-classname"); + command.add(".*Tests"); + command.add("--include-classname"); + command.add(".*Demo"); + command.add("--exclude-tag"); + command.add("exclude"); + + // System.out.println("______________"); + // command.forEach(System.out::println); + + var builder = new ProcessBuilder(command).directory(projectDir.toFile()); + var java = builder.start(); + ProcessGroovyMethods.waitForProcessOutput(java, out, err); + var code = java.exitValue(); + + if (code != 0) { + System.out.println(out); + System.err.println(err); + fail("Unexpected exit code: " + code); + } + } + + @Test + void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp) throws Exception { + var out = new StringWriter(); + var err = new StringWriter(); + + var args = compile(temp, out, err); + // args.forEach(System.out::println); + + assertTrue(err.toString().isBlank(), () -> err + "\n\n" + String.join("\n", args)); + var listing = Helper.treeWalk(temp); + assertLinesMatch(List.of( // + "destination", // + ">> CLASSES >>", // + "lib", // + "lib/apiguardian-api-.+\\.jar", // + "lib/assertj-core-.+\\.jar", // + "lib/byte-buddy-.+", // + "lib/hamcrest-.+\\.jar", // + "lib/junit-.+\\.jar", // + ">> ALL JUNIT 5 JARS >>", // + "lib/opentest4j-.+\\.jar", // + "src", // + "src/documentation", // + "src/documentation/module-info.java" // + ), listing); + // System.out.println("______________"); + // listing.forEach(System.out::println); + + junit(temp, out, err); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java new file mode 100644 index 00000000..438ca769 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import de.sormuras.bartholdy.Result; + +import org.junit.jupiter.api.Test; + +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.4 + */ +class MultiReleaseJarTests { + + @Test + void checkDefault() throws Exception { + var variant = "default"; + var expectedLines = List.of( // + ">> BANNER >>", // + ".", // + "'-- JUnit Jupiter [OK]", // + " +-- ModuleUtilsTests [OK]", // + " | +-- javaPlatformModuleSystemIsAvailable() [OK]", // + " | +-- findAllClassesInModule() [OK]", // + " | +-- findAllNonSystemBootModuleNames() [OK]", // + " | '-- preconditions() [OK]", // + " '-- JupiterIntegrationTests [OK]", // + " +-- moduleIsNamed() [A] Assumption failed: not running on the module-path", // + " +-- packageName() [OK]", // + " '-- resolve() [OK]", // + "", // + "Test run finished after \\d+ ms", // + "[ 3 containers found ]", // + "[ 0 containers skipped ]", // + "[ 3 containers started ]", // + "[ 0 containers aborted ]", // + "[ 3 containers successful ]", // + "[ 0 containers failed ]", // + "[ 7 tests found ]", // + "[ 0 tests skipped ]", // + "[ 7 tests started ]", // + "[ 1 tests aborted ]", // + "[ 6 tests successful ]", // + "[ 0 tests failed ]", // + "" // + ); + + var result = mvn(variant); + + result.getOutputLines("out").forEach(System.out::println); + result.getOutputLines("err").forEach(System.err::println); + + assertEquals(0, result.getExitCode()); + assertEquals("", result.getOutput("err")); + assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); + + var workspace = Path.of("build/test-workspace/multi-release-jar", variant); + var actualLines = Files.readAllLines(workspace.resolve("target/junit-platform/console-launcher.out.log")); + assertLinesMatch(expectedLines, actualLines); + } + + private Result mvn(String variant) { + var result = Request.builder() // + .setTool(Request.maven()) // + .setProject("multi-release-jar") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("--update-snapshots", "--show-version", "--errors", "--batch-mode", "--file", variant, + "test") // + .setTimeout(TOOL_TIMEOUT) // + .build() // + .run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + return result; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java new file mode 100644 index 00000000..1162db67 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import de.sormuras.bartholdy.Configuration; +import de.sormuras.bartholdy.tool.CyclesDetector; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import platform.tooling.support.MavenRepo; + +/** + * @since 1.3 + */ +class PackageCyclesDetectionTests { + + @ParameterizedTest + @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") + @Disabled("Need to pass --module-path...") + void moduleDoesNotContainCyclicPackageReferences(String module) { + var jar = MavenRepo.jar(module); + var result = new CyclesDetector(jar, this::ignore).run(Configuration.of()); + assertEquals(0, result.getExitCode(), "result=" + result); + } + + private boolean ignore(String source, String target) { + if (source.equals(target)) { + return true; + } + if (source.startsWith("org.junit.jupiter.params.shadow.com.univocity.parsers.")) { + return true; + } + //noinspection RedundantIfStatement + if (!target.startsWith("org.junit.")) { + return true; + } + return false; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java new file mode 100644 index 00000000..6bd09543 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -0,0 +1,239 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +import de.sormuras.bartholdy.jdk.Jar; +import de.sormuras.bartholdy.jdk.Javac; +import de.sormuras.bartholdy.tool.Java; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.4 + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class StandaloneTests { + + @Test + void jarFileWithoutCompiledModuleDescriptorClass() throws Exception { + var jar = MavenRepo.jar("junit-platform-console-standalone"); + var name = "module-info.class"; + var found = new ArrayList(); + try (var fileSystem = FileSystems.newFileSystem(jar)) { + for (var rootDirectory : fileSystem.getRootDirectories()) { + try (var stream = Files.walk(rootDirectory)) { + stream.filter(path -> path.getNameCount() > 0) // skip root entry + .filter(path -> path.getFileName().toString().equals(name)).forEach(found::add); + } + } + } + assertTrue(found.isEmpty(), jar + " must not contain any " + name + " files: " + found); + } + + @Test + void listAllObservableEngines() { + var result = Request.builder() // + .setTool(new Java()) // + .setProject("standalone") // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments("--list-engines").build() // + .run(false); + + assertEquals(0, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + + var jupiterVersion = Helper.version("junit-jupiter-engine"); + var suiteVersion = Helper.version("junit-platform-suite-engine"); + var vintageVersion = Helper.version("junit-vintage-engine"); + assertLinesMatch(""" + junit-jupiter (org.junit.jupiter:junit-jupiter-engine:%s) + junit-platform-suite (org.junit.platform:junit-platform-suite-engine:%s) + junit-vintage (org.junit.vintage:junit-vintage-engine:%s) + """.formatted(jupiterVersion, suiteVersion, vintageVersion).lines(), // + result.getOutput("out").lines()); + } + + @Test + @Order(1) + void compile() throws Exception { + var workspace = Request.WORKSPACE.resolve("standalone"); + var result = Request.builder() // + .setTool(new Javac()) // + .setProject("standalone") // + .addArguments("-d", workspace.resolve("bin")) // + .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments(workspace.resolve("src/standalone/JupiterIntegration.java")) // + .addArguments(workspace.resolve("src/standalone/JupiterParamsIntegration.java")) // + .addArguments(workspace.resolve("src/standalone/SuiteIntegration.java")) // + .addArguments(workspace.resolve("src/standalone/VintageIntegration.java")).build() // + .run(); + + assertEquals(0, result.getExitCode(), result.getOutput("out") + result.getOutput("err")); + assertTrue(result.getOutput("out").isEmpty()); + assertTrue(result.getOutput("err").isEmpty()); + + // create "tests.jar" that'll be picked-up by "testWithJarredTestClasses()" later + var jarFolder = Files.createDirectories(workspace.resolve("jar")); + var jarResult = Request.builder() // + .setTool(new Jar()) // + .setProject("standalone") // + .addArguments("--create") // + .addArguments("--file", jarFolder.resolve("tests.jar")) // + .addArguments("-C", workspace.resolve("bin"), ".") // + .build().run(false); + assertEquals(0, jarResult.getExitCode(), String.join("\n", jarResult.getOutputLines("out"))); + } + + @Test + @Order(2) + void test() throws IOException { + var result = Request.builder() // + .setTool(new Java()) // + .setProject("standalone") // + .addArguments("--show-version") // + .addArguments("-enableassertions") // + .addArguments("-Djava.util.logging.config.file=logging.properties") // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments("--scan-class-path") // + .addArguments("--disable-banner") // + .addArguments("--include-classname", "standalone.*") // + .addArguments("--classpath", "bin").build() // + .run(false); + + assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + + var workspace = Request.WORKSPACE.resolve("standalone"); + var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); + var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); + assertLinesMatch(expectedOutLines, result.getOutputLines("out"), result.getOutput("out")); + assertLinesMatch(expectedErrLines, result.getOutputLines("err"), result.getOutput("err")); + + var jupiterVersion = Helper.version("junit-jupiter-engine"); + var vintageVersion = Helper.version("junit-vintage-engine"); + assertTrue(result.getOutput("err").contains("junit-jupiter" + + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); + assertTrue(result.getOutput("err").contains("junit-vintage" + + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); + } + + @Test + @Order(3) + void testOnJava8() throws IOException { + var result = Request.builder() // + .setTool(new Java()) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setProject("standalone") // + .addArguments("--show-version") // + .addArguments("-enableassertions") // + .addArguments("-Djava.util.logging.config.file=logging.properties") // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments("--scan-class-path") // + .addArguments("--disable-banner") // + .addArguments("--include-classname", "standalone.*") // + .addArguments("--classpath", "bin").build() // + .run(false); + + assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + + var workspace = Request.WORKSPACE.resolve("standalone"); + var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); + var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); + assertLinesMatch(expectedOutLines, result.getOutputLines("out")); + assertLinesMatch(expectedErrLines, result.getOutputLines("err")); + + var jupiterVersion = Helper.version("junit-jupiter-engine"); + var vintageVersion = Helper.version("junit-vintage-engine"); + assertTrue(result.getOutput("err").contains("junit-jupiter" + + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); + assertTrue(result.getOutput("err").contains("junit-vintage" + + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); + } + + @Test + @Order(3) + // https://github.com/junit-team/junit5/issues/2600 + void testOnJava8SelectPackage() throws IOException { + var result = Request.builder() // + .setTool(new Java()) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setProject("standalone") // + .addArguments("--show-version") // + .addArguments("-enableassertions") // + .addArguments("-Djava.util.logging.config.file=logging.properties") // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments("--select-package", "standalone") // + .addArguments("--disable-banner") // + .addArguments("--include-classname", "standalone.*") // + .addArguments("--classpath", "bin").build() // + .run(false); + + assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + + var workspace = Request.WORKSPACE.resolve("standalone"); + var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); + var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); + assertLinesMatch(expectedOutLines, result.getOutputLines("out")); + assertLinesMatch(expectedErrLines, result.getOutputLines("err")); + + var jupiterVersion = Helper.version("junit-jupiter-engine"); + var vintageVersion = Helper.version("junit-vintage-engine"); + assertTrue(result.getOutput("err").contains("junit-jupiter" + + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); + assertTrue(result.getOutput("err").contains("junit-vintage" + + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); + } + + @Test + @Order(5) + @Disabled("https://github.com/junit-team/junit5/issues/1724") + void testWithJarredTestClasses() { + var jar = MavenRepo.jar("junit-platform-console-standalone"); + var path = new ArrayList(); + // path.add("bin"); // "exploded" test classes are found, see also test() above + path.add(Request.WORKSPACE.resolve("standalone/jar/tests.jar").toAbsolutePath().toString()); + path.add(jar.toString()); + var result = Request.builder() // + .setTool(new Java()) // + .setProject("standalone") // + .addArguments("--show-version") // + .addArguments("-enableassertions") // + .addArguments("-Djava.util.logging.config.file=logging.properties") // + .addArguments("--class-path", String.join(File.pathSeparator, path)) // + .addArguments("org.junit.platform.console.ConsoleLauncher") // + .addArguments("--scan-class-path") // + .addArguments("--disable-banner") // + .addArguments("--include-classname", "standalone.*") // + .addArguments("--fail-if-no-tests") // + .build() // + .run(false); + + assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java new file mode 100644 index 00000000..33243c0a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.spi.ToolProvider; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; +import platform.tooling.support.ThirdPartyJars; + +/** + * @since 1.6 + */ +class ToolProviderTests { + + private static final Path LIB = Request.WORKSPACE.resolve("tool-provider-tests/lib"); + + @BeforeAll + static void prepareLocalLibraryDirectoryWithJUnitPlatformModules() { + try { + var lib = Files.createDirectories(LIB); + try (var directoryStream = Files.newDirectoryStream(lib, "*.jar")) { + for (Path jarFile : directoryStream) { + Files.delete(jarFile); + } + } + for (var module : Helper.loadModuleDirectoryNames()) { + if (module.startsWith("junit-platform")) { + var jar = MavenRepo.jar(module); + Files.copy(jar, lib.resolve(module + ".jar")); + } + } + ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); + ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); + } + catch (Exception e) { + throw new AssertionError("Preparing local library folder failed", e); + } + } + + @Test + void findAndRunJUnitOnTheClassPath() { + try (var loader = new URLClassLoader("junit", urls(LIB), ClassLoader.getPlatformClassLoader())) { + var sl = ServiceLoader.load(ToolProvider.class, loader); + var junit = StreamSupport.stream(sl.spliterator(), false).filter(p -> p.name().equals("junit")).findFirst(); + + assertTrue(junit.isPresent(), "Tool 'junit' not found in: " + LIB); + assertJUnitPrintsHelpMessage(junit.get()); + } + catch (IOException e) { + throw new AssertionError("Closing URLClassLoader failed: " + e, e); + } + } + + @Test + void findAndRunJUnitOnTheModulePath() { + var finder = ModuleFinder.of(LIB); + var modules = finder.findAll().stream() // + .map(ModuleReference::descriptor) // + .map(ModuleDescriptor::toNameAndVersion) // + .sorted() // + .collect(Collectors.toList()); + // modules.forEach(System.out::println); + + var bootLayer = ModuleLayer.boot(); + var configuration = bootLayer.configuration().resolveAndBind(finder, ModuleFinder.of(), Set.of()); + var layer = bootLayer.defineModulesWithOneLoader(configuration, ClassLoader.getPlatformClassLoader()); + + var sl = ServiceLoader.load(layer, ToolProvider.class); + var junit = StreamSupport.stream(sl.spliterator(), false).filter(p -> p.name().equals("junit")).findFirst(); + + assertTrue(junit.isPresent(), "Tool 'junit' not found in modules: " + modules); + assertJUnitPrintsHelpMessage(junit.get()); + } + + private static URL[] urls(Path directory) { + try (var stream = Files.newDirectoryStream(directory, "*.jar")) { + var paths = new ArrayList(); + stream.forEach(path -> paths.add(url(path))); + return paths.toArray(URL[]::new); + } + catch (Exception e) { + throw new AssertionError("Creating URL[] failed: " + e, e); + } + } + + private static URL url(Path path) { + try { + return path.toUri().toURL(); + } + catch (MalformedURLException e) { + throw new AssertionError("Converting path to URL failed: " + e, e); + } + } + + private static void assertJUnitPrintsHelpMessage(ToolProvider junit) { + var out = new StringWriter(); + var err = new StringWriter(); + var code = junit.run(new PrintWriter(out), new PrintWriter(err), "--help"); + assertAll(() -> assertLinesMatch(List.of( // + ">> USAGE >>", // + "Launches the JUnit Platform for test discovery and execution.", // + ">> OPTIONS >>"), // + out.toString().lines().collect(Collectors.toList())), // + () -> assertEquals("", err.toString()), // + () -> assertEquals(0, code, "Expected exit of 0, but got: " + code) // + ); + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java new file mode 100644 index 00000000..2a1fad0e --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; + +import java.nio.file.Paths; + +import de.sormuras.bartholdy.Result; +import de.sormuras.bartholdy.tool.GradleWrapper; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +class VintageGradleIntegrationTests { + + @Test + void unsupportedVersion() { + var result = run("4.11"); + + assertThat(result.getExitCode()).isGreaterThan(0); + assertThat(result.getOutput("out")) // + .doesNotContain("STARTED") // + .contains("Unsupported version of junit:junit: 4.11"); + } + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = { "4.12", "4.13.2" }) + void supportedVersions(String version) { + var result = run(version); + + assertThat(result.getExitCode()).isGreaterThan(0); + assertThat(result.getOutput("out")) // + .contains("VintageTest > success PASSED") // + .contains("VintageTest > failure FAILED"); + + var testResultsDir = Request.WORKSPACE.resolve("vintage-gradle-" + version).resolve("build/test-results/test"); + assertThat(testResultsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); + } + + private Result run(String version) { + var result = Request.builder() // + .setTool(new GradleWrapper(Paths.get(".."))) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setProject("vintage") // + .setWorkspace("vintage-gradle-" + version) // + .addArguments("build", "--no-daemon", "--stacktrace") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("-Djunit4Version=" + version) // + .setTimeout(TOOL_TIMEOUT) // + .build() // + .run(); + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + return result; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java new file mode 100644 index 00000000..624e239c --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; + +import de.sormuras.bartholdy.Result; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.opentest4j.TestAbortedException; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +class VintageMavenIntegrationTests { + + @Test + void unsupportedVersion() { + var result = run("4.11"); + + assertThat(result.getExitCode()).isEqualTo(0); + assertThat(result.getOutput("out")) // + .contains("Tests run: 0, Failures: 0, Errors: 0, Skipped: 0"); + } + + @ParameterizedTest(name = "{0}") + @ValueSource(strings = { "4.12", "4.13.2" }) + void supportedVersions(String version) { + var result = run(version); + + assertThat(result.getExitCode()).isGreaterThan(0); + assertThat(result.getOutput("out")) // + .contains("Running com.example.vintage.VintageTest") // + .contains("VintageTest.failure:") // + .contains("Tests run: 2, Failures: 1, Errors: 0, Skipped: 0"); + + var surefireReportsDir = Request.WORKSPACE.resolve("vintage-maven-" + version).resolve( + "target/surefire-reports"); + assertThat(surefireReportsDir.resolve("com.example.vintage.VintageTest.txt")).isRegularFile(); + assertThat(surefireReportsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); + } + + private Result run(String version) { + var result = Request.builder() // + .setTool(Request.maven()) // + .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setProject("vintage") // + .setWorkspace("vintage-maven-" + version) // + .addArguments("clean", "test", "--update-snapshots", "--batch-mode") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("-Djunit4Version=" + version) // + .setTimeout(TOOL_TIMEOUT) // + .build() // + .run(); + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + return result; + } + +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java new file mode 100644 index 00000000..312cdaca --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java @@ -0,0 +1,158 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.xmlunit.assertj3.XmlAssert; +import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; + +class XmlAssertions { + + static void verifyContainsExpectedStartedOpenTestReport(Path testResultsDir) { + try (var files = Files.list(testResultsDir)) { + Path xmlFile = files.filter(it -> it.getFileName().toString().startsWith("junit-platform-events-")) // + .findAny() // + .orElseThrow(() -> new AssertionError("Missing open-test-reporting XML file in " + testResultsDir)); + verifyContent(xmlFile); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void verifyContent(Path xmlFile) { + var expected = """ + + + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + + + + + [engine:junit-jupiter] + JUnit Jupiter + CONTAINER + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] + com.example.project.CalculatorTests + CONTAINER + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] + addsTwoNumbers() + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] + add(int, int, int) + CONTAINER + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] + add(int, int, int)[1] + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] + add(int, int, int)[2] + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] + add(int, int, int)[3] + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] + add(int, int, int)[4] + TEST + + + + + + + + + + + + + + + + + + + """; + + XmlAssert.assertThat(xmlFile).and(expected) // + .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // + .ignoreWhitespace() // + .areIdentical(); + } +} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..8595a6c3 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts new file mode 100644 index 00000000..91c857de --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts @@ -0,0 +1,134 @@ +import com.gradle.enterprise.gradleplugin.internal.extension.BuildScanExtensionWithHiddenFeatures + +pluginManagement { + repositories { + gradlePluginPortal() + } + plugins { + id("com.gradle.enterprise") version "3.12.1" // keep in sync with buildSrc/build.gradle.kts + id("com.gradle.common-custom-user-data-gradle-plugin") version "1.8.2" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" + id("org.ajoberstar.git-publish") version "4.1.1" + kotlin("jvm") version "1.5.31" + // Check if workaround in documentation.gradle.kts can be removed when upgrading + id("org.asciidoctor.jvm.convert") version "3.3.2" + id("org.asciidoctor.jvm.pdf") version "3.3.2" + id("me.champeau.jmh") version "0.6.8" + id("io.spring.nohttp") version "0.0.10" + id("io.github.gradle-nexus.publish-plugin") version "1.1.0" + } +} + +plugins { + id("com.gradle.enterprise") + id("com.gradle.common-custom-user-data-gradle-plugin") + id("org.gradle.toolchains.foojay-resolver-convention") +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + maven(url = "https://oss.sonatype.org/content/repositories/snapshots") { + mavenContent { + snapshotsOnly() + } + } + } +} + +val gradleEnterpriseServer = "https://ge.junit.org" +val isCiServer = System.getenv("CI") != null +val junitBuildCacheUrl: String? by extra +val junitBuildCacheUsername: String? by extra +val junitBuildCachePassword: String? by extra + +gradleEnterprise { + buildScan { + capture.isTaskInputFiles = true + isUploadInBackground = !isCiServer + + publishAlways() + + // Publish to scans.gradle.com when `--scan` is used explicitly + if (!gradle.startParameter.isBuildScan) { + server = gradleEnterpriseServer + this as BuildScanExtensionWithHiddenFeatures + publishIfAuthenticated() + } + + obfuscation { + if (isCiServer) { + username { "github" } + } else { + hostname { null } + ipAddresses { emptyList() } + } + } + + val enableTestDistribution = providers.gradleProperty("enableTestDistribution") + .map(String::toBoolean) + .getOrElse(false) + if (enableTestDistribution) { + tag("test-distribution") + } + } +} + +buildCache { + local { + isEnabled = !isCiServer + } + remote { + url = uri(junitBuildCacheUrl ?: "$gradleEnterpriseServer/cache/") + isPush = isCiServer && !junitBuildCacheUsername.isNullOrEmpty() && !junitBuildCachePassword.isNullOrEmpty() + credentials { + username = junitBuildCacheUsername?.ifEmpty { null } + password = junitBuildCachePassword?.ifEmpty { null } + } + } +} + +val javaVersion = JavaVersion.current() +require(javaVersion == JavaVersion.VERSION_17) { + "The JUnit 5 build must be executed with Java 17. Currently executing with Java ${javaVersion.majorVersion}." +} + +rootProject.name = "junit5" + +include("documentation") +include("junit-jupiter") +include("junit-jupiter-api") +include("junit-jupiter-engine") +include("junit-jupiter-migrationsupport") +include("junit-jupiter-params") +include("junit-platform-commons") +include("junit-platform-console") +include("junit-platform-console-standalone") +include("junit-platform-engine") +include("junit-platform-jfr") +include("junit-platform-launcher") +include("junit-platform-reporting") +include("junit-platform-runner") +include("junit-platform-suite") +include("junit-platform-suite-api") +include("junit-platform-suite-commons") +include("junit-platform-suite-engine") +include("junit-platform-testkit") +include("junit-vintage-engine") +include("platform-tests") +include("platform-tooling-support-tests") +include("junit-bom") + +// check that every subproject has a custom build file +// based on the project name +rootProject.children.forEach { project -> + project.buildFileName = "${project.name}.gradle" + if (!project.buildFile.isFile) { + project.buildFileName = "${project.name}.gradle.kts" + } + require(project.buildFile.isFile) { + "${project.buildFile} must exist" + } +} + +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh b/src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh new file mode 100644 index 00000000..b0727562 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e + +rm -rf checksums* + +export SOURCE_DATE_EPOCH=$(date +%s) + +function calculate_checksums() { + OUTPUT=$1 + + ./gradlew --no-build-cache clean assemble --parallel -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Reproducibility + + find . -name '*.jar' \ + | grep '/build/libs/' \ + | grep --invert-match 'javadoc' \ + | sort \ + | xargs sha256sum > ${OUTPUT} +} + + +calculate_checksums checksums-1.txt +calculate_checksums checksums-2.txt + +diff checksums-1.txt checksums-2.txt diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml new file mode 100644 index 00000000..447d02df --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml new file mode 100644 index 00000000..6ac9b5d8 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml new file mode 100644 index 00000000..05801fcc --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml b/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml new file mode 100644 index 00000000..029e5df1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder b/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder new file mode 100644 index 00000000..daded8e1 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder @@ -0,0 +1,7 @@ +#Organize Import Order +0=java +1=javax +2=jdk +3=com +4=io +5=org diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh b/src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh new file mode 100644 index 00000000..ecd0482a --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +readonly checksum_directory='documentation/build/checksum' +readonly current="${checksum_directory}/current-checksum.txt" +readonly published="${checksum_directory}/published-checksum.txt" +readonly github_pages_url='https://raw.githubusercontent.com/junit-team/junit5/gh-pages/docs/snapshot/published-checksum.txt' + +# +# always generate current sums +# +echo "Generating checksum file ${current}..." +mkdir --parents "${checksum_directory}" +md5sum documentation/documentation.gradle.kts > "${current}" +md5sum $(find documentation/src -type f) >> "${current}" +# skip module junit-bom because it doesn't contain relevant documentation +md5sum $(find junit-jupiter -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-jupiter-api -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-jupiter-engine -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-jupiter-migrationsupport -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-jupiter-params -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-commons -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-console -wholename '**/src/main/*.java') >> "${current}" +# skip module junit-platform-console-standalone because it doesn't contain relevant documentation +md5sum $(find junit-platform-engine -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-jfr -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-launcher -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-reporting -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-runner -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-suite -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-suite-api -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-suite-commons -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-suite-engine -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-platform-testkit -wholename '**/src/main/*.java') >> "${current}" +md5sum $(find junit-vintage-engine -wholename '**/src/main/*.java') >> "${current}" +# skip module platform-tests because it doesn't contain relevant documentation +# skip module platform-tooling-support-tests because it doesn't contain relevant documentation +sort --output "${current}" "${current}" +echo +md5sum "${current}" + +# +# compare current with published sums +# +curl --silent --output "${published}" "${github_pages_url}" +md5sum "${published}" +if cmp --silent "${current}" "${published}" ; then + # + # no changes detected: we're done + # + echo + echo "Already published documentation with same source checksum." + echo +else + # + # update checksum file and trigger new documentation build and upload + # + echo + echo "Creating and publishing documentation..." + echo + cp --force "${current}" "${published}" + ./gradlew gitPublishPush -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Documentation +fi diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java b/src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java new file mode 100644 index 00000000..9bbea1d4 --- /dev/null +++ b/src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java @@ -0,0 +1,9 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ diff --git a/src/main/java/com/test.java b/src/main/java/com/test.java new file mode 100644 index 00000000..fa5851ed --- /dev/null +++ b/src/main/java/com/test.java @@ -0,0 +1,7 @@ +package main.java.com; +import org.junit.Assert.*; +public class test { + public static void main(String[] args) { + + } +} From 01626836c384602b76d54152589ef2e26c97e2b3 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Thu, 6 Jul 2023 20:28:22 +0700 Subject: [PATCH 16/18] j --- .../junit-team-junit5-da216b8/.codecov.yml | 8 - .../junit-team-junit5-da216b8/.gitattributes | 7 - .../.github/FUNDING.yml | 8 - .../.github/ISSUE_TEMPLATE.md | 23 - .../.github/ISSUE_TEMPLATE/bug_report.md | 20 - .../.github/ISSUE_TEMPLATE/feature_request.md | 11 - .../.github/ISSUE_TEMPLATE/question.md | 11 - .../.github/PULL_REQUEST_TEMPLATE.md | 19 - .../.github/actions/main-build/action.yml | 19 - .../.github/actions/run-gradle/action.yml | 25 - .../.github/actions/setup-test-jdk/action.yml | 11 - .../.github/dependabot.yml | 17 - .../.github/stale.yml | 69 - .../.github/workflows/codeql-analysis.yml | 41 - .../.github/workflows/cross-version.yml | 53 - .../workflows/gradle-wrapper-validation.yml | 14 - .../.github/workflows/issue-labels.yml | 14 - .../.github/workflows/main.yml | 110 - .../.github/workflows/reproducible-build.yml | 31 - .../com/junit-team-junit5-da216b8/.gitignore | 34 - .../CODE_OF_CONDUCT.md | 76 - .../junit-team-junit5-da216b8/CONTRIBUTING.md | 159 - .../java/com/junit-team-junit5-da216b8/KEYS | 70 - .../LICENSE-notice.md | 8 - .../com/junit-team-junit5-da216b8/LICENSE.md | 98 - .../com/junit-team-junit5-da216b8/README.md | 100 - .../com/junit-team-junit5-da216b8/SECURITY.md | 12 - .../build.gradle.kts | 65 - .../buildSrc/build.gradle.kts | 31 - .../buildSrc/settings.gradle.kts | 1 - .../src/main/kotlin/JavaLibraryExtension.kt | 7 - .../buildSrc/src/main/kotlin/License.kt | 4 - .../src/main/kotlin/ProjectExtensions.kt | 19 - .../src/main/kotlin/TaskExtensions.kt | 5 - .../main/kotlin/base-conventions.gradle.kts | 6 - .../src/main/kotlin/build-metadata.gradle.kts | 28 - .../kotlin/dependency-update-check.gradle.kts | 21 - .../jacoco-aggregation-conventions.gradle.kts | 17 - .../main/kotlin/jacoco-conventions.gradle.kts | 13 - .../kotlin/jacoco-java-conventions.gradle.kts | 31 - .../java-library-conventions.gradle.kts | 289 -- .../java-multi-release-sources.gradle.kts | 43 - .../kotlin/java-repackage-jars.gradle.kts | 52 - .../java-toolchain-conventions.gradle.kts | 42 - .../kotlin/junit4-compatibility.gradle.kts | 29 - .../kotlin-library-conventions.gradle.kts | 26 - .../exec/ClasspathSystemPropertyProvider.kt | 9 - .../junit/gradle/exec/RunConsoleLauncher.kt | 86 - .../org/junit/gradle/java/ExecJarAction.kt | 27 - .../gradle/java/ModulePathArgumentProvider.kt | 41 - .../java/PatchModuleArgumentProvider.kt | 47 - .../junit/gradle/java/WriteArtifactsFile.kt | 36 - .../ModuleSpecificJavadocFileOption.kt | 26 - .../main/kotlin/osgi-conventions.gradle.kts | 114 - .../kotlin/publishing-conventions.gradle.kts | 115 - .../main/kotlin/shadow-conventions.gradle.kts | 69 - .../kotlin/spotless-conventions.gradle.kts | 49 - .../main/kotlin/temp-maven-repo.gradle.kts | 30 - .../kotlin/testing-conventions.gradle.kts | 84 - .../documentation/README.md | 34 - .../documentation/documentation.gradle.kts | 457 --- .../asciidoc/docinfos/docinfo-footer.html | 33 - .../src/docs/asciidoc/docinfos/docinfo.html | 18 - .../src/docs/asciidoc/link-attributes.adoc | 190 - .../docs/asciidoc/release-notes/index.adoc | 31 - .../release-notes/release-notes-5.8.0.adoc | 21 - .../release-notes/release-notes-5.8.1.adoc | 62 - .../release-notes/release-notes-5.8.2.adoc | 45 - .../release-notes/release-notes-5.9.0.adoc | 148 - .../release-notes/release-notes-5.9.1.adoc | 53 - .../release-notes/release-notes-5.9.2.adoc | 61 - .../release-notes/release-notes-5.9.3.adoc | 59 - .../release-notes/release-notes-TEMPLATE.adoc | 73 - .../docs/asciidoc/resources/fonts/Symbola.ttf | Bin 2440452 -> 0 bytes .../resources/themes/junit-pdf-theme.yml | 6 - .../src/docs/asciidoc/tocbot-3.0.2/styles.css | 1 - .../src/docs/asciidoc/tocbot-3.0.2/tocbot.css | 1 - .../src/docs/asciidoc/tocbot-3.0.2/tocbot.js | 136 - .../docs/asciidoc/tocbot-3.0.2/tocbot.min.js | 1 - .../asciidoc/user-guide/advanced-topics.adoc | 12 - .../user-guide/advanced-topics/engines.adoc | 122 - .../junit-platform-reporting.adoc | 138 - .../junit-platform-suite-engine.adoc | 50 - .../advanced-topics/launcher-api.adoc | 243 -- .../user-guide/advanced-topics/testkit.adoc | 164 - .../asciidoc/user-guide/api-evolution.adoc | 63 - .../docs/asciidoc/user-guide/appendix.adoc | 232 -- .../asciidoc/user-guide/contributors.adoc | 4 - .../docs/asciidoc/user-guide/extensions.adoc | 951 ----- ...nsions_BrokenLifecycleMethodConfigDemo.png | Bin 60272 -> 0 bytes ...nsions_BrokenLifecycleMethodConfigDemo.txt | 19 - .../images/extensions_DatabaseTestsDemo.png | Bin 94331 -> 0 bytes .../images/extensions_DatabaseTestsDemo.txt | 27 - .../images/extensions_lifecycle.png | Bin 223256 -> 0 bytes .../images/extensions_lifecycle_source.docx | Bin 15930 -> 0 bytes .../images/writing-tests_execution_mode.svg | 386 -- .../images/writing-tests_nested_test_ide.png | Bin 230224 -> 0 bytes .../src/docs/asciidoc/user-guide/index.adoc | 44 - .../user-guide/migration-from-junit4.adoc | 155 - .../docs/asciidoc/user-guide/overview.adoc | 90 - .../asciidoc/user-guide/running-tests.adoc | 1073 ----- .../asciidoc/user-guide/writing-tests.adoc | 2554 ------------ .../src/javadoc/junit-overview.html | 26 - .../src/javadoc/junit-stylesheet.css | 155 - .../src/main/java/example/domain/Person.java | 98 - .../java/example/domain/package-info.java | 5 - .../java/example/registration/WebClient.java | 19 - .../example/registration/WebResponse.java | 19 - .../registration/WebServerExtension.java | 43 - .../example/registration/package-info.java | 5 - .../main/java/example/util/Calculator.java | 31 - .../main/java/example/util/ListWriter.java | 31 - .../main/java/example/util/StringUtils.java | 25 - .../main/java/example/util/package-info.java | 5 - .../src/test/java/example/AssertionsDemo.java | 150 - .../test/java/example/AssumptionsDemo.java | 55 - .../example/ConditionalTestExecutionDemo.java | 218 - .../test/java/example/CustomTestEngine.java | 40 - .../test/java/example/DisabledClassDemo.java | 25 - .../test/java/example/DisabledTestsDemo.java | 29 - .../test/java/example/DisplayNameDemo.java | 36 - .../example/DisplayNameGeneratorDemo.java | 58 - .../java/example/DocumentationTestSuite.java | 36 - .../test/java/example/DynamicTestsDemo.java | 197 - .../test/java/example/ExampleTestCase.java | 59 - .../example/ExternalCustomConditionDemo.java | 34 - .../example/ExternalMethodSourceDemo.java | 34 - .../src/test/java/example/Fast.java | 26 - .../src/test/java/example/FastTest.java | 28 - .../java/example/HamcrestAssertionsDemo.java | 32 - .../test/java/example/IgnoredTestsDemo.java | 31 - .../src/test/java/example/JUnit4Tests.java | 22 - .../java/example/JUnitPlatformClassDemo.java | 39 - .../java/example/JUnitPlatformSuiteDemo.java | 27 - .../MethodSourceParameterResolutionDemo.java | 66 - .../example/MyFirstJUnitJupiterTests.java | 30 - .../example/OrderedNestedTestClassesDemo.java | 41 - .../test/java/example/OrderedTestsDemo.java | 41 - .../java/example/ParameterizedTestDemo.java | 470 --- .../test/java/example/PollingTimeoutDemo.java | 33 - .../test/java/example/RepeatedTestsDemo.java | 68 - .../java/example/SharedResourcesDemo.java | 66 - .../src/test/java/example/SlowTests.java | 126 - .../src/test/java/example/StandardTests.java | 67 - .../src/test/java/example/SuiteDemo.java | 28 - .../src/test/java/example/TaggingDemo.java | 27 - .../java/example/TempDirCleanupModeDemo.java | 28 - .../test/java/example/TempDirectoryDemo.java | 76 - .../src/test/java/example/TestInfoDemo.java | 49 - .../test/java/example/TestReporterDemo.java | 45 - .../test/java/example/TestTemplateDemo.java | 88 - .../test/java/example/TestingAStackDemo.java | 98 - .../src/test/java/example/TimeoutDemo.java | 42 - .../java/example/UsingTheLauncherDemo.java | 156 - .../callbacks/AbstractDatabaseTests.java | 51 - .../BrokenLifecycleMethodConfigDemo.java | 60 - .../example/callbacks/DatabaseTestsDemo.java | 61 - .../java/example/callbacks/Extension1.java | 35 - .../java/example/callbacks/Extension2.java | 35 - .../test/java/example/callbacks/Logger.java | 54 - .../defaultmethods/ComparableContract.java | 44 - .../defaultmethods/EqualsContract.java | 45 - .../example/defaultmethods/StringTests.java | 32 - .../java/example/defaultmethods/Testable.java | 19 - .../exception/IgnoreIOExceptionExtension.java | 33 - .../exception/IgnoreIOExceptionTests.java | 33 - .../exception/MultipleHandlersTestCase.java | 62 - .../RecordStateOnErrorExtension.java | 55 - .../test/java/example/extensions/Random.java | 26 - .../example/extensions/RandomNumberDemo.java | 41 - .../extensions/RandomNumberExtension.java | 39 - .../interceptor/SwingEdtInterceptor.java | 48 - .../registration/DocumentationDemo.java | 58 - .../example/registration/WebServerDemo.java | 41 - .../session/GlobalSetupTeardownListener.java | 92 - .../test/java/example/session/HttpTests.java | 37 - .../testinterface/TestInterfaceDemo.java | 29 - .../TestInterfaceDynamicTestsDemo.java | 34 - .../testinterface/TestLifecycleLogger.java | 54 - .../testinterface/TimeExecutionLogger.java | 23 - .../testkit/EngineTestKitAllEventsDemo.java | 72 - .../EngineTestKitFailedMethodDemo.java | 44 - .../EngineTestKitSkippedMethodDemo.java | 48 - .../testkit/EngineTestKitStatisticsDemo.java | 48 - .../java/example/timing/TimingExtension.java | 59 - .../example/timing/TimingExtensionTests.java | 36 - .../test/java/extensions/ExpectToFail.java | 51 - .../api/tools/AbstractApiReportWriter.java | 104 - .../java/org/junit/api/tools/ApiReport.java | 40 - .../junit/api/tools/ApiReportGenerator.java | 117 - .../org/junit/api/tools/ApiReportWriter.java | 27 - .../api/tools/AsciidocApiReportWriter.java | 74 - .../junit/api/tools/HtmlApiReportWriter.java | 74 - .../api/tools/MarkdownApiReportWriter.java | 73 - .../kotlin/example/FibonacciCalculator.kt | 29 - .../kotlin/example/KotlinAssertionsDemo.kt | 101 - .../registration/KotlinWebServerDemo.kt | 35 - ....platform.launcher.LauncherSessionListener | 1 - .../test/resources/junit-platform.properties | 4 - .../src/test/resources/log4j2-test.xml | 19 - .../src/test/resources/two-column.csv | 5 - .../gradle.properties | 31 - .../gradle/libs.versions.toml | 56 - .../gradle/wrapper/gradle-wrapper.properties | 7 - .../com/junit-team-junit5-da216b8/gradlew | 244 -- .../com/junit-team-junit5-da216b8/gradlew.bat | 92 - .../junit-bom/README.md | 8 - .../junit-bom/junit-bom.gradle.kts | 42 - .../junit-jupiter-api.gradle.kts | 32 - .../java/org/junit/jupiter/api/AfterAll.java | 100 - .../java/org/junit/jupiter/api/AfterEach.java | 91 - .../java/org/junit/jupiter/api/AssertAll.java | 86 - .../junit/jupiter/api/AssertArrayEquals.java | 449 -- .../junit/jupiter/api/AssertDoesNotThrow.java | 91 - .../org/junit/jupiter/api/AssertEquals.java | 199 - .../org/junit/jupiter/api/AssertFalse.java | 66 - .../junit/jupiter/api/AssertInstanceOf.java | 54 - .../jupiter/api/AssertIterableEquals.java | 212 - .../junit/jupiter/api/AssertLinesMatch.java | 239 -- .../junit/jupiter/api/AssertNotEquals.java | 280 -- .../org/junit/jupiter/api/AssertNotNull.java | 51 - .../org/junit/jupiter/api/AssertNotSame.java | 52 - .../org/junit/jupiter/api/AssertNull.java | 53 - .../org/junit/jupiter/api/AssertSame.java | 53 - .../org/junit/jupiter/api/AssertThrows.java | 76 - .../jupiter/api/AssertThrowsExactly.java | 77 - .../org/junit/jupiter/api/AssertTimeout.java | 86 - .../api/AssertTimeoutPreemptively.java | 156 - .../org/junit/jupiter/api/AssertTrue.java | 66 - .../jupiter/api/AssertionFailureBuilder.java | 205 - .../org/junit/jupiter/api/AssertionUtils.java | 117 - .../org/junit/jupiter/api/Assertions.java | 3625 ----------------- .../org/junit/jupiter/api/Assumptions.java | 319 -- .../java/org/junit/jupiter/api/BeforeAll.java | 100 - .../org/junit/jupiter/api/BeforeEach.java | 91 - .../junit/jupiter/api/ClassDescriptor.java | 87 - .../org/junit/jupiter/api/ClassOrderer.java | 271 -- .../jupiter/api/ClassOrdererContext.java | 56 - .../java/org/junit/jupiter/api/Disabled.java | 66 - .../org/junit/jupiter/api/DisplayName.java | 51 - .../jupiter/api/DisplayNameGeneration.java | 57 - .../jupiter/api/DisplayNameGenerator.java | 378 -- .../junit/jupiter/api/DynamicContainer.java | 109 - .../org/junit/jupiter/api/DynamicNode.java | 71 - .../org/junit/jupiter/api/DynamicTest.java | 243 -- .../api/IndicativeSentencesGeneration.java | 71 - .../junit/jupiter/api/MethodDescriptor.java | 91 - .../org/junit/jupiter/api/MethodOrderer.java | 334 -- .../jupiter/api/MethodOrdererContext.java | 63 - .../java/org/junit/jupiter/api/Named.java | 94 - .../java/org/junit/jupiter/api/Nested.java | 54 - .../java/org/junit/jupiter/api/Order.java | 72 - .../org/junit/jupiter/api/RepeatedTest.java | 155 - .../org/junit/jupiter/api/RepetitionInfo.java | 53 - .../main/java/org/junit/jupiter/api/Tag.java | 75 - .../main/java/org/junit/jupiter/api/Tags.java | 47 - .../main/java/org/junit/jupiter/api/Test.java | 79 - .../org/junit/jupiter/api/TestClassOrder.java | 85 - .../org/junit/jupiter/api/TestFactory.java | 79 - .../java/org/junit/jupiter/api/TestInfo.java | 110 - .../org/junit/jupiter/api/TestInstance.java | 123 - .../junit/jupiter/api/TestMethodOrder.java | 89 - .../org/junit/jupiter/api/TestReporter.java | 80 - .../org/junit/jupiter/api/TestTemplate.java | 84 - .../java/org/junit/jupiter/api/Timeout.java | 388 -- .../AbstractOsBasedExecutionCondition.java | 70 - ...AbstractRepeatableAnnotationCondition.java | 72 - .../condition/BooleanExecutionCondition.java | 54 - .../api/condition/DisabledForJreRange.java | 112 - .../DisabledForJreRangeCondition.java | 45 - .../jupiter/api/condition/DisabledIf.java | 97 - .../api/condition/DisabledIfCondition.java | 31 - .../DisabledIfEnvironmentVariable.java | 108 - ...isabledIfEnvironmentVariableCondition.java | 75 - .../DisabledIfEnvironmentVariables.java | 47 - .../condition/DisabledIfSystemProperties.java | 47 - .../condition/DisabledIfSystemProperty.java | 108 - .../DisabledIfSystemPropertyCondition.java | 64 - .../api/condition/DisabledInNativeImage.java | 76 - .../jupiter/api/condition/DisabledOnJre.java | 96 - .../api/condition/DisabledOnJreCondition.java | 40 - .../jupiter/api/condition/DisabledOnOs.java | 112 - .../api/condition/DisabledOnOsCondition.java | 61 - .../api/condition/EnabledForJreRange.java | 112 - .../EnabledForJreRangeCondition.java | 45 - .../jupiter/api/condition/EnabledIf.java | 97 - .../api/condition/EnabledIfCondition.java | 32 - .../EnabledIfEnvironmentVariable.java | 107 - ...EnabledIfEnvironmentVariableCondition.java | 74 - .../EnabledIfEnvironmentVariables.java | 47 - .../condition/EnabledIfSystemProperties.java | 47 - .../condition/EnabledIfSystemProperty.java | 107 - .../EnabledIfSystemPropertyCondition.java | 63 - .../api/condition/EnabledInNativeImage.java | 76 - .../jupiter/api/condition/EnabledOnJre.java | 95 - .../api/condition/EnabledOnJreCondition.java | 43 - .../jupiter/api/condition/EnabledOnOs.java | 112 - .../api/condition/EnabledOnOsCondition.java | 61 - .../org/junit/jupiter/api/condition/JRE.java | 244 -- .../api/condition/MethodBasedCondition.java | 110 - .../org/junit/jupiter/api/condition/OS.java | 157 - .../jupiter/api/condition/package-info.java | 5 - .../api/extension/AfterAllCallback.java | 70 - .../api/extension/AfterEachCallback.java | 73 - .../extension/AfterTestExecutionCallback.java | 75 - .../api/extension/BeforeAllCallback.java | 70 - .../api/extension/BeforeEachCallback.java | 73 - .../BeforeTestExecutionCallback.java | 75 - .../extension/ConditionEvaluationResult.java | 102 - .../DynamicTestInvocationContext.java | 36 - .../api/extension/ExecutableInvoker.java | 72 - .../api/extension/ExecutionCondition.java | 71 - .../jupiter/api/extension/ExtendWith.java | 102 - .../jupiter/api/extension/Extension.java | 41 - .../ExtensionConfigurationException.java | 37 - .../api/extension/ExtensionContext.java | 713 ---- .../extension/ExtensionContextException.java | 34 - .../jupiter/api/extension/Extensions.java | 48 - .../api/extension/InvocationInterceptor.java | 248 -- ...ecycleMethodExecutionExceptionHandler.java | 123 - .../api/extension/ParameterContext.java | 151 - .../ParameterResolutionException.java | 38 - .../api/extension/ParameterResolver.java | 93 - .../ReflectiveInvocationContext.java | 74 - .../api/extension/RegisterExtension.java | 144 - .../TestExecutionExceptionHandler.java | 65 - .../api/extension/TestInstanceFactory.java | 76 - .../extension/TestInstanceFactoryContext.java | 50 - .../extension/TestInstancePostProcessor.java | 57 - .../TestInstancePreConstructCallback.java | 58 - .../TestInstancePreDestroyCallback.java | 122 - .../jupiter/api/extension/TestInstances.java | 69 - .../extension/TestInstantiationException.java | 37 - .../TestTemplateInvocationContext.java | 69 - ...TestTemplateInvocationContextProvider.java | 89 - .../jupiter/api/extension/TestWatcher.java | 103 - .../jupiter/api/extension/package-info.java | 5 - .../support/TypeBasedParameterResolver.java | 81 - .../api/extension/support/package-info.java | 5 - .../jupiter/api/function/Executable.java | 49 - .../api/function/ThrowingConsumer.java | 53 - .../api/function/ThrowingSupplier.java | 54 - .../jupiter/api/function/package-info.java | 5 - .../org/junit/jupiter/api/io/CleanupMode.java | 53 - .../org/junit/jupiter/api/io/TempDir.java | 139 - .../junit/jupiter/api/io/package-info.java | 5 - .../org/junit/jupiter/api/package-info.java | 5 - .../junit/jupiter/api/parallel/Execution.java | 101 - .../jupiter/api/parallel/ExecutionMode.java | 41 - .../junit/jupiter/api/parallel/Isolated.java | 51 - .../api/parallel/ResourceAccessMode.java | 36 - .../jupiter/api/parallel/ResourceLock.java | 72 - .../jupiter/api/parallel/ResourceLocks.java | 48 - .../junit/jupiter/api/parallel/Resources.java | 84 - .../jupiter/api/parallel/package-info.java | 5 - .../org/junit/jupiter/api/Assertions.kt | 289 -- .../org.junit.jupiter.api/module-info.java | 27 - .../ExtensionContextParameterResolver.java | 26 - .../jupiter/api/fixtures/TrackLogRecords.java | 83 - .../junit-jupiter-engine.gradle.kts | 53 - .../org/junit/jupiter/engine/Constants.java | 366 -- .../jupiter/engine/JupiterTestEngine.java | 102 - .../config/CachingJupiterConfiguration.java | 118 - .../config/DefaultJupiterConfiguration.java | 131 - .../EnumConfigurationParameterConverter.java | 73 - ...iatingConfigurationParameterConverter.java | 67 - .../engine/config/JupiterConfiguration.java | 69 - .../jupiter/engine/config/package-info.java | 5 - .../descriptor/AbstractExtensionContext.java | 158 - .../descriptor/ClassBasedTestDescriptor.java | 524 --- .../descriptor/ClassExtensionContext.java | 105 - .../descriptor/ClassTestDescriptor.java | 82 - .../DefaultDynamicTestInvocationContext.java | 34 - .../DefaultTestInstanceFactoryContext.java | 53 - .../engine/descriptor/DisplayNameUtils.java | 130 - .../DynamicContainerTestDescriptor.java | 77 - .../descriptor/DynamicDescendantFilter.java | 94 - .../descriptor/DynamicExtensionContext.java | 73 - .../descriptor/DynamicNodeTestDescriptor.java | 62 - .../descriptor/DynamicTestTestDescriptor.java | 81 - .../engine/descriptor/ExtensionUtils.java | 192 - .../jupiter/engine/descriptor/Filterable.java | 30 - .../descriptor/JupiterEngineDescriptor.java | 73 - .../JupiterEngineExtensionContext.java | 76 - .../descriptor/JupiterTestDescriptor.java | 238 -- .../descriptor/LifecycleMethodUtils.java | 105 - .../descriptor/MethodBasedTestDescriptor.java | 149 - .../descriptor/MethodExtensionContext.java | 87 - .../descriptor/MethodSourceSupport.java | 67 - .../descriptor/NestedClassTestDescriptor.java | 90 - .../descriptor/TestFactoryTestDescriptor.java | 210 - .../TestInstanceLifecycleUtils.java | 46 - .../descriptor/TestMethodTestDescriptor.java | 323 -- .../TestTemplateExtensionContext.java | 80 - .../TestTemplateInvocationTestDescriptor.java | 78 - .../TestTemplateTestDescriptor.java | 155 - .../engine/descriptor/package-info.java | 5 - .../AbstractAnnotatedDescriptorWrapper.java | 65 - .../discovery/AbstractOrderingVisitor.java | 189 - .../discovery/ClassOrderingVisitor.java | 87 - .../discovery/ClassSelectorResolver.java | 162 - .../discovery/DefaultClassDescriptor.java | 39 - .../discovery/DefaultClassOrdererContext.java | 45 - .../discovery/DefaultMethodDescriptor.java | 41 - .../DefaultMethodOrdererContext.java | 60 - .../discovery/DiscoverySelectorResolver.java | 49 - .../engine/discovery/MethodFinder.java | 40 - .../discovery/MethodOrderingVisitor.java | 84 - .../discovery/MethodSelectorResolver.java | 251 -- .../engine/discovery/package-info.java | 6 - .../discovery/predicates/IsInnerClass.java | 42 - .../predicates/IsNestedTestClass.java | 40 - .../predicates/IsPotentialTestContainer.java | 49 - .../predicates/IsTestClassWithTests.java | 57 - .../predicates/IsTestFactoryMethod.java | 33 - .../discovery/predicates/IsTestMethod.java | 30 - .../predicates/IsTestTemplateMethod.java | 30 - .../predicates/IsTestableMethod.java | 55 - .../discovery/predicates/package-info.java | 5 - .../execution/AfterEachMethodAdapter.java | 33 - .../execution/BeforeEachMethodAdapter.java | 33 - .../ConditionEvaluationException.java | 31 - .../engine/execution/ConditionEvaluator.java | 84 - .../execution/ConstructorInvocation.java | 59 - .../execution/DefaultExecutableInvoker.java | 58 - .../execution/DefaultParameterContext.java | 80 - .../execution/DefaultTestInstances.java | 72 - .../execution/ExtensionValuesStore.java | 245 -- .../InterceptingExecutableInvoker.java | 116 - .../execution/InvocationInterceptorChain.java | 160 - .../JupiterEngineExecutionContext.java | 195 - .../engine/execution/MethodInvocation.java | 63 - .../engine/execution/NamespaceAwareStore.java | 83 - .../execution/ParameterResolutionUtils.java | 194 - .../execution/TestInstancesProvider.java | 37 - .../engine/execution/package-info.java | 5 - .../engine/extension/DisabledCondition.java | 53 - .../engine/extension/ExtensionRegistrar.java | 71 - .../engine/extension/ExtensionRegistry.java | 70 - .../extension/MutableExtensionRegistry.java | 209 - .../RepeatedTestDisplayNameFormatter.java | 41 - .../extension/RepeatedTestExtension.java | 68 - .../RepeatedTestInvocationContext.java | 49 - .../RepetitionInfoParameterResolver.java | 76 - .../SameThreadTimeoutInvocation.java | 84 - .../SeparateThreadTimeoutInvocation.java | 45 - .../engine/extension/TempDirectory.java | 393 -- .../extension/TestInfoParameterResolver.java | 93 - .../TestReporterParameterResolver.java | 35 - .../extension/TimeoutConfiguration.java | 146 - .../engine/extension/TimeoutDuration.java | 98 - .../extension/TimeoutDurationParser.java | 66 - .../extension/TimeoutExceptionFactory.java | 39 - .../engine/extension/TimeoutExtension.java | 232 -- .../extension/TimeoutInvocationFactory.java | 114 - .../engine/extension/package-info.java | 5 - .../junit/jupiter/engine/package-info.java | 5 - .../JupiterThrowableCollectorFactory.java | 37 - ...est4JAndJUnit4AwareThrowableCollector.java | 70 - .../jupiter/engine/support/package-info.java | 5 - .../org.junit.platform.engine.TestEngine | 1 - .../org.junit.jupiter.engine/module-info.java | 35 - .../api/GroovyAssertEqualsTests.groovy | 192 - .../api/GroovyAssertNotEqualsTests.groovy | 196 - .../api/PrimitiveAndWrapperTypeHelpers.groovy | 70 - .../src/test/java/DefaultPackageTestCase.java | 28 - .../src/test/java/example/B_TestCase.java | 37 - .../org/junit/jupiter/JupiterTestSuite.java | 39 - .../jupiter/api/AssertAllAssertionsTests.java | 224 - .../api/AssertArrayEqualsAssertionsTests.java | 1977 --------- .../AssertDoesNotThrowAssertionsTests.java | 321 -- .../api/AssertEqualsAssertionsTests.java | 771 ---- .../api/AssertFalseAssertionsTests.java | 111 - .../api/AssertInstanceOfAssertionsTests.java | 109 - .../AssertIterableEqualsAssertionsTests.java | 478 --- .../api/AssertLinesMatchAssertionsTests.java | 337 -- .../api/AssertNotEqualsAssertionsTests.java | 768 ---- .../api/AssertNotNullAssertionsTests.java | 65 - .../api/AssertNotSameAssertionsTests.java | 87 - .../api/AssertNullAssertionsTests.java | 152 - .../api/AssertSameAssertionsTests.java | 101 - .../api/AssertThrowsAssertionsTests.java | 295 -- .../AssertThrowsExactlyAssertionsTests.java | 335 -- .../api/AssertTimeoutAssertionsTests.java | 168 - ...ertTimeoutPreemptivelyAssertionsTests.java | 255 -- .../api/AssertTrueAssertionsTests.java | 111 - .../junit/jupiter/api/AssertionTestUtils.java | 100 - .../junit/jupiter/api/AssumptionsTests.java | 245 -- ...playNameGenerationInheritanceTestCase.java | 36 - .../api/DisplayNameGenerationTests.java | 441 -- .../junit/jupiter/api/DynamicTestTests.java | 203 - .../junit/jupiter/api/EnigmaThrowable.java | 22 - .../jupiter/api/FailAssertionsTests.java | 153 - ...entencesGenerationInheritanceTestCase.java | 36 - .../IndicativeSentencesNestedTestCase.java | 33 - .../IndicativeSentencesTopLevelTestCase.java | 33 - .../junit/jupiter/api/IterableFactory.java | 28 - .../AbstractExecutionConditionTests.java | 112 - .../DisabledForJreRangeConditionTests.java | 133 - .../DisabledForJreRangeIntegrationTests.java | 89 - .../condition/DisabledIfConditionTests.java | 112 - ...edIfEnvironmentVariableConditionTests.java | 144 - ...IfEnvironmentVariableIntegrationTests.java | 93 - .../condition/DisabledIfIntegrationTests.java | 97 - ...isabledIfSystemPropertyConditionTests.java | 131 - ...abledIfSystemPropertyIntegrationTests.java | 107 - .../DisabledOnJreConditionTests.java | 231 -- .../DisabledOnJreIntegrationTests.java | 165 - .../condition/DisabledOnOsConditionTests.java | 267 -- .../DisabledOnOsIntegrationTests.java | 175 - .../EnabledForJreRangeConditionTests.java | 132 - .../EnabledForJreRangeIntegrationTests.java | 92 - .../condition/EnabledIfConditionTests.java | 112 - ...edIfEnvironmentVariableConditionTests.java | 164 - ...IfEnvironmentVariableIntegrationTests.java | 101 - .../condition/EnabledIfIntegrationTests.java | 97 - ...EnabledIfSystemPropertyConditionTests.java | 138 - ...abledIfSystemPropertyIntegrationTests.java | 113 - .../condition/EnabledOnJreConditionTests.java | 231 -- .../EnabledOnJreIntegrationTests.java | 207 - .../condition/EnabledOnOsConditionTests.java | 267 -- .../EnabledOnOsIntegrationTests.java | 207 - .../junit/jupiter/api/condition/JRETests.java | 65 - .../api/condition/StaticConditionMethods.java | 23 - .../CloseableResourceIntegrationTests.java | 83 - .../ExecutableInvokerIntegrationTests.java | 92 - .../ExtensionComposabilityTests.java | 122 - .../api/extension/KitchenSinkExtension.java | 180 - .../TypeBasedParameterResolverTests.java | 159 - .../subpackage/SubclassedAssertionsTests.java | 37 - .../SubclassedAssumptionsTests.java | 45 - .../AbstractJupiterTestEngineTests.java | 68 - .../engine/AtypicalJvmMethodNameTests.java | 48 - ...AllAndAfterAllComposedAnnotationTests.java | 71 - ...chAndAfterEachComposedAnnotationTests.java | 71 - .../engine/DefaultExecutionModeTests.java | 186 - .../jupiter/engine/DefaultMethodTests.java | 230 -- .../junit/jupiter/engine/DisabledTests.java | 76 - .../engine/DynamicNodeGenerationTests.java | 533 --- .../engine/ExceptionHandlingTests.java | 359 -- .../engine/FailedAssumptionsTests.java | 69 - ...alidLifecycleMethodConfigurationTests.java | 131 - .../engine/JupiterTestEngineBasicTests.java | 51 - ...leMethodOverridingAndSupersedingTests.java | 194 - .../MultipleTestableAnnotationsTests.java | 55 - .../engine/NestedTestClassesTests.java | 369 -- .../engine/NestedWithInheritanceTests.java | 65 - .../NestedWithSeparateInheritanceTests.java | 95 - ...NonVoidTestableMethodIntegrationTests.java | 42 - .../engine/OverloadedTestMethodTests.java | 79 - .../org/junit/jupiter/engine/RecordTests.java | 38 - .../junit/jupiter/engine/ReportingTests.java | 83 - .../jupiter/engine/SealedClassTests.java | 45 - .../engine/StandardTestClassTests.java | 252 -- ...estedBeforeAllAndAfterAllMethodsTests.java | 95 - .../engine/TestClassInheritanceTests.java | 283 -- ...stInstanceLifecycleConfigurationTests.java | 234 -- .../TestInstanceLifecycleKotlinTests.java | 80 - .../engine/TestInstanceLifecycleTests.java | 1067 ----- .../engine/TestTemplateInvocationTests.java | 792 ---- .../bridge/AbstractNonGenericTests.java | 65 - .../engine/bridge/AbstractNumberTests.java | 32 - .../engine/bridge/BridgeMethodTests.java | 103 - .../engine/bridge/ChildWithBridgeMethods.java | 38 - .../bridge/ChildWithoutBridgeMethods.java | 38 - .../jupiter/engine/bridge/NumberResolver.java | 46 - .../engine/bridge/NumberTestGroup.java | 61 - .../engine/bridge/PackagePrivateParent.java | 43 - .../CachingJupiterConfigurationTests.java | 138 - .../DefaultJupiterConfigurationTests.java | 118 - ...gConfigurationParameterConverterTests.java | 153 - .../CustomDisplayNameGenerator.java | 33 - .../descriptor/DisplayNameUtilsTests.java | 191 - .../descriptor/ExtensionContextTests.java | 345 -- .../JupiterTestDescriptorTests.java | 406 -- .../descriptor/LifecycleMethodUtilsTests.java | 214 - .../TestFactoryTestDescriptorTests.java | 195 - .../TestInstanceLifecycleUtilsTests.java | 115 - ...TemplateInvocationTestDescriptorTests.java | 56 - .../TestTemplateTestDescriptorTests.java | 113 - .../subpackage/Class1WithTestCases.java | 24 - .../subpackage/Class2WithTestCases.java | 24 - .../ClassWithStaticInnerTestCases.java | 35 - .../subpackage/ClassWithoutTestCases.java | 17 - .../DiscoverySelectorResolverTests.java | 900 ---- .../engine/discovery/DiscoveryTests.java | 230 -- .../predicates/IsInnerClassTests.java | 54 - .../predicates/IsNestedTestClassTests.java | 58 - .../IsPotentialTestContainerTests.java | 71 - .../predicates/IsTestClassWithTestsTests.java | 214 - .../predicates/IsTestFactoryMethodTests.java | 86 - .../predicates/IsTestMethodTests.java | 146 - .../predicates/IsTestTemplateMethodTests.java | 57 - .../AbstractExecutableInvokerTests.java | 98 - .../DefaultExecutableInvokerTests.java | 36 - .../execution/DefaultTestInstancesTests.java | 48 - .../DynamicTestIntegrationTests.java | 46 - ...ExtensionContextStoreConcurrencyTests.java | 52 - .../execution/ExtensionContextStoreTests.java | 90 - .../execution/ExtensionValuesStoreTests.java | 379 -- .../InterceptingExecutableInvokerTests.java | 46 - .../JupiterEngineExecutionContextTests.java | 111 - .../ParameterResolutionUtilsTests.java | 465 --- ...singForArrayParameterIntegrationTests.java | 67 - .../injection/sample/CustomAnnotation.java | 24 - .../CustomAnnotationParameterResolver.java | 35 - .../injection/sample/CustomType.java | 27 - .../sample/CustomTypeParameterResolver.java | 32 - .../sample/DoubleParameterResolver.java | 35 - .../sample/LongParameterResolver.java | 63 - .../MapOfListsTypeBasedParameterResolver.java | 32 - .../sample/MapOfStringsParameterResolver.java | 49 - .../sample/NullIntegerParameterResolver.java | 36 - .../sample/NumberParameterResolver.java | 49 - .../PrimitiveArrayParameterResolver.java | 34 - .../PrimitiveIntegerParameterResolver.java | 34 - .../extension/BeforeAndAfterAllTests.java | 385 -- .../extension/BeforeAndAfterEachTests.java | 524 --- ...oreAndAfterTestExecutionCallbackTests.java | 465 --- .../extension/CloseablePathCleanupTests.java | 114 - .../engine/extension/EnigmaException.java | 20 - .../EventuallyInterruptibleInvocation.java | 32 - .../extension/ExecutionConditionTests.java | 210 - .../ExtensionContextExecutionTests.java | 83 - ...gistrationViaParametersAndFieldsTests.java | 928 ----- .../extension/ExtensionRegistryTests.java | 213 - .../extension/InvocationInterceptorTests.java | 355 -- ...eMethodExecutionExceptionHandlerTests.java | 570 --- .../engine/extension/OrderedClassTests.java | 270 -- .../engine/extension/OrderedMethodTests.java | 745 ---- ...rogrammaticExtensionRegistrationTests.java | 311 -- .../extension/ParameterResolverTests.java | 501 --- ...rogrammaticExtensionRegistrationTests.java | 756 ---- .../extension/RandomlyOrderedTests.java | 95 - .../engine/extension/RepeatedTestTests.java | 176 - .../SameThreadTimeoutInvocationTests.java | 54 - .../SeparateThreadTimeoutInvocationTest.java | 80 - .../extension/ServiceLoaderExtension.java | 28 - .../extension/TempDirectoryCleanupTests.java | 369 -- .../TempDirectoryPerContextTests.java | 1308 ------ .../TempDirectoryPerDeclarationTests.java | 1068 ----- .../TempDirectoryPreconditionTests.java | 119 - .../TestExecutionExceptionHandlerTests.java | 216 - .../TestInfoParameterResolverTests.java | 86 - .../extension/TestInstanceFactoryTests.java | 727 ---- ...stProcessorAndPreDestroyCallbackTests.java | 260 -- .../TestInstancePostProcessorTests.java | 172 - ...TestInstancePreConstructCallbackTests.java | 432 -- .../TestInstancePreDestroyCallbackTests.java | 215 - ...ePreDestroyCallbackUtilityMethodTests.java | 111 - .../TestReporterParameterResolverTests.java | 72 - .../engine/extension/TestWatcherTests.java | 308 -- .../extension/TimeoutConfigurationTests.java | 156 - .../extension/TimeoutDurationParserTests.java | 77 - .../extension/TimeoutDurationTests.java | 43 - .../TimeoutExceptionFactoryTest.java | 73 - .../extension/TimeoutExtensionTests.java | 851 ---- .../TimeoutInvocationFactoryTest.java | 89 - .../sub/AlwaysDisabledCondition.java | 35 - .../sub/AnotherAlwaysDisabledCondition.java | 25 - .../sub/SystemPropertyCondition.java | 66 - ...cycleMethodInDifferentPackageTestCase.java | 32 - ...AndJUnit4AwareThrowableCollectorTests.java | 134 - .../api/KotlinAssertTimeoutAssertionsTests.kt | 291 -- .../jupiter/api/KotlinAssertionsTests.kt | 223 - .../jupiter/api/KotlinFailAssertionsTests.kt | 122 - .../kotlin/ArbitraryNamingKotlinTestCase.kt | 26 - .../kotlin/InstancePerClassKotlinTestCase.kt | 62 - .../kotlin/InstancePerMethodKotlinTestCase.kt | 61 - .../org.junit.jupiter.api.extension.Extension | 1 - .../src/test/resources/log4j2-test.xml | 18 - .../discovery/JupiterUniqueIdBuilder.java | 66 - .../junit-jupiter-migrationsupport/README.md | 19 - .../junit-jupiter-migrationsupport.gradle.kts | 37 - .../EnableJUnit4MigrationSupport.java | 57 - .../conditions/IgnoreCondition.java | 59 - .../conditions/package-info.java | 7 - .../migrationsupport/package-info.java | 5 - .../rules/EnableRuleMigrationSupport.java | 47 - .../rules/ExpectedExceptionSupport.java | 67 - .../rules/ExternalResourceSupport.java | 56 - .../rules/TestRuleSupport.java | 168 - .../rules/VerifierSupport.java | 50 - .../adapter/AbstractTestRuleAdapter.java | 52 - .../adapter/ExpectedExceptionAdapter.java | 41 - .../adapter/ExternalResourceAdapter.java | 39 - .../adapter/GenericBeforeAndAfterAdvice.java | 32 - .../rules/adapter/VerifierAdapter.java | 34 - .../rules/adapter/package-info.java | 5 - .../AbstractTestRuleAnnotatedMember.java | 31 - .../rules/member/TestRuleAnnotatedField.java | 41 - .../rules/member/TestRuleAnnotatedMember.java | 26 - .../rules/member/TestRuleAnnotatedMethod.java | 31 - .../rules/member/package-info.java | 5 - .../migrationsupport/rules/package-info.java | 5 - .../module-info.java | 27 - .../JupiterMigrationSupportTestSuite.java | 38 - .../IgnoreAnnotationIntegrationTests.java | 85 - .../conditions/IgnoreConditionTests.java | 150 - .../rules/AbstractTestRuleAdapterTests.java | 83 - ...igrationSupportWithBothRuleTypesTests.java | 87 - .../rules/ExpectedExceptionSupportTests.java | 125 - ...ifferentDeclaredReturnTypesRulesTests.java | 78 - ...pportForMixedMethodAndFieldRulesTests.java | 94 - ...urceSupportForMultipleFieldRulesTests.java | 71 - ...rceSupportForMultipleMethodRulesTests.java | 75 - ...ceSupportForTemporaryFolderFieldTests.java | 42 - ...alResourceSupportWithInheritanceTests.java | 16 - .../ExternalResourceWithoutAdapterTests.java | 40 - .../rules/FailAfterAllHelper.java | 23 - ...rBasedEnableRuleMigrationSupportTests.java | 114 - ...pportForMixedMethodAndFieldRulesTests.java | 62 - .../WrongExtendWithForVerifierFieldTests.java | 46 - ...WrongExtendWithForVerifierMethodTests.java | 50 - .../src/test/resources/log4j2-test.xml | 15 - .../LICENSE-univocity-parsers.md | 168 - .../junit-jupiter-params.gradle.kts | 51 - .../jupiter/params/ParameterizedTest.java | 246 -- .../params/ParameterizedTestExtension.java | 160 - .../ParameterizedTestInvocationContext.java | 46 - .../ParameterizedTestMethodContext.java | 276 -- .../ParameterizedTestNameFormatter.java | 122 - .../ParameterizedTestParameterResolver.java | 122 - .../params/aggregator/AggregateWith.java | 48 - .../aggregator/ArgumentAccessException.java | 39 - .../params/aggregator/ArgumentsAccessor.java | 191 - .../ArgumentsAggregationException.java | 39 - .../aggregator/ArgumentsAggregator.java | 65 - .../aggregator/DefaultArgumentsAccessor.java | 129 - .../params/aggregator/package-info.java | 7 - .../ArgumentConversionException.java | 39 - .../params/converter/ArgumentConverter.java | 65 - .../jupiter/params/converter/ConvertWith.java | 46 - .../converter/DefaultArgumentConverter.java | 302 -- .../FallbackStringToObjectConverter.java | 174 - .../converter/JavaTimeArgumentConverter.java | 72 - .../converter/JavaTimeConversionPattern.java | 47 - .../converter/SimpleArgumentConverter.java | 50 - .../converter/TypedArgumentConverter.java | 80 - .../params/converter/package-info.java | 7 - .../junit/jupiter/params/package-info.java | 5 - .../jupiter/params/provider/Arguments.java | 96 - .../params/provider/ArgumentsProvider.java | 48 - .../params/provider/ArgumentsSource.java | 48 - .../params/provider/ArgumentsSources.java | 46 - .../params/provider/CsvArgumentsProvider.java | 164 - .../provider/CsvFileArgumentsProvider.java | 216 - .../params/provider/CsvFileSource.java | 227 -- .../params/provider/CsvParserFactory.java | 90 - .../params/provider/CsvParsingException.java | 38 - .../jupiter/params/provider/CsvSource.java | 281 -- .../provider/EmptyArgumentsProvider.java | 66 - .../jupiter/params/provider/EmptySource.java | 53 - .../provider/EnumArgumentsProvider.java | 73 - .../jupiter/params/provider/EnumSource.java | 183 - .../provider/MethodArgumentsProvider.java | 207 - .../jupiter/params/provider/MethodSource.java | 135 - .../params/provider/NullAndEmptySource.java | 44 - .../provider/NullArgumentsProvider.java | 39 - .../jupiter/params/provider/NullEnum.java | 26 - .../jupiter/params/provider/NullSource.java | 43 - .../provider/ValueArgumentsProvider.java | 66 - .../jupiter/params/provider/ValueSource.java | 109 - .../jupiter/params/provider/package-info.java | 8 - .../params/support/AnnotationConsumer.java | 34 - .../AnnotationConsumerInitializer.java | 69 - .../jupiter/params/support/package-info.java | 9 - .../params/aggregator/ArgumentsAccessor.kt | 29 - .../org.junit.jupiter.params/module-info.java | 30 - .../ParameterizedTestExtensionTests.java | 369 -- .../ParameterizedTestIntegrationTests.java | 1483 ------- .../ParameterizedTestMethodContextTests.java | 80 - .../ParameterizedTestNameFormatterTests.java | 303 -- .../params/ParameterizedTestSuite.java | 38 - .../AggregatorIntegrationTests.java | 408 -- .../DefaultArgumentsAccessorTests.java | 166 - .../DefaultArgumentConverterTests.java | 242 -- .../FallbackStringToObjectConverterTests.java | 243 -- .../JavaTimeArgumentConverterTests.java | 122 - .../TypedArgumentConverterTests.java | 176 - .../params/provider/ArgumentsTests.java | 59 - .../provider/CsvArgumentsProviderTests.java | 389 -- .../CsvFileArgumentsProviderTests.java | 536 --- .../provider/EnumArgumentsProviderTests.java | 139 - .../params/provider/EnumSourceTests.java | 118 - .../MethodArgumentsProviderTests.java | 998 ----- .../provider/MockCsvAnnotationBuilder.java | 210 - .../provider/ValueArgumentsProviderTests.java | 170 - ...erizedTestNameFormatterIntegrationTests.kt | 62 - .../ArgumentsAccessorKotlinTests.kt | 50 - .../params/aggregator/DisplayNameTests.kt | 35 - .../src/test/resources/broken.csv | 2 - .../src/test/resources/default-max-chars.csv | 1 - .../resources/exceeds-default-max-chars.csv | 1 - .../resources/leading-trailing-spaces.csv | 2 - .../src/test/resources/log4j2-test.xml | 15 - .../params/two-column-with-headers.csv | 13 - .../org/junit/jupiter/params/two-column.csv | 4 - .../src/test/resources/single-column.csv | 5 - .../junit-jupiter/junit-jupiter.gradle.kts | 16 - .../module/org.junit.jupiter/module-info.java | 20 - .../junit-platform-commons.gradle.kts | 40 - .../platform/commons/JUnitException.java | 36 - .../PreconditionViolationException.java | 36 - .../platform/commons/annotation/Testable.java | 81 - .../commons/annotation/package-info.java | 5 - .../junit/platform/commons/function/Try.java | 374 -- .../commons/function/package-info.java | 5 - .../commons/logging/LogRecordListener.java | 149 - .../platform/commons/logging/Logger.java | 118 - .../commons/logging/LoggerFactory.java | 187 - .../commons/logging/package-info.java | 11 - .../junit/platform/commons/package-info.java | 11 - .../commons/support/AnnotationSupport.java | 488 --- .../commons/support/ClassSupport.java | 74 - .../support/HierarchyTraversalMode.java | 38 - .../commons/support/ModifierSupport.java | 241 -- .../commons/support/ReflectionSupport.java | 336 -- .../commons/support/SearchOption.java | 43 - .../commons/support/package-info.java | 11 - .../commons/util/AnnotationUtils.java | 510 --- .../commons/util/BlacklistedExceptions.java | 60 - .../commons/util/ClassFileVisitor.java | 76 - .../platform/commons/util/ClassFilter.java | 78 - .../commons/util/ClassLoaderUtils.java | 94 - .../util/ClassNamePatternFilterUtils.java | 90 - .../platform/commons/util/ClassUtils.java | 95 - .../commons/util/ClasspathScanner.java | 261 -- .../platform/commons/util/CloseablePath.java | 121 - .../commons/util/CollectionUtils.java | 206 - .../platform/commons/util/ExceptionUtils.java | 83 - .../platform/commons/util/FunctionUtils.java | 52 - .../junit/platform/commons/util/LruCache.java | 56 - .../platform/commons/util/ModuleUtils.java | 100 - .../platform/commons/util/PackageUtils.java | 102 - .../util/PreconditionViolationException.java | 39 - .../platform/commons/util/Preconditions.java | 318 -- .../commons/util/ReflectionUtils.java | 1757 -------- .../platform/commons/util/RuntimeUtils.java | 74 - .../platform/commons/util/StringUtils.java | 259 -- .../commons/util/ToStringBuilder.java | 68 - .../commons/util/UnrecoverableExceptions.java | 56 - .../platform/commons/util/package-info.java | 11 - .../platform/commons/util/ModuleUtils.java | 218 - .../module-info.java | 54 - .../commons/test/ConcurrencyTestingUtils.java | 69 - ...nit-platform-console-standalone.gradle.kts | 93 - .../junit-platform-console/LICENSE-picocli.md | 194 - .../junit-platform-console.gradle.kts | 75 - .../platform/console/ConsoleLauncher.java | 131 - .../ConsoleLauncherExecutionResult.java | 83 - .../console/options/AvailableOptions.java | 444 -- .../options/ClasspathEntriesConverter.java | 27 - .../console/options/CommandLineOptions.java | 363 -- .../options/CommandLineOptionsParser.java | 29 - .../console/options/ConsoleUtils.java | 40 - .../platform/console/options/Details.java | 57 - .../PicocliCommandLineOptionsParser.java | 61 - .../console/options/SelectorConverter.java | 149 - .../junit/platform/console/options/Theme.java | 140 - .../console/options/package-info.java | 5 - .../junit/platform/console/package-info.java | 5 - .../junit/platform/console/tasks/Color.java | 83 - .../platform/console/tasks/ColorPalette.java | 128 - .../console/tasks/ConsoleTestExecutor.java | 150 - .../CustomContextClassLoaderExecutor.java | 51 - .../tasks/DiscoveryRequestCreator.java | 145 - .../console/tasks/FlatPrintingListener.java | 109 - .../junit/platform/console/tasks/Style.java | 40 - .../platform/console/tasks/TreeNode.java | 92 - .../platform/console/tasks/TreePrinter.java | 172 - .../console/tasks/TreePrintingListener.java | 79 - .../tasks/VerboseTreePrintingListener.java | 189 - .../platform/console/tasks/package-info.java | 5 - .../console/options/ConsoleUtils.java | 42 - .../console/ConsoleLauncherToolProvider.java | 37 - .../services/java.util.spi.ToolProvider | 1 - .../module-info.java | 25 - .../junit-platform-engine.gradle.kts | 19 - .../platform/engine/CompositeFilter.java | 90 - .../engine/ConfigurationParameters.java | 152 - .../platform/engine/DiscoveryFilter.java | 30 - .../platform/engine/DiscoverySelector.java | 28 - .../engine/EngineDiscoveryListener.java | 56 - .../engine/EngineDiscoveryRequest.java | 88 - .../engine/EngineExecutionListener.java | 140 - .../platform/engine/ExecutionRequest.java | 92 - .../org/junit/platform/engine/Filter.java | 115 - .../junit/platform/engine/FilterResult.java | 113 - .../engine/SelectorResolutionResult.java | 125 - .../junit/platform/engine/TestDescriptor.java | 309 -- .../org/junit/platform/engine/TestEngine.java | 195 - .../platform/engine/TestExecutionResult.java | 132 - .../org/junit/platform/engine/TestSource.java | 34 - .../org/junit/platform/engine/TestTag.java | 153 - .../org/junit/platform/engine/UniqueId.java | 363 -- .../junit/platform/engine/UniqueIdFormat.java | 161 - .../discovery/AbstractClassNameFilter.java | 50 - .../engine/discovery/ClassNameFilter.java | 77 - .../engine/discovery/ClassSelector.java | 111 - .../discovery/ClasspathResourceSelector.java | 104 - .../discovery/ClasspathRootSelector.java | 85 - .../engine/discovery/DirectorySelector.java | 110 - .../engine/discovery/DiscoverySelectors.java | 716 ---- .../discovery/ExcludeClassNameFilter.java | 62 - .../discovery/ExcludePackageNameFilter.java | 79 - .../engine/discovery/FilePosition.java | 183 - .../engine/discovery/FileSelector.java | 120 - .../discovery/IncludeClassNameFilter.java | 63 - .../discovery/IncludePackageNameFilter.java | 79 - .../engine/discovery/IterationSelector.java | 93 - .../engine/discovery/MethodSelector.java | 220 - .../engine/discovery/ModuleSelector.java | 76 - .../engine/discovery/NestedClassSelector.java | 127 - .../discovery/NestedMethodSelector.java | 188 - .../engine/discovery/PackageNameFilter.java | 103 - .../engine/discovery/PackageSelector.java | 76 - .../engine/discovery/UniqueIdSelector.java | 77 - .../engine/discovery/UriSelector.java | 80 - .../engine/discovery/package-info.java | 7 - .../junit/platform/engine/package-info.java | 5 - .../engine/reporting/ReportEntry.java | 109 - .../engine/reporting/package-info.java | 6 - .../PrefixedConfigurationParameters.java | 79 - .../engine/support/config/package-info.java | 6 - .../descriptor/AbstractTestDescriptor.java | 187 - .../support/descriptor/ClassSource.java | 223 - .../descriptor/ClasspathResourceSource.java | 174 - .../descriptor/CompositeTestSource.java | 93 - .../support/descriptor/DefaultUriSource.java | 61 - .../support/descriptor/DirectorySource.java | 99 - .../support/descriptor/EngineDescriptor.java | 53 - .../support/descriptor/FilePosition.java | 178 - .../engine/support/descriptor/FileSource.java | 130 - .../support/descriptor/FileSystemSource.java | 38 - .../support/descriptor/MethodSource.java | 254 -- .../support/descriptor/PackageSource.java | 93 - .../support/descriptor/ResourceUtils.java | 48 - .../engine/support/descriptor/UriSource.java | 78 - .../support/descriptor/package-info.java | 7 - .../ClassContainerSelectorResolver.java | 63 - .../EngineDiscoveryRequestResolution.java | 263 -- .../EngineDiscoveryRequestResolver.java | 293 -- .../support/discovery/SelectorResolver.java | 676 --- .../support/discovery/package-info.java | 9 - .../filter/ClasspathScanningSupport.java | 70 - .../engine/support/filter/package-info.java | 6 - .../support/hierarchical/CompositeLock.java | 83 - ...DefaultParallelExecutionConfiguration.java | 67 - ...arallelExecutionConfigurationStrategy.java | 177 - .../hierarchical/EngineExecutionContext.java | 26 - .../hierarchical/ExclusiveResource.java | 134 - ...inPoolHierarchicalTestExecutorService.java | 231 -- .../hierarchical/HierarchicalTestEngine.java | 115 - .../HierarchicalTestExecutor.java | 60 - .../HierarchicalTestExecutorService.java | 104 - .../support/hierarchical/LockManager.java | 88 - .../engine/support/hierarchical/Node.java | 362 -- .../hierarchical/NodeExecutionAdvisor.java | 51 - .../support/hierarchical/NodeTestTask.java | 265 -- .../hierarchical/NodeTestTaskContext.java | 55 - .../support/hierarchical/NodeTreeWalker.java | 105 - .../support/hierarchical/NodeUtils.java | 33 - .../engine/support/hierarchical/NopLock.java | 35 - .../OpenTest4JAwareThrowableCollector.java | 32 - .../ParallelExecutionConfiguration.java | 76 - ...arallelExecutionConfigurationStrategy.java | 33 - .../support/hierarchical/ResourceLock.java | 46 - ...ThreadHierarchicalTestExecutorService.java | 49 - .../support/hierarchical/SingleLock.java | 61 - .../hierarchical/SingleTestExecutor.java | 82 - .../hierarchical/ThrowableCollector.java | 217 - .../support/hierarchical/package-info.java | 8 - .../module-info.java | 33 - .../DemoEngineExecutionContext.java | 26 - .../DemoHierarchicalContainerDescriptor.java | 56 - .../DemoHierarchicalEngineDescriptor.java | 53 - .../DemoHierarchicalTestDescriptor.java | 67 - .../DemoHierarchicalTestEngine.java | 98 - .../platform/fakes/TestDescriptorStub.java | 30 - .../junit/platform/fakes/TestEngineSpy.java | 47 - .../junit/platform/fakes/TestEngineStub.java | 48 - .../junit-platform-jfr.gradle.kts | 31 - .../jfr/FlightRecordingDiscoveryListener.java | 103 - .../jfr/FlightRecordingExecutionListener.java | 162 - .../java/org/junit/platform/jfr/UniqueId.java | 28 - .../org/junit/platform/jfr/package-info.java | 5 - ...latform.launcher.LauncherDiscoveryListener | 1 - ...it.platform.launcher.TestExecutionListener | 1 - .../org.junit.platform.jfr/module-info.java | 32 - .../junit-platform-launcher.gradle.kts | 28 - .../launcher/EngineDiscoveryResult.java | 110 - .../junit/platform/launcher/EngineFilter.java | 172 - .../org/junit/platform/launcher/Launcher.java | 131 - .../platform/launcher/LauncherConstants.java | 142 - .../launcher/LauncherDiscoveryListener.java | 91 - .../launcher/LauncherDiscoveryRequest.java | 100 - .../platform/launcher/LauncherSession.java | 50 - .../launcher/LauncherSessionListener.java | 67 - .../launcher/PostDiscoveryFilter.java | 36 - .../junit/platform/launcher/TagFilter.java | 185 - .../launcher/TestExecutionListener.java | 175 - .../platform/launcher/TestIdentifier.java | 347 -- .../org/junit/platform/launcher/TestPlan.java | 294 -- .../CompositeEngineExecutionListener.java | 81 - .../core/CompositeTestExecutionListener.java | 112 - .../core/DefaultDiscoveryRequest.java | 97 - .../launcher/core/DefaultLauncher.java | 117 - .../launcher/core/DefaultLauncherConfig.java | 112 - .../launcher/core/DefaultLauncherSession.java | 127 - .../DelegatingEngineExecutionListener.java | 54 - .../core/EngineDiscoveryErrorDescriptor.java | 51 - .../core/EngineDiscoveryOrchestrator.java | 234 -- .../core/EngineDiscoveryResultValidator.java | 66 - .../core/EngineExecutionOrchestrator.java | 156 - .../launcher/core/EngineFilterer.java | 94 - .../launcher/core/EngineIdValidator.java | 80 - .../core/ExecutionListenerAdapter.java | 68 - .../launcher/core/InternalLauncher.java | 25 - .../launcher/core/InternalTestPlan.java | 121 - .../launcher/core/LauncherConfig.java | 365 -- .../core/LauncherConfigurationParameters.java | 290 -- .../core/LauncherDiscoveryRequestBuilder.java | 332 -- .../core/LauncherDiscoveryResult.java | 78 - .../launcher/core/LauncherFactory.java | 195 - .../launcher/core/ListenerRegistry.java | 95 - ...utcomeDelayingEngineExecutionListener.java | 90 - .../launcher/core/ServiceLoaderRegistry.java | 36 - .../core/ServiceLoaderTestEngineRegistry.java | 41 - .../core/SessionPerRequestLauncher.java | 77 - ...reamInterceptingTestExecutionListener.java | 99 - .../launcher/core/StreamInterceptor.java | 118 - .../launcher/core/TestEngineFormatter.java | 49 - .../platform/launcher/core/package-info.java | 8 - .../listeners/LegacyReportingUtils.java | 86 - .../launcher/listeners/LoggingListener.java | 130 - .../MutableTestExecutionSummary.java | 308 -- .../launcher/listeners/OutputDir.java | 110 - .../listeners/SummaryGeneratingListener.java | 138 - .../listeners/TestExecutionSummary.java | 184 - .../listeners/UniqueIdTrackingListener.java | 190 - ...ortOnFailureLauncherDiscoveryListener.java | 65 - .../CompositeLauncherDiscoveryListener.java | 60 - .../discovery/LauncherDiscoveryListeners.java | 121 - .../LoggingLauncherDiscoveryListener.java | 107 - .../listeners/discovery/package-info.java | 9 - .../launcher/listeners/package-info.java | 7 - .../CompositeLauncherSessionListener.java | 41 - .../session/LauncherSessionListeners.java | 45 - .../listeners/session/package-info.java | 9 - .../junit/platform/launcher/package-info.java | 7 - .../launcher/tagexpression/DequeStack.java | 48 - .../launcher/tagexpression/Operator.java | 138 - .../launcher/tagexpression/Operators.java | 41 - .../launcher/tagexpression/ParseResult.java | 61 - .../launcher/tagexpression/ParseResults.java | 42 - .../launcher/tagexpression/ParseStatus.java | 84 - .../launcher/tagexpression/Parser.java | 34 - .../launcher/tagexpression/ShuntingYard.java | 164 - .../launcher/tagexpression/Stack.java | 28 - .../launcher/tagexpression/TagExpression.java | 50 - .../tagexpression/TagExpressions.java | 107 - .../launcher/tagexpression/Token.java | 51 - .../launcher/tagexpression/TokenWith.java | 26 - .../launcher/tagexpression/Tokenizer.java | 41 - .../launcher/tagexpression/package-info.java | 5 - ...it.platform.launcher.TestExecutionListener | 1 - .../module-info.java | 41 - ...onfigurationParametersFactoryForTests.java | 28 - ...LauncherFactoryForTestingPurposesOnly.java | 36 - .../LICENSE-open-test-reporting.md | 194 - .../junit-platform-reporting.gradle.kts | 28 - .../legacy/LegacyReportingUtils.java | 56 - .../reporting/legacy/package-info.java | 5 - .../LegacyXmlReportGeneratingListener.java | 133 - .../reporting/legacy/xml/XmlReportData.java | 143 - .../reporting/legacy/xml/XmlReportWriter.java | 417 -- .../reporting/legacy/xml/package-info.java | 7 - .../reporting/open/xml/JUnitFactory.java | 35 - .../open/xml/LegacyReportingName.java | 26 - .../xml/OpenTestReportGeneratingListener.java | 266 -- .../platform/reporting/open/xml/Type.java | 27 - .../platform/reporting/open/xml/UniqueId.java | 26 - .../reporting/open/xml/package-info.java | 5 - .../platform/reporting/package-info.java | 5 - ...it.platform.launcher.TestExecutionListener | 1 - .../platform/reporting/open/xml/catalog.xml | 4 - .../platform/reporting/open/xml/junit.xsd | 30 - .../module-info.java | 30 - .../junit-platform-runner.gradle.kts | 36 - .../junit/platform/runner/JUnitPlatform.java | 201 - .../runner/JUnitPlatformRunnerListener.java | 96 - .../runner/JUnitPlatformTestTree.java | 167 - .../junit/platform/runner/package-info.java | 6 - .../module-info.java | 25 - .../junit-platform-suite-api.gradle.kts | 15 - .../suite/api/ConfigurationParameter.java | 55 - .../suite/api/ConfigurationParameters.java | 46 - .../DisableParentConfigurationParameters.java | 44 - .../suite/api/ExcludeClassNamePatterns.java | 50 - .../platform/suite/api/ExcludeEngines.java | 46 - .../platform/suite/api/ExcludePackages.java | 45 - .../junit/platform/suite/api/ExcludeTags.java | 77 - .../suite/api/IncludeClassNamePatterns.java | 58 - .../platform/suite/api/IncludeEngines.java | 46 - .../platform/suite/api/IncludePackages.java | 45 - .../junit/platform/suite/api/IncludeTags.java | 77 - .../platform/suite/api/SelectClasses.java | 44 - .../suite/api/SelectClasspathResource.java | 59 - .../suite/api/SelectClasspathResources.java | 47 - .../platform/suite/api/SelectDirectories.java | 44 - .../junit/platform/suite/api/SelectFile.java | 58 - .../junit/platform/suite/api/SelectFiles.java | 46 - .../platform/suite/api/SelectModules.java | 45 - .../platform/suite/api/SelectPackages.java | 45 - .../junit/platform/suite/api/SelectUris.java | 44 - .../org/junit/platform/suite/api/Suite.java | 85 - .../platform/suite/api/SuiteDisplayName.java | 57 - .../platform/suite/api/UseTechnicalNames.java | 56 - .../platform/suite/api/package-info.java | 13 - .../module-info.java | 21 - .../junit-platform-suite-commons.gradle.kts | 18 - .../commons/AdditionalDiscoverySelectors.java | 116 - .../SuiteLauncherDiscoveryRequestBuilder.java | 234 -- .../platform/suite/commons/package-info.java | 11 - .../module-info.java | 26 - .../junit-platform-suite-engine.gradle.kts | 18 - .../suite/engine/ClassSelectorResolver.java | 128 - .../engine/DiscoverySelectorResolver.java | 46 - .../engine/IsPotentialTestContainer.java | 42 - .../platform/suite/engine/IsSuiteClass.java | 34 - .../engine/NoTestsDiscoveredException.java | 23 - .../suite/engine/SuiteEngineDescriptor.java | 32 - .../platform/suite/engine/SuiteLauncher.java | 67 - .../suite/engine/SuiteTestDescriptor.java | 147 - .../suite/engine/SuiteTestEngine.java | 78 - .../platform/suite/engine/package-info.java | 5 - .../org.junit.platform.engine.TestEngine | 1 - .../module-info.java | 28 - .../junit-platform-suite.gradle.kts | 14 - .../org.junit.platform.suite/module-info.java | 19 - .../junit-platform-testkit/LICENSE.md | 98 - .../junit-platform-testkit.gradle.kts | 17 - .../platform/testkit/engine/Assertions.java | 88 - .../engine/EngineExecutionResults.java | 106 - .../testkit/engine/EngineTestKit.java | 446 -- .../junit/platform/testkit/engine/Event.java | 260 -- .../testkit/engine/EventConditions.java | 473 --- .../testkit/engine/EventStatistics.java | 142 - .../platform/testkit/engine/EventType.java | 65 - .../junit/platform/testkit/engine/Events.java | 473 --- .../platform/testkit/engine/Execution.java | 156 - .../testkit/engine/ExecutionRecorder.java | 91 - .../platform/testkit/engine/Executions.java | 302 -- .../testkit/engine/TerminationInfo.java | 147 - .../engine/TestExecutionResultConditions.java | 145 - .../platform/testkit/engine/package-info.java | 6 - .../junit/platform/testkit/package-info.java | 5 - .../module-info.java | 29 - .../junit-vintage-engine.gradle.kts | 85 - .../vintage/engine/JUnit4VersionCheck.java | 77 - .../vintage/engine/VintageTestEngine.java | 85 - .../engine/descriptor/DescriptionUtils.java | 37 - .../vintage/engine/descriptor/OrFilter.java | 42 - .../engine/descriptor/RunnerDecorator.java | 23 - .../engine/descriptor/RunnerRequest.java | 31 - .../descriptor/RunnerTestDescriptor.java | 185 - .../engine/descriptor/TestSourceProvider.java | 91 - .../descriptor/VintageEngineDescriptor.java | 36 - .../descriptor/VintageTestDescriptor.java | 141 - .../engine/descriptor/package-info.java | 5 - .../discovery/ClassSelectorResolver.java | 82 - ...fensiveAllDefaultPossibilitiesBuilder.java | 151 - .../FilterableIgnoringRunnerDecorator.java | 36 - .../discovery/IgnoringRunnerDecorator.java | 49 - .../discovery/IsPotentialJUnit4TestClass.java | 44 - .../IsPotentialJUnit4TestMethod.java | 30 - .../discovery/MethodSelectorResolver.java | 121 - .../RunnerTestDescriptorPostProcessor.java | 76 - .../engine/discovery/UniqueIdFilter.java | 87 - .../engine/discovery/VintageDiscoverer.java | 51 - .../engine/discovery/package-info.java | 5 - .../vintage/engine/execution/EventType.java | 18 - .../engine/execution/RunListenerAdapter.java | 251 -- .../engine/execution/RunnerExecutor.java | 58 - .../vintage/engine/execution/TestRun.java | 276 -- .../engine/execution/package-info.java | 5 - .../junit/vintage/engine/package-info.java | 5 - .../engine/support/UniqueIdReader.java | 56 - .../engine/support/UniqueIdStringifier.java | 62 - .../vintage/engine/support/package-info.java | 6 - .../org.junit.platform.engine.TestEngine | 1 - .../org.junit.vintage.engine/module-info.java | 26 - .../engine/JUnit4ParameterizedTests.java | 81 - .../engine/JUnit4VersionCheckTests.java | 87 - .../VintageLauncherIntegrationTests.java | 288 -- .../engine/VintageTestEngineBasicTests.java | 42 - .../VintageTestEngineDiscoveryTests.java | 803 ---- .../VintageTestEngineExecutionTests.java | 923 ----- .../engine/VintageTestEngineTestSuite.java | 38 - .../engine/VintageUniqueIdBuilder.java | 69 - .../descriptor/DescriptionUtilsTests.java | 62 - .../engine/descriptor/OrFilterTests.java | 78 - .../descriptor/TestSourceProviderTests.java | 38 - .../VintageTestDescriptorTests.java | 51 - .../IsPotentialJUnit4TestClassTests.java | 61 - ...unnerTestDescriptorPostProcessorTests.java | 80 - .../discovery/VintageDiscovererTests.java | 144 - .../engine/execution/TestRunTests.java | 60 - .../engine/support/UniqueIdReaderTests.java | 57 - .../support/UniqueIdStringifierTests.java | 101 - .../src/test/resources/log4j2-test.xml | 16 - ...ithUnrolledAndRegularFeatureMethods.groovy | 20 - ...inOldJavaClassWithoutAnyTestsTestCase.java | 22 - .../junit3/AbstractJUnit3TestCase.java | 26 - .../JUnit3ParallelSuiteWithSubsuites.java | 39 - ...ingleTestCaseWithSingleTestWhichFails.java | 27 - .../junit3/JUnit3SuiteWithSubsuites.java | 38 - ...Unit3TestCaseWithSingleTestWhichFails.java | 26 - .../junit4/AbstractJUnit4TestCase.java | 21 - ...unit4TestCaseWithConstructorParameter.java | 24 - .../engine/samples/junit4/Categories.java | 32 - .../junit4/CompletelyDynamicTestCase.java | 22 - .../junit4/ConcreteJUnit4TestCase.java | 14 - .../samples/junit4/ConfigurableRunner.java | 69 - .../engine/samples/junit4/DynamicRunner.java | 36 - .../samples/junit4/EmptyIgnoredTestCase.java | 17 - .../junit4/EnclosedJUnit4TestCase.java | 42 - ...thParameterizedChildrenJUnit4TestCase.java | 59 - .../junit4/ExceptionThrowingRunner.java | 29 - .../samples/junit4/IgnoredJUnit4TestCase.java | 36 - ...JUnit4TestCaseWithNotFilterableRunner.java | 20 - .../junit4/IgnoredParameterizedTestCase.java | 42 - .../junit4/JUnit4ParameterizedTestCase.java | 56 - ...SuiteOfSuiteWithFilterableChildRunner.java | 23 - ...SuiteOfSuiteWithIgnoredJUnit4TestCase.java | 23 - ...aseWithAssumptionFailureInBeforeClass.java | 23 - ...hJUnit4TestCaseWithErrorInBeforeClass.java | 23 - ...Unit4SuiteWithExceptionThrowingRunner.java | 22 - .../JUnit4SuiteWithIgnoredJUnit4TestCase.java | 23 - ...uiteWithJUnit3SuiteWithSingleTestCase.java | 24 - ...aseWithAssumptionFailureInBeforeClass.java | 23 - ...hJUnit4TestCaseWithErrorInBeforeClass.java | 23 - ...escriptionThatIsNotReportedAsFinished.java | 21 - ...nerWithCustomUniqueIdsAndDisplayNames.java | 23 - ...4TestCaseWithSingleTestWhichIsIgnored.java | 23 - .../junit4/JUnit4SuiteWithTwoTestCases.java | 23 - ...aseWithAssumptionFailureInBeforeClass.java | 34 - ...seWithDistinguishableOverloadedMethod.java | 35 - ...ErrorCollectorStoringMultipleFailures.java | 35 - .../JUnit4TestCaseWithErrorInAfterClass.java | 41 - .../JUnit4TestCaseWithErrorInBeforeClass.java | 33 - ...t4TestCaseWithExceptionThrowingRunner.java | 22 - ...escriptionThatIsNotReportedAsFinished.java | 24 - ...WithIndistinguishableOverloadedMethod.java | 39 - ...JUnit4TestCaseWithNotFilterableRunner.java | 30 - ...nerWithCustomUniqueIdsAndDisplayNames.java | 39 - ...ithDuplicateChangingChildDescriptions.java | 55 - .../junit4/MalformedJUnit4TestCase.java | 28 - .../samples/junit4/NotFilterableRunner.java | 22 - .../samples/junit4/ParameterizedTestCase.java | 42 - .../junit4/ParameterizedTimingTestCase.java | 68 - ...eterizedWithAfterParamFailureTestCase.java | 49 - ...terizedWithBeforeParamFailureTestCase.java | 49 - ...lainJUnit4TestCaseWithFiveTestMethods.java | 64 - ...ainJUnit4TestCaseWithLifecycleMethods.java | 68 - ...CaseWithSingleInheritedTestWhichFails.java | 17 - ...Unit4TestCaseWithSingleTestWhichFails.java | 27 - ...4TestCaseWithSingleTestWhichIsIgnored.java | 28 - ...PlainJUnit4TestCaseWithTwoTestMethods.java | 38 - .../junit4/RunnerThatOnlyReportsFailures.java | 34 - ...nerWithCustomUniqueIdsAndDisplayNames.java | 81 - .../junit4/SingleFailingTheoryTestCase.java | 29 - .../TestCaseRunWithJUnitPlatformRunner.java | 24 - .../platform-tests/platform-tests.gradle.kts | 96 - .../jupiter/jmh/AssertionBenchmarks.java | 59 - .../src/test/java/DefaultPackageTestCase.java | 28 - .../junit/jupiter/api/condition/OSTests.java | 79 - .../junit/jupiter/extensions/Heavyweight.java | 93 - .../jupiter/extensions/HeavyweightTests.java | 110 - .../AbstractEqualsAndHashCodeTests.java | 42 - .../platform/JUnitPlatformTestSuite.java | 38 - .../org/junit/platform/TestEngineTests.java | 60 - .../annotation/TestableAnnotationTests.java | 48 - .../platform/commons/function/TryTests.java | 114 - .../support/AnnotationSupportTests.java | 325 -- .../commons/support/ClassSupportTests.java | 43 - .../commons/support/ModifierSupportTests.java | 243 -- .../support/PreconditionAssertions.java | 34 - .../support/ReflectionSupportTests.java | 303 -- .../commons/util/AnnotationUtilsTests.java | 967 ----- .../commons/util/ClassLoaderUtilsTests.java | 70 - .../ClassNamePatternFilterUtilsTests.java | 137 - .../commons/util/ClassUtilsTests.java | 44 - .../commons/util/ClasspathScannerTests.java | 411 -- .../commons/util/CollectionUtilsTests.java | 280 -- .../commons/util/ExceptionUtilsTests.java | 65 - .../commons/util/FunctionUtilsTests.java | 49 - .../platform/commons/util/LruCacheTests.java | 35 - .../commons/util/ModuleUtilsTests.java | 37 - .../commons/util/PackageUtilsTests.java | 98 - .../commons/util/PreconditionsTests.java | 268 -- .../commons/util/ReflectionUtilsTests.java | 1906 --------- ...nUtilsWithGenericTypeHierarchiesTests.java | 182 - .../commons/util/RuntimeUtilsTests.java | 33 - .../commons/util/SerializationUtils.java | 34 - .../commons/util/StringUtilsTests.java | 192 - .../commons/util/ToStringBuilderTests.java | 168 - .../classes/AExecutionConditionClass.java | 26 - .../classes/ATestExecutionListenerClass.java | 20 - .../commons/util/classes/AVanillaEmpty.java | 18 - .../classes/BExecutionConditionClass.java | 26 - .../classes/BTestExecutionListenerClass.java | 20 - .../commons/util/classes/BVanillaEmpty.java | 18 - .../platform/console/ConsoleDetailsTests.java | 251 -- .../ConsoleLauncherExecutionResultTests.java | 87 - .../ConsoleLauncherIntegrationTests.java | 68 - .../console/ConsoleLauncherTests.java | 110 - .../console/ConsoleLauncherWrapper.java | 69 - .../console/ConsoleLauncherWrapperResult.java | 156 - .../console/options/ConsoleUtilsTests.java | 28 - .../PicocliCommandLineOptionsParserTests.java | 655 --- .../platform/console/options/ThemeTests.java | 36 - .../subpackage/ContainerForInnerTest.java | 30 - .../subpackage/ContainerForInnerTests.java | 30 - .../ContainerForStaticNestedTest.java | 28 - .../ContainerForStaticNestedTests.java | 28 - .../console/subpackage/SecondTest.java | 25 - .../platform/console/subpackage/Test.java | 23 - .../platform/console/subpackage/Test1.java | 25 - .../platform/console/subpackage/Tests.java | 25 - .../console/subpackage/ThirdTests.java | 25 - .../console/tasks/ColorPaletteTests.java | 207 - .../tasks/ConsoleTestExecutorTests.java | 183 - ...CustomContextClassLoaderExecutorTests.java | 76 - .../tasks/DiscoveryRequestCreatorTests.java | 339 -- .../tasks/FlatPrintingListenerTests.java | 175 - .../platform/console/tasks/TreeNodeTests.java | 97 - .../console/tasks/TreePrinterTests.java | 123 - .../tasks/VerboseTreeListenerTests.java | 96 - .../engine/FilterCompositionTests.java | 74 - .../platform/engine/TestDescriptorTests.java | 38 - .../junit/platform/engine/TestTagTests.java | 105 - .../platform/engine/UniqueIdFormatTests.java | 141 - .../junit/platform/engine/UniqueIdTests.java | 299 -- .../discovery/ClassNameFilterTests.java | 151 - .../engine/discovery/ClassSelectorTests.java | 46 - .../ClasspathResourceSelectorTests.java | 42 - .../discovery/ClasspathRootSelectorTests.java | 35 - .../discovery/DirectorySelectorTests.java | 33 - .../discovery/DiscoverySelectorsTests.java | 852 ---- .../engine/discovery/FilePositionTests.java | 118 - .../engine/discovery/FileSelectorTests.java | 42 - .../engine/discovery/MethodSelectorTests.java | 51 - .../engine/discovery/ModuleSelectorTests.java | 33 - .../discovery/NestedClassSelectorTests.java | 62 - .../discovery/NestedMethodSelectorTests.java | 75 - .../discovery/PackageNameFilterTests.java | 127 - .../discovery/PackageSelectorTests.java | 33 - .../discovery/UniqueIdSelectorTests.java | 35 - .../engine/discovery/UriSelectorTests.java | 35 - .../PrefixedConfigurationParametersTests.java | 90 - .../AbstractTestDescriptorTests.java | 162 - .../descriptor/AbstractTestSourceTests.java | 85 - .../support/descriptor/ClassSourceTests.java | 181 - .../ClasspathResourceSourceTests.java | 120 - .../descriptor/CompositeTestSourceTests.java | 75 - .../descriptor/DefaultUriSourceTests.java | 59 - .../descriptor/DemoClassTestDescriptor.java | 71 - .../descriptor/DemoMethodTestDescriptor.java | 89 - .../support/descriptor/FilePositionTests.java | 122 - .../descriptor/FileSystemSourceTests.java | 95 - .../support/descriptor/MethodSourceTests.java | 293 -- .../descriptor/PackageSourceTests.java | 72 - .../hierarchical/CompositeLockTests.java | 93 - ...elExecutionConfigurationStrategyTests.java | 201 - ...lHierarchicalTestExecutorServiceTests.java | 44 - .../HierarchicalTestExecutorTests.java | 753 ---- .../hierarchical/LockManagerTests.java | 124 - .../support/hierarchical/MemoryLeakTests.java | 78 - .../NodeTreeWalkerIntegrationTests.java | 257 -- .../ParallelExecutionIntegrationTests.java | 845 ---- .../hierarchical/ResourceLockSupport.java | 27 - .../SameThreadExecutionIntegrationTests.java | 82 - .../support/hierarchical/SingleLockTests.java | 45 - .../hierarchical/SingleTestExecutorTests.java | 62 - .../hierarchical/ThrowableCollectorTests.java | 70 - ...dingDiscoveryListenerIntegrationTests.java | 51 - ...dingExecutionListenerIntegrationTests.java | 77 - .../launcher/DiscoveryFilterStub.java | 32 - .../junit/platform/launcher/FilterStub.java | 46 - .../launcher/PostDiscoveryFilterStub.java | 32 - .../platform/launcher/TagFilterTests.java | 226 - .../launcher/TagIntegrationTests.java | 121 - .../launcher/TestIdentifierTests.java | 113 - .../TestLauncherDiscoveryListener.java | 30 - .../launcher/TestLauncherSessionListener.java | 30 - .../platform/launcher/TestPlanTests.java | 80 - .../launcher/TestPostDiscoveryTagFilter.java | 23 - ...CompositeEngineExecutionListenerTests.java | 161 - .../CompositeTestExecutionListenerTests.java | 246 -- .../DefaultLauncherEngineFilterTests.java | 194 - .../launcher/core/DefaultLauncherTests.java | 623 --- .../EngineDiscoveryResultValidatorTests.java | 69 - .../core/ExecutionListenerAdapterTests.java | 75 - .../launcher/core/LauncherConfigTests.java | 141 - .../LauncherConfigurationParametersTests.java | 236 -- .../LauncherDiscoveryRequestBuilderTests.java | 382 -- .../launcher/core/LauncherFactoryTests.java | 255 -- .../launcher/core/LauncherSessionTests.java | 109 - .../launcher/core/ListenerRegistryTests.java | 48 - ...TestExecutionListenerIntegrationTests.java | 151 - .../launcher/core/StreamInterceptorTests.java | 103 - .../AnotherUnusedTestExecutionListener.java | 17 - .../listeners/NoopTestExecutionListener.java | 20 - .../launcher/listeners/OutputDirTests.java | 87 - .../listeners/SummaryGenerationTests.java | 277 -- ...queIdTrackingListenerIntegrationTests.java | 343 -- .../UnusedTestExecutionListener.java | 17 - ...FailureLauncherDiscoveryListenerTests.java | 96 - ...bstractLauncherDiscoveryListenerTests.java | 47 - ...LoggingLauncherDiscoveryListenerTests.java | 128 - .../tagexpression/ParserErrorTests.java | 107 - .../launcher/tagexpression/ParserTests.java | 82 - .../tagexpression/TagExpressionsTests.java | 109 - .../launcher/tagexpression/TokenTests.java | 50 - .../tagexpression/TokenizerTests.java | 96 - .../legacy/LegacyReportingUtilsTests.java | 93 - .../legacy/xml/IncrementingClock.java | 52 - ...egacyXmlReportGeneratingListenerTests.java | 444 -- .../legacy/xml/XmlReportAssertions.java | 62 - .../legacy/xml/XmlReportDataTests.java | 79 - .../legacy/xml/XmlReportWriterTests.java | 280 -- ...OpenTestReportGeneratingListenerTests.java | 136 - .../runner/JUnitPlatformRunnerTests.java | 901 ---- ...eLauncherDiscoveryRequestBuilderTests.java | 458 --- .../suite/engine/SuiteEngineTests.java | 395 -- .../engine/SuiteTestDescriptorTests.java | 120 - .../testcases/DynamicTestsTestCase.java | 34 - .../testcases/EmptyDynamicTestsTestCase.java | 28 - .../engine/testcases/EmptyTestTestCase.java | 18 - .../engine/testcases/JUnit4TestsTestCase.java | 20 - .../testcases/MultipleTestsTestCase.java | 27 - .../engine/testcases/SingleTestTestCase.java | 23 - .../engine/testcases/TaggedTestTestCase.java | 22 - .../engine/testsuites/AbstractSuite.java | 23 - .../suite/engine/testsuites/CyclicSuite.java | 25 - .../suite/engine/testsuites/DynamicSuite.java | 23 - .../engine/testsuites/EmptyCyclicSuite.java | 24 - .../testsuites/EmptyDynamicTestSuite.java | 23 - ...DynamicTestWithFailIfNoTestFalseSuite.java | 23 - .../engine/testsuites/EmptyTestCaseSuite.java | 23 - ...ptyTestCaseWithFailIfNoTestFalseSuite.java | 23 - .../engine/testsuites/MultiEngineSuite.java | 22 - .../engine/testsuites/MultipleSuite.java | 23 - .../suite/engine/testsuites/NestedSuite.java | 33 - .../engine/testsuites/SelectClassesSuite.java | 23 - .../testsuites/SuiteDisplayNameSuite.java | 25 - .../suite/engine/testsuites/SuiteSuite.java | 24 - .../testsuites/ThreePartCyclicSuite.java | 43 - .../testkit/engine/EngineTestKitTests.java | 82 - .../platform/testkit/engine/EventsTests.java | 287 -- .../engine/ExecutionsIntegrationTests.java | 120 - .../NestedContainerEventConditionTests.java | 148 - ...Basic-changeDisplayName-flat-ascii.out.txt | 22 - ...sic-changeDisplayName-flat-unicode.out.txt | 22 - ...Basic-changeDisplayName-none-ascii.out.txt | 1 - ...sic-changeDisplayName-none-unicode.out.txt | 1 - ...ic-changeDisplayName-summary-ascii.out.txt | 14 - ...-changeDisplayName-summary-unicode.out.txt | 14 - ...Basic-changeDisplayName-tree-ascii.out.txt | 18 - ...sic-changeDisplayName-tree-unicode.out.txt | 18 - ...ic-changeDisplayName-verbose-ascii.out.txt | 28 - ...-changeDisplayName-verbose-unicode.out.txt | 28 - .../basic/Basic-empty-flat-ascii.out.txt | 22 - .../basic/Basic-empty-flat-unicode.out.txt | 22 - .../basic/Basic-empty-none-ascii.out.txt | 1 - .../basic/Basic-empty-none-unicode.out.txt | 1 - .../basic/Basic-empty-summary-ascii.out.txt | 14 - .../basic/Basic-empty-summary-unicode.out.txt | 14 - .../basic/Basic-empty-tree-ascii.out.txt | 18 - .../basic/Basic-empty-tree-unicode.out.txt | 18 - .../basic/Basic-empty-verbose-ascii.out.txt | 28 - .../basic/Basic-empty-verbose-unicode.out.txt | 28 - ...ailWithMultiLineMessage-flat-ascii.out.txt | 27 - ...lWithMultiLineMessage-flat-unicode.out.txt | 27 - ...ailWithMultiLineMessage-none-ascii.out.txt | 23 - ...lWithMultiLineMessage-none-unicode.out.txt | 23 - ...WithMultiLineMessage-summary-ascii.out.txt | 23 - ...thMultiLineMessage-summary-unicode.out.txt | 23 - ...ailWithMultiLineMessage-tree-ascii.out.txt | 30 - ...lWithMultiLineMessage-tree-unicode.out.txt | 30 - ...WithMultiLineMessage-verbose-ascii.out.txt | 33 - ...thMultiLineMessage-verbose-unicode.out.txt | 33 - ...ilWithSingleLineMessage-flat-ascii.out.txt | 24 - ...WithSingleLineMessage-flat-unicode.out.txt | 24 - ...ilWithSingleLineMessage-none-ascii.out.txt | 20 - ...WithSingleLineMessage-none-unicode.out.txt | 20 - ...ithSingleLineMessage-summary-ascii.out.txt | 20 - ...hSingleLineMessage-summary-unicode.out.txt | 20 - ...ilWithSingleLineMessage-tree-ascii.out.txt | 24 - ...WithSingleLineMessage-tree-unicode.out.txt | 24 - ...ithSingleLineMessage-verbose-ascii.out.txt | 30 - ...hSingleLineMessage-verbose-unicode.out.txt | 30 - ...ntriesWithMultiMappings-flat-ascii.out.txt | 28 - ...riesWithMultiMappings-flat-unicode.out.txt | 28 - ...ntriesWithMultiMappings-none-ascii.out.txt | 1 - ...riesWithMultiMappings-none-unicode.out.txt | 1 - ...iesWithMultiMappings-summary-ascii.out.txt | 14 - ...sWithMultiMappings-summary-unicode.out.txt | 14 - ...ntriesWithMultiMappings-tree-ascii.out.txt | 26 - ...riesWithMultiMappings-tree-unicode.out.txt | 26 - ...iesWithMultiMappings-verbose-ascii.out.txt | 31 - ...sWithMultiMappings-verbose-unicode.out.txt | 31 - ...ntriesWithSingleMapping-flat-ascii.out.txt | 26 - ...riesWithSingleMapping-flat-unicode.out.txt | 26 - ...ntriesWithSingleMapping-none-ascii.out.txt | 1 - ...riesWithSingleMapping-none-unicode.out.txt | 1 - ...iesWithSingleMapping-summary-ascii.out.txt | 14 - ...sWithSingleMapping-summary-unicode.out.txt | 14 - ...ntriesWithSingleMapping-tree-ascii.out.txt | 20 - ...riesWithSingleMapping-tree-unicode.out.txt | 20 - ...iesWithSingleMapping-verbose-ascii.out.txt | 30 - ...sWithSingleMapping-verbose-unicode.out.txt | 30 - ...-reportMultipleMessages-flat-ascii.out.txt | 26 - ...eportMultipleMessages-flat-unicode.out.txt | 26 - ...-reportMultipleMessages-none-ascii.out.txt | 1 - ...eportMultipleMessages-none-unicode.out.txt | 1 - ...portMultipleMessages-summary-ascii.out.txt | 14 - ...rtMultipleMessages-summary-unicode.out.txt | 14 - ...-reportMultipleMessages-tree-ascii.out.txt | 20 - ...eportMultipleMessages-tree-unicode.out.txt | 20 - ...portMultipleMessages-verbose-ascii.out.txt | 30 - ...rtMultipleMessages-verbose-unicode.out.txt | 30 - ...eEntryWithSingleMapping-flat-ascii.out.txt | 24 - ...ntryWithSingleMapping-flat-unicode.out.txt | 24 - ...eEntryWithSingleMapping-none-ascii.out.txt | 1 - ...ntryWithSingleMapping-none-unicode.out.txt | 1 - ...tryWithSingleMapping-summary-ascii.out.txt | 14 - ...yWithSingleMapping-summary-unicode.out.txt | 14 - ...eEntryWithSingleMapping-tree-ascii.out.txt | 19 - ...ntryWithSingleMapping-tree-unicode.out.txt | 19 - ...tryWithSingleMapping-verbose-ascii.out.txt | 29 - ...yWithSingleMapping-verbose-unicode.out.txt | 29 - ...ort-reportSingleMessage-flat-ascii.out.txt | 24 - ...t-reportSingleMessage-flat-unicode.out.txt | 24 - ...ort-reportSingleMessage-none-ascii.out.txt | 1 - ...t-reportSingleMessage-none-unicode.out.txt | 1 - ...-reportSingleMessage-summary-ascii.out.txt | 14 - ...eportSingleMessage-summary-unicode.out.txt | 14 - ...ort-reportSingleMessage-tree-ascii.out.txt | 19 - ...t-reportSingleMessage-tree-unicode.out.txt | 19 - ...-reportSingleMessage-verbose-ascii.out.txt | 29 - ...eportSingleMessage-verbose-unicode.out.txt | 29 - ...kipWithMultiLineMessage-flat-ascii.out.txt | 25 - ...pWithMultiLineMessage-flat-unicode.out.txt | 25 - ...kipWithMultiLineMessage-none-ascii.out.txt | 1 - ...pWithMultiLineMessage-none-unicode.out.txt | 1 - ...WithMultiLineMessage-summary-ascii.out.txt | 14 - ...thMultiLineMessage-summary-unicode.out.txt | 14 - ...kipWithMultiLineMessage-tree-ascii.out.txt | 21 - ...pWithMultiLineMessage-tree-unicode.out.txt | 21 - ...WithMultiLineMessage-verbose-ascii.out.txt | 31 - ...thMultiLineMessage-verbose-unicode.out.txt | 31 - ...kipWithSingleLineReason-flat-ascii.out.txt | 22 - ...pWithSingleLineReason-flat-unicode.out.txt | 22 - ...kipWithSingleLineReason-none-ascii.out.txt | 1 - ...pWithSingleLineReason-none-unicode.out.txt | 1 - ...WithSingleLineReason-summary-ascii.out.txt | 14 - ...thSingleLineReason-summary-unicode.out.txt | 14 - ...kipWithSingleLineReason-tree-ascii.out.txt | 18 - ...pWithSingleLineReason-tree-unicode.out.txt | 18 - ...WithSingleLineReason-verbose-ascii.out.txt | 28 - ...thSingleLineReason-verbose-unicode.out.txt | 28 - .../src/test/resources/do_not_delete_me.txt | 3 - .../src/test/resources/jenkins-junit.xsd | 118 - .../uidtracking/gradle/groovy/build.gradle | 1 - .../groovy/sub-project/sub-project.gradle | 1 - .../gradle/kotlin/build.gradle.kts | 1 - .../kotlin/sub-project/sub-project.gradle.kts | 1 - .../listeners/uidtracking/maven/pom.xml | 1 - .../src/test/resources/log4j2-test.xml | 23 - .../modules-2500/foo.bar/FooBar.java | 3 - .../modules-2500/foo.bar/module-info.java | 3 - .../test/resources/modules-2500/foo/Foo.java | 3 - .../modules-2500/foo/module-info.java | 3 - .../test/resources/serialized-test-identifier | Bin 1079 -> 0 bytes .../resources/test-junit-platform.properties | 1 - ...latform.launcher.LauncherDiscoveryListener | 1 - ....platform.launcher.LauncherSessionListener | 1 - ...unit.platform.launcher.PostDiscoveryFilter | 1 - ...it.platform.launcher.TestExecutionListener | 3 - .../testservices/junit-platform.properties | 1 - .../platform-tooling-support-tests.gradle.kts | 174 - .../projects/ant-starter/build.xml | 71 - .../java/com/example/project/Calculator.java | 19 - .../com/example/project/CalculatorTests.java | 41 - .../projects/graalvm-starter/build.gradle.kts | 29 - .../graalvm-starter/gradle.properties | 1 - .../graalvm-starter/settings.gradle.kts | 11 - .../java/com/example/project/Calculator.java | 19 - .../com/example/project/CalculatorTests.java | 41 - .../gradle-kotlin-extensions/build.gradle.kts | 33 - .../gradle/wrapper/gradle-wrapper.properties | 5 - .../projects/gradle-kotlin-extensions/gradlew | 188 - .../gradle-kotlin-extensions/gradlew.bat | 100 - .../settings.gradle.kts | 1 - .../project/ExtensionFunctionsTests.kt | 59 - .../gradle-missing-engine/build.gradle.kts | 38 - .../gradle-missing-engine/settings.gradle.kts | 1 - .../src/test/java/FooTests.java | 22 - .../projects/gradle-starter/build.gradle.kts | 42 - .../gradle-starter/settings.gradle.kts | 1 - .../java/com/example/project/Calculator.java | 19 - .../com/example/project/CalculatorTests.java | 41 - .../junit-jupiter-api.expected.txt | 12 - .../junit-jupiter-engine.expected.txt | 11 - ...unit-jupiter-migrationsupport.expected.txt | 11 - .../junit-jupiter-params.expected.txt | 13 - .../junit-jupiter.expected.txt | 5 - .../junit-platform-commons.expected.txt | 11 - .../junit-platform-console.expected.txt | 13 - .../junit-platform-engine.expected.txt | 13 - .../junit-platform-jfr.expected.txt | 9 - .../junit-platform-launcher.expected.txt | 16 - .../junit-platform-reporting.expected.txt | 11 - .../junit-platform-runner.expected.txt | 8 - .../junit-platform-suite-api.expected.txt | 5 - .../junit-platform-suite-commons.expected.txt | 8 - .../junit-platform-suite-engine.expected.txt | 10 - .../junit-platform-suite.expected.txt | 4 - .../junit-platform-testkit.expected.txt | 10 - .../junit-vintage-engine.expected.txt | 7 - .../projects/java-versions/pom.xml | 63 - .../test/java/JUnitPlatformCommonsTests.java | 37 - .../projects/maven-starter/pom.xml | 85 - .../java/com/example/project/Calculator.java | 19 - .../com/example/project/CalculatorTests.java | 41 - .../maven-surefire-compatibility/pom.xml | 74 - .../java/com/example/project/DummyTests.java | 26 - .../multi-release-jar/default/pom.xml | 73 - .../integration/JupiterIntegrationTests.java | 54 - .../integration/ModuleUtilsTests.java | 64 - .../projects/standalone/expected-err.txt | 18 - .../projects/standalone/expected-out.txt | 19 - .../projects/standalone/logging.properties | 3 - .../src/standalone/JupiterIntegration.java | 38 - .../standalone/JupiterParamsIntegration.java | 25 - .../src/standalone/SuiteIntegration.java | 26 - .../src/standalone/VintageIntegration.java | 39 - .../projects/vintage/build.gradle.kts | 33 - .../projects/vintage/pom.xml | 64 - .../projects/vintage/settings.gradle.kts | 1 - .../java/com/example/vintage/VintageTest.java | 27 - .../java/platform/tooling/support/Helper.java | 158 - .../platform/tooling/support/MavenRepo.java | 60 - .../platform/tooling/support/Request.java | 183 - .../tooling/support/ThirdPartyJars.java | 33 - .../tooling/support/package-info.java | 7 - .../platform/tooling/support/HelperTests.java | 73 - .../support/tests/AntStarterTests.java | 63 - .../tooling/support/tests/ArchUnitTests.java | 104 - .../support/tests/GraalVmStarterTests.java | 58 - .../tests/GradleKotlinExtensionsTests.java | 49 - .../tests/GradleMissingEngineTests.java | 67 - .../support/tests/GradleModuleFileTests.java | 165 - .../support/tests/GradleStarterTests.java | 58 - .../tests/JarContainsManifestFirstTests.java | 43 - .../support/tests/JarDescribeModuleTests.java | 77 - .../support/tests/JavaVersionsTests.java | 67 - .../tooling/support/tests/ManifestTests.java | 91 - .../support/tests/MavenPomFileTests.java | 75 - .../support/tests/MavenStarterTests.java | 56 - .../MavenSurefireCompatibilityTests.java | 70 - .../support/tests/ModularUserGuideTests.java | 203 - .../support/tests/MultiReleaseJarTests.java | 98 - .../tests/PackageCyclesDetectionTests.java | 51 - .../support/tests/StandaloneTests.java | 239 -- .../support/tests/ToolProviderTests.java | 144 - .../tests/VintageGradleIntegrationTests.java | 73 - .../tests/VintageMavenIntegrationTests.java | 72 - .../tooling/support/tests/XmlAssertions.java | 158 - .../src/test/resources/log4j2-test.xml | 15 - .../settings.gradle.kts | 134 - .../src/checkBuildReproducibility.sh | 23 - .../src/checkstyle/checkstyleMain.xml | 39 - .../src/checkstyle/checkstyleTest.xml | 25 - .../src/checkstyle/suppressions.xml | 7 - .../junit-eclipse-formatter-settings.xml | 295 -- .../src/eclipse/junit-eclipse.importorder | 7 - ...ishDocumentationSnapshotOnlyIfNecessary.sh | 62 - .../spotless/eclipse-public-license-2.0.java | 9 - src/main/java/com/test.java | 7 - 1693 files changed, 176438 deletions(-) delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.codecov.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.gitattributes delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/.gitignore delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/KEYS delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/LICENSE.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/README.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/SECURITY.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/README.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/fonts/Symbola.ttf delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.png delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.png delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle.png delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle_source.docx delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_execution_mode.svg delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradle.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradlew delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/gradlew.bat delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/serialized-test-identifier delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh delete mode 100644 src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java delete mode 100644 src/main/java/com/test.java diff --git a/src/main/java/com/junit-team-junit5-da216b8/.codecov.yml b/src/main/java/com/junit-team-junit5-da216b8/.codecov.yml deleted file mode 100644 index 04dae764..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.codecov.yml +++ /dev/null @@ -1,8 +0,0 @@ -comment: false -coverage: - status: - project: - default: - threshold: 1 - informational: true - patch: off diff --git a/src/main/java/com/junit-team-junit5-da216b8/.gitattributes b/src/main/java/com/junit-team-junit5-da216b8/.gitattributes deleted file mode 100644 index d836e426..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -* text eol=lf -*.bat text eol=crlf -*.png binary -*.key binary -*.jar binary -*.ttf binary -release-notes-* merge=union diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml deleted file mode 100644 index 0786ce3e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/FUNDING.yml +++ /dev/null @@ -1,8 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -custom: https://junit.org/sponsoring diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index f1ce8b0c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,23 +0,0 @@ -## Overview - -_Replace the following bullet points with your issue description, -after answering yourself: **"What kind of issue is this?"**_ - -- ( ) **Question.** This issue tracker is not the place for questions. -If you want to ask how to do something, or to understand why -something isn't working the way you expect it to, please first use Stack -Overflow or Gitter. -https://stackoverflow.com/questions/tagged/junit5 - -- ( ) **Bug report.** Please provide us the version of JUnit 5 you are -using and, if possible, a failing unit test with your bug report. Don't -forget to describe the rationale for this issue (e.g. expected vs. -actual behavior). - -- ( ) **Feature request.** Start by telling us what problem you’re trying -to solve. Often a solution already exists! Please, don’t send pull requests -to implement new features without first getting our support. - -## Deliverables - -- [ ] ... diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8d847d25..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - - - -## Steps to reproduce - - - -## Context - - - Used versions (Jupiter/Vintage/Platform): - - Build Tool/IDE: - -## Deliverables - -- [ ] ... diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 24e26904..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - - - -## Deliverables - -- [ ] ... diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md b/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 23c796b2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Question -about: Please first ask on Gitter or StackOverflow before creating an issue ---- - -This issue tracker is not the place for questions. -If you want to ask how to do something, or to understand why -something isn't working the way you expect it to, please use Gitter [1] or Stack Overflow [2]. - -[1] https://gitter.im/junit-team/junit5 -[2] https://stackoverflow.com/questions/tagged/junit5 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md b/src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 1bdc52cd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,19 +0,0 @@ -## Overview - - - ---- - -I hereby agree to the terms of the [JUnit Contributor License Agreement](https://github.com/junit-team/junit5/blob/002a0052926ddee57cf90580fa49bc37e5a72427/CONTRIBUTING.md#junit-contributor-license-agreement). - ---- - -### Definition of Done - -- [ ] There are no TODOs left in the code -- [ ] Method [preconditions](https://junit.org/junit5/docs/snapshot/api/org.junit.platform.commons/org/junit/platform/commons/util/Preconditions.html) are checked and documented in the method's Javadoc -- [ ] [Coding conventions](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#coding-conventions) (e.g. for logging) have been followed -- [ ] Change is covered by [automated tests](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#tests) including corner cases, errors, and exception handling -- [ ] Public API has [Javadoc](https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md#javadoc) and [`@API` annotations](https://apiguardian-team.github.io/apiguardian/docs/current/api/org/apiguardian/api/API.html) -- [ ] Change is documented in the [User Guide](https://junit.org/junit5/docs/snapshot/user-guide/) and [Release Notes](https://junit.org/junit5/docs/snapshot/user-guide/#release-notes) -- [ ] All [continuous integration builds](https://github.com/junit-team/junit5#continuous-integration-builds) pass diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml deleted file mode 100644 index a7817cd3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/main-build/action.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Main build -description: Sets up JDKs and runs Gradle -inputs: - arguments: - required: true - description: Gradle arguments - default: build -runs: - using: "composite" - steps: - - uses: ./.github/actions/setup-test-jdk - - uses: ./.github/actions/run-gradle - with: - arguments: ${{ inputs.arguments }} - - uses: actions/upload-artifact@v3 - if: ${{ always() }} - with: - name: Test Distribution trace files (${{ github.job }}) - path: '**/build/test-results/*/trace.json' diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml deleted file mode 100644 index 50139359..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/run-gradle/action.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Run Gradle -description: Sets up Gradle JDKs and runs Gradle -inputs: - arguments: - required: true - description: Gradle arguments - default: build -runs: - using: "composite" - steps: - - uses: actions/setup-java@v3 - id: setup-gradle-jdk - with: - distribution: temurin - java-version: 17 - - uses: gradle/gradle-build-action@v2 - env: - JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} - with: - arguments: | - -Porg.gradle.java.installations.auto-download=false - -PenablePredictiveTestSelection=${{ github.event_name == 'pull_request' }} - "-Dscan.value.GitHub job=${{ github.job }}" - javaToolchains - ${{ inputs.arguments }} diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml deleted file mode 100644 index 4e8c9626..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/actions/setup-test-jdk/action.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Set up Test JDK -description: Sets up the JDK required to run platform-tooling-support-tests -runs: - using: "composite" - steps: - - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 8 - - shell: bash - run: echo "JDK8=$JAVA_HOME" >> $GITHUB_ENV diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml deleted file mode 100644 index 29a11556..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/dependabot.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -registries: - gradle-plugin-portal: - type: maven-repository - url: https://plugins.gradle.org/m2 - username: dummy # Required by dependabot - password: dummy # Required by dependabot -updates: - - package-ecosystem: "gradle" - directory: "/" - allow: - - dependency-name: "com.gradle*" - registries: - - gradle-plugin-portal - schedule: - interval: "weekly" - open-pull-requests-limit: 10 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml deleted file mode 100644 index 2ef85c0b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/stale.yml +++ /dev/null @@ -1,69 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale -# Configuration options apply to both Issues and Pull Requests. -# We configure those individually to match our workflow (see `pulls:` and `issues:`) - -pulls: - # Number of days of inactivity before a Pull Request becomes stale - daysUntilStale: 60 - - # Number of days of inactivity before a Pull Request with the stale label is closed. - # Set to false to disable. If disabled, Pull Request still need to be closed manually, but will remain marked as stale. - daysUntilClose: 21 - - # Comment to post when marking as stale. Set to `false` to disable - markComment: > - This pull request has been automatically marked as stale because it has not had recent activity. - Given the limited bandwidth of the team, it will be closed if no further activity occurs. - If you intend to work on this pull request, please reopen the PR. - Thank you for your contributions. - # Comment to post when closing a stale Pull Request. - closeComment: > - This pull request has been automatically closed due to inactivity. - If you are still interested in contributing this, please ensure that - it is rebased against the latest branch (usually `main`), all review - comments have been addressed and the build is passing. -issues: - daysUntilStale: 365 - - # Number of days of inactivity before an Issue with the stale label is closed. - # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. - daysUntilClose: 21 - - # Comment to post when marking as stale. Set to `false` to disable - markComment: > - This issue has been automatically marked as stale because it has not had recent activity. - Given the limited bandwidth of the team, it will be automatically closed if no further - activity occurs. - Thank you for your contribution. - # Comment to post when closing a stale Issue. - closeComment: > - This issue has been automatically closed due to inactivity. If you have a good use case for this - feature, please feel free to reopen the issue. - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -#onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: [] - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: "status: stale" - -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -# Limit to only `issues` or `pulls` -# only: issues diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 222d54f2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [main, releases/**] - pull_request: - # The branches below must be a subset of the branches above - branches: [main, releases/**] - schedule: - - cron: '0 19 * * 3' - -env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - language: - - java - - javascript - steps: - - name: Check out repository - uses: actions/checkout@v3 - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - tools: latest - - name: Build - uses: ./.github/actions/run-gradle - with: - arguments: | - --no-build-cache - -Dscan.tag.CodeQL - allMainClasses - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml deleted file mode 100644 index a6a6e0c3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/cross-version.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Cross-Version - -on: - push: - branches: - - main - - 'releases/*' - pull_request: - branches: - - '*' - -env: - ORG_GRADLE_PROJECT_enableTestDistribution: true - ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }} - ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ secrets.BUILD_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - -jobs: - openjdk: - strategy: - fail-fast: false - matrix: - jdk: [19, 20, 21] - name: "OpenJDK ${{ matrix.jdk }}" - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Set up Test JDK - uses: ./.github/actions/setup-test-jdk - - name: 'Set up JDK ${{ matrix.jdk }}' - uses: oracle-actions/setup-java@v1 - with: - website: jdk.java.net - release: ${{ matrix.jdk }} - version: latest - - name: 'Prepare JDK${{ matrix.jdk }} env var' - shell: bash - run: echo "JDK${{ matrix.jdk }}=$JAVA_HOME" >> $GITHUB_ENV - - name: Build - uses: ./.github/actions/run-gradle - with: - arguments: | - -PjavaToolchainVersion=${{ matrix.jdk }} - -Dscan.tag.JDK_${{ matrix.jdk }} - build - - name: Upload Test Distribution trace files - uses: actions/upload-artifact@v3 - with: - name: "Test Distribution trace files (OpenJDK ${{ matrix.jdk }})" - path: '**/build/test-results/*/trace.json' diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index b4182954..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: "Validate Gradle Wrapper" -on: [push, pull_request] - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml deleted file mode 100644 index 3dadaf6b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/issue-labels.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Label new issues -on: - issues: - types: ['opened'] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: Renato66/auto-label@69b3cbe79438b2079aed0a49474275d3e572cae5 # or v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - labels-synonyms: '{"3rd-party: Eclipse":["Eclipse"],"3rd-party: Gradle":["Gradle"],"3rd-party: IntelliJ IDEA":["IDEA","IntelliJ"],"3rd-party: Maven Surefire":["Failsafe","Maven","Surefire"],"3rd-party: Pioneer":["pioneer"],"component: Groovy":["Groovy"],"component: Test Kit":["Test Kit","TestKit"]}' - labels-not-allowed: '["dependencies","status: blocked","status: declined","status: duplicate","status: in progress","status: invalid","status: stale","status: superseded","status: team discussion","status: waiting-for-feedback","status: waiting-for-interest","status: works-as-designed","up-for-grabs"]' - default-labels: '["status: new"]' diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml deleted file mode 100644 index b1e40cf1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/main.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: CI - -on: - push: - branches: - - main - - 'releases/*' - pull_request: - branches: - - '*' - -env: - ORG_GRADLE_PROJECT_enableTestDistribution: true - ORG_GRADLE_PROJECT_junitBuildCacheUsername: ${{ secrets.BUILD_CACHE_USERNAME }} - ORG_GRADLE_PROJECT_junitBuildCachePassword: ${{ secrets.BUILD_CACHE_PASSWORD }} - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - -jobs: - Linux: - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Install Graphviz - run: | - sudo apt-get update - sudo apt-get install graphviz - - name: Install GraalVM - uses: graalvm/setup-graalvm@v1 - with: - version: 'latest' - java-version: '17' - components: 'native-image' - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Build - uses: ./.github/actions/main-build - with: - arguments: | - -PenableJaCoCo - build - jacocoRootReport - prepareDocsForUploadToGhPages - - name: Upload to Codecov.io - uses: codecov/codecov-action@v3 - - Windows: - runs-on: windows-latest - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Build - uses: ./.github/actions/main-build - - macOS: - runs-on: macos-latest - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Build - uses: ./.github/actions/main-build - - publish_artifacts: - name: Publish Snapshot Artifacts - needs: linux - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Publish - uses: ./.github/actions/run-gradle - env: - ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} - with: - arguments: publish -x check - - update_documentation: - name: Update Snapshot Documentation - concurrency: - group: github-pages - cancel-in-progress: true - needs: Linux - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && github.ref == 'refs/heads/main' - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Install Graphviz - run: | - sudo apt-get update - sudo apt-get install graphviz - - name: Restore Gradle cache and display toolchains - uses: ./.github/actions/run-gradle - with: - arguments: --quiet - - name: Upload Documentation - env: - GRGIT_USER: ${{ secrets.GH_TOKEN }} - run: ./src/publishDocumentationSnapshotOnlyIfNecessary.sh diff --git a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml b/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml deleted file mode 100644 index 3327cc7f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.github/workflows/reproducible-build.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Reproducible build - -on: - push: - branches: - - main - - 'releases/*' - pull_request: - branches: - - '*' - -env: - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - -jobs: - check_build_reproducibility: - name: 'Check build reproducibility' - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v3 - with: - fetch-depth: 1 - - name: Restore Gradle cache and display toolchains - uses: ./.github/actions/run-gradle - with: - arguments: --quiet - - name: Build and compare checksums - shell: bash - run: | - ./src/checkBuildReproducibility.sh diff --git a/src/main/java/com/junit-team-junit5-da216b8/.gitignore b/src/main/java/com/junit-team-junit5-da216b8/.gitignore deleted file mode 100644 index e5f0af67..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# Gradle -.gradle -/build/ -/*/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Eclipse -.classpath -.settings/ -.project -/bin/ -/*/bin - -# IntelliJ -*.iml -*.ipr -*.iws -*.uml -**/.idea/* -!/.idea/icon.png -!/.idea/vcs.xml -/out/ -/*/out/ - -# Misc -*.log -*.graphml -coverage.db* -.metadata -/.sdkmanrc - -checksums* diff --git a/src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md b/src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md deleted file mode 100644 index c8726293..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at team@junit.org. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md b/src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md deleted file mode 100644 index e2a44b38..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/CONTRIBUTING.md +++ /dev/null @@ -1,159 +0,0 @@ -# Contributing - -## JUnit Contributor License Agreement - -- You will only Submit Contributions where You have authored 100% of the content. -- You will only Submit Contributions to which You have the necessary rights. This means - that if You are employed You have received the necessary permissions from Your employer - to make the Contributions. -- Whatever content You Contribute will be provided under the Project License(s). - -### Project Licenses - -- All modules use [Eclipse Public License v2.0](LICENSE.md). - -## Commit Messages - -As a general rule, the style and formatting of commit messages should follow the guidelines in -[How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/). - -In addition, any commit that is related to an existing issue must reference the issue. -For example, if a commit in a pull request addresses issue \#999, it must contain the -following at the bottom of the commit message. - -``` -Issue: #999 -``` - -## Pull Requests - -Our [Definition of Done](https://github.com/junit-team/junit5/wiki/Definition-of-Done) -offers some guidelines on what we expect from a pull request. -Feel free to open a pull request that does not fulfill all criteria, e.g. to discuss -a certain change before polishing it, but please be aware that we will only merge it -in case the DoD is met. - -Please add the following lines to your pull request description: - -```markdown ---- - -I hereby agree to the terms of the JUnit Contributor License Agreement. -``` - -## Coding Conventions - -### Naming Conventions - -Whenever an acronym is included as part of a type name or method name, keep the first -letter of the acronym uppercase and use lowercase for the rest of the acronym. Otherwise, -it becomes _impossible_ to perform camel-cased searches in IDEs, and it becomes -potentially very difficult for mere humans to read or reason about the element without -reading documentation (if documentation even exists). - -Consider for example a use case needing to support an HTTP URL. Calling the method -`getHTTPURL()` is absolutely horrible in terms of usability; whereas, `getHttpUrl()` is -great in terms of usability. The same applies for types `HTTPURLProvider` vs -`HttpUrlProvider`, etc. - -Whenever an acronym is included as part of a field name or parameter name: - -- If the acronym comes at the start of the field or parameter name, use lowercase for the - entire acronym -- for example, `String url;`. -- Otherwise, keep the first letter of the acronym uppercase and use lowercase for the - rest of the acronym -- for example, `String defaultUrl;`. - -### Formatting - -#### Code - -Code formatting is enforced using the [Spotless](https://github.com/diffplug/spotless) -Gradle plugin. You can use `gradle spotlessApply` to format new code and add missing -license headers to source files. Formatter and import order settings for Eclipse are -available in the repository under -[src/eclipse/junit-eclipse-formatter-settings.xml](src/eclipse/junit-eclipse-formatter-settings.xml) -and [src/eclipse/junit-eclipse.importorder](src/eclipse/junit-eclipse.importorder), -respectively. For IntelliJ IDEA there's a -[plugin](https://plugins.jetbrains.com/plugin/6546) you can use in conjunction with the -Eclipse settings. - -It is forbidden to use _wildcard imports_ (e.g., `import static org.junit.jupiter.api.Assertions.*;`) -in Java code. - -#### Documentation - -Text in `*.adoc` and `*.md` files should be wrapped at 90 characters whenever technically -possible. - -In multi-line bullet point entries, subsequent lines should be indented. - -### Spelling - -Use American English spelling rules when writing documentation as well as for -code -- class names, method names, variable names, etc. - -### Javadoc - -- Javadoc comments should be wrapped after 80 characters whenever possible. -- This first paragraph must be a single, concise sentence that ends with a period ("."). -- Place `

` on the same line as the first line in a new paragraph and precede `

` with a blank line. -- Insert a blank line before at-clauses/tags. -- Favor `{@code foo}` over `foo`. -- Favor literals (e.g., `{@literal @}`) over HTML entities. -- New classes and methods should have `@since ...` annotation. -- Use `@since 5.0` instead of `@since 5.0.0`. -- Do not use `@author` tags. Instead, contributors are listed on [GitHub](https://github.com/junit-team/junit5/graphs/contributors). -- Do not use verbs in third person form (e.g. use "Discover tests..." instead of "Discovers tests...") - in the first sentence describing a method. - -#### Examples - -See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java) and -[`ParameterContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java) for example Javadoc. - - -### Tests - -#### Naming - -- All test classes must end with a `Tests` suffix. -- Example test classes that should not be picked up by the build must end with a `TestCase` suffix. - -#### Assertions - -- Use `org.junit.jupiter.api.Assertions` wherever possible. -- Use AssertJ when richer assertions are needed. -- Do not use `org.junit.Assert` or `junit.framework.Assert`. - -#### Mocking - -- Use either [Mockito](https://github.com/mockito/mockito) or hand-written test doubles. - -### Logging - -- In general, logging should be used sparingly. -- All logging must be performed via the internal `Logger` façade provided via the JUnit [LoggerFactory](https://github.com/junit-team/junit5/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java). -- Levels defined in JUnit's [Logger](https://github.com/junit-team/junit5/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java) façade, which delegates to Java Util Logging (JUL) for the actual logging. - - _error_ (JUL: `SEVERE`, Log4J: `ERROR`): extra information (in addition to an Exception) about errors that will halt execution - - _warn_ (JUL: `WARNING`, Log4J: `WARN`): potential usage or configuration errors that should not halt execution - - _info_ (JUL: `INFO`, Log4J: `INFO`): information the users might want to know but not by default - - _config_ (JUL: `CONFIG`, Log4J: `CONFIG`): information related to configuration of the system (Example: `ServiceLoaderTestEngineRegistry` logs IDs of discovered engines) - - _debug_ (JUL: `FINE`, Log4J: `DEBUG`) - - _trace_ (JUL: `FINER`, Log4J: `TRACE`) - -### Deprecation - -Publicly available interfaces, classes and methods have a defined lifecycle -which is described in detail in the [User Guide](https://junit.org/junit5/docs/current/user-guide/#api-evolution). -This process is using the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian). -It also describes the deprecation process followed for API items. - -To deprecate an item: -- Update the `@API.status` to `DEPRECATED`. -- Update `@API.since`. Please note `since` describes the version when the - status was changed and not the introduction of the element. -- Add the `@Deprecated` Java annotation on the item. -- Add the `@deprecated` JavaDoc tag to describe the deprecation, and refer to - an eventual replacement. -- If the item is used in existing code, add `@SuppressWarnings("deprecation")` - to make the build pass. diff --git a/src/main/java/com/junit-team-junit5-da216b8/KEYS b/src/main/java/com/junit-team-junit5-da216b8/KEYS deleted file mode 100644 index 0433e72f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/KEYS +++ /dev/null @@ -1,70 +0,0 @@ -This file contains the PGP key that is used to sign releases. - -Importing: `pgp < KEYS` or `gpg --import KEYS` - -Adding a key: -`pgp -kxa >> KEYS`, -or `(pgpk -ll && pgpk -xa ) >> KEYS`, -or `(gpg --list-sigs && gpg --armor --export ) >> KEYS` - -================================ - -pub rsa4096 2018-04-08 [SC] - FF6E2C001948C5F2F38B0CC385911F425EC61B51 -uid [ unknown] Open Source Development -sig 3 85911F425EC61B51 2018-04-08 Open Source Development -sub rsa4096 2018-04-08 [E] -sig 85911F425EC61B51 2018-04-08 Open Source Development - ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -mdzS2PJko7mm -=l0XA ------END PGP PUBLIC KEY BLOCK----- diff --git a/src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md b/src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md deleted file mode 100644 index 520713de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/LICENSE-notice.md +++ /dev/null @@ -1,8 +0,0 @@ -Open Source Licenses -==================== - -This product may include a number of subcomponents with separate -copyright notices and license terms. Your use of the source code for -these subcomponents is subject to the terms and conditions of the -subcomponent's license, as noted in the LICENSE-.md -files. diff --git a/src/main/java/com/junit-team-junit5-da216b8/LICENSE.md b/src/main/java/com/junit-team-junit5-da216b8/LICENSE.md deleted file mode 100644 index a32decd8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/LICENSE.md +++ /dev/null @@ -1,98 +0,0 @@ -Eclipse Public License - v 2.0 -============================== - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -### 1. Definitions - -“Contribution” means: -* **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and -* **b)** in the case of each subsequent Contributor: - * **i)** changes to the Program, and - * **ii)** additions to the Program; -where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. - -“Contributor” means any person or entity that Distributes the Program. - -“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -“Program” means the Contributions Distributed in accordance with this Agreement. - -“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. - -“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. - -“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. - -“Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy. - -“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. - -“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. - -### 2. Grant of Rights - -**a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. - -**b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. - -**c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. - -**d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. - -**e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). - -### 3. Requirements - -**3.1** If a Contributor Distributes the Program in any form, then: - -* **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and - -* **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: - * **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; - * **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; - * **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and - * **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. - -**3.2** When the Program is Distributed as Source Code: - -* **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and -* **b)** a copy of this Agreement must be included with each copy of the Program. - -**3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. - -### 4. Commercial Distribution - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -### 5. No Warranty - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -### 6. Disclaimer of Liability - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -### 7. General - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. - -Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. - -#### Exhibit A - Form of Secondary Licenses Notice - -> “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” - -Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. - -If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. diff --git a/src/main/java/com/junit-team-junit5-da216b8/README.md b/src/main/java/com/junit-team-junit5-da216b8/README.md deleted file mode 100644 index 59318bd5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# JUnit 5 - -This repository is the home of the next generation of JUnit, _JUnit 5_. - -[![Support JUnit](https://img.shields.io/badge/%F0%9F%92%9A-Support%20JUnit-brightgreen.svg)](https://junit.org/sponsoring) - -## Latest Releases - -- General Availability (GA): [JUnit 5.9.3](https://github.com/junit-team/junit5/releases/tag/r5.9.3) (April 26, 2023) -- Preview (Milestone/Release Candidate): n/a - -## Documentation - -- [User Guide] -- [Javadoc] -- [Release Notes] -- [Samples] - -## Contributing - -Contributions to JUnit 5 are both welcomed and appreciated. For specific guidelines -regarding contributions, please see [CONTRIBUTING.md] in the root directory of the -project. Those willing to use milestone or SNAPSHOT releases are encouraged -to file feature requests and bug reports using the project's -[issue tracker](https://github.com/junit-team/junit5/issues). Issues marked with an -`up-for-grabs` -label are specifically targeted for community contributions. - -## Getting Help - -Ask JUnit 5 related questions on [StackOverflow] or chat with the community on [Gitter]. - -## Continuous Integration Builds - -[![CI Status](https://github.com/junit-team/junit5/workflows/CI/badge.svg)](https://github.com/junit-team/junit5/actions) [![Cross-Version Status](https://github.com/junit-team/junit5/workflows/Cross-Version/badge.svg)](https://github.com/junit-team/junit5/actions) - -Official CI build server for JUnit 5. Used to perform quick checks on submitted pull -requests and for build matrices including the latest released OpenJDK and early access -builds of the next OpenJDK. - -## Code Coverage - -Code coverage using [JaCoCo] for the latest build is available on [Codecov]. - -A code coverage report can also be generated locally via the [Gradle Wrapper] by -executing `./gradlew -PenableJaCoCo clean jacocoRootReport`. The results will be available -in `build/reports/jacoco/jacocoRootReport/html/index.html`. - -## Gradle Enterprise - -[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) - -JUnit 5 utilizes [Gradle Enterprise](https://gradle.com/) for _Build Scans_, _Build Cache_, and _Test Distribution_. - -The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently, -only core team members can publish Build Scans and use Test Distribution on that server. -You can, however, publish a Build Scan to [scans.gradle.com](https://scans.gradle.com/) by -using the `--scan` parameter explicitly. - -The remote Build Cache is enabled by default for everyone so that local builds can reuse -task outputs from previous CI builds. - -## Building from Source - -You need [JDK 17] to build JUnit 5. [Gradle toolchains] are used to detect and -potentially download additional JDKs for compilation and test execution. - -All modules can be _built_ and _tested_ with the [Gradle Wrapper] using the following command. - -`./gradlew build` - -## Installing in Local Maven Repository - -All modules can be _installed_ with the [Gradle Wrapper] in a local Maven repository for -consumption in other projects via the following command. - -`./gradlew publishToMavenLocal` - -## Dependency Metadata - -Consult the [Dependency Metadata] section of the [User Guide] for a list of all artifacts -of the JUnit Platform, JUnit Jupiter, and JUnit Vintage. - -See also for releases and - for snapshots. - - -[Codecov]: https://codecov.io/gh/junit-team/junit5 -[CONTRIBUTING.md]: https://github.com/junit-team/junit5/blob/HEAD/CONTRIBUTING.md -[Dependency Metadata]: https://junit.org/junit5/docs/current/user-guide/#dependency-metadata -[Gitter]: https://gitter.im/junit-team/junit5 -[Gradle toolchains]: https://docs.gradle.org/current/userguide/toolchains.html -[Gradle Wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper -[JaCoCo]: https://www.eclemma.org/jacoco/ -[Javadoc]: https://junit.org/junit5/docs/current/api/ -[JDK 17]: https://foojay.io/almanac/java-17/ -[Release Notes]: https://junit.org/junit5/docs/current/release-notes/ -[Samples]: https://github.com/junit-team/junit5-samples -[StackOverflow]: https://stackoverflow.com/questions/tagged/junit5 -[User Guide]: https://junit.org/junit5/docs/current/user-guide/ diff --git a/src/main/java/com/junit-team-junit5-da216b8/SECURITY.md b/src/main/java/com/junit-team-junit5-da216b8/SECURITY.md deleted file mode 100644 index fca52da5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/SECURITY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 5.9.x | :white_check_mark: | -| < 5.9 | :x: | - -## Reporting a Vulnerability - -To report a security vulnerability, please send an email to security@junit.org. diff --git a/src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts deleted file mode 100644 index 5379f61f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/build.gradle.kts +++ /dev/null @@ -1,65 +0,0 @@ -plugins { - id("io.spring.nohttp") - id("io.github.gradle-nexus.publish-plugin") - `base-conventions` - `build-metadata` - `dependency-update-check` - `jacoco-aggregation-conventions` - `temp-maven-repo` -} - -description = "JUnit 5" - -val license by extra(License( - name = "Eclipse Public License v2.0", - url = uri("https://www.eclipse.org/legal/epl-v20.html"), - headerFile = file("src/spotless/eclipse-public-license-2.0.java") -)) - -val platformProjects by extra(listOf( - projects.junitPlatformCommons, - projects.junitPlatformConsole, - projects.junitPlatformConsoleStandalone, - projects.junitPlatformEngine, - projects.junitPlatformJfr, - projects.junitPlatformLauncher, - projects.junitPlatformReporting, - projects.junitPlatformRunner, - projects.junitPlatformSuite, - projects.junitPlatformSuiteApi, - projects.junitPlatformSuiteCommons, - projects.junitPlatformSuiteEngine, - projects.junitPlatformTestkit -).map { it.dependencyProject }) - -val jupiterProjects by extra(listOf( - projects.junitJupiter, - projects.junitJupiterApi, - projects.junitJupiterEngine, - projects.junitJupiterMigrationsupport, - projects.junitJupiterParams -).map { it.dependencyProject }) - -val vintageProjects by extra(listOf( - projects.junitVintageEngine.dependencyProject -)) - -val mavenizedProjects by extra(platformProjects + jupiterProjects + vintageProjects) -val modularProjects by extra(mavenizedProjects - listOf(projects.junitPlatformConsoleStandalone.dependencyProject)) - -dependencies { - (modularProjects + listOf(projects.platformTests.dependencyProject)).forEach { - jacocoAggregation(project(it.path)) - } -} - -nexusPublishing { - packageGroup.set("org.junit") - repositories { - sonatype() - } -} - -nohttp { - source.exclude("buildSrc/build/generated-sources/**") -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts deleted file mode 100644 index 264ec067..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/build.gradle.kts +++ /dev/null @@ -1,31 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - `kotlin-dsl` - id("com.github.ben-manes.versions") version "0.42.0" -} - -repositories { - gradlePluginPortal() -} - -dependencies { - implementation(kotlin("gradle-plugin")) - implementation("biz.aQute.bnd:biz.aQute.bnd.gradle:6.4.0") - implementation("com.diffplug.spotless:spotless-plugin-gradle:6.11.0") - implementation("com.github.ben-manes:gradle-versions-plugin:0.42.0") - implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2") - compileOnly("com.gradle:gradle-enterprise-gradle-plugin:3.12.1") // keep in sync with root settings.gradle.kts -} - -tasks { - withType().configureEach { - options.release.set(8) - } - withType().configureEach { - kotlinOptions { - jvmTarget = "1.8" - allWarningsAsErrors = true - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts deleted file mode 100644 index 29744ec1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "buildSrc" diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt deleted file mode 100644 index 834374bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/JavaLibraryExtension.kt +++ /dev/null @@ -1,7 +0,0 @@ -import org.gradle.api.JavaVersion - -open class JavaLibraryExtension { - var mainJavaVersion: JavaVersion = JavaVersion.VERSION_1_8 - var testJavaVersion: JavaVersion = JavaVersion.VERSION_17 - var configureRelease: Boolean = true -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt deleted file mode 100644 index 4f4e78ea..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/License.kt +++ /dev/null @@ -1,4 +0,0 @@ -import java.io.File -import java.net.URI - -data class License(val name: String, val url: URI, val headerFile: File) diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt deleted file mode 100644 index fc8a5159..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/ProjectExtensions.kt +++ /dev/null @@ -1,19 +0,0 @@ -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalog -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.the - -val Project.javaModuleName: String - get() = "org." + this.name.replace('-', '.') - -fun Project.requiredVersionFromLibs(name: String) = - libsVersionCatalog.findVersion(name).get().requiredVersion - -fun Project.dependencyFromLibs(name: String) = - libsVersionCatalog.findLibrary(name).get() - -fun Project.bundleFromLibs(name: String) = - libsVersionCatalog.findBundle(name).get() - -private val Project.libsVersionCatalog: VersionCatalog - get() = the().named("libs") diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt deleted file mode 100644 index 7ad4e7ab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/TaskExtensions.kt +++ /dev/null @@ -1,5 +0,0 @@ -import org.gradle.api.Task -import org.gradle.internal.os.OperatingSystem - -fun Task.trackOperationSystemAsInput() = - inputs.property("os", OperatingSystem.current().familyName) diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts deleted file mode 100644 index 0c4c4c5f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/base-conventions.gradle.kts +++ /dev/null @@ -1,6 +0,0 @@ -plugins { - eclipse - idea - id("java-toolchain-conventions") - id("spotless-conventions") -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts deleted file mode 100644 index ad58b489..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/build-metadata.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter - -val buildTimeAndDate = - if (System.getenv().containsKey("SOURCE_DATE_EPOCH")) { - - // SOURCE_DATE_EPOCH is a UNIX timestamp for pinning build metadata against - // in order to achieve reproducible builds - // - // More details - https://reproducible-builds.org/docs/source-date-epoch/ - val sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH").toLong() - - Instant.ofEpochSecond(sourceDateEpoch).atOffset(ZoneOffset.UTC) - - } else { - OffsetDateTime.now() - } - -val buildDate: String by extra { DateTimeFormatter.ISO_LOCAL_DATE.format(buildTimeAndDate) } -val buildTime: String by extra { DateTimeFormatter.ofPattern("HH:mm:ss.SSSZ").format(buildTimeAndDate) } -val buildRevision: String by extra { - providers.exec { - commandLine("git", "rev-parse", "--verify", "HEAD") - }.standardOutput.asText.get() -} -val builtByValue by extra { project.findProperty("builtBy") ?: project.property("defaultBuiltBy") } diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts deleted file mode 100644 index a62a03a1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/dependency-update-check.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -import org.gradle.kotlin.dsl.resolutionStrategy - -plugins { - id("com.github.ben-manes.versions") -} - -tasks.dependencyUpdates { - checkConstraints = true - resolutionStrategy { - componentSelection { - all { - val rejected = listOf("alpha", "beta", "rc", "cr", "m", "preview", "b", "ea") - .map { qualifier -> Regex("(?i).*[.-]$qualifier[.\\d-+]*") } - .any { it.matches(candidate.version) } - if (rejected) { - reject("Release candidate") - } - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts deleted file mode 100644 index cd32b2d6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-aggregation-conventions.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -import org.gradle.api.attributes.TestSuiteType -import org.gradle.kotlin.dsl.invoke -import org.gradle.kotlin.dsl.`jacoco-report-aggregation` -import org.gradle.testing.jacoco.plugins.JacocoCoverageReport - -plugins { - id("jacoco-conventions") - `jacoco-report-aggregation` -} - -reporting { - reports { - create("jacocoRootReport") { - testType.set(TestSuiteType.UNIT_TEST) - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts deleted file mode 100644 index 03e5c885..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - jacoco -} - -val enableJaCoCo = project.hasProperty("enableJaCoCo") - -jacoco { - toolVersion = requiredVersionFromLibs("jacoco") -} - -tasks.withType().configureEach { - enabled = enableJaCoCo -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts deleted file mode 100644 index 265ff893..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/jacoco-java-conventions.gradle.kts +++ /dev/null @@ -1,31 +0,0 @@ -import org.gradle.api.attributes.LibraryElements.CLASSES -import org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE - -plugins { - java - id("jacoco-conventions") -} - -val mavenizedProjects: List by rootProject.extra -val enableJaCoCo = project.hasProperty("enableJaCoCo") - -tasks.withType().configureEach { - configure { - isEnabled = enableJaCoCo - } -} - -val codeCoverageClassesJar by tasks.registering(Jar::class) { - from(tasks.jar.map { zipTree(it.archiveFile) }) - archiveClassifier.set("jacoco") - enabled = project in mavenizedProjects - duplicatesStrategy = DuplicatesStrategy.INCLUDE -} - -configurations.create("codeCoverageReportClasses") { - isCanBeResolved = false - isCanBeConsumed = true - isTransitive = false - attributes.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class, CLASSES)) - outgoing.artifact(codeCoverageClassesJar) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts deleted file mode 100644 index e2df86c8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts +++ /dev/null @@ -1,289 +0,0 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import org.junit.gradle.java.ModulePathArgumentProvider -import org.junit.gradle.java.PatchModuleArgumentProvider - -plugins { - `java-library` - eclipse - idea - checkstyle - id("base-conventions") - id("jacoco-java-conventions") -} - -val mavenizedProjects: List by rootProject.extra -val modularProjects: List by rootProject.extra -val buildDate: String by rootProject.extra -val buildTime: String by rootProject.extra -val buildRevision: Any by rootProject.extra -val builtByValue: String by rootProject.extra - -val extension = extensions.create("javaLibrary") - -val moduleSourceDir = file("src/module/$javaModuleName") -val moduleOutputDir = file("$buildDir/classes/java/module") -val javaVersion = JavaVersion.current() - -eclipse { - jdt { - file { - // Set properties for org.eclipse.jdt.core.prefs - withProperties { - // Configure Eclipse projects with -parameters compiler flag. - setProperty("org.eclipse.jdt.core.compiler.codegen.methodParameters", "generate") - } - } - } -} - -java { - modularity.inferModulePath.set(false) -} - -if (project in mavenizedProjects) { - - apply(plugin = "publishing-conventions") - apply(plugin = "osgi-conventions") - - java { - withJavadocJar() - withSourcesJar() - } - - tasks.javadoc { - options { - memberLevel = JavadocMemberLevel.PROTECTED - header = project.name - encoding = "UTF-8" - locale = "en" - (this as StandardJavadocDocletOptions).apply { - addBooleanOption("Xdoclint:all,-missing,-reference", true) - addBooleanOption("XD-Xlint:none", true) - addBooleanOption("html5", true) - addMultilineStringsOption("tag").value = listOf( - "apiNote:a:API Note:", - "implNote:a:Implementation Note:" - ) - use(true) - noTimestamp(true) - } - } - } - - tasks.named("javadocJar").configure { - from(tasks.javadoc.map { File(it.destinationDir, "element-list") }) { - // For compatibility with older tools, e.g. NetBeans 11 - rename { "package-list" } - } - } - - tasks.named("sourcesJar").configure { - from(moduleSourceDir) { - include("module-info.java") - } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - - pluginManager.withPlugin("java-test-fixtures") { - val javaComponent = components["java"] as AdhocComponentWithVariants - javaComponent.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } - javaComponent.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } - } - - configure { - publications { - named("maven") { - from(components["java"]) - versionMapping { - allVariants { - fromResolutionResult() - } - } - pom { - description.set(provider { "Module \"${project.name}\" of JUnit 5." }) - } - } - } - } - -} else { - tasks { - jar { - enabled = false - } - javadoc { - enabled = false - } - } -} - -tasks.withType().configureEach { - isPreserveFileTimestamps = false - isReproducibleFileOrder = true - dirMode = Integer.parseInt("0755", 8) - fileMode = Integer.parseInt("0644", 8) -} - -normalization { - runtimeClasspath { - metaInf { - // Ignore inconsequential JAR manifest attributes such as timestamps and the commit checksum. - // This is used when checking whether runtime classpaths, e.g. of test tasks, have changed and - // improves cacheability of such tasks. - ignoreAttribute("Built-By") - ignoreAttribute("Build-Date") - ignoreAttribute("Build-Time") - ignoreAttribute("Build-Revision") - ignoreAttribute("Created-By") - } - } -} - -val allMainClasses by tasks.registering { - dependsOn(tasks.classes) -} - -val compileModule by tasks.registering(JavaCompile::class) { - dependsOn(allMainClasses) - source = fileTree(moduleSourceDir) - destinationDirectory.set(moduleOutputDir) - sourceCompatibility = "9" - targetCompatibility = "9" - classpath = files() - options.release.set(9) - options.compilerArgs.addAll(listOf( - // Suppress warnings for automatic modules: org.apiguardian.api, org.opentest4j - "-Xlint:all,-requires-automatic,-requires-transitive-automatic", - "-Werror", // Terminates compilation when warnings occur. - "--module-version", "${project.version}", - )) - options.compilerArgumentProviders.add(objects.newInstance(ModulePathArgumentProvider::class, project, modularProjects)) - options.compilerArgumentProviders.addAll(modularProjects.map { objects.newInstance(PatchModuleArgumentProvider::class, project, it) }) - modularity.inferModulePath.set(false) -} - -tasks.withType().configureEach { - from(rootDir) { - include("LICENSE.md", "LICENSE-notice.md") - into("META-INF") - } - val suffix = archiveClassifier.getOrElse("") - if (suffix.isBlank() || this is ShadowJar) { - dependsOn(allMainClasses, compileModule) - from("$moduleOutputDir/$javaModuleName") { - include("module-info.class") - } - } -} - -tasks.jar { - manifest { - attributes( - "Created-By" to "${System.getProperty("java.version")} (${System.getProperty("java.vendor")} ${System.getProperty("java.vm.version")})", - "Built-By" to builtByValue, - "Build-Date" to buildDate, - "Build-Time" to buildTime, - "Build-Revision" to buildRevision, - "Specification-Title" to project.name, - "Specification-Version" to (project.version as String).substringBefore('-'), - "Specification-Vendor" to "junit.org", - "Implementation-Title" to project.name, - "Implementation-Version" to project.version, - "Implementation-Vendor" to "junit.org" - ) - } -} - -tasks.withType().configureEach { - outputs.doNotCacheIf("Shadow jar contains a Manifest with Build-Time") { true } -} - -tasks.withType().configureEach { - options.encoding = "UTF-8" -} - -tasks.compileJava { - // See: https://docs.oracle.com/en/java/javase/12/tools/javac.html - options.compilerArgs.addAll(listOf( - "-Xlint:all", // Enables all recommended warnings. - "-Werror" // Terminates compilation when warnings occur. - )) -} - -tasks.compileTestJava { - // See: https://docs.oracle.com/en/java/javase/12/tools/javac.html - options.compilerArgs.addAll(listOf( - "-Xlint", // Enables all recommended warnings. - "-Xlint:-overrides", // Disables "method overrides" warnings. - "-Werror", // Terminates compilation when warnings occur. - "-parameters" // Generates metadata for reflection on method parameters. - )) -} - -afterEvaluate { - configurations { - apiElements { - attributes { - attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, extension.mainJavaVersion.majorVersion.toInt()) - } - } - runtimeElements { - attributes { - attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, extension.mainJavaVersion.majorVersion.toInt()) - } - } - } - tasks { - compileJava { - if (extension.configureRelease) { - options.release.set(extension.mainJavaVersion.majorVersion.toInt()) - } else { - sourceCompatibility = extension.mainJavaVersion.majorVersion - targetCompatibility = extension.mainJavaVersion.majorVersion - } - } - compileTestJava { - if (extension.configureRelease) { - options.release.set(extension.testJavaVersion.majorVersion.toInt()) - } else { - sourceCompatibility = extension.testJavaVersion.majorVersion - targetCompatibility = extension.testJavaVersion.majorVersion - } - } - } - pluginManager.withPlugin("groovy") { - tasks.named("compileGroovy").configure { - // Groovy compiler does not support the --release flag. - sourceCompatibility = extension.mainJavaVersion.majorVersion - targetCompatibility = extension.mainJavaVersion.majorVersion - } - tasks.named("compileTestGroovy").configure { - // Groovy compiler does not support the --release flag. - sourceCompatibility = extension.testJavaVersion.majorVersion - targetCompatibility = extension.testJavaVersion.majorVersion - } - } -} - -checkstyle { - toolVersion = requiredVersionFromLibs("checkstyle") - configDirectory.set(rootProject.file("src/checkstyle")) -} - -tasks { - checkstyleMain { - configFile = rootProject.file("src/checkstyle/checkstyleMain.xml") - } - checkstyleTest { - configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") - } -} - -pluginManager.withPlugin("java-test-fixtures") { - tasks.named("checkstyleTestFixtures") { - configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") - } - tasks.named("compileTestFixturesJava") { - options.release.set(extension.testJavaVersion.majorVersion.toInt()) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts deleted file mode 100644 index 9b3155ab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-multi-release-sources.gradle.kts +++ /dev/null @@ -1,43 +0,0 @@ -plugins { - id("java-library-conventions") -} - -val mavenizedProjects: List by rootProject.extra - -listOf(9, 17).forEach { javaVersion -> - val sourceSet = sourceSets.register("mainRelease${javaVersion}") { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().output - java { - setSrcDirs(setOf("src/main/java${javaVersion}")) - } - } - - configurations.named(sourceSet.get().compileClasspathConfigurationName).configure { - extendsFrom(configurations.compileClasspath.get()) - } - - tasks { - - named("allMainClasses").configure { - dependsOn(sourceSet.get().classesTaskName) - } - - named(sourceSet.get().compileJavaTaskName).configure { - options.release.set(javaVersion) - } - - named("checkstyle${sourceSet.name.capitalize()}").configure { - configFile = rootProject.file("src/checkstyle/checkstyleMain.xml") - } - - if (project in mavenizedProjects) { - javadoc { - source(sourceSet.get().allJava) - } - named("sourcesJar").configure { - from(sourceSet.get().allSource) - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts deleted file mode 100644 index a000989b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-repackage-jars.gradle.kts +++ /dev/null @@ -1,52 +0,0 @@ -import java.util.jar.JarEntry -import java.util.jar.JarFile -import java.util.jar.JarOutputStream -import org.gradle.api.internal.file.archive.ZipCopyAction -import java.nio.file.Files - -// This registers a `doLast` action to rewrite the timestamps of the project's output JAR -afterEvaluate { - val jarTask = (tasks.findByName("shadowJar") ?: tasks["jar"]) as Jar - - jarTask.doLast { - - val newFile = Files.createTempFile("rewrite-timestamp", null).toFile() - val originalOutput = jarTask.archiveFile.get().asFile - - newFile.outputStream().use { os -> - - val newJarStream = JarOutputStream(os) - val oldJar = JarFile(originalOutput) - - fun sortAlwaysFirst(name: String): Comparator = - Comparator { a, b -> - when { - a.name == name -> -1 - b.name == name -> 1 - else -> 0 - } - } - - oldJar.entries() - .toList() - .distinctBy { it.name } - .sortedWith(sortAlwaysFirst("META-INF/") - .then(sortAlwaysFirst("META-INF/MANIFEST.MF")) - .thenBy { it.name }) - .forEach { entry -> - val jarEntry = JarEntry(entry.name) - - // Use the same constant as the fixed timestamps in normal copy actions - jarEntry.time = ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES - - newJarStream.putNextEntry(jarEntry) - - oldJar.getInputStream(entry).copyTo(newJarStream) - } - - newJarStream.finish() - } - - newFile.renameTo(originalOutput) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts deleted file mode 100644 index 87c334d2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension - -project.pluginManager.withPlugin("java") { - val javaToolchainVersion: String? by project - val defaultLanguageVersion = JavaLanguageVersion.of(17) - val javaLanguageVersion = javaToolchainVersion?.let { JavaLanguageVersion.of(it) } ?: defaultLanguageVersion - - val extension = the() - val javaToolchainService = the() - - extension.toolchain.languageVersion.set(javaLanguageVersion) - - pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { - configure { - jvmToolchain { - languageVersion.set(javaLanguageVersion) - } - } - } - - tasks.withType().configureEach { - javaLauncher.set(javaToolchainService.launcherFor(extension.toolchain)) - } - - tasks.withType().configureEach { - outputs.cacheIf { javaLanguageVersion == defaultLanguageVersion } - doFirst { - if (options.release.orNull == 8 && javaLanguageVersion.asInt() >= 20) { - options.compilerArgs.add( - "-Xlint:-options" // see https://github.com/junit-team/junit5/issues/3029 - ) - } - } - } - - tasks.withType().configureEach { - javaLauncher.set(javaToolchainService.launcherFor { - // Groovy does not yet support JDK 19, see https://issues.apache.org/jira/browse/GROOVY-10569 - languageVersion.set(defaultLanguageVersion) - }) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts deleted file mode 100644 index 89ca169b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/junit4-compatibility.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - `java-library` -} - -val junit_4_12 by configurations.creating { - extendsFrom(configurations.testRuntimeClasspath.get()) -} - -dependencies { - junit_4_12("junit:junit") { - version { - strictly("4.12") - } - } - pluginManager.withPlugin("osgi-conventions") { - val junit4Osgi = requiredVersionFromLibs("junit4Osgi") - "osgiVerification"("org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit:${junit4Osgi}") - } -} - -tasks { - val test_4_12 by registering(Test::class) { - classpath -= configurations.testRuntimeClasspath.get() - classpath += junit_4_12 - } - check { - dependsOn(test_4_12) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts deleted file mode 100644 index 16319094..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/kotlin-library-conventions.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("java-library-conventions") - kotlin("jvm") -} - -tasks.withType().configureEach { - kotlinOptions { - apiVersion = "1.3" - languageVersion = "1.3" - allWarningsAsErrors = false - } -} - -afterEvaluate { - val extension = project.the() - tasks { - withType().configureEach { - kotlinOptions.jvmTarget = extension.mainJavaVersion.toString() - } - named("compileTestKotlin") { - kotlinOptions.jvmTarget = extension.testJavaVersion.toString() - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt deleted file mode 100644 index 9c6423dc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/ClasspathSystemPropertyProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.junit.gradle.exec - -import org.gradle.api.file.FileCollection -import org.gradle.api.tasks.Classpath -import org.gradle.process.CommandLineArgumentProvider - -class ClasspathSystemPropertyProvider(private val propertyName: String, @get:Classpath val files: FileCollection) : CommandLineArgumentProvider { - override fun asArguments() = listOf("-D$propertyName=${files.asPath}") -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt deleted file mode 100644 index 88889bcf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/exec/RunConsoleLauncher.kt +++ /dev/null @@ -1,86 +0,0 @@ -package org.junit.gradle.exec - -import org.gradle.api.DefaultTask -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Classpath -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.SourceSetContainer -import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.get -import org.gradle.kotlin.dsl.the -import org.gradle.process.CommandLineArgumentProvider -import org.gradle.process.ExecOperations -import trackOperationSystemAsInput -import java.io.ByteArrayOutputStream -import javax.inject.Inject - -abstract class RunConsoleLauncher @Inject constructor(private val execOperations: ExecOperations): DefaultTask() { - - @get:Classpath - abstract val runtimeClasspath: ConfigurableFileCollection - - @get:Input - abstract val args: ListProperty - - @get:OutputDirectory - abstract val reportsDir: DirectoryProperty - - @get:Internal - abstract val debugging: Property - - @get:Internal - abstract val hideOutput: Property - - init { - runtimeClasspath.from(project.the()["test"].runtimeClasspath) - reportsDir.convention(project.layout.buildDirectory.dir("test-results")) - - debugging.convention( - project.providers.gradleProperty("consoleLauncherTestDebug") - .map { it != "false" } - .orElse(false) - ) - outputs.cacheIf { !debugging.get() } - outputs.upToDateWhen { !debugging.get() } - - hideOutput.convention(debugging.map { !it }) - - trackOperationSystemAsInput() - } - - @TaskAction - fun execute() { - val output = ByteArrayOutputStream() - val result = execOperations.javaexec { - classpath = runtimeClasspath - mainClass.set("org.junit.platform.console.ConsoleLauncher") - args("--scan-classpath") - args("--config=junit.platform.reporting.open.xml.enabled=true") - args(this@RunConsoleLauncher.args.get()) - argumentProviders += CommandLineArgumentProvider { - listOf( - "--reports-dir=${reportsDir.get()}", - "--config=junit.platform.reporting.output.dir=${reportsDir.get()}" - - ) - } - systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") - debug = debugging.get() - if (hideOutput.get()) { - standardOutput = output - errorOutput = output - } - isIgnoreExitValue = true - } - if (result.exitValue != 0 && hideOutput.get()) { - System.out.write(output.toByteArray()) - System.out.flush() - } - result.rethrowFailure().assertNormalExitValue() - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt deleted file mode 100644 index afc97f40..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ExecJarAction.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.junit.gradle.java - -import org.gradle.api.Action -import org.gradle.api.Task -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import org.gradle.jvm.tasks.Jar -import org.gradle.jvm.toolchain.JavaLauncher -import org.gradle.jvm.toolchain.JavaToolchainService -import org.gradle.jvm.toolchain.JavaToolchainSpec -import org.gradle.process.ExecOperations -import javax.inject.Inject - -abstract class ExecJarAction @Inject constructor(private val operations: ExecOperations): Action { - - abstract val javaLauncher: Property - - abstract val args: ListProperty - - override fun execute(t: Task) { - operations.exec { - executable = javaLauncher.get() - .metadata.installationPath.file("bin/jar").asFile.absolutePath - args = this@ExecJarAction.args.get() - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt deleted file mode 100644 index adb478c4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/ModulePathArgumentProvider.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.junit.gradle.java - -import org.gradle.api.Named -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.FileCollection -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -import org.gradle.kotlin.dsl.get -import org.gradle.process.CommandLineArgumentProvider -import javax.inject.Inject - -abstract class ModulePathArgumentProvider @Inject constructor(project: Project, modularProjects: List) : - CommandLineArgumentProvider, Named { - - @get:CompileClasspath - abstract val modulePath: ConfigurableFileCollection - - @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) - abstract val moduleSourceDirs: ConfigurableFileCollection - - init { - modulePath.from(project.configurations.named("compileClasspath")) - modularProjects.forEach { - moduleSourceDirs.from(project.files("${it.projectDir}/src/module")) - } - } - - override fun asArguments() = listOf( - "--module-path", - modulePath.asPath, - "--module-source-path", - moduleSourceDirs.asPath - ) - - @Internal - override fun getName() = "module-path" -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt deleted file mode 100644 index f13fd8b9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/PatchModuleArgumentProvider.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.junit.gradle.java - -import javaModuleName -import org.gradle.api.Named -import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.FileCollection -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -import org.gradle.kotlin.dsl.get -import org.gradle.kotlin.dsl.the -import org.gradle.process.CommandLineArgumentProvider -import javax.inject.Inject - -abstract class PatchModuleArgumentProvider @Inject constructor(compiledProject: Project, patchModuleProject: Project) : - CommandLineArgumentProvider, Named { - - @get:Input - abstract val module: Property - - @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) - abstract val patch: ConfigurableFileCollection - - init { - module.convention(patchModuleProject.javaModuleName) - patch.from(compiledProject.provider { - if (patchModuleProject == compiledProject) - compiledProject.files(compiledProject.the().matching { it.name.startsWith("main") } - .map { it.output }) - else - patchModuleProject.files(patchModuleProject.the()["main"].java.srcDirs) - }) - } - - override fun asArguments(): List { - val path = patch.filter { it.exists() }.asPath - if (path.isEmpty()) { - return emptyList() - } - return listOf("--patch-module", "${module.get()}=$path") - } - - @Internal - override fun getName() = "patch-module(${module.get()})" -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt deleted file mode 100644 index 12107fc8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/java/WriteArtifactsFile.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.junit.gradle.java - -import org.gradle.api.DefaultTask -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ModuleVersionIdentifier -import org.gradle.api.artifacts.ResolvedArtifact -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.provider.SetProperty -import org.gradle.api.tasks.* - -abstract class WriteArtifactsFile : DefaultTask() { - - @get:OutputFile - abstract val outputFile: RegularFileProperty - - @get:Input - abstract val moduleVersions: SetProperty - - fun from(configuration: Provider) { - moduleVersions.addAll(configuration.map { - it.resolvedConfiguration.resolvedArtifacts.map { it.moduleVersion.id } - }) - } - - @TaskAction - fun writeFile() { - outputFile.get().asFile.printWriter().use { out -> - moduleVersions.get() - .map { "${it.group}:${it.name}:${it.version}" } - .sorted() - .forEach(out::println) - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt deleted file mode 100644 index e9391b22..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/org/junit/gradle/javadoc/ModuleSpecificJavadocFileOption.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.junit.gradle.javadoc - -import org.gradle.external.javadoc.JavadocOptionFileOption -import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext - -class ModuleSpecificJavadocFileOption(private val option: String, private var valuePerModule: Map) : JavadocOptionFileOption> { - - override fun getOption() = option - - override fun getValue() = valuePerModule - - override fun setValue(value: Map) { - this.valuePerModule = value - } - - override fun write(writerContext: JavadocOptionFileWriterContext) { - valuePerModule.forEach { (moduleName, value) -> - writerContext - .writeOptionHeader(option) - .write(moduleName) - .write("=") - .write(value) - .newLine() - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts deleted file mode 100644 index 3a2bf7c6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/osgi-conventions.gradle.kts +++ /dev/null @@ -1,114 +0,0 @@ -import aQute.bnd.gradle.BundleTaskExtension -import aQute.bnd.gradle.Resolve - -plugins { - `java-library` -} - -val importAPIGuardian = "org.apiguardian.*;resolution:=\"optional\"" - -// This task enhances `jar` and `shadowJar` tasks with the bnd -// `BundleTaskExtension` extension which allows for generating OSGi -// metadata into the jar -tasks.withType().matching { task: Jar -> - task.name == "jar" || task.name == "shadowJar" -}.configureEach { - extra["importAPIGuardian"] = importAPIGuardian - - extensions.create(BundleTaskExtension.NAME, this).apply { - properties.set(provider { - mapOf("project.description" to project.description) - }) - // These are bnd instructions necessary for generating OSGi metadata. - // We've generalized these so that they are widely applicable limiting - // module configurations to special cases. - setBnd( - """ - # Set the Bundle-SymbolicName to the archiveBaseName. - # We don't use the archiveClassifier which Bnd will use - # in the default Bundle-SymbolicName value. - Bundle-SymbolicName: ${'$'}{task.archiveBaseName} - - # Set the Bundle-Name from the project description - Bundle-Name: ${'$'}{project.description} - - # These are the general rules for package imports. - Import-Package: \ - ${importAPIGuardian},\ - org.junit.platform.commons.logging;status=INTERNAL,\ - kotlin.*;resolution:="optional",\ - * - - # This tells bnd not to complain if a module doesn't actually import - # the kotlin and apiguardian packages, but enough modules do to make it a default. - -fixupmessages.kotlin.import: "Unused Import-Package instructions: \\[kotlin.*\\]";is:=ignore - -fixupmessages.apiguardian.import: "Unused Import-Package instructions: \\[org.apiguardian.*\\]";is:=ignore - - # This tells bnd to ignore classes it finds in `META-INF/versions/` - # because bnd doesn't yet support multi-release jars. - -fixupmessages.wrong.dir: "Classes found in the wrong directory: \\{META-INF/versions/...";is:=ignore - - # Don't scan for Class.forName package imports. - # See https://bnd.bndtools.org/instructions/noclassforname.html - -noclassforname: true - - # Don't add all the extra headers bnd normally adds. - # See https://bnd.bndtools.org/instructions/noextraheaders.html - -noextraheaders: true - - # Don't add the Private-Package header. - # See https://bnd.bndtools.org/instructions/removeheaders.html - -removeheaders: Private-Package - - # Instruct the APIGuardianAnnotations how to operate. - # See https://bnd.bndtools.org/instructions/export-apiguardian.html - -export-apiguardian: *;version=${'$'}{versionmask;===;${'$'}{version_cleanup;${'$'}{task.archiveVersion}}} - """ - ) - - // Do the actual work putting OSGi stuff in the jar. - doLast(buildAction()) - } -} - -// Bnd's Resolve task uses a properties file for its configuration. This -// task writes out the properties necessary for it to verify the OSGi -// metadata. -val osgiProperties by tasks.registering(WriteProperties::class) { - setOutputFile(layout.buildDirectory.file("verifyOSGiProperties.bndrun")) - property("-standalone", true) - project.extensions.getByType(JavaLibraryExtension::class.java).let { javaLibrary -> - property("-runee", "JavaSE-${javaLibrary.mainJavaVersion}") - } - property("-runrequires", "osgi.identity;filter:='(osgi.identity=${project.name})'") - property("-runsystempackages", "jdk.internal.misc,jdk.jfr,sun.misc") - // API Guardian should be optional -> instruct resolver to ignore it - // during resolution. Resolve should still pass. - property("-runblacklist", "org.apiguardian.api") -} - -val osgiVerification by configurations.creating { - extendsFrom(configurations.runtimeClasspath.get()) -} - -// Bnd's Resolve task is what verifies that a jar can be used in OSGi and -// that its metadata is valid. If the metadata is invalid this task will -// fail. -val verifyOSGi by tasks.registering(Resolve::class) { - bndrun.fileProvider(osgiProperties.map { it.outputFile }) - outputBndrun.set(layout.buildDirectory.file("resolvedOSGiProperties.bndrun")) - isReportOptional = false - // By default bnd will use jars found in: - // 1. project.sourceSets.main.runtimeClasspath - // 2. project.configurations.archives.artifacts.files - // to validate the metadata. - // This adds jars defined in `osgiVerification` also so that bnd - // can use them to validate the metadata without causing those to - // end up in the dependencies of those projects. - bundles(osgiVerification) - properties.empty() -} - -tasks.check { - dependsOn(verifyOSGi) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts deleted file mode 100644 index c4e28d5b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts +++ /dev/null @@ -1,115 +0,0 @@ -plugins { - `maven-publish` - signing - id("base-conventions") -} - -val isSnapshot = project.version.toString().contains("SNAPSHOT") -val isContinuousIntegrationEnvironment = System.getenv("CI")?.toBoolean() ?: false - -val jupiterProjects: List by rootProject -val platformProjects: List by rootProject -val vintageProjects: List by rootProject - -when (project) { - in jupiterProjects -> { - group = property("jupiterGroup")!! - } - in platformProjects -> { - group = property("platformGroup")!! - version = property("platformVersion")!! - } - in vintageProjects -> { - group = property("vintageGroup")!! - version = property("vintageVersion")!! - } -} - -// ensure project is built successfully before publishing it -tasks.withType().configureEach { - dependsOn(provider { - val tempRepoName: String by rootProject - if (repository.name != tempRepoName) { - listOf(tasks.build) - } else { - emptyList() - } - }) -} -tasks.withType().configureEach { - dependsOn(tasks.build) -} - -signing { - useGpgCmd() - sign(publishing.publications) - isRequired = !(isSnapshot || isContinuousIntegrationEnvironment) -} - -tasks.withType().configureEach { - onlyIf { - !isSnapshot // Gradle Module Metadata currently does not support signing snapshots - } -} - -publishing { - publications { - create("maven") { - pom { - name.set(provider { - project.description ?: "${project.group}:${project.name}" - }) - url.set("https://junit.org/junit5/") - scm { - connection.set("scm:git:git://github.com/junit-team/junit5.git") - developerConnection.set("scm:git:git://github.com/junit-team/junit5.git") - url.set("https://github.com/junit-team/junit5") - } - licenses { - license { - val license: License by rootProject.extra - name.set(license.name) - url.set(license.url.toString()) - } - } - developers { - developer { - id.set("bechte") - name.set("Stefan Bechtold") - email.set("stefan.bechtold@me.com") - } - developer { - id.set("jlink") - name.set("Johannes Link") - email.set("business@johanneslink.net") - } - developer { - id.set("marcphilipp") - name.set("Marc Philipp") - email.set("mail@marcphilipp.de") - } - developer { - id.set("mmerdes") - name.set("Matthias Merdes") - email.set("matthias.merdes@heidelpay.com") - } - developer { - id.set("sbrannen") - name.set("Sam Brannen") - email.set("sam@sambrannen.com") - } - developer { - id.set("sormuras") - name.set("Christian Stein") - email.set("sormuras@gmail.com") - } - developer { - id.set("juliette-derancourt") - name.set("Juliette de Rancourt") - email.set("derancourt.juliette@gmail.com") - } - } - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts deleted file mode 100644 index 19028422..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/shadow-conventions.gradle.kts +++ /dev/null @@ -1,69 +0,0 @@ -plugins { - id("java-library-conventions") - id("com.github.johnrengelman.shadow") -} - -val shadowed by configurations.creating - -configurations { - listOf(apiElements, runtimeElements).forEach { - it.configure { - outgoing { - artifacts.clear() - artifact(tasks.shadowJar) { - classifier = "" - } - } - } - } -} - -sourceSets { - main { - compileClasspath += shadowed - } - test { - runtimeClasspath += shadowed - } -} - -eclipse { - classpath { - plusConfigurations.add(shadowed) - } -} - -idea { - module { - scopes["PROVIDED"]!!["plus"]!!.add(shadowed) - } -} - -tasks { - javadoc { - classpath += shadowed - } - checkstyleMain { - classpath += shadowed - } - shadowJar { - configurations = listOf(shadowed) - exclude("META-INF/maven/**") - excludes.remove("module-info.class") - archiveClassifier.set("") - } - jar { - dependsOn(shadowJar) - enabled = false - } - named("codeCoverageClassesJar") { - from(shadowJar.map { zipTree(it.archiveFile) }) - exclude("**/shadow/**") - } - test { - dependsOn(shadowJar) - // in order to run the test against the shadowJar - classpath -= sourceSets.main.get().output - classpath += files(shadowJar.map { it.archiveFile }) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts deleted file mode 100644 index 9f82998c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/spotless-conventions.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -import org.gradle.kotlin.dsl.extra -import org.gradle.kotlin.dsl.provideDelegate - -plugins { - id("com.diffplug.spotless") -} - -val license: License by rootProject.extra - -spotless { - - format("misc") { - target("*.gradle.kts", "buildSrc/**/*.gradle.kts", "*.gitignore") - targetExclude("buildSrc/build/**") - indentWithTabs() - trimTrailingWhitespace() - endWithNewline() - } - - format("documentation") { - target("*.adoc", "*.md", "src/**/*.adoc", "src/**/*.md") - trimTrailingWhitespace() - endWithNewline() - } - - pluginManager.withPlugin("java") { - - val importOrderConfigFile = rootProject.file("src/eclipse/junit-eclipse.importorder") - val javaFormatterConfigFile = rootProject.file("src/eclipse/junit-eclipse-formatter-settings.xml") - - java { - licenseHeaderFile(license.headerFile, "(package|import|open|module) ") - importOrderFile(importOrderConfigFile) - eclipse().configFile(javaFormatterConfigFile) - trimTrailingWhitespace() - endWithNewline() - } - } - - pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { - kotlin { - targetExclude("**/src/test/resources/**") - ktlint(requiredVersionFromLibs("ktlint")) - licenseHeaderFile(license.headerFile) - trimTrailingWhitespace() - endWithNewline() - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts deleted file mode 100644 index d180a3a8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/temp-maven-repo.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.tasks.PublishToMavenRepository -import org.gradle.kotlin.dsl.* - -val tempRepoName by extra("temp") -val tempRepoDir by extra(file("$buildDir/repo")) - -val clearTempRepoDir by tasks.registering { - doFirst { - tempRepoDir.deleteRecursively() - } -} - -subprojects { - pluginManager.withPlugin("maven-publish") { - configure() { - repositories { - maven { - name = tempRepoName - url = uri(tempRepoDir) - } - } - } - tasks.withType().configureEach { - if (name.endsWith("To${tempRepoName.capitalize()}Repository")) { - dependsOn(clearTempRepoDir) - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts deleted file mode 100644 index d5a105f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/buildSrc/src/main/kotlin/testing-conventions.gradle.kts +++ /dev/null @@ -1,84 +0,0 @@ -import com.gradle.enterprise.gradleplugin.testretry.retry -import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL -import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED -import org.gradle.internal.os.OperatingSystem - -plugins { - `java-library` -} - -tasks.withType().configureEach { - useJUnitPlatform { - includeEngines("junit-jupiter") - } - include("**/*Test.class", "**/*Tests.class") - testLogging { - events = setOf(FAILED) - exceptionFormat = FULL - } - val isCiServer = System.getenv("CI") != null - retry { - maxRetries.set(providers.gradleProperty("retries").map(String::toInt).orElse(if (isCiServer) 2 else 0)) - } - distribution { - enabled.convention(providers.gradleProperty("enableTestDistribution") - .map(String::toBoolean) - .map { enabled -> enabled && (!isCiServer || System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY").isNotBlank()) } - .orElse(false)) - maxLocalExecutors.set(providers.gradleProperty("testDistribution.maxLocalExecutors").map(String::toInt).orElse(1)) - maxRemoteExecutors.set(providers.gradleProperty("testDistribution.maxRemoteExecutors").map(String::toInt)) - if (isCiServer) { - when { - OperatingSystem.current().isLinux -> requirements.add("os=linux") - OperatingSystem.current().isWindows -> requirements.add("os=windows") - OperatingSystem.current().isMacOsX -> requirements.add("os=macos") - } - } - } - predictiveSelection { - enabled.set(providers.gradleProperty("enablePredictiveTestSelection").map(String::toBoolean).orElse(true)) - } - systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") - // Required until ASM officially supports the JDK 14 - systemProperty("net.bytebuddy.experimental", true) - if (project.hasProperty("enableJFR")) { - jvmArgs( - "-XX:+UnlockDiagnosticVMOptions", - "-XX:+DebugNonSafepoints", - "-XX:StartFlightRecording=filename=${reports.junitXml.outputLocation.get()},dumponexit=true,settings=profile.jfc", - "-XX:FlightRecorderOptions=stackdepth=1024" - ) - } - - // Track OS as input so that tests are executed on all configured operating systems on CI - trackOperationSystemAsInput() - - // Avoid passing unnecessary environment variables to the JVM (from GitHub Actions) - if (isCiServer) { - environment.remove("RUNNER_TEMP") - environment.remove("GITHUB_ACTION") - } - - jvmArgumentProviders += CommandLineArgumentProvider { - listOf( - "-Djunit.platform.reporting.open.xml.enabled=true", - "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}" - ) - } -} - -dependencies { - testImplementation(dependencyFromLibs("assertj")) - testImplementation(dependencyFromLibs("mockito")) - - if (!project.name.startsWith("junit-jupiter")) { - testImplementation(project(":junit-jupiter")) - } - testImplementation(testFixtures(project(":junit-jupiter-api"))) - - testRuntimeOnly(project(":junit-platform-engine")) - testRuntimeOnly(project(":junit-platform-jfr")) - testRuntimeOnly(project(":junit-platform-reporting")) - - testRuntimeOnly(bundleFromLibs("log4j")) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/README.md b/src/main/java/com/junit-team-junit5-da216b8/documentation/README.md deleted file mode 100644 index f06f5b2f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# JUnit 5 User Guide - -This subproject contains the AsciiDoc sources for the JUnit 5 User Guide. - -## Structure - -- `src/docs/asciidoc`: AsciiDoc files -- `src/test/java`: Java test source code that can be included in the AsciiDoc files -- `src/test/kotlin`: Kotlin test source code that can be included in the AsciiDoc files -- `src/test/resources`: Classpath resources that can be included in the AsciiDoc files or - used in tests - -## Usage - -### Generate AsciiDoc - -This following Gradle command generates the HTML version of the User Guide as -`build/docs/asciidoc/user-guide/index.html`. - -``` -gradlew asciidoctor -``` - -On Linux operating systems, the `graphviz` package providing `/usr/bin/dot` must be -installed in order to generate the User Guide. - -### Generate AsciiDocPdf - -This following Gradle command generates the PDF version of the User Guide to -`build/docs/asciidocPdf/user-guide/index.pdf`. - -``` -gradlew asciidoctorPdf -``` diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts deleted file mode 100644 index bb832b1c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/documentation.gradle.kts +++ /dev/null @@ -1,457 +0,0 @@ -import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask -import org.gradle.api.tasks.PathSensitivity.RELATIVE -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet -import org.junit.gradle.exec.ClasspathSystemPropertyProvider -import org.junit.gradle.exec.RunConsoleLauncher -import org.junit.gradle.javadoc.ModuleSpecificJavadocFileOption -import java.io.ByteArrayOutputStream -import java.nio.file.Files - -plugins { - id("org.asciidoctor.jvm.convert") - id("org.asciidoctor.jvm.pdf") - id("org.ajoberstar.git-publish") - `kotlin-library-conventions` -} - -val modularProjects: List by rootProject - -// Because we need to set up Javadoc aggregation -modularProjects.forEach { evaluationDependsOn(it.path) } - -javaLibrary { - mainJavaVersion = JavaVersion.VERSION_1_8 - testJavaVersion = JavaVersion.VERSION_1_8 -} - -val apiReport by configurations.creating -val standaloneConsoleLauncher by configurations.creating - -dependencies { - implementation(projects.junitJupiterApi) { - because("Jupiter API is used in src/main/java") - } - - // Pull in all "modular projects" to ensure that they are included - // in reports generated by the ApiReportGenerator. - modularProjects.forEach { apiReport(it) } - - testImplementation(projects.junitJupiter) - testImplementation(projects.junitJupiterMigrationsupport) - testImplementation(projects.junitPlatformConsole) - testImplementation(projects.junitPlatformRunner) - testImplementation(projects.junitPlatformSuite) - testImplementation(projects.junitPlatformTestkit) - testImplementation(kotlin("stdlib")) - - testImplementation(projects.junitVintageEngine) - testRuntimeOnly(libs.bundles.log4j) - testRuntimeOnly(libs.apiguardian) { - because("it's required to generate API tables") - } - testRuntimeOnly(libs.openTestReporting.events) { - because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting") - } - - testImplementation(libs.classgraph) { - because("ApiReportGenerator needs it") - } - - standaloneConsoleLauncher(projects.junitPlatformConsoleStandalone) -} - -asciidoctorj { - modules { - diagram.use() - pdf.version(libs.versions.asciidoctor.pdf) - } -} - -val snapshot = rootProject.version.toString().contains("SNAPSHOT") -val docsVersion = if (snapshot) "snapshot" else rootProject.version -val releaseBranch = if (snapshot) "HEAD" else "r${rootProject.version}" -val docsDir = file("$buildDir/ghpages-docs") -val replaceCurrentDocs = project.hasProperty("replaceCurrentDocs") -val uploadPdfs = !snapshot -val userGuidePdfFileName = "junit-user-guide-${rootProject.version}.pdf" -val ota4jDocVersion = if (libs.versions.opentest4j.get().contains("SNAPSHOT")) "snapshot" else libs.versions.opentest4j.get() -val apiGuardianDocVersion = if (libs.versions.apiguardian.get().contains("SNAPSHOT")) "snapshot" else libs.versions.apiguardian.get() - -gitPublish { - repoUri.set("https://github.com/junit-team/junit5.git") - branch.set("gh-pages") - sign.set(false) - - contents { - from(docsDir) - into("docs") - } - - preserve { - include("**/*") - exclude("docs/$docsVersion/**") - if (replaceCurrentDocs) { - exclude("docs/current/**") - } - } -} - -val generatedAsciiDocPath = layout.buildDirectory.dir("generated/asciidoc") -val consoleLauncherOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-options.txt") } -val experimentalApisTableFile = generatedAsciiDocPath.map { it.file("experimental-apis-table.adoc") } -val deprecatedApisTableFile = generatedAsciiDocPath.map { it.file("deprecated-apis-table.adoc") } -val standaloneConsoleLauncherShadowedArtifactsFile = generatedAsciiDocPath.map { it.file("console-launcher-standalone-shadowed-artifacts.adoc") } - -val jdkJavadocBaseUrl = "https://docs.oracle.com/en/java/javase/11/docs/api" -val elementListsDir = file("$buildDir/elementLists") -val externalModulesWithoutModularJavadoc = mapOf( - "org.apiguardian.api" to "https://apiguardian-team.github.io/apiguardian/docs/$apiGuardianDocVersion/api/", - "org.assertj.core" to "https://javadoc.io/doc/org.assertj/assertj-core/${libs.versions.assertj.get()}/", - "org.opentest4j" to "https://ota4j-team.github.io/opentest4j/docs/$ota4jDocVersion/api/" -) -require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) { - "all base URLs must end with a trailing slash: $externalModulesWithoutModularJavadoc" -} - -tasks { - - val consoleLauncherTest by registering(RunConsoleLauncher::class) { - args.addAll("--config", "enableHttpServer=true") - args.addAll("--include-classname", ".*Tests") - args.addAll("--include-classname", ".*Demo") - args.addAll("--exclude-tag", "exclude") - } - - register("consoleLauncher") { - hideOutput.set(false) - reportsDir.set(layout.buildDirectory.dir("console-launcher")) - outputs.upToDateWhen { false } - args.addAll("--config", "enableHttpServer=true") - args.addAll("--include-classname", ".*Tests") - args.addAll("--include-classname", ".*Demo") - args.addAll("--exclude-tag", "exclude") - } - - test { - dependsOn(consoleLauncherTest) - exclude("**/*") - } - - val generateConsoleLauncherOptions by registering(JavaExec::class) { - classpath = sourceSets["test"].runtimeClasspath - mainClass.set("org.junit.platform.console.ConsoleLauncher") - args("--help", "--disable-banner") - redirectOutput(consoleLauncherOptionsFile) - } - - val generateExperimentalApisTable by registering(JavaExec::class) { - classpath = sourceSets["test"].runtimeClasspath - mainClass.set("org.junit.api.tools.ApiReportGenerator") - jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport) - args("EXPERIMENTAL") - redirectOutput(experimentalApisTableFile) - } - - val generateDeprecatedApisTable by registering(JavaExec::class) { - classpath = sourceSets["test"].runtimeClasspath - mainClass.set("org.junit.api.tools.ApiReportGenerator") - jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReport) - args("DEPRECATED") - redirectOutput(deprecatedApisTableFile) - } - - val generateStandaloneConsoleLauncherShadowedArtifactsFile by registering(Copy::class) { - from(zipTree(standaloneConsoleLauncher.elements.map { it.single().asFile })) { - include("META-INF/shadowed-artifacts") - includeEmptyDirs = false - eachFile { - relativePath = RelativePath(true, standaloneConsoleLauncherShadowedArtifactsFile.get().asFile.name) - } - filter { line -> "- `${line}`" } - } - into(standaloneConsoleLauncherShadowedArtifactsFile.map { it.asFile.parentFile }) - } - - withType().configureEach { - inputs.files( - generateConsoleLauncherOptions, - generateExperimentalApisTable, - generateDeprecatedApisTable, - generateStandaloneConsoleLauncherShadowedArtifactsFile - ) - - resources { - from(sourceDir) { - include("**/images/**/*.png") - include("**/images/**/*.svg") - } - } - - // Temporary workaround for https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/599 - inputs.dir(sourceDir).withPropertyName("sourceDir").withPathSensitivity(RELATIVE) - - attributes(mapOf( - "jupiter-version" to version, - "platform-version" to project.property("platformVersion"), - "vintage-version" to project.property("vintageVersion"), - "bom-version" to version, - "junit4-version" to libs.versions.junit4.get(), - "apiguardian-version" to libs.versions.apiguardian.get(), - "ota4j-version" to libs.versions.opentest4j.get(), - "surefire-version" to libs.versions.surefire.get(), - "release-branch" to releaseBranch, - "docs-version" to docsVersion, - "revnumber" to version, - "consoleLauncherOptionsFile" to consoleLauncherOptionsFile, - "experimentalApisTableFile" to experimentalApisTableFile, - "deprecatedApisTableFile" to deprecatedApisTableFile, - "standaloneConsoleLauncherShadowedArtifactsFile" to standaloneConsoleLauncherShadowedArtifactsFile, - "outdir" to outputDir.absolutePath, - "source-highlighter" to "rouge", - "tabsize" to "4", - "toc" to "left", - "icons" to "font", - "sectanchors" to true, - "idprefix" to "", - "idseparator" to "-", - "jdk-javadoc-base-url" to jdkJavadocBaseUrl - )) - - sourceSets["test"].apply { - attributes(mapOf( - "testDir" to java.srcDirs.first(), - "testResourcesDir" to resources.srcDirs.first() - )) - inputs.dir(java.srcDirs.first()) - inputs.dir(resources.srcDirs.first()) - withConvention(KotlinSourceSet::class) { - attributes(mapOf("kotlinTestDir" to kotlin.srcDirs.first())) - inputs.dir(kotlin.srcDirs.first()) - } - } - - forkOptions { - // To avoid warning, see https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597 - jvmArgs( - "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", - "--add-opens", "java.base/java.io=ALL-UNNAMED" - ) - } - } - - asciidoctor { - sources { - include("**/index.adoc") - } - resources { - from(sourceDir) { - include("tocbot-*/**") - } - } - attributes(mapOf( - "linkToPdf" to uploadPdfs, - "userGuidePdfFileName" to userGuidePdfFileName, - "releaseNotesUrl" to "../release-notes/index.html#release-notes" - )) - } - - asciidoctorPdf { - sources { - include("user-guide/index.adoc") - } - copyAllResources() - attributes(mapOf("releaseNotesUrl" to "https://junit.org/junit5/docs/$docsVersion/release-notes/")) - } - - val downloadJavadocElementLists by registering { - outputs.cacheIf { true } - outputs.dir(elementListsDir).withPropertyName("elementListsDir") - inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc) - doFirst { - externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> - val resource = resources.text.fromUri("${baseUrl}element-list") - elementListsDir.resolve(moduleName).apply { - mkdir() - resolve("element-list").writeText("module:$moduleName\n${resource.asString()}") - } - } - } - } - - val aggregateJavadocs by registering(Javadoc::class) { - dependsOn(modularProjects.map { it.tasks.jar }) - dependsOn(downloadJavadocElementLists) - group = "Documentation" - description = "Generates aggregated Javadocs" - - title = "JUnit $version API" - - val additionalStylesheetFile = "src/javadoc/junit-stylesheet.css" - inputs.file(additionalStylesheetFile) - val overviewFile = "src/javadoc/junit-overview.html" - inputs.file(overviewFile) - - options { - - memberLevel = JavadocMemberLevel.PROTECTED - header = rootProject.description - encoding = "UTF-8" - locale = "en" - overview = overviewFile - jFlags("-Xmx1g") - - this as StandardJavadocDocletOptions - splitIndex(true) - addBooleanOption("Xdoclint:all,-missing", true) - addBooleanOption("html5", true) - addMultilineStringsOption("tag").value = listOf( - "apiNote:a:API Note:", - "implNote:a:Implementation Note:" - ) - - links(jdkJavadocBaseUrl) - links("https://junit.org/junit4/javadoc/${libs.versions.junit4.get()}/") - externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> - linksOffline(baseUrl, "$elementListsDir/$moduleName") - } - - groups = mapOf( - "Jupiter" to listOf("org.junit.jupiter*"), - "Vintage" to listOf("org.junit.vintage*"), - "Platform" to listOf("org.junit.platform*") - ) - addStringOption("-add-stylesheet", additionalStylesheetFile) - use(true) - noTimestamp(true) - - addStringsOption("-module", ",").value = modularProjects.map { it.javaModuleName } - val moduleSourcePathOption = addPathOption("-module-source-path") - moduleSourcePathOption.value = modularProjects.map { it.file("src/module") } - moduleSourcePathOption.value.forEach { inputs.dir(it) } - addOption(ModuleSpecificJavadocFileOption("-patch-module", modularProjects.associate { - it.javaModuleName to files(it.sourceSets.matching { it.name.startsWith("main") }.map { it.allJava.srcDirs }).asPath - })) - addStringOption("-add-modules", "info.picocli") - addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf( - "org.junit.platform.console" to "info.picocli", - "org.junit.platform.reporting" to "org.opentest4j.reporting.events", - "org.junit.jupiter.params" to "univocity.parsers" - ))) - } - - source(modularProjects.map { files(it.sourceSets.matching { it.name.startsWith("main") }.map { it.allJava }) }) - classpath = files(modularProjects.map { it.sourceSets.main.get().compileClasspath }) - - maxMemory = "1024m" - destinationDir = file("$buildDir/docs/javadoc") - - doFirst { - (options as CoreJavadocOptions).modulePath = classpath.files.toList() - } - } - - val fixJavadoc by registering(Copy::class) { - dependsOn(aggregateJavadocs) - group = "Documentation" - description = "Fix links to external API specs in the locally aggregated Javadoc HTML files" - - val inputDir = aggregateJavadocs.map { it.destinationDir!! } - inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc) - from(inputDir.map { File(it, "element-list") }) { - // For compatibility with pre JDK 10 versions of the Javadoc tool - rename { "package-list" } - } - from(inputDir) { - filesMatching("**/*.html") { - val favicon = "" - filter { line -> - var result = if (line.startsWith("")) line.replace("", "$favicon") else line - externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> - result = result.replace("${baseUrl}$moduleName/", baseUrl) - } - return@filter result - } - } - } - into("$buildDir/docs/fixedJavadoc") - } - - val prepareDocsForUploadToGhPages by registering(Copy::class) { - dependsOn(fixJavadoc, asciidoctor, asciidoctorPdf) - outputs.dir(docsDir) - - from("$buildDir/checksum") { - include("published-checksum.txt") - } - from(asciidoctor.map { it.outputDir }) { - include("user-guide/**") - include("release-notes/**") - include("tocbot-*/**") - } - if (uploadPdfs) { - from(asciidoctorPdf.map { it.outputDir }) { - include("**/*.pdf") - rename { userGuidePdfFileName } - } - } - from(fixJavadoc.map { it.destinationDir }) { - into("api") - } - into("$docsDir/$docsVersion") - includeEmptyDirs = false - } - - val createCurrentDocsFolder by registering(Copy::class) { - dependsOn(prepareDocsForUploadToGhPages) - outputs.dir("$docsDir/current") - onlyIf { replaceCurrentDocs } - - from("$docsDir/$docsVersion") - into("$docsDir/current") - } - - val configureGitAuthor by registering { - dependsOn(gitPublishReset) - doFirst { - File(gitPublish.repoDir.get().asFile, ".git/config").appendText(""" - [user] - name = JUnit Team - email = team@junit.org - """.trimIndent()) - } - } - - gitPublishCopy { - dependsOn(prepareDocsForUploadToGhPages, createCurrentDocsFolder) - } - - gitPublishCommit { - dependsOn(configureGitAuthor) - } -} - -fun JavaExec.redirectOutput(outputFile: Provider) { - outputs.file(outputFile) - val byteStream = ByteArrayOutputStream() - standardOutput = byteStream - doLast { - outputFile.get().asFile.apply { - Files.createDirectories(parentFile.toPath()) - Files.write(toPath(), byteStream.toByteArray()) - } - } -} - -eclipse { - classpath { - plusConfigurations.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) - plusConfigurations.add(projects.junitJupiterParams.dependencyProject.configurations["shadowed"]) - } -} - -idea { - module { - scopes["PROVIDED"]!!["plus"]!!.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) - scopes["PROVIDED"]!!["plus"]!!.add(projects.junitJupiterParams.dependencyProject.configurations["shadowed"]) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html deleted file mode 100644 index 22136651..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo-footer.html +++ /dev/null @@ -1,33 +0,0 @@ - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html deleted file mode 100644 index 30e46e0a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/docinfos/docinfo.html +++ /dev/null @@ -1,18 +0,0 @@ - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc deleted file mode 100644 index 025ab6fc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/link-attributes.adoc +++ /dev/null @@ -1,190 +0,0 @@ -:javadoc-root: link:../api -ifdef::backend-pdf[] -:javadoc-root: https://junit.org/junit5/docs/{docs-version}/api -endif::[] -// Snapshot Repository -:snapshot-repo: https://oss.sonatype.org/content/repositories/snapshots -// Base Links -:junit-team: https://github.com/junit-team -:junit5-repo: {junit-team}/junit5 -:current-branch: {junit5-repo}/tree/{release-branch} -// Platform Commons -:junit-platform-support-package: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/package-summary.html[org.junit.platform.commons.support] -:AnnotationSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/AnnotationSupport.html[AnnotationSupport] -:ClassSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ClassSupport.html[ClassSupport] -:ModifierSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ModifierSupport.html[ModifierSupport] -:ReflectionSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ReflectionSupport.html[ReflectionSupport] -:Testable: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/annotation/Testable.html[@Testable] -// Platform Console Launcher -:junit-platform-console: {javadoc-root}/org.junit.platform.console/org/junit/platform/console/package-summary.html[junit-platform-console] -:ConsoleLauncher: {javadoc-root}/org.junit.platform.console/org/junit/platform/console/ConsoleLauncher.html[ConsoleLauncher] -// Platform Engine -:junit-platform-engine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/package-summary.html[junit-platform-engine] -:junit-platform-engine-support-discovery: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/package-summary.html[org.junit.platform.engine.support.discovery] -:DiscoverySelectors_selectMethod: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod-java.lang.String-[selectMethod(String) in DiscoverySelectors] -:HierarchicalTestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine] -:ParallelExecutionConfigurationStrategy: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy] -:TestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html[TestEngine] -// Platform Launcher API -:junit-platform-launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/package-summary.html[junit-platform-launcher] -:Launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/Launcher.html[Launcher] -:LauncherConfig: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherConfig.html[LauncherConfig] -:LauncherDiscoveryListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryListener.html[LauncherDiscoveryListener] -:LauncherDiscoveryRequest: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest] -:LauncherDiscoveryRequestBuilder: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder] -:LauncherFactory: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory] -:LauncherSession: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession] -:LauncherSessionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSessionListener.html[LauncherSessionListener] -:LoggingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/LoggingListener.html[LoggingListener] -:PostDiscoveryFilter: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/PostDiscoveryFilter.html[PostDiscoveryFilter] -:SummaryGeneratingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/SummaryGeneratingListener.html[SummaryGeneratingListener] -:TestExecutionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestExecutionListener.html[TestExecutionListener] -:TestPlan: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestPlan.html[TestPlan] -:UniqueIdTrackingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.html[UniqueIdTrackingListener] -// Platform Reporting -:LegacyXmlReportGeneratingListener: {javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.html[LegacyXmlReportGeneratingListener] -:OpenTestReportGeneratingListener: {javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.html[OpenTestReportGeneratingListener] -// Platform Runner -:JUnitPlatform-Runner: {javadoc-root}/org.junit.platform.runnner/org/junit/platform/runner/JUnitPlatform.html[JUnitPlatform] -// Platform Suite -:suite-api-package: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html[org.junit.platform.suite.api] -:junit-platform-suite-engine: {javadoc-root}/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html[junit-platform-suite-engine] -// Platform Test Kit -:testkit-engine-package: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/package-summary.html[org.junit.platform.testkit.engine] -:EngineExecutionResults: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineExecutionResults.html[EngineExecutionResults] -:EngineTestKit: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineTestKit.html[EngineTestKit] -:Event: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Event.html[Event] -:EventConditions: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventConditions.html[EventConditions] -:Events: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Events.html[Events] -:EventStatistics: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventStatistics.html[EventStatistics] -:EventType: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventType.html[EventType] -:Execution: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Execution.html[Execution] -:Executions: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Executions.html[Executions] -:TerminationInfo: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TerminationInfo.html[TerminationInfo] -:TestExecutionResultConditions: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TestExecutionResultConditions.html[TestExecutionResultConditions] -// Jupiter Core API -:api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/package-summary.html[org.junit.jupiter.api] -:Assertions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html[org.junit.jupiter.api.Assertions] -:Assumptions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assumptions.html[org.junit.jupiter.api.Assumptions] -:ClassOrderer_ClassName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.ClassName.html[ClassOrderer.ClassName] -:ClassOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.DisplayName.html[ClassOrderer.DisplayName] -:ClassOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation] -:ClassOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random] -:ClassOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer] -:Disabled: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled] -:MethodOrderer_Alphanumeric: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Alphanumeric.html[MethodOrderer.Alphanumeric] -:MethodOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName] -:MethodOrderer_MethodName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.MethodName.html[MethodOrderer.MethodName] -:MethodOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html[MethodOrderer.OrderAnnotation] -:MethodOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Random.html[MethodOrderer.Random] -:MethodOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.html[MethodOrderer] -:Named: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Named.html[Named] -:Order: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Order.html[@Order] -:RepetitionInfo: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/RepetitionInfo.html[RepetitionInfo] -:TestInfo: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestInfo.html[TestInfo] -:TestClassOrder: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestClassOrder.html[@TestClassOrder] -:TestMethodOrder: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestMethodOrder.html[@TestMethodOrder] -:TestReporter: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestReporter.html[TestReporter] -:TestTemplate: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestTemplate.html[@TestTemplate] -// Jupiter Parallel API -:Execution: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html[@Execution] -:Isolated: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Isolated.html[@Isolated] -:ResourceLock: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[@ResourceLock] -:Resources: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Resources.html[Resources] -// Jupiter Extension APIs -:extension-api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/package-summary.html[org.junit.jupiter.api.extension] -:AfterAllCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterAllCallback.html[AfterAllCallback] -:AfterEachCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterEachCallback.html[AfterEachCallback] -:AfterTestExecutionCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterTestExecutionCallback.html[AfterTestExecutionCallback] -:ParameterContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterContext.html[ParameterContext] -:BeforeAllCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback] -:BeforeEachCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback] -:BeforeTestExecutionCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback] -:ExecutableInvoker: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html[ExecutableInvoker] -:ExecutionCondition: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition] -:ExtendWith: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith] -:ExtensionContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.html[ExtensionContext] -:ExtensionContext_Store: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.Store.html[Store] -:InvocationInterceptor: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/InvocationInterceptor.html[InvocationInterceptor] -:LifecycleMethodExecutionExceptionHandler: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.html[LifecycleMethodExecutionExceptionHandler] -:ParameterResolver: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterResolver.html[ParameterResolver] -:RegisterExtension: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/RegisterExtension.html[@RegisterExtension] -:TestExecutionExceptionHandler: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html[TestExecutionExceptionHandler] -:TestInstanceFactory: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstanceFactory.html[TestInstanceFactory] -:TestInstancePostProcessor: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePostProcessor.html[TestInstancePostProcessor] -:TestInstancePreConstructCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.html[TestInstancePreConstructCallback] -:TestInstancePreDestroyCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.html[TestInstancePreDestroyCallback] -:TestTemplateInvocationContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext] -:TestTemplateInvocationContextProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider] -:TestWatcher: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestWatcher.html[TestWatcher] -// Jupiter Conditions -:DisabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledForJreRange.html[@DisabledForJreRange] -:DisabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf] -:DisabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.html[@DisabledIfEnvironmentVariable] -:DisabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfSystemProperty.html[@DisabledIfSystemProperty] -:DisabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledInNativeImage.html[@DisabledInNativeImage] -:DisabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnJre.html[@DisabledOnJre] -:DisabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnOs.html[@DisabledOnOs] -:EnabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledForJreRange.html[@EnabledForJreRange] -:EnabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf] -:EnabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.html[@EnabledIfEnvironmentVariable] -:EnabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty] -:EnabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledInNativeImage.html[@EnabledInNativeImage] -:EnabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre] -:EnabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs] -:JRE: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/JRE.html[JRE] -// Jupiter I/O -:TempDir: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDir.html[@TempDir] -// Jupiter Params -:params-provider-package: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html[org.junit.jupiter.params.provider] -:ArgumentsAccessor: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAccessor.html[ArgumentsAccessor] -:ArgumentsAggregator: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAggregator.html[ArgumentsAggregator] -:EmptySource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/EmptySource.html[@EmptySource] -:MethodSource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html[@MethodSource] -:NullAndEmptySource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullAndEmptySource.html[@NullAndEmptySource] -:NullSource: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullSource.html[@NullSource] -:ParameterizedTest: {javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html[@ParameterizedTest] -// Jupiter Engine -:junit-jupiter-engine: {javadoc-root}/org.junit.jupiter.engine/org/junit/jupiter/engine/package-summary.html[junit-jupiter-engine] -// Jupiter Extension Implementations -:DisabledCondition: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition] -:RepetitionInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java[RepetitionInfoParameterResolver] -:TempDirectory: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java[TempDirectory] -:TestInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java[TestInfoParameterResolver] -:TestReporterParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java[TestReporterParameterResolver] -:TypeBasedParameterResolver: {current-branch}/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java[TypeBasedParameterResolver] -// Jupiter Examples -:CustomAnnotationParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java[CustomAnnotationParameterResolver] -:CustomTypeParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java[CustomTypeParameterResolver] -:MapOfListsTypeBasedParameterResolver: {current-branch}/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java[MapOfListsTypeBasedParameterResolver] -// Jupiter Migration Support -:EnableJUnit4MigrationSupport: {javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.html[@EnableJUnit4MigrationSupport] -:EnableRuleMigrationSupport: {javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.html[@EnableRuleMigrationSupport] -// Vintage -:junit-vintage-engine: {javadoc-root}/org.junit.vintage.engine/org/junit/vintage/engine/package-summary.html[junit-vintage-engine] -// Samples Repository -:junit5-samples-repo: {junit-team}/junit5-samples -:junit5-jupiter-starter-ant: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-ant[junit5-jupiter-starter-ant] -:junit5-jupiter-starter-gradle-groovy: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-gradle-groovy[junit5-jupiter-starter-gradle-groovy] -:junit5-jupiter-starter-gradle-kotlin: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-gradle-kotlin[junit5-jupiter-starter-gradle-kotlin] -:junit5-jupiter-starter-gradle: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-gradle[junit5-jupiter-starter-gradle] -:junit5-jupiter-starter-maven: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-starter-maven[junit5-jupiter-starter-maven] -:RandomParametersExtension: {junit5-samples-repo}/tree/{release-branch}/junit5-jupiter-extensions/src/main/java/com/example/random/RandomParametersExtension.java[RandomParametersExtension] -// Third-party Links -:API: https://apiguardian-team.github.io/apiguardian/docs/current/api/[@API] -:API_Guardian: https://github.com/apiguardian-team/apiguardian[@API Guardian] -:AssertJ: https://joel-costigliola.github.io/assertj/[AssertJ] -:Gitter: https://gitter.im/junit-team/junit5[Gitter] -:Hamcrest: https://hamcrest.org/JavaHamcrest/[Hamcrest] -:Log4j: https://logging.apache.org/log4j/2.x/[Log4j] -:Log4j_JDK_Logging_Adapter: https://logging.apache.org/log4j/2.x/log4j-jul/index.html[Log4j JDK Logging Adapter] -:Logback: https://logback.qos.ch/[Logback] -:LogManager: https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html[LogManager] -:Maven_Central: https://search.maven.org/[Maven Central] -:MockitoExtension: https://github.com/mockito/mockito/blob/release/2.x/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java[MockitoExtension] -:ServiceLoader: {jdk-javadoc-base-url}/java.base/java/util/ServiceLoader.html[ServiceLoader] -:SpringExtension: https://github.com/spring-projects/spring-framework/tree/HEAD/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java[SpringExtension] -:StackOverflow: https://stackoverflow.com/questions/tagged/junit5[Stack Overflow] -:Truth: https://truth.dev/[Truth] -:OpenTestReporting: https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] -:OpenTestReportingCliTool: https://github.com/ota4j-team/open-test-reporting#cli-tool-for-validation-and-format-conversion[Open Test Reporting CLI Tool] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc deleted file mode 100644 index 6b41a364..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/index.adoc +++ /dev/null @@ -1,31 +0,0 @@ -[[release-notes]] -= JUnit 5 Release Notes -Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juliette de Rancourt; Christian Stein -// -:basedir: {includedir}/release-notes -:docinfodir: {includedir}/docinfos -:docinfo2: -:numbered!: -// - -This document contains the _change log_ for all JUnit 5 releases since 5.8 GA. - -Please refer to the <<../user-guide/index.adoc#user-guide,User Guide>> for comprehensive -reference documentation for programmers writing tests, extension authors, and engine -authors as well as build tool and IDE vendors. - -include::{includedir}/link-attributes.adoc[] - -include::{basedir}/release-notes-5.9.3.adoc[] - -include::{basedir}/release-notes-5.9.2.adoc[] - -include::{basedir}/release-notes-5.9.1.adoc[] - -include::{basedir}/release-notes-5.9.0.adoc[] - -include::{basedir}/release-notes-5.8.2.adoc[] - -include::{basedir}/release-notes-5.8.1.adoc[] - -include::{basedir}/release-notes-5.8.0.adoc[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc deleted file mode 100644 index fc0763c6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0.adoc +++ /dev/null @@ -1,21 +0,0 @@ -[[release-notes-5.8.0]] -== 5.8.0 - -*Date of Release:* September 12, 2021 - -*Scope:* - -* Declarative test suites via `@Suite` classes -* `LauncherSession` and accompanying listener -* New `UniqueIdTrackingListener` -* More fine-grained Java Flight Recorder events -* Java Flight Recorder support on Java 8 Update 262 or higher -* Test class ordering -* `@TempDir` can be used to create multiple temporary directories -* Extension registration via `@ExtendWith` on fields and parameters -* Auto-close support for arguments in `@ParameterizedTest` methods -* Memory and performance optimizations -* Numerous bug fixes and minor improvements - -For complete details consult the -https://junit.org/junit5/docs/5.8.0/release-notes/index.html[5.8.0 Release Notes] online. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc deleted file mode 100644 index 8a54b4f5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.1.adoc +++ /dev/null @@ -1,62 +0,0 @@ -[[release-notes-5.8.1]] -== 5.8.1 - -*Date of Release:* September 22, 2021 - -*Scope:* - -* Support for _text blocks_ in `@CsvSource` -* Java 18 support in the `JRE` enum -* Access to the `ExecutionMode` in the `ExtensionContext` -* Minor bug fixes and enhancements since 5.8.0 - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/59?closed=1+[5.8.1] milestone page in the JUnit repository on -GitHub. - - -[[release-notes-5.8.1-junit-platform]] -=== JUnit Platform - -==== Deprecations and Breaking Changes - -* `@UseTechnicalNames` has been deprecated in favor of the new `@Suite` support which does - not require the use of technical names. See the warning in - <<../user-guide/index.adoc#running-tests-junit-platform-runner, Using JUnit 4 to run the - JUnit Platform>> for details. - -==== New Features and Improvements - -* `ReflectionSupport.findNestedClasses(..)` is now thread-safe with regard to cycle - detection. - - -[[release-notes-5.8.1-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* `assertLinesMatch()` in `Assertions` no longer fails with a `NoSuchElementException` if - a limited fast-forward followed by at least one more expected line exceeds the remaining - actual lines. -* `assertLinesMatch()` in `Assertions` now handles fast-forwards with leading and trailing - spaces correctly and no longer throws an `IndexOutOfBoundsException`. - -==== New Features and Improvements - -* `JAVA_18` has been added to the `JRE` enum for use with JRE-based execution conditions. -* CSV content in `@CsvSource` can now be supplied as a _text block_ instead of an array of - strings. See the - <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User - Guide>> for details and an example. -* The `ExecutionMode` for the current test or container is now accessible via the - `ExtensionContext`. - - -[[release-notes-5.8.1-junit-vintage]] -=== JUnit Vintage - -==== Bug Fixes - -* Relaxed version constraint in published Gradle Module Metadata to allow downgrading the - `junit:junit` dependency from 4.13.2. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc deleted file mode 100644 index 831cc74a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc +++ /dev/null @@ -1,45 +0,0 @@ -[[release-notes-5.8.2]] -== 5.8.2 - -*Date of Release:* November 28, 2021 - -*Scope:* - -* Text blocks in `@CsvSource` are treated like CSV files -* CSV headers in display names for `@CsvSource` and `@CsvFileSource` -* Custom quote character support in `@CsvSource` and `@CsvFileSource` - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/60?closed=1+[5.8.2] milestone page in the JUnit repository on -GitHub. - - -[[release-notes-5.8.2-junit-platform]] -=== JUnit Platform - -No changes. - - -[[release-notes-5.8.2-junit-jupiter]] -=== JUnit Jupiter - -==== New Features and Improvements - -* Text blocks in `@CsvSource` are now treated like complete CSV files, including support - for comments beginning with a `+++#+++` symbol as well as support for new lines within - quoted strings. See the - <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User - Guide>> for details and examples. -* CSV headers can now be used in display names in parameterized tests. See - <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, - `@CsvSource`>> and - <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvFileSource, - `@CsvFileSource`>> in the User Guide for details and examples. -* The quote character for _quoted strings_ in `@CsvSource` and `@CsvFileSource` is now - configurable via a new `quoteCharacter` attribute in each annotation. - - -[[release-notes-5.8.2-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc deleted file mode 100644 index aa292b95..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc +++ /dev/null @@ -1,148 +0,0 @@ -[[release-notes-5.9.0]] -== 5.9.0 - -*Date of Release:* July 26, 2022 - -*Scope:* - -* XML reports in new https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] -format -* Configurable cleanup mode for `@TempDir` -* Configurable thread mode for `@Timeout` -* Conditional execution based on OS architectures -* New `TestInstancePreConstructCallback` extension API -* Reusable parameter resolution for custom extension methods via `ExecutableInvoker` -* Parameter injection for `@MethodSource` methods -* New `IterationSelector` -* Various improvements to `ConsoleLauncher` - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/58?closed=1+[5.9 M1], -link:{junit5-repo}+/milestone/61?closed=1+[5.9 RC1], and -link:{junit5-repo}+/milestone/62?closed=1+[5.9 GA] milestone pages in the JUnit repository -on GitHub. - - -[[release-notes-5.9.0-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* Fixed handling of global post-discovery filters that apply to `@Suite` classes. -* Since the Turkish language has special characters such as 'ı' and 'İ', the uppercase - conversion in `DefaultParallelExecutionConfigurationStrategy#getStrategy` previously - caused all tests to finish with exit code -1. This has been fixed by using the root - locale instead of the default one. -* Absolute path entries are now supported in JUnit's Platform Console Launcher on Windows. -* Attempts to load a `Class` for an invalid class name representing an extremely large - multidimensional array now fail within a reasonable amount of time. -* Fix concurrency issue in classpath scanning. - -==== Deprecations and Breaking Changes - -* `ConfigurationParameters.size()` has been deprecated in favor of the new `keySet()` - method. - -==== New Features and Improvements - -* Support for the https://github.com/ota4j-team/open-test-reporting[Open Test Reporting] - format which supports all features of the JUnit Platform such as hierarchical test - structures, display names, tags, etc. Please refer to the - <<../user-guide/index.adoc#junit-platform-reporting-open-test-reporting, User Guide>> - for instructions on how to enable such reports in your build. -* `ConfigurationParameters` has a new `keySet()` method which allows you to retrieve all - configuration parameter keys. -* New `IterationSelector` for selecting a subset of a test's or container's iterations. -* `ParallelExecutionConfiguration` allows configuring the `saturate` predicate of the - `ForkJoinPool` used for parallel test execution. -* JUnit OSGi bundles now contain `engine` and `launcher` requirements ensuring that at - resolution time a fully running set of dependencies is calculated, avoiding the need for - these to be manually specified. -* JUnit Platform Standalone Console JAR now also includes the JUnit Platform Suite Engine. -* New `failIfNoTests` attribute added to `@Suite`. This will fail the suite if no tests - are discovered. -* The output color for the `ConsoleLauncher` can now be customized. The option - `--single-color` will apply a built-in monochrome style, while `--color-palette` will - accept a properties file. See the - <<../user-guide/index.adoc#running-tests-console-launcher-color-customization, - User Guide>> for details. -* The default theme for the `ConsoleLauncher` is now determined by the charset reported by - the system console on Java 17 and later. -* New `--list-engines` option added to the `ConsoleLauncher` which displays all registered - test engine implementations. -* Configuring included `EngineFilters` that do not match any registered `TestEngine` - results in an error to avoid misconfiguration – for example, due to typos. -* New public factory method to instantiate an `ExecutionRequest`. -* Documentation for overriding the JUnit version used in Spring Boot applications. See the - <<../user-guide/index.adoc#running-tests-build-spring-boot, User Guide>> for details. - - -[[release-notes-5.9.0-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* When cleaning up a `@TempDir`, only one retry attempt will be made to delete directories. -* Since the Turkish language has special characters such as 'ı' and 'İ', the uppercase - conversion in `DefaultParallelExecutionConfigurationStrategy#getStrategy` previously - caused all tests to finish with exit code -1. This has been fixed by using the root - locale instead of the default one. - -==== Deprecations and Breaking Changes - -* `@TempDir` fields are no longer allowed to be declared as `final`. - - This improves diagnostics for failures resulting from a user-declared `static final` - `@TempDir` field by throwing an exception with an informative error message. -* Private lifecycle methods (annotated with `@BeforeAll`, `@AfterAll`, `@BeforeEach`, or - `@AfterEach`) now correctly lead to an exception. Although this is a bug fix, it is - technically also a breaking change since there might be existing user code with - `private` lifecycle methods which will now start to fail. - -==== New Features and Improvements - -* `@TempDir` now includes a `cleanup` mode attribute for preventing a temporary directory - from being deleted after a test. The default cleanup mode can be configured via a - configuration parameter. -* Support for FreeBSD and OpenBSD operating systems in `@EnabledOnOs` and `@DisabledOnOs`. -* New `MATCH_NONE` mode for `@EnumSource` that selects only those enum constants whose - names match none of the supplied patterns. -* The `@Order` annotation is now a `STABLE` API. -* New `TestInstancePreConstructCallback` extension API that is called prior to test - instance construction – symmetric to the existing `TestInstancePreDestroyCallback` - extension API. -* Extensions can now leverage registered `ParameterResolver` extensions when invoking - methods and constructors via the new `ExecutableInvoker` API available in the - `ExtensionContext`. -* A subset of the invocations of parameterized or dynamic tests can now be selected via - the new `IterationSelector` discovery selector when launching the JUnit Platform. -* `JAVA_19` and `JAVA_20` have been added to the `JRE` enum for use with JRE-based - execution conditions. -* `@MethodSource` factory methods can now accept arguments resolved by registered - `ParameterResolver` extensions. -* `AssertionFailureBuilder` allows reusing Jupiter's logic for creating failure messages - to assist in writing custom assertion methods. -* Three new `abort` methods have been added to the `Assumptions` class. These are - analogous to the `fail` methods in the `Assertions` class, but instead of failing they - abort the test or container. -* Support for enabling/disabling tests based on the system's hardware architecture via new - `architectures` attributes in `@EnabledOnOs` and `@DisabledOnOs`. -* Default `@MethodSource` factory methods can now accept arguments. A _default_ factory - method is a method declared in the test class with the same name as the - `@ParameterizedTest` method that is inferred as the factory method when no explicit - factory method is specified in the `@MethodSource` annotation. -* Thread mode can be set on `@Timeout` annotation. It allows to configure whether test - code is executed in the thread of the calling code or in a separate thread. The three - modes are: `INFERRED` (default) which resolves the thread mode configured via the - property `junit.jupiter.execution.timeout.thread.mode.default`, `SAME_THREAD` that - executes the test code in the same thread as the calling code, and `SEPARATE_THREAD` - which executes it in a separate thread. - - -[[release-notes-5.9.0-junit-vintage]] -=== JUnit Vintage - -==== New Features and Improvements - -* More accurate reporting of intermediate start/finish events, e.g. iterations of the - `Parameterized` runner and classes executed indirectly via the `Suite` runner, when - running with JUnit 4.13 or later. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc deleted file mode 100644 index 3c828153..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ /dev/null @@ -1,53 +0,0 @@ -[[release-notes-5.9.1]] -== 5.9.1 - -*Date of Release:* September 20, 2022 - -*Scope:* - -* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for testing in - GraalVM native images. -* Minor bug fixes and enhancements since 5.9.0 - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/63?closed=1+[5.9.1] milestone page in the JUnit repository -on GitHub. - - -[[release-notes-5.9.1-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* `ReflectionSupport.findMethods(...)` now returns a distinct set of methods. -* Execution in GraalVM native images no longer requires `--initialize-at-build-time` for - `OpenTestReportGeneratingListener`. - - -[[release-notes-5.9.1-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* Headers provided via the `value` attribute in `@CsvSource` for a `@ParameterizedTest` - are now properly parsed when the `useHeadersInDisplayName` attribute is set to `true`. -* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that - references a factory method inherited from multiple interfaces no longer fails with an - exception stating that multiple factory methods with the same name were found. -* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that - references a factory method whose name is the same as other non-factory methods in the - same class no longer fails with an exception stating that multiple factory methods with - the same name were found. -* Assertion failures thrown from methods with applied timeouts using `ThreadMode.SEPARATE` - are now properly reported. - -==== New Features and Improvements - -* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for enabling and - disabling tests within a GraalVM native image. - - -[[release-notes-5.9.1-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc deleted file mode 100644 index 249e805d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.2.adoc +++ /dev/null @@ -1,61 +0,0 @@ -[[release-notes-5.9.2]] -== 5.9.2 - -*Date of Release:* January 10, 2023 - -*Scope:* Bug fixes and enhancements since 5.9.1 - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestones/5.9.2+[5.9.2] milestone page in the JUnit repository on -GitHub. - - -[[release-notes-5.9.2-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* The Java 7 based constructor for `ForkJoinPool` is no longer accidentally used on Java 9 - or higher when invalid `ParallelExecutionConfiguration` is provided. Instead, an - exception is thrown for invalid configuration, thereby preventing invalid configuration - from being silently ignored. - -==== New Features and Improvements - -* New `TestPlan.getTestIdentifier(UniqueId)` and `TestPlan.getChildren(UniqueId)` methods - to avoid parsing unique IDs unnecessarily during test execution. -* Support for limiting the `max-pool-size` for parallel execution via a configuration - parameter. -* Suite discovery now ignores cycles encountered in a test suite and logs an informational - message at `CONFIG` level instead of throwing an exception. - - -[[release-notes-5.9.2-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* New `@MethodSource` syntax for explicitly selecting an overloaded local factory method - without specifying its fully qualified name. - -==== Deprecations and Breaking Changes - -* The `fixed` parallel execution strategy now allows the thread pool to be saturated by - default. - -==== New Features and Improvements - -* `JAVA_21` has been added to the `JRE` enum for use with JRE-based execution conditions. -* New `junit.jupiter.execution.parallel.config.fixed.max-pool-size` configuration - parameter to set the maximum pool size. -* New `junit.jupiter.execution.parallel.config.fixed.saturate` configuration parameter to - disable pool saturation. - - -[[release-notes-5.9.2-junit-vintage]] -=== JUnit Vintage - -==== Bug Fixes - -* `Parameterized` tests are now properly reported when used in combination with the - `Enclosed` runner. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc deleted file mode 100644 index 09dc2a8e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.3.adoc +++ /dev/null @@ -1,59 +0,0 @@ -[[release-notes-5.9.3]] -== 5.9.3 - -*Date of Release:* April 26, 2023 - -*Scope:* Bug fixes and enhancements since 5.9.2 - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/67?closed=1+[5.9.3] milestone page in the -JUnit repository on GitHub. - - -[[release-notes-5.9.3-junit-platform]] -=== JUnit Platform - -No changes. - - -[[release-notes-5.9.3-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* Parameter types for _local_ `@MethodSource` factory method names are now validated. For - example, `@MethodSource("myFactory(example.NonexistentType)")` will now result in an - exception stating that `example.NonexistentType` cannot be resolved to a valid type. -* The syntax for parameter types in _local_ `@MethodSource` factory method names now - supports canonical array names -- for example, you may now specify `int[]` as in - `@MethodSource("myFactory(int[])"` instead of the _binary_ name `[I` as in - `@MethodSource("myFactory([I)"` (which was already supported) and - `@MethodSource("myFactory(java.lang.String[])` instead of - `@MethodSource("myFactory([Ljava.lang.String;)`. -* The search algorithm used to find `@MethodSource` factory methods now applies consistent - semantics for _local_ qualified method names and fully-qualified method names for - overloaded factory methods. -* The `+{displayName}+` placeholder for `@ParameterizedTest` invocation names is no longer - parsed using `java.text.MessageFormat`.Consequently, any text in the display name of - the `@ParameterizedTest` method will be included unmodified in the invocation display - name.For example, Kotlin method names and custom display names configured via - `@DisplayName` can now contain apostrophes (`'`) as well as text resembling - `MessageFormat` elements such as `+{0}+` or `+{data}+`. -* Exceptions thrown for files that cannot be deleted when cleaning up a temporary - directory created via `@TempDir` now include the root cause. -* Lifecycle methods are allowed to be declared as `private` again for backwards - compatibility; however, using `private` visibility for lifecycle methods is strongly - discouraged and will be disallowed in a future release. - -==== New Features and Improvements - -* The search algorithm used to find `@MethodSource` factory methods now falls back to - lenient search semantics when a factory method cannot be found by qualified name - (without a parameter list) and also provides better diagnostics when a unique factory - method cannot be found. - - -[[release-notes-5.9.3-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc deleted file mode 100644 index ef56e8c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/release-notes/release-notes-TEMPLATE.adoc +++ /dev/null @@ -1,73 +0,0 @@ -// TODO: -// -// 1) Make a copy of this template file, replacing TEMPLATE in the file name with the -// current version (for example, 5.10.0-M1, 5.10.0-RC1, 5.10.0). -// 2) Open the new file for editing. -// 3) Replace all occurrences of VERSION with the current version (for example, 5.10.0-M1, -// 5.10.0-RC1, 5.10.0). The same version must be used in the file name and within the -// file. -// 4) Replace MILESTONE_NUMBER with the appropriate milestone number. This is an integer -// which you can determine via https://github.com/junit-team/junit5/milestones/. If a -// GitHub milestone does not yet exist for the given VERSION, you will need to create -// a new GitHub milestone or ask a member of the JUnit team to create it for you. -// 5) 'include:' this new file in index.adoc. -// 6) Delete this entire comment block. -// -[[release-notes-VERSION]] -== VERSION - -*Date of Release:* ❓ - -*Scope:* ❓ - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/MILESTONE_NUMBER?closed=1+[VERSION] milestone page in the -JUnit repository on GitHub. - - -[[release-notes-VERSION-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* ❓ - -==== Deprecations and Breaking Changes - -* ❓ - -==== New Features and Improvements - -* ❓ - - -[[release-notes-VERSION-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* ❓ - -==== Deprecations and Breaking Changes - -* ❓ - -==== New Features and Improvements - -* ❓ - - -= [[release-notes-VERSION-junit-vintage]] -=== JUnit Vintage - -==== Bug Fixes - -* ❓ - -==== Deprecations and Breaking Changes - -* ❓ - -==== New Features and Improvements - -* ❓ diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/fonts/Symbola.ttf b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/fonts/Symbola.ttf deleted file mode 100644 index 055f02558b9a1edd0036bb1f48f435b250d474bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2440452 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%0DX?)du0 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Ó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 ^ij~u(C>=z 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ÕLzM%u{gXc%dOmJ9T`pgLNO&jr=dCO1E2v?fzJ`HT3XUbd@~ zFQl%S;CeBIv0?EQrxO0=$fD^D6t`QWEs76%fmwX_ki!-q}^DP+*!4m>Cmr}yhO z9lx5?4sqLLMy?+))FG$#y;-F2MQw!>Y3SbyEy&|!`YqsH+zPt`>ZO9x58#6mf?N#Q z`XH;Kp>paXvJ{x7Ya#3PF0v1F(GHTw$Wda~MLP|ijepyeL_Q~9GW&y#fl98h$QQeR z1MLryG}@-1YPUb2Mw&eucQ7c5K&vq=&}O5Xidhu?4ps+11=Z21vbaYbio{=s1-12D z|1V(ylPnL-IKs^U7c^VjLMwjJ5@pWXMcw#SDwVaY5kSG`cAp8kanaPKHYO;G){Y#x z7QbSe0WfH0HUz8BWL57X<#=Jo$m z@0wjhlL0A^{w5Hiw)R&o66H-G1SU(OvPGOQO>|T?gQQN*M9F##5q~eIL^FU0(=z*+ zXBPIGHogBHZTpPzO(`1oA98&adT@rXzkJUz*) ze>+=(aH^!3H>Vnf5V%^3gq9&R!#3+*(r z=iACf@fE2RrA9S-JOgLT^a#EeB^jX`oF&)X!rvG0|JMp1PxPqPWH^s?A|>y z+v~2LaHJ)==7bv>+v=bwb-v@@3NHv7ZyjEE5YWP7kgv@dEy$e5&se@TS@S~XJR(|f zvuR#n&Ndk>#8;z*_DpdLqd-S9tRUxXldTPPv$Dw62F%6sA>$<=PPhcB7Z_6^a~?6K z!kh6D?9rg80h&Tl{X%9Yp!$WZ04gh)kEIiB(V%ld{bE@&RjR1%rY~l-$@H##yb)l;ll29HzVf$26a0%);zIdI zer;2N3BPuO#zm!+D5V1lZCl4?yOOi*BU>hBkZsKeQ(WHsgg%;*`BPY^!}h63{*xkv zb~j6KXei|K?_r3bmSUmp3`m^Z_;!fDuHlJVT6za9tyWB595cRMuN^{WJl3dp%9`Sc zEPiDc2*UKV?0yVnJl55juxETL25oU?KL+MVSxh%ILpww@oN&Sx!r#H6QAdok@!T0V zlBVr%>eDdU)HE>TbVf6X*`(cUsN!wgPol8?-u#fo1CBbJl>-_9Q{3A?)CSq}x5E{J zZ!^*A6g$~4#T{*=6}Fe#yEY>)N98$3?aeDU4JjJPt@iETh&kyj&)L{Mj-xC?$w{Wr zY1gazgga-;E|2VpKdK`%F_OX;R3Ok=5y3Ld+kG3TUL`Xo`+`1rO*Dm^L)Px!z>FnZ zfid|2)9F0OIFb$LDnWjP2UslR+-q_qMI1zFra+XYtY3vH5Xh30`4K}QXNjF3fw%&5 zudSlnnjN+P$s~;mgo(h(5oJ=DGC)Kr4f36tGBl6@Ed}zOL8|fq4NG!<+jJuSV$1~^ zUs%%lI&_AcpiKOKfH7)O&kZ4sc7H#l@maUD^se2~(z-RF2}ArfHWEfskV{F4OAx$>jBQu@59F!lWX;&W zXV3B_`}Yem|KJVpRRRp2uIXvrx;1((lGSj>E!=i)2e+HCGY{FA70ro&42Vhpn8Pcv#dujyJ%bY}HPYGsvMg1w{Wo~8qQ~SvQn@+^j;yB&dZzs<-I~iMp(DTODsZnoY>HLu1cj}7IE+a zvE0K$@hG339+F?K>e1BU8&o}Uh@XzSXu}4kCO08~ko-O+PgmY*giz_>ocl}o;;*Y! z)(OOL&K}R?wi}XwkToN&?&XkjHol}}-j+j^Ue-K8Xs?g3Xa+WA5C{2V$V7g*^sSK9 z>K4Rw{&lS2#;g>`z_eQ_5?OZmwxk`?s>Ha?zBXm;8lzAex&{gwh!@G`f9Z8&jU#&mDlcPsiNr829-$i`n=TJR28S%!WPp zI>vpv09HWFNz}DQkA|#kjUElm!j2vd)PSfUva0oBvYgnnonpo@*{UF`TFX|2nrx?^ zl5>#l^c$P*B;sa8vFsNP#s32$RL*V~)*y(qn3jnA0zQGV z75L?g`P1Nc17r=c8(&N;(OMd~qzoDOTK+Fe@e z{^qRkRJXXkZ@xRGvh*KBk>4+i`~sQEq(PSPDzXG$t({CQ?So3ZgUmm>j45|72yCt2 zDM#u&mgt{fp0^x6)ZSu=fgj!@JuRfUAc_}0`9vrp6-5I6l(rYqmbcs<;W}~TnkUD) z4t0O_)E435KLEpGA(j;(t4n*0FZE59F1Wc_d@Azvrns)7$~@zrXrev5}89uy-yp5|n)#Tm$I ziqW8-fuh7kKo+vk0u?0)5p2<*{-7ukLiCVF{nlprvZD%?PzJ5s3l#^aLZ2R@1 z4u_Aj?asCbLV>lY4|w(Ugo$FxbCoF)@JB|nBM1p2|ClmrT3GIj{|hMO52E+i(YRO= zo2tzpmM`YPenmx##rhq*P$X9PSdz8*TCH`l$uqwwhjgbp_VInO@SoV@T6ly`jg5(k zrT)d*$VeKg%|by-$fnO-p`QRh8I*IoKh>0`g5U2JR&op5!S zBKC$4i9e5BS(?!w=!ScZV#` zz}dW2*F2mq($^A4If)|%)r`5HULAH+jlF5a@%*H|OO_Q3bl0Zbxn<6Dtpe`(z zs2@$DsELJuZvW(tK*Fxs=t@M7ni|(Tb=%e&;`XqgtK2Kk# zke9WYsd4+&NN=Jm1PB!=V3h#$kzva^>dy=y%v}Omq-vQc>b02IoC5wgZz;KLS<&lB zNRCg6<_PGo2AvoVu?wF^X)x|9# zz*V6jb6s4v@Dr!|3Lf)Szcw*(MA#p#4>y~_!_AVuiPNS_>UJ?EqbWT`uh%R6mA>(Y znVC5;(NR%pX1zXC@D&1+!l#m5w;JEviX`e^)XHdnrqT1AmN{oQ{X%ziq&lx{$;x*$ z`}Xczsyp;t(t2W^WtnP`1~eh>L%E=YvMaa)_mE zhPhAE!f!v8X=sP9RTE}cxVCbrooP{xP@KIbPVhrXVaI03B1*;pYi74vIKZ8& z+^pK!CL<&n=v5=28_L&u4To9vO&y-KF|97mAc!_gnm|^I{B6N0og3KGFI#WQ%u;Xe zQ)w}MTe(gcI7smTP0Ms~$xJ5Jo)$}k=}>PxWr@_Xq3B!M#2BRs17O6Xr^sEm9&t3d zTMJMAW%EjX8Bi6akH_A+hqn(QX0xYNw$@;>(=_X*3e z{x$bKXUlE^HW$pW9>_GwBS|#Dye4TxRs*{O)W90bgeo&#H-jem4wmQaVRDE(;S?2v z%3v5p`w8Si{hj4NMVkwv4a}~NXag0&?D@@FvHa>ad=xv{z#OP{w6OqeFI$_Pg@y54 zQ~nCYm4qWmxe204ctmv^DzIE@8*gp_%%~@o>Mem74aEvO7;KstSv1>R>x}N2u;oUX zu8I@VS~AYM`ma$-avW2gUzTnG+ORkzND)spF3t$A6Dv&CG%>;uo^3`M*TwUU@%n_+ zy@-0<2==hPm7t)1i9}N5ko#P4Yh=iAmujHPzKLtr&ATUHsOql91sjFVk;BrmCPW!@ zPVMyT8*VG+c$<5l`+)lxFbDd1?3km9VGcdZfMU<0mc_EDbs^nZUR6{eEeET{D5h>c zg-l~=9qS3AjRznD%7YMtdx<KT#|5`p4umKpPjyx3>Hzs6HxV4cW30Pg{azL&h4i zucv?((h_Fb7)BkjhHTkbO*b?$1V}^Xlzb10=a{)wq#P)o*JN%DWpTXD5jTiAFmr1mEvl2bb#^n<{rKy) z4Ych4#Q>UGmtChBgBVhgxBx6m236pNzace;_@q_mR!#BqWhN2_Bf`yOP;qcsIJsgN zKxshh%uYI~h|pwC;@?G5%U$%u6?d9|Y@0S2yC!sAP3z)MJlvLf zXbh)Fa;B?WyG-(uQCZ2~yzpp)&Jw!3j;(^Wf@q(rvxV9Tk=krWfYH>NVNK~Jkz;hv zN&4Fmof~xB=84Oji*_CEI`X{^Q=-qRQq@Z5}`4`AY9me<< z6Uh{^lGIi*e~cWpWo$h~PLns7QrLe8qK7XT2emPj%(u|J&CW`p%0cf&V(pmMx;rIO z5*&F3Y`d=o0r0A8Y>F~#02RXR%AB{yBck;8hBARm%hb;0)FALnjhUiR%@a4GfPRez zlY7FM1tK@1Op498*?~t zGu)TlM??ffK<*?WvWNUd#0ynCNl`S z_^pJwx~r>x_3G8D_g=lK%I?>iEwOV4mc2a+;hY7a%GFy%xCpSOSmdv|0Hq9%o8$_c7ObD$)tCqu4oZ zQ#{U7*?0}^K$ez5Y~Y0WjRQfOm_ktPMG~M_L*p6n=CAM?lMCEAXWon^e#MRS0bZdMl1dT_3rOA?f55EUl`d`*P{+s>{QQFXs z9QoBXjo8IegRif@X6XyoIj1&Kvj z83pDJ!cV`-{K&NwmH|GFt1&N9!!3gy$kuS{xJ{7XHge9;eT7$GBHuA2K{W z^KyJ1T+*<+ac06q%!a!_AR3N)(GouM-SRm(wrJ>|zz_x4zT>VrSsKythQT5sL} z>6%A{)O2t@Mj$dYHYT?)CX;P4(wU$OM3cKmPAbwkGtwwLen{i(F-97t5w6gQ@mnen zZ;S7@BNxTz!JF@~YuK~Dh1PI+^B{wKXaEfl2jsJ8=-GsaLVQto+ZsdH+S~f-HW`hA z@Vh%~?Ke>kULryNB5!)z`YYzw)(Qfj@@VMgb(q&Z)0A!yhzcN#oM#*~+%QYOV(IR+ zU2k3IxaD_rax1R?xEeZudi}>Q-_I+InArG_JCRD`e-5kx=}5Y;)uq*J#|>$9X$@IM z@bolX{ekhA7#)Fr2FbJVI{81g-UD|w{|ZL_^W-~N`O!!&@d8_C8o`_SI6jHTvrsab zk0F;Y;5+hN_!8KOv@bu9ujGfp{^q0kYPWqiZsBj`*Z#M30BI!g9Q_H-aL5jZ-*}os zGdpsv=>k^GF)M5{2{-yC9?_WgM{l1ikt2H;V z9M+Dm;nu?*=D+m(Rot;Fn4ks`BcH2=XQkj?6~6YTxP{zG)4s2OnyqCy9K(dt+o|NlD9o^Qv^UxWr-)M9cU5W37eu zoRN!oNbe{A6EhB80waH4(+*dA&Y>1oYup8q2lsM&xP9D1>;$wYxu>~jxua~?sgn?S z@D|$v;xq0v+v)o|?jqN~c7QOD5E4$JNF2ePzAdoFCGG}MN^lnlJX>`*xdC>8sFtG- zaBs_{WI6Z_aQ0!dd1OcffP-k#qLpd--%#9qjZS%15w$r=-~j8{eAGR_$@w z%em|K4bIb5=kPaOYr24DY`!*K1}y#&EqM$AKvUj}8SlRAz(+m22O3;ICXB90#f?Gj`ig?ZxAxu3HM)V;DN9GB+vey+ygg`(O*BET=jjf zf1y4gpZXC+V%Z!5>H@9V9D=gnbvfw^I)VE>*V=gm>BMzuAIc}l(`;ub*AA3l!08{Z zof>c_N?dt~5ed@SsJIhl8+HowYMAvut`Wew>TFQAM+;Z=>@Xjj zB{Ur+;8i<71*z%Bq}$OLqM$4kjkPwpEw-QO6-jwyfembEH?%Ptd!%Fp5}fFB3x>um z&Mg;c*R4klY<}U92Gw@e|IOVO+zv^vy~=wx{X*TK310#3zi9Rvn`(TaDU=n<4q7c8u-*hjYTd}!K>A@j?& zkXoMpm|yhwxl?}@uiR(gbBm^Hc8)+lsP~{vGkf)m9JFHBumOL#2TmMJ+0wY^qiuUj zY>lMFJySj?i0;i#Y6)8bwhnoa-o0_u(fsIK`b6;N+l01P>NE&MAy6M$@!!+}_~rH0 zv$Cq_V;}TfG?nAFT=RNB9-NC+OuCYuq!;PO&I-MrjBu`8oJHo41*C?pS*T-YW8s>G zz0MsSpCo^i&&I;tncgJt65Ja>@)w+kwTnLy2iw^Z=MY@yWLZHYXveG9FZAID{-Fh2 zwie(qD!W_KmoqhB7BbRO^+KG%sI%u0g4@@jMAR_g50Z2S5J1bN2r+h~psMWsz(V6|7ke$6WL{aP8Vxogi(Pb4kae7-jyKJF?Yjm^?I-e|Me5ZrTl zHM7$nV&_P??DSX333g(owBIt0%D5sxveS8XhPN~-D?1nAsEl@cv2#U$%T9;Wy#E-X zH6yfDM@XrSjvbt1_rJapLsY-_cb4Ro^GzYWm@EuOMZwfD%&1 zV)q7;B2NysCru@5x^di|nou8=M5OmH4=qpXXjafsN01kh{p;sM!@1TQL$2PA`DT_E_^D{_@|8zg##Zo}D>rQSwX+-N+wreCUdTulVd>J8c|LHVj2r>oT z|9dj7VeBT{ha9JGqu;jE4dcG;m6m3a^&pdl>4E)Hmr{%`z+hk?YN~_pl&B`JV zS$Tt1c+qMl^Bc~>`!1`MuG7a_tx|rMsBjVTi{?11Ge4Fc=9L>GT+GT6*Jou}`TO9_ z3LoNVn~gtViHCH7x^)3W?vNm3g-)zqHdZecAQ>W~lZ=*;jlmRikzyu#9xHqTzSHx> z#Oh6U*=%&3?hIXLvoR8mLV`BNk@<*S2kp|JzeU)~@tmFu;+jFJ!GejH!id$RzePOA zSBMXb2X$TPJ9Hj>rvcm!L+Jr}fPYB5M27A&JWLO80&Cko`WxY!5py7E01W>0`07I| z$U%IG&FLbtMmRzaHf$ybzY^Yo>dMbf)P1aemRL-7%tJqst{^wl7B9R2rNcL&Jvr!@ z&i8UmCkLI+#=Doxo`vW2@%V)J4|w*-3ol5e>h5hEqq|os)6fp`knyI;L&t}e=$$7fj5b$m>86ilRNMCmQ2yh4c2v)YaQKBJ31`IIjC{4=uV zlTXQQpU-}d{`sY2^pRuFk?USMMg|=dmv0KnyuXy_>i9BO!quPQgLog)zd#>8_BacKn)z)n@*DZbJ{W)`vZkSM0zH--+zQgMt_FIdv4P_lWE-%}$VfLn_ z14d2A?KF2=ht^OH=x^#aXvJmwuZi9gW64iLN0OZw1bcoUz;kLg(S>C7mKy3P`iH|W z+{~9=n#gw*-yqw^(^pp=t^v&nG`=>9{&f##e$h6!z*jYPOu%n)C zL~fBc)eMBoz^KXYJYzs}lAmt=hH6ZzXg~N;_YyA3S97%&r{@I?Y&CF@Pk@nKQiDP7h4#9MXNp zN{{}T&vo-(J0f#_s@dW+^Wqr)uv!%s1X8d0w6afr+bR-n4k=r=qHgcn-9!4VdurhI zyGo0?`K=fU7XK4b6GJ*ps9Uk-{&|xp9v@e6AOi*+ZF|{3-W2+;ku5SU73}6zVu6@( z8=O%WX-PLl2Gl?=(J$#~P>K`9taUo;y3#Xse7{ZW)@|}zhgWGnxIT_KR`TaErc@uG z{m1}%km4|}>C;#TztUf?AeTP_X@YeKxa@2ljbmTV)ceK!jfB-vFMWDqEeeQ#pm(SS zSqMFsM_GmZfq-u)&!88{T=Ddq)p1?Qi^f*>xOwx!vfg4%pmiZT$B)gWUoD&Y<@B`c zhZOaIfwyxvkyC2sK5lj8%Kf(iaFH@1c#gjYW9(&A?<0C_>7<&GPamtQI*}4w^6Rf0wl@DYX-AGeuTae@GveUUXZe;@{y;|emp@^?~HNYJXU%2RL zPs_V;oCcv9Un?$8iw~9>`nI@z;QY>qk?nZX7gBMkH}l%@f(ENj)sE3z3(n$(Ry}r0 zjSDTzMsHPnif<@}wBw?JMhHcdTGObaMK+;Dn>z}DVRQR zDL92yPWxC(&86=4u;Z9JUQ(tQ$ZEPqa*1lx5PgG|unVTTql#ZTjbFzn3Lq`vb}*yY zqnGM6w94T+s>@|dZau7B7YwA9yWOiM*{d1@*VSKzi7H!HOR!Q8+t%@!JI(l`p0=)g zo4faBuU4&lEfo7!jsJodL)P)OLSbQ#V$*6WKnJ^DdLUZfq>6{k{?tQFo8sL2AsO;VNq|Wx&;-#mRvDk~% z=>eYEIWBr;aXDPErsaK)KPrpU#l>!PUJX{}#F~+ZUl=iB|H?r#N@~_KEAx4A3TuEf z^}g#N4t29H^NVMz?Mp8eQOl5Mt~r;f;bHaYBs*bTY^)un=m8IFlD@03h;Rn&N(~D; zhbzU7)ItHoNw>`Lrie-h6N>H9j@19tS<$2`Alg8-LTs3?Q?By`fc^7 z&{DJuCqJOrg#-I2cA>UHFy`4>b(8EwS09Mv;<D?lPyqBpWHyY(-~j|&Qzq! zF$Jv_<90XePm2p}K^LAB&IY}*16QKPQC%Pg^Bvs05>LbgAqX_RC93Pw-{o zsdx!Dmym5eH81UST6LX}i~0bbZR<4ZO#oH?lR{*DmX?l z@6>(7#Kwb8ZY?K|rM|5=SH-*yt{0t_+cxJI;bytf4$ihaz-%~eo#e6Bo6Xi(?XKad z$6cP^!7bBo6DZ8on&b!phzfAcvg)^uuBsYM|D@P!AC4YfwO29BIF9Fcamx&Gx(Ika z)%Cp38Bs$V%kFuvHW%hizVNiJKt}QWPHw3ol9kiaRgT};OGD(Ss;W`+f)Cs2!?B~Q zyc$VRBq#6*K$~NXHc?!hs#MjegJX>T7^~E2#Rg)NiCCF5kNHBL-vD`jrX%k3^66vL zeMYg*TGjsyp8g}L zILRJ)tS3&=U5E+lw&SLaFiBU~CEI@80W7q}&F;8k`TZHKda!vop5Fsy&jp$pxNwe> zXW7ir5Htb+bU!$)iDgJKVLP)N93MlLU|aFa;91JrIGMAeU(ba+pqVk)UkzLPJ!Tth zU*=`<3&Ho0Y=FuoP0Viz{5s(4aG8cGj`KTdRK^p0L30CyN*@yi%Ao6 z=3rFB#PlI@RD^@N#1$tH=o^OjWI2-S!r@nvsPMeU@O(La#MV1!LD0i{bLSr2SY6W# zf*$$}uYW|etEo}BvD7WXp?DrdIM6XJ$^hS!O2BkVmNU)O?QK;_8mCK*&7BxdA&5&H z$eaaa&6XN5VY5Vsdh!|gU62@x^a0&fZtpuJ9}AfX<^4^TL8`X!DTP#Z{~N3OcZxjf zeuG*bi4~iI^6pd^!9z09@dzYmCNP9l=eOnlWdog4)z@Kq*dxxs1wrjaF}%&-{NzPm ziOC8<_^KvE7agY%2PPO4DF(8iYyldL0UDtkgV;BXBy)8O{YUgb9fSw;;479R7{@CT z4fJ)w4oM;WKncQ2+nTd7iZd9Z%0k&fb61tC4|ba3cMHemZsmXj^H_AV+aauW41 z@4jF~Y<`F1kV|Y-NpWh-kJ_3Fn*?!Nb4Mm!?R(C>!z>%3_v z%mPnJ8+si^Uv=t6pOD=dYFjWF^n9F$S9!*5z~1?J)cAh^Iv*7Oqaa2Xl(IEj$)1Wx5KUr znLifv8#PX}tf3F8a7tF65kmfAjBzX&MFo=?ddYA^UaUJ-8dB1ywq|fl_39wQz&Ts` z^l<#$iS?O16S7iMvL^4+;-yjBhEJF_UfA=+a(?23;f{C>;7`8oZl4h|JHHflGB+Cv z;{~0R)f*LqAx07)Z1%_n+Kr~$Ed%-;I#@Yj?67Tv`wy(#FDIHd zMpymmp5db!N4lf)OGgW~w{tG5(3Wp^K_dBV3q4OOM?6^_Gq|RJmQqegDr{+;0v(b&1r)a0p!xNUwT)5+*zJu#l_v`KWK_RBFD(Lq0FeiFQQ+(S_S=JvLD%=sd(<=isQ;ZIBa-O0S=5{89?yc?K z){;HzhVET@vTo9>Eqir_em8FJ*P~~@O^XMH&2-IC2zNZTXYWBRTE`3Y^X&N@4|Gcx zCQTT=^^Oq}r;llz?2efnjlXc=8!a8NpR19BQUR!dh;~yn>*lJ4b_4qD%GFP-UL6!t zev`xD^qG0$BKvATrw4>R^y`v%e)7++`7r*g!%-p&hgkudZ*y{T~;u zUadrOk8n;^u#+LQDS ziCg*lG1F^G3p|-sBZCsT4DUGY44@Ba;=8L>A6h*w&CqM@c~`&IaxF9s>p6(U8xVEB z@tny~JhaYu357`{Poeu5ExtFfa{C>X6&00t!Mo#Yg-Al(RurQr+2_y!*q~(Vs$Fs%eOF&>bg?B9|$m=pigS+-Tti{>5t8qD#*R@qsr;gGJ0dhS0>mR<=_CaiGRo`Gb4ycGI50IGN zdxLn)l$m#3ZuaW=L8U9Q#9{@O7SP`L;l+iO^iIBh^A0b1je7jWSpgLef(IW-5ezms zELjfJkrXgkPzq6G3J0pCx44IXJ7xZgF{`&uwDlO$bIP3AZD$OgclX$=?xMvPMseA( z)=N8A7Id^F+bhOoTXUwhFUc#;NV7Zs?cU5y>I(#PnS4+bx`N7pN&~Q5p%0Anb1GgP zHLmT@=bjtdF<%_)3zy@KxKm_yan8p2iFC`Nozc5`eT?sDye9%m`tc!{fD zo34-Vl9MCO_C=6=;&i^A){M1J+`np-2MRSWC4p~G#Y=j@(u`OH2E&Z+X75Poz3(mC zQ@8BG+Qmtsk;`sh-XiEW$CFK&~|tKG6$?7)xFu zYa@FTjYJ)tS9hgfuiMfxGRV{^C)YRM?h}{&Y5R@O@~i2-QGF)vy!qy)X`+qmg7q~J zD-uI|g^TQ=JgK5jlh5crGRk+acDyi{ygOwi%@ON7>ERxuYYQa4Q*RN{1Z3rGVu_9* z0mOIGqwA;A$0x5R-h=53B$}>oC=n2SZRGs7vxj&8h3xleAr0HEbpA ze)bLj>+$%pQ`$}(aTh(1HaC_0pus>5ki<0c(Pkazv zMJCa^NR3~3=Y(0a9W&_rnl?xOi630J)-xsz^o@W}RnggGGTpX;Z|)ZoZ>Hy_)0M(3 z4Wzxm|7 z#q;xL7d~|#z1P$@SA#o{HU=s(Q#;KR$m&(PY6Gp>uz~E|;1}E(Qb%V9Gw9u9ytZEl zf(sU|nrtA}ji$H&C@8*1w@#l(R@_9{}u@q?~^IEDt#x zSo_8?>nfJm0^&Nl`VKnSu=D4rxNOtrvQF^6sl1clN}PP7X65q5 zZ@#r?*@~Liw~d%SeZ;mqN6wfrLR*)XfsE1MJw-3BJynp@puo_trL}`M{78n~aeP$i zOna$c$Ye47iarkN+rM|xzcmLZs1|E3i^cLs_ef(Lm<6iw=a-TG;=z3jyPHh=-gl>d z`Nn{#PahliaJ`=V_@24}w%&jZ;I1RJs`_#4# z#BlGx35U`JHQ~mc#qnbo=KEjntzXrnC}(+><=wJPt=ij5 zr@-m1`7^TTm9{dq%xwb|(aLU|!|=mlpl?B21g6O>qu*|(-|~<9B|0A_hVT~`df*8_ z9zsk&Y69%3Ah{_5q3J4mFWtG}%~Sq|;a*@U_U4Pk7v6Y-hj2P~dj#SV{Lu>M+LchSq`Ti0GsVCibKNwVoY2-us}Q*yz`Hz4B@uFsZ-&SI&?z z^j>m|?4w6WHww{UT6#kamOpw26O+Y^5Cb4C-9SIw$kTW0-+j+7HvU%e9QZQ^^GS3h z*`uLd05()GTPzUhb`t|=FLFq{mpn-a@cz9Le6CnEzmnfGUpt=JSOZ zqY{GZ{ z9y8qH7V5w4o;M?Uc0O$Mq2*m%CFZ9km~Vvz0;r=G0%7D7Z773ZekMP-g)|^ucfBDX z+Dp9S9C|UbL2!#_2!QsLnlJ}fY=b}Af259e5O=x9HmLUwvSW0WKk8e(L_9E!I*dI&wYy(^s#= z{d-180ME#A5drYaD6ioXD_M+$KYGX0&$8!tIMCICK}K8aM^2nEhVD!_e_oz*Q}YZ& zx%uN53;@(bP6R^wxvnq@zm)$(6-<%9&-lT!pW0XqgFndvUKEUv!KjJ=Ad2(l5DUNf z_}LiPFpIDA;0aqGyMnOY4H1_n;vx`?dpyD~{GB~pMnUGyH=5#4SL>10T)>`@skCmjSi%lr?MoqJim-Jsuc3`^NyovD`P5E~1X$ z2%vr6@Dk187wszOE*>!*o^1?KqW);8>f2IeM*xuNC|8(AKu{q}iUoUU$KHT;p7Qkn z1TI6zsKY;2vzQNmqVpwH49GSN`S8#e0b7*7Q56Y-x|$UK5rFL9q{KqXVIk{S zEQmkoq?r^C>44ynX;Z7}zdslA2{46L-w6c2XN1UQja?~9OspI(vYN$+_`^@PJ6Kfg zRtOrIHj3_xIrlvdSnhHQBU${(1$pj{`8ETbwo8k&xxz^NGBs~fg_C&pJ6u!h2cy+b zR7oJ+Fw0kniC_E$J_1cNv<^To!}%#~v`GNi#RhfgiC?_;xdotj@I*jzP7{R^07yE_ z6_Vl?;wn{Oik4>s@Hx2rI|YS-K%3mXz$clxeXtF-tqCeaVS zf=5IXxe@@%_dKFl0s>0T(HI2V6{1;QrZrU^cvzI8#T)=l!D@3xwFH2cPEv)q_yzhW z7UR(Ovat@Y4f_I8B&-pn6KiP}LXr zKHzo6NiWTgVZbC#aTo~f)SL#s*a9agFId+0E?c9$7PSDd{9mmT2lhOFH2pn`utJ*~R%2 zMh#fHY4(O4Wy?F7)p*ssmk4pFUOccQAEu6goH4KnS^-ClF_tJV1bwDv1KmL;t`nb? zDOhg!;u%j^xeNGg@wtgFz4Ve33;*`XS4lUwaoC2dy*%lMlc0srxB|9yKy=%5-z{CU zqeunqmQpyhtN5FEl*Tsr!PB#X;sDyXO@Y(^(}IcQ38zFP6w) zkKEC}ZKgfDWQ!+N*Dvl}F_CnLS`;BV9++Hn!?0aW;3WC!9vugF&w|M*_r4U%R%d8x zV1+P|G-7Y67e6Mx3*jiwZlF)ppDA6R;n|JnPpA9m>UeV5%u5*PO$B=$pmRy}|3{F@dnPTIO@ zVeB?f%2jR~P_b-E=MKePr!O8dxc*}Io^zbg`R$|gi-*q<-(IfGky-acguH!bT~ zJ*Gn$NY~>iUGMl)N~Vtu3O~THhUW7-B^g`NVc1|v#@^qtd@Y{i|_{ z{vCFf0LD@D_UwV$^8{=f{?^`iE%Y^R`H>F$;YT|B#~;bQAATec{Mh*mIuFhgSaAAF zvhK6b$=cJ!Z_*8KyhS&kdXvn3<1KRIsV+^ONTAB<;zh_BbNG_Zha4Avg*m=Ow?Ia7 zSVow!?-L8W)qU8N*WKsZ{HsC=pC=z&Azi$ljNr)|W3HS&ZgB{ndpE<`30H$kbPG2y1^rJr9z$>Sg$N3Cbg*e(19IlWg%D&Oc2@jrpa~1lP&wUoI4!`(3CE^Nc zX6FMX%tsTFrTMYc$3#KZG5FvllQ2& zI;2sbdU1txaGr|6!Hz_p{Bi>~@yhG2$5|wg-9$al&bTT&%Pv7v?=dQwN zrmSLj-_HYG^ztqqSBI~fKEdM(iRbf#4^O@;hcWH?E9X09|8D0V<%;rqH5kqJDIr%# zC!eQ?fFI}5CSyN?4Rq)@Z}uO|>0!vCxeBr z11+AzNGTHu;(mgI)_&sn(0bj=KfaH)X_M*5_b=)m@tzLmMAL|_Pkp^`=qJb{BmjQ_q_SC%$JPms?YVdicdjxpaXS(q(I>zh9 z3wQg=PnFJler&Z1(68|j<99w(KeDl?VUGtsO*-3@#xP=dd88+lh_9VNrJPtHtf-l zbZ=`79`*#Xr#I|M{?3}Z@E~b)+J3I2_l^fsd-7frcs%GMr3IfhuDmcYN$lto#3ZK= z+Q?~RoT-IFx<1HmfIY!Yc1zgh^sd}dlCrwKT%X`3!+l`!N*9l`NcF&RE0Q%(+)|tB z#s`L5!qOCOaHeiN1iULr(gbXJcUdpf8%_dMa(5__mwcZ9 z1D8hT%QSSRma#Bt$&#wDcuDJ&!2WSN6iLj7286ruZs>HFf1tRfw5&J4umE>Raz}m<8%;mO^6PqM`{nf|4oXdE|N$bm~j}zNv(9QXb z3!Bc_fAAdZAUNfUQ*0cx_#E}ZbO(p;lGg6D^e*c;X0u3^oww&?TSc+x(4OO8)(yS5 zO*d3O66}(upJBEL+HiiKLCJtGr`63Y?|bWM&-$jy!DwT!tUs);r|*C%2iA*ZT{8>Y zWv`=5cFG{!l~xrN3O z8uTt&=v`Jij_GXI1y&}Q2)Yj)A4=LJXg>N2v}faMs8d1)o_Lu)fFlW`KaM(R0yQ2A zf@b4|N|lrn9F2FYC^3ZRf(vN(8D(Wqw%Scu4mlkk;`b7L6I` zN7gq)LfH(l+K1ty6^yA!<6tz2(xFix4``SmptAl#<7*0)eLc&xTlrZqh|}>&$pk`L zE4~cxw8qh#jk@?en6pAU1HiJl7ao6&_S9cM)e)OZHLj}0Y#fJWTS*?}$s&c&-k$*- z0{(!|jp;%PgRxyS@Yc1Ej*kyu4z3BQx0kz#CMV(_6BYK~Up7g%57SgGu3d{XN0 zz?H~fQkPe_THOx?keL#GtTm&zq+`oDFv>ILmU3wDcq`lR%fhQlSTQ;i*YT*Wi!iIZusbV-TfC7Hl8 zu0cTtEDzwYD#l9gtO0LOUSJu zeZ_oSZG!bOu^mYn9hG_^KS^z)t)9|1u#wc&lsX9D#r$3LmUR16&SZ(?&{IM6qPY!a{3npcG1D>geI4iE-WO_x+V>qe0IqIatdG)1H7?ez zZjja_z*VgupP29|9sD`{RIa|E`1>U-LiW$|L-kRt{+{W8mkFBZkQ>%q_N;rRW6}o1 z8}KJ;B#07d?&8KZ8AF+2Mn2t zFwlS%3oZjmzI#3cSn-eL4+#;mR6@hI9&~kOMB zR5;-;ALDlZc7JJtJ|d-<#w8!21(7<^z{2{WC+)I`k^32J5CTn!y;v=`6uM*)1J?la z34D`j^jss%{|HT{Q949ZWkC**i@wQxc#Z-ZLz8I;cL{1`Y%-0~;T1e!2s`BrYF80( z`fwbV1hbVE*d@L-mkm2H7Q*faU14Q%Ih=()kh>0MdPi`hx$)d2ZW=d>o69ZaYPeucZ-?eLUTgGn4X-!&xQ_RJZsOjrecu-@Z}rgD zS4|IvPDnT7Eru^wtXM&7ma{invx2O`U+{%4S;4P$eTOT;dbgjDK(|?Y1)kN{d$|aY z>+9ugrOchBVEQHSi1hACqp$OF5lh6xu4Eci{94y7tP9I|Ipzd4ba33gzZ?0`gOroL zWFWbYTu(-j(PTWCM5d8ha0dE9QbU%Jm1H$pM>dfj-ZdVT~ynjgU^ z`ns;9@pu1n)BV!)SNh!V{rN!K;d1NgFJE8xwb#>R{zBmo|ETmKy^HD}(szgFCFx3| zuN%_!hNf>zcYh8fk(B1Az>{8s4+Ka4${d){t@&!&pTfD8^S%aXA%N5W)2w zFFIE6M(q_TvCz!>b;tw~=)#-2E9I<@@}EXRWrzthrIfQF7V9E?$9Gn>CVm=XmBjv1 zO4%Zwz*5R}WR&#vNYk_unt88_+%BrxVcRLFus&$FfJQ9q?xnCgr$0A{8^R6eZh)Ba z3EUKJ1~;3V&n@Pba?80@+*)n}w}rcdyNkPBM zdxLwEdzbrw`-J;~JHvg${hRv{EG3Z;q9?&5jF?CaNg&CvU%i!N!NHh0B%gGEg|6Lk zY>TnvH}L|0mjw@tX!->!AXzr}`M>l+(gkr^dFOdWQr>SNOo+of{D*!oeSvon3(oKG z1;mH?CwvEaqq%a07d1tw0XTtyFoIG5>5HIJs{e&#tXKhhwA))ybG!f@5rq}|1YSom z<@^r4V7U4{umm zUF0&QIxdHQP54W6AM&?Lej{(bj38$7OH+j?6Fk#^Q%QD1Db71@Xi@PUhA*a z%@em*OjsTxg4gmOhL_b(c61T$#!Gwk2X$Yf#}UsR&>w6#qVHCv2jR@C?m zfBxAXK6m%$)dv=fCu;T`Sj^`xK0w;;{`~XZeD0plFbQ7SS0kQSeBb~eRLa0Pc<@Ur zh_LOPsDx_qAYb9c2J2{~^`P$0qMt~59-N&-^>XHe6;oc28-3-}DfJ8Ltk>Xi!viZ7txf;{Bp)1l@+&RhC{eKS;ChxJ z%bFDeO^QXb!Wj%iRIP>GeShOge$al=Koa-U&IkuiXjjuG7*-dUxM-C}vlZ7C_EFF0 zI&hu2?p!I?3w)XbG@8?Nl~;3xtUGN6zotZAOA5(1!p1Kt1h(HImN@tY-|(06@_yCh zZVWu4dP=#EpK-+I<*s~Hkju^{F>yATf)AEjrdT!%88YNT<&YutT&acJYoVQ_-tEQ%ch-D~FWBHBx1vV@lIWA!Loe%j8dfi$%;TweYj# zba0Q|S3ZT8EsfumT7jVDe#kf`x* z!pDwO=m&&@Iv6q}Jg{dnF=WvHYaB+}2s5#MP&e0o$O}{G(}Xl7l}_&{dQo^;N(@zn z{MvI-`Uc%oIKo;XaYI7^|3#GU0Vs=C-GkBFniecbxA`3Lq<9h%HU3?=&9MZMLO!j@ z0R9P~3iE+N`6tAAqzo=WKnSn8$Fu-qwi%@awY9aFh7LP-4)d6QX{^4vzBT3n`&8?j zHxB2^#3cPcFb~%~$OG=m352tbB~l_WiO*@=$*}Y$96v-@FXzJ#5r5#z=wTiN3D3Fi zN%_DL7MOqxJSU~0hil;sAj18Fi!w&)mdJS+BY!>&#k{7QjCr{3Ik8{@X!(9pBJnly zfa5WSMMd9WUf&=liAlPr8ixagHS~qMNPkSAk8Rw^=Saz_8G+sC8_e@h(8KhwPz4nW zg?wDJM?8>EoRomPL*K8JQdOVBw!So0_qIe|JxqSSjWF)vzu><>EY5q72iz4*asmLr z(lngajT28wbprZ!lIhFyoe%}#pt;L1ALl)WMKBqq1l8xHB(x>Ts0Az)T$Ev}L|L+$u%Rr9&*SqT57#}&1MUJADM58(KbFQ}6$a4Z5|&FjhARCC>{r2pp~`X2aZZTg z%kW)@Vf9k(V}CF@Q>+meiHmsH;8RNJILEUqkf#8&a6JS1RB$uxQ{;NOpYA8uYoF%V z;8k)x%VW49L~jRuEKWL~l1qcY35ZQd3?Z~HeHs4xlE0E7`1@-$ zT}8ixzg6Js4u`**cf`jZ7x4wms!E5?;caztDV>P?ntF-T9nr4`RE!2XkcCVdzC~Nn zR^% zDXl*p4KtFnU^Wtc?XdL^Zu(-AKc%f~?77Ox#LE3E{Z#T(@>j|e<`=`i_4sxc-{_6_ zP9DW~aX-HE1?+nl_Ptv5UAoTFNa>|#SvD*~!2qc(@S^X*+w1rx8s5kdcoR3_x4ZG% zSm!r(4ezpa?1A^-7iIxExU2mlL6dCG_8W6+MVkrs_R55Jqsd@2#o9~;Q>2&n{PtV= z@Z$CBFRWj@_yahiz=P`wo_9n(PaAa$N_%Qsd-<1~pFXw$CPvrbsg)BgEce+Hnm&0i8iC&_(uZ)q_jJJej7#m<^ zt6mxugk+^-J)p>bNVoaYaAB#GjsUTsUcyo)>S1V*O3?p{_QgCwW1?k2oS_V4d;}B$ z8ZS}xA31X7QDyYNv<}WWLg1LEem+Ug!ACEZa--k)9j&Bda3E7BRW|^h-3His344gN zMp`NstB}UNLZG3uEcy-nTC3&|C3|#ZAJ@nU9$ z>ck9$l?Pmmp;MZLwOdQROo)N@fKik{yJ1kH5QC|!@p0WrWF3S9w^0W%@N!x?`q98k z-JG=^Lak9o2leuKN<7)8>VH4B2Kv!^SR_d{Y zfBwkYwMXz(y=u&uRrun|qzgyZO21bDT9>~8DGiLH^6`l;08TGHKBiw`TH^Evieuao ztMkR)&+;Ws=bAcOHw%;!Au7i2vBicdVv_*1;bkm&gqGy9C{5s`w(ieJ9@mljBua zDlPqh*OeB;R#XTswi7Sv7Ks%ZK1sxKJxz#QLk-0KHRAj$xtril?)24L{S> z4wwVx5EMT?Z%{$^*ozDqx1dMFPqdCM%kOK92HV zMR8&>a7KM3XEUY6V)vJ1lOh>Wb~rw{K>r-2kE9P!vD^%?ImBHg$TGTt9}-3Xd?6QZ z&oztXls~Dx^?0`co9S4E+_QEO0ss+JTN3 zN!V69Vm#9635r2U=)!#zD31!@y3ytbnJI!B@?&yGI49+IBUh}uO*aTy9ptJNIvC1T z-EEC$b+Ofs$BK2ajsd~|*>~bf%Th|IAPJUtBln!JM0nLbA7P2(q7YIoKJ%OqB3AJw z;xm8^e1XoiQi&`TB=Le9eGP8)%V59-u97}&e8|9aJGgz^6!1NQeTU%J0`>!TXSJm33*jTv$UbaV{E|Gy=-*xuf{5%2oJ%zno z*=L~{e|06&yI_z$v0%I^eHL075WNH^56!5UXf&g2YLn_AdNw)oEqW;;BI>y_AyKx) zZ!`-snS?fX8^hCtgsPDE=%5kNa0pJ=);vBjEXerw(o_Te`LHoPlAb>un@KNyliu=2 zlGHr*JV|M#e}7g zH2W2kDVgtZx5=C+#0?FLH#cj3f%K=3o&T0TM*5#W0oYQGpMaBwKLvIIj)MS=AWmNe zyB9yky$-y&@P!-L>H>ZnBM6l(SLcx4f%y zENggR2*yS%7xpZB0yqbFE7Jzq$zS28A;yST@eoCWu@KX-AIKh966A*^MH}H8=EQdO zhJ2;`;SuFw+;N%V4C4M>`q+&x#5QZQyG{I<_!)ijLy`(}j^vym-T2tB+;NkV;v%lQ zzM?p5$gQI`HOp*YIWN&1HSC7TQw9{33@wP?e0|5n#M$X39rH6%GAlcWho?6yUsGZ) z655vA7qsgTnG+f}w!^|nDSe7cdu693<+kf=h>8jo4(OWK{H;~1l3Q|* zOk}~$S;Ze);+D4R+@;D98=7n|7>q5WLV|5 zgZo)xOhvkc(6GwPtZs4fopSPqbl_X$n=|u^`ki0bvwUKgk>hVOnxjpY@Sxxp&C?CW zu&A7N^A|)#^=aES(@>gjDI(XW@?AT%YnRU3TBoK}b|Za-yf(1Lk#N;u1O@4C(GHhVrsz_^w6}@1|M5osU1w}{b1wOQWLHYXL z#z;eWgr1MoH5&v+}R?dU4|(k z=k<~f{hBAW$|Hl$o{Y7mr(0rAp5?&Llzz!#{Ls*bd!BoZdkfYB{G0okM3Q)jEy{s- z|5EZed5*kB-XWimugMR@!Rz=?KAKPFtq^ltz;^>GMUXh)a3k_DzL2To3gJ>VK}NOZ z#}%gAK(H3ZbtSk9cm}*|M)+BnO(J+(S9l)YviZUk9d<xB`BZKM%=m^>lX`048v_( zp3MLcz!vBR3vdL3Q9#mdQXYUbo50r;ER^KKa}fmch3%$|fC=b=;s*$aPqOM5^CNgh zC8QJ(r~tBMp42!dNPr6PI|GWxO63~0 zIIQ#b!D(OIm(n~bXUV$uiM0^H2zunYyuL$2`;?Rv z4^1u%Y9E^sADO-^&)71&t$x|8(Cdt5-W*P%f-;*&G>eUjFlWZcw(M>yZ{OvPkdpQ- z1{<&E;|!^ZBz<0LtBkVTR!^3ekIj!y6LNw(8j?-PiOnKoBAX>n=^xtlFSSK!RiKT5a zTegX{nS$09A7cxaDM-;U2f_hoUCid8=|s08=?)Nr9pAJa9u*s(kC9CIdEe|M(>2= zu!NZ58A0s?Rk`dmjy6>I3h3!X$w6nw;jrz7FCA!4$&U|TH=9+?%IWZgh z3@!|h%#Y3fOmpJXgT}EDqVeM`&FBn?ZCOb38Fi$)Y72hnZLvV02-K;1> zUU*d7QK8Xs9fA{+?zhGplTyQznn$%9niCxzbzk>lbGYfH)cC~o<}IQ_;$!2*XD!Jg z$^5yT0LA?C#bksjHwF0CBK0fSLT3QkSo*?yusdm`Fn!6zFX-i+WXJ+uM2M&x9wB|*$m4`<`Gix zGHs-T=xyR()>jRhU$bP>0`k;hv5+o#fb=6BNrE^*{PrU9jX@+$9#|mi20%)2BSIRZ+*5wxUAN4aQu$r2iCrgs^K$Ffqe_l9ojDpVARe(blx} zQ*;C$@z`TRXZo6Wk>CV&2+1QCDVQx_|G1uoh%MLCacRv+TAX0j3)*}>pzlwcM&Ca> z6$Z$Nsh+u3lWh9NgY^IY{&&&_uvJ`Y6h`Q$Vmug*G;t_tLG6SfpSWOBP$-PZkKf$u zo_)5*?x%#GFid=jyeSm%h4=3oaC<_*!j3$-G>l!Qtq&Z8EV$}RNxIMVgSZ5b{Ln>G zn8tF|eJqc0)5Py-n*)!4PVSnoX>z_*%_W}B zd*_sfG66wn*G`^Xi!Tq;+GE01N>>Y5iY|R>?5;P!N=rM;V!Aw$h0HJjeWnG0VVN;@ zL1C1n-J4qwwU^S0$^eevpu-!xAFr!Besn|XZT0{9SN(0N8;-*76Z@8@Y~R<|xNm#P zE&Hl#r%tJ_n>wYg@yWU=Q)}@>O6rauuiNmN@v}Sj?!DtP<7*r6_lg&ziSXdz!w*t1 z`o$Ge%DO32>frghTHq^h*o{1n=eA6t9UQ!agF0YGHHBFc$~E8E_uZo4T)yvwXFZNr z1KPTJK-~BwLjg#Hy?~R7h?59_(_2ks%v0q$us$(`3lV-24nkyDDpK7@5UNCGP)j5Y z$wgM-rxe;S6iIu$Zox1Tlw!8g*J`IssjZtlxlUMXhPz{L!<%)-yTe`a@RZuxDU+qW z8w9Q`{R#xs1@Kp4D@$!`(-a7T3+bh%je1tbV8nvyGcF7z?CS&tgyT!)o3I%`&}UqD zN~Et->guLUuB#JMRoE~xjth#_7en7I;f8YKxS8An?q=>bZX>r9RtfwW)(89@)&?Ah zbpc2v(C-wx1OzQMtMpsa|I9!`2vLPQW=Z*zlB4*|NK4fNAKPxH2!V%+ouwlkW$` z3JK=KJYn~Um>$tV!p%=_jxC;cd{tN8 zCr!!8;5kShD1UZHOHWUuKc~aLwd_)hWU@>b)<2(5(zPF7*s^6|N2|3X{g<5l9ZX1n zN>58mPmwRpmOq6-dPBY`)~M5kwT%i3(zoasmN_y!t$EJ@qir-Uwe8%wZBxk^+vov3 zo2P}3%nUowWk8!zquLDUaw;sUZJ17Hj5XyO^pP#}k2Y`K(;ggbr$y_JA78)a)mKS1 zgKRR5kUt-T;=cskdD8DlS`yyO+KT5xQuSTdw=OJfZSB}`fSi_2mJ`>@7w(Ziq5A>1 zx%1tP+)c19<$e{vy$dT)zJb*zBCI^YIi+8T_u3H~vSv6KYm&DExDYrFxJ<>BV7&;y zVqj&Z!&P{yJ1}pabdD^MKc%~HP#WZBh=FqAV!?XD zJSFLEm7h0jo^$3W-@ocStA8cpx|`@LbOijpa?^DYZA%_oXgP8F1N+w;&sg|aiCC{< zX;KsXRAhO#u=wb#`?KJGbbMI1^2n!xBX!?J($|sKk>?eDSGY7z#qW{HEwC$)QDyCQ zNrdG0H$*;++>TuDMA8_2?DxREa z-M6$87%Xi&mG*5-k%j~~IqJ~}+P~GH_K{(OdoEv7n%%C|w_RQ!MRgr! zS7023JEm4DOa@{$f;%OGWD3N98#@%*qTo9?NeT+%VAF@fSSKj`qeeRWGzEfsXkPq}vb7xZ%=4xE!uN3rp&TSNDT0Ss`-t_w*Z074`h zczlqiFkEr^7pw^hG4=^>ocMN5awoH;vu++4HS2@+BciPFvC-X@je2^*%h}_i<2pwq zEt>Gk&94&O)C5ah&y^(Xjq@*cxyf1_+6=&rn*Mg18=_<5tx;WXt~xaONM5zMQ?%K# zV9fKkd`nNl{2KEKLaatG$joHWA9BFrDTO^ahQsa~Ga%A!IqbOcF$eB6rM2_(&c`bb!m@349Wp}}yCv;=+(+V;rizC-g# zhifh<2|RAlk2NVhE@;azG{0)j)1Ujwg&WE;+jI!F*attZ`6N_xK}q0ogT!l6dR)lW zByb$PLf0+1DCdah&s5X5V6H0y=8;pt8oz;?&MkrUr8r)rlM;Fo4sD?JxqN}DD|o+v z{g%deZXSfT61>A>B|Wy{)l4Kzq;j>M812kuZ= zO*KkhNj9JtS~Chv1u*3iX)MtFXL<8=yL)b-XE(LUB&C_ddKPV=A1#d9V$Ufn-J<(? zP3JEPs-oiCUmuat@ta=Xbd1P|9%Ft}|NnS<6Zocz?QuMFZ*G!iYxZ|*ijVHeSj+8!s{`>!C@Iwkv>9waeY^rmkr$%&g#NHy8h3+AZH8;0$zT$GcVQ@=MWKgFT2O)T%}yK$)8 zoGA%cssb{!QFgmFN@#$W@O6`t3I^$OmS@Oinvhtuvn(XQ z5F91y77Y&fS1SF(D_`&2JxUcLlm{y!!!U}I$U{W|7~_ImBduI}=zMB8a6y z1S@}O=Fs(jv_O!HR7p7w!(rvLfeLb6b>&d2(Th0U)xq-AHt=O|KkI(-)xY@SN| z3-2=Xse6ut&<5eCZf*f%B^%qsZ1dSw#C|-$KVX2*5){jifwQ>vC3f1%^AO9Rd6$ji z?9}R}xtU9vbp8w-5E!6O${bo0rmHLISW?!Moo^Jq-n4AuMD2=>)zf2+#stp@o{*BT zq_JVjl;zD=sZHTq!nLEcBNC!Y!zY&p33J13MVoe{6vR+3J+rB;A~&PJp4<@gXpEvj zai%yYr})^GnVnmVLAAr$MjqW1N2UiA?})o zksDAc_Yjas8S){ug!@`@0+(P&XR|}+U`LG>K!w}B&%dS*=4B45D~L{@X_S~VMZ31C zlX|}7;Wr;V_bpz~6*9u1*Nn)D4JlHEimbgAQ`&}9#xslN`ss8lW|`l}_g^_NXmi&> zZuEhtI(Sn-wUu&556TU*!wzN7MfTiej~2 zcK*Tw!d}IPi3fJliqA_RQkS5JJuD_SwoFcwMr+mZM`t*bGaY#gv z)h4P=lq>P(FB>c~&=#Q8lfCcSsgCZA+GUexEe%+`dfK$fHl9Q!qsNHFvU;`7Z&-3L zm&>KcmH7G4K4yPsQ9ID&0DO@g8_T(vc(!o}#^Er=!~BUyo*4;PR)>?(vf3=P8B8`o z5`XlUcym}{wWy6+Y&T^(!&O|rhL*MQ_IG<><~ssynPF-8?bXa=XGR_!JC5cGf|G~& z>D2Wy5i^;VsX)cCO`bMwL3@$Y-wdGn@t4k;yiB{Xd-ZBi&u_!u2u=g=*e=d|&PoE3KdW;?Tz?c7?WmL}O)hM=SY z&uSY;Gte_x+H=61AK`xL_z>iPfik%dQ+N&&JC~>Bfg1^w&4UJU|FiRNYSN~5hsk1h zXJ%Fbg)jIYy&!A#YEO@$J=GHs3?N}LLBWvh^==eY+R^Sm!PzLlCr$ta4GBVDkUsba zy%dcjy%e`b^i|}MzD&P^rnnmq?{+t|z^QjP^hvYc-O&FqmfxL9fR47Zr0@W^`FQmc z(vL}-lyPb~3t@arMk9Z}Ps7H-CK%E>6B*JQpwU8*EsC924nc`zN)}Ryr9cTy0*nk= z!0H}ofa%-)5A7T0?q}9S+UUvtH&_KP^+mj4x>R`e2Ulrgs@*B@wb@RWFNlpUF`t~! zRqDH8T)FWd)lA*`9j+>T%owt)db+>R*V^Nsl;IqdpIoDni?x(YR#7v&D(OPw(kRUX z7G~t%Vzn$F;<^F1dUSop;(cbG+zD1hi#ii;9=dY?!rOt*gHod~n$X7|*tng^?I2{Uw9SkVb8rRSe^XC@XN3*qI^QiO|n zdJu3v=tD}lX;*P^zv$>j`n$>vcdrsKRv>!d4Lrg-NIw|hEAsY!zA~Sx^e~dm(4U}J z?S#USJW1OKIn}tF>E<~&q0na*a&|(z#_u@4z_?F|LfF2;^F`zz@Wi$T zI9CvLj>R8o5)Do6xbHzS%k|iF(mVQxGh~3zHYMpy>>2PcGSq=H8bqp2Qh8ya~JX&`Dk3YbRuN6vV zGT#Cd9WE4$g_2;?!!xFLbxcpRs+a&RU&Y&7aK;|fAq>Md(R7vmi zQ^$>)+IKxMU&*A7u!*(mXx=P|LM*hU2$dq=Sip5(&rrX?nTp}d3Tu0|mvL){870ag zxn){MslFgCpk+s5b87Y0I{(Dni1KvxaBE8t6Zc5ts8NlNOs#E8mj+d7!WBw&WX0ym z#h8nv0nG~8SY(SxE6Pu!r7mucf8MejZJaVI#6N$KHi}mER2Q|`?oH5U6w88Ag64IU z%NhT~XsUGKx{0GUY{*k`@!F*e@SYq#e^5X)H`Uiy6A^u$B;D^bY$)R^7K6*X(M{)CC70u1o9H z2BbLBMvbiOJDFiCA@kvhxSM~Tr(%8ILm*l!8I9cLk?r=1_N*W^*kIm?Ov6B*0}!F7 zX<$faAU%;CwQ{|e`KIH_Ra`K~(PNFk}qVrmPwe>O4-}%`yD@nV%rKMI_X%l%1dA5C#NBbB`hc9i8Y$Zmv%{DzGZTFMtJr zP?3>esGo_j4}L7T$N?vS1lFgEH9i2-fy(PGJZMpt6|f^};0c6L5avM2lSBFZ`{O2b zZ>V{~Q9uu;H#N8J>Ks;?BNM%_e70*ya71xsWpPCC_?{k=_>qv>;1eE>C}1c+_9isI8Sq zO+}--#@}~g=cD4W?TeSsD>-nccVeruZ+GsepXSP&$DhwFjnL~ON^{STZ$>MkqGlqM z$p0wF0PQV1@)?Nz^$q6-;QuKQaW@y0qFU65#zGv;HZ%t6ZZ*8A*H%4VQxBQ z-va;;pEyrpLPHV7wDfl%_C7X$VWTG)<7w^*8$N39Cp|E3;%qTVz;D z?a0^_eqRnVD@%&xhRUk+^fBq^nc9@FNSjfXrBtVOPo3Ibl$T$GZ)7nYS?}#>KmT39 zpg{%2d3nV%+uLX2pNfj66=fM!4b5uZ2*jkSLo#zjn)0H!xDj!^q%@mCJYu9oT=bqz zDGCkeOLB{(Qil{VzPxu$+Ry;}t=1F}iWrebsKwuCL|P&9cCc2ZkjaoQ<3K~zbWngm z7otPub$r!O6vYh7jZdpewN+N8L}d(4O-jx-nw+uGnqaL|b@{SNs`WKR$2v{M?Bt}> z!5L90m6f*Csceb*G$5HlFIOqF z4h44;mM_6F^!DOm73Ys1KVLCyF}i>+;s4_QZf|ejj%==6>co=nz55?|=HkU?9@)RQ zo0@|}_@{OFnfiK&RRR82_!4(B^G|lo5iEb*>=C3ka5v*cnqX6?=@}OLa6DAu7S3SC4dx|{e}t=bzr)at&$)rO5Q8|E!AIQ4jA!u< zHI%b;Cb1;RnVhXNlxyW1yIfmeb$$G*Yim~*HTG3X|0*@M3psATCV7B%4#L1<9CHTj zkwm}Y1H*J$3{EW#qXt;83Tvo{=;Ak!LJc|4y6 z;A!#{{3^|qUFHPdQIl4XkWi3TBhAZAPfbnF&683WUpf2|o_TZY9=v;5&-)XRaLeq9 z=J2?<@aBryTNbBI9{hgKvLRLX?nB!(qJTzIl}LlO@4L5Z$cx4u93 z(xq|uJNyog!%v~FexmBcRpvC;{Xbz18jLQWXe1wZ=_RTJZ^1eE0)Dd{O?ne?Pd`ig zM;q?utz=vf*<%G~Bf%UIxbOkIfV?D9*Mmkdps}+G?n_5Hf*b>PiQorBvL6(J85c;+~H?QOtknpGv-dL@?DcOXTIgi+=S)h>mTf$e!71A@`SlpzM2j*NYFH#jGKMDH0WR8&&bZL zgUy8<6tdRzTlCHjcN{QYcUlSABfbSOe9!UW?2k{Zy0E`RqUNzwuAYF9WlJX)Mn0 zn;-DTGv9$;MWX8k{XQlN$0SD%8j;8-T#j6&W#mZ18;?`)3H%&RMlZwIIS-v~!JF{U z=*#)+_N6w;(EcQ^^z=!75lFQ3(Y~fatF5rmW-X+?Am8Nb4*(B9t2ZpvceO#qA6;rKEU?)MASWDf z4lvUwm-7zq1ot!O#ca$;cIPsXnnWJ(PF!EkboU?JuoqmL$v-^C;ok8a8>_w-a-Vy_MGC!3{RnLC`Vn|;uOpNK6}#rf(p}*qr4X3hcqXOXUJ|+LFU^Ck zKP}H2TLb0wL+7p^DQ^I4Y%B8q0B>4g5gw>WN#5t~cIbz6pi~c}ZrY&M4G@VE*4l36 zA7p-j9=jfPo!P;8fODAhGUpO#u)iWM3IL(x02lox)QuLSd*IE4$%9xW(t*_=ftP!P z2P+gflV7A^}Cj z1?cDNg<@$h<702~3)UL^Ocg$YN3PHsV?cmWLtlAB;G->yGiZb3#%q0;Ua43J4+O;h zU!TJfgWF4n$ZtGqVf+KN2mKXMDsBzFc;N!Rh%6T_AWKiYLg3>Y@0$>+OYkl7^`YhQ z5%`KfR~a3xwt2MLS2O_tFyAI$?=IXB3@4W!1D>Z z&;%&WN1(v5{b%t5chRcS3{#d0SM(RvU-bLrP5p1(a|;sV0CZ=a7K@V{iP7*!Pe>Xm zleFPo{YW!a_yN-V>`gw*y;6x_0BHh=^j^kCJ3bDO9ap5i6WM?BIoIy6kZ7Bj_Ae;s zk9Td-$wz55Ml|9A3dipQQr}16=w{rVsE9Y@dLuBGrg88g~Y=n&3H!^S+6F?TX-h0c!7WEI^^_2b#X^eMoz&B@EI1z)cKy5*JiwcuY(T z83UB#Z}}9nleMjqSY&sxta^7aZ|f>DQU*P31dc!qlY9yb@lN;%(a*q>n$XWJI1j%9 zpFG%rCK*1@;wS0`4XPuD>)ZY_sv&n6-hp=v%k`VL^y0;(^T4p`RR$@Oee_mQRF=Dl z{sQXycB2No`_jUdD;NGhsuwUyc1&kgfHwMnu9v4ie!0V7S#k5Qzg;i9oUPL(ybFOk zMaD!R#y|deoj6YK3 z*9Qr3;!YTM7!q*jd)v0X_x{$c?^9bBk3T&A$Pjg3pL)mU!qr08J$F9x=fK6%akjpl3r>g3*^K&@k1i=P)a90)D)E z{*qBTD^Hjqzji0Kg6gP88z4;WOnmbu2X}|@(AqzM*#wFkK*g%_ zL@M`B&h$sSV7nw^CK*EnAjlb3I{C@SH{`4Ra|jZ6$IuAlV6{3!3d@R*LAk-V21dv+#euApT$3v4qfzAQ2Jmr z2n`yX5p;af(9)i74jlM~9Mre34B>w>vC(~u8-9%bWSrsd3C zu=T*V-;(^${-t;<)4@vu-6xdoL$||4unH~iHBwCiO53S`6c@wbqnKQkcb4j4@RdXAHf8p+^yR@{Tj_fX&7Lcrb#Jr|t&?=J&|pRqH)q;~nq8dpD8FuwVcVzCDKOa21u6 zg){mq$6}%q?Z@{YBiY=*01h^xy17|>&$`uDKHtmTP3AW7E8I2k4l|)biVRs{uNHQOQ$Z$PXdp>oNx+K;+S%7}*IaSR ziZVu|WM28~;%7alPW7OAyyv4UI~tp*)a5vNO0Lf2D+6B7rM18vJhS6oS&rIllQd&gETQ&VSfXl4(c z*^>dxc^7U(jTt?Cr(DB!y>@i&-v?VaF<@pHclW^bxQd z(^mmLHWC*DICNU|tnGwn08$~I6H!P`q#Av-?}g0Fmk{&VQ%^mHZ!D#$H5*r6Si1DW z%8h3-c_A5K4ZMl*-fg&+zWRDkXWzW8xlm=!qqtMh%Y4h$9~2A^-O&&zKn}u6M#C0( zM2`Z3VU+`iRj*VitqLVrAG8`l@5KA2#3oHR^Jsmlqt})+^6@vuC&f+KG--6p1IYP! zXQ;n!_7lkYKuaCB>fp;SJCgd-{?wXY5V?TFCBQeb1v56 zlei8x?<+>vdO8S?cof(1{{gn97>MT&{S$&)fK1|Z-3&_3vQLW{JXXML4k`-~1OyqG zNRf)X`z`+;0V^k`=GgKkpk}-bW5C6drrHN5FP?6uc`hlQLyi-MU^FHY!ADvEpwuZ8DUp-zOJyl+iSWKmN0gcqmcZ&xo#%E(As zvKlFF2wp>e_}Xh{@mC1+U;_Wm9eg1#4EPJ!q8YLl&F*kQtX(_Tpodld5N%g$l9Qnp z>2!P{UJCKait)1Zhflr$*!dLpC@gv5`NMCxzL~av|Fn5Kb~HCP&vvT0Hx$k}wY77c ziW`jDIh=b7vCm! zyw{})Bw))Oia&e&>XjYaKY*PTqfiT4iJrid@jvl)+y(KeFMxqucl6Vbwr~HSc~@uW z-hIn56A%^mHjLcND(}Q`pv z*7wx*&S~4XPw&`{`*uCK{3u#??%%!7w0E9;X!E(Uv-R6Q`t1IlpQ+#2_5SjOYu5j? zcvjZoO}MS2Vp>WC#0MXPvxDuaO_+aVcVfuNN!iGkA@ujv$@auhx=ns){;!*rtUjx_r(XnZd zKRqdM{yV$31U~dC{@4D7n4j=hM;}^#)%7HTwP*x(PA~5>=x=0yU=D~sqW-(*%GJQD z0o5ds7%Pe3lJ}Ww)t*yJm!8_~T7{nOMggb(f&9DC@!!t!oK)$`agQy1?zyFpja%6} zd&?>0i~nx+1&-tY3dHhM=Li#f#nfN&Vxv!C{ zM}pn+??2QT)cN6#O+kAv_qEQ_)x_j@?Anjs{E&Bw`386`$O0Oog!v%ir{RNOB9M1# zL$PZNN*Rw|#@4aub^P);ltOKHeWRr=QirsZ($&HyVf$G08k>#UR=i=Xho0XT_ydnk z;p9Te?3@q?8kS?h@Xu{t0!GWDSYZ$A)ZE-mt5GU|99X8Ng@9b{PD%rDI|Tm!-1+v- zmedI!EX~|pRhY;vb!hlnQ&{4HV;f@<64Mvzrfy3sNU413#e#%D5nmawwr|Tjvsbl# zFlWyL)7GPTJ3n~<-zc0nJImfF{C9nG*df@DSvz4fW?n6|E$KtD`suS~t;hRO_m}3K z_{k@W(;g^8^7C8At-}Y8Zuij1Z2m=N2&@Yt5hD3CZ0=0McFs!jx$TO7v7-wQ>&BJc zxHOlFnuQK^qeDj^trXH`dZp0^tVoAH>p#Y#sr?r>8vpDLfI?{L=7-FBXg9)Pp{JSc z1~RE*^=1j4fo$`aR1lYw`J-$`yMcj>c(}s>0{DAir>w7>@k5;PWb! zicg!iz4Z3;r`|fX1C2ymrh@7^4Cb_Ep`|DqAHh4O;#Trr#jQBW4&bxJuxIu**bnXoJay9?tucmd8OO; zik0|KruQ9Ic0e)gi?rUls`naam^r)k9wLgpOMnp;44Xjf1$7s81~Ks0%+~2bL?O0x zCO7K(p2LUtT#w3S(rqD<;j_m~d360jwD;ipN2iQ&(fCSsX(?qkm>he@m4_aD&~DGf zMeW0ewUdKdQ&F>F?lWfYxD91V))E-`ex@`gIGqVb^I(V&bV}GIO! zjv_mLEe5Ynj(7HU98>01*L1w#u73NV`=-onNXe@ooKxAja_W@P%DGJ%P19WE=qXcI zHdf{guFp$pm^tOXj+OiJ!&H90Z@l5_rwYs8x6)PZu6Hsu=vYUuGd_7pM@@AecDSpZ zvgGBL9fM2;gK3c5UDs5#=gyZ|(FQ|wRx(+VhNPZC7`nffP+cVYmGlvydlL8?*0*-S z?(u8c9&!;p(7xjQ%()H`eSBdT_$Y8`u)!p{gQ#R~?&vmu^VQHtjG*LOhzY_DorI7p z5H>kuZSBP68`8X@k??^}l92-B9>}HQt5}665yJR+p5*a8!vs&tZB*)IMe>*Ht*<75FUyxoA3dP$JvYRNh_)|k$8EzJ*BN1JFug>EyZ5GT#PcS zt1~mJwd1q3VWswba-KN0Fh(1Q%E);c3e?6Fj!h)z`S#K)HXQI#0qJ);|G_#^y33lN`_=GFVM?}G23e1lF zu>QUWVeWCy=EZ2aMUNM>wY9m%_m@VIc>F(G{T{rJ%01ZC*4A~fWX6RHGfMD~#hdq_ z2#ww{os`4VdHT@W%xd&rVbIjz#Pk@$*o{B@uyL#bB3A`Z4Jv#Ok!n)IAIO~1NRIx-qE%X--lMx*fl3%@Cig;td8s!(2fj`! zE;?FwzIt)YM6sv>!dfg#f}(h9NJkUh>SrY4kp=vOu;BK#hm@HV=!8XbBC z{&4wf%)B+>5VseVu-8vy4Z$L^E5Lsre;j|gW^mE!k9Y6p*Rt$`M6dSnk2KiFZ6s$R zXEyMWO`N@)eIRQehCOcg1J5vmz=7V#_DB$MM$k=xF+ks9yBIsd>hJg9a=;o=V4oAh zJFNQKdL4AOIK(a>0saEL(o-%OytBE%AY``yCxVx#weIkP%JHCSwM;LHX zO3K>9hu5a0?A)^_Z(eKbJUEtyg)sXj7#~i~5$oL)Nt<7JzE`M!lV`C#1L=q88 zc&Rn|^yz3TlK9uEt*Ef;=={l=&5nr?Q}RcT&Yu#oXR~JV{G(m?xK&;2&%EJ=DcLo0 zQj2o=G5q7&r5iePb36OauJ$+ShVrN%p`=9(PE%%KVW!F1kTce7HfK`2Aweep)z)aP zX5G5HwQF6M($ljU%BBHApH%Jb_QdUhzZm5?`)3NSD&+J2BH}5&4;7Z6n1E z$+pC(EQwBI6d5{efA|57m>eN8ha!pzu?P%inrqTC7Cl1xa1^1ZBSTSGB(0C*+H_oF zG#zQ7bq?eRp(sCppP(S0fE$@&BQN5OP`Q=+xGq{9ZnmXcW|isv^(o0ob0f3~xC{bd znFL`PDiVE;0_DbNUUY;apeT)>TNo0I8Nkn1YYwiZXJBCOJpX7vXl zTp5I24My>@G&+_UnO0pPtZYv0dnF@M?VnUmZ($-rBJ(SPxFHnHQMQ0|WI&V}?|( z4ycU|1R*-=2~`BfKPn0+AGx28>yk<;WAh8xax+xz8r3g|Y5if*M3qpc|gc?7oNoSO+#6D7mM&u)Ae8e3_OHz1*yi_6= zgr+J*LLX6>k35$ON#n^B(!lW1Jc}k!9s-fhoU~jj_N8QceVQpWFi;>9O8gX3N~F;! zg3bPBa2^&5RC=YKHl2x%wbdG;2l*7m7-j`J>XA%IWdvn00)^NbkfiVp&=_^*km$X-&Wgi=Te)jT?c;*0%xR4~o;f$eF0)QBez5lakGksp$TND!$oSF9Gn z=w2k!3b-NMCj?6V7MX}ft2ItVVlehwT9Z0HKU>WqqKOuA26Ku)U*{l$jE%`|05)}Ne4x=9hy>tz zj|-G;KF;G4-Pi(ixjf>6M=n_QLKqA0)$5Z`!+cwu7C)BrWzXKWw!J-H=HSP)aklxW z5%cF%Rm~yC#^~1ij*j})XsW9%K3dbDiH>jU-ntcitNMQDzy7uJKT6*6eo>4fDo<=% z{oQZb}lIrp8eZ*tGAt~Wb&(K&#r<4cQq$jtx3%;&Y`VqzWQp-)u<;{J)``YR*{BG>HFq4`(xsoQ^?c?l-}g>MPFIoPWcrpl^vJh03w= z#P{!5DIf&5g--%bYn`CS0ABRqrwnq#+fd<+QA9yR7h;5fT;Az_ZeoB%+HP!~H+bfdxHiu|fdGHqv5Pe?{=0v7gLAy7-d%J14g z7Vq>}BB=U)1HoO*2xw`}sosi;-cvatx!YGXH?P>9>pIf^=*u4kJ&n6R@-l7DKQUK=0H-_y|w#e#PSdt^PkT8UB!+e7j`i z8|RU4`Vzi^-^pxKE5@*e!@wGE&&bS7X4AcRb7P>4|{q!53Sg_R~hKT~CNkUZF}i3iJr(Bg!s{GhP% z@$e*yXlt9z{R&7`cuU@hVDCL8th*$h<)NUHhrxUtNSp!vo8}uVA&;{bOZ5D#rWXEDUn?d5Fc+z(Rq2{4(Bo(-}Fb$O^9zQf;j+~+CdTE z{#$^Cp;4#-wZhh8ov`9|A*{IFj2=MyVSlP;LDM~lK0u$L@6k`N6#FK{p%6u}-0V;F zTZ`tFa_3P4d2VlBR(boIN-qgdqa|u5SQp{PsCH=C3oRtmaOT!2m24WU`haZEo@V%g zwr3Nq5V$4^sS}}~{@g@K1zt>4OpwX!E=ZmSAScd6k9mB^&3`6Yu5!i(-8nwprLK5l>pyRQx)p~epk(vTOu1$-JmrHEj}U2;bocrO~HRdm30aD zf(SlO?&~KU?jyFA*FP?bOO+%wi4?*U3SCo;Srby*_<=YfNt{;cYYtaOpAxAw1)@}~ zEY!!EButCc3PgN!^fsQj$X>8gp-<#5$qfxG3{e<^9hRYr3KSsFDkUkxc!}1+1o_F- zDnYu93X9??7@LiXw$ricOq5-a5arM6hJJpC8v@Fr&NV7DTWk-Zp0`ADBP_0XI^P^G z2n~R6cfr(KhIl#-)XXpx8hJQD8!X6qwRcuk-p z0u)H2Ja7S`{NfmWBE>Zu1g0t6(qw*`>q}wtt4!MH8MI%hStRz8Y|bt4$;3Owdn0@V z49`~*zs$EWMHj?~Eym0hBDHy>)Ec3RDsDZ`$b)2DbWS|d78k~sCODGudOA%OkLbap zQ=2P=71e1Y`=*G?ll;|@^lMH{R6#`uH>e^%G9;pJt3hlJ2#9@Gqzbi26rq+4N~@2G z^4ICLK00oihi~0QD;0`->`8eo5z&L0L9tDT#wG!sGl740AWzK>z{~3>o9X$#Nl#uxX(;XD zF=Y3U&AZoDY(65D9@)I&nwQ>q{izRIOr{pz=PXQ!GYuc(uYoux#PbvMJqV4VV6+Bj zn36=u=pO($lnQ_mKKwQFyZ7KB_Q`TOp_wr=w$pKM-t?NrB{vZPh3lFH_EoO<=6O8o0v+qb`k1eG5F%uDcg{%lyg zP4=V*cZ1tM{Mnd`7r=*$`oJf49-4?7@GWjyN})X-WWHc2SL?mkX4|dN!ag+0+9$jNw<(mFweEq~v z6Q@2jVn*@2IY*EDr+CJQho(+Mm!F%z@E6Rm?b7!vqMmzr#w^>$S%+tIe0g}*M%%0z zWS*AxqKQnmm&*U93nHe;hiyW)HV@u`;LA*8GD-exl+#d?ySTaK?CjZRTbdW=*0j`h zBg4VcgUHZb*W$W&Sz79{zd&1yCeEy$T3&YG%$Wma!*mJ$e_m%Qj z5U;ns)-{)HqyI@~4=)%s+QiKov@)c*a>d~h)ypfJLskyT;+jT}T5#{HWPQNc7T0-q zV{yS>`Luvyp5Um!dpL{J;>HMT^@mDApU_HS-xT+L6A(xdYR)1ErZdzk1-lD{2Op&S zu&J;pkm12fd%Y=)bsGg;1@bZ)pjOy64#?OEypAVzr1`|rx_}caBmBdk#ba&h!uZr@ zBE#(wS6!iW={WX%!uWClZ~|LT=GQJIk|T6+;Yv1 z9h33K3B_xVTf!nI)}^+7H??NT+zQoWYdSy0qyMvcy1&ny?MSeqxx|#JO)eOFainc% z>)|nlX{uCnQ8P<>5Ho|?4zZ%Ze~q932B{vd0q)h*_GPm+k4ny5IP;zv4rkKF=IY4! zs^zVXam;J|&vMDLMuKT}K@*DNI0Ha4jw^x*dd+H+5Gg% zXPyj=u4yldk&4j)^c3EP4=uOwJNuSQIlXF3&PZEGXkc>R2MD~kJ>^pbhs53hGvRWw zSBO<^a;RxJBc~2Iu}$qn=hRMI1;k-q%u_M{c3p&@zR#$xS*{gq8KrRRM<|1M4FGO@ z5%{Mv#5xV=C#v+EKa3CVSMdVz z7BsR6Pe(n0Jkis3*>--oAQ zLPHLoPB4ygFgNnqDu3MND_pMAEf+$SKO$h~v+2k%kdE|hdUnDa>GI`LUB=lTWkpqro{>30Q(y%@7$B zumv~Bz7y<9Fq+&6LJ&Sj2_xxH3)vkrlU)++G?9IbKs@qcp$UvEf%QWWYFIU`(J~5{ zyr3C2%B0&+0T(VLc=!GTtQ-4+C zU}ZXb@Fm|EQDz7FiF!<~tZxdf#xh6n+oo7Q0h;|OiZXwQmm$uP;`Ho3>9C~vb(!y? zIot!eY1jyJjIR`|*U;jRb0hl9yr*!zNHl7iub(U6SMcOgVRfzcdG5z*HNG#D3DP8< z!=n%%Z5%6*^Q!j62KW8TG|KgB&F~l#EL?52=n5bPoGZ9OqtD6`U(DfZbeXtBX%K^ntJ%e~WuWG-akI@qr|fKR#k;R!aPaeq5E*z9 zyl=|;dRgaCs#$^>xne2L_ELb^7=ephBG5{CkZUYDxzSX3lJp-gM~2(@-}40EmLdng zUeK9XM{MGbjTqP;4%!YJYV1~6v_w=WtK15^%36>dOsdTHIOj4%q4{+TnAm2_rZ{~U zT42!qTnjY@|8oqs;b&UgQv<`8Nn`LxR~WkQGJ0+{K2Iqq*!oa{E@FEMHPhAo?Dal~ z@d1CYP)rJ5UxDI=Ac-OhC*yI5Iwe8Kg`ahO{d-uAoajG+6}mT28@rYt$nP)1I}*3v zY|1U9ht!_^9An#GhPdpxrr`-w^}FUjjO%ZM_;%b{!9VN|a7zNq!Qy|QcQOnxf)~o| z;Hx!%72h?VOex_F89FOPUQwcSj42w*D{nYArUNJZ#hScvKtCmJxLwSxVkFk|xXJoO zV>G@p*AutZiMfqlbYN@$LyBR*7Hv1!mEPn6X6gSyF{1}}ccGZ? z7c8g;Px9wnpL@ZH9{T8`J83(LtiSy|ZF|N;o)!RcM?Cky91F(4zewlhdFjoqgZV=W zW-U=S@X@rsSmmoY#k&S~0sq;0YfQ-0*qSm0LXmZ?w5!Rh8bI{7C)a#{43Soz$+}iqnEo(M_O1vj92;svwdHrKP_-@b zIh)n1iBa~q^QiGu|o`->n!(3QW&^IRo&z`*0!@fQ9o%!j~{1Hg8a#LgoT zZNWsMmVw3JBZpzM6NL~+Swx#y`Hbj(scZ(Z%(|edO^GHD!8A`z-xADL6Oq(JOdELH zKgCOU0aV%B!4C9&b!bzgT#lmV6&<u@b(}H6NjG2)M>amFDCYVHFyHr z@ipF&K|P1ZTtyA7?KjTgjG>(}y+4XURa^-&3nPT+9Bk-}XGFB1CnJpJfR}7MshB&n zZ{o>9(?+s~muGHG1hai+=nJ{vsoDVJlv@jl^&H~=_6H*kCCrKZexSi8BMt4Jk2I8? zk%oZd+7ILU;jRPJa8MQYQ&qUeS2ki~a5)~75cnK8aLG~CYe*7$8Xx>(SI)G)j)KJL zl}V?dUbb`xE~og6&(W+!Lb2%8bW`7Q?moOiC@gK2Xs-Hwq)GV|=a;Jwg0Z5?wVy2? z-s0N-h;PheXkCwQ zI<&9jGra#)QswkS`o4Ef>92}lvS8m)O3bIwA^VN*nE=N}epfY;)>5Gmb)}*vyy~F3 z96fU<78-1o|XtWaW;Wp2Z}9d;k9_;ZXy z_#e<+0GHsvbIU3~s1?>-c-d^(kuuD@^{*X|Gjo2nm}x8R>ZwA<=_7PqCy|kkY(M-`%zF3zHFFSk%39SJ#5v^f5*ljW6ulIc?O~%EGBD zGNz3z8-!9umQ8G{h6F$hOuhQy9lTGNk6{)+lEaZ8+CX>}h%nZNRtetnT7wc+267?f zgx#iaTbaltC+`#d!a3KG@u<0?cw$ST0oAMU9$zZvncaBL5d71YZ}DUIF3|RT_2ABb z32hyenZDPDs+>HrxNrg&e{ygx7A8Njcnd1~b`uf|eRamc2funBv>s@uLVTHjoOuk+ ze88R{2X4bLfeV4jz&9`k1iCl`agehsXgqN8kK=~x@symYUwI=LFJS`E41754dM`bU zERU$k$f(icmzj&dr5(M|!Gynh{lQ>n()C{IDY6nGy%vn7ygzs+>_T|=xBe9f(3AZT z(6;`wKZ1o@=7FVeO+QflKZJ?w^LJNVcthoV{%1HEfDmo%hj8HRuCU^853ILN2t5h@ z1n%tr1^I39hREtsPwRT8|5+`#U-rY^cdq|z0OJE`cdbvxoo&#c0Cf0!nv8&zC)u1K z|JPc1x6S{xh6C+1aP~LrdaLPfo!qK4w59Ma^&a)VYEEo6{cY%7+dn(^&i}vLxxbYM z>ib9S-Jk2O?S8B3sP;|@xRr7%4~$X0_bMQEWUuj$x74P+>i-w^BDm0)o7+eZVDesj z7W~n61MYl{M8~=v7BDFO6=-gwxBtCM!Y#=9{QfqKo;m@ntZn+%9uK#{>FpZ(draZi z{`{@CFhO*O+i~%yZ}4{3z1!RFR$I?|2Wkh1N4eA--U&JlAmNaJI_{XD)SMTHj2*ZK z<7jf1VRMj(knZ@9knaV^$>v25L!40=l;g?kj#NpaRFZ=(WrJoyJW{WGB6q1I=Ro{U zlG|On`KQ_kjfa~4|ZeE){ z{f8g+QZ0MKlBREksi+V3E}uJe-6x3m0JxkbpnUqpH(l}k7fzpkmFXKn$1^RB`B5`m z=uk}q0P6K#HqyqOZ~ddU+*Ty%Yo+**;AQ3}=siP0^QnbmiFp+atUjJV9bmR|vpk5U zK*o>YGe*=rw;a9p`Ihya;miS#Bf-nHDo6H^ z3Ooeu4H=shuEUe?7x-allQqKpp-!gIo%Rd-5}iW-@L4rz z3@T~ye{1wwTsGpym*PylLQ4N8#dTxRy<)jO&$R^)8i9^byB@eWYuGPyz z3Msm8JgygKQY+BW5jY1(4f;IHx$wxGi#x_IZQ(FrnhHaFnjv+E<^Oi8N6ZsrAw)$Y zW=@MYL*V_mtpLA^|J?#{jo&TUhMwMCb>Fsmc*|wSnwFL|j>~wzw1At{ z{C{zOg|;ncce{k~zefXr8Gf*R1CY?DtXl$D3f-Yhq3KC7G^^GLE*tPc;IsiB*a+H7 zO@K%uB$eHXofHKP%>P$%VqRT(WU^1OrDUvS%OZRgUtP4t(rVr$OpZ*i%S%kID)0H? zp$}Z)bLJU`^+H5)*^^;I=JmM3FFtgwr@X2yE2(0~c7!YnXRP0l8-Ck&R3v2&ZH@{@ z{G94x`SsEKywKd?Nw(zhvEwF@P(72zjfqIMB@GWEBN#0;w_hAsqS>z3c_$t@(`_s#o! zzJL5I>vXqwySKYDJ3BiwJ9B~>;ufSE{JSP(HbwB+O(exlE^moLK=&L7GLR(LWit>o zHWNVmH5Yd5E+#YqI>Kg!rU(jrXpw+EhB#2_LqC`~gV`>($YEzl5N!u-3iJ!<5bzjI z_VHOjb_n=IdP-2C3iqG@Kq19HWnqcq?ye2>kOkbjnIMJu@OBw;Tdnl)^s4eGt;b5K zcwc`yORu-sa_>j&j_NN@kTdZI@l*JzgKqvoXpSsBU54fa`QLNsv*`Z&#L}g?HVfaW zRNt;I^{Ddlq+0*sVP+uiIO(_(8W0egmj{Z1j)mYHrsE_y9_(01?^#Dhhf;xV;cjUo zHGV#UC6e`?p@A>I+z-8tqdHfDZ@anfDjA&@6V|U^Sd8PWOvU)Emy`th_-QIa@m*?N za~k9gq(U8g{^8ktY6l<5GBp7qApyCa-&61K!((HGG*&nVSka<#@OkC~KB9a8QY4T= zuQr^R@e=g{ zdNKdF`|-n*=i<-W@Mm);ueH%GwtIO}^&p9I2>tBo31jBY>d4E@IsfQ$KBW|@d=Hsc zZ@jmjqIfKP^TWfJND5TaVkd3R9XGq@lAnHIA7%eqC!tL{^+{N?}QJVnVtk?&)z& z6KhLGPYL!5(?xg&p)4|%7|{s)2j{|W68vBF2i+&k%n*PQ!j5ugm^{tU5n#q_0E-nu zJ3#aboE)V5IF_$-_+NW1?S=u_CrEpS;^y((b18Q zmy{JI{1E1IKrBA+Kcr8@r@sFlpF+joe~*eo@arFbh+jwXAAX49@tG+V6;sF;W&hvH z`AhlrP*zX5y#>Dhp|iXx&@Zeg1dnss${qt-hZ2adG1zGp1U&-XKwwJZ{XzE#(is^b zt5qh1DkN#aW|ecY5zvNM5fVaV7hG&~Aqts(h?G_!Dsl+FEK5V2Ot3Oy-pPuI$s%9+ z<;p83-x}0EV%~6zW%#^^{)65+d8INdvwe4JN@3ijP;YO~i1;iYjm9S{KEl)6J9JW9 zVM^-m_DqM>rVTH&tu0ASE%`aMq=Zd#7MQj7@j+!?IaRG&yd5vZ#?tBDTUx7fyvhbW z?iQ<1s&j)TH=4|DPe~+CxtUFklY??0?p5qVRq;WEyd$$ohBwHXrl2vhrp!z*krIJS zs0lwN{(}7uv=<~+T(ZEE)rdxj+C;Nqm%=j9I?;CUn{Yz(9IR3mAj1O*9SufNfJo4b zFz7*!C&=u{Py$b6uuYFl;5I~_gJ0MRNfhNOuQxqAc z&T&Y(+6MPYrY_5OG7^O}uzHSRJi>udQ(}c(OPI`Ft zwo8)m#z!`?#mJU1lX1yLVQU92_BIuzA+6*6xzqgohRyOrS6ve(mA5?pw1>x;eQVx! z^=va#OC{2K6((lg?Npcu@VKQ zQL*)@srBS*3e^XMfLU?0(nTLA2@FE-T~*;FXu8ik+pfK}?V2x|jTicc*(U6~`Zg8x zyz8vJBdhnK0Voo`6OfaK@8mwCwinZ3_>G*_geKGA6#Kigo~RqKYqlGGIze4?pd!Hy zPZ-^3E{G0GUpB3#diFyFw$1fx`YGZ9(mf~8i`~s}Bj+C+S-atK&EV>q@fibnet7Z0 zDtV%RX08u@6ZN0BwRG~KdFLs&)avR~_#$7moHmD{7`4((rFWMAkD7wt7w5385Xt5m zp~GY#Ikmuo1T>s*mGQ zWARJeX$zFS8;5|_L1se4;gtkY;c>u9QeLuP?g8=Eg*O2$bmnp92RaBDpePl*l^AYV zA;K>dF4UNCRW@7l^x*ze z$9a>djOF1)LxxO#;^MsZV^u>&d3uf-q8ht?-o=#i(h>Vc4s=cCveFV`;V&^Qi%WJL zIC9^J((uCU($LDr3^VR%57UWPEUNe4W-y5im48zc$NOQ5zXqTVczp%}*a1pzzYx zk^3Vxp~lvIlO8%TuCAbQ!TNsv)-Px*s2g|Uq1J-D$CjUNc{d)N3>n+V}e#2Kqqiyx-;f_`T-jhdPjK@*G2+M5#06{c$yTu^EZ-R{-c=u1SL^xYbFPTdXn(GK$hG+2m;1pyL-uNI_ z-kTph5h9{^rlB11b&`GcF_M3#P(YKA3-UX5kb(pogZp;Deue@V$pz1NItc@mUT>y@ zot)8M#Be{~38HV&`HnM=XXz8UtX|LNvPNA*gwDuhL`G(iZ-@Bm-L>rDd)169zbG=A znjQ@f{R17d$=!&I43H(1;Wm1bSW0$5!a^R=YB>v=80bm()8+W1stIM7xcaKYyXm72 zwHmKSFV|38ZZfkR*995EW%dsSf!@y!afLv74D^QU=`3-nlPV3GW*9D2QFW3ynK0nbMt)RL$SV(XPui9e@WQ8@WGJ?9NUEsScTqfxSg$QdQu^phevbnc8kP$sygz!kJApS-FtM z#2g8EY(rKLu&zo&5W=IoL4*pzEa-l;_7v1&{kP#E!DldrCJnA0cofY@JX$*`A|%*( zF}q_;Kp?eorvB8#H67TkW<<>hnQ$(o7Z*^cnztq|tr6xa8{-Rg?m{~O+j^?Irh{}G{TZoR4|+L~ zs=HR?fe!NS``<8YAx4Ft;JMpO!U%NXkCT^&F0}V{W7mt<9xXf+d#IXVMmJJaLqZWC z#&1HwP&nh(Jg>33alHWeE~sySgn0mN##uq({dgT)FZKefwgl34T0qJNfJtO_@RhTi z8XP+cNG$ILtg}XJ{n592yW$k7OySW)G~;Lysb}@z!PQs)QnxwHc=KPo=JSO@<(kpD zN18~zH<(OlRNtyP`-*-E@XuG+Yu&Zp0al;5@-}F|9~ZbhR|cZK&LZfSoP9*V)N$`# z$K}fhz5r_AY(LuL>^%WQ8&L+nu(9zlfT_E8gte+@xp*JYE$2H~E3q&$5N}RS@W5Kk zN;W_DU2%!`br;mPc>aF(YpFbEJ)A&s)~xrH(9RfWOb%zKm}n+2zYfM&=XH;I#^O)!B>G!dsPr@@@7}JHy zFd5OLUnT3haFzJGC-n+8>-u7=j*w;Nj7rF`FrW6qPqGgB6Gj8Smk=wVPvMHHcapB2 zadR)ew~p}C7Y79D2h$2UgA)E$HNc-XF#5X6kX1g>*svy3Y9#k1Zj0>5BWi^=XDH2O5J6eLLm+goh2w7pbCK3!&kT8tHD1adCk35P1D;a4H`Uj;3Vg9%F6QWoQmb`{WHCo z|KRU_7=(H381y5r_R8u%yEG=)6kR49g}w&(F(%l?tcSJ&Q72}|-2f!R2SU(-&`20K z#B&27UjDv~x#MXxTHIB~(N$Rp4n|JDW9^qP&^Pav0z=L21+x1YX# z{pq(Uz{k4#GsFX!Z(%(tz?Q6g1b|8iG{pn>aj0MnxMDs%1{Lt*sCD>hI6;MwM6E-= z@GqrPNy4F>N=oNnq>|J5m!KJ>TwqY|F`tsPNhl&1A{5Bn>uf-;;@Trrsn8O6jCp79mDiTRYi z0~RtuOTaJ-K1v-eoLFScr~H1LP2a&;=*Ax21!c73O=uK-$KmoL8bx8ALZdJ&B;7ay{ve|m1TwAGzxEvorD7I*MFx|A8N}75Ad3L3CL2gO6$zF> zWECJk0(m^Kcm}Dw%}yj2b`_|oAY6fo2<3%yI10~@V2t3KcyeWOHRicDkRqj??0CmFDEUY|6Vz%hieToI0^zm6i(0 z%Z{itV?Rz?O1|7AB;ordT#_wxpz{CM-O6>EnM$qkgWc*Nz>A z4-ajbGJb@%zAAMXuh5R4((-Q0Q0;g=BX!tVZ9Q5#WxRH13qCb=SZWoh{K@*S0I%bo zhB=tXJp72wB=K)VFdliaV8{Thu}s)|ON4jb!ai843xtb%`p!CT+=RQ6nVb0R7oT0g zXO~QU{PC$v-p#dFSKD**5-Tba^Pv3kcd&Tv-SI%zf5^sxuPmi4*+{XguC%o7aDTH- zXYOAV5)>3d>eK6=Unclrii8z-Vh;v1i>8R$MJvH?&uY;ch%E{{gAj(>2pi{2FyW~g zC2b|P796+=mf7S^aTY^lQ$S~pNJw?wgCik~kZyv&E;1Fk9qu|pfX!ar8Y%Q|%_d4U zqEmaYJAl^c#82Y?biDFP2ZTY;z4pQjuOXe;C&2o!UyyMd{^dFNgHT`6vllO(MN<5S zr&dv_4bx;*mM>bnYkE3fpo_LnRGJmlMn7id^;LWJthx@zM^|4rzrJG0lI3r_v78+7 z<-PD{wuNZ5AvQP$1>5NbPG1se@0t?3y<`olTv8Ap54!Lt-wRY?9A1Z5H&^U%Lm$q~ zii*mbyR=_iTtAp(f#JeD1L0T1Ec{9kCGo>Jz=&wh9rGSRh~d1jLlTW>-=Oyr(W!MARJ4xBv-jDy$=o2u+=!g12Y9ny2|21Y&ZGXiq zK5tEl%?6lO;rvy;r%_|R2hrZV2A4RTy3vJi`EhaifNwM_#+dd*G~)vJ2BDf5Kug|D z4{mA2_&yDewrIdWJ^-Ia6g&d%Ii?|s;Gz`62|-SObPO@>fXnU{L;+SNUy>UWG$nCHLMxrVb0m761=VvALg?GTjzjf1X-!b zY*v^_NaUYxjO-#nFoS^Ak8lr;U8n?GPYL5k3rCP~LS*tjDsDoXY~}}O4hvdMq8)iK z-AQBW>z~kSyuzO7!J95xgWnAF^3;y-k5tCD)fPXrdB@D`VK~s{7Z$uy5fT{K-rUUe zzrOlWCtZP~*-25zamDLhbj2PVK^sVE|~9Q(UVPz3A!i4z$FBEx67&?o|+ zb&eII2wH+KN;N3b-xdGh%ArVCh{M6jP=vem*YqkBQI-DOU%(=e4g)dwaZfEk+&4xU|D#r;(LPKrQ7r!Y)s7Ab7KQveUVXI#`SE`a%bvMo^Fzh8ZRAn@ zNN|{+4Shjyge3B71K|(Q14(UuezuieSN|QdP;Fhv5= z7XfR|lV4iGAAyI%;o(DY9nQG?;lwb-nqs@Xc+Kyw{wP8t<>!!_v?SZb6-98ad=+3? zxJEid>fQ&zcrU=aaLyIqh2(H|=`VnH;Z^CB>0crqH5L$$fhdLB#80zEk8Ca)6kYV(N+d%lemTf$(Fa-M zz-+(*rc-f*_Y;YDi1QLCT89`20R>4;&WBiJKx^PgS|^-HHxpUd~PcC#a?2rSLi^jpTNE zV1P3K8EG=nMnN)EST3l^g>oQ!_b(_!$xTa~i|BUli4m`>r4f%s_%!&8DT;UZv&CMB zy@E2RP_4x!B!+YMX_SP-O5I$>xJPAk^b&Da$}*S29LmcQPA`%TGz{^yyEXc_C^?l% zFB3xmDo+o!hnK>`on;uQw_XWR!qmD#I$kW5*D1V>i6PYs2d{B8M^{eqlY7?=9x<{s zA*;sbvnIxx=QrM%m1H$)gR4_KJoK*FE3zyJbY#9|YE*(O#?5C~;(@qAd)T$kqjgIrA00n%fJW_RkKY^U zDi+hyP?=OB4f5tRUM>>ftn@lg;vQfM&!hU4P9JP!65Nx+#O{$ZYohwac=*#%Io=Y+ zN=4KC`QV8G|ISgudoW&pr9DH$N@&9osq^F zjQ%MJkwIEUsw`J}>+JGzvNXn2KqYRNU2itHPOh{iFbNsCHh)F3LEYk|H2S&(8|5Jt z#RjD;f$??qs0t3vQ2Qju#aAa%ffl7XKB4&5@|=FnY4yVwaSFM&-or%_=&$#c$3{(= z>g`<^85u0jokpE$J2rVq-C1cyP6mF-RY|2MM@JcuIXp;LnTd+%_=p6uRxyM9{gH$$U)CUIEG}9WcCLu%HhD>P7}=&EeFna7tBg0Mdw5>i>^Y9v9Cn8M0XJlUJF1r z0;*~&N(W&`B^nIgMJJ$H=wTRzf>4tnwz=nw*h!Ib3C1@)XT52?XT5j->FnP=_fL8M z|CIJ`+tK@6?^$m^|u(P%Q0E(RQD7-}>Lm)mu*YE0ACJySnT1e|V<1fq%>1bE~^A*{zXL>8`;E zgA%%af9v{1O%Eh=-=NlX-Ef@hI_XLI7mxfsQ%~-D^$(WUbL)RA*Wb(e7gg?lr2Dt0 zR^18RzujF(Xp&6Sh_`V&*-r$TqyhbNG^}DWK!R2w_~V%Y_Jk{iSWHLAY!3tlrc|)B z5F{Efo2k@YvJLV}7#m0;hCjknPT#g*YuNpC&z+w9spmPZdL!#}R4PIemQh{BHi_-^L z@bWl=E;d#l6we-18KI08E0kh<-)>L12c;{5jc5tU!Ph4w*fCFDL8}uCYHN$t4k5ek zdSwFsS!uK<1a)5hJMTsroMn8K2qimne`ScX2I(zUVEYKW20LXzt}LvD*gAl1G1$^J zfG6cGuTT3BlZvcoWQ?0xtM?Ay#9$$_L9fmyS-`u zL`J(7KL6|d8xem0PaGWZaAwX-+ttDfyqjg~{_SDi#0_24jWPfaS1@>s%atAMe8(iQG|PQqk(&y^;}q3 z)*U|&geHGxP`8Jrrq%IG*0B7`uVfXvf#U};7ve3INOW>>H}B@+;tmdS z^G|n?K=dl`a2`GgXE)$|)Y$>MHO|Fm`oP3*@bNBAc zx#iPk?_Xh$ux_vdk^mK?B_Nm5$t8$A z(!{^Wzu3gQ(Ydz*eT+V4-5j6tUxGViT@ybEr=m-slj_aem9r~T@3VvsGotDEJhz0s zB19A*vHb*U>vRL>bowU9*opHqSl5t^Gj`$*O%2K%3B@fj`rIAiV>J4RAMr6xzxx?` zN5)sme0|ALMmGZr z+k@nbXhM;NHb69Xa7aVW#a4WF9?F}C<~e-l(%z@7Q=n+$z3iRh&YxeE0X??_RE;e(xMj6?LZg zx)v}6d#4Ab&-(i7S?NJ<{Q7GIf00V#FVgXy?}=OR0=$49wwDE}22y?_5XMMUd|803%8eL zl~OaO?oz+kQS#dE-MXlu)OWJ5fjRZ#ievW_PTnQ+q0lw{(sAl1$6;M@D}UgC5>!vY8;75_jNa=WdWB4Rk3VxCM8l zf^Ru5r1=spR`K8AmXpU|%hYN9QyDB{0#V|-=y(Rc^jg0}x~>_|BYorsdp{O&`^C<2 zMk3qifku}^uox{G53>JhPzB=tpTOP@VqWmb&jHyc7sND;*vP>QWl}M619)Krg)&*= zBiP(P7UwFkAqQ3id|kq5f_~=2u(LrdJY`D-mcxRAjb^Y%W^GWSnruZSAGM$ihWG3b z7~m##?6c@X0_m51qLkqP6z_2REIWVPHp><+lj&v3uo&dB3oZ5Ey_kfYURGZ0$iTyE z0u)OB8lGRVZO1aS1O4a40=4|ua(*#2qhBSGzeu?>;fwAHb|MN zb^MZFU-;#hi|Dt`8XwD=9XD?5SYz>_+}cLqn>+UJ-+`1P+R8(dN7g%G~K>v{XA zU3;G^F2O$V3@Ec^0 zgPjmF8@Ii$I~n3$2K!$4{rBgAwBfVsH!zM34mG(JDA{^YzV>lz!+&*h!+PPy^S}Ro z0X_Bm1&4aWdLifYA{Kezo#J0u2WYmY!~UH8q6@%%d2vADf7$@Pup7wW5H_cB1Wq#8MnNQJWUGZ*V6!0_5j@=|l`!cbW+P9FfBAXZZzomZL1~)~ zP=(X_1uXwf?71-B?3gxR;v&zLut*vn>?UUNRbtoqtUS^_O*$_*@;8~TDJo_@$H_}T zZx|dRWte_G63KkGu!AmMUWTYm?<-t=eobK^_*rH(0Uj}mTAz8z62tto0`)f5_2Jp33x&@zm6XfBdT~O6EVv zJ&srYuJGjg4UymZMds>KG(wbg>-PacO{I5}=oA8Bjw#n3cow46s8N2M%?KFJwR| z9oc-Wvgq_=oGN96kje<;$!mjw2u{b%c$hBcq7`3t3wU_XyzwzAWY0%(v1ha;5wG7I ztcyYODgQ}&w~LP5<(GEt!Z$DYC!$>%+=f<0#9TmhzS#e{#Zyk1WfkZmzmHNUN5?lu zX?(@LBTOE6`~}9N_5y5}ef_b??bWI?(965Ou+zCK*_d7>UQh7$LV@p5)-KsSj zwRCGhfMbgm|JZ*F^$YJYra!()o$rsL@zwrgcn|6qpd(D5x~N4##7^s_5AQm6j){ROd)kEy{LdFI;8S3GaLoB7evP`{b%1+T3lq}-@7lk==WPvm58yeV%3wKU(CGb}Mx96Py^lDt^ zkUHyg=j1tnL-!aFLrWE)afSDi>0Ad&l5l7#v^n0EjKCO$s06s}3x=&jQ}3m%8@2#V zWiR4oaB?pVFN0)e%(7w2mk(Qprrvo4elVOY0}m}^?98~&a*JUvoQGh~)d%Zf(10^q zZn3B1iY7c<ZWBjk@m|yuC$341`I)nz^ zIf-sTn^>@A+|BJ5>LP}kxd|S`!D5F49-44sG8S-k|D97@@$Gw?sHf;Gz6gKK7qNfx zMR2y8D{kV89Ov0ld=d53y-h2rr}!e$SHwoLyIfbo4+>U5gmS@Ne&Eh!!l_WMRG3oF z@T~GReBj>A*913k`J`w^9hqTYyLXevQ&}Z!*do&@sWWVI<+jsLf}a6@7;#q6-ik~x z>yZUHfLDtMW5;y~qhPvIfjb$&%o^fv0VedhVKN&47nV{yLc!DVZ|Bb8-;mq6bI1+9Tc4R(Pret)vlX{KMg>0T z`PD-QAvHYp-JqeXai$M``eX8JcRptZr(GFZfL|21v)@1))xvtvxkdsOL;!G~3U$qo z@ghR>VQVPNT;6>vFCH;8C#RtyCugW*+Th&WhG99`L+PCmhouwWg{`^Czdq*4wAZbB zaxebw;sx;A>U-f5QZc)QEDa5Lxr2x1<}?g<41?#FKol45H`J`5V)?gOwWR{TbNC|i z#oqvV{01!R0ZoA}T?an%sjji5fZF>J8VoMU;UDQVh}EfGDP-1xZ6<8&)2ZcV8m9CY zKO~|9I8Ya1SA98tFY%N5dK6QR@0Guc2GnoFTkw0g-`!YGEAcRX7}|x0QM=GCYB%37 z{V15rYQX4n_1(K$?;_0@bmJ&~dklt=BaEG{`VhMmr%fEJr(KZ&ssmQGf^TiwPV3ZA z6%b@tXgh0Zd82ynyYzO)M=g{e{@#pA93Pn}znYf2eCy|C{u=$|0AMdHbKdxzq6)wX z(`4wW1I_uP^#LjJ&&HgmkVjETI=%A!H#) z0FhbF!s1WI8DIw|2bD?t@IyY0O8$^ahJ*C>Mig^95pTI&kKTOu-Et&0KU0tIC*DTW z_y%e>00)4Cb^)-OkB-5sj~*p(`3g1)-kpxWLhFwnC4EAR+VE+RC9MLhRV7%b5N<-) zPYpa7VfM&^379meOAaecW-=XnVpfsQkW4xQ5set6wI$e^w zK&=hd$5E=dM98c03=3*7a-3;s5dT}hEgD<-mVW+zK+AzI=!ZYyo)J?(LxJBh5qb_Z z7%b$iz~X>p3if`1)hlcWATeji;z-FoGaf&TW8kxREIsm@ueW{kH9dxp!tq7<6_x!; zzUJ>xdNdsG;6I_n7HR|v*#7FvJMicHBmB!~Qdx3>J*5mlB-(>-aYNX{T~R+^)+R3% zjyi70DLjWif#;k?^XX^Me0+;3MHg{K=P{-ey?`?XnBTmAg;TPn0!<~HT3;&K=e)0- z35&Lj7_nv3=+T=f#io%XH*FX_Vsq#FJ6c9;-aKN|7N%sx#*HIJZ`$Ngj@1=u>*!fpMkXpaRFvw z`|OYs>m5FY$3`5VFl)oC3CBksFZ6Lh0-QJ#MjV3#Jom<~T{k}2x$_g`SUB?7$m1nS zheKI%98MQ*crc-G*Nq#yc7E~+R1e@d{(b?s7V1ajjUuqU{agJ=@BY1pFx~d4Bexc} zJy^*NXw8Gw+`!-bpX-U=LYE(`DvH62dTL9H`u1h|f7hGlJ}~}&>)BUE78Q*gQCvLY z|DlU_7L6QPR6Jq?^fT}o+*`~Mn12B4LSYHo{SibOfE0vebP*pQerO*OMbn7F z?OWPYXI9sYEiPZCv20#9c}kmSzqrKllXWqX=tau4iFrqB{Gk2c70A9LNL6ccYU=2}s3Ht~yhGSQCV5q{G;DCxO zf5D%f@C3L3?vi*2Lgjd2w>eak3~d|T6ZV`te`g_e<%Cbfq^z_d!<$>O1Nx=ToVswz z{PF$0edLB*!~NZ-Qx|+Nt~hn^5ytf4tO*@WzWD*mrZYHs`l69z3?ZrS?hfFaJ@Ck< zpWPNRvmf#Uwz`z z=r%Uf#mHZMp=jRXHR8pSC{Gu>MLcuELrVxxIs@;6bZTFlE*Pb2TTog zT%BB))hv}4RZaHNu}}IHPL6wq%KGYF#OHbD(%rQ_0ea4Zp0jH1*mk!es(gG!FG2NG zd2zm*R5q)z6@iS;t+=3UNJah#HOqf=44s0I5tLOrYs~D;huEyS&68ym>j;$@nxYgT zEY94q1D%)1Lkbn1&CY0gT+%YkUtwd;+AQ%x0$H2!jD-K11NgMHWA>c4{9Fdeb zC7M>UH%rZk9elPSP^s~U;Gg)-Tfk)gBsZ8ne|#O|3)_#{nux7jKxSHcK)5wA%_HEo zYsiJp<#W8f+`Z(!?3{a9pUrDwSmf67&}*A!H2I6YR^ab|Wa@$>uJH+RXct4dk9dgG zl@W#CKgV5T^|1c+ft67Z)ED+Zayp8JIY=%{4*(GhS@Z}(SeSUZYc5HP?pd$q-B^)M zd|Oa�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 s&|F?&E&K`&RJ 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 H>B* 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

@$%UOnCy3 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 z zw@kY;8gkhAxtWD~t43rOEOj_iW1~t$O5J5a^TZ3Q*P}W3mpRr%-z-#@Ds}4Xpxk6|6ciBRHxA z(Qv^YbM{WJZ!9Q?sl4Yro!Eb|<6NARUR4WgA4N~zTAEC+CNB%0Ah9hxyYCbFukh@+ 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%0A6kkYOl<7(LHlBb1q-r2IpeKA#>gPM^@ZN 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
Y(BRji6-58t2q& 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

bSfupmslN-*%+92oPx9kKt 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 z ztd^!-G#Hf0Q(|`9#vctgZPXu=!wtnuBO<>ZK3|8of>e`UD1GJjBhU9L@tcG^?56~VFhY0ueT^tPT)?_*7jd)HTQtl9C)UvujZ7WyCPKi=U{gaMQzIJMgDN1| z1d2o^JTc5Z5sr=BSEi!UbJ1657kV+g0VRWHzySfZ7!6b9A%&Yi`3l-wK4v@y4pJQD z!(D}bX#2u^DRrNgC8CH%2g6CdQG<}3V9as7F#uG;(NHPH6=*zed9K4zol4k_OssTNb?p{C#j0k96X#yM=$b`?69?%^SKbozg9Sa= z)60eo<7TtYPNBq6B2bN;F-16iw?R>L$Y~t;qH1 zqmfbggUTkiSMFH*NZ*kk>a;V#E7zPpdr(GlnhVK~_goH_b&l?Y_tCaaDLS0ina;em znV(0Rgo>)vcwa(}U|FhjUF^p-h;d|MD-h0T*Nq%wR;)9auOW%4;v#8*RcgAWWy>t= zf}6LxS_Y8j$rl`ci#%LT*@K4D&y%XvAM5^IX%6;`V7)wpbhQQK6ccF z?$cE)cTb74#FMevjQ_w)5}PN_)Y49sT_fI@Q@OdE|6v|kR>w-5TQSQkhs)e*R$D-b zVYDNIl8scwM#hqDL02_d+2D93Hi3PKb>u4RukgO($`4@snQkj7iaY9sPHaEP&#d}t ze4}UQv3VtfNMAosO4#Bdnzmcnd4=^^3lYh>(%Gyc1)AoX2E4*#Ih{`>iGl4G*$h8? zeOENzr#ID^FELD;ABAI54jVvZ7EkN!v;??UF$mqSBU4p$%3R*-N+V6)Zcdqp9gU5I z+>{VIZM|$E145hXJ9buqFDWx4Yh}+NUGi9TSiRLUqt&wZMNZL^W|GnDq#Hc(r<4QK zRMfSSBjx1$=uD)cl5Xo7*meMkc8RL%B?v7jcX>Nn|2f7 ztOB-xVZL~2@Zj5TU3~MS4?fR4roef6zCaSl5H=uaNhCzJav#>3qjBUF&@`uAGwmF* z;9I)RA+NHCT`(jZB;#7Ao|e)@L9!12j8whYt1#ISgEh6_B_mIWn`ch#z;Z`hGx7+R z(U|6>NV_B@czNCCSUi1@ zJnbWSy;$g01tdp%Y1;v^*W@8Fw}?q6=hPHZC@gMCW?M8<(#g@5?zZ}Qw4vE_vr_O8 zJ%w zZ3%wIbKdC}zs?h&;1S^9BO<#a zpP-ncAxb8{_d(%?gB(P{z7Je7!PdhM1UqC~oyHM@+v7t+pCVKsg<>gug5u!D8Y{gK z&*7L2eT?Xf8fAroC=^hYJ{n&|)j+&}$`SezzkbNN9qFsDzGFwn_Knk?Jz=aPH`M3U zByHE>PujN^#-eGpV{8|>;lRvLdt0iW%@j5k@2M5e>*ZFE&GzVZJ5F4Ky`_T{|@HY~Q}?h?bMK)$U1J3ud((j=00>s#Vu& zhgxk~LQZ$Cp-Fg5DI4#qO>dre0CS=)VZ%Jo93MOtmCKg|f4pJ-(8Y7V_~^);zna}Q z<*laK<#p}L4KKHT)wOp7%9?G8i1d1HvS_*Kp&4$=^74_`>z8CwNDsI-V-umibA2bq zl!Y((=b_vD39(i45K|LIBmy56u*m@Zymu5Q$B&zgf-N_PJx)hWM54E_=XtNzh*ze< znikX0i;$d;y72D1B;IoMrNK`ww}ab+E#n{i^U+(AEjM5I&G8GX)scihZLv7xEK<4V z!;9iQPaYLK&Mco{>WjL%XPgzBJv(a^&LFvuxa6rfuU|SNU6!Ip1aCZkMsTmb;DtlJ zcv93%UcKazhsa@x?e`yh%@`8z2M=C(aZ*FFl-egQojB%0_dambg`J1BcTAtNnr~nK ztBD^4zrOZ}Up%_?m&}4Gvs>FbHU>Wr-Z|#_nWfYbt}>ABu$zssT|E_~5bZo+sCV%d z$yut?H}9Hq*l)W;kGnRqB63{h@=0AV?6OA3?N{?$vt)*3PO;hk&wDyI?AS01aY(k5 zbQ^sw0{76&&lHQ|Q>V$t!cVPxpx)?ysv$Nky!1k-5Z0}b{E@SPZEf!uPP#p9w%?PS zKQK_7)|b#zxw;;+3Wu4Cq=E|P$a+sQdeW4UX0!Ic=z0%0Nv`T#d@9GTst#4vIp;7l z-96nsJvq-#p3T~vSG&???J5i9uo8%jkU(Gr0t6U=0TFD#0kDmE#(p;8J#2#wwt0Y` z@gHOSkhb}Mw|Zs;{=KKt_Ec9_-BkCSd+teJyL{Wet%_QIi8tZrygM z?^DxuQxDv}P`|Np>P$qA=GWepnutt~<}~!LhEW~hzV9bhxxcK zu9q?y`s>>x@&P#DXiB>t=|9|mQram*VS1oHqq#Axdx15?!O)ZxLuKHJI-?n3}vIt##u8?tUPt8>hP29EL=b>i) zJUI}V>UUkUJd2(StZ24K)FE1Wo1@O=17ajz$2e8T20cbJj(C(vuSCpsN{LY93w!o60}t%=vB>_F zEhkc()VWvzBcVhHiXKq)O;C+|xry*QDaCU%w>l1ef`o}KM znapQDa@Q9pe9iH2rL$I7n*vLpW4{yh?akct*jS09J|Xz-r09Z#bg)B(J-lo7(YM^= zx$oUiPN~AynSv-^dH(6Xps-S_AfM#<4?Uai#v1psSGiq1bwr44SpRc$1F61KfI)Hk zvLT+M?+&S7XRDY@M7|fC^wEx>PQ-OG6P?HK$Dw!JLjo#s-f+}~IDrt3sA7DwS7+wW ztY1~@A2gYF?7wGr{56663)bbEA33|@5M-2mu6NrDmu@fbHkWH?&#Xrg49~8+W4bRL zZsuDn<*JZ5HNWHQ+82ihFH$d^TirI&`K)&(YEy5M>{HuU?yJ6$VGC31M;Gmt2X?Ji z1`dscYZJ+2r8KcV_FU!7N$){VeqYJjW;Ji|Vl`);*zFmSU0l>9sq@9&v$R&J=_~2E zV^1C>DOJD+KZ1l`Kc=3-mt+=^xnx5`vIp9D=h%m#>l>erPKSKKmIbdKyC#by1SH|Z zz@5oqTSw*yKJnYeBiA3tH8QZ|cd{?ST_!e2hz-Pcsrw-2DzA3?JX^R;YfC*)v9BU&)v@;7oAGSMkKhsYPuq3mqp{L#Ey0W zuu{)i02xTEitZc@MNRgOqVUKe1PJw>|xZ2fl5W>5+afa+pG`>q@v8y7y;5`3i-Zx^iE`I zQP9b)n0jRR0+ohN+rAh=oWwV33_U4kh*QRmpQn| z$GpwHP$uv5j^Ap97lznUgbUnRWCG9V?w`1e1C5C|k~z&Iq0Z0WaY>#Y)J>EgPTTh9 z5!xTW*X>bv1*5@swW{YwuJC3+_!pzkzdV{#l*tD&;XeK=;k9E-Y_O8cA&FTmn(zWd zJCjWEmEvTA0rE;~SS#1dF>PTOd5J5fS!7u&11*CVBOFD0DLcSmZxHiywCuxvjngRP z>ZN;4q~6Tad=z=*l6b=7UqiA(t++5VV;AiM_6(Adi_62Zk`XfLYJHyM`a=3JJuT5R z0FZg)ITnowr{1e>yMf{+C&%?ZiesbEpxQ^pH7bfcYGI1aLiF0;Ig8w1Qus<73(2wq z*_E(X@CZU3G_& zPe5f=uX(%oI)&gV!a*G1Bc}#On(uN9a1a}1qEsgk0gTUK3`B+2RS*$AO1g_&o6y`W zCdd+ou*8w~ypzq>}xmEYhQ4tF^tafb?bk)U5|oLCRm# z8it(kL}-c)ON0O3eo$7$!PrD^K^q(E{BX>h^}3NH($;-tWUh`Rtwgl)=({!KdtR&d zNx|3 z;P*58mC)c=%wp9(Pw|D~+P=F3m}K#QKOGGYg+g)li4)IUb-OFfG++2p@k0f#i2eI2 z6Yw6$3>*E~#EJ3$Gk$XIrg}O*y32L!qha3LsD$qPnybB9TR1#aoZcR!2Ifm2DREhs zCR3B+Mj|mgy1SUOI3QwE^l7aZ=5_0VN7?)J)n}QPzo9C++Bhpbi7ce@ z)wl4pTb(^<`3&=^k$s&%r#=4YaC^MmRHk-NAvIb0Q0YY7ljdZ7sg?Fr8Fl9n5=CN@ z|9~jC18?j6?N?7xe%d<_iv9EO{~G>q+MCm3=ET5xs;7tR`Q)$uN!Y>sJ~BWI_DsO% z7cv~^#UY3;tvKt`roMSI%5`J41OappGA9vDjK@G|O-KGALYy=A$c#l5^U_FR-w~yx zuMM`{?%+Ppz?b`O?!5c(wyT#eedHrQylJg(V%sC^JzEYPyn6QReepz&y3?hFnAD%W ztLR-E_;0m@@TXU=>c9H*K83k?R3GcDCX6v-C>nV8rD37IqoeF<^<`tR!S`$Eea zne{Teme{d1zFE8WAGzMNZZFM#>PufR(vQ9Nzkg-_C*+fZ=U(&Xd+*I``Sv&HOVyM5 zWB&c$tsb{~?Z%K#Is2)1Xq~q+uYcvrYkqOV7e~&8%f&=)ZfFR6mgErmy6_q9hdq0G zKHc-ju&DeTHg`nHb|(tNEr;E9%?!vKK;{j`Y4a~eY(whBh$4NqBQiVEvNKF@ss>-e z^ugo;lX7_Kb-8(SxSax=`6}?$jkiI#{U!&-iy-8DJ3R(Tu`TTMoEn@L z?$p>M7Wf>8CNznX1db3J!XkmLOo7-2 z(Ktnr=WS;)fG9q*Pu#uLi}bpqfHDgqacDIkU%2h&xw)HfTS(+n1>n>O4 z=O^+@6N49?k=55dpQ!43CG+Nc_1t4myp%rCzSuauZF1?hdhO&6yBac^*7Sjuu*m7u zg?+~!r3S7=)B54}m1wO$YNS;cf78N=m;AX@I`{O`x%r&`rTxDzdpz#c z@RlPJY5!FSq>&*WIDj$e+mJVP=!Zk->R~mQeC3_-IFHO8TMNapV6<_xu-sy)9pY8L zkwfB3myl9bcW4{yQnbn#C9C*1dO_CCamf41k#J>T#7v6DY# z7j+|b23CW&QtyGK@w3#=3AH#BLS~LL>uf*+V=BKYtaJemohiS$uOKV9kBBL+5&~gG z8>H6dN`wx`u}KR8VNtyl;ckxjzcCS=o4O(iZY7T*-xZ;Uu9N`XNDcByW&n>PIeOQ= zMSe%gT~nWf9|VKg204=)S@=RqBv0TVk_e$qJhNdP+n{pLHrboC;Md-5a`%%A#756H zsA!x5FL1`BOFqu88^WBku_9uVL*IC?i6~7R#3+RhJLeAd=SUA;t;zY7v<dbCx@uOh)GUH%98=n9mn6*x1mlGHdq=nM{HDP9+-E;@)yo(+iQ+ zlI+bD+4?1aMGHmMzT{tU1wL~8B!BYcoh8W>{O*7eyJxCgZZ{@$df4p(GLemyp}gsorcTPDhxNnMLo~ZMXf+@#1RhvROz35+0A0 zoqH(sK{FgVf(%1)G38ICOTN%ZIGkjV;6SzZTz`#C^QWXITB!oME*}h?>&pv1` z<>Zx8C|HquTkdQ$`pI-CJUu;qA`Lk2Mxij7lw)dZL`!*O-4M%J)9=wreRA{s+FE(f z_JEv~Q|XA7m_Jed)W80_2y>8q#HaX%rYf3fG4Vuk_OSKZo0?0l%;Y$%aJ|jO=%jDW z=Lx4}!BePESuKW4)oyAf5QyZt<5Lm9Dt10ht6Wq%M~x1sxj7Hz)2+<3CmEk;6v~Oe zi2DrPIH6KKfM(n}JiNs>jJ)5yogXM3r8i^Pp3y=+J(sSIdgIMN)X&hBetzL^I75u3 zr4$1r$KC^`TRwdFjw3#AvaH+I4l7x3r^H%ywlun%tMv9NW3HMjIZvy>&W|NC+(Me) zxJ~b_3A*^gv65h(o6`f~Dm+HGdZFO!3mjUFrfytFy3C-sL+bm)O4OR;J)uCNSNP>a zMxy`5@%LQa@28Ha;Xsh87I?PU&mQS~=>ASe(Cw;k=gegwXfG}8AEv(?UGRQ)su^%c zZOI+=%P!bmOIriTg~qrMtu(up>=PC6>0bgs=>0t+vU!A%U!(+$-f+(h6`+75NMzxz z9~@c4Q$%A2mpI#5|GU0`&U*g;`o8h3O#}e22=cFsSBwX8U9Lg4XM}q=TemLf=|&B; z`nxQ~VQF9^n+|$i7TD!j;3i#8Qtv0+#c??#Vu*7_c9{Z71Uqz2p`UX8I@NW{11|#N z2@$FAmIq-?jDXq1~2rJ7NHwX01g`FjKJnSPC_Aag}NfaKJ60X zQj!Z=Y83x%30hWgJsu9n*Tdmp^msOz%pO*aa8UJdpHF0Gdh_;;l}R8BnEFk+!RuFV zT({_j;@Y-GvEtvM4rJVO);{|?=~8B@wS4%#qsR1iB|PA4#Y6^t zs%jJ!-muor-Yoa3NKbj@v4P={0knl=ps8$pj6n53^%o3Rrd`}8aSbm?7he`q=7H@vEH>T0_- zHFf&h*);y+9+lAPVQ7b3jNdyS35BER^RwuDIvfprIG#-U)NtH=ZFS4QNdJ5D0C=0K zCSPLZKe#)2|BZ#EWV)VASbU&1(+tc>R~JK(!1#PVv2SGsm2b*76z4zQboH};Y@Wo}JexcFqPo>YY(fQQT`GuL;h1U4{{M=`6&nGgjE3MmY z^YY+V+Jm2KlxptFp=>IJZ2r2k=$&ZMt7`6z9Dm#3BbT3B%%t;?@DEqxfG1a}Yw>s- zJERYHZs2EOjd~+;!hNOZTZn%%r~*<2?M8mu2dFnglKFG$2ZUZQ>$1Rh=(b+?}cBOjKNPsDdonq$+3WcCKLW zNhUBUwtIrljo`1%Nli8b&LcL+fvDGs>kerN&fsQalE1{o2QFM71JVM=k>}M?x^xEj zBos{yNJO1Yjt#-f>9Nyua`n~f9a~1dMOa8{;|J11+z!cP0~2OLG1ToVxyuIek_WoQ zpe43Iv`lyic`iwqK$nhjqkzH$G@aB)PD%41BBB2_fj>`?p~l^%u2@M*h98d)o>QcU zWB?n^dH2+57fHlfBZI`J2)?Hy#yGiq$0&Zb@3*zcnbw$(;q(1BEapXPpf84meCpeF zKs%_io|k{u(g)vq_v6AT*Xhn%7S6rz9f#7>0C`T1FIM~QK#GwACj(RIoK${Yp_CY3 zd~m0}{;(^Z9UqLr`6fV5$zf>deY0#L7UY)<(5#~_M9<1`rzGWCLLsR#qm?uJ{e9oLT*j>TELDp zBPn`~JvJScDs})wDzTn}fYW0vKs=T*!O>>+mH*=n)d0-x_IZIDE%*#~g!0-d zfAg7Vzjf`h80l-bk?Z7nx69);8L3Z?YVaBo=pV$A18gA9OOt&3BtrG>=EQQJ&w#W= zm03H~8fuPQ8_n#z+={$8)jp=``I1Dv;MM@+3qK=w7QkmbR5Z}KcGegd*Xu%N{s3Gb z0zyh=*@$Io`TMBcXme}>IsVff+|<|>N^kAzKhkK7ZymqkSVn?7h+Fg?7zQ+e z5K4FxCygB+ORW^s>4AZvBGng(ga$mBVzjtSzv;X043&{KuD^WjqK_JjWb5HQYoSCD zDF*4)h%23nUC5P9B`oL(KoRBu3(k1_+5k4W?o;nmS|x~It{k>o*y8P)Dv?04xB0`zOOgle!igr@6Uk@z!aNCp0;0NxFw; z`u)P-(mH)@bhlYcq*8#Xx%S2ZuO0*jUC}26Ep2APt_15v>S;C-2Bx+NtRS7%R7ngR z$s$?n=e@(y2q(>qy)BZ9DM8naqgQ%fxEpr=3kX2#qlP!MAy&031_%RWLq?W;5Gp=5 zHV}lDfYiZ_a1k#1UD8KdxM8y6z%Q?|Ih;9*JfUu^(DlJQ!VR`c6McF-5NOvzLa*Q% z6&ETg)*sLL1L^7QeVIVYv}G`#sIV(q|8_*=(zro*7IA0|DeY_Jw@O zQgUo>y6DHe4;rqhJy@;``jdmj(ybp3Px}U5b7WuqHSxhcqa&j;yW(ZQSZWiQV|!BR zOxr?!8}|ueR1nAbBW^0}QSy~oER_sh@YiAmj}F)-p_IPZeCNfO^0$2lPbBMVB9eUa z5n<<8)Hi%kA9m?sF)9XzC~N;4o;Y&!@z)++I(=$+<|FJO0eZFU)XC3$s zG~CrgbcwFR1ACI5r&sCi^a1(^eS$tq-%8(0KLL;IXXxkY57D2be~#R_c~^A z!$+R3*MMWzRTLBA0%1W`LJ8!bX=gSB>@F=`|9rIxu;vnekO8K$EjbZ6HKQ4#Zbor$`G)=b#Gh zww%Z>sM6`M)8gxAfE)yjn_X*}==#Pj<6#{VGBvSrFW0+X8Uxa`;YkLd03zc{T@O47Ff~*}yy@T$S)aKsn+X z3Q3VaxS0&5MNVCfwTY76?i@@}F{#8W z*bIiTF=f2KqB7ikiu!ME7dV>Be<(7)q_a`M%lLV!SMlV^Oms}MjRIV<^QS+?J@k{^ zfbcRi1!Mdkby4L4g1SxdF#Wn76Jdl07?)QSRS|$r0e?u-V{DCJPtl4`N>ATJZwJWJ z^v!>-r};SV1>&=0`EqI~H5Cu~!&6OFtub`e)s%eDlF@FSO6vgAQZf`An<-Wgvz|nQ zi})wgv=U&36@L*GGG09meZ5D{Xcm-8Ng>VvQ(41)sp_$9zX$oGvw-;(q6`FdEkHce zf^In^`Dlw5qbescoS$JT@XzqUlpYb?j0ulkP6@k#S{NQtqehOPn|u90?=wUWZp`Vq z@Wea5?C%FiBh8qin})5-%V!Z(0<=UR<^jcN2S75XKkVc5_iN$+JaTpaDB#I~ zKCA(VF)aQkm*f+_Ky%`5c@^>>Wq8F<3<)YR zs0)6YOZuXaqG=lmNpm+LhJaVufgmGkTAaZtFJ9I>wkj$QXoZmEUE@4aAhdJ-{1LQX zVMHrs84{U5xe_0Q+nSo872PJN7hZrcsY%zOrAU!@Zqe2fwh>5EKAN$YeqW0F!CYVa3Bn zOkhsr{lK0t`ZdGU6hN^vNAW)SnPp+&P2ODZ?qDM^@wzKa^DrRhfR{)&TwF!TxXc** zmlPAg;bMbrGSQiM#ijvJPY(&+!9srO;cf5v)7LV~deEKU@pftIkYLd?FB!B7Cl{Z0 zs97w8f|4()2<~>no(G&EOGyCai?h9oze>?Mz~qB1-%{KaFvMPf?;tir?uRRsUqHY^ zSn#nqH&DJg?qvuUFuBe#oG;$&V60MyJ)(+H^1*#Mvv1+KKGcJNCVcx-NPRTec} zB7Rv)#5TPBfDX-2G}7e)n-EBIF2vYqeQG%oR|>D(=xtOhYRqF~>5hxdFdpC}N*Y`+ z_(K%Mi!}~Frn9>SX3#gR`807WpXFRWgvA7)jb`tIQ>>(@ zvbU-GTydFo(N#g%9aK4)RUwgRm02Gay6<8Folzy@^1uz%=_VbFsTFcl@3mI94n25wO~m&fBpq>Kg3 zbxrgl8i`X~thxge>pca8e6%wniCpOJ{WJ3kAULemvB{egZ(VncLZV>Kyk0nnY3 zx9e&GS*i9G3o?N4&I!VCS#rBM$iq3v!*3Omz$GGR4BzYdN8mC1wCCqNuTXWW4g9Ma zKyR&6HzR`ajnq4+cLN9M1JsYHe+53;FQ}J+X$lZZq%H8%VLC(i(k*&|UW5!yd_6Yz zZs;kF6uoKQ0@^NwTpV7Aum+aw>j#!%0x{fuVB^z~VTm%Yg)Jr)*MXc9NXDmd=XH_L zsQ|x1EFzx}w>CsB0(b0u!HU0e`PH)xd2gdNUFS_yzHuKKw`l+cQaOV1oclnB5VRmX zz{m~>q6sO6m^6vNl_TFBSqKWSuJbqvVXQlnHAEHCTHNFO#k0g^%2C*L3-*wDDMxk@ zs*cD6Il)KDbR>q9NtA8ZM+&0T^#j#I7_sWVc>}ri`YmLO4N0bv6uNFSXfAR>;~xY$ zr&vfQxcR@1y@27nUgxhBwo$heJLrrS%85~qK$tR}p&)W+9b#fVhkuZj9FYv-RD>ep z4+K-ZM7)pzB6~V}dwBK`i4P(hb__U2cOV-PeWW=+q%TuIQ-!2Li2`nE)m1MP_50 zwF6O+mIrc(co!rC^Egj4QJ~#sASci&Zx<}DZb(+8B=c%77m~e670{FZ@RGkI*{zF% zXTrmQ^1~IkKM_mDII)*)9AN66jOg-MTF~S1$2lrMCyObym5jXd9b1x*8Y&{ZB$pIr zdD%6D1ssT&MiI+9BhddU$`oro@!Lxxn2&_$rg;s)*ZqMe)=;gBMs08(#=GS#5kkFg5zkch{V*my!C+CNTek-O}e(x(UN>KLE z%o9i-Yu4mTv4oLMP496R$D1%F7MF~~uBnr++=pT{D)une1ISJ6B-+USvf%Q=qf9hK z7cD9lyiwED>Vz6=6s8Z&-g^94bkxj=-at7%s|S_l<$jl_SKk~6jSNqi@-Ls|UHkyv z+ypQ2WnC4uI}mq&4*xQ8lvk5-As9F%JTFUtzXiarK>0*p6#%!g(FUk5+8d`8nThjo z$K~k}VbS0B;Jccq%H!L;$ph3a)T7mHF254GEywP;;nZ)nZVCRX=f!OtHTH}iQ5HuZ zNlck}w^t+RM3TX3IvBk##jd)LGb;>9$X6-aCxQs8zCj#jjrJVtiwdp*7Ph5)$S{Tt z-WIsG2(rZ$gIYYU!|P2^^k6|S>2y)__0mSv8)r4en~3NEZ}Rlp(u>b^ezEs6UmoI= z>BiKP+|~9)S9Jc|TejML*=$9_GTG`YjNEaWQ2@ING+|d5(xPZ`QQ#D-v`m+HxPw$# zKtn6uS3XF68nT{HUZVmH+fZlRV`9L^GI^P1gC$XnWWzMI|5U|;d9U0&ZYHLW#94)zZB^}qgL;=l>>wf8Z%axPAM z<*l__?@f*!_CA^kd1AINC}eVpgeR5U8XbM+`9JM^k9m{kLsY!FjIh=!URFt?JG-vj z5~xQMuPsdO-@gjQa5C7Ko?foC2bcOAo-?00`1J1anRYu=cSTjpQ>-1<%Bzy58VOFE06H+_3f6?r>(a2WOoQ?IW-Zf?YI!`v8yRpClRb_MKau_yr59GOx!klJ0 z2ixpqsisFBy!4u}S~W6~M+BxesMQ}Din_d%tb}|n*+T$GyrMe>ow_O4ye@Ng@_IHnL{a8z_R`BA&Y{@8!qz` zice7V@ZB@>_XO5P5GU-3!KIgrFmKm1aX8bzlhP%)nPC%H32dX|w|$tEj0vOMTRXS% zTJMS%ZMQnN>q4!h%RE59e>`Y-&mMC@r90K7&Xf4;S&P-v&~S=k?M4p&0eC(iqF z@d2rU;)Oml5W)2qFXuQ>(hmmn#jOKHnSPJ}8yLQl03)SEN zm3izqt*WPTA=XFT>z!;R;<6Rt>COY1dxaOI8A^|7yE9OJm7a^MnE53RHR5sfS2GG?7EPmx%6?cLE`WClBpCH}bb5JHD`2 zotQ0L@{HawF!jhW?^3Q27~ZpEeE6}Mmm+K=+FX8S>;e}{CZ~1v(EigS-y1pl+iyDg zx88w;YTd1#EEd-D*#Ud#!2Wjqp_w-}eZffImN!h^qR~%yT7#?ehgLiPzU9PQ&YZ}+>&0#NuaET|ux}m>Y>eqAg&O>8ffOuJ09H?V zxfXoPx)3flV~3~~)3T6=!4hhn)p%!{?;#)gyPBSx{otG+P5j5gQ-AztPc8mpp8xqm z3J}i^-Iwo7QJ+iSN8N3fsZ3J49~*^9D&P6`WXJvE2dMtecWej8@gD_?n}J{S-FTbi z9nnNTM}$AHQ-{Aeukt!KB=4?aWYPpy3alE}kvxJtM_wNhavjyQ1ItKefTQTcv>;_V z8g&F|k|IzxdC?n^Jb6^G&iMU-%G(Z}T{@q6>aU-E^gLtcQj);uS_$eF)hmZ<)KzMZ z`t!YS`h4fRZ#vA-u3HDL$lKby($}1+r*rT0H(%>|@8E25aQyTgxAb2*Hm0{+Umm=? zCwI(y_=bHRH9DW(L20#Kcc^WYxPzRPc>O>BQ|BMfKYqtj>F7|zKZ1#wIHU(fe&qF9 z#LD&_|DC7w+-MeSTxOl=S1#OpIn#gdYc*g{v{P!`|uf=L;^aOy>TtXE1G_tsDg;(`q zAn}p>+fVj@SE}ethc6V_D z(PA5fmx?%MylpH`j5AlDdM7JU_k80F;dMU8B1+c1mNPl57U30S(? zzcMATZv$FSCG^ly7h-)z-Pc|(B@!9d9sN71MthA?_*;^ALFbd#{y~7TyEs^6LNI;{95!tt6a?&)P8SM^Q)6v(=*9rj(w5m^BFPK_tV^h zVh8+@e^2;wGSCEnqWMD}E#c{W!^M}gf?_~jP!AIpH3f#g3nfh*ZvufO^d$oXfgSw zn5n4z`ur~rVEg?^V|1(_qlK|6BJ_uGH! z5||*uLu5nYEWsNS^-o@S>H(ft0{i|_oET={O#%C@%gl^C_O8y)W;@@Tncep2O^+=b zBTtpLl}ZEe4+O5QsZ}|zUXdB*T<70s+)2e39k9YnZ@%>R)xMEdZU6ZzM{jugtJHfu zrNHV^I`c^MsdEQD8J+)h=WEG_#`f>&+kfNAuCdtI1JCx(Eci2vO}#j?YtnP!!5@ZY zkK!-KGVw%OkV=K<{+G&~e`{7$6$;Y0<}28T-!oz=0(6x^Knd$2_7_$_js>FO@Y@rS zaArT7iUXWb9S*y2+sPu?MVsiIyqR`g>A<{zvmI;mHW@0Uty1Nn9M;kfi{oL>DHZe-HGQ+9PBtY=8t1E9HWLE{Rrk2vFBov zkh_rKij;&4&Nr-lM4E>t1y4KklW4y%B?b7&Z{%wvdZHB6Jg{*=M0bo(c$_0QIdiXB zC#R&;6mmV~$jN_^jIK|9n=ohGP5x~drJYJ`9&tBL~whtKd{gV3$daWa;LA_?DsD8co$78Z7Uw zCZ7AORoSvnIWyZIuhJ}~N^#%*P?HZ2*mdh*Y`9wUwS^_QP*QZC5sw>U(2s3o?v{1- zK%lq0olly9l(^de%#i7p*h6w5V%mLur*4bzL1<>5yY_d2VThrb;ZQUx`C_80TBp4s zF6@SWT%`{`x%=G{m5crnsDG!2wj*^&K=4{jsLrr_P)GDjZ^Q6Lc9qIySun8okP>3b zrF?U7y;xAj+IF?DwNf64hhdRQ6;qR2XC^1o@9BI}a_zo-`?b#rE;Z!)I)yjME`%68 zXXNYty&v8Bv=)t}D_5>OyEStIhD=GQUViGep0$yjR?B;3TTpyDz`zhxnUG*!l5$_o zZ+C^XzKp;B!m)P`3&!2YwFvdtN4~IlykapTX|XoP%HA?~@QKzfs$VEa&G)|j@VVfC zd&-m)MEM{C$Jw27bMJbz*u-*97w^=xM0?57kyL4j_G$)08H`Nxa&0gYXQ&tGF{nc0 zlz$kTBq0-YNl7Hud(X|I2pQnDJ>F1BO005JCQ~xuJ{|?srQe^(_681|yk+}vC7Y2X z2HAudIu|oMswrBA;q#wLxVFScCIYZk=FK^z+7haVf(#=TC4j{~wRSMvf~^ydzJ-zd zJc+Op^|sp!v+2C#vmV7RBT-|c_S8V!EJ)t`i9N%|9{17Htw18~GAK^YZgpD}teR40 zWLPpzKHIq1#Yg>yFvI-|;uUYf8uLZ0Fu)&&dqp>FHVbD`GTSkM9V@;=4UXCwlaz#g zky%R!X$ zj6H(oyOL1XJ%FfD$#&j(npEh(vTio4iS%=5GI3FLn97Dx->Ke0Q-zr?ed^CT-+Aft z&)m74kwUyj3-$&*dWw#hTF6$!9JVww-OoyQvr3=piwFsLTzd_PRinxHWVSte=b^)Y z@zsBL`KuP=kHkwce}GR)VpuTK*oRxTY7QaR8{Ur8_isA5tuI|Udw6>}Rk|j-!j(#& zC@LIaacKckNNJY_-Dyr-Q?-CV2ca!h+{P}OcSkL^Y2-Mo_%mMh$%+LccK-6DK%MgQl3TrR~0nYyYb4mwd$=Xl|z(Fr)J# zqlSXPuq!_7c>{c(xBaT0e?RvK@Nf?FoOV`}*Y!Mw9K$d6{4Qc1{|e~XKkoSjf@!;s zFwR`rR9uny2O-0G&GzR0)|mig*4a=vb!WpS7j~Rn@C&9OV!((O1(^zjn>$}H%P`4) z<2*xd`oCPqJS0CrfzSQF?)bIqn+_ZQwRFtL|7+=XmP}<#dE_$W-+yv$=jha2Z>3>0 z*5*#!bX)sC!p$)H#kak1S@8^ps{Q`n;jCZ}G=}1ZRZd)3x%JZXRAl&uMwNPM(dW(8 z=R3c;JlluNWS6}CN4d`TF0ZZLe*5a;9n?o|oSZy(czpbH=Uo?u22{rvLs&9}@gp1<$J)YQQvlT*j8{p#+uwOg*NY`ycTp$B5^eGO||3(lWksCn&l zCGsYho!(w7ng)`wJ#*of=cpro-kJ{Aqph)ZZ?Rab@-%<@>h||uVkGzO^~0L$=!3^r zZL2leTxWJOd*|*6#A63{`RpPktX;Xhx_ZyOoy_Fn!;=#yZ~Uv}vlq84T)5OZ*w`}L zY;M`Y9EuN4Pk5hd|L)qAyH?gNUFjT{Ie1`t>d2waALW~*AA|d=Yb(`(r4{PWm(QMA znY-nD=dY%Y9GRLtbdc6pFI`+)x&87r>27bje{Uo%NK9kUMyxD?|0a5fGR ziUPR@YC?7<^UYy~ga>XEh-74F z0!(jB$}v2?%8VVFL8%QQ!x7KBCgJd}9(d|{&A8X$$s37Gf5T!(1Qj%!*tPiUPdlr{ zrZ@ku6=S+#XYU^9pwmW&YMntLve_s_9Eu#ru1(wKCjxKcD zjX8g8rv-#-pv?{7H4um}#=5(dLT>Fg zwEGF`CHjUnwSn(WFS~!S2wxw~u1a!qo@@?Mx4oP501lTMqud<@ltPA@lxaHdWS|d6 zVL|F3qq2FRI3#V_eCha$##}!zsJ|fr^HRdfv4>L=%`hx!P|`H+z}7u_j;*>fBy|8Ty27vgHdc7%xSF@b?(osr6kACEYqx zqYFPuY}p667wgNd`ee8?_*5V7DegMF9tdXAnK+--y~P|d z!TKVRQPwCt*lLcXB@Zu$fUumEv>UC0C?og*@`NVoid@Fls>SK55fN0{mta-OaHU0L z8&QmOzqNIUw>=)+Ws4l6GqCTmis$tb!Y9J;+<~){V)z)3cid81NX*H|M#OS^=l}#E zo)=Vx*9B8{Q7oMYc<+Q1Dtht)JeyLKqIY)nt84zgn@=Af3RXr8Z|Tu_wO<{iG==n$ zQFxJte+H|_I#hU^^kqS{cz8_d@WkUiNi`ky+)9Z&$MX7({d?UR-D0D>;>PV> z1d+h6+TaAZ;&L*pU`R9<99ulS43cgdDpI$qx&oUUZVS*1rGzy%$1{^8S7q5;ZfKK* z5hKDtF7m|K5Rx*f6zk^DAeNVq?+M-}^8qu@iK^~Gb5WZQIixg8>ys7PKo!BAseMOW zq22bVMX^IuvMwUkD`Hat-8;&9Jf@U2;h_bFDbac#gD$u!cNljBwMgFqIvnq;7fmrB z`^qlvHIhoRvLss6U7e48N)IRN@QC}c96JiO^T0i~HOlqZHT zQyB*SiQD5Vk}~TH#Was#cOF1;0!6;Ije7VK!EBl0lXeh}@u6f?3(5Y256`ma-y3Ng ziTs}PUo%=;q}UYa_V=yLu80CJ*=*C3cQ22Q&cUpEa{1j|7A9qUxuieN@YG z=sY~Kt{9mcsa>Z%(GkfqqNc7@jG~Z_asJ34+kZ4rV@xB^c|zH#szH}O7%%xX^9ET{ zN`e|t(qTSZh2v3x?N?-1^LMae0^Rd|UM|XKCcL*UEzzYKYsql~d3z(A=xglSvW1%} zMTMY<*jwBy=j4#;lll)O%UTfL4To4-_KW^%@)AjPI5z`XU;WToMO4PO?z03RM08bH5a8eneXEPg zz;#OX$VuM{jD$vP+-zooB4)j8!H6Y<1;y_cBe1WF{b{&MMg?~ysamDhdCXYB@({D0 z%0!06mg8;7&!~;7y zg?Uto9+Ox%vXz;(KVT$f52qQC=&)*dJO6X(e~#V}YIpV>%vU;xy>Ll@paswrr)l`94q=KsO-%gG)6QwV7Nv-8kwgJNq$RZ@ zKZhe3pZelgI&c5!pFA@f9*z$AMw|A^Ai&{MRF(QY>N0g%+Ep+8sOC<-yrjdL=&RlJ z^)rpd*HfO|b4cioM9ZIk-9)h(W)*MP6I_l=&K7*RQ2W@}H+R2s7jm3TbCHZHr`DgM zqS=)id-6R`?WERT{z2oIJ7rb=^0q`_(rj;=j0ydHGb7u!cP=dQ#kh+bjO)vK$tuAu z+3sfx?zkd*{Dw;_dQ#tF19}LCj){kYAw?89#V-7niBRA;8+3a-k0R>Fo&7rH);tnC zoV9U=?wl8WE)Ny3Tz?3ld%xTLQaFypusYJ|8k(I|V1Lnbd5?=RI*)XI(fQwdM@+c+ zc%;d8?Bqjx&tn=}ze`2`?Wgq7Z+!&Od$E8Fx~D*QptJ zIi?cveQyrdruF&Gk7oVK?N=%jvtB_rqLt)PikmuXq_g$ORBX?l4~{b;zc8~*6|eos zl~XL$zDR8gUcCREEl+UePmXgtc3vD_d+xXIn;3u0w>A}KJ`k;H-{2xiIoREOaWLg2 zb7`{aB1Y5=8wPerglg(>%s?{xiDXIUm$U!s1_qOD52WQR*|$O6T!WzlN(Um}ng)!S z@m5-R@{y~ZU%v0uFYkHxwU^)bH07S@=d&SqG#87Xh)iczy)U&(iuAYZ`-hgb_@9p_Gj3F_q2!dRoc2Ebp z+I$-0@oR@e)DD**oWk+K!SHo`2b@vExONYRKXqfK$WD$~XJbZMW?>5RpAPX}q&{{B_-t z)8lUMyM4e-6ig69Pn=CnZ1)xRC1+xR95-~}oGILqKmHukVlI@IyfD8s)C#o54A<08Zgx@&4 z`!lyEZ96&A{8ri9PW#Q=iNZ;r*`~)oSWyHe=wa4RXtK!13qyDA*}1d0 zvOh93IN%T6Xspf1oF{m+P^xR7OkX~t+wKG%^)}m(3lxUhSqX&^rLq19UG$l`l4<$b zfyUI)#kQ?=KHhFO{pm9$(@wUn;-KGGjYNV~e^Rs%hHP}M6TL8NWAQ^0iO2 zkx?{B^}x4!`3;@F*c*xE)xsE~$?+=Jm=BI@Rk8!Q{N(Z5{;BuW;}14{E3~< z+ignsW{3Uh!p^}#E7d>J_UAG$+M%r7K!$vl{_C-wH-~*&+M(3^{FZPsZ^V~oXIDP{ zo#z5u#?pngRgW1D0W?MPXO9fJ6z}k;pi)~xdBiK6!K6qRO*0+z2Ya6l)OJp+*qwB< zJ(${UW-|6j{KK?@{k|CC*05dF>*wF zj@nBdf;ak|ziDsdx@-+|9PFyA4I;$g5W=QzgYRmH@QGt(yg6-|hHTgQLP_f~a&~t!!gE z?zFz!5>PR;7bN9fmt$|1;xHf7j+iGD<(V@uA<~p=VnoovlqtmDZ zjqb`!o8?o)o(-7>tOKG8`Qvng!)0fU5boE!3A5FU!m0XbDfh12-s;$7tEq=;XMCk?I}z20+;Q+6t|v44+CSEAtln)! z{Q)tCc;2zw`}$?8x_@wu5w!3^fO9SVQlWDlDKmCAb!$TbfG!i?DY*)o=*Es49&r96 z{@YfATMhqF%@@=KA0vi(^_(9Gb&-bQkm5^vZc{HFma{6QEgMHa6`kQyI?Jl6Vsd`r z4g>+;_F#VX@S$6FV`l)&)Lg10!+lGz1O)H|PX(#oaMP|;%jGm?_ zS3q(l>j)m}TpX?UxhA*KT+h2^t0&gyW~LSMx$ldNp)5~u(p=uUi=%1Z>+Z}IvDet& zA7o6AXe{^yKhL{z^8<}tKDcf-C_V?L;;K&FN7EizTPhc74L1!@U%2&bG|<-{cl)$~ z%GUXLEjoO`U)f5#-SoI+DS73Jzw_hSt7Dheo@uR5O$CyiC#4t6K9@he-ad^@a^`5; zs{D_azktktB6nC|eO$2Q(G-o%enN|W+W;;B2&4g zP&?F8Ol3a@RYzIQxHELn^aXz4f7=VmHX{dRGoZQzL*wR+%7iOYsnl83W2F#(h#^*8 z!B%{%Ix^@ti)rd#b}_^BmdtArG~^1>l8iVjVDwBsY%fgD-H1%1+dAJCM!2tHXYu{^2)qOeS-Rt6U*`+)GOd8u&+CGfGCL# z=0V5^CM6iQ2|-`K+$A-WP^UwiAPRCgrkRaw3mZhgX@zmrl8*lw=m|`RhyZXd0Uz1a zDxyY*Fp2o(Tx-Fjw(B=ch@Vhkov2p@qV((52NH*T&>)c%L<|AoN@!I_49&~e z%4N>wUYs{_g95as3Im;=Vh^(P#2p!q|Nkj_4>&olDqVak=Um;@Ip^+~p6Q;RoTbrd zB#m-TmSkC$CE2oN%W}dw+Zfx}28^*;SPTYZz${*DUN;moZ?$lPa7LV5~f1KI&fk(lI?5 ztAYx9PcUPdK|UoZJj3P7nP;Q*fNnn-Y|2fA4fQbvRyyUBZ4;SG=yb1#Xyiw<5tA;L z_%qEcFT|stbHH(~%M6f0l>5xV)Jvzo+h=~l$=Q4&>vW5zuNnFqgc`g`T{U{euUCzniZjeR70L`|G6Q{vh0t9FGsddiLODS<$A*(Z zZw0bBwLBrhOCzzkG#T8y6m7lbjTK=kE^iGT{UN{l$;nH{Be8QQN3n*S7~Xg6sT9KQ(*(iJy^c|g<>KOgj9X{x{EdNj0Lxpi>qkFVy^ydq0kq# zvx?2@9`sBaNQSB7rZO4NO<3)}n}$moGC&d~)?lbqbRk_H%l%*x3nCUg>kpVQTLv>^ zfv2P*EUZ~@6~&$nMGU>7hk}WmjZ9TPu$_o4!G6_NPZf)&PK^xP)GbnQ!w?5stmx43 zX5Bf_{`s)9X=;6~mE`p^ty>1KIQaw3G?WM45T9zVqOX75>s~iH72WkZtVh+J=i9dm z>v^xIuV*EYr(1wLJ=k-+=W3vTZ|}Lc=M5cuPnzf;M4t9o(7GTRi1r1|gUBFXD8Ot2 zL53dyC+#RKgcSn?I=^CFQnV1AfWe<6{v^sO;JaWELFxa+r7aXiMlz!I6fF$-ZV}uV zF`Y#e^*?c&^^d*K{-?p`YPEOPPT$gg<<q@{a0pjUQ_++_idS@^u^NlkE?-zt(P+GH~+^!1as`K4d&IV-OK(1QmPrRPAKfJg84swNVmBIl=jE}n7y6- zaBBSQzKefMf93Ay0IB@*wrl#R;XUB8J_(${^+3kl2ke_~tNZ`ac1Q-15xRP(PiyjL z4G5Yor&2~*zhK}8V91j0)tvpH1@!$C`4GrkVjmc&^v4Poo zzOm09qr+^5JJ(qIAW}%Mul{!SMs_S%d-ai_BkbjknOC1pja|DAgl*{#QbdD`7`q~|i=!f)=m8(#e-lsxL$_XcBbb)P4gYHYZN5l7F-1_=-Wtr zSQT`w&JmIpn;hw&oCtr)`NGRToj)r`_%l0KBF0v7T9Ezsb!$&th;_z_ovEpiQEPGI z7W8_}3Q9<(}x-3rYMCH0AU7 z)#ZkQ=)^`;xj)~Cgzjz~Cv$gInIcV3Bs4Ia7?%EStLwri*nTv#zamE`K^R5`d?>!o z*;#WEt+M!<@BTGA2snaoMM_X&j|Zs0uM`>=E(@|jRbH+gBCTQO?D*UT#1SobQ~xiD zc9JwhPtLAib5pq9jDy}oFp~G&g5Z9|GQx}&b}J*NtaRUnN0MIu`%3@$fa@(ZXRZafjn>8Vtcono^}q ze?sFdwKP0A9-m$efV)uUZj`UKY`4(cd~o&B9hrYgFBCVz`WuNwR#LLjoYe1JJws>T z{q);Ud!eXCSh4`SqXQ-d&65!{FI=WYV`wB1X*^3kcI&~NS0M^72j_6sI_%{|@gtk6 z1z@D3$T+C&(dsrM{R{Ohq?pL9P(yG>(gEk(j$eN8 zn-qvP!du_8>XB8&9~5Jw!GL^dYN&Ndzkb&fyD!Y!0<73-OjbB6I`dcUue|3i@7x|d zY$seen-!;G6yshXq8xqkUC0};x&1z2EBE=HJv|ro+=QIn%iN!S!;>CK3?ZG3tQrUw zrTY`{e9J0v@C4AQ zaZ_Is`rR3~BP8ZttKYvL1SIKyWFf^hg&hZG`V3*(3%y@{=xaj z=bIC*L*1MZ}X5g3}Rg?16 z1N0&|PE>_%GH@*lP;VlE81Es>wxDmk*EkMt3|gL4)yGDnGZX!ruKe;DPLpLZb#6y} z-(%M%r>-+$U!YnAzPdJ;T^Ptao4AXR9+lVPk~Q}J`yPL|Uz#rRMq>B&+`4pe0{M47 zrX!77D3BNR=*Zj{zGR~N4xKtQT-vRM#v&Q+8}AXF+4TOq`5a3Lh!KuEDNPT@xIQn9 zjPZ|!`i?&myExJu9aT+g#|w5bK5V@D^^ctZ<2XReG%ILcP)B7U&8Lv|mW6%xoe>p0Tj4H+(20DR>xdf*kv$2$7qzw*E5 zzR>eHbdtxxd+{OaQ`A?e?@~Xb{uANa9$lo7aFt%7cl%s%XXsn$hv}#2=jczZ3tTbrrpkpzD#cKpGHz$ zw}uI?6*demGI;zwbn{N{Cq13axt%EkK?8s4_Wos-^>^Pu(fy57i&$F7NyJ*QJV=K9 z0Rw?fRKDUe2pWY+{W`w^Db?~P2BG8ZAO-!}FAvC0aPn*Fh?}4gKFK4TA90=0Nj`}S z*nifJAE)z5Iwlz(ZB--=V)*TdHbGLHjZ?!UcMCy;!2?SsxnR-v{6g9Azb;3(5&Mce zOjH!K7vv7!3>ou&ZE!Bqj`-0Z!9bpzJ`8h6|8VyE*PFHso zZrW-)e5j_+UUJD_W&l?LSHkFg|muWpFCcTzt1%(nA2i z7+34^K+vW2nb9~Hlw_`dSHGI#M3K%6=2vYF0|+%JhmMa&_DdJIg{qjAnq@u~X2GI` zfK`qKmxC)Z zleIlZTnao?JYttMiW^wXY1^rEur#x}k1E^wW60XwduSIom?-cXB|l{-^$ZJK+QTtm z<4jPxToa5#wka@x~?k|_|~qVZ%npy{+pwZ9@FriO-5Mfi{i3a|(Zdr>_e)M;K9 z#P`uzDS%z!>mZ;DO6~svJcG+Upeb=J$mrBHk`W|C07I=xZR-aoNYyN=g#)*BZ||0|h0NU6o!D8_I8(y{ms$tiKs@M?RmKY&O-1 zfMnPcH^_ltZ*(Y18H2&vj!RjJit`T70uZRE;OmN4xQLB3=@wY9y+Ep5sOqoT6jRKt-J$0LiH8QCx$GVDT#bD3Zu?uFbW zdK%X|&xsV%zN5V5?Xxt!h%jP={qMEF+h#>rVE;Ttq~EpAD@mnbPPY<*DcRG9jo9tq zl0yv>^fqgg@sW*B+$^r2-4etD$r)kgXL)7bHiY*7N@O$0))Hg6{fbLZDUw)W!BcJs zcG5PIg83En--HCGxW;HywrXl1rwIk#%Bw0bg+P|2hZRnN`$YaFubZIuv)J}``zsol z$apwmPPr*b0z(C885ya4E~Kj(1%Mo}7y=EecvL|8Z<-zOdLdom$-OacmD!NM#EdIF z$Ko@tYzw~%+M26Swkn_qCBI^Fx=I&3{ux)6bS{XFO7oy49~Uh*Ex}5uyd8s`=Mf&q zi|lXIR6qjbg(4%~hFXBrUSWWB{Y*sBjD)yej*FTYzd}i1^yWiIZZ9hAP6Yj^rpNKf zkp4ampjIS0mbBX03wYU5MN2W6(_pF-JX&I&9B@4`5QAvZL4?R_*MZ|smCpV*6ESfT zIJ?nT4JODlo=B==%IDJH;nOT8lZE&*;IZQsQ~EhUB1oFNj|HWNMn)%PYwfoOTjtpQeTIz`9tcLU|Rb<)dtU+ zLOb0xdPiYniSgyNTWfH-v+5zU7NF_=Csw_LZQoaO$RxS^52_Dgef3v19j`Z}kH59Z zLYqCG_nR4n@FX!{vyj_h6$i6m0A#kPBXLs^4qH;z z|K?XEg9pUy4Z|;-{5VeLbn=Pt1YCe3@I3OKh)U|KHQ34fm_70=I8?;1Fj4tG65|F= z>L_(kAn>rAA8>U)K&ypU-Brl^)BK}_tRMS#-XN8S^9)ykM~-;8{4f(j5f^L{cRQ{x zpnd|m5BV8?$VyaPuq}V6r#p3?)@zh&F&$eG}|N5!VNE?PP*|x$IIJRD& zp*|@qWhGMRA06d^`Q1rr&sKl(iKoB4OUY-wos#K}OiS(knDL$wW4luCDF4Gd&wYN0 z?~N_RZL?IM)E2dJ%gaN@zL!%zVFY8ktVgMmo|5JKQsHf1cAkI0eD6mNY&~2Y4Vv=%HcgYU6=c74;%>MSmh7Bm@ZSl9BkpF})i8N-pb-NsmznnR zOe$kZC3e`EErpU*g(d+jEqwIOV{?+ZGI5mmR1jU&)Uqof;EQE0&1Zfg(i*Kt=vriiV$7XB_@m zuK-mn1!#R$h>OAtc}_NEsgYDX?{fv$6~CzfJrH#$<&}I6jrG3i1^@gdgp**9| zunz_i>9Br9^7vG6O33WXF`>lf8`e>8kJghr%R+elJ(ac)G{HGa1%qL=&N>IZ10`0G z0!pcWb|Xs?UX-=u zv+0nfA=3Ztco^^60DP9!MzA2qbL=^R*OIogTB{1IJzz$P>DV3NIC}xC1ne!WB(n~2 zkuf*$0r6!;+{^OnMpyj@RpZ>Nl43OC2t5JF@iz%yM!ehpo|k&Q-19vk3$CVarXHl8 z1diZS)K{q=P`^elT9=N1O};_T(W@b?`WP@7h+tU$ zBogj5UpzlOQb&+?WGohD5M{)o1QFv){H~Pj10%6NH+1A(ch_J45(C!%=enJ*%NOnl za9GIvrF0y)35X!XG(n(sK?D%ASfSxNWHd3eb=>l}uD|33?*NG?^=m?0@jHh*fjppY2x<0z^QE(I z*T6*8LVO&N@XNl!&Jq){bos9S>J){Rzmp{+V6eXM?PlW&HgRcJC-0K5xPy@ao{w(r z(0cut+3+qAnoAyxtQg59`q3PnSp(Jf-yN3RVA38wFeuRCYmhhfH*b86gu+SJ8pHSDq@x>;{%h2 z*zAC*jm5IAgskF&|41gaAdKMj#g9%gCSTi*Q z1uC_wzJu|=`pwGwxwsk$W^K68Svt$Cd?Op+;I@XC4OEPb>W1s(hiS`TwL+x=Tb!%u znS69|&(W&&**zcCg?OTckdlmPYcMOTr!!HbcTu}iVj#_D{`doywM)9p-N`Gs6>r;2 z)(B{?Kxyajzn(GNdmw|zOaV3m#R&v#rhS_jlsW7SA)KV1Qh`lRnmTJa<@Xda$==H&jIWn&P?$@VYZl7CH!c=eG<94cp zOBD69rN=-1Kb~&b3qWGUu45bt;R_58ji1rLzoW&Syj$^`kE=XB?6((vV0kdU0h1Id(%{nS$wT=b* z>IZhWpSMc6WJIrlnLbsx@^#MO(KBW9=?!lMjlGBDU}0x3?;yK7ErqP;5pn$Bs^MhO zUCIjJD?isSiqw*;3v3yr$uK5|-Cl4C>b}m8^u=0GFc6l=Bn6uJJy#Pmd|ee8{uilc zzZ-W~dFGx&htFyV^G_iGTjba2*~<#Wf$TsvY*uoa_NUp+V!@c9Wiig24vC+Ug}$Je zV^uE5iOKIcX^JzRf-XK2wF``r6S(>I&v|(qVJjr(_2%}w5V|9RIpPV-5g`R zGRw+qN5n191Q2u(mcC*RIYQ*759%f&r2{he1vYVX5nZ$^43~vsN9YhI7*SK9kNN_o zL5O=HdaD#TC5cH3r$UN#CIBMAJpVkD?Fwqt0Y~xOp9Rie9KVvK0nwW~we1S51|%Uo zaUG*9sS#)*Nht!)^0!WT5&_()PiJ!RQRgru_8^l%Zx;HZ{1k}d88K{cbDP&k!qH;| zGVYqICnoNT5j7}AuBAl1x3Xs8ZTv;= zi5xwYjnWj)tqvSrQ$S9hdnpzHHJ+LYQs4-B{>qV&r2!DTYVE(Pso~Vrg{k{iPt2TR zd$&$i>zQ~fj67V1C%3GF4`se0n?Bbn^KbckUkQ`THKCBG7+kW{ku8Kvz}M2(x8Cg&**IYUbpFO+Bns) zIma}=U7t)vyfT}fk1*ndDF(w#V=E(k)ur6Rj-wM?$jJe^sV56=sl0BnRLs203B&Gn zlYN&k;U_Y^0L}`+U@M$01~tnV=hH^?3u}jm*VyM?+5TiL=Jl=~?Y~RgHa)%Se%>__ zk*JXgw7+)E#_?TMHx_ctxyj*tT%UtU@XnN0P16>*9lediN?%n)V#(r~1Cc%{Y&#(u zW{(uYLCdvZrXWrREQ?yRKj3*F2JVB8?$=a|I@(>I`m4O9ON|i4mY}d$% zE^e13Y=plBuw{srTs!c4lLfj^#bkSk0uih-k7~`eqNf$6(-WB zjiJK);tW8%b|zkxL%o+~rz5|e@DS4K@i~|MNomQ7>)=q;LRw4-Fqn*J3e3r}VCbsF zN?e!`0)h6Op!iA-G=02kxlsmvogM z#RM5DCKEZZh1;5`BI$^9Hv&DQ6ZL6v{R{W3c+<>U#htXUW@Pnv>+Wlh1Z$;@Yj#Bj zmL4l?8lz5l6Q~j2a@wz6dR&bd;H#HiJ0lAv1@Z#aNeUBkL2nyVG@DNY(2zg(;mpFo zfvev^Uvg0#(b&kX%rMZE01N>0tP1r;h8Ya2Vq`!>_8EOpLizx@01HWb){rfH=iC?DSG2D<_eFXLCapKn zt;6Tq?RT`FYE##o{>l*jfh^)?V1S2hYl8KlzUDQ>hnr9NzSi_W%6btN+-3wEe-> z;x*LH{)1eFex5q?_)8`dMUEZM_CI{v$=lY9rmKrbzDAAFJ-@l(i$7|AyY2n_lHK2= z{+Tj9d+s;waQi3Q4pdhcBX?A(J7v2PD23bayLsY*`ZNerLU@k*g~vExh6Vu9`o z2O=Q;%Z63PYu1k4{Q)dBGMx#&Gaq|EPIUH)gCwxe*AsAAUw2qOfK>_PCm7s_7=L@KTk&A}yi~?|sUuX`?z_R6%cVLvB)pzw=PmrjpMK|O>Sx^><`H)N{ zf z>IklvlvFfwQN&K!p*olhQWW4~B%|XPkScRrO67in}i@*xS#|S2{%!o5=GV0syb|dmd3AV1JFT4 z@M$8qfGJWsFDop!pfNcql`1hRBGM7L%Lh0urh2T5%qA7e1L|w{djplvQ{1P2ke?RM z>vZH-)3y-lwXUC0dXc(Mzn}(KRzNP$JxMt-6lvCK(@D^b_Y2o@gX7{rQk-tYtK<1g z&)qg1#j0eh^vPyOSYbSR4Klfej7fuPpIIY|D`lPPzaa;o_#Nq=8fpSbVD(W&)f7Aa zTu{;A2$3CnZF{Qyg=nKbhUqM}#p}K7s|%E|h0B%1NGmlzlPvGjLXrawE-KsEfUetG z(T(@Ai%ghRa&<~my<)Lm3mS>6Hhk&M36Ex61Sqi%T%#gus}vokvX-n19FytSXw?9N z1Y@Ka!9tjl4Oa-*1o((th%iPV<$#KflN~F;l#!v#7WkH*GQ+kkEK&tH7ZVJ8a;zQT zRGXy}Cal(A;dCTHX)-Dsv33WbU~<2TkpR= zY6Ptv18d~KD5YMmi34+p&lbSk{-m@8Zq}vt7nekYM-Issr~*gjQr=%BqJ}xby-~S< zybQZJ(#iGyxbO=5PT*&6CF?$VH84R}AU9-@X)$ZhYsJe_0a&qR2>~g?XMK<*4nmUp z8yVVv+OmGgHmrZa&hm_iHyD(0v24B%(u`cn7pmlBh_ud`{#uTR$pOE3$I;WhJ}KC_ zTQ+^ZCd+dH5uWFjUcwMGKmMe zRSMaDvfnu>bTC0AlOvKm0?gDxCO*JX@C5h^84JoV+>ro2;8f|{9=E^&U|fjjXb6dH zFfj$mH)09((IC+58^f4C@uoU2&Yy36q=@fgLqTZsWY&XspZ6$oCETNkzDn#3Xnd?vI;K#WqY>q@b!~mi7mOMV)^c!3Q}Wl zyYeVY-Jd%ZA1g(6^{qDJvErbbu*C3}rC3Ng_mZ`6)jnAXXI5lyPP;2InVd#Z)X<9L zMHdfjEYM5L{x5|XWV*XW1Bybm)AfWFvMoL1h7;2fnR%`TXcr>TWZgjzmN=L@`&b zq_x)W+&cMYXR3`P?}p^zXF z1}kJnWWm@YyI}Z0B^V0{!YqW9anKd8;((ha&!~=0aZcQUp_(!gBg#>*MaB(qD2ZVe zAmOzvED&%50w4e%4081FCND|`Yb)TSRW(p~rM_P)B4p48jv#<`UPAFqW8z%2^PSP}llqgENU>iqFy`}0^!-6&$8BA>4n-5*n zyv&Hl$0HNzM}pkZton{3F(qq!>_C`X!Jz{L7h6ie?n|qoU?JZAUn0$M3@FhkCo~_! z4Wq;K-QoROwD(9No;47KEO8oD)i{|XB9!&eh1l$tD9*PgwcLS{Z13&eae88-@*43}kP7s=nWz?>SP0!8GNG{07`XXQEijGO7X^3I`_Ji1Nkc`>J2xhI;;1Uu7l8$a zVTln{=5^iyBfh%e6pVBp91Mgjk@zCo58cPp66nKN18I9dn&O zvykb_pU^PX_*iBi2-5w+dn_}UCsQJsAV^U%uXny?^zGtAK1-HXShE$de8LPxa!_C%93m&-uV2ES`cO;|e)O~U zXWO6u`A_aTc^`E!L!Vg3?A$I-)cRH(*tE1&8tToDE*zq$|GuJqhZtPQA2=Eoj>+p+ zuuiaa&57GS`1*IAe60P!|2e2Qo7;c!cMN3lCqMkz^l;;`hn@gAqMQE6GcVpqg$v9D zODBf6rR-|o<9m;saZ6lTqGqangX!{O_FuP7-Fm-vddJG!P7Gy><;+atNULUL<(*4L zZ*y+drtOl@n-oN2#$@@9%Tb|a&1y`5$+zR+6L7#o{jF?NYD;PZYxBD?~d$NPK!3f}TZ zVd3}y^*7Yls2?F?YIShjVsZm$KY2dVO{a{NWiYw7r zaT(Ip0d&SieG5VNA#h`2_$JjwF>(U_2xPaTyPxxMGrm)=b_OIpzb-dFZ;>AE$iR0l6blTQh)x zqb33Ww`RsyvpXiJ&efU>u7SQoULL4vh%hP;E!b}=(r54hMAb(p5?*=q(GQ)8#i#0W z3bSyS-x%uWtHBEo?1~SMy%AZi)x^r<@yR!TTTVsH_SvtuzwZ{!+jgdvL~uq4Sjh4l z8wr*`myyn!Ym}ITqytQx$ucFU!O^L3VLd&*mq~D8J{^J&9guC_kczSy<&?x6qO*}m z>%Fwrms;V4BETODiWFn0AV+yNS4yl~pmtEktN$Dl0PkeZO)ZMN-KtD%^ZMM1(wnkH zDU*VZCg)^!Lmtb0JLUxj;$Th}5~3Di59DNzUUw#&5=@UZ^#B#MC_OI4qq@Y}FmD6s z1Y-iUFl4#yjk#_vhduBd;ggDm0VhYE%u{g^n=ZS_RVviFP*5uX2CqPuoKpxjdZJJ+ z?O&f;eB*8AYUh_?M!bSLdLWwKboN)FzyS@af-GewY~VKOX-g>Sv=%HG=10TuBXX1x zi*R-T)FEHXhM{W$T;=3-uJl~lHPYP5K%}>lbrYlQkB*3XT?&*ydp#gXtEOyoI5x;W z_QcV_p)o!cHl0{il4+!7yezQ?`QaXHL~}cfjbO2t&yS{#7NL5?kyrgwhR|fFuuj=h z5o9H5yKLw;@J6OEP!2{01*A5dfZ9hrQw#Q2MUFoAi!jZze9~jWhGuBdnrw&rE|qll zQ8zERNpDu$P`48;ZZr^jyU1=}pq9sC!&-Q1I7QtKB|*VN(kidNEB1!dlT_G>H+oGZ z8lqTcGtyRX9-a!!uBxW`fHO`f$EUJl*ekoKTm)?vrZpaD+?5-GTY?)y10b7e9+65^ zl!w;tRDeGSKQ15I&P|`Cp zAD9cz$#CNG+A10BxS}3I4r<8&zeY6#wtsgG+8B!ms~#&&v&SY*Z0GW<_mJ6OUGx!P zs6iJrqqYBqx^H@BAF|ztXnq=qcA62^rlZk>E`}K@t!klYEKwTF#G*4X$u0_Dpgk~R z1a`1z4-O5~KT&UflSM~W#Y9ALrY}F}eKa|6*zwNZlPQ}u#?V$p_V@p>51yn9kFZgt zP!hSQRFTp3_26@x%O-^-k`gLc8j331oEY$2)1A*`(y49C=N@`UsIFZ}%bp&;R52S~*s$ zFYT*FYLk*0&BO-gW}1AWFMs{xq8Qw`_~_}h$gzKH3p||}x4;gnOcZlf>c=wQc)eO5 zY>sU7`jP3I&*#cql5Niq%=d~RZhNc2QU8NZ3SJ&7H&vll5B70Z`)Bf4%r!w9(f(^7 z=Pg-V*^JgVd*xj^KNb>2q)j56fbDC9U&4RW0{bS2@H$bn%U%T{LCHo~v&ppMJ287Y zt`mRmg>b`P5Q7k53oad$BK*?+)}3@n{JlT5_$PD)Jc$$!Sfn@>^HwKN*B8$I5r3vz zz6fM>cP1DlGXZ8fT!>@+xtT2be_5+!vgdwS+ZpIfZ#w?a_1CX?*v`Ex3qa3yDR2$- zChCFcC)4l!{M;6FT7<_;&!>~Eb5V}IY+#R*UD9I1+b4 zcT4g%BizeLYw4>Eh3@OWAdqLz{$oVQq$`BX$kH_Q;ZwzRw-sO5Er&K*!wJw{Q?ORY zMg|YGU!wL}^_tg)Vd0hRrXc%pX{{Pa#gi_h>JPNPW&}X>7~&0XUJg$l zO52Gu{A?|iZ`;(_>UOKZojXo_QQ5#dZYb>{tG%0d14qP;5uU?ctuI(D$!^#(yI$nRg>D z`~~zDCJ0@HOwlkp672$-SEt<=94|VEJ8P0@H|;l}uhyhVrV(Ps^NLK9DKg+^X$&*j zP8;J~P-BrGDvyD^hg@WcLph2;3c><80g4hS+H?f}C{rB5DIU}!*b#A7o^%mX4IfJY zUy)~b`d$aL-tqPb_8CkQqE+5)_6lo)7k{e#)%!nozzUUf8!-W$IrFUgA&#Y*kUAWT zjK@@s=Pq~$%Rl%R8*jes(P4Ur6BVdWyxF1Z7kom}9;0dMy|b0UBPW8){#D`NMwhzy z>MJg9i_!ao)USr8jVu5CPAd~ky8nLd;2rHxgl>}tDe>{~&r-26&-ZOzVl(fKMypP& zaPFYkie0f{A_fCMMjSo;5>+W3yd>0H2pW2UtGD5|(Zsz!ZV&H%pEEAzOM%AfpS?=` zj^04g`;#x_mev%kfNRn!D5+Ut*vnekoq=|Yu4LMOE$tIo>ep{aJXN&Wn(K4c)Y{*F zF;V^%bpzY}fxPE0%I!1sT~wg`l~>^vxtm_S^8;5(+^4CDQ2V>UI6m6m`#zrEeDy8u zuWQ2JgRfcr*6-9cizl6!F#K$(C1e>>LT&VdeYiRjd5!asw!zE+%c_Tl7x zt<3Q|K}TvdQ$)sKgWva0_xV5Vb=A+VwE44pbW}ZD1a(8$INkLy%hbD`)Mdw5x(dD1!A0RdCJT}rhKzW8P`m3#USxeN$ zFKhq)h>u-MQgg}4=y0$WsII&V<^;vns-@?{eS@oaB=fU&y13o|%){)Pq^J$| z&JGRzbb;lqnW)gdB@u=>AZGE=e$%ek)KP}|&|FYM?4Y5^X=paA_cv*|EW0hyUaZ>V zXH!vWmZ9~sr3NbTL)3IygB3|L^+E`UR1bs&PyfR!+-#_CxVRXRv-y4X?b`7Cz|iq4 z4{X`Ex-neZ$Xqg8`1gf5%(uq3F-O6_h?v)F@;%vT0oiBMo)J zFe1Xd%CtY;nk^q=6A`;L+gBFbS9{9#V09_RtPevYL12>VEwy`7TsWMvk)OOh>sFk8 z$Y+vg?m_;$+}WNPc$H3okL5mCVBQa2yADqW!Q*gAjE3%k7$eXHlsmqp9O7i0-U zdOR6N=%k&Y16TlN=gtYRvy)5WhXjl#{&2!P?+;i1FwX4=>mC##!)*wf1nK|82jKq% z4+jqU9tuKkjl&(k5Qc@n(!u~84)Wgx7P`2*=G4~qT`Tsl-+pN0rUOspwbg;; z{iXd*!wDsi$UD?X`wO{DI3Lrdis4oWQ;W?$v}(u3T2$d~4;OY+qnT@XWAEscdiw8& z_J^k%2KBPUR{F%k+!yw1DJ}WC79$kb56n&`*NslE89Q-oVbfD-Wr=~-&P&- zWV82@gcP2C+7O$^lALL6YX2u2G>#{wR&8wh>eh|6e5&;QdV39%2-BkasjJy~c#7vT zA|H0#RQqmgRz#}Bwe8bh8hH#tGQxKyJ1j|(k3aoShW`wArso)PyWR$70ua2xcMCKL!%adN`_;x|e^@58fGyfS!_aH8KSk#NQCA`62IXRhL;nmo5 zx|pc`Woa8y+U*=gmN9r7L;8{uV)>%8}8J{*R708j$qNH(qPFL-F#PZQrSK> zK7C_2oSZN+L4}vsja@x5y1AH+4t}*bby6MYl-&xOPEHdCha^zk_64H>w}@M`#hp9b zlj?@Iir&hF6?Exc6NVM)pKI^5Qrn+-u`f{fE;*_3+L`v*^z6~dmfg{TpF2Yx9{p#; zmEcC%i}t+^Jo=BKh2I6K@F7GIo}fMo%i6c7?@>P@td<=)(J_?#sRxj~We6thw{8&> z52R^)%NV|OTG*$`=``{34B5F*{52+X=4Z$jUpV8r1KE-3`fKX z=^?F75F4<&^uUIc2d1RO;b-)zJX~(@9s1`H+gDF#Diw0DmH{C|9E?HFw-(QcFAQYy4KSf0DCfSs!50#r67;{|v$K~WRp=ZVr@Dw7R04PFh`HgG@5Us3o@}U2 z!^dQjvwffC@^L&7Bpqlmb6q7Bog5n!SUfW^YZEXNZiY{y{|0bVF93c^Qpx3Nb!(5u z!PQ9tJQ^8*Ex)eB@kvGZT=WBekhr2wV_Vn>Ix6>uKFL^v+4G{GUC zA0OUk4sN}NUj4AJ?&bh{&(Mj{%jZ1K5d!y!rzTp85eSy7(Z8my*!YgjT&zB+C8z8{ zpt8@Sv^aOuP48Kg66kYJgf(RJWu}8=rwrg_gxlklhLk{MZ=c%!S88c5HSOdzc%8g( z`!_Ue-*-X#Yn=Q2sK^#eXYWM@37&cj_2>%jK>Lxiw^8qTZ~JGKhfw@x`{wq)Q@3$% zb&D3Fp+VDa3THnWBQC=L*Ir=EXn`_C9Ui{`4|(URQPB}uHew`Q)iRW@#r1;Nx}*s- za=pT9sW_qBbPT*r|s?6?UJ8z-0o!l4bFXW}ZnhH5AuM9v%#6gm_uCM4SnDgdoe zmM9~d-EnBu?PXFP0Fi&ZO=;ghAefZQuiW$3kwKRlliNRea@E6gYRG_BDG0^ej3GOa zs*0)}v%LV9M6g;S$RSz|zj6o~)>zLM zBn+N=GwvvWH2`if>D>LCU_>Z-6--*p6^aQ5>kkuS&B@4-NN+NbZY*i#!5!4#MO6dvtp?a5;PsLyKzs#+}*!%6X zW5&6!j$BTKsK7vV|7cs^L5q)kiF`b43&O z8XX?9lobSKm0~QBn73M83} z5#>cVV1ZhvG?ub0DX40tG=k}6@E93_8j{m?R*-mFSYW5ySKF&>osW-%z%F#!;j~5Y zA9>W0ph}i-?j?y(j_wCJ0j#Ibn7T?uRRqXGT?0<_!T-F6dzyW*ryo3*T@M)p4_U{c zjX~~95OA^hdKbq1pL8_*NQg_whG9&kNvAmX^zDzd|E>M$zkVmZJvel4EJMh*l&W|9 z%p>D(S+{D{x=rg(fBw+AvxnowenW}%U&fbnRNp{fjtOUJr~Q-XK0~oTq1I_FZN(o} z6km*v4LPYTFJE4J^4U+`Gq%)UtIk|`)#Ad(9;V)4POdT3@Z7b7*IhcND2tap>D!EM z7hdM6o_7PS`W-TxpyI^pzRbaf+WN)-UuE>yJVgjG$Sm?X&@uZL&oB0Ty7QvHfFnkJ zGSPHB43KlaQRvS=Prek!YajxWcyhYC5=TCPll8kc8 z+n;LLtu@JX-+BG`gb0T%pFF64Ndi0tYP|E6s7uQ??ELLaDFowlzJ??Wec7#q>Tq3865cU-(O~!amv2E%=st6I=J4-8 zuK!=IdH%Ke*AQNg2@@$n4#q0F5pV}n(=o#^2DVE}m3VxpH*`TrAJdC<_!?^Y3CXJ% zvJlKoI*pYoPt{6dreG*VWJuDz5XZ(NVODkX$l{vSR-}@phFN8~T{*82Uo&Tp8P>QJ z3UP9Hz$sSj@Fl^B%DROiS)f))RM2c3X_!vB$f<0xh`eLlhLg!YainBb8q%N}+8MGN zg0?$e&qSle*w!F%=L6~JPB9}~HAtnX9KD*QMy6o8whQqZrNW zH{BPiO*dINGd66vFxd<0)ZWNoWcaH0uZZgj$!RPF5H&h`y(`fD)})0HqS#jIm-QMWnE()3s6_A({1Y7})87+*Bc%K4)D#8H~kchZ5M@fULWU+8>ewnSe2$n-L~Mvj_oK)x8yS zSIrny)v~(Y3?)RT0+9k?WW-f`6sh4tnUGoKFX1UYHI;giQR?dW7ED&o z{0ARf)pz(;djYC|@CVvdDb}4ES(aWo+q z#U1HY)J9_>mzF^28-$PGeOjfyVXV3{c)&_d@6u8e>m{?EsuYz;IiQB`o;8rCW)hZi zq}ZE^_OfE%-)xNev3$2N{{fr%A=p5_*z<#)bC7`@yaEV%I)k?(|IP~*e-saQ9Jm;b zM68j2&?Jih-;wdz4U#6Zg9IXBgLLVBlD&X0AyE+W3zsEud*GeBU!ke{7({ZO&pl6y zK!iaD_b){-=r}^;7P)dW-Sv6{BA+F24g5fk6QvV?h=QNX0kY7qK2b6WT2$ytb8--t zh2_$u?#zE!MGm02SOD1J2yZZHePPGWOnid{%@G_ec6C^!;1ATeNON8I5VetV=jINM zH)3pMUR1n>shMMz=78X0XHK?wN%m5ibomlMfdq4EfCiH1pV`Ewa@Vq!#3+(pH9O+_nV2!Z;<>-S5^kQt1*n{Q8*2V^m1(XJKUjhH5N(F!Sd?0c!O zs2Y4l2EBtzKe}(0&Rg~a^X-D!C6V?qF?0o3&%L1 zkE*3eExRip&MhG)g<0l?%w%OIw%t&Ih}(|yR-qP>@4Z-!OI#(*%lc{aCj%=$m;%fZ zl28P0(t>LX;g!g6ry<{iL@DxvfA#u)-3qyP9`D4Z-XeU1m%-iq@t&VkEY%?MdAAdL z=y>SP{yZ@_jJO(1B3Vhdebc6rf_L`~L?_`~>Mm{^;$T!{&{h95Tbk+i4 zS2@2e*Xd73_*S>m`ia^)cZWZSjJ)LyHOGqXd{6F_C%VD%%!u_==Sj%CxI73@Gm_1y zV-D$DqBUqh)#wf_e_V7u7Tqfe--w0vrK5!$}EX7-wUL$~@9g&-L!=)KMuH-Tzqo~{VJ8UYfMI$xF?G4yb*2$aElw1aOI$DeJ zY^p&|u0LW1DNWg=%3wwlVkIsxoS$q=(saGK2C~zP4#ZV>?G3|j4#9s8>Z$;v1bR0= zzVD-b+}irwE%zXPR<_u9-pQ+KCN{c{wsQShS$ZP+;_=@;Us^xCQ4x&6k6VkE>>WR~ zZsnyHH+x^*xfYoh`8{@J|DNdLUg{QZZPn_R1?G8&+Oe+Rkh2Ss_f%Fop^LJTo9O3} zywDuVtk`q&bhfu|i294*j!lWD7lJi=ozXv>Nls=@8Kp13FmGOq7{K7}#`Iu|nk<$h zS*f>VjXK~nS9)eBjTHn6pF(O~_Sjy@vEz}( zN;%j%&=*Ct3LA}isVT|frfyZkq2ze3I87Omh$3x2Qy)~~iM5FFH zYf>NNZj#N6a4b~YV4L-E>Lfcqm7np>^&#^NS1xA7fRWTSWNTG*M?#RFtj9w`mgK}# zv(yJ1-RAaN`4;zcaB5!)kJ>#w@96n(&p-A2jvA%rDFlUe#$dNcl4vZVXf97KJPaAj z_PLIf?#v+_O&0NAWH5ANLti^6y93c5D$qlrF~dXvgKcuz$V(<1=&O+Em^?6u$Z$Px zZ2*)GU-2VRVe!W&h{CWz!GKF<2$()PihtK;*cn>q=a%JBXZwcZ^Y&!_mdmy5DCYRh ze{#%PxW=;3!O>ugNF?aGz9tM$^YT?*bCHhgMerXIL7FICMBT8Y@}Hh>dBekdV`-{r zhS=~*$!G>Qq~?n2ZwswDe))stt-EW-E_--exNlNBapInoJ{y94mR6nQc&uUN!t-wV zz;yq_4L3y^d(uuNFcb*6OJ$jw8;rHT>i$Ahz~*;v$`0#=Z4C8=0rgJJ;--y2<$}N+ z*Il{y%By?Xz!jJFRpeCEG)A@9?EYP~;Z5sqJoFUk!H=~k`>3=9miSH+63RSbH6>*QMPoEZ@ederVabn)7aVL`CQHs5?=7@|F&N_MZ$ zQqdH@6^Lg*%^fR@WQ63{?ou}}d^LHso?5k-+S<1lp4viyU3x)Qq`?i5c*(HsZx8IV zx0O_?y;oB<3J372ze%r}f>xH`xMfc>QmngM0ncy5K zTZiJJ6{%Vk^^_6{-L|u z=m;{4T03B>y5S|egZLxCmR(l%6tZcBA5$YCdK*(ID{bBFHR%RTT%C4e^!sKmjoOOq z+zS}NrKbhAKe&Nqw-;S2PR{$_B>UryaNh0)Qt*tAn*S)gV^?-nNKq>`a-6=1i`fWu z{#>`RnPQxWFq?pzfJJ=e#OY7WrqiFl0GwC0Lt-P_mq9^e-o^R8uIG2b^9Ff;n>#-j zSvqiU)NKw@NntYXX zLAow87b;00bqbdUF28W@g9k0|-?iP_KDhOg>v9TW33S^@to!mTHGARS*`eBwPwlz- zv>Bgy-AKk4IoYaAb8zi0$ds+nnNMDmn_d_w9L(egQWL8>PXBPBA&fCQ*wND$94Qsy zx|-N=(Q^y(uEY0TI1bgE;;inB4l_z3CKapkRaMT7YtbNpp)w#7El`522$}a94|NV| zaqU8FJy@9$QC^Cz-Z&l{=nk%$9NxI2dil=GMv2v1tBs4b2h7d! zt_YoT$QbCy`u;KDDek{p_QB%v*I3zq1|0q0!UC>>b1Feqs6OB)%^=oyKXoA@eQ&1j zM8NagfhPSvph>@gNZ+48Q~!GkY#p=?+$gBz=nlFYsM2%vW_lleoW7jCo_?5q7yVxP zgY?Jg&(U9{zfFIi{^|K~c3uW*%0gtD=svIB|8p}t7Dq((zA+rgUsx>9>rj$3{wAKF zQ>3Rf@mx6W>vp*|wuT}xv-{s*aK{61_7n&Rvd=Hl*PZg+p_+y~nXupDp>-5jYUDG+XV`(DLgH&J+KeOCL!x-YDkwZ92#8N&otcf7 zZ;1~KoVJVVa9eVqG+|z@&&)O2wua+{PzY!1a=55jX4`}ksTgrFF#M*|r>yKL^%G-i zS5IbrX1uWwy2Z(;J9ug7M!2BL6l1cqrS{Jq>)dZg^ozIx9qEK#=Oj?FS})kwQ5Z`P zM*HVNG}O*!l{grff|KBKx!7WIyt=u#rZ3fyC9jWh%583$}_O44IY)Tf- z{W7Qq0R>C~s8u%Y$mwS+F%0zL!-o>UnTuMU>8Lm)Iu9rDUndBfbRqXm z%~jc0Kr>A1Zv+KIR=^-uWn<~bIYCa-LjY;A&&3hQA^^4V{FLdOfN!$SxO6_)dEy-l#T=fpA==Vfk6i#Jj!zcmQ8FA2ECw& z2pd7sbb;y%s0i{iKpFM|EIG0c5LVv%EPRk9Aos@Uf{~D;kSt#4VAH_rdKu29fWnQk z-qWeHMPMFX-kB8vuI%!A2huPO3z^PD;!qU+mKM!FUtInNBK}!myt2kJ2$R@p&^tM!P*?>ExW2-oGmJ!(l^0k^_Oy){`M>g z2b{!`{xgtBZIA6bGwrliaEfREtW)MK$+VelI-1DFh6}Ts%y}Wt$q1r9FqQ6_)_RYH z;w5onymdTT<%3E*TC4R1K$5F0rgTuvRUf(O(ZrCNPcH3FR5WPs3?a%yb4#zut}1`2 znl)7iBVZ!257s?{S_B@ir2SPYnfwJ~N`mK^HVCe~UjVIFw81d;kAlJmnWYPP)wGzq z1>j;{&8^|1p!Kve%H<3lUFw(RG&sXP2s4jttcSNU3lHG!NliFO(bjhu_qFI+8Tg;A zP~IfKM)zG1=TaetzBM&5V~dfjD7mujKqto;pt*|+KUbr1bqV&RG%vhrNDgx&f(ijr zeb&Q3TFq(-1~O~WpTk%O^r*IUq4g^`l?yj8(N=|eh80{{@UB(p8Y7()plsr)$%K}c zmbNmy4xfqpSp{w%-w=RRz%kT|!HzD~7;vNAxhIXmlzq|!#n^f zO_@Gx_KG-{Qyba!`qED<4UPtP0GkAuoylf zceFg&@(frhzu59Us^GIsucvlk7YDKhPu4pOSfaK#znznfyo8;lSRyzl%eyxDzVbH~ zQF8XdC31diGzCVt1F>-p1g12POikN7F)ri54&;Ss5r^F+zH^gb+@Dm>ai6;9fKsX4fiN8lT zn)?#&hJ-I9x`9Fz7ugHW6>%GuHGV~TUo4@si3+-N1>`9WULi-pNpK()m2DsZQ&oeftm?s#F$-{(-vxNm7PAXD7Kf=wHXK$0fw(M(?v<|!ieIJ2213F`F+upuTNU<GIbPz8g)?7g!tL$pk&Xfi!5*SE%dl0i?o{kfL3CnAW3(ld3@N6X zN{kybHI88nS^AP`4h`)Ch;}&Tn(3+lSChR;o;BVViBK{?!qnWj61+AQk`?a(1vo(g zC)^2JxX39|U}=p0Fq4cX*ltyLUp5 znwH`?snw^%Gw)X#bsg5+-R`F@6VG_>QTDqvqt;nIXHB;&$*DtMW`vG>EFI{mQq&bo z-=qSG@#_sC3plGWRRK7O%$D=Xp^gr13JAS|VMeN5R-)4xFhy{{4DMU48Lfp@HO#tE z&Z&SLd*dR#wztO;=C0Wd>otxPyJ>Fe=B0PizZ2tWVY>y$r?(h`a4_(822*jp?Qqif z+xt2=Xg>y>&m-{GJ4XQ}9kXxi|CQM>$^U%9H)jZOv%+61Gab?u(07{J9$#c9-Yq~D z!=!Ft6(AF&DZ~4w*&fUxX!hZZ;=kWau~=f7kNR#Y{(^zGq3Fcy>v53|beOHO0#&fLqo~!F{y=qXPfl>9oSC0hH1D@uW_-F< zjgM~OR-cJ1KPm#f@QIVm@{*q-mA(-p80I`*MLq1M)aS*%w;p!4X7&6~TYM@L6SHhFbbSjFq> z^N@qVTpD>s=j|eIe3=J+LInz-9!8##r8ENAJ^=7&n05_~dEnZ+?OD<2C>sHRDdf%k z^@3@pIfSagnv=sG&$R6KKEgfCJ>QaUIR&rW6^#!${gP7=f@m;W{ZFsi*T|$>h~Wc1 zY{X?2LmxvJDjiHCvc>y{S+eu_-^h0|r}4dcvxq54-oj~!%#HKku*>@BTI8hO-Nik9 z{Nyt|6IvlT`_`;8HWPa6z}^>Mq9RZ|oVvDe-uvx4e&s!}aqVqqK6YgL@gK&n-hbx3 zAHV#?|NPV=Cr>|d{nX^@m@_)KVgA^3il-68xw_Flws`s8&Bx#N)vHfE_~K(vc&iTf z#u~XD^#6SB!_Q4y#o)kiXQH9YC++(lglhyC%YE3`ZwZbdhftCznQ+D`L6g+A;Hj^&oEwj+D|8({9*AvpT(52Bt zMSzw{lf`IdL29lV9O)N-Ny?-Vw~o7x$%QiMzP#UYh-SyxG}ByAm%9fzVYn~6;yvQM z?tQ>}#`_=d?bPnbL?Au%^c`1RdHd6YDLWdrgKq7r^&7qS)z|Rx`r}vL;=Oe{P!f8= z;po4;{dhsUEFdn^jd!& z^Y6F+pF1%59x}@tlHVmw-cO8Vf7JV<9719j@opp=#{&!1<$rlYA?j-<$Rump6`Fe+WL1B7Ko)LJ#$`%(@-2X# zZgAiJ)EB+4c|Usj>jjYQgwl^oaBs*@j%}Ok8_s|I>J4u_dc8#nX3}`QmM`D+?2an5 zpQkqM!U2eJHZ9$-s?yOju%_Hy@7>>1Ppq$X;2b~u&2Vw;q*SJ#yz>j6y+6#x+VYpi z;NH@zvK(W91cjchhguQ2166p)x-laqUQ%@(4kwEWCvlc-7u!|c68ZJ5%-YQ0yn3p?=`K^pC%I<%d-1!abRp-lLmNfay0FbIK!I z2m4z)W+PkdsGXVLbxmfXx}}{uoGoXpVIe!C6>8H2ESx$-eXuf8tklZmg_5_kc>2W^*}QFD>u2eTMCa$%T}$9?T+ zh$ya_XCVUBm&-6D4KWn5%W(t`S%~iiC z*7<8b$vs2n3WEL%WaEGkg~49%cbevQ(R`Zd6#w+?WpDyeq>zL|P~-_uwAKvA`}E7_ zzV`AnX}z2NM6@V#Ay7VQpv6gCws&8x)~GfP*1_SF@&2YGk*F8ePHi*$gCNC;fcHtE zwE5|gRY4s@7xM%08kbG}<@<7QE1B5vfFegmslQgdbe>o9R6u!8K9zB9=o=a7ncFuxy|rUI8=ct=t=;Rd7ixg1F)THA?8x0q zpV_mqr@Z%gG_^3(-Meas7g`*e9va@7I&?u}V3xW*HF<5Jv3k9CeQD3u&Y`XOdHNe0 zz019$mJ3bbZx5C%o8qK6taRc)F2AXgNH~sryfJvl%6Sja^+bnjX9BK(GnBk4nd}%| zWJ-#h2CsU=K`2V)xN^hErRS&?Wn^SJ5Yi;TCQ@ufw&oq9t*zb}Dk`yHLA*K~4Q-6G z($YKCh?}D>&tIC9%uS&a$6e4JG22Q2htW|ZQC8=}@$CXXQp&86_$fYrEe+BpM{YYv zb&8j-@m@|kR>2H}J3AtlQyPg5v)m%<9XZk}GUAoFaEHo_+}B#lrm}G*l5!m<-qRDa zEw?q`&V`dzDO`w!s^tXM{T%FBL&86C*8#O7+tLM_)^0={U5^-pN1(g-Xv@E~cyyjV zLEl2(OMij>GX2-|tMo7F-vdA@0-7iFePMsP=)3jZU`v1WEPT(SjjO1@E-O8D2bO7Z@%eo;mzEd z7YYbB{?n{%vKTbCmZk#3-+u6C^D<@|bRYzR52qR8kOG|s_Ns17`UDt)Foy{3H5~gf zYjMp*jqqs(I1qINh=~4IhDZ(ye4-l1%cX|f+k=x(Q6M1czLGtzfBTKkv4 z=h5gxRygUBvva6U7ID%&*p37w2gE@#K1j9Ec37tILJ`nOq+}#Z!LK+^IF3=Q9;m|b zWT@Al6nL2wnUKIV6F}FK3#1=}CU^|ez_7)Y5ZP?H-3HMDqND(Efh;Wi)3N#=p6ou# z1&VFm{MPz~n+D^5_+nz9KgLhT`e(*-=ROsinT|7axp93UnMzZaWGAREFDT5p=VN2jS*ANVLr-=D&i&YF%c{(r zlGAGGbdCJ`)B^dPs(qpjU)H8-R6&XaB|-TaAE%ftNx1`auFb} za)_5BcS?v8k#w6v;6?oGQoHRv7ii1N)Fn!e`g^&ka`5i6>6qMQgE2${;3*e03{Y;m zu&FQ{BAgAdZHRSB4y9$P!Y*RrK9D;yC6(Q8of&z#$ibjwLC_ebM9AZ6qUe*FB zDAdKcS{*?QL4g4YHqT3Lw2S8uvL0ZOyDGEoA#gCtnr1SUh!#f3yly$1&VzT)W(`D( z#%TfI-3F~ham?nU1N4lV=%O9EOD9qZ71qyY4*4Ak>TXoKx}A;naQsn zi&2lpC+2cXN3*r=o*Iw`YJ=&pcl9FN*`qzR;*Lxp+pokgrjKN1XOh%i>8ZKg+4t8g z{OUOMqwLH~@-^xu@<_aA8}dD9&o$qnRJdn(h>o?hSXE!p54RYW-`yG#WR z^$4kv!Qh?{g0jtlxKROGAK11yo)!#lkRGFM!>Pscu9WkIrK`A#tb}bvlap`XO16JMXOYi?R#!g5UymRhYkW2!s!iM*bY9zqNTeUOvJfmOmujIjl#QUei5-LOTy zEc~8(v}G8w+*)`jUC?qX(EGmH@~xJi0HGm`UA6%junG95Y@zm2mr&PIH&S;1iT+`D zr+fghq@M$)+4m7k`g?kU-bTNV{t*2s`bGLn^vhs3|2h3T=33@{=3(Y(=IhM&nOBze zVD$1%gl)>-yZz%f_Dlamws8LgDIo9fCFI`%j}B_foOPt*4W4i98E_wW7RpCo5oUGY4DAqpQH{czPk z*dY&;r1?G(5B-!$lBI^zLyC*Lc;DApujG#J%O_2e7(DM$a;WLZ_S^h3MEsbM7^y*m zf{@e;VGnygq%JDwG_&z53sDn#{$@L%e&^f3KSSa|(}#kD)gqkN)#IvFYaEeC3iNHbH z2V$)6K^-I!PfZ068pKb|KOhsyrzz3m7rLk^IH6BSPAGxjJH35GrPI9P;|w$fV!!UF zdd>&9^Gk?K$l;g>vHk}SmISmFK-^YJ+w24KAfzRf5VdYXCHs;ed5iR69d}5~LkWj@ zczydq-Ex(D6DkQjhBQXHsQCg!YEpRwT%bB72$w4b_C44kz9Ba>1@4j{=aoP3`tlY0 zMdW-Ug8Qcg{Jq?8K{7F#@E>RoGEe+*g>n;LUL0_mr;C;Pk>Ws=(@KvDXLQnh6>30-lV^~{}DG}$%OXLCaKE?r&aS81_ zssP~X-fB2|f96i&$+8A@nLkm_SY{m!1Per>@qXnw}tLoH~ z=_5O+NR5@votW7gi87DbR^hX17+M}>g0FS0<0F{Rs>t* zb`p{jz+5e47RUO;q5Dd`T0B@ut=hOf`N+)g=w}XewC^4VP}169Ox2B2DFOj1V{KkN z3DRIkZ%^w>@3pmA+KuHHjUH26tc^>V;bdAVz9i_wtxpO6gHJ z2=aXzOhZX018`NQRse``#5_s3V5GMgc51Ex6p?oZifgwAQ<}W=Y|xC!jZ?}5D_n*U z4wL#1!&v$UNu{f-Wtav7BV-}o4h}kN;X6HgvW?~ja@|XZ3!pY|DPaQoznd&BWGg-1 z&$*ivIb|~Tj8z7unR}xJ0_#9-KS1i(a7Q)K7jfUuZtmn_)N4`Z9q-OoPx6$Tnd)tS z`+L0Ka1tDBmL34U03!HnAJ71mVW_B6b?Bcagd7tWm~rMXrzT@<5d_sd z7b-Kc3@?-sl*4dTjdysqM|NHCgQe1Dhr_vHa6(T}(e~AFAXTWNwwsjFlYyz(dszFHc*dxt4*c6rqiy(IvdoDx;!C*4a3%I3NVegQnWETlicbGfe4Te z9NU{m9B~G;-k(x~az8oRMv_JvO9mbUWt%u5HJG7ZVG& z?z&(Y!@nqua4Kh%Z`z~Boa*~t*)NZ3Q5(iLu2t>`iJW(7vVq7#CfsV|D^JDh2zIQt zF2RYbE{Lke2V@XBIgBJn&RlS~-u?899CBL|ZKbM^u_8mOM&;wD5wkZO4ZFhVz@k%~ zoe5E!TTdA1&X5^ow58p!!6g*>$Mw2}5Gf^qBavHQdVT)1QyK+Dcx<-`Z`|4z;ANPr zz#G1nPEH)7nVCW3>t+NpZ9Mc3*+h1r9$rwG&mRN2~(t1DO2>Sh~mFZ;+ zfjVo3Qxfpc(>a^*yc5aE^7^gcv^Mvp2!y)H7CY(tO&+>+1vF6Z4YAloQa7WRLq_nw zMe$ZdscKesKu+6nxggUb;utMQ@!q_7KY)&5K9cx)&FJ00^y}}{*KUyDtpN+4KDVv` zCo@B)_wY8nhfJ2q)1knP);h!kifo?x`E&Sf&)g*3S@!t)0cq@R=bmn z#lUeG7R&(MXG9^yalM(K0g`k*aqc&Pwj}s;WtwF|ven)*P2IYCpBhYZcBNytoVOLe z7-Bkw2-vpVctH*r{N)3(EXC4hvL^AO9t%Y3@jE7J}i z=dVbN*KMvZ82(tYEHk}jOM}QTidg@eHrgLo6Np9T#H>|Iv-GM^h^j~d6S_*K<+%4z z;bHD);Dj20blbGShMJ62{!i?zkOx95LK`+PvLQEx2wxz8bmi}M1t<~;G!c1Pc=&hQ z|Kb5}ulJ(&S#J;ZI5kD_Upsx}a~nJMESyN;A6!`2x8JYdUwIF+ukrB~3!Y?ao4QmYHCRn` zDxyfC%mRoGA+|#Ki9BI9`fRdo+kmP?-5R}CH!u|O2k`WOm0&^qfCyj_O&|N(e6=ST z?#k8HU9oh_Gb-f{SgGB+l7Rt5Q=g%tHK;>E;nsoHaHvGdHK0;Vb=EWSwc&w(sV)xJ z2WjK_zohTozIOj-z4zWd>fIPT?fuiKE4sFC?Yi_TD$|bmvC+GElX|CjI@H@2rf&7F z3HJf7}Y9WaYi`-Ua?g^{@`6L9|FJEij|iLFsM%fJ?9HE zE3!C5Wn4hG-?B{f##bV)u%tntq=QEoBt^fAdw>j23fnjGQMc$4+=!h z6YUdyk`Fb6IP zZ=*K85;ek0w|?bA|26hG>W3LR?yOnoUYLFS^hfWMoWSZYoO|G+!#CL3{MhtgJ{xV7 zV*R&Vd(FW$-~FQZhg7I-ao>ZlQNN(2U*GV!_pOIcUi=W%ebu^&#$9*+_hR>s^{sQm z1HqovlYO)ElQNSzx;U`z8h7Wx#Vy-M6=!nGy1v=d*5*y?AKL5$4!*eQM5&9_GFzz~ zwfwKrm22O>X|6Oe9g;5^mrg~e?Xvgle9t9M&(3LZGd(M`B&j9xfW5W zkNG&0O||92l5&M3W!Yuwg~>!0b?EbiX?F? zk{Y}xb3HR@X1Yv&s5&FH1$S;N{)A46wKZAbB@8QDIzYNIKO0gW zzArm|=bB?~WlMfCCJEx2dsf#dIXFC3v8|;8sR3$JAQ3TYtM0hgdC7{>Jm5)F!RoMh z*{i962}7x_3F)7qrzi>XlJ~rn5o_Cp#Y?E7bQrLfxBcBk@#EWS;dZxQl;z^IkPxQk zQsaRfLKL~i(CL|l$DH=@`>4{~*9Z542Cz4VIOPrbyYp*SSz|{JW(55Ni3v~4T^qog1DCPAB3jBha@E(iC2`qbMWy7JTOE0^w2-H+*4z<_s&uuJNl;DJkD3G-sr`P`85}}Q9XNd@y5+_b7g&^ zo`&sXEUq$OT#@N)W>rVNU4YJ93r+XlcXU@()|jrRpN550uI@UTcEgIJ(~T{wXN_d< z#GJ|>f6u9f+Tc3xN>!#rM}DF8nyY~@Iks!2&fdUwpSz+sA;*=0soT7N7LqBzl679P zuM&+`_AQj12uwFIm3q0I5AlKc-t?P)xK3qS|K{fQy9#G&g-73X%fq)WI?me5M1(Rx z27Q1Qlh74vPB31yy6^q=Cj!MdKSWndv&tMSXwRSeWrveXI_Ea0ffXh@cD-&o8hm`y z0aL+$q6AoOEDwem5ZIh1hLSjU=+u|$d0^sxA{0_pwMD{Mx))l%-$LVcu;paSEpR@b z1Qx*#>S$9=SefHw9H9#vzG4DB=d>Cwu(I0ROujs7)B@XfZ3#R$mAjl9iHBD`rl%M zrs>3bsrFTPh}!bF{@T^tZtAk# z;)EedIg7e@qDXt69N4N?&2>kZg}cac|Q{4p7^AFUuBbs$92~xTWj88V5&5{WiN- zNIY`#=)qI(iiy4QJ4hIdS;(?NYpSaft+Lvgo4nUKfJ4PuL29sC&5Q&?MZN+`Y_=Bc zHr{iI8>jdU)50wj!)7cSPR*b<;?@O@Kji%&KQ4m+ke+lBldqoKO_iiv3X*<*yBeIn znAhXA{r&B9J-z0OSj!V@^zItymw;r_c-OlrLxqyYLZbv%2Xn!&o)CYcRow!R_v17j zcqrA;Galz1mC?X;ZO5!m>QOc}2fk`GE;7t6N>`$?2|9W<+n!rYXVmouzYxll>WMzs zqgsK1$wbBgWe`r&l&vlt-R(&dhhP}~T!`hAZUcz(tdGnCB-OtZuLv4g`igCnPW z?(T?%z$YH|EHFoEQjp?X;4cI|Rqj7A;Jc}}BBt`g&{uz*dWHHGJwh+gyXYfiwl`P! z5>#1aiuzJQbEdCokNq{AC~%vzpX@eG72f&P-yiD!+$VF1tq8V%QY62W{`3CQ z(A4HOw;AFR)GXA>!qnU2uTg~W4L7j3VAlGZ6Bdc)&P6;1{PhI-TrT7OcWo0FOnUk~Dk01g>{nImi4 z^0QEMkAcsSG!m%u$G~fe4SDDHV#)LzDFIA-+wdwa8n3BcL!OZL){2 zC`_T(#a)sm^wi`*2!2HNb6{pbhC~lb+7z`R8g0T|=hqS1-sMIhCGzXc1Ab@a3z`7^ z=G*4Gx&KV8+BQ3DW8r6`U3F@r7LKvnw$9>^J52T0YMD`iUX3$+s~U+Xs!vs)8p=gS z_NG%6YRC3tXk#UdwIBt~uGZqZO2DvXhCjMj~#k19KRN zx!FfRr55HkTcVlB%<*js)E6g$IXNwbu6u${*au=z)xlQX00bc|Mc_vmOz|Sj z%|(Iub(gYYc=bc~Zx_b`g&uQFH*j||BZB_q(P9t=G2kJ|1+KWU)vARuOvj16w%E&F zrCRf=_E}K%9WZNQt&ffmPYA5{$gzpS#{LfdbFJkhs788Pp}#5y0qbfwi{UmZDT$hW zh}A(qH<^g%>99FhkBBPEWO+4mFmh#sp+zSYK7b(n2wzQ#FQr3>|4-Uw0}LZV54&bv zqEP8VWJLl2Wm)=dg-?ja)MBhC@w0E0G}$ooqW2JA+H>o>Po>>x;M^;rK(stf4W?Lm z`~kb)NK0nx`uv)W(0jE4RuVwuGT?Mdthn7~CsILxKFe%nZA=TnVFLUTqKQ~TlK`{) zG9bvi;pGtRN(*d3<=??W{cDs960clD#jz2*n{98@s#O(QDuGV6f!O4_MDV2pmn{|p zLwBYlAvVd&?E*vdeQ7DYHi<}5i@^YyYYh*F2cp27Pcmw`)ITRX`JJDA9!$DGokYL_ z%(V$KTO86x-#yIm(XtY2?2;Wb$;cLExE&IrV!G`OG&^Pk7{=zL@~{+wQIT1r2p|oB zMK&93)l4kzY-Y#0qRXqPw*X?ydN?!DNt@9Dv(Q(l7Bf6>gN(RFWf`%MbpU;s3)>4> zDa=W`o?1&C?A|n%VPLhVg?yyTEZ8xP+4jX%BD0u?mWI|hGJvs+Q}g|tq*fL2wg+r= zDKm9@I2aKYx;l=yA!)FFa`(E>pb8{O0sgHZjt$N_PSi|g6ZWq3Bhu=6_O{)@O03OU zbne(d@x8$7L||0svEx@S?N=f!Gh)7$$^j)O#4K1+8Y()0VZF}>18!OcV5qKyp|a|4 ze~T9FZ&QH~8KkBHd8Z$NbzY^RA)Mc8F-PaY`*}ex< z_7o5x1K&oa}``R0`8i25Z2*Q1uQ5+nZ?wt1LZkAJ-K-ETwB*kBO9F zTTo-Rw+6MDd7+P&B0d%bWX_{J2XFPuTi)Gr7F^L;vj1Rp0s+SIu@-n-%iG5r8wj~T z7L_+XzU`$^fdZeLU?HAmx_lY2xh;^T&)4DjZgpf`@`X^cbops8R&xz(ZhU~R_E$0J zjY>YD23a=izO*<>+2}`5m}&pgO&hXMM#QAL;PT8SLsNZ@&04YSZ4CFo`j$# zA-k@4kg7du-Y+yJwV%H*~ zTQtn$v2tX5U7RbsU1?q3l~oSz-rx^ZY|GHC~4n$)=B;^}9H zslqf>w2isLLNV1D3Kx=nQJ!$aud?jEuG>?`W^M;VSs)Fsz^%{sT-vcgIy9S=omngH zUG6ExuhA;9~yIr1El&YdtMAZ!>DCW+jHoRH_VDW$*?@USgXtWq=PlAp@@9waZij+|t zAa+~o+~8n&-R9V8)RC$in@7ydxmZCKiVO>?VBkI@T0eFumWF3w>xnN6TLsx#?aCub zrKHfKg_L1gPAwL$(8`Q5eLx*fM>>NY)vDW#ptpTycKqREo8UVJM>+U(6_wqVTohiN zjN=d{urU9H(>~oBWs4xQVBUT6h>iVmVq-of7yo@c@%x;f2k{VmY5Dxl0mq2+v;1yl#1kaS)FWd zGbVeTLK!w#8B2a8*3;RhSz(~=edMysR$pNiw?xCOR4f&qz-89$+4$A@eHV9@QU+Cs z2FE*EB_<>Yl9eo}?W${}t>hj=9f0vTd1T8S)aOrtgDsdf+HyIun<(A@THRkBGN^>b z1mMpvY2VJ-5!VXcX;ep~Ak#A(76MulTJKE&-z=MJ9B4oIipQrM8c^VF{~mfuX*D_&#A*by2vO>m zpf|g4%g&c~7672m7M*ZCKA}2^5g{Odgr@rx{YefOn^BoK!LCs z+TOzS($k-J}Jou**JtXZBg`IekUr1u?u{I51T^-F2k5VGx3?r(Ca@QP;x_|PhPc&}Pj9gg z>eZE#9%E&kZmctGAa2FLKns|f+?C7i7&_r?eeNTt zPCdo*Pn|mT5&22{_z3eGQ9JtryGpVG7qtcLa6HgckJzL6kd<;m!cq30ppA4|DqvlkAXYrYFM*>5AFMwunRH9jUI`1N zgKNGq12#MIf$<9D2M8E#>F#PN1NwSc z&+)Up$F>|>sCBh3uJoA|M|EMVW_n24u4Yl6;I1>Ii zwf;9Rcz>V-q$qfQc;V?^|N3eCYiZG1F7>|2KgIncH2X$Ns3p}>f`xA#q6zmQX7<{a zx3oM!T7#_i*lw49mmo~y@a>Yl`%ka0NRp)@y|S}lA8l@nE##L^3D$t>6PMxcMmE!s zi_ijMt0PPd^i(BdLzzR44tbZ$9jO_UG}; zft}gp_$qE43i2WpbDs~4^INAnU*&00I;5(`+3goxe&~Ubo35j~ZfQ)~WnOMOoN(QZ z^_#udt}cD>9`;!0yGjQmT_9^h*qehw*)B2kk+zn;F7-V>vFj) zVk;2LX;63wc|c!HQpWRecO>bPWdK?xUxI_72KnPDA`#+B4~#M{Q5@j`febu{Xzm13 zN`ibg#*}cC$S$2O))_xU1yTj13>cIl77>xuz#tgFg{2SgA2_}1`ZF5H&SwRPy_uV^p+gAd165Kb)hFfsv{s| ziR49ILl%BZqJtV_9CagqbfilHT7Aj8nCnDo$r$k`4CH5lgv~GC%L+`NU=0hck2vV#TLvLz`VaScTP zMTIorCaUm%PVslQDY03#pEFoW2$kK!K2?x{@lWfHl4*$$UK^cN$?04wDkc$$CtPLJ=8;)=GmG z1fGN@<)j$+yCniiTF?+tBGH5yD*@;S7^#S$Au$5~U6l_>fHHCzLV{z!Zw*ePsfJl+ zXmB9t5t<6*UBQL~shS0ZnS?{3v>t<@iU!>a{8VW@0lc4(p5t63lS^f2Sq4Ow3yxv1 z1gPHMfkhdN06Kj15PcpAA{M~JEeqX^2$ckD)bN1!IK*Qr5d^^%qG>~|?e?d6Sz+}AjW8SdSa`p%HBH)4G@wdUpmjlx88IUJ4hGN8B&g7x2kwjM#%#3Ix zL01FlCLPjX`#2tr@F_t98^L#3RFccuV#cV{-DJ32g zHAVv8gcxVDaI@tCnrj3jn1unr@v#&I)DzLn6dX|pmo^tQfywtAW4al>-IF<8$skTE zXjI{b8V64)k1!Pt0c&`2K-5?SJ4<3VdiLH={~LQOmsn6WVCa~(7_3?tkQxI2bu&)u zI$VMQy+p;?K|sx6SP3A-0m`402qyTjqBE4<8icE=6;awOJ;g>u=nYsosw40XV^GxM zIBF^|@rYr{vJ*L4;ng16m4i;$aZELWWk@k9APi!#+^~E`lrun*i9?F)n!Cg<_izApi-?yIe32kC%j%VFGzW64Gr90AZC{$^_lCl*2(u z1&$16hYA;CH7$bx0F>i2MY}*5qa+-vm3W-BoTMluL@khU)u7BPI7(+dh~TA&u{syit&~aOfX5qFP;uEHu>KT| z&d0!9A)Jg^3e5+ohzf&>ilYrs8Nr^Qn&Mf3J&9u>r`0dRmu(r^sQLztQdmC z@~UXNA;3)v;UL9jEC=-!ag5(#@mwxxae52`w_l2j-VH$*bk{O@E^LaWvyWDHvD>0& zADCo!Ic3(6&%QXx?r(E)XP@!UP@DdErcieio3kC zL9RedFZoHfZ-jzlha`Km|M3-7=l{ktz6OfCvFy=7oW@9C4mnuV3A|QQ{qCoR4HGJS zY{TU4vaOiB=t~PZNFNQm!4cG-b`&$;oMfotmGU zU$u_wKYkb0byvBnyLVr)@SYC~Y6wsfDJgjU)mR0bD@y^o^P8oC!C2MQAWV+ zP&hP5ok>SNl)ib2Hb)}8$xBb$$jYi2miZ>{Gp;^96`D?clv<;*;Dkt6? zop&lK$G?LfI{~JJ53PTUzOb?ut5R>cbH}aym-RK)tsfl$HQSIKhy>>bir4j91+l;m zZrDS$&94h@ti>(cvLhWE+>P0AIT2p1Dc)DvO@iFknyAJNRnILHhB~_@1LJ|pgVfqp zKcIkbhY(9m40!)N5zP)QJ^DT8v=oZ9=E}0L>1_+?iOyHP%h~y@7t$HE-JvFeYqcOB zC}kXK;WN{B9WH;hQ`l_{r=n&#kTyyc8MDm&?6&W*N494bt>f`fN6q~iCA4Dv>+o;* zyzp1tJAlm4)=~!t?4Fh*I6J=ySd#B*d2h>8;BkKr{<46nAY%d_BJhz9#>evaCNNw^ z$oNjHnJydp7@T;|k5r5)U)9|l+n7-PdoG;h{HcI}ZQ*ZZ0P&GH3X#cFCr)_8m#w#l zvY?Pbdd*ovgj|xjZ}I6P?sftNbqzyO=qmU!EpbVPn#L0T$}?WS_D%n;M6Q?(<|GXH zpx84}>B)nuLCK^8+#<*6^q$M1IzlwSG3L>0^oy3hS#B!_HR#c78;}fHe*KLR_x3)! zJrMn`rE}bkLoYtMCp1ua$$L!erVfUdcEE}-$@k_s&9SN0R3wn0Z=7no%ax@Z0 zjH{W_vdO5p_W+DUDQ&!=AtVq)-*aw`?Y@22aW zKW$+5`IEx=r_NlqF9$3PQZ^EI*wsg>K+Y-TgFVVG846O*H9cFK6Kgp&7e_~bZ)-l@ zbYN*(T$=ASO&)%Go-Yx~(_}Oyi9iSDSGn1hu-^JTh~hL2u%r_$yxGJKYChEH17Trb zFBEWiANNSh2>w&e_C)6}7CH1bJBze$4^FSk`ztm_R220rHoJ;EM|#Td=;kvkJ+<5z zSSNn{=n1Eo-jE!BaI?3;-Mu}L7^)25^pd)uo3jpFCDK}KII=%pndKvZZ4SwS&t2Uo zUm7jVurViNq*;Nzd~ll@DQ}?TT09KG=73$11INd*tuF0c9yBCmVJaEUDi z=?;q#3PxOTh6RZYCn-+mdzW4i_y8R627wEaGqHKH#Q-QFSw>^Q?xAvxrH{v2W361j zD6U;KCm}|)QdYx^(^lT7MvNP+D_AZUiWE};-VFu}ecTAgWxM#XJ)#lVoJs*-quvm8 zP0Ge&fztFx-Hx03DE*@!>)xyEHO>D|*n0p-Qk84NQ@OfQhwAE_bDZhilk@D%PS~B< zoHM&DY#=QlIfw!h774BhW>lgm7g10_LB)vI@S$7-*BtNtj3B$+{LiVLMZDkl|Nj>B zRGo9`)TvWd@B5xNJgX^f+5wNd*9>kp07+Vr0z%!TsFPu ze7{@b1z+8wtV$V6*9ligC*3_tGOSa>jtR}(9i1v~zjmQ}YwKf%H<9$Ko@^vN6>#uG zVs+ZF;86{4Cg_g=h{35_vVNyww>tpv7!aLy>T{t4n+8a&BDcr=`CyjT9Ysr=W;6*d zEj)k>6c;>(TmMUjBa?$`$}9}mQUXi(K!o8{!5MpblG@Tj7C^j@1_JP9~#~RLtkKDA!NtQsrLud~WxR%mVa7hR9N`nQthrYkQPRReQo?DY8PZY#b|$u0ep!p zl&d}tGB^XHt(UU|+n=1981fo1w}-B&iKUH#?fCKQcLU2X;-A~RdvN=^zv)=avinP) zdDcjVFDzLUzpoq^n(0oj86EwEd#gXWW%gaO#+oTMVfSKbAX#)|w)I(J(+l0-fnGh5 zNV|f14*&}Um(?BW*^E$hC&G1+S{E9a_P1bf3XwamAz3aKVN_TGs&~~}j77tdL_|hT z7zeTi(-L)8UK_V5g3(@}wi`YPmWuC0gR3E2ZB`xLlvVKvcGa))27q70gn`E+>uURx zNCF$lv$_=`7J?cLtX^DR%%4pnIa%7D7CFQb#JdI%N3g%^d}PR8UNc1F5xcGFFvCMY z9U(25#Le7=5n)-rF=eL?M-Yfm)v-bo5*&{KcaH2BHn%GZFB#M(LHSHl*#Jx9q8-B& zSwk2}8$qfQphxQj27osa!iersZMGA+O!umHw?6y#Q_r3EPRhITtWBH4q1uFRT{_42 zeQ!!eTC}Q^Wi=|5fMdul!NpXa&sQWz3b2mmC63TOD+d2_0m(%!oj%*X{J}364n5v{^!T@Kx}|;B2XFn_+5i4;twL14;kp3j@&9ls zd9!wbL%IH%^icbU-~Hdt)l1h3uYL8KADLLeZ5d?Q`$Jm?zhm0^+V2rQ$o(29>Yv3k z!$gOn9R({P>U5JCQ6)0{%NyM6<3u1yK(g$F1>t~9amP{AgLmG#{_+}ZZGDC$L;ZhwdW_?6bH6ei`7M*eermwW-u zA>8)S1+n4ucmjaQiP)4~s(XV)DJ%KLFt>WDkhAq3atVoiaMxhC3T3TbUM!RBX|r)H z>G7miC@ag0vYqCf;_TQycP4Y4tCA}$PI5icW>ssd(fPSAI6yn~C0M4Xfs}I; zGbwC1!*Ci@O0;fZ1K~9MrZNFQ&{&wzxU#6o1%sVsk+oTGxX##^?AK)bEN>K4PFPgN zb4Yp@K;k7O5EuMxaY4mdsB25F&&sT6e2ivzE|?~5+cx9BH=+U%R1!r z(?@vPXaOJXTBit9g1jK6wAra*ps!!vfZ5i$INli2f!RN)-jQ3j%9=It@#N;+Mjg@Mw}I0=#nj80go zncWlmw*0z6>Cw?JKt@|GGRYzaZCmwWeS7lY@1(}IX2?YPK;ii;^oyUxjoDcKHbweek3h3 z3}~iZ_q0yvG%YrtD-uETRTF_PUInDz8Z|Q5uc2}fWM>?_H!9qs&|Ga1Qj?Iw? zTl|xkHF}4hbF&(C?5KyOxJ1;jM+KhNn>I=Db_cXfq1jyFwR$aWmw$1m&mqowwWcN~ zuTZ1miG{EhsIaq{$p_zkqdf?TNl!@bxF*RxSBM65f;$WD@}{-$*@(-Aw;JEHEkxbA zd+mr3zp@Y>a^+&v4;=r3%u`M^9spl$jwd{+i z(dq=;d-S*Z$1X4Lerr#{t-HDDd24V$@(}lv6+t9Kcl7ewKsr{MirLdR)}1i5jS!3d?~qc8<2GRsbBALa7#C{GLN^-I|{BhOOBn zia-^2UbA8P$kuJ&{m|}Rif7}B)hA(`Nw3xV6iLPoq&PuLy4nD$ZdwTG*1G)Px&*LL+0_ zY!h?gx)0bLPYxOhda!p;DwSA06@%Kj1VJu34=B29O50AY=;(5>a4dWIa^ham6(j7afv^tOiizDoX>rAIKzuaLCvGN?9y0 zYD(0jUNvlWGSHw$kvgN04mr3U`?btmB+dc|Ly^SuVeu_0TJ)2+r; z=c+1~E6ZV9BE(x2!yOb1p8^m7A)Mj`$Jx3&9|+h(yhZoBht~Sr&Zng6Be!$*{d;*`ei|O=93F_((nF+=4T{clthjXIvgV*>Yz2a5%_m#}3St7NuG{Rextu-}{*mby)=|tNk z+z?{{Hz3$dIFTHK2a^-cpj?C!K_Oul5Mz3kV40eaf(Au; zGW)|#Lq0HRKWUEc0bXiyZwv^nz`)GdqADVXaA3|6v`%DP)s7;R*w;H-4a^HSfWe}G zT;2pZ1%4T(f}CUWwlwrZ%k^d;vU%IfGKQ&gfj+DPD?A*T%vm=@3G+sl9~8O7KN;!r zT@phGZi0v+m}fk8;<8cgJT_dx9J@IhSCxn`;MB`u%bk0|siv<2IHfriWCYm3nI?xP zmm{!T3y?Fem+u za=RS~N-0?=xcA1W!Htd$z6`gY_E8q$T0_eUw-;nXzo-P<8d}^p+Bz7?FqDUWuhq(L z<|Bm`moQuo>b?H;RDQjmZNH`(h9~?$iVrvo6`P<|64cjR`6?2<3UIezGh##twLht+ zY%!WXI9wkbZ@-k4!ePAx{3mEh?6Ow1B6uJcRyaLwG5UP`&pgPT%!Yl&2#jW`(qPWw zZNVxcz9Y<+j_N@pC`N0PHLa)E_Mf`-Moyx7rS?V15L*(DxrIr^Ym2CqKkK#Ia&Uas z+85^db#}Wo!ZQiYD)kGo743;pRSidf;LNO>gIk`KurjuASUZ-rq;w{^PVfZj__#9) zy#j@_q3K}j<9Su@gI7gX%Y`k;qex?SZ9#Gl(=e8*1CE~Jz<`B5=J$A6SU3NuCy;?Y zDj+)kc^i_=CnawhY4!95&3-T#^SO*(@uiZrZ*s<90;+1n?JIqMEV+FNOWQps3p+Sb z?x9|l#xc`Bu5s#Kneh$s)Ku6xwriUHqr@hWAg+C4C$%H*FT1OhF9+8FpX~t=VPT1g zbu7NmUzd-<3FGj?S=ksHv`L1GP3)mB_2rGAOEY-QZ+l0)&*}2`+QA@RKju8@;ce8` z_Gi4^`q{j26ScX1mMeR^t6*#uc?y7GkHtoodSoqINu|2m@28Ghv%^yW3B8RzzjgBx z4pxS&OTxbP36M-#+0N>2WE-G*oZXaB4d@dP@a&ADZq^qP1$%pC4lti9Jqe3DVWkgf znx_@%afMw*uA5+nq|~l*0+V*#b7+|gRgoMW=Mv* zQg-(#ZoPFJiE*;pb=0jn1mOY8SCM(AuDip|v>&;Doe5r-_57n{AQULF4o{cOZVh=7 z6+We>G|uZd)pS0jA{Ae8hK>zsK|F9yU4tleX%KL0I(4t1@;*TfY9HktfJutA@3&4{ z*aRcaN|KSIt}oi{%Bg2FSx3rmQ^#!77o9_CT^^)L7Hi6hNC6*T1-8LGK=Dm0w$=^y zdsM+^u|%?LsFzMx>Aw^N3!^#&rzPC$tirJfkhXz*PH417OMXBa0~AZ05~wksMYp(h zwHMyr&vUY<2di)p@ay4_KR_LHE}pu>J3bxpmn?g)>!)s03!Bc3O~tQQE7S{WpDher z+ETio8t$d{1p5`HXG*v*Yj0gs(k{ox}w`6NTD}@EhW&zRbs$>pkz*jqbXY6c8gc70 zg$W{CfXAJdB*-oi*~lqGQZm&LW=kqbL>_Q>Abmq|VTvUt3ntbeBC?Q+pc~}kB_bKY zwOkgNIxG~I?hLV$FAyBu^er?&MoAqyg?-K(Hjxvt2~WOyBDK1|Z#Cy{d;Y-u)}k-y zdf3A5qc8IiY@-{X)0m>LHhEGXf8A|s8I35wlf+uqS|AzWU>@x zg#P}CtRW?vtCiN@+312@C{qJcPiahai0S93Xkc#9vo@6~p1ONV8`YT~y`~{|XROg! zXnPO2or{td0O`qY($_d_J@g0GE}of|*`7qAkYTkmG{m<4?6qsY0Fu;%ZB{$|zP073x-H;4`L)RReY)f- zm#3&ZU3=iHBy5HWAVj^-myGh(D$9OqF_k{+wcp8oeG_SCX{cJ|-u^&t;NYp_wV@Sk z$dk35Ay6N;%q3`1m_BvA<&77UOGPSVg>c|9 zIT*+kcWU}|``{{i-yc;yaMlrb{+HBFb%!+CTZzX4eZYu2-rmL?gWfQV4DA(A0%+s~ z_?D9|4vH!A>Y94NPL?hJ6m(=}@);1afR|k0j-7aatM4zF#?t!Ix&!*Q^_jhE2mbf% z?|A45%0}6qcyRFU`(AnV!*{>uzQ4Wp;di(GE0tn)wO@PwyU`~u?SJb{&E1K<*n#qU zuI(*<)(I!lYejjuJ!aczwvbYx+c$Mrb zB4Xs5UEc>~`z7*joTLQcH3zVE+(%uA`!H<|5Xt#YoCLC0cXm2|8`TQTO3i;QL>`!q z@w<+j22sNdnA$ypWdvt$5D0wr1P1}+wWA})+?aWmI_*gd)SC)6vQHKk0%!?@f1*hP zEowj|*ioU@A&KMc&NP?Y7OZv1F@S48SRlOdMsFff?#47|vA*w%={<#I5e z8o@t`%XU{pE|7?w9}EQ-g;7?1%wkEWdkb&bHdzRa23k+HV{ypxUXO=rmAuXIn&!GS zdrUWywYGU_n+MDY^=4Q@U-u2p=6bK7*SvU|1p1flS|A18hk7qmgD^6B4rwlv< zPX%DAab5Dbv{tyAv#Q|ce#X*%*5hWOB{^pGP~l-MnDYXr8~8Ls@_QU{!Qr?Kl2cG} zF#o;2>7%$N7gxlLgYzCjtgt-7TzOFk4LXfmMbwoXN??{S;XF~XjC>x z^VO8CeM9>j7K_~7Un;Ysf_Giakgu~sFc@{nSR$HKWxqp@b>FdxZPZ?E z+m8KL&{g~D0b>8?f!uSm@G;(jXgCX^sUjwQsKLHD+I24l9WN+Dg31B~X8;NRR#1;o zp8^ujbJX+HH|Q`Or}Oam8KftHiL;L02CttZ^hNZQ^xNrM>37lZryl~QJJBkA4|GLH zyYb~fRRGHgl=}dFwh+VW_})5afS^E~($Ofue9)l_rVP^2 z$rF|PX|hA8hDn4#MMy2_|BXsinM$3G$YeUFb#|BOSLYMu1>%qc;5So2)alUdl5}nU zpN5=f3N+~4BwR##a(4_W#J)kO-@n#_K#-9@?_n$xHlm{50L#5!&*m&dDJGX2%;wC}C$rZ#3%ku{>~81BtD9eJu>^Jr@V87i$XeHeij|^ z*K9{dL*j7Ag$>*_9 z&2mdqS!6a{E?Cy%nWvI+GrtY~5_K6)MJJv8<$lcb3CC^-m&Oac( zscn_qel|AhNNK-aY8`4FTKe^e{&0jj?{^>k@95QXO*gc4uL7s_-?vwiGoi_$B z?WC5D9(~o_BduoXQ@>VRYh8Bc=3^}YKugq-ZH(Pb7o&lIadq{ZAg|5)YL50I?Yw-odZk}QIbrI8aq?6^!06syAvBSR4o=PN5)!G=3T3<`fP^Vh7@Em5h3;MdymzG zkr8X0x=-@}mqv}Ax_xyy9_^o@*Jl@I_2h=h))zD9ICF{hJ>wTwjTQZRvHbw+vj-y? zS2uN4HS1sNjU3QdwsXF~G$mOb3y!4S?qcCTkF@o{7-MmknYG(0e^or8yia@1?sG(3 zlyTh=>Z{7COp!N25-@Ih*!4-2qoM8I8pQ9{*~%clXJBFgKU&bNd^!j=D3tbu%?l zqD8gRq!`Y5y;@aexU5IVvRuLKSZkE~Gmf|;ZohfXwSuFg&TYElFU?dl9B_*0K(c?- zxkIQqrEGqSTDApNO8!Xy+%82oMi^IUFzR-WX07F!P%4ov&*j*SO3)jL_Jx68&dC`I zNBukz3(JCjYF&?N>66(3kG|c}`f*W!ibv9&-d}NsJ#94vdQD|p>wqt$3(!?j{oS$f zCGd6fmlElExwpj2Wt9z>Ax+*$|3KKgtuok*+oCX=O^%IiTkX#TU4FNwjs$Wy9w^2S zH%3NA{n8F&kW+l&N@MK&Slwc?iJa;VpSrWZavU*X6FQfo4+aW}oN;Qgc^N$)=)Ys) zF8G@l27#TMZszmR@r%cU*37%)p=t%03d_Z$?VO&$Y=W+axJb27-D0QDtU8qQa|7Ec zUY$v#3IIQ_pL%cekb}CHDY&~st>5s1H6j}_ciA;xSU=r zM{>}{ZW~|w%+-7Ar}nWind;7m%C7cWA*?b7^Ujghv&nFc`fyYje7P8F`l*!?t64Q8 z)OyLGO3{HhwMNQHfd^P*@X;9Y1QG&TWg-_4{M_$BhWjBM1&^PgH^Tpc2aPr5dN8vP zZ8A>qo{k{fFv#kkh_^(*HR22bh$BTHnGRBF5PwuJ)$A~Uj>JrOxp_V^;}NdZIUOyC z7;maj-ta*%9e=J zy%&&loXv9`6%Zoyu&yX#`;TxA7;&#;%C_)49r9O2D{o89S*;Vv=%8NL<5fN`Qi5zK zhgZb~L)@sA<|p&TsfAdkJhzl(@7N3^|4=D0D|y$Cc~{ZY;2t(QRQ2>)@8x+b5;^%4 z1wMoVU-7D;h@Pb3otmo3K~V@pr`zkS01^$4&T7?zIt9=>a<1h>gJy(4M6d;c&&L)E!?E}#HB%J@ zbuz}vy=MwL7PKSj)m(JSg9t{(dDKoN4Ojmq{noJPH(c~ZbLG&Gs@>rpr4#pu9h zvG*)z(MCyPdbnJ2=ld_w7&QZ`L$u!)G{sCfr2A>!d7y{Wd*g{(wc*dN-LN`H8Co!? z?Ww9^manxx*W>Znvg#o!LhbDi2Bq|*A8GjLWP}wnti<_CJBwN*q9!9{3rR)ib>;Mw z&;aspRE#F0DOu{Nt1{SAqu>5F!4qhb_3Id|@05mW*^;37tE-_UvaGpJDJEd+90=__ zOV{W1t7}Wg6#vTg5}#)iLl^7m+M|$8(>(%3# z*>iq=;9FY2$O=Bg)h$`HBDL`CZzK6&gzC$}!P+t+USWwL1PLciCc+DAY=U6KuDI4J z2OSYajv|+xrti{Rez9DdkheWzM6x11s(ALs=`12BgX^PXaM$*`D8!8hY>1l%=simn z0tr7W$%ybA-p*$-{er{wSG&b(HEcr>`w{@(HH`&|k8QTkr}6^Q+U!PVS)LR7QV9XZ zIogJp6@*rw}qJhl^(9q$}IgGZ7B16nqFd z7Nd2^=d!^X^G$C4OV?Hox~rDg-j$eJ{l47VD1D<)hBx&@pY2aK`_HIztjFUDvv;&# zoW8jz(?PGt;-=2+;(*oCCraEnauyg}UaV5fU0wLnfB=YfWEvp0Jkr217Md(#2)zff zBPn7}?~79ViPj6YI(?DqZ{5{;XNJCDr1g#V6ZAO%BsoGCS}&iZ)l>gC*?O7MIAgZA zZ+^b7Id}3;^L>3QSN8SIGxxTA)ZLDmQ@fDT@A^|0hnV+oflK$VsQX&KrQPjS)S2z8 z+m}+;L#KJ7y^lKHx3JLHH@DCvfBR?Wpe0<>zJ~ueufmVx2IRbbsOxiG-|G4iyf{w5 zdm~3}qkd1lMsu_qtLj;L4ZRco8As`R=nrEJ{crRa>2K0MpnpRDn&fIQ7q)R?` zO$KNNkhnU`*j(e1834%#0XOP|XSk}VCitS=3B8qhXhLi)>&ifd8$gQ&+ty703ZZJo z;DF^4NOCX|Bn(~FUYaW)SBIS>$%+d6gb;U9Le)Fs0c_`B0w@^<2$RWgOa}_H0H$;} zd4QP^y8_`!q+{atfnLCjNXQqS8S3psjYpJYgj*4FM5i^lka<^e$`_Rjo-9u?f*0D`ACk(%d z=BF3DkTuLjFBa_P=rGphzi3C&2AE8<^AOUcCF#RFgY*H8j_WkQhRHACnjOw<#@3lE z+MGK;QJCrhCz%T}5;P4_9d~eY7JLhFQOp~#emk{bzGQ_BFB0;=$cV@<#%j(dmd8Xj zOOzMp>E;-ay|!}-UMcw3V5Uj!>B98s{!*yK^1T94hxCB^ed9gD7$NwWqa$|+yVdeZ zr#mqrbaAV2NE~1IR$Bwi|UH(L#<@TIARi|_< z5UvjaKRegGs`OgH?ku|-O5I-e+X@`@v=lt(Dk$UCj3BWN-s+6^^vYB8Sw8IOfM}9CoYSB}5Bug)Zi{N0y3K zX$EOc6*WAV>_?b-R^V-dTmLXjl}GsfjWHY!rcS*(kLAl>?`bz z_NV8(4$W>2>TW}9|7Ec-Y3R20Nu5J5jT&-!ya9)mekBnNr?QCIkHv%)f&J;MyWWDv^loiN&LdeF+$;&I8UaryX(%OMTpC z&1bPMvb-LTOO6>kZFeyJPA%;``gkfFO}sUnN`;wcGqjdutIk;-a09A@)Z{C|lMXGP z^0p2DTEs5p-BY%BcZUDwWO6j&n%ZTR+&$D*r`s1nN=`b&Ik!?CpO25IGfu$mKtj)5`;*10( zZX9HPcptDg6(Kg+-eA+_ z!^1AC(sv%0yH2&aHqvvdeZM4q`wIc@O-RD(b+<3}8+PCnAZu|(vonlCjfr7Vv`98D zV{k&i>GTDnT!8&hVa}hfS$Lq!G3B*-Oc_IktOyKh<38$RQgLl{5UI z$qB~kWo6}Rp1rq{39F2SMDrGZdrC=?3WD2JOxFtDYd0}n9l zh;tSI_aogrm+{yD{4p2~5BF6Af#CVL#Pe**x(+BIBIB~Bs?WJ~Khn%nLdD@ubL*+B z!j?bf*WDz|eU~3{NATH?GrS|^_4ZwllDiA_QIwv8ZAgXX6By`_R z)|`auf<=&WLb=V;2rnV^9iD`C07UOZAb>cd5W3h-J)zC!lGo($u;{}DpYTU87O-!y zXOq=+RORLsT_(c?B)y3()L}o!3`Ke|H*oT0MrhcR@F{>FJj&ycTL_OduR_>7;yIy7 zAOpb_&EQTqM&4MqnGuiq(=3j-TH*`>xDH9ezg!`9g*Nha%6NvQ+S`80No$4Hmy<}bMm>yQD}o#IBhXki1nV74@7-y4pH0Yc(0@)cHDaP+IO3B>a;Tt6bx*;Kgo z4_nbGPi%2JdPC0D=~UpB=s06>xq!SF6gBFaT&~`$$N2jIESlWYp0y0KIZE<+1DcV^ zQ@0m#veg6jNjuj&faT(_(ZbM?Q%{5Q)@cY{QXUeH~y! zM*I~$XOB|x9uc6zf-Jlxw8No#j#?M9RO`?1#P)=t)^bYJ=(P9#WD90~zDdz#xCw)#R z8}ZO|XmbAC9hcmkmcWIITfVe=U|7s3^yq4S+*`?o;9Cw&J+jR_HF6Q3jWWMD+?(K- zsBiD_vM&}3w|^Vx_w?9oI`eC#EuD4nvN=~iY8z1B5gsdU{v!45n}yv$8W>BW?r?h~ zPr9{}8?uVW>DxTgT)$SNV_$b>8UJY9 zI&4+rxrD^0W`k?D*%-h;Y;b9}NzBLK(3i(C0-fQ;LXJQmqgV&sCg!*= zcfG6YLtT#`FW2Yc%?=!au0Mi8ejNzSf1$hR0$ry2pmkgXlE7j5Lf8`Tqd!i6j{Y)p z7IPVMD|0{dIP(;2ibTdWKYNoAX>mzg&x>FKmLxZIKg(nweWyYV7PHr!n>BIlH3~}L%qW|Yv3(Jd29~LN7$ zPyfQauNSdrzN>XwJZ}iGIZo{iP7Y=_vzZY-7%GX9ng>+8$Z|Pk6tVIX){2*AYn-)5 z5J+ezzsqP@wP#oh+B#;x=;{%p6wXTo^#&yDH%LN41 zW|e*ETu9ljd7=@K>a}w@%2i7zqT&E!PXH5v-rjBVhZP>l_JC|34@44)vkZ1kqHK5A zYYPIk*uA57QsNS^C2%Wz( zz7(v5hF3N+sXg=cRdmcA3AF!2b3CKG6MiPvGkw73(ki+jo`8ejIN!%oS|Ayk5aTPI znM@@IpCL|1PJw17V)v|bqy%r>+WVX^Ik~D0pgBjL5*?~nv<97W)PqHtK?NNisI37% za1#}@6?0|gg0Q#3JFV(GyQ%dBudKFDAs=?Q>hu7{CovQeSyzrmc8w^VO*olVfG5-> z2W4lxX}}c9UiVPMYq>-iL@H;iT5x2CSjBIJrW+ozvQ0^^lI9aDvpy_+mU8`JpU)og z2FF;dr+ewn`JiCS1lR}mOu0mdz4e+u6dO;4>S2$u)g7Il%&J}kxQu!_=;X7Ecx$73 zb-!a$ix;ht0G;}Tb-K8-T-o1F*Q^MV66DaffVIW^;c!g}1XVTdj}44Njij#zm>y#H($OfgdzE@pA2DKZARP(?!bVO(K5n2A(D7tI*P~x;ZRBtN z$6@|%Cd6nNA+=Bab4Q1jZ9x=yEu89!jX zxC*Z{k!=qNIk}wV7Y#81z*~yugFz)?kJ7xD1_F^?D`rx7OjS0~&p2Eb*Tc4i6B4L9 z%#@8VFzIuekV*QyaV2iy-}9BHScf<1N@chdFU6!ll7@$RT*1=Rqq~vtCgIG;D=69D zw1lEOfH@^@NXc0MONejQY0C;3XIf=MSz=Zg1EF3PZP^1q4X zXg&gnN56uw%}oMQ$IQFsptrK3-xafJd{671su6M1fS|VnVm+uc7C{pI^z8>CKL7sp z@kYTpm8teL=eOlU9A0T)WG*ilbQBCOUK;K>t`9OJX!eTWtYJLf$BZ zXwaOF$VO>2P;dt}Hq(V@ZWB#p=E}^9>Fiogx#xFz=e*5gWJOlsST!tiFy0?bl^Xp) zz--R=n+2_A2ZCtS>-2K=E5e0UQfg=2H|iE>thqmV&QO^C%*Q$Xr1CjVrAED!KIiUx z1YWCa+E=qF?oYtmxCLJMU+wyC*UMeMBYvu2Z_8Ue%>&FAjLHFSgLNXfAGQX9$2$Dh z{3U#;!*Idgh&<*Z5(HRLy=uUuW6Fr;FI<_(>88z-#0ugf1aq&$$iVGzq0TQkpKwRQ zq7g%PdV6zU?#?j56wO8IFkL627U9QejfFXS1wcE|yrL&8B%z}gFeC?T)#=lM4x0kN zvsdBO=+qg%1%xImAW&($>*4liv%XR$faC>LppnvCxpGEEar3@lIL;}mQMAQn&Oh}$ zF9$sQ@TI9lq^}3)EuKu;nPfy^t){qh)Mab?j{GW>Dd@?)rs4;BnSdZAq@`Ig;n%g? z3RYr;Lwb}~B;;m2xBekTL)?mij8=}=Ix786)l+j#8m{fg_G1Ynkc1Z)r*4guT&-X2 zNf(cWV|NC^Ksb`eSmOchY^&fErv#Sy?DvdFJ7cRLb5u$fVqxADnri$gy&)FbF}n4AkFI2?KDj{+AF*g+ zzF(+XNIn7No@w79e3571J)t9hx7hUxWu?4`sw*HmcL?5-i_}&`=pLmmq25M3BJs@e zl%Y%@k`#HF@UnH3A&qKBFzb{<*W#k04fq{&wvJ_seAs;$%5u)igH?wcEFEm04j+^8?e`7yhA*Xk0 za9w;3xYj>A#2#L1Scr5)4U^>-8URZF^!CqNLeE^yG|u4ba$^sTaWNuenwB9_WS&l3 z0RN@xjT)lkG0($A{#i)yw#^e^T#5rh+g`0ljZU^e^P_Vye>yfJF%6)x(zHYm-ya&; zm6a&3EJj4(y31D}_>YlYm3-2k2=@S~nSJ&jJ5#%mOrzu-LL%g?0oH8`^4cOB3Wlt; zeU21cf45}U#Xg>%v~7qo+w@|oYVFl_+AiJ9dB*ayP_?q5xi#V?q5?Ep@iuiufnUKK zNGwg)`RroGm107`5%X0kAg9WjSCJjTT;KZ5zNWi(#Zo}`YmW59-l2`mM9o)!pnlGp zYIZ$8wT;^~T(5_wcJ4faOl=l{Vm6=gJyvlg2hkH6#a*4MS3l-urWHH4!(F2C&l=^Li+ zsvX$#)vY(bY`KB5Z+v*-TIEo6hi__rKGM^3^QuC0VQ-|kuuwdXnd+tu*eq&YP8aUDqxHRuFQ#1aaycHWRk}pbja|H-uXU~MI^17g<=M8`wzD`!C zI*YUyzyvg#;MWWaIsM(^dn9f6cnkv}_ktxm(N>>UXOokbNyKT+`$j}ZhYg$g=}vb} zOrOXkWx#sL5hV(a@M&Wu?B@NPC1H;olFkO|IU=%dI{c$++9!SffE02Pw6A()b&#Wq zbTSk4F79!2VV;Go%WILMe#bP#a$xhpXp@(@yR@jPDwAQ&8;@ibm9*EBw1nmMq5NIW zTvliJw;-dgp~?`b7-)H4O=l1mZ9}*(0xyB-t$OT-HaWtUpP%}qPQ_|~zjn$21xWK> zrn+|pA=c{l@C0)XKxP~bkkw&yAwLo`_o{9i$x;M zQmyJp2L9`Df9rNB;ty7`>~1NsuRv{|7z{N0w+>4mhr4m>?@=T$ddVJ$sDC}{i?;}M zX%zlHElKysRSA1&V;_g+FN2$AR_BWqy zzvtSe;|yq1TS3g#?|nF%qx~GrWiRmvn6x zKEhVIoL#xDYS&=bO8A#hN#buWAkheUshWDap0t=s6Y?gb#QX}O!AZjOk?QcJPGBRx zD$}tXXqmv0fXeS9r-oY}M$!P$iNufAAZ7av^FHSB_5#pqOUe1?Z_558X^$UGQD35d zn{5|u<-(lfEzH{U_Uz`k@1OKC=QFJ2ViGGT`VZ|Fe&70s2HaM|!k28+U$4@B*#1d- zoohnj3}<3-*wFmzR|lVd?A9Bo%YnZc_j+gpgp`9hx*Gt)79~UgU8$}`#C<#pJ*?lP zGF{7Ax6O~S^WD2mFgxMV54G<+^)1Il$G+k^bYe+ zBn^^t1ACx@9o$gTH=%n7u^`&v<@+5wR{I4R<+aDAXhNQ?n=5kV=>2NdZ=Q+4)a`>E_89}1m*4$ zFkA;^lEHKrD4lRmxpebKuRCjhihW|_>Q7Ws?O)8Zmz;6#8U8@D+$d(pdtG!~_dyDA z+5+LS)x&q&5NFD>tuHIoHRWP$-$WW-WTD1|eWkG#BjeTbAoZBwwCzf#ve6?CR<+^L zk4BRN&di15)_PNW$AOXlF*#2iPxn@;?QglolB7(I*m6>}vfztTp4cQsO-}DN3L{j# ze`h(}YK@f#E-HmwNlsVoJ?A${g`=9*+ua2wSTPMI@Ii&oF468Y=#UAUGYR~{pC!en(ao|cz+`k zpnjs|_Xi@~)v$+2)mQ7j(nu+9_*-x;5d}#fJO9?IHGlRs)Pc#7X{WGGU$arPIOgN& zzTT711RSci`GI^`iZ-f_ax|0f@4a$KM1lS zcratHgnZ@N&Fsuu0u$52+ly;@W5HSFM}rHmt&KHH14mt3O9fZ^XZ66PLhC}I{fWzZ zE~CbewYR@+M~L~Y+50~K!4G68d;T*Y>SF0Nua9u=(SPh-Tj|=I}=0?I7vNheE4Rzc*|MKu7e}32H1?oGDw!XKy(SE>l=Fm?c z{>z^qdFzYC%4~&-P*=3CY5zxlx|aJ7>OT5->zq78O~+(Qygk;t0nHB#ZP_+*7Ea&T zY;3e1^dB1Awq@j?XTM`T=mbsu;dPq(JokC{_95ME5)OR~`3mbbd=!urO#Ehi_zmv! z*SBwKf9fU*aUO$zf6pa%e|P5>ZoG?no!;BJm-5oXOYeC1!abDm?n_>7-%fAc8oFlR z$a`KVF|fD4{<3ft_heTCbbS=L!sffyb#3a}(X|J8rw$=!*d<+80oD4>uJ^!V|0rzm zQF|2f4pNZkfjwHU*2yqT*~#c6s4d0cRMWxd0sb1YhU4MqZB~LlvbD5-S=9+vxR^%6$Jg{R>5gK?B zCSL%>Cj5e!D@;s~Na2*-p$rHMdIv`}ty zhltq!+%r&zS`-rL27|;)2Isv0^Au!NHI-G%E&fNRr1yVqKvw^6J^_J>Z$9MSKQp|2 zOtovVFg*0CAfdf$a1D6goKh4vua+*C|2d-wQCPwH*}|dG z>-AfW#C~yi)^(fnol4}8us`a$S-Q~a-O26tIjEo1W4+dpt^M@1cJCSd-pr+=H|n7? zg*{R0@t@4P9gBuH{N&Y9*M-9QmA88?jEmO@=k#2+b;)U62^`a{sf^RQLUG1-t#IfQ zk|RPlHjXykM^k!^p>`(IJ%dI05@$V1zu0*DAnl|%s;q6pAG*r`O#?I_kR^7tJgSIHSQdiAJYb;X?xchp_?BwW=% zG%y+rg^{(+snv(tt`}c?G#CsrS9UkMf2>N%lris;E)aQX!oE@#r1LdKn)J*{g0dl? zisHCy!NRwL>Ftg)6Ub78ObH4vPJ0%tmP0l{TIrvWIY;|_{)>6bg|a0fs-LzhmU-Wj zL)&Y&vs(fi4CmLpw@A`eHbgjBR(MAzMB!4hFAcdb7rD3Ci9L&tVj-WtfWEg!E$hn9OXFbWYw&jxibY2clPp9tR=@vr5OFUSg8_e$&m>C zx5$rgIdtO0p<6QTPqbemm4b!*zWRz_i9}v~CBiP9`aY9?fSPar+xq@~_^`iu40*(1 zk918yN}lUl4K2V%WJEjwbm@zbk>g4ek>;MR5B~p*(kY|~_b0+Ls1=a=>QO9S{gXXj~g zW-qg0aA*T1ZRo8n{k322|MhrJX?(oYGv5B)N@1mSai=gXQg45w(M8hfdiRoOTccUb z-~a2=;NEiKrj<)urZ${?*&~m%UfJ7v;@F0bd-lw)J&a@KNqJZfTI1Ho_*wq5Hrtoy zgxU7XDQo(=!KDpDjfv6iy-M$kJrm=l()h#+F>7qC%{F08SzB_a{>#f%%(qu)@=EE{ zrRE;t}M>dwyW&TO0Qz3**%@7}w&_uP_l({6fC2G^+41|I*?VJn+wc2*+3ek!bIzPOQ_k}|=PCbZf4P!AZ-V?L$A0t7?(=qk`!-#BedJu!!TTd!|JQTzQ|CH%>3Qceci8Fe=XGxT z=9xq1?VdHy<*E5OVQE~`>jJ@Y=b;p?HMy2w52$pTJ?ilL2V`%k% zY%xBx{~g*Nq^BRKbVW|8WZ~k?7HdTStG--xgmhsFjgGeYQ=lj0PG)eyAs);&vf9k=GSdY;IRvwz~>$ z=9y?N(d%Zu7R?7j(2dD(K#M4LuR8%>K_|9GjDN3eBdYOE+#bRIE_|c*SWnU(#{d3= zJsh(~msi{HFHiq)`YB`hL-MsBjej`aJhk`L*RaPS^Laow3YpeSz-tt;j<}?y#k5V! zNXWSetSy_C5JKTxFhbsJC@0{D|C4%G$f+e{WWI|l31lpk(wdf3Hjo>QA!7$|rF^ix zPEY#mN#C5T&z|+oNu$KKm)wN&cOSyq5wN1w8nBeu3sbr{dmw`?JYmCVs zI{m3MN2OHyOZO+^IzfO;KVf=M^{{F+wFE({O6e0VH-AlyGF@C!jY`#Pm@OLWa70je*GRQ?o#wP@!Qu#F9nxy`2RTn9&W2u!2VKL?w9iS&FMkatC+kFb45h?UMu>kE{~{-Z}o$zfYVN0_oS~Dtpm^xt*MI4NM1un z(%i^YPhr~1Fg2*JAVht$kE%kwdKp;sd9Vh=glIt*Q+J3?CVG3Sx6GvF<`rPz_|R1a zeFC&QG^;kI`}E;>uwQmz6!1nvpEy^BTJj)})3KTwb92>?TY~9I4@XV1wG9^LYAt2_ zfXtW__mS)mw-{$Ei+@<=E;ssY{?)Z7g>0^zu{no>eyb~N3Y?FeXM!3j+(;o5U zkHkY^m&@nc+#s*msYZBlzP37?;w%EZsiHo?Dh7SF=}4^??z1hbUzY4{N!q>ec}Ui! z-2~C=(4`bYZY35gHLK+kr}QL-+REdRU>w}N&Xdp7AwQf5r2c_ob7gPjeyz`lx;2f& zxP};vZO9ZSO9{!9k3?)CzCM?ey;e~M0?>{3#6ouNp}8H-oXB)~!V#^Hv#F9R=b96& z&t!V<_Gz9q&LmJYl645%-1dpO*`i<%g=G&?tT7tPGO%d3ZHaNSddkj_d%Cw!vJW>v zUM4P^s~tI9IESpQbkB2-wg-{Sk+ZE_=H0g3+c?di>0c~5;{`b^UuXpgkIN4Ie;7(> zM3!2^a){YkYE)#q&Y$u`KD?J8|Du?blgSK{X)+v-rH(ae75()-o)c4UAviCH)Lxx- zTPQ|8pW_kBID->rj&$p4z0=+RhwV`9naA6LqcZdBjN*t-vz+;}4`^?QNSGr78>o)F z^yNXj?nF8}*HDwLB!RyvZj~(c1;v$HJKq=~j}WV|f_$D$*gU+gw>#>xtAM2T3ELY4 zyWQ1tQ9-FwE}MzS>cI-I3N} z4FhTx?!v^DwIS1wL-7Wr{IONfAT7+> zMIj>~3RRY4c7LRGj6;?qOYl;KuSbMe!r~4AXiDU~EO@t& z!e)2)kdhSnUM!45)damjY(itavTKW*AFtPDMWjCgb5kH!x+QyehsF&H&S@GS^xWj< z6i*#{!dh~UuxAfQt8_eirmng60t8_HDg%**_niH;h$KlsibL$`eB)}QcTLy)0s07v z;jO@8mmy7Ne8@cHQ(Fz4#CBv=J3_7^A0xMsyU1tAW8{nE8S*^&HhGo2j= z)*DL87ikXS4{0RlEAU;9KHTGGx-(FUE`gftFsXB5kPrmgpbL7a3&AN{? zCz5X z53}r7TNSM&^HxML);qqTS&e<2SG@W!(|yK`p^zH2=@}%oVlUk@+HK=%yZt*{Gk|tH zQ`#CummaR%+AV7BBkb`TdK-H0Ay?Qsr|)^(KMM|<>`-H(eY+j$hi!b`GFaE8M~fR_ z+pOpQ^>UR9ASZOjyQV(jZEqN2kugIHh1!f`yEwO-+eHrfyOHZ!1XdKN~nZ9^PV zfK&He=W-(xq`s~>>~Aj&b5a0dZhpnC-paUod;3{6&HJ2!oESoGb7T&h7hnN6rDg@M z#}()bQHMbI3B~{mAowi=_&Po{*n?!0&{KlxSvW}%`}{EUE7?H6SF{Q)pP&m~uA98b zI7QL85gK;5s-=|9(UOJq_w%F=3t-|6Mg}VH`8%9#oCAz-Zj4dl7amjm@is#{a))(C}I@ILbe0Yw2j6Sx^ca2?9STsLH0q55+#+~ zF~4qnaRqsUbPk8>)?aC_{O$weM_QB|bs`^!YDpfLKOec1V|ew$?DgVkiIe(`9}HZw zroJd|glNwi|IvYHCrOPGa+{+EX~m8bd1B%a+)ivp#mao>fcvfDIP>psuz=>UH8NqJ z_t@=g-#*hsR*tM)F_V*W4za9td7D;qX93s=k;&q)aKp8Q7|A!kR2$@g$kb9B z6Q%$7F@u_S~$D8TJDY-EB#W7Zu}(cb|GP% zJ9VVJsjZnP#uX4Y_?3cu(W=!AX&`p2 zUD`glZpns&#PUY-#sjxJa;2hk+nZaSe>$0{5BR*XTa1r&l*(I-Yk8%+6u;sg*L@wOj?+Kl zmk*p?&!1vDPv6dm8(O?8FObf{`{Ctvds*YwpB!BI7Ww9mx6WSliH$~0TTA#`pFt7~tIZ ze$9Buc*D?(?Zrjx3YJ&xmX>VP5-&2o2OP>4TLLVSza%cRjFVq70bSh`uZguH%v(cp0k`$MeNurhI z7$n6p4(q`vA_x?x_e%}_U~M57XqX>fwLy+$Q=>-(zJy#WL=bpSrartz(?W_O+Z4_3 zvAaC67Iyo*@m;%Ri90bsHlHTxmmN**a^=_FPWC1d5@1;e6hN+>P;c=z1TL`+1>C7zwBi+ec<{zvy8fDjITTEb6%sAW1pTl z$=;X(`dzBMxO%_WpZ0oPPA=uWKxuXI@xe0h^19%Moo+UK{(LqVNT=yLxYLw9-+~Tu z!pvTH2Q){Ysd*51BsAaiSAa_L0#MIiLDcdOYu-Ry-Y;t2u6Y+IC4WM;ng6UgQ}Yi< zt0IulRbU2tVMU9=j#hwPxEX-oz;`Eu@N%6&W|R4dab8ANA&Hb}foipe2Pu*kF`qs;&_Omnm`moP_}TYx+IS>|y{1!iU`zEpsm5 zZw=kT8*je)4Gexv_!PX#CbBwyt5XASIHs5hgpQfU(RUwyqaqD=!!gu(WNI3qnoblG z0SlAs|7u~zJ$qY`w-GWSwdN*8?0M)nOSSCOatE;%+3vE)Ycg5Fo2AuS+5(1Q)2>Wy z%jVZCq!s7B=)v5EGWEQ&^G`#|OE`sqptf^O&hO%*r}(d@ni?$~8$BMPEWmhLv+#t< z>NpR+$;lrbZSkL$pcbV_DCBY1ltg+My%jwDLq~!G%=+wTz3BOYzFH6`SM`!?k(!39@$Vq~MDU*b;0a#3npa zOtP+O*^?b7@&e$SU%X&s?c8;4X1JrhzqPlwwRc)G=WCracjC!gpfh{nU|HF4rk--4et z>6=-n@r5*5lE&Xt*r#Qm@ENV~H3@dTxmP{`HhEr(XY((-Ru{x52+7_-V92!Lo%5byvsOgydPxt1g9y+ zB?zg}ZEjrx#lIK-Df0Cs$SqF6SmhMR;kfnQgmofeU7g}w-%haczXD|O`r|iW zHxD6UPFtk#)82(CzB@ZJe0yrh*=Vz=zT9(<7!U2c_A6x1;Lz;8WJl9k&&5YZ+jpcm zD=Wx+>kXW!T_AE#X7tc(mCyQIxij9zHHGk3J%Z%+Nb2IUpg!wQ z?p)7V@7NYwIx*56&&7@P&GAqq+1;58hsf{zwT0R;`{BWoJ)WJ`70)G&&5~J&;a!#p6%eS_CTj^K&Wp$U(&@h? z5>J{{*goJ2tm8{bW($nJRj@Ki;2B5@h|Adlk^{GuuPqFc1*%MyuR#5+0?F7nd^ zuQ+H&X4g0h6$cXuvV-=SPLtjE^~U3*m`+zK|7!O1fw@cmATeU)F`2b6J`Zbo8Aja^ z|0jH`bd67aB=|;Dp5?O`PlbLKBpuL%1uEAOV8QP3*b;vQf-Cua$m%@ccBBt@ZK;DO z+~CMP<+Z2(78DcY4_<4$@}b8{mZnlq#^Zyz9K8|@?6P$FDf1RQl}P*z+o-@0516f< zPWY_2IjAAK6A5Fp%#%C(@SY6e{qkA6uvWboDQuAS_*TxT|7Y56EE%}z1#ai>69P54nmSX11otIKH}nIyCjc6N zL``x*DoVyDx3Lr`@%R1cL(;w9c=xFB_v57NedFU~i)(Xz>z$V!d1U_*^2ke{I(qPd z%D?T~$9?d`cH^RFD^BLY$`a<6-`nvZ6FfDixodda;m zivY-j=hJ&w%olnj5RCb8i~FcB&3c42+p(a6>hu2d|)? z?q=R*ezm=FUFAB){~7YtqiYn!VA-9!x%rzeU$Ol1#eZ9L`SRtL8!s92$YZ}~E~XZ) z3rDig_QhW4?eeDFGuCoJ)%YOSv2UH8OFd;BaeUof=<**U1 zoV?t)|MU+IALfp%y_6W=+QXE8*tUwbT|BYk*wGamrq8-y`LUyCez=W&dK%eiT~LS= z(=Dqvx=KHvm{6{XunOntm%rE}Z5+);(#4Kt8)MlY2U{kTE8}^kY@yE^KF-Dw@fU&> zR>M0W^F3l_LT;%FMo89T3PiYRRQ!Qsep`v+&z$=-ndk&yf;x5F2PoT=ZO7XOOM@x? z;HI0R)zm>$!&nU_DnX{1?3wmX1DYT-nFpDx8}Rwb9fas079TCV?Q7r#mX-c19sC* zLgxBuTp*z>To7>~8*%!UpO5_;!`KViJ?-jer@3u5mqkuav!A)pG0qB2Bl%LB66$aS z9NlDxrSrK3j$~R2ZSb$!I`i&TVnfdQG;l*L9<6tpd*;k}Nxoj{J-+I`z&bt6Rtw&U6h9zP3?lPeMZM%ww&1;> zacgJ9zwn1@#^DpW0dwq^HGd{G&`Jkr6bhvI)#M^_2;hdd64MI>p<~sTX6n_U>&lOO zsxJ|J)L2JSMKJxOCl|7ME7ecq1)#GNzG_v`+(T_%XijG$-GXRLC3b2kq?S1MxQV>5~Vc0~pxM zF;T2{IwI4PpB@ZbUezF3eF<^wRdd zP4?x}5^s~b(d+LR73vr0oiTeXZMB50!CBvH-X;|O_z;f%3KIzD!|ZDt68 z){N2-%I|g8edT_8F5%I?l^o_`ox3)_7;d@t>`1R=VRH#;$>9mc|A%-}PpgNQ8wMhB z-t9J?cDq&Rq2(ZdZ*yyTUAd`y$juwC)picF>wYH_Qw9>ns9taEI?^{R&Ee$-8_Tltw6A$6KSH#`rdD_VQMs>orcGi8 z*0n5IXPsx$2PEH6p?BMi3y*JSfQ-0VP$M^IyJo1qIUWX@dBTWdD1W({xsp-E-l2S6 z*WsgL-I5;ZYL5@KmK4@mmpA^VhkbfMUK42V?mN+uOV!JN$d;!EBJG0EU2t>(lpwgX z>ENv4yr)$a+L^|5L*8ZUDW(VerWw2~db}x^?(rw*4!MUIbQgBm{A}zBS3@r9*KB^V zb0-!&eX5(ZFgI%>Sel49WxuO2n{0H;VE5EtZ$9EkUc$<~6aLyqfUI_2F7`>iSvFNA zK<5Y#Tb$aABX1;dFxkmDPYDVo1<80dIV`xnNe-OVCA=3|+u~*(AxwS1&A_z{-Vf-U zTIDlUK8H9nCOb((#FrwzzS-A|-Fm}P_l}a#H-4+};N8I{mp>GWwMToKE?A)0o!0n| zNsg>|H#IyPS_^OO?vWwmONj5BzV$k!bgKK#)^OS8bOZl8=wun*IXeD1EL?UjbvCxy z{TBV~8)_XAC^W53O_%e&qTm=E)tel~tEV^4^|tPw_0o#5j)j{Cli{FDJpTTteia{% zO`8$wD|M=j0K3dmDNO4>&aA zF5|Va>5JM^N%osAyUfYRw=6WBX>ry;p|2NVCbh7`EMUE+$E;x@pOWo|^Rusyn6led z;tTwL!hdlE{Ob3bo{QJjd>mf&cfmL5^U!!c0og5ybj4yE+G8XV2DlPLN|`1UaZ)-< zKh7RAK?h=!se=l&I#LR565cNI&tSkQ@(nqadTnG)jzR5#@F@BxBbt0fC@DI!4xT#Z zSM}C-{v}H1;~qASLjlK1`IG7I43!8~y@3Cie*AHd_(J>=;;ww(nKhmZhbupDlYOal zy3beW_~ubh?ScCl;EcQTjT)y>!rWu`6Qs@xUl8MgA{ zZ~QCiOS?lCo(Ol|Pm2DFg^9dQ_@d?&;_l@xU*34rbyt4ksWrj!&ODGz^~9Y|^9NV| zgvm}HKl7?%xUIM{*tVfP+T;u%M|Sy!JA;xq_Y^ts(AoEY_}F7GNKemq1-C3%uy**= z%(Gv7;RU95o8L9V*_2=|jm&J{e$4oW%m1@jslof$W5%u*Uch`#Al5D*e!z9ZQ!qT4 zkE{ooug7Z^)~u{qi)`1MA%pA!Qu3wnaywdc0yF;lnj7&ZeG-`&@2$DN=5vr;ABShu zQ_xq|0KS{P1c=!%-xY*m!~rSTihb~8(cz}dw;B!h|K}ITxh447lw9~`@umCp*L&VC zfB?;U+OCm&w9QoA`R%8Ur|rcLjO}(oiLs{}`B%A+#U+xPJB-hfcxPVLj*SgdkcD0D$X!wgk@87aSVPUq{<=*QCOU%;Qi=`_CG+jvO)mHmm=LF=Nx{%+u!g`$voqXU?42 z!yTL~eDyLERO@J)O`Gr@SV7m1iGPKUTMUs+1!OyKscA=q4vo!O0a_tC#CRg>pv$;( zJ_MMxpydz?`DQO`n#oCAjAP2eU~X<@c`r{(=<~0BX^wKw)W;+OC=mauI6in(aC#Vv z;+osh{%QX1k~pi_czUUDr6fPs*lN5Pg9_jHN|{VEip3V=yU7`3)c9(V^cu@H89&%S z8jSA@kuGk2Wj}MW@;X!d!F$ZS%46KxGpFcR^nCCh_gv-c%plujJF^4s=_mXNxlsla z4%s|NU{Fs!chc#_N^&7^@7`%lFAKFA>z0t0>JkgE_3jcfG;i6087F@{Klm@V?1NHr*+4Oyi}*jc?@-T--Zx%8^-`*_amOR4muNj-v3wD)Izv8%Gx1qZEZ{cCL&amRQ=ITne|iph0yQm}cD`&#lYXjJpv=@u4? z(~c1My1`i zv|-0RnTCN;$9PvF6dCVS+6Ngm5mDF&{CHEEP=ZlS}$5tHo zZ`twC#&Jjgg*Ux+*%z<(?f>Ex-@V@F)@{zQV83hB-`m~TbaLARmD3-8ckQN2%dcoV zE*~WW%l6$&8XG$^!->XNc*d5o=9i_FGfvIbAF2Goy)6Be>sHMcOk@~Y?EVqi+Ohb8 zy&g}|T_baJrLKvwP3#_gO znw0KgE7_sk_uWS0UUwvwwa>8m>;)yKd41%!$Qx*1J+N|7<+Yc-IVYbRob%}45d}W~ zA3w2h+6e_Gg7~3iz|>r}I?sXTU`kS;PPie{$w}osB%eW6R+DB- z(hVUg3RP-Ad#-*am)?^H;b@aUQBp?b)v2y1f@fRV#Fa6HR_b^`8CR(p=p9p=|16}# zMpP-1tDdXWsiYuMoc(fB>3lB@Gv=<^7kqjtiA6NutjrQ zIvp;xiNTRJNmJXjJOPO0 zHr>KG7=KvM8+*gEo^XpD-63bm7x;Kb>kU!Kp-DrBnb^D;dxkYp8+F=w*TY;-WQI2r zi`BUY2|uXC<68Mg9)uu-4I(@c=jKAEj4r-A|eJI^5IIk$BCMHs)lM~5O zOJbs!G=HvKkZf*BE%?U*($ixZe|ceF-=f96eG84NKJ%I9yP0LnJB;@?jL+D#aptUz zZ!hfaU$nTdcOiR6Kk|ZIIndkJ-{03eaOJ?@;DB)&NRe^jOSWJzlJ@cGz)r4XL%ppt z;4yC5KNcICMvT8LT`@7S@=OCF^(`S@XFcxVNXrUIs&39=Pe}j^&zVi;9NJ}J9b@I%9df|}4V_DvmK(51 z$Q=|TktVS(F+*Hwyk@-j)^8BP(lgkMj4&PxA*vS4ttZ>kv!DHxoQgRAz!D@ zT&R@%rLplX7tNTtd6O~GyI?_o-~5Gy!OePpU|{fh+@^0}JJ+yiwFhZryiS#Dw?;;Z z8u8uG*SC66L+*-Y3l}Y0vh>Z6>hQyPn=lM}i9Q_-yyUPWV{LG1mpY<6Y1S0uuWFnl zB~7_RO<4<0nygHM9Jx>Y4*y5UK~}6OJdh*jK#E+BwFD#`Q~scTvyfk?=O_%dM5~F% zY8+adw@q+NZlgrRO23SXscG3}k_o*3R5EF#8H=m(Ocvc?hl)<<@zK2M&#IdXZoROp zP{@0kx2&nNuWL!+#me`My~f_k_wUH>eVV+Fl<^D6Zw)Un4qp9fi{1Mw@~U3PG*!O) z-hHRu?_aq#JYaJs@d)RBb;v z?M2SB+w&3|b7#)JfV2F7DMG~a|2E(B8{_9VhZ`wn7UG=0|LRefdK-qa=f2;+cWTGR zW$WUd(ooXnvWcMW%W5Bp0+=Hi9B@>JTdsV`Tp_SUQlvdJ* z4$oYgpQW4y>>kcd;on=SMWY0=daj%EdTE{PW%5|`qtotbd`;HCN;CHBeIIH6~yS`-lJ>U|PW3~`y3edL0`U~uk9zD2JB9w6xI zykXF;yjA7|eaJ_~fga6);E4Y=lIlR{4TM9q`Kah$>JmnOyw$(Stp0xpL6Ioesl&e=uOEU1bE1Ddw0rcs60oSgAIvlRY|D?zEcx29A644XvaAHWmQ>W+Xw#9{DiCMnQH$Vji1cY*MUuL6 z9c;^w+mTk;)%(T}HlAJeW8p1(@4`;eA!FHDdB3#T4=?IEEzG%OVU{HN*rqPOXwzKq z6qYj_w3}t)3Rdo#&J9x^TV#zeK5Oq?(k9weqEvoY#^Lso76YCp+_SjoYX zLBJ_vr`J zUhdI0$tCTv_Vod^aeAxo%)w6ChA~T(_Zy*;v6YG}=V3%aanu6;OmFuQCe;$OM_EN@ zI*lXt_8E*g&ujP(;7&u^@e;=C95EGB)Y7f@qW-of}3TW zP2@(JY{nCuIM~VCU7c3nk)c2`x;CK3lL1{}UAEZDCdU0?r`;j$WyS8=AjgfAY|Iws z#OYg@XS7dNM!!;I*# zD%jq+f~^B~uFe#ZOOE0FVz=LqL_N{Pk&a@PwYUVULL0e4HlE?#br7_(o&O5)KK@V<{FR_N<-;#lzvSD270S!TPV#2@dXu}^7MqN z?YYX!#>K|PRQpJ-A=fZ%MvL*|Z@*%^LGs^zg%tiYclx$%)92o_b8KMQvVpOkH{IUb z*w{uR<@KXN7fqwj$(`bl^> zeh=P_@4&C|16VpO$a793aHzhdpG=>!FHqYet{>VZiVQ%>W_nWOr};rV2y_M4p`3>< zOH3P|d5fm_OE2gmQs_2?d%Y&C8)ZZ#}y-1yAdx7jdRarPJN?x1Gt z3~2~fd!ehNT={+ufKBzaEe8TFfRX!=hf8%l*WTV{91S>tuO39&E4x;CC0XCx1C7fM zGj&Z}gcS}ySdgl+0|T^?P$sy*OXDx zQ>weStErIhDAuyxu#{vOmm>Ya0u3^ zc!f&k??jgbdxkVV>vk2{TH{Gh;&^LQo$-tZ`;E_tDkr-lfa9nEb((KnApQk$4l7~v zyQpSA>`7O_Z~g(y+g~GUuBr(z%|w`;cx&lIom{aaCt^)&3kaB(3ODA|qSK=qNrs}y zspCP&G<0mH1axlxRT}{#ld1vCS~w^m`!-%|b5G2)CB2}3=c=hWkq!WBa_SY)YHAUy zG8T0#75T~f>hP-7I0>8(Xd`nll(5jCUfR#3B9Zzt0Ml#A`0q6vi+}0WT5Z#P>oN^V z-YvP~3%d|f3ztHD*!cd}&l-1;^=H~PWK?%nji>dmdULbAkExm2PRkgR796?8x!daG zOjplA!Mk?9_FZrGU!L5$TYJtHWSGxdUkC$t@`U#ljUB6Zgn8wFD$WzPSS(8{PVek? zPkTCk_Rhx;N_#8<-JViTr1ExOvl~FVTDEn1s@4TeCcN0$ctTKIN|T~?t5%gPd95;Q z>5|!F9)Et}+?nLnW7oEAaz?$u72!5}Ag=R*JKxp*#A{?d;$b#;=SL20xZ1M2~`bl zt*vC%Hy>fI82c_9-hb&_rWPKK9BZ$1?%9xP?c9ual-i{~#gE_}WwGwmVb`jms~gy#95i@>Dn{^L;my;hoP6;Z%lyb0h5KvU znJd2V3i*Ecr=R@fn{R&d?l)SVId<%8&m2AWjPWUP{h9ULinEUlkwdiZlW)F$*RiiZ zd+g{luyx@~^Nr8(UlU?A{aE3`dxvNgw+6fbeNVnat%ykkm%!49PKtA{wzn-?!zpJ1?6>I-7n_ zxN6ULUwC=O=$1d6I%$0yEYLeuBYa9M4xZ!{pohW|va zVQyAWWZPSt;0}ubFxki4WSqWyBvjk7qCcN&zltzy;{(?l6N~5WKJegUuWat?-n)4- zE0SL@#kupWt(q@SZn{A1&DT%cwy)B+Y4iB-EURkc+fTrK)Bn|6WFwx%9 z$ozKU_Hs*G#d5*;a;VWR9WLU6q(bo2f`EBUqz44KGiE&DUVW%}75g!#~6C>}48C`ZS(1{hTt&`aMS|Ax%9JDEew<;*qAoo4ph=b7&?2wkJjQIoG4rF8%0W4>xI zOPS6VkSjn;@mxR%7vTo6Iv7%?mnxZenLP$N} z1<;q$WIa@oQ8jD98LN7ExG4JKm@|teJ*6&`U?V!PBXB3MWqN#>(-$%pD$qG58ofpw zM)&B&l#s+1w}948mST@G3}67#7KDo9W=uA%rfh`{4IXw`s>#I|Xru|s%}@mF(1>ct zag;8BMVgK15YY)jtI^CPBWQz7TnCl$CQog40#R?WHSLDpVVumPi{|sBC!@F__%%Io z>Al3DGU#4F&OD@MXamE=&aI106a!|O|%t0sH2i++I*TWn$-}e!++cldV8>8 z>hdSca383D4lW0MgIA*-BQx&{HZe*X+#x2Y!UQlIaH7Mrr%wTAGv5t*Msw7(#EVtHv$IdnZ1c@z=45&9}C6Sc2WPqDu?uxh%A{gk$Y8 zksZcQTuX(Z%ceJmZ@AGVDP0<)trn&OCqeo+Q8Me2`k3OkIU}+j>x-s)bUn&=td2QN z?+p$$E>inspCi(G_HrrUcDsvK1YnDt^&%ebI&9HRB;W~?&wzy4$_x{?36T-aExi-4 z**3aat0)Kv`*lZb2#cUqzi7!edl=;CiCC5Vw9%GKnuH}?QG2vrZ!(_VXtzs| zVCkaD>5xYdkS+0OOVE>^rCM68b?z3~_=>2y)z!e8gZExM=u%0O#hMymU4GHSAU#SB zCMD_jZV@0FfDnu&V`q7mBW|b2|J>)+09@xO+HKPv#SUYItg}|PDp~6@O7cO!OAOy5 zDMahFv?)c4qmh;2exq2e?mVLfRn1Z_+ZB(8bATO+)`&Il0y1_^>~gD?P$&@#Zpt6J zN+AsKTUz4U?sw;Csu%hS3SA^{nqtior7oZ_Zr?Vq+*(^V6x9*f?ufRZ5ZuTL`&j!mOZhU}W=pjn|;cK|mkx<~8OT;R}MRtOlJfJM`CF#GcLD zd`RD++m{0NRpIQsb(D~fy$d$uiRVSdTN`!{*Jh>#+>gZv;?u5T-w1pBDF=m%Z3$6(HwvgIa#oX&d2;hFa@o~EE~h9yaE^#Tg=$d6L2ba!5MM} zNpCbOdjRYtFe+mgkZ`Ms3s%x6mIm5auH3#L3B{I-ggJ3X+Q})%tSSPD5jywWy34f~ z*|$8UNr@e+hZA_>efgH&m>?7YrY;~BcfK4$mge|I7A~+ki+7OOW#{@Wb}qp?j4ub8 zGB(|5b@pKKCVOgSm(CovN)qo8Z6)3KIn$F;{1Uucb-UCIV39YR45NsuJ>ibAKhta; zzr?gfPv2mdtui1BRC_L&B-e#Go{=pM+;S!TU__JbR^D$5Mtf=#4y3J>2Ym2QWa=D< zNN{n^bj%^xmEL?#WcwpL$J?b^zdS^Xg?DV@t+r#Nvuh+5*PIB_N^y!9{hQ(c zw9_I6RLRLAEv{8i(H_PejZ-)PtK%hv!fAnk@RWI}gtfd-FSx9+uzSL8C%{9O--A(! zvqOoLiZ=BI{BfUkP_jg&$A!IVWMp9+{p_DQGA!?ORUU~&6u*o2P^5F&3f>(6?>+(d zco#fF9*3XQ^U&MBUh`JX@1XJcTTO-V@D6c9e;BX^O)^X&>cyV3eSbCWitsM46Z|S1nW{P1!f)I8LS>~+agl6IyL8*2@&WoN@r6LEp3^Y&TKfZ*HN?-1^0c0JjMw2Dd|*n<2T7L)79n3iKh#UW+6#Ak_{~ z7BVi%iYP`svdTId1RmI8Ks6IYCCY`-P|yVXu%xTeX5$AP#w{aPhFV-h4re0b?}I!h zadFW$+B-IM_wZQ6aSDBX_$D8a{eY1ne z$8DDhwsb7kVg*CpWo56s+`{j(iIH_7NjNC7{Ef4aBMK?6^J$x)hILugVs=3XngoMe zio|Y;G)H%OK?ZHAtjhu~sA9pvIwBE9Vx_3VZt>$jF!nKb&g%#oJ;uS&TN9Di=8`Mi z-INN4eYMk$Dc;KpjUln+`0oCvcdnZ7@Vu0DFYS&7@(2e+HnEr(zW5o^Ap8FfY2PG! zm)~Y_7c7d0;UtDzBv>uTCc{YAYZiwnE`i52V_~$4stO{z26$GEAs(76fGW?$U6go% zKg?MH&pOUaoK+P0-Sn?mYnSb8T$WLtM4@D5SGpSm@nYXXyJeJ=^O3H$XzxrdPt+%8jSVy`ofWgk^O7=lH`EpVw>~S6g${BSdFbXtTr;^`Q%mw@Kk*;K zlROl4B;sX@w*NZ$uqYpn+7F7>LsdMy2gT1LmjDZ2V;^uYsAhKzYv5I&aC_htUNgD& z23#uyKICwQ&K8?gE9pogjjKb%9?Bsc(+VAW%WC=m+&*4y41H<5r40*uILjsD<+gYm z-GsO4M+%VyrPwBZ{(}d_C2hv9ESq(nEDu!vLyB05wCED)Mrl`oED_vBjWr@GmcFgl zM>o3#vLsOXv)N?D<&tqaK$Z(`e$Z{a48J`=Hp(vJ+gKmxB*9{|$%65cjnwm2qr+)@ zi<3ysq8bm1i$DA!-*EPzF`M@=5evs=&ps@xhEATeu!qhbw-}>t2V>(bD8H9D?FLJp zq7{dX8G_@y_I~mO6rcV{-W`hw3in4|J_EigTgWa!H1^q$j_p!TGLCo!#ln(ZHlVaq zd;2?xBYU*w%ZT`&4lkW$WH-EYt|gx$50jV4_sNgRzmeaNKhw1~rC=r{q|_a`%&G+5 z6yvdtb)H#7i6^M;UF4;fU%|rh1+# zkYctC3kWbm3dP~US+Q#_cP1CIlc%J4Vym|WIp6+SRVw0`YB>%w2bIK7=-@Flp^2_# zC|VRPfNIe+o||jD>h+uBLOdg__DJ_Pil0F`KEx1(5 zdg$UA*PNvYSkweuy)`1v>MdhQZpr`{G){uQ)Cy0d*m0X_WMx@=r`D;BdYTqE*DdMt z_v^RlCEnrCe6>iy=EN*=A^B#=?QWhed-zyqpmzACZceHl?NoL5m<(NJeH32wEEm3d zz$Q7NEwyZw?y+obh=p5G#7gfrkB;4O?V?-J)6Jhj!Jk_^r{TAT$_UYjA$}ggpvz0Y;0p9>cPc9r_5V4X>`@|t$d0T$Tcp%JJ>c8^6i|B zj%culu!?ipQmO)|sG=X#vcn~%+?t^A6!8pi=9HOQo9UUDQMGoJ8#F;H5*ap{tq zjx@F(Cp91~MTY#^Jx=acxSmF``#rk@Hv#m|bD75jJFRuKo|u4V znuVXW>6I2f7mzcO%OyE>$@cC!(CRK+!C2#MrDA`r%8>hA9s$vTfC!%NzqS&1w891fe!-5D(Ryd_v_1=;;((4{PmMr$+k;{9TLu>RlOgH4eX z-{*EA^=vK`QqlxjM{DIE*AwnC77MJE+{Tbyf4P)A$qKG9fd``m`H?t?CmYKbF4B1E`g zVKg1zvNLYu+n(^hzd*ib%m?>Hqax)NmY^jEZ1Q0R-xa5@FOVoJ7 zkeziA9ttU^a#D5P6*j(ND8K; z#ZJp!x3gu<%dTXqPGz&C!&c|WdF%z3#TVu+$&jMk{i5g#>w2EEcoctSr996o;e2IhLV#bsM+QAbo?OPd`UXK|m?a?-6-o(~wujaj*D zzlT?$qTFjqmsrbQQDnv%9m3lBUQx`-bC0&nj5n#yr05>HOjb6mAnbb4;;^c*SkWh0 z*U$gEWgo+n_?&_DFVA9+*#XW@u9FqS}xcl#8RtCT69{jkxQ`6eqZPpc4^uQi{{Zf5=TX1kJx~t2xSJvh1d;z z`FmjVng%S0g@_JXgDA1BK(pEl)Vq(>d;(En$l5bGud9<0J4thzfh@r)N9P)Xfaui4 z4+Itb1y#hvtM2RGI8SjcpnC&98M{p$7#e;TaaGaPn6x+`E)7yIb>T0{U24Q0Fvz}eW{H$rXCCA7{ z4Q#RY%G>n|z0B2>%Y}ez(?AzXwizD_Ud=m6<+SkTo9xe(nn}fH`yne z3U}3#PbZHbL-eM9=rW`40e?ibi|*0+7cz?RdUwx~%1>hh*6xng#_Ls#yd(ThL9bba zb=@8C)%n+&m+)+>w3>>eAAzZWTMlk9C8d;No9u@!6exF_q_9-aUwxqT*_aOYW%h#yjn8B`9;Jvn^RRyseIQVo=QB?Y#9TaKlSk1bz3Emr^fw-m za)h#CF)=j~Fb4QS@;fDW!_ve^;my(+HumWs`u$;-2O3ZJR$DCNP~3LQ&629h-c=K8 zr}K)UN>3<3&SJBPwvZYO6DTBEOI|VBwydu04TOAfVG-qYJ;X3~2|Cn13(^xk_D5CjB46h#q4v98z~_TJa-Ywx|U zZPis*cUQ<9zu&o&p!xeOj- z^}xLA24eFjX5;+5=bYe@qH>N?);Tj6rXqTi*^!>|<$jg!U4M-DU?c+=HED(giNDaX z88ab^Wk5Y<>S{Ej%Iz~ae(+Wlw!OS?gm@rNXUTC zU}@wFD4MQSE+?->ZyBuwqbmKa2~mDVk<}zu$8Nw9L-bw`oQWF+XgW%i~{qaJ|_JC7QOScVSi` z9ANGG-lyr+2bynNb1E_4Mk3!dx*hhZyWxjq;*@%YfBi09w$17~7Fjoq64^4(0krgk7mf1N9;Y`tW$%WR|PTcbWDQTDpZ`%DAfz54#{Uh+hC zyl=qNJupDuifO94)^XLUe!8;1BeR?==^Yrj9#@|4@6Jlx?5ybSeP(B6bXQgk1?LdKsvaIhwI>EW{ML$|mpJGyVluILEm+!QFmH61EC`|N>vynb5h zk^`>n?OCKVyCS=|o2UnzrQJ{VdkedX7Ekj(z1`j9xxwynE)85`*BfiH$hG|)-79(n zdEI1je|I;1ue-ND>$`s3Mc>od1*MX_9SLf!3HnU0+HMMyzJ71^3{HKx+v_vO9B1@4 zBwZ@^l;-9s6Dx)8j)}ZxPH`+>tIO8dOr~J1&L;3huJvMCNM(`CT3c4) zjACnW+Id&sbsNxJA0OVcVI$o&!)WI?g%CP*x85PZ`Zch2{lKJEtA!5}ZbxqKpA6Zq z8fWRbnn+Mx*`G^t68>&_zQ)<0vy;Q3(yyeib!V3M-`>&Pds8>SFyG5A>+Zj$qobcZ zl`yVPJu^dD-%U1jbUVwR>F(~=bfei8cXYcFnk?3Wi#Ho`4wRT7hJQtuE`k7rjPaZb`w=E-rnEI4X)5IS#R)A_TYV|?JXRzgTk?HWr*g+1G!$8+O3S7-6SRUI9 z1R2Xy8iEAmc@DG%^CnV&U>!k-K^#sI@+mgtSV?xCQ6OwFqskMkJg>4n#%HnlQ~uyE zy(c?jpbWg$*p2dMbSC{hv9mn6N)X&|*J!2kyvkTH`N_a!rR6jmgMIk7fp4{e=zyM_ zkXJ#yPzxv+qjJnAZ|qysQ#L9dlYfEtz`QCXFn*&?n-&3W$j^%$dGeF6*`GOtphWty zv6k&ExFEyx$fXcIoEBdJ&z_#zLB?Q;k$(kb#VE{stOO_&`#mfwu$~b3%Cd)(Yh@PDM2=ke3q+)o~S6HMd6?78~x-LSES#4Tg9d!UmTP(60L*;~AqSwxX>an!VoTurtNZYrn zGbLW{Y*JgeWj4`_2X>gOFqN1%fe2F##yNtc!0Y|9CAX0NDvF(DpfGA>^emB%sI$xfj zyrpyAL-bIU>9zc|^&?mAq2DjrudmQ+t$7!RZKgR|ark|1{kzAmvl31WhWtcns!%He zq-S~SNrDh)70Ap+OEBB7S7)nVHmEH@xFKKQaKPxUFbP4tlRHxD9EDa}W^Sn>R$s5w zs%(yXG%EdVVnlVBTD#d|bs5y=kjmvTtL+*M@~r?GKU%-bKHJ6Rl&Z~UJ6WI-47}N= z3Td6WqM&fQLVC$)?dJkb#vHQ#mE10aS7nJhm4++UM!g7)<0nlv+6%2Y*#+%f)9%C^ z&KT+SM}?By)>s*5ud3IQtF#j&Zb^5&BrK7{sHm^@Zvxw=Q*P5M7Fx;6ib%)iP>XTU ztl3U9YJ-aYOQBn-G*3{D6Or9Q(nbokf+Xe@s!Ta>4r!?_@Oew^c@gvjgzqYJR%dp? z`+^`4qdB6rduy{w=1sMAcn{jlTr4+h`t38ciqNcqpwgo>bQc#{nmVQ{JwB~Aq(n+! zizkadtG1jjAP59F%PKwhZqvw-Gw2TswUIo5&s7OS?{X)-{hE+3>75FNrW6)8ZI*{F z?CP2;c1e=nOP(wXA^uq~S-(^iXPa|rdzKaY0m;mGKIZ>_m3R|;{i+ZHI297&Zt!=P zXWRk{_>-`KzXB`x2hg2;!#w}N(Qz!aaumzhM?#>WMI^?wXHuF{rD=de0^N}?d|43T zi5*#W7Kw6f+~R`~D+f);d6h8yVcD>!ot+ZFv60URDP!l-{0pvQWV5K`gv7LB^7%Aj zm5DTHgcTGR3=<<54$u#jku_$ZIrszvGF|{Kf+nVSQ7BEai6Uy^BT)xD-B?-45h(Sh z(QiO)7;$pgg8(amD8qO`h_UPpc1rRI@Ne-Y1oxl`$GTkKr?7_)V)(}_m5a15+ z(^wQ65dUxbQ+7-{Z|ksHIaZq6%bl$ zr)9tA_H-xpUO?+0fi0Gzc&gcr;H3E#_wV2i1-b1 z2zn5ST7q%RvK#bgOqO#svlyOz&J#KY_gTs+e=wGD{R0p#dG$7jjOzzcy3!rd61sF>hB_zyCI zz%s$0iW3EJRc4$J)A@U47>G{hoh;XqgU)0;*(E4e;4=cUGLsrb6gSy zl~UM!Asw+O8@Ee2F8bFETB)iKNNkdF>=Zg z*LV3pD4_dwe-C5LU%vgCkfN`@`OfD3^80TD@h8HK-0*G54?lmc(b*Na_QRK=_?__2 z?|7r_oBUMn-?cmsD9JBGEitIS&SC!RGZCM)M0^HO+M5wk%Wzz0WW9&h>I zZ2?Se$3ndL({fpietBza%<2*q%5a=~kqDOgG(JoIj%c(lUS57BPjcSayobJZ_~Gw)vxXmgFYF}R8yGn(nJycEvg0!5MR|^qhon3# ze}C(C>BuZCT@ON5b3VO0P@CJND)MDpmGmyV3^aHnH&Lw#3H z^U3Bym$0O~t)slG3ju->Sl1lI ziI?$8Ukqq*megfx8UJRz zTu0{q?PRex);g}NY+NgS5EMQPw z@`YKh!u-^1t5_HeXv z#CXTROjAnMW!P|++e0n@-(;qDWuqDrCJ)9dDMiyCWEYY@Nk{sFYy7Devn9Jc7|by% z^p3Is7hKjd?v8C+@962;==SDo^7D$+fckSfox@ryDJ#g&$tkGmJk5KsI>An;4(y2-RM5bAVXYdAGYDpOAG?NVwLi3X z%#JKFexDb_5AHNDhyY9r)dJz59B%RwzDa%yVE52F0hyH+dmVD;G!SF@DkjwkvS-I- zVs10ad?<^ei=sGicEBJqc9}&;z%M4u9!Y+|U`6pz5vIqJJlk>}-!@c3a!Mkxc)+Tm?a-zqVKnOv@RC_Fw_o}f`d+KJW6HzdJWdRw};u@TJV7<|$-j`}OH>d*)9 z=22_<15$}Y8Cr$)G8=>voIzhiSy?lJLv$y6lv#$-os%aAyvW@dOwR9uDaZU)wS(7W zPFklnO;!<&^nP|-UT!p6UmhxYw1U@KE3Dn+>e|BQJqQHws$I*n9uf*RZi(okd0s9j zZ+Jm6pavpsVXLFCc%gHH<7S6>PzO9thtis(Py*h}&Uq~!pVF-M98P28ucn zPg1Qi>eQu5oxx2^4(?D(`Am2SRt&2+yRXyO#_~&Mf`>j=dRQDm*6k(0(0m!#ERU1# zITcsWwR01=$=oc&XszZ>=5}-YxC?-ya6NY$cONHvjYtY!A#tPjHVpA}S77f)rp@f7 znb{jfk5kHe&Wm@4_U%a6#l(BN1_zJlRX7U8QMeM8+$%D`lV0FkH(ryeu6+c5DM-$ z%-$h?W2e&F61m79FMy<`@0AH~dN|-2)1Qn{9VV`geUO4? zY?tw0kio|apLilXS9-dY$Cm7&&Kd#FCzoYUEjJCg88CwIzO>2wHnT**n3}!`Yw+#t z9#*{r!POY&@QQ`cC(gtS%&{;H4^xh*9HHVk#&m=0vI&=Y{EW!}$T5cE(IksQMT+AX zqSYXc$;EIZwrE*a1ER_h>dImUV+{I+eGiI0%qy5~`Ry*ZfUmk~BOonF|b_V6n!dYN9_}oy^jmRLEha&~rsXvXX*AHpI`}}oE@I=(>^>j(L zD53Rv#6XP!zV)0YE9^Isu;?n(WM(S^FtL^6I(j* zDxFIM>+g~Va`H0enkucu9XAKzXVY(RBH*lUy;5%qv@iB$!pSXAf94LOcgw`heX5ZQ zN@_~WVzc(RrvOvem^1ZApc$bDU6OT{C4BNFZnd`aT&@3=Es{wPlxB%jYIuSmC2KZs z@S39fc&TFz*RL~c092#V1{H8;vAR8i3IS++A-zxC;L=F$xDxJ_b`w|Wm)x3E*ktiV zO=d5BQnV^XrB6|2HQ6;nwN=jv?gEulGQnG4ZPRG7I-|(xsg{b2qFP}SgzdG%chEI~ z1@zU6troYCC|2s=Iq%nW`Ln0Kvf^lT{FY0@oy2faY21@7YRy_HTNf*J)lF5+Qkr{I zeyF+?N+JP_0jWL}s#!E;@Y@dLvXzcemIp{lTB0=^8g}J7aA0Lo2JaDw24BY5g9pkYE3YIMj~Hw zmKuzbAy1_aiej18th6g~wGympifXN>gvvBv5=4jKYUUjcHnSll>hc4;74cRzwoIkq zQEJ>4r6s7+di5qmFB!EyFZ8S$wK|%kR_3dlKKrv-kL#^5PHcgYm8?!6t}Z93>m%pGQjXsZ#!1*QV%MfndjRr;G1 znrug&R;LrcunHO|P(pU2E;^s{2$I%1H7FtL7OCdp>Fe%QU>qWRu3BT^ki;CBTi!B- zeHJXQYa9p{^pC^%MoO*bASc0ldA!!ii9&R~#%xd-#d5q7r9qJm7C~jS@&K(vE|%X^ zl0ke5I<8YPF3xxWS@-^+bJ-XHsnRWOEw4|?mc{^2^l8?^S1AIQAyf#gX~GAKBg zCC}N?Mt?YE(oNSDtyndXAKzt<6qWSq>W*K#`R3ctda7%F#NRidbm|hi5}!Hbt&NO# zmY;NG(G0qIP$3{qR&RTk&|Gy&#g&yCi1krxm)X7Yls)b4Ywmjfsd-x0M2%MMTvu)A zZrV|F#b1|=oTReM>aBh(_4Sr52Pc-!%bh#u?>pt=-m6lw?@&h7~l z0M6)FjuIN*S31FIvQ^aHxZov6%GlewyecU(PP ztgByNG$&lYQtk8W6%FI=zvH%O&%>_<24@-zdexFmRXcg`S>o~3B2%|farS**u3Niz z>G+Ef5VCh;-h%Z{_v}wLS5`DleV#tB|0U}=wUJQB;wv*q_QHX_gL^NUa>4%9?dO!F zqI$1OZ}c-BUd-_R_Nuf@I1dhES}6tq|YK? zfZdX2rlk3q9{NhIW0E6lN|q^`+gm&Gkkk-a>x-V@_wCE6Ec}cg-@DaZgH)6jk0Zz9 z5lzI?QWy3vb9r~wSS!0?w`B@+l>1>=`E83_5mxgD44WLg*88^_k75(B}&{#>(RT+T!m4t2vnHb_Rgh~O=-h?e2 z(}GB^0FI5tOtB5SE;G2ya}vRmlHbCj-IL(6@IulwiP0iOe?>*h;>E{17B603Q6bzr zo<7ys7{V`jYJ5Y;=5ldwx?HwU!*i)=H|FJClb2WS3B+OnkJlfI`Mtv4=jprTG`a=L zo6m1beKInmwUz5n9c^vpk8s``t2Y+&T65^9ZEdp>g|S#+BA!!N2sJYFOgGS5ghS$A z@tYc1FaN!ljN(i%{1YT-N9U;}a1jVH))a&kg6o*Uj`79vIYe~Xxw*X+6}`Qc72~;B zfo0{M$*_~gwC$eaghHvLYhBKn0WyUb)=b#+V{A@pEz%Y1ivoEZpBoe+6+Jx_6lWSC3!AXn7T8!BN*d&K}()j#EsTGMHaa$D>D?+#v1w5cMkTB ze!D@+6Pu9>VHa#0*T7c!O2#`U<^v{)V4S9jGE^#zzO#2?h2aicW~-D7urq9rc|x*j z0~9}f4+u4MkodK7D3Bd8QCf08|MYU+pGygcJlcC)upA*-9sBHYildAmD`r=o}6@QXI0nq{IGlW zCAoZk>%_HnMx~>}VKS?xlus%wZml->>gFxnC!T$Z=<|QYb#EHqIik*XxXRACaNVjw ztvjB)(YI{-87s;SSxSX@QQslIGw#l};e4+pO@vTeETIb!V$|+49v5LuRWn+#CpGf&pKRK#EG}|8rmgSnbv-qhPeA?jF~- zY|26lzinD)FZaCNZqGn&jT?S@RdpFTo}q+YZ3vN)^X0r6n={TrPL>;C6MsA7W1wk$ z5A1CU{JS8-z9SRhKeURhM@0A*vXg24)7`?(cF+O9JqE)`%qXGsi~_wZi{9Yvc-^4e zY50D*|43LPz5@a(d*h($=@K|jI*@!IDlun8+$upu$ZUD6Jae;-o^bnPmU4V$Ij(uR zB$U2z4~&N8UhiOCI*dCZ{=p<2gJv|z>J!T)REe&Pd)N{UCWBZ>{`A+gN2n%ASnq6tSjUW=&g8K=sy`^up=ht14AL$3}ewGR2sHe6xUgLT14Tc+XWAf zCt~6;g8ihZ0}qa4NT(6FSfzK^7OfbS2Pxo$CA5xpw4fc$5KpoigGn*!2*aL^0?ah8 z(&!f+3iVXa963+b63-OK-4D1e#=3A)!?R^{{; zMpj5_H!>;aCa2A!Q@Hu@f-;*;EYvnhzZy2@+3A&jQdDxrkW?^ELtnQ6VPTRzlNd7{ zo~h;^$915x)Y-z(@3M zuLN^H((NF}tIi48rYtnk?SXf+vrh75>y8J_Dx*Oa)Nl$>DROFW+~v*rMTx(#+Uyd) zbsLXo1I^Qpv<4bcb1-1dY288~$8ci%Fx>DDvkE9LeSNnWgbED*fFW$^yS z-vr{R5{iUEp?+sVOuS&pQHnZ)wP%)UMdo$$s-uyb$`^BZgO*dNd?r1FEQ?Z^87(xK z9hzdn*5lOblxEQ=S~)}z5{-p-X9YrKd(A|Ww0aX@VNI6G9QgGRpOk=nDQRrky09x5 z+iWty`kf#NsCOGTaG={R#w)=7VaBB**J=8=?j&=?Cvm6cPe1)WwSCf&0H zdYQ^>>$7PTBT8{31}RQaU?EfVYSWwzsXghs+O}A#HrvvtZ0McjM|Kjj0wn5x)OHne?wFS1yCS&mh7mQ7O=3N zM1UBm4m-vXP*Cx@cyjA=ib`T^qus%y3@9t(=usQkNvcb>ylGfS6y-O_ARGpW%;xp1&cZx ziHVz~v0IG`=3H^-vK2#yy)*pu)=#dudgQC`=<`C}(OGWpBkMEIys>WUjO_%8+5NTC z=wE-Nbf+6BfFpl>=Y{UthI5?WyPkP{cF#?>y<5S}q)*a%q=%};=P_D;*1=6#-=9)u zJcrDgRc%$qwrto{U%GAUrcX;bi}Jt=jWvf!M(XT_Izeb_zWe5DmQT6)wuh@)BEwzd z{zc!Zd-7TSzDjz^u@!&Lc{o*7R&JiN==|z4zu&fY`Q_ZEQ$LT#asm(SKi%JQKv=lq zif2yQE)~k}X#Q^|#e3+ZPHg8wv@Qi5&kaax9$+DJYI%>xKjQV4; zE(=Nwl0CX8h$G|oGmJb-cnxUt_eR#<si^FqhcqOAE3WMi*u;4zJMZR%+|l8g%P);$@x3*L@J! zzi{5r8BOyb#5iugiIN}de|>s$WB%%TF}oTp1T{faieQszrkZ1Yp20JkxF(TVzB_*-;f~wTq zDsIMw7hG^*>Y_?Rm9i=yiyljEl~O)#(bLK#84<7lwak8>T2WK_>$xG=+$^Dm0Shv! z2Ew zP4pLde(o)8DX5*jl^E%KeOcua;j(o38L4l-sWHztT(a%JqK!|VeaXtj7u-Oz02-gV zucU7A0kSzpgJixz#x@l)GUzyv&}Z;I@{pro5uVm>l65iYaBMiUN5m73J!uSVkIY)j z?*dGbOb8e$P4i&vM=?s0U_Xm}Mr(lGjVaCLN<|b#jK30Ik25Q>a*(`hTc~k)WZvP~Hg4&WfBh$G!+iEM+w@z-@wrX;P zPv>%k8-cLoUKl>=vl#=vz2U&IpCVhmVnrz($KP9It1I*!oOaLo<2b=+ym{GMN4YH~ zljzL!G|=s4d&z05+AEaAVd14Rr@wUXyzABzQWNf~LDGn5Z^I%&GU4_V{G`NRDk&Jf z<=59>s+kS@qR+`G|2j#B&26V+`4G zdk7g#3+RRPS)O}#{^WT?_a%Lu#OUj14ie$Id;fM0aeg-P<5!h;esal`zb@hV=@m^c zerGe$6{#Q1ocY+LdAB6=F))9iJ&kN~ax$)P#936GiQ?@b7ox^7XyzmDsm zGZkScyok^%)y6<8DQsK#XCQ4gm{v7xEIb z$;lj8q8s!N#@45&%XsXB76TqAh|^@Tn(TuV4{SkclRq4nl77Td`E7`6N=8vFj^Xdh z_7VDN0iaCOCi{3R(^D1k4xNxzm{8Dj^MB5R`Sd9vt#A(e~Og1lvh{Bo2!;st|uk`x|Y8;)x-*yoR1i~t*q6{!x3N@l!qBZBdeC}$hjAbLwLeM9LT5PNFw_)+e%TWn-~}) z^7O@HCI_?eM{U@_1)L;~q$!4v--JjCRthI> zzH1I$#bp$~@(3~Bc|EP^s!Hu){1CUXiaapu>C-mTdy@GB_ifm4hw0Yy&YqA z%jtmw@0Nvj(j1~=KJDptzcnKZGCiB&(!5L@Bcm4DuU2_{Oh8=pg}3LD zi`H-b2p$WmmrAT9+3H6#>U3=2as{-dU7@Wkxu>T z5Xt-L_;hkbay2jg^>xy^I!Ugg&(YTo(0`<2jf3wJ1Gjzfb$V>#?Q?d}1y_@vk(HbMm)ZahyH@3%?mulp{m)Jg@ zeU$HmE?xr}qyzKWzjLV!WelDZvUJEc@LFeiCm47lV9eN#%@ivGhc2IC{^@>%+A(1p z_k~aF@wNAE-+nJ!gvbAtv2tNo&nHbiw=U}L{-nA4$#6@3zklV+3;+KXHA^7{f71?lZsJ$+4E`8YFi4CW)aZ3wS zJ(ZOzlY*_pg7JchWC>z^u_+6|TYP5m<&&Fp?6uj|I-7rPW0{X{Yo9x(z3m+Iso(ix zCu9kEA}U zCOVC%uLj7FnvQ$x@8>rEp724;2AWtozdVodBqUs&MBMp%#egy(2@cwNq|JI3>2ydS_17#mu z3w|Gq;(*!V_jL%ltl;N*Vg-gg?!X-m1cdn6+UH z!NxendQxUXK|g>_OyE=?S;bhK2VzO!(8?p)Q{H#TYdJdK!n!ktB9G+SkSCcvUPL{~ zP#Dstg@wbj`b{zyIx3jq4mWZdGcB1MwIcr<{08=rY)zucFgPiZ+32t(KptdYf<$yl zWyWo>4o6F2QF=K-Uu>$VYDh2iH~KJrkN%~(sjaTMv#qYViN1KGRg)V{FWvsDdY37Q@=c-~{Qjy_pg)=Eyl{@%pW!>M!3v1(sV387C}*EV{M&Xbp%e{kvM z>?I={86-orlRi(I4lVHAcHMzY;ik&IacAs$Wa&xNnuB4MJ0FJ zGxs&#N1q*e$otrvWZqYQ`Rr@td?@OCaQF2Sxm&nRi!Z$HHMJvAQm1#XcDt6l^r}d4 zN%HuGt8?$1P|?`lT|2J1yS7mrDvQwHgc=q7preCcUn3;RgYt>!wqeUTGyS${7Ji~iqHl0y@x&7PPjfh+ zCAQMRO;wR-jfUhHT=WY&{X2m@nieQRo*$s})&0PSV*JO=kWW|IvzPcD8@7d~1s|Jg z3(xX#JqY>?3wfUWq!3o6HkGXa#n%W4%8M~2nb3)YD)^NJ zO%@eNGl6~+!?%$>e39;1WETHHQ@oos9(w~Ofu;PQu(Ox;0S_-$IFqTfsG0#PBRgh=}TDw zS;oM5+1e>#0x88+M^uD%BPbUR0#FP0u<0Zi$4&5~gvnG^x7v|F58>``V)uhGuxlno zT1_o~BMa4Lx)cxwHo_d~b*C||%72UH`hA0Ij9X+s4=pP1*jbCpt;#e| zq8|wu6Ku*>swR~va@++r4m~HEPnew&ajfd}>kj*>^G*J)DJ5Li%+sB2ty!?cD^emH z{d}@O?7){*0!`bY;JGX~?IDAjkrVkWgHj7(3R*Z^Nly#H@?dM?`*yVD7v$D8O;p)j zYTc%Xx~f9ewGFXCRrGHYiT2`s77}}OKq2m+cdcxXWG)GJt<3!hws| z>(%hUH1QT&row79IMfO&Y*@UK*9sAzHoz$y$O>z87*R(+WuO!2CvGbVtH@>aLlStS zWF{1^CT-GC<2T#9qA_Z%DVRJtzc68|+NrI{EgCRO^SeClDu2Tg<4)z>CF*RxL|^RJ zyB<>ML_-#&gT^YiP7J$Uvv@;w7UJg%eE|NE)EwUzRrxud+^XKz+uW01P*y)QX&NsK zEY9gbF79IYc#B_Ac~KRwaEUXA8hVYt+<)qIi3yfYvnXV>loh%S`Ng)pcyp|zfFAp{ z{BL&4r%uaD_wA~kxuKSRMBjEVxMt+z8w=}rr@wlfKi{KqRYh&p|=}qj~U#Ujf9hjqtcT6`mGn06FYH#s%=c zyBtvu*UA?0J2LJ8t$G9qKu^Pa>Lp+Ry_J!n!^NSPJ2zIEZU6T^ zE5pu#HpWCsCmsKd)AN^|of7DNZiF1Ar}mFLeL9yvp042kuFckFPe;B)=X70`F6(}e z$zvKj)MnpbtoO{4FA`m*E{g_d&n7{m*JVKwoh$X#q)FU*b{h@Ooy#s5y{QK$Pv)-B zJ9AUt3=JKcJD2tk4sx#;y?S@*(aDqPS2JdiD~E>YSA&D(D6W2lb6?M#NsgQ-eD>tY zdr@G<%$a8n4&s1a#@?ATXDvszqxY~O3IpK*pb84kos{dT3k zQ)&8EGW1CXms#pGOJ6HZeMQIez3*N#A25xAo8uCXk`=Bey7xtjL!=yT()Xgk3#U+qr8% z-q6o`$ixBqVJF)eoRHqx#g2O9Tsso_@nw?l-}+17AnWj)<8 zxw&m(G{0k9VebFyXYwP3^Co*{3>4mIvjG?_Uu&>ved_RDX<=f;g?(Ko#g-S$nmn8p z&7a&nYg$`Zyt*dA+QX0cvr2jn{$>`)HBR7Vm*oa2o(FEssmk3NXb0a;r)H5`L8U$fkq;Z_@LB z5u-68GD@vviXj(bdnPQh4jo&lTu;ym`6l_=?=)wOhDgs*jxGuYbA#4?3w{46Jx=~2 z9yoa1O0N{2Cy&x~la3p1c~r{{G@8i@y~`H@^|xx4kS{c;zt)k9_!kL?MD{F$OYfcq zP$}0FE~~*mPwzIkW@%iy>?e#qqYsA;E3RrYsb~It;y3zAa7yo{o9E4AM@Aq0Xg>bv zFE@R5*PWjpUA}nnb!*n7mM&XHzQoxqY~h|r?^(9&syTD6TDD9(GS_Ogaew*CrQ)?D zKI!e_yG7Gy$C>~9vEy$Yd@OhSM+$>p*-Ot>>PfA`shrYB=Q5q_&p(Ur>Fr8WuhOtf zUjL-jgLJq}%5JssN2Q@pY5J#9KY%l8V~^VMFUc^VRsVuAl9uf0q+LqWfa{+qCKJh|IW-z9pDVn>I&DV&uW89p`6C^b9h8?)sP4(j$x4LTq^A*oM^0Hb?Y}Z+i@F zzh!(npI*Uxqx|uB&Ef*07TJ9aHB>%#dzfzk}M<&k6ar^F~TV}q%Y}~AE4vAm=fd`1dD}~v(Yts}9 zIM(a{nSQ!Mx>Nk(1l`Glw@nAIZHC}$!Z1)*XKVmJ!)OlUW-iRQ6j-SLiRz?z znfHJk{aMDBkh=aO4T969T^&i<4geKY616PMDJa zPL~+3!syKy?=rR>qf7sd$C2)&O1dl<0DbDS(2s9|ed{A}=YbSFI?4TXV`}8Gbr)Z{ zj$ZaR;Zph!OEh(eK7aGWTCTgnM3(E_)*|AeS?a~)V@>MEI&wb$ILMVYdxqYncgd%Z^&0&W!xx%5xoIV7(a?miyl^f&=r}F5jV1(Q*E2!0H^XFf>aN(t(O%JVFbl~}a#(nzf#o`5|WZ=!? z2Sx6a;|~4|uIrGmhOY{6Y^|bIG;G)o+~lNY1wR87&)6_e%y+sSheA z_tHgVF}>iYpTzqtjEL!X%Im)=_1Fg~>rxx#eG>>*f10?N84<%i$Xzc;*=Zf%jZFP6 zob0uK*-P6%|CFZgzW#ptdHZR7V=FG&LCx$G8SJGWu$>)j)!xqNnHT-~4r!VA3%sY8 zRjU+ydbiA}4`Vi6Ds$*(XY2<;$YHrhU!8G%#?3(IybHOE9+I(~o(1OJE9ucK$9u|5 zh0JpR<;HK9VscM5LdJ)ZWHic+Q>K%k7wt1vnU8VaKnI{m*bj7OJjYzDR^ z(WhCmW_Ax#5rDbD!g?YW+y(t0h70SFY!;1TVZD-V$Bx-Gc5ZB8_d2kQoo8k7?J;Q? zZ98^(<2d!orI)XrI(x-ywSt;ke2^BC@zMA-B2>~&=CK9d~bYD#h&<{_+Ig*ysUZ6_QHAgLSto{T570tI01{@;LD=| zOj%3%_D{e+ImC=y_iWvIkG$Tx^-)ZqUv1ue&-(SZu3vxKnl-*~h;TZus>0u(*owbLXBsXYx?qtON6wz8P3_>>c6!%V+d^ zA{w2^zTn*4`Nx-%K6>lQ8&3T3HJO@yRr_+uGJWQrsy)W6)NA!ib_KGNrPZT305XA0;v~$(OKBJwX4bxp`7$a|17QG}ewkdN(QU?@#@_W(~P}Zh^w9UiVK00R9GE zKXy#K_@iTsxF@;iQi;@yzx+a)ZoZkur%(UpApS1igTLOqyk@BT_MP&}FMFm<<5J&z z!&zT_l@2Xk+<lczH-#tFoq9{^=HVM|;AUQW3G5YeZn8tj z<@22^v8UfiSkBg7fw_%geE*RaFu8pZd_5S}_v^spmETP`j?mOxHo`Hp$M{jnobk?M zA2x5X2K_@|$R7vZ*gh-szxUa|Waq}F9kvDg#PWEubfR*c1XHWAz~e~19M6s(fyj4- zO?J@4qz7LsKt zIz&I>2Iw!pem;~skG{)o;O?g%&!;z}PNKKK2Xip>*dxM`9Xse*+lQy@*gkFQDMz+% ze-+15ckFmg-bx>y(PYQ&vOjV`$Qh16wKcUZ(<+EIm`* zGO9g#M%Em?3$*A4Waelmq-2h)B7Gyb; zVsdvy7mTu`woq0xS_U&1BV>RijA43AV&ovLSImKMKxoh*I6!1aZ)cPYF$-*s@GrKN zu!wLJMIn3o0s}#VR|n?BI5fb_aEmB?h^{ccp{jH}5}P&ULBRCq=~01SA-jpAxU4ro z$s~rCJU7dMS-1xTl1;RD2H7=Q*2}{V$IKQ?KMnXnH$n0}<_ZbjJX}@r2vNj*B=O4- zG7^i%SwK_}Y6tdI41&agnvx6<7S%dXR{rW*f)f~i02IJoz!5-Op+=NMRd^sgvDGHs z53<~S;f3T~abH$xFgo#r4F->)?szFZBK(=$*+Bm#CJ#UyeK}Pm{B>FZ$8{nn!&FUF zx6z=TrpUkJxJSIvsBVMp?v2~e6Ex0B5up*t?V%`=4ys?D!t0QiRjWP2MHb9I?hz)0 zwYd)$66JQ`)8kJ5lX2z_Ry_C2}xRtx^EzQSB_QNRMcp! zoT@nD6BL$mf++t=9ntE1lFC*oA`4O;{QV`K+DeM-7L|HNeUVy=Y$BW{Yz`C@HrcIp zv)W31!qs!bCapHJqRg9xtP&=(y)yMPr%l~Wwd801Dc&rNoP``sg2$#)lYd0^k~yJW zrT>hc$;}OYX@G5{!-@QDN~1zEK?F!z@7+qh2Epb;aWU6?is(}COHbjDw|=i`0_Q|B z9`D1s-a@y>)mF5ku!WF0^xNJkR|3RF>2ZCAaz?{}?&EXpytwpmerw!2%&uU1>1vShPrRaOZyfajMoR z26yN+x12T+NsDZpPL;`N9GL;DdQN760x1HetZOU>xB`?s`MWRq!Gj0P0j)}N9Z2vE z7thHTv$TM~Q7W<(k}zqXR+II`N2bRMPcs}iu*|GJJ7=(0q7)gIT53k!?Lk6^tMA)&op~UnYRH*Pxs`Ub~cBZ z*`Qr%wY$Z66^)uZwcz$&JL1eA2y@NM zUgnOO^YOhHVV9b>yoXHB>_^nKh`OFRGIJ?&{>&ZB*)tC?Q!`&?;xo^ae{KBz2e=Qu z>9Y@fYUsSjKlIeYk52A-aPhu}KlJ$h{tqqL|LEh7KKl5FKD27r!;e1t@Po^DJn-nl zk3Y0)HUHp(s~`B(ZI5ve;tlO3Gc*4Vd`~eOXYOJ4&s-Z@$n!>9S#<)r#k|pDl~kv6 z^iS&0BW<^~xHj8cC({S~v?A)W8-`HS&iJ&A~jF+H`*cV~$(hc`u#qej0WmzB=dM z=KOrluif1;C79mof_K8rQoxxIGT*&~&s3iH18*@Io}! zt8OUnAlQrW0Z5I#A()DJ+Cy|3!zI5^@8c{jTvm}ngatHhxiL%Z6KfimUzFL0aoC#K8RZoiJ=KC zFkO)kRxwq88zRr7J4*A!%w|<@goVJ&=JBDSkxTf1pg)q?*V!?M@oZ^iy7v-IHKRqd z$62Bq0YB<=<&Q!lBL-o10#cddIY!hpHFQ;U-eTRt@n=I{66%vXq>Q3pQk~p5kXEhs zDduwJ5#G?1kxPb$=N;RiimFJq&X*S~nCjL-b0^|g8-{=3JaJEIF+A7BgA0{#H?01e z-J+FGLd!axK4+n6lCK5}>cXz2aK-E!={|WAS++wr;xj*ktJL|o=f>H6xdr>Km5!0qv*fBQ z{u+1q4Iz@AJJ{CtQ15Al3CSP3zEJ3E6X$B7U`%=;oEH4K&OQ1rnQim?f}K|`3@>L3 zTG^IyjFJ#BF513`#x4(co)#>1z$|AQEA0&J?8u3}Wb_3oHc(7NeTn=abA61rx`umC zdkEXg$Ay11L{U(UM%zs{eXg^kXTq1=Rtv8aBE7z?wwrEh98!W(XJOSxCGysrZhCRfopIA}9MO>C zcXDmH8xDrk5y=dv1m(~T`O?vwZkicf@c!pX|7E8$>&QDV&=>E>O^Kq~?Mp7*Laz3u zBkzmP7fQZ_Z_VftxD}Mn$rKqaEUc?^`Ltk24N2JDGyS-(C@S-K;|U)TR2XOdL6C{@ z(nl*xd_}f#`TOne7w4RDS>vlq$-g9%ajaOZY1|^bLm;jW#s{6ut#j^l^?L7}^8r_* z_cT0Mc;0sAU~>$%{kT(ZYWxeYQC6(dy>WiLXC5~SL8FET5lT0R&UvKYp>a8ZD)4DI zRm8w-`AZKp1g|00xd4hj4Fu`io$_8!j}KN!e-L5r?w%rjYwP`iUYKSDIVorfU_vKX z1-rn+2Jd!=Qq^yP*pfh53{@jw-bHnUMR&r}k5f7!nj4+LfJxaR8J9vC2b zE~$hYVJ&Qim|qV#;SpjQ))EDNvfEX~lKtJ?y-LuMJ2G$j%zBjk+@zt!57_#h+e};L1*c0e%%moBHaDv6qmZN_IW6+ZCJu%VxMl$(g zR^wpeyYU874v}#!$`Li);9{{|bhN%lhjlDv5@weCLRe+9X@+U+iZP3Y<-+h{xcim6 z;P`gAaP&Xeidu1Wn4wf>7Cg_g&tsG}FmjRpD{{L{X6jsb&+yTIVUBXFaqPG3BU<3t zM&>9pF|&~Qa)hYK0#T2Bj~yVyV(!^FpD!5hSOGO9kLjSrMCGptFu#^#xG_injf?fc9l8}>eFuk?NFyI1YLP>tX? z&-H(}cWJtDVWUw|4y!5i-=MIach=fN_x^D3sn1=wsGu)7Kc<8ZvHNB|7AY<#XTE)A z8UL|op1Jh;p{YxXLsxxu7yrf2cGU%+-@ljn?!H>bwT+28haQ{XxNgk(CqcmucxG04 zvaeni?2CRKi6@oHX?HBbXP=q4Ey{jMi(1p@utEhpr+Gmmv`M zEZtxS7XmOsNMByO3;qEpOI=YpPzu$A)I-mmbKbl9?B^cc9a+3I@`>+}>f(hzd9wGh zN7jvRh^@cp>>ZzMoZC3|jn6;!vm39z^Uh!0m}*~5mI)8d+#+0OCnqOkk=x$W_|NZZ z*ByHo^WsLW(Yv~+72C!OQSv0S|3@F0;qSR)zV%gi&4Jcki+=2^cum^T`mapj%m-hM zn3*>>Gv%>rkrpoGAHmW$r=8VR0cPyb* z5aeQ~SyNSz&&`MuLC-PPkuOYcz!;2J!oNvMP?>oD-7w;T(}?bWz2AY}g#L?^3U8cP zD`7_??GnO;?;E-^xj5mk9@?{H=)B#NzRXBsa`WUv9pP??Ff_MtVWa|}}oRQpQd9{A~4JUBc#xS)LN>bjlJ^u$6#seF9yv^YJleDlG1D`2Fd z<`f=Tf2Da_`#Fa*>ng$S`dqOZP_q3wqgCeu?<`3Net(X-)`sNLVR@jc3@(Q&;r2Y! zWtO{~o@}Zb>fX1nJ5)<$JHls;ZP+k2Z#{}MhGlX0eV$VlF5*9lvq^89a|>3Fz4Iva zNJ*=6vM~JXc23$k(4XjR!tEN}=&WFzctmG(`_Bozv)RcIf&H*`_aDpZI4tS?!(@x- z)V+Ua9u7K{@IMcIYnHlRgt(@=Q-zBTEbCkI%;kwqNxgr^Bf;D|cXXPmh3Q+rxU6f% z;8=IK!m{t$cmDkK3rdMdPtqwRdNPySrY_jGa4myluED;BqwmY5(V$tWPmZI3GnqZU zoi)DR?$r8<6HKQa>LL?ugH@*;T|;fwk=NubS$djp-8JWCrEtyJLX|J;;zI!`cj@)( zv`W{qdNtNv_D7?^2d+N2VZk;7J`gqQ%&Ap-uidf8)WTY@uiqXHb%i^!shZakRe0NW zy2@r3lWz}hPAnf(ssr-y(&PrKJuG` z7E7nkGkhsFbnhoFTz~HH4*N- z@3V12_i*ER;T-;Z&?!;P`D%AJW^2FHmC&Bwg1rlLL`ctvyca${AD;8!Igicx=<$7+ z&tlj3_vietIX`ZC?)g0qY#qbN5e^bpJ$`m1h11&=aM3=@kB7+^SpdI`OQ3sSgYybo z$!TOSITN$y!{h?jw$YX3T5=j*aP-6Iy>YiWFq#?t9((`yf&=m}=L(cTSx@S} zL4x|Ue0}*jo((FXhfqB+4_XhfU(^bQUV++wBCZ1^+I(&s(&lXfxi?tK=~D&L_#|xg z3=HH=m`LSx)=@CMoNKo?z9_u+_QubHfp5L%{?FfuAF+^21ui+X|J!)_f&@m2F3U+q|b?iv+bTpCqi_xVafRZi||rMV_7o zL=(0UY|d5+#Ym8Am|3BCJe0dnT{Gib3l+_F;uQ54q9Q-PU@69$d_FAvHXn+`-vSi37Vmn|1F%@W#$g(lF8+`DZ^;vS9VG{O@_BXEh-7(O#<&@dA}~I zf*O+olBQx^80MvkB(vfM+b31#!F${3aZ|4h#4WWF(U|8nRSZ``6;O!p$3#By$>u$M zE@6jgC9C^Ya(ksvvGBc(A&DEB_w;%8*MT{>ifG$5!}RPr`Ah4PW7DoUz&obqPrz9R~sYThod6byk|h9|N( z6*oI8{%9;wNmckyR{W;=0iak>NrkkPm3#yygs{GG8qQc86iiqTZNm!3B3KeUj1eOp zExOW7gczc-3(r+pgU>;?>`fk^hVG{hxUtNO-58!_CAy3Y{UAN#Re~BfQ!LqRYfZ$p zDonM}Rh<0vObP=E7G5yPAV34b)*OS@VfoKyrUb5cb4MzfV~UsP_gDyk;3|m(IHS z{+*GFiGE3=99Gp(sJ-#n>5JQpAo-heyMI9?AGdXNi$CGxzYH^Kyu}ArEn7Je2+e$8 z$px(5t&Y#*4p!D|(9ejq4Hp+K+)4)HLo=Uwj(kHt_W5&^_(<6rSVESy<%QKsW6v8m zw&OT)`@;6!Z&*>?F>f?inVR{ymX4N|GfVpy`^}lDMN#F#u;o`W@#P!4isoE87BuI~ zZ@iDcf&UGxpPcRPQ+f-|uKXi*!+mScuduY%OUB{3k9Q7`j6{ z#o*kk`|%o#jtE`#Kf*9xtYI$z9fY_BXWToCK@b43WQXX!PInGZ+cVdLU}&^3#Blww zW8MihdkJUw#D0q}W^l@`M4hEkPo|=cgWptyqDe8N!KmKF0|XccQN_9FPDbfifX*)< z1d$`Xh5Fp`lF+>bq>G^*A}C0I7O3cHa*T!OPKPv7A%yNzpcDqE9d3c_mXK z-TRW{8f|ALPgbP1HR9l)L2e}z()-{tkY!*qnzhOB+{P0sC&YM7)^oMwPQt7|lf;7& z#bOPqvaX&?7RgA$nV-f$i8n2Twp57Q^};hdyQ)(+#MrJKJ#axgq!&LI zBE9W>XOzeNsUHPyCieu^3>}X5Grcb~e(=reuuPI7OfHUnFpv)(SP^`~S(|fK=-3rP z=QbrEOBnDD`5BV4yErY;)7fGBnDa}Dy}_OsR2i+*dDFrj(VeR=*s^*513q*29U&s6 z+3N=b63fYcwmdR-61()PXEy%W_>LSk+tNN+7OM-nG8}Gh8XN2Fv^xi~lZmq6ORnh3 zwe6eKO*IL-h4B#N4u&aNe|(E$cL#{S*G}o}XLjT}xI#MDL4KifG9xf^i%O+!rN}$o zd@Z5qf+>kI7Dw{d%x@SKOWnf!ehudP*jPGM)rgo#ILd-f-l?|G7zsx$${3wpf-F-=%T~L`Sf+)ed5Ds5dQ*y zb<~{eD~#v?jYH3fvs^5aCgH)hOg41vl7w!u;iRKGNvTaZl5`}A1;>N^0KC(8%;{}h zEPh=GVL#DpokMHw-ToDPm;QdvpXMB$^CDqSngQi-wzi#gJAP2}HtQ}rVE+er2KyDznklSz;_wHAj1$~`-ll(LJ7xF{$6Y?|iEAk)Y z_vBCH=&X5Aka3t8lVWmAiD_rLnSOUBG>@5J7BNeimCRaZBeRv+$?SpWnFGur=6vjB zyNr1wa}9F?a|?4Da|is;yp6e!c{lSu=0ThZ{|NIq^Az(m^BLyz%$J$3Gv8$XnfVvy zhuApwXM85|Qz5>8YAZwa{F8JCO4i6tiycS42RQNqb{8fd* zdkclN?tkOa)?M@2IPuvFojLPVpcQpsq40JQailVPH4IpoBhRW=Gpz_fZL~zcc@ta=pF}N>T$7Iz2?BdI{wj7 zyguWmp}pn+{(-L5T~CM?m_ng&2|ic3zw{1f66vM&AzwXy+X`aNe_0TXVk4&vcB7tt z#V8uh?R(Ye*8#%5at5zJ)+q{cQc-7BgjYL-!e7S$e;o&SV+JY*PbDW>^09S&Juv?r z*#DC>{}XBdCvf~Zb@SiJ@Acq+HJDSy{UweA&YPHc@5IDf_rLLI>#q51ocQd8&YXFw zP%rM-r(Zd)2@!uS2oLV)MXwLegMQ%~e-8A2C(Y}n=h$nO@73z))OFi9sU2QwCp6vv z{c!;Q`-zE(x8k$M{iSzAK7Q*yK61h%4Pw^6EXWN$ddgrL#lG_uqi8g2%G1lMeO?3P zRWe{sP1Y$2K~5~{tcvhzXJP{94`=BEu$cDO`TX(u`O7Brn1G)&naAV*KEsfLa*{=K?2?WPd7%>YwJt*F}Ln#U+Ff(+0Q56zgBz; z{`JEBcj5p4PWO7t^ICAP6n}DiwAxGj3z*Lz*gUoCgwFF)zm ze>?RkzVy?0Z}Y}|m^15v2Fz?ZDS!qZYo(g-6CP(r{q5Azr(PxU6;qxvua{BP zm`(Tb5>Yb$?+eXO!05{fe;ZF~o=r@+9ew_r|nJsEcPa|^yzuk@M5&%V<8Ry(xnwI!oz`0V(<2pPS;e2x3Lx_Mn+KI!i@ z0Uj)0QT}clX#;{I<;FM5@{?Mz{F=>pG6461 za(RRM-*~aD?ZGlIl=0iZh0H;?8}=gZ2%r!_Z*d{(+uE8S6qG@WAl^%R{1+k2oVGTa zgaXhE8VI9Tb~g0vdm2G8(nR#iA~O&%<6UTMlL9Xn`f%z%kHDFKaOtF2PQW@l%h~rV zl6hI5NcSrjkQNNIG%M?*T56K(5kWyHC}_bi!e~Z-!2e(6erw(RuW~=soUga!&&l$n zC1+als0Bx#TVbvHSGt}o?@lG(W~ZGE-@2b2-?~2;&+O+v_nzWxr8yOT8h5I13-(mk z*9-qja5TIn?_2m=YD`5F;D}4skqmFaz|A|Bx#c=?FbQ=))VHX{RLm2Y} zFucc0-gp2T5Oo*vBQEH}072@P9<&g+$dX#twC>|hYKh{b|DMUh0$2{(4 ziw8=~L+(Q}cjh#Y2!vAvG#&}qeJvu920434DE8A&q>?75>1csHP!t6SKsNI@thf9g zRr6t>1}V)90p-Qxm)08K2}d)>W3#qsg-0T&W?JWtvmp|J>{sWkd2w9c$P?_heGdEY z{uAeq{=oQg`X|X$m_EAamhMb-&tKA=uHMJpm+GF>qDLli#FOr~bvK^2K(J?(#^-9G z(n}EDp4=YnjrF#r)&&tqfNyJ z>Pz&pN6pX_EmJ3WfSrvDwPwi)kLG)~#UjR!?WXxqW!2=)r28tAcHY%r$3 zNKnK1E^6+_I{}N0(eAO_Ds2aBfu;u=<&B4yyzM~Y>?`gX&|#4o9Z}gd2Co18{IFExL@rL31p+z4)gojY&ob)yBW(`9*kUCh^Pcd zh)Ae2kP5ZM4gVQhA|M+EF>F~93syD-&lZ+pFA~pY+55k7V;8q_k__CDR^`F0T)MY> zL3V6xNql-$VH}gpQZ&hJFMU^IOA7LKJ$Uj-uzq`|5Hcz zuNj5Mb*pL?I^Y=me3+3ma=y@HTX!(mJ|ZwR)6t4t#4!VWLDCAEkAs7B#f)$!w@9v* zD@VVXod?Sw-MZpevl6U^D;ya>^$NlM;ffp=r6+sw_fgGc(m^ZmUYK++m>+D42C{AQ zO}jpxp4T(CyqZbJbCdoKGZCt#m2E8J4+P|ue!t!|bec3ad_=C2NXV~@Eg?dmF~G2< z&$J=|`P>IBEB2ts>5-_UD*+{I`QauR2GskyEfQZ#B&}~tZhA$@j`-v0g?5mz&y=G! zqqPa;^RDk+;t%jD{J`h(@pyZu#zn#mUl`b1OJbM1&IW7_i(N#@l|p%ohe0saw`OfM z3i}iSuY}3UNGjGYx0B?3j1ag9ySK^SotsAANFel61L`Veu$#y5FMPP5SWQ!7K1uNxM=C#^_ z-Dfs_WL7H6)dVLCh9Js{YGzbbm_OZE&1ro~GX^JyaP1E)o4C=Ufw#gEH>DOacVSX_}KCJ7abz(+&i zqYb&v5v%*{j)?;OlGzuLsJ?EKmXjd0cXb>4;*wt-67k*e&hCU6sG!?S4Sq32QE495aXIJ9fY|vPSqSasRjK?gh(j~>`|(y zO15n!J9Hj-m}3&fWQtAZb7>}#5{F=#Fdj;<{qv$eS<#Ctd(1&o*f2DG%Mn)0j0T;{ zR`E)HMQr3?w7ckg;U`&ey=*zm{vgsZ*|%E~&pczzZk}fo$0@*|&XosA7lN;rCpcTsm)L-PrWJJ#Wk$y>Nctj_Z$& z*laE*i%w9L9u4=T{l<{ysG?{DcXs%MSf}swkS^%r2rL>&0>fwmqvj-2Wz`|xkkqXs zQqFe?qnVsYE-j_A!STtFls~m%W9EtxjjJe!CQ3bnLQI<&>DhBdvH;O?uPNK@neIx; zO5{18K;&|yT8dVKg-W@URPk51?uQD=rc**t?GKc#)Y#PWHS^-*)9)f?BvK5;L?z)k z^0<)E!ghdDeCk|I`k&K7ioQ3Ob1LL|RA9MA&6E=ebb3`?Z-KzVXxW@yaA z!NhVdBlAEhi=2h~rI;ovrrJZ1;|W|8e(3O}BT^hr^B5!zw3_%aSr{WF%7KuA@-vtF zI+VS-_5p0l4=F2$L!K7*ro^Kd0xaZ#2Ll^?QPRBVZ!HKG)?&ld@2pE|Gczw7bDbt- z$hk>8_++szz>oj}^6p5UmJt6StC{$v8}LUeG?3K8L(k+6xzOM`mvL@;3p$#uqPY)yb+<*cwEyO@mU!d);c z_|xK&wOeh)5_KVE%ZetcbV3E3(v@VECB$$^D0?%ddMYvZ@KboAwlilGde ziq51pNx?8X;m0xZWH=mcbA}BV=GAi)brG+EjiP<$eQiPR^B2q zK66sX5zekcnb(uQ@*RSi3s68D@w>%V+<$@U86Fnd(iEBjP=u4+nBcOY&UgQia*l|pxWVK|p8Eb3;ii0S>iMWbV}&+!K@RW#eqE-}Lk zBjg>{{0nRKTSj?3_LSZw@Kovc`Gq7uj$g~{9BvCdv& z`}ByFMK6Gbx9Pq6`;*D6CBI|G{t4bJOk0Lp9N~F3UN@srD|SKbtXTJlyUn_(SP_2L zYRFDW)>x%Kj7fktJUm(3{{gg}55h&<& zWrBOE*()~OdDF1gfSYP|+#}2IKl~CZij1aj#(`tG18>U0>B#(eEal854$9*|bBWFq z+{5=&GebWVnvT6emsG(@9NKsPJ*$aD_~Zyt}P4MkHU#BDH72SRU= z9gLYE2h<1@AFz%}Pf?dJvK34MU@%^^d@hRv-9X?nd%gXndu=xvrJvn zgwtd-%L?iFh8c1)HM3Y!G%GjH4ENZb`MGyTRV$Gs7YiMNx_i_QJz^+X)H;ir8qdYV zgm{_ZV}wKFStCTu#Kv_IqbqDy9$6k7R)dbX{q2vQ9oV@nO&H^T|9uI@;?t2#GN3iS z&hnf%vzdgF^M1EuC&Mnee0w235Z=eWRr0M~`quFauj}_&aiREN*Eb!xZE2;wYl4lY z4(MlKU|%ik!JaG1#c+2;4W*(f9~aUhL8eWM8R;yy%oI2_)T0QYa$`I> zdflRaF+4uK0)|>RdBMo=Xb)=#b8*4ZL3;1NOMb+|xK zk4eV-o^FBHxN>@(*&Egqp@j?5(>YET;~^#>#S$t>YPpU?0!>L&N9d5!<4ma1-ac2c zjRnzI;|o%0`u%JCX(bVkxBHD~-6|PlMwrnsWtP|URXBzg@n3$3Q$35O#BXz+OIC%Ek13 z>I(Q9eP6NI6Nt8%Qc{YCI(DBiXmPTsILfvoS16&7a+?EX}&!>sh$0aEgz07AB$Dyt&@hdn!$3s zC~(Z}TKZ3m6_YW-!?9?g@e6Uq-CNi9*$h!wGsIeDE-}Beqm=622z&P=-sX&2!jgsg z_CiA8M>n2TQWCzx*_j9-d`VZ!D%t4Isc9m1#PvTsFuy(ABPx5;Vdts+(wRQn92wAs zfDU88+*@)PLdd!6=gnWd?ah;1z>tEh_KvgHx4*MJK7SFPZd?_w_#*kv$Fd^}Xcmo#6-uY& zkfu9iSoz7^pcECmJM(2R)y0WyFE2BquZy)__zHm!VD1Q8iwFGSMuR2t*X&JxCYCt1 zJ##~DesAnPqh>_6`8Z4Cv*FM+ENtYvNzT#~aZDGq(iwJG_iIY2@vnvs&yBXkCA90E zc<^G0Rbd}v!_MB@W?C0bttaX`85PJGSoJL>eN-8>5hjkLL@D@Yz z)XJ34c;X5kU0ha0`Mb-1T&Cy=nPvzcsPdO`Rs$*v%G$j-00a`nf_NmNdW+Tv1~UZr zOT`)hs4@XpjH|toE*KwWe&Db!?*_M{?A4QBEpb2zhAC2*g{Nu@PyOPuR7!wT3=PzH zFw~IKPqp00otClr0bYtPMHL&a3a2RYLN7Ik>Wb46RlJ}E>Qu-zmkL}(krz$Z9w>`% z))>8dQr7*Y3>}57d#*Y>;pG;NVsqyn)DFEzfUAFZ;bFa(Iw-02QV|W67M%h>{6HL0 zNs%J1C_)UarKW&yp`)^f%312kgVwSqH|tbt1rdNsv#4?ks}w*jLeOJM0JRHkEz~ll z-l@5EK59Y85l@fRO`Fki4Uf=u4cL6Oa#U}RG(ZTOn;6>A(MGLG>&5LBRoqBE+2DoP zC{IKfk+2pDgFUIRUos8|J_5r%DGqLCwqz3`jOvH|p^&7BqHLs8zZ&MB@fF_Mb$MJ& z$YqV?q^`D5Lh`9bM9crzrFK2laClHhYF=`KL-U+f{i+g@usk<$;Ror1Jl2PFVrpi8~?ztA}bU$O=J|^ zkPImqi3W`>Ns{#}t63<77~}j_sPT&%8eiG*^b+0PV3*dI-D9;*KI|l7J{Sm=om?J8 zjU|0T#ox_vK{K*R;+U$BOTkG|vm%3&~6@7~wNvU=u~t8F-HoNSB6V(vl3E{J;>Tf-O$-=qN!92N@p* z{mN)S{ER{PF(b%kVwF!RA}fm)qbhQ2k0AM=APW;vEXy*f#;k(AY^>y8;LGN0!4aai z6_NbBwm&+3({{D2WF$?<dXK*&J_WGi~HM46PC(%=kfcQC&&3hg344SRFM7v#lgK@s>3 zQ6mU1CeD?HCU*!@fXN#nIlYP>QLHGt{j1roq@k%DD%_E;y_LTy+q;Z|ugoOhpCrrH zYZxuXs$-W+=~$SmTiS&IRcqHm1yMC%Oo`gGi?MPr%gSkmm+FICb!}1i)?}sYtiH(! z30g5&NDV4%In^t|I?nyO(+k=HWY5A%Li%6iN>fu{V8?3rkvJdtoFNLoEMsg=~{SS~&ldq8P zlmEiHaEhtH^2sjdFitjqjrkGt9CH-TM(gYvh_$~2Y#h;146aOS0lLT)r21cG_DC>1~B~$_+#9gdx$^|;9fn;h42O2H((`5pe zGdBf?C(vl>CI)IK$%XNT|1J6A@=aF|xICF6bA=vd+upaypXn22&LFEfk8rc2ndWFH zok$@9lCg)g-;?xC(X(EEZud$`-Lu zO(kH6D!#C73&EIic~0Aa2Z2S*1Vy?o4eZd^4Nw_Xg2<-QoaWiOqrtSYyi{1kr8=>a zB~+;pqy{xzEtb1pKov}GdQd~JRMgN(5~#FYIYV(f)Sfsslbf3@!d*d0MJWV3g@UA_ zH0D5xUG=E6XuXC8U}Vy!7OI*92viFN2j!m7r$97oI!d5dc=&>wECEKX;Haa3Izcpk zy(pU0D|WSBE7=%CV3wDn*0(^V+l+KpDdNr(gpLj4;#ko@ZFr&_Q9Tr=Cv4puY1-!E z5JgX=sF&LS^-IzZ`)ycJLZ?Djm;t(7f)1jZeZrflx(`^v@oLBcQC?-zRzZPFRSP$> zsB(L|3rvXtt!{^OL%p@5DkwoCQqhl&;?)$Tj@H$HSJlAhswj>pMKoHYSjBjQ_KRi} zpp2*h+Q7$y+TlIo zpf=FpDAHX^6QJIN-hdk0qdkP8^#)QXC0;A~KEr0SOBi*RU|~>-HwXO&Zvq|bxf$j9 zs#v4*DhB4(y!YK(E2Q@{wa}{2Ua#HII;cWmr@JHQxb}GKQEfEubE^te`%xqAi-wYW z9R#Tc3RK06iUm?!;U2}Sm=d1c55}k{OPyxkpREPa-)S)Q#)7t@Wkf@OqGof@D3PLo z+Q@FT5gODT_EFU~&Bj$Hc_Uqb=6Zn9t!=lxYp%+{9RjS(ymWy9u?56QFxF*0CFnPmkG_+ghp$k zZ&@^d>T1s|DF&RP+q~#IG+Sgy;hX9i^k!HFZu;HvUeA(?kAQmup0X=DBxXwkE)doV zXe%QF%0=q6LKf_^-|M=~CZPR+cDH7qr4+gWA#hZW-*^as*P|Se=ucin91kj1TV-`q zp;0l|&(Vy*J89NfHO50z(4bh*ZUL|8btkunxnZ-o0~I}0QZ0!^?BayF%6&a)Rk@_% zO`z$iu88h6D)l*iXevs*%aJG}qaanh9&{4qc0jz!=qofcWb1&LQ&^Wq+NlBNQbYS3 zeWuL072gyHNbI6&zJ$oYeIIGRq(tTVfQ)8E(R;gzXxDJ*t%4bPb=Kd9m^HswB~64z?wc9w+@ao(rD7RYb!nt~NP zEhTD+bjT-5NhMos3m&E0M{ueJEtA7gX?yDKDB+Nx_yNn!B!8aI%2_> zPC%MFuhY`G5I+zy3T>r8!0*hp{mkOA3>R~tDl4hJ0<5Vf)2X@@@6_2oE*whRkwQpd zSy9zT7i1D^S4@qMZ|X75zAe0NS@&Z7H0Mk29PAACZS9QIHb6Jn6_9)jbA47%&h+>9 z%AGO6QsO!%lwqM*$y+KlP7fqw1vxQD^eAgZ1|6cyq|OF} zV0z36U<-&a^RoeXHZ3xvj#VDw0@}B=5JwosR4Q2pOSNJ;Xnc60U-gMxI2yMYmeVmk z2#v#8pI)%ouxP_sqsHEDh!`5{+oxg5w>_xa0+!yvom}IBS!Ednbi-lz=(qIg%A_+wC z#mx8stHFm5^l5`56a6a(^_v&xRKZlH{Txp=FW{;rdmxh(WC4mppWnumZP7>8TJ6d;{kr2zF}xx2*s;@lep@s(FLQ7-8iv-i+`E3N z!%2Q2M*LY;6fm0%7Y5E4c0}EmI(MY5Y9gyPX14jIgeX~>GB+3JRcDJ5%9su>2@!!r zB~jRQwa)f08HRanSWMN-g>sY-0XnN_&c~%yu@71?LY9chXJLt%V5O($XF=JF`~Br{ zW4RD2!M}O}-eoiCWHMm02_qpIJV_OZ6{w>1|3*^s;^av`BYl3=z&SA&@@OmO*XND}!J z$H#d=mo-80Rq&>T!m8OR2uhN*bw3xZ%3;+P>{I-0LZm35GpG_9%ao?2jL4rGkUQEK zLFg~~)7d1?agsbXCiaK;0!jGc*oxuX{FuQin7oCQpkGUD2u$WvjgR@v*ujx?9dbxY6tfwhp!MP9=SjYUiKL*+Pl#zLjAtx#j*g3r z9@rhI&b$XoVMUR}`?z2wAa5{XD_aW4s#WUCnHf2db2Rv1=Ep9&hBa2U2O7_XMU+V( zhG-S=+84@6fhpz7jPye;91xwDXcu=8U+(Acj&Uo1$e#7QoEA;y_*`pV6e?6n+n?_<12DXUJ^Jy&#dkg zjG2dut0doP6S~0`NL#KTtnFaPz|`JwH&|Pr*0Xn%ogZPHgKK`aZ1Wr1bU&-viN&kI zH$M_&r9rW4XdpScXzHkxk>C%$O+k{CQdWwnf0Mm2YYv(DXt*QXIXn%fd1WwsV^Ya0 zGKt^HcC$Xw`3{?(yC`g)AIS$DXr;0@WD6N%ZSMW%`*XwfUisplsi9#$ToIwJI=qt0 z<}5;TDK==CA>~Z{bAlNj*|0b16Mc5|s+D(4T>qxF>QJa>^kadcu4E3p%4z&pYTGWT zfK;I#71|TU(ENo)kzXSeCE>2|_D)A!Q;J6W1J|dgWsBdwsoqoYEg0QsB=ra% z>)c9s6K(D{EAftkoL8WPT0L)id9OI#ky|NTn61TQMb#hQct()_vYDEmOgZZ|q58_J zBadX>ZRev{pqzVCaA3pxtawrRc|cs!eMAjg<@VTuFGuoSx<7kcl;0CMveCR~KEp`L zruuMNi(J+_PmP-oFHsjwGH=$$S0wU1{O|Qq|9L5u%f$ODwiY?BZ&KxDK~0~w>9WxD za*nx&koJnnaoEh36I3RU6)aW^i~jfRE#FtNHMQQc(w7btwFvf^PiaplR>Y9O1Oc7}!AoIbcjsJ>7mvu1QwkdxqbN=@1L=L-fe*>$~ zTq@#|{BmQlHW;@%FNv<`PfsKUYoWGIY?qxFvxu#AuWanNe~me|vFoxeq~zxr)~7G; z=HZOMkPQ+M{m>o9cy^1>8Rdl@3BHsSj>~O{vdK^=z(Z71F`kFhI6dNH=8`0l@yfBh zA%ubqk+=vH#hhiNnW&0n;zYO?JBFpyShp|CW_+q`&da8OM*o>Ygo@} z?Q^q2Ut1Vk);YdXUS3~-cQ6#_hf8VVQ%CxW@mL`gs#M>evhOx~`lURMr1?aw0hn>l z4#vXix&0&_P)*ffVq$tIT3yt5DmXrLWsksEkqWs^SFjMrAErBwsaHG5N$WqAyW^bh z$V&#lVMm#piYqoPR;_4#{#v{J{1r)q7xmIwyK`4*-jJnly6^fZceDJK(={$I9UPe- z$rM$c35OGj%HhomICI%Z=bO@unC&;$j4agqRNmRJWbPVuC-%p!C+7v%GD;-JaHDo{ zT!dyl83YNCcuKV>N=l!PQy4)XX-{P$$&v1dJFmHvC#+a*$n#JHO1YWi^RSgKzq zYX{>dr-j&) zwN>maOkhxcsbIjvAPaqJ5PJfDBp+bEhtB>PuEwqB`}i?_DZiTE%k3p{K5I%7sFLVl3VN{rcFU9r3c441zws4_v zM7UMBOL)8Rfbfv;kHW`=&k0`@zAgMf_^I%`D2YD2!fEJzhs6crQgN-gRoo*U5HAof z7q1oHB;F~$U3@@%Nc>0f}2^keCl((j}fWC$5KA{XRNxhjv!Q}SAQ ztGq`(AfGQ^CSN1JNxoBlyZk=+5&3cXv+`HuZ^=K9e=0vO|52V%1Z*U;m84Qsx|EtS zrYuxeC>xZW${ETbZH0--K3tTo}nI6FIL~E-k{#5-mTuNzDNCg^@Hk@>ZjCa)Gw*es^3?Cto~B{o%(_{ zN0T%k9B!qxyw;%&XrtO9ZKbwR+oc`WF4eBqZr1M7-mbk@dq{gy`;_)2?OE-6+7Gqo zwBKld(q6>&5ly%BsGirm^}0TyPv}eaHTpLFH2tuCfqq24LBCypKz~?&On+K`M*ouj zto{T2=lXB;7Yqic5G*5Z6pT)zYK$6F#tLJjv9q}w3Uhv_0qC3^6M5VV&=b3w59n&3 zz-eh#FuC{U{}dhyKImxROVrhUL-XOOi@c5fC4uTD=v*IP{6m>Q)6gdXK-OjkS0nCX0;qSd%?na^&u(}f zrS)KGd8jHJWEGpMue3I+tz=MyxGC$%kme$~>($hO9jd~uW2(jhGN`^=NTJH3^+omM z2=|n)$D7Mj8PZ=M5TUUadR-kDC8JLSQP=|(a!@i9r#9l|;+=7WX01r~%A!)BM5ojj zTiXe2gwYs$Q3j+%9lG~U>q=XpZsW)r7S9|P3Q8oJ2Yyhy_zk%vqowI;yFfM9I3zIu zAB}DWxtdOBk#apQO1E9G^NT8z+*+oz(Dnl%wUDdZql$0~P300yH!XPHGU)a>=&>lV z=weBjBSc%H0_d(fYyoz=3+SP=QV$(esb4MP30hkkih_YSs=}p&d&JXtO1HaoK}*tu zg%H{ofSIZ`Y1_DuKtMxV-3A@kjYlx;d9*!gbpY1YuAy*_S2&kiwFb4rctC&j`l#E| z*eOr@nShrPsqmY1gHaj1l;k?9quEj@B@MvWd-a{fldd#4_BwwOE3MkQWeXOP2Dtoh20M_r;2@`Ywh~;8X9^|x51GkuHGBu z_M&EJE4F~jFQT0&F-YUpin~ozq}|=7lU&@;{hqU~W6;ggCIW=JQbox05J&Q3u+$hW9yUBN==Le#8B)bT0dJYrgat!u07uXJs}d4;JLdr>g@#Pfk*>yozt3aBIzN7q?Em z%)J(I0g*YS)}zX+l~&S|uO7Kg@<$16IWIxZEza>`^}nn>T11|Fb(a8uuvM-T$KzW)smY;jy!Nf4nuM;?fp%0aUeIQB%x;Vp zQ7vq(T+oPg=^G5vee+vjts}`Ec>Q=?w34;ZwBX$xPc&bU}H|JADxV@zb*h190I<)6NmYj^Z#r#0t ziJYMYNVye#N1T&$blDhq5!T!=5SrBqTb1F^8@w%WYTqR4cn?8d)TrNa-WUwET5eW3 zXBP*fPp{4Cnq@8IZLjMuij**yu{3|d9S}tG!|3xR{FIUshBB=(&}X_piWd#|a#TL` zw}=E|Z{eUQT1k#y5nV;X7I-Th6AqF87Gw$d>rM#5s#)09^>}JZe)Gkc zT?qs^^t()JcIa`mt*Qgm$r!;4UAF5ACAa?o&54|tg3vPqkS(;bsPw=nkya)Iotva7 z(WsR1l9t>B!|DKKSCHN4_2~b!$*D~9K=|f6=^|K~e9n=eCHw~o4 zY~?`*3odtR@~z{Qgl0oIy>5-9Rb*}wH}igsjA$whf2tH8O;54E)Fe$xnhV$%Q$f(W z{lx|H2G$T$&9Gdx)urhMS6Uf$_f5~a6;`7SfaHjAo28eQ)oXydb6iq*LZ7}yUSVhV z5IPp444w`NFaV?FrIpgEfhMuo!PmTNy|W_CnD)hHb{O&_#3j1b0cZfFs&#FJm)!6X zczJhwCpBKtgp?1|I{MEz?<-e^Hv}skZ~lMDdJizkuIgNLPR==>I+bHrS9Mochw2=r zCv;EliJByhMw*c{%19cNh(bbS1V#uWzb*qK6S!A`|Q2e+H3vmU*Q!+)-1u4Rp1rpd3A)gD|bK7l)~F5 z^In0@i#}W$x@GLtv8cC}_wp>ypahvl(GkJMQ31DKQ)sl3Hfos-n4ZW|oM zhNE)McNjY&fOmn@!j`=J$l)>mHB>1UjY(s)Ojp^( z=>j4;@Un3kU%n{GR7(|oYLb_|Ivw})r91Q8W%FGpZrj*>%l`cb?mB;->B^foEBSBE!QmUfu2zHi1W+@9OrexxY60e6Sga}wu5+l<5tALh7 z?u3WLWZBU{W#P~<<0DmoQy5vVAE!J(O^)El|dklgPDPGhOkdZ-ktJ z#9*D}*^*J#%SM)D3*)6qHpL>Opewozr>E|+$Ifa}OQ?8~S74~coo8>UHOkT4wX;qW z9#;zubQ#OwG>|peGQ4$CA>*;46^NRsQ;MOnaJiyPcPt~gf(vMrDF+lS*A=yg;0MDL zSYcX}`H*)sBn)QYZRA<`vWGwvC@4a7ti+?OID-5_ zZXf*Kyri*=ME{CHDKyWTLSE%W)-Vi4s7Z$u$Astlal8pQ zGn^D~Dx<$s*9~nQwMvWuL6|_vVEYc&^PLaU5F*Oog<%kV z3Ejxj99)-%Xk2n)C;l}79=fY~V0xd*NBL-Z1w)*Y_xIBzyw!~Hb)Jj~k- z7wKMk89(z3$LYu@=Mn`GdWJS7o^O8AitO-$l{X9hf8caZzXT|Z^E4hZ!Y-mOiyFvW zs2@s#DE9N5O`(D`^$DX$@lt8?^W{t_*%=i5(z|q1H=hByh?Ja*i?Uhle}YzEstZUY z$nan}q5db*+Bv5Ge%X|)qT(pd=YwpN>1mp-u6UmBQs3ZM8^gqyNAj>8J*ykK@iN6U z7-@}%nWl4Ij{%>8GC{M5f9GUNwq}3|VUSS_ZRelJM%m~q$mJ;WZOALk8BXjUPCzsC zDaUqDW)$)lQIEhWJ%>gG>lka!ufn|?^}klgp5V=#Yqzu>1cM_e5wHwd;PVKa{}&MssDGK`@y;hoUQ;+zjG%UG5^BD}Fd%AcB_qBs8JcFX8#HyIAmB97ZF=e}mCL z3*SVZIZd$~ZIeSd#$XwW?vNqa{~MSOQvZt%0{MME67r&w)|Z&Ck*t zHN>*8F8COl+CaX8@J$ASM)@GDAT$l>k0|uZ8^4P|iyGI83j7pDlU5CrhHm~6jmy8C z$e?dQI6%8X$KZ!$cqW3Hx;)Al%UZWo!@|5Q$Vt^ zd?Q_4vgR%QwabnsNs0+GO$f|$(gkPyGvi)K@nlagw}h-Asj*-n@+Ue7J1Zg}Nr^1T zuBpWIs}BuLDoS3AGkK(4vEb3!k9^mWo39kgp4WL*Zi>zFs)HV~d839=P|ma>oCbkpgEGu1c^gcG}nuy~M! zdKEJ;3bSlp1NKlJlDP9?!!{n~^O6`^iU*s6)lk>ob(n}(Ma=kEGBYfX^d>3DNg&OE zd~$3#XmhfF%q}*7re+fjMj!m3_M!NP%4zZJ_)_AWh;_^iO(oyoC#JfgQ-Ko z){>BB%8v7L(wJAJpmmfm_pr=e0xN+$2D6<8E0@Xec!(@rn3c=JHiEh^6>6L#fI1FY zGpcE1kVplgp_Db~QKYvOWeu!ptS?kp2UrHfO$xwph@3>3Ufg0R7qzryo7On4JxALz zNNNPeLaMXISe)!Avpg>0!mt6MnJJ0~zaHCoHo$a3OtW0zFv?I_!4!;Hv{s;MF^D{H zG#D(jK%r!iESUFP6-TrntHw2fr~Q^=he47pEbXH+J(Uhw!#08R3usBmB>~g9NJ*j^ z`B)J!Tt-CwX%OMq5JeOyo%S0Vw*)D@#6VASU$Epke;?g0jWZUFPA4!7ED0!*IL9!+ zWni^{)Hv9dEEJqX!T7WA3?NVNC>bm&c3PYbOcfDDfV8r(#7PP;mL4(%ToDm25GNGA zktzzK0Zu`~#FQZyVacJ%GLMN5TOOtxrK)%^fMzgR3=$h_CKjGM36GOiu-t)g01i;V z-Vi}hZ#;|_NCie_iS{)>H^=HcZJ>3KHj3+M`c*1hI1b%+1s1>wK@tDbint&uVNiYp zg6>u`G5;F(1jP&P4MR8%jSb849Is3L-I{JtJ3$k~t;!y-$HTas$q0`?N9@*IeZLZ_ zPcUSnDj7(5T98WstYO^B$kS8LLr#4Q>or}I|1pfBpFrM1BpGU;p0;E6f610~s~y|V znVR{4=Xo~oC|A-+x!=-TV(31bdIIH{stfObk{bl^G0a?G0fjzQ!>CQYKcf?F{eRk zHvwFlObb5W2lSCBQI7O)g%Y&&h53{2$b2>XG`SNQa$In3fUJ^YvkOJxy zRKBJhr7%xeEhFkuLYo+?ETy=vvtsG?x79#ifLQ+;tAWaqZE?U3PaqMVhl$&N8xbZP zJ?2>M1(+ib%Bjf{dc>qvxY2?)MGL=%_l@(EWJ@&3*`&oE6X^ zzQTmkyI0IghdeBZsRdZ>FipMFnK*tmOi_3m#E;O^BB6y8S%s;Nz{KodmKZe?qm2EZ z;^;_~)y)sXj-}6dw%`9LqF92!NqSqz_CE$&ih4(q$*$|VwHD`-x1b>XJk|)CUqC)0 zN52Pl?dB0c137RgBF!Jt;Sb^{dK2v5{-0poDFcy~las5ke@F2Mz9i|1M@5e5{uYo1 z*SU`OC+O3n$O_!NWrAQvmKI%R#L0T)w{ z+6OaD=qtn?`zw~;`~l25tXLJ5k71mB5er|Ko-}hOnm274R{s;Gg?vw*7r%ua->Iq^ zbrM%#`hSDHr?5_YQ1z@S%~9_GF~jc=Tk;Ed4xa+|0#ItS`cD7`mcVWU) zUxK4%Rp1PHnk*mbAE6J(^8c%_Eg>c$#>w1dW%SkN-;%xpZdj#GI&DGDIGeu;?F^5Q zPJIVf-sZO?k;d{K>lqG-5twFw#L0@#Bfc&u*MEVvCc@)KJr^iYZIo_cjr@O9`1@#~ z0=YO1x;-|4)lL>38w}fj2$S)rz^Xz_N^BicTO!U7R-hF)xnSkFJk|euEE&Irc?6mY zft_#xxr5cAkaam`y>QTJ`j0B$fVs* z9VKj-0`Rf5avJv~&d?qS!|?E&)&@Rb9|7h`B!8tjtJp@iBHsPtiv-|Fun1mV&&9e1 z0q0^0M;UDMg$Z)$hrzDoZqmJQYVs}F4Oju3m}D@)o9mIYqJePX<5o!n zc@M~P5<4#LtY4N5ZRcV46$fwjIHuV7RI`Uw1~yKsJHH#P#Q}G`8~TH zvD!MD%fnq27^P7?bNQa7ZdU^V2sh4!yJkdwqkUi8V&!KS);rpQCTY%o=l%u^x2XQ^ zI&Ck%vU>g}zbEDdB{Jn#1qVAW+-x;km3CftxGbxA=l?Ewa^L9rPGcr1)e-eI^kDHxY*R9D1l&di?OG zp^$?+F6Fx2#hsNaH?OwO#!(LR40Leg(W8=yBgWw|3D`)DHCC?qU$NSUHJV@9ZTU@A z2M@y$qj4XFk2(v`cbi9h6>!$(oLO0$CBrZ2g%R<}pr!zzw$Hom>a4 zOke31%&BdB8~GYhN^<2F?_eNj$oxawJA~JTd?K-DoHae$R{A0Nn;&z>fgwu6NMsHY zp+VX*XhRx~ODA!!M)(uS(do^i$;f+5ZDB> zCpX4V5?V3H4+ybz_q6{Yz0k$!-2kpcIv0Q>BAR+B3I_)uSVx+Yl0o*;gnH5@k7O}| z3x)>+7vZ}lI2)werlNPN1JF3qRwC=jy_1_kx1_u*Q%ov-dT5i7iXZeJT0)wM{AALE z`XpKf7#crQQs*c)UscC$$HLJs&Fw9=)l!wKv=uj)Ny1zW#0?!o1;0WrcYiJit0 z%5VZ%^V6BYHu*?cD%z#Ig1ekV=Wsg3k zXf}V`FnuNRY^E?HShQJaE7|IX!VhB{Q}(7mrSkSiyzRZ;scsAR{G}PktIi zBKkgGx-eyhh5O(Dw^inx&$H5aqB?9*m%voT%#|i_t*oNM`yuQ-H*@#2s5kHJGMeJb{ft#Al8wF1%P9Ps9tz;{-n>`Nt?oy|~(Itx!^Y=(!rGAXy)8^}E z9fr-^&egz?`fFV-`xiW5N%cmrreRVF=}xoTUFW$ zLl}T_qbI5Qsk&XX^orz!Ga(~s9g6nz;mAJ6NlN*Qmd`GF3D&ej&3r~Zb8L3u?n-9# z&dUdTm+q<9$sO~{6QiTE@XgUHw5g48v#$t{`#O8}JbuaS@cO%N$u_b@+Ay2W0x}NO zs)M8}QyiM!xERNPlppa_#V*^TthHw(mHN#@t zo6t!@n%o|X6~p!w$|x~8IA3`s009oT4E2MKE0-5@qA`(^nLV3#QnENt6JhsvP&ZWX3*b&Sj3C( z;Pl)QtWALjXUGn9$Ho3@h4*orDYwau)GMerQoliciu&DD6Z{qRozzMrk}(C#3J{j% zR0P$xL}eZFjxY=mK{yagwQ-VUPr#=H(oBRhfFBSjr8MO}-5tUrHM?9Q`H4^_a%Zsn ze?N@qmVpvUK^NpS6M*pwgg**vxAD|cte45LNY5tX9zw5HCN>_CNIo&YQV|9;VcHNP z&eCg=-$a-X4)TeNOD_aDH`s*ZQ`t`1N=}8tiFAeJ9GHJzjR@%UlIi#4ut9Sn&Y=&; z0lsCDt{A`~vFpzoY$?IT;A(_AVjC)qe%$^@;FBN(z*l^#Q(zGiF`d5BE+dyn&yC-t zWn1m3re{E#i8=w@Ssm}7P)Jo@y8AX*K^SnW0D|y7*_=+;+cY@|4L*TOcwvkkt|519 zA1kbL$oOm_3g7e%OLg+SDZOgks>e;Ni>UfuLkq%s$=b7=8ByDDHgN1@cBDC9J^Hrp zvXQMFh`WWJU`~~Sal4!i;6yEmEAH9azN9Wuj*VD98!_#S0U*C9U$2*FIEh2cj5A;g z(uaDvm@;=sx2ogW5u?<2%RQNJ48r)#)UOm@Q=9~qw`Q={BUkO=Ou1CvIGw9qSrFDO zTbmh;z46jYE>|5|+Wq*ETsOMnX3k9Xj%H81`Yro2^Ns1-@13aMFh5rQ)5fa(AJ4++ z#bP|^GrFm4PQB*(3$J|i;>A}`zq~pzp;T9=H(QGCdeRbC3&nkbczbUD{%Wk3nucaZ zr%EjmMFSkaJi6yprlKn5k~Ow-t~1=#4nF*IAM5YS7jnh%up?Hai%Bcz%FOf3jq7Wd zkN0-X9^U-?^4#2VP7*AO`%CzGvl`-f%Gr|1FqUrlhN+b@F(`rAhIpW)$!x7A-NXoD z)gSI!c&K(qI%fMVrdBHPlgfS>!Pg5?qMp>*dTV24=aJWaYW{UjI9gsE^?=kE2Y-%J zl#M-xRIFagWmdACM%|K(hT5*{Ecj_z=@wzQ%4Y-Lo*i+df+)^(=A?t2{P`!q>8)tT zCPu4=ihgxnuUWxaXL|kTybMUjC{8&U-rcQr_Z))T6|A$%{O)#je7&W%TH`KzX-Nup zPSqOJ`Q4Wv`_jl`S6jbv^)r96uNUp)Wd)f;Yn z`OA_1Xv6u#=5GFem?`=&!&_A&oJYJ`SmO%0VR!7S;3?Lb2`5K)&NhopFb!7?bMc@W zEE!(d8XjrFvm;v7m4i>^^Mx7N9?BGoEY)sP#1T0Ip+jaQ`GheCq*{+qGhh?;gPbIGQkU!r{Al1~ z7kqQ$gBO>7Nf> z(rEs`!3S)%gwieC()7r|XWW@?EBZ%YKD0|4-gzWB^NC;Xe`D?KeEpffe)*nCuk&*HNxJJ+gRx>)`PG!urI> zUE@qH=$!oc?`iqmr6>8`zGFxX@6L_i91pV+vobjXFw2+-l8J8eK&1zE0tN1AxwC(I z-~IC=Ba>hRR3#x+JXJVhCPo&#ZkBI5)gf@!)5cHUoSCT~a+T7=+N~rKjj*MzA#ZzW z)y%Fg7R|W{TN;{IGY!y~-$B{-Lb7sNZRK^4}h+wZNPDaPQ(Mo7sQbY)3HFhwK{>6SdcyaA;f4jq--0`{HOto0>BV3#&8V51P3- z;0UN6x7+Pe-zRizJJ=oF{#S)R<=7n)J1*_GYsaI583|e{G6*@mr$arRMr3vjCIV6T z>69A$jdwCB1Q-B;PX;YKPDF*0x#F4I)cH`{6^kfW?B1Iq`Y|;Q;0+^Z-#WAKr+atb zE3N(ZFZ6%hy5Y$?XD2hO>v?|J;Y4e8V&Mee(b%-tw@SJ#gy4)T6)JUOB!t zviF&Xq?b8%+~P0wfBdTVz2V8{-~HtLI{m?$hc91M6>*lnHKX@#L@VC#LU%P&U0(fw z9Bkb6WRuOfXXob+vcG@N+AfXx`Nx0qfw#^4#@s^>EPlAO@6sne^7Z%anmu;#(6Q$k z*8ln2^5)Fg?q^qox!RdOv|lwXss8xilW!Z{*KYpWLm%lr%v+_Ie|R1Bo2QS|A1Lz8 z{#REUnbrO^yHtJXnd0=#yLQ19CxH9zBf|4Mwd1iJZ`$$p9Y3VrP5lD({vA6wcPORr zPkWhk+&~V&G$L5i)IuWm;6Uo6Ujp-pM?)5jM81%Ox3n+u$BSDB_R7{CFA1Ae9u2<3 z6Wiw14t!8`L*zhx;eDH5EPa5);vs%CpO`g+r%ogl20}`PMIL-wd|)s-Qt1b1^JG13 z9C^}cd5v&&H-fuB@)Xe+Xv)uQCc6NdP(rG)+X=5 z^ZhSh`)j*U*4Nff%p?5+$iaqj9@KI+yEw;Pr{wK(B6I_Jvc)46ha<|l9EM1%?junY?n`&3lC zIi9`slV5oHVz6#|yRY8W)F!mT{IMHWhDHzE|JfzIl@aZ+sYvfdSNzP#f){!`UC5S) z>s9aiu-m)I9o7y#B~MLv{htqh?!m*;W52a9=8d-e)$3)oe_pi@PAAvT^L1mzlT=NP zBB9U}Gzk&quHiBNn8biI1n!r!fv`NB!#h&>x_fW> z@cw%D^63#ikwYiuLBwcvx!?O;+y{uFn}yu&$e1Pw+Ddmp)i-KFK zp>RMwaHdDHQn4o0_Se>!k%wlyi*m4V-z|3^-@EV9eL1jC%6V&X)v?TkKN$%&Mb$L~ z%&zTv`p}gFes%2eQzP&E+|t^qU9}1?CB5Id^alsWn!Bz9Z(GpMXxUa*9=>y|JaodB z4W`JN2g=#_#1ZqBU59A(H;<^J>f#;uChEwE>BnBS`SQ9o-_tRXDNAZ*7%#fxA7@@R z#wUCBIb*UoXT94hzoE=}O4L%CVKqO4sTAzJa`^&6jvH^|4(-CaQn0xiWslSW-Kid0 zbF#}p`?c!19v8a&v?&HjwW-NMh4*fK#4PRl>sJ+Lin4_u2B>&|>e;YVE2B%=jvbjD z8~x{mXSsjh@ljM-`P_~#@Aw+HHveJA_jdf}j!gv7ZR#lXr>Hvc&(yzBKLqqpr9HYr zcTsm>H+_sgKZqG@**K|48rVE!rf(|)%rCn-)tgMsF6=Xyk(l4ufvgJ2Bq9=vs1IV% z;P1gS#=bCi; zA|uU))kgTmz;c(CO@LHO?L^?p)47d&U(_MY4@ll12dBv5Efszw>2Z95@Rp%Po(o>b zUKhw}B2`jhHc)J-JR8`Lq#-Yg)Pbl?#c_b$Q*jM-mrAHD8b`A4i#_=>J#4^dh%dGd z8Qf_q--uo%jV_baO*k#LUvNndx`2E+c#U@gcbjv!qDZfXY#^hO%%vQW` zgSc_j6#)MoE`X));MDL!E>cw@B4MhgcDILbN$-+66NEGk3WKzC&^kPc)K9rYk^DXm z`T&-9kRI=Q>FOU8dPrF&8UW#wXD?oVwDt5`_RfbWiNdRmY@{a>@ApRz+;Z33PaFky z&bUDB;X3Yycm4?H+MrZAwb+U*QQ-{EZsnDO(-1qF92ATk2nRgo)nmn-_g~m`)9po+ zV9`v}cG9h-rzR(tI>Rka8(XO_9jI~nslC_TGZm{8Y&E;tDkUzsj>~RPZ+Az$L)p4f zH7f`GuP4$f9}dSfiX^d$&Agw6eF%|^l3D#gi%(7^M@P;1v)$(5+B1^(^w`3r@QT*_ zXytC%&Kw&V(fmTE`LM3-pSvY0o6K7g#c83&VX-0?-q|`GP97@JqOE($x~!k<%uPx~ z#%i8?)B0mp{%zfZ!#B^`rN>IjNoI7YaB66ee!hEuyFH$irQ^T(zrB@f zhwXB4IJdxSvFg+&e}2_Ddg!Ra^#522f>tLZc+G>GKVgaq>zuO2*mzjoT+CW-@Jhxu zLKe6ke$2P6EL*p<$iLM84}CPt-wBy50SYpor)hUcTLZ@$AUl%+IFH~IY%Fji<$VBx zPK5jWPvzutFv?Mh2Y?%p;vBmy!fWj%CqHuOLc=?~f3YyV%Wv(itUt9!b;FI$C;fco z*Bc{L?Z@nFMIELcGf^vt$-3WCg}gltFw6vSJEq$5j(KrO&vc`3Hp@PkDA8EGIhLJ0 zegAxGtYep_a(Dj0($cZT#zm+4szp$F3$5ODL$}utk9C$$u1#e&^j0D7wcTK4ZgP$J zP)~bc{vq?4e4Dk4zdat82*jKQbfH&I%i+t0@`R64c9$xmz1UomTs%-26uN^7_@fl2tc`s!PMS zEgp!JS-tQ?zB9guk2g+i_`mHp!mjBLEied3<>MP&=fErz+sqf?P-QdyFPc4nMr`mx zD>6#qEF89vf3yUu75i^lc0xSQwncwf%Vs8qZraOpD_jgP+V7XgPBg;CtDPbM1G6ee zn5s~Bt%tZT@JUM*lkf+w^DYFVWv& z>dY`R!z?lTnCqAunaj)_%>B%(nAbCJ8t5yMgqMoaf!G#OAquew!3Qxlpp;L=IgyP2 zO#qX?HE}C!*@N&AxC2r8qV0(s{H?R1R>zCN7ZN=6CxFTUt08zydwx(K$W>yV6EhM4 zwN5W}%))y+uu2D~9{5C&0uByMNKYI!?xpeVv~g%Mu9tcf@d?tFG#KB!^g^{wYRajX zL41n!6RuTgrHi-<%lT=Yd>EL6upP(x=7IBVgt7&uw6VT%z|NRzRNN`UrEZvj^% zH%c}UZ82DoprND&5ZPe~fpw3xoEHYGLTJh<5Qh?<3tkNT$7!(~+{g9{=SxYGw;li* z4V{oWPJ8H7awhyskM5ECoq{&UO${C@G;a_0Nj9UK2agZR2Q5rr=%rM3fqJ$+P||27 z={lsrrRxLq2^j;zi(Qebut7@(A2C$YhLSrand3yok!|=J{hqD~$Yt?@#zcA=>=|MV zq^Jq(v8CzB?j)!OV+8x82d114y{&U0op5pj@j#-uQw#!mjJT21MQj@%Sk zKOu{~hT+4fe6?g*K8pI&gm%OAkqgQT0! zJm0BCUDi?<90ydq zG!L9XxzUEATpt}(UmbvAaU`EFvGZ{7h^;i)j|x>o2c!+KUL9PhKrx|c0PEY5Dv_N;;>17{qnrhb3^BUE~w;D*J7@5oJ}%~ z`ZUMpP-4eqPwkv|%Y7A7%NlX{jg=1apqw1|gOHrmL8xvfG55>0gV|0}0Hb9iG3&Fe zuREL~Rj3ZSB?yH6Gf3qS?akW=Ec&9y!XwSoMX&#(8`Q#H72#JsUj!n%EF|VF7p}!i+|{v!Fv`>7qZc}LbNX(u>dAWJ$x$iWM98vOC=BgCueA+BmV!m;t;&N_9sr2b zvf9y*;>B>a=8C_*5CaycH!G+hCHi~hUXVe$6Tt4cv#i>%SiG#A36YLBtc z6=m7z{|?+VRDeoA(NNf?nSy4rwhR=!0Omj78I6EJHCi5Uca}w|SPr#PGKIva1TH(a z)1z5=Q6Fv~eQ$_Yj_yMmBbTs^L-C#^W8BbsiG*R;qWE5ws@K;$c$5?8E>p%iY7UIw zLX`DDjK?J-p~`GR<%#{*DxqBn0I-q~Z>@m>H@2AKu-R;DE(tVw4AAI`Px;KwK;%sf z@FGI|F6=S*W%d53ABc4I`pw%6fvQUDwO6tx{iEJ9`?` zs(X*C8x)%)o-zsG09T_IGjyP}IR=XFtYB zu=x$y1+3j?|5Y{sgJh(J9L0f{b@aY0t`$c2ofMjP_rE(=&e5erTXY0G(?vk|Gl1KvqP~Y&)^t1GHsN?Yho`b`TGwaO8 zOEDqn;BCVFlt2o#vWe~|u^_1Vflf(6Yj;R+1#^pao|v9+-Kq)VVs#3D)et%LQRuoemFWnpQhwh2Kq4Q!UJ+a@knT%RF8|?DESTnkbxPJ2G=~|h9PZ( zzjc7*8gx;5+XFWQj(M>U2JohBON#&vNN1wEKzKlIYw&&Afn+-|PGHZ%gvEFGzvV?j zZxe?yDY?-F=8AYFuq$paHT21DFSR)RjP_v*-qVUEq`AZ&0SgFDV)6@?*Y?oCSxLuZ z;E;!e|I{QFSU5dS+kqK}1Jlo>#7S!Wr4KuGauCNfF{nrfrwvZcUD)REo8Yy51#+9I z(Vsqo?UoHjGr=Oj7E5=5rHCa!+AZBFxV_VdkMGh7#i`McsAV1fS0Be3gA|1%Lv}J9 zH|SQJ5MVY!r8b58ey;^XNYRF*Y*C~m-DRL)??Xbk<)M~z90DcDIm;wPDUWP-B!Fng zP-_b5GgiEsHS&?IN)dlg+1K>omT(l6Q$xUyY3eeVcDwg+*>GsM=7%Jm%Z&NsqY-nW z#3`tvn+>BqIx17;z!4tpwUD(F?2>7fsk%MpT-ZPgmDzoKrYU(*GrLC_S!Cw5yq(C^ z{dV=OtNMvA-|3WRkkY>d3SiVP(!e9$s3DaMkRnN8{>%l`+#ZkAZpNe)cV5m@^_V^X z8Lv4%!9TF}b}ddOctDMEeDl~mEGEWcP^K!dXx*Q?VPRBo6qFVYkV6k3fT-JAfEm%6 z(+WPS^m&wTHbrPZ)_iL%l8(h-)Xh9dQqHQ5U^4JmO- zC_^KGNf=>B+E!s9nprneGpN;VP*uSL=D^U&GWaKd5vs9-L?2+^725Esw&ENW6p*J2 z3Uc(3M5u;2fI^T)FXm^EAOdn|I-?`S54;SZ?MB0J@1Bl)%t9S$i%9MQCJ!2d6=lRh zZ4;c=x@_Qyq8KmlfT90|k1{qAyc{!teOE*hjnh+Yq zHy~?C8wNZcrY7qXN!O%^_48`c#AV0LcyVoj&bpx>>rkj5{L?7kbE@jMiZw1 z@GsK|>XJ2G%1sD$AVy+dzwC|*?K1eW71wBVPJvh+U?Z7EqN?D@;Ym5{#B^Ps+HAPT z{a0|vG{Tx;pFcdJ_y~?58`(q#;ScOAueCCI-l80FUw&#BNyhWV`+9H|!bk<~gog0} ziO1MTA>+M(AED%ivYPB)(gVT9Is?Uf0XNhtS==JhokLTdP->bbhrJ|$?H&qrBn6RJ zu9=0ZAf+2&`<> zj)EMJgLngVgoU(3&2-f)s4F<$nP&u4;Z{w_KCX%2=ijfP_pd#rgS$D40<)@ws^3UI z;q&uV<~JltvPi|Wr68|>vKuiZB)tJLn$R37!EqKk3K4zeN?;O1&x?}4WIZdU?Z#OH znH3Zr*rEX<`y`{H7H<4`lxF3)&2@=c=K%h6eev%Zr_6A74Omkgst>`Z?9x zX|Wt)wrpJ_^|%i-DS1yXkiL_X3^WJXmY#|D$#5#@^%tWs1C0qqy>$ubd9<9=rh@Js zhyK9tfub6Cw$d{WY#7``o`3~X?+g!@13sqzKt_WIiq>if=H?2oDR>*+KBcbY zd?4;ZIvhdK56u{&YhZW4+OGQovToS|RvUCx)S zQ-WDQb!+ZMf!s^Xi*X*rPO=35gx(O864H46swo3_4nQ$-YZWQUI?Q1grAC2!ha&yo zS_p4Z01uj-6c}WmCx%S@kp<(S5CbvEnzTsODTNI|QX<%4h7tZlCng1QeXR25GPh^c zG1ZJz!7m_l5t^kS1??`VW4r<&xh8c^89Cp}a=b z2CdBrqa%yvxF$Nwd1F@q^taJfqMIj`nkYC$!=%bZEXEC}`UI$fHs9nNut4G9^#5Di?M~u8+hi zYbH6WXGU3VmD8o+t}?8kRssT<5z_#dK%@ks8X})vk(kC^n8xnLF#smNJWqY|UH z-VeuJv|ve^Drd%kgS9d%C`W-_JED*47&+H>tfN^$@q!V<2K|UC9yW5w!1N9pqXKe< zBcUtpWX$lMuzIR-##$UY++jj@?{cHf{E{@i8pQ(Hc6*T>@QD*ZST z45lpc+U=kXkQ*xI(+6eKo>>KfeAxdw@|qd$5N;o}k%Y3B1?!o`8VwQL|ES?w*^JeE zq)!ZofcX1x8yUeq!_sF*Vo)R?C$v4zhsUx_#;N5O2$0Q&pWzpHm%D>HMV+Vaq#mUI zhS>p&e}p;2oI$kcaprx@XP7@?{*w7S=DS-S2C`mFV+uqWprj?l9#q-nU>lK|ywFL1 zLGKVP1?ih=0`d(}#SoQ5{_+GGP=|m_QST7iTbdf)a26??id{mb(U%?juwm;aalp0;PdlGzq}qSq zvc3KPw>9l1(jE8!QsnecSP|Ro(c2~#PDf%aq}_;&ZQX4zz1cxDDIF5EGHx~PDn3u8 zro&5pK=f{^K8Zy^Mio2&r2oL_o%STLZl+-W!SOM;doq5AeK8n@shTI=m94$gV^fo1 z`%{M)4`ig`{AtP>03kR&JrJf^5BqHm0x}e!{^Mge-^{?5wdEZFMLYsM=oo(BB*E`=T#{;kFc;7bFmTYh82IE5;#I-@Vmgpb zXh`~>L=VxWq@~UP--vJV1Yp9T8`7Po80-(fOlrlDjX01PMCsV*rEryUdPbZh-Ls5S z6SoaPL=$fZN`m%+9`EV`5{f*AR0DItBSpc($XLh?MOhNb#FA9bE@)#WxNE>2BsWZm zYn1M0SOC~XI*+xC%FtLLgQrbJZ6a~V3^ZsN(I`j_Mdmb#_Yk3BDIWwYiipyASjsC) z1*DTtg{-J?5x{)IJWLqfqCHb3JVIdOQ+O^wn1waqWl1!Rmblmp!Dch6B5gIdGb@=u z7RX@RL#?h%NF2{L3$93DlN~Ngs|n*vdGHH4)nXUb!TGF&d$>2gA zKpF-GT@aa}%e2jna?pNE9>g~+1T9rzEmS&p7&;=x2$rU>Zy>?xy5f`MsDuyzN;wJn z&I7K8ydtRgIN;erT7_%vJwN8`ardf3bsL7XF-2D^I*MX`~- zi^xAm;c*~3gxG}}aWPylfj_OnJ)#Pa^3848b6bi8otc~4IQ&}#e za*BnHQ8lH)Bjll?C=kl0f!R$kJ|GJo3ks(YCEEn;<**$juv3CZlCiOC>*jEiKmG-P4;ChXbp9+!%(;=i4rmt>}dKn)XoE~7qEXiS9&wc zXy7Ks5}l3zd{mX7XBQ6Jhrqt^e~WqW3=Xkncq@F6H;NJOH+V(y^q3Q*2r(d)3vRm+ zJ7L|EU*;5Txt2r1aZB=OlPS- zk-D(v5CaOXUADk(a07>-<0Q>@LhG4Bg5hjp`Wx||?*qQ=U609|m7EuJ5#^v$QiTh9Dj2e7*2=DD# z0N;+iJ_B|pK2%5GK^2)b(-h|d>;^vd%)bIw^n*RRWmqg&u5W!Bs=?@u!aEv%#$1l zi*RnWn7`>b8LVbor)i{tGC~cN4k=cc@>gGo!KzEh(AX79g;x=ge2%(~@ogmS26P4B zVwqDdOSEMTE>J_!g8rxM{m#u|oiJivx4%^cPZoT~)Eom%ATh{~7ITgI8gL4DP~h8G zG^WR-{)Yq^VJrbA*>0>k3^l{TeOB!(e-xYp~`@oK0q(W+q`UmR! zVC*yL1iAcedYqo4S6_-@rRaEA7DUHwzo)5{Bvu2>n7#%U9^4eEvV_tkI7%NF)Pc$) zHW1kcMU5@wJLq*pV#se|JCOG+)lJkY*~=URDWD9=9=JGhnvlzE9qS{`03!@GByce> zdj=Ji3F3w{^u--v)J>4SATQF~fuTn9Ie8&PiR=xtiClzSi&z=k78}V;O4~X3VhY9q zS_TV{9FQh`;CJfD82A}*EKCbrh5WhY+8FGgb`r4|Qbf>;jYzwWTs*Z)(_VlTPiz5K zuu?V<7*}b}pcy1bB8By&Eg!TW@3rv% zhZthPl<-?86>r~-wS99BbThYPDz#JY5x4urQOFTZ%0_QOvEIBLYs0O47{!^{@GwKy zM*V8(rUKF4!d;{~t;-yL#dUwMY@tGHCZpExIAu*K?@$+yfAHm~aQEKVRLq`P znHXtyFNf67rO&RfO|I)Gbou%jjo-}%=X{i^oQlTY8J~%t&MV7OV_L2})tMOe)x2$1 z)bRLYE9|4wLtdd;E8P-JtvV?9Ygj@oOr4~S!s*t>Tdf#uuZV}_OA3%Xap|yc79J?z zCSGg2PW|BvpSDKt9LecgJ=<=IFhPS7>8*E3~LeFEb)K zcA1Hdm$maXRhLlVmQ#-GZalX#%+4^&FO zN0)9@tWunTN1=l?W*I4^Z3@e6xbgM(s2*0;uql|y7J^2y5ygtl{?gDbojon1fB!|b ztqqBdeV3p3wJ0;4&tzJDav)!;iK41{Uo7M2ZKmdd76RJ(0QC`jRj zaBtnFz4oK}+F2L6C)xAl&Sh&gXz|XCZ|_X#BeTJo;I9ALXwK!Rrk>pPJQbX)Wo9d# zVpa-gZ~WF;QsPxB`%C{kex&>kxikYCRPMcdt=*lg=jR)0a=G2;WIkSUpMLCs*Lr`| zPQWiWZJpS)y84MHi{)VpB;5>KL9Oa(*v+i3I6&y+i{QV4U9gGP z8qS%_wOY-57LLBWoH7^iFamQ!(tkh`51Obu>VxIXyorOnXz;KS@p9)<24R#Md>Ni?_ci z3Py*`O0!h5#xsXTyIoQo`R4tLM>fi~Q46YGJ-ih|ghSj5I3C`Y_JAP50^d|Xd&oBA zi!kGnZn4AIvE0AHpX5FW*CG;p=mYe1^rQ42)8C-~j{Y9qXH0}ivP=s)0n)8DC>K>9 ztd7&Q=2i;nK%!z52Z2C}fu(C9Ar*3il`aGrmas(ZdgU&WJP;m(<<-_s17)(cECz^? zhh)K5sfZ+(PWJ|2Zy@7pMDnB;@dMx_iAI6EBHIQS#BCWC5R@wUHpf7O55ynI=}3Ll zaDNO0>OecZ=)Nb{f^!?E11tbOa$E;;6DLY<7S|zyGHow>_*f5Bm+VIKoOXF&Gg1wUI!9$EH-}&W3!{uZ|$QJ?TYKM-20^%!J z{;_oXl!g4X$Nsgk^Q6x&8zrZ#EA9bD=HCqFxa?GZ_=8A&K}6!NtNzH#_guVl!Ybe1 z-EFs;V88)&6j#eVQQ@oJ(hZ#Ax$dj)I%axmuzOrWK~fXL(~5Ds*QRYwfVjP<1T0*6E>wKwD~H7eJ}5SjCa!XKSKo?Y818ak(=YnfYcX z%oc)$W3CtJVvZ3#$Lkd8R!=a+*=e8faXCCtp=kwlsrwVtm44n z2LUGlaiG%$873hj!ZHbvFPQZiBm=ab5MGM@2I-&xMAtlIVi6qU;UE7iEEhhYH zs-OitiBttqq=Vt{y{03Z-Dx-bD{A6}dMzi`7~4lWi<}?(#89K1k)tXgrrx zpOZXS6h?tG0No6ipD|U{FL7ZEcZbqq_mUzEr!;ff0tFMDmp}U})gf_ofA)BOT6L{m z%N9cVwwYH7g$z)VSPa^Zp8)|JWk(rjk7m+QoSmCL>1x#yDDAt*7jm<%TNDdnrYz5* zY=s==RTt?|p~+n-CjNyf3}L2mY0vKQ)Bf73e=(tf+5|~42JLi)t||!ULabGjG|xkRJyM8xPj!Ys2JL5EZi+0i3PsJZ zoQM^(<*{=TR;Zx4r*pwL@5v?9NW~I%ZEiy6wWABWlsc+XO9oSIIh5jCQ?~36BaE`_ z*NmM8HF;FZgb4i^%d}UlSYb^GOwdbEqKUc?sC&R-peP37Pk7)3u=a8wbkaS^04=H* z>WaY(J@6Wu_3tpw3?FitMCg`-Ol?tUh|^N`GhZd#2bBgqD!t^`b;T1HNi^(ZH8BfD zHfYN`od6v1lR0ke2|A}RR<%0A>0Uvs!JONP+5p0#Zb`#{1if3%hDb#EeT9)jS!2$4 zs_wD8y`MUh*Yfq$!-NsS#yLfFhrIuftoMMER&Ip;Lf-IK%4%+Bo0 zrkM@1S*w*+(kkaw@LJJ9l4V<#g(L@@jcwV;JZwK~W8=g#*uZBn{=mjRz`z3@&pa>> z`1r82!~5T=o|X8%Uv22FaPO_^y62pG&j0+6mdGmX*CH{Lq(EM)RSv6i6dnL9^?@%} zzy>cWQtil_Rk$UM?5b{QiJZ*VP$ajd@I`utW#d?P=j6mK&dmMKFomBiyz)5ehZIG( z^zLkB>snF$V01mYZj}Gj*c$72aiNh z6NRi_tFTu-#NNUk8u`x1?~nW)kePQ=Pf#yXpQ1hsX1YJ2enR~{^-t6*$P10qNxF>n z>?Qh5Kx)27f0+IZ{W)cQfCd&zW28Fc4p^~F7WCx)f;Do`SYV#`pZ)vwZ@_;Pq=~@#fB5|*&|fWN^;cEOAY6gxQ&T$ut?x@BYJ{J!{0o|Hch zG6sxoFb10_7*8-%V(Xw^3u%{NZ%{7SR>%1m~hw2KpewL)YUDpe_M&BIuPwRM0M2@%tsP$Qg(?k#D#JHy-@T zIN7%_@r!e4jdY8%%4GC8^5XV6J%zZqHgT<-$cjuRNl{q^4l+ceLF6B?X8TDYlU;?~gXKIEmOPROoye_wxFiJh3SioMvU}Y!Mef0&G@hs5%y*JJ+ z8M6phD`%j>#dsAW5k)6D@dIm9TN%X;*QDi=KCOpR5@?9|J%ZvwL9r3=SBppm;S-c8 z=t4phsZ1!DRCm$ry@&cg;FQ#J5~OT07A_=v`G&m-KfDD-R`*mhn~jZyVtRD@Cg!$z zH6vA~ESF#GHD9F7q!1W_VqE@*Vc0=&E2;$yMwrGTtnfaOs~q9X9#pV-Oznqoa>GMmtPm1;i=Qt7EfOt^?h|V9 z_^na(mkzb^nZwCv@2W|zmaT|1IJuESgqW8BwS`*}QRc}`K&r58RjY>U$Iyq7sbFTht*o96>}&=P%oOmfeUd ziL#hxTMi|gsj7`Y7Zp}L)w8RgrY+RJ0IRoQnrH0_Vk@+rV-juxkYt_#^=~8I*NOvB z&cP$9JZRP0{~$P(eQqz>Of;3Klh>#XXfD()N-nB{5U2e9t%N? z(k!(&Y8}A|hhLKm%gla`i4i-_8JXC=Us&XROx-~}PMgfG)}6bzH&0<^fF*bM46e-H%4fm+vOXk_!)T4En@x<{LVB;thjZC8~CuL|@3qq0a{QGBh9&M(`aYxRyAN$gPm?@U-~t z1;_j^{&@g$CP_v2Axy-%67m=#YB2&T$Q2>lo;+56EWUk&9(-HJf8749klP8mB#+Pc zr4ZC;IZXw~T+%2WD(;jFh>YKVD8cjT1NlRegm9O5fWC{U43se`KD%RAkt7@#i)e9Y z^Is3&gzunj+>(DiX`_rsPE0NGiTnt@#R6LlPmvsY-FZQm#3>h~$KwJpt^4l=@izI- zzeIA8$!Lg?hE8DN;7-Xu`A8+wBI(KB5W5Oq7-HLvmjIyxJu)uC z`_{!zh3A5QQ&gXrYI*W{lcxj2*=iGpl3{Rd2vWgQATKSsE%Gztagv^c2TuN-$IsaJ z50S(EhIV1%1{fc-ihIVh37Wuj!kPpR-^S3!(Gb=sE*=6gnM*JZ$@Kw5N+1s5GTuwH zMmSn_wqT;5aq^>+KoK$6VMhNafD{=mff4~ygiqw9^4}J$)#3TWb>QpAWn@l*F?!7R z4w8k8v?Bk8eLmJ?Ea;vCMKpG>zGrI4Q;GeL# zrDp_Wog94%)v-`rM*`#o(P}qMoIPjbu2&Ov&saSG(qV-1B^_`A;8{sg3UF)*s0M@j zk8HEwu_P*7Ow3qtsi?F9aDdL8+ecMUpNmpvHZ-6!Q1(iT!d${S8r1oKNXG#994x3w2AK;@?XT*#>aiqZhfGHP~NW5_K?mIVQ`okeX=B#9&8_1K5z&Tfju4JbsC)X0kBQ~peDbP6S4qcp7v~k0LOVveaT@(?p zKw-5ga>D@N)c~XrP|j8s=T0dx=PCv$Qy^Lu7;j74R)? zlxeSm+He4dvTTv&JS)_KWlln^&z!UPP8Y zDa-@ckYidIzEMq#sv%G33>SAD7rOZw z_^k3Rk@JCv%D zI$8o`JwU?f23<-K*a?a|0OJZw8pX;XUO-9<0AlRkF~{>bBn68$;KitNRSH$YCXFJy zizpR_JM6r&U#f59w$F(uP|AD|Hk7DI0D_=oiy)^#-O~mt2oPMpO)bG#zWkL)TIH!a zp8*zJl?otiTa9J4YF;ftjssp9BK|4KJ+4U4BVpzdDJf+|(UEejM{V%cuyEa^N`*6*^38^QYo< zp@=sLc*cC>s`0CDoYa$JH$)`7jk*j+AdT4Ls*Dny2Ssr7zb8LicIj5T#$K(Ke~=mXhtk8z2|>XbGxl^1f&IIRC%-km=rkK zsDx-E(iuXM1B0TyK>C429!v)FBpWaHA~=S0LdoJ4EU{@#PQ`MNJV4;lHX!#B z9M+*^G%5@FL6$CpWkPe@ri(Qtm%|cEk79P}Wie9JJkB;(c@vfGqWDqG0wZ>82&ahR zC~=jn$YdCitXzN;3BxbX!yRfWdte5pQAS5tXEuJ(TC!8KkC$rf()8$*lVEgPr%H?o zXF2tLWSF6F64)Jplu=<$%Y0JB+>FPaj+;QDm6%|iP(%Y3NI><=h>h||Nc6Kg!GKHC z1VB5Y_km!S#+wq*^T6F;;-d5qrYOqQgiuf?ZeUpkL>-*|^3Nv#=2HyAX^r2;&NOBp z^iXA&)GFgd*#a&S8H=P^S|e*R%VNf4i@?ZJIpi%8tUYoO@kAM~U^9hAL(u z2NiZGYmydCDJe&IS5zWy7u`ZOVnv9UG}!K0EsspC8A>c<3q?rft~V1gQz`3swl=;V zRu|3Tk#<<- zqjFUi_-Bx$ATTx{NLZLo@~yHFJ!Z_LL<;_75irI&A`xjo{;GxSp+l!Zq?e}DsHOv}2dCi_eZ$sT|&ErTSG{X;Fal%$G?=yN~+ab!GO4?sHw zb#iBis3* zfuBM~J{`SXm`=%24V969f;p?GO}a_FGD;a~Z`7NzEp0pke4%EzN)%ECP!m)cA{=X+ z@2fW9tNuOVC%iQBQ)&cNwA(b2Wl^uiMRn&XdV$`cuR-SBee_fCPJe{{GVwqAep{G9 zulb;1eBQEiNMAZ*qCy?DN?Pf+f(fksVOE`mIW%>6-2IV(S7AHHShqXA|{A-3+q7DEOA<#YP z8Fm`_CO&X~3+&w;fB<=31OG&(Ad z7DnE@N{1-VNryF9Ql=8ku$i<~IjXAH!T{|di;I4vDVX<*B_>QrSXswUW`Yqjq@|#w`lip)y;DI^gCYZBTk% zp%7YifO`i>QWmMhx|QtSuzL2HpSHGt^Lm9y#lr2yi&{F7(3F_l zu!~5YGd{a;HwXMQt4ZEyAq?lV>>xo{*5Vw6Wd8_-Vpj3^b=&{QC|O3ll@Z^;3fs>S z79ET?3=eojO+cBWt|9E!izuaQUB2vei^Yj_PNhB{?J^$$b+;N4#5992_Dv|6ZDgK3 z-E9`=Ujs|Ic=QCfVQT zN=HLL5X3ENj|(Ck#3a)o%&`=w8(WKh*v896Rh_o@72R8HrXyJt22XcSft>(ES16B~ zdGhYnk2bd!VrLJhCPq`J|6I9 zYJuVhMg?W&plG8!T`eC}@}N&BE??a})X83FzJK3ejeca}HgAwQ*!^YjZE6HUZ6>w0 z5))@v4%C*AUA`7FlQUAjULWu5iRareknF;=kgZMSj5=GX6^c^Gji;0|QB!*ZLM0F*Op^t8wONc5d)wa<8Ce(T^C;X+^DYt|=%aIzT)zA) zQq%Fqvq&C{ATKpce_fuu_|E3c`1L%u{a-~7FO?zu#NK0r2e^@GjOmQk`41IgWj-Q& zIaE~OEM;nuNyMxV5j;q4J-6~clm@>cboAXDh9LH<&OQ^W_MQgLZA}Y zHjVT(@=S=v8q=^NApJ$+3#!y0(4{_M)|w&{O(Js}!iREmD8f~cy$wBLWuDX1*i@4_?}!@79z7uxZ;QQDE1FUqECf-u*;!hhNVGvvNb6hKnWafBrNx=) zQdrzabE<39+93+%*YybO_}#4GwZY@DJMArJ#oj9*rTLzo68GO?uADNE=tT+x+jM-Q z=+r=N6Okio>Ha0Xa)8bQMa;-~2>~KUv?JlDY1+ywUlZa5?ih~Rt^4MQ#U?_l21oz+{cR^%>QJ?{aoG=FQ=RXHB@Hq;}=N4ZM?v8W< zl+W;<09?V7Md$(HB=km_#1TT{PWr}}e97Vz8&Bj`e zJUEd|j`nF8dNj=am>NoQ?OVm7z10GO+tGF*H6^1);K z4&2U43m-fbMpEmhTU({LVvYOI^9LTWE7P_Yjm~~tp4Q@fP8|5UVBHxt=i43rs^#2R z$^p@JthjLG%XK+i&2Gq6I2X@OsluFKTyrYvcJs?-I-lxpN=Ske_QIBMq_${WuUC0_ zY*eqqde7Dwr;WTdLZu|6r&r=&0Sb-IEw4S$=+`6tWL%@Ij4rF9ovFrUUb#-qPbp)x zL--_RoiMy7@>JpAfe9v2Mmn64dE2UY(HU16IF*l?p$Rv+wCn*Thx*tpz*0PE`$ZZH zo5EVu@W6>EG?maR|5DwZ*DLdLKRR1>Yj9&BBNJke&2v%qo!~D+o$(OTDP>mM2Kgx% z71ZV=tx2eSSj@49e`_@qjUo9}-v*H^&)M+^uaxRsnz@dXQN4|BzWQ8eWpN;#Ju6Ih1#&r$d$PaZR}DR_d;>dUX54G+!vghkYAO*E%!}`xt;#Rzl53WSbc( zbLrx5y!fJ#3%%o7b)mu6IX$GeAy88_mAO`0cmjoj>`e5twJ3O1J_C#hDs&=;Q=W59 zIocr=+$!xcN_=Yby`HFr!dc5jA&u^ITs6!dz8=fIyL|2V()9FdDUXQ$@4ay3iFcoh z&X}zM;1%6X#cMalPoKMo>b+A8^Ga+{b5A_7x0W3%mAI={BJr{_$164D4Hl1oAO*6N z!-IMoUh8Zvo{TG+gW8SJxRIDsLCrfC&upz0DbRZ=SiV+3#6&YOod-#U;}-NpSaX^J zh#`@Q>bi%{?%gYtH*UVK*t~i9;f`dtQ{xNn{MaIU8`2>94b_)Tx_4<#;IRvAG!-g?6;77J!ZBmIubG_>|6PjyPiIu*p;yU-H4^+ ztKWF@O@+P9%fA#|oXm2X_kX1)6O$T>76$S&WSs#bXSI7Y`6utaM_EU6-sdsp%K^@mrDM!KKH7>9;zfjB1rLR$KwhDc`!8M2Fr z#K7G|_TVoOqP`7IM4c~hiP#(J333iOj5LoxP?LXsDT`(T=}OFOa)p02sgOqGBk74; zyVD*%6D>k^_Xlp@kGu}RgFQUcKbH(7(1w9B2|5Tmd9CxH`(P>I%SnusC?*0i9QK0v z8-@hPMj&OO1$?C)43l($UWb|r_cFZ9FLyykFi*ko4jvnB1O`7jm8i8V7m}X)bC8QeHI4IKt*d3X;)y@-JmRX)^OMRKELmvv>Auh`IliHGHLI{@D5 z_WdOe)GQuG5$Xy-xT|1>}R=K_Qv}8F2cG7 z*juzhc5gHmlVpIs|BQIdEI_GR&9xig#lblAeAfHsSA_b z@$%wb3s!8>Hq}_?&HZpf*`KsZ7hk$?AiIoUdt%&3gaj*1%Zd6RnRL&@o%y?$>y>uoyzl z?7qoes4}m17gG18+?o4%tF|_pdn!jCUGK+$3*(WcKQ1BIoQ*5W92eUTRnD9(u@7IL zWQ~jW+;eezzH-&s68FgMu5sVpci%_Z%NwWqg?Fr+wvf6At^$g`1#%2|lBQ)hE`Li@ zU9eH!L3YK$*z?^^x91>HJ$|ZE4@r6Zh@p1V{hK17Tvv8opRIt)^5z(CCo=JFCR(aL z(>{yUh?FaLb?Jzi3T0Dax2%h9VWs`e?;kmK@0{8JSB0HzuX+6=`t^(Ijn#dlOOc3^ zPUQBCt_hJs=7f3M{7j|ws8k-+saPx#vCM>WJg?@F_e~j+2M{M)KJ>9rv0h%y8g}gs z|M%|uXP4ru2;WYIL1v)IP9r%ucGpU>I>)6Z7Td9?*SvAr`dB636sHlHkPN^O<&0L` z5{u!&0p83vq(|%J$vv(ZhhtERs^Pw?Pzoa7ax7!*x>Yiw2NZ`9JZnR#gJ|ojkhECy z+^+Ykm;d05G-1@Hm2d29xm;VwL&xn{NHYO@{uvPe1*19v>f~MDlU_7f+l} zfyhw77_aUJ$$prd9X(c*Vjj2Xuvuny6tw~mZGT31miy_*5#+r$sHxYllpt8ik_M`0 zXTu7~S44212cj=9%*l!dvJvtJe}a%iAPcYjOO(oRyRr{5l-T0ff>?sDE;=M}3@rr$ zcITp<&Vuh(bdLW4QVbS)AS8WM7m?M0B0@Vup-u-}g4pd3GthlIn!y*_K8gowEKGD> z;FR%4x}$U;ca32rfvir*{ouF{gYnT=L>>#zcBF*6zHj2#h5Q}#d*_vGkc zA{LRO3bPC<;62|_gouJID^5S`1&qBx%#%#H{8`=gqdR@ZDD%p z!0m-qwyiw~Ysv_3Y z>FG&*v)w)9eIP#btxrU|#d$TMTQLxFLy8LISLQ zGlzC17%R-WTW)qerE`;yQ^3%~qPy->4)C70xVFkg-hbQFv6YSWa?=rw=$PA56Un9Z z-QhuYzLl_@BYpK=ZSnR88pkMV3^93NfsxXEHk7U0vYOQP{@sDXX5+{s$IgnmPAF$A ztLZy6dpA$d&Yn!p)>02Y$i&n7XarEMoY(0=AnEvyUSy!ysTe5Qa|bydcuK1&XV`dZRNh#YK~J+-OpZKIXIeKN=|{$ zXT8%c+-*^^5_Ow3L_PF&ZG^(Sd!O(!_b=2s@y24UNi?|s!r{#)bAp&wS9S#R1nRms zd{_Nbe4_+A5Zbq`5kfb)Gg)_zIDu6)G?$3w1^aF1An)5?zHwv|YY!CTemxbkr%$Z0 z9ixs|E&fL46J{qSk^lXQr-J~`N~FXNnFM6Rq#?k#5puGBYG6tF2N9?xXA|ZId?z** zTqI+oq&g!xHR!|dGH8z2g8tWWoR5b?8V{!TaEI?OAP3N#@G@`->svS5p_5b_;cw9K zGvTjp0E{1*nqCg+ylUQX2lAIAw`DVRUP`|E!Orw`i%X+nZu@gZYQJpQTCB7GY!zsn zUN^pcES#SFi;!Nsn%Y_P>et5zzteml2+|E;WsDAew^BooD4(*F7uAEo$CxIpcH zM>=0Y4x^sey<6YXI)3+4lK5c#z6GhU{h?T!vc1~$or~cuIyL`uZ)I*`!;I`+MZG|K zYUK&996q^qh7FCXF&CvP*|>w^9JE!T1aoV;$oxJEk;o_L{$wfI%!T4n_uz*>AGzz6 zOUp*gY3}}oj~Z09|Lw`i{bh;y{;wQvWeblTJ-R*~rjBGz>)ZcQ2orF2v@tqM?HvP; zP_3#L4BfsoFuc7~uff~zE=p8+W_qlr{pp>pg@Oax1&}{_SsF(!viGZ`8vtnw@{q?Qh)l*h*xt^KZL0_rW=Ec-Kbk@Y4Ca zLN%^@{cR@@f2)_nv2df96(%}4J6+Nx8wAFvU#+x=FOkf}j|iXP{@tI8#G<wEH^3 zpCVX};MEE=hyM-^lbM3o7z;Ju2}Lwkuwmw0acu?b1!8LN93B3T89*Qlc$NJ)_c&Q` zmGa;@BPO~(grSNhFFV zau8*E8B8BCjmS(I;DE2l$XJHMAPY&H;SZANN5^K!xdA%BpLm4x1!oNJ)#tsz3>18b zG8(@!Tu1nfcI15Gw72!+JfT(eR`KR3?XxN%1_;yHHo?W9+K2|C<#*ht#+|?83xLx(Sm=P;)t?(y5$vK?% zo=6lPE(O3%aqK3y23ys9;)cwL&D21vf|nuaGj@%|^bPiQjtCMpr)KD8E;7%N>)^d?W!oly={>wEP~J!I;ZB8SHLlq(%J z%(rk;d_ARrmt`aMYoHxZsZ4VDt`W z0gD@AE=C0WPpSiw`7pRwJEza2AFL(L?WE$kwKdcX;utdh^-l z8E;I#bisj^@5PbZEuCFQ?0GPl&d<%Cw2V3Y?rkxsA1^jcz76R#x!TDG;u?%fBosOMl+J3LH<=quc)|d@|nSyqHmk2Tup$ zN~V^N>Q-Ghwm;dWaHow_r9sQywOs1x$>Qy|A?}k=J_`bMKLzQ6*-b@&7hYJcJf*Ps!pk3;}4uz^3PgBk2Yh;SUc{ZgX1#P{p_2%zA)@j*oReAfVPgJc@H zvi|-z@Y;uNxV5S-PiOikXW!|{Ivl%PJj1-bGfnA>b7@^VpI6f*)09vUHy7awp=?{y z1S56rM@s`%n4Z6VbLsTf+U{%4-MOC0#!_R^hO0E@-%&kWM@rsCKiVx7+6gYcd2Ogv zJf0FbiY6%C-byImomdxH>fm$-?>*D*GfY)-V#X3zt~5aIqRT0eIZS{vFcO!`9Rr|_ zJxSFLX=6glQxiweS+`$1U+`jdCSf1n{^x5uoW<(JiJR{Oc9lJH(7f@{aI|}P!+zk< z8LM&?&+QjapSfYioy|%`=K5FvESt`V9*Q-I8bGn{l)MlFzi)!~Tqk8Oe&Qb^_20yr zc<+z;i-q}q(w19L0;( zw(_v)_0u=q`GLE~Lxz?#pPq4=lL8Rh55HKOkxE|FGV0fvoAo*)*!NvnisOhDs?D8E z?Hc@-cq$XiEzTTX$ut`MQC(Au0syx5fxT>2AFD_zqPo^bByEGmLSXmxotl|sI$I4dBLZvJka=92cyc^ld(&Xk-#qF1Figso)sEZa~ zh`u{hR(WP~`#WiHggd6=#J4{P1bIh|hmx|Sg=?RCFlHV7cFp>W*@LB}9LULI^m`mH zg7`yreEU^(-!l3<##X@6HV7aL@s1BZ!X$)3Qt~jGZZpIX@I& zL@4=NVA1%t{!kJG10poA!^=3|xl0B`2;b)zXd`nf*d#C0~j}m&4_2KIXJ1?)>!^oXU7Q`pS>fMynQ% z7|grY=2{ayj-82BQg$ch<)lpJS~mJ{Rw-sD_ik<W6lAgzLBiNEb2EZ(O4*zkN`4& z&^6Nqs($%vr+(q?n|lj+EgC81^4EPRUyV4qR@r(mGkyPt?pRAwto_U#)VJzaN0w)M z_s(yeJwE2B(j*cA7ALlg8%8dx@4Zi(lQR7Y>iPEj3fcPH8wT5Avyx90mKUvbnjRe= zk^S}YXSshJxpU-=BTtRIK!vHRsO!N3ejoMl>wQ#sLxVYr_uqfhhi`tFy!SXVRI5aH z`z%;?`!Yf4dEKc{& zB>{tA!jWMN0Y1UJB_@i$ng|5I(2ggkfhgd0#z@(ppTP)xnGwuIUpEs*M;FWNVDzs$ zhYSM_1HSLu^%w!><(NMrKg{r&l@hd$+wwUINCwwyeumfL7KFxuJS|GV;E13>rXAc1 zE+PeZ2#8|{pRFZR5A2ERJPo*&CZV+)K(4X&BL?oe+m zxlAMJIHVXSw_c(`S7`}n?Vy3I8o>V}laDAjJg!;iE|_YzA*^+FjlKG_+n9wy?=hX) z(^I0Q4pJ?ug;%d$jz&UV-L*{^=Dl;=d@alC+;!VGC+Dhvmwx!k_UlW%YP1{Sb?KW#RNevOC2@Srgi$` z6Bo9w;h6S&M-N_z&0FeK=Obdc`Rnc0#6)Ihax(N`bH6!m({p#c>u=w7Jl5*6;vn5a z$-;1`xViyRTPUYQc3DWw+%PwFTx~hK)8+i&=@xkG0w#V2)S6P(ySj7%-+ z0hCgRo7BH9H;M_pn9scG0qMx6m_~xtV$vI^e=V&wJ}W=8E3=uHUQdQ1s<;q^S3>40 znOU9HOiFwBJ;iFRWAkxdXw6(hMcmZI)WIpuEA*$gPAEzeU{0zoJOdhphqq8kAyX?n zU6^?A)bZx=gC{nbcTKk2EkjuuJ$^7Au~rMk*sf{K8BI;+CdQRUdSzl_QU5{?3_8i=lu5gu~m&BBj(W#l<( zh1#S}LuTAg#DZ}X;f32JQipiw{D5L` zqJP!+fN-l2N)(`Ad?-A2qaUFkRJ0ol3f9>K5dWit22k8n_uSmUtgDHp-g`T;Y$#QW z96k`bM=KpWR0A2X?9A9ej;rmDTmZv}+*su!umrqhW>?pVw)tv#09Mnn<~&mHiDZGqcVJJQEX)P+Nb3 zb}LV_mRGap3%T5`iz^>!F2pxSUv8}K*}La^xm>X+ zvxT>89o_!HQB?HMTu&BPmZG2emMSB+N6^-GTaEpWlQD{V=V$iO!dha#nL;X8Ds6?e zOd-+aV)+(|+|&mP`HVz`3gfNou3FBZ+R3SA^8Wk<9ECO*!3D){~+4$11z6b*uhxvD2=U?>IkwQ>i6WrOMT9nT_aa zrvr{^IuH8xWc=*YsXLPbwe{}Btl|_qv1oc?Pq=sNWHM%b=ZBiZ7r};Y%q$!K_J(A0 zeD*XPiq_WLSnGW!!jqE=dm{PhZl;{gF3oE3Oz8kv?yPTdbx!o!(uGQEG*ud$&qi%d z31wTL>)<5cNAMpJ;Y-{fjckql{mB1IDb!umB_N%j4eSG7*%F;SoZ!&kWP1(mMrJfT zaOB-4e_xxtWbV9v{*Vdbe@C7NkPm2t;Ds^cceH&S8P&vT;P>DQ3}R;ZXZpg%?|7&4 zBmqE^LHR)|A|Qz3CHs8u{FRxRMMRBkCg>W8`wi0^-IDVHY`-JPszYX8}fuAAl=}x z1-qvyq|02vDWehM^nhc*2e7~w1FK5-Qvdi&Vl2ypuW>tk^rz}s`kKg~PpMUNRlVwyCH{4uhYI@H?Uasd7Ra%b}W{xup zP$SXY0^O|vI)CRukPA-4YR|Iz;*@jTjpQfY;_Qo;Za%UcA6(pOE^7&UzhcF_YO^0< z&L(4(b<;2{S5@Ux=}|5cT8Xq)_C5?NYVegGDQ0IL^ZQ6Yx~mYrd+J~@e^WhvU0jv) zt?3zkJ6o4P?O`~U!)+gO;!b9wTm)fNZ6=3S*Mmuz;JkRIsC`?Kj{B6XvVyjP$*gp^w>@A`+;u|c&rZzimz#r@gHBR9NRE`p)H zT%k>^TtF;4zixHtev9@VZ$=|V(QbddRk0R7)k{^5FR!%{4nTdeL1K>)(?L);ioyw>v+F3eknEcemkH^1Vv(tJ4$(}Ll z3I1^%g*>A3Q*nNK(b1=!R8%%qZ!VeJ{`A8SKC;=JyYV?z6;R#-q(vZ(CvBmXe+FH{~#piydp z+MrHRH-lgAKI&0qRlG=j3|SRlroKu2F7?;o*K<(|xk4COFy8|agt5Cw1I{;CfC?jaM9DW*{GQ3;g&0CEF^v4bzSyA3J~_s~&+a5k7<3?zlgJIB zJJJAIcKC7!C;RbGd?7$vgh+UpP-q?(;t1!}< z|NHpAi|;id_e5+ZvOq*{xRh`a4}>4sqKe9_teixhmvqVy1~jUXR8hGEA%Wx`O6o6N z`zzajasm;|bq#E46(#1CX(1d!4PH${DSNG2%%Nmnq!Hc~e(*+XDH=|I-XLWmMq;EJ z$HWSdqmNMZwU7SkAFJ*=to;#jOIm7#rb3`*NFu<}3h~u^Zq#mUPtO>nW?C*Ro?@hl zywl$PoRZu;EiF^A!BmHPoWA&NhGC3IXNCLj<9wN-zhP~jpgb)z$}arzA~UD8v92k- zFk#)l^jz=QLg!ja=_%Th9P5Mafty&Ol9A#%>dM(QkjJN4T}$(3woQz#%Tt+8+o8sk z{jq(ASm}KNbxomEK71~-i(+1GT$IiTOg;R|a%>xXS-n*yc9c`wckgG6c@(ptC_I`} zZu_^A&qw3uE~>@MU*AC+$7F5BwO8Mfnte+=wi3}$BX0Xs#l1?XIt^0Suvk?PIZ|v? zTsdfgBi`Wm6qfn}Q<1hmFmX@zZR?FjV|Cvj?4h;u`)t&^+5XqL;{M_u-qB&gbR)Ew zeqkNSRSHB>rau47^bhdlUhqg$f(g$GrT0j_BR;jlr$9GZ!P zND*PH5f3`#Y2ho}FO66u`H|+xT_cZ;ymREkBcC1l!pN5p%W|j$wMHGH&Qdo*e>_gT zlX`*ru+P%^CF(bb-5RJ2@-LA%z7R2qj3VE#?tLP(LmA#c4e?w4VJ*eQDPT5_5y_#Y1@XbZa3-w}Kx_k;2Ii*YmpHm))h6UwFI0(3gGhW%$po=lPKb zz;(F)md(d%&D-^`<1R+brR!s%T!VS1rS}(RlgU=xEykkZ@wN9IE&T0`_dZliWnRhL zd*o2qSYsaNVyK{&j#SiA(TdgP4=-*1S~8|cCgpK8r(Bq;#cbR14!_({3^4qt^+qRq zn~Z5TCxsJIbNf9JEu#H4Uuz(4mXL}MSGf?+e!PCPK&QrA&Di4Pb^_!RtZ02Fwy+ex z++^NQUllnZUjO7nUhnyCb()QBJb&S<)a?g4olq;E&uyGJ(z)sqw?y%G8k`#Hp8JOO zz{+H9Zn@EGu;R?h!o&A4cgZ=&$t{MPlq#M1dCauDWL*8E-P9jHu((oBq8^h~RJrSm z+Cfk|&2?+B?L(UQX#Lpb@f9P*Jn!g8f{Ahy-7xoXruXRQqlm0kKJ#4r1vkU9g+9HE zIrMVNw`&0KTAEf_sG>!Mp*m#{jF-_REUmF zUw6rAUX1Pga&waT)llTzy%$=orNw6BV)c6;_`!I}OMa}gab)``Ej6Kk+8pD)>6O!< zLituRU%PNmIi8eQ#ZLa=O^sqJv|HW&h*_U8&4n@gr8gLau7uOZI365|F(GjS@WRN7 zh;9GNQgv5K`}HJE$HTw?6n_}YBt`_tzGwNDxxXEGZsgzKJc&?o*v#!K-g-X<6$GM) z#RbX+Ogc<9f89tH%fSM2Xz5~wO_tQYYs+8T5;C@(t=A^@Trg*G8UomX)Un7U#*RUD z$2f*SK)x0sn)Cet zLS*&$`K0TdIrC zv4g3g3W9SqUF|-8+s5%D-hJL^X?)r|u-*PJiWG!R{`^=na`_7;Sg+Q2FBUJ}Y~FTp zxpT*(Su>KmR)kwRDm6gIW^Ao;ik|4+<;Gwq8p(rGQ zeY9rfX6xnD|LS;` zF6^GZ;h{Lq%tN9iqRiS{C)@L8mm5rYadM%k8q?Ef5Og0+t8^(%d#-Dm&CS)7-0paP zd}8*h%swTWI{vaV*(vlx8|#Ft*A;JD*?s4koEtYoB7?katJzIGw0*T^E$j`K9esgw zY;G-TXa4<>$GgXl$FrdQQ1vu5foerTEwX#~XSu%^dGE;ohJ71G=42CTr7J@e%w5#Q zU{3j}hrmAZk_E_6--FcmHIhF&%CDPMc*n61DT;W1@w(#O^-T=&f?|K5vjw6K$*YT* zhYgnu|Hl*pc@0sx*f+$8b*{YhzCnShwc{n*nJhaC*#B$|U@l_Nfzd){NT9gMh=w2i z#UTk?4aX3S2vg|ws+@2VUb#CmrG1T#Gkk@f4mu$7njG_WIu_qFkt7%=CLQ|15n{Cv zOiht^zAz9h6p4+2r6ul|vEabM7wpHanRzJqXP>`xBZb1p*Pd>je3^IXA2M~_)Md}g z&oF$UKF5r<9=a(sXCTd`n#(rDqLPlqfa#J&BU_{YI2pfnWjEDW+;zy(L+_n`B0+s~ zkhm#93!{Q`+Nw3?zWk9^-!tt(s}hl7QADVy!<9W(75C0X)$zxsM)Tbh{8lob<}l>x%Lw# z%0#+rS*i>U=lb?s$OViNFK8>`L@5Wwy+6PKtsV(sen;Lrm6Yjso|@^asDzI+bZ4H&spbFWr8yveKUz10A|7 z+Xv#hpn%8?HN{f{dVl%cOJ8-h)y+4)eZ9UG?`Mn-3W>^chRf!o;CEJ>|+4gt3r=9kgLsKKH32Fr3|LVE) z_Frzqv5XkupiAE?e4hQ$$a^sVVQ9XlM*-p_Q-!?z!_A+EWa|5`ZZMnuNj%hx0Ce~> z+CSg-O22N-`MNH+kj(7?5fbITAI^eeeVrH>PW)g(LEW)#ktkVACsIE^-~$&p=Nskz zWjj)UjFL>Y0qA?Nkpc9F0X9rU;(6i6vcCE4FK~zrk3aqm3<&=zxhCiP*GrhTWOV4$ z*S9`JQ((Y5W#2cCsY+Zc1Pne9J|9zl_r>k+7YbtuV{+-1uu-vdb`v#z(zQbJCeX9S zm8TXpBtiN@CYW}YTIr83ed|Rlz ztNh0o&6oftiNvEKH)$s{r#^mLcsb1JrV@#`0z5kll{XzOd}glnrdEAM-2T(To&%tL z2=5zIuLc`NA)*&{zs0Jhc&7PcJALRwxz#7CVE&oc($|;m`qHL%xlp%yxkxHv%&}qE z3@yQk^JbViU6VlgSWKqsQ7bG~+=4iBcXGb}C#nSoPcPH4si{ToT-q%qQ6`n5GOmuI zxv|j`ZWwWW|!oo4r41rL8CK^f^;No2Bo@1@$L)S$2 z=Y9`da=BKalPOe;{mIx^W~zAI*C^|d`;3@h8l_d4uNFdImzU%Twz2_P!fSMNuv?Yg zoL%Sa7>cw}>Q_U``rZ9ZCpER-+w+uJuBSB)m7gRLw2g+-*O2i}8Dn8<5Fb4ZLRgbm z^Ht@qtk!9BP(bY$5k-Y%DGDO?BnYdLWNr~`>HD}6&jMNUY;NS!4jJc zzVFAjUzg>CmV>c4m|etM$p;2!FxLpF0hvEsX@B^z3W#rE*I_Vju+sk_>pcJ@InMLY z?cCEjPtG~*?99$=&UyE4_j1kwhXd}Ab0Ua9fD{3O1Pzb`NKhg{3Zh6cNP46x$x7Cv zqHWm{MOhYU%aY|s*?zWY$`XVdzOTAx540cVurt%sU0qdO_19m2_&!rlIkiw8fGY8{ zyT?V_1@xZdvbM+VJq}*TV#JD5a{%3{NDEhmo!Ax|^Q3t53kP?$otiy7JUn4_j@o%K zIWjWp7D6&@mAH242VjMYUNuJ@quAEZ z)yor_jsUBtbJT5#m|7kFr&?mftVt%UUk}viP;%VJGS!T-rL3`(dRJqkpuO|EVaMbq z{(82m6*t@GpMS=TBxF5B^P;XQX`lp0(~bjx7eqvuY=kR`R&JDf$+}O zlq{a#GY>50Qn{uf>p}DCN*V;-#dTH#?!3Hs1 zHRjDcGrZpd4j5BF--ZoJ0Jnte3X9euJ=Xcxw|wrW7iXheOLaDPWXQx={tUl>IH|{m zehmnpI+dcT)Ce_3{TB6as6PWL%G)qcsetiM5uFjkql~9ap0gE z>EFx0EV!Hh7e~j(%9zZ&Yjy_;A<85cBV;5w9vS7R10o;%wS|myOaW3oe^2I5Bt$0# z!#UXXA=P%^M|HcUHoUwr{rEEq)j~8A=?@f)bH~Tr;49+KC`@8d^+FI}8S$qUIT1JY zD~!7m&Dqx%eXMtX2J^m`W!#_AL9>Hu__LNw^X{OlNu1VhucIixBorT%hwZUTVn?7L z`(5XEB-)QFP#4mdOdeg?VP+h+Oj|=Qn#8CqWHtGwiqh0`? zzI$YhUzn(RF-oUh?N*Rt-15lM2ai4&UY!qbc?PS+rqaDgGi&p8;FPfiBOz`s${3zI zrD4Y!rjNN%in%0;bBM_16VoZJkstr~$yKq_#y7S3MbOs(0UZ2*8(tcQW|OTO zsRx?cs&s0yapQkQ8?KhylXv$_a>&YV;#nO7j@sN+!sLOKM{t?azB<~V63mQI-B z80!Mn>jRI9t4<`N59^ryL_t_78VE^XwC!>tmvBJaLQY%x< z(=j&=i-QTk%iPYV-oGfvB2hagrLu=8J9%g3qe_UaXzJ*Bt-=f0GE(=4+nrgDae-1s zYe_-lXXHoZ3d7Z4Ah3j4zRnbvzx0>vWiahndi}OjOJ~lyx=?&IkrE<~2yCwzE|iFj zC_tx|VOywXOEU0^Z;fDNheW0lO>*F$AM)k%M+JxbhoL`(7x@R2NPUv}N$O|eE&g5V zPpJQbDWT9&xw?!yBnZX#?* zH6XQ!gd@V=fA{qWd{TjK;`@*)+4Y$e`X@=G0#5mGJxYNGrEr~4=Qrv`QQ zR{_7t};% ztbZTW33?ZVXVA*Qs^E~k9CW4sP*5Xy8;GEV8IOQKp{kJONXdbffkfGoPRC!;PRMcM z_Qw%a#6RLU2u-lbnI!TKf%pCyq*btH;3V?=a2!aBE_~Er74HfTwGfA!OPWuCR6&c+ znQSR{1&ji~^6$hvWTy|GQrT)TLxOL+l-gwJBabJuw5Xr{sptsMDl;Lwcxr87G2R-N z)%3B5U?eJTB@C{vj9Jd<;Ff&38n1W8q(rk;qvjFbZ5qd@)18q!A6CNgc65&%D(D+r zB&3z6Ph6Z{$y;EIPNox7R26znz>NblF0L1{)&7%MgSw50{eN*C8K^VudUCCC%O&C3ZNBd|blqrZ0N;}e#h}pdZ6b^|& zmDmgCGvn0JYol*Hek38h^M_X36F>{ivf+H5szNn9SuZtWwe5d=3kSi#aOgTfp5k*k zO8Wy<>gv#R#R#u%@SzVc?ezb9H_oILupjV~z?IF+SZjKT^8_ZY+L5~qfV^@B8`q3y z9Kd|mx{WDT5PC>+*%Pv?(9)a^*O9A)Qi4`o8;J>-G$%bKFakf4-Z7Sohne}hR&vwr zhQY{G-T^_SEH-Y*UWNI*4z>7918i?P9SR-cjESe_+Rd4kWqPJ$Mboa8sNZWzO9@?( zZEn;_RARRjH#TZ=rm?tZZjSzFmSR(}*{Q1$4CFAx%)6qNd~$dFx4{>Gd4NR1znh|x;RE>_{1zyg!H-09g>NOO zJE_+|ybV?dUrrCasH6xRVpV{%cXx$DWyqTKV{2OwsVeJea0$JptfRB^+QDmY7UE+5@P&0o`BUh7$ z)C-;$p-lHHM_ShNA8zmjek@ystRX?o#{AwUb>DsNxMd2??cL`!xGQ;Z1i<{qJCnH9 za9MCMsiJ>T84uF0w(qqgN#%Zc*#ONK;bykqji~x8g&#>Tpu#wL1d$3I;fG-00S>xs zmUVdK1p^El`|P=qp1{Rw0QCfMjvmuJP;w-(1YJ?`3)M_C+5n)aTtM)}=&g?yUM!lL z$;b{Rf-%Ek5PF%Lck0p9*?1&N@e$i&34=-WecKyn3lkm9p+S&>d`~yaFSw3j0F{mJ z*4zu$rd4%gvYqj=92FZbCzG;QI$>4dICjU=qfJVS$lG?{{jG?k6^T+1?x>9@bt0wr)J3+4n+)j+CP zZbeB{KNB5qmFhV(cx6yVd8)^M+u`nqktSXT(9< zp5(%?`U@hZW=AQ&{YFF%l-)qm)j{J2^8yc=?|1&K?b0!jrC_@TdK%p>vpk;*)9{zY zE~j`$6yNzF54*q0-#cZ5G7L*6WO;-C`9fW)nfERT!jlaqx9{emVs3JwluqJC@<)@R+*A)WWvMQs#!9yL}~F-7PQu}sm|zu zZmCnN^)81>fHEsiZCmO2=!hIH?G24}SQU2EXpuG}yi=KA)Td0RGNL0COBT`nH7HUHB{^XZ51Z1&bPp>c$C6s8T?2?J45L6}cge2HNM>vDz zr;Hfpq_PVKFBqDraAPkI!X{PWE6xn8vr0Xx0mQ3HH|$(|($%PLSmGEn<~Zkmor$vf zHDle0RXT=P&P93Fu;e7|uxbuX1t+{1=fef(kdw32Oww=kweG!2*v42PzV7 zfRgYcC83@O^$6NWzJPnszb07Ff*SjOaTYPNAv731CUT#k_X0zw-#xx>i?H6k+v;G) zjAF^!jW&bV6dy=={=*9ksFGg|>>qu@d{F9m_~x_3oyPssarZ&;H9S?g4E6A<;WrSb zd4$&ut|H4lzV^WNhx-SoVc91ANB+l+!lYx7eFbdpK`b?kf?2R zt~ZMPsITI{!XT8rwiiY%31{g4rdAEwb+c{QI^!nbjKPz_`S=SW6*8X6!l7GbjyS}9ZA;=x!Mx#2NE06pRQ)>f|<%M^|J zM_6mM7B-TZw$hv*e%1~tN_fo5o*74!9><0h%1Vq=jCxm*eaJN}3X~HzJu3lmN)wYg z&Mhz!5Y)YriZl_L65Ve~3V47zn#PNg;&pd!dA-%jvPw9iWb!iwD}v2)j7EPYbUCbaCUtkDT5#PkkY~AaNU!o1 zYjG;UQfu&Fs^tRC(>xFXNCJ!{HDKRX&1Dkv16NOM9*FVUd6kV5P<<8?T% z)s;0CK{PTev7V!Y!K4B9ESLX>Z)+?SUttuG3Env;S`srl9Z-I?Qf0UgA2)(3IVuZ=S0{L~=_5|*>yd_A(77f^I zRu`_6Bn4oUB6BcTnYGhA!2kCEkOq8v8BtZ!EUU?&5u@R3VYKMKXCzhU)Cp?O6)W=0 z8$u`-YKu-(i&c8xs>xwDd*hV{LeV3)1FA%6LRI1=Wu%buoc@8c#fP@_%lpkg3ss$i zpVxITeFy>oyCXWR#<$##eXu@nWh1IQd2PO^i85#p8NCu?_wBWpkBB+(TJ)JbsIPLR z1`N%5vb?RVH-_<1Jp7Zru5N|W;#W43rTr(q3I6o*$o#>QHJ{9VwT<59_!67D^TBJ^ z_;jR7zB|*FsDKd;02^YnFpvbZrBZY!gZ!rK`vGBn^!%QGvLL(lo-+2- z#2MOPJ=Rc5^|>>WQyK@YPa+iOm4>2I-~NP||L|mfBr!5;X4by4|F~wj>iqCQxwl5<94(v_RCjFT8@s zUw!UcVV<$U?MXN52RM80req^*Uo0w??M2!vA1L2;YAhR#M4Vwg){D2Ni}a;%o|Bx+ zBm<1+j8Qfi4tj0~>=1E27LtnyTsdaJcn=UKj#U_tDmeu?Jd4;m%~ABa;d*S&=ABFv zOhlp(R>fyjmBr}YR~Dg2DT}Piyau}ti0^KXC=){b95`9jZdNt5&We%U8+{wg$otY2B&rsa9oa}7ANzw{r5rd=XEYqX<2grx3oHabs{mDyt9A* zrWwJ*&6L1wx00C8IXAY9&UAjsO_atv;Lmtaxk`N5B;p4#XdsBqU#$#2WCxw zVI@aNv?av<&BAxf!V2IKK7D8WZ@o!F! zVnxC^sOZfjVb(XJpfH5742}{D3Zhg<#TeX?SXfETeT!-j7HA*1pY#;*hzlRDBK1Lw zPwdgdEhi84HLR;z($ZU2?!Kec!ycg4t55wbMycBdujS0 zN4FFw7aQq%?ApS@!ramxu&V$keNi+)JS9Z>|7|PjdD+5<5^dY;gjmaEFUL7ey!^!3 zJ+!N<^3-TLI&9ZyR;UV-QzOIO)6w#>3$(@V;%vmzjJbtMlNIyz+&N}zaag64-0)l@ zzMe9oBMZH;Ltl9O?;7FwiNyXbQ{u-Do~|GM&~N1yKHcpuR_5o|_*y1DnTmw3Z7Fs# zr$yTvkCPB)kjfgGrop3qBK5k}om*Zt@#Ru0*YP|8&w6KR9$CSfF>3PB760_?_&Y2z!UW z@j`3yHRz<>Od50tShWNDmJgDSatAgx6ug`85%xVh2&U;mU;j#iZok_9;imJ2ldIhL zosGxAbo9uFGxWvcU0j9PShBRtW>nY@z_Chd=e?f9ZHV!8v24y(Dj`Eqb1TxyjF@@l z0g7^O`*aUU0oA+Cu5(xZ-J6H>3(uYWDK)w|H-kWB&%Lm;w(sz2c;%@Nef058baF9K z($bTS_EOTVe4pBQt^fZN>`q+luwm1POIC&g!k6Vl7ramewliL1+*$Q;!uCJR$$Y%$icD(ucu7D`2dms$w*iCVP`%|l^G z0BTSWT@#QDQpl<5X~oma*?8L`n(fr=KZv0OU6wUTIU(Oh%a5&_e8D3|A*>;d-*h~eaupuF9>c}xq zY6z_r^zjcod;Yew2=7*18=)eApcd=hj zU5U(frLkZ5>ADh=JP`q~xzBuY{QOw!V6@&4Y>R7!o6h+D#B4EnaAxMCqnUUvWgow9 z`4MI6xyRaH&m&5m$=jdlt^IbkGo|Nj*vi;wSO!6L)B~Lqx{IYJ!BQ`ZS`Vw`@|i?d?SJQqN@8bn>38l>MYp7iY~|q@nzw-P zY$r+~B~kdYO-CZ0)VbZ|BrD6n&2n^dXvlzW@@?TaxU)l-;rsp}tdl;%ICUH-P9*c~ z)6`qk|3&>O_1n-^h`R$XkOunu>fkRXFEj(1%;CTpPTu`ZW$?E=68sc6uKhU=5i)o| z0+7WXe|*X%=p#f9LEPmAWN^cDK1;l@w1hR}M6p%DThWbJd1qPyvoa$qu5~Tyj z@E89EqR4m7_r{6ff!fg|!Apz}mE!j(Zo2AP6He4_wdjY2{9>$hRxSqgM1 zLU-g_jKVl0%s{;F9w!C{c)tB8E0`5Xl1dL^iL?L#L<4IBu{&U`854d(ivCWxwQL`1 zGu!27K_AX?tJ5DJi63@pjMuu2p;*|WNA{+dVk1{O$>u+68?mF~<0;9DCY#ps&d2`x zlvz}?1PccEZ>8j^Zw|kIHM7}nb5W(Ogn`=@xn}kMD0;=T8;Ra9<$X4N+Z8cWz(qS7 z8djgtBwLo`Y_SCj?PSdXvnsg2)pYu?bo630^K_91MIin6nMgvAdGZ z*0p_S4|bz^_(q*OdG5sC`SjC`Y?lUq7_HgTc(ZuOoz%SRcSXznj~eMH7m09H$7#o_ zGzja((fXc9!81eDjZHQh6T?Y8RkWI4in^0w@S-J=2rSIzBJ7;_GC)@&4ez%q4mWDT z8Xi^zJzR)wQU5KG;}alxZ1Rd1|6s8*+%9sF%S(~}l5{r{p2)0FKa)~m@880vMq~hR zeq$nx?6wQ48!@Zwv%-PjeGAPa! zf@Zqay1Z)sywIBt9e7W)5L%enDlFXg_8&P)NSqyInWo0=QKEeI`8%)H%*393{om7K zNlv!YG3#P1(ixqL@l0bY{X)l}Pl(Z2R4rE0%A^Cb@@P3*Dmm-m^5L?_W!Y78k^Zku zG^p*=ao!N(oY8sq#@}Saz>+Msx?!y-U?nrv?0(0Iv8>X6du-U;^tQrU%=G5yqeUo_ zVtOP&(}^i6lo6}BMw?af1QE8XscVchS0(&&Py9&d4spkZ9vOOd=$pvWA;=J$kZWIJ zC??4?n9nkQ#C)ImTcjX*EQ!4o%9zGwVWb6OMr zfgLS)BY%l_8W`b-xcMdAGSNb#}tw*P{Htls9_MZWD?^(7*hN0Slx=EVKmx4fWJQo}+ z;rXBmqza`m;!-A0#(%un0}OQ5K-@#*``=M2tgb`_B?}cW_XEW{7!uegpd?ZPdB^_% z*BFPCC4GQ6BE&S2BnlXOal*~VN#cY2??JDUwPfHFh7H-TBk|`8q#>k1xGw<|BF`6B zkq3r44}gDg3I^v`8+v=to4CuMR6NLFP(eu-#xa6O5q_cgzyS}ObOVl)H`3x@1qp_W zUsb_J?Fh8+L180d6o&r^{0OlCpnLu9#@Cz9ax8Ed0)9LMahc;AxkWELN54CW23?H> zkc`GOHvQy(6dcBt!SX#ejzi>D!42__Uk6{%Dfk*h7Wg*^kGrI&a49LoZ-f6%klF`T z#GVAT1TZ3cg|sQy4WUDc;lJi`*d=Xgi|3 zM$w?o7VkPhArVo7vn{m571X%MSLKZBa=fi8ikYKZTA?(006cRhV{{8g>555B$CJ&i znPUv)By0KEV|z<`Pj8GCvIi?n3Q5$V+#M6G$u+f2wX`&+yL3Iz)f16F^Z;wm5;Ev)ZjqtI_uI z+lfp+EDBO~Vf!dOJu;H@z*Vh+U3#a@Mx#*}-IS;a^x#QTO2*@IC7Dko3V40bG2FQaT zkc+hFl#*JEv@RCpgpBk!$uh@{G%SlYW z43w|3qZjXyJzEC=YtCjJL&$UKd`YK7)nQGEMV)KY%|cDF;a*Ve@MJk+rFcNGJBq>B zS|SC`c#B~=RybNv%WjiGUW^%Ec<<8vg{U*N)63V|AL2DJfz;EG-j3^lZx(t6;%>p4 z7fUmU#bEHjh5m=MBEzeuo*l(8CIi=owoq6H-v!!|>vSjMCX;9s(eAsY(YYN0HsbFmVI&H-;#C`7ke{m^H1^`jp9fkOSD zd~u^KTX{{hr-gRN1OX&lPbQFESIydR#h%n&BMh` zu?e^lC|X0jTq#R@4(ga0r^J3gs_BVNgksHFR+A}lwyJ;x>DyJo!bVP@X|@@q?*pyaK3w0lV9PG33D`b`svDZ=Pu2kJa%CD zcyfNWhh(m3)N4ejxT*Bt4+H~OHH@UCs?iP;3D+5V-&%a8kWZyGH}Bat0yX3XIS1TS zwHBwGkrx*m4_G_YgJz?wNcZpW=!RTomxk36LL@{uVwf7u!y1sx-gw$MXE-&$KAr-2 zL}(;0v=Gb}8BWZ=Ny$6w^=iSB>tH`Or`cB2Ts07dGASyKo+^W+Go*W!CNCihB}KF8 z)uR+0&l(cuCmj)-v6=V?Z%C$Kf;2hL@(DaYb(M4FX`Qb;B!kM}l0Xdu;YSqd5ca?< zFxkNkstEDu)-f$U{nFXJ&qYo~yf#gbw-S$}fd-VOS-w8M;mInkjp+xoN|xp9X)%$j z0dpxVMZ;~;yIomIKam~_DW2g(#mR!ZxN6F_;YEg{hTfqgov}zorY2T1o)`Jn^Y__@yq&|t z^K_Bt+d*o43{A|jUgCtF-?~59e=H7n8U~}Dr4ueQ#$%6* zEMB0L2nyJ{6-HoyaHG-6Un0JE-cZCsh|SUrq?k;-2eWF`DzY2H@ynT5#=<^BM+A-( zx;C3NiU6PnO5@Ow20Os#g)j0Ha#>S|fn1>WQkQ_A_82Uv-=Kbz`WMunQiy~^`q}Q@ zKkRFizO|mX0D_eifAiVEA3)YpqAQaH70NI^f%K7B5utEmeI-Z9`t2t$L8bFwf%ZcR zzWJK=x#~$QBu>Urs9b{$KGY2Ye@MHaRE}b_stcy<1+O9R+nI7ecEo zx&@Tt3R$R1L6uyRy$kb!A5~r|5)G3>NMaVcHO_?b2I?V*8U0(3=s#a+Lv!X3_>a30 z6EbPY_}DbegT5hvs9=8Co`C8@HNtlo5=c|XH*=tQ1g)`eDy5{&#J~_JD}(rQ-;gqB zH;?;_BI^oe`<>|*IJkimBVvFj--Sm?A{IN7W0yWDsqNA;(a){)#|+bTSs=fuQ#NG#Msx-fM*`Upp}i%*H{ zb!5ugmxQG{=-TP1U6@}q374Pk|M~G{zb?J?_;b~{*`Y?LP<>uv5|w^Ik%7Q zjofwD7h|&2zfR30#==tnigND!J^Ngve<`+O7O%XQ`uq3Z`Cyvs-xgBdLm6uS3Q#w` zN&RL2x+F)b=R2#(v3Xa_86z|F*0j|;y?RAEno3_tZXUIr{(nN!kee<@IaRD1bzOH# z*|2+L@y2f=Bnc{yP^;)?b}(;mIUQNPH`G!7KcNNov8(>6jtJv z84goQ771ZO4ASaNm^QrliO2r)ffj3Jlw^#WIJWc;Th@bMQ{Y^6RzaX4XS0gfcSX%$ zEn^mBI1qSDD3th9$s>F;Ncaf>+KNY(&`HVGxVV)cFRa6kjMa=+J=tBj%aY;HlogHd zWChSE15j{X(7BLsd5cRRqH&HhruE#$#b4$g{G}78a|lQS(cCA`KKsZW<$a77`_@-) z``qxd?9?+`@A=)YJtN)9!*Hw|rKIR&TpL5w?@bBrp9$?YB(C= z5IcW%1~Hl8wtAtcLnl#=EB3l=&Tg|0f4KQ}c7uFBI^Euueu7!BD z#4+V>G)to(tNdx|mFP)CV}__9>(Tq*lr20X!@Qr>Age5$M@CCz<;`Wy)KWO$ULM_L@?D*troKLP>we-7P%oq-4- zva6#>N_N%wCR@}J{`zvs zx6xwjNcMjaWjX~@ZHR5x1OHKF8)&g#cw=+ z-`$*$UDT4pbX|7NxJwblD*y>1J#HC#B76Bv?nLhOt*zJdl)IitTfMAe(1M&T#Vxt^ z`Kv#_UH{|JiPF+`!3duZ8=86P@~4_7^ zP1)iUEqQmCA>bQqrD7EMM)ZQv&`0|JTDXh*y`lU3Ec`zh`jer52T#G@4!wPI9Fx_O zjA!z~=xdX~St|N&eDaBmaExOAJ@`b%v_EXfz8z@)Zfd=J`?dSiw=}T_`BwuF1*Or*Cz3yR?eP8=7yfZ!ZGr{7$eC>_)PLuLq3_;3Y)SQl2RApj&Za)` zxV}A=EpkHi%&AuC)Z>r1u~JcgR7G^WmeOB{WPa}03op<2((zPzgz;2)S{UIWmVluY5s!%w7J1jhMZP6Df7+ zU*BqX78knh&8bW>onrp{=#leT@a_ot7ank!(CC?^$7=23Bd>I7&s{*+xi(W@dhmR& zTp7D}wF$a}F|*j*{z~eBr#`r~S|42pKbyAq9F;Qu@Z)>ZR&I5EZc1TC^l^P^J!HiX z9bUQg%!R*cc4iyqvUzfC!78MSF%8k;&xcBfrl!yJpXhB`PD-x#*i5y2@8PM9^8S7M zmuBaX0DMcmu4(3h!zZ>44sfdRr9$uU6Ms830^AiF0#^dBH~{}*)HOVm9+^7x0yxFs4D!f7GvVEpvIL0fVISRo8X9Dz)R zrLauashgi+Ln4Rqh7n9g`zVNLF`CHe9L2yviai;e7*=Hz?Tc_ed}z$;pEi0FpX5aRB-fe8bei%?3h#FmwE00t44LMmsc4(pdj;SkGWN zBrh@w^E*M>{ke^x`JG{8JRyyrcAz`9+ zXlA9`+2_%{*+VOtb!|c_*V_9%TR?E>1y;`O-!s90On_r2!Xq06&PcFAqqS!>&E>er z-5Vpwz)YPqospPf+UeZOZ^#QH$Il})M`JWgw_wN66%P=hsW{7*x}zIl2&G+@jYQTK zs$#KtoNco)7PxwVm8xuwhQw$R`4t|=2r2CIgO!_$K-ehW_>F=oNHIS9sFDA|zyAL6h$q^78EKn3+^@;@zB2a9 zGAKau8Mjj{O0>pvtSn48?kcbtk*^X<4aaWOEWRLg`4QJDg{SAiL7>BFqltPMc)Ljs z_}agV#0QT{xoPXZm()aZB{TX?nOh2%qsNXN8%u$p?BJneQ!(Mxv59n7NF48&sXb}} z+-TqUvSgLhDT;sOQ)f#jid!rd3gtuFlXKnBRpE`v-qwUBYEKV`MjDOI_xLfk8eh5e zTz>sN`&5>N`k3r#aSA*^mDK(?(u0Vg82sowe z&rGre`U_p22q*s>vRdK=f#PRvVYc}tBZ6u0@+aY-5d1n$C`)&X$44THg0I1ScKs;0 zBDjc12eL6Hcl0X(0YSE~V*_E~$3o>vazYT>D||ezXbap=nf=^FKq@Z?4XiK zWTN;VedU)FNt!yKFve|n_srv@3Vtv6Zi-=G>iH6FAWw16QT!ZqV|dK=D-LP$?$0;@ zvL{G!BLRh^MG!JX_Mwl7`w332JW>P+h{qUrL6G?fFZZ;zktoM^HZ25kxd!!5l02`p zN9{QG{wcAX&AE+kK2j91)y?(<>|QyWJG7Jp+lP_wQXPFkPYOLXqPdRBjY-h53)n0_hyZNMgnAdsa$2NfBMR40n3ge{tR{*VIRtdo|v0 zYw<<`u(dL0n(l;Xf!66Er+wBou=0AiR|BQz_4GPi{?3X_@vzg zMY#pAyPlKc^5>X)Wv?zrqw?pconbEJIFZB&p%$tZ#tK8|yyr^`PBWG4#*f_@W+LU(meX&vYi&SO`tS@(-=T&U-&wT6` z&g>7{-M1TOZ@oLqeU@{rk|}Zy?I2fH;;(YLVIt`Sq^dXm|b(-v4(wHIP&cWP`8L5R+FR z1a|cne~J_Fl%j+#$%-X{2Vy6f(t#6vAe?;d%l{PQLh!r^0k6xtha0vhq69*2+N zsuUUfqMps~)b^izqskli|Ks(T5VfpRp@kE?nVB^vRCfF$`0smZZ5qj*$sR)W=*Nba z=d0ej#!}(U=YOjIo3DKM<&U-&SfM-_PtkF$GGf{+FMzXW2u|i%grM=YtlhhjN+%NO_i;r-sAS^9OlU0|?R+i9vCQ_! z_R)+SIo+y&OI2A>V%o7od8>HtL^x7pDcw2w*oAAE*3sr85w@gzp^)q;sj(NswXP1M z{96wLdRN-qb5IgG(jDJ@W`s)3J>d5Ldt-bN6e?QnKR$lTxDI%-$CP+s^(WeQa+Fco zr)P8f{%lmHC2MZ9_1a5zetAPwL$$4*8hhivQd9QRdbg`zfA7TyC5z0H&DUOgqfZyd zG(pXdAJLYLP)(v^hn{)ulb`I5xU*C~KK*mmeaCv8Lj4;}s!M-tHi@|LNNVbn{g3C; znM|_KVUA0#5mPrZ48;|MrO5}9>BtO>dG)2)!1n%^!pq#>5B(1HCB)kO9#Fylg8FaN z-_coM&h+RxdXv8N?kpv9$wT=1NEvg)U&D!30TVpth>JIp<3~nqFm{8*(jUbB0Wxpi z{YDmf4Bf#I97iPcVByb$1BLv6xlXRBBO;d=nEVSc!N`khyB-0ArP$dyYh#e|FiX-GBauLxKs2O9fziUH9eej$hjKG%&wqM?e~Ys`@euJW%*Z`p$Qq1a=hC$s`2GS8sZxFTFrMk$4XOAPL?m`!Ru(FA`lJ zz5zv%8Zc?nMCIHLjTA436ANkJ&6)iTnHlW~dx!$?JpTZl6=O;brUsRcPy`@Dcx17`-%rn}P-hA|U8E>md#ltmw*t&BJqkM> zBim*q&7?czJ%-RJ>=NfBqTj zR5q1{4a6SbvpPBR=;B2{|BKNammWPh1q}hw;EnmpW-=O0KE`W~O4VViys>6Np)uZX z<>`!?iFpoXMeFHFb8$?8Vutw)6tn3y<|~hGsITXiMO#lIh!Ze4c-&M|i>QvHmtvf? zlQoP|sWI$jwJ|M;2S+uoh+?6#&Qw!bB#ULs^=h%C07XY+&Gj&C2^nT46w#U#UyZ7^ z2s3J-f>_SNEOqYG1mdNoyx6rh5ZO5Hv4ckE3~9Wi@Lk9?x^ za!Ibr)!7w3Row5={r@~wmLu=GuC3G5OgPbS9N7}``BK=-T#0mqIjh@%v)xMOW;;nC z!B<_?tQ_h94(363nYZkNGm+UQ7IRPFxLmTbQ#10~5sh+JILYW7nJMpSdNuBRi?7_fe8STcT`^l>67OpV~9t&iuiPFMWt| z*btx{%#r?|+G%mSzQX~4=GH9HeC!ePsTc?3f^!y#Uc_HR)D(6NU1bQJP;MpY(4+tnCtWhfY)n5t*pLf3jMT>}O|3!>Rjxa(laG{Y0?*pCf zCJIA6fo$D9va9m$-gkE~3k-QEFj%pC`s|GF_Z|3yQ48$keQ)zX7bGh! zNk}Ua1#z&y+&%X0Mm%WPZhd{m=%f- zMTd_c4C~b+x!Js1kvxVUpBd8+@BWUjdKP+R`g zr}PiK^wNiJJe$fER-eiqMrwRt?bC1739>|c z$!jvd9bXEYo$0rJ(0_72yF_nX*h@9oRAs+dO4v)>8);J#ob=PYnT77?wcq*AUI_3V z&u|5>Vr62(^vm7EShG3#NSckW)sEC^qp|MaS7|CERQ~?qO)*)|Eg^$miSRa`mq2pl{j0~n?Myt7r z;k~D4k9!URZKT@we!sBi%|EF-JS!_Ljv|^B!ArcX8%8Z;)>VEKE1DXta)y%(X~sh(xc3;Ek@%H z{bK(kAOHC4`Nc@=zOVLQ{=~;$zj5KEaV0X3xN_u|?3bfs4?Jnvk?-`$2*Ij$6z-n(N6&a3wS{l9NIL_4KIfbF_ZuF-*w^Oa|frB|= z@2HdJ0L4;9C?Ocp+BzcDi%z#yOXS>BhjP?!l)t(3g#s5*GbI_C>_3*fNF+v#9P{1e z&)3dhDfXXtZteg6j2as5D!H%Src+;*&aQr-ek5%c3M2P5xmY{2V5Z*L*@pnin3qX? zvS{QJ+AyFiX)nDFuy>sAP z?q~X^AB!`$D?L?F>RkVO=MRDoDRKA(aMJU(Eb#_U|DIVK>28Nt1?Sjo?G`>ZmpU{v zd8Sh^wA?$tZzatIWMTUJ7(dDh|B5@0c%m18ruk)H9e;-|(@P^M$m}Z%Uxi2eDTEYD;=9=*?j91qz72sSCQ!?-$>I;x{SSv-tPf zwUW$Xvjp-EVp_o^h!yasp??Z6#ZVjaV90JTs6w!~>J+TP{^P-8@&y2SF2q$#T25+A zsx{bG`CkX_U_Wd=2t>f?DT*w%$mk>?1Y{S3i%}4H3L%x2Ssb#C5%Q>2ps#q6tze zpBNrHR!=A7Y=ISJJ_NwsLScul3(*`Ce*Ckig=70^W4+s*z3q0_wu^;ko)rXUVozfl z%0moi^H{Ax7yKLw;+qowfmT}>kfW*W!_$Sc!LWsQggSZ9!=R4 zFWUF-9Y5sO`tJ>KwM<4v-YE@JZ+9hDb`#^TDic|ErI5JsIbA6wcv<1~gvG^`%$izp z4wUAi*j~l+YuRE>=BYo6XAaqUzG*jh6h*l@pJ+=UtQNJ1tJVF`@$ zSxMnT2jx`ztjtb>I>2=r^;l7jMxTvEn1)f(k~j85r7$=v%NM|TLT8Zlq{()aQrmPW zlJbOoA+`;~b?38EG(nFpB-gbQB_p>8M{Ts~W=37QGA#&NxE>#6wN@{kJ?Xc{Z?*k4ok}QYECfKQ-&5z{s;)asEUb==#mW)6x2UNB$rL=&p0HYqhG|Cr`a~XpwNSSsAPsU-c6;boqXlAxC|OBUeL4U;kJ|PTA55@W+JT2 zG{YixeD@;2XZqNjJ3kDT&3WMJODYoUI%`a`d)5>=guTZA9aH^cEiO%S?WlnKv!;;f z>Dk_mzbkN8jbiUA2kfAnWd=M=pA$y7za6@D=zBx|72!(+ePf3@L)`)<@+YaEpkDnk z%bJInQr~ohKjgD7TOqHAARmNkL1dAaO9EPgf4-b0`y*fYK{WZ|_s71GY##q>unpQ> zBLsgjA*Bt(q~MoKe)d-tUs47{P!MB~d%^PHZ%lCJuDHd;?{1GEV6ifdV?p$5M+(Hf zNm`Zwsf`r|rl`Tz$G;hxK!iEY8OI_;-XMbm)ry4C`Zw_#baUV2FGFN8Bi-VcK`H_{ zOKJu))Q1iC9L|tJSQ7 z&+*|`9^G=JJ2OQoWjtx7<5MNKP-`!=_iKquMeBgutj*U-rFbK)xc~GuPq|V)Rbb22 zlSVC^&*g^Sw^AK*3ne=r@{G*r@tqr|cD#Ds8}=sRuh5S!Y56h6t~*&K^sy{Rp!Cv# z&gDY*nTa#Don-3$f9H)%8`AXuN7#FSNm8BX!d*F6SFX-E=P=zpIqjs~*_jQqIVV_l z*~|uZSwO&&vxovAK*A^^Sq3B&0E4nETgbY;vSrD-lCG|8*$P*>dSq$0x!{3Q$U%IV%83bv0XIgAKT5KG78giwH^t@1 zjBG%{@S|dwARtx9H|g8V;*nL2v(qdwa99*X<{ZxDWo>}BBJ6$VxwT0uXh7oI%+_rO zd_2_=wG76ell&*FoUfF~eG@Yd>^%0-+D>eAaOPoHfD)tBiRojfPj30f-yY$3zFAr~ zGed`7sMq>wfoG>9zWNJt=1ViB=()4^A4yLI&V!k<)F13KQfnyL--!9EzT3;2mS+mQe#1hbrKCN=u-eUbbK z8EcXTSpquEP(==zz5y-SLmld_0>WwFuZ)4dCLL#EX7~4J4?Te`;}!6fCAgHqQS7Sv0IDM{y= zbUox77X=2?n4IX*VD0oQ^C7^X^`0++C+NR=euoK;m;D)#jh}?vU^IZ<>=!!zLwJ~RLk%($Z%q0kaRFC1YE7YZD)3XORO)Ru&YxN_=}8Xc?yOK=AonWc-|f&k7Om~JB!eYfww^TOb=MHc~h z2}=?ussb#0&~*T32zG!gmw>w>*m5g}cEVY(Xa$aV7mwjLm`9M9o@@~46}l+xo@pc3 zDtH#BG$%@tMk4Dl2A09hyGqcVH4VTZ+y<>)5U2yvedyT6S9xP_CCMoR!^@ldZ=XKV zB6*@WIjzP|9X>u5*-sSL09CHfSKSwda6UCTJQkSXw4Xfn@oyK4t$yz|i0C`7`KFBA z#K^K4Z}#ETSWV`%%JTf`o?alrh3Jql0bUDFKG5HP_X9DW^zjPNdFpfVc)HzJ)8=C% zPsXLAf;sY)>~uEDnQPVtnvFo5HTEd61SA1p8!jFf!J8_r1WR*4+Q)6^@n-YALz|+e zX?A~Z5OYSLz77Y{zEo&D(2{LP_{h!%N3*fJ&sRrSC98txB+qs8C-QZK?X!WW>Th=1Zv!A`xM5$b6&=1T(G8nn?b*#uXH@V4-P zbelOQkEn_nD&p34Z$5sWRl+q+4YWF%7_MyQRT847Sas?%FJCx#s(mk^thSiqzFkK9 zpanixtT`XwuO_d*erjq}UI;KZ&sDE#&-4rSvg>c##*BURZ>(0UFSBW*e{6hWc{ws3 zEy~_phX)O0y(ca1G-D%Eq2`8kIzHyx`uqyJ{?^kY_~x&BqdV?Yit&OsFpXglq#rEm z3978b*7E~H{8XTQkci8>L+e`8TW8lwirQ^VaVvdb7Of$MKGP>&8ZHJpll@bl!jjV_ z-8&N4BKs?AXS-jEVzWI#AiwhuvE4sRO)7;o(G`b#&B123Q7hQXA;dr^CL3kLTPruU zBqd9Uly4v!hM>%%P%al#wrEERd%e>E-*d^FK1?vhEx{9g-CY|xK0{V{YtIFNakQLYROwktN?lX=H|q2_p6*#sZ17il=A5?UW1^2|56L zeC2%iUSJruqaPtkdPoWeVxWTnKYYO5k5QLAbDn#^5C03Ee+2J`-vP(FADCg=fV4db zF)nWiMvye*3NHvg96D4O_sBZt0vB`z8vz64u0`GtAWqp3#|+9PWYvT#kk2SmYNPe8 zqh4|_2HgWt%ZZX&l1mghwt8xS`AwP?y)0Y<5&Ou~4MRaY< z$CwbN4FodO(E94CKl9%mWlsZT_m*>kp3y6p-7??HCK*Du^RQMre)`&}s}0DDSDY_n z;ayw7WBp)3e(>DY6h^PI)1l>-Va#(eRl2XgxXQj6>z>4J#vzqV_R73rC<8R$Kn%}bfiqY$qhl}0+9INMgAt?-(ZP2MKYgoybW~J_b|NX?_2eZ_s z$hFbd!3FQ8OaGRr78OtcRQ3XE2PEj-lU=kFz2cn*ZJ!})!4{Q6x4GcC)5fT}< zlN%wJCy4pYX!nzT973*n)8GT^b=+TEdvmLB>3PV)OU7ZYPi$mpAYTsp5CLcjfW;^N8+fH9& z-_}_7@sH2$yZ6ks0?)h>lQZ5L83NvNPRXZ@=P*Wyy!GI&$M??l#Fv*Ag7u;y&CHJ^ zRyOBP#O|o2>I=NrAEP2Zt!zS0hIA-kAK%X2{=m^ z2N3G9XhRaiyUKAWeU^rf8=2>f8pq-fz0u?%1B9YEFpdy0B9k!IRy;yN;+QC(cy~&=N2qJTN`2 z#AC~uk-MLIbd#1ov3u~q|IzyFM{v2lHR%f|TVbpJqpd#smUo&*i@)D~-C zV0y&*q8Yv>y!ozkbtw{yhP`V)otpf7V`LjHu(_mn^ytae%1VZS%z^rFe`ECvXMQA)1;89onTq@GR*~R{}=vV>S=Pp+U z-JL0vYct0R<(bnLQ?b-gEfGy-dwXJ`Z-nBd>db7ZIE?*MzSLihhBAfz>L~W$?sj=- z%V2fz503?7jfc;Ur4#rkSM;tAMSH6ufAy;&;Vwo9uLkvuYxAbBsRxhgU+ep1yQ!v|3w*J-xi$`<@j&y+mfTSZkD; z{gpZ$<4@K~fBZq;?1}zd zIiJFAp1#8J&h_oAb3Usz2;svR77z-(>t`>TL#0|IPe=i&=fYeJ+|mE!=PRCXdAIbfO&tlV{lR&eE)ZZu{Ez%sodC)p2l!k$$$O8$9!f1m5 zyQsl-)G*F2SHMP!T@+OTxhb4Ks1WBG_zTwntE5|>#n}{NvUs{fI;se`7yLw^0u^zz z6VAOHs{tHySW!@KL6a&RK_yr)KSJrytryFK84+p=G&#%z8^`zey>I*AU;l===c)q- zPiN~rf88_LS6i)Whw{Vf_WAPW{KEMi6WGq@)(qHz3-yLHV1;xxwi6UHnI8w{`DrGoL?sa(fbdZ}3m%hex{mg4oY5{!{Pr zXv4oYPYe`>@`vZ&ivH~Q^_NcVyFJhxr~G49w6{@jZ<)NO=a%{}Z)$>{=?3zdy*uCk zfx+ci|3a$z**`ne-t>ht|D268@#Y`*sv?m5`qLl1c56%(nU!k_wjG+r6CesDIum{~ z0NG~Sn=C6q8j%jU9uitdQ+}0~_eHg0XZwyDR`e9(+03msTOrmkr)OG~g#+W+T;J{Z zZI53{+?m+3DfPvBXK%X;zW(^`>#!TQ8rdHNZ@BTPH3yP>YY^D5)nH_Z6{O&rtI1Ek zQ0+?;ds>Igu-)HD=3`AdYaF}AuSw16Ezj)jN!gm3tp$UjTI%4fm)@SwVlT{hVi>m7 zyz1hL`Rupfv-SRgUFYw@e-jv9Lz>x`)fgD)S@+Xui@k_Hv3)bOdTz&uc=6oS*4YWZ zo}V9Xtp_VWRAQv4?V(|=yN><^`9tumje15rv!1IxrL!z=C8yXwhUW!w5bdF9%@??Yz^N~E{bkH>%T&9}Q>`N>cJ7UR3W{q7Hb z^tHF&{>Be~_;;At{XgIS@vATW>K{J!0j-b^GqxcjnaIyU_9f^{KxC+?8}mb2HC3tQfzpiFk;)VwsvNe4n?3b%>3lh?r-1r zbYJhj_ICHTkG~J&-v(Rh*xQ(x{c?B94Uay29rmH${=;LdmS?(K-+Tl6;B^m$w+FuW z_~YN(^4y7QKBQuyTid-Sp2HvQ{`&qq@4O$NP(gpYzrr?6wg36f+kVuU#x^|mR(#`3 z_ha3U&vbrt+uHf?*!W{_z4h3Mr=LF2EZ>9$d`|Ap}BMIuVo?3Yf7`_W~(L z>7e&!5x>|vwQlW}$B(a^!LifLdR;3OC-#=JSMFJ;N78;VTrStTKT6$O{NWd#`0Byk zFTUr78#1|aVDI1k$evN&e_?I4(ny^=dA_Us@|s=3ym;mgcoWroZYOR!^=jy2N8f+& zv-$IUp)EN&DXyC z-Pb<$v4P_^)IN`m?mKu>r~9j8qqo1e|LLbc_j%m+=MP<)U$JT<7I^N9?|ZM4Q~jUl z0QI^j<{9)Xcy9810Nx%vyQw0a?{P&9XRtXdGDkK*84aA*bk?E}W7if}XRc%cLG$jq4vsr}P>mLqVkT7xRZ1z~FHsd; zjrt8BbYX2@px4gHzw$9XLNw3`)><6oRZ{OE_ec=qy@<%vcyYavXe@}FnimYEhwGJNC~g-@`njzCgk;r4^KhfA;h;f;nkJ_&}sa!C^#5mRf-rUKqmS9nUB#Ar~AL-jkVO!P6NZ`z?q5We;5sDVQ!-Tki`Ot z8>oR`CPyP`=>#yupflai8oIRIF`Pdw=W_JhJ;9y!iB_fwnY>{a*ei<2@{GJmvY_jO zY9nCAKq)uk->iXWy%`so3z}IT0?!IkVU^Qf$RGyCauHAEmo_h>ywJBTP{s4NZka9HJr60_f-sRfnuhq8HJF8@LeSF~Z_GNKXd( zq2QLeXjYd*RXrO8(jgzitj}>V34Gs-^W>n6IO!@eD}$XH>jZM4Pu?8AF`?fV#qWP+ z?{?rJrnos8qVi2hTfhNnlm_{`kb&0A0W+Bl`8qhrkz_(>ssy5+PopUzJ9)j2;~7ka zg!ZhDWMxPg3~9)Cv=dTR;9de0RwfpqXwrtvU^?WGkhy7*6fb?>e+Xp1nr!-#8yJQr zDP}?(GrAvaDw03Uk2Ti)+t4c_lHhQi$WW?}2vfL?16u0TsFp>oBd>75iw?nmjf6sfRP&1uzG-^e7F)76w**t$e}`rK|REzSg*(- zaxO!bSSl%EMoSvCpc}3yGaxmxvh{yym zWwpWrhgF8p8kO!Bq9)|5n;0Is3cw4tGfpv?sW7q1wU*4*JgasUC1 z*`({r1LGSBdB{Suc3LVfq$n~Rt3O`J~z37{neO{2SW(YykHgN{HLD111_5mYP! zA8>dgWavR~)CMd=zYwUkz^Z{HQn`O;GS9GPt-3MRa2@09#ZKfKJJQ zDR_}CGNC(VTaJPjox#9UcWA~D1Hpq3y#pFd%IIA1Z$iHiU_lN_q>lyL4!X@E4Fp`{ z7Kv(!itV)NYY78vLJPm?-O|(Ct$7!Q2Vy$rKW@7!&MvxD#l4@x}>J2ga|0KSu~X12eP3*B&1p@5f@LIZR6Q0ogB5 z84hie{5^xFT8IJ6S!OVbK^6yrghYPew=ZB2whek28W;&R18BP70C2Y1QdA%zU0?1|IZ> z{=z0TZkRQF1O{wwB;>sfZ#S|8U~2FR#g^LAbnVfC0=etV&8jJ2;tMjD7OP^EB0`or z;^kh3Q*5O+{RE`t#-;};^))8RrbwAyMB z3IO`(^vuMe=Ueym6o}0@Pc5sIpE=|gZNp2GJ;l`YeVJYcgGfwBz&AblNCy~7G@noV zE^F{BhZ!u8f6t(rn>CAp)=NYZ!c6Lq17PQTm9=RuqDHnZ-yHU-S`8}?Ua!T}M4*}& z!|}Zx0!V^iiWF;Rd@xmxnjzCn#IrKsg+K;L9J#%CfdSqpMQl~qykQLY(%}dp0Rc6y z0*OUb_Cd6mFTw#^fCjl1!84fCd<1CnNKh$(a|<@l$3YOtrGdtw zBhDZkLJ}NUR?s&PZjN20+2W|;SgUmS`;Og%Fjd#2$DA1655k1G%m+S7UY_*QF{7U0 zM#HksiaL*BYBgCJYpVffW1uty}L5^_#h| z>FYXL3af7ylA)uac&bwAFA7;PLyZjED-t!^_jQA{^We0U^+m=)5Q83#)1d;uGwC_Dr(}%-8)CW9Am~WAH04tdj z_w5Q5OY}&{eEBEsy~sJev=bT}nkohVo$Xx?`p}k6CK+&(Lx)fyNGpMw3<(-e*bblx z{H~$7cxm~J`T&q-JLfseDLB>XCGJ&=Jq!JCp@I39s{6zBJ|o=jEiQ}<7v=_qm#)h2 zA|GzVRGgzLW(ZPpJvFbUtYSS^4UZddVjn8W&2Le#&K@X>8eysz9+>4(nWbItyb^nRJe5&>TjSW1ahFr+}xp_wf6aw1xnd}5Dp zEC}<3NU&l&Tb?TEu&fBMksQ@Xa-5vqejrKnjR9Tt@?34W*&|6Z6~(2P9G6uEk}Ff3 znl^YL%4&f$1eL^^KCu%MD6+>W$Uc!}>5)WVGb6j>{ZHH4X%1)v1lc`HgVx;%gMX2> zbFnKd%?5zSj*JHlinLVF5~yvRug+4^X=#)f2pxe&a%PSsg}RZG`NM$-_bM>y#oZB% ztb$GvaFyGUpkOemyJ!!eR2%Nfe5;zPTSW-JdM}+be45C=PB8Xh(s+WYpxx&cs zy23~;Wc1a5HdST6-1+GKYwkJ!5MA5KXP{ML$zpM+2<@~1a?sehd>Qh!$+PjfA6#E7 zbrQZB-u=$tp3}Kc?#M;_GBkQffn-%9TKDPMJHp{27A3n^##2I`sU0irTt8q&p!0w= zH30c(p=EKognek?!g!eQm$PwbPGH65H9V7!)c}0zQ5ABDCCxtI#tj8ikl{`U`T_tk z&A!A49rDQqqX(cT20=aIv?MC&fW}5)rb??EB{;YP4IYz#tVRwjdoRa(RehiVY}UyG z+d*#OR0(op=OSzX!u9o%$3wsx< zJq!LiUi+?^0y+$c^(ZI#V(}Wf2MGTJIMpuQ(CtkC{O+g+3Bv7NsDHKT-zPHZy|;!Z zW+J7&a&9%YP{a+1XEp{SBjHKKjJEI0%LA;a1h{&s#zgGq==T6cO|PjYr=mXy83(hE zmT@3#rkJt0?I$t+0l~m!GF|AK4h+X?rrEQ?#^itKH8Rp6U$F4yP?FnR)>r7Ole!{< zH>c?5+xa94Vq>42 zOPE0T`D$c*RSksAjoJCk3bED+L{cO_$W`Q@fcGFb+aT;2VPg@VKm;Wz7;9q;?mtB>|tu~@o^?{1VSEVJv<$6_%~ z*!kY67rBHJ`*jEXBK2br>oGjXK_`2+=UMQ{G%@ISpqoWl5B_t`-;OhcU7ah410WYJ z^#Z2@I<7G|;UEYa_|9DMj-$&W$iIUFHncZ%PxwUG;`WZq?#0Nr8E&ULHGnfLEQ3l$ z%SERaZZSdS`-iDhtQZ{mdSZTiCm*bGaM3PzT)@GFBMq zuISXINzI0RjI+WnXTTmmSKEmy#zgN&)!p8g)_25h2y=&?7YJ2 zjX(T(n!-crOkQOaC9aprQChBS=;Jrx#}B`pSZk8AN!7cvu_Io9011Y;Frpg6ieC|f zNsRcbjpIj7ci47~h~%o9`_C3o#!~QDC~x?}wQ@v)PU`Re#HPWOSYEs1bUs%v?L6(H z_a3Bc;okd4i|DVA0~2`rZJNF*t0lB)O`97Kqd9rA-1*MCen zH`o1)kyy6>x$uZx9~tcaN>741CLFmoa>E->uR5?}L=Nwgy@m9BpJV}GrL{T9axJ41r|*(xq=DOk;WNk zo?RM6&g5@j9#U=(ccpe151<|fPkpqxvva2n9{SD=9V4i5;fqF;%UQUXtOQPn0qKOcc`=xjoZ*6okrONTUx%DC@{!WakulIL;0(P* zpE)P!&p872S11@zXUP%-7gBHHsmcCio?@;%o3+x`q$*)O*;qryuIF`mEfhS$M+E79l>(BTH-a`y8@#UQxg*# z!hwW_Q&Xe8tMCK;b8E0nVPYZhD3)iE5w@n%`DB0Z;qza97SsQjKI#3|I!w6gQ=2#6 zDhWNc3RQUJOE`o_4eJZ>i9{b~s}ufx{^1BD8YJHztE_nFD<}B%e*Xuyj9;j4dOD+- zoUyOV3dyh*NKQW86*!x=6n_655baxCxR?8MBu5&EnG456Tg zu2DyXaB=NiE`-O1!19nbFd)4T7FX=De_OD(KlR0t^vZ*G*Y|FHu>}mHBB(xy?3!5}jL2F_PtW>`wKS%wCr{LM)Ip{g* zx!#eTQ2&Q64569OaZ%@8{2zBZG>ia8HPJy55u%%_;%Lok*`Ps0X>Bq|h=j>H;}B+; zrGJhH1Hs4{RBRS57y#m#ECVBF@f#>X2Qcz;^+)z!n=+bXsp3QUA$*P1#&$@BiJtuS z#w)|S?Dv7Nef`M5O730nq`gm3?xQCuFDQLqt}HkCU)z> zUD2*TS$|}3A}gId_LFGheILZ|qiepK^xeO0cO1Xx^qJEgLeutw)?l|NS#!~3|Mbgy zXU`36tfs9Wer4lfFLup^Jv+mz_OpNgN@7QK&5e)TI5>x4YlmZa0}J1*MCa7-R5(4+ zp|QCmTiaD>nW-tm$+S8FqQO9;tc}vkocH|p_sP$Z{{Zi~=^64Y_uPQqa~#md!6GLn zmT^$RMJ1r~fdlBE^B+w9DF3hBv6ub==D2%ch{JctwTi%K1hbun11WRqpCgDOntR)k zW}`Tu2LMfs{9N~gfAU`cAMd^Ue|E>8gBSJ9qibG6FAiUR^4Kej?=t!S``Q!N$0L`U z3EPXm^j}}=+5eY!k6l48TLJIv$iRB%4W1lc>%7rd{y*ON!k(R8`CaXU`ZW)8$SGi| zdlUF7Bj8&xh2_9%F^BEMUdMiq-aLxQKyd3U!yqGtBe1)vYr%`c{S6}kz}uZp-r42Q z4xyb9mnk-RIV&}Kt+2U<(6ke;W>lS^3TH1^0vvG*HTj|#=8hT*xZd0yORD}Q{C{T+ zVxnfvEKW->IF~jwlAIQYTcAM-VjJp3P-|#^*x_VB zoI{~AL46kEz-@3JMKxPotRlAo_r|c(rAtRI0=-T50Z`Zqlme3I(jI5pbmlrJ9FD?2 zr^8=>XtW&9W6uZZUWgV3odeU6d0n#` z#Ko<81+rS`kfJ$c?cO#9377JMCU$!Z8|-|^FAh$#k%K3A1FLLJ&ok8>@%)M0+SYzm zQzo*nD8Bkmwo&4Jn^zaYP1)b~^4v3b zviypAHTU2LXr`|E3>g3d2ih(sGRV7)taXDt6q+y}QHzpe9~tJmIGEDUxw4ElEYB%oWz{4H-p z&-NYOb5-aWj4VtPOv#tPPY+$Owp5x_I1?@;fY-aUF@6*zVL`@1Bt5wE^p#g``RI!7 z{!O3AuCoHw@7&V7hET&kU+X(_4`mO4hq+kp`Gl{*s^!&Nz(Bz^#=vPzhqyz%yVilI`u z;%E!+mw>srx_`3Z)tRy0kfH*Wf4tq~C}4fUSXzTc5UDY=Vz&-{E3n}OUT^nj0y8A% z*Dh*me_ewhKqll*)PSV7K`hVX0n3UoA#h@s`RQc#;lJ71BYQ!|l2mJ65RvMf4rb?> zS;vfhIyq_^u7b zy(?uAq4z2OFVK{~R3-=Y|4?7&w1ICGc=3*=r`Jy~KTC?Jrp-^1HbGV}VKk zyTKX1W%wv$UG@5lZ&7b)#d zE<15`?dZfz?a|`8YPB~O&iM}+N48Cv?NGRyNaCP2>j@fa-@atB))Ow=#2SXb*}QI> z-CX8Peuu0l*BGH-dpMWxO-EYAc&tf{^wNaVU4 zt9(_DznN$GU+J9vt2od8yAe76D47yY?%B}KO8pZf4>A6Glbwb9+E#0%mhB|0$-VJI zk$!63B$2&i-N1o%{6YJ^;h`}vH?(Qb3ICd96MkRP;Pqc{Tu0=hZsR^aG_ zdc33Vfhp9H-<+WVb0v%m=w(jdxqK2bFt#BK`*L&$j3j5?MVaN{a@6CYSQ)ZQqi&Ci zMz%_raZ&Gu>)c+8WOP&}s+C(ODIXIIEF0>I3W2$0TgJq6P*;C z)qx;<2Lh0|g)W7>hR!X~Etd8%pFVTNPe1<3r47~5?<~A{#hLWR1#LE3$<-^BHfJRg z)li}*YTK1^CzI)vE4Cf&Nd)SNM07mbo0Y@a%&fLK8|DF}Tk%LJ8tpS?W1nr0<|6S@ zg|{ZD?yt=qW8pc5DU6QohzCkFE52iFu);7?sj(eq_t#3^6j-h@7s-vbKO39X`=dZ3 z3%SsII5_xmmC*=6=mvfzw&8+?|2;Q~y>uXl6H!GEcfSxx#A4m;rAWA3ibRWDE)j{v z$itWJ7`pP2kt!Pq58rk^i(gw@ubq^uYadFdGqd4PrjrQNQYlG-#C1TF(zFwuOelOj zov!U3ACgb1*A{!29jTFOIDbt$b!NV{>I@a?iImInmRVXq*ApJwn~K*#)wFjkH^wj{ zg|WTG5nn5s+B+7j_eUT;Umlzjk?xkD7b1$9i{QXmv&(b?s zd?kyI1dGWYhZ}xTFoqjA(;upPyX8QG`y3s3vCac^&|3uVKtK%$HF3Nb8_d;b&Tau_ zfj!=+RY=}Hd(tXL`WKFaXH7kPmzWsj{VjhwJwlZ7YJ#-+N;{j4)NwkPl{2G5YlOa? zrQ}BN1351c;3f!ZqwU-psbBg=cV7UD2qDSG`v-c*Ur&}AK%$u?!Tt+NFd;hzN<$nQ zIp zr%@~vb#P1pMpDXWNsW9Qha5yuUOFoUI>Z2FHaCAkG1u=T6B)YQnTpUNJq2WNgj%Nv z@NR_s6*gySaXlkc@jyV+>A~;-BWO!=1RNf@lkBc<7JxJ9s~ zFxzbOrn*Bq+48D1ID&Gc3PviIb|+GuE8p}|w*fO6P;h{upGpxmI*1d3Xi#2= zWFuT1)ibPW zb(+A`YUJj^ZSXXcQ|T684T3dK3=9lW_BfFU#!Ld3Y>$Es8h9sG&s`z7L!J5#{ z>*Oq-VVeUh{br$%uUjb-7*2o}3sMK#<0pIkT7PL_tjXIF%P^28m;!~iFFehSmwj4l zc+yPalw^~ULd_7K4_3eh5ihO;Rw>zpxPOL{CL%T?x70yO#_1D={zPt>XlSuuC`D7N z-rqa;in*qDz!nmPUaw`@yJKG3BqI| zzf7Z!tOyM4#gfR6)ZKJ62g7XV99g=;i?ytd%Z}%`Ng!eX3whk5&FSAuvsecOMLJzo zZz6|$vc{&^_ro0*`_Inuv{1*NGz`x)$ z-n;z^kkWyS4vbwHs+07;V60b;iC)H=iUaLyu`iPm*l*50tnSX2$^LK~kK*7rih1CJ zErvRLjv$$+Xt#zTS9Us6j=k+E&Vc=}lT5xxW((clRpt)Q6uhzyzD0afQF~ZP7P^m~ zwu6;;EZir8qa=erGjt6_v$B6qX|j~re;zAR(FR>LSFYS2#?9az{tDscHg1g()E_?r zS(oYyf5tInpr?16RReN|O*k$3Bj-c#xoeZ(cx3k;6>sG57QM!(=mi|onPdz|!~~Y2 zCQI@nXi?1aAX$sXx?i9UJ(5gzddKpVu0v{f&(TsWzWlwZWz-*E!s_^5AcykhgEDuh z`=v|gx_^YeAVOcbz`PE8JWqH&?D>@EbJ(N6Q27b$73{wO@BYuR|Ae05v@$dYa%E_t zbpY6o0f-jmGA9-ZIt?_~JD+vZM2!?}x17w(vWxd%3EOU}hKtdZ>hkzp`*1Fu4-EO<|>4;}; zbscdF3P;*b7l0ib;ZmQ5IxhBcxRJZL^)Q#%fm|mZw~mWF9cfI#wabyX+=l~yP$XzV z0j?yw^i0s(;cE?z6SUr6l0A_<@3JiFaPh-Iw^W4griI=WkpDQ5{7vL7?@$84-X=2Y zLrXA7`3lMU+&aT+08$@!>OhyG6LmPz9nJ8r($->G?o&A@LjyBlelP4`tG!;BTumI; z1xB>MV{~QJ-fk=7O8KR7;ll;Z{{~*-88Ob=AiY7ZFrSz)XS8rXKRXa0qc~PlfOCoq zvohoL$*RuG#IbV@-78kY>D9Du&lmA}|8?g*sv> ztxF~WY2$gI?7~f-N`aP+z|ut6!cDSzUhgRf7+=^4L1$o~S zD`yI#rG|lQur7X)WC7SaH|r?nTHOz*fPJmytD$n*|y9JN#%eZLZeA9`DoTL`1`jv2p^(I3g3lLcqUC zVb2e3I}hIb?=&^mg7H4=C*_=JEJAUww34!qAB4IS%Olc`CnLE?WTFq~FU z#19|Rv?4R9$EXlTT>QRQ%P(xqZXSrPSf%*=A(fFhjh9F|6;`YOg;A1#b1?6BxG~^f zR#Uu8tDMSy7%yP#iqG-p@RzMLOA)q)`zT;&Xn=qqD;7K~#PgK1nYQqTo=AI7Xv?%} zb3D|bFr1aW%;;5IC+9Oan4K*^wuw;`2i=wyIsPZs78~A>I^_K-l-|n@iGEBPYQ<+Cc-J8Rz;;d<~Afsv^z;>N2`JXf-`7Aj^?8!*oKt9Fk69} z9(qvcU7^p#Da*E6OBw~R);UL!;118{O3di-;jIH4!>fnahH8MmIJ==rZsvCPH6w%C z;^_leyABsZE#XXOasiht$G-8(qxAkaR6dSOfmhhZT`@BKZVgosXn2Jl(%4Y8eOeLlUvjZ2%ZA{ZVxaWk+Cmc0}n1o=0qtp2pB9n=~Eu)+#FKmIuQHeeB6 zRFw#$z?lM8y@vI%_$F_f%T)9UVw9yKa+Z=-U}a5&`m5=PFBp&O7_XWlb8L(ct29`l zF#Hh+At9t8^+{l}0Qwj;#`K+pHA5La8L)6OH3RTE^!;oxj<;qQc zU{8vTcQmhJ^C}jQ{n~N~qc{zM?o8ewFsam%*j^EWdF=?5(3k+jfNzBmfP|AnV!kBm z5a4s@5X&l%$ZCcWhX@Vum}5jpN8zVIL1D4rumu-Wmcex@EQ_(0DnsTR)Tl*qtE~+i>L# z)$uF2<&Rx`ZTBP0NGCcsmk5saCxog1RQM5LU=)G{NV*?9glIMTltAr};mHOW$USLf z`GGu^>c>fHxmAY1L0aW`l}{N)WI0J1X=N2f^_3|CR^Jmb+{;F<8l-tlnhFc@ALb#8 zo+@4CqhTcv=1?xmm4j07)@^3Uum=gH0`{vO~ULA40&We$*%;H)JMw@QsrIvGsa8!AQcagZHhW+U@B1e zLL15e@;u}X^)s4Gr_(fKE1*deGKunrOz}1i{2L;UL4+sx%KBLFEv0meX}!sdQh?_} zeLf8&1#w7bAw5cxx=IY1p#W4wQNYrc;Nk*wt*y1B630|bH)63GBk%+)%+R{*B`QMw z%=1x90yfOQ#gTrZhj9ohGYH5WFwg+h7S~H*v~XIYM0d~+0SVwqwuO)geE}F~I$v6i z4($RONZkNU@7f$h1Bk0aoFSl@!;gz_*;%&L7qy)=kh@(e8N8Lz)sClG&A~RP7r3ay ztrlSvgz~nDc-5Bb1V@r+2?>R|W`WCMbJEE!)m&0^A~_ha9Fej+8-zAO;m&k|b`Y%| z9Nh(#xA^E;1pG@FkGS*U7{t#YxQ4qs2cQ~g7Ih%pDHSxqi|+;=^0Fg^J1as|W(RB_ z2(r}x0h`L1;HWuVv#Z%< zl2^c%5A6)DB{*4~P}xyAVB=(;6{poguo_QMcz~oblU9<~VZq7J2~fX5`X`I3VY4&!^5HNIUZ6j++LGPoW!(#$ej~)}W0j5AFydbB>ay*`^a2j361Z62CQC1~n zNP2Rez0KZLNmqw(nh?Egh)Y6LSeW4iU$vJ~f)s1X(Xk|5%}6v&8=(@0uVsWY9H=uO zZ7~g+Q!1X#m4f>@i1!TCv@j87lBw~S%;F)O)QcTX@@bku22x{G2p1tyIHy{~XvHQe zuPl~2pbzxgR5;Km+XhamQH9m~83N*BUTKgs!TGq_P{o+^Wm$Wk77MXTSnd~cG5=7= zs;F_!ZvY#+6pN}d2&kk61XAP*VuY01Cqo1{jPNBj|A9P`8ynJd1pNYl5HLIjt_Y<2 zCC~>cz>+qc4i6A%Efg29R6IzN^o$|{E1*9iab)&%IWMgQ@Ad2hHu=9XU6d*bOnD}G z^1hz^{=2)kM?{tmMra*~X@q`UQDn)IEFkMsDV&QCg4YtlO^8+1!f^%SS&fmK>Ua1X zX~-^aJqs%qB?aWI5E??rWa%MHTUH)ix7DJB;*i7x7?GTu9}5GufIPlR!qYe`ZtSb?jUV3~4IV#`iV*^)YrKM)!|6mmf*(o^ zXl5dm2Hh5@dw{kx8d9=3j7jTdK}i=xWdLMxIt^MQmQW>*mu$agL84N&4BC3g2t|iT zh$XhhKpc1UACEK7Q9t)=_ni0K;&}w*@n=8}@_EP*`tRUtjJ)~*##|sp((95)<_cyA zdmNBk3>aF%RTQnU=r@cmHPv?72^3pG6-S3bp)x>-&Zg2%Az=)hpV06JaCD9VR)I|k zfX)U%5}o42jCUYcUk8MAIIGh_etsZZ1L~L!y8(#NsevOx9AsJe)}&g_w`L8g&m74R zl>(TEu+716$9FjDJ{7`VWL+~+dSfI5XNZhjGq?!0Fh)5SVT_9ESaW0YLB@o=XtLR!y3Fx2jB_kZ{71q%WeWa4N!cRhw5Hczb8qIP~TY^1}oRF7StaScvHUhwyTG z#c|Bj{Qz;((}c<-(t`Kwk>sJP-!oSR3e52vKXOmj@)L{(c3x2FVizHmIStE5F$)Rz zAO*1uLAD^NVaD4QKc!&pMN%A-(A`~ z_hhI~xnoigN1*R&0tXBgdVcodP~XV)SF-p#xeF_;e{sbC_ZEpYi`U#7#4XShOm2n@ z#&Yd^WCAn(@}mm-!-3IT{f#&yi9PF55J?c_uK6L>YvclV-h#bCE{ks51$ypNa|7CP zmB%tOoqzHHDkTI(*c6F+cZ1Tb!`QwcFEqnGGl+dkDU=FXFQklWeMI@GCMl^kH$Gb? ziJ}tOLg16383-IW@`vM_&u`QO%Mz?iL3;zcMJKO*;=+Zi-YP7xK~vsY{!HWo96mLP^q+Cgh>orc7J*8 z@l6nIM9OB$s92%u@Rs zgEsUNggOI_Q+IR5IKmePwcJ+^vTGI-mtOv-AxKB(T%DJ*okSevYsOJKz9l zT}=w~6>vkSlzV4X!7Th=s=9lF#fl+SCA!c}R0@SG1~v?1@0j{8m*vWoE+Bh>gvf$O zdRXte?*SJ4ZZC1`2j7>{6*;$ZG0J^1cqT_|v>o$199c@9?nd6jogLjZXky>F^*fRq zdJ^|(;Q1ZV6#x)kAn0mc_=YzDJ6v-gJdwLyZmi3D9MR6v#5(#~c-NqWfmJ)m*`WAw zQyHa^pH9=Nrx!D$+EyJgpdn)=oQ8M=&~rh4zaDodJQ=JRkV3eLd~p|zj;0Rt40IfL zGd8pg^dRT2!wLe2P=|qcM>vOoQ19>W*`8WXa+1z8MGNy$jS8gr;cdBZ&>J4bsVF_i z>NZ``yr3{-G<&`$<*Nl-NF%1*pvEWqd<{q^sRJp2&UrB|d-a<@;-OoUEi121s+>*8 zLD64bZN&!srYsqRHa|MhW3yClYkNZ0b{&%0t-DR+Qmp zqqz~%+W0k!P!u5Umaq^nd6g(fZl!q|<_-b(aw12r39lPm1DWUSh-i=EKA#C%0$^_Y zsTrqOR!Beq~J4Gr~)yZ~}D2669rkQXBfp3Xy*t3D`jbm~kpMbhDv z)Nf=c4n$M|L=RRXDSG+>xNG`si3hDUV!%6F)7G@tzY`}?oTm9;Kg`=Tp!eJ`_jK9J2hCkDwtOvjSC%+v;8%{q|p3OLCjwI ziXOCW+)6_(G&>#PKxHBSU(((Kyp8Kj8|Cx?1_KNRWzc&ENss`+-b7Mjr$mYpMfKjT z-et)umLqq`z1O%TcHEt^DYj!f*(7#0ZIeyn^gm@2+na2%-q;r5`9jC+}#=3%V$s4d$capjp# zdG{6u<^lL7Ezx*TLHBu46A`H@S{O6HbVpewA7M^(9k->NXDqb|SFb|=9@f54Fl*HM z>UK2M)In9FX#sW%Yaq}SG&^XM2h(+0s@8<|5F7QMX-_#Fy(YHKw8@9!o}+YcDCB8f zY!Ooeqrfujiqevk*2Iv%u3$Gt2u?|zubGcI;h2?zt!=G+8Jc$meI6yqDWxY0scvgD z8dXIeaowWo4Dlu^qpG5V%J#2mzMf;N7g&y5>I`7o#=bkmI$Y>-Q!AWG74i zR+qAha_NhVo2p65)PK>nQ6cgEKSx9qcQpptAHu3=7Hz_74#i~T{toM$Q2MKk;#ZCm zqNqLKOC(p~S>Yh}ZqLDUuXI~pk|k70D@I6F9zyhBK%U)+k_RMi3;T%t zOE?^?&lRSCP7o5TvlQILrdw4%yn}|)hh@e`#8{mTh0DcN%U9{wg@NlvxT$bZ^MtJ0 zyRRjE#ReuCjK>AhN(v(LM%6sjtWs%~J!JA&6lpYLlVzhfSetQ~4^kPmxz}zMjjG9# ziu;31+l6mOtE1JIngC-G@R>w4;y#Y=QKr_LNttG9m8=>UMKzj2UgmBa6P7v6n9n-j z|B{x;qoS8ANmh7mMX^a^hY&fzt31ncVc}G|x*Fz0zhq%(Q48AM|D72SpIGWZW9o9I zd<(e0EqM1A8Lyx3-gF>p8@l$9yRH`J&*SK}dzgUJxpvLov5v6Y8>luK4ZZ;{thwo6 z$X(sy4s=%^z43aMx_geub`J=5v42E4`ojULMq}8ek9b4r3(s#jE75;S4JAgV8Fn5M zn0d)_e$m6V0S}_rBfq!~1*ZkoL{$gsT!pa1-o9hWRI|l6GjQciXV1nEudGnnf}FJ2 zLnglM(@&Vf;%lA9*WYyh(B+J_h80kQ*M|9ai|`EUz4s%-^EBdfzEkzDz+V1u%8o3Z zc4X3;4LjrGrc09zDu8px`=24v#kg)n2JPX zBHWmGI|$dq%qUAv;FRSfWw1b95z^1l=NQrZ5UQXzRA{snbsVkA^nzQ0@Xzu_vOGkt}wlI`GUx3 z9SFP7d45AjSv)R80L@4QKV2FkvO^OFrVPbMI#up95wW0a;ye^w#Rw2kvPMct;tcXD zlpRVKSx5{p6gD=Izlg+A79ogT4Hv;>q38`seIGlNF06Yao#)v$7%S6~Vxe!9RC=z|EIl z5}eg`kOP7D0pJA8%P?*?8b#QkX%x{#6)SfFHkvGTM#LK6_?i>7igp9`-+)`eRdi+~2sOn9mtjPCe03^77hcW*34J zOh&KU(gDwX+ng}HGTYt3^Vcz#vP>j`bP4;_C^|0HOi`szS&ah@We&jNm<0$Qg4o8g zHQOTHW7LHyS?)7L4W+}1LS4sMU%WKDl|n`OY?cPOWhTzrQNoQDYNlYWEFGmLUttm< zt{qk@{}z|X*YLFM_GTc1*0Suwa*M^%CVZMUm&g zeHB&Ge^B+)s^3)o2{DxKR?Sd61soV4K}Jzauoiihom3z7FwK?iX2r^K28F?;k z5R#&AvAF(8XvwUPdX|u5;eV)Y5<;Nc!-%E@0s&&|B$^6bqVP)txPs_1XcuAxS|tKR z9%^rt9M<;}LYKr|N=H1N4bleUVkMA>kVB`SUx_n`4;wfc_@8_sw;`mHM2~}Pu{fKz}p zQGh|hd{{sa;6a~dHWx>&Ja(1mK!f_&5;~6?z&bc<8X-@m51amc`orqf6`Th=4&#f& z9E?1OIs#Y0JfM%@7PGd$GI_@!m5F@$mgWD+%d5OV{j=Ny#3I1@P6`0w#(EcYCywxK zC}$+(Td-8KO;O53OP*AdpO*~rPS@q7H)ft5y44!q$|E9#;vB=-Cd4+InmVPL;gKb3 z)O5S$z{9!e`*RJBo-KAVL`tXl_H1PT@CD z#Q-^IDg8|o4I*_*s1S1Ratjt)x*gGjjsuETFgd)!mw*XlO2An9@dv zWGW3X@I8>YZUglj)+HEb{zeNy2AX4#W&Db0p!b_KvpBQPAy9FL!Aid>IpsEv`HRo^{*M?VL%ZOfWO|_* zN!(^ZK@cHIT}bS>p=~$o72#bpI!qq5J<%_iBR7PQ%@=*6u(@8agj%PY>g?bpMw<a_#2g|hP9 zW*g8{dQXbhxmT@x!2qxx!Qx1}koD=z0_SUc&=zwjOr)0@rymOR@{L2B(d?Q^1!?o3 z|59peLnzJxW55uAsu9iBu~BD~;S3f~alxF$K!ELeS)!244ts+ew8jyf!G#?m6uN^=nt^+UnS z&b*T{Xq@1ndo;F23Wzh;cmaMXu=Hbs-#im(;*GV&46{Jc)C)i1WviE2Cpv^I=GBEh z$<1-h@4=C6@BbI;=Gj`-Omn;qMa^U!zWK$&0N^_&wl1O#Kty7B2qCnE6)c(zt(mRY zh{?e`$7G;H7)YRIP;BEGK%WY%0U-&pG|w*wO*wh6^s_;i&q*`tDbsx3{bQt=%STLR zRYb}`$l7X9<<9rIGERUThBHZX3kw&XiF32OCG17rT^J}3xW@{j!3?L`n!(nYKN-}u zUlb9`2H;d+c$$0Z-O%~0h?6%6N7xmh_Lo(iK}7Jahzb5Atdh@GeZK0;RsT$_px4p6 z=*#G>InXH$zeFe0cWDGB6GRa5DM&|?POZpa zA8tw5uCVE`nmT2MWSLBq1$JFVM?*QdT_JZ8{-Q@9k;@y=GXWhSo)|*s$Zz950SWi zTu!uP^bakNqo}Y!9#%I86UweX*~KR!R#qG)d&`}g70N5aPMX7m5as|*rgy&F0_i6~ zIWAPr#3$GeLYj04;{q8pm>F=`!9#&?68}9khl|ZS^mj{mO=Uz#-j|*eQ4hv}hB*B} z$+Pf69sp^07VJrqW*XzT4r4*?s0l}Wi*Jn^{$hwUY#bX~S3mK)BF&EQrY(m76r&I!nvNp`O$BtjQIqmE5jc zCE8-8jh-5r5{<_uMSGxCFe5C~!ZcETUmIhzG+8u}5rWmv-SaDcy>GYw?l+BsbXb0EB>Ux}%B<+=FKjqQj1>Q|Qva|40U%%~ul zXy8JMknqkRu_Bc`DouFT43)Di0^ZSgc?*em&49VBwe_3V|ZAGG-}F785^W%fe#ww zfN*LPK<_=Eg-EGFiU%zK9n$Y!y2=r4m`?FEu-!7qNkS|u%bS29q*yh?fFeRG*kl%OE)06p3UyOumL5$(c@%i0Kq4lvE2yhbwHV%Ww30>@Rq% z+8j&^gVG^6S<$i8;zA(B87?h5Mdgs>Gv94;GxR%>62n}c30WGEEN2HoFI-Q^5Qqbf z7r}eg3Y9k5-6}O8sYW-RfJ5jREyh#>=Rv7Ddq{xioENw-u$nzE(z*>$-(;$YI4w9W zBcUu%^X+~y6p@VGt%?s($!@P;@L+gMRL~@=)b%nHWeY8faCmxSl-Z;iMDS$VU=Ynw zk&Rd^HUr0a(m_SB(Uj3_(I`d^m>&-5q z;h6Vcy0h2?k=ZJ6yeIDrS(yTF_E{yPN*Qflwve{Vo({D*&)cNNf!<(H5l$7h6`OgZ z!o(;VtBHi2qx?H1TN)Jvl8KVh!0`sB$-x-xz!SByE<7+F^(8g4FXN49)`_QZP8vlD zoHd+f+NRP-xK%B3^B^yjbVt7E(2ZYa^VT~6(AU#2T#1S>Ot5+pGMu{m!Spy zsOo1`zl9d`pH=T7lTJe|^*q%|4WPcqrPO)qF6se2i{k6l52&A0zo-6_w$UznA^qRX zTxJ_{gt@Z9NoTd8vK8ebnI)vQyn*-;f?NI{w3Sdk?9l%pVWed>X_n0-!0?hJC=?rE z96U*cCFB|BlRrNElT;GIYOnA&Cxzl3gsb9|9Ocl;Tow8g$Sz^i=nI&d9*n@lKc(-0 zf>l6*8V?;0r=h#xBcyx8za%s=Wp=Fz-~qS|VYaYyl^GO=o00T44L08-Pna}e55(w2 zHWGqHOjHP{1+yoO>1qpMMUUg&1ri8$eXhhe|(0crG~)H!q({UIV!! z1T%68_L~uA4c!n<(WW{wN;nVi5Q=&c23$E4__?p0qf?U=?dv47jWF zH_2wrYiRW$HW!p0pvL>s^9LZl+c{+Hig6>|8aBbo7*>F+20xq1N?}8iwtDQGs+;}H zZq60!e$J6a#tGed;W-E%CZ6fF+O#$kTmV99ColAV!^$*A4BjVcC_2c0X44|W(HV)q z%@i~vc=k4%1id56!JlY!AO@NJq)G#k<9C5>BO87=H}tfXe0U4DOh<`$cT`UyOE>M_aZvKZ<_E!?Zl{5ujT9Xu;i$1Rde zi#elU{m4tCPZ;Hp-yfKRZ0WU&lYWV2#Hc)76nr9{u-?ndfX0weydl*v^SE3faaz(E zl>rd3(O@^pq9F;_G(h=n=iSJ&$lLkpSDj{8ZA$t8e4QJP9QOb!|Z{_6qW;*!|KA`GZY7KKx1E{B+uO4VmgB=-Y> zZ(whN|E}CQYdWBDV1$q3YNBS^kzLTtw6AW-P8c2Tq{Wxm%)y#^^3rxg z#AEW+^k`|OR^G~^`mQ>mI6P519F0&rhsKoR)_(Tcb3Hey@Be4iS1|iIBRtY(M_?$m zux83O1XRU*X4C52n%5EGTpH~U2W(L*OuyCLF;UA|vb5RiM#b>%YA+Usz(Ta5SQ}Ja zQ{5=Fv`MqJTIcezb6KiwQf}Ho*VL~G7q#UGy%A8LhmH9pMpYOKi|ECOOdHH@Q-Jd! zu}I5EOxEPK`3VTP2@R+wckr~QV20qXSuKy82t>(ZZ#!jHu|KXAZ6=r7C~f& znIDNUMv5L+gJSxApGa{U#kN8Y@CwHqS%vbnNx;(b`&FyhPP-jh-x8VbYyYZn$0px*GZqUGxjt0%tzGSl9OS^90){sL>5?$v>WkaDw8BJ%-WB*Q2Y3pm6AMGUoarp zj4pPZ89-6Fpj^**03T*GyQKwu&cw-Kh@LEkpf^h~*k-Gih7!)sK!eW}j|K-%$9;S~ z{o`9AO0Cvk-`&>N5w@gz!Y;qp2MwiOm5r$i;)4UQ`GCXI5wnpU{)j*hAM^a+v|=} zPNrtjdi&z|Q0X-E$m9N<8`HLUPiO}~K-}RK?)u0O%6PR}0?JOw*|7O^O#E4YjggN%-p8T61-YL4%bQLUYHbjJ#=Zbd!q_E%qgYeRJ2i z2kNVB@c|EYlJ|&IZY)03(VR8AV)-Ut{YrCuNlR->ms6W5*`iZTg5r$Qyl1j$yTAEN zV`^+}xVJi$GE-gjC)aB+qeV)^lwj17rdZBrp%J$2nt4qz(4waZT%GpAi&&T%SR?xv zB{(ML+YL<~lh@nnt`j1jLib@VOVi%i+#}0peirEJ?HjGjcpD6=q=kDD>uk)c_b+0e z+r2B;X0W*~fo0HSpG_JkfW(j$BAt^S@B z>(!`jzY^$Ox+{8W+#I;N6i2;;Yo?cks8wLZi=;VA9K-M)cF=E}+FEl|TIlc&qTS_LE}nBsKQq;w7;aLKGD{N^Z30|tk=EvVO5Y(eb6 zV^t$nOREk+LwmOB`&GZJ`aSSH{;LYv?UlJrCU03p)Xiq)C1fQ#A@_44A~Rle z4aF-yqFH=TaWgWr|M3xWHRxaXE>RLu+onq0DGb-y;7fZ`tFUX+eQgQ^g(eipE zn9bqxLM!*Ua^1z|$&KTr3q%P>T9KQGpliKbHmGlCt`T1Tq$4jplQDSf?!KXK(Y<$H zVhKJ}5EZ-6Q4ImLshKl$BGF9?(vHu3XDpov!8nF{ zGZ&7yTP%UhyskD+K~42D@0fk|jB|2WlokxzZs=m{fuq8{!p7R=-V88ZF8p@%TycfF zTB#lF89(F!8c&THSvs%(>Y~3UQe?j5UcAmj+%lj2-1lz~Y*b&1$?j?xJ#p_m>2C*S zevgz2rasB!#+m+0tCCWZYD@^W zh*LCev`cY=s`T&4LYN5pX{KPXPEE=6?Z7ALIO53M-CO#W&-N){eAVi+aoYaCr8i5c zeOG6VMS4<`v2$l-@0=t1=)xx(0=-%)-c9`}JKqwjzxH@@&9cnGQ!@vRc1KdJPS)32 z4vq;%i2X&WJHqe|m7%upmM%&s?Ev?(I`~NswItQcBC8n2b zS!SJoP*=sz(ikAx|TbK7_AjRwfRleTUCDpW)OmD%K82b zQ6?P?7V@oLr{9>gWTxqVcL0%@%y}~3>MJx~|3nzg?0SI9u{<3SOF||rv^U_m60HeZ zAYlReq$Nj?TC6wx5l}1?9KF~qNu7nthx{@d)OwwNgyHriLEA7t%=StD)E5dTI{ac_#C6-`)^FETem&nr%CxR(Hid)6|F|nZfgR>Jxo|4V{AWU z@Hoxy_;{LD!_KA7o&Jp3ms|wxVQOioBJm=l8fZhybucAI46ADad&3XUQ=+ib(U6sB zSn9kvrt}IgK|LyMv=%AeYyxt(QELFWjheRA45D765~!D?#+jla86{uCdZ3E^x~9{NT17j5X$}Z)E<;K3&M< zR^3+mm(|YRce-hq%VgDX(nDElt+7-cYVnLfe`Jg(tx2L;jRr3-!EOqG z{Cz9_$!2|$i7-aQ3D_O5rR{!x`XTt%D3@{0RQswEzE9Ve1Ul(Re{yrk2naCM(b=X% zQLkL8%?eDGYkQn&zvWh{)=9NE5H!zT=rRbjc}Y);Jsh_uM}Y36c>PGWhbK%_6A`7Y zA-3A&l~9-Hlw;C?7~o~p=H&)IPZ^ExEp5w$yXN|QykjrL&Ex&aYjRA3)mIajWV3Y> zk6aQ_jaTP@4ppS*Nd_yKPKm&gOl{9M)T$gal<(XAq|0iYryW?-2w5V=OT zb3n!^n+PxyA}_Wl))Ko*N|Lrt$=g;(4;gpu$AWlKQ#51n#x)@&l)3MBm) zL?anBfhY@8e~oA_w5ooK=$6y<^(JLaDi?C)Ga1S)$c=6-JsAzTn$BJ|Uk$bP#ax>isaC<(rNV$g zO*Ur@OONenlVQm7RcEcCV7oOL4oh0jEVJUgqAf2_wl!8y08;|8KZL#N*hr7bEF>(U zcH_+NTD`qHj^`&v=ik}dKEP9nnyxcF^=(-FwUV}D?J}1^3D4`Eqt)-eZ7AQz&5`5u zW3lR9u^R4kM2n-mj8};n+w-E52sPhaoYzqIe(=F-QyYedhZgO5+tb&%o-r2Mhi{*2 znN-uQ9j>^UF}wS>pG{Q9<7#xEz6SWU6Qxh_OHdy!41D$_Kx?R?!XJI@#FSN4$XAtL zHhCE~XXWjcKM4g(U~1_vto-uI5KD}1F(k$(w1HVr(E|jYxhREWdPMX5l6T(|QR~rQ z$#tG7Zh3^Vz4QttKQeaH)_Rvw_SmN28R8{h_4>iV4IwxE=5O!2_jlcWo2i&`$q7@} z1`#TGgxOg$aQzGBuBT6|dvW~1csz-E0~TMqW@cp}m#tG<@3Oa*K4p^nH@g?aPE{=2+*N>q@`*TIa$4Sg~Sn*IK)1keq62%Ln(r_13+6Hf>GR zIgBR2zU`Vb@0y;{RS5Eb?RrGVPHZ~)l?&&=Cpn|vvG=Lco4>rH>sU=90`*uWWBb9E zQA5XHl|WVV<%lk-;yyaIWghQ@&#ZJWNkoF zf^r7lh1kgt-%RBlR^AN)R-k^N3G!|T>n!lky?YN-W}_CKZ#%#L)pK3vGua!uul?p> z?_SSj*dg+~ty_gvtT|A#)#VO2AN%Ctx+X1CXHjXj&OKl}gL0T6Rr)REk?bZj=lnlr z!6tR2N0$ZSKv4wskHEivHcVpa0IBTyD+} zu6=qbuo9yiy4NjS_;q8(^w`p8!viKOvJBXWedxXWpvzHJFxk{Q%~=oyaADhUN>r^8@t2K(%bN2 zf@WH))Y2uw+q?-F`At>xs`jJ)!xca%AzqJX;q`b$kNx^3Flzr+ku}KBlPwxzW@#73 z7k^boxe(GY!CAZy#)n$I_(Oq$V2ctGs@u&; zs4G+1p^Fo^7Qh|221k&!T?sCaH6ndFfBh0+9auE_O zL7D9c;>tG)Z=Yx>N_MHSdH4RKCq7X+@QucloM>Hq3QGkZxcA-%W{yAjzyl9b^)dA+HP$;kOdY!Rs0;1IP)1xDq?o#yKS^dHc>Y` z_so+|KJ(0ze>a=wQ}5hZ{#g?V`8u2|o2BQ@*%yj<=6G1vtIq9247pmm2lF;r)l$`k zdAk5{EXNSXLa=Ect$GeK_tmP`5mo+EXoi2Ox)qv{)g+XCEVrjwLCiDlJO5&mQ4K9cePp(W^ zGOx%?C9WiWQkMmirdd16>;x{;wnF~meAN9=|KlW1lHd=0x{yDNbce*l8}I(FmE-N7e`9-aY%#m`ul=0^&i7upPfhlm@xm5F+} z!Yc9w$YMm>dN7wxw&9IMtPvs5WZjlmnlrBJtNOW>tN{&+;3MZb>{J+c5?yR2 zFtvYKJK%^1ll#{*?w!SH+Q9l>W&I61J8y5h%?u%K{uH*57wmL7?+SJo9z8=?bU(^Eaen{T}7rW;FqRJ^>H zzg!9qc26(q9(uw%m~V-sH~#RZ8*jXc`o-4Yf9(^3tgUiUi(RWVdF7hk@j}r5kj3b5 zSWctTx@7m%J)BCpt!7kydR+5lEu+I*?sns6H`PSN9PYYDQ>h|-RjGEz(KFQF?%V0f z_3kh2v$__|-@bGr-0ADyc*AHe+x*G;S9@)BLDwpT(uNxq*_3Nu>Rh*U>Acp~uQXC? zl?A7@%qr>{pNTOlMMKjPhw;L|Nz0=Qy@{GCMd}mj zbLrA)`CaUqd1>P^8@vY8-8*c)^@k)X6&v)Jy;>S57jCAlttmD(8WmHKU@(%}QVYY> zu5Frpix(#dai*=KH8DCIkB-eRU6IAILw>eedR%y$d$X#wsu%0-Xw{WfH_S$WJg2X| zpCJ0G@r2dhcB6`@F?FR;r0QQ#>rGSUlN zhV~oF#Gq`3gb}i#e0_!IPzVqwr{Q+CS!Q)nbRr!iOK)~HqD@X`K~53~kKKAx*ekqU z-?=c|;5^W1_a+}J{q4Q4bW`RAv)0pZ;*wgwD^7H+kxa>@K7_s!l%ti)K_>A+pf7ch?WI)?+ez+N>rU-&7a?Le46|OfX z@4uyX)xE1I=bx$I9@Z7`WU4(r57pc0FV?5VzE5Y>CCM$8@T%DRw>lzCd1~Wfpc#98 zQ6oDhx>Cz8Ik@?I=}o)Cb8MdDL#o@~psiYXb2xSW>~BL?Fq#-lUuyVbB%F>rd*)qm zkp}KteRsCbYbyAuznwpS?i_wg=f=m)H_T1Pua}5>7XNta@a31EIfLKQ?*&#`O8tH7 z)-BsMZ{2c$;ye(N=GXzelTcXJq86$AsLQ27oPs|0l<)|*5!s_!RkZ4l6d(?C^G8{e zBW8F|d0p3q!Q;cfiDykW@9DrQpwuweB>*m>pJ7F~3Bima8SW^1p1{rFBGX%9qSgj@ zq-W3j@DH1{60xc;m=6!kwn2{Si>b`4K61Q#$7mC<3vfGPY@FqeYK3886=6Ysbmfx8 z#*>QBameDZxs#?592G4_i-lr=m?q203;r(f|5=R3TbpAmN+W?#eO=$&Tz%)v-#ha8 zp1!(#2fKTpHfI4BF^XJRF-EMM1V?JMIlxuPgnaiiW6t8K_U*e6; z@o4eBd^9m!S2sME&kqh1>W2nr4#ux-l*JXIae2DG$-UIYn%ILBSGv#QLfFY>V|>uO z+E}_T>6EDr9Q|X4`tjPMijR7v^fS8j>(VMm2$k8NEsa}TZVUAps+zL*T)yElw_SR! z|Jn<4v}jPJduF~+?Y1!A-SdCO*UahhbzIR|yQlPo>_Ll*OZOtcf|~yGx4-3X+`Vmx zmtcaV#^|hO?a&rnxqHvCt1w$lUA`qJCwkVo7--M0{MrZOOMAOD?6dj;a&OBOv>VoQ zy>9es_KRFEY=#>2hmX$Tf0(zJl9*!uzosFXOyv#4b9S~7CeR4KDZUq+m3_>GfqZ^o zAYV5$^YhMPLvLT9&`tj;q57@PgnjScq{EaAXRS)|;C{`bxJ~Ae!y>!9suI&oS%15d z1a80CU1Km&2%J#VJM5$OwqTD9>Hby{OIwq;PKkt-qYkTVl)UUK`H_))esrw9I5=1= zj0|7+b2PZ;+>@mr)1@Dj#vFc+M7>bzv$#D9Afj6-*S`Iqce*X6yB=G7A)v)jfqU)D z!!=$D^VhHao2R>OXp6P0Yq+kN=A<@x^NOpN4jyI}*sauX`|{DzhY6oWt?tr0oSU1% zn7e?$^uLXHnT36PS!4VwBmkD1$}6et`tTT8^dF8k8QhPJH(ciBp+>yw&{$*R*jQu3 zf|;j#TUrK(o0?)_CXf!wMvInlrZqWd@YSkn#_O$><8GhVZ?`7haxR#0su`y$2=6q!wC0h!Z*$I5xq_elcGJSi#>Rz{#nuJmElm^Y)P*OL zQO}xNUVi@(XUJ!w&l_g8BF@UnuKh{r17GNo&#!#{TfoOM(KpZRbNH}-;OB2!=NZN( zSM>Fb7js5TYN=b6=Ijs~EU+?pG+u_tOvB3Q`TD(V`HPy~>Z%-8I^*;9L{DS#~!u+v@hOyCNadh;; z&*R~Jw>??-%MQm-QbTxsf?r520F1SE}DsX0_|{~zZXa=EV@I#_xgYhbh^;i8@? zb*erSa;nG)US@CU?{k77!`(Z7(r!kYXs3Va^NyMTNjwNOa;*?GT>f5OaVT6XjD+UIr6$IEYNNg=}q|x{V-^F_@OReR* zR5Udr+Tr)2z)36`AJgk9=D3P5_@P)?Hf89xxQg2L4$AbKiU5q1(3f#|2xaeOj{wy{yU7_V7(q1J(T0b5wzHAaPbvt}dySzU5PpPi(%yE-zlV%R=|K07LwPHKNMs|4f z1IpB5YXDe(u4sd>@zAjiVlLuT+7TJHY(g4i`*!#Oxlo$1McluoE~n1_%;)1Qq9a7- zV&PKvwKv>w?aWuNJAeJTzoomoYQ6P+nNsTL17AG_Wer)o3xK0;TXy2)$rCd#oH%~$ zBz0!Z*d+rS4sY8-*|y_n=EZIJq4U;=-EQ{Ro z(k@oE#aFC&X}o3i^fjwiIa0yGntLYe10J9X@4R963tKjB-n3!cs|U{1_d8R0`o!>; zR;?O|bqZ$rNJCw!<(S{n?a0Qw$2Ix5H&;quE=Kz)>T4}F3sT16r9+)7vk{$;?9Jbu z5w;3vI=XrzfG4jW&6G}2m(~qfLnZ^Dct!^n-F^36cj33RV{+1TpX}&fViQa->0N*Q zxpVj}eV^kp)bID~*|Trgo;@dME+DkD^g98lD%iYUTL6A^V@nGvw(Kh1ChX?(@DYq5 z8|_V~sbkb)Y7?*^zCwMC`VsX<67)}pfMS@T^K>iSM-S3t^kRA?y_rxsvT7=8MhCsZ zs?li?2{W(w=*nOcl@qb75IyxjpGRc7*>y0xVu}s*WHFMP5dT4i#pp_#9_U=AcFqd7 z!O4zt4iHgN6u&yz4ff6xcEHSKTsP?ktI`;!W2cjJT7Z+YE}DKS6!XN zrn7jEeR_Cj;D-D;LET>8s!lPSkYb#+~1?W?AOR$NTcQ^Mrb z5~R5zBs`itzqklTxxKns;KZJmaGiO=i>{S$n#elWJRxb;Y#3FV?P249yAj zex9OL)^0?AB$AM z=-;AIUZOYDxhY`(KQwl8X3yui%+w2an@%;@&G2rVAH}~8!kIY(KK`0XP0fh<<~Ept+CBRgFBPqV(-%YTj=8?{E=Qw98&>AFWkuetA?q%@aihsWnMBRa8zjysalUaN z$x?oo%~P*!pXtsmm<}MxTv|P*npK)rLv3}TYJbEZ^q+*%I^nC%#H?%ElSA{El5cKD zPT+FweZKT|t=?mE!4ksp<33Izpr8M@+lkq>xyrC+X>{y*cj+5nlEiyIx1yW*0RDoI z>JQr6*IHwlYF}i+sD$PXmBPHqV;`AgOeKeFvujrtJLq49fLVf0jjfiD1|OJSkjr0K zSUa3d8Rv}HJtqE8OL65|>Qkchr8jnTulOAIorxm9cRyFVR4F|d3I&7sy;swW0B!%? z(pqlsZoW8SE}!zZ zNCe{FsVZ0o6_g?h0m-P2DpGT)32GU&9&w3DTbL*;NhWL)Y)xfLIlMlghR9hio8_IF zP0P_BVW6;to+XJ*PlxN%|I$eulLX}kfz`<)p;+ZFIz!M2=0yv+FxyI{HRL4ekqID* zFtO;OU_$RvB1wN-4~df+C07gK4Ze2qH2zIbseptQtpJ&vvqJ_dh-i~O~vF=pU3MR z_lZ29(W0Azmg?$SrugX3Qr7VlV2KyD^`xop_WlhszMO(Wn1^L^I4m#m1_EA24f+EC zM+(q%?Ohi}lDE{e-|UfGR}J0x*yPBhd+HR0NajdGbmyVHi#&sk%O0{y0b_j6ODK z<0mJ1TacAZf>`>=;MJd!I6?6FJ#C^zwi{|J-scMpbhWTPUb(+CJfXPiUZbe{)3rmTxBiJ57j8Fr9CwXo`U=eB!;3;TyJ5+S znFd!p?QB^;;B{C_w=idnR#r7!cbFJ%5AQF1r>BQMS>Iq`?@;ZwGcadv-nM1yZ-9`_ zFs`KAsSSlZ8pC**bu<62=uD@aN>Br%DgCK*& zRo;NKqHiqH{wkr$Wio)v2_3+Bu$~gpkz7tzm;Q;Pv);XO0~Z|#IKQ&?KE5`I%7q^3 z%TuRI<;MTNc?fjc^HJq3uFJY`5P))g)IZQMN&Ev%HgEFL(qG>F;#?a30cCJR@W-12 zdEy>0UJCcX)w_OL_)KDG-?q)EY`fpJ^x()Xr8inPpF4bH--g8-e2I9`XNm=b4RoT- zzK|KLj|J=HoJYId0%NAlKKdx%o=PuR)Iy1OiigZ;%ZSAki@P*)WPCC-qN;;kog?RG z{R{VWJT`X4fq(EX7#nlXE#9@C_!qLy^`%`Ox)FZs?`_L^bFGPgPcI$+e{&H1Ty@jCUp{x}#HX5f#DgJ6!~7LX_w0S7;fqHKea$VK z=%y5R-KvbU+WLfdEZ{{!EL$O!x}LHbcMT;5{kDK1yTIOIQ|1nsw4u4az5_=)Q?46t ze0DoL5MtQ_A!w-;AMrq(7*t(>f@|gY?Lp#!P}TW&>K=x3=S$a)jhW7W+{bW8_b{Bf z96kolw3PbqEnBv3+q`A#AlwN)n)C6+2GvT8mBsq9hoK6!Jk`=9cyA8a(PM~v-Ud9X zFID}#>OXX(z#x?-73Mi;2IcG%JsyeRNMI?HR}lWkxAGo+8G-H0E+Z_RSuRSfCeR7U zb}^iFvNknIj*CG5GE4Zv`qW>RpmDeRv0!lBf zfh!Y;lfz>Z>kHzN$m-QS5n#=D1RY%%{1S-qAQd2QO6jp!Y|1u*O?l`1~W^Ho4T^Owb_QKK$;%tL@|S<6SoMiWN7P{+Kzo@7~n-zYaZr z!8x^lrK7RMu4Nt=p+2j=ZZ;0wd-f)0xaToZ>1R(bx2dKfS@hG02<<9$e^B-Ecjg)R z)rK{ku|IBdx|5Xc3?z#8ulzAJLM?jj_BR=Ww3uphrl?>c9xdH0$kg0;`$~>qb4zw( zS6kW_qL6cmECwjPMj-X_!EwFYVxV3`V;PXtUFg@ zwaRAaNZi#3zuE&YHoZBpXlkAcBaCP3)h;vAY4&C{5P!Pn(snTm4^)Jr)6W8A6@VCrd*u6tZ#7*3PC}a7&(Xz{) zxFgBfk0+ejwJIwYMwT%QfQ}_sSLrpXr6CgZu52BkWM_X5wOI5*a1*$6|NTe`Fu2>A zSSBF&sd|nJ2LlOMv3B3dF(EaoSpzd~4-QJ-))L`%PZp=7-F9zd0wsp!OM;~pbLK$q zeZh&qch1Xt4Uls!SZhnL)*7lJRdrZz`%$Ot8e|#}TP3NB@Ost1Rs9Oc2gJ$(TW@7! zKMVpyuCo$v42LvUhuI9x!jP#93AciTr8UNNyCpFN6O#j}PK#*@Utp8Iew`oZQy<1r zk^DBjl( zQHmt{DxrU-%;}Dz#b;C9{AB6NUoPG7ugDyA*b_sGPEae}qi&^cd5>B-VZCiv>ABK| z>G!_A{jRP1GM}Dlp0S+|Zs?{QzXe2I@Hu-rZQo=|ZY=$wk9RI|Dru#3_b(1^QW+Df z`6&t1QgQooFH?hiHcqXWu8q`KL#{cc%iSxR{SGVT4vr(A(XtsorMxJ^m4i5W?P#Ji zZ;DShdu=Et;+Z$kZ~sxEz6Ok-@#XECw{E3wy5x@AcPy||^Rg}!#0AKuu+ythn$aVr zW}emz27};U>()>fB+IZ7B+=IhIr5j#<=cW5bhN%npsI9l&9CO>?;``LbHIpsT4qD0A z+KRpNY_!_Gy!3n`v}AJe*pz5f);+%`?CO|^)zl`MP98e6=S`FqQw#CZUk-(wor~T1 zLZo!p=%DG#I8^HB?iQ$hc9ZB6iyCXxsJhHzC%&L4l$Wck!Mycj-hLkh?G?<~X`mP0 zt|M4|9r3Dvt$H7|mTF=2?xGfM}3KUg?bIuPJc=Lf%>aHrz^|= zlV_IXV`}M40aJ>6!arQ*D`mQjxkETY8--xk@@yk=9l4Z9ayUd(J3zZvR*G(Bb2?@l zCH#rdb$%A+0`v^`C<}oQ&?**p-9R?mCNb&58mFscenMDhymx) zHQEY}BHF}3vRl`LNt5_qvE@~SSRHBtmy>EK<#nvPL3AM*v0&w(F{BW^-Rnvqu-#sB zBj0K>$Ljr|@BIDk@6;luf{8rSDf_DpM!H#`CYPR-3o^B}^Z0@JC93KVQoya;Iytng zx#wt`$P9-d%nY;Rlq%PIbM0NGUEQ|(7~#lmCC0lRE!KocYOJDYCb2PyIuO`zFoWa zl@=Nd*1XW1>*}oS6`AqIlTVPVWVD(3+)7zU%|F2icT;pf^Vs5a!lLZY?Jm7J z+Sodg%VoM2$jh2@wguN3>ge4QE%T-dEF;>v0oT+a$uhCArg8m-QbT^~%MODfd__d- z5rSR19DK=FW%;W+*KT}kbYO5~=D#LJM#rhASXmUUNUI;SxB?c{W}SHm8RdZB=T~W} zT~eH?h57!?t%)tEw^~|zYSz0cK3?y2-+Oz+4fW}`=e|EL%y>LO8TDaKUA=L;sTg!Q z5vI)_*!1A3#T#O>`|W3_ny{yaX^PM63b%;x=r+jwZPXL@g&W=K=g& zpU14`3W{WLXKJe5wPQ(FfT4RWsEFPa4Z7_8y)(Z|Ix+_PNV35iJ$&fUZ!(#TYLS#Y z+neyuEa8%z+w2XPwyO;ZHJNB#SM2RF0E(&A@b8BX9V#80SfpMV^t;`DKqN?JhT7^b z7;W}!(&%ZBXMT8JCNWhrO`UYMb&V{V@Oh)1)9bUS)#^#N8tCeR+lmx)x5r%i>-Nst z?bMUSHAdN8`T!27(wBHXN_}h56n+*>Hz8LaugVMAJVYd7N9MgTugU7Smm3Q z(W6yE$j7}3`M5739~TkV$jaBiKa$i2YBwnFO+ZI_oO*%!I_U0CP-Xg$)ZeHXTA-~J zJ=)PQD>RoWW>q*eE0pUb2_iDo={N`+5g3Rmh?4xbn#Q>716UB1kAt74&1_3{z(;SeM^2MM|WDn422%#n0#}swF9h^yMF*z>^ zNJ8`uNh>-mQjn@u2{As7Dx8P?mVGX|#2WG>QXVzSdsbvj@a zk7UlWz0X-+Xw)!-WDlXuPR8r93c2D!TZoEhJ*EFD-CugDRI=43qDKlvg9(0E>6({qU85Jb%edyG47j93E#NvXB zcPkw?wd6wf>clk*w=b!m{HsilE}UpG-+9?xH5Oh=H@+4xZaj`XnLrd*@_w3nA1?LTo&x!5aLH+0vyLFE|{`l@YEhnvpg;SzneBhRRDlI5- zkGS4R)r~q`_0@(#k~L?t_5IDR+x{=Q-UPsos!SX1+V{G*>eh1ied&8|-+k+SU(=m* zmd@58d&ojU2w4eaA%UjXT_AaBzt)r&H2cpM#$J{Iu(E4i5ye=vNvtQ3iJUBH0V*3y1H};eOXf6!L&9Dv!L7T&K4+DjqDJ&y= z8f6-WmpHb$moK%kuBDOMzJphGa6kvr6STg8`L*#b--b;Wet0~Vr>Wr$N3hrhUHrlF zuzqs$1{rZS(lX z7H=I}d_9iSt-8+`8d3bW$o?$P_B1%WK!(DGIyyS{t4yEZTAFeN2{Cr`=w+|QfNDWc zQ?;OJ9Y1pPGVAD)CBpS9@?5prXwArMqFl=RG#4OKV5=l;grdeAN;(0+Co+WFA(c*-}mgf82e~Qf*f8K zAPG9>ic~z4#>U%ldJ(JnOzbTvV0}6LNj}~#g|<-24(!MpB+gf_o-lddWSk^q|K`Yz zV00EqjQ}VPLeXIjxCqvO&%zoo*Rrx@XUo2pQy|fA2d=6oTE5)!O~3$u4wx@JgiM8c3cYVP-HrNi(xwc%GA zVahe(0%Q;Iuv_wl!(V^;sln2N4;FgL63@ixLc|S7V-Ze|1mAk{nfBCOAKiY<5Kw+5 zSUMZ_cVU;QunO!7^$c}l-+6Aw&ktJB(y1H%yya_as~(4!>c8adH8+;M5~LE=#^@EF zy7!xpztw%uP}J}G;J!z1xJ~nUCQ|`m#xtuoS!)7jc>J~ttf!9c*m@XSQ_6H;e_}Uo zqyYPSAQcJ3>eiaif9{Jvw7o7{;7_IRXbOHq+^f9`m zq?Gx|l#Vg6=2$>8D3}$YEb3th2Be4#f)EsqMu$Cq&Mybi82M|gapnKk5B|=~VfS?u zMg`-*{Qrga+rG8`ZEm(li#<9th#Ff+h%|t%q?u!8K!86?&#wXH04D3RQBM@)WRacP zVLkUs|LU8ryK*sr;f}L{E7H1w0B6zn9s81i&W(?1l;@foZWA)Rw-rmog8}ywx1J6t zQByGU>nG{fWx5o|nFmiltscJO&tr3U=YNuKsE=H65zr{Ld4i{DoWPDlTC4Sp zwmb2qYfqoP7Q1)f=R0?wa`-Ox|H8M+D>Ew(96EBT)pFV4BS)}t>*&m$o4+@^Y16F5 zZ(6@@3wA9;%&Ni;iX+VgK>d~HKCU?lFS85qa5XGymhcBv(dF=jI6&#EADij)*U0F) z2rWGJyrgg2cqQH$40f3Lk#%Q)%59JScCLhD<>2int^yw3-W+k?ZO;x!D*g3~*dO;Fdbc70ktZ z`b;m83-?&x#gg?cK!Zkh;`?T1)-G8*_x6=n99Su7hie_ z{Ie`Cc5?OVnHl(LGdV*k1MIk@mXm9A1Mumke%N&r^*D?zlpAvajIA0(Fzy3u;BQ<0 z2eM!oNMb=AF@%CTV#jK2z;mY3y>BDG41`Rk5dCM1c-37x6 zx;5J%vhW28Jexn%G|;0~+ivi3y@X&VqQ>73RA`$`l@kMEod3nn0qp}ZfiP5&GQBXH z&Cht~*`Vo-`K>?s(nDht-F2-$5m~?Ls+&67n3qov?^xA`O$=Xna%OEbG2kdw2m6*4 z_H78Ytyyu%=bgef1z)O6&o0{_`(H?XX36PY{@Cf|8|S8vitb`|a%MVnI4lA;pB|i<-1GWBIkqmp z_TI`ROFpspl(46(@V&i@X1d9PyYBDo><=fci5m_cs4vRw*IM003z%&iPA(FCb@8Ih z_NP60SHyZl)m_@v;aDvE0DymJgoHhUOb6CUx392mZ^omal1w1%l2m$Mad=TiYcQb@ zi}yW&dbo9R*=#)?a~o%wVMa`b-xwSj8MJQc8z1k(;xa(bF@tW7)M%H(Z!OgT?lCVI zv=aa}aB^v5(aDQ9EUTAsxmFxJxoCKHdRwQMLBczoZ_J10BZowd#lYq}Es7etnCIo+CQgjVeAs=bO`zUpHG^=!%8 zaq;HsANW-2z7se7W3B6=iPke$U2`ZN&$JI+Bk#EDQLCvTk=9Hj83VFTFJ0 z9|>BIQZ&Ojv9qhdf(t*LJP=`kO<4wzOlMo73pjvOw+p8vqAl6vIK7W~4meI91+T&P z!DH~>;5nyZ&fN@goEKx4VOIjn{T~3ZL zC+G+5!ay_iU=t;_DX?nb-?PISVZ7T~W<3SPk>L^sT~nrXVaVlSx6Kw$APk_z@I5rv z0NT400$m2T2OO`#4%!DD6r~D6D?z11%?0_7~Gc~qyRX;&}^s+UC%B9 zAK>Y!6lx5r2R$cL8Wn`pMUZ0v_sP_XF#t6~JZXpwz<#vw9-s~=^jx7jstKH2qy%5v zFdgs>k(A6uk{*qy`vt?bVS-k;VPBSZj%{*@8W!jPo+@w33&AAVkg4Ds^SD&-$@*9| z&oP}v0P-&GOy%0Lfzf2(SSc(pY|4Gh(CRDA4C9Oe`qt{1_;^r_EfU-p7An0IXB9Mu z1E5U9G^F@d3W{6ydaEUOqo(Q+Ms@faWHhSHZLCwQ?oMU+h=&YEc@HfLbgWp%J5)aw zm3Xy(TMSljoZHjO0h<;!;FY~*Ak3{$MgKnx=lZ=QfaJuYZIm;t`yB`NJ4-@@%Em%I zV!IrzYqXaUbG+bo6Ta=+eNr1HcRNH*vu*`4G2j$E-Q#mCw{G@x8kKY^YH5dNDqdg3 zIc^3+7_qu7u0q$pO&;BVf*MZ^Yv+gr@o` zL#}dIh|$Z`=wzT>j%-D!qJDN;G}CJ&YHYeNZ5`1?Uu5ZuFkwnYSbwTrQo0bHE{?nO z?#ii5pTSn5A(xy~W1M3t&3$aq_)?0;1jkg0-m~&r$}excz~9K{gKcGpkT~pBBA%ns z&?Nv5-|y-0)wX4-7dy2u=Dj@0iCSPR+L)oYt>mNBAN^ha>7c+Z9h*!#g!Wv(@T~Rb z_TUGD^{~&Yn-8V8yRJyIhXQ`b%#0(@MX$cXwLA4G#blLaUu)|cj@%h+-62%-?V?V; zMG!zZ1G!_mmfo_RgWM=_ zMmQtpEawaX;8#s$JsfTwVQ0%%%JEjCk=qoWflX{lkI@CSGgum5u$a*na?iSP%lJTt3Jx zT~SL*8w$FnvEt$r>k^rf^rj1zR}yXvYIFg%WEaNzJ^oD2fPIz~aDVSq-0$~}WaQo& zE394r>dH@PYcRl@)BqbTU1G8P5N0UAH=ygdThvuurkDv6;Fle^-%q&jedVi4YeF2q z8GI4(Sr-CQZSsenrG9VQ431%A*e2`{c02gm9>Jbm&;xC+F}P)b<78fZ*hAl5RwIGe z6eQ>vSS67t1=)Zippeu+-!~RyAV|TcOb4L?LZ&HHKvXLRRRe*6L@m@cFS8L=xUFC! ziH}50)0k=3ZOdz0=L=xqH-k%pxB~&x0QrRC;GqTRLQokE#%c5g2v;QEQPWUO_LKxF zB76hBZDZ|wk*AE1vo{&F2#fGR{AeeC*PXbHgRgpDanNFEndrkt{B$5U=RSC#V z?-^hnijdSUx?4z_#R>Yt-nhyVwS1KuDvKm%@^mQF<`WoE%C2(-E9)f>mQ5R6&=9ND zA+O|>xnJ&CHTstxM^{BJT%X50FHGEi2bO#+4Yt+-&?=}Z+Bprn{QJ>iJ{d5J<1p|HE}t6NCU#d!pRA>{Gx zqxU7E?@0NCDaXlDB2fMcDMLEhu$RyR8s5DtzetLWTK{?p&&e+CmT?&LK$o@&gY_o> zIJ8ar%9207fmT(WlV|gru7~9y|C`WF%eLh@s+2G3aHRbHiIB zfF3ql$!2^?s1{y-;jKU`J5xok0Kyov;w?e0|FLh~e&JF6WZl7PA)oK)n~!CdV=F%Rq6;rb zd{%9hdAW~NfCU<8V*n6ZWbxI2!EZubz7ld`T2M(r&iaR}Z!?!s>mbHu5~2lmv>XQQ z(;kj67|?(~Kehw{i;ojrP!LIlWDGilL&Rpd1u>pBH#AmbH5g0igUyKmR>^tU&{T(- zDezDe2$6;F=67HOUPki%$>r;V`8%K7y5e&WUEEa*`qphpW*{3ngSCG9#Zkjd_kEvHQbMs<2X@Brg4BdW01SywNLY_BNYKIiPArt1Z|^)4N>-Dx}TFc{YzVI~6p`CVjJ`C!4omHvtyK zwu*on4({F<@_E2o72QS&UIK#_P0f*?ve&ApHlz_S3o=5pUskh}(XgM50g4aYMj4T? zp_NZ9hVWG2e7#OBdSmC|E2aj%ef+NBf#vvk7gvxwvR`IGBJ>YFzQ<{e!DZ_zNF;W~gGDG^R1GaHL!#Ut;b9mh(F2yA$g9jKOP&P<$Zn)}CPdgLdUhgE$g}GF+=z|RW zyeei0Aw*CO)u)JE;!Gw8{7_N>qwxQd6`XZTEVPHfxRJ2n;ITTc7caUeGE942^>{B> zsbRSy(WLdMgON+NbXF!Mhp8C68lP;3L|1FX=fD?5)81A=1#h3KM54vK81hBHr-$QG z*gMcWEEjW1mzR=i*v{@3v-AH4W_=3rYHH3H^bphndZkbDY`4%+398jVU_BD2{kMb2kPE5&Xyy z0xJbPKu=c(a?r6EAK@HqANVyx0^T*E81*_FLNep_J6(a`t3dh@qg|Atv_@6UFR)-2 zX^T$F&Kwnu<9a%2D6a$9FGuTDYVH9aDD<8}Ax|fOErnxU;h{!XAMR12J)NC`;f$sQw?~gdaFyf; z0CZK*f7~w`e7~pw14OR^$p3jRtFTday;NwEQcMo8!8u?m33g{1!T7M7(j~=g;~MF3 zI%?>ggJ39t#3j-KyainX_-6nnOkA*39=(H7%37RthI(0_NzFlyAvVCrPyqTGis)h4 zm7-r_i36qnD4U2RwBYy!02-sP-R>nLVO zN)sF;O}RIC+9AFstyE&PjpHN}0S@2>f zl9dQr`q|SAZ5s#esi-+2*>fm54Q)UZ)trs!3WVt|m>w4@#r|d*oWCq`UZ3A+7Z~cI zjIBJvj1J7l^R?9}TfITkJQG$Ne$Myd=Uvy7%d?way*V+Wuh=`Y+9YVkO;%ZaI6Qvz zp~K(&%i(kPw|dPLemo)jK5RYF!&v{Z{$%j1Z(VQ@BK!liE8g%Q4GeW$H*)m@mu8pr zOfL&{_&T>vxMFRy=@b0~L=DbpvA(N=%T}4J%Qu&m>aw<>PmaY8uLO1oCtqQ=W|s?Q z&205Os^S0KGqQR`olTzUj>&^qr`a}~a{^JzQ_apS@b@ z2Hxf&Ddw2H>{Fllh;isc%I1q)*Gj}xdU8kH8(;j#-h(@ij2+_klt%t7#iY0u#-fgs zKONv*ocq1N3@V46{?M1trRTmGfd|yXk!69bvu0_`lN~0m{ZaYj zaX}H?0|2EWw%l#~gxN`V14co;Wf1aGwnHxH35b*UaLavw)A%&3D4Y}-2`AzZ4v*W* zZdgOW#BbIcD9Qm;7l0tNJ5(YGJqg@XlmW47afnQ9npB}g;%`19H*eE$(NrjGwE@om z8?Y^d`fJ=81T=s?g)}Enf&T6|)Ppo76upa-T!e(%ydo$iJFPET=d2%D?^<)#SF!7U z)!8~RQ;j_N@-yFN8W-l6!ce-~dg5byue!*3@7=$&{N-oXkFoGiFyhZY!=k$njxXAC z@rvQSR&4K*$xHW-FFM!=GQ#RTyEpSpP)<#ZHtNHZQE_hW$jHQjy+g|m6Gvw1jm68$ z^%*O+rdnAt)u^qs^3#p>iKX?@Qlht~cc9aX_oA%9UaJ`M>dD^D_pNjH96Rw*FxEPs z$L>A|&3XS5PqbIGm$1$EI}eRyY5<9U={0BHIlg7nvA5qnwrR`p*T*m2zi7p-E5;XZ z-#s~g@Q@V>(S;%v5`iJpFx(NCb4HFFm>9ln&(hlT;zoJ-lCM@*uBtQ^FWu0-e7e0h zwRo(*yKnhG9yrjx;mxJ>9<;vUK%SgtaGD3?B`w6Tao!CRc7-If>tQZs#}QVPSLs zxZFo9S~onpwy5y`GIi$6+;7&7jLzP9rPTG)xCj&8k5H}H|6p6Nk>6u~vi?x5Vxq*pEVQ*0>cfgh-moOiz)cn9KQA*9Gm~97mSk4FHrT(!?u42y-K^?@Ee)a&~g(h zYruIngEA~cU{!R!1TsCMOTzM{f~szywIceBJTK5QP-CG+)OLH+qLFXUSm>%?AxkmVRNI2YiM9Y2TH#^4M z#cNj%PWQ`naJeJOOYN6n10%4iD2%JdGJ_|JI>Z73gEJKlFsA6Dij0sIxDdycdDB6s zJuDw-lenGQB8~g7wI>I07D0~o(W35jq$E5t%R(3-P#z1SN=87Rpdspnvz`V$A|*Rq z4p9YaT7{#^MVgWPl#8T*)PjL9B49!Ocvkl5s_=p{BWfMfKTSwhAK)1JwFc!BIi6h{ z3ofxNT~28)soj3p(B!?fZ~%jJyPoQ(quSP%7%i1DIL1U1ZSk!eLzqMK&Rme`N|==k zMbF^9pT0|pzq}lGrF)%WG5nUbZ+m};RfYhiE7|KI%fV1ErKDeT2+KKs^p5sCx49ne z8S3udyIk>hNvsr^?!9r}irC&wjXQibewk!^r-yZ^e9T=XJ(0dOUQEauYx@!*Ul5p% zGfpQk%7M-kNP2rO@jh*YdE&#E( z1IJ;01G3)TkMwJbG|ah!)ho;;y@s`WTh5=)i`+oNo4Lhn-iEEetQyt$_5+JI^c$Ym zL(FZlIzqds_|y4(cPIJAJO@pAq{EM0S?}ou>z&uvTL0!Lxe;y9T=zy9XF> zAH@KKjEW>v(DQ9I3jIRk?{CMlJ%pQM#vaJXVqu#bpi@JKx5pfO0Xa0b-ei|-z)8(P z2_?_7V}KF_!IP0%BLZ;HC=~`(jZ`I|SViLoU3GqR!#GBDz#su}(X3gsb4!3irVOHk zX&`HYz1af}j=<7;-r~1l(y((t?RvoH8$(h9Y%{!LY?7Yhb$v z*D?B-3SwR8%p?j>gl7b>CeQ}>j6%Y|m<56g9hiUK0Gw=F`60~>nT}90HduL>8~*ey z9Pbd4^af2n?g%j)-RA<^LEaG68OhmJ>5LmNZix=A>rn`J*yfOCfRU@i^nf;l|K<$Q z2gya1$d+a1MY1nbcW@Q|n8&TsqG$W?(1l}o4*w^7@wqRayLuP?#npr_ey_+=YH!`C zM5|Mjuw}}%94yPshIByF_AhzS&wXHqvuR$Sv3&yAuYrC{QJEE7gmh{YDP7fnLxkJ! zr6#=@Tu!XJ`N7qUs)miZvvhaTlVyM&j`Oo_o!t3}aJb^aS|j6o_ZsOHVCZ14?%uDD zFh1`CX&sjR*nc?!6vYIn-0V%3O0L!mX*X4i#zMh7Mkq0Yo!i?=v~Pn(sPB0 z@uH@TDy}6(S`cN&FFD{-4^gndVcSIa)|a#$EbS*~+2!M?I#7&n0EU;A?;>V~9D}iN#9hdMTMvslNgjxW;qf`D%}v?ikARQ zY!qLFFUM!`t)Q78Cc}CIP17l*-cYIyn2i-QbYasKWMs96t^uyYTGF0}(ddV%7YQ#L z8rH5r2V)+p2n|ytP0%p3B@7yKc5M}ukb%sWa1sjen2!s!3p2EB5462^f46=FK?r%@ z3pGKd%_j7K*&8WJAaiQ-AJDa%hY+23QvxjfHer9>oN59BjSU`#66es-5NJ0@BHKT< zY(_V(F}6}-{{mw#Z1!#0V%FY;&>0h+36w=CAE+T9du*Uw`~G03DZ@}p5_yos^H0G* z;uUt23+7^!9s`f6WG|A@4cchbs0H?;wEep&xC~O5&^6F~z`Y_5X7h1Cpvt^H1kbRF z9v$=hsTG0z$7EGE<-s2x9n%ZqD|XLc5x`G9Pu0@@~M>16OIt-?_XE+TLUNL8$dGjk!cJKZ!^t*?m2}~9Mdor~ zQYF3P+$9hn;SJ&v>*bP68lxmPZb%*i^c@JJF&Uuab$e4Y=>(Is49;WtgFF3V znRXU5P0e9ZQEY>h`Y5CJ87?Nwm8)$XMr>oq%>eqoE<{Dh8t&03D&4;BE0HB#H1#>Z-UpuK?IuQFIy zIKy)>i;TD`h4vZErOB$uixr_w#0J!2+#2EfI%wL&$4VxPol}P)_JR`i_X$-c3XDUP zbnCuLA(@}h2^L!pKYze|l!LNF(m?|V9U)o&h7-biAuECjxOfD2Npj8d>fHWEpbNmP=d@kAy+bow+2*KB00qcc7%9>9#TiY`KBCX$HSmeP&^;f z+SDKp2&LRs-h@PQ(8kk2!-!}>wp>m#0fN-Ca*u~~Cn5%y7AT@BXVsEekqlq87-3^D zHFDl1W1=wVlflYpco`;6`EUyJQi==cj;Ri>#4lT>aDG2vj3;^8puRhJ&|nnwF2pi{6k;Hxz~jt-jlh<$^FF$!oJZ?G5P!58 zgD6HM{Lfb&7T*pjDt#Qu%MB^*Ob%fFHR_Ca(hB$n(AW$-acsK&km6#w55 z9BrUTR3}87K(+7(Rh?`y{=$ve7fK*C76o(8x9EHuKr&Y0*3d?S`VMdqND!g`W+=Ll z%dnu~+WIP5F*k^#eYu=7;z>lxo?LER$&0mXZY!`J+^2pZ`qE;rrf8!(<3s+jh#w=} zf{vSz;2Rm<>)jFs^$~buiE5e7t>2RG)J#7c^#`)RNUq?y{zlq;fjh7#(EI-C-tl~{ z;)s0CGt}Li^n|U~T$B^X{$YS28W=8vH^xUgYZGHN2y*o)znUHCB7_SX`0QA8kar4I zfQ3r6ExX`kaz%ovS|>$qOEu)cWC&9e!SyB?@<#lJm|1iL@;#lk{KO9kOE1J_)+njJ z4o4#42hgvzhNc^!ExSdsC&p0}Aa%qPaA{@4iB~-PR>_CC^*tXD0LDrw{YqoAm`P_M z)s_C<(AyY(>eQXPf%BtRQQT3;CvdC7`?fBBEjh`{!)H!SguNc0f9up$A4xbkuj;_U zStXcZmh3PTr<|5T-|H$57Gj}LYrZ?R=h{Lq5lB*LH_ay-Llj(pi5HgC@Sk=Yj6eo9 zZx%FyXRM{EMIGR9g6V7^H0C4}o)jDzCs^I(j=pTFG?Whc%zOL+=*H6@V@eSdGKPUY zSaeM-y>V&z+|dSUq``yrV}bbej(dd1&oR=UT=#ZOJM`mJHrV`kmtk-WW83eap?^*N zprs2q`Bnq=-u9Lw!1Q#U=NBndEvQr3YfRL=kh)|4MtvLU26i8Z1*`oT=_WA4!&0ev zyiw=SjLiVTsr{d`zvRF~l}y2`j`mPE0P5RzeW)~6r+?kmaXGcJp?Ot)?mY+^(s8e@ z_doQKucLiY|Ca69N6nzdGnY-?9h(G)7-?=eh2iS?&Xay@1;fqpY<@*sTH4WopiF>ueW~NHp`J~ zmn_*WENxXUH?tHLwn}(Qu(w*B`%^TCSq{U(BXcifw+&p*($+(BAO8~JB{}OR3ct0p zbF#Z$jx0`<+{ON4k+oWIHj}gfHXr^WKg$!$bs+Pi@P5t!cG|}vZxF2E_QORF7)Nhv zK57jqvq}Z&(=ftaV9!EQ0{KWlG$7>(J|h9r6d|yWmdmzCfn#>~fc;KjeQqn+@LtjR zri$J*l5Xf-!HctXM;%?F&e*ypZTt5ZjN-Q30j13rTX2Q>Pf++e9P(CK7waznYqs8> z&*kdFMmvUy2CcfRo3mVgWjo#&v_4x$8fiQBv3Yu$BX1LO?p zWy%p;FGWJ#J5G+=y`%@z``Xu}OK~bzq99HOz~%S5#l}HQIJYA8MtOrnf44X2{mO-u zwK)F!UT5a#_%CGlZ^z@xbJybN{foc{5(ZxG|AL<2!Md>_z-|5%tg~JP)aGAdzei=@ zHA9p{qXH2S&9MrBQw&%~0m*TGnu1<(ep>(*f+YkB-Gr`z7E1G@5RJrzARjcAlc`$M zu5w-oG!G&R637749=>RG&@61L@F;l1-bZJmdQl82#N5M`=EH};goQ3-8;~Iu40Xr~ z>Rzxle?!lK|MtQIeFB{xOg#)hr-1PXhbqz9t74yLpKJF``*ax(zy#Pdk5yppx3Ga* z@qBT(m4!}kB)~|F!dak?LP7zQ)|+@VT{guW7`@PP58XR5exqxlp@t5i8jzRCp6cz^ zHX8x=1v4*TyrBC*0~0nIaS6gkA@mBZf12wX!25@^d_X|3YeoYM{6~Oq2#4kiz~A$` z_EHR?>cTO2p6FqqZ+kG>(<>aMQ+cX~}Yd3V`SV7~pfX9%SN_Vfm;dOU}CuXJ#qQOeeVpSnn@}iQ| zzf&XP;1~DBB*~?9T}88GM3r@%WMyd()7_rBhKF1ih=vAW!CF$63_nK2>R-Svz@Gbz z*+Ger&5p}{TrKtJUb8yD#2R^*psTyPInCh=GX5eC!JjhIsT2$BVl%H$K#pM+IVV>A zJnqTr>s`Scjn*k6D;jbei|BIjeNuEfbR!vYJUhPv+V<{rkmI;8`#iNhtuIEm|UbloBm{`?CcO%P52a= zSJ(IT6*gVgxpfl5Bc%`hD?S$)(#?2L=sL_gN;si=Ps5#-ulLmh^Z=)>WK3$^7Bxm2 z5dzS+1Vi>Y6t-JVV9By836#M>FUw{W9pgW41PM>l5fRl(Jiac7XU0OXil91GJP~U( zU5VD&L60(^TpZNQC}~7P2IoDFIY?Z`SQqJ4L_wZ=R8|{wiVlN+MfLO2XrJjnNf%{x zSd}~EO~r>p$?epznSiw%JT0+I5S5)L0}lY+vXc~fA_%Knr!{r?KKxxxr5LAHvVN#f zP3eV@&mVe4l{piRyh2iAQ^3Op!ThbCA0HR~p(~qO88FEyh_pm@`rE1B0Xp$(z$)>R zmVa;g9dzlffO&NYI}Vol8^JbzS99HA4|b4R^TNp91t|f8bzVGz3e+yYU*W3D1$tLZvl!P39I?) zZDY=P+Z43VHtN)o$Te#7g4PbywA%tp7Ptu{dm+RhE`#JS(%Eb|45>(9bOC`6R(7N# zKr0b%Fth_LKG3bhC$#24;t`G@V?Epq`U1J*nokE6Fd+#F0{rc@4xDS>K5aj<=97Rn zBXSJn;(+B0Tr&jz0r&%MKO2b>wF^1i(F1OpqoB#Qc*Igr7PS%u+~4jGW`=qS1w807 z{6@|#hCSx?wpLuDho;6c@hMyu1CW*?2(m6k{QiLI>&*t-)@+&s>T}?o2o_?aMx4{` zvcB=;iS4tI3ahYU)$0X#>#&fQ2nwRI{0Yj16BV4(fI&6tmSd{VBb(&9tN`H?3}7#hDx|E1|TOqC<$qN>9T2(cZp`J zS5;wEuRB6UnD5eCm-dhBz=T^^H`6Ys=17JiS#~q!rc@@fG`d*Y{H! zR+h+s-#0eCvK|2(twF^%)R_C7P^D>qi1i12T|22^yepTSnv`Rng7u?aE1z^%)My{; zHd^aNO7C>o}WFwk^N9X8FPcY=C7NM316pk|A2Bz8oWs47c7z4&I;| zUZHfx)H2-!U6EYVezFuL0eRl3NbSz7BS2!p>{)>oiZV?Om-!W|SFlbahh-DV^o8A$ zPd0*-Vy=r-$Gm<_3N`qk*50oITR2$iz!((q?pd7bXzkv)ZrSST@3|6HYG1L4`ACIo zQ$!}=0xZ_$*6qaW)PF$KL$;;T($jJ=@S`7VxfYnbZw1u7k3qgCXn4r|g*I@cbhR z3_}6fBN$haQ5Ae{_P|9Sk>Ub4{k4dWOs`B09CWtMpMoRksIBH9fkV6=xf_>5pFVu} zZtKPn9aVZoWB0Np*kN*oQ~J`ES3P#@`16wG4iy4kGp-wcj~D;s9`f|6gVuXF8skbq zR^x;=NH7uUMTye#1Fji;NLo8QgdNeH6DvZKeR2OS)IfVVG_d+G_Px0m@wpYD{?Lds zTJ4>HJTFlnFV_~Z(UL@mx+;F-qfW);oV#Y=!tB~#3=Qq5 zZ#O&NGUH)+ZUvPz&F_1oA$e}Hjg}HC5}tjIevA^g=0zu#u3T_9lIau*az6ELr6Z9b zMDnr8$uoUEkM3J4Is1G*H}EX=o|&9{|CiI#NBf4`TrT76^z@;w&a5G01R_O~;axw- zd=`8r{VfxKp12vi2-}Ap1E!msuseVP>>Jp3upc1gU3d>DVq1!A=ZVz$LFe}Hlvn(L3Al&q^du+4buw`dXIT;)JI82B)j8`@0r^W)?&2@n?{+LSU0Nr0$vS(DDrJP`7#)(H{tsI=BVC-21>2Z4o2 z2+4{#<%pals2&PqK_5JSVZ&)3E@?-BtWCcAggpz z41t3uhUYZ_=4!(ATS1`OO%Ea87iVP$u$)CiDJM;JPIP7YP^-YLf5%^EU0RsRe-eM~ zlDkN7#A7F{`*GJPvq5U0n%k1~Tz#jFw@N zQPpfS!WhHfx%|_PWJJbJQ6dloctgE_nZmFlI8S)qSH|P51fvBRUL`me0GQysN@9{j z6d+FlmP9(|5(}*^=`M;+;ztSafTL{aZjNyP&=npaXa{RjtPqZ?1mrw#8~`{=DT{}< z;|DaC8w2VO5uh#Bg$?)iU8V$E=cd!vv*q2fXjxKu|Ag;m?B%h~9_hV$`+Aq+?4BTY-*Di-ZpYFA zlaw~RlS&m*oWd<<;vzR&J{B0C$R+#rV_RPhH+bT`@)JA1F0WpE_iY>-{Q0ll5SpB}M)ef70*Kzm_uP75zr5;>u9IqOpY>#i^|RtC zmn)#jX+E!;Ge~x$6v<3i@%Nk6#qQ|hAOnES(r=UE&bhR`!(3_ z2Oc`IDWatUh+c4=pa}%Z!hcvO{Ox%F9A&>xByAT?MQ;q=*ZeL5ywH)Y2CaVJWSBwh zoHWgTk#-~_99k%VIzUpl%c2Dp+R#h@(?tW!U})yCbK20X45l;G{pJlp%_-1c=Am8* z-OK(G;n1V6kRPczRe`e_W<3Py_IIp^06Gs?drE zKEY}XcF-vcQ$KnLHTW7ewE6Wy)H~X%LM`Yip_vjrNju1(xg2XgSWxKDyRl!9?XE)e zy={&|nn(%;fjt)BqpeY(lWbd-jUrJ;-$P667|-+Tyr#7ehicHbx(2yo=w=p5#q7pF zJ@EXYOmlq*`Dr%MHyD}W`P;7%E%M+A*>4AwiiHiZL*YT3e_H6FR?Z(VY{oYDvMD9t zQGuCr;Y|38gu=qfe^&(%#x5KOH3BAgr0&imiJ@Brm8t>yg#FRhBJBHy!?wx=`u2Rs zfk%jL)r2+lc|bC_CJV82CKY$`2|n!SAt(UmPcA6a5iw%81aPg3&LEAa2&bsHBnDzj z%^aJT*8{%1I$`eeLOP2|DHP*loIxMv1yWAD&m7@!M}X6*GUy8_pqVE4bhn?93WRqE z!psN<2k=%Ht!TR9)YCm4sziu5_>~hBkHX@r z;_;F$zY-!rMIc;`5T;O6A4M3PqOm^O1KwtEC3WDcQ^Q1!!s86#1Dp&JBP5Rkb}pQu zA#RBCIti#MKoh{3%b8FG=J2#Tao~3C;AN-UiZf1?NP$lbu!khsL2{6JWn#Ll;XYZ& zc}kFKEIBZVl6-hj<=q&A^R%Rz4q4)P6}TE9T)}C=T9zJ&w$daplMoID?2nubEtO0N ziFHsACJRcqPK(=mS&|_%J}TxiyvX|qz5-Dq5Yz?w1;8PLb5W(*C77;g0@AcpLV$@< zNXlLolJQ*>rIW0Kh)ewxPD>%DUvh~A7#}IfonxGZDY8|Mfw7 zey6V{By_MAn2cK%{j>t{_JRR1Ub%1xu)tKs>DHtShR1-4lBQJPyrrCiNd$0s;VMuM zNcFjKoF=gc4VIbP;ijBEp2YaEnT%p)n$a^cnvR3k2}^@F{%QO3iLk0?V>q8)>>s$AU$#!*sGp0 zyJ5zP^}~R@@M&#H%Gf)stezOhNMC zBxv`b0WJ|adXx+~AO^|IOOnr1a0HDwIUQmm7+{ZyPS(l^0*ie$7-Z<6P^27lZw1?3 zSjf@EF`srlTa~ZkCGdv*o*ZE%`OAW|p2bOjq3u#JJUBAC)!fbm{SY`Lm?o2ui{O(2 zzNh8Zbh?J=3Nl?d^uP+Zn7;L3mIe02^Xec6GSi|hQXuy zyF<23bp8mGK&2KA*=AO>4^Ows!GlU71p*mNAsGiPIO`OUOHmGtK>FgsH-B3SfaB0Y zkOD9DLILvI+1A!#D45Npa~JkSa~(Sf>4UfDK19Z_FEvIu)9>~8M-uK}bmW7>r#_^m zhq}53myE9N=vs5T_5JoCo*k(UrA$|02>(MhAP}wA#BS?hzp~ULtZ`XAOHw>55Z9dh zI{q|%_1rHK&n&{PcXBldQc~W3w9Pye7mm|z%mXP@_`7rOW1pWZm~_=aQy+(wb*flt z%cV2b>YhU3+yfuHjbB10-~VpBebgBZc)a28h~H0swCBXlmC@0zuEk@ct4Bu8y%uX9 z*5rvsax@eocCt|cnyy^NXHI%>Ck*Y!488$sJU? zdR-w`j-ABk%7h69mREOL6U0`w%Tyi8fStE+~ac-SXUrUVP*D zSI^uDSSTCM^t{mBwY7t}`3eRrLm(RdqfpO7J1*$+T6?Uy;!Pu$Z@CN) z-hcEbF6+gIh?@bAKE7o6x~E^g;>sJ>{O-YB_q&VR50wsK=JRSeKJD@)mvnnedT!~> z6E5q&ZgW@KGbK%$Y8zw{dYiZEZ|_;XbeJ3pb-%VtUyt#4v_3+XZg}q5n_SV6dQ51& zYhdrwM;FZuRO&r4K(D^={F5W~`pn0l{@SgRfzG$qriX_H_wiDix@3!Gku>=MZ}cA7 z{mdd__*>ti-*}_)jD&stTfcthD|4-v6{Evj)@{rzkGBrLVBPrer}qxhCL}!Rxd|5jY zoSEUxu0xaI>Za=-57k_HwRLc+JPEQP46@-_mZ837TRiI^8~P#7Yf;N`;G){uvIh`m z4}*L-1xz`}3VIi0NZjA@aLZ#h>g+2m-vPYjAKJW-|GCh^!0-fmr2X#E?`HRE_5mBh zzWMr@W&xuBIiJ-OXp0PyVkqc!@X`s&Y}67F2&V*!Md(pBtX#c-5g=6zq(-8m1awG< zoCW7aDxsT+X1~J{W~#q8(Fxq>MvCINmP6L=}+2S+MN*tMrvW{tCR_@C$gz;HYj{Pv@te(hLM z;=OKPcJGdnt>1`I*l)?`ch-0BI}~5iBPP7%Y-TOcChj`6LN#X&+}Vi@S-%vH2im#c zSs%Qev7WJpt!J=N>=oSODTMkMr-RK_m!7zqbD&9PV8uuO^cFV$gmvAE*lR1KRU>5B z`p#q6m*QjOnZmlcpIIN|CePrHz4fOXuy2ZqkjoWHh?@w)X?^Efocz#d=91Y=&2&bt z`E%!;2e8+#K6M`*!QU)y^;dPQbF1If=KgyN!3Euptupr;!TP1U!{2fIn5hycKJy{# zkLxGgW&D$WazS*Nrcx$S#aq@{n##7*T}!ly^UePw;q4|J0I>WCswx(bjLe3 zugeV#i{eCga_f%rZap;-O@=~OPd#|2^%K{zzf2Ee%m47g4L7`S z*4j=wv=mnH{oHy9;_J$oW7{jg#h!cI`l1kJdf)l)Uxl~?5{_muW$4fE)Jj^#Dp67>oC+tWu73Vq(|XSOBi8rxb=D6Se(^3iOGffsTLxF8O|xcC_K{P_$>lo^@$o6lIaB^GVbHIx^LRwZa)G#9{NwFNr+ z`L9qIdGiZ2IFKvtuX{k{4B{4Hh1hc;g9emchaIqezPh?S0XLU(XxR$OT>DQI{_t(q zjn#zk$P?B#-gyVm={w(f2kZPGh(#lnp~V$xP1cdfS^x6f`LD>9 za>oU`Dh_pcSHa4cx>s$hsA_d@{!Sp!^HK?)NAyiZcJ}j|g>$D?bsqLIHFc&zyXg|P zDDOV)^#EIQNyUal)8+mDn0xQ=xQZ)oSaa+4-MhW_qTSVMrCn)Pz4u;iS(fB(x!bt+ z-YZ}*28=PybTBr(69OdkP(lmE5CTa^0TP0d_VS&%D;Wa`@AudHyt~hBbLY01GiT16 zIp=r8CC&kK&xP;B?_+;_nEETc&G?(4FQjUz)1MWuRGj{JJh37k$cpAE@>J}$m^T!Q ztD-9QjdCQjSPL1AE|RDyDADLlE$H_A`aFd+cN)#tMb$P}3sXh`N?#cX*1ky$>Vy5j zwla5tjC2fUmp?&e=m@$IEPMWjz9o8zTZvP|Bg6~DyToUJ=%XcFq@RqENwS=5gW69s zAFP?j{a<<%3RS^^B0&H!6M8w!@p98Sg>|};(~J>R1$eZI)!J#@ioi85@CC)(GLAiE zp`L+8Ks%3lmH{gu@KzA3;0%5?=oIjH7qGR3Ibc#N82V#YmWL;+1bu~|`5D*a<2#IV zOU&{x^*|b?SNt&oye6pSg^8yzZjNC+@u#N00Tcr8+QOrS8-wm+Ot+BsoDgQM(|Qtk zJ**l?yJ^6OV2=iP5YRi;EC?SnR*GYq23({U-Z?##hq8qe@S;#0dQiLIRQ%RZi1;pP ztpT2T5Nbp?DVYB0xdkx*ZPPDIefj4564b0G_8%iw&PCJeZk;l@xs;5w2n;`F=XHV?MHfp@E4iz!L-FzuG0FIKt@A(_=xCX;b0t=-C!1v0}92%}st*%y+SW|$ayH77UAvSw<%wrrVE8f z_xhwdk98ulB%~0;^Q1lxXTz%;?;~5{JU!)>(Re^+@a11&skQsfNfHrf?V8S=z&fE9~HE z%;~K;TD02VTA&1<>R$5*-((~w_*lEoZy`z7d|kKB)uJd>={X3;z*(I^z0;PNrP4|r zvIdDytYFKQ5qg!#st>?4l%Ij9>mnyW3^ERnTTF-z-a?U*nXND8=TxYi8jV?DXCwgg zqf@CXA&w22U^ZzD7jsIR)#h<~22@gw-zu?zrIbSJa@8nZQklk6!|7a%7QHXh)FFji zTuId@W3*o+ONyL>to*NDq${wIt$=?)pf4mcQfGHKOeS-xM=H{RmL@LN8FKR#Vhu&A zC}W|i(g0N<(`+U#HHS4iyle|h-RU*^#B*z5UIcyJ@~SjJ^cG!2k~)h*1RhxVfO@UfO9RqQ zQP?fG5~*2BnOUnsmnj7oDx?9L@zTo;lsZDSf$9$ctq7MxqmyavQjW4HJ!o~d zlazuLl0gRU!Dg#mDsI&few9pFu2dPVKD7pXS@jl!vRJWz_Dao3Q#5k|L)$D;nc1o& zDMFm;7Ap$!2-UpI0&%%q!x;yCu2Q|m8I>nW=_a&M&gJSrr^OMsz*kEYN~PS&A|oZ(D)S2^S%BezrPXLgKE#gffat<&U>tV>MsLrbAFm0W&RACj z+!;(5E|6eG+hn9UYUphb|rPck~VwmQI;cUOY1C z#{%(B(OU}QP1QxUiN=W|w*Na4vmNB+&FpRT+kl1{0Kb(kh)cQ`(1C6Q ze~1S%9tT|fS0JYRpEACH7^UAsoV2$B$(>*`-w)O{3n6~Srk~OvP@f)^ zWgm_Wa2k{!&-a5R27C`jQvvA{CM%O%db-@82N4cSb6BX4>*5?d5kGePHi8HRavJzn zV*^jH3I$YoSYh+fd|e#0c)GOc4=Oq=&B0BCkw8M@$&a(Qg)##nfX?>&LIB@EW@?>N zrrWGmuq10Os1KTr(VEm6tJUh%Lzic<)rU5@Ef~%>?rYM;>XJ zA+tol6mNR5BbYZtJrwNW|I9x*fv?;tvtLF?C?g${0%Tlap{WCAc2qW!C9WFDvREx{ zgAN;fHe?xexwZ6FK@W5@vz_8*9otSRo5Es|k$;l3X3=6h|0lCSBqu64WdX;Oic%Xn zxxvX%XtF`wW?(uch)X@~u%ov|J`wUe7a26II;uxZeS33aChzhZw6mR+5bDVUAkVx@ z9h~0naYtMdwUH5JPF-ZBYu=e_8jV^FEZ1mci_}zsgji*EY6^Fn8GrK=hK+IW%8hp+ zo#|rQxhsM;Kfh)T-Io*d2f`5h(jSOr&B{b(d8*&%vRa%TQE!>A(GHHNK9|Mf@@E2~ zuiFa={Gr^a-Z@t})USR>Jk<*EKO)jZg-AktKm8)qZ zs`ioKis&Luv@G>&QI%P(P&W__qD-R`z>?efv(i4h)nJ*J3ZpejO*wxjHNi)*&;?F( z>ndgK=SI=YJ{>=k5TjZD%mcU6J*)nqkyCNM#(I3qak2EoiB&6)qS=pK3_u4axzB8hSh(xR zNwgFKckUwhUk$@?(6V%WKL|08uLI4C3P$Hvz@MrBy<`X2p$vjuCd9SEZ9EChSBVH; z(hh1*T9}d!fOUyxTo*0y0b!7X8<5b55Jjf*;h~A(#0f1L?v!4G5)kts8rmox65(DB zw>|dnZOqE$cV|AZbN6)(Rs31siFb56_-_NotFP5I@sDPraBXe=g8u$v^XDJy?_ZE# z+c$W_Ll>|L{S!|a`%?NHuyMhAaDR7BsGp$2}Q0~SBk=eaY3T||9Y z{R+at7!y8UrZC6IVpb;<8>nuD{R>J?R5Z8qrh^Ap4qyK|{}!sAe*deV?AS88sj2eh zdpmbs(>814_CMZ!=_&p=y7EclJ%#?3s|~aAC!gj&DCQ5&So!Fj>AMSu7e7@-p1kko zZHHH_nza<&_dC>5iuPS~aPN2bO&s|CH)~cO*?RKM&-PzBgJ0gxAAFUPqpbr|{Z)K8 zQB71NR$lb@L-!or|IFI0VAIsWuVzm%T9ApNfNWQn(F(NmlQ}Pe)22W5NBzqM>HnQX z{ukw(S|EFTWoPHgC!`Ci>(=dGxN!fvy6RNsI^|ot1_pM$rCit2vH!&S^(Xdsw4gUG z8g`UxWBe;+?ZXB64UX!H;W)p2>k4m!p@6@szJEn!Nw>G9X+;tB?Z$12$5-syx$<$v zwv8KFyVk7fY8}lvbVU8uuA@hHy`?^K=uqF_-o1l;2oV2d8DD5Gl)nlJwZtH?{@Mx{&_=cRPOl|H{vvn$~h;+3Gj8C_&yZQG9<2!HI%qSzKQzm0tJwG{W;PUb~4%H2tjq~T5OsV+^ zr8$}eFX094Mq5jp*^+vAkHN5Kx6zEIcbcs&-NpcFO@OmYOs7NZb{I@MHaZQX@5MP~ zegqo8)!VJ+ZJTXIbP)2y<7x$Y{|zTMU%6)OEBpweUs=26%FQQl`26CdXU`tJSZ;pf z4U_fFH~*|k#I(v}vQ<@>ujS&o{0-&>bL{rv1%y!(E0k$tG5#rYTZaJ_w9K$`r@^*u z2mg_^txIofo5-Jr^Z4pd3_IbuySEWeZ7f%(%+KflYTU8YVBEEhpNX@;l5WRi^%#fx z>&lq^%e981`atU)=XbzQG5@_6=iAWAQ>ygCt_YjbLDw&gUlo|9cfn)#{m<3G=f?)>h#|lNDcYp-L>> zkS=C{xG=|x6BMea-KB0o`E|eJE_2g=??Zatk_6`R%7y>V!0V|UlASQWjnr?gkqTh{vy zWKWs*?HSm{|KqdI5Vvh$&%UeYqc>{!Xxq-6t1d!+fT!+Xm)HlTV+H-`^rS z({a(*>MiKPA4S)mM4G{%~ewm&GYJ%xE7w#xv z1y)xsTox`tPG*ry54)@j96ep&g*^f%jxn0=6M zsf@iGn>EJ@!G->J>s{2}|71f!R$E(ELBo+qFc{GRLzv&+&sV^2b4fKq)!Pf}>vwIo z57m^EtoL~#h@kj_vU6u-0zDPLTu}{3F5SOeZctj#26NM{_t<(LCGZ-zzx3Wu*Fah@ zO8CXXn#xQWzF&_n<1KYnM3t)|b#pjLEQ(A>T`bnjZ5&=h{;FiosJy3!3On0IUQD>D zEKkSCrzVPg&)GQIX}G=~$=@Ch{z3`8`oMw}JcmA2W~Lr*+DS}u7pD#v&L@^g4I7f} ziqX>Yh2+1kUc!W7Ys#@3DqIwlA=Y^sHT)w~p;+;P^Fo_;wxX^?O8$ z3b&!i%7F!75&T}mtGVY_{J{C$|DAY2rt~f$$5;J)|K}+e=1iX@@A=`{U(PSQ(9g5{ zLIdXiUQ|N7Jm@Hrh-FC06&1;(5(7ZLN8ypA!Ra(WnHEKzWR5{1l@%3nN(8u~MUKIj ziISq%_beP(8&zw`*ZMc62?hC#rZt=y**?SS6DlTc% z2Lie1xw7@kpS<}l*uUC&86CrPNOM4U7^DVxp^Ox6878@cr7Y2WQWR2?wZoFw1B z^&J~`a@XjsKhHy0f1Z}wC_#e&tDH~!tIO10h2ueAbaR%bz0{t%J)J;aoZgQ7e*6r% zZG3O!=JYwDD7{T(jh`VR<9pNM z9NZ5kOADi=4WA2#BmWdO0G~aUJo3r-9vT)-r8Ws0V(oZ(YU%hMj7gjMtJwP(8gvQ6 z8Ji$x{pBF^VL1z|A;7UYA5_<1L*T+@78pGTh{_KGDA3Xa56I_mFjwsJ1B4Ei5q@1h zpe5+RmJl3&1l$B@5Do-qbl3`k(}Jl2#G;40!9L{rVJ_jk$_f*+{=bZ4A)USN6yV;~ zgt}DSdV2IyM%S$*8nbH=p*5d+mWJ1T_7p@~s|~_=9YZdn_1#LcAyNkdg>CFS|7!L~ zS9XAUG%#g3`^J_D6Slm;E^lbvessl(quW~>_|MiZc2$}Z{QcFvi^>Yy+y@tf`7BW% zYK)K%z!NmgbN5T_s4lxv18+dCg*Q+Wb)lvx{lIgKR!2Ygpx92-M;g@KI(l?Fquoh8 z<`0b22FQWk2SjgfIdo{to1z1|clJ!$wrx_+nw9=KebgFhs<~+9f>}L_wql+QSh0X% zGy;6*B;fX8?H6|H5{!k$rC))4W8)s-FE|}#6S!j1DX^Dj%>qMCGxag%E(pF*Yf!@2 ziZ?9^r^AW>yC26d6FNgYD7;YZ0Bm0{V8UbOv}i|%si3@E zEb%2YcFw{m)kWFaYkIQ1`O|0H6e`9pa;W7E3f57c+tsX$m5HYh01%|a<8r86a=Tcs zJhGv+dR}mT3?jU+cGj(y)&jPeukrg?ld3BNHM#n|DqFmJPBOl5rpu<11oSH)c2ZZI-zmb~S*WEbX62YcF^ zZ2F~2 zzGWa7yZiJSSH26PC<=W9UXSb3g)OdCXbOT|)_BK_x92Gadz%TiSZRwaTyJBfgSVJ` za0>)La~C$$_35!?5vB=2Wr9)9&r^UlO=;MU3YbC|PMv>p`uXW=#>fue7hiDUvw>)V zZ?M1O{{lW!1~eVpI3_~;pTzDFkHW@(W>wHvIwUm(5FCJ}bZQ)6ieV%2dKthh(h4#^ zpJe1x9T=h%KrkK(3?o>%f<^{wsGIU0Kl0VrmmeRg#P_V=|E#nllalJ%v*q>Ix9o9> zYxDEH)@L5`+8FR<(2D2O@*`6B9=Ch1TcU^IhDu(L$0?QIuBFjPrFvyy0^F)pDm7x{ zdV1C=W#SAILqn{#o>G@1&A#jQ7wt#Umg9#SH(b@! zICr>p#wM{$u_v ze%_KLSGzO|Z-$6B=z4Hpag?hG39A8QVx*)k;xCZqPLXo&V+{G&pG_9M$+3BhcZ--X zOwmea$;g7!uk!!q_ik;1u=nb#=!L5o^E&=Ro!Dzqpy3A}LXV1+SIZP5o8d{5&@mKz z`_@&g>&VoB#JaHy^kSsS)&L2sE`~5!lIqJe?s%E**}M01G%^jBi9Tz84+*b_Tkgzl4~_ zcR|GFJ7I6Guy-4O2~|0|`9X5y!=qd7CD%MWy7>&OGYFvBE$px8ub|F$WMo7@dc}w< z8qC9x8ek}t*yTMLpz+!t0Voh~2>?wS4JCOE&fL^N{2Yh*wsiYALgkr|ybxSs?E}LMx)LA!=}-f_Q1i ztS4kI+*iT(6102Ye?D9O^bJ~tR9aYAQ`j@Dizq8=tf<^6DnT+tU2(udt~k5pyZA#l z&G2sKcO)y-a)bW#!xI9)U52s^X5yj`Z{2>ym2<>5b$95&B0odmEqmE#p`AHkCT~92 zpN!At;Ub5SEKoga?3$GR<5E1|k->Qo5plM$zhDzFmeq@~&Y?yClQG>f;XEWm3O52^ zgY-Y$Yhg3$^EZJrAabTp(@zopZIZH2AHMBnC;temQ@fVGM8zz{nH_^$hvv+gF(0fx zHAMVq(Ymzg1CiYA*PfM z!cb(H{MMeKIr;G>X*zk4WnCfb>=%?i)DCz)Rm0Sbx@J0ExUHLL4X>e4;+D2}9Vz4yS^j3aO z-=!j#Z9_wqep7g!OASJqBx%vk=v!4EMQ{73PD1|aK97RCZB}_s1Z8zq9Mebgr*@X* zw;OJaCUW)5%^`!OUoxR<@N|XW>8hEH`6`|lT_!rokT5=wW|%X4Fym5-rl7gFmknr= zppx*A$v8H&!^I8-%|f@uVq8I1z-+)XrC{uj*p~v5Dgep@9^L09;hq>=OQ5OpvGL@EtrG4#-gX6=f(NGmLJP$ExFrfOBm(td z3IfG}(7ZhXA>0O#2Ke_0t$vKJ3+#K$wMNLA9?uFlNK<+GA;M5(>HxHE9EXMFlxe7M0hk+h+F;a%HRMeS7(f{Fh5z z?M}G?0i2;(Rv%k&t&OuQC3)FvT#L$Er`rG^z^_;OC>Rwfh^QhHJdE86jl0OsX>$y0 zX#^q%g8RO+X!s(hIoH#$?r#%UZw|xkah0LK3~<{1_QqabCUQTwa&T2H;14oVrOU5H zHfbL#(JSR$HmX%3mIr;*Uj7LMx{2yp$Ty+;^->C=p#lB_rJpa87qV)JJ4G3d$|RwK z$iN7|nEcr#B?HpTj(EQ`M~*s7S$Ukqnrq2Hv#kXhvY@D0Ka(i*JtcE0b{Va|lGq8O zHK;06NiLFpEFwIdAs)4wvSSWQBG;lbgI9%A#8j&7E{i!=sS{_~`-_S~{o8hLdAuZnp*4c>Bq6ObCp z8qhpzcDtOrMnsB4Dy_+40}y`Vt;qq8CxF{K^o!sBUQ|P!hWY9kpdLcM2*`$M5(Y|y z9?t9Yf<6s`cp6xNl=JgkPMF)EXjTKxwgHy~89V_zch4pb0Z|SA%>DnUXRK8{ih;J@ zzrugRPv*<`SKdRCyMK2JlX+(UZ@`)8R$}36&mnp0fiEZg@E|_!O%eUS;f8E1tXDReE*WR`5HvXNNSO3m|d;q=$+a+Jeo}|Bl(Pkq^g`J>dp9EH3(_nrAYdJ2;SONO>4dBtdBV$j- z0l;v-TtIfbF5{+*TQlwyqQ5~pY;-=96{&t6g}FtiwO0(&W_RhXfU5n3 zPyF4Vlvxd#o^^}RpF&x=2a&?ZvgrO_JL#KhYnSuSp~B^7&V2Uhqo47wqX_SYvD?kE zU;PTrY-spfLj#(8|NW^_*uKrbjAE&RM;}FJ@g<)<@<{4#{v{OWk3!O%#>T(lRQ_0P z?U(iS#4CIUIyL%mZ7rHpTf2&X9wqRy>d{BPgv<|Dcg+AdjnH!kXJji{OB%NhOEo?1 z)4>}jIcuFd^FWa=r6fLnA^;&LYDzj%&P7{`CtP|g8AjTJ7R56Tl5J~ifV_W5-vQ_-6ULSy_9$)5SOIni z4zN22faO6Hdcq`FAC!arK|NR?w1W-8M6g1b0(J<)V2Q98Y!OxiisWX%k=zYPl9!;% zAzt8dh!}VhVg}xYPNTHO6*{ee(02;m2ks;>wZR$|tZu~vPL>G>9fu$!V>u|TRZDk< zKwN^GB5tHYF9Ttm&hHz9J$xp;2*MMK3K~pn*ib#M7P{A{U7e|u&VdG%$^qD{>K_#8{SDT@Dv!`LRUT}E@H86T4)6s(ZboA#gdF3y8PA0;$GPz_ zd$dO}WayV?%V^yShdmi>iHX(rV38+O<}Q?mWPl?@t6S?EZz_tG6xmu_QLl=gW$@WE zGkc1}Vsfp!N@Or)YHBq^(&d|?RG0%znbG{7ESD+Aokv##&P%*X(_ApgTEfO9TH2?# zIWz!C48ZA`P&Xy6m?N_=%Rf2C!8jRuB7pGH8l6b3XDn43K(Y*~%_bX-w5mp*bqN5% z1I}S>alGOx)})hV$z)-f!E1MxLf|8}Go+EVm~$OYakec4A?V!_QIV(^@D;^Mi&AZ_ z{w`zg(x}gBb7#?QJ9do3Vm+}~do1>!ci!pQvZW~!`D)9STi$%LXXj3S5Da>Q)Q%ma zvDht;Vapa?5sRUwH{ReCJ9eP}VvwO!5{+_*iZ-LA1NhXi)-Al^a-Bom#Xt##!)Ku9-3a!r< zLJ;nzI^bni=xy<&SuQJc6%=}1@x*{QDyABBJrFvus0mHds2w_M$S09-K4+b=R571! z%Ci;tn!n2?AOaY}hap->qpA7?IgG)YP& z(Q*TbE*3cpOLl3A&#{(MD}2FVwy)i4P)6mhW~Bm9gs6bTFA+IKsR#3Nbt;HGOp233 zi5HT;OMWY?XTaVI33LDs_^T5`N&W;J;9W@KuO2%bGTLGP1vot;$un>Yj*lgVOm;ZD zI`Kl{Yn<KZpK?KZo-0in{LRk7TAm+$etIJ&(97(GK`Qhx53X6 zp2Oln9Pm3qJWgxs(=+@(FdpoDEX2crr{jy#f8ieRqVP58!36#+K_C~H&)BdV_J!9F zglKPWjKbrEAGnS`7mqu@rT}~|gaOKVH*y>k?}ytZ*jr`>e2#$LCzF}VAi6L_Cy?ZZ z>{M^xfFq{h!rp=YDdIU0%aXQ8L(*mo!G=Sfftd-EoEc~f1Cbirq?uAP1#!T#LpE#a zj2>@XD#`P9Pis;+gSldoqRo(@&??fN49rXtRuliB)u@JGbWTv=D+{gOnQdrCIC+s# z4JDUYHqzp;CY9hq>4YqtT_#PQwo>PoB^GtGHV?=|QxttI`W&-4YN(#v;G|^=by+y8 zxoc5k+3(a&IT-h78z2y}m!afkjVMl{Xj8F@A_wbup|(6~Fc38x#hy3`0gt)m6&1@lXjS)oFF_PSh zqFIVqHgQO;YE&BhwWMGAB9jVKWHwG}xQ$bryxM$&(@nl9^%He|0~huim0TU~c6uGU z0$n`pl+bco^`zmFo}L}^2EeHppleC7D;$TjPOoLly>h#f<19XQVCLQWMz8;>tNhXu zzy7{?QyWw&#`e_cHO4?J6rlk)r@NcDH95aL>T$aa`P#g!8^+q$cJ?=*XVidYPz`7r z4}rDD9ni)AO%IcGex3%*RiHbA*6?4`@HBUt+JxpObm@sCp1~N4Y7NPP`xmqlIH^YY z=^dcd^dIgEE!jh#ze&1DkKG8X1YO~qhd6&}!Bw&GLWezvXWk zY+v&dvZGdshW{6&e8ZpYZ(Z{$a-t5o=DWY^>j3yoYU5Oz#uAHC>9rS1m}2Vw%%Pf^ z_G^B}PrR&k2AQ*_YaYNt4Ul)1`4nQYJg|7Z&zE)XX`9JTvW!%t1wcMG?R4d5x#KzJ zsLo=vn&>$}9U=@(pTB&=4pi~!OI9`F-~ZR`>rma<3;|2^diD-_6y~q$GNu47($b8L zfD`gNu+#YjEE!Z-i$wwzKwSmKDh{d^%teKNx{}WGNnBZJ8&Ql20~I9H51ua=8)pa{ zE=UMF7t3C*S7(R*^Y`AT+nUth6%67>KMz$UQgQv5Ip76nC!F z&|f9d$FmXgc6aykC#|(CSuOcGD>JB*I!mA6U)(zdv0Lwb|M`8_qEKbI+ZY6YI*SOf zyYrfAG!k9QPA6`C8f2c@h_b!tyKx8bzk*$-S~LmOPS6vTw7CGAN1xlErU{A#echb2Ku-? z$LUnroHALRf}y1#XlD~-;8La5*FIFrsGMb##97H^8nV>{DcY}AySjF&3)ON)|5Ub8 zEItm%eoRIue=~a-{de$OCwAW%v=vJoacph`iN4U?;2Jp)r_@b~0{ zBn|&Kii-&?%x*!7K)K~G#@EL=*dJ#!*yP5b^Lubuv#~KUpwI%{dE7$S5`kR_p5JxY( zRjo??(IHtq;09a*DSsI@yD)O zdV*My`iwA*KD76YoLadU&`af{{29>jp^GZZdUwT2*GEVIR9d;-`%;+yvox?E<^SPE zcSHOo$%PLg5B$Mu@Hg|?>4p4~bMC+9vimRRWQE zOyEHlxB!C?+w#AXp7X&oARLTbT@cL{HHV>sk|FhIdd4KPB1 zQwY8vb4MU$%-w*=;;rA~_vEBg;fk?Tn8g!f4WzM`g-g+8 zgD^`2gz5)~oHSd;y||!@74$ej#22K;a3Fbj;e-#cu-W51fpfP7yC#I0rqIOppz_=^ zsxSj?ngIn69UANkAx>o2zjy; zexE8lx|lebnx+f|$d`N#5JFQ`D;|Z2gvDQ`9z=-$9xZ+3k$e%OW2Jf`q$G6_&ICbp zGl>d_eL?Ki@8P|Dqc=6=JC>$?IGHyZaiEp1| zEBenZCw~3--qWW~&Ajj4d-oBKufFyeT`=ONwvVi*EWB}L|C-AVujyY&Lkz(g2%y^7 z3!b9@Z6VeYwtCAlY)$=^YON}q zFnQWc<~HpzuIlN5i7n#V-kDi7{Og0r8}zsrr9Y|vx~p$IdELzeMAK`xKlaFNufKKb znP<=Ny9RE#_SPG(I?+F}9zEU_25+Z)%Vg8tAxUP-a&2Kgp`NW~wi)c%8;4OS`+cWc zD%~&eo(s!6_x$&BV3`6W&brvN*B0x-7+pynG&FC(lf=D85;h5tUC-A5H7&6HrTw2q zh&wY=b7-2F8BDz$`E>vD+b(_NZvOL}>w|Mi^3vd%9Av!v{qf2n@8~&x>1n_g{zrN*U(2-5J+` zCHFH3PzeFv(gY*&0GbW)WwwDn@Hp}N#G}5kb=kXV@64SJdZa1$L&Q>2EtwS2AD;Io8tBZO$FBQjRHAAtATfc zc{dgqFKHls;5ZH(mGrT+0TKZG0$@7v7#;t?6d2!K_^>p78~zHYY`9~34Ks%0J3l;( z#y7<`NWub?0x~lGz_Dp$Z1ox*i>-t0%;3|6;)iauBocI*l4`Nksn#ZB#?oT-N>&Gu zPHMUZ;1o!idp$eBts+>3-C+dKb%$6>Xc!}H_b~<$MX3S+tj>TB#kr=b_8OVd1aYuT zz$GcYiGf%tg~*trVu|UF+InT7GBOc@B}FUbv{yTr$*8i4eaK(#;Hcpwqvy&BqawLJ z>9#@)-gARNaTwwaOATHyXciS?;P$dY3fsWPkL{9F!g?Qh_k!TiaD zb~iEcB9?Qik(nPDOd2>p#4=%`*_na7N=1RfZ<(W)Mx?AH$7RU|eT2z_7`IdbLC66_ zpJQT~N)fu2U*Ca_w@T#s^NuKDGxiX&2)|jZD_VXnAzS*18~xVB)`(qHlOb0$8n!pX zteJ*%TG4fOz!ouz0rKKTbMr;~%S zlJ(zKzla8zpf;#dpb%LB)-_@UV`8o9Ky90Ng{Qi&KvzDm%u|Twid4t?rWHIA?#!$= z`l>fqX(+vd{}?>T!r8&avRfsE3a2?sM2057H0DADOPG7=W4g@!q)2^Jd|gl^xkqGy zSUv_$E;f1%nQ}jvuNdt?GASn=oSbvZ9h_DQUL|z>sn;|<{)%k`VdA%%e|z|{W2oGA zt@#1vV*VPf@4y|R3euu5OP%$d^24;Ey|$)K3{p>mM$V8I0M~%VpN2kmXcB(n_~}&3CpWO zE1f?%b|#O_Ct(+61@Lq@TrhS%aN(uUU20hl>}u2hbbAJU2ZG5ljbKX$+yMS+A(o7= z8z)@=&y(-LO&y=o8+5=O;Ys;;57LE`$#@<{<-w8D##11kL_YWf2zkdZ0RAwR!S5+U z2F!+jNJ=DekTuQN0z!rmRt?9RIKOzL$fJR0>+llKhkM~={Pg(c z86)4q%2Aj@0AqIY+}V;w!6Bz zJ=EM@S;hacsIbOe(^6i}fB1Cgw@5-GeI%g($nhDMgJA=jT#%nVp`o!BoikFRgAoOU zGbI4rju8z@wL8wrjAw5k#aDDvbOZ8z{sbjHU4qoClTxZ#R%|#(Do>ncAwt3j*U^fD zG9#ly<>pTgNOTL&8kHYW2-qwVQ+>>RoyEoVr3)WCdX%uwnX`WK8UE`} zK7nb-Xg5*D|CJysPd*<#FlW=lNN-JjPQ1FYxw5sQ^4mLQhL8P7689KM@j|Kb<83m- zR9pmL^jcHnHg22@2Ok6SuQSJ#M=7+QP>zp>qz#cNXTM!CM(yli)wLeT2ai7Gw1WNMz=y3>6I*+eD)? z!=WmLRnpAnndG%%(s!#?t2-dqdjgVIMOk-N$xPP#YQrk6N+NY#rZ!ehmf8%4OK47Q z%p1Ldjg}GQAJv6ogGMf2A%jq5p3!^7S(Hpg)qt1(}jq~dsx2; z*1%9SFk;pLy1XkVijn~zOyzSf0Q`Yc*SKg9C1Hjl2P6X+b8sl&Vns~~(Zha&yw9(d zRYbuh_7c7;HC-m*P|XXo`7dU_fDr28Qp5E7^J($9VMcTXy$b024`iDg=SU6}}aY1%zQ@S%_JYrZSHk z7!C~7lVAV=@)sVm$C5rTC@Ap146$$Pb3;K!vcyBO4O1)s#{0InS>jTKWI1D)*stVr zZ={s};3O=&P;L{8WpO<^BR1O7R)BsW!07SZ%JQatK0bD~*=v}g;`wuoCoUPf=J5idbr6eL>x&4z4;N)k0&YF&Vm zX^e@av?(YCJ0CKO@uBSy2JK>-ty>zAAw8##O!9bwnOxP7IgcOvH*>S z$vBQtjzL?3=g4tin9u-{9h4Am+Zgl>2M&dKHpq|X!{S&|43#HTqEMCT3MGMi;7(c~ zps_&&u47QBa4hEua1i$gGZ(!s4>ox~K|lJ#P^B2TfwY9`#%s6~k8{&ZNnlID1%Q+Q zlz|n~n6Kq?0TMgnrKkAECNkU*=^iKsXfS;>j0a$>k$#>q8p7`ngv!DP!L1-)T+jA+ z*mK)hpIWIXi!j4-k4dVN#%We0XXG*n^y8QM%n%b8;`Fjo`N(cwYOzU;N~MEYr()cc zQ%fR%{8i{^k#gJ3`Ya1$=6s#SwU7AMjbL>$&)2p>b0 zT}~`TTG6>9c?xQXnXEncNSm*?fIQx*Mk1NUPw6F&tyZ_y$|yP(n>&HFSQ)nCLZ zgjOuiPD-s1yM}*^wQ(Z1!@wMlltGk8FQ^~D+vcAVU0lYAm5ey>T?a9VWK)mJnbT?o zoh5(%RkizEkmdZOm{5>b2pzagyHTsllIg{o7lx%q_*_HkWoqTN^)X7MvUf|()un?H z#U+b15{ih)Q?IEB%~Jk-iSE!mh*Saay%nm1)syvEjuOthsF>vjRS@sko#=Fz`@vT( zt5W4kgbrBto}E4q^t~p{4Q2Is(H5&RU_!kn8O+6+eKiG#gyG5AbZ12=5W z0L(JzJ6$o*fr45OvpTG=08$CWvHt`}tO73wDUQWF9rMe4E-6@Efr=Hh-alX1wWf0l z(y0`^OUv(Jl$40DXjtOX%S4<^%?uE|7+nNWntu3 zsFHAEH46bAwdVD$B6FKog>#26)mSEBi~Tpk}UYXyeAo*t-hSwuf9@!x%Mb` zvsw6l@F8M2u)yV@47`BJmViF4LFfnOLsX6*MIB)M0T{|798>_Bw*XLtU`mL_)98X? zL}-vg3fMP5+edns827>u8()Ui576Fl69hA%^ZBv;(a)3Fms4-@3;BhqH;E9s3|&qH zmq^X2QAUDJZQ<8^`2MrcuS9=excA~=Vq)quT0}64)EBpH+;r=$n>OB-I+OZFtS3wq zrRUWP_wHTDtJk1I?|pzS9$i3-QlCjoRL+sbi;o;xxadmVff+Ln9GW)m(1vCCWfk%K zGJQN0%!`KxiC*F1VZeL2jL1yAUF{D4QEo;nA9!ha-u?X7OO~WwdqQshqZKXNbnltX zn@*iV;XkU2A6KN-An}qV{J-vd?KJ|)%}+S9(4oagk1k$#&9Ox@F28)n^rJ_oud7Yu zH8m84i%UY`63n|fu!Y{mzQ|DEW8?#!b_vu$y?}OrV?XSL*gD5E?#g&F<2M<E*dqB6`K_`zEbR4WB#p z%_UDh%_~+;IFnAEb*6tkbyCZvzNf^5DfOA0K$^Zy?>_R%i;sV>4t+82z@a%gb0>nI z-P}37^XB!=L67ptf0lU^B1R5QxEo9QMy^bKuU8T#0t|Xn zp8=YB_o%8uC1!lZYl|lk?+!Fg8EP6FY91J99@0;!?+Thr@=AIeJG<+9*Dr&g-eqyQ zEl#J!JxH$k>gHX!I$d)6Ns#4A?>>0w6#vYq^t$qF63imhlI)7%)ZYm;-;D0#|8v6+ zsZ?>8xtP)MAFMSVW{JWLRRx7r>+fi8K0K6+WF@CeO=e{!Kf1NK`PQ{{Wo31#n;!&V zD*gzv2C08sEDElrpAo6n21TQPdx&NDD=A3c+8g!zqrF{GpD(++Im<_hgV_bmIl;_m zb6Z6O2>$c%1svih>Z)U$*Fb%#394Wmclovl!gdzr7m7fAy%4FMQGM1R)1bZ1P=m<)6M zGXSw*K14xU2L3|p0J~rt;AbBJ(HtZsP)VjmHY|3-c!Gx2SUbw6T`;(S)`@KpIatOG z3-J~&=WP_BAHo*Acp10$9nX6{&)7BW*mZ)%FYM!Ejhl6+M?3-;0`nEv1r;@oP-3Ul zTkmI7~S@_QDSTdUxtST|{L^>Kq(*cc;EdCw6!9Zy<_@iZn98hTRNx zdNJKY|3{cVlEN&cIztb>E925b9^kOy*j6>rgSG&QY)?iXc;Ze4)M^~%D&wd1gk0cH z`roStxwts4?jKCB$1Cbb6^kovEDyFI9XH>`#6V4c>Z!)Yub+BqU1MYFE?jcN^SHdf z#;bqg6*|lR7A5&BAXj4}Iq^Hbiy9pLBe^)`ZEQrJJoQv6R9A=Q3ulRe`ubEB971yX z+WAlYf84!yfK)}gH#~J>=NzVU_jJ$n_i zW59$thc)b)NQ#J}?ykzrobvnC>25@K?|tumzwfVadQPfz>eQ)I_0$u7&$IN}{rgwb z{iL2=I~adF?W7;HZ)7`?Psa3*Z{N_qp<9V{U5b-FzxZ_T)zV8H{bSpeU)#A_>=A)j zu8sw4=u-M|O+QHN@cne4t#5;@?jOmvX8628%#Lgw&QL>#L+gG5a3|+O+kO?knM;!R zHn(G@d?f9uQ`l|?)4obOk@hq2!DYf&SwTpg^Zy4L)~gtHIHW0;0Q2oiPyyEi_iY!D z-|hhR+kHTPdz3suo+i(em+)TkqEsm&nw=qzl%0ZFQw1(qpfI@$)O*V--Kohhbxfg@ zv1@!?Zj~45U{|WFIF;=5BTJ?lPNV`;Wu%UYa5$omGI8^%TmC*8F3Z?W7{8OHN|lv5 zo=Tjm{Ap=Yaj9dRIbAuu@@&w6`{6%4gELua7F#M}$J&i?e;GSe#Fm*nvUA4YWXII@ z$Ls%;=6@P?dRdvtd$2k{hWCKn)_$^6YMnEO?lD?O&;lu?`EH$ zRL-x|+YH9}KEK~r#iAE^{eEwmKOMpwL|Od{*`2Fc^n4a`BfI~tH5#X5tKaAISNekf zKskd;zu77ID$i7Fu+yS985h7Wr^)EAV%30**k~-b=yg{5&CHqN%HFv6an<(n?PTtD zs%$6o)RWR3WKQy2nTZozZY5{1sQxX<@I(T3Iadu~X|}V(sY_b4jb)jq7CRSrz^B`G zl1TTT>=5@Y^!O_91ZM`*`F0p*BnAn0!~S#|GujbeVl7%=+q*ogF6 ztybOpa6!KXkJ}y$1iFynPgVz!-q%|!Q;@3@56?<)IDFi@dP{pU-+H85&u;UccU`jl zE`PwgI(d)lOr}cXs??1x#tm|ua+Pmk?=<0#LwDit(4IXwVt@Fad&oWMYqULi4+JaR?H+JjBmx5 zIcBmkr6OXp!#*)Wu!v;-oS=il@uD?qWwSo`jT|4bowlgLiXGuH1R4N?W2Udre1UUo zMufxLo6PYp5b|;aQgwS}1|A{mhG%Y)9TWLF29| zp?4I`2@Y{`OXd8&>2%8_+~wS7yJC;f$A22D%ht{8%Mst`->B?Sq*qTK%xR%bU7}ZY}!npr@J}szI$en6^p{$ zfNfh?8(PtZo8-qNJ9ym-Fc+*z+n)AN+T&^OrhT3EQyK+rF#>CeDP$G_A_lW2P5m%B zfn$YsI>oI@D5=gait*db-6PW!fa4u_%U5I_9Nh@B^NZzp(8ls zSpVPzKRCGsp{eS-vxQLNBdT8A~jo7MyAsR{!7s*cz~^ z(v4?yv&+Me#|OrqLSBZi)N-Tc|ppH>CC0Z44^$` z|H)IZWW~|Jm%z_-8<%G?I!qkzE3k)ngFb5X>LQaigz5))B*P~n7P(aDZ{A?@RI#UGtgi-?lp@u)8rzupw%e*=`YGnB>qso-WM#f3(iQ6-ej`- zU4cv;`IbJo$sF;>I&zjBY&w|I*rCHD-cHWA98T2I`C%m25VaX?((s{VRQOgaSL++R zK&N~JS2r$WdYx#ro-l2!hSPOfr`%2VY0V@(B5LUh+nB4Iaf6pUOW*KU&hTa1Csk=p zjsioiZ%kKI8K)T$S zC3~w@(w{WmEZ&wg9t>1LC?%CiP#YOvH65yk~wj-EhNzc&CDIyVXv1rQV z;{1ZE#@*-7HwW7@25ZM|v&AH%f5Ztp3|i0Ui|L;SjJt+pw%8-4kU5}?BLf73PjH-R z^p{l)pXGzp-zpPwQAS2iuD#QqQ@ac-<k%ea;t8+3c1?x3fADfao0M>i z;mrq4u0vNyx>FMFu>%G&33u!167El*>-vT0Pm^%Nb6`TkJ&Z}X9b!_#>^aE&|3kaQc|+m6_5kY00B3ydV|pwa#1(qZw#THgVK-|5IZXh=Vl6n5LC@S)2!?lTV> zy-e6`u)w}=oU~UJb}xEt>ozWw5O)7?=rAdyg`>G9dW_?n?|ijb6?Qv9P)BCM?luU! z_du4N#p%jo>4LmSpMK~fy?Z)tb-?LJ3A>HI3A-KUgK{7x>=q)Q3#SRYEzUCy5O#m@ z1wEM%T??MWhx@)daDW||sJrhF#M-*Vb?>J*1`ZrxN6rEf_xHFCe*QTT6X{rbA&@AG z#j$JNrHa4R@A!K88Sql)rfvQYdMFkUO6kN;!jR)-Vyibv zl9Yl;iV}b(Mk?sHkJnYk)Y#R}nNbU;r73P8iS&s~r#^R8za^Ox#?%QK19(qaJUiv$ znCr{zkTlrT>6a_hBPo=6*HBy)iPK`+> zWXdk=OJhqP_Vux2>We)6Yy7XyQ~6k))cNUY*;C74lCN;;w;TZo0^!YqId&Cqx%Gf( z(3p0q8k2ptWmwUdOSv4MXeeyQH^$?Sjd9SQq?qvE8`ou*oYKv%7B7dk-5tNAgBW zhYaaQh2pq<@lk{4AaC&TP*rh6TF_cYw>!`P(#XU3qdyB$H z|Mz@Stc<{ILDynk*N(MO88)^?!h$I|k+5slr4wQ7n7aP$_@BZOc~}}&w*2@nTjjZ- z3-NbS-b@1G@ZQJxdx#Hq!-wfU?sFXayFmgJkQVy1dP(>7uiV5Pqc8Ax(Wm=YUd0`! zFVN3f$n*4o8bLq5>Z(n6>@)hmKX#1%a{M@NJ$`)ED%yMeo3qd9|MBauiSW%g%FhV5 z96Ppbnewl%zgGTr;slY79~bP$k1PK=e!Tz36DI`Mv12Tb@CZXGMEors?LBdV(K8Ga z@oMoI$pfioEBKJpFtVxgf2O{n>T6V0vJnB=v$$enai*E!Vn!37@0r;IWBEchmk{nS zl^1mp%EGa*h``_`p`8JhpA^Kt_{_ZGzWqh>Go}|2uSwzqhH_qG)izmNLp27%<(3L= z60DeJHT+s_J8!Jus%~r;Ks;r3eR|C9DW zlrGE3%SBr-`{h5w2BsI%-A>rmZG{c!gDu~G;V+pJ-hptE^(dB89pge<4><3`9tD2-_it!2KlBTvBK67p)nc5 zUnp+qit6Pi**tXM#=I_DoOHx3y1S%#OE8?PO*&yhD~XujFoy3z}Gs?K=9 zD0q|`S=cz*-)W(*)!XOMF?rpV>S5(mA6R(v$li*0_qEA6aMI7ifaVhyqurg)~X14OK zC{II&f$cK;UYIfy(`Iq1snv^9c_6F%>V>P%yO}H!q8$8#wYB%a*mZpSf`Q)VcZ(=*Qa0-#!_-h3+j`>8mkZ#jq~v zM)`J0uSzSDI$&xiWS>TY1H$+TWf|zEDIFhxyt6RI=SawT(FV+PKEVZ1n}Na9B$Ff( z5qLU+$C*Sh`+nrxZ`-)?ta;zPS90IN0RWPoJ3ac@Ve+OuuOf@D8F*1m9IC13(mR_z zC4X@QEuoB9Q=YZ)8G7NUw#gH8MrYxr@iDE1H~vT}yQlB-mlyHrJ*{h6mpn<|r{f2E z!rZ3qXJoY(|-Io&lN zBF>>V5B``u;VgoJT0wEp(EK!AJ8bxbjsC1*?u-%D1$kTgNNe|mz5e=^Wp2~ryvWjh z^b5LRU^vWOy=`{ZuxyJnI0_PrF1l9!n`BXC7)D3}J(%NS=re_B<*L7mJV(1F&l8BOZst2m4NP4BF&V@7#LH7h3)uEm z!giQhwqWc~uV9gj{0t(L(T3^~%2Vv94li+-R^kqo5Z3>VzJGEz$(Q6_?rl-&A2Gq8 z>6YmEo(6Z1hJR|jXzmvJzaMMRcT427?*b=w-jKJP`N195CPTnC2ANF#>`X13To+s7?5vC6N?QR8 zsWrf{2gO5pD>?(-N#Ye%VRBKG+1Tr=WLJqO*d?kliN1SUIU~02GL1nTbTq#A)|0_sU)4Pktc2X3K%>Le%QVik6UUa#@I#ck!;VoF&Se zv&_IYG)?tn5UVj@b7;~h3Z^I|5}bvrXl`imW+`8}roh>^s7r@^j7MXflAa+MEFP2D zD4V8ue2uaxi@*=4G&k zF}Su#uE~;Rk;ar`mN#qd6G-|}-l7cvaP$h#6pd*SbogA7MOjr}-xworIVVW=vm}Gf zAsM!5tP|7E5_ATSXfka!X(oEoCrgG*-fp-`W6dphF0*CXwAW~?Q#@zMW^KgeRQb-k zXk1mN3($WnM%l;me-O-k>!CvH(s8ie*R&*~$xJ9#i#?V6h1e*?59P zg%z|F%g6*B%Pt(?`UCq5JGQA;*hQ>Zq((qmsC!sAojRBArEm1@s+~=UAg{SeXut1* zh6NnC_s^vKwx-X*WPh{rqQ`o9?ssu|ujD1eBHg()Ihf>WZU1QgE3KoyTk8_u)4KY* z^=|HIgR#F$HVc14c#mWuU-v&U;c*h3TEj~9eSQr*_AvB3;Lhr418t(tFyRwm2B-7kyLO4d2f*F*dG9dm7%v|C;s%Jazp9T^f;C;HN7>GFjWI zWEv)*1i8i(KL0Pz{=E#0d?1P4eBxc8V9erf1mYcF0#x-^PaPOBrv|D3CJFOO%=oQs7 zn5-wy{@XCyZ?TR_BDh6|{Zxp(ZJu;%ZEN&7d!3cZ4>G$dM_65F3z&#VyHkfE-daTGqtH1xeMhkZJJRG|JQjg_8I{>FU*8|_mF`P}rb`$7R_ql&MNOFY z=we_V{wZxOv=ui4>+oja9R3Ats~w?{}~>`?oMPNiw1=3l{p!*B)pd)ezb3OboV(yDn40j_6&g5O+47Y-7A%dJ;8sODwT_gMO7(j@+kK986Or7 zymXT$aK1V4m;Nt=V1#cGf;n=70Jrl0_UChxScq`78egVZOM*NP$kM(KjV@t<$<+6O z)^VraVK?|9_-hJfWP~Q1x;!rsi3A$Mk&MvUp=c!Xp5CDGZDC>OvBZx>B2h%J93elC z{L#>Pp%5~#phN~v@L0@S1CdY&fw4eSG8-!jw|wu^W#UG^zxiYAmUX9UFaau>EJrky z2xE!W@_ew^JyjjVsrN#iBu_|vrr*j&ouVPOsU^^FNt5`O7khsZ;~Iaj!XFS98oj+w zYwr@_N+ulT^}JRWDU&r3zRG2E>S~R4r>@RuvzcnSV5rt^^yn&GMi&bYhzQ4-$rgh1 zz7WaGif~+JmMD2RF1E`ax5t+SY?hGtOdNz2s)}a{K9+6JvGZ|wpednH80SIyx*&*l zpACL#R{c1!aVQ!IBB*mn)!%qX$_8I4;oJ8&&ShF+w2gvjQ-3OhoH5Ch8?^g=|BED3 zir`kVP0H@g6+ak1tbJ7b@U{^nJ4SZ3cXYIO;bhcErD<6Ea530>5Zf2@A$f4`8Dc~4 z^NDT${ukB1-sgEo|8ZvLmD*Bi-kd*wMKO?l(%^Hq>sOsdCoYDzY8LcUYoK+`Rv;6g zlR6tC`pUF(F`{3Rb{PS6R>Wj1^kgxoh_6z7S4ZLqVL6L=2 z`Dl?kvbT_77~4lm zLuip9V-yyD3t}ORui$VZ7l>a7KjbW?P3FeHs$Rq^+?jv}uOk2hjDreHUL!>!IhMA} ziYcQcp&{$ek}FC}5}An_qm~m#a9Nxsg$`}992Ak_hBAFQcNHs;m7Cy?R5SwYAbAfc z)UoB2S`D?l2tEvWDK}}?83#=KT$JQ zP!&3pnxwQutr}p@BT&m{&xf^rvTmuWuvOftbB&IEOkdx*RIDkHh3{^s@6scGCFHp~ zg|=2r=Mx8iJ<gIgZHWy6HF2&v#7<#mE|+)svwqKjS1lw0)Jx`$&MLRrcAkxJq)Q zrskQ3hG}`$`lk%gMWCT=6C24odzLx(Sz}TCw2N0>z9d;d!xV4P{ZCmf4Howl_pnL1 zF}YuHWSitT*IGA>tBrX)e-3z+iB*diRdcQN&iMYaGe(I+mC3bpD;JVAj+ik|X}iSP z?tDY2I%9h4^j7Ykwh@;ICK}nfaPunVPHW5-eRk(ga{V1MtPM}l*X!#^dDvgC-0AUK zBjjh^np@AkU0*+QQdf&+(vF%Ma=mwh_3AhL!06mZc=~SGs&LB0#H%Zzi z9V`%vQHDi1$yta+S8s@sTQs&NqfyDI5u^`z+=VN0+L{bLoL_g}d6Lj93UrRHSqC9CE9YZ%-$OL@qW*1XdXqC9xkLNy?xW?q#o1(#n16RRXQtk6uM?;T~T6rgCWuIXR6E zZiPbj=Us-ri#+WkW^ACX7ffr(5Kf$&P$hb7Z_Rd#|FF+JZ$tYDxu$uU`EUyfn&{#d z@~TOBzJ*&QH!7nzXVuc)Jno8S{ub_u6-spr_ttVH+RPnVu9#Z5lPi=FlAc?&g32O! zd^rSA5B8kbG`Xf)A`ZIz0++F`lXo_#axXAB4px-v0D9{ughbGD>ie>B$!Rpzq)ap}^bs%1~jDx9=})!W>WE zm4k&f-oDkP!ltrROZ|r?>S-)*Ex#<*61&)TIN2t``?ht>7ucHZ=(4tRvYWEUH_{K2 z4S@dSI_5%5Pn_bkG3Hqu{53I2 zv8KY|sYRt^?T91E&Hjfpsq53zq{>OfWg_ptw{AwVDai}{;r?T%HJUiD27ld?>L$K% zM(r8&8+JuLV|)4~+rNYu+P1uA1>xt^&cXOxQ=@!aQ&U^5ra3UNeiHSrs9sJ2Ecad( z{S({K+n3cW`}Y?zAo&vH+@9X6ertDW39`I%F#X`}Pko<`#<_A3yDL~Z^AACJ3y zxNk#&sy-=g4(ONP>;iUya=9<Ub^I_Vn`qGQ#_a&t4?!cuX$aC}{RKifUVcFK!v|3AOxb}v~} z;{d2-Lqocwy`^BuBJPR?_jz{nl6v==1$hkv69;8YUJh5Lyzyp7Szgby;+hxg$S=wp z#B^1R6AR0S)hcgT>9UTp!tTjs&Ga}snNn6y_FR$0NvY^>dTPmw!)lOH>Od7wV(Y`E z`s@BbUnJz`o8)s~zZFyG7N+3`B;H6ET45GDqX3yhSSG<@r|#5Q8gBt9#|X3p-FOPs z0JLaU9C)d2;H3g57c4n2F|6`AKP!MZpVs%&(Pfzi9Y1(;!3DlXgC)Ia{Kdu%u40|Z zR!@qi&?7|?U9OBk2qcfLB|4~Tq0gU_;R`w(rL*YK4?id8aWn48yRY|0(Ny0zpBqRg z&_4Pvpzg@<3b33k1hV8AA2jaG?=TL!=rQ}t(E-Lm^j7MDQOlaaS7yeF0s3g1UOcUB zXMTQdP9!{XD;-U*rx$$t6IXlX&)jRunJ}}3#=4NU9as@_#0T)zdGWPXK%<&d?bEP| zw?}L$2n4ID(%u4vgjwa-@GSAK`ZlsHEFR4VgDIE?CLziNoe~8v9NN>o2|3 zeN)A#dFuHtYi!jwXrbGBM z8B4`3SiW_1Z0zc-B{C2S>!0H)=p>X$P$lth!NZrh!Vjzha6F%q)x zOU>;DvLqA}=PoR6y10Bcy_1f7 zb7jt`;ki}-T~sz$yV_0O{E{wKx_C zpLlMSp+{r#c|u|Hjw_h!%9u85h^`~urJuf{nD-E|y-nXPaxme@td&YVrK4m{;~LS@ zlxZ6-cm?-kOC7_Vj`XEgOUM?4 ze>1$6bdqso8kq-NCx^&) zWbjFK1lGq`@3I4M2GML}PPpt=6>(IB1vW+{U}?}x8H)iKFsLUwWhF>Eru)QF;EqfW zC^HE{oZ?1c@Ml^-N#2B-ErGNGO)Iq=)D;eVaQCFb6e=eJDMG~pOn?FcWigIx)n<;d zbYYH%6fmh%1-^&@Lm`pUg&39<4!}}*m^X8V-{Ms5@R*)Pq5z3~1PqG@b%#d+RseH% z76ii>>Rs$+s<_wb#`8h$TZW<;b&AYjGcYWRxSI3S-C5>Sw@+2aoq^o!Nz@uAaWb2I4Fso+k19sr%!$zcmN$ zfv0h*ZOdAOJy2rYK{EChS>wPiGF60`s4e0`%O!4!n1t07RAgDoWRqZQK?3#pQ9ZO3 zdl>fM8F2>^X&Uv?KwC%Q$>%EO<%wo}ptN3ElU+~;Js6JM_P>ZH?C}R5eJ|#N=D7wT~3Iy%R>xx^RX; z_R8iabC#aZNVjBbv zi|CYq?Ajg>Ig@Rekdr4E#0K%os_ZPY4wPozh2Ten1tC9JjTt0 zRSsOWjVvt82n2Qjy~l1aiQU9vvW0h;>`sH?g8S6SD%DX$adTZj@7(PBY1^85uG?OBW)S>T*1iAaFT# zC1D_O<_z=->ALCe^Ib022FYWS%-kkkh={pPz58)~u}@!Fq@5j$Xk7d}m-G6%B0)dX zle5eqxQK7NlNcrU2GPS4!UZ&(=^{XKcJ0m_E;u}lzwWc$9$N7TEW4KJP znk09XJse~BLqd5v(4Ad{)mocvpqbNVFBbHE!xg;4Bol{em(iQ67x{W)sZrKAtQj$X zL(rmgYOR{p4$nOHjNnUacu~w4P+)d+TOY^*%2P!)nGg(HIdPfSe@9l1rl2QioQoy` zbT_#$9rZO_&AAEjh`d-+mS;7#`TpiD7Xt8FXE$1|Z3qame355QL%}$FnW)fGE$`89 zbUD*UxJJTtL`k~UQ(Pcsw`)27DnX;oW=2MXL6cq}_k-WQ1R4$N;Y;VvwEHoOevx)K z?dbo~R$(+5539x*@Cde$EX6E)KIYj=VX1yC*+y=|O#2vlk-SSjAYYK9E z0Pw-^hc|{e7a3=tz8FkN&7~G*Xo^W;N#4U6g=%pY`93@P{2PZB$amB(hF-M_I?QkOw;mM>f z87e}_YnKdzIT=eRhQ`KPtewOX4ogEQOt&w{X2#_Km~1fR}q56=9czd(3>aPL1Q3#Nozmn2z?W^NJ`%?!Yw zku!)aYOUpWW0@|Rjehfet%8jJi3GlcL=#WUZm6elTvQh$Hh`MB2$A5p#VpA1lH_!X zexY?ReTNhc7Fw4xIEw+;vuk?mBabW}#J3(p#4+ZK46X!^wc?MvfzR0xbd{1b%-I9yFO+XNQ!in}_c6^dtuwZN5^XH zM+LROYxaZ%BA{vE3{}jQ0zixP+Bwn0iG&m2m_QOu!orX4BU8-wba~*If1z8fqZe~L zRd{Zs@{l5PuT-7MG{?d=Krb1!T)!*bZ|r|k=Qdkz65|qIM}m$l*_`>aSu0lW*hb&n zD$7^8gHYy6zhb4)Yqx`I2Y3~rzHx#@sUF>(MIz zVgD;3dLRhH5Bh0zV`!OQA!I8z!cVIJiaH>4eumN62o_z@INW zI7Ob*VSdtLe!5n=L5g6tHvp30@o7_FQ@;dq;B!=-DI4IN%z*{gG8ky;plJs(8+ZfE z&$7AzPdFG(tfS*VRT2^l-)Z%d2lmKIky|CCYL#LgY}o5vF6?S zfaVg?V>EcC(x>S~zqHSo+r5l`aryYQv}4^hPh8kGnciQad_K5_8v##HPtcDq9u&&r z29ud9lv^`Lq-T)}3I*!rj`X*a)wfRT2wivi6=N6n95O`mdN9Ave(>u1*PL-_q}nid zN|n3SUn7XuJn#v3Bv8BDCiCOXtEQ2*sqJf@xb{N2?~3QIyMoHgy5`O14HYzNY$jdE z42Yosb=kQKmeHNclS8~EkzD%2-S&O2tXhTkXLc$t$iG0RsuL3T-D#hu9RPOCKN4+^ z!8`TKV#?cCa3=U1N#;};ro>EZW`s?9iJyw1sV?Wx;D7ir3zpM=Z1tM@1@JoHZ8P;2 zIfSPsp(=*@%1Tezxx}Erim@DhBF1bTSb-`yta|_WsRC04Cywg-KfOAb=*q+jr~)v| zSG9De^(z0O6D)wz^h$bO11hk|%Llk<`&^UHSnxy6u0Xvx=-_PqA{_vp!<`nZscTn! zXkKSi+?bU=wqUBs735$Z3^!gFl{7lL!77`yZQyc?7Evo2EiT#A+}t|Z;^ZVya%R)+ zTkP2euSuVond!Mjtao%Si+Bb?7g^8J+pf`T%sTp4FUjL2`l8-JO0;mF=#GgxvyIbP zC5uBN8g&j+xUA8UWlh)G-GLmb@}+P?U0vZ=qi4^XTw$ca9J2bAD1Gm9g;dagSV9g@ zA)W#L$GADhi`sWOa^@@)^=9A58Zyp6CR-w6+qjK;ST{&cF z_5-oO18rpijoz-YrWdx98~ipW6pTGlAvRR!i_Bh>>GyJS-A!DczcIbgnk7tHle6?X zWi@nxCJt!ncJdloIfvV%K>LGXD$~XCkFbf*LUS>oN^9Wr9c)9^eX3k(JWkR8_lq9F zPSr4bf~Al$uSs+?b<9ch7e;Hs$_72l5iiFW1N#~<`_yD{7+s>@fhJ+5Zs=X|kDiQG zFTMI)>}hgGS(QOg|3R;`+W)fu>3GHX$L`-9yOT^e8*M|tpQG}NZ*f|&u)En?-CQYQ zt>KD!J#%}ey%h62!3`tBl_QH{TzG!8Ei-)nXFrO!(Z}4SRpxn92 z|I%yA8qc9)>D$>_E7|fzM&Qi-&(JSoH$Hsd%<@I_Cfe;b+TfuiM1P&S)Dkk}71X8= zZnIip`rvG@u72mXXUX{*W#8kXPCIlFmmY=b=Rl36`QXoI(pQzM3nz)*%c~qVx2vnT zWHp*sZQq^pX4w51AU9{2Sy5nSrDcNAO@I=oB~fNR31uB-9f{9ZW{_}YnHBK4g|Ci< zS>?^jx4rLiW1hSgeuu|i&E3&ShmsY_aeB^Le$}Y{U+Eg|hc}f$bi@t(b4&Y2EqGix zL>>bx?*>>HeW}c*=YB_PzT59ySDKUEb5;$f3I?!{DqSjGcEKbbnP+Pa*X>jGq?T54tnK7`=6(0 zchetjWBifMZp5G!bgI<>lnzbjjfz)ToFq8t9lchj=;DuS` zWy(*z$H*;HSN6;wRn;(h*<<6E@Z^>&>Hg-`Wa3jfvGQ{#k~ZDQb<#z8oBL9(^yZtD zShF@g2fQba8=xF{ma|c5;+nEpPG@w`FNV;g`jYlPWysu{&nTih&m16A>9CKcge+)rlfx^zw7q?DL3%P9UZfD z4&6I1a^zZ%zhrnzu<>Zg^wQFpuJ1{H*Z%$~qQT%bct^k3_a*;Qe+}E1Z7L?ad&M5; z&6&}fu#iZ2-@$W(aKqHE`ai-1cWOyhh1DfyJhnJRSZa}lB@|m4CE_^Myp9nfDnl2^ zfP;i_0Bf-d3<1el`QBf4%ac0jg$^&7d-6@P!t181#nQaWSyM9_&YL)(`?`sHV%Y;m zmo*iHM~xaV=E9Lxc@+W2ta*>zc;dv94xPxZMbnkk~%kNAfP;K>v2JgbrQYT3LoZ3EJ$}Z251ZA*pL< zhBcWBx(h*QFF-RPigr7-K^n!xi#TXGIj#ka#ouVra`FyLLe;~MiNklaXbE|T@8}JS zUv$xdWMSW2ZX`F7j@-JHjwEyGK+!4tiFJDiup1RpMsE_~XO~l&0m_XZ^=Ii56N$i(cl8#n22 z$pSi%|B+ry&fyA__j+&VmnkN05&al9KAY|(L#9v1el>|Ho7tH6>rl}z4#C%&4a+)) zzX)$zgrQ{tbKDBvMJ61=CFTlzqaba-kVkg{QYcXBVOzs2MiR*LPy=N11%o_gy)09N z`aDEYjA~^L3oAs?Psfj(J=AFzD7U0FNS;TM6=t< zhUC*BWZs3FF0RTmEz>I3N!);fZAMez;%p3JhMq^^swUmwEp%A_>mgPE=Ciht(GV~o8_S+gq7V6==VD6S}~9UHAI$l)*B=Ph;@fFDeQ^iIf)E~*U{wUhDO z1bRSe<*pi{Jj#Wv+BMvphDpumdPr&gnVdz?XHB2DlzPeMvPYghxWum4aq_KYYr~=3 zbR}KlK4|FZPIub;lg12Cw(!D0`m!h3l;;L5#}&0vZ9#X|NA{OZ{62%e2X4t2-Mv$O zQG7!6nbCqVeuip+r?y(62PH^XMsl-WfuFUi!lCvk5X5N5aH3EBq4|h4GcLvxqfb)G zS^39Sl%VseCl%ON$S+c%Yh6$Lj3!in& z8QfVzly^DKHFRyDJFKg$Zz~8DbXHc7jV*5quq)1!%$Zd)xIM$q0G`A>H;gE>N$wje z*Z2dOPNn=~@-h9KriTZ-#NSd-(9|>}C)^UA*Umrn`_@$+)jQVtp?~o0nPcytupC1d z8}ENzCRKyYa2^rlrmRkd^C>r8y9vdBJE8TzgGu@wXx_c5m? zzE+I&$=4!P&=muX6B-AcxuVurF|&61m@}&#JyWO6uD0AjmR>(7(lm5n?uK)0=hAKY z^Lyy|ng>Q2%Q_lcCrm6`IEPz(Z?q4D9L7g)v zR@7ZzY%8m2CV#8H(k;a&M7@FaIdlf;K6_nwvhJ2*<*C9&!oosbPF{65y}&bK2)U~B z?x7Z)!Iv5dqgvqp)VFCpKdUp-)Bov%d@a38$c&EeII@toJxj#P=pH&=`P;`If6STgTg5ez ziZLJeJt!PG+!x`e?0!_}e@=LVj$8WBYq=T9sQ2kTyxtC<8#29z`+{z9L}o+D@~ z7;z5ELxthcC$GbMPL3YzwK4C0Y=BW|d-V+_NGC=!v3Cl91-OGmhQwT5DGwc3-?2L5 zhU%)8^17xU=$RMgWClYS1B`oGH_i&{v{P={u(nb##Le( z;qwJ_`j1~}n$p#!cfGczB$7_fSh```JVNzeP#F=GuuYa2H4_iypIQ^ zFmxH%oWfEU4}z_dpWx5M%*Ql4r<%@-lgyyiMLGACXTW7dT9gs_QQ1Jp(ddYniJ8 z(;K5W8Q+`nw^c4Oj*MT-XXmLc70=S9!dTu^{?xg84H6}lj^$%vEDyrm zEIhfX=c%$%ai`zsw6ux*afY)xndVd(rvseKTumTAX9R=vwMc}!2{t;j!r}S2>4puQ7zil2>({>%iM$gG@(n0%MrS9x z1>L$~0}Oz;n|N>kebFd+I~Y`6M3jh{27{~=R*BaU@@{9Ra!ok=&gjwPtw5mv>Wqwv z_3Lj$#Nr4vIuZGc-_QMZ-MaF&Hd?Y_!#nHO^Sa9}>z}CBfzzs0r!S*+*tieZuiuU= z`Qb2Xw{!I99jGza(V=V!262PL{B5-{QdQcqaU%D)Qj_}6flPDxY~Y2KlCk)B|%WaCEM6;BA9j%b-EzDFwj^q(;y{L)Lk z_2SarD}{@r>%!r1OZ1yuBDT?savRk1%QEY6c4r$shD-WTygvIzq&u2P7PLK#lS^`y zpI~M=Gdhr^I2y0bRkmdDWO9s%VWmnvYe)uiU$Oi*hx^~lB4gw9nJh9P)(}^EqEUWC zSot-|g;+caD9&ok{AXP0&)_m*pG1}8ae*JI)`oV+=u=tb36|uND0w1&bG-kpOnxvT zk$+>>$>d$XiEwy0F;4kvt^!KT*A469W@ zBpBl}8&t*zO$ho_i*8WOOxp;$MsAfk9x3G!%S3@J6+36g|LQ!IjvbkD6NH!gJn@#9y1_Sxs{?PTJN89$C6e{||pGG*#i1ROvZjvMqLe)Re11j@00gQY^IK@vvdM^|G)OAa2XKduSwg9Ihjek zL5|@4LWIp;j~k?<$|wgFQpzYzEdl)%slcM?zj@7t1VzB=|F?I{aKlpX?*Gj@M#p2C zjW9_@t>k@q)=*g4pAm56<>U_|W^JaQ1=nbU%WawH74;*E%cbSlI-Ny(OnxZo)+yz*azCegk!&JbSDkmR z*RKrZ(v_nnT(N9c-qILvnm(O%gDtVT{3dhnGrqWZ5$gl@j~{>5r=Na?_dQ|y^dH8L z|7yw&5TWeumwm8&nX4PGKl#v%QK5 zh8N2u8R!_0b1_4D_R$l@S_YFDJf$?Sl$iXUQA;8N=!pF8>p$(=XR3;)cZ*c_V4wbf zI!}eMqx$JX^9;U^8Hx|sSygN-V!9Qq3XUR@7!^vHgS6j%iS>G7S#|nyDKj~CpihA; zXLjz4&lp9?YFn?20Kk`}nT#Oq)(mn-O+*2AKov!cEfL=H~X=T5hDl9`=O%{kIvT z{FRPurJ<^7F4PP|08VgZhStt43IsC4wO)(O zyEv+mV>0Odm|rNW`Ez!+M#fn?B6-&Ee>N@?i^k_Db(?WLDq>K$|x8}UA& z?!aVxi@s7h?6C)iRgyE`dh4yg-o1~Gxc|Wi{=z*Prf<^ie=RMmsw}Dg$Uf{G((wJc zJ6yuR6&ZuH12?3{Jwq)`mT{4$0`m=TU)ui^nRD4I?<`*YPV!WFb^7#G6DF*hKK)>4 z=Yk0nj!&C*VQ1%YP>jdOLv+L|GuCaGO$Sb!wg9o}9srUDpj#cqsbXCK?a_iwYsC_(A=8(B ze@R2bu3FfbXB!;hKidK^eQ~B$DhOCMK4~z}y%ulofrf?~tE;y)G_2h0FATKjWVyMe ztImJ)*!C%O968b2dRcDnwz9HM)Fg|!b2V1w2E$DVD=8V!&@iA3LI%b=z8f|jZ>ur} zMp;Fn`JV=nA(o)nm=qQy5#9-YOzMU2n@0q%;u^^JJoq48xOXpo&;*9-iih9&$m%y$ zWbm)W%0@o&$h*%y_B0C5{N?ZBKyn$B(%Wc#-zwqKBmLHx+mlgVdeX?x;mx0X0@}`k ze*T*Lg|Iz|2wMQIVFRppMkf6!&4+DY!kRA)KCsZ!7$pYaEP%nP3MlHZ1GPh3CpuEZ zouJ24?m?Z&`ArWf8Fey+hzK&fs?(THYH!4V$HOQv3PTd~b>M6{VT}L=mdYttg;DGZ zn0ly#15qjJ!`sYydW&@^!;!Xdz81cQ+;2GQ7{{0U=w#@)*; zR!&$7qR(G)@k=jWvf;V@w&%(F%0W)Q&7;e37ZrTsiYq6C^9wkIp*y4?OV;Qt&hx{**A2kvJw~`tpLmbD*h(AY-mx@B;}K_f!c2_dFHwGmpuO* z&kr2fJ(I>@8hvZ&?f$y4U)HukD}0Cv9Bfmwv7tmFfkJ+e-CX4Oe~5bz@VJVrZ`^bH z-tBw0_g)w`@NS;eyD-g_??+qi%YwgCg78VuMN2-v2Z-a|qsAz(@f9TJkz zA&`Kj9e-!;O4#A${lDMyJi$bLY01GiT16@;l)1M5|2TW^hmUFB}DBugSXN|SkWyQmG^eSO78h_uZ(*OsF$~- zOcvf`0U_;|XeGirP==S%yhZY5_y`SA4;IJ>gIEaR4auuGM6zd$_SYEY(2(MJly)rt zuV_cu^KM9c;6OC`izKvgBJwp{{jzP5li!Me&>vi%d}_zzAGH1I8VI#t^z^~(Eu$2r zRmXhak5LuYoO}TH%XGD633zP$-=HgI`B4Mk9sQ=uoc;QA65D$znu3o#GmNh_fjO@D z=+!4Np7ZB{^hNnFVb@*qd~tVYYb!45Qn}w5Bfd`ts(WSX>(3tl|B3pHz$5nyQHX(D z42W0T#_fgm#F25o8TUE@oHam{?*xzRHRu|MyF3D#*GJLw@@kX#Yk^Z+n80rmW+02` zqyYFRFc7eyk*%C%sWcPJ8Rq0LB;vS5X3L!CF5x_KXfj*@;(LHD-Qe+Z2;+J{^(oXv&b8olk=kpFG9u3lT}>YL?A?quD#*G2qI%Cj`&!Vn|a1 z+zQDoKg1JZO#Uz+A;`>9UON<05fCF_-JA=Yf*Pl}kHPE?Ajx1alg*B@Z~@L@;KV6= zJLIf5FsHzkr8L}_+~0+y1bGE`Sq=mX(m1(Ds^U@rMwZ)(JU4j~lhFh>ko_wnFhuzT zFy9nOOEXpr+8c^MXgSr!#3ewMYKUWdPW*tUfoFvDZ19`%gLxDpHleL*kOXn7Ake@o zz&i=imay*1cX6lB3?h)|Fc~--=QwxLAl{=hu~vx|)hbJY%~T(ryV%NzC{9hw4-M4V zD9*r!^O}6NfM^zYMzket45z7AAsW&?hiG=e)8-g1;)^sxEEg2mX#5+9H-x0$yz+d8 z5@{~U0)iYhRHqZeqLO~qoHz1Uoy~ea+Bf;A!@()cMq#9jRhhhyn}!x>VIqf0q!`6` zSv9>%tMk=mOEo^JB0nE8TG=fEe$6TXma3%WRAQsA2%o52hY@39csI{GSe3;!Pmv$= z=2@&ZM$!3{j$6Zt0a2hT8O=yRLrtSUzzRmm$w}y@bqRmGnPwoXCIGZp^;&@y5yeJQ ztRR3(YOoIHJH3Uq%DiHWC)iM*00F`y>9hc#!mE{r7QJYwK;c4#Ay8nnxYKHz&n*gW zZ>Xg_&5H{0Qk9WW8XnRFVgaqINNJv}=fmbAdVJg<3JuCKCjKeU+1@`Hs!ugsrnblC z(10#Nl?H=brq1C6PWK$e>o{F(!9Y(}Q`F%!(vG!nXhK@Opq|_?F&0;87?n;}qni*^ zT0}qyHBtyEt%lj=A$3ZPZ(#Llos0H34Xi3;G#44*Ng!o3EgBd$WJD(F#~)~oWPCOp zm}e+bNFy)tJ~g8iR2l_W1isB~iW+`}_80^~cfUT{ZuT4XDph&;boK$UNT*U+4fPs+ zDrdX{i8>`9bt(oO=QSm3%R)Mx(W!4V&=%a&O5Lc_bG5n=R=;Ara>c zJUot=Dq$KO&2S|zEr`iB$83lW&Ie+Y46J0fl#+D?Swvb)FvuZ>9HwDE2%a+RDVRP( z;6s%nc<6A=7^pyC5TEio9mSJ^5l$P4RTu!IMt>m`krcpnN4LffK}jx6S2#%?jC=^o z01KrLn2_l-p)hpXHn&A#4lQK){vk&=oL$9DFm}xc5>NycOM;Zo?7UFNiw%)TsG&X{ zn=UFj-35v$nJ(pR&{$ z(<$uX<%3qLy==-l$RY<3%XKU=v@1Ysv zzz1Sq2-OhgPW*Skm#mmL5J2>j*)kdc3@cs`>VNSAT@D>yN`sC=B2H$b5AsOWe(A3m!uRa{rk?C9>Y&(PT{UU2vSt9EGqJj+f3UK) zAC+X^M{NbmO_5}mvn*1OA1iaRs?xG5dVa}DHE{p1uMurA>hpXqyFmq+*>K9UynuRN zrM0VUKYmHKV)RBF)W?-<%rMT!&^L*+0lwv+mxl_l`X`!HKw4m7 zg!(KnEyDzD1}VcLt15`&5#-PV<{)G5!mLlaN}kk#I>;dQmC7nA07HZ5FmMBd>Odjj zMFP|Tfl`2Z3B3#lGeSkl59gFx`3J3W{SO!GV-Rn{&uFIUPv&Iu(DiMZ83W(e;aBn< z1xkZSMR`g}9LidSf2EdE(<;*z8=rOaHieG*LOF6V{$Pq_U?$^18YKfWaf!+b5l~_} z&N(MRxB#yiQ07ZPP*Ns38_{YFQ_ko(yGC#uYmA&NB+}~Pa|NB1hIPK=P^Ptfk(m~+ zz8Y159d!6|z0a?w)q=!abrr+1`PTe)t=En5!Sg?v_3OOp&w6@9K<-uQsEAZlq;86e z(U?X)6 z*Dskg`HcH6oUKGdjKdb)WJsDp4%wtI2x`wqrAkdT8V4T6XP$@;Jeel9ZUf;L$uEI}M|2}-|#CHAFwHBr!toyA#9*_8=dC^$h zVa1y?da30K;{i{wZf-`eHtHoIQ+5Lc>$HbcRj%U9%;osyBQ#!T14kpej(xCTn=AH7>5>$nOxl0!gtWOD-%--Lbt{^w`QzSwudSSQ_%A06)$8eGhci?)r`WC( zHJ*^Qq&OP2^)zMqUomGiH`iSPOH;i`VJzo_-bXjr77tc?&{n&hPPIn!qrpknmZgKA z4DvcLzoe`dqG_X#9#QF+IDwB^#(7VEz<QtWVru+e7CSofz~Uw3J>0Yzj3Q#8vyE!GgKWl8UNHLzU&_l|z%O%CpDtQt3u< zrkIc%;-J_mwu{rmLQD1zaBh@ zGw4FUAFuN-0aT>Xf9Q<``}QrEzptwuzfghRnB36ZR6k`(eRFd|gV9(J2n6QNg|FPd z?6+1c+G(}coL1}QjT^yhY92m=d4c`zzo0#r0WwG%AcG8{*)lT77HS5yfLcLqpte)H zsY|GR)D6^O>K^J5>bKO3)N9l`)L*F2sqb<;WYqCn79mNG{?h(v@h=^PQwR+aa~F_q zElJA)KD!@mjj9=qKM_R-_c!@5m?damwf!E z@>8|VW6H`g)e~9Y62_Mc3WkOJ=>Ax}v0n=rcQOvjsS;8EEOD&hzyF0(VZ@$RwN#3b z!JcclpEroykn-1m92u=2k;mTYJte<2K(77g>;Bg^GC>V!`v2(VpZ6}gF35GzxR%V% zdrvNuyC&DkKkM9|4$BlU))-^$oZJ7Y31DOF$k^}L8DqbIC6jCMvENb&3glU8tmNmN zRr>RSvHh`oFfT-GjM-~tA-pSU(W+FcuuZK2pKP0?WfcuZgNdot8+CldNRR@SuyXc)T#B_nBAaZ#cH*N)`e_>f;AQwI%rm~22~LFY>r4qL1~Y9R5WXg z$ZAO7XNC&&6o=O?T8g=9QPk+GD6PS2 zj_7ĵ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

D;lqR_ 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ðQP5*h_K=;T6=E&zRE!rUS#ztQUdrj5taGy~lwb@RUrh*r!+! zxZ47T{-;TY_2SI~m}<%^wy&-V4Q`#{O7g?+_fna?ySpRa`Z{l`HMC3K1#iH}Kn>fTuHG?xaklCy>Y>f&TV~ItF3rYz z3>j!Wrl-B}%;dp}(LdN%!(_W*-8-oUuu_Vvwd)47)j30XbDHH|Z~qqc-+G6S#&uku z$1$bl=B)<7LTU^%QW3!lqKi{1uQt?&l^(s0KI#Kjn0Od+@UX>$AJMM?Q3mU!!f|!% zXq@zI_^d&IakIj+?rbSuF^#&ubmFMNvtY9s<%o?YF^VV~q~+A;v7^+7w1-3b_|3}v z5DUlY$V7|f@)KDIDhY3=mcV_%zd?OSzE120>UAaf<2VG1U*pB7LuQ+Y3n*eKgG091 zaY(z08HR^d1-uH>zRpBX zv7R;Z1y#i%B84R&9#?SmoN!)re1AxjSB&~9hrlQN!c6LDLtY_u$|#01GMEN@)a&Gn z#M%E+tO6B$d8`6_?J$!WFh?3JjWNnI1!m|oesxS8*sIF;Utm�anKPEziV$6k^p% z*lfur!u8yiwYS&y-hRT)gL)SB>u&98eDL1QJ41w=t0A6Ma}`8r|Mmwi^ex4ny?}|! z9v_&TgAd7{!~J0oYbP!Ksqh;Fg_KwM6KRw9O7H})40fXaJbL*^Hg@BK=di_{r_9d3 z?kO8zxN6Xln4h1dA6%ufy>zD7YwD(sgpdln_uxiUv%=KlhrM}ho{4(i0s)lWJq8P8 z0g5VvXC}w=0eJ(A>Ax1gL5*KAenZm;%MUb?5A);j;VNoZNk9-0OvnHyiDG`nx_}9F z(Y^cdveB+@mOq~#(CAAVp8ENZDG|!J!=Yv zpQpfe5#;=-w=sC5MDgb&MDUy#Qx%5;O@xIS{c7Bhi{JMrLhonSK|gC;Vx!N5qvJ6a=FXp9O@xhAChWrDvgzG8t9|TXbEI8E zT|CJaZZEP^m(1Y#mvPz!B=B#M9U*%WUg35=3tdPo3pek~UfltSi%mw0t|Tt-g?iMr z?LDbL!7QDcryyRG@~*t2#?We&+)RH;)ck@fa|!uzS^FWm1fB;=Igm3~lOhfVeTou+ zhi{uh6BVr<{$edCvQ7X9;Vh#W-*Pkf%DQMB6tO-Lt_YOc#2pqIL3j*X?pt*czG|41+l`NvD@Ry5H(kZh`>|C$d-a* zM!Aq3yY<*9x$L&10>gYH)XUJNW4EYOHxp3NU2BqZNL6fSNFAF+7^cH` z@El=1^g7E7at_B}7(Eo<5zywzm6$Y`C!l@482xhoi(@VE`+Pv&1#4lM+yVXTkTO)m zoiq!`yB5z{{Q*n3BjWkUUR*3`1+gRCMyNN9Ek847$xgfC@n50c^J~kf*FeWy30ys{ zgjA$(arNTSE-(Uaq3x2`2dp%B*8i)RJoT4v5R-@XX@Gg7WAamo&i|8`{GM+dl8?*N zH(>4|l*rUs_+Q23MPD~0j}o%MZWh3tW2+hZ4}hRbV5eD8AKsQ=gf;?H@;{lH7 zsVYBpufGcVfCx4U*`e?C$Z`$j^{DZldTT68Dq?UmP{>U|ykcoQhoh>Doq=^Ylre}~ zdRWUa&f2(If%tjc1Z2e56#uVNvbzVbz3x{m(bnSd^97~BW+8ir+qY_3)v;UE8jt}# zsg4To92tLVH~K`uQL~Cb4J-I63S9`R+M5=j7u(}l)Z1hu@izEN z07(hC71&be(g6xC1Us;?er2PLIT^DN+4%a=b&VEvAR_Qx>|B0e#?Z0!&~S02R23PR z8}VdqdtxA%Om>wJI0{ldZDer6x^TNQEr(M5BS4J{pCp&#R z1_Dic72@7l1{XzK&#Qfu_N#VRbu~{f%Z z9GTY?5&A9_34Nc)!ZYVaGBZL$B^#)}DjFL`1^{qUjr8>MUOohW&;pdMP&c(lHmr@b zK~@9(ydMxH#0T{L3=EXG+XVJTKJ1940R;4Rm9bC2GzQQtgRjyMC5_WeVsX@Ie)xfi ztfo9`M{-7?(k%8!RjXGY8qah4iH()zHVs+iFF78i0xmXzirEAaL0o|uJrg}Pv`q(d zKqk0iFO!LAC-@bLnK&~&4nWaOI;rB}#i_UkUX#gRJX7!+PoQRkf65#Sjk1fAMPgnq zFB~jvnKrAfT)9S)E4ja6V#nyJWwUl{&U06^G|cL)nbvI&P_M18uxZzqrzD-FKG~C9 z)mPuuQP+bC{g7VAO3Q8RjfBz?4IM;k-h$zP)9D`^c6;iJ`~@AokwR~=`+K+gZFRTi z*s0ukrRh%F#zyOT@1z7y6 z_8)-7r#)f+Vh8edvH1UDTM~!jGy0u;7oO=qf&q(fn7eJ8OE3o@*l7%7isj$QLqH8*iUEV?HWN4g&oE%<`LBrq1Ae|p z9GJv_VP5!*?1pxqB)KMR_g{olfXS-&a- zEN%lY0Na7afW`d;*boKQXT+BU~UpBMv-xMgU@?rn{=?bA0^S2uQ7Hg{KbHn)UwI}6gXcc03fv+l_F zqO_c$9&f5=?a*j@)2ij|(;;7@qP@ap1gp8OHdmIS)TTvy=X%C&Uv@~gZFsP3dVMji zFFi266=1L9HJftg(#GM`$iQxn8<5`&pVslO;c0*ah<%?}N4)Ys!lqFVeJyNS>~+!s z^JAmsF}NHV$HUjdro~?W5^Ng8w_zQgC*A>{VOLb&mO3YTpG>cEB@@q9(k z*)u(rW!-n*)l;^UbOdJ34A@y{C$)%W$FChfKeP9dNA}%$&;G|B-@kS5f}3w%xF@=A z(Wz65;8E!N8PGL&-^r}|7h~Zx;wxCVrq6}K(bxNsEP?C6*#BKD9QEOsVc}q|E)u`N z_9L#F__eWcZJ!5)gJ-bCeok7zE(IS~oENaPfKAlb!@^;)@WoKL_}sq_G=+7%>KkL> zP~=OoaIu$(CyD3(zrn&$=l@MC9IX3~VBTmf+&^&$y}@Th<`-b$BA*9^gY~de?~oB# zcbdoX75*J89MXIV77q45u~%UKvkC9XAamDxwO;}@;{OqR+U0)N z(~#y1@M*9{XW+U(+f#DoQ{b4df=}!C0(ct3JERk>!)rxTz&FRIRs3ss8opQTE%GR= z6X4VEz5XNkw7b6!JT3mb--j_wA|kFpjsk#P{YLmS-B*IAVSE~_<(tH#llU~oi%1(S zlV%G{%6|r*X86kRG@xB4@atmmxzYGE%t6lo5&7b$A-X&#V`L@BRn)G^F{e__Wy1iMwD-Umc%@_@9eU16d^o9st_{>-%qxPiy-E zcp9du*o(Aoq48<7WP#2~|1o?T);wPZPh%xSs0wZ;dw{OjUjg;{p>|J##hmb|KG^gT zHVuKoF(8Qi#CJ3}ogAobGCoFUsevw2Vsp_BQ^piR>x{ieZKgKG{zNFyO00P+a~#~qB_?i|dTwHU zW4V7G`T?~$_8xt?m4pIcp1#V|RkK;FFiK=?__I5!xu8nb_i>?sjl%e#im!@2m&zla zM`h>6J94719}y%(Vu$%BIAsZPu)_R+_~?L+KF6UZ;Q54Suw^-E2B*Y#PWFE~^3Ugd zr6XT=M!&*+KWA){>%d!{MCJz~#r^^^F|V+#tuQwh{p=C)?av4~e}5F=N;x#+s;4a)ygs(A0J z(?509t8WKsGoPZJN`5=1A7pSU#MGOiruQ+RoC{D*@|W1srY$XC1KPlf@4aaV8yH}~ zL{qzQrjuU$Ee@f;cczYE&p8}ml0wkeq#ArT@I73~IiUC04DKBJ0_Y5wtBzq00p9(^ z??&&>=@c0{nT5$=KvOj3(?PlJK8Md}^Oh7$n~Ur+xi<W(-)pZl`xvo8r>5*;Wtq zs7aEjQYse2)@J`d_Ra&osdD|}=Xp;?7tkFVN|VwJEro7ITPUN90;ND%El`%SL{>l$ zM8LtQWrHh_9}(ZPX*c@|r^i7=Okc(EO&d()ex7 zyCTOhUAiZ7;EU$div3QS7M!OT%YH8>p+Y=>TZCSvn=xhQW<0NyY09+1v_e&(N|_?L zExq6FZ8y&!d)v3T-+Vqq^qV-dKW7?0`?%sV*dXL!f%%&`-d&KYud*rRVcayy4kBya#TSQZ`+#sfSLwVxApBo#(gJ6Cb z>7CKPAPcm@z9x@l74$cLOCJ%(^GhCjXwE|qHI>pwJmhrOlj{qzGWs{xvdyZe_M-%3HjkOK-u!es4SWGZO2=)ik43ly}1BQT7!2NOX(S zpwtz}p;1+T^%xK$<=aGc!EJ#aozueAw zA$f>&GxlyHNO&#`@+U4pttS{zWaUh;k{FwX)2 z?Lr*C$>u?&Yp2L_DcJK_#?%xmqO)_zuvbzFZVrxuCCfc%y|1BKN^O7I-fw1z(<@u- z9Q|7&wDGHKeoU@Y3w|raE8ihl@tMR;$4s-On1jnXB{)0qfQW{8hhLq8nuDF>0=u<1 zOPj8CkJ4;YiG66xZn3MO#$`z3Kec@7BL^qioshC?Cf>(BJK&Ox0? z75!3-dCDz0Yy-Cw6ONAJ=W2mOHk7Uwk7@ZVJNG!-Gj^Uv>+(wbvS8a$m*FQzhoR}_ z))1JdsKpoAKe)y?SD=e#YFqQw8tw)>1D-;%-v$y|kf$_tz|k21?|Vj8 zG7k{S@QMBNpy|rivlL#cvGYU9O(VF8+$?S}w+5-nC1zs3HJlx93nFrXrlgM#a*>i| zJ34&&O%g(B4Kc}FLZj9857ft!H_!@1qQ{D^O(GBFA`P`rw_N+DXF*1m^s~>=K7^eT z5|Wvb$I#3Ehi*y~*@wP=daY-vQw`%rV{+>>%wAl8l*2h?9JXpWJK5we6ORj2Ib<`vil(sBxSIr|T7%{XnCoXc$FdBpb33Ei&1F zAo5MLGCC$YMV!A;5`0cfGH7P!JVXX~^{Nj--f`xhj}YElcw^$!mk8=I84R})@I zJ_+d>8#Xj3I5~PCVC!kf;zgk{2w4%)V^Q5Zp~M*g8GjDXC@d_Bk1iaR(z*Ai@o+cwXbo18!`3H>r=V5i`Z)y0 zUEwac#m=e5&NWNDYXMfq4`Xier`%6?uCSxdo`W7Tn|!P# zl^Or*;+bc>)6H_{*%s%V2SC?&sRgtRbN%!)&U2>#ns56oq`&W^%tvdXOSmgAF?|;* z^e$1KJ?V?CDDJH(_O>KNKy-!>=02tCj*DDqp@>j+w~=Laqx4M>(y zD2_#1@j^3~HjZ@i!1zUOEsiXJ!;J7fcpgclKnvcJ+%Ip5hbNwuBdde-pvyXEP zF79gB;WEnMv9PSmPQZO{huKV1A5^KJsc3ZggLci~hy|l>)37$SXA8!s^lFtFa&jHZi~WnajEi&m3DbZq1zGy<^k~DkaayJ#>6*+T5Kb zW1RuqbL)qeR8%BFXKmuTg3-ynDp#j>mBl0vs$E{3+qbXwbdQv7-F9smo*WS!KY)6j z+hVvIxkKEu+&lE&A^SKEOQ!d!?j6FWmwf_ssqUN^sjg9- zEv{?e?!~j`UG6J9f7*iUJUDawlJBbBJA2?S-$9=KZ|2W1Fxe-0Ppu+9OZFRu zbA2t?&!d0t4j6Exd;1@rwhe-D?(8uA88Z#GWWgG4Hg_qv4CgC0a@XR1aLR(7>=|z| zp_^xN(-|*;;b3=mJ*;w_4lUVmC>{$tY$*JWLEu9#5cFtZgSWH*j!=kxofMm*gg{j~ zdEo*K8hZ>ZnOSsy>H=+MuY*N*XRpaf$~5*%35Rnp?a0nl7Q~S8DdEPhFmy=4AZH-B zde-of)26;Ybwx(k=!DL(@{#M6}_w#eLV}|G1*g*u0`EA$MrMiKkikIJrU`w>cszYbha2ibfYpX{63NL8m$3eiqnD{ zxgB`UxWH@D$4GCx_ZvgX2Dy(IXaTi zTk;+p$Z!_@fCJcEp=H%p7)f{WAEZM8ZiVZ65bAA$SL44OePm-lGice=-Nv8~z*OX| zMaWwi0p*mn+*;_tn{ZX?`krcf#!F!Jv!h*0PPh(IEp)b8+}HM4N;2?}AAya4(g_F@ ziBMM)vSWm(?U)D*xUsy0j!n1%Sx58LKgZ?=MRW-sYaFAl%kTPoH2xGGG!Cn$dFubf z<%Pt=;B@QbT`m=Vip8H~K{{u^#Vkz{L2vj@Avl?IBQrZGI8H1UI)>%43>-Tg^@N(rHw;h}rYg>0%n|NP@^983RXJH?>&_+^=sR^zus?D6obtp9r6unz;8t;0bM@%0 zb1$B265Nt%W77M*^IOPbi;`Ehcfa)AxCnxV_gi-=H2o;SE(?IPKub@d(0B#)nsE83 z^HLqrf*~#F(h(+FaNg&pSW)0iCtt_o`#-jAA z;|gLjyRS}NAD`>IOK;Kxzx;atL;u^f;kF0IEL%0|fk(!!-c<4LUB$)Q2H+SMJtwXG z^5$cQ!8Y!8?%}r8_bm2UOCW4{j}45ZsPAAuw;@7?m=8}=b!(`Q{M*q{^|&SPn0VBU zjjkwpcsOyU)%c1D_LygtPy^vNcFuw=sYFP_Fn0##+Qgodh>bU0TH~*BcwAi?-4Kr-JSBNEt$;Lg(&jOg`k!<|pXB~FH9qMfvP3Xv zd)>0h?g{h0g|QOuGDbSwPv$XT{ZVmm=!q!ikz+PLS4+Q>EG`$bVanJv@MN5+a7E)| zM%LmlsT7xS%GpkFp{cIswEtKox5V5y%xRLGteWw4-!^Cbe}0h|D;jeSoBb;#-7KSsJ+g^zQ-k~zplbb zu?^_vb`5&^)}yo1KD>s!o!ieHM9;2=xyNu_dWL%*oqms_*Y64LJ?;aXq5TK<1@;%6 z<<4>c;eO@*~Z4?n@L@CW<_dIBVaWRbokj}(z&GMEe_ zBgq&to=hZ@$#gP{%q5qS#pH6bl3YR7lTGAWvW?V}-DDrRmE1}0A@`97$)n^6@-)3R z&M(YOQYN7>XntWD`<_&YGsJis{{sJXfuzEG{28MH3+1N!=wovM@5Dd(^b7k#pT#z3 z^(?%PZixO79x8sv=hElkhNVx-ZNAUW(_DC#{UUZEcV|A?O;m+p}y(;hJWh43-(enyurzM{o*#m^vq zx7c&$&C^5W_-T?|V1AaXZz`(!lhWrp*~8||OWu^lDbRfW(6>zgMA7raYVV0>A>H5f zw(uPvr2JC1pi@enx~^2Gn~dL*9p(4RM!P3sEZT|{;9B%lH`iZtDC;S2g;`FbWQd2M*TGP%5-&Ecz9Dr zMwg1|gO80>*D2%G1u~gDQ8jX8cDb%Drm+qmDSuX{Ys{uM&Q-=ONQf(x%g?{lE25%y zi2Uw{WO-TL__&@~vO5)1CnY8i9xcCDmY$;GRXtK=wr=FlxCn^@R4_YU$Fnr|F ztH8V=>e>i(uk-iH3kwrQFQ}C3@jW*V)#>C1=?(lix@`4Gl}g@OR!|UMQKuYxQg}vF zcz9V^dd13VvfcQ=rh_`2Y$&~Px>|jCvbrQJjDJ*ohHgiq78Tz&Aii(_} zy19{jSKOe1=yc>;dLyqwJ)i9lQmH5^N~ov{t~^z$p4v&BX84AkBk&tO%s5N8R0-G9 zTf0{hU0K}p@VJa$gBI6?R(0*K-7lOWk7vhJ)Rk|?2bJk`J@H%kf=)U`Y=dT_PB*4O z+mNNxZJn&EQTIEPrR}s(J8E27dH1`}@sxY8L9V!^iD>~H4$rVkd_vPMyb-lv zYi(vlMHhX!_;?rI!$;$m)<)*=mua=N+TxI%+O_)Acy4`#`%I6&63BfuSJu$aZ#b${ z4%MXSV(K!+EUpR9CJ}fNEYOTH;*o$O4VvrN4c(6Toi&w9Yoc87}?wgcfvg;vUviYg6H5RMmBH5Nq8SVf=}R|jA{Oa^I#zOn~Q{y z&Lon=k~k7iv}naEP)?F5$aGEK| zPl{G0(NU0GMNBUJiZdYIgBPLQB8|QT+F_^VFcp$+-Z64W8P66J2=|F#BYqdQDv+W1 zSrCmQRP^)F*T&3)SpPy2$U~hlHU|4HQ_Qzi^cIz9OQE2nAj|08vTx;kl*&Ddf6I3( z&!6`UP429%Tpbai*sWBiAIPW~I2`u12ZW3>9xu`$+b>&l^~SBEcgde?HFc1qD@qDr z*rD5Zly9i#Q+w;*Z)?VY1F!K1?mxJl-?=bZZFs_mI6*F-C7-+)uBepfEQDvLk@&Ll zG5`gw7v6Y5amHds8mlFwu@gz-Cb$(zL(otj1xwO+7fvB*d5|1GzOo8Es1$R%?8_5{96Kq_R*ofuyJ$ zl*%)d@>kmEh>Wq?k}=-(j4@u2uJ*8|Jpg0aCdK-ZA-!XRF)8NiprBi1%hpE8!!|2_ z_cd!A4g$>IE|H-!Ii!+)h7etf~oL>Kd>b`sL+p0PIQs2Fhv0k@EXPn_n*w|=| z8GHSNF@m8pL_!S2F+C#{dYbf%0jB!QC>RS9U^40%GhjB%hlOw%EC)&(8{lfV2DUlT zGS0#|_z%`-{)8s@AK{6Tgpw|(XT+eMkwAKoRMM05COM>#3?PHZP%;Abj7qH8Od&Or zn$0S*sbdocEy|m(^1WDmkrX2AXbv`|3U`ozZkO^3Ed~kSZR!{)0$O0}mKEIixTXSm zsZ_y;LIneTk2^?#WxxwJj<{N>)NbqX$lZ;^BaS%XHaHmP*};I1+^(A#x>3nRNfLYv z5c1EK&572KAa%mbyqt?!o@hL`@3?|p;2)M<-D+Jzs9d;F>Ts~^*ST8-Idt&g_UjKF zGEVRzFTj82ER$_FelIWXhkAs+^lvC26uKZS(CSA5B(ZLeY{-Qo7zl%4DC_2^gn6(4 zmYAv^SHecv0$ZUTcETRiByNZOaM0Y>@iaV-eI3WKuj4Iv7d}8v_znKcGzpOtqKJy9 zNq5vFL{2Cm{Yfbqf}Bu+oG`(P6IPNngytsUQqxiUH!%5$Rw8nv`5d~CdZ$MNaI08i zNK~dK#m3~|*dG>E*yh`<%;64k_?Q?v3M4F$881;Nc8A^GIL;$NFr1MaXDF4hQ*res z*}}yu=E!b_58S4PRy0*yX@@gj^f4`5)PNAWV(Xq6Tdq88yhnbf)qnuiD=B2GR1TJWMflNT_6c58= za1=x)c*ET5`6+fpoFN>MksziWM3b(p6M|MLdYQEY$^^6*f@%j-NiC_ve$NGD30X!~ zv3}31$u(pvRw|eSZO5ep99YNRPS)9p&729~%|=dpS^{lQD8$QW!HzSZ8gwE@m<%{2 zs(?+U!QMt4iwUeL!oh0_3Ix736(@X*3z(R$P<9k%Sa|??ejF2fn`}g%zFoV$Or~TG zK7Ya^da$W47^{sx&8+G+e(cp_w{#l&%dBD1BStP7wYhcS(*9ly0yh~di=TWX@#cMIAKd~fZfMby7&QoQU`|=)f|n`sf#j_ z3!?h*?|8A}(#~rb&iYb%_^|3w#;K0N6{t7Rx&-aBq;(0ZH*_N!tV_^7OInwpdV|>?ST*&daQWfq;EZ~7P+E@suCafD4-KwjEv@qw>duACWk-?CkMHeD4vPq|#bvCV54 z)*~dw**?St|KWrX>~4rMbvFoJ6|}pd(A3>9!sJyU^gu3yRd5BYH}yc?0K2f3aWmF3 z?uL8eep4-j`c}LGufZEYr#nA1yH%XWsf*^x&KOqDNR&)=QpG{2W{hQ17t<}KF4lBx zPEf>zV=E-p_F2G}H+vicWMJ~j=V`v3v7tCz9r~{YDgNs^l6<8}4J}x)} z)B=zAzz1r6w)^m>d9g#(=OMIr2`OU`T)!+Kd%02IYXS>HGBBwFBm) zUa@xVlR3%LSN%AA!L3o5Q}ye8DIoZZUxRD2lrx#nr7%aSgS9wEN&TU=;f%yC*vDKO zsD|(WJOYoylkhCOh#JCi)DTW!rQsX+4t{{2(eaYHLs933U=qgq7Mgn^1&$E976gu{ zBGu^JFoVn{^T|SVheBtl4$cp4G)+mzB&5R^?N(N>X+z~-MFopF%4RR9jzSM}B@>nB zKlO|lHi&~4q;Gr338Ssdrq22r5_rilWz%ye8>oxA=-`#9N|H!Bx;(vwyl@IT6hwz?4{Ht`-G2eA4mibt&hykp(iAR(KX|kH5I_go{lexk ztZc2DJtR8{EfmX*f8JD*E8nY(Oqf{RJwnk~A3z2VjH%(p4nPU|$me$$U#J~(SN)Su zT|YOwVszG}ci!beO`_xSg2@|_dO`Y0U4r``@SuPY=GH`sfjUoTL0^`nQ37Sy%{Wrh z%~)r0VOS12>{6gE4A(g6P53+fgJo(6eofSep~;3fWD-fkIz=DM*eE0eO@2+(he7ab zsw0a!w5~vB6&?C5Sb7|>Zcn!6S>-#J<`Q#h?t}CBN1J1l%r2n|YjZ?Q5v`$B*5=SI zdNWzQ2_F|6Ivfrhcd+&wUJ@LRMmi6)usJE|FsQ<~_Lc$h$%9j5JC(}R+tbdhA5awj zqaZoB5ngf9$9e%Du`08>ELpbuVwMQv?6n?zBl;P2bCyA2YYNg~z{Hs&+H?Y&S24e* z+t>7sS<%C|*iE~oG#|mYnuAv|~ZRk!gc}RTesmC=ecMygu~gm1)_beJW4Bzp(LX2Uy^o1`IdbnSOxV z+oPy3+s><>@-Zx2%#6?&vV6g=rxyjy3w!DKE%LAJ5rfmPDtopC-?#PPg1r~^rC);o zXaqR9&__Y_=06^p2J>)P9%cOH&RDNF^P(50it)apaoF>C85_~30Y2bWG&fX%G9CSMLzn0G4N0H> z(!$a)jeP<@5y-@JClj-A)~1n#!8MWfi>E}y^&K8ExAWGyX`O21!S)3hk&-ShR8amndP;?$}WjiW)l?_2>W3GqN}xGJ4Ry8M9yC*MC4{|DtJq zdoP<&3ir2%OtD1Lw)wh`r3v`Er0?;f)+xO1u>iBKh^(5t_r@2yToE;(pk~HeI_mTE9_={h(6005I=(a$688le!5WJc4%-&2?vl!2kB$k;f9yCef zdd3wr-SRe;ByqpVnW@<|`fW^-cppx))T!TC!ew*nR1{0m?k-84DnR9;l%-BpV8W$! z>eNbfj^5m{d4h#9DbosR*P|El!9hD^JK_U_7|{tzi0<|`h@jC^G(5Y55QOaK9SVL_ z!lOgvja8l@!>`sIl<;1A@Pfb1N_2?E$MZLTK68APw(y$gw``K{Yqj>k*VyQvYNNH|{tUmP ze(H zxkjAApb6KMA?TE4Z^$uCSq@2PzDri8``VaHI2VpQYkl_Z4{C%|Rioa9Xi5QeWxKTs}x`Q~?yKSTxECR?^m zmRtBLpEr1f3wEb5Wr?=B9p1%j`bJ${j4oR}Ql*l2mK7AlSJWxTwj%br)YkTk1IW;3 z&X4DBwhmm7KiBFih21I5rY+Vz4o_j$=|-KRp{C!IDe;Y;$>n9*9y--bVGFVoQBjqe zzV+HAO*)-UR*A8eO}p?$)Pk+GnH3dX^yT8?TQ*NL_^_V=dl+{6N9MJ}lY!<*(Fsi< zPfB2eOMrSj3Z6~E%*8gBDB-`*$h;jDgidNkkQgF(M5p4UW^eR}&Lc&nm<%SvO!Wxr z?KqQpL{o3a6=XFLJfg27+eri2+oAgxti%bn=bYUF!fC5Go5ysLVnOAeBeX&1&2U%Ww-7tN&d(f2n*R3rb>DZ za*BdO1{J~a6ovl3VPX8EW+(~3M4Q2cwb29lwdjepYx6JZGkZ)$&7cwUtE-bo7UDg5 z3o<6G_^$kt>gtS1Y13AGUw-JOsrr{)7A5RrMq_g8sY3t5)O#Kqwcw*6t5Wx#SX%Mu zfhpm&&w{FW+^&Nq10K9-_+`eK<>a6QJ$&g7!EyfLE8PhqQIUui32{YI+Sth4wD0Q$Ki{g;8&c)potaDsh6=Nj>HqK*+qgT zUzLzDQcgzWBt{jPOe|9`S7U0$)nt2zRx>VkzQFVhG^U85%h7MJ>nRIbs14Z>p>T|hLIiOm;R6e%GxXp(}5KEHqmst(h`Yvg=#9GOuhLC!7 z2b<>|h?t0HkkaG^?PQyR1?B05sjDGed^Iv(?SYi03 zdV2k}lO{~2U#nn9d&m_Lc0LLu`j{?cT{HSrjmb*ynO#-UJ3Y{s=}uoJ>De&nk{Q#l zYnXHC%;|<+9a6FQy}G1)MCo|;)o*;VWe^OV&^;oWd1VV-nk}<1gx<_$u*y6aO8YN% zVNK-b7FigSFaCutm*2p@F<1y@m$?B3oA4PkK5vE`a`9rPl#cbIb={;L1 zL((yoE`C6t)WIP|HAf?K>Y|L~f~bDR&Nh2AecCADFLR%+aRE)zbiGd^zy-omDjiQ? zZIo86m2}Z{vf2qF2W`OxPFHKmtb{k{fWd`h2h0E$;F?AZI-UL;-r%$6`k-#%B`)Y} zZ`n@u9vjGp;$AW?vOyq*Cj(qiTdU>UuSnqqF4%2z?KXHfAfp9+khfaQZupc7{BKS} zNZZyx5~M*U^s&g*ng%mK%*~{#0rj%J+T=O;5FCah@Dw};FTtzuH+UOPqJPxKEFI(j zP&N1tRfC_IY9Q1og0V)?1yi=VvL1(IrW*7m`J_MVaS&Alp++GjWi0K`YJn9@Fj7$A zD(iUg!Ub=KmU9oTuK$jm8^p_h`;C`uP$NJAzNdf|BN)XFMk?g1B_~~1_WqYGV+kwCI9Z38htaXHH z5Y+?f`LJBlbFh_pKHP{s2e&h4C?RL_sL2uPW1PPHCptoX1!tLp@H?Hw#J+=OFKQKY zf1o+tq8ByoaumEDDzWc?W^LAyW-scsWJ`ypidHHKt{EzzB4I%ctydSU`UIFLAs~Wf z9)(bR5&IczqJ`C_oD^ZqL?U^bP?#}wOnB%8)!|&I-IEtSb7rK_nCiLaVC@M-&RWlK zK7_JN(?P5x)3SptkT~gUbQsnF1}t6I9M0eD+&Dr0pr<;7wK0^j=Zca771XZ9XOlS+I#943AUChKsp&(db3>q5Yj2 z&RDuk7~k-r@L-~dJnGZHaW}Q0&*YQbv)qf^G46HlEp(zj#eKwm%Kel3k~vZT!2Qhq z&i%#dfdd|t5Xxp9grv-YFbIahNEn0Zn3I_5FdJQ{*RkB~>tH)Hu;i;-(TDmTxR0e{ zJ^|E+TIgGNi=|_J48NH?pLn7m!K4$3#AHl0o2L|9CWRVCF)9sW>Xo20&|ZZ_%wkQxrqy{zvU5g?>nSE0s4}SUc`}FkiacQVgP~bF-=HV1A%@pC}}V-`yLGaMEUJ-l$~H z*?TKa_MEM!wT()l%0u*siB`pyLIF>2`)}N)@hRi+BArs1wm<#Th4qFEymXgM*Xf$- z>5ckn>Xqs0vheVxjEpW7(+3|LtFBYVs|#c@d7^6M$n0|Tj&7{ON6MeYIHYWP<6LFj zf`qt2x%~V)y&@`VhqO>K^s$>1>qmz4jt%B{xH>547TL13QSz|ON?C6QEfgRK81ieZ zNaSqmn<%UW4cTsd|F}DTnpxFt{Mf6i0%S(`} zLUtwH-CZS%?$UMNB!5&oVCHK)wL9QpEeBku@4^;Qvd*4>g;g|aE zg^cyOHM*wfoETO%ePAh37>2dF3&UFdptjghX0wr#JZj%9+m|AUe14@oXCXX0jl`FY zmjNh@T}87g-+M&-Mzb|IBTj8nCrhmzw3c$lR6U`c8PSsd%UPuo#qB`2dpz&(vL~dxd)q)4ATkbgmD%Pq@!9o$CzhAi`{C1jI<_ArJaNG3p_-uX7?y zfm-wYhTt(JWPsm+O30(8$qg}yOQ@kV`#l(#3KC%^hD?c@)M)1LFc}jzgmjIyWCPhu zgmew6d)!LyAa|4dQ2Th4Jc0VhbL3@moV-a+kW=JC@(KBzd`Z40|0X|>pULm!FJkaF zeFQLI8;zO@V{>x~sUDJIxx@?C;^M`<_+8qHU`yu!tMN$D-3Us2fQN0J>m05VO*s|nRtO_Ue@m3sV6Hn}Ijt;q4w3L`OCwWDS zrbmpwRg;zBEue+A;HOG=6(s3Dnwz!oSO*f3|-1XWbr>wW}8{35C>EHW0R?m2l4hfpVdmLr7zcEoW;)D*xo|0_kccz0yResqcD?S0gRGb3ajZi<1JA?D z@EW`cC$N|0Lzah1eZhe_ZAoWlX~%0O5uLUMks)L_dTos(6U@%wmy*RM&4xOIQ_ZHH z>>@XkTTDE50ac<*t(O8wNLO}b3#=1KT{LN-ih60bI(VHf>MbxTP>;eLYU+FyflhiH z+8T+h2XOqn64mqtiY0bxPy9zWb<^~iX*S@zYf7(f`g-paq?-P+SCFc3LPv-}fr8ZI z*)bJ$<=gStmFaXn@mu(UPCEHE^aiC4p?$`qL+EyYEJn$+cI!_~hZUPNNrFk|jGF7- zofS5`H-9!zm9oxo^<`B`_L*QJI;j+`L=86y6{)u7nd96WOhcjx@2>I;Wf`g?)i9P3 zs!yw68`Ps~=suRCb~oG$f=Bt2Oi?<mMYq4u#_Sfz&R!jbT(SZiNhQhiGxZM z;}V+OlE%0sSJDGKigAgB3oBVc)?m`?M&y#~$qurU>?Jpo+i|k#Ai1ABLY}@5*@U8p zC4PX*`iP`b;^pcn{57Dzv|4}h>yai$g(Mte;uks-Mw(80Va~~sAuPG31`#+|qIO3Kp$bkaZg-n@c6pRJREYn~n%z;agT{gmIxDMx+T6(#hfaY9V z>i+gU`~<(kpXLtaWD8y?A!Tf4N$h`_!93p<5;1E|=tvfLEMW(4@t-7F@a^M_e%q_Fdhz0Ng%CzMC zyre?bb&bn3jmgFKo&=zuj+LFne&iM(*k+Hh`L(T{b}^1^`#}%!E6&(Nzsa-4V%Mg& z$&>HC6?WOlH^Y{I{(9^FVMk=+bWXdecm3kMT_E~&SV^Cu%k%q&q|bk8Vd)r|!tIXX zi)en^ALzy7 z$oj=oBI5cE51HF}>)fq{-3(tP`$yP@i6KY3UT152wa2e&qRi<;3eRyKGi4 zjEcJT#l~McF6r=RN8Lnr;i!3lcUr|eZwGkCzj%iZVyC6%ho`6%N^y`qAn!Ql9qPmv zP5lT0%+n6cQ@XzHvH|(9Fyyp4qiWKn{2u}8q13Zxao@z*nQ^KB_q3tZv#)OBxbkJN zEn=GL`QA$!f9|;8L+)Lh{I?`6$NA2y?^lyYA2D_4L_#c3x4uN|(CG=iS+%P_l)@0x zloYLUEr!csC9Fmd;;UG-Yddr6yNS(99RRBLyoJ-=r%ZYePDsJO;T)@Wc`HY;!QY6- zM*Yd-s#R)IY)npJOezykigB)n&VUPbuonG2R(kW{)zV56~Xpfvm@uW+u*tdFb;?GZS@i1*}K+m}_7w zPFxG#ebhbXUN{bKV8!Spe1IOmpJDdV8Tc0aWqyR8;dc<+V`yF?ok61YqwY9^M7w8t zN!(+E6oV>OJ*p)%#X#CSvkBe&c9GjI4vMh5inm`YZ-67b#t~8e9ZBG%f0u+?t?`EJ zHg4AqP=%9mXjzPDnCN@IA1Z3mrE9FTT=Jo;?K*M;cwuXHNM6lmhYXv=5u$eB40nB8 za-k3gm|TG?pb}G(rb_sN&NvH>E;q77fxFp^^CK)#;92HJ_l1csSU)Z_f)U;6n)#wD ziDxs;86=DJMMsw+mM9?hr%WVMSRS*$7t4sw!J7um>xFdI?(c3B@8))Ip$LqE_p#OY2lIwe)504eOB4EU1Kh8z`8dahoBpLOoxt z&O?LDN<~poLPcG0<*8Ej)K2O&!#63R6_bnMH|p)f(DK>Xs%;7bggBH_Nf83i&%{w`!kUrwy;FR2H0bA*Qbeyf*Z=y=!WtNR!jZ{Z{vK;kMIlp z0e^vB;)W89j4q;5L%ZZOb_Xd zeIGRGnf868kaSE9qIDV3X@7)75t+pl5t{TY)Mc(F&2<^kY5$=B(Fpb-ZAdgqD|oTW zpKLJ?+i$Arf-#?xbRE%;pA}fHmdN z5Txe3X^~XS_lXc9eh8?D5#@#%821X zDXV}nzVS1;yiD6erhBmE1;d3%8Z4=XTBIURsV|CW~3oQP$8R?jP~HbU&H51{fdF$$ulYM61gam2W-nejwY?D_dmfVy5en}#X^&WW~bH6Sl-|EbB_WYCX2p=X3 zSjA$Jm#40%lUJN@+xWw{=#~NT$%9j5JC(}R+tbdhA5avocYD^>@S4nci9)eE>~{EW zuzGqIb%uVex}qXysBUf~-xW#EKy*6tExpm!T_nV?Dzm#RS+={aWe(#nD-s77z9FOg zCmNo>YjW?fhn?OOe5Je9t}-J!?J2y$P| zl{MPZ(l%->(M^*NTt}#vndkhmio1f_;6whP$;S_yQ+H^e37yKNsXKp%e*m4Dp(@8u z7dn5KSH=87Ig*GxMF2uf5RS6^p6XVL7JX20|WnizbR8ZkU4De%YuuGK{j#MFb>(x?c;9YZs+!+ z4st*DF!vaD6q9rW9pp{!1os|G()kDX1@{&62BEpc!qg0p-XKEPD@`P}bOoWw#8eTP z$`p~gaH+{*Yz=b<*@AgG)M4xqcpRQ&j`FXV^bqPW_IKtFLi2Q_4r4T>Ja6m@8rBwK(C;;#i3vLwCaVp< zJIM<6M7glrfnlt3Q^4?LBL>-6DbHC5&rT!pW#eT43a@Dw?sLOAjxvMLsX-@gyMY#x zkr;YGHsmrNu|Y5tYbax2JoEXefmy7EvcxoLdnG3DQJ;@H;U4r6qd7E`8lGiyGOu9{ zjp$4I59TU%mdIHSO*r;t2tHydY`(S+X(k0Bhh`G?We8I;3nUqQn@s(rd&nIZIXB>X zZ>a#7muzcbam9|Tt!gDU5{2dLo!bTrD++Lt9UQxwS`}aXhAb|`zEN8=(O8EZK^_qG z0DikYqLBBuvLb8b+onxq$KSe{0X9@+B9aHJz2lPoD98y$sG(x8? z%1ADV>SrwUrAnbS8G3j-+t-;!Vc)kj88RTt!mpGn6XRLFuRY&V${BPT_ff_f&tWg< zt0q^E_d)Ch{hFy0KcgNINn#mih&>wA$D<$1yBkJE65697a)yw0w;ZQ&r9B#Z2+jJv zk34pf(S}WCNHJF!oGhXyiNcPTxx@uEfE^~|Hd{J~uooFEq2HLPpkBf zd?j!EB|hGzRNerWc-0(4G(64qz2BHvLtz;$;K*0Kxq`Bcb&^sQL~sq+$<&bB;V$eX z6;gSRu=(1T&D|UyFi!aw`m0m_(?8H(y?M?y66+_{{-^y(2^m7lamscqrqxX*)2-5Z zH(g|G(mW075{6(GB{pLwqMNBXy@jZW#O^iK7O$Rpn zSlZwp^hq;@w+km!+OS_WX$-T}8sukMt!LQkfDk%36`4xq5KZz%O9w$AZ5J+w%@@$N)=|@icAIj7Wv#|phlJ;BV4q7`dY8Mfi);Z(+vpP)RawM-dxhNl zobZWxx&C`w(t+{+Z1Bi7(@d+6IRV}daDpQ%^INTqE~HZ$_1=hmwLN`m3rBVeBdk7c zv+(|Gi2MB&maNc;xsckDiE|?Vh95Aq?Kkwmkb06vVHHHp+{~#bX*Tv+6k!cyFrhV&3R4ZFmei4X zCJzi9*-Yv?xT+wL6O!1NCKc!<++o(?-S5f-7FREJcgE5UOJ*j^@=jr>l6A&%P3g><5ueoB!Ay=1?%iQY{+ao zeh^TB9JMUT=ui5vTLS(&XPIof@q2l3zfVnt32)7pddd(XSeb}2d71YxcWM@5;;Yc9 zIU2?>w~HF|GN`Z3mp_sz*8V}P*6W}As6LSlap3+@~hafNi$JIC#_6Oex(ke zC4?rTjKs=>ko+q3HSh2YA%wK1=I0fnQ-q4G^WFgnu^qt5$;y!?VA)<^ZuDRs26i@w zTQ`Bo1y__vqXeO#V;vonAvRY}ufKNEgvs=4)%oWdXhd*M}Kq z);sR@F+(U4Gx}7G$x82;T~*OL-7vW$h!9^k%(-O7^y?btTsm{QY-6hlLa?y1tV-CB z3uI$1!a@c7UR_c?qI5j_`k~i-D%yu5W&xcKT?B$F=Q{Ker+p6FQIFWoe8lg<4F^zQt zY(iIwgRCR+Ff?~W()rN$-~%|#{H?x%Z-s6LLgzyT-)@?~+LP5R3Q2P(sgN49&$5IZtN!q$^GPE@)$Wvo*^%iH^_(NpZ-?aVDpV)IUpulX{{6p z%cgiSE`FC@lsj&WaGcB`bXJ)30Vl}fZqY*^7cpX<8fH?n%w|h&aQ4=`SQnv=8il9_ zV6$jy8j3RbUa)7h2NemZHe+kInt8=aA#u`|YU^oi&xvInuMK-h-7CP4DkbE!)JsCY z$Sc9GhW?ur>qmz4jt%B{xH>547TL13QSz|ON<(iH9qhMaxD`}l^6QjeV_n7&{X3hM z*?AZI+uorX<5PC#PxU`xw8T*Ss7ia|XzUEHl1EXZY1)P7VAO)GwV4$aUG(LmQMoU>JmmAO z%ruN$g@TQ@>orHvdFusqAb-{5K>h)D8Xd_0g%0H3aNjWp@;|u0I6ZS97YV5=#3Lc4 z;GC=Iz*P)`K_H|m%&8aVMs5Mx0ZQFJ9|37s$6Kr>L)||=hc7{xYW)xV#$2`p_l#tg zGTVnaj}IWFWGE5bGpNtjbmpHS%(c>#*>z+iq51UnWEa^>ZXtJ&yUD%e0rCi@%sxq; zB`=XT{f>;1=wfXSj>Q`>NqMCZnQsn?&5e%oHZGYaGQCDbu9}NBk9pdh&XsbL6^~Hh z5ZS`?w%zJ5y%^ER_kE`1fHNy`{as!GBj(WI4gTTfPT%p@yH-0zB%2kZ#-Z+3hb~MI z@`v16{RZcTh_1!Dl0!!i_#OU&CiowbksuOgQi-~e1k!_X z5bbO&Bm>AGK1qye~?6_!A}$V z6}PR!sPW%W(knA6b{lBaYE4Keehm%X4#hotMX3%zOmg24O<*QN%+eaADH*5l@R0PX#uQI z*D2%G1=0zbGXn;#>=xEN>amBqb`Mjp96X?Di1!LSvh~6?n_r`8lS|q2hBKKOO|L9f z_fg4FQG1h<4qQI+^}?)NdS^y#Oi6O**o|wGdW_YEM5(%^6t5bPm9L-euH+Gjhi$Wn zwjb}>b?3C0?qN|!(PFavXk1wL=&Ad6@xObgXmfrH+uU8|7cm|&K6^Ay(aCBc*Cvd- zqRGtE=v}>|gXW|zCpRV~D?*oDH*j7;j{#onlJ#ZHhTG+5 zdPQfP%cVr3vGP5qGr2-`>~!-q%~GITBKEXwhXzSLz0e8!IGfCU5sslVDeZ7+b|(D> zCv+*bP#r^@(Cviv96FzyKoU(!vec!N_PWs7n$ZEZg5rV10K`V*YcQk*Lt!wef@vd% zd3lyvXX0O&@3+Sn&5ONT<&);8Ia%xjj8HMP)CbZ-g|*mVte9;qFc!>)x7inX8-Hv% zmlahpbJ&JwLLcaAE^>GhapJxsm#>3NF6`J6Hv|PM0Rg_SI1||Ez<*h&RjtrW{4(;S zx%S{ea*43;yPmJ=)76>t+&bqTR~m6zJniG zx_is|LU;4J+*h(Lk21}?P7kcl&evxztjQ*=0Ctp`Z8lz+->)Vlv`cqocueGmjG`e^ zre^S#&-z`3Xh83dYg9Ykg}=chmc0k zb3c5#Y6q^V1J~3}3fs2I0qnk(*VKs-NfDS`8j%};`K4Z1ziE_3Gbp%mDel6voP26q zFBZ_25m!@PPcWku14MOP?omS zLhYz=Y31GL>U1H$=V+pqYAWM*YIndaoz7T(ovHzD#Ty}`7SwdgB2n3z@Wq-j;=>IO zvk?G)E6!V8OY?J0=YZjJ{{Zg@^Mr-6TnbDN*R7eLd%sY^BWouu7)Gz~n2H0#!-pgskq+SJIF~CqQ%2w1^gvz2g&Q&t`$Xf^d>=1utj zy6sOBlkK94%bwVyRPIqW?)JzK%Gjr+vjX(<((q$}XcA$-aXP=9c9f-b0PBYHHy=89 zaQpR#4#^*Cwf&PVCk3wrRY?3Gc<|4`L}Id^zOXnz@N5v$Dg*~=>fLY+dOqzy2kIMf zLQ$x0zW@OaaHmbqh1#!hP1*(=;3c0YM>t_FuZ!!y_+224=1ea?Y{w6908wmDRJORH z|7(fEt9WCcxK_iuR;~WVWi~+KwFq^u3#bR5^be50mpdk|s8JOvokB59xQ!J#Jqz zHGrm2{7%&X!bxWJRIJM_NiwT95NGv-B<@O<#4SysSVh(b6ag zQw3=MJI$pM60d2l#9`+Dd;(5k9+fxi18d2a01tSb2e6MWP4u3Obv|70b`jr}1*B;Z z??R{$&{4yV8bu}hSSS}P^{iY_Bgvrp#5*uR=j6yiD~m?h@2B2~vMtgGs#|?;ix|_^ z)<53Bgtp0^G7hu!Rn$|G^e;n08_`of)D6Dle&l}P{@~7|vmxh@6{bK9&hFF1Dc5xa z+I>%*4e3IT=kViQsZ5^bag1my?xb z4Y?9s4zK9|oFFL^*pk{=Ey2oIh~3w+)6crQ&rOZM!MuKRGk{yzaPim?UQ`kS>A`S- zzY|BMv*|$oxyrZ&32}vT`T2KxMO4%dajS`6ey1J5ps~)Sar{>J3Epn!6%9cFH9@D- zf3`McXKit9t(M;?G#+{h6Z9KQeFTPX=(H?9fTs`CZLCQiGDNAEnx~0brm1q#Di8

Y-EX2 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 AoB#j- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml deleted file mode 100644 index 1cba8c42..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/resources/themes/junit-pdf-theme.yml +++ /dev/null @@ -1,6 +0,0 @@ -extends: default -font: - catalog: - merge: true - Symbola: Symbola.ttf - fallbacks: [Symbola] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css deleted file mode 100644 index 3fd131d8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/styles.css +++ /dev/null @@ -1 +0,0 @@ -.transition--300{transition:all 300ms ease-in-out}.toc{height:100%;width:280px;transform:translateX(0)}.content h1:first-child,.content h2:first-child{padding-top:0;margin-top:0}.title{font-size:3em}.content{margin-bottom:95vh}.content ul,.content ol{list-style:inherit}.content a{color:#0977c3;text-decoration:none;border-bottom:1px solid #EEE;transition:all 300ms ease}.content a.no-decoration{border-bottom:0}.content a:hover{border-bottom:1px solid #0977c3}.content a:hover.no-decoration{border-bottom:0}a.toc-link{text-decoration:none}.try-it-container{transform:translateY(84%)}.try-it-container.is-open{transform:translateY(0%)}.page-content{display:block !important}.hljs{display:block;background:white;padding:0.5em;color:#333333;overflow-x:auto}.hljs-comment,.hljs-meta{color:#969896}.hljs-string,.hljs-variable,.hljs-template-variable,.hljs-strong,.hljs-emphasis,.hljs-quote{color:#df5000}.hljs-keyword,.hljs-selector-tag,.hljs-type{color:#a71d5d}.hljs-literal,.hljs-symbol,.hljs-bullet,.hljs-attribute{color:#0086b3}.hljs-section,.hljs-name{color:#63a35c}.hljs-tag{color:#333333}.hljs-title,.hljs-attr,.hljs-selector-id,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo{color:#795da3}.hljs-addition{color:#55a532;background-color:#eaffea}.hljs-deletion{color:#bd2c00;background-color:#ffecec}.hljs-link{text-decoration:underline}.toc-icon{position:fixed;top:0;right:0}#toc:checked ~ .toc{box-shadow:0 0 5px #c8c8c8;transform:translateX(0)}.toc{background-color:rgba(255,255,255,0.9);transform:translateX(-100%)}.toc.toc-right{transform:translateX(100%);right:0}@media (min-width: 52em){.toc{transform:translateX(0)}.toc.toc-right{transform:translateX(0);right:calc((100% - 48rem - 4rem) / 2)}.toc-icon{display:none}.try-it-container{display:block}.content{margin-left:280px}.toc-right ~ .content{margin-left:0;margin-right:280px}}*{box-sizing:border-box}body{font-size:1.2rem;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif}h1,h2,h3,h4,h5,h6{padding-top:0.5em}p{margin-top:0.25rem}pre{display:block;background:#f7f7f7;border-radius:2px;border:1px solid #e0e0e0;padding:2px;line-height:1.2;margin-bottom:10px;overflow:auto;white-space:pre-wrap}code{display:inline;font-size:.8em;max-width:100%} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css deleted file mode 100644 index 6265223f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.css +++ /dev/null @@ -1 +0,0 @@ -.toc{overflow-y:auto}.toc>ul{overflow:hidden;position:relative}.toc>ul li{list-style:none}.toc-list{margin:0;padding-left:10px}a.toc-link{color:currentColor;height:100%}.is-collapsible{max-height:1000px;overflow:hidden;transition:all 300ms ease-in-out}.is-collapsed{max-height:0}.is-position-fixed{position:fixed !important;top:0}.is-active-link{font-weight:700}.toc-link::before{background-color:#EEE;content:' ';display:inline-block;height:inherit;left:0;margin-top:-1px;position:absolute;width:2px}.is-active-link::before{background-color:#54BC4B} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js deleted file mode 100644 index 52fe1837..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.js +++ /dev/null @@ -1,136 +0,0 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // identity function for calling harmony imports with the correct context -/******/ __webpack_require__.i = function(value) { return value; }; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 5); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/* unknown exports provided */ -/* all exports used */ -/*!***********************************!*\ - !*** (webpack)/buildin/global.js ***! - \***********************************/ -/***/ (function(module, exports) { - -eval("var g;\r\n\r\n// This works in non-strict mode\r\ng = (function() {\r\n\treturn this;\r\n})();\r\n\r\ntry {\r\n\t// This works if eval is allowed (see CSP)\r\n\tg = g || Function(\"return this\")() || (1,eval)(\"this\");\r\n} catch(e) {\r\n\t// This works if the window reference is available\r\n\tif(typeof window === \"object\")\r\n\t\tg = window;\r\n}\r\n\r\n// g can still be undefined, but nothing to do about it...\r\n// We return undefined, instead of nothing here, so it's\r\n// easier to handle this case. if(!global) { ...}\r\n\r\nmodule.exports = g;\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8od2VicGFjaykvYnVpbGRpbi9nbG9iYWwuanM/MzY5OCJdLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgZztcclxuXHJcbi8vIFRoaXMgd29ya3MgaW4gbm9uLXN0cmljdCBtb2RlXHJcbmcgPSAoZnVuY3Rpb24oKSB7XHJcblx0cmV0dXJuIHRoaXM7XHJcbn0pKCk7XHJcblxyXG50cnkge1xyXG5cdC8vIFRoaXMgd29ya3MgaWYgZXZhbCBpcyBhbGxvd2VkIChzZWUgQ1NQKVxyXG5cdGcgPSBnIHx8IEZ1bmN0aW9uKFwicmV0dXJuIHRoaXNcIikoKSB8fCAoMSxldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2goZSkge1xyXG5cdC8vIFRoaXMgd29ya3MgaWYgdGhlIHdpbmRvdyByZWZlcmVuY2UgaXMgYXZhaWxhYmxlXHJcblx0aWYodHlwZW9mIHdpbmRvdyA9PT0gXCJvYmplY3RcIilcclxuXHRcdGcgPSB3aW5kb3c7XHJcbn1cclxuXHJcbi8vIGcgY2FuIHN0aWxsIGJlIHVuZGVmaW5lZCwgYnV0IG5vdGhpbmcgdG8gZG8gYWJvdXQgaXQuLi5cclxuLy8gV2UgcmV0dXJuIHVuZGVmaW5lZCwgaW5zdGVhZCBvZiBub3RoaW5nIGhlcmUsIHNvIGl0J3NcclxuLy8gZWFzaWVyIHRvIGhhbmRsZSB0aGlzIGNhc2UuIGlmKCFnbG9iYWwpIHsgLi4ufVxyXG5cclxubW9kdWxlLmV4cG9ydHMgPSBnO1xyXG5cblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAod2VicGFjaykvYnVpbGRpbi9nbG9iYWwuanNcbi8vIG1vZHVsZSBpZCA9IDBcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); - -/***/ }), -/* 1 */ -/* unknown exports provided */ -/* all exports used */ -/*!**********************************!*\ - !*** ./~/zenscroll/zenscroll.js ***! - \**********************************/ -/***/ (function(module, exports, __webpack_require__) { - -eval("var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**\n * Zenscroll 4.0.0\n * https://github.com/zengabor/zenscroll/\n *\n * Copyright 2015–2017 Gabor Lenard\n *\n * This is free and unencumbered software released into the public domain.\n * \n * Anyone is free to copy, modify, publish, use, compile, sell, or\n * distribute this software, either in source code form or as a compiled\n * binary, for any purpose, commercial or non-commercial, and by any\n * means.\n * \n * In jurisdictions that recognize copyright laws, the author or authors\n * of this software dedicate any and all copyright interest in the\n * software to the public domain. We make this dedication for the benefit\n * of the public at large and to the detriment of our heirs and\n * successors. We intend this dedication to be an overt act of\n * relinquishment in perpetuity of all present and future rights to this\n * software under copyright law.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n * \n * For more information, please refer to \n * \n */\n\n/*jshint devel:true, asi:true */\n\n/*global define, module */\n\n\n(function (root, factory) {\n\tif (true) {\n\t\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory()),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?\n\t\t\t\t(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))\n\t} else if (typeof module === \"object\" && module.exports) {\n\t\tmodule.exports = factory()\n\t} else {\n\t\t(function install() {\n\t\t\t// To make sure Zenscroll can be referenced from the header, before `body` is available\n\t\t\tif (document && document.body) {\n\t\t\t\troot.zenscroll = factory()\n\t\t\t} else {\n\t\t\t\t// retry 9ms later\n\t\t\t\tsetTimeout(install, 9)\n\t\t\t}\n\t\t})()\n\t}\n}(this, function () {\n\t\"use strict\"\n\n\n\t// Detect if the browser already supports native smooth scrolling (e.g., Firefox 36+ and Chrome 49+) and it is enabled:\n\tvar isNativeSmoothScrollEnabledOn = function (elem) {\n\t\treturn (\"getComputedStyle\" in window) &&\n\t\t\twindow.getComputedStyle(elem)[\"scroll-behavior\"] === \"smooth\"\n\t}\n\n\n\t// Exit if it’s not a browser environment:\n\tif (typeof window === \"undefined\" || !(\"document\" in window)) {\n\t\treturn {}\n\t}\n\n\n\tvar makeScroller = function (container, defaultDuration, edgeOffset) {\n\n\t\t// Use defaults if not provided\n\t\tdefaultDuration = defaultDuration || 999 //ms\n\t\tif (!edgeOffset && edgeOffset !== 0) {\n\t\t\t// When scrolling, this amount of distance is kept from the edges of the container:\n\t\t\tedgeOffset = 9 //px\n\t\t}\n\n\t\t// Handling the life-cycle of the scroller\n\t\tvar scrollTimeoutId\n\t\tvar setScrollTimeoutId = function (newValue) {\n\t\t\tscrollTimeoutId = newValue\n\t\t}\n\n\t\t/**\n\t\t * Stop the current smooth scroll operation immediately\n\t\t */\n\t\tvar stopScroll = function () {\n\t\t\tclearTimeout(scrollTimeoutId)\n\t\t\tsetScrollTimeoutId(0)\n\t\t}\n\n\t\tvar getTopWithEdgeOffset = function (elem) {\n\t\t\treturn Math.max(0, container.getTopOf(elem) - edgeOffset)\n\t\t}\n\n\t\t/**\n\t\t * Scrolls to a specific vertical position in the document.\n\t\t *\n\t\t * @param {targetY} The vertical position within the document.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * If not provided the default duration is used.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollToY = function (targetY, duration, onDone) {\n\t\t\tstopScroll()\n\t\t\tif (duration === 0 || (duration && duration < 0) || isNativeSmoothScrollEnabledOn(container.body)) {\n\t\t\t\tcontainer.toY(targetY)\n\t\t\t\tif (onDone) {\n\t\t\t\t\tonDone()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar startY = container.getY()\n\t\t\t\tvar distance = Math.max(0, targetY) - startY\n\t\t\t\tvar startTime = new Date().getTime()\n\t\t\t\tduration = duration || Math.min(Math.abs(distance), defaultDuration);\n\t\t\t\t(function loopScroll() {\n\t\t\t\t\tsetScrollTimeoutId(setTimeout(function () {\n\t\t\t\t\t\t// Calculate percentage:\n\t\t\t\t\t\tvar p = Math.min(1, (new Date().getTime() - startTime) / duration)\n\t\t\t\t\t\t// Calculate the absolute vertical position:\n\t\t\t\t\t\tvar y = Math.max(0, Math.floor(startY + distance*(p < 0.5 ? 2*p*p : p*(4 - p*2)-1)))\n\t\t\t\t\t\tcontainer.toY(y)\n\t\t\t\t\t\tif (p < 1 && (container.getHeight() + y) < container.body.scrollHeight) {\n\t\t\t\t\t\t\tloopScroll()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetTimeout(stopScroll, 99) // with cooldown time\n\t\t\t\t\t\t\tif (onDone) {\n\t\t\t\t\t\t\t\tonDone()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 9))\n\t\t\t\t})()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Scrolls to the top of a specific element.\n\t\t *\n\t\t * @param {elem} The element to scroll to.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollToElem = function (elem, duration, onDone) {\n\t\t\tscrollToY(getTopWithEdgeOffset(elem), duration, onDone)\n\t\t}\n\n\t\t/**\n\t\t * Scrolls an element into view if necessary.\n\t\t *\n\t\t * @param {elem} The element.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollIntoView = function (elem, duration, onDone) {\n\t\t\tvar elemHeight = elem.getBoundingClientRect().height\n\t\t\tvar elemBottom = container.getTopOf(elem) + elemHeight\n\t\t\tvar containerHeight = container.getHeight()\n\t\t\tvar y = container.getY()\n\t\t\tvar containerBottom = y + containerHeight\n\t\t\tif (getTopWithEdgeOffset(elem) < y || (elemHeight + edgeOffset) > containerHeight) {\n\t\t\t\t// Element is clipped at top or is higher than screen.\n\t\t\t\tscrollToElem(elem, duration, onDone)\n\t\t\t} else if ((elemBottom + edgeOffset) > containerBottom) {\n\t\t\t\t// Element is clipped at the bottom.\n\t\t\t\tscrollToY(elemBottom - containerHeight + edgeOffset, duration, onDone)\n\t\t\t} else if (onDone) {\n\t\t\t\tonDone()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Scrolls to the center of an element.\n\t\t *\n\t\t * @param {elem} The element.\n\t\t * @param {duration} Optionally the duration of the scroll operation.\n\t\t * @param {offset} Optionally the offset of the top of the element from the center of the screen.\n\t\t * @param {onDone} An optional callback function to be invoked once the scroll finished.\n\t\t */\n\t\tvar scrollToCenterOf = function (elem, duration, offset, onDone) {\n\t\t\tscrollToY(Math.max(0, container.getTopOf(elem) - container.getHeight()/2 + (offset || elem.getBoundingClientRect().height/2)), duration, onDone)\n\t\t}\n\n\t\t/**\n\t\t * Changes default settings for this scroller.\n\t\t *\n\t\t * @param {newDefaultDuration} Optionally a new value for default duration, used for each scroll method by default.\n\t\t * Ignored if null or undefined.\n\t\t * @param {newEdgeOffset} Optionally a new value for the edge offset, used by each scroll method by default. Ignored if null or undefined.\n\t\t * @returns An object with the current values.\n\t\t */\n\t\tvar setup = function (newDefaultDuration, newEdgeOffset) {\n\t\t\tif (newDefaultDuration === 0 || newDefaultDuration) {\n\t\t\t\tdefaultDuration = newDefaultDuration\n\t\t\t}\n\t\t\tif (newEdgeOffset === 0 || newEdgeOffset) {\n\t\t\t\tedgeOffset = newEdgeOffset\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tdefaultDuration: defaultDuration,\n\t\t\t\tedgeOffset: edgeOffset\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tsetup: setup,\n\t\t\tto: scrollToElem,\n\t\t\ttoY: scrollToY,\n\t\t\tintoView: scrollIntoView,\n\t\t\tcenter: scrollToCenterOf,\n\t\t\tstop: stopScroll,\n\t\t\tmoving: function () { return !!scrollTimeoutId },\n\t\t\tgetY: container.getY,\n\t\t\tgetTopOf: container.getTopOf\n\t\t}\n\n\t}\n\n\n\tvar docElem = document.documentElement\n\tvar getDocY = function () { return window.scrollY || docElem.scrollTop }\n\n\t// Create a scroller for the document:\n\tvar zenscroll = makeScroller({\n\t\tbody: document.scrollingElement || document.body,\n\t\ttoY: function (y) { window.scrollTo(0, y) },\n\t\tgetY: getDocY,\n\t\tgetHeight: function () { return window.innerHeight || docElem.clientHeight },\n\t\tgetTopOf: function (elem) { return elem.getBoundingClientRect().top + getDocY() - docElem.offsetTop }\n\t})\n\n\n\t/**\n\t * Creates a scroller from the provided container element (e.g., a DIV)\n\t *\n\t * @param {scrollContainer} The vertical position within the document.\n\t * @param {defaultDuration} Optionally a value for default duration, used for each scroll method by default.\n\t * Ignored if 0 or null or undefined.\n\t * @param {edgeOffset} Optionally a value for the edge offset, used by each scroll method by default. \n\t * Ignored if null or undefined.\n\t * @returns A scroller object, similar to `zenscroll` but controlling the provided element.\n\t */\n\tzenscroll.createScroller = function (scrollContainer, defaultDuration, edgeOffset) {\n\t\treturn makeScroller({\n\t\t\tbody: scrollContainer,\n\t\t\ttoY: function (y) { scrollContainer.scrollTop = y },\n\t\t\tgetY: function () { return scrollContainer.scrollTop },\n\t\t\tgetHeight: function () { return Math.min(scrollContainer.clientHeight, window.innerHeight || docElem.clientHeight) },\n\t\t\tgetTopOf: function (elem) { return elem.offsetTop }\n\t\t}, defaultDuration, edgeOffset)\n\t}\n\n\n\t// Automatic link-smoothing on achors\n\t// Exclude IE8- or when native is enabled or Zenscroll auto- is disabled\n\tif (\"addEventListener\" in window && !window.noZensmooth && !isNativeSmoothScrollEnabledOn(document.body)) {\n\n\n\t\tvar isScrollRestorationSupported = \"scrollRestoration\" in history\n\n\t\t// On first load & refresh make sure the browser restores the position first\n\t\tif (isScrollRestorationSupported) {\n\t\t\thistory.scrollRestoration = \"auto\"\n\t\t}\n\n\t\twindow.addEventListener(\"load\", function () {\n\n\t\t\tif (isScrollRestorationSupported) {\n\t\t\t\t// Set it to manual\n\t\t\t\tsetTimeout(function () { history.scrollRestoration = \"manual\" }, 9)\n\t\t\t\twindow.addEventListener(\"popstate\", function (event) {\n\t\t\t\t\tif (event.state && \"zenscrollY\" in event.state) {\n\t\t\t\t\t\tzenscroll.toY(event.state.zenscrollY)\n\t\t\t\t\t}\n\t\t\t\t}, false)\n\t\t\t}\n\n\t\t\t// Add edge offset on first load if necessary\n\t\t\t// This may not work on IE (or older computer?) as it requires more timeout, around 100 ms\n\t\t\tif (window.location.hash) {\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t// Adjustment is only needed if there is an edge offset:\n\t\t\t\t\tvar edgeOffset = zenscroll.setup().edgeOffset\n\t\t\t\t\tif (edgeOffset) {\n\t\t\t\t\t\tvar targetElem = document.getElementById(window.location.href.split(\"#\")[1])\n\t\t\t\t\t\tif (targetElem) {\n\t\t\t\t\t\t\tvar targetY = Math.max(0, zenscroll.getTopOf(targetElem) - edgeOffset)\n\t\t\t\t\t\t\tvar diff = zenscroll.getY() - targetY\n\t\t\t\t\t\t\t// Only do the adjustment if the browser is very close to the element:\n\t\t\t\t\t\t\tif (0 <= diff && diff < 9 ) {\n\t\t\t\t\t\t\t\twindow.scrollTo(0, targetY)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}, 9)\n\t\t\t}\n\n\t\t}, false)\n\n\t\t// Handling clicks on anchors\n\t\tvar RE_noZensmooth = new RegExp(\"(^|\\\\s)noZensmooth(\\\\s|$)\")\n\t\twindow.addEventListener(\"click\", function (event) {\n\t\t\tvar anchor = event.target\n\t\t\twhile (anchor && anchor.tagName !== \"A\") {\n\t\t\t\tanchor = anchor.parentNode\n\t\t\t}\n\t\t\t// Let the browser handle the click if it wasn't with the primary button, or with some modifier keys:\n\t\t\tif (!anchor || event.which !== 1 || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Save the current scrolling position so it can be used for scroll restoration:\n\t\t\tif (isScrollRestorationSupported) {\n\t\t\t\ttry {\n\t\t\t\t\thistory.replaceState({ zenscrollY: zenscroll.getY() }, \"\")\n\t\t\t\t} catch (e) {\n\t\t\t\t\t// Avoid the Chrome Security exception on file protocol, e.g., file://index.html\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Find the referenced ID:\n\t\t\tvar href = anchor.getAttribute(\"href\") || \"\"\n\t\t\tif (href.indexOf(\"#\") === 0 && !RE_noZensmooth.test(anchor.className)) {\n\t\t\t\tvar targetY = 0\n\t\t\t\tvar targetElem = document.getElementById(href.substring(1))\n\t\t\t\tif (href !== \"#\") {\n\t\t\t\t\tif (!targetElem) {\n\t\t\t\t\t\t// Let the browser handle the click if the target ID is not found.\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\ttargetY = zenscroll.getTopOf(targetElem)\n\t\t\t\t}\n\t\t\t\tevent.preventDefault()\n\t\t\t\t// By default trigger the browser's `hashchange` event...\n\t\t\t\tvar onDone = function () { window.location = href }\n\t\t\t\t// ...unless there is an edge offset specified\n\t\t\t\tvar edgeOffset = zenscroll.setup().edgeOffset\n\t\t\t\tif (edgeOffset) {\n\t\t\t\t\ttargetY = Math.max(0, targetY - edgeOffset)\n\t\t\t\t\tonDone = function () { history.pushState(null, \"\", href) }\n\t\t\t\t}\n\t\t\t\tzenscroll.toY(targetY, null, onDone)\n\t\t\t}\n\t\t}, false)\n\n\t}\n\n\n\treturn zenscroll\n\n\n}));\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL34vemVuc2Nyb2xsL3plbnNjcm9sbC5qcz8yNzMyIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogWmVuc2Nyb2xsIDQuMC4wXG4gKiBodHRwczovL2dpdGh1Yi5jb20vemVuZ2Fib3IvemVuc2Nyb2xsL1xuICpcbiAqIENvcHlyaWdodCAyMDE14oCTMjAxNyBHYWJvciBMZW5hcmRcbiAqXG4gKiBUaGlzIGlzIGZyZWUgYW5kIHVuZW5jdW1iZXJlZCBzb2Z0d2FyZSByZWxlYXNlZCBpbnRvIHRoZSBwdWJsaWMgZG9tYWluLlxuICogXG4gKiBBbnlvbmUgaXMgZnJlZSB0byBjb3B5LCBtb2RpZnksIHB1Ymxpc2gsIHVzZSwgY29tcGlsZSwgc2VsbCwgb3JcbiAqIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSwgZWl0aGVyIGluIHNvdXJjZSBjb2RlIGZvcm0gb3IgYXMgYSBjb21waWxlZFxuICogYmluYXJ5LCBmb3IgYW55IHB1cnBvc2UsIGNvbW1lcmNpYWwgb3Igbm9uLWNvbW1lcmNpYWwsIGFuZCBieSBhbnlcbiAqIG1lYW5zLlxuICogXG4gKiBJbiBqdXJpc2RpY3Rpb25zIHRoYXQgcmVjb2duaXplIGNvcHlyaWdodCBsYXdzLCB0aGUgYXV0aG9yIG9yIGF1dGhvcnNcbiAqIG9mIHRoaXMgc29mdHdhcmUgZGVkaWNhdGUgYW55IGFuZCBhbGwgY29weXJpZ2h0IGludGVyZXN0IGluIHRoZVxuICogc29mdHdhcmUgdG8gdGhlIHB1YmxpYyBkb21haW4uIFdlIG1ha2UgdGhpcyBkZWRpY2F0aW9uIGZvciB0aGUgYmVuZWZpdFxuICogb2YgdGhlIHB1YmxpYyBhdCBsYXJnZSBhbmQgdG8gdGhlIGRldHJpbWVudCBvZiBvdXIgaGVpcnMgYW5kXG4gKiBzdWNjZXNzb3JzLiBXZSBpbnRlbmQgdGhpcyBkZWRpY2F0aW9uIHRvIGJlIGFuIG92ZXJ0IGFjdCBvZlxuICogcmVsaW5xdWlzaG1lbnQgaW4gcGVycGV0dWl0eSBvZiBhbGwgcHJlc2VudCBhbmQgZnV0dXJlIHJpZ2h0cyB0byB0aGlzXG4gKiBzb2Z0d2FyZSB1bmRlciBjb3B5cmlnaHQgbGF3LlxuICogXG4gKiBUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiLCBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELFxuICogRVhQUkVTUyBPUiBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GXG4gKiBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuXG4gKiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBDTEFJTSwgREFNQUdFUyBPUlxuICogT1RIRVIgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsXG4gKiBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1JcbiAqIE9USEVSIERFQUxJTkdTIElOIFRIRSBTT0ZUV0FSRS5cbiAqIFxuICogRm9yIG1vcmUgaW5mb3JtYXRpb24sIHBsZWFzZSByZWZlciB0byA8aHR0cDovL3VubGljZW5zZS5vcmc+XG4gKiBcbiAqL1xuXG4vKmpzaGludCBkZXZlbDp0cnVlLCBhc2k6dHJ1ZSAqL1xuXG4vKmdsb2JhbCBkZWZpbmUsIG1vZHVsZSAqL1xuXG5cbihmdW5jdGlvbiAocm9vdCwgZmFjdG9yeSkge1xuXHRpZiAodHlwZW9mIGRlZmluZSA9PT0gXCJmdW5jdGlvblwiICYmIGRlZmluZS5hbWQpIHtcblx0XHRkZWZpbmUoW10sIGZhY3RvcnkoKSlcblx0fSBlbHNlIGlmICh0eXBlb2YgbW9kdWxlID09PSBcIm9iamVjdFwiICYmIG1vZHVsZS5leHBvcnRzKSB7XG5cdFx0bW9kdWxlLmV4cG9ydHMgPSBmYWN0b3J5KClcblx0fSBlbHNlIHtcblx0XHQoZnVuY3Rpb24gaW5zdGFsbCgpIHtcblx0XHRcdC8vIFRvIG1ha2Ugc3VyZSBaZW5zY3JvbGwgY2FuIGJlIHJlZmVyZW5jZWQgZnJvbSB0aGUgaGVhZGVyLCBiZWZvcmUgYGJvZHlgIGlzIGF2YWlsYWJsZVxuXHRcdFx0aWYgKGRvY3VtZW50ICYmIGRvY3VtZW50LmJvZHkpIHtcblx0XHRcdFx0cm9vdC56ZW5zY3JvbGwgPSBmYWN0b3J5KClcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdC8vIHJldHJ5IDltcyBsYXRlclxuXHRcdFx0XHRzZXRUaW1lb3V0KGluc3RhbGwsIDkpXG5cdFx0XHR9XG5cdFx0fSkoKVxuXHR9XG59KHRoaXMsIGZ1bmN0aW9uICgpIHtcblx0XCJ1c2Ugc3RyaWN0XCJcblxuXG5cdC8vIERldGVjdCBpZiB0aGUgYnJvd3NlciBhbHJlYWR5IHN1cHBvcnRzIG5hdGl2ZSBzbW9vdGggc2Nyb2xsaW5nIChlLmcuLCBGaXJlZm94IDM2KyBhbmQgQ2hyb21lIDQ5KykgYW5kIGl0IGlzIGVuYWJsZWQ6XG5cdHZhciBpc05hdGl2ZVNtb290aFNjcm9sbEVuYWJsZWRPbiA9IGZ1bmN0aW9uIChlbGVtKSB7XG5cdFx0cmV0dXJuIChcImdldENvbXB1dGVkU3R5bGVcIiBpbiB3aW5kb3cpICYmXG5cdFx0XHR3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShlbGVtKVtcInNjcm9sbC1iZWhhdmlvclwiXSA9PT0gXCJzbW9vdGhcIlxuXHR9XG5cblxuXHQvLyBFeGl0IGlmIGl04oCZcyBub3QgYSBicm93c2VyIGVudmlyb25tZW50OlxuXHRpZiAodHlwZW9mIHdpbmRvdyA9PT0gXCJ1bmRlZmluZWRcIiB8fCAhKFwiZG9jdW1lbnRcIiBpbiB3aW5kb3cpKSB7XG5cdFx0cmV0dXJuIHt9XG5cdH1cblxuXG5cdHZhciBtYWtlU2Nyb2xsZXIgPSBmdW5jdGlvbiAoY29udGFpbmVyLCBkZWZhdWx0RHVyYXRpb24sIGVkZ2VPZmZzZXQpIHtcblxuXHRcdC8vIFVzZSBkZWZhdWx0cyBpZiBub3QgcHJvdmlkZWRcblx0XHRkZWZhdWx0RHVyYXRpb24gPSBkZWZhdWx0RHVyYXRpb24gfHwgOTk5IC8vbXNcblx0XHRpZiAoIWVkZ2VPZmZzZXQgJiYgZWRnZU9mZnNldCAhPT0gMCkge1xuXHRcdFx0Ly8gV2hlbiBzY3JvbGxpbmcsIHRoaXMgYW1vdW50IG9mIGRpc3RhbmNlIGlzIGtlcHQgZnJvbSB0aGUgZWRnZXMgb2YgdGhlIGNvbnRhaW5lcjpcblx0XHRcdGVkZ2VPZmZzZXQgPSA5IC8vcHhcblx0XHR9XG5cblx0XHQvLyBIYW5kbGluZyB0aGUgbGlmZS1jeWNsZSBvZiB0aGUgc2Nyb2xsZXJcblx0XHR2YXIgc2Nyb2xsVGltZW91dElkXG5cdFx0dmFyIHNldFNjcm9sbFRpbWVvdXRJZCA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuXHRcdFx0c2Nyb2xsVGltZW91dElkID0gbmV3VmFsdWVcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBTdG9wIHRoZSBjdXJyZW50IHNtb290aCBzY3JvbGwgb3BlcmF0aW9uIGltbWVkaWF0ZWx5XG5cdFx0ICovXG5cdFx0dmFyIHN0b3BTY3JvbGwgPSBmdW5jdGlvbiAoKSB7XG5cdFx0XHRjbGVhclRpbWVvdXQoc2Nyb2xsVGltZW91dElkKVxuXHRcdFx0c2V0U2Nyb2xsVGltZW91dElkKDApXG5cdFx0fVxuXG5cdFx0dmFyIGdldFRvcFdpdGhFZGdlT2Zmc2V0ID0gZnVuY3Rpb24gKGVsZW0pIHtcblx0XHRcdHJldHVybiBNYXRoLm1heCgwLCBjb250YWluZXIuZ2V0VG9wT2YoZWxlbSkgLSBlZGdlT2Zmc2V0KVxuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFNjcm9sbHMgdG8gYSBzcGVjaWZpYyB2ZXJ0aWNhbCBwb3NpdGlvbiBpbiB0aGUgZG9jdW1lbnQuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0ge3RhcmdldFl9IFRoZSB2ZXJ0aWNhbCBwb3NpdGlvbiB3aXRoaW4gdGhlIGRvY3VtZW50LlxuXHRcdCAqIEBwYXJhbSB7ZHVyYXRpb259IE9wdGlvbmFsbHkgdGhlIGR1cmF0aW9uIG9mIHRoZSBzY3JvbGwgb3BlcmF0aW9uLlxuXHRcdCAqICAgICAgICBJZiBub3QgcHJvdmlkZWQgdGhlIGRlZmF1bHQgZHVyYXRpb24gaXMgdXNlZC5cblx0XHQgKiBAcGFyYW0ge29uRG9uZX0gQW4gb3B0aW9uYWwgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgaW52b2tlZCBvbmNlIHRoZSBzY3JvbGwgZmluaXNoZWQuXG5cdFx0ICovXG5cdFx0dmFyIHNjcm9sbFRvWSA9IGZ1bmN0aW9uICh0YXJnZXRZLCBkdXJhdGlvbiwgb25Eb25lKSB7XG5cdFx0XHRzdG9wU2Nyb2xsKClcblx0XHRcdGlmIChkdXJhdGlvbiA9PT0gMCB8fCAoZHVyYXRpb24gJiYgZHVyYXRpb24gPCAwKSB8fCBpc05hdGl2ZVNtb290aFNjcm9sbEVuYWJsZWRPbihjb250YWluZXIuYm9keSkpIHtcblx0XHRcdFx0Y29udGFpbmVyLnRvWSh0YXJnZXRZKVxuXHRcdFx0XHRpZiAob25Eb25lKSB7XG5cdFx0XHRcdFx0b25Eb25lKClcblx0XHRcdFx0fVxuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0dmFyIHN0YXJ0WSA9IGNvbnRhaW5lci5nZXRZKClcblx0XHRcdFx0dmFyIGRpc3RhbmNlID0gTWF0aC5tYXgoMCwgdGFyZ2V0WSkgLSBzdGFydFlcblx0XHRcdFx0dmFyIHN0YXJ0VGltZSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpXG5cdFx0XHRcdGR1cmF0aW9uID0gZHVyYXRpb24gfHwgTWF0aC5taW4oTWF0aC5hYnMoZGlzdGFuY2UpLCBkZWZhdWx0RHVyYXRpb24pO1xuXHRcdFx0XHQoZnVuY3Rpb24gbG9vcFNjcm9sbCgpIHtcblx0XHRcdFx0XHRzZXRTY3JvbGxUaW1lb3V0SWQoc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG5cdFx0XHRcdFx0XHQvLyBDYWxjdWxhdGUgcGVyY2VudGFnZTpcblx0XHRcdFx0XHRcdHZhciBwID0gTWF0aC5taW4oMSwgKG5ldyBEYXRlKCkuZ2V0VGltZSgpIC0gc3RhcnRUaW1lKSAvIGR1cmF0aW9uKVxuXHRcdFx0XHRcdFx0Ly8gQ2FsY3VsYXRlIHRoZSBhYnNvbHV0ZSB2ZXJ0aWNhbCBwb3NpdGlvbjpcblx0XHRcdFx0XHRcdHZhciB5ID0gTWF0aC5tYXgoMCwgTWF0aC5mbG9vcihzdGFydFkgKyBkaXN0YW5jZSoocCA8IDAuNSA/IDIqcCpwIDogcCooNCAtIHAqMiktMSkpKVxuXHRcdFx0XHRcdFx0Y29udGFpbmVyLnRvWSh5KVxuXHRcdFx0XHRcdFx0aWYgKHAgPCAxICYmIChjb250YWluZXIuZ2V0SGVpZ2h0KCkgKyB5KSA8IGNvbnRhaW5lci5ib2R5LnNjcm9sbEhlaWdodCkge1xuXHRcdFx0XHRcdFx0XHRsb29wU2Nyb2xsKClcblx0XHRcdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0XHRcdHNldFRpbWVvdXQoc3RvcFNjcm9sbCwgOTkpIC8vIHdpdGggY29vbGRvd24gdGltZVxuXHRcdFx0XHRcdFx0XHRpZiAob25Eb25lKSB7XG5cdFx0XHRcdFx0XHRcdFx0b25Eb25lKClcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0sIDkpKVxuXHRcdFx0XHR9KSgpXG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2Nyb2xscyB0byB0aGUgdG9wIG9mIGEgc3BlY2lmaWMgZWxlbWVudC5cblx0XHQgKlxuXHRcdCAqIEBwYXJhbSB7ZWxlbX0gVGhlIGVsZW1lbnQgdG8gc2Nyb2xsIHRvLlxuXHRcdCAqIEBwYXJhbSB7ZHVyYXRpb259IE9wdGlvbmFsbHkgdGhlIGR1cmF0aW9uIG9mIHRoZSBzY3JvbGwgb3BlcmF0aW9uLlxuXHRcdCAqIEBwYXJhbSB7b25Eb25lfSBBbiBvcHRpb25hbCBjYWxsYmFjayBmdW5jdGlvbiB0byBiZSBpbnZva2VkIG9uY2UgdGhlIHNjcm9sbCBmaW5pc2hlZC5cblx0XHQgKi9cblx0XHR2YXIgc2Nyb2xsVG9FbGVtID0gZnVuY3Rpb24gKGVsZW0sIGR1cmF0aW9uLCBvbkRvbmUpIHtcblx0XHRcdHNjcm9sbFRvWShnZXRUb3BXaXRoRWRnZU9mZnNldChlbGVtKSwgZHVyYXRpb24sIG9uRG9uZSlcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBTY3JvbGxzIGFuIGVsZW1lbnQgaW50byB2aWV3IGlmIG5lY2Vzc2FyeS5cblx0XHQgKlxuXHRcdCAqIEBwYXJhbSB7ZWxlbX0gVGhlIGVsZW1lbnQuXG5cdFx0ICogQHBhcmFtIHtkdXJhdGlvbn0gT3B0aW9uYWxseSB0aGUgZHVyYXRpb24gb2YgdGhlIHNjcm9sbCBvcGVyYXRpb24uXG5cdFx0ICogQHBhcmFtIHtvbkRvbmV9IEFuIG9wdGlvbmFsIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGJlIGludm9rZWQgb25jZSB0aGUgc2Nyb2xsIGZpbmlzaGVkLlxuXHRcdCAqL1xuXHRcdHZhciBzY3JvbGxJbnRvVmlldyA9IGZ1bmN0aW9uIChlbGVtLCBkdXJhdGlvbiwgb25Eb25lKSB7XG5cdFx0XHR2YXIgZWxlbUhlaWdodCA9IGVsZW0uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkuaGVpZ2h0XG5cdFx0XHR2YXIgZWxlbUJvdHRvbSA9IGNvbnRhaW5lci5nZXRUb3BPZihlbGVtKSArIGVsZW1IZWlnaHRcblx0XHRcdHZhciBjb250YWluZXJIZWlnaHQgPSBjb250YWluZXIuZ2V0SGVpZ2h0KClcblx0XHRcdHZhciB5ID0gY29udGFpbmVyLmdldFkoKVxuXHRcdFx0dmFyIGNvbnRhaW5lckJvdHRvbSA9IHkgKyBjb250YWluZXJIZWlnaHRcblx0XHRcdGlmIChnZXRUb3BXaXRoRWRnZU9mZnNldChlbGVtKSA8IHkgfHwgKGVsZW1IZWlnaHQgKyBlZGdlT2Zmc2V0KSA+IGNvbnRhaW5lckhlaWdodCkge1xuXHRcdFx0XHQvLyBFbGVtZW50IGlzIGNsaXBwZWQgYXQgdG9wIG9yIGlzIGhpZ2hlciB0aGFuIHNjcmVlbi5cblx0XHRcdFx0c2Nyb2xsVG9FbGVtKGVsZW0sIGR1cmF0aW9uLCBvbkRvbmUpXG5cdFx0XHR9IGVsc2UgaWYgKChlbGVtQm90dG9tICsgZWRnZU9mZnNldCkgPiBjb250YWluZXJCb3R0b20pIHtcblx0XHRcdFx0Ly8gRWxlbWVudCBpcyBjbGlwcGVkIGF0IHRoZSBib3R0b20uXG5cdFx0XHRcdHNjcm9sbFRvWShlbGVtQm90dG9tIC0gY29udGFpbmVySGVpZ2h0ICsgZWRnZU9mZnNldCwgZHVyYXRpb24sIG9uRG9uZSlcblx0XHRcdH0gZWxzZSBpZiAob25Eb25lKSB7XG5cdFx0XHRcdG9uRG9uZSgpXG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2Nyb2xscyB0byB0aGUgY2VudGVyIG9mIGFuIGVsZW1lbnQuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0ge2VsZW19IFRoZSBlbGVtZW50LlxuXHRcdCAqIEBwYXJhbSB7ZHVyYXRpb259IE9wdGlvbmFsbHkgdGhlIGR1cmF0aW9uIG9mIHRoZSBzY3JvbGwgb3BlcmF0aW9uLlxuXHRcdCAqIEBwYXJhbSB7b2Zmc2V0fSBPcHRpb25hbGx5IHRoZSBvZmZzZXQgb2YgdGhlIHRvcCBvZiB0aGUgZWxlbWVudCBmcm9tIHRoZSBjZW50ZXIgb2YgdGhlIHNjcmVlbi5cblx0XHQgKiBAcGFyYW0ge29uRG9uZX0gQW4gb3B0aW9uYWwgY2FsbGJhY2sgZnVuY3Rpb24gdG8gYmUgaW52b2tlZCBvbmNlIHRoZSBzY3JvbGwgZmluaXNoZWQuXG5cdFx0ICovXG5cdFx0dmFyIHNjcm9sbFRvQ2VudGVyT2YgPSBmdW5jdGlvbiAoZWxlbSwgZHVyYXRpb24sIG9mZnNldCwgb25Eb25lKSB7XG5cdFx0XHRzY3JvbGxUb1koTWF0aC5tYXgoMCwgY29udGFpbmVyLmdldFRvcE9mKGVsZW0pIC0gY29udGFpbmVyLmdldEhlaWdodCgpLzIgKyAob2Zmc2V0IHx8IGVsZW0uZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkuaGVpZ2h0LzIpKSwgZHVyYXRpb24sIG9uRG9uZSlcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBDaGFuZ2VzIGRlZmF1bHQgc2V0dGluZ3MgZm9yIHRoaXMgc2Nyb2xsZXIuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0ge25ld0RlZmF1bHREdXJhdGlvbn0gT3B0aW9uYWxseSBhIG5ldyB2YWx1ZSBmb3IgZGVmYXVsdCBkdXJhdGlvbiwgdXNlZCBmb3IgZWFjaCBzY3JvbGwgbWV0aG9kIGJ5IGRlZmF1bHQuXG5cdFx0ICogICAgICAgIElnbm9yZWQgaWYgbnVsbCBvciB1bmRlZmluZWQuXG5cdFx0ICogQHBhcmFtIHtuZXdFZGdlT2Zmc2V0fSBPcHRpb25hbGx5IGEgbmV3IHZhbHVlIGZvciB0aGUgZWRnZSBvZmZzZXQsIHVzZWQgYnkgZWFjaCBzY3JvbGwgbWV0aG9kIGJ5IGRlZmF1bHQuIElnbm9yZWQgaWYgbnVsbCBvciB1bmRlZmluZWQuXG5cdFx0ICogQHJldHVybnMgQW4gb2JqZWN0IHdpdGggdGhlIGN1cnJlbnQgdmFsdWVzLlxuXHRcdCAqL1xuXHRcdHZhciBzZXR1cCA9IGZ1bmN0aW9uIChuZXdEZWZhdWx0RHVyYXRpb24sIG5ld0VkZ2VPZmZzZXQpIHtcblx0XHRcdGlmIChuZXdEZWZhdWx0RHVyYXRpb24gPT09IDAgfHwgbmV3RGVmYXVsdER1cmF0aW9uKSB7XG5cdFx0XHRcdGRlZmF1bHREdXJhdGlvbiA9IG5ld0RlZmF1bHREdXJhdGlvblxuXHRcdFx0fVxuXHRcdFx0aWYgKG5ld0VkZ2VPZmZzZXQgPT09IDAgfHwgbmV3RWRnZU9mZnNldCkge1xuXHRcdFx0XHRlZGdlT2Zmc2V0ID0gbmV3RWRnZU9mZnNldFxuXHRcdFx0fVxuXHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0ZGVmYXVsdER1cmF0aW9uOiBkZWZhdWx0RHVyYXRpb24sXG5cdFx0XHRcdGVkZ2VPZmZzZXQ6IGVkZ2VPZmZzZXRcblx0XHRcdH1cblx0XHR9XG5cblx0XHRyZXR1cm4ge1xuXHRcdFx0c2V0dXA6IHNldHVwLFxuXHRcdFx0dG86IHNjcm9sbFRvRWxlbSxcblx0XHRcdHRvWTogc2Nyb2xsVG9ZLFxuXHRcdFx0aW50b1ZpZXc6IHNjcm9sbEludG9WaWV3LFxuXHRcdFx0Y2VudGVyOiBzY3JvbGxUb0NlbnRlck9mLFxuXHRcdFx0c3RvcDogc3RvcFNjcm9sbCxcblx0XHRcdG1vdmluZzogZnVuY3Rpb24gKCkgeyByZXR1cm4gISFzY3JvbGxUaW1lb3V0SWQgfSxcblx0XHRcdGdldFk6IGNvbnRhaW5lci5nZXRZLFxuXHRcdFx0Z2V0VG9wT2Y6IGNvbnRhaW5lci5nZXRUb3BPZlxuXHRcdH1cblxuXHR9XG5cblxuXHR2YXIgZG9jRWxlbSA9IGRvY3VtZW50LmRvY3VtZW50RWxlbWVudFxuXHR2YXIgZ2V0RG9jWSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIHdpbmRvdy5zY3JvbGxZIHx8IGRvY0VsZW0uc2Nyb2xsVG9wIH1cblxuXHQvLyBDcmVhdGUgYSBzY3JvbGxlciBmb3IgdGhlIGRvY3VtZW50OlxuXHR2YXIgemVuc2Nyb2xsID0gbWFrZVNjcm9sbGVyKHtcblx0XHRib2R5OiBkb2N1bWVudC5zY3JvbGxpbmdFbGVtZW50IHx8IGRvY3VtZW50LmJvZHksXG5cdFx0dG9ZOiBmdW5jdGlvbiAoeSkgeyB3aW5kb3cuc2Nyb2xsVG8oMCwgeSkgfSxcblx0XHRnZXRZOiBnZXREb2NZLFxuXHRcdGdldEhlaWdodDogZnVuY3Rpb24gKCkgeyByZXR1cm4gd2luZG93LmlubmVySGVpZ2h0IHx8IGRvY0VsZW0uY2xpZW50SGVpZ2h0IH0sXG5cdFx0Z2V0VG9wT2Y6IGZ1bmN0aW9uIChlbGVtKSB7IHJldHVybiBlbGVtLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcCArIGdldERvY1koKSAtIGRvY0VsZW0ub2Zmc2V0VG9wIH1cblx0fSlcblxuXG5cdC8qKlxuXHQgKiBDcmVhdGVzIGEgc2Nyb2xsZXIgZnJvbSB0aGUgcHJvdmlkZWQgY29udGFpbmVyIGVsZW1lbnQgKGUuZy4sIGEgRElWKVxuXHQgKlxuXHQgKiBAcGFyYW0ge3Njcm9sbENvbnRhaW5lcn0gVGhlIHZlcnRpY2FsIHBvc2l0aW9uIHdpdGhpbiB0aGUgZG9jdW1lbnQuXG5cdCAqIEBwYXJhbSB7ZGVmYXVsdER1cmF0aW9ufSBPcHRpb25hbGx5IGEgdmFsdWUgZm9yIGRlZmF1bHQgZHVyYXRpb24sIHVzZWQgZm9yIGVhY2ggc2Nyb2xsIG1ldGhvZCBieSBkZWZhdWx0LlxuXHQgKiAgICAgICAgSWdub3JlZCBpZiAwIG9yIG51bGwgb3IgdW5kZWZpbmVkLlxuXHQgKiBAcGFyYW0ge2VkZ2VPZmZzZXR9IE9wdGlvbmFsbHkgYSB2YWx1ZSBmb3IgdGhlIGVkZ2Ugb2Zmc2V0LCB1c2VkIGJ5IGVhY2ggc2Nyb2xsIG1ldGhvZCBieSBkZWZhdWx0LiBcblx0ICogICAgICAgIElnbm9yZWQgaWYgbnVsbCBvciB1bmRlZmluZWQuXG5cdCAqIEByZXR1cm5zIEEgc2Nyb2xsZXIgb2JqZWN0LCBzaW1pbGFyIHRvIGB6ZW5zY3JvbGxgIGJ1dCBjb250cm9sbGluZyB0aGUgcHJvdmlkZWQgZWxlbWVudC5cblx0ICovXG5cdHplbnNjcm9sbC5jcmVhdGVTY3JvbGxlciA9IGZ1bmN0aW9uIChzY3JvbGxDb250YWluZXIsIGRlZmF1bHREdXJhdGlvbiwgZWRnZU9mZnNldCkge1xuXHRcdHJldHVybiBtYWtlU2Nyb2xsZXIoe1xuXHRcdFx0Ym9keTogc2Nyb2xsQ29udGFpbmVyLFxuXHRcdFx0dG9ZOiBmdW5jdGlvbiAoeSkgeyBzY3JvbGxDb250YWluZXIuc2Nyb2xsVG9wID0geSB9LFxuXHRcdFx0Z2V0WTogZnVuY3Rpb24gKCkgeyByZXR1cm4gc2Nyb2xsQ29udGFpbmVyLnNjcm9sbFRvcCB9LFxuXHRcdFx0Z2V0SGVpZ2h0OiBmdW5jdGlvbiAoKSB7IHJldHVybiBNYXRoLm1pbihzY3JvbGxDb250YWluZXIuY2xpZW50SGVpZ2h0LCB3aW5kb3cuaW5uZXJIZWlnaHQgfHwgZG9jRWxlbS5jbGllbnRIZWlnaHQpIH0sXG5cdFx0XHRnZXRUb3BPZjogZnVuY3Rpb24gKGVsZW0pIHsgcmV0dXJuIGVsZW0ub2Zmc2V0VG9wIH1cblx0XHR9LCBkZWZhdWx0RHVyYXRpb24sIGVkZ2VPZmZzZXQpXG5cdH1cblxuXG5cdC8vIEF1dG9tYXRpYyBsaW5rLXNtb290aGluZyBvbiBhY2hvcnNcblx0Ly8gRXhjbHVkZSBJRTgtIG9yIHdoZW4gbmF0aXZlIGlzIGVuYWJsZWQgb3IgWmVuc2Nyb2xsIGF1dG8tIGlzIGRpc2FibGVkXG5cdGlmIChcImFkZEV2ZW50TGlzdGVuZXJcIiBpbiB3aW5kb3cgJiYgIXdpbmRvdy5ub1plbnNtb290aCAmJiAhaXNOYXRpdmVTbW9vdGhTY3JvbGxFbmFibGVkT24oZG9jdW1lbnQuYm9keSkpIHtcblxuXG5cdFx0dmFyIGlzU2Nyb2xsUmVzdG9yYXRpb25TdXBwb3J0ZWQgPSBcInNjcm9sbFJlc3RvcmF0aW9uXCIgaW4gaGlzdG9yeVxuXG5cdFx0Ly8gT24gZmlyc3QgbG9hZCAmIHJlZnJlc2ggbWFrZSBzdXJlIHRoZSBicm93c2VyIHJlc3RvcmVzIHRoZSBwb3NpdGlvbiBmaXJzdFxuXHRcdGlmIChpc1Njcm9sbFJlc3RvcmF0aW9uU3VwcG9ydGVkKSB7XG5cdFx0XHRoaXN0b3J5LnNjcm9sbFJlc3RvcmF0aW9uID0gXCJhdXRvXCJcblx0XHR9XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcImxvYWRcIiwgZnVuY3Rpb24gKCkge1xuXG5cdFx0XHRpZiAoaXNTY3JvbGxSZXN0b3JhdGlvblN1cHBvcnRlZCkge1xuXHRcdFx0XHQvLyBTZXQgaXQgdG8gbWFudWFsXG5cdFx0XHRcdHNldFRpbWVvdXQoZnVuY3Rpb24gKCkgeyBoaXN0b3J5LnNjcm9sbFJlc3RvcmF0aW9uID0gXCJtYW51YWxcIiB9LCA5KVxuXHRcdFx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcInBvcHN0YXRlXCIsIGZ1bmN0aW9uIChldmVudCkge1xuXHRcdFx0XHRcdGlmIChldmVudC5zdGF0ZSAmJiBcInplbnNjcm9sbFlcIiBpbiBldmVudC5zdGF0ZSkge1xuXHRcdFx0XHRcdFx0emVuc2Nyb2xsLnRvWShldmVudC5zdGF0ZS56ZW5zY3JvbGxZKVxuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSwgZmFsc2UpXG5cdFx0XHR9XG5cblx0XHRcdC8vIEFkZCBlZGdlIG9mZnNldCBvbiBmaXJzdCBsb2FkIGlmIG5lY2Vzc2FyeVxuXHRcdFx0Ly8gVGhpcyBtYXkgbm90IHdvcmsgb24gSUUgKG9yIG9sZGVyIGNvbXB1dGVyPykgYXMgaXQgcmVxdWlyZXMgbW9yZSB0aW1lb3V0LCBhcm91bmQgMTAwIG1zXG5cdFx0XHRpZiAod2luZG93LmxvY2F0aW9uLmhhc2gpIHtcblx0XHRcdFx0c2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG5cdFx0XHRcdFx0Ly8gQWRqdXN0bWVudCBpcyBvbmx5IG5lZWRlZCBpZiB0aGVyZSBpcyBhbiBlZGdlIG9mZnNldDpcblx0XHRcdFx0XHR2YXIgZWRnZU9mZnNldCA9IHplbnNjcm9sbC5zZXR1cCgpLmVkZ2VPZmZzZXRcblx0XHRcdFx0XHRpZiAoZWRnZU9mZnNldCkge1xuXHRcdFx0XHRcdFx0dmFyIHRhcmdldEVsZW0gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCh3aW5kb3cubG9jYXRpb24uaHJlZi5zcGxpdChcIiNcIilbMV0pXG5cdFx0XHRcdFx0XHRpZiAodGFyZ2V0RWxlbSkge1xuXHRcdFx0XHRcdFx0XHR2YXIgdGFyZ2V0WSA9IE1hdGgubWF4KDAsIHplbnNjcm9sbC5nZXRUb3BPZih0YXJnZXRFbGVtKSAtIGVkZ2VPZmZzZXQpXG5cdFx0XHRcdFx0XHRcdHZhciBkaWZmID0gemVuc2Nyb2xsLmdldFkoKSAtIHRhcmdldFlcblx0XHRcdFx0XHRcdFx0Ly8gT25seSBkbyB0aGUgYWRqdXN0bWVudCBpZiB0aGUgYnJvd3NlciBpcyB2ZXJ5IGNsb3NlIHRvIHRoZSBlbGVtZW50OlxuXHRcdFx0XHRcdFx0XHRpZiAoMCA8PSBkaWZmICYmIGRpZmYgPCA5ICkge1xuXHRcdFx0XHRcdFx0XHRcdHdpbmRvdy5zY3JvbGxUbygwLCB0YXJnZXRZKVxuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9LCA5KVxuXHRcdFx0fVxuXG5cdFx0fSwgZmFsc2UpXG5cblx0XHQvLyBIYW5kbGluZyBjbGlja3Mgb24gYW5jaG9yc1xuXHRcdHZhciBSRV9ub1plbnNtb290aCA9IG5ldyBSZWdFeHAoXCIoXnxcXFxccylub1plbnNtb290aChcXFxcc3wkKVwiKVxuXHRcdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgZnVuY3Rpb24gKGV2ZW50KSB7XG5cdFx0XHR2YXIgYW5jaG9yID0gZXZlbnQudGFyZ2V0XG5cdFx0XHR3aGlsZSAoYW5jaG9yICYmIGFuY2hvci50YWdOYW1lICE9PSBcIkFcIikge1xuXHRcdFx0XHRhbmNob3IgPSBhbmNob3IucGFyZW50Tm9kZVxuXHRcdFx0fVxuXHRcdFx0Ly8gTGV0IHRoZSBicm93c2VyIGhhbmRsZSB0aGUgY2xpY2sgaWYgaXQgd2Fzbid0IHdpdGggdGhlIHByaW1hcnkgYnV0dG9uLCBvciB3aXRoIHNvbWUgbW9kaWZpZXIga2V5czpcblx0XHRcdGlmICghYW5jaG9yIHx8IGV2ZW50LndoaWNoICE9PSAxIHx8IGV2ZW50LnNoaWZ0S2V5IHx8IGV2ZW50Lm1ldGFLZXkgfHwgZXZlbnQuY3RybEtleSB8fCBldmVudC5hbHRLZXkpIHtcblx0XHRcdFx0cmV0dXJuXG5cdFx0XHR9XG5cdFx0XHQvLyBTYXZlIHRoZSBjdXJyZW50IHNjcm9sbGluZyBwb3NpdGlvbiBzbyBpdCBjYW4gYmUgdXNlZCBmb3Igc2Nyb2xsIHJlc3RvcmF0aW9uOlxuXHRcdFx0aWYgKGlzU2Nyb2xsUmVzdG9yYXRpb25TdXBwb3J0ZWQpIHtcblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRoaXN0b3J5LnJlcGxhY2VTdGF0ZSh7IHplbnNjcm9sbFk6IHplbnNjcm9sbC5nZXRZKCkgfSwgXCJcIilcblx0XHRcdFx0fSBjYXRjaCAoZSkge1xuXHRcdFx0XHRcdC8vIEF2b2lkIHRoZSBDaHJvbWUgU2VjdXJpdHkgZXhjZXB0aW9uIG9uIGZpbGUgcHJvdG9jb2wsIGUuZy4sIGZpbGU6Ly9pbmRleC5odG1sXG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdC8vIEZpbmQgdGhlIHJlZmVyZW5jZWQgSUQ6XG5cdFx0XHR2YXIgaHJlZiA9IGFuY2hvci5nZXRBdHRyaWJ1dGUoXCJocmVmXCIpIHx8IFwiXCJcblx0XHRcdGlmIChocmVmLmluZGV4T2YoXCIjXCIpID09PSAwICYmICFSRV9ub1plbnNtb290aC50ZXN0KGFuY2hvci5jbGFzc05hbWUpKSB7XG5cdFx0XHRcdHZhciB0YXJnZXRZID0gMFxuXHRcdFx0XHR2YXIgdGFyZ2V0RWxlbSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGhyZWYuc3Vic3RyaW5nKDEpKVxuXHRcdFx0XHRpZiAoaHJlZiAhPT0gXCIjXCIpIHtcblx0XHRcdFx0XHRpZiAoIXRhcmdldEVsZW0pIHtcblx0XHRcdFx0XHRcdC8vIExldCB0aGUgYnJvd3NlciBoYW5kbGUgdGhlIGNsaWNrIGlmIHRoZSB0YXJnZXQgSUQgaXMgbm90IGZvdW5kLlxuXHRcdFx0XHRcdFx0cmV0dXJuXG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdHRhcmdldFkgPSB6ZW5zY3JvbGwuZ2V0VG9wT2YodGFyZ2V0RWxlbSlcblx0XHRcdFx0fVxuXHRcdFx0XHRldmVudC5wcmV2ZW50RGVmYXVsdCgpXG5cdFx0XHRcdC8vIEJ5IGRlZmF1bHQgdHJpZ2dlciB0aGUgYnJvd3NlcidzIGBoYXNoY2hhbmdlYCBldmVudC4uLlxuXHRcdFx0XHR2YXIgb25Eb25lID0gZnVuY3Rpb24gKCkgeyB3aW5kb3cubG9jYXRpb24gPSBocmVmIH1cblx0XHRcdFx0Ly8gLi4udW5sZXNzIHRoZXJlIGlzIGFuIGVkZ2Ugb2Zmc2V0IHNwZWNpZmllZFxuXHRcdFx0XHR2YXIgZWRnZU9mZnNldCA9IHplbnNjcm9sbC5zZXR1cCgpLmVkZ2VPZmZzZXRcblx0XHRcdFx0aWYgKGVkZ2VPZmZzZXQpIHtcblx0XHRcdFx0XHR0YXJnZXRZID0gTWF0aC5tYXgoMCwgdGFyZ2V0WSAtIGVkZ2VPZmZzZXQpXG5cdFx0XHRcdFx0b25Eb25lID0gZnVuY3Rpb24gKCkgeyBoaXN0b3J5LnB1c2hTdGF0ZShudWxsLCBcIlwiLCBocmVmKSB9XG5cdFx0XHRcdH1cblx0XHRcdFx0emVuc2Nyb2xsLnRvWSh0YXJnZXRZLCBudWxsLCBvbkRvbmUpXG5cdFx0XHR9XG5cdFx0fSwgZmFsc2UpXG5cblx0fVxuXG5cblx0cmV0dXJuIHplbnNjcm9sbFxuXG5cbn0pKTtcblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vfi96ZW5zY3JvbGwvemVuc2Nyb2xsLmpzXG4vLyBtb2R1bGUgaWQgPSAxXG4vLyBtb2R1bGUgY2h1bmtzID0gMCJdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); - -/***/ }), -/* 2 */ -/* unknown exports provided */ -/* all exports used */ -/*!******************************!*\ - !*** ./src/js/build-html.js ***! - \******************************/ -/***/ (function(module, exports) { - -eval("/**\n * This file is responsible for building the DOM and updating DOM state.\n *\n * @author Tim Scanlin\n */\n\nmodule.exports = function (options) {\n var forEach = [].forEach\n var some = [].some\n var body = document.body\n var currentlyHighlighting = true\n var SPACE_CHAR = ' '\n\n /**\n * Create link and list elements.\n * @param {Object} d\n * @param {HTMLElement} container\n * @return {HTMLElement}\n */\n function createEl (d, container) {\n var link = container.appendChild(createLink(d))\n if (d.children.length) {\n var list = createList(d.isCollapsed)\n d.children.forEach(function (child) {\n createEl(child, list)\n })\n link.appendChild(list)\n }\n }\n\n /**\n * Render nested heading array data into a given selector.\n * @param {String} selector\n * @param {Array} data\n * @return {HTMLElement}\n */\n function render (selector, data) {\n var collapsed = false\n var container = createList(collapsed)\n\n data.forEach(function (d) {\n createEl(d, container)\n })\n\n var parent = document.querySelector(selector)\n\n // Return if no parent is found.\n if (parent === null) {\n return\n }\n\n // Remove existing child if it exists.\n if (parent.firstChild) {\n parent.removeChild(parent.firstChild)\n }\n\n // Append the Elements that have been created;\n return parent.appendChild(container)\n }\n\n /**\n * Create link element.\n * @param {Object} data\n * @return {HTMLElement}\n */\n function createLink (data) {\n var item = document.createElement('li')\n var a = document.createElement('a')\n if (options.listItemClass) {\n item.setAttribute('class', options.listItemClass)\n }\n if (options.includeHtml && data.childNodes.length) {\n forEach.call(data.childNodes, function (node) {\n a.appendChild(node.cloneNode(true))\n })\n } else {\n // Default behavior.\n a.textContent = data.textContent\n }\n a.setAttribute('href', '#' + data.id)\n a.setAttribute('class', options.linkClass +\n SPACE_CHAR + 'node-name--' + data.nodeName +\n SPACE_CHAR + options.extraLinkClasses)\n item.appendChild(a)\n return item\n }\n\n /**\n * Create list element.\n * @param {Boolean} isCollapsed\n * @return {HTMLElement}\n */\n function createList (isCollapsed) {\n var list = document.createElement('ul')\n var classes = options.listClass +\n SPACE_CHAR + options.extraListClasses\n if (isCollapsed) {\n classes += SPACE_CHAR + options.collapsibleClass\n classes += SPACE_CHAR + options.isCollapsedClass\n }\n list.setAttribute('class', classes)\n return list\n }\n\n /**\n * Update fixed sidebar class.\n * @return {HTMLElement}\n */\n function updateFixedSidebarClass () {\n var top = document.documentElement.scrollTop || body.scrollTop\n var posFixedEl = document.querySelector(options.positionFixedSelector)\n\n if (options.fixedSidebarOffset === 'auto') {\n options.fixedSidebarOffset = document.querySelector(options.tocSelector).offsetTop\n }\n\n if (top > options.fixedSidebarOffset) {\n if (posFixedEl.className.indexOf(options.positionFixedClass) === -1) {\n posFixedEl.className += SPACE_CHAR + options.positionFixedClass\n }\n } else {\n posFixedEl.className = posFixedEl.className.split(SPACE_CHAR + options.positionFixedClass).join('')\n }\n }\n\n /**\n * Update TOC highlighting and collpased groupings.\n */\n function updateToc (headingsArray) {\n var top = document.documentElement.scrollTop || body.scrollTop\n\n // Add fixed class at offset;\n if (options.positionFixedSelector) {\n updateFixedSidebarClass()\n }\n\n // Get the top most heading currently visible on the page so we know what to highlight.\n var headings = headingsArray\n var topHeader\n // Using some instead of each so that we can escape early.\n if (currentlyHighlighting &&\n document.querySelector(options.tocSelector) !== null &&\n headings.length > 0) {\n some.call(headings, function (heading, i) {\n if (heading.offsetTop > top + options.headingsOffset + 10) {\n // Don't allow negative index value.\n var index = (i === 0) ? i : i - 1\n topHeader = headings[index]\n return true\n } else if (i === headings.length - 1) {\n // This allows scrolling for the last heading on the page.\n topHeader = headings[headings.length - 1]\n return true\n }\n })\n\n // Remove the active class from the other tocLinks.\n var tocLinks = document.querySelector(options.tocSelector)\n .querySelectorAll('.' + options.linkClass)\n forEach.call(tocLinks, function (tocLink) {\n tocLink.className = tocLink.className.split(SPACE_CHAR + options.activeLinkClass).join('')\n })\n\n // Add the active class to the active tocLink.\n var activeTocLink = document.querySelector(options.tocSelector)\n .querySelector('.' + options.linkClass +\n '.node-name--' + topHeader.nodeName +\n '[href=\"#' + topHeader.id + '\"]')\n activeTocLink.className += SPACE_CHAR + options.activeLinkClass\n\n var tocLists = document.querySelector(options.tocSelector)\n .querySelectorAll('.' + options.listClass + '.' + options.collapsibleClass)\n\n // Collapse the other collapsible lists.\n forEach.call(tocLists, function (list) {\n var collapsedClass = SPACE_CHAR + options.isCollapsedClass\n if (list.className.indexOf(collapsedClass) === -1) {\n list.className += SPACE_CHAR + options.isCollapsedClass\n }\n })\n\n // Expand the active link's collapsible list and its sibling if applicable.\n if (activeTocLink.nextSibling) {\n activeTocLink.nextSibling.className = activeTocLink.nextSibling.className.split(SPACE_CHAR + options.isCollapsedClass).join('')\n }\n removeCollapsedFromParents(activeTocLink.parentNode.parentNode)\n }\n }\n\n /**\n * Remove collpased class from parent elements.\n * @param {HTMLElement} element\n * @return {HTMLElement}\n */\n function removeCollapsedFromParents (element) {\n if (element.className.indexOf(options.collapsibleClass) !== -1) {\n element.className = element.className.split(SPACE_CHAR + options.isCollapsedClass).join('')\n return removeCollapsedFromParents(element.parentNode.parentNode)\n }\n return element\n }\n\n /**\n * Disable TOC Animation when a link is clicked.\n * @param {Event} event\n */\n function disableTocAnimation (event) {\n var target = event.target || event.srcElement\n if (typeof target.className !== 'string' || target.className.indexOf(options.linkClass) === -1) {\n return\n }\n // Bind to tocLink clicks to temporarily disable highlighting\n // while smoothScroll is animating.\n currentlyHighlighting = false\n }\n\n /**\n * Enable TOC Animation.\n */\n function enableTocAnimation () {\n currentlyHighlighting = true\n }\n\n return {\n enableTocAnimation: enableTocAnimation,\n disableTocAnimation: disableTocAnimation,\n render: render,\n updateToc: updateToc\n }\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9idWlsZC1odG1sLmpzPzdkMDEiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGlzIGZpbGUgaXMgcmVzcG9uc2libGUgZm9yIGJ1aWxkaW5nIHRoZSBET00gYW5kIHVwZGF0aW5nIERPTSBzdGF0ZS5cbiAqXG4gKiBAYXV0aG9yIFRpbSBTY2FubGluXG4gKi9cblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAob3B0aW9ucykge1xuICB2YXIgZm9yRWFjaCA9IFtdLmZvckVhY2hcbiAgdmFyIHNvbWUgPSBbXS5zb21lXG4gIHZhciBib2R5ID0gZG9jdW1lbnQuYm9keVxuICB2YXIgY3VycmVudGx5SGlnaGxpZ2h0aW5nID0gdHJ1ZVxuICB2YXIgU1BBQ0VfQ0hBUiA9ICcgJ1xuXG4gIC8qKlxuICAgKiBDcmVhdGUgbGluayBhbmQgbGlzdCBlbGVtZW50cy5cbiAgICogQHBhcmFtIHtPYmplY3R9IGRcbiAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gY29udGFpbmVyXG4gICAqIEByZXR1cm4ge0hUTUxFbGVtZW50fVxuICAgKi9cbiAgZnVuY3Rpb24gY3JlYXRlRWwgKGQsIGNvbnRhaW5lcikge1xuICAgIHZhciBsaW5rID0gY29udGFpbmVyLmFwcGVuZENoaWxkKGNyZWF0ZUxpbmsoZCkpXG4gICAgaWYgKGQuY2hpbGRyZW4ubGVuZ3RoKSB7XG4gICAgICB2YXIgbGlzdCA9IGNyZWF0ZUxpc3QoZC5pc0NvbGxhcHNlZClcbiAgICAgIGQuY2hpbGRyZW4uZm9yRWFjaChmdW5jdGlvbiAoY2hpbGQpIHtcbiAgICAgICAgY3JlYXRlRWwoY2hpbGQsIGxpc3QpXG4gICAgICB9KVxuICAgICAgbGluay5hcHBlbmRDaGlsZChsaXN0KVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZW5kZXIgbmVzdGVkIGhlYWRpbmcgYXJyYXkgZGF0YSBpbnRvIGEgZ2l2ZW4gc2VsZWN0b3IuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzZWxlY3RvclxuICAgKiBAcGFyYW0ge0FycmF5fSBkYXRhXG4gICAqIEByZXR1cm4ge0hUTUxFbGVtZW50fVxuICAgKi9cbiAgZnVuY3Rpb24gcmVuZGVyIChzZWxlY3RvciwgZGF0YSkge1xuICAgIHZhciBjb2xsYXBzZWQgPSBmYWxzZVxuICAgIHZhciBjb250YWluZXIgPSBjcmVhdGVMaXN0KGNvbGxhcHNlZClcblxuICAgIGRhdGEuZm9yRWFjaChmdW5jdGlvbiAoZCkge1xuICAgICAgY3JlYXRlRWwoZCwgY29udGFpbmVyKVxuICAgIH0pXG5cbiAgICB2YXIgcGFyZW50ID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihzZWxlY3RvcilcblxuICAgIC8vIFJldHVybiBpZiBubyBwYXJlbnQgaXMgZm91bmQuXG4gICAgaWYgKHBhcmVudCA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgLy8gUmVtb3ZlIGV4aXN0aW5nIGNoaWxkIGlmIGl0IGV4aXN0cy5cbiAgICBpZiAocGFyZW50LmZpcnN0Q2hpbGQpIHtcbiAgICAgIHBhcmVudC5yZW1vdmVDaGlsZChwYXJlbnQuZmlyc3RDaGlsZClcbiAgICB9XG5cbiAgICAvLyBBcHBlbmQgdGhlIEVsZW1lbnRzIHRoYXQgaGF2ZSBiZWVuIGNyZWF0ZWQ7XG4gICAgcmV0dXJuIHBhcmVudC5hcHBlbmRDaGlsZChjb250YWluZXIpXG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGxpbmsgZWxlbWVudC5cbiAgICogQHBhcmFtIHtPYmplY3R9IGRhdGFcbiAgICogQHJldHVybiB7SFRNTEVsZW1lbnR9XG4gICAqL1xuICBmdW5jdGlvbiBjcmVhdGVMaW5rIChkYXRhKSB7XG4gICAgdmFyIGl0ZW0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdsaScpXG4gICAgdmFyIGEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJylcbiAgICBpZiAob3B0aW9ucy5saXN0SXRlbUNsYXNzKSB7XG4gICAgICBpdGVtLnNldEF0dHJpYnV0ZSgnY2xhc3MnLCBvcHRpb25zLmxpc3RJdGVtQ2xhc3MpXG4gICAgfVxuICAgIGlmIChvcHRpb25zLmluY2x1ZGVIdG1sICYmIGRhdGEuY2hpbGROb2Rlcy5sZW5ndGgpIHtcbiAgICAgIGZvckVhY2guY2FsbChkYXRhLmNoaWxkTm9kZXMsIGZ1bmN0aW9uIChub2RlKSB7XG4gICAgICAgIGEuYXBwZW5kQ2hpbGQobm9kZS5jbG9uZU5vZGUodHJ1ZSkpXG4gICAgICB9KVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBEZWZhdWx0IGJlaGF2aW9yLlxuICAgICAgYS50ZXh0Q29udGVudCA9IGRhdGEudGV4dENvbnRlbnRcbiAgICB9XG4gICAgYS5zZXRBdHRyaWJ1dGUoJ2hyZWYnLCAnIycgKyBkYXRhLmlkKVxuICAgIGEuc2V0QXR0cmlidXRlKCdjbGFzcycsIG9wdGlvbnMubGlua0NsYXNzICtcbiAgICAgIFNQQUNFX0NIQVIgKyAnbm9kZS1uYW1lLS0nICsgZGF0YS5ub2RlTmFtZSArXG4gICAgICBTUEFDRV9DSEFSICsgb3B0aW9ucy5leHRyYUxpbmtDbGFzc2VzKVxuICAgIGl0ZW0uYXBwZW5kQ2hpbGQoYSlcbiAgICByZXR1cm4gaXRlbVxuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBsaXN0IGVsZW1lbnQuXG4gICAqIEBwYXJhbSB7Qm9vbGVhbn0gaXNDb2xsYXBzZWRcbiAgICogQHJldHVybiB7SFRNTEVsZW1lbnR9XG4gICAqL1xuICBmdW5jdGlvbiBjcmVhdGVMaXN0IChpc0NvbGxhcHNlZCkge1xuICAgIHZhciBsaXN0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndWwnKVxuICAgIHZhciBjbGFzc2VzID0gb3B0aW9ucy5saXN0Q2xhc3MgK1xuICAgICAgU1BBQ0VfQ0hBUiArIG9wdGlvbnMuZXh0cmFMaXN0Q2xhc3Nlc1xuICAgIGlmIChpc0NvbGxhcHNlZCkge1xuICAgICAgY2xhc3NlcyArPSBTUEFDRV9DSEFSICsgb3B0aW9ucy5jb2xsYXBzaWJsZUNsYXNzXG4gICAgICBjbGFzc2VzICs9IFNQQUNFX0NIQVIgKyBvcHRpb25zLmlzQ29sbGFwc2VkQ2xhc3NcbiAgICB9XG4gICAgbGlzdC5zZXRBdHRyaWJ1dGUoJ2NsYXNzJywgY2xhc3NlcylcbiAgICByZXR1cm4gbGlzdFxuICB9XG5cbiAgLyoqXG4gICAqIFVwZGF0ZSBmaXhlZCBzaWRlYmFyIGNsYXNzLlxuICAgKiBAcmV0dXJuIHtIVE1MRWxlbWVudH1cbiAgICovXG4gIGZ1bmN0aW9uIHVwZGF0ZUZpeGVkU2lkZWJhckNsYXNzICgpIHtcbiAgICB2YXIgdG9wID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcCB8fCBib2R5LnNjcm9sbFRvcFxuICAgIHZhciBwb3NGaXhlZEVsID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihvcHRpb25zLnBvc2l0aW9uRml4ZWRTZWxlY3RvcilcblxuICAgIGlmIChvcHRpb25zLmZpeGVkU2lkZWJhck9mZnNldCA9PT0gJ2F1dG8nKSB7XG4gICAgICBvcHRpb25zLmZpeGVkU2lkZWJhck9mZnNldCA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3Iob3B0aW9ucy50b2NTZWxlY3Rvcikub2Zmc2V0VG9wXG4gICAgfVxuXG4gICAgaWYgKHRvcCA+IG9wdGlvbnMuZml4ZWRTaWRlYmFyT2Zmc2V0KSB7XG4gICAgICBpZiAocG9zRml4ZWRFbC5jbGFzc05hbWUuaW5kZXhPZihvcHRpb25zLnBvc2l0aW9uRml4ZWRDbGFzcykgPT09IC0xKSB7XG4gICAgICAgIHBvc0ZpeGVkRWwuY2xhc3NOYW1lICs9IFNQQUNFX0NIQVIgKyBvcHRpb25zLnBvc2l0aW9uRml4ZWRDbGFzc1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBwb3NGaXhlZEVsLmNsYXNzTmFtZSA9IHBvc0ZpeGVkRWwuY2xhc3NOYW1lLnNwbGl0KFNQQUNFX0NIQVIgKyBvcHRpb25zLnBvc2l0aW9uRml4ZWRDbGFzcykuam9pbignJylcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVXBkYXRlIFRPQyBoaWdobGlnaHRpbmcgYW5kIGNvbGxwYXNlZCBncm91cGluZ3MuXG4gICAqL1xuICBmdW5jdGlvbiB1cGRhdGVUb2MgKGhlYWRpbmdzQXJyYXkpIHtcbiAgICB2YXIgdG9wID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcCB8fCBib2R5LnNjcm9sbFRvcFxuXG4gICAgLy8gQWRkIGZpeGVkIGNsYXNzIGF0IG9mZnNldDtcbiAgICBpZiAob3B0aW9ucy5wb3NpdGlvbkZpeGVkU2VsZWN0b3IpIHtcbiAgICAgIHVwZGF0ZUZpeGVkU2lkZWJhckNsYXNzKClcbiAgICB9XG5cbiAgICAvLyBHZXQgdGhlIHRvcCBtb3N0IGhlYWRpbmcgY3VycmVudGx5IHZpc2libGUgb24gdGhlIHBhZ2Ugc28gd2Uga25vdyB3aGF0IHRvIGhpZ2hsaWdodC5cbiAgICB2YXIgaGVhZGluZ3MgPSBoZWFkaW5nc0FycmF5XG4gICAgdmFyIHRvcEhlYWRlclxuICAgIC8vIFVzaW5nIHNvbWUgaW5zdGVhZCBvZiBlYWNoIHNvIHRoYXQgd2UgY2FuIGVzY2FwZSBlYXJseS5cbiAgICBpZiAoY3VycmVudGx5SGlnaGxpZ2h0aW5nICYmXG4gICAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKG9wdGlvbnMudG9jU2VsZWN0b3IpICE9PSBudWxsICYmXG4gICAgICBoZWFkaW5ncy5sZW5ndGggPiAwKSB7XG4gICAgICBzb21lLmNhbGwoaGVhZGluZ3MsIGZ1bmN0aW9uIChoZWFkaW5nLCBpKSB7XG4gICAgICAgIGlmIChoZWFkaW5nLm9mZnNldFRvcCA+IHRvcCArIG9wdGlvbnMuaGVhZGluZ3NPZmZzZXQgKyAxMCkge1xuICAgICAgICAgIC8vIERvbid0IGFsbG93IG5lZ2F0aXZlIGluZGV4IHZhbHVlLlxuICAgICAgICAgIHZhciBpbmRleCA9IChpID09PSAwKSA/IGkgOiBpIC0gMVxuICAgICAgICAgIHRvcEhlYWRlciA9IGhlYWRpbmdzW2luZGV4XVxuICAgICAgICAgIHJldHVybiB0cnVlXG4gICAgICAgIH0gZWxzZSBpZiAoaSA9PT0gaGVhZGluZ3MubGVuZ3RoIC0gMSkge1xuICAgICAgICAgIC8vIFRoaXMgYWxsb3dzIHNjcm9sbGluZyBmb3IgdGhlIGxhc3QgaGVhZGluZyBvbiB0aGUgcGFnZS5cbiAgICAgICAgICB0b3BIZWFkZXIgPSBoZWFkaW5nc1toZWFkaW5ncy5sZW5ndGggLSAxXVxuICAgICAgICAgIHJldHVybiB0cnVlXG4gICAgICAgIH1cbiAgICAgIH0pXG5cbiAgICAgIC8vIFJlbW92ZSB0aGUgYWN0aXZlIGNsYXNzIGZyb20gdGhlIG90aGVyIHRvY0xpbmtzLlxuICAgICAgdmFyIHRvY0xpbmtzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihvcHRpb25zLnRvY1NlbGVjdG9yKVxuICAgICAgICAucXVlcnlTZWxlY3RvckFsbCgnLicgKyBvcHRpb25zLmxpbmtDbGFzcylcbiAgICAgIGZvckVhY2guY2FsbCh0b2NMaW5rcywgZnVuY3Rpb24gKHRvY0xpbmspIHtcbiAgICAgICAgdG9jTGluay5jbGFzc05hbWUgPSB0b2NMaW5rLmNsYXNzTmFtZS5zcGxpdChTUEFDRV9DSEFSICsgb3B0aW9ucy5hY3RpdmVMaW5rQ2xhc3MpLmpvaW4oJycpXG4gICAgICB9KVxuXG4gICAgICAvLyBBZGQgdGhlIGFjdGl2ZSBjbGFzcyB0byB0aGUgYWN0aXZlIHRvY0xpbmsuXG4gICAgICB2YXIgYWN0aXZlVG9jTGluayA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3Iob3B0aW9ucy50b2NTZWxlY3RvcilcbiAgICAgICAgLnF1ZXJ5U2VsZWN0b3IoJy4nICsgb3B0aW9ucy5saW5rQ2xhc3MgK1xuICAgICAgICAgICcubm9kZS1uYW1lLS0nICsgdG9wSGVhZGVyLm5vZGVOYW1lICtcbiAgICAgICAgICAnW2hyZWY9XCIjJyArIHRvcEhlYWRlci5pZCArICdcIl0nKVxuICAgICAgYWN0aXZlVG9jTGluay5jbGFzc05hbWUgKz0gU1BBQ0VfQ0hBUiArIG9wdGlvbnMuYWN0aXZlTGlua0NsYXNzXG5cbiAgICAgIHZhciB0b2NMaXN0cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3Iob3B0aW9ucy50b2NTZWxlY3RvcilcbiAgICAgICAgLnF1ZXJ5U2VsZWN0b3JBbGwoJy4nICsgb3B0aW9ucy5saXN0Q2xhc3MgKyAnLicgKyBvcHRpb25zLmNvbGxhcHNpYmxlQ2xhc3MpXG5cbiAgICAgIC8vIENvbGxhcHNlIHRoZSBvdGhlciBjb2xsYXBzaWJsZSBsaXN0cy5cbiAgICAgIGZvckVhY2guY2FsbCh0b2NMaXN0cywgZnVuY3Rpb24gKGxpc3QpIHtcbiAgICAgICAgdmFyIGNvbGxhcHNlZENsYXNzID0gU1BBQ0VfQ0hBUiArIG9wdGlvbnMuaXNDb2xsYXBzZWRDbGFzc1xuICAgICAgICBpZiAobGlzdC5jbGFzc05hbWUuaW5kZXhPZihjb2xsYXBzZWRDbGFzcykgPT09IC0xKSB7XG4gICAgICAgICAgbGlzdC5jbGFzc05hbWUgKz0gU1BBQ0VfQ0hBUiArIG9wdGlvbnMuaXNDb2xsYXBzZWRDbGFzc1xuICAgICAgICB9XG4gICAgICB9KVxuXG4gICAgICAvLyBFeHBhbmQgdGhlIGFjdGl2ZSBsaW5rJ3MgY29sbGFwc2libGUgbGlzdCBhbmQgaXRzIHNpYmxpbmcgaWYgYXBwbGljYWJsZS5cbiAgICAgIGlmIChhY3RpdmVUb2NMaW5rLm5leHRTaWJsaW5nKSB7XG4gICAgICAgIGFjdGl2ZVRvY0xpbmsubmV4dFNpYmxpbmcuY2xhc3NOYW1lID0gYWN0aXZlVG9jTGluay5uZXh0U2libGluZy5jbGFzc05hbWUuc3BsaXQoU1BBQ0VfQ0hBUiArIG9wdGlvbnMuaXNDb2xsYXBzZWRDbGFzcykuam9pbignJylcbiAgICAgIH1cbiAgICAgIHJlbW92ZUNvbGxhcHNlZEZyb21QYXJlbnRzKGFjdGl2ZVRvY0xpbmsucGFyZW50Tm9kZS5wYXJlbnROb2RlKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZW1vdmUgY29sbHBhc2VkIGNsYXNzIGZyb20gcGFyZW50IGVsZW1lbnRzLlxuICAgKiBAcGFyYW0ge0hUTUxFbGVtZW50fSBlbGVtZW50XG4gICAqIEByZXR1cm4ge0hUTUxFbGVtZW50fVxuICAgKi9cbiAgZnVuY3Rpb24gcmVtb3ZlQ29sbGFwc2VkRnJvbVBhcmVudHMgKGVsZW1lbnQpIHtcbiAgICBpZiAoZWxlbWVudC5jbGFzc05hbWUuaW5kZXhPZihvcHRpb25zLmNvbGxhcHNpYmxlQ2xhc3MpICE9PSAtMSkge1xuICAgICAgZWxlbWVudC5jbGFzc05hbWUgPSBlbGVtZW50LmNsYXNzTmFtZS5zcGxpdChTUEFDRV9DSEFSICsgb3B0aW9ucy5pc0NvbGxhcHNlZENsYXNzKS5qb2luKCcnKVxuICAgICAgcmV0dXJuIHJlbW92ZUNvbGxhcHNlZEZyb21QYXJlbnRzKGVsZW1lbnQucGFyZW50Tm9kZS5wYXJlbnROb2RlKVxuICAgIH1cbiAgICByZXR1cm4gZWxlbWVudFxuICB9XG5cbiAgLyoqXG4gICAqIERpc2FibGUgVE9DIEFuaW1hdGlvbiB3aGVuIGEgbGluayBpcyBjbGlja2VkLlxuICAgKiBAcGFyYW0ge0V2ZW50fSBldmVudFxuICAgKi9cbiAgZnVuY3Rpb24gZGlzYWJsZVRvY0FuaW1hdGlvbiAoZXZlbnQpIHtcbiAgICB2YXIgdGFyZ2V0ID0gZXZlbnQudGFyZ2V0IHx8IGV2ZW50LnNyY0VsZW1lbnRcbiAgICBpZiAodHlwZW9mIHRhcmdldC5jbGFzc05hbWUgIT09ICdzdHJpbmcnIHx8IHRhcmdldC5jbGFzc05hbWUuaW5kZXhPZihvcHRpb25zLmxpbmtDbGFzcykgPT09IC0xKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG4gICAgLy8gQmluZCB0byB0b2NMaW5rIGNsaWNrcyB0byB0ZW1wb3JhcmlseSBkaXNhYmxlIGhpZ2hsaWdodGluZ1xuICAgIC8vIHdoaWxlIHNtb290aFNjcm9sbCBpcyBhbmltYXRpbmcuXG4gICAgY3VycmVudGx5SGlnaGxpZ2h0aW5nID0gZmFsc2VcbiAgfVxuXG4gIC8qKlxuICAgKiBFbmFibGUgVE9DIEFuaW1hdGlvbi5cbiAgICovXG4gIGZ1bmN0aW9uIGVuYWJsZVRvY0FuaW1hdGlvbiAoKSB7XG4gICAgY3VycmVudGx5SGlnaGxpZ2h0aW5nID0gdHJ1ZVxuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBlbmFibGVUb2NBbmltYXRpb246IGVuYWJsZVRvY0FuaW1hdGlvbixcbiAgICBkaXNhYmxlVG9jQW5pbWF0aW9uOiBkaXNhYmxlVG9jQW5pbWF0aW9uLFxuICAgIHJlbmRlcjogcmVuZGVyLFxuICAgIHVwZGF0ZVRvYzogdXBkYXRlVG9jXG4gIH1cbn1cblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vc3JjL2pzL2J1aWxkLWh0bWwuanNcbi8vIG1vZHVsZSBpZCA9IDJcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOyIsInNvdXJjZVJvb3QiOiIifQ=="); - -/***/ }), -/* 3 */ -/* unknown exports provided */ -/* all exports used */ -/*!***********************************!*\ - !*** ./src/js/default-options.js ***! - \***********************************/ -/***/ (function(module, exports) { - -eval("module.exports = {\n // Where to render the table of contents.\n tocSelector: '.js-toc',\n // Where to grab the headings to build the table of contents.\n contentSelector: '.js-toc-content',\n // Which headings to grab inside of the contentSelector element.\n headingSelector: 'h1, h2, h3',\n // Headings that match the ignoreSelector will be skipped.\n ignoreSelector: '.js-toc-ignore',\n // Main class to add to links.\n linkClass: 'toc-link',\n // Extra classes to add to links.\n extraLinkClasses: '',\n // Class to add to active links,\n // the link corresponding to the top most heading on the page.\n activeLinkClass: 'is-active-link',\n // Main class to add to lists.\n listClass: 'toc-list',\n // Extra classes to add to lists.\n extraListClasses: '',\n // Class that gets added when a list should be collapsed.\n isCollapsedClass: 'is-collapsed',\n // Class that gets added when a list should be able\n // to be collapsed but isn't necessarily collpased.\n collapsibleClass: 'is-collapsible',\n // Class to add to list items.\n listItemClass: 'toc-list-item',\n // How many heading levels should not be collpased.\n // For example, number 6 will show everything since\n // there are only 6 heading levels and number 0 will collpase them all.\n // The sections that are hidden will open\n // and close as you scroll to headings within them.\n collapseDepth: 0,\n // Smooth scrolling enabled.\n smoothScroll: true,\n // Smooth scroll duration.\n smoothScrollDuration: 420,\n // Callback for scroll end (requires: smoothScroll).\n scrollEndCallback: function (e) {},\n // Headings offset between the headings and the top of the document.\n headingsOffset: 0,\n // Timeout between events firing to make sure it's\n // not too rapid (for performance reasons).\n throttleTimeout: 50,\n // Element to add the positionFixedClass to.\n positionFixedSelector: null,\n // Fixed position class to add to make sidebar fixed after scrolling\n // down past the fixedSidebarOffset.\n positionFixedClass: 'is-position-fixed',\n // fixedSidebarOffset can be any number but by default is set\n // to auto which sets the fixedSidebarOffset to the sidebar\n // element's offsetTop from the top of the document on init.\n fixedSidebarOffset: 'auto',\n // includeHtml can be set to true to include the HTML markup from the\n // heading node instead of just including the textContent.\n includeHtml: false\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9kZWZhdWx0LW9wdGlvbnMuanM/MTg1MSJdLCJzb3VyY2VzQ29udGVudCI6WyJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgLy8gV2hlcmUgdG8gcmVuZGVyIHRoZSB0YWJsZSBvZiBjb250ZW50cy5cbiAgdG9jU2VsZWN0b3I6ICcuanMtdG9jJyxcbiAgLy8gV2hlcmUgdG8gZ3JhYiB0aGUgaGVhZGluZ3MgdG8gYnVpbGQgdGhlIHRhYmxlIG9mIGNvbnRlbnRzLlxuICBjb250ZW50U2VsZWN0b3I6ICcuanMtdG9jLWNvbnRlbnQnLFxuICAvLyBXaGljaCBoZWFkaW5ncyB0byBncmFiIGluc2lkZSBvZiB0aGUgY29udGVudFNlbGVjdG9yIGVsZW1lbnQuXG4gIGhlYWRpbmdTZWxlY3RvcjogJ2gxLCBoMiwgaDMnLFxuICAvLyBIZWFkaW5ncyB0aGF0IG1hdGNoIHRoZSBpZ25vcmVTZWxlY3RvciB3aWxsIGJlIHNraXBwZWQuXG4gIGlnbm9yZVNlbGVjdG9yOiAnLmpzLXRvYy1pZ25vcmUnLFxuICAvLyBNYWluIGNsYXNzIHRvIGFkZCB0byBsaW5rcy5cbiAgbGlua0NsYXNzOiAndG9jLWxpbmsnLFxuICAvLyBFeHRyYSBjbGFzc2VzIHRvIGFkZCB0byBsaW5rcy5cbiAgZXh0cmFMaW5rQ2xhc3NlczogJycsXG4gIC8vIENsYXNzIHRvIGFkZCB0byBhY3RpdmUgbGlua3MsXG4gIC8vIHRoZSBsaW5rIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHRvcCBtb3N0IGhlYWRpbmcgb24gdGhlIHBhZ2UuXG4gIGFjdGl2ZUxpbmtDbGFzczogJ2lzLWFjdGl2ZS1saW5rJyxcbiAgLy8gTWFpbiBjbGFzcyB0byBhZGQgdG8gbGlzdHMuXG4gIGxpc3RDbGFzczogJ3RvYy1saXN0JyxcbiAgLy8gRXh0cmEgY2xhc3NlcyB0byBhZGQgdG8gbGlzdHMuXG4gIGV4dHJhTGlzdENsYXNzZXM6ICcnLFxuICAvLyBDbGFzcyB0aGF0IGdldHMgYWRkZWQgd2hlbiBhIGxpc3Qgc2hvdWxkIGJlIGNvbGxhcHNlZC5cbiAgaXNDb2xsYXBzZWRDbGFzczogJ2lzLWNvbGxhcHNlZCcsXG4gIC8vIENsYXNzIHRoYXQgZ2V0cyBhZGRlZCB3aGVuIGEgbGlzdCBzaG91bGQgYmUgYWJsZVxuICAvLyB0byBiZSBjb2xsYXBzZWQgYnV0IGlzbid0IG5lY2Vzc2FyaWx5IGNvbGxwYXNlZC5cbiAgY29sbGFwc2libGVDbGFzczogJ2lzLWNvbGxhcHNpYmxlJyxcbiAgLy8gQ2xhc3MgdG8gYWRkIHRvIGxpc3QgaXRlbXMuXG4gIGxpc3RJdGVtQ2xhc3M6ICd0b2MtbGlzdC1pdGVtJyxcbiAgLy8gSG93IG1hbnkgaGVhZGluZyBsZXZlbHMgc2hvdWxkIG5vdCBiZSBjb2xscGFzZWQuXG4gIC8vIEZvciBleGFtcGxlLCBudW1iZXIgNiB3aWxsIHNob3cgZXZlcnl0aGluZyBzaW5jZVxuICAvLyB0aGVyZSBhcmUgb25seSA2IGhlYWRpbmcgbGV2ZWxzIGFuZCBudW1iZXIgMCB3aWxsIGNvbGxwYXNlIHRoZW0gYWxsLlxuICAvLyBUaGUgc2VjdGlvbnMgdGhhdCBhcmUgaGlkZGVuIHdpbGwgb3BlblxuICAvLyBhbmQgY2xvc2UgYXMgeW91IHNjcm9sbCB0byBoZWFkaW5ncyB3aXRoaW4gdGhlbS5cbiAgY29sbGFwc2VEZXB0aDogMCxcbiAgLy8gU21vb3RoIHNjcm9sbGluZyBlbmFibGVkLlxuICBzbW9vdGhTY3JvbGw6IHRydWUsXG4gIC8vIFNtb290aCBzY3JvbGwgZHVyYXRpb24uXG4gIHNtb290aFNjcm9sbER1cmF0aW9uOiA0MjAsXG4gIC8vIENhbGxiYWNrIGZvciBzY3JvbGwgZW5kIChyZXF1aXJlczogc21vb3RoU2Nyb2xsKS5cbiAgc2Nyb2xsRW5kQ2FsbGJhY2s6IGZ1bmN0aW9uIChlKSB7fSxcbiAgLy8gSGVhZGluZ3Mgb2Zmc2V0IGJldHdlZW4gdGhlIGhlYWRpbmdzIGFuZCB0aGUgdG9wIG9mIHRoZSBkb2N1bWVudC5cbiAgaGVhZGluZ3NPZmZzZXQ6IDAsXG4gIC8vIFRpbWVvdXQgYmV0d2VlbiBldmVudHMgZmlyaW5nIHRvIG1ha2Ugc3VyZSBpdCdzXG4gIC8vIG5vdCB0b28gcmFwaWQgKGZvciBwZXJmb3JtYW5jZSByZWFzb25zKS5cbiAgdGhyb3R0bGVUaW1lb3V0OiA1MCxcbiAgLy8gRWxlbWVudCB0byBhZGQgdGhlIHBvc2l0aW9uRml4ZWRDbGFzcyB0by5cbiAgcG9zaXRpb25GaXhlZFNlbGVjdG9yOiBudWxsLFxuICAvLyBGaXhlZCBwb3NpdGlvbiBjbGFzcyB0byBhZGQgdG8gbWFrZSBzaWRlYmFyIGZpeGVkIGFmdGVyIHNjcm9sbGluZ1xuICAvLyBkb3duIHBhc3QgdGhlIGZpeGVkU2lkZWJhck9mZnNldC5cbiAgcG9zaXRpb25GaXhlZENsYXNzOiAnaXMtcG9zaXRpb24tZml4ZWQnLFxuICAvLyBmaXhlZFNpZGViYXJPZmZzZXQgY2FuIGJlIGFueSBudW1iZXIgYnV0IGJ5IGRlZmF1bHQgaXMgc2V0XG4gIC8vIHRvIGF1dG8gd2hpY2ggc2V0cyB0aGUgZml4ZWRTaWRlYmFyT2Zmc2V0IHRvIHRoZSBzaWRlYmFyXG4gIC8vIGVsZW1lbnQncyBvZmZzZXRUb3AgZnJvbSB0aGUgdG9wIG9mIHRoZSBkb2N1bWVudCBvbiBpbml0LlxuICBmaXhlZFNpZGViYXJPZmZzZXQ6ICdhdXRvJyxcbiAgLy8gaW5jbHVkZUh0bWwgY2FuIGJlIHNldCB0byB0cnVlIHRvIGluY2x1ZGUgdGhlIEhUTUwgbWFya3VwIGZyb20gdGhlXG4gIC8vIGhlYWRpbmcgbm9kZSBpbnN0ZWFkIG9mIGp1c3QgaW5jbHVkaW5nIHRoZSB0ZXh0Q29udGVudC5cbiAgaW5jbHVkZUh0bWw6IGZhbHNlXG59XG5cblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL3NyYy9qcy9kZWZhdWx0LW9wdGlvbnMuanNcbi8vIG1vZHVsZSBpZCA9IDNcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); - -/***/ }), -/* 4 */ -/* unknown exports provided */ -/* all exports used */ -/*!*********************************!*\ - !*** ./src/js/parse-content.js ***! - \*********************************/ -/***/ (function(module, exports) { - -eval("/**\n * This file is responsible for parsing the content from the DOM and making\n * sure data is nested properly.\n *\n * @author Tim Scanlin\n */\n\nmodule.exports = function parseContent (options) {\n var reduce = [].reduce\n\n /**\n * Get the last item in an array and return a reference to it.\n * @param {Array} array\n * @return {Object}\n */\n function getLastItem (array) {\n return array[array.length - 1]\n }\n\n /**\n * Get heading level for a heading dom node.\n * @param {HTMLElement} heading\n * @return {Number}\n */\n function getHeadingLevel (heading) {\n return +heading.nodeName.split('H').join('')\n }\n\n /**\n * Get important properties from a heading element and store in a plain object.\n * @param {HTMLElement} heading\n * @return {Object}\n */\n function getHeadingObject (heading) {\n var obj = {\n id: heading.id,\n children: [],\n nodeName: heading.nodeName,\n headingLevel: getHeadingLevel(heading),\n textContent: heading.textContent.trim()\n }\n\n if (options.includeHtml) {\n obj.childNodes = heading.childNodes\n }\n\n return obj\n }\n\n /**\n * Add a node to the nested array.\n * @param {Object} node\n * @param {Array} nest\n * @return {Array}\n */\n function addNode (node, nest) {\n var obj = getHeadingObject(node)\n var level = getHeadingLevel(node)\n var array = nest\n var lastItem = getLastItem(array)\n var lastItemLevel = lastItem\n ? lastItem.headingLevel\n : 0\n var counter = level - lastItemLevel\n\n while (counter > 0) {\n lastItem = getLastItem(array)\n if (lastItem && lastItem.children !== undefined) {\n array = lastItem.children\n }\n counter--\n }\n\n if (level >= options.collapseDepth) {\n obj.isCollapsed = true\n }\n\n array.push(obj)\n return array\n }\n\n /**\n * Select headings in content area, exclude any selector in options.ignoreSelector\n * @param {String} contentSelector\n * @param {Array} headingSelector\n * @return {Array}\n */\n function selectHeadings (contentSelector, headingSelector) {\n var selectors = headingSelector\n if (options.ignoreSelector) {\n selectors = headingSelector.split(',')\n .map(function mapSelectors (selector) {\n return selector.trim() + ':not(' + options.ignoreSelector + ')'\n })\n }\n try {\n return document.querySelector(contentSelector)\n .querySelectorAll(selectors)\n } catch (e) {\n console.warn('Element not found: ' + contentSelector); // eslint-disable-line\n return null\n }\n }\n\n /**\n * Nest headings array into nested arrays with 'children' property.\n * @param {Array} headingsArray\n * @return {Object}\n */\n function nestHeadingsArray (headingsArray) {\n return reduce.call(headingsArray, function reducer (prev, curr) {\n var currentHeading = getHeadingObject(curr)\n\n addNode(currentHeading, prev.nest)\n return prev\n }, {\n nest: []\n })\n }\n\n return {\n nestHeadingsArray: nestHeadingsArray,\n selectHeadings: selectHeadings\n }\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9wYXJzZS1jb250ZW50LmpzPzg3ZjAiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGlzIGZpbGUgaXMgcmVzcG9uc2libGUgZm9yIHBhcnNpbmcgdGhlIGNvbnRlbnQgZnJvbSB0aGUgRE9NIGFuZCBtYWtpbmdcbiAqIHN1cmUgZGF0YSBpcyBuZXN0ZWQgcHJvcGVybHkuXG4gKlxuICogQGF1dGhvciBUaW0gU2NhbmxpblxuICovXG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gcGFyc2VDb250ZW50IChvcHRpb25zKSB7XG4gIHZhciByZWR1Y2UgPSBbXS5yZWR1Y2VcblxuICAvKipcbiAgICogR2V0IHRoZSBsYXN0IGl0ZW0gaW4gYW4gYXJyYXkgYW5kIHJldHVybiBhIHJlZmVyZW5jZSB0byBpdC5cbiAgICogQHBhcmFtIHtBcnJheX0gYXJyYXlcbiAgICogQHJldHVybiB7T2JqZWN0fVxuICAgKi9cbiAgZnVuY3Rpb24gZ2V0TGFzdEl0ZW0gKGFycmF5KSB7XG4gICAgcmV0dXJuIGFycmF5W2FycmF5Lmxlbmd0aCAtIDFdXG4gIH1cblxuICAvKipcbiAgICogR2V0IGhlYWRpbmcgbGV2ZWwgZm9yIGEgaGVhZGluZyBkb20gbm9kZS5cbiAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gaGVhZGluZ1xuICAgKiBAcmV0dXJuIHtOdW1iZXJ9XG4gICAqL1xuICBmdW5jdGlvbiBnZXRIZWFkaW5nTGV2ZWwgKGhlYWRpbmcpIHtcbiAgICByZXR1cm4gK2hlYWRpbmcubm9kZU5hbWUuc3BsaXQoJ0gnKS5qb2luKCcnKVxuICB9XG5cbiAgLyoqXG4gICAqIEdldCBpbXBvcnRhbnQgcHJvcGVydGllcyBmcm9tIGEgaGVhZGluZyBlbGVtZW50IGFuZCBzdG9yZSBpbiBhIHBsYWluIG9iamVjdC5cbiAgICogQHBhcmFtIHtIVE1MRWxlbWVudH0gaGVhZGluZ1xuICAgKiBAcmV0dXJuIHtPYmplY3R9XG4gICAqL1xuICBmdW5jdGlvbiBnZXRIZWFkaW5nT2JqZWN0IChoZWFkaW5nKSB7XG4gICAgdmFyIG9iaiA9IHtcbiAgICAgIGlkOiBoZWFkaW5nLmlkLFxuICAgICAgY2hpbGRyZW46IFtdLFxuICAgICAgbm9kZU5hbWU6IGhlYWRpbmcubm9kZU5hbWUsXG4gICAgICBoZWFkaW5nTGV2ZWw6IGdldEhlYWRpbmdMZXZlbChoZWFkaW5nKSxcbiAgICAgIHRleHRDb250ZW50OiBoZWFkaW5nLnRleHRDb250ZW50LnRyaW0oKVxuICAgIH1cblxuICAgIGlmIChvcHRpb25zLmluY2x1ZGVIdG1sKSB7XG4gICAgICBvYmouY2hpbGROb2RlcyA9IGhlYWRpbmcuY2hpbGROb2Rlc1xuICAgIH1cblxuICAgIHJldHVybiBvYmpcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGQgYSBub2RlIHRvIHRoZSBuZXN0ZWQgYXJyYXkuXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBub2RlXG4gICAqIEBwYXJhbSB7QXJyYXl9IG5lc3RcbiAgICogQHJldHVybiB7QXJyYXl9XG4gICAqL1xuICBmdW5jdGlvbiBhZGROb2RlIChub2RlLCBuZXN0KSB7XG4gICAgdmFyIG9iaiA9IGdldEhlYWRpbmdPYmplY3Qobm9kZSlcbiAgICB2YXIgbGV2ZWwgPSBnZXRIZWFkaW5nTGV2ZWwobm9kZSlcbiAgICB2YXIgYXJyYXkgPSBuZXN0XG4gICAgdmFyIGxhc3RJdGVtID0gZ2V0TGFzdEl0ZW0oYXJyYXkpXG4gICAgdmFyIGxhc3RJdGVtTGV2ZWwgPSBsYXN0SXRlbVxuICAgICAgPyBsYXN0SXRlbS5oZWFkaW5nTGV2ZWxcbiAgICAgIDogMFxuICAgIHZhciBjb3VudGVyID0gbGV2ZWwgLSBsYXN0SXRlbUxldmVsXG5cbiAgICB3aGlsZSAoY291bnRlciA+IDApIHtcbiAgICAgIGxhc3RJdGVtID0gZ2V0TGFzdEl0ZW0oYXJyYXkpXG4gICAgICBpZiAobGFzdEl0ZW0gJiYgbGFzdEl0ZW0uY2hpbGRyZW4gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBhcnJheSA9IGxhc3RJdGVtLmNoaWxkcmVuXG4gICAgICB9XG4gICAgICBjb3VudGVyLS1cbiAgICB9XG5cbiAgICBpZiAobGV2ZWwgPj0gb3B0aW9ucy5jb2xsYXBzZURlcHRoKSB7XG4gICAgICBvYmouaXNDb2xsYXBzZWQgPSB0cnVlXG4gICAgfVxuXG4gICAgYXJyYXkucHVzaChvYmopXG4gICAgcmV0dXJuIGFycmF5XG4gIH1cblxuICAvKipcbiAgICogU2VsZWN0IGhlYWRpbmdzIGluIGNvbnRlbnQgYXJlYSwgZXhjbHVkZSBhbnkgc2VsZWN0b3IgaW4gb3B0aW9ucy5pZ25vcmVTZWxlY3RvclxuICAgKiBAcGFyYW0ge1N0cmluZ30gY29udGVudFNlbGVjdG9yXG4gICAqIEBwYXJhbSB7QXJyYXl9IGhlYWRpbmdTZWxlY3RvclxuICAgKiBAcmV0dXJuIHtBcnJheX1cbiAgICovXG4gIGZ1bmN0aW9uIHNlbGVjdEhlYWRpbmdzIChjb250ZW50U2VsZWN0b3IsIGhlYWRpbmdTZWxlY3Rvcikge1xuICAgIHZhciBzZWxlY3RvcnMgPSBoZWFkaW5nU2VsZWN0b3JcbiAgICBpZiAob3B0aW9ucy5pZ25vcmVTZWxlY3Rvcikge1xuICAgICAgc2VsZWN0b3JzID0gaGVhZGluZ1NlbGVjdG9yLnNwbGl0KCcsJylcbiAgICAgICAgLm1hcChmdW5jdGlvbiBtYXBTZWxlY3RvcnMgKHNlbGVjdG9yKSB7XG4gICAgICAgICAgcmV0dXJuIHNlbGVjdG9yLnRyaW0oKSArICc6bm90KCcgKyBvcHRpb25zLmlnbm9yZVNlbGVjdG9yICsgJyknXG4gICAgICAgIH0pXG4gICAgfVxuICAgIHRyeSB7XG4gICAgICByZXR1cm4gZG9jdW1lbnQucXVlcnlTZWxlY3Rvcihjb250ZW50U2VsZWN0b3IpXG4gICAgICAgIC5xdWVyeVNlbGVjdG9yQWxsKHNlbGVjdG9ycylcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBjb25zb2xlLndhcm4oJ0VsZW1lbnQgbm90IGZvdW5kOiAnICsgY29udGVudFNlbGVjdG9yKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZVxuICAgICAgcmV0dXJuIG51bGxcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogTmVzdCBoZWFkaW5ncyBhcnJheSBpbnRvIG5lc3RlZCBhcnJheXMgd2l0aCAnY2hpbGRyZW4nIHByb3BlcnR5LlxuICAgKiBAcGFyYW0ge0FycmF5fSBoZWFkaW5nc0FycmF5XG4gICAqIEByZXR1cm4ge09iamVjdH1cbiAgICovXG4gIGZ1bmN0aW9uIG5lc3RIZWFkaW5nc0FycmF5IChoZWFkaW5nc0FycmF5KSB7XG4gICAgcmV0dXJuIHJlZHVjZS5jYWxsKGhlYWRpbmdzQXJyYXksIGZ1bmN0aW9uIHJlZHVjZXIgKHByZXYsIGN1cnIpIHtcbiAgICAgIHZhciBjdXJyZW50SGVhZGluZyA9IGdldEhlYWRpbmdPYmplY3QoY3VycilcblxuICAgICAgYWRkTm9kZShjdXJyZW50SGVhZGluZywgcHJldi5uZXN0KVxuICAgICAgcmV0dXJuIHByZXZcbiAgICB9LCB7XG4gICAgICBuZXN0OiBbXVxuICAgIH0pXG4gIH1cblxuICByZXR1cm4ge1xuICAgIG5lc3RIZWFkaW5nc0FycmF5OiBuZXN0SGVhZGluZ3NBcnJheSxcbiAgICBzZWxlY3RIZWFkaW5nczogc2VsZWN0SGVhZGluZ3NcbiAgfVxufVxuXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9zcmMvanMvcGFyc2UtY29udGVudC5qc1xuLy8gbW9kdWxlIGlkID0gNFxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Iiwic291cmNlUm9vdCI6IiJ9"); - -/***/ }), -/* 5 */ -/* unknown exports provided */ -/* all exports used */ -/*!*************************!*\ - !*** ./src/js/index.js ***! - \*************************/ -/***/ (function(module, exports, __webpack_require__) { - -eval("/* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**\n * Tocbot\n * Tocbot creates a toble of contents based on HTML headings on a page,\n * this allows users to easily jump to different sections of the document.\n * Tocbot was inspired by tocify (https://gregfranko.com/jquery.tocify.js/).\n * The main differences are that it works natively without any need for jquery or jquery UI).\n *\n * @author Tim Scanlin\n */\n\n/* globals define */\n\n(function (root, factory) {\n if (true) {\n !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory(root)),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?\n\t\t\t\t(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))\n } else if (typeof exports === 'object') {\n module.exports = factory(root)\n } else {\n root.tocbot = factory(root)\n }\n})(typeof global !== 'undefined' ? global : this.window || this.global, function (root) {\n 'use strict'\n\n // Default options.\n var defaultOptions = __webpack_require__(/*! ./default-options.js */ 3)\n // Object to store current options.\n var options = {}\n // Object for public APIs.\n var tocbot = {}\n\n var BuildHtml = __webpack_require__(/*! ./build-html.js */ 2)\n var ParseContent = __webpack_require__(/*! ./parse-content.js */ 4)\n // Keep these variables at top scope once options are passed in.\n var buildHtml\n var parseContent\n\n // Just return if its not a browser.\n if (typeof window === 'undefined') {\n return\n }\n var supports = !!root.document.querySelector && !!root.addEventListener // Feature test\n var headingsArray\n\n // From: https://github.com/Raynos/xtend\n var hasOwnProperty = Object.prototype.hasOwnProperty\n function extend () {\n var target = {}\n for (var i = 0; i < arguments.length; i++) {\n var source = arguments[i]\n for (var key in source) {\n if (hasOwnProperty.call(source, key)) {\n target[key] = source[key]\n }\n }\n }\n return target\n }\n\n // From: https://remysharp.com/2010/07/21/throttling-function-calls\n function throttle (fn, threshhold, scope) {\n threshhold || (threshhold = 250)\n var last\n var deferTimer\n return function () {\n var context = scope || this\n var now = +new Date()\n var args = arguments\n if (last && now < last + threshhold) {\n // hold on to it\n clearTimeout(deferTimer)\n deferTimer = setTimeout(function () {\n last = now\n fn.apply(context, args)\n }, threshhold)\n } else {\n last = now\n fn.apply(context, args)\n }\n }\n }\n\n /**\n * Destroy tocbot.\n */\n tocbot.destroy = function () {\n // Clear HTML.\n try {\n document.querySelector(options.tocSelector).innerHTML = ''\n } catch (e) {\n console.warn('Element not found: ' + options.tocSelector); // eslint-disable-line\n }\n\n // Remove event listeners.\n document.removeEventListener('scroll', this._scrollListener, false)\n document.removeEventListener('resize', this._scrollListener, false)\n if (buildHtml) {\n document.removeEventListener('click', this._clickListener, false)\n }\n }\n\n /**\n * Initialize tocbot.\n * @param {object} customOptions\n */\n tocbot.init = function (customOptions) {\n // feature test\n if (!supports) {\n return\n }\n\n // Merge defaults with user options.\n // Set to options variable at the top.\n options = extend(defaultOptions, customOptions || {})\n this.options = options\n this.state = {}\n\n // Init smooth scroll if enabled (default).\n if (options.smoothScroll) {\n tocbot.zenscroll = __webpack_require__(/*! zenscroll */ 1)\n tocbot.zenscroll.setup(options.smoothScrollDuration)\n }\n\n // Pass options to these modules.\n buildHtml = BuildHtml(options)\n parseContent = ParseContent(options)\n\n // For testing purposes.\n this._buildHtml = buildHtml\n this._parseContent = parseContent\n\n // Destroy it if it exists first.\n tocbot.destroy()\n\n // Get headings array.\n headingsArray = parseContent.selectHeadings(options.contentSelector, options.headingSelector)\n // Return if no headings are found.\n if (headingsArray === null) {\n return\n }\n\n // Build nested headings array.\n var nestedHeadingsObj = parseContent.nestHeadingsArray(headingsArray)\n var nestedHeadings = nestedHeadingsObj.nest\n\n // Render.\n buildHtml.render(options.tocSelector, nestedHeadings)\n\n // Update Sidebar and bind listeners.\n this._scrollListener = throttle(function (e) {\n buildHtml.updateToc(headingsArray)\n var isTop = e && e.target && e.target.scrollingElement && e.target.scrollingElement.scrollTop === 0\n if ((e && e.eventPhase === 0) || isTop) {\n buildHtml.enableTocAnimation()\n buildHtml.updateToc(headingsArray)\n if (options.scrollEndCallback) {\n options.scrollEndCallback(e)\n }\n }\n }, options.throttleTimeout)\n this._scrollListener()\n document.addEventListener('scroll', this._scrollListener, false)\n document.addEventListener('resize', this._scrollListener, false)\n\n // Bind click listeners to disable animation.\n this._clickListener = throttle(function (event) {\n if (options.smoothScroll) {\n buildHtml.disableTocAnimation(event)\n }\n buildHtml.updateToc(headingsArray)\n }, options.throttleTimeout)\n document.addEventListener('click', this._clickListener, false)\n\n return this\n }\n\n /**\n * Refresh tocbot.\n */\n tocbot.refresh = function (customOptions) {\n tocbot.destroy()\n tocbot.init(customOptions || this.options)\n }\n\n // Make tocbot available globally.\n root.tocbot = tocbot\n\n return tocbot\n})\n\n/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(/*! ./../../~/webpack/buildin/global.js */ 0)))//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9qcy9pbmRleC5qcz9iYzY2Il0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVG9jYm90XG4gKiBUb2Nib3QgY3JlYXRlcyBhIHRvYmxlIG9mIGNvbnRlbnRzIGJhc2VkIG9uIEhUTUwgaGVhZGluZ3Mgb24gYSBwYWdlLFxuICogdGhpcyBhbGxvd3MgdXNlcnMgdG8gZWFzaWx5IGp1bXAgdG8gZGlmZmVyZW50IHNlY3Rpb25zIG9mIHRoZSBkb2N1bWVudC5cbiAqIFRvY2JvdCB3YXMgaW5zcGlyZWQgYnkgdG9jaWZ5IChodHRwOi8vZ3JlZ2ZyYW5rby5jb20vanF1ZXJ5LnRvY2lmeS5qcy8pLlxuICogVGhlIG1haW4gZGlmZmVyZW5jZXMgYXJlIHRoYXQgaXQgd29ya3MgbmF0aXZlbHkgd2l0aG91dCBhbnkgbmVlZCBmb3IganF1ZXJ5IG9yIGpxdWVyeSBVSSkuXG4gKlxuICogQGF1dGhvciBUaW0gU2NhbmxpblxuICovXG5cbi8qIGdsb2JhbHMgZGVmaW5lICovXG5cbihmdW5jdGlvbiAocm9vdCwgZmFjdG9yeSkge1xuICBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgZGVmaW5lKFtdLCBmYWN0b3J5KHJvb3QpKVxuICB9IGVsc2UgaWYgKHR5cGVvZiBleHBvcnRzID09PSAnb2JqZWN0Jykge1xuICAgIG1vZHVsZS5leHBvcnRzID0gZmFjdG9yeShyb290KVxuICB9IGVsc2Uge1xuICAgIHJvb3QudG9jYm90ID0gZmFjdG9yeShyb290KVxuICB9XG59KSh0eXBlb2YgZ2xvYmFsICE9PSAndW5kZWZpbmVkJyA/IGdsb2JhbCA6IHRoaXMud2luZG93IHx8IHRoaXMuZ2xvYmFsLCBmdW5jdGlvbiAocm9vdCkge1xuICAndXNlIHN0cmljdCdcblxuICAvLyBEZWZhdWx0IG9wdGlvbnMuXG4gIHZhciBkZWZhdWx0T3B0aW9ucyA9IHJlcXVpcmUoJy4vZGVmYXVsdC1vcHRpb25zLmpzJylcbiAgLy8gT2JqZWN0IHRvIHN0b3JlIGN1cnJlbnQgb3B0aW9ucy5cbiAgdmFyIG9wdGlvbnMgPSB7fVxuICAvLyBPYmplY3QgZm9yIHB1YmxpYyBBUElzLlxuICB2YXIgdG9jYm90ID0ge31cblxuICB2YXIgQnVpbGRIdG1sID0gcmVxdWlyZSgnLi9idWlsZC1odG1sLmpzJylcbiAgdmFyIFBhcnNlQ29udGVudCA9IHJlcXVpcmUoJy4vcGFyc2UtY29udGVudC5qcycpXG4gIC8vIEtlZXAgdGhlc2UgdmFyaWFibGVzIGF0IHRvcCBzY29wZSBvbmNlIG9wdGlvbnMgYXJlIHBhc3NlZCBpbi5cbiAgdmFyIGJ1aWxkSHRtbFxuICB2YXIgcGFyc2VDb250ZW50XG5cbiAgLy8gSnVzdCByZXR1cm4gaWYgaXRzIG5vdCBhIGJyb3dzZXIuXG4gIGlmICh0eXBlb2Ygd2luZG93ID09PSAndW5kZWZpbmVkJykge1xuICAgIHJldHVyblxuICB9XG4gIHZhciBzdXBwb3J0cyA9ICEhcm9vdC5kb2N1bWVudC5xdWVyeVNlbGVjdG9yICYmICEhcm9vdC5hZGRFdmVudExpc3RlbmVyIC8vIEZlYXR1cmUgdGVzdFxuICB2YXIgaGVhZGluZ3NBcnJheVxuXG4gIC8vIEZyb206IGh0dHBzOi8vZ2l0aHViLmNvbS9SYXlub3MveHRlbmRcbiAgdmFyIGhhc093blByb3BlcnR5ID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eVxuICBmdW5jdGlvbiBleHRlbmQgKCkge1xuICAgIHZhciB0YXJnZXQgPSB7fVxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYXJndW1lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgc291cmNlID0gYXJndW1lbnRzW2ldXG4gICAgICBmb3IgKHZhciBrZXkgaW4gc291cmNlKSB7XG4gICAgICAgIGlmIChoYXNPd25Qcm9wZXJ0eS5jYWxsKHNvdXJjZSwga2V5KSkge1xuICAgICAgICAgIHRhcmdldFtrZXldID0gc291cmNlW2tleV1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gdGFyZ2V0XG4gIH1cblxuICAvLyBGcm9tOiBodHRwczovL3JlbXlzaGFycC5jb20vMjAxMC8wNy8yMS90aHJvdHRsaW5nLWZ1bmN0aW9uLWNhbGxzXG4gIGZ1bmN0aW9uIHRocm90dGxlIChmbiwgdGhyZXNoaG9sZCwgc2NvcGUpIHtcbiAgICB0aHJlc2hob2xkIHx8ICh0aHJlc2hob2xkID0gMjUwKVxuICAgIHZhciBsYXN0XG4gICAgdmFyIGRlZmVyVGltZXJcbiAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgdmFyIGNvbnRleHQgPSBzY29wZSB8fCB0aGlzXG4gICAgICB2YXIgbm93ID0gK25ldyBEYXRlKClcbiAgICAgIHZhciBhcmdzID0gYXJndW1lbnRzXG4gICAgICBpZiAobGFzdCAmJiBub3cgPCBsYXN0ICsgdGhyZXNoaG9sZCkge1xuICAgICAgICAvLyBob2xkIG9uIHRvIGl0XG4gICAgICAgIGNsZWFyVGltZW91dChkZWZlclRpbWVyKVxuICAgICAgICBkZWZlclRpbWVyID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgbGFzdCA9IG5vd1xuICAgICAgICAgIGZuLmFwcGx5KGNvbnRleHQsIGFyZ3MpXG4gICAgICAgIH0sIHRocmVzaGhvbGQpXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBsYXN0ID0gbm93XG4gICAgICAgIGZuLmFwcGx5KGNvbnRleHQsIGFyZ3MpXG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIERlc3Ryb3kgdG9jYm90LlxuICAgKi9cbiAgdG9jYm90LmRlc3Ryb3kgPSBmdW5jdGlvbiAoKSB7XG4gICAgLy8gQ2xlYXIgSFRNTC5cbiAgICB0cnkge1xuICAgICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvcihvcHRpb25zLnRvY1NlbGVjdG9yKS5pbm5lckhUTUwgPSAnJ1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGNvbnNvbGUud2FybignRWxlbWVudCBub3QgZm91bmQ6ICcgKyBvcHRpb25zLnRvY1NlbGVjdG9yKTsgLy8gZXNsaW50LWRpc2FibGUtbGluZVxuICAgIH1cblxuICAgIC8vIFJlbW92ZSBldmVudCBsaXN0ZW5lcnMuXG4gICAgZG9jdW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhpcy5fc2Nyb2xsTGlzdGVuZXIsIGZhbHNlKVxuICAgIGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsIHRoaXMuX3Njcm9sbExpc3RlbmVyLCBmYWxzZSlcbiAgICBpZiAoYnVpbGRIdG1sKSB7XG4gICAgICBkb2N1bWVudC5yZW1vdmVFdmVudExpc3RlbmVyKCdjbGljaycsIHRoaXMuX2NsaWNrTGlzdGVuZXIsIGZhbHNlKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplIHRvY2JvdC5cbiAgICogQHBhcmFtIHtvYmplY3R9IGN1c3RvbU9wdGlvbnNcbiAgICovXG4gIHRvY2JvdC5pbml0ID0gZnVuY3Rpb24gKGN1c3RvbU9wdGlvbnMpIHtcbiAgICAvLyBmZWF0dXJlIHRlc3RcbiAgICBpZiAoIXN1cHBvcnRzKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBNZXJnZSBkZWZhdWx0cyB3aXRoIHVzZXIgb3B0aW9ucy5cbiAgICAvLyBTZXQgdG8gb3B0aW9ucyB2YXJpYWJsZSBhdCB0aGUgdG9wLlxuICAgIG9wdGlvbnMgPSBleHRlbmQoZGVmYXVsdE9wdGlvbnMsIGN1c3RvbU9wdGlvbnMgfHwge30pXG4gICAgdGhpcy5vcHRpb25zID0gb3B0aW9uc1xuICAgIHRoaXMuc3RhdGUgPSB7fVxuXG4gICAgLy8gSW5pdCBzbW9vdGggc2Nyb2xsIGlmIGVuYWJsZWQgKGRlZmF1bHQpLlxuICAgIGlmIChvcHRpb25zLnNtb290aFNjcm9sbCkge1xuICAgICAgdG9jYm90LnplbnNjcm9sbCA9IHJlcXVpcmUoJ3plbnNjcm9sbCcpXG4gICAgICB0b2Nib3QuemVuc2Nyb2xsLnNldHVwKG9wdGlvbnMuc21vb3RoU2Nyb2xsRHVyYXRpb24pXG4gICAgfVxuXG4gICAgLy8gUGFzcyBvcHRpb25zIHRvIHRoZXNlIG1vZHVsZXMuXG4gICAgYnVpbGRIdG1sID0gQnVpbGRIdG1sKG9wdGlvbnMpXG4gICAgcGFyc2VDb250ZW50ID0gUGFyc2VDb250ZW50KG9wdGlvbnMpXG5cbiAgICAvLyBGb3IgdGVzdGluZyBwdXJwb3Nlcy5cbiAgICB0aGlzLl9idWlsZEh0bWwgPSBidWlsZEh0bWxcbiAgICB0aGlzLl9wYXJzZUNvbnRlbnQgPSBwYXJzZUNvbnRlbnRcblxuICAgIC8vIERlc3Ryb3kgaXQgaWYgaXQgZXhpc3RzIGZpcnN0LlxuICAgIHRvY2JvdC5kZXN0cm95KClcblxuICAgIC8vIEdldCBoZWFkaW5ncyBhcnJheS5cbiAgICBoZWFkaW5nc0FycmF5ID0gcGFyc2VDb250ZW50LnNlbGVjdEhlYWRpbmdzKG9wdGlvbnMuY29udGVudFNlbGVjdG9yLCBvcHRpb25zLmhlYWRpbmdTZWxlY3RvcilcbiAgICAvLyBSZXR1cm4gaWYgbm8gaGVhZGluZ3MgYXJlIGZvdW5kLlxuICAgIGlmIChoZWFkaW5nc0FycmF5ID09PSBudWxsKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBCdWlsZCBuZXN0ZWQgaGVhZGluZ3MgYXJyYXkuXG4gICAgdmFyIG5lc3RlZEhlYWRpbmdzT2JqID0gcGFyc2VDb250ZW50Lm5lc3RIZWFkaW5nc0FycmF5KGhlYWRpbmdzQXJyYXkpXG4gICAgdmFyIG5lc3RlZEhlYWRpbmdzID0gbmVzdGVkSGVhZGluZ3NPYmoubmVzdFxuXG4gICAgLy8gUmVuZGVyLlxuICAgIGJ1aWxkSHRtbC5yZW5kZXIob3B0aW9ucy50b2NTZWxlY3RvciwgbmVzdGVkSGVhZGluZ3MpXG5cbiAgICAvLyBVcGRhdGUgU2lkZWJhciBhbmQgYmluZCBsaXN0ZW5lcnMuXG4gICAgdGhpcy5fc2Nyb2xsTGlzdGVuZXIgPSB0aHJvdHRsZShmdW5jdGlvbiAoZSkge1xuICAgICAgYnVpbGRIdG1sLnVwZGF0ZVRvYyhoZWFkaW5nc0FycmF5KVxuICAgICAgdmFyIGlzVG9wID0gZSAmJiBlLnRhcmdldCAmJiBlLnRhcmdldC5zY3JvbGxpbmdFbGVtZW50ICYmIGUudGFyZ2V0LnNjcm9sbGluZ0VsZW1lbnQuc2Nyb2xsVG9wID09PSAwXG4gICAgICBpZiAoKGUgJiYgZS5ldmVudFBoYXNlID09PSAwKSB8fCBpc1RvcCkge1xuICAgICAgICBidWlsZEh0bWwuZW5hYmxlVG9jQW5pbWF0aW9uKClcbiAgICAgICAgYnVpbGRIdG1sLnVwZGF0ZVRvYyhoZWFkaW5nc0FycmF5KVxuICAgICAgICBpZiAob3B0aW9ucy5zY3JvbGxFbmRDYWxsYmFjaykge1xuICAgICAgICAgIG9wdGlvbnMuc2Nyb2xsRW5kQ2FsbGJhY2soZSlcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sIG9wdGlvbnMudGhyb3R0bGVUaW1lb3V0KVxuICAgIHRoaXMuX3Njcm9sbExpc3RlbmVyKClcbiAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdzY3JvbGwnLCB0aGlzLl9zY3JvbGxMaXN0ZW5lciwgZmFsc2UpXG4gICAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcigncmVzaXplJywgdGhpcy5fc2Nyb2xsTGlzdGVuZXIsIGZhbHNlKVxuXG4gICAgLy8gQmluZCBjbGljayBsaXN0ZW5lcnMgdG8gZGlzYWJsZSBhbmltYXRpb24uXG4gICAgdGhpcy5fY2xpY2tMaXN0ZW5lciA9IHRocm90dGxlKGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgaWYgKG9wdGlvbnMuc21vb3RoU2Nyb2xsKSB7XG4gICAgICAgIGJ1aWxkSHRtbC5kaXNhYmxlVG9jQW5pbWF0aW9uKGV2ZW50KVxuICAgICAgfVxuICAgICAgYnVpbGRIdG1sLnVwZGF0ZVRvYyhoZWFkaW5nc0FycmF5KVxuICAgIH0sIG9wdGlvbnMudGhyb3R0bGVUaW1lb3V0KVxuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgdGhpcy5fY2xpY2tMaXN0ZW5lciwgZmFsc2UpXG5cbiAgICByZXR1cm4gdGhpc1xuICB9XG5cbiAgLyoqXG4gICAqIFJlZnJlc2ggdG9jYm90LlxuICAgKi9cbiAgdG9jYm90LnJlZnJlc2ggPSBmdW5jdGlvbiAoY3VzdG9tT3B0aW9ucykge1xuICAgIHRvY2JvdC5kZXN0cm95KClcbiAgICB0b2Nib3QuaW5pdChjdXN0b21PcHRpb25zIHx8IHRoaXMub3B0aW9ucylcbiAgfVxuXG4gIC8vIE1ha2UgdG9jYm90IGF2YWlsYWJsZSBnbG9iYWxseS5cbiAgcm9vdC50b2Nib3QgPSB0b2Nib3RcblxuICByZXR1cm4gdG9jYm90XG59KVxuXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9zcmMvanMvaW5kZXguanNcbi8vIG1vZHVsZSBpZCA9IDVcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBIiwic291cmNlUm9vdCI6IiJ9"); - -/***/ }) -/******/ ]); \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js deleted file mode 100644 index 4468ef4e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/tocbot-3.0.2/tocbot.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=5)}([function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){var o,i,r;!function(n,l){i=[],o=l(),void 0!==(r="function"==typeof o?o.apply(t,i):o)&&(e.exports=r)}(0,function(){"use strict";var e=function(e){return"getComputedStyle"in window&&"smooth"===window.getComputedStyle(e)["scroll-behavior"]};if("undefined"==typeof window||!("document"in window))return{};var t=function(t,n,o){n=n||999,o||0===o||(o=9);var i,r=function(e){i=e},l=function(){clearTimeout(i),r(0)},s=function(e){return Math.max(0,t.getTopOf(e)-o)},c=function(o,i,s){if(l(),0===i||i&&i<0||e(t.body))t.toY(o),s&&s();else{var c=t.getY(),a=Math.max(0,o)-c,u=(new Date).getTime();i=i||Math.min(Math.abs(a),n),function e(){r(setTimeout(function(){var n=Math.min(1,((new Date).getTime()-u)/i),o=Math.max(0,Math.floor(c+a*(n<.5?2*n*n:n*(4-2*n)-1)));t.toY(o),n<1&&t.getHeight()+ou?a(e,n,i):l+o>f?c(l-u+o,n,i):i&&i()},d=function(e,n,o,i){c(Math.max(0,t.getTopOf(e)-t.getHeight()/2+(o||e.getBoundingClientRect().height/2)),n,i)};return{setup:function(e,t){return(0===e||e)&&(n=e),(0===t||t)&&(o=t),{defaultDuration:n,edgeOffset:o}},to:a,toY:c,intoView:u,center:d,stop:l,moving:function(){return!!i},getY:t.getY,getTopOf:t.getTopOf}},n=document.documentElement,o=function(){return window.scrollY||n.scrollTop},i=t({body:document.scrollingElement||document.body,toY:function(e){window.scrollTo(0,e)},getY:o,getHeight:function(){return window.innerHeight||n.clientHeight},getTopOf:function(e){return e.getBoundingClientRect().top+o()-n.offsetTop}});if(i.createScroller=function(e,o,i){return t({body:e,toY:function(t){e.scrollTop=t},getY:function(){return e.scrollTop},getHeight:function(){return Math.min(e.clientHeight,window.innerHeight||n.clientHeight)},getTopOf:function(e){return e.offsetTop}},o,i)},"addEventListener"in window&&!window.noZensmooth&&!e(document.body)){var r="scrollRestoration"in history;r&&(history.scrollRestoration="auto"),window.addEventListener("load",function(){r&&(setTimeout(function(){history.scrollRestoration="manual"},9),window.addEventListener("popstate",function(e){e.state&&"zenscrollY"in e.state&&i.toY(e.state.zenscrollY)},!1)),window.location.hash&&setTimeout(function(){var e=i.setup().edgeOffset;if(e){var t=document.getElementById(window.location.href.split("#")[1]);if(t){var n=Math.max(0,i.getTopOf(t)-e),o=i.getY()-n;0<=o&&o<9&&window.scrollTo(0,n)}}},9)},!1);var l=new RegExp("(^|\\s)noZensmooth(\\s|$)");window.addEventListener("click",function(e){for(var t=e.target;t&&"A"!==t.tagName;)t=t.parentNode;if(!(!t||1!==e.which||e.shiftKey||e.metaKey||e.ctrlKey||e.altKey)){if(r)try{history.replaceState({zenscrollY:i.getY()},"")}catch(e){}var n=t.getAttribute("href")||"";if(0===n.indexOf("#")&&!l.test(t.className)){var o=0,s=document.getElementById(n.substring(1));if("#"!==n){if(!s)return;o=i.getTopOf(s)}e.preventDefault();var c=function(){window.location=n},a=i.setup().edgeOffset;a&&(o=Math.max(0,o-a),c=function(){history.pushState(null,"",n)}),i.toY(o,null,c)}}},!1)}return i})},function(e,t){e.exports=function(e){function t(e,n){var r=n.appendChild(o(e));if(e.children.length){var l=i(e.isCollapsed);e.children.forEach(function(e){t(e,l)}),r.appendChild(l)}}function n(e,n){var o=i(!1);n.forEach(function(e){t(e,o)});var r=document.querySelector(e);if(null!==r)return r.firstChild&&r.removeChild(r.firstChild),r.appendChild(o)}function o(t){var n=document.createElement("li"),o=document.createElement("a");return e.listItemClass&&n.setAttribute("class",e.listItemClass),e.includeHtml&&t.childNodes.length?u.call(t.childNodes,function(e){o.appendChild(e.cloneNode(!0))}):o.textContent=t.textContent,o.setAttribute("href","#"+t.id),o.setAttribute("class",e.linkClass+p+"node-name--"+t.nodeName+p+e.extraLinkClasses),n.appendChild(o),n}function i(t){var n=document.createElement("ul"),o=e.listClass+p+e.extraListClasses;return t&&(o+=p+e.collapsibleClass,o+=p+e.isCollapsedClass),n.setAttribute("class",o),n}function r(){var t=document.documentElement.scrollTop||f.scrollTop,n=document.querySelector(e.positionFixedSelector);"auto"===e.fixedSidebarOffset&&(e.fixedSidebarOffset=document.querySelector(e.tocSelector).offsetTop),t>e.fixedSidebarOffset?-1===n.className.indexOf(e.positionFixedClass)&&(n.className+=p+e.positionFixedClass):n.className=n.className.split(p+e.positionFixedClass).join("")}function l(t){var n=document.documentElement.scrollTop||f.scrollTop;e.positionFixedSelector&&r();var o,i=t;if(m&&null!==document.querySelector(e.tocSelector)&&i.length>0){d.call(i,function(t,r){if(t.offsetTop>n+e.headingsOffset+10){return o=i[0===r?r:r-1],!0}if(r===i.length-1)return o=i[i.length-1],!0});var l=document.querySelector(e.tocSelector).querySelectorAll("."+e.linkClass);u.call(l,function(t){t.className=t.className.split(p+e.activeLinkClass).join("")});var c=document.querySelector(e.tocSelector).querySelector("."+e.linkClass+".node-name--"+o.nodeName+'[href="#'+o.id+'"]');c.className+=p+e.activeLinkClass;var a=document.querySelector(e.tocSelector).querySelectorAll("."+e.listClass+"."+e.collapsibleClass);u.call(a,function(t){var n=p+e.isCollapsedClass;-1===t.className.indexOf(n)&&(t.className+=p+e.isCollapsedClass)}),c.nextSibling&&(c.nextSibling.className=c.nextSibling.className.split(p+e.isCollapsedClass).join("")),s(c.parentNode.parentNode)}}function s(t){return-1!==t.className.indexOf(e.collapsibleClass)?(t.className=t.className.split(p+e.isCollapsedClass).join(""),s(t.parentNode.parentNode)):t}function c(t){var n=t.target||t.srcElement;"string"==typeof n.className&&-1!==n.className.indexOf(e.linkClass)&&(m=!1)}function a(){m=!0}var u=[].forEach,d=[].some,f=document.body,m=!0,p=" ";return{enableTocAnimation:a,disableTocAnimation:c,render:n,updateToc:l}}},function(e,t){e.exports={tocSelector:".js-toc",contentSelector:".js-toc-content",headingSelector:"h1, h2, h3",ignoreSelector:".js-toc-ignore",linkClass:"toc-link",extraLinkClasses:"",activeLinkClass:"is-active-link",listClass:"toc-list",extraListClasses:"",isCollapsedClass:"is-collapsed",collapsibleClass:"is-collapsible",listItemClass:"toc-list-item",collapseDepth:0,smoothScroll:!0,smoothScrollDuration:420,scrollEndCallback:function(e){},headingsOffset:0,throttleTimeout:50,positionFixedSelector:null,positionFixedClass:"is-position-fixed",fixedSidebarOffset:"auto",includeHtml:!1}},function(e,t){e.exports=function(e){function t(e){return e[e.length-1]}function n(e){return+e.nodeName.split("H").join("")}function o(t){var o={id:t.id,children:[],nodeName:t.nodeName,headingLevel:n(t),textContent:t.textContent.trim()};return e.includeHtml&&(o.childNodes=t.childNodes),o}function i(i,r){for(var l=o(i),s=n(i),c=r,a=t(c),u=a?a.headingLevel:0,d=s-u;d>0;)a=t(c),a&&void 0!==a.children&&(c=a.children),d--;return s>=e.collapseDepth&&(l.isCollapsed=!0),c.push(l),c}function r(t,n){var o=n;e.ignoreSelector&&(o=n.split(",").map(function(t){return t.trim()+":not("+e.ignoreSelector+")"}));try{return document.querySelector(t).querySelectorAll(o)}catch(e){return console.warn("Element not found: "+t),null}}function l(e){return s.call(e,function(e,t){return i(o(t),e.nest),e},{nest:[]})}var s=[].reduce;return{nestHeadingsArray:l,selectHeadings:r}}},function(e,t,n){(function(o){var i,r,l;!function(n,o){r=[],i=o(n),void 0!==(l="function"==typeof i?i.apply(t,r):i)&&(e.exports=l)}(void 0!==o?o:this.window||this.global,function(e){"use strict";function t(){for(var e={},t=0;t> and <>). - -[[test-engines-junit]] -==== JUnit Test Engines - -JUnit provides three `TestEngine` implementations. - -* `{junit-jupiter-engine}`: The core of JUnit Jupiter. -* `{junit-vintage-engine}`: A thin layer on top of JUnit 4 to allow running _vintage_ - tests (based on JUnit 3.8 and JUnit 4) with the JUnit Platform launcher infrastructure. -* `{junit-platform-suite-engine}`: Executes declarative suites of tests with the JUnit - Platform launcher infrastructure. - -[[test-engines-custom]] -==== Custom Test Engines - -You can contribute your own custom `{TestEngine}` by implementing the interfaces in the -{junit-platform-engine} module and _registering_ your engine. - -Every `TestEngine` must provide its own _unique ID_, _discover_ tests from an -`EngineDiscoveryRequest`, and _execute_ those tests according to an `ExecutionRequest`. - -[WARNING] -.The `junit-` unique ID prefix is reserved for TestEngines from the JUnit Team -==== -The JUnit Platform `Launcher` enforces that only `TestEngine` implementations published -by the JUnit Team may use the `junit-` prefix for their `TestEngine` IDs. - -* If any third-party `TestEngine` claims to be `junit-jupiter` or `junit-vintage`, an - exception will be thrown, immediately halting execution of the JUnit Platform. -* If any third-party `TestEngine` uses the `junit-` prefix for its ID, a warning message - will be logged. Later releases of the JUnit Platform will throw an exception for such - violations. -==== - -In order to facilitate test discovery within IDEs and tools prior to launching the JUnit -Platform, `TestEngine` implementations are encouraged to make use of the `@Testable` -annotation. For example, the `@Test` and `@TestFactory` annotations in JUnit Jupiter are -meta-annotated with `@Testable`. Consult the Javadoc for `{Testable}` for further details. - -If your custom `TestEngine` needs to be configured, consider allowing users to supply -configuration via <>. Please note, -however, that you are strongly encouraged to use a unique prefix for all configuration -parameters supported by your test engine. Doing so will ensure that there are no conflicts -between the names of your configuration parameters and those from other test engines. In -addition, since configuration parameters may be supplied as JVM system properties, it is -wise to avoid conflicts with the names of other system properties. For example, JUnit -Jupiter uses `junit.jupiter.` as a prefix of all of its supported configuration -parameters. Furthermore, as with the warning above regarding the `junit-` prefix for -`TestEngine` IDs, you should not use `junit.` as a prefix for the names of your own -configuration parameters. - -Although there is currently no official guide on how to implement a custom `TestEngine`, -you can consult the implementation of <> or the implementation of -third-party test engines listed in the -https://github.com/junit-team/junit5/wiki/Third-party-Extensions#junit-platform-test-engines[JUnit 5 wiki]. -You will also find various tutorials and blogs on the Internet that demonstrate how to -write a custom `TestEngine`. - -NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation of the -`TestEngine` SPI (used by the `{junit-jupiter-engine}`) that only requires implementors to -provide the logic for test discovery. It implements execution of `TestDescriptors` that -implement the `Node` interface, including support for parallel execution. - -[[test-engines-registration]] -==== Registering a TestEngine - -`TestEngine` registration is supported via Java's `{ServiceLoader}` mechanism. - -For example, the `junit-jupiter-engine` module registers its -`org.junit.jupiter.engine.JupiterTestEngine` in a file named -`org.junit.platform.engine.TestEngine` within the `/META-INF/services` folder in the -`junit-jupiter-engine` JAR. - -[[test-engines-requirements]] -==== Requirements - -NOTE: The words "must", "must not", "required", "shall", "shall not", "should", "should -not", "recommended", "may", and "optional" in this section are to be interpreted as -described in https://www.ietf.org/rfc/rfc2119.txt[RFC 2119.] - -[[test-engines-requirements-mandatory]] -===== Mandatory requirements - -For interoperability with build tools and IDEs, `TestEngine` implementations must adhere -to the following requirements: - -* The `TestDescriptor` returned from `TestEngine.discover()` _must_ be the root of a tree - of `TestDescriptor` instances. This implies that there _must not_ be any cycles between - a node and its descendants. -* A `TestEngine` _must_ be able to discover `UniqueIdSelectors` for any unique ID that it - previously generated and returned from `TestEngine.discover()`. This enables selecting a - subset of tests to execute or rerun. -* The `executionSkipped`, `executionStarted`, and `executionFinished` methods of the - `EngineExecutionListener` passed to `TestEngine.execute()` _must_ be called for every - `TestDescriptor` node in the tree returned from `TestEngine.discover()` at most - once. Parent nodes _must_ be reported as started before their children and as finished - after their children. If a node is reported as skipped, there _must not_ be any events - reported for its descendants. - -[[test-engines-requirements-enhanced-compatibility]] -===== Enhanced compatibility - -Adhering to the following requirements is optional but recommended for enhanced -compatibility with build tools and IDEs: - -* Unless to indicate an empty discovery result, the `TestDescriptor` returned from - `TestEngine.discover()` _should_ have children rather than being completely dynamic. - This allows tools to display the structure of the tests and to select a subset of tests - to execute. -* When resolving `UniqueIdSelectors`, a `TestEngine` _should_ only return `TestDescriptor` - instances with matching unique IDs including their ancestors but _may_ return additional - siblings or other nodes that are required for the execution of the selected tests. -* `TestEngines` _should_ support <> tests and containers so - that tag filters can be applied when discovering tests. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc deleted file mode 100644 index 655d6bc7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc +++ /dev/null @@ -1,138 +0,0 @@ -[[junit-platform-reporting]] -=== JUnit Platform Reporting - -The `junit-platform-reporting` artifact contains `{TestExecutionListener}` implementations -that generate XML test reports in two flavors: -<> and -<>. - -NOTE: The module also contains other `TestExecutionListener` implementations that can be -used to build custom reporting. See <> for details. - -[[junit-platform-reporting-legacy-xml]] -==== Legacy XML format - -`{LegacyXmlReportGeneratingListener}` generates a separate XML report for each root in the -`{TestPlan}`. Note that the generated XML format is compatible with the de facto standard -for JUnit 4 based test reports that was made popular by the Ant build system. - -The `LegacyXmlReportGeneratingListener` is used by the <> -as well. - -[[junit-platform-reporting-open-test-reporting]] -==== Open Test Reporting XML format - -`{OpenTestReportGeneratingListener}` writes an XML report for the entire execution in the -event-based format specified by {OpenTestReporting} which supports all features of the -JUnit Platform such as hierarchical test structures, display names, tags, etc. - -The listener is auto-registered and can be configured via the following -<>: - -`junit.platform.reporting.open.xml.enabled=true|false`:: - Enable/disable writing the report. -`junit.platform.reporting.output.dir=`:: - Configure the output directory for the reports. By default, `build` is used if a Gradle - build script is found, and `target` if a Maven POM is found; otherwise, the current - working directory is used. - -If enabled, the listener creates an XML report file named -`junit-platform-events-.xml` per test run in the configured output directory. - -TIP: The {OpenTestReportingCliTool} can be used to convert from the event-based format to -the hierarchical format which is more human-readable. - -===== Gradle - -For Gradle, writing Open Test Reporting compatible XML reports can be enabled and -configured via system properties. The following samples configure its output directory to -be the same directory Gradle uses for its own XML reports. A `CommandLineArgumentProvider` -is used to keep the tasks relocatable across different machines which is important when -using Gradle's Build Cache. - -[source,groovy,indent=0] -[subs=attributes+] -.Groovy DSL ----- -dependencies { - testRuntimeOnly("org.junit.platform:junit-platform-reporting:{platform-version}") -} -tasks.withType(Test).configureEach { - def outputDir = reports.junitXml.outputLocation - jvmArgumentProviders << ({ - [ - "-Djunit.platform.reporting.open.xml.enabled=true", - "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" - ] - } as CommandLineArgumentProvider) -} ----- - -[source,kotlin,indent=0] -[subs=attributes+] -.Kotlin DSL ----- -dependencies { - testRuntimeOnly("org.junit.platform:junit-platform-reporting:{platform-version}") -} -tasks.withType().configureEach { - val outputDir = reports.junitXml.outputLocation - jvmArgumentProviders += CommandLineArgumentProvider { - listOf( - "-Djunit.platform.reporting.open.xml.enabled=true", - "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" - ) - } -} ----- - -===== Maven - -For Maven Surefire/Failsafe, you can enable Open Test Reporting output and configure the -resulting XML files to be written to the same directory Surefire/Failsafe uses for its own -XML reports as follows: - -[source,xml,indent=0] -[subs=attributes+] ----- - - - - - org.junit.platform - junit-platform-reporting - {platform-version} - test - - - - - - maven-surefire-plugin - {surefire-version} - - - - junit.platform.reporting.open.xml.enabled = true - junit.platform.reporting.output.dir = target/surefire-reports - - - - - - - - ----- - -===== Console Launcher - -When using the <>, you can enable Open Test Reporting -output by setting the configuration parameters via `--config`: - -[source,console,subs=attributes+] ----- -$ java -jar junit-platform-console-standalone-{platform-version}.jar \ - --config=junit.platform.reporting.open.xml.enabled=true \ - --config=junit.platform.reporting.output.dir=reports ----- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc deleted file mode 100644 index d38a312d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc +++ /dev/null @@ -1,50 +0,0 @@ -[[junit-platform-suite-engine]] -=== JUnit Platform Suite Engine - -The JUnit Platform supports the declarative definition and execution of suites of tests -from _any_ test engine using the JUnit Platform. - -[[junit-platform-suite-engine-setup]] -==== Setup - -In addition to the `junit-platform-suite-api` and `junit-platform-suite-engine` artifacts, -you need _at least one_ other test engine and its dependencies on the classpath. See -<> for details regarding group IDs, artifact IDs, and versions. - -[[junit-platform-suite-engine-setup-required-dependencies]] -===== Required Dependencies - -* `junit-platform-suite-api` in _test_ scope: artifact containing annotations needed to - configure a test suite -* `junit-platform-suite-engine` in _test runtime_ scope: implementation of the - `TestEngine` API for declarative test suites - -NOTE: Both of the required dependencies are aggregated in the `junit-platform-suite` -artifact which can be declared in _test_ scope instead of declaring explicit dependencies -on `junit-platform-suite-api` and `junit-platform-suite-engine`. - -[[junit-platform-suite-engine-setup-transitive-dependencies]] -===== Transitive Dependencies - -* `junit-platform-suite-commons` in _test_ scope -* `junit-platform-launcher` in _test_ scope -* `junit-platform-engine` in _test_ scope -* `junit-platform-commons` in _test_ scope -* `opentest4j` in _test_ scope - -[[junit-platform-suite-engine-example]] -==== @Suite Example - -By annotating a class with `@Suite` it is marked as a test suite on the JUnit Platform. -As seen in the following example, selector and filter annotations can then be used to -control the contents of the suite. - -[source,java,indent=0] ----- -include::{testDir}/example/SuiteDemo.java[tags=user_guide] ----- - -.Additional Configuration Options -NOTE: There are numerous configuration options for discovering and filtering tests in a -test suite. Please consult the Javadoc of the `{suite-api-package}` package for a full -list of supported annotations and further details. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc deleted file mode 100644 index 2c8b25e7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc +++ /dev/null @@ -1,243 +0,0 @@ -[[launcher-api]] -=== JUnit Platform Launcher API - -One of the prominent goals of JUnit 5 is to make the interface between JUnit and its -programmatic clients – build tools and IDEs – more powerful and stable. The purpose is to -decouple the internals of discovering and executing tests from all the filtering and -configuration that's necessary from the outside. - -JUnit 5 introduces the concept of a `Launcher` that can be used to discover, filter, and -execute tests. Moreover, third party test libraries – like Spock, Cucumber, and FitNesse -– can plug into the JUnit Platform's launching infrastructure by providing a custom -<>. - -The launcher API is in the `{junit-platform-launcher}` module. - -An example consumer of the launcher API is the `{ConsoleLauncher}` in the -`{junit-platform-console}` project. - -[[launcher-api-discovery]] -==== Discovering Tests - -Having _test discovery_ as a dedicated feature of the platform itself frees IDEs and build -tools from most of the difficulties they had to go through to identify test classes and -test methods in previous versions of JUnit. - -Usage Example: - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=imports] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=discovery] ----- - -You can select classes, methods, and all classes in a package or even search for all tests -in the class-path or module-path. Discovery takes place across all participating test -engines. - -The resulting `TestPlan` is a hierarchical (and read-only) description of all engines, -classes, and test methods that fit the `LauncherDiscoveryRequest`. The client can -traverse the tree, retrieve details about a node, and get a link to the original source -(like class, method, or file position). Every node in the test plan has a _unique ID_ -that can be used to invoke a particular test or group of tests. - -Clients can register one or more `{LauncherDiscoveryListener}` implementations via the -`{LauncherDiscoveryRequestBuilder}` to gain insight into events that occur during test -discovery. By default, the builder registers an "abort on failure" listener that aborts -test discovery after the first discovery failure is encountered. The default -`LauncherDiscoveryListener` can be changed via the -`junit.platform.discovery.listener.default` <>. - -[[launcher-api-execution]] -==== Executing Tests - -To execute tests, clients can use the same `LauncherDiscoveryRequest` as in the discovery -phase or create a new request. Test progress and reporting can be achieved by registering -one or more `{TestExecutionListener}` implementations with the `Launcher` as in the -following example. - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=execution] ----- - -There is no return value for the `execute()` method, but you can use a -`TestExecutionListener` to aggregate the results. For examples see the -`{SummaryGeneratingListener}`, `{LegacyXmlReportGeneratingListener}`, and -`{UniqueIdTrackingListener}`. - -[[launcher-api-engines-custom]] -==== Registering a TestEngine - -See the dedicated section on <> for -details. - -[[launcher-api-post-discovery-filters-custom]] -==== Registering a PostDiscoveryFilter - -In addition to specifying post-discovery filters as part of a `{LauncherDiscoveryRequest}` -passed to the `{Launcher}` API, `{PostDiscoveryFilter}` implementations will be discovered -at runtime via Java's `{ServiceLoader}` mechanism and automatically applied by the -`Launcher` in addition to those that are part of the request. - -For example, an `example.CustomTagFilter` class implementing `PostDiscoveryFilter` and -declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter` -file is loaded and applied automatically. - -[[launcher-api-launcher-session-listeners-custom]] -==== Registering a LauncherSessionListener - -Registered implementations of `{LauncherSessionListener}` are notified when a -`{LauncherSession}` is opened (before a `{Launcher}` first discovers and executes tests) -and closed (when no more tests will be discovered or executed). They can be registered -programmatically via the `{LauncherConfig}` that is passed to the `{LauncherFactory}`, or -they can be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically -registered with `LauncherSession` (unless automatic registration is disabled.) - -[[launcher-api-launcher-session-listeners-tool-support]] -===== Tool Support - -The following build tools and IDEs are known to provide full support for `LauncherSession`: - -* Gradle 4.6 and later -* Maven Surefire/Failsafe 3.0.0-M6 and later -* IntelliJ IDEA 2017.3 and later - -Other tools might also work but have not been tested explicitly. - -[[launcher-api-launcher-session-listeners-tool-example-usage]] -===== Example Usage - -A `LauncherSessionListener` is well suited for implementing once-per-JVM setup/teardown -behavior since it's called before the first and after the last test in a launcher session, -respectively. The scope of a launcher session depends on the used IDE or build tool but -usually corresponds to the lifecycle of the test JVM. A custom listener that starts an -HTTP server before executing the first test and stops it after the last test has been -executed, could look like this: - -[source,java] -.src/test/java/example/session/GlobalSetupTeardownListener.java ----- -package example.session; - -include::{testDir}/example/session/GlobalSetupTeardownListener.java[tags=user_guide] ----- -<1> Start the HTTP server -<2> Export its host address as a system property for consumption by tests -<3> Export its port as a system property for consumption by tests -<4> Stop the HTTP server - -This sample uses the HTTP server implementation from the jdk.httpserver module that comes -with the JDK but would work similarly with any other server or resource. In order for the -listener to be picked up by JUnit Platform, you need to register it as a service by adding -a resource file with the following name and contents to your test runtime classpath (e.g. -by adding the file to `src/test/resources`): - -[source] -.src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener ----- -include::{testResourcesDir}/META-INF/services/org.junit.platform.launcher.LauncherSessionListener[] ----- - -You can now use the resource from your test: - -[source,java] -.src/test/java/example/session/HttpTests.java ----- -package example.session; - -include::{testDir}/example/session/HttpTests.java[tags=user_guide] ----- -<1> Read the host address of the server from the system property set by the listener -<2> Read the port of the server from the system property set by the listener -<3> Send a request to the server -<4> Check the status code of the response - -[[launcher-api-launcher-discovery-listeners-custom]] -==== Registering a LauncherDiscoveryListener - -In addition to specifying discovery listeners as part of a `{LauncherDiscoveryRequest}` or -registering them programmatically via the `{Launcher}` API, custom -`LauncherDiscoveryListener` implementations can be discovered at runtime via Java's -`{ServiceLoader}` mechanism and automatically registered with the `Launcher` created via -the `{LauncherFactory}`. - -For example, an `example.CustomLauncherDiscoveryListener` class implementing -`LauncherDiscoveryListener` and declared within the -`/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener` file is loaded -and registered automatically. - -[[launcher-api-listeners-custom]] -==== Registering a TestExecutionListener - -In addition to the public `{Launcher}` API method for registering test execution listeners -programmatically, custom `{TestExecutionListener}` implementations will be discovered at -runtime via Java's `{ServiceLoader}` mechanism and automatically registered with the -`Launcher` created via the `{LauncherFactory}`. - -For example, an `example.CustomTestExecutionListener` class implementing -`TestExecutionListener` and declared within the -`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and -registered automatically. - -[[launcher-api-listeners-config]] -==== Configuring a TestExecutionListener - -When a `{TestExecutionListener}` is registered programmatically via the `{Launcher}` API, -the listener may provide programmatic ways for it to be configured -- for example, via its -constructor, setter methods, etc. However, when a `TestExecutionListener` is registered -automatically via Java's `ServiceLoader` mechanism (see -<>), there is no way for the user to directly configure the -listener. In such cases, the author of a `TestExecutionListener` may choose to make the -listener configurable via <>. The -listener can then access the configuration parameters via the `TestPlan` supplied to the -`testPlanExecutionStarted(TestPlan)` and `testPlanExecutionFinished(TestPlan)` callback -methods. See the `{UniqueIdTrackingListener}` for an example. - -[[launcher-api-listeners-custom-deactivation]] -==== Deactivating a TestExecutionListener - -Sometimes it can be useful to run a test suite _without_ certain execution listeners being -active. For example, you might have custom a `{TestExecutionListener}` that sends the test -results to an external system for reporting purposes, and while debugging you might not -want these _debug_ results to be reported. To do this, provide a pattern for the -`junit.platform.execution.listeners.deactivate` _configuration parameter_ to specify which -execution listeners should be deactivated (i.e. not registered) for the current test run. - -[NOTE] -==== -Only listeners registered via the `{ServiceLoader}` mechanism within the -`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file can be -deactivated. In other words, any `TestExecutionListener` registered explicitly via the -`{LauncherDiscoveryRequest}` cannot be deactivated via the -`junit.platform.execution.listeners.deactivate` _configuration parameter_. - -In addition, since execution listeners are registered before the test run starts, the -`junit.platform.execution.listeners.deactivate` _configuration parameter_ can only be -supplied as a JVM system property or via the JUnit Platform configuration file (see -<> for details). This _configuration parameter_ cannot be -supplied in the `LauncherDiscoveryRequest` that is passed to the `{Launcher}`. -==== - -[[launcher-api-listeners-custom-deactivation-pattern]] -===== Pattern Matching Syntax - -Refer to <> for details. - -[[launcher-api-launcher-config]] -==== Configuring the Launcher - -If you require fine-grained control over automatic detection and registration of test -engines and listeners, you may create an instance of `{LauncherConfig}` and supply that to -the `{LauncherFactory}`. Typically, an instance of `LauncherConfig` is created via the -built-in fluent _builder_ API, as demonstrated in the following example. - -[source,java,indent=0] ----- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=launcherConfig] ----- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc deleted file mode 100644 index 665c4dae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc +++ /dev/null @@ -1,164 +0,0 @@ -[[testkit]] -=== JUnit Platform Test Kit - -The `junit-platform-testkit` artifact provides support for executing a test plan on the -JUnit Platform and then verifying the expected results. As of JUnit Platform 1.4, this -support is limited to the execution of a single `TestEngine` (see <>). - -[[testkit-engine]] -==== Engine Test Kit - -The `{testkit-engine-package}` package provides support for executing a `{TestPlan}` for a -given `{TestEngine}` running on the JUnit Platform and then accessing the results via a -fluent API to verify the expected results. The key entry point into this API is the -`{EngineTestKit}` which provides static factory methods named `engine()` and `execute()`. -It is recommended that you select one of the `engine()` variants to benefit from the -fluent API for building a `LauncherDiscoveryRequest`. - -NOTE: If you prefer to use the `LauncherDiscoveryRequestBuilder` from the `Launcher` API -to build your `LauncherDiscoveryRequest`, you must use one of the `execute()` variants in -`EngineTestKit`. - -The following test class written using JUnit Jupiter will be used in subsequent examples. - -[[testkit-engine-ExampleTestCase]] -[source,java,indent=0] ----- -include::{testDir}/example/ExampleTestCase.java[tags=user_guide] ----- - -For the sake of brevity, the following sections demonstrate how to test JUnit's own -`JupiterTestEngine` whose unique engine ID is `"junit-jupiter"`. If you want to test your -own `TestEngine` implementation, you need to use its unique engine ID. Alternatively, you -may test your own `TestEngine` by supplying an instance of it to the -`EngineTestKit.engine(TestEngine)` static factory method. - -[[testkit-engine-statistics]] -==== Asserting Statistics - -One of the most common features of the Test Kit is the ability to assert statistics -against events fired during the execution of a `TestPlan`. The following tests demonstrate -how to assert statistics for _containers_ and _tests_ in the JUnit Jupiter `TestEngine`. -For details on what statistics are available, consult the Javadoc for `{EventStatistics}`. - -[source,java,indent=0] ----- -include::{testDir}/example/testkit/EngineTestKitStatisticsDemo.java[tags=user_guide] ----- -<1> Select the JUnit Jupiter `TestEngine`. -<2> Select the <> test class. -<3> Execute the `TestPlan`. -<4> Filter by _container_ events. -<5> Assert statistics for _container_ events. -<6> Filter by _test_ events. -<7> Assert statistics for _test_ events. - -NOTE: In the `verifyJupiterContainerStats()` test method, the counts for the `started` and -`succeeded` statistics are `2` since the `JupiterTestEngine` and the -<> class are both considered containers. - -[[testkit-engine-events]] -==== Asserting Events - -If you find that <> alone is insufficient -for verifying the expected behavior of test execution, you can work directly with the -recorded `{Event}` elements and perform assertions against them. - -For example, if you want to verify the reason that the `skippedTest()` method in -<> was skipped, you can do that as -follows. - -[TIP] -==== -The `assertThatEvents()` method in the following example is a shortcut for -`org.assertj.core.api.Assertions.assertThat(events.list())` from the {AssertJ} assertion -library. - -For details on what _conditions_ are available for use with AssertJ assertions against -events, consult the Javadoc for `{EventConditions}`. -==== - -[source,java,indent=0] ----- -include::{testDir}/example/testkit/EngineTestKitSkippedMethodDemo.java[tags=user_guide] ----- -<1> Select the JUnit Jupiter `TestEngine`. -<2> Select the `skippedTest()` method in the <> test class. -<3> Execute the `TestPlan`. -<4> Filter by _test_ events. -<5> Save the _test_ `Events` to a local variable. -<6> Optionally assert the expected statistics. -<7> Assert that the recorded _test_ events contain exactly one skipped test named - `skippedTest` with `"for demonstration purposes"` as the _reason_. - -If you want to verify the type of exception thrown from the `failingTest()` method in -<>, you can do that as follows. - -[TIP] -==== -For details on what _conditions_ are available for use with AssertJ assertions against -events and execution results, consult the Javadoc for `{EventConditions}` and -`{TestExecutionResultConditions}`, respectively. -==== - -[source,java,indent=0] ----- -include::{testDir}/example/testkit/EngineTestKitFailedMethodDemo.java[tags=user_guide] ----- -<1> Select the JUnit Jupiter `TestEngine`. -<2> Select the <> test class. -<3> Execute the `TestPlan`. -<4> Filter by _test_ events. -<5> Assert that the recorded _test_ events contain exactly one failing test named - `failingTest` with an exception of type `ArithmeticException` and an error message - equal to `"/ by zero"`. - -Although typically unnecessary, there are times when you need to verify **all** of the -events fired during the execution of a `TestPlan`. The following test demonstrates how to -achieve this via the `assertEventsMatchExactly()` method in the `EngineTestKit` API. - -[TIP] -==== -Since `assertEventsMatchExactly()` matches conditions exactly in the order in which the -events were fired, <> has been -annotated with `@TestMethodOrder(OrderAnnotation.class)` and each test method has been -annotated with `@Order(...)`. This allows us to enforce the order in which the test -methods are executed, which in turn allows our `verifyAllJupiterEvents()` test to be -reliable. -==== - -If you want to do a _partial_ match _with_ or _without_ ordering requirements, you can use -the methods `assertEventsMatchLooselyInOrder()` and `assertEventsMatchLoosely()`, -respectively. - -[source,java,indent=0] ----- -include::{testDir}/example/testkit/EngineTestKitAllEventsDemo.java[tags=user_guide] ----- -<1> Select the JUnit Jupiter `TestEngine`. -<2> Select the <> test class. -<3> Execute the `TestPlan`. -<4> Filter by _all_ events. -<5> Print all events to the supplied `writer` for debugging purposes. Debug information - can also be written to an `OutputStream` such as `System.out` or `System.err`. -<6> Assert _all_ events in exactly the order in which they were fired by the test engine. - -The `debug()` invocation from the preceding example results in output similar to the -following. - -[source,options="nowrap"] ----- -All Events: - Event [type = STARTED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.082280Z, payload = null] - Event [type = STARTED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.089339Z, payload = null] - Event [type = SKIPPED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:skippedTest()], timestamp = 2018-12-14T12:45:14.094314Z, payload = 'for demonstration purposes'] - Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.095182Z, payload = null] - Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.104922Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] - Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.106121Z, payload = null] - Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.109956Z, payload = TestExecutionResult [status = ABORTED, throwable = org.opentest4j.TestAbortedException: Assumption failed: abc does not contain Z]] - Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.110680Z, payload = null] - Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.111217Z, payload = TestExecutionResult [status = FAILED, throwable = java.lang.ArithmeticException: / by zero]] - Event [type = FINISHED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.113731Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] - Event [type = FINISHED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.113806Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] ----- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc deleted file mode 100644 index bac0e8b0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/api-evolution.adoc +++ /dev/null @@ -1,63 +0,0 @@ -[[api-evolution]] -== API Evolution - -One of the major goals of JUnit 5 is to improve maintainers' capabilities to evolve JUnit -despite its being used in many projects. With JUnit 4 a lot of stuff that was originally -added as an internal construct only got used by external extension writers and tool -builders. That made changing JUnit 4 especially difficult and sometimes impossible. - -That's why JUnit 5 introduces a defined lifecycle for all publicly available interfaces, -classes, and methods. - -[[api-evolution-version-and-status]] -=== API Version and Status - -Every published artifact has a version number `..`, and all publicly -available interfaces, classes, and methods are annotated with {API} from the -{API_Guardian} project. The annotation's `status` attribute can be assigned one of the -following values. - -[cols="20,80"] -|=== -| Status | Description - -| `INTERNAL` | Must not be used by any code other than JUnit itself. Might be removed without prior notice. -| `DEPRECATED` | Should no longer be used; might disappear in the next minor release. -| `EXPERIMENTAL` | Intended for new, experimental features where we are looking for feedback. + - Use this element with caution; it might be promoted to `MAINTAINED` or - `STABLE` in the future, but might also be removed without prior notice, even in a patch. -| `MAINTAINED` | Intended for features that will not be changed in a backwards- - incompatible way for *at least* the next minor release of the current - major version. If scheduled for removal, it will be demoted to `DEPRECATED` first. -| `STABLE` | Intended for features that will not be changed in a backwards- - incompatible way in the current major version (`5.*`). -|=== - -If the `@API` annotation is present on a type, it is considered to be applicable for all -public members of that type as well. A member is allowed to declare a different `status` -value of lower stability. - -[[api-evolution-experimental-apis]] -=== Experimental APIs - -The following table lists which APIs are currently designated as _experimental_ via -`@API(status = EXPERIMENTAL)`. Caution should be taken when relying on such APIs. - -include::{experimentalApisTableFile}[] - -[[api-evolution-deprecated-apis]] -=== Deprecated APIs - -The following table lists which APIs are currently designated as _deprecated_ via -`@API(status = DEPRECATED)`. You should avoid using deprecated APIs whenever possible, -since such APIs will likely be removed in an upcoming release. - -include::{deprecatedApisTableFile}[] - -[[api-evolution-tooling]] -=== @API Tooling Support - -The {API_Guardian} project plans to provide tooling support for publishers and consumers -of APIs annotated with {API}. For example, the tooling support will likely provide a -means to check if JUnit APIs are being used in accordance with `@API` annotation -declarations. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc deleted file mode 100644 index d92194fd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/appendix.adoc +++ /dev/null @@ -1,232 +0,0 @@ -[[appendix]] -== Appendix - -[[reproducible-builds]] -=== Reproducible Builds - -Starting with version 5.7, JUnit 5 aims for its non-javadoc JARs to be -https://reproducible-builds.org/[reproducible]. - -Under identical build conditions, such as Java version, repeated builds should provide the -same output byte-for-byte. - -This means that anyone can reproduce the build conditions of the artifacts on Maven -Central/Sonatype and produce the same output artifact locally, confirming that the -artifacts in the repositories were actually generated from this source code. - -[[dependency-metadata]] -=== Dependency Metadata - -Artifacts for final releases and milestones are deployed to {Maven_Central}, and snapshot -artifacts are deployed to Sonatype's {snapshot-repo}[snapshots repository] under -{snapshot-repo}/org/junit/[/org/junit]. - -[[dependency-metadata-junit-platform]] -==== JUnit Platform - -* *Group ID*: `org.junit.platform` -* *Version*: `{platform-version}` -* *Artifact IDs*: - `junit-platform-commons`:: - Common APIs and support utilities for the JUnit Platform. Any API annotated with - `@API(status = INTERNAL)` is intended solely for usage within the JUnit framework - itself. _Any usage of internal APIs by external parties is not supported!_ - `junit-platform-console`:: - Support for discovering and executing tests on the JUnit Platform from the console. - See <> for details. - `junit-platform-console-standalone`:: - An executable JAR with all dependencies included is provided in Maven Central under the - https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone] - directory. See <> for details. - `junit-platform-engine`:: - Public API for test engines. See <> for details. - `junit-platform-jfr`:: - Provides a `LauncherDiscoveryListener` and `TestExecutionListener` for Java Flight - Recorder events on the JUnit Platform. See <> - for details. - `junit-platform-launcher`:: - Public API for configuring and launching test plans -- typically used by IDEs and - build tools. See <> for details. - `junit-platform-reporting`:: - `TestExecutionListener` implementations that generate test reports -- typically used - by IDEs and build tools. See <> for details. - `junit-platform-runner`:: - Runner for executing tests and test suites on the JUnit Platform in a JUnit 4 - environment. See <> for details. - `junit-platform-suite`:: - JUnit Platform Suite artifact that transitively pulls in dependencies on - `junit-platform-suite-api` and `junit-platform-suite-engine` for simplified dependency - management in build tools such as Gradle and Maven. - `junit-platform-suite-api`:: - Annotations for configuring test suites on the JUnit Platform. Supported by the - <> and the - <>. - `junit-platform-suite-commons`:: - Common support utilities for executing test suites on the JUnit Platform. - `junit-platform-suite-engine`:: - Engine that executes test suites on the JUnit Platform; only required at runtime. See - <> for details. - `junit-platform-testkit`:: - Provides support for executing a test plan for a given `TestEngine` and then - accessing the results via a fluent API to verify the expected results. - -[[dependency-metadata-junit-jupiter]] -==== JUnit Jupiter - -* *Group ID*: `org.junit.jupiter` -* *Version*: `{jupiter-version}` -* *Artifact IDs*: - `junit-jupiter`:: - JUnit Jupiter aggregator artifact that transitively pulls in dependencies on - `junit-jupiter-api`, `junit-jupiter-params`, and `junit-jupiter-engine` for - simplified dependency management in build tools such as Gradle and Maven. - `junit-jupiter-api`:: - JUnit Jupiter API for <> and <>. - `junit-jupiter-engine`:: - JUnit Jupiter test engine implementation; only required at runtime. - `junit-jupiter-params`:: - Support for <> in JUnit Jupiter. - `junit-jupiter-migrationsupport`:: - Support for migrating from JUnit 4 to JUnit Jupiter; only required for support for - JUnit 4's `@Ignore` annotation and for running selected JUnit 4 rules. - -[[dependency-metadata-junit-vintage]] -==== JUnit Vintage - -* *Group ID*: `org.junit.vintage` -* *Version*: `{vintage-version}` -* *Artifact ID*: - `junit-vintage-engine`:: - JUnit Vintage test engine implementation that allows one to run _vintage_ JUnit tests - on the JUnit Platform. _Vintage_ tests include those written using JUnit 3 or JUnit 4 - APIs or tests written using testing frameworks built on those APIs. - -[[dependency-metadata-junit-bom]] -==== Bill of Materials (BOM) - -The _Bill of Materials_ POM provided under the following Maven coordinates can be used to -ease dependency management when referencing multiple of the above artifacts using -https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies[Maven] -or https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:bom_import[Gradle]. - -* *Group ID*: `org.junit` -* *Artifact ID*: `junit-bom` -* *Version*: `{bom-version}` - -[[dependency-metadata-dependencies]] -==== Dependencies - -Most of the above artifacts have a dependency in their published Maven POMs on the -following _@API Guardian_ JAR. - -* *Group ID*: `org.apiguardian` -* *Artifact ID*: `apiguardian-api` -* *Version*: `{apiguardian-version}` - -In addition, most of the above artifacts have a direct or transitive dependency on the -following _OpenTest4J_ JAR. - -* *Group ID*: `org.opentest4j` -* *Artifact ID*: `opentest4j` -* *Version*: `{ota4j-version}` - -[[dependency-diagram]] -=== Dependency Diagram - -[plantuml, component-diagram, svg] ----- -skinparam { - defaultFontName Open Sans -} - -package org.junit.jupiter { - [junit-jupiter] as jupiter - [junit-jupiter-api] as jupiter_api - [junit-jupiter-engine] as jupiter_engine - [junit-jupiter-params] as jupiter_params - [junit-jupiter-migrationsupport] as jupiter_migration_support -} - -package org.junit.vintage { - [junit-vintage-engine] as vintage_engine -} - -package org.junit.platform { - [junit-platform-commons] as commons - [junit-platform-console] as console - [junit-platform-engine] as engine - [junit-platform-jfr] as jfr - [junit-platform-launcher] as launcher - [junit-platform-reporting] as reporting - [junit-platform-runner] as runner - [junit-platform-suite] as suite - [junit-platform-suite-api] as suite_api - [junit-platform-suite-commons] as suite_commons - [junit-platform-suite-engine] as suite_engine - [junit-platform-testkit] as testkit -} - -package "JUnit 4" { - [junit:junit] as junit4 -} - -package org.opentest4j { - [opentest4j] -} - -package org.apiguardian { - [apiguardian-api] as apiguardian - note bottom of apiguardian #white - All artifacts except - opentest4j and junit:junit - have a dependency on this - artifact. The edges have - been omitted from this - diagram for the sake of - readability. - endnote -} - -jupiter ..> jupiter_api -jupiter ..> jupiter_params -jupiter ..> jupiter_engine - -jupiter_api ....> opentest4j -jupiter_api ...> commons - -jupiter_engine ...> engine -jupiter_engine ..> jupiter_api - -jupiter_params ..> jupiter_api -jupiter_migration_support ..> jupiter_api -jupiter_migration_support ...> junit4 - -console ..> launcher -console ..> reporting - -launcher ..> engine - -jfr ..> launcher - -engine ....> opentest4j -engine ..> commons - -reporting ..> launcher - -runner ..> suite_commons -runner ...> junit4 - -suite ..> suite_api -suite ..> suite_engine - -suite_engine ..> suite_commons - -suite_commons ..> launcher -suite_commons ..> suite_api - -testkit ....> opentest4j -testkit ..> launcher - -vintage_engine ...> engine -vintage_engine ..> junit4 ----- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc deleted file mode 100644 index c005e9e8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/contributors.adoc +++ /dev/null @@ -1,4 +0,0 @@ -[[contributors]] -== Contributors - -Browse the {junit5-repo}/graphs/contributors[current list of contributors] directly on GitHub. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc deleted file mode 100644 index a1fee08b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ /dev/null @@ -1,951 +0,0 @@ -[[extensions]] -== Extension Model - -[[extensions-overview]] -=== Overview - -In contrast to the competing `Runner`, `TestRule`, and `MethodRule` extension points in -JUnit 4, the JUnit Jupiter extension model consists of a single, coherent concept: the -`Extension` API. Note, however, that `Extension` itself is just a marker interface. - -[[extensions-registration]] -=== Registering Extensions - -Extensions can be registered _declaratively_ via -<>, _programmatically_ via -<>, or _automatically_ via -Java's <> mechanism. - -[[extensions-registration-declarative]] -==== Declarative Extension Registration - -Developers can register one or more extensions _declaratively_ by annotating a test -interface, test class, test method, or custom _<>_ with `@ExtendWith(...)` and supplying class references for the extensions to -register. As of JUnit Jupiter 5.8, `@ExtendWith` may also be declared on fields or on -parameters in test class constructors, in test methods, and in `@BeforeAll`, `@AfterAll`, -`@BeforeEach`, and `@AfterEach` lifecycle methods. - -For example, to register a `WebServerExtension` for a particular test method, you would -annotate the test method as follows. We assume the `WebServerExtension` starts a local web -server and injects the server's URL into parameters annotated with `@WebServerUrl`. - -[source,java,indent=0] ----- -@Test -@ExtendWith(WebServerExtension.class) -void getProductList(@WebServerUrl String serverUrl) { - WebClient webClient = new WebClient(); - // Use WebClient to connect to web server using serverUrl and verify response - assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); -} ----- - -To register the `WebServerExtension` for all tests in a particular class and its -subclasses, you would annotate the test class as follows. - -[source,java,indent=0] ----- -@ExtendWith(WebServerExtension.class) -class MyTests { - // ... -} ----- - -Multiple extensions can be registered together like this: - -[source,java,indent=0] ----- -@ExtendWith({ DatabaseExtension.class, WebServerExtension.class }) -class MyFirstTests { - // ... -} ----- - -As an alternative, multiple extensions can be registered separately like this: - -[source,java,indent=0] ----- -@ExtendWith(DatabaseExtension.class) -@ExtendWith(WebServerExtension.class) -class MySecondTests { - // ... -} ----- - -[TIP] -.Extension Registration Order -==== -Extensions registered declaratively via `@ExtendWith` at the class level, method level, or -parameter level will be executed in the order in which they are declared in the source -code. For example, the execution of tests in both `MyFirstTests` and `MySecondTests` will -be extended by the `DatabaseExtension` and `WebServerExtension`, **in exactly that order**. -==== - -If you wish to combine multiple extensions in a reusable way, you can define a custom -_<>_ and use `@ExtendWith` as a -_meta-annotation_ as in the following code listing. Then `@DatabaseAndWebServerExtension` -can be used in place of `@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })`. - -[source,java,indent=0] ----- -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith({ DatabaseExtension.class, WebServerExtension.class }) -public @interface DatabaseAndWebServerExtension { -} ----- - -The above examples demonstrate how `@ExtendWith` can be applied at the class level or at -the method level; however, for certain use cases it makes sense for an extension to be -registered declaratively at the field or parameter level. Consider a -`RandomNumberExtension` that generates random numbers that can be injected into a field or -via a parameter in a constructor, test method, or lifecycle method. If the extension -provides a `@Random` annotation that is meta-annotated with -`@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used -transparently as in the following `RandomNumberDemo` example. - -[source,java,indent=0] ----- -include::{testDir}/example/extensions/Random.java[tags=user_guide] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/extensions/RandomNumberDemo.java[tags=user_guide] ----- - -[TIP] -.Extension Registration Order for `@ExtendWith` on Fields -==== -Extensions registered declaratively via `@ExtendWith` on fields will be ordered relative -to `@RegisterExtension` fields and other `@ExtendWith` fields using an algorithm that is -deterministic but intentionally nonobvious. However, `@ExtendWith` fields can be ordered -using the `@Order` annotation. See the <> tip for `@RegisterExtension` fields for details. -==== - -NOTE: `@ExtendWith` fields may be either `static` or non-static. The documentation on -<> and -<> for -`@RegisterExtension` fields also applies to `@ExtendWith` fields. - -[[extensions-registration-programmatic]] -==== Programmatic Extension Registration - -Developers can register extensions _programmatically_ by annotating fields in test classes -with `{RegisterExtension}`. - -When an extension is registered _declaratively_ via -<>, it can typically only be configured -via annotations. In contrast, when an extension is registered via `@RegisterExtension`, it -can be configured _programmatically_ -- for example, in order to pass arguments to the -extension's constructor, a static factory method, or a builder API. - -[[extensions-registration-programmatic-order]] -[TIP] -.Extension Registration Order -==== -By default, extensions registered programmatically via `@RegisterExtension` or -declaratively via `@ExtendWith` on fields will be ordered using an algorithm that is -deterministic but intentionally nonobvious. This ensures that subsequent runs of a test -suite execute extensions in the same order, thereby allowing for repeatable builds. -However, there are times when extensions need to be registered in an explicit order. To -achieve that, annotate `@RegisterExtension` fields or `@ExtendWith` fields with `{Order}`. - -Any `@RegisterExtension` field or `@ExtendWith` field not annotated with `@Order` will be -ordered using the _default_ order which has a value of `Integer.MAX_VALUE / 2`. This -allows `@Order` annotated extension fields to be explicitly ordered before or after -non-annotated extension fields. Extensions with an explicit order value less than the -default order value will be registered before non-annotated extensions. Similarly, -extensions with an explicit order value greater than the default order value will be -registered after non-annotated extensions. For example, assigning an extension an explicit -order value that is greater than the default order value allows _before_ callback -extensions to be registered last and _after_ callback extensions to be registered first, -relative to other programmatically registered extensions. -==== - -NOTE: `@RegisterExtension` fields must not be `null` (at evaluation time) but may be -either `static` or non-static. - -[[extensions-registration-programmatic-static-fields]] -===== Static Fields - -If a `@RegisterExtension` field is `static`, the extension will be registered after -extensions that are registered at the class level via `@ExtendWith`. Such _static -extensions_ are not limited in which extension APIs they can implement. Extensions -registered via static fields may therefore implement class-level and instance-level -extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, -`TestInstancePostProcessor`, and `TestInstancePreDestroyCallback` as well as method-level -extension APIs such as `BeforeEachCallback`, etc. - -In the following example, the `server` field in the test class is initialized -programmatically by using a builder pattern supported by the `WebServerExtension`. The -configured `WebServerExtension` will be automatically registered as an extension at the -class level -- for example, in order to start the server before all tests in the class -and then stop the server after all tests in the class have completed. In addition, static -lifecycle methods annotated with `@BeforeAll` or `@AfterAll` as well as `@BeforeEach`, -`@AfterEach`, and `@Test` methods can access the instance of the extension via the -`server` field if necessary. - -[source,java,indent=0] -.Registering an extension via a static field in Java ----- -include::{testDir}/example/registration/WebServerDemo.java[tags=user_guide] ----- - -[[extensions-registration-programmatic-static-fields-kotlin]] -====== Static Fields in Kotlin - -The Kotlin programming language does not have the concept of a `static` field. However, -the compiler can be instructed to generate a `private static` field using the `@JvmStatic` -annotation in Kotlin. If you want the Kotlin compiler to generate a `public static` field, -you can use the `@JvmField` annotation instead. - -The following example is a version of the `WebServerDemo` from the previous section that -has been ported to Kotlin. - -[source,kotlin,indent=0] -.Registering an extension via a static field in Kotlin ----- -include::{kotlinTestDir}/example/registration/KotlinWebServerDemo.kt[tags=user_guide] ----- - -[[extensions-registration-programmatic-instance-fields]] -===== Instance Fields - -If a `@RegisterExtension` field is non-static (i.e., an instance field), the extension -will be registered after the test class has been instantiated and after each registered -`TestInstancePostProcessor` has been given a chance to post-process the test instance -(potentially injecting the instance of the extension to be used into the annotated -field). Thus, if such an _instance extension_ implements class-level or instance-level -extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, or -`TestInstancePostProcessor`, those APIs will not be honored. By default, an instance -extension will be registered _after_ extensions that are registered at the method level -via `@ExtendWith`; however, if the test class is configured with -`@TestInstance(Lifecycle.PER_CLASS)` semantics, an instance extension will be registered -_before_ extensions that are registered at the method level via `@ExtendWith`. - -In the following example, the `docs` field in the test class is initialized -programmatically by invoking a custom `lookUpDocsDir()` method and supplying the result -to the static `forPath()` factory method in the `DocumentationExtension`. The configured -`DocumentationExtension` will be automatically registered as an extension at the method -level. In addition, `@BeforeEach`, `@AfterEach`, and `@Test` methods can access the -instance of the extension via the `docs` field if necessary. - -[source,java,indent=0] -.An extension registered via an instance field ----- -include::{testDir}/example/registration/DocumentationDemo.java[tags=user_guide] ----- - -[[extensions-registration-automatic]] -==== Automatic Extension Registration - -In addition to <> -and <> support -using annotations, JUnit Jupiter also supports _global extension registration_ via Java's -`{ServiceLoader}` mechanism, allowing third-party extensions to be auto-detected and -automatically registered based on what is available in the classpath. - -Specifically, a custom extension can be registered by supplying its fully qualified class -name in a file named `org.junit.jupiter.api.extension.Extension` within the -`/META-INF/services` folder in its enclosing JAR file. - -[[extensions-registration-automatic-enabling]] -===== Enabling Automatic Extension Detection - -Auto-detection is an advanced feature and is therefore not enabled by default. To enable -it, set the `junit.jupiter.extensions.autodetection.enabled` _configuration parameter_ to -`true`. This can be supplied as a JVM system property, as a _configuration parameter_ in -the `LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform -configuration file (see <> for details). - -For example, to enable auto-detection of extensions, you can start your JVM with the -following system property. - -`-Djunit.jupiter.extensions.autodetection.enabled=true` - -When auto-detection is enabled, extensions discovered via the `{ServiceLoader}` mechanism -will be added to the extension registry after JUnit Jupiter's global extensions (e.g., -support for `TestInfo`, `TestReporter`, etc.). - -[[extensions-registration-inheritance]] -==== Extension Inheritance - -Registered extensions are inherited within test class hierarchies with top-down -semantics. Similarly, extensions registered at the class-level are inherited at the -method-level. Furthermore, a specific extension implementation can only be registered -once for a given extension context and its parent contexts. Consequently, any attempt to -register a duplicate extension implementation will be ignored. - -[[extensions-conditions]] -=== Conditional Test Execution - -`{ExecutionCondition}` defines the `Extension` API for programmatic, _conditional test -execution_. - -An `ExecutionCondition` is _evaluated_ for each container (e.g., a test class) to -determine if all the tests it contains should be executed based on the supplied -`ExtensionContext`. Similarly, an `ExecutionCondition` is _evaluated_ for each test to -determine if a given test method should be executed based on the supplied -`ExtensionContext`. - -When multiple `ExecutionCondition` extensions are registered, a container or test is -disabled as soon as one of the conditions returns _disabled_. Thus, there is no guarantee -that a condition is evaluated because another extension might have already caused a -container or test to be disabled. In other words, the evaluation works like the -short-circuiting boolean OR operator. - -See the source code of `{DisabledCondition}` and `{Disabled}` for concrete examples. - -[[extensions-conditions-deactivation]] -==== Deactivating Conditions - -Sometimes it can be useful to run a test suite _without_ certain conditions being active. -For example, you may wish to run tests even if they are annotated with `@Disabled` in -order to see if they are still _broken_. To do this, provide a pattern for the -`junit.jupiter.conditions.deactivate` _configuration parameter_ to specify which -conditions should be deactivated (i.e., not evaluated) for the current test run. The -pattern can be supplied as a JVM system property, as a _configuration parameter_ in the -`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform -configuration file (see <> for details). - -For example, to deactivate JUnit's `@Disabled` condition, you can start your JVM with the -following system property. - -`-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition` - -[[extensions-conditions-deactivation-patterns]] -===== Pattern Matching Syntax - -Refer to <> for details. - -[[extensions-test-instance-pre-construct-callback]] -=== Test Instance Pre-construct Callback - -`{TestInstancePreConstructCallback}` defines the API for `Extensions` that wish to be invoked -_prior_ to test instances being constructed (by a constructor call or via -`{TestInstanceFactory}`). - -This extension provides a symmetric call to `{TestInstancePreDestroyCallback}` and is useful -in combination with other extensions to prepare constructor parameters or keeping track of test -instances and their lifecycle. - -[[extensions-test-instance-factories]] -=== Test Instance Factories - -`{TestInstanceFactory}` defines the API for `Extensions` that wish to _create_ test class -instances. - -Common use cases include acquiring the test instance from a dependency injection -framework or invoking a static factory method to create the test class instance. - -If no `TestInstanceFactory` is registered, the framework will invoke the _sole_ -constructor for the test class to instantiate it, potentially resolving constructor -arguments via registered `ParameterResolver` extensions. - -Extensions that implement `TestInstanceFactory` can be registered on test interfaces, -top-level test classes, or `@Nested` test classes. - -[WARNING] -==== -Registering multiple extensions that implement `TestInstanceFactory` for any single class -will result in an exception being thrown for all tests in that class, in any subclass, -and in any nested class. Note that any `TestInstanceFactory` registered in a superclass -or _enclosing_ class (i.e., in the case of a `@Nested` test class) is _inherited_. It is -the user's responsibility to ensure that only a single `TestInstanceFactory` is -registered for any specific test class. -==== - -[[extensions-test-instance-post-processing]] -=== Test Instance Post-processing - -`{TestInstancePostProcessor}` defines the API for `Extensions` that wish to _post -process_ test instances. - -Common use cases include injecting dependencies into the test instance, invoking custom -initialization methods on the test instance, etc. - -For a concrete example, consult the source code for the `{MockitoExtension}` and the -`{SpringExtension}`. - -[[extensions-test-instance-pre-destroy-callback]] -=== Test Instance Pre-destroy Callback - -`{TestInstancePreDestroyCallback}` defines the API for `Extensions` that wish to process -test instances _after_ they have been used in tests and _before_ they are destroyed. - -Common use cases include cleaning dependencies that have been injected into the -test instance, invoking custom de-initialization methods on the test instance, etc. - -[[extensions-parameter-resolution]] -=== Parameter Resolution - -`{ParameterResolver}` defines the `Extension` API for dynamically resolving parameters at -runtime. - -If a _test class_ constructor, _test method_, or _lifecycle method_ (see -<>) declares a parameter, the parameter must be -_resolved_ at runtime by a `ParameterResolver`. A `ParameterResolver` can either be -built-in (see `{TestInfoParameterResolver}`) or <>. Generally speaking, parameters may be resolved by _name_, _type_, -_annotation_, or any combination thereof. - -If you wish to implement a custom `{ParameterResolver}` that resolves parameters based -solely on the type of the parameter, you may find it convenient to extend the -`{TypeBasedParameterResolver}` which serves as a generic adapter for such use cases. - -For concrete examples, consult the source code for `{CustomTypeParameterResolver}`, -`{CustomAnnotationParameterResolver}`, and `{MapOfListsTypeBasedParameterResolver}`. - -[WARNING] -==== -Due to a bug in the byte code generated by `javac` on JDK versions prior to JDK 9, -looking up annotations on parameters directly via the core `java.lang.reflect.Parameter` -API will always fail for _inner class_ constructors (e.g., a constructor in a `@Nested` -test class). - -The `{ParameterContext}` API supplied to `ParameterResolver` implementations therefore -includes the following convenience methods for correctly looking up annotations on -parameters. Extension authors are strongly encouraged to use these methods instead of -those provided in `java.lang.reflect.Parameter` in order to avoid this bug in the JDK. - -* `boolean isAnnotated(Class annotationType)` -* `Optional findAnnotation(Class annotationType)` -* `List findRepeatableAnnotations(Class annotationType)` -==== - -[NOTE] -==== -Other extensions can also leverage registered `ParameterResolvers` for method and -constructor invocations, using the `{ExecutableInvoker}` available via the -`getExecutableInvoker()` method in the `ExtensionContext`. -==== - -[[extensions-test-result-processing]] -=== Test Result Processing - -`{TestWatcher}` defines the API for extensions that wish to process the results of _test -method_ executions. Specifically, a `TestWatcher` will be invoked with contextual -information for the following events. - -* `testDisabled`: invoked after a disabled _test method_ has been skipped -* `testSuccessful`: invoked after a _test method_ has completed successfully -* `testAborted`: invoked after a _test method_ has been aborted -* `testFailed`: invoked after a _test method_ has failed - -NOTE: In contrast to the definition of "test method" presented in -<>, in this context _test method_ refers to any -`@Test` method or `@TestTemplate` method (for example, a `@RepeatedTest` or -`@ParameterizedTest`). - -Extensions implementing this interface can be registered at the method level or at the -class level. In the latter case they will be invoked for any contained _test method_ -including those in `@Nested` classes. - -[WARNING] -==== -Any instances of `ExtensionContext.Store.CloseableResource` stored in the `Store` of the -provided `{ExtensionContext}` will be closed _before_ methods in this API are invoked (see -<>). You can use the parent context's `Store` to work with such -resources. -==== - -[[extensions-lifecycle-callbacks]] -=== Test Lifecycle Callbacks - -The following interfaces define the APIs for extending tests at various points in the -test execution lifecycle. Consult the following sections for examples and the Javadoc for -each of these interfaces in the `{extension-api-package}` package for further details. - -* `{BeforeAllCallback}` -** `{BeforeEachCallback}` -*** `{BeforeTestExecutionCallback}` -*** `{AfterTestExecutionCallback}` -** `{AfterEachCallback}` -* `{AfterAllCallback}` - -.Implementing Multiple Extension APIs -NOTE: Extension developers may choose to implement any number of these interfaces -within a single extension. Consult the source code of the `{SpringExtension}` for a -concrete example. - -[[extensions-lifecycle-callbacks-before-after-execution]] -==== Before and After Test Execution Callbacks - -`{BeforeTestExecutionCallback}` and `{AfterTestExecutionCallback}` define the APIs for -`Extensions` that wish to add behavior that will be executed _immediately before_ and -_immediately after_ a test method is executed, respectively. As such, these callbacks are -well suited for timing, tracing, and similar use cases. If you need to implement -callbacks that are invoked _around_ `@BeforeEach` and `@AfterEach` methods, implement -`BeforeEachCallback` and `AfterEachCallback` instead. - -The following example shows how to use these callbacks to calculate and log the execution -time of a test method. `TimingExtension` implements both `BeforeTestExecutionCallback` -and `AfterTestExecutionCallback` in order to time and log the test execution. - -[[extensions-lifecycle-callbacks-timing-extension]] -[source,java,indent=0] -.An extension that times and logs the execution of test methods ----- -include::{testDir}/example/timing/TimingExtension.java[tags=user_guide] ----- - -Since the `TimingExtensionTests` class registers the `TimingExtension` via `@ExtendWith`, -its tests will have this timing applied when they execute. - -[source,java,indent=0] -.A test class that uses the example TimingExtension ----- -include::{testDir}/example/timing/TimingExtensionTests.java[tags=user_guide] ----- - -The following is an example of the logging produced when `TimingExtensionTests` is run. - -.... -INFO: Method [sleep20ms] took 24 ms. -INFO: Method [sleep50ms] took 53 ms. -.... - -[[extensions-exception-handling]] -=== Exception Handling - -Exceptions thrown during the test execution may be intercepted and handled accordingly -before propagating further, so that certain actions like error logging or resource releasing -may be defined in specialized `Extensions`. JUnit Jupiter offers API for `Extensions` that -wish to handle exceptions thrown during `@Test` methods via `{TestExecutionExceptionHandler}` -and for those thrown during one of test lifecycle methods (`@BeforeAll`, `@BeforeEach`, -`@AfterEach` and `@AfterAll`) via `{LifecycleMethodExecutionExceptionHandler}`. - -The following example shows an extension which will swallow all instances of `IOException` -but rethrow any other type of exception. - -[source,java,indent=0] -.An exception handling extension that filters IOExceptions in test execution ----- -include::{testDir}/example/exception/IgnoreIOExceptionExtension.java[tags=user_guide] ----- - -Another example shows how to record the state of an application under test exactly at -the point of unexpected exception being thrown during setup and cleanup. Note that unlike -relying on lifecycle callbacks, which may or may not be executed depending on the test -status, this solution guarantees execution immediately after failing `@BeforeAll`, -`@BeforeEach`, `@AfterEach` or `@AfterAll`. - -[source,java,indent=0] -.An exception handling extension that records application state on error ----- -include::{testDir}/example/exception/RecordStateOnErrorExtension.java[tags=user_guide] ----- - -Multiple execution exception handlers may be invoked for the same lifecycle method in -order of declaration. If one of the handlers swallows the handled exception, subsequent -ones will not be executed, and no failure will be propagated to JUnit engine, as if the -exception was never thrown. Handlers may also choose to rethrow the exception or throw -a different one, potentially wrapping the original. - -Extensions implementing `{LifecycleMethodExecutionExceptionHandler}` that wish to handle -exceptions thrown during `@BeforeAll` or `@AfterAll` need to be registered on a class level, -while handlers for `BeforeEach` and `AfterEach` may be also registered for individual -test methods. - -[source,java,indent=0] -.Registering multiple exception handling extensions ----- -include::{testDir}/example/exception/MultipleHandlersTestCase.java[tags=user_guide] ----- - -[[extensions-intercepting-invocations]] -=== Intercepting Invocations - -`{InvocationInterceptor}` defines the API for `Extensions` that wish to intercept calls to -test code. - -The following example shows an extension that executes all test methods in Swing's Event -Dispatch Thread. - -[source,java,indent=0] -.An extension that executes tests in a user-defined thread ----- -include::{testDir}/example/interceptor/SwingEdtInterceptor.java[tags=user_guide] ----- - -[[extensions-test-templates]] -=== Providing Invocation Contexts for Test Templates - -A `{TestTemplate}` method can only be executed when at least one -`{TestTemplateInvocationContextProvider}` is registered. Each such provider is responsible -for providing a `Stream` of `{TestTemplateInvocationContext}` instances. Each context may -specify a custom display name and a list of additional extensions that will only be used -for the next invocation of the `{TestTemplate}` method. - -The following example shows how to write a test template as well as how to register and -implement a `{TestTemplateInvocationContextProvider}`. - -[source,java,indent=0] -.A test template with accompanying extension ----- -include::{testDir}/example/TestTemplateDemo.java[tags=user_guide] ----- - -In this example, the test template will be invoked twice. The display names of the -invocations will be `apple` and `banana` as specified by the invocation context. Each -invocation registers a custom `{ParameterResolver}` which is used to resolve the method -parameter. The output when using the `ConsoleLauncher` is as follows. - -.... -└─ testTemplate(String) ✔ - ├─ apple ✔ - └─ banana ✔ -.... - -The `{TestTemplateInvocationContextProvider}` extension API is primarily intended for -implementing different kinds of tests that rely on repetitive invocation of a test-like -method albeit in different contexts — for example, with different parameters, by preparing -the test class instance differently, or multiple times without modifying the context. -Please refer to the implementations of <> or -<> which use this extension point to provide their -functionality. - - -[[extensions-keeping-state]] -=== Keeping State in Extensions - -Usually, an extension is instantiated only once. So the question becomes relevant: How do -you keep the state from one invocation of an extension to the next? The -`ExtensionContext` API provides a `Store` exactly for this purpose. Extensions may put -values into a store for later retrieval. See the -`<>` for an example of -using the `Store` with a method-level scope. It is important to remember that values -stored in an `ExtensionContext` during test execution will not be available in the -surrounding `ExtensionContext`. Since `ExtensionContexts` may be nested, the scope of -inner contexts may also be limited. Consult the corresponding Javadoc for details on the -methods available for storing and retrieving values via the `{ExtensionContext_Store}`. - -.`ExtensionContext.Store.CloseableResource` -NOTE: An extension context store is bound to its extension context lifecycle. When an -extension context lifecycle ends it closes its associated store. All stored values -that are instances of `CloseableResource` are notified by an invocation of their `close()` -method in the inverse order they were added in. - -[[extensions-supported-utilities]] -=== Supported Utilities in Extensions - -The `junit-platform-commons` artifact exposes a package named -`{junit-platform-support-package}` that contains _maintained_ utility methods for working -with annotations, classes, reflection, and classpath scanning tasks. `TestEngine` and -`Extension` authors are encouraged to use these supported methods in order to align with -the behavior of the JUnit Platform. - -[[extensions-supported-utilities-annotations]] -==== Annotation Support - -`AnnotationSupport` provides static utility methods that operate on annotated elements -(e.g., packages, annotations, classes, interfaces, constructors, methods, and fields). -These include methods to check whether an element is annotated or meta-annotated with a -particular annotation, to search for specific annotations, and to find annotated methods -and fields in a class or interface. Some of these methods search on implemented -interfaces and within class hierarchies to find annotations. Consult the Javadoc for -`{AnnotationSupport}` for further details. - -[[extensions-supported-utilities-classes]] -==== Class Support - -`ClassSupport` provides static utility methods for working with classes (i.e., instances -of `java.lang.Class`). Consult the Javadoc for `{ClassSupport}` for further details. - -[[extensions-supported-utilities-reflection]] -==== Reflection Support - -`ReflectionSupport` provides static utility methods that augment the standard JDK -reflection and class-loading mechanisms. These include methods to scan the classpath in -search of classes matching specified predicates, to load and create new instances of a -class, and to find and invoke methods. Some of these methods traverse class hierarchies -to locate matching methods. Consult the Javadoc for `{ReflectionSupport}` for further -details. - -[[extensions-supported-utilities-modifier]] -==== Modifier Support - -`ModifierSupport` provides static utility methods for working with member and class -modifiers -- for example, to determine if a member is declared as `public`, `private`, -`abstract`, `static`, etc. Consult the Javadoc for `{ModifierSupport}` for further -details. - - -[[extensions-execution-order]] -=== Relative Execution Order of User Code and Extensions - -When executing a test class that contains one or more test methods, a number of extension -callbacks are called in addition to the user-supplied test and lifecycle methods. - -NOTE: See also: <> - -[[extensions-execution-order-overview]] -==== User and Extension Code - -The following diagram illustrates the relative order of user-supplied code and extension -code. User-supplied test and lifecycle methods are shown in orange, with callback code -implemented by extensions shown in blue. The grey box denotes the execution of a single -test method and will be repeated for every test method in the test class. - -:figure-caption: User code and extension code - -[#extensions-execution-order-diagram,reftext='{figure-caption}'] -image::extensions_lifecycle.png[caption='',title='{figure-caption}'] - -The following table further explains the sixteen steps in the -<> diagram. - -[cols="5,15,80"] -|=== -| Step | Interface/Annotation | Description - -| 1 -| interface `org.junit.jupiter.api.extension.BeforeAllCallback` -| extension code executed before all tests of the container are executed - -| 2 -| annotation `org.junit.jupiter.api.BeforeAll` -| user code executed before all tests of the container are executed - -| 3 -| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler -#handleBeforeAllMethodExecutionException` -| extension code for handling exceptions thrown from `@BeforeAll` methods - -| 4 -| interface `org.junit.jupiter.api.extension.BeforeEachCallback` -| extension code executed before each test is executed - -| 5 -| annotation `org.junit.jupiter.api.BeforeEach` -| user code executed before each test is executed - -| 6 -| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler -#handleBeforeEachMethodExecutionException` -| extension code for handling exceptions thrown from `@BeforeEach` methods - -| 7 -| interface `org.junit.jupiter.api.extension.BeforeTestExecutionCallback` -| extension code executed immediately before a test is executed - -| 8 -| annotation `org.junit.jupiter.api.Test` -| user code of the actual test method - -| 9 -| interface `org.junit.jupiter.api.extension.TestExecutionExceptionHandler` -| extension code for handling exceptions thrown during a test - -| 10 -| interface `org.junit.jupiter.api.extension.AfterTestExecutionCallback` -| extension code executed immediately after test execution and its corresponding exception handlers - -| 11 -| annotation `org.junit.jupiter.api.AfterEach` -| user code executed after each test is executed - -| 12 -| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler -#handleAfterEachMethodExecutionException` -| extension code for handling exceptions thrown from `@AfterEach` methods - -| 13 -| interface `org.junit.jupiter.api.extension.AfterEachCallback` -| extension code executed after each test is executed - -| 14 -| annotation `org.junit.jupiter.api.AfterAll` -| user code executed after all tests of the container are executed - -| 15 -| interface `org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler -#handleAfterAllMethodExecutionException` -| extension code for handling exceptions thrown from `@AfterAll` methods - -| 16 -| interface `org.junit.jupiter.api.extension.AfterAllCallback` -| extension code executed after all tests of the container are executed - -|=== - -In the simplest case only the actual test method will be executed (step 8); all other -steps are optional depending on the presence of user code or extension support for the -corresponding lifecycle callback. For further details on the various lifecycle callbacks -please consult the respective Javadoc for each annotation and extension. - -All invocations of user code methods in the above table can additionally be intercepted -by implementing <>. - -[[extensions-execution-order-wrapping-behavior]] -==== Wrapping Behavior of Callbacks - -JUnit Jupiter always guarantees _wrapping_ behavior for multiple registered extensions -that implement lifecycle callbacks such as `BeforeAllCallback`, `AfterAllCallback`, -`BeforeEachCallback`, `AfterEachCallback`, `BeforeTestExecutionCallback`, and -`AfterTestExecutionCallback`. - -That means that, given two extensions `Extension1` and `Extension2` with `Extension1` -registered before `Extension2`, any "before" callbacks implemented by `Extension1` are -guaranteed to execute **before** any "before" callbacks implemented by `Extension2`. -Similarly, given the two same two extensions registered in the same order, any "after" -callbacks implemented by `Extension1` are guaranteed to execute **after** any "after" -callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_ -`Extension2`. - -JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies -for user-supplied _lifecycle methods_ (see <>). - -* `@BeforeAll` methods are inherited from superclasses as long as they are not _hidden_, - _overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of - Java's visibility rules). Furthermore, `@BeforeAll` methods from superclasses will be - executed **before** `@BeforeAll` methods in subclasses. -** Similarly, `@BeforeAll` methods declared in an interface are inherited as long as they - are not _hidden_ or _overridden_, and `@BeforeAll` methods from an interface will be - executed **before** `@BeforeAll` methods in the class that implements the interface. -* `@AfterAll` methods are inherited from superclasses as long as they are not _hidden_, - _overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of - Java's visibility rules). Furthermore, `@AfterAll` methods from superclasses will be - executed **after** `@AfterAll` methods in subclasses. -** Similarly, `@AfterAll` methods declared in an interface are inherited as long as they - are not _hidden_ or _overridden_, and `@AfterAll` methods from an interface will be - executed **after** `@AfterAll` methods in the class that implements the interface. -* `@BeforeEach` methods are inherited from superclasses as long as they are not - _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of - Java's visibility rules). Furthermore, `@BeforeEach` methods from superclasses will be - executed **before** `@BeforeEach` methods in subclasses. -** Similarly, `@BeforeEach` methods declared as interface default methods are inherited as - long as they are not _overridden_, and `@BeforeEach` default methods will be executed - **before** `@BeforeEach` methods in the class that implements the interface. -* `@AfterEach` methods are inherited from superclasses as long as they are not - _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of - Java's visibility rules). Furthermore, `@AfterEach` methods from superclasses will be - executed **after** `@AfterEach` methods in subclasses. -** Similarly, `@AfterEach` methods declared as interface default methods are inherited as - long as they are not _overridden_, and `@AfterEach` default methods will be executed - **after** `@AfterEach` methods in the class that implements the interface. - -The following examples demonstrate this behavior. Please note that the examples do not -actually do anything realistic. Instead, they mimic common scenarios for testing -interactions with the database. All methods imported statically from the `Logger` class -log contextual information in order to help us better understand the execution order of -user-supplied callback methods and callback methods in extensions. - -[source,java,indent=0] -.Extension1 ----- -include::{testDir}/example/callbacks/Extension1.java[tags=user_guide] ----- - -[source,java,indent=0] -.Extension2 ----- -include::{testDir}/example/callbacks/Extension2.java[tags=user_guide] ----- - -[source,java,indent=0] -.AbstractDatabaseTests ----- -include::{testDir}/example/callbacks/AbstractDatabaseTests.java[tags=user_guide] ----- - -[source,java,indent=0] -.DatabaseTestsDemo ----- -include::{testDir}/example/callbacks/DatabaseTestsDemo.java[tags=user_guide] ----- - -When the `DatabaseTestsDemo` test class is executed, the following is logged. - ----- -@BeforeAll AbstractDatabaseTests.createDatabase() -@BeforeAll DatabaseTestsDemo.beforeAll() - Extension1.beforeEach() - Extension2.beforeEach() - @BeforeEach AbstractDatabaseTests.connectToDatabase() - @BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase() - @Test DatabaseTestsDemo.testDatabaseFunctionality() - @AfterEach DatabaseTestsDemo.deleteTestDataFromDatabase() - @AfterEach AbstractDatabaseTests.disconnectFromDatabase() - Extension2.afterEach() - Extension1.afterEach() -@BeforeAll DatabaseTestsDemo.afterAll() -@AfterAll AbstractDatabaseTests.destroyDatabase() ----- - -The following sequence diagram helps to shed further light on what actually goes on within -the `JupiterTestEngine` when the `DatabaseTestsDemo` test class is executed. - -//// -PNG generated using ZenUML: https://app.zenuml.com - -See corresponding *.txt file in images folder for the source. -//// -image::extensions_DatabaseTestsDemo.png[caption='',title='DatabaseTestsDemo'] - -JUnit Jupiter does **not** guarantee the execution order of multiple lifecycle methods -that are declared within a _single_ test class or test interface. It may at times appear -that JUnit Jupiter invokes such methods in alphabetical order. However, that is not -precisely true. The ordering is analogous to the ordering for `@Test` methods within a -single test class. - -[NOTE] -==== -Lifecycle methods that are declared within a _single_ test class or test interface will be -ordered using an algorithm that is deterministic but intentionally non-obvious. This -ensures that subsequent runs of a test suite execute lifecycle methods in the same order, -thereby allowing for repeatable builds. -==== - -In addition, JUnit Jupiter does **not** support _wrapping_ behavior for multiple lifecycle -methods declared within a single test class or test interface. - -The following example demonstrates this behavior. Specifically, the lifecycle method -configuration is _broken_ due to the order in which the locally declared lifecycle methods -are executed. - -* Test data is inserted _before_ the database connection has been opened, which results in - a failure to connect to the database. -* The database connection is closed _before_ deleting the test data, which results in a - failure to connect to the database. - -[source,java,indent=0] -.BrokenLifecycleMethodConfigDemo ----- -include::{testDir}/example/callbacks/BrokenLifecycleMethodConfigDemo.java[tags=user_guide] ----- - -When the `BrokenLifecycleMethodConfigDemo` test class is executed, the following is logged. - ----- -Extension1.beforeEach() -Extension2.beforeEach() - @BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase() - @BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase() - @Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality() - @AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase() - @AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase() -Extension2.afterEach() -Extension1.afterEach() ----- - -The following sequence diagram helps to shed further light on what actually goes on within -the `JupiterTestEngine` when the `BrokenLifecycleMethodConfigDemo` test class is executed. - -//// -PNG generated using ZenUML: https://app.zenuml.com - -See corresponding *.txt file in images folder for the source. -//// -image::extensions_BrokenLifecycleMethodConfigDemo.png[caption='',title='BrokenLifecycleMethodConfigDemo'] - -[TIP] -==== -Due to the aforementioned behavior, the JUnit Team recommends that developers declare at -most one of each type of _lifecycle method_ (see <>) -per test class or test interface unless there are no dependencies between such lifecycle -methods. -==== diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.png b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_BrokenLifecycleMethodConfigDemo.png deleted file mode 100644 index 3f661c200d6031f9622e7987969643e3cf2894c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60272 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 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt deleted file mode 100644 index 8da511fa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_DatabaseTestsDemo.txt +++ /dev/null @@ -1,27 +0,0 @@ -@Starter(Platform) -Result = JupiterEngine.execute(DatabaseTestsDemo) { - - // @BeforeAll (static invocation) - AbstractDatabaseTests.createDatabase() - // @BeforeAll (static invocation) - DatabaseTestsDemo.beforeAll() - Extension1.beforeEach() - Extension2.beforeEach() - // @BeforeEach inherited from AbstractDatabaseTests - DatabaseTestsDemo.connectToDatabase() - // @BeforeEach - DatabaseTestsDemo.insertTestDataIntoDatabase() - // @Test - DatabaseTestsDemo.testDatabaseFunctionality() - // @AfterEach - DatabaseTestsDemo.deleteTestDataFromDatabase() - // @AfterEach inherited from AbstractDatabaseTests - DatabaseTestsDemo.disconnectFromDatabase() - Extension2.afterEach() - Extension1.afterEach() - // @AfterAll (static invocation) - DatabaseTestsDemo.afterAll() - // @AfterAll (static invocation) - AbstractDatabaseTests.destroyDatabase() - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle.png b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle.png deleted file mode 100644 index bf8671b33f14e5734cf783128583603326cff54e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223256 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

8p%Mdq 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 F`+o(w=xqQ1 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle_source.docx b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/extensions_lifecycle_source.docx deleted file mode 100644 index ed2d03f158c31a403164fe66fce94f2fcbb02f9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15930 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 y;LuO`d;kCK^7%XXujAz(PynEj2019-01-012019-01-012019-01-022019-01-022019-01-032019-01-032019-01-042019-01-042019-01-05A.test1() A.test2() B.test1() B.test2() A.test1() A.test2() B.test1() B.test2() A.test1() A.test2() B.test1() B.test2() A.test1() A.test2() B.test1() B.test2() (same_thread, same_thread)(same_thread, concurrent)(concurrent, same_thread)(concurrent, concurrent) \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/images/writing-tests_nested_test_ide.png deleted file mode 100644 index 8a5dd0a1d1587bdeceb95ba49d5daee71f1df8ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230224 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 z 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 h2EPt_KAIC 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

*Yu9?kYueZX07fPg7FKePXyy`SA1+k?Nae>Sre zen0>D-j#($vhVMok4iWEq<2mJn!L2~(<@i6#b1l>W`4bXvhLdbYk=_I`LE&GJ!dLUUHx3vIVCg!0CIfKCjbBd diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc deleted file mode 100644 index 57e0f309..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/index.adoc +++ /dev/null @@ -1,44 +0,0 @@ -[[user-guide]] -= JUnit 5 User Guide -Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juliette de Rancourt; Christian Stein -:basedir: {includedir}/user-guide -:pdf-fontsdir: GEM_FONTS_DIR;{includedir}/resources/fonts -:pdf-theme: {includedir}/resources/themes/junit-pdf-theme.yml -:imagesdir: images -:imagesoutdir: {outdir}/user-guide/images -// -:docinfodir: {includedir}/docinfos -:docinfo2: -// -ifdef::backend-pdf[:imagesdir: {imagesoutdir}] -// -// Blank lines are not permitted in the doc-header: https://asciidoctor.org/docs/user-manual/#doc-header -// -:sectnums: -:toclevels: 4 -// - -include::{includedir}/link-attributes.adoc[] - -include::{basedir}/overview.adoc[] - -include::{basedir}/writing-tests.adoc[] - -include::{basedir}/migration-from-junit4.adoc[] - -include::{basedir}/running-tests.adoc[] - -include::{basedir}/extensions.adoc[] - -include::{basedir}/advanced-topics.adoc[] - -include::{basedir}/api-evolution.adoc[] - -include::{basedir}/contributors.adoc[] - -[[release-notes]] -== Release Notes - -The release notes are available link:{releaseNotesUrl}[here]. - -include::{basedir}/appendix.adoc[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc deleted file mode 100644 index 09d351fb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc +++ /dev/null @@ -1,155 +0,0 @@ -[[migrating-from-junit4]] -== Migrating from JUnit 4 - -Although the JUnit Jupiter programming model and extension model do not support JUnit 4 -features such as `Rules` and `Runners` natively, it is not expected that source code -maintainers will need to update all of their existing tests, test extensions, and custom -build test infrastructure to migrate to JUnit Jupiter. - -Instead, JUnit provides a gentle migration path via a _JUnit Vintage test engine_ which -allows existing tests based on JUnit 3 and JUnit 4 to be executed using the JUnit Platform -infrastructure. Since all classes and annotations specific to JUnit Jupiter reside under -the `org.junit.jupiter` base package, having both JUnit 4 and JUnit Jupiter in the -classpath does not lead to any conflicts. It is therefore safe to maintain existing JUnit -4 tests alongside JUnit Jupiter tests. Furthermore, since the JUnit team will continue to -provide maintenance and bug fix releases for the JUnit 4.x baseline, developers have -plenty of time to migrate to JUnit Jupiter on their own schedule. - - -[[migrating-from-junit4-running]] -=== Running JUnit 4 Tests on the JUnit Platform - -Make sure that the `junit-vintage-engine` artifact is in your test runtime path. In that -case JUnit 3 and JUnit 4 tests will automatically be picked up by the JUnit Platform -launcher. - -See the example projects in the {junit5-samples-repo}[`junit5-samples`] repository to -find out how this is done with Gradle and Maven. - -[[migrating-from-junit4-categories-support]] -==== Categories Support - -For test classes or methods that are annotated with `@Category`, the _JUnit Vintage test -engine_ exposes the category's fully qualified class name as a <> -for the corresponding test class or test method. For example, if a test method is -annotated with `@Category(Example.class)`, it will be tagged with `"com.acme.Example"`. -Similar to the `Categories` runner in JUnit 4, this information can be used to filter the -discovered tests before executing them (see <> for details). - - -[[migrating-from-junit4-tips]] -=== Migration Tips - -The following are topics that you should be aware of when migrating existing JUnit 4 -tests to JUnit Jupiter. - -* Annotations reside in the `org.junit.jupiter.api` package. -* Assertions reside in `org.junit.jupiter.api.Assertions`. - - Note that you may continue to use assertion methods from `org.junit.Assert` or any - other assertion library such as {AssertJ}, {Hamcrest}, {Truth}, etc. -* Assumptions reside in `org.junit.jupiter.api.Assumptions`. - - Note that JUnit Jupiter 5.4 and later versions support methods from JUnit 4's - `org.junit.Assume` class for assumptions. Specifically, JUnit Jupiter supports JUnit - 4's `AssumptionViolatedException` to signal that a test should be aborted instead of - marked as a failure. -* `@Before` and `@After` no longer exist; use `@BeforeEach` and `@AfterEach` instead. -* `@BeforeClass` and `@AfterClass` no longer exist; use `@BeforeAll` and `@AfterAll` - instead. -* `@Ignore` no longer exists: use `@Disabled` or one of the other built-in - <> instead - - See also <>. -* `@Category` no longer exists; use `@Tag` instead. -* `@RunWith` no longer exists; superseded by `@ExtendWith`. -* `@Rule` and `@ClassRule` no longer exist; superseded by `@ExtendWith` and - `@RegisterExtension`. - - See also <>. -* Assertions and assumptions in JUnit Jupiter accept the failure message as their last - argument instead of the first one. - - See <> for details. - - -[[migrating-from-junit4-rule-support]] -=== Limited JUnit 4 Rule Support - -As stated above, JUnit Jupiter does not and will not support JUnit 4 rules natively. The -JUnit team realizes, however, that many organizations, especially large ones, are likely -to have large JUnit 4 code bases that make use of custom rules. To serve these -organizations and enable a gradual migration path the JUnit team has decided to support a -selection of JUnit 4 rules verbatim within JUnit Jupiter. This support is based on -adapters and is limited to those rules that are semantically compatible to the JUnit -Jupiter extension model, i.e. those that do not completely change the overall execution -flow of the test. - -The `junit-jupiter-migrationsupport` module from JUnit Jupiter currently supports the -following three `Rule` types including subclasses of these types: - -* `org.junit.rules.ExternalResource` (including `org.junit.rules.TemporaryFolder`) -* `org.junit.rules.Verifier` (including `org.junit.rules.ErrorCollector`) -* `org.junit.rules.ExpectedException` - -As in JUnit 4, Rule-annotated fields as well as methods are supported. By using these -class-level extensions on a test class such `Rule` implementations in legacy code bases -can be _left unchanged_ including the JUnit 4 rule import statements. - -This limited form of `Rule` support can be switched on by the class-level annotation -`{EnableRuleMigrationSupport}`. This annotation is a _composed annotation_ which enables -all rule migration support extensions: `VerifierSupport`, `ExternalResourceSupport`, and -`ExpectedExceptionSupport`. You may alternatively choose to annotate your test class with -`@EnableJUnit4MigrationSupport` which registers migration support for rules _and_ JUnit -4's `@Ignore` annotation (see <>). - -However, if you intend to develop a new extension for JUnit Jupiter please use the new -extension model of JUnit Jupiter instead of the rule-based model of JUnit 4. - - -[[migrating-from-junit4-ignore-annotation-support]] -=== JUnit 4 @Ignore Support - -In order to provide a smooth migration path from JUnit 4 to JUnit Jupiter, the -`junit-jupiter-migrationsupport` module provides support for JUnit 4's `@Ignore` -annotation analogous to Jupiter's `{Disabled}` annotation. - -To use `@Ignore` with JUnit Jupiter based tests, configure a _test_ dependency on the -`junit-jupiter-migrationsupport` module in your build and then annotate your test class -with `@ExtendWith(IgnoreCondition.class)` or `{EnableJUnit4MigrationSupport}` (which -automatically registers the `IgnoreCondition` along with -<>). The `IgnoreCondition` is an -`{ExecutionCondition}` that disables test classes or test methods that are annotated with -`@Ignore`. - -[source,java,indent=0] ----- -include::{testDir}/example/IgnoredTestsDemo.java[tags=user_guide] ----- - - -[[migrating-from-junit4-failure-message-arguments]] -=== Failure Message Arguments - -The `Assumptions` and `Assertions` classes in JUnit Jupiter declare arguments in a -different order than in JUnit 4. In JUnit 4 assertion and assumption methods accept the -failure message as the first argument; whereas, in JUnit Jupiter assertion and assumption -methods accept the failure message as the last argument. - -For instance, the method `assertEquals` in JUnit 4 is declared as `assertEquals(String -message, Object expected, Object actual)`, but in JUnit Jupiter it is declared as -`assertEquals(Object expected, Object actual, String message)`. The rationale for this is -that a failure message is _optional_, and optional arguments should be declared after -required arguments in a method signature. - -The methods affected by this change are the following: - -- Assertions - * `assertTrue` - * `assertFalse` - * `assertNull` - * `assertNotNull` - * `assertEquals` - * `assertNotEquals` - * `assertArrayEquals` - * `assertSame` - * `assertNotSame` - * `assertThrows` -- Assumptions - * `assumeTrue` - * `assumeFalse` diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc deleted file mode 100644 index d06a5eb5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/overview.adoc +++ /dev/null @@ -1,90 +0,0 @@ -[[overview]] -== Overview - -The goal of this document is to provide comprehensive reference documentation for -programmers writing tests, extension authors, and engine authors as well as build tool -and IDE vendors. - -ifdef::backend-html5[] -ifdef::linkToPdf[] -This document is also available as a link:{userGuidePdfFileName}[PDF download]. -endif::linkToPdf[] -endif::backend-html5[] - -[[overview-what-is-junit-5]] -=== What is JUnit 5? - -Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from -three different sub-projects. - -**JUnit 5 = _JUnit Platform_ + _JUnit Jupiter_ + _JUnit Vintage_** - -The **JUnit Platform** serves as a foundation for <> on the JVM. It also defines the `{TestEngine}` API for developing a testing -framework that runs on the platform. Furthermore, the platform provides a -<> to launch the platform from the -command line and the <> for running a custom test suite using -one or more test engines on the platform. First-class support for the JUnit Platform also -exists in popular IDEs (see <>, -<>, <>, and -<>) and build tools (see <>, -<>, and <>). - -**JUnit Jupiter** is the combination of the <> and -<> for writing tests and extensions in JUnit 5. The Jupiter -sub-project provides a `TestEngine` for running Jupiter based tests on the platform. - -**JUnit Vintage** provides a `TestEngine` for running JUnit 3 and JUnit 4 based tests on -the platform. It requires JUnit 4.12 or later to be present on the class path or module -path. - -[[overview-java-versions]] -=== Supported Java Versions - -JUnit 5 requires Java 8 (or higher) at runtime. However, you can still test code that -has been compiled with previous versions of the JDK. - -[[overview-getting-help]] -=== Getting Help - -Ask JUnit 5 related questions on {StackOverflow} or chat with the community on {Gitter}. - -[[overview-getting-started]] -=== Getting Started - -[[overview-getting-started-junit-artifacts]] -==== Downloading JUnit Artifacts - -To find out what artifacts are available for download and inclusion in your project, refer -to <>. To set up dependency management for your build, refer to -<> and the <>. - -[[overview-getting-started-features]] -==== JUnit 5 Features - -To find out what features are available in JUnit 5 and how to use them, read the -corresponding sections of this User Guide, organized by topic. - -* <> -* <> -* <> -* <> -* Advanced Topics - - <> - - <> - -[[overview-getting-started-example-projects]] -==== Example Projects - -To see complete, working examples of projects that you can copy and experiment with, the -{junit5-samples-repo}[`junit5-samples`] repository is a good place to start. The -`junit5-samples` repository hosts a collection of sample projects based on JUnit Jupiter, -JUnit Vintage, and other testing frameworks. You'll find appropriate build scripts (e.g., -`build.gradle`, `pom.xml`, etc.) in the example projects. The links below highlight some -of the combinations you can choose from. - -* For Gradle and Java, check out the `{junit5-jupiter-starter-gradle}` project. -* For Gradle and Kotlin, check out the `{junit5-jupiter-starter-gradle-kotlin}` project. -* For Gradle and Groovy, check out the `{junit5-jupiter-starter-gradle-groovy}` project. -* For Maven, check out the `{junit5-jupiter-starter-maven}` project. -* For Ant, check out the `{junit5-jupiter-starter-ant}` project. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc deleted file mode 100644 index e88d0056..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/running-tests.adoc +++ /dev/null @@ -1,1073 +0,0 @@ -[[running-tests]] -== Running Tests - -[[running-tests-ide]] -=== IDE Support - -[[running-tests-ide-intellij-idea]] -==== IntelliJ IDEA - -IntelliJ IDEA supports running tests on the JUnit Platform since version 2016.2. For -details please see the -https://blog.jetbrains.com/idea/2016/08/using-junit-5-in-intellij-idea/[post on the -IntelliJ IDEA blog]. Note, however, that it is recommended to use IDEA 2017.3 or newer -since these newer versions of IDEA will download the following JARs automatically based -on the API version used in the project: `junit-platform-launcher`, -`junit-jupiter-engine`, and `junit-vintage-engine`. - -WARNING: IntelliJ IDEA releases prior to IDEA 2017.3 bundle specific versions of JUnit 5. -Thus, if you want to use a newer version of JUnit Jupiter, execution of tests within the -IDE might fail due to version conflicts. In such cases, please follow the instructions -below to use a newer version of JUnit 5 than the one bundled with IntelliJ IDEA. - -In order to use a different JUnit 5 version (e.g., {jupiter-version}), you may need to -include the corresponding versions of the `junit-platform-launcher`, -`junit-jupiter-engine`, and `junit-vintage-engine` JARs in the classpath. - -.Additional Gradle Dependencies -[source,groovy] -[subs=attributes+] ----- -testImplementation(platform("org.junit:junit-bom:{bom-version}")) -testRuntimeOnly("org.junit.platform:junit-platform-launcher") { - because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions") -} -testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") -testRuntimeOnly("org.junit.vintage:junit-vintage-engine") ----- - -.Additional Maven Dependencies -[source,xml] -[subs=attributes+] ----- - - - - - org.junit.platform - junit-platform-launcher - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.vintage - junit-vintage-engine - test - - - - - - org.junit - junit-bom - {bom-version} - pom - import - - - ----- - -[[running-tests-ide-eclipse]] -==== Eclipse - -Eclipse IDE offers support for the JUnit Platform since the Eclipse Oxygen.1a (4.7.1a) -release. - -For more information on using JUnit 5 in Eclipse consult the official _Eclipse support -for JUnit 5_ section of the -https://www.eclipse.org/eclipse/news/4.7.1a/#junit-5-support[Eclipse Project Oxygen.1a -(4.7.1a) - New and Noteworthy] documentation. - -[[running-tests-ide-netbeans]] -==== NetBeans - -NetBeans offers support for JUnit Jupiter and the JUnit Platform since the -https://netbeans.apache.org/download/nb100/nb100.html[Apache NetBeans 10.0 release]. - -For more information consult the JUnit 5 section of the -https://netbeans.apache.org/download/nb100/index.html#_junit_5[Apache NetBeans 10.0 -release notes]. - -[[running-tests-ide-vscode]] -==== Visual Studio Code - -https://code.visualstudio.com/[Visual Studio Code] supports JUnit Jupiter and the JUnit -Platform via the -https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test[Java Test -Runner] extension which is installed by default as part of the -https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack[Java -Extension Pack]. - -For more information consult the _Testing_ section of the -https://code.visualstudio.com/docs/languages/java#_testing[Java in Visual Studio Code] -documentation. - -[[running-tests-ide-other]] -==== Other IDEs - -If you are using an editor or IDE other than one of those listed in the previous sections, -the JUnit team provides two alternative solutions to assist you in using JUnit 5. You can -use the <> manually -- for example, from the command line --- or execute tests with a <> if -your IDE has built-in support for JUnit 4. - -[[running-tests-build]] -=== Build Support - -[[running-tests-build-gradle]] -==== Gradle - -[WARNING] -.The JUnit Platform Gradle Plugin has been discontinued -==== -The `junit-platform-gradle-plugin` developed by the JUnit team was deprecated in JUnit -Platform 1.2 and discontinued in 1.3. Please switch to Gradle's standard `test` task. -==== - -Starting with https://docs.gradle.org/4.6/release-notes.html[version 4.6], Gradle provides -https://docs.gradle.org/current/userguide/java_testing.html#using_junit5[native support] -for executing tests on the JUnit Platform. To enable it, you need to specify -`useJUnitPlatform()` within a `test` task declaration in `build.gradle`: - -[source,groovy,indent=0] -[subs=attributes+] ----- -test { - useJUnitPlatform() -} ----- - -Filtering by <>, -<>, or engines is also supported: - -[source,groovy,indent=0] -[subs=attributes+] ----- -test { - useJUnitPlatform { - includeTags("fast", "smoke & feature-a") - // excludeTags("slow", "ci") - includeEngines("junit-jupiter") - // excludeEngines("junit-vintage") - } -} ----- - -Please refer to the -https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_test[official Gradle documentation] -for a comprehensive list of options. - -NOTE: See <> for details on how to override the version -of JUnit used in your Spring Boot application. - -[[running-tests-build-gradle-config-params]] -===== Configuration Parameters - -The standard Gradle `test` task currently does not provide a dedicated DSL to set JUnit -Platform <> to influence test -discovery and execution. However, you can provide configuration parameters within the -build script via system properties (as shown below) or via the -`junit-platform.properties` file. - -[source,groovy,indent=0] ----- -test { - // ... - systemProperty("junit.jupiter.conditions.deactivate", "*") - systemProperty("junit.jupiter.extensions.autodetection.enabled", true) - systemProperty("junit.jupiter.testinstance.lifecycle.default", "per_class") - // ... -} ----- - -[[running-tests-build-gradle-engines-configure]] -===== Configuring Test Engines - -In order to run any tests at all, a `TestEngine` implementation must be on the classpath. - -To configure support for JUnit Jupiter based tests, configure a `testImplementation` dependency -on the dependency-aggregating JUnit Jupiter artifact similar to the following. - -[source,groovy,indent=0] -[subs=attributes+] ----- -dependencies { - testImplementation("org.junit.jupiter:junit-jupiter:{jupiter-version}") -} ----- - -The JUnit Platform can run JUnit 4 based tests as long as you configure a `testImplementation` -dependency on JUnit 4 and a `testRuntimeOnly` dependency on the JUnit Vintage `TestEngine` -implementation similar to the following. - -[source,groovy,indent=0] -[subs=attributes+] ----- -dependencies { - testImplementation("junit:junit:{junit4-version}") - testRuntimeOnly("org.junit.vintage:junit-vintage-engine:{vintage-version}") -} ----- - -[[running-tests-build-gradle-logging]] -===== Configuring Logging (optional) - -JUnit uses the Java Logging APIs in the `java.util.logging` package (a.k.a. _JUL_) to -emit warnings and debug information. Please refer to the official documentation of -`{LogManager}` for configuration options. - -Alternatively, it's possible to redirect log messages to other logging frameworks such as -{Log4j} or {Logback}. To use a logging framework that provides a custom implementation of -`{LogManager}`, set the `java.util.logging.manager` system property to the _fully -qualified class name_ of the `{LogManager}` implementation to use. The example below -demonstrates how to configure Log4j{nbsp}2.x (see {Log4j_JDK_Logging_Adapter} for -details). - -[source,groovy,indent=0] -[subs=attributes+] ----- -test { - systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") -} ----- - -Other logging frameworks provide different means to redirect messages logged using -`java.util.logging`. For example, for {Logback} you can use the -https://www.slf4j.org/legacy.html#jul-to-slf4j[JUL to SLF4J Bridge] by adding an -additional dependency to the runtime classpath. - -[[running-tests-build-maven]] -==== Maven - -Starting with https://issues.apache.org/jira/browse/SUREFIRE-1330[version 2.22.0], Maven -Surefire and Maven Failsafe provide -https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html[native support] -for executing tests on the JUnit Platform. The `pom.xml` file in the -`{junit5-jupiter-starter-maven}` project demonstrates how to use the Maven Surefire plugin -and can serve as a starting point for configuring your Maven build. - -[WARNING] -.Use Maven Surefire/Failsafe 3.0.0-M4 or later to avoid interoperability issues -==== -Maven Surefire/Failsafe 3.0.0-M4 -https://issues.apache.org/jira/browse/SUREFIRE-1585[introduced support] for aligning the -version of the JUnit Platform Launcher it uses with the JUnit Platform version found on -the test runtime classpath. Therefore, it is recommended to use version 3.0.0-M4 or later -to avoid interoperability issues. - -Alternatively, you can add a test dependency on the matching version of the JUnit Platform -Launcher to your Maven build as follows. - -[source,xml] -[subs=attributes+] ----- - - org.junit.platform - junit-platform-launcher - {platform-version} - test - ----- -==== - -TIP: See <> for details on how to override the version -of JUnit used in your Spring Boot application. - -[[running-tests-build-maven-engines-configure]] -===== Configuring Test Engines - -In order to have Maven Surefire or Maven Failsafe run any tests at all, at least one -`TestEngine` implementation must be added to the test classpath. - -To configure support for JUnit Jupiter based tests, configure `test` scoped dependencies -on the JUnit Jupiter API and the JUnit Jupiter `TestEngine` implementation similar to the -following. - -[source,xml,indent=0] -[subs=attributes+] ----- - - - - - org.junit.jupiter - junit-jupiter - {jupiter-version} - test - - - - - - - maven-surefire-plugin - {surefire-version} - - - maven-failsafe-plugin - {surefire-version} - - - - ----- - -Maven Surefire and Maven Failsafe can run JUnit 4 based tests alongside Jupiter tests as -long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintage -`TestEngine` implementation similar to the following. - -[source,xml,indent=0] -[subs=attributes+] ----- - - - - - junit - junit - {junit4-version} - test - - - org.junit.vintage - junit-vintage-engine - {vintage-version} - test - - - - - - - - maven-surefire-plugin - {surefire-version} - - - maven-failsafe-plugin - {surefire-version} - - - - ----- - -[[running-tests-build-maven-filter-test-class-names]] -===== Filtering by Test Class Names - -The Maven Surefire Plugin will scan for test classes whose fully qualified names match -the following patterns. - -- `+++**/Test*.java+++` -- `+++**/*Test.java+++` -- `+++**/*Tests.java+++` -- `+++**/*TestCase.java+++` - -Moreover, it will exclude all nested classes (including static member classes) by default. - -Note, however, that you can override this default behavior by configuring explicit -`include` and `exclude` rules in your `pom.xml` file. For example, to keep Maven Surefire -from excluding static member classes, you can override its exclude rules as follows. - -[source,xml,indent=0] -[subs=attributes+] -.Overriding exclude rules of Maven Surefire ----- - - - - - maven-surefire-plugin - {surefire-version} - - - - - - - - - ----- - -Please see the -https://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html[Inclusions and Exclusions of Tests] -documentation for Maven Surefire for details. - -[[running-tests-build-maven-filter-tags]] -===== Filtering by Tags - -You can filter tests by <> or -<> using the following configuration -properties. - -- to include _tags_ or _tag expressions_, use `groups`. -- to exclude _tags_ or _tag expressions_, use `excludedGroups`. - -[source,xml,indent=0] -[subs=attributes+] ----- - - - - - maven-surefire-plugin - {surefire-version} - - acceptance | !feature-a - integration, regression - - - - - ----- - -[[running-tests-build-maven-config-params]] -===== Configuration Parameters - -You can set JUnit Platform <> to -influence test discovery and execution by declaring the `configurationParameters` -property and providing key-value pairs using the Java `Properties` file syntax (as shown -below) or via the `junit-platform.properties` file. - -[source,xml,indent=0] -[subs=attributes+] ----- - - - - - maven-surefire-plugin - {surefire-version} - - - - junit.jupiter.conditions.deactivate = * - junit.jupiter.extensions.autodetection.enabled = true - junit.jupiter.testinstance.lifecycle.default = per_class - - - - - - - ----- - -[[running-tests-build-ant]] -==== Ant - -Starting with version `1.10.3`, link:https://ant.apache.org/[Ant] has a -link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher`] task that -provides native support for launching tests on the JUnit Platform. The `junitlauncher` -task is solely responsible for launching the JUnit Platform and passing it the selected -collection of tests. The JUnit Platform then delegates to registered test engines to -discover and execute the tests. - -The `junitlauncher` task attempts to align as closely as possible with native Ant -constructs such as -link:https://ant.apache.org/manual/Types/resources.html#collection[resource collections] -for allowing users to select the tests that they want executed by test engines. This gives -the task a consistent and natural feel when compared to many other core Ant tasks. - -Starting with version `1.10.6` of Ant, the `junitlauncher` task supports -link:https://ant.apache.org/manual/Tasks/junitlauncher.html#fork[forking the tests in a separate JVM]. - -The `build.xml` file in the `{junit5-jupiter-starter-ant}` project demonstrates how to use -the task and can serve as a starting point. - -===== Basic Usage - -The following example demonstrates how to configure the `junitlauncher` task to select a -single test class (i.e., `org.myapp.test.MyFirstJUnit5Test`). - -[source,xml,indent=0] ----- - - - - - - - - - - - ----- - -The `test` element allows you to specify a single test class that you want to be selected -and executed. The `classpath` element allows you to specify the classpath to be used to -launch the JUnit Platform. This classpath will also be used to locate test classes that -are part of the execution. - -The following example demonstrates how to configure the `junitlauncher` task to select -test classes from multiple locations. - -[source,xml,indent=0] ----- - - - - - - - - - - - - - - - - ----- - -In the above example, the `testclasses` element allows you to select multiple test -classes that reside in different locations. - -For further details on usage and configuration options please refer to the official Ant -documentation for the -link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher` task]. - -[[running-tests-build-spring-boot]] -==== Spring Boot - -link:https://spring.io/projects/spring-boot[Spring Boot] provides automatic support for -managing the version of JUnit used in your project. In addition, the -`spring-boot-starter-test` artifact automatically includes testing libraries such as JUnit -Jupiter, AssertJ, Mockito, etc. - -If your build relies on dependency management support from Spring Boot, you should not -import the <> in your build script since that -will result in duplicate (and potentially conflicting) management of JUnit dependencies. - -If you need to override the version of a dependency used in your Spring Boot application, -you have to override the exact name of the -link:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.dependency-versions.properties[version property] -defined in the BOM used by the Spring Boot plugin. For example, the name of the JUnit -Jupiter version property in Spring Boot is `junit-jupiter.version`. The mechanism for -changing a dependency version is documented for both -link:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#managing-dependencies.dependency-management-plugin.customizing[Gradle] -and -link:https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#using.parent-pom[Maven]. - -With Gradle you can override the JUnit Jupiter version by including the following in your -`build.gradle` file. - -[source,groovy,indent=0] -[subs=attributes+] ----- - ext['junit-jupiter.version'] = '{jupiter-version}' ----- - -With Maven you can override the JUnit Jupiter version by including the following in your -`pom.xml` file. - -[source,xml,indent=0] -[subs=attributes+] ----- - - {jupiter-version} - ----- - -[[running-tests-console-launcher]] -=== Console Launcher - -The `{ConsoleLauncher}` is a command-line Java application that lets you launch the JUnit -Platform from the console. For example, it can be used to run JUnit Vintage and JUnit -Jupiter tests and print test execution results to the console. - -An executable `junit-platform-console-standalone-{platform-version}.jar` with all -dependencies included is published in the {Maven_Central} repository under the -https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone] -directory. It includes the following dependencies: - -include::{standaloneConsoleLauncherShadowedArtifactsFile}[] - -You can https://docs.oracle.com/javase/tutorial/deployment/jar/run.html[run] the -standalone `ConsoleLauncher` as shown below. - -[source,console,subs=attributes+] ----- -$ java -jar junit-platform-console-standalone-{platform-version}.jar - -├─ JUnit Vintage -│ └─ example.JUnit4Tests -│ └─ standardJUnit4Test ✔ -└─ JUnit Jupiter - ├─ StandardTests - │ ├─ succeedingTest() ✔ - │ └─ skippedTest() ↷ for demonstration purposes - └─ A special test case - ├─ Custom test name containing spaces ✔ - ├─ ╯°□°)╯ ✔ - └─ 😱 ✔ - -Test run finished after 64 ms -[ 5 containers found ] -[ 0 containers skipped ] -[ 5 containers started ] -[ 0 containers aborted ] -[ 5 containers successful ] -[ 0 containers failed ] -[ 6 tests found ] -[ 1 tests skipped ] -[ 5 tests started ] -[ 0 tests aborted ] -[ 5 tests successful ] -[ 0 tests failed ] ----- - -You can also run the standalone `ConsoleLauncher` as shown below (for example, to include -all jars in a directory): - -[source,console,subs=attributes+] ----- -$ java -cp classes:testlib/* org.junit.platform.console.ConsoleLauncher ----- - -.Exit Code -NOTE: The `{ConsoleLauncher}` exits with a status code of `1` if any containers or tests -failed. If no tests are discovered and the `--fail-if-no-tests` command-line option is -supplied, the `ConsoleLauncher` exits with a status code of `2`. Otherwise the exit code -is `0`. - -[[running-tests-console-launcher-options]] -==== Options - ----- -include::{consoleLauncherOptionsFile}[] ----- - -[[running-tests-console-launcher-argument-files]] -==== Argument Files (@-files) - -On some platforms you may run into system limitations on the length of a command line -when creating a command line with lots of options or with long arguments. - -Since version 1.3, the `ConsoleLauncher` supports _argument files_, also known as -_@-files_. Argument files are files that themselves contain arguments to be passed to the -command. When the underlying https://github.com/remkop/picocli[picocli] command line -parser encounters an argument beginning with the character `@`, it expands the contents -of that file into the argument list. - -The arguments within a file can be separated by spaces or newlines. If an argument -contains embedded whitespace, the whole argument should be wrapped in double or single -quotes -- for example, `"-f=My Files/Stuff.java"`. - -If the argument file does not exist or cannot be read, the argument will be treated -literally and will not be removed. This will likely result in an "unmatched argument" -error message. You can troubleshoot such errors by executing the command with the -`picocli.trace` system property set to `DEBUG`. - -Multiple _@-files_ may be specified on the command line. The specified path may be -relative to the current directory or absolute. - -You can pass a real parameter with an initial `@` character by escaping it with an -additional `@` symbol. For example, `@@somearg` will become `@somearg` and will not be -subject to expansion. - -[[running-tests-console-launcher-color-customization]] -==== Color customization - -The colors used in the output of the `{ConsoleLauncher}` can be customized. -The option `--single-color` will apply a built-in monochrome style, while -`--color-palette` will accept a properties file to override the -https://en.wikipedia.org/wiki/ANSI_escape_code#Colors[ANSI SGR] color styling. -The properties file below demonstrates the default style: - -[source,properties,indent=0] ----- -SUCCESSFUL = 32 -ABORTED = 33 -FAILED = 31 -SKIPPED = 35 -CONTAINER = 35 -TEST = 34 -DYNAMIC = 35 -REPORTED = 37 ----- - - -[[running-tests-junit-platform-runner]] -=== Using JUnit 4 to run the JUnit Platform - -[WARNING] -.The `JUnitPlatform` runner has been deprecated -==== -The `JUnitPlatform` runner was developed by the JUnit team as an interim solution for -running test suites and tests on the JUnit Platform in a JUnit 4 environment. - -In recent years, all mainstream build tools and IDEs provide built-in support for running -tests directly on the JUnit Platform. - -In addition, the introduction of `@Suite` support provided by the -`junit-platform-suite-engine` module makes the `JUnitPlatform` runner obsolete. See -<> for details. - -The `JUnitPlatform` runner and `@UseTechnicalNames` annotation have therefore been -deprecated in JUnit Platform 1.8 and will be removed in JUnit Platform 2.0. - -If you are using the `JUnitPlatform` runner, please migrate to the `@Suite` support. -==== - -The `JUnitPlatform` runner is a JUnit 4 based `Runner` which enables you to run any test -whose programming model is supported on the JUnit Platform in a JUnit 4 environment -- -for example, a JUnit Jupiter test class. - -Annotating a class with `@RunWith(JUnitPlatform.class)` allows it to be run with IDEs and -build systems that support JUnit 4 but do not yet support the JUnit Platform directly. - -NOTE: Since the JUnit Platform has features that JUnit 4 does not have, the runner is -only able to support a subset of the JUnit Platform functionality, especially with regard -to reporting (see <>). - -[[running-tests-junit-platform-runner-setup]] -==== Setup - -You need the following artifacts and their dependencies on the classpath. See -<> for details regarding group IDs, artifact IDs, and versions. - -[[running-tests-junit-platform-runner-setup-explicit-dependencies]] -===== Explicit Dependencies - -* `junit-platform-runner` in _test_ scope: location of the `JUnitPlatform` runner -* `junit-{junit4-version}.jar` in _test_ scope: to run tests using JUnit 4 -* `junit-jupiter-api` in _test_ scope: API for writing tests using JUnit Jupiter, - including `@Test`, etc. -* `junit-jupiter-engine` in _test runtime_ scope: implementation of the `TestEngine` API - for JUnit Jupiter - -[[running-tests-junit-platform-runner-setup-transitive-dependencies]] -===== Transitive Dependencies - -* `junit-platform-suite-api` in _test_ scope -* `junit-platform-suite-commons` in _test_ scope -* `junit-platform-launcher` in _test_ scope -* `junit-platform-engine` in _test_ scope -* `junit-platform-commons` in _test_ scope -* `opentest4j` in _test_ scope - -[[running-tests-junit-platform-runner-technical-names]] -==== Display Names vs. Technical Names - -To define a custom _display name_ for the class run via `@RunWith(JUnitPlatform.class)` -annotate the class with `@SuiteDisplayName` and provide a custom value. - -By default, _display names_ will be used for test artifacts; however, when the -`JUnitPlatform` runner is used to execute tests with a build tool such as Gradle or -Maven, the generated test report often needs to include the _technical names_ of test -artifacts — for example, fully qualified class names — instead of shorter display names -like the simple name of a test class or a custom display name containing special -characters. To enable technical names for reporting purposes, declare the -`@UseTechnicalNames` annotation alongside `@RunWith(JUnitPlatform.class)`. - -Note that the presence of `@UseTechnicalNames` overrides any custom display name -configured via `@SuiteDisplayName`. - -[[running-tests-junit-platform-runner-single-test]] -==== Single Test Class - -One way to use the `JUnitPlatform` runner is to annotate a test class with -`@RunWith(JUnitPlatform.class)` directly. Please note that the test methods in the -following example are annotated with `org.junit.jupiter.api.Test` (JUnit Jupiter), not -`org.junit.Test` (JUnit 4). Moreover, in this case the test class must be `public`; -otherwise, some IDEs and build tools might not recognize it as a JUnit 4 test class. - -[source,java,indent=0] ----- -include::{testDir}/example/JUnitPlatformClassDemo.java[tags=user_guide] ----- - -[[running-tests-junit-platform-runner-test-suite]] -==== Test Suite - -If you have multiple test classes you can create a test suite as can be seen in the -following example. - -[source,java,indent=0] ----- -include::{testDir}/example/JUnitPlatformSuiteDemo.java[tags=user_guide] ----- - -The `JUnitPlatformSuiteDemo` will discover and run all tests in the `example` package and -its subpackages. By default, it will only include test classes whose names either begin -with `Test` or end with `Test` or `Tests`. - -.Additional Configuration Options -NOTE: There are more configuration options for discovering and filtering tests than just -`@SelectPackages`. Please consult the Javadoc of the `{suite-api-package}` package for -further details. - -WARNING: Test classes and suites annotated with `@RunWith(JUnitPlatform.class)` -**cannot** be executed directly on the JUnit Platform (or as a "JUnit 5" test as -documented in some IDEs). Such classes and suites can only be executed using JUnit 4 -infrastructure. - -[[running-tests-config-params]] -=== Configuration Parameters - -In addition to instructing the platform which test classes and test engines to include, -which packages to scan, etc., it is sometimes necessary to provide additional custom -configuration parameters that are specific to a particular test engine, listener, or -registered extension. For example, the JUnit Jupiter `TestEngine` supports _configuration -parameters_ for the following use cases. - -- <> -- <> -- <> -- <> - -_Configuration Parameters_ are text-based key-value pairs that can be supplied to test -engines running on the JUnit Platform via one of the following mechanisms. - -1. The `configurationParameter()` and `configurationParameters()` methods in the - `LauncherDiscoveryRequestBuilder` which is used to build a request supplied to the - <>. When running tests via one of the tools provided - by the JUnit Platform you can specify configuration parameters as follows: - * <>: use the `--config` - command-line option. - * <>: use the - `systemProperty` or `systemProperties` DSL. - * <>: use the - `configurationParameters` property. -2. JVM system properties. -3. The JUnit Platform configuration file: a file named `junit-platform.properties` in the - root of the class path that follows the syntax rules for a Java `Properties` file. - -NOTE: Configuration parameters are looked up in the exact order defined above. -Consequently, configuration parameters supplied directly to the `Launcher` take -precedence over those supplied via system properties and the configuration file. -Similarly, configuration parameters supplied via system properties take precedence over -those supplied via the configuration file. - -[[running-tests-config-params-deactivation-pattern]] -==== Pattern Matching Syntax - -This section describes the pattern matching syntax that is applied to the _configuration -parameters_ used for the following features. - -- <> -- <> - -If the value for the given _configuration parameter_ consists solely of an asterisk -(`+++*+++`), the pattern will match against all candidate classes. Otherwise, the value -will be treated as a comma-separated list of patterns where each pattern will be matched -against the fully qualified class name (_FQCN_) of each candidate class. Any dot (`.`) in -a pattern will match against a dot (`.`) or a dollar sign (`$`) in a FQCN. Any asterisk -(`+++*+++`) will match against one or more characters in a FQCN. All other characters in a -pattern will be matched one-to-one against a FQCN. - -Examples: - -- `+++*+++`: matches all candidate classes. -- `+++org.junit.*+++`: matches all candidate classes under the `org.junit` base package and - any of its subpackages. -- `+++*.MyCustomImpl+++`: matches every candidate class whose simple class name is exactly - `MyCustomImpl`. -- `+++*System*+++`: matches every candidate class whose FQCN contains `System`. -- `+++*System*+++, +++*Unit*+++`: matches every candidate class whose FQCN contains - `System` or `Unit`. -- `org.example.MyCustomImpl`: matches the candidate class whose FQCN is exactly - `org.example.MyCustomImpl`. -- `org.example.MyCustomImpl, org.example.TheirCustomImpl`: matches candidate classes whose - FQCN is exactly `org.example.MyCustomImpl` or `org.example.TheirCustomImpl`. - -[[running-tests-tags]] -=== Tags - -Tags are a JUnit Platform concept for marking and filtering tests. The programming model -for adding tags to containers and tests is defined by the testing framework. For example, -in JUnit Jupiter based tests, the `@Tag` annotation (see -<>) should be used. For JUnit 4 based tests, the -Vintage engine maps `@Category` annotations to tags (see -<>). Other testing frameworks may define their -own annotation or other means for users to specify tags. - -[[running-tests-tag-syntax-rules]] -==== Syntax Rules for Tags - -Regardless how a tag is specified, the JUnit Platform enforces the following rules: - -* A tag must not be `null` or _blank_. -* A _trimmed_ tag must not contain whitespace. -* A _trimmed_ tag must not contain ISO control characters. -* A _trimmed_ tag must not contain any of the following _reserved characters_. -- `,`: _comma_ -- `(`: _left parenthesis_ -- `)`: _right parenthesis_ -- `&`: _ampersand_ -- `|`: _vertical bar_ -- `!`: _exclamation point_ - -NOTE: In the above context, "trimmed" means that leading and trailing whitespace -characters have been removed. - -[[running-tests-tag-expressions]] -==== Tag Expressions - -Tag expressions are boolean expressions with the operators `!`, `&` and `|`. In addition, -`(` and `)` can be used to adjust for operator precedence. - -Two special expressions are supported, `any()` and `none()`, which select all tests _with_ -any tags at all, and all tests _without_ any tags, respectively. -These special expressions may be combined with other expressions just like normal tags. - -.Operators (in descending order of precedence) -|=== -| Operator | Meaning | Associativity - -| `!` | not | right -| `&` | and | left -| `\|` | or | left -|=== - -If you are tagging your tests across multiple dimensions, tag expressions help you to -select which tests to execute. When tagging by test type (e.g., _micro_, _integration_, -_end-to-end_) and feature (e.g., *product*, *catalog*, *shipping*), the following tag -expressions can be useful. - -[%header,cols="40,60"] -|=== -| Tag Expression -| Selection - -| `+++product+++` -| all tests for *product* - -| `+++catalog \| shipping+++` -| all tests for *catalog* plus all tests for *shipping* - -| `+++catalog & shipping+++` -| all tests for the intersection between *catalog* and *shipping* - -| `+++product & !end-to-end+++` -| all tests for *product*, but not the _end-to-end_ tests - -| `+++(micro \| integration) & (product \| shipping)+++` -| all _micro_ or _integration_ tests for *product* or *shipping* -|=== - -[[running-tests-capturing-output]] -=== Capturing Standard Output/Error - -Since version 1.3, the JUnit Platform provides opt-in support for capturing output -printed to `System.out` and `System.err`. To enable it, set the -`junit.platform.output.capture.stdout` and/or `junit.platform.output.capture.stderr` -<> to `true`. In addition, you may -configure the maximum number of buffered bytes to be used per executed test or container -using `junit.platform.output.capture.maxBuffer`. - -If enabled, the JUnit Platform captures the corresponding output and publishes it as a -report entry using the `stdout` or `stderr` keys to all registered -`{TestExecutionListener}` instances immediately before reporting the test or container as -finished. - -Please note that the captured output will only contain output emitted by the thread that -was used to execute a container or test. Any output by other threads will be omitted -because particularly when -<> it would be impossible -to attribute it to a specific test or container. - -[[running-tests-listeners]] -=== Using Listeners - -The JUnit Platform provides the following listener APIs that allow JUnit, third parties, -and custom user code to react to events fired at various points during the discovery and -execution of a `TestPlan`. - -* `{LauncherSessionListener}`: receives events when a `{LauncherSession}` is opened and - closed. -* `{LauncherDiscoveryListener}`: receives events that occur during test discovery. -* `{TestExecutionListener}`: receives events that occur during test execution. - -The `LauncherSessionListener` API is typically implemented by build tools or IDEs and -registered automatically for you in order to support some feature of the build tool or IDE. - -The `LauncherDiscoveryListener` and `TestExecutionListener` APIs are often implemented in -order to produce some form of report or to display a graphical representation of the test -plan in an IDE. Such listeners may be implemented and automatically registered by a build -tool or IDE, or they may be included in a third-party library – potentially registered -for you automatically. You can also implement and register your own listeners. - -For details on registering and configuring listeners, see the following sections of this -guide. - -* <> -* <> -* <> -* <> -* <> - -The JUnit Platform provides the following listeners which you may wish to use with your -test suite. - -<> :: - `{LegacyXmlReportGeneratingListener}` can be used via the - <> or registered manually to generate XML reports - compatible with the de facto standard for JUnit 4 based test reports. -+ -`{OpenTestReportGeneratingListener}` generates an XML report in the event-based format -specified by {OpenTestReporting}. It is auto-registered and can be enabled and -configured via <>. -+ -See <> for details. - -<> :: - `FlightRecordingExecutionListener` and `FlightRecordingDiscoveryListener` that generate - Java Flight Recorder events during test discovery and execution. - -`{LoggingListener}` :: - `TestExecutionListener` for logging informational messages for all events via a - `BiConsumer` that consumes `Throwable` and `Supplier`. - -`{SummaryGeneratingListener}` :: - `TestExecutionListener` that generates a summary of the test execution which can be - printed via a `PrintWriter`. - -`{UniqueIdTrackingListener}` :: - `TestExecutionListener` that that tracks the unique IDs of all tests that were skipped - or executed during the execution of the `TestPlan` and generates a file containing the - unique IDs once execution of the `TestPlan` has finished. - -[[running-tests-listeners-flight-recorder]] -==== Flight Recorder Support - -Since version 1.7, the JUnit Platform provides opt-in support for generating Flight -Recorder events. https://openjdk.java.net/jeps/328[JEP 328] describes the Java Flight -Recorder (JFR) as: - -NOTE: Flight Recorder records events originating from applications, the JVM and the OS. -Events are stored in a single file that can be attached to bug reports and examined by -support engineers, allowing after-the-fact analysis of issues in the period leading up -to a problem. - -In order to record Flight Recorder events generated while running tests, you need to: - -1. Ensure that you are using either Java 8 Update 262 or higher or Java 11 or later. -2. Provide the `org.junit.platform.jfr` module (`junit-platform-jfr-{platform-version}.jar`) - on the class-path or module-path at test runtime. -3. Start flight recording when launching a test run. Flight Recorder can be started via - java command line option: - - -XX:StartFlightRecording:filename=... - -Please consult the manual of your build tool for the appropriate commands. - -To analyze the recorded events, use the -https://docs.oracle.com/en/java/javase/14/docs/specs/man/jfr.html[jfr] -command line tool shipped with recent JDKs or open the recording file with -https://jdk.java.net/jmc/[JDK Mission Control]. - -WARNING: Flight Recorder support is currently an _experimental_ feature. You're invited to -give it a try and provide feedback to the JUnit team so they can improve and eventually -<> this feature. diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc deleted file mode 100644 index 45bddb0d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ /dev/null @@ -1,2554 +0,0 @@ -[[writing-tests]] -== Writing Tests - -The following example provides a glimpse at the minimum requirements for writing a test in -JUnit Jupiter. Subsequent sections of this chapter will provide further details on all -available features. - -[source,java,indent=0] -.A first test case ----- -include::{testDir}/example/MyFirstJUnitJupiterTests.java[tags=user_guide] ----- - -[[writing-tests-annotations]] -=== Annotations - -JUnit Jupiter supports the following annotations for configuring tests and extending the -framework. - -Unless otherwise stated, all core annotations are located in the `{api-package}` package -in the `junit-jupiter-api` module. - -[cols="20,80"] -|=== -| Annotation | Description - -| `@Test` | Denotes that a method is a test method. Unlike JUnit 4's `@Test` annotation, this annotation does not declare any attributes, since test extensions in JUnit Jupiter operate based on their own dedicated annotations. Such methods are _inherited_ unless they are _overridden_. -| `@ParameterizedTest` | Denotes that a method is a <>. Such methods are _inherited_ unless they are _overridden_. -| `@RepeatedTest` | Denotes that a method is a test template for a <>. Such methods are _inherited_ unless they are _overridden_. -| `@TestFactory` | Denotes that a method is a test factory for <>. Such methods are _inherited_ unless they are _overridden_. -| `@TestTemplate` | Denotes that a method is a <> designed to be invoked multiple times depending on the number of invocation contexts returned by the registered <>. Such methods are _inherited_ unless they are _overridden_. -| `@TestClassOrder` | Used to configure the <> for `@Nested` test classes in the annotated test class. Such annotations are _inherited_. -| `@TestMethodOrder` | Used to configure the <> for the annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are _inherited_. -| `@TestInstance` | Used to configure the <> for the annotated test class. Such annotations are _inherited_. -| `@DisplayName` | Declares a custom <> for the test class or test method. Such annotations are not _inherited_. -| `@DisplayNameGeneration` | Declares a custom <> for the test class. Such annotations are _inherited_. -| `@BeforeEach` | Denotes that the annotated method should be executed _before_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@Before`. Such methods are _inherited_ – unless they are _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of Java's visibility rules). -| `@AfterEach` | Denotes that the annotated method should be executed _after_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@After`. Such methods are _inherited_ – unless they are _overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of Java's visibility rules). -| `@BeforeAll` | Denotes that the annotated method should be executed _before_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@BeforeClass`. Such methods are _inherited_ – unless they are _hidden_, _overridden_, or _superseded_, (i.e., replaced based on signature only, irrespective of Java's visibility rules) – and must be `static` unless the "per-class" <> is used. -| `@AfterAll` | Denotes that the annotated method should be executed _after_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@AfterClass`. Such methods are _inherited_ – unless they are _hidden_, _overridden_, or _superseded_, (i.e., replaced based on signature only, irrespective of Java's visibility rules) – and must be `static` unless the "per-class" <> is used. -| `@Nested` | Denotes that the annotated class is a non-static <>. On Java 8 through Java 15, `@BeforeAll` and `@AfterAll` methods cannot be used directly in a `@Nested` test class unless the "per-class" <> is used. Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as `static` in a `@Nested` test class with either test instance lifecycle mode. Such annotations are not _inherited_. -| `@Tag` | Used to declare <>, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are _inherited_ at the class level but not at the method level. -| `@Disabled` | Used to <> a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not _inherited_. -| `@Timeout` | Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are _inherited_. -| `@ExtendWith` | Used to <>. Such annotations are _inherited_. -| `@RegisterExtension` | Used to <> via fields. Such fields are _inherited_ unless they are _shadowed_. -| `@TempDir` | Used to supply a <> via field injection or parameter injection in a lifecycle method or test method; located in the `org.junit.jupiter.api.io` package. -|=== - -WARNING: Some annotations may currently be _experimental_. Consult the table in -<> for details. - -[[writing-tests-meta-annotations]] -==== Meta-Annotations and Composed Annotations - -JUnit Jupiter annotations can be used as _meta-annotations_. That means that you can -define your own _composed annotation_ that will automatically _inherit_ the semantics of -its meta-annotations. - -For example, instead of copying and pasting `@Tag("fast")` throughout your code base (see -<>), you can create a custom _composed annotation_ -named `@Fast` as follows. `@Fast` can then be used as a drop-in replacement for -`@Tag("fast")`. - -[source,java,indent=0] ----- -include::{testDir}/example/Fast.java[tags=user_guide] ----- - -The following `@Test` method demonstrates usage of the `@Fast` annotation. - -[source,java,indent=0] ----- -@Fast -@Test -void myFastTest() { - // ... -} ----- - -You can even take that one step further by introducing a custom `@FastTest` annotation -that can be used as a drop-in replacement for `@Tag("fast")` _and_ `@Test`. - -[source,java,indent=0] ----- -include::{testDir}/example/FastTest.java[tags=user_guide] ----- - -JUnit automatically recognizes the following as a `@Test` method that is tagged with -"fast". - -[source,java,indent=0] ----- -@FastTest -void myFastTest() { - // ... -} ----- - -[[writing-tests-definitions]] -=== Definitions - -.Platform Concepts -**** -Container:: -a node in the test tree that contains other containers or tests as its children (e.g. a _test class_). - -Test:: -a node in the test tree that verifies expected behavior when executed (e.g. a `@Test` method). -**** - -.Jupiter Concepts -**** -Lifecycle Method:: -any method that is directly annotated or meta-annotated with -`@BeforeAll`, `@AfterAll`, `@BeforeEach`, or `@AfterEach`. - -Test Class:: -any top-level class, `static` member class, or <> that contains at least one _test method_, i.e. a _container_. -Test classes must not be `abstract` and must have a single constructor. - -Test Method:: -any instance method that is directly annotated or meta-annotated with -`@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, or `@TestTemplate`. -With the exception of `@Test`, these create a _container_ in the test tree that groups -_tests_ or, potentially (for `@TestFactory`), other _containers_. -**** - -[[writing-tests-classes-and-methods]] -=== Test Classes and Methods - -Test methods and lifecycle methods may be declared locally within the current test class, -inherited from superclasses, or inherited from interfaces (see -<>). In addition, test methods and -lifecycle methods must not be `abstract` and must not return a value (except `@TestFactory` -methods which are required to return a value). - -[NOTE] -.Class and method visibility -==== -Test classes, test methods, and lifecycle methods are not required to be `public`, but -they must _not_ be `private`. - -It is generally recommended to omit the `public` modifier for test classes, test methods, -and lifecycle methods unless there is a technical reason for doing so – for example, when -a test class is extended by a test class in another package. Another technical reason for -making classes and methods `public` is to simplify testing on the module path when using -the Java Module System. -==== - -The following test class demonstrates the use of `@Test` methods and all supported -lifecycle methods. For further information on runtime semantics, see -<> and -<>. - -[source,java,indent=0] -.A standard test class ----- -include::{testDir}/example/StandardTests.java[tags=user_guide] ----- - -[[writing-tests-display-names]] -=== Display Names - -Test classes and test methods can declare custom display names via `@DisplayName` -- with -spaces, special characters, and even emojis -- that will be displayed in test reports and -by test runners and IDEs. - -[source,java,indent=0] ----- -include::{testDir}/example/DisplayNameDemo.java[tags=user_guide] ----- - -[[writing-tests-display-name-generator]] -==== Display Name Generators - -JUnit Jupiter supports custom display name generators that can be configured via the -`@DisplayNameGeneration` annotation. Values provided via `@DisplayName` annotations -always take precedence over display names generated by a `DisplayNameGenerator`. - -Generators can be created by implementing `DisplayNameGenerator`. Here are some default -ones available in Jupiter: - -[cols="20,80"] -|=== -| DisplayNameGenerator | Behavior - -| `Standard` | Matches the standard display name generation behavior in place since JUnit Jupiter 5.0 was released. -| `Simple` | Removes trailing parentheses for methods with no parameters. -| `ReplaceUnderscores` | Replaces underscores with spaces. -| `IndicativeSentences` | Generates complete sentences by concatenating the names of the test and the enclosing classes. -|=== - -Note that for `IndicativeSentences`, you can customize the separator and the -underlying generator by using `@IndicativeSentencesGeneration` as shown in the -following example. - -[source,java,indent=0] ----- -include::{testDir}/example/DisplayNameGeneratorDemo.java[tags=user_guide] ----- - -``` -+-- DisplayNameGeneratorDemo [OK] - +-- A year is not supported [OK] - | +-- A negative value for year is not supported by the leap year computation. [OK] - | | +-- For example, year -1 is not supported. [OK] - | | '-- For example, year -4 is not supported. [OK] - | '-- if it is zero() [OK] - '-- A year is a leap year [OK] - +-- A year is a leap year -> if it is divisible by 4 but not by 100. [OK] - '-- A year is a leap year -> if it is one of the following years. [OK] - +-- Year 2016 is a leap year. [OK] - +-- Year 2020 is a leap year. [OK] - '-- Year 2048 is a leap year. [OK] -``` - - -[[writing-tests-display-name-generator-default]] -==== Setting the Default Display Name Generator - -You can use the `junit.jupiter.displayname.generator.default` -<> to specify the fully qualified -class name of the `DisplayNameGenerator` you would like to use by default. Just like for -display name generators configured via the `@DisplayNameGeneration` annotation, the -supplied class has to implement the `DisplayNameGenerator` interface. The default display -name generator will be used for all tests unless the `@DisplayNameGeneration` annotation -is present on an enclosing test class or test interface. Values provided via -`@DisplayName` annotations always take precedence over display names generated by a -`DisplayNameGenerator`. - -For example, to use the `ReplaceUnderscores` display name generator by default, you should -set the configuration parameter to the corresponding fully qualified class name (e.g., in -`src/test/resources/junit-platform.properties`): - -[source,properties,indent=0] ----- -junit.jupiter.displayname.generator.default = \ - org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores ----- - -Similarly, you can specify the fully qualified name of any custom class that implements -`DisplayNameGenerator`. - -[[writing-tests-display-name-generator-precedence-rules]] -In summary, the display name for a test class or method is determined according to the -following precedence rules: - -1. value of the `@DisplayName` annotation, if present -2. by calling the `DisplayNameGenerator` specified in the `@DisplayNameGeneration` - annotation, if present -3. by calling the default `DisplayNameGenerator` configured via the configuration - parameter, if present -4. by calling `org.junit.jupiter.api.DisplayNameGenerator.Standard` - -[[writing-tests-assertions]] -=== Assertions - -JUnit Jupiter comes with many of the assertion methods that JUnit 4 has and adds a few -that lend themselves well to being used with Java 8 lambdas. All JUnit Jupiter assertions -are `static` methods in the `{Assertions}` class. - -[source,java,indent=0] ----- -include::{testDir}/example/AssertionsDemo.java[tags=user_guide] ----- - -[[writing-tests-assertions-preemptive-timeouts]] -[WARNING] -.Preemptive Timeouts with `assertTimeoutPreemptively()` -==== -The various `assertTimeoutPreemptively()` methods in the `Assertions` class execute -the provided `executable` or `supplier` in a different thread than that of the calling -code. This behavior can lead to undesirable side effects if the code that is executed -within the `executable` or `supplier` relies on `java.lang.ThreadLocal` storage. - -One common example of this is the transactional testing support in the Spring Framework. -Specifically, Spring's testing support binds transaction state to the current thread (via -a `ThreadLocal`) before a test method is invoked. Consequently, if an `executable` or -`supplier` provided to `assertTimeoutPreemptively()` invokes Spring-managed components -that participate in transactions, any actions taken by those components will not be rolled -back with the test-managed transaction. On the contrary, such actions will be committed to -the persistent store (e.g., relational database) even though the test-managed transaction -is rolled back. - -Similar side effects may be encountered with other frameworks that rely on -`ThreadLocal` storage. -==== - -[[writing-tests-assertions-kotlin]] -==== Kotlin Assertion Support - -JUnit Jupiter also comes with a few assertion methods that lend themselves well to being -used in https://kotlinlang.org/[Kotlin]. All JUnit Jupiter Kotlin assertions are top-level -functions in the `org.junit.jupiter.api` package. - -[source,kotlin,indent=0] ----- -include::{kotlinTestDir}/example/KotlinAssertionsDemo.kt[tags=user_guide] ----- - -[[writing-tests-assertions-third-party]] -==== Third-party Assertion Libraries - -Even though the assertion facilities provided by JUnit Jupiter are sufficient for many -testing scenarios, there are times when more power and additional functionality such as -_matchers_ are desired or required. In such cases, the JUnit team recommends the use of -third-party assertion libraries such as {AssertJ}, {Hamcrest}, {Truth}, etc. Developers -are therefore free to use the assertion library of their choice. - -For example, the combination of _matchers_ and a fluent API can be used to make -assertions more descriptive and readable. However, JUnit Jupiter's `{Assertions}` class -does not provide an -https://junit.org/junit4/javadoc/latest/org/junit/Assert.html#assertThat[`assertThat()`] -method like the one found in JUnit 4's `org.junit.Assert` class which accepts a Hamcrest -https://junit.org/junit4/javadoc/latest/org/hamcrest/Matcher.html[`Matcher`]. Instead, -developers are encouraged to use the built-in support for matchers provided by third-party -assertion libraries. - -The following example demonstrates how to use the `assertThat()` support from Hamcrest in -a JUnit Jupiter test. As long as the Hamcrest library has been added to the classpath, -you can statically import methods such as `assertThat()`, `is()`, and `equalTo()` and -then use them in tests like in the `assertWithHamcrestMatcher()` method below. - -[source,java,indent=0] ----- -include::{testDir}/example/HamcrestAssertionsDemo.java[tags=user_guide] ----- - -Naturally, legacy tests based on the JUnit 4 programming model can continue using -`org.junit.Assert#assertThat`. - -[[writing-tests-assumptions]] -=== Assumptions - -JUnit Jupiter comes with a subset of the assumption methods that JUnit 4 provides and -adds a few that lend themselves well to being used with Java 8 lambda expressions and -method references. All JUnit Jupiter assumptions are static methods in the -`{Assumptions}` class. - -[source,java,indent=0] ----- -include::{testDir}/example/AssumptionsDemo.java[tags=user_guide] ----- - -NOTE: As of JUnit Jupiter 5.4, it is also possible to use methods from JUnit 4's -`org.junit.Assume` class for assumptions. Specifically, JUnit Jupiter supports JUnit 4's -`AssumptionViolatedException` to signal that a test should be aborted instead of marked -as a failure. - -[[writing-tests-disabling]] -=== Disabling Tests - -Entire test classes or individual test methods may be _disabled_ via the `{Disabled}` -annotation, via one of the annotations discussed in -<>, or via a custom <>. - -Here's a `@Disabled` test class. - -[source,java,indent=0] ----- -include::{testDir}/example/DisabledClassDemo.java[tags=user_guide] ----- - -And here's a test class that contains a `@Disabled` test method. - -[source,java,indent=0] ----- -include::{testDir}/example/DisabledTestsDemo.java[tags=user_guide] ----- - -NOTE: `@Disabled` may be declared without providing a _reason_; however, the JUnit team -recommends that developers provide a short explanation for why a test class or test -method has been disabled. Consequently, the above examples both show the use of a reason --- for example, `@Disabled("Disabled until bug #42 has been resolved")`. Some development -teams even require the presence of issue tracking numbers in the _reason_ for automated -traceability, etc. - -[[writing-tests-conditional-execution]] -=== Conditional Test Execution - -The <> extension API in JUnit Jupiter allows -developers to either _enable_ or _disable_ a container or test based on certain -conditions _programmatically_. The simplest example of such a condition is the built-in -`{DisabledCondition}` which supports the `{Disabled}` annotation (see -<>). In addition to `@Disabled`, JUnit Jupiter also supports -several other annotation-based conditions in the `org.junit.jupiter.api.condition` -package that allow developers to enable or disable containers and tests _declaratively_. -When multiple `ExecutionCondition` extensions are registered, a container or test is -disabled as soon as one of the conditions returns _disabled_. If you wish to provide -details about why they might be disabled, every annotation associated with these built-in -conditions has a `disabledReason` attribute available for that purpose. - -See <> and the following sections for -details. - -[TIP] -.Composed Annotations -==== -Note that any of the _conditional_ annotations listed in the following sections may also -be used as a meta-annotation in order to create a custom _composed annotation_. For -example, the `@TestOnMac` annotation in the -<> shows how you can -combine `@Test` and `@EnabledOnOs` in a single, reusable annotation. -==== - -[WARNING] -==== -Unless otherwise stated, each of the _conditional_ annotations listed in the following -sections can only be declared once on a given test interface, test class, or test method. -If a conditional annotation is directly present, indirectly present, or meta-present -multiple times on a given element, only the first such annotation discovered by JUnit will -be used; any additional declarations will be silently ignored. Note, however, that each -conditional annotation may be used in conjunction with other conditional annotations in -the `org.junit.jupiter.api.condition` package. -==== - -[[writing-tests-conditional-execution-os]] -==== Operating System and Architecture Conditions - -A container or test may be enabled or disabled on a particular operating system, -architecture, or combination of both via the `{EnabledOnOs}` and `{DisabledOnOs}` -annotations. - -[[writing-tests-conditional-execution-os-demo]] -[source,java,indent=0] -.Conditional execution based on operating system ----- -include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_os] ----- - -[[writing-tests-conditional-execution-architectures-demo]] -[source,java,indent=0] -.Conditional execution based on architecture ----- -include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_architecture] ----- - -[[writing-tests-conditional-execution-jre]] -==== Java Runtime Environment Conditions - -A container or test may be enabled or disabled on particular versions of the Java -Runtime Environment (JRE) via the `{EnabledOnJre}` and `{DisabledOnJre}` annotations -or on a particular range of versions of the JRE via the `{EnabledForJreRange}` and -`{DisabledForJreRange}` annotations. The range defaults to `{JRE}.JAVA_8` as the lower -border (`min`) and `{JRE}.OTHER` as the higher border (`max`), which allows usage of -half open ranges. - -[source,java,indent=0] ----- -include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre] ----- - -[[writing-tests-conditional-execution-native]] -==== Native Image Conditions - -A container or test may be enabled or disabled within a -https://www.graalvm.org/reference-manual/native-image/[GraalVM native image] via the -`{EnabledInNativeImage}` and `{DisabledInNativeImage}` annotations. These annotations are -typically used when running tests within a native image using the Gradle and Maven -plug-ins from the GraalVM https://graalvm.github.io/native-build-tools/latest/[Native -Build Tools] project. - -[source,java,indent=0] ----- -include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_native] ----- - -[[writing-tests-conditional-execution-system-properties]] -==== System Property Conditions - -A container or test may be enabled or disabled based on the value of the `named` JVM -system property via the `{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}` -annotations. The value supplied via the `matches` attribute will be interpreted as a -regular expression. - -[source,java,indent=0] ----- -include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_system_property] ----- - -[TIP] -==== -As of JUnit Jupiter 5.6, `{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}` are -_repeatable annotations_. Consequently, these annotations may be declared multiple times -on a test interface, test class, or test method. Specifically, these annotations will be -found if they are directly present, indirectly present, or meta-present on a given element. -==== - -[[writing-tests-conditional-execution-environment-variables]] -==== Environment Variable Conditions - -A container or test may be enabled or disabled based on the value of the `named` -environment variable from the underlying operating system via the -`{EnabledIfEnvironmentVariable}` and `{DisabledIfEnvironmentVariable}` annotations. The -value supplied via the `matches` attribute will be interpreted as a regular expression. - -[source,java,indent=0] ----- -include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_environment_variable] ----- - -[TIP] -==== -As of JUnit Jupiter 5.6, `{EnabledIfEnvironmentVariable}` and -`{DisabledIfEnvironmentVariable}` are _repeatable annotations_. Consequently, these -annotations may be declared multiple times on a test interface, test class, or test -method. Specifically, these annotations will be found if they are directly present, -indirectly present, or meta-present on a given element. -==== - -[[writing-tests-conditional-execution-custom]] -==== Custom Conditions - -As an alternative to implementing an <>, a -container or test may be enabled or disabled based on a _condition method_ configured via -the `{EnabledIf}` and `{DisabledIf}` annotations. A condition method must have a `boolean` -return type and may accept either no arguments or a single `ExtensionContext` argument. - -The following test class demonstrates how to configure a local method named -`customCondition` via `@EnabledIf` and `@DisabledIf`. - -[source,java,indent=0] ----- -include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_custom] ----- - -Alternatively, the condition method can be located outside the test class. In this case, -it must be referenced by its _fully qualified name_ as demonstrated in the following -example. - -[source,java,indent=0] ----- -package example; - -include::{testDir}/example/ExternalCustomConditionDemo.java[tags=user_guide_external_custom_condition] ----- - -[NOTE] -==== -There are several cases where a condition method would need to be `static`: - -- when `@EnabledIf` or `@DisabledIf` is used at class level -- when `@EnabledIf` or `@DisabledIf` is used on a `@ParameterizedTest` or a - `@TestTemplate` method -- when the condition method is located in an external class - -In any other case, you can use either static methods or instance methods as condition -methods. -==== - -[TIP] -==== -It is often the case that you can use an existing static method in a utility class as a -custom condition. - -For example, `java.awt.GraphicsEnvironment` provides a `public static boolean isHeadless()` -method that can be used to determine if the current environment does not support a -graphical display. Thus, if you have a test that depends on graphical support you can -disable it when such support is unavailable as follows. - -[source,java,indent=0] ----- -@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless", - disabledReason = "headless environment") ----- -==== - -[[writing-tests-tagging-and-filtering]] -=== Tagging and Filtering - -Test classes and methods can be tagged via the `@Tag` annotation. Those tags can later be -used to filter <>. Please refer to the -<> section for more information about tag support in the JUnit -Platform. - -[source,java,indent=0] ----- -include::{testDir}/example/TaggingDemo.java[tags=user_guide] ----- - -TIP: See <> for examples demonstrating how to create -custom annotations for tags. - -[[writing-tests-test-execution-order]] -=== Test Execution Order - -By default, test classes and methods will be ordered using an algorithm that is -deterministic but intentionally nonobvious. This ensures that subsequent runs of a test -suite execute test classes and test methods in the same order, thereby allowing for -repeatable builds. - -NOTE: See <> for a definition of _test method_ and -_test class_. - -[[writing-tests-test-execution-order-methods]] -==== Method Order - -Although true _unit tests_ typically should not rely on the order in which they are -executed, there are times when it is necessary to enforce a specific test method execution -order -- for example, when writing _integration tests_ or _functional tests_ where the -sequence of the tests is important, especially in conjunction with -`@TestInstance(Lifecycle.PER_CLASS)`. - -To control the order in which test methods are executed, annotate your test class or test -interface with `{TestMethodOrder}` and specify the desired `{MethodOrderer}` -implementation. You can implement your own custom `MethodOrderer` or use one of the -following built-in `MethodOrderer` implementations. - -* `{MethodOrderer_DisplayName}`: sorts test methods _alphanumerically_ based on their - display names (see <>) -* `{MethodOrderer_MethodName}`: sorts test methods _alphanumerically_ based on their names - and formal parameter lists -* `{MethodOrderer_OrderAnnotation}`: sorts test methods _numerically_ based on values - specified via the `{Order}` annotation -* `{MethodOrderer_Random}`: orders test methods _pseudo-randomly_ and supports - configuration of a custom _seed_ -* `{MethodOrderer_Alphanumeric}`: sorts test methods _alphanumerically_ based on their - names and formal parameter lists; **deprecated in favor of `{MethodOrderer_MethodName}`, - to be removed in 6.0** - -NOTE: See also: <> - -The following example demonstrates how to guarantee that test methods are executed in the -order specified via the `@Order` annotation. - -[source,java,indent=0] ----- -include::{testDir}/example/OrderedTestsDemo.java[tags=user_guide] ----- - -[[writing-tests-test-execution-order-methods-default]] -===== Setting the Default Method Orderer - -You can use the `junit.jupiter.testmethod.order.default` <> to specify the fully qualified class name of the -`{MethodOrderer}` you would like to use by default. Just like for the orderer configured -via the `{TestMethodOrder}` annotation, the supplied class has to implement the -`MethodOrderer` interface. The default orderer will be used for all tests unless the -`@TestMethodOrder` annotation is present on an enclosing test class or test interface. - -For example, to use the `{MethodOrderer_OrderAnnotation}` method orderer by default, you -should set the configuration parameter to the corresponding fully qualified class name -(e.g., in `src/test/resources/junit-platform.properties`): - -[source,properties,indent=0] ----- -junit.jupiter.testmethod.order.default = \ - org.junit.jupiter.api.MethodOrderer$OrderAnnotation ----- - -Similarly, you can specify the fully qualified name of any custom class that implements -`MethodOrderer`. - -[[writing-tests-test-execution-order-classes]] -==== Class Order - -Although test classes typically should not rely on the order in which they are executed, -there are times when it is desirable to enforce a specific test class execution order. You -may wish to execute test classes in a random order to ensure there are no accidental -dependencies between test classes, or you may wish to order test classes to optimize build -time as outlined in the following scenarios. - -* Run previously failing tests and faster tests first: "fail fast" mode -* With parallel execution enabled, run longer tests first: "shortest test plan execution - duration" mode -* Various other use cases - -To configure test class execution order _globally_ for the entire test suite, use the -`junit.jupiter.testclass.order.default` <> to specify the fully qualified class name of the `{ClassOrderer}` you would -like to use. The supplied class must implement the `ClassOrderer` interface. - -You can implement your own custom `ClassOrderer` or use one of the following built-in -`ClassOrderer` implementations. - -* `{ClassOrderer_ClassName}`: sorts test classes _alphanumerically_ based on their fully - qualified class names -* `{ClassOrderer_DisplayName}`: sorts test classes _alphanumerically_ based on their - display names (see <>) -* `{ClassOrderer_OrderAnnotation}`: sorts test classes _numerically_ based on values - specified via the `{Order}` annotation -* `{ClassOrderer_Random}`: orders test classes _pseudo-randomly_ and supports - configuration of a custom _seed_ - -For example, for the `@Order` annotation to be honored on _test classes_, you should -configure the `{ClassOrderer_OrderAnnotation}` class orderer using the configuration -parameter with the corresponding fully qualified class name (e.g., in -`src/test/resources/junit-platform.properties`): - -[source,properties,indent=0] ----- -junit.jupiter.testclass.order.default = \ - org.junit.jupiter.api.ClassOrderer$OrderAnnotation ----- - -The configured `ClassOrderer` will be applied to all top-level test classes (including -`static` nested test classes) and `@Nested` test classes. - -NOTE: Top-level test classes will be ordered relative to each other; whereas, `@Nested` -test classes will be ordered relative to other `@Nested` test classes sharing the same -_enclosing class_. - -To configure test class execution order _locally_ for `@Nested` test classes, declare the -`{TestClassOrder}` annotation on the enclosing class for the `@Nested` test classes you -want to order, and supply a class reference to the `ClassOrderer` implementation you would -like to use directly in the `@TestClassOrder` annotation. The configured `ClassOrderer` -will be applied recursively to `@Nested` test classes and their `@Nested` test classes. -Note that a local `@TestClassOrder` declaration always overrides an inherited -`@TestClassOrder` declaration or a `ClassOrderer` configured globally via the -`junit.jupiter.testclass.order.default` configuration parameter. - -The following example demonstrates how to guarantee that `@Nested` test classes are -executed in the order specified via the `@Order` annotation. - -[source,java,indent=0] ----- -include::{testDir}/example/OrderedNestedTestClassesDemo.java[tags=user_guide] ----- - -[[writing-tests-test-instance-lifecycle]] -=== Test Instance Lifecycle - -In order to allow individual test methods to be executed in isolation and to avoid -unexpected side effects due to mutable test instance state, JUnit creates a new instance -of each test class before executing each _test method_ (see -<>). This "per-method" test instance lifecycle is the -default behavior in JUnit Jupiter and is analogous to all previous versions of JUnit. - -NOTE: Please note that the test class will still be instantiated if a given _test method_ -is _disabled_ via a <> (e.g., `@Disabled`, -`@DisabledOnOs`, etc.) even when the "per-method" test instance lifecycle mode is active. - -If you would prefer that JUnit Jupiter execute all test methods on the same test -instance, annotate your test class with `@TestInstance(Lifecycle.PER_CLASS)`. When using -this mode, a new test instance will be created once per test class. Thus, if your test -methods rely on state stored in instance variables, you may need to reset that state in -`@BeforeEach` or `@AfterEach` methods. - -The "per-class" mode has some additional benefits over the default "per-method" mode. -Specifically, with the "per-class" mode it becomes possible to declare `@BeforeAll` and -`@AfterAll` on non-static methods as well as on interface `default` methods. The -"per-class" mode therefore also makes it possible to use `@BeforeAll` and `@AfterAll` -methods in `@Nested` test classes. - -NOTE: Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as -`static` in `@Nested` test classes. - -If you are authoring tests using the Kotlin programming language, you may also find it -easier to implement `@BeforeAll` and `@AfterAll` methods by switching to the "per-class" -test instance lifecycle mode. - -[[writing-tests-test-instance-lifecycle-changing-default]] -==== Changing the Default Test Instance Lifecycle - -If a test class or test interface is not annotated with `@TestInstance`, JUnit Jupiter -will use a _default_ lifecycle mode. The standard _default_ mode is `PER_METHOD`; -however, it is possible to change the _default_ for the execution of an entire test plan. -To change the default test instance lifecycle mode, set the -`junit.jupiter.testinstance.lifecycle.default` _configuration parameter_ to the name of -an enum constant defined in `TestInstance.Lifecycle`, ignoring case. This can be supplied -as a JVM system property, as a _configuration parameter_ in the -`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform -configuration file (see <> for details). - -For example, to set the default test instance lifecycle mode to `Lifecycle.PER_CLASS`, -you can start your JVM with the following system property. - -`-Djunit.jupiter.testinstance.lifecycle.default=per_class` - -Note, however, that setting the default test instance lifecycle mode via the JUnit -Platform configuration file is a more robust solution since the configuration file can be -checked into a version control system along with your project and can therefore be used -within IDEs and your build software. - -To set the default test instance lifecycle mode to `Lifecycle.PER_CLASS` via the JUnit -Platform configuration file, create a file named `junit-platform.properties` in the root -of the class path (e.g., `src/test/resources`) with the following content. - -`junit.jupiter.testinstance.lifecycle.default = per_class` - -WARNING: Changing the _default_ test instance lifecycle mode can lead to unpredictable -results and fragile builds if not applied consistently. For example, if the build -configures "per-class" semantics as the default but tests in the IDE are executed using -"per-method" semantics, that can make it difficult to debug errors that occur on the -build server. It is therefore recommended to change the default in the JUnit Platform -configuration file instead of via a JVM system property. - -[[writing-tests-nested]] -=== Nested Tests - -`@Nested` tests give the test writer more capabilities to express the relationship among -several groups of tests. Such nested tests make use of Java's nested classes and -facilitate hierarchical thinking about the test structure. Here's an elaborate example, -both as source code and as a screenshot of the execution within an IDE. - -[source,java,indent=0] -.Nested test suite for testing a stack ----- -include::{testDir}/example/TestingAStackDemo.java[tags=user_guide] ----- - -When executing this example in an IDE, the test execution tree in the GUI will look -similar to the following image. - -image::writing-tests_nested_test_ide.png[caption='',title='Executing a nested test in an IDE'] - -In this example, preconditions from outer tests are used in inner tests by defining -hierarchical lifecycle methods for the setup code. For example, `createNewStack()` is a -`@BeforeEach` lifecycle method that is used in the test class in which it is defined and -in all levels in the nesting tree below the class in which it is defined. - -The fact that setup code from outer tests is run before inner tests are executed gives you -the ability to run all tests independently. You can even run inner tests alone without -running the outer tests, because the setup code from the outer tests is always executed. - -NOTE: _Only non-static nested classes_ (i.e. _inner classes_) can serve as `@Nested` test -classes. Nesting can be arbitrarily deep, and those inner classes are subject to full -lifecycle support with one exception: `@BeforeAll` and `@AfterAll` methods do not work _by -default_. The reason is that Java does not allow `static` members in inner classes prior -to Java 16. However, this restriction can be circumvented by annotating a `@Nested` test -class with `@TestInstance(Lifecycle.PER_CLASS)` (see -<>). If you are using Java 16 or higher, -`@BeforeAll` and `@AfterAll` methods can be declared as `static` in `@Nested` test -classes, and this restriction no longer applies. - -[[writing-tests-dependency-injection]] -=== Dependency Injection for Constructors and Methods - -In all prior JUnit versions, test constructors or methods were not allowed to have -parameters (at least not with the standard `Runner` implementations). As one of the major -changes in JUnit Jupiter, both test constructors and methods are now permitted to have -parameters. This allows for greater flexibility and enables _Dependency Injection_ for -constructors and methods. - -`{ParameterResolver}` defines the API for test extensions that wish to _dynamically_ -resolve parameters at runtime. If a _test class_ constructor, a _test method_, or a -_lifecycle method_ (see <>) accepts a parameter, the -parameter must be resolved at runtime by a registered `ParameterResolver`. - -There are currently three built-in resolvers that are registered automatically. - -* `{TestInfoParameterResolver}`: if a constructor or method parameter is of type - `{TestInfo}`, the `TestInfoParameterResolver` will supply an instance of `TestInfo` - corresponding to the current container or test as the value for the parameter. The - `TestInfo` can then be used to retrieve information about the current container or test - such as the display name, the test class, the test method, and associated tags. The - display name is either a technical name, such as the name of the test class or test - method, or a custom name configured via `@DisplayName`. -+ -`{TestInfo}` acts as a drop-in replacement for the `TestName` rule from JUnit 4. The -following demonstrates how to have `TestInfo` injected into a test constructor, -`@BeforeEach` method, and `@Test` method. - -[source,java,indent=0] ----- -include::{testDir}/example/TestInfoDemo.java[tags=user_guide] ----- - -* `{RepetitionInfoParameterResolver}`: if a method parameter in a `@RepeatedTest`, - `@BeforeEach`, or `@AfterEach` method is of type `{RepetitionInfo}`, the - `RepetitionInfoParameterResolver` will supply an instance of `RepetitionInfo`. - `RepetitionInfo` can then be used to retrieve information about the current repetition - and the total number of repetitions for the corresponding `@RepeatedTest`. Note, - however, that `RepetitionInfoParameterResolver` is not registered outside the context - of a `@RepeatedTest`. See <>. - -* `{TestReporterParameterResolver}`: if a constructor or method parameter is of type - `{TestReporter}`, the `TestReporterParameterResolver` will supply an instance of - `TestReporter`. The `TestReporter` can be used to publish additional data about the - current test run. The data can be consumed via the `reportingEntryPublished()` method in - a `{TestExecutionListener}`, allowing it to be viewed in IDEs or included in reports. -+ -In JUnit Jupiter you should use `TestReporter` where you used to print information to -`stdout` or `stderr` in JUnit 4. Using `@RunWith(JUnitPlatform.class)` will output all -reported entries to `stdout`. In addition, some IDEs print report entries to `stdout` or -display them in the user interface for test results. - -[source,java,indent=0] ----- -include::{testDir}/example/TestReporterDemo.java[tags=user_guide] ----- - -NOTE: Other parameter resolvers must be explicitly enabled by registering appropriate -<> via `@ExtendWith`. - -Check out the `{RandomParametersExtension}` for an example of a custom -`{ParameterResolver}`. While not intended to be production-ready, it demonstrates the -simplicity and expressiveness of both the extension model and the parameter resolution -process. `MyRandomParametersTest` demonstrates how to inject random values into `@Test` -methods. - -[source,java,indent=0] ----- -@ExtendWith(RandomParametersExtension.class) -class MyRandomParametersTest { - - @Test - void injectsInteger(@Random int i, @Random int j) { - assertNotEquals(i, j); - } - - @Test - void injectsDouble(@Random double d) { - assertEquals(0.0, d, 1.0); - } - -} ----- - -For real-world use cases, check out the source code for the `{MockitoExtension}` and the -`{SpringExtension}`. - -When the type of the parameter to inject is the only condition for your -`{ParameterResolver}`, you can use the generic `{TypeBasedParameterResolver}` base class. -The `supportsParameters` method is implemented behind the scenes and supports -parameterized types. - -[[writing-tests-test-interfaces-and-default-methods]] -=== Test Interfaces and Default Methods - -JUnit Jupiter allows `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, -`@TestTemplate`, `@BeforeEach`, and `@AfterEach` to be declared on interface `default` -methods. `@BeforeAll` and `@AfterAll` can either be declared on `static` methods in a -test interface or on interface `default` methods _if_ the test interface or test class is -annotated with `@TestInstance(Lifecycle.PER_CLASS)` (see -<>). Here are some examples. - -[source,java] ----- -include::{testDir}/example/testinterface/TestLifecycleLogger.java[tags=user_guide] ----- - -[source,java] ----- -include::{testDir}/example/testinterface/TestInterfaceDynamicTestsDemo.java[tags=user_guide] ----- - -`@ExtendWith` and `@Tag` can be declared on a test interface so that classes that -implement the interface automatically inherit its tags and extensions. See -<> for the source code of the -<>. - -[source,java] ----- -include::{testDir}/example/testinterface/TimeExecutionLogger.java[tags=user_guide] ----- - -In your test class you can then implement these test interfaces to have them applied. - -[source,java] ----- -include::{testDir}/example/testinterface/TestInterfaceDemo.java[tags=user_guide] ----- - -Running the `TestInterfaceDemo` results in output similar to the following: - -.... -INFO example.TestLifecycleLogger - Before all tests -INFO example.TestLifecycleLogger - About to execute [dynamicTestsForPalindromes()] -INFO example.TimingExtension - Method [dynamicTestsForPalindromes] took 19 ms. -INFO example.TestLifecycleLogger - Finished executing [dynamicTestsForPalindromes()] -INFO example.TestLifecycleLogger - About to execute [isEqualValue()] -INFO example.TimingExtension - Method [isEqualValue] took 1 ms. -INFO example.TestLifecycleLogger - Finished executing [isEqualValue()] -INFO example.TestLifecycleLogger - After all tests -.... - -Another possible application of this feature is to write tests for interface contracts. -For example, you can write tests for how implementations of `Object.equals` or -`Comparable.compareTo` should behave as follows. - -[source,java] ----- -include::{testDir}/example/defaultmethods/Testable.java[tags=user_guide] ----- - -[source,java] ----- -include::{testDir}/example/defaultmethods/EqualsContract.java[tags=user_guide] ----- - -[source,java] ----- -include::{testDir}/example/defaultmethods/ComparableContract.java[tags=user_guide] ----- - -In your test class you can then implement both contract interfaces thereby inheriting the -corresponding tests. Of course you'll have to implement the abstract methods. - -[source,java] ----- -include::{testDir}/example/defaultmethods/StringTests.java[tags=user_guide] ----- - -NOTE: The above tests are merely meant as examples and therefore not complete. - - -[[writing-tests-repeated-tests]] -=== Repeated Tests - -JUnit Jupiter provides the ability to repeat a test a specified number of times by -annotating a method with `@RepeatedTest` and specifying the total number of repetitions -desired. Each invocation of a repeated test behaves like the execution of a regular -`@Test` method with full support for the same lifecycle callbacks and extensions. - -The following example demonstrates how to declare a test named `repeatedTest()` that -will be automatically repeated 10 times. - -[source,java] ----- -@RepeatedTest(10) -void repeatedTest() { - // ... -} ----- - -In addition to specifying the number of repetitions, a custom display name can be -configured for each repetition via the `name` attribute of the `@RepeatedTest` -annotation. Furthermore, the display name can be a pattern composed of a combination of -static text and dynamic placeholders. The following placeholders are currently supported. - -- `{displayName}`: display name of the `@RepeatedTest` method -- `{currentRepetition}`: the current repetition count -- `{totalRepetitions}`: the total number of repetitions - -The default display name for a given repetition is generated based on the following -pattern: `"repetition {currentRepetition} of {totalRepetitions}"`. Thus, the display -names for individual repetitions of the previous `repeatedTest()` example would be: -`repetition 1 of 10`, `repetition 2 of 10`, etc. If you would like the display name of -the `@RepeatedTest` method included in the name of each repetition, you can define your -own custom pattern or use the predefined `RepeatedTest.LONG_DISPLAY_NAME` pattern. The -latter is equal to `"{displayName} :: repetition {currentRepetition} of -{totalRepetitions}"` which results in display names for individual repetitions like -`repeatedTest() :: repetition 1 of 10`, `repeatedTest() :: repetition 2 of 10`, etc. - -In order to retrieve information about the current repetition and the total number of -repetitions programmatically, a developer can choose to have an instance of -`RepetitionInfo` injected into a `@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method. - -[[writing-tests-repeated-tests-examples]] -==== Repeated Test Examples - -The `RepeatedTestsDemo` class at the end of this section demonstrates several examples of -repeated tests. - -The `repeatedTest()` method is identical to example from the previous section; whereas, -`repeatedTestWithRepetitionInfo()` demonstrates how to have an instance of -`RepetitionInfo` injected into a test to access the total number of repetitions for the -current repeated test. - -The next two methods demonstrate how to include a custom `@DisplayName` for the -`@RepeatedTest` method in the display name of each repetition. `customDisplayName()` -combines a custom display name with a custom pattern and then uses `TestInfo` to verify -the format of the generated display name. `Repeat!` is the `{displayName}` which comes -from the `@DisplayName` declaration, and `1/1` comes from -`{currentRepetition}/{totalRepetitions}`. In contrast, -`customDisplayNameWithLongPattern()` uses the aforementioned predefined -`RepeatedTest.LONG_DISPLAY_NAME` pattern. - -`repeatedTestInGerman()` demonstrates the ability to translate display names of repeated -tests into foreign languages -- in this case German, resulting in names for individual -repetitions such as: `Wiederholung 1 von 5`, `Wiederholung 2 von 5`, etc. - -Since the `beforeEach()` method is annotated with `@BeforeEach` it will get executed -before each repetition of each repeated test. By having the `TestInfo` and -`RepetitionInfo` injected into the method, we see that it's possible to obtain -information about the currently executing repeated test. Executing `RepeatedTestsDemo` -with the `INFO` log level enabled results in the following output. - -.... -INFO: About to execute repetition 1 of 10 for repeatedTest -INFO: About to execute repetition 2 of 10 for repeatedTest -INFO: About to execute repetition 3 of 10 for repeatedTest -INFO: About to execute repetition 4 of 10 for repeatedTest -INFO: About to execute repetition 5 of 10 for repeatedTest -INFO: About to execute repetition 6 of 10 for repeatedTest -INFO: About to execute repetition 7 of 10 for repeatedTest -INFO: About to execute repetition 8 of 10 for repeatedTest -INFO: About to execute repetition 9 of 10 for repeatedTest -INFO: About to execute repetition 10 of 10 for repeatedTest -INFO: About to execute repetition 1 of 5 for repeatedTestWithRepetitionInfo -INFO: About to execute repetition 2 of 5 for repeatedTestWithRepetitionInfo -INFO: About to execute repetition 3 of 5 for repeatedTestWithRepetitionInfo -INFO: About to execute repetition 4 of 5 for repeatedTestWithRepetitionInfo -INFO: About to execute repetition 5 of 5 for repeatedTestWithRepetitionInfo -INFO: About to execute repetition 1 of 1 for customDisplayName -INFO: About to execute repetition 1 of 1 for customDisplayNameWithLongPattern -INFO: About to execute repetition 1 of 5 for repeatedTestInGerman -INFO: About to execute repetition 2 of 5 for repeatedTestInGerman -INFO: About to execute repetition 3 of 5 for repeatedTestInGerman -INFO: About to execute repetition 4 of 5 for repeatedTestInGerman -INFO: About to execute repetition 5 of 5 for repeatedTestInGerman -.... - -[source,java] ----- -include::{testDir}/example/RepeatedTestsDemo.java[tags=user_guide] ----- - -When using the `ConsoleLauncher` with the unicode theme enabled, execution of -`RepeatedTestsDemo` results in the following output to the console. - -.... -├─ RepeatedTestsDemo ✔ -│ ├─ repeatedTest() ✔ -│ │ ├─ repetition 1 of 10 ✔ -│ │ ├─ repetition 2 of 10 ✔ -│ │ ├─ repetition 3 of 10 ✔ -│ │ ├─ repetition 4 of 10 ✔ -│ │ ├─ repetition 5 of 10 ✔ -│ │ ├─ repetition 6 of 10 ✔ -│ │ ├─ repetition 7 of 10 ✔ -│ │ ├─ repetition 8 of 10 ✔ -│ │ ├─ repetition 9 of 10 ✔ -│ │ └─ repetition 10 of 10 ✔ -│ ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔ -│ │ ├─ repetition 1 of 5 ✔ -│ │ ├─ repetition 2 of 5 ✔ -│ │ ├─ repetition 3 of 5 ✔ -│ │ ├─ repetition 4 of 5 ✔ -│ │ └─ repetition 5 of 5 ✔ -│ ├─ Repeat! ✔ -│ │ └─ Repeat! 1/1 ✔ -│ ├─ Details... ✔ -│ │ └─ Details... :: repetition 1 of 1 ✔ -│ └─ repeatedTestInGerman() ✔ -│ ├─ Wiederholung 1 von 5 ✔ -│ ├─ Wiederholung 2 von 5 ✔ -│ ├─ Wiederholung 3 von 5 ✔ -│ ├─ Wiederholung 4 von 5 ✔ -│ └─ Wiederholung 5 von 5 ✔ -.... - - -[[writing-tests-parameterized-tests]] -=== Parameterized Tests - -Parameterized tests make it possible to run a test multiple times with different -arguments. They are declared just like regular `@Test` methods but use the -`{ParameterizedTest}` annotation instead. In addition, you must declare at least one -_source_ that will provide the arguments for each invocation and then _consume_ the -arguments in the test method. - -The following example demonstrates a parameterized test that uses the `@ValueSource` -annotation to specify a `String` array as the source of arguments. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=first_example] ----- - -When executing the above parameterized test method, each invocation will be reported -separately. For instance, the `ConsoleLauncher` will print output similar to the -following. - -.... -palindromes(String) ✔ -├─ [1] candidate=racecar ✔ -├─ [2] candidate=radar ✔ -└─ [3] candidate=able was I ere I saw elba ✔ -.... - -[[writing-tests-parameterized-tests-setup]] -==== Required Setup - -In order to use parameterized tests you need to add a dependency on the -`junit-jupiter-params` artifact. Please refer to <> for details. - -[[writing-tests-parameterized-tests-consuming-arguments]] -==== Consuming Arguments - -Parameterized test methods typically _consume_ arguments directly from the configured -source (see <>) following a one-to-one -correlation between argument source index and method parameter index (see examples in -<>). However, a parameterized test -method may also choose to _aggregate_ arguments from the source into a single object -passed to the method (see <>). -Additional arguments may also be provided by a `ParameterResolver` (e.g., to obtain an -instance of `TestInfo`, `TestReporter`, etc.). Specifically, a parameterized test method -must declare formal parameters according to the following rules. - -* Zero or more _indexed arguments_ must be declared first. -* Zero or more _aggregators_ must be declared next. -* Zero or more arguments supplied by a `ParameterResolver` must be declared last. - -In this context, an _indexed argument_ is an argument for a given index in the -`Arguments` provided by an `ArgumentsProvider` that is passed as an argument to the -parameterized method at the same index in the method's formal parameter list. An -_aggregator_ is any parameter of type `ArgumentsAccessor` or any parameter annotated with -`@AggregateWith`. - -[NOTE] -.AutoCloseable arguments -==== -Arguments that implement `java.lang.AutoCloseable` (or `java.io.Closeable` which extends -`java.lang.AutoCloseable`) will be automatically closed after `@AfterEach` methods and -`AfterEachCallback` extensions have been called for the current parameterized test -invocation. - -To prevent this from happening, set the `autoCloseArguments` attribute in -`@ParameterizedTest` to `false`. Specifically, if an argument that implements -`AutoCloseable` is reused for multiple invocations of the same parameterized test method, -you must annotate the method with `@ParameterizedTest(autoCloseArguments = false)` to -ensure that the argument is not closed between invocations. -==== - -[[writing-tests-parameterized-tests-sources]] -==== Sources of Arguments - -Out of the box, JUnit Jupiter provides quite a few _source_ annotations. Each of the -following subsections provides a brief overview and an example for each of them. Please -refer to the Javadoc in the `{params-provider-package}` package for additional -information. - -[[writing-tests-parameterized-tests-sources-ValueSource]] -===== @ValueSource - -`@ValueSource` is one of the simplest possible sources. It lets you specify a single -array of literal values and can only be used for providing a single argument per -parameterized test invocation. - -The following types of literal values are supported by `@ValueSource`. - -- `short` -- `byte` -- `int` -- `long` -- `float` -- `double` -- `char` -- `boolean` -- `java.lang.String` -- `java.lang.Class` - -For example, the following `@ParameterizedTest` method will be invoked three times, with -the values `1`, `2`, and `3` respectively. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ValueSource_example] ----- - -[[writing-tests-parameterized-tests-sources-null-and-empty]] -===== Null and Empty Sources - -In order to check corner cases and verify proper behavior of our software when it is -supplied _bad input_, it can be useful to have `null` and _empty_ values supplied to our -parameterized tests. The following annotations serve as sources of `null` and empty values -for parameterized tests that accept a single argument. - -* `{NullSource}`: provides a single `null` argument to the annotated `@ParameterizedTest` - method. - - `@NullSource` cannot be used for a parameter that has a primitive type. -* `{EmptySource}`: provides a single _empty_ argument to the annotated `@ParameterizedTest` - method for parameters of the following types: `java.lang.String`, `java.util.List`, - `java.util.Set`, `java.util.Map`, primitive arrays (e.g., `int[]`, `char[][]`, etc.), - object arrays (e.g.,`String[]`, `Integer[][]`, etc.). - - Subtypes of the supported types are not supported. -* `{NullAndEmptySource}`: a _composed annotation_ that combines the functionality of - `@NullSource` and `@EmptySource`. - -If you need to supply multiple varying types of _blank_ strings to a parameterized test, -you can achieve that using <> -- -for example, `@ValueSource(strings = {"{nbsp}", "{nbsp}{nbsp}{nbsp}", "\t", "\n"})`. - -You can also combine `@NullSource`, `@EmptySource`, and `@ValueSource` to test a wider -range of `null`, _empty_, and _blank_ input. The following example demonstrates how to -achieve this for strings. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example1] ----- - -Making use of the composed `@NullAndEmptySource` annotation simplifies the above as -follows. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example2] ----- - -NOTE: Both variants of the `nullEmptyAndBlankStrings(String)` parameterized test method -result in six invocations: 1 for `null`, 1 for the empty string, and 4 for the explicit -blank strings supplied via `@ValueSource`. - -[[writing-tests-parameterized-tests-sources-EnumSource]] -===== @EnumSource - -`@EnumSource` provides a convenient way to use `Enum` constants. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_example] ----- - -The annotation's `value` attribute is optional. When omitted, the declared type of the -first method parameter is used. The test will fail if it does not reference an enum type. -Thus, the `value` attribute is required in the above example because the method parameter -is declared as `TemporalUnit`, i.e. the interface implemented by `ChronoUnit`, which isn't -an enum type. Changing the method parameter type to `ChronoUnit` allows you to omit the -explicit enum type from the annotation as follows. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_example_autodetection] ----- - -The annotation provides an optional `names` attribute that lets you specify which -constants shall be used, like in the following example. If omitted, all constants will be -used. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_include_example] ----- - -The `@EnumSource` annotation also provides an optional `mode` attribute that enables -fine-grained control over which constants are passed to the test method. For example, you -can exclude names from the enum constant pool or specify regular expressions as in the -following examples. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_exclude_example] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_regex_example] ----- - -[[writing-tests-parameterized-tests-sources-MethodSource]] -===== @MethodSource - -`{MethodSource}` allows you to refer to one or more _factory_ methods of the test class -or external classes. - -Factory methods within the test class must be `static` unless the test class is annotated -with `@TestInstance(Lifecycle.PER_CLASS)`; whereas, factory methods in external classes -must always be `static`. - -Each factory method must generate a _stream_ of _arguments_, and each set of arguments -within the stream will be provided as the physical arguments for individual invocations -of the annotated `@ParameterizedTest` method. Generally speaking this translates to a -`Stream` of `Arguments` (i.e., `Stream`); however, the actual concrete return -type can take on many forms. In this context, a "stream" is anything that JUnit can -reliably convert into a `Stream`, such as `Stream`, `DoubleStream`, `LongStream`, -`IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects, or an array of -primitives. The "arguments" within the stream can be supplied as an instance of -`Arguments`, an array of objects (e.g., `Object[]`), or a single value if the -parameterized test method accepts a single argument. - -If you only need a single parameter, you can return a `Stream` of instances of the -parameter type as demonstrated in the following example. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=simple_MethodSource_example] ----- - -If you do not explicitly provide a factory method name via `@MethodSource`, JUnit Jupiter -will search for a _factory_ method that has the same name as the current -`@ParameterizedTest` method by convention. This is demonstrated in the following example. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=simple_MethodSource_without_value_example] ----- - -Streams for primitive types (`DoubleStream`, `IntStream`, and `LongStream`) are also -supported as demonstrated by the following example. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=primitive_MethodSource_example] ----- - -If a parameterized test method declares multiple parameters, you need to return a -collection, stream, or array of `Arguments` instances or object arrays as shown below -(see the Javadoc for `{MethodSource}` for further details on supported return types). -Note that `arguments(Object...)` is a static factory method defined in the `Arguments` -interface. In addition, `Arguments.of(Object...)` may be used as an alternative to -`arguments(Object...)`. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=multi_arg_MethodSource_example] ----- - -An external, `static` _factory_ method can be referenced by providing its _fully qualified -method name_ as demonstrated in the following example. - -[source,java,indent=0] ----- -package example; - -include::{testDir}/example/ExternalMethodSourceDemo.java[tags=external_MethodSource_example] ----- - -Factory methods can declare parameters, which will be provided by registered -implementations of the `ParameterResolver` extension API. In the following example, the -factory method is referenced by its name since there is only one such method in the test -class. If there are several local methods with the same name, parameters can also be -provided to differentiate them – for example, `@MethodSource("factoryMethod()")` or -`@MethodSource("factoryMethod(java.lang.String)")`. Alternatively, the factory method -can be referenced by its fully qualified method name, e.g. -`@MethodSource("example.MyTests#factoryMethod(java.lang.String)")`. - -[source,java,indent=0] ----- -include::{testDir}/example/MethodSourceParameterResolutionDemo.java[tags=parameter_resolution_MethodSource_example] ----- - - -[[writing-tests-parameterized-tests-sources-CsvSource]] -===== @CsvSource - -`@CsvSource` allows you to express argument lists as comma-separated values (i.e., CSV -`String` literals). Each string provided via the `value` attribute in `@CsvSource` -represents a CSV record and results in one invocation of the parameterized test. The first -record may optionally be used to supply CSV headers (see the Javadoc for the -`useHeadersInDisplayName` attribute for details and an example). - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=CsvSource_example] ----- - -The default delimiter is a comma (`,`), but you can use another character by setting the -`delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a -`String` delimiter instead of a single character. However, both delimiter attributes -cannot be set simultaneously. - -By default, `@CsvSource` uses a single quote (`'`) as its quote character, but this can be -changed via the `quoteCharacter` attribute. See the `'lemon, lime'` value in the example -above and in the table below. An empty, quoted value (`''`) results in an empty `String` -unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is -interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value -can be interpreted as a `null` reference (see the `NIL` example in the table below). An -`ArgumentConversionException` is thrown if the target type of a `null` reference is a -primitive type. - -NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless -of any custom values configured via the `nullValues` attribute. - -Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed -by default. This behavior can be changed by setting the -`ignoreLeadingAndTrailingWhitespace` attribute to `true`. - -[cols="50,50"] -|=== -| Example Input | Resulting Argument List - -| `@CsvSource({ "apple, banana" })` | `"apple"`, `"banana"` -| `@CsvSource({ "apple, 'lemon, lime'" })` | `"apple"`, `"lemon, lime"` -| `@CsvSource({ "apple, ''" })` | `"apple"`, `""` -| `@CsvSource({ "apple, " })` | `"apple"`, `null` -| `@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")` | `"apple"`, `"banana"`, `null` -| `@CsvSource(value = { " apple , banana" }, ignoreLeadingAndTrailingWhitespace = false)` | `" apple "`, `" banana"` -|=== - -If the programming language you are using supports _text blocks_ -- for example, Java SE -15 or higher -- you can alternatively use the `textBlock` attribute of `@CsvSource`. Each -record within a text block represents a CSV record and results in one invocation of the -parameterized test. The first record may optionally be used to supply CSV headers by -setting the `useHeadersInDisplayName` attribute to `true` as in the example below. - -Using a text block, the previous example can be implemented as follows. - -[source,java,indent=0] ----- -@ParameterizedTest(name = "[{index}] {arguments}") -@CsvSource(useHeadersInDisplayName = true, textBlock = """ - FRUIT, RANK - apple, 1 - banana, 2 - 'lemon, lime', 0xF1 - strawberry, 700_000 - """) -void testWithCsvSource(String fruit, int rank) { - // ... -} ----- - -The generated display names for the previous example include the CSV header names. - ----- -[1] FRUIT = apple, RANK = 1 -[2] FRUIT = banana, RANK = 2 -[3] FRUIT = lemon, lime, RANK = 0xF1 -[4] FRUIT = strawberry, RANK = 700_000 ----- - -In contrast to CSV records supplied via the `value` attribute, a text block can contain -comments. Any line beginning with a `+++#+++` symbol will be treated as a comment and -ignored. Note, however, that the `+++#+++` symbol must be the first character on the line -without any leading whitespace. It is therefore recommended that the closing text block -delimiter (`"""`) be placed either at the end of the last line of input or on the -following line, left aligned with the rest of the input (as can be seen in the example -below which demonstrates formatting similar to a table). - -[source,java,indent=0] ----- -@ParameterizedTest -@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """ - #----------------------------- - # FRUIT | RANK - #----------------------------- - apple | 1 - #----------------------------- - banana | 2 - #----------------------------- - "lemon lime" | 0xF1 - #----------------------------- - strawberry | 700_000 - #----------------------------- - """) -void testWithCsvSource(String fruit, int rank) { - // ... -} ----- - -[NOTE] -==== -Java's https://docs.oracle.com/en/java/javase/15/text-blocks/index.html[text block] -feature automatically removes _incidental whitespace_ when the code is compiled. -However other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a -programming language other than Java and your text block contains comments or new lines -within quoted strings, you will need to ensure that there is no leading whitespace within -your text block. -==== - -[[writing-tests-parameterized-tests-sources-CsvFileSource]] -===== @CsvFileSource - -`@CsvFileSource` lets you use comma-separated value (CSV) files from the classpath or the -local file system. Each record from a CSV file results in one invocation of the -parameterized test. The first record may optionally be used to supply CSV headers. You can -instruct JUnit to ignore the headers via the `numLinesToSkip` attribute. If you would like -for the headers to be used in the display names, you can set the `useHeadersInDisplayName` -attribute to `true`. The examples below demonstrate the use of `numLinesToSkip` and -`useHeadersInDisplayName`. - -The default delimiter is a comma (`,`), but you can use another character by setting the -`delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a -`String` delimiter instead of a single character. However, both delimiter attributes -cannot be set simultaneously. - -.Comments in CSV files -NOTE: Any line beginning with a `+++#+++` symbol will be interpreted as a comment and will -be ignored. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=CsvFileSource_example] ----- - -[source,csv,indent=0] -.two-column.csv ----- -include::{testResourcesDir}/two-column.csv[] ----- - -The following listing shows the generated display names for the first two parameterized -test methods above. - ----- -[1] country=Sweden, reference=1 -[2] country=Poland, reference=2 -[3] country=United States of America, reference=3 -[4] country=France, reference=700_000 ----- - -The following listing shows the generated display names for the last parameterized test -method above that uses CSV header names. - ----- -[1] COUNTRY = Sweden, REFERENCE = 1 -[2] COUNTRY = Poland, REFERENCE = 2 -[3] COUNTRY = United States of America, REFERENCE = 3 -[4] COUNTRY = France, REFERENCE = 700_000 ----- - -In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double -quote (`+++"+++`) as the quote character by default, but this can be changed via the -`quoteCharacter` attribute. See the `"United States of America"` value in the example -above. An empty, quoted value (`+++""+++`) results in an empty `String` unless the -`emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a -`null` reference. By specifying one or more `nullValues`, a custom value can be -interpreted as a `null` reference. An `ArgumentConversionException` is thrown if the -target type of a `null` reference is a primitive type. - -NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless -of any custom values configured via the `nullValues` attribute. - -Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed -by default. This behavior can be changed by setting the -`ignoreLeadingAndTrailingWhitespace` attribute to `true`. - -[[writing-tests-parameterized-tests-sources-ArgumentsSource]] -===== @ArgumentsSource - -`@ArgumentsSource` can be used to specify a custom, reusable `ArgumentsProvider`. Note -that an implementation of `ArgumentsProvider` must be declared as either a top-level -class or as a `static` nested class. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsSource_example] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsProvider_example] ----- - - -[[writing-tests-parameterized-tests-argument-conversion]] -==== Argument Conversion - -[[writing-tests-parameterized-tests-argument-conversion-widening]] -===== Widening Conversion - -JUnit Jupiter supports -https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.2[Widening Primitive -Conversion] for arguments supplied to a `@ParameterizedTest`. For example, a -parameterized test annotated with `@ValueSource(ints = { 1, 2, 3 })` can be declared to -accept not only an argument of type `int` but also an argument of type `long`, `float`, -or `double`. - -[[writing-tests-parameterized-tests-argument-conversion-implicit]] -===== Implicit Conversion - -To support use cases like `@CsvSource`, JUnit Jupiter provides a number of built-in -implicit type converters. The conversion process depends on the declared type of each -method parameter. - -For example, if a `@ParameterizedTest` declares a parameter of type `TimeUnit` and the -actual type supplied by the declared source is a `String`, the string will be -automatically converted into the corresponding `TimeUnit` enum constant. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=implicit_conversion_example] ----- - -`String` instances are implicitly converted to the following target types. - -NOTE: Decimal, hexadecimal, and octal `String` literals will be converted to their -integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts. - -[[writing-tests-parameterized-tests-argument-conversion-implicit-table]] -[cols="10,90"] -|=== -| Target Type | Example - -| `boolean`/`Boolean` | `"true"` -> `true` -| `byte`/`Byte` | `"15"`, `"0xF"`, or `"017"` -> `(byte) 15` -| `char`/`Character` | `"o"` -> `'o'` -| `short`/`Short` | `"15"`, `"0xF"`, or `"017"` -> `(short) 15` -| `int`/`Integer` | `"15"`, `"0xF"`, or `"017"` -> `15` -| `long`/`Long` | `"15"`, `"0xF"`, or `"017"` -> `15L` -| `float`/`Float` | `"1.0"` -> `1.0f` -| `double`/`Double` | `"1.0"` -> `1.0d` -| `Enum` subclass | `"SECONDS"` -> `TimeUnit.SECONDS` -| `java.io.File` | `"/path/to/file"` -> `new File("/path/to/file")` -| `java.lang.Class` | `"java.lang.Integer"` -> `java.lang.Integer.class` _(use `$` for nested classes, e.g. `"java.lang.Thread$State"`)_ -| `java.lang.Class` | `"byte"` -> `byte.class` _(primitive types are supported)_ -| `java.lang.Class` | `"char[]"` -> `char[].class` _(array types are supported)_ -| `java.math.BigDecimal` | `"123.456e789"` -> `new BigDecimal("123.456e789")` -| `java.math.BigInteger` | `"1234567890123456789"` -> `new BigInteger("1234567890123456789")` -| `java.net.URI` | `"https://junit.org/"` -> `URI.create("https://junit.org/")` -| `java.net.URL` | `"https://junit.org/"` -> `URI.create("https://junit.org/").toURL()` -| `java.nio.charset.Charset` | `"UTF-8"` -> `Charset.forName("UTF-8")` -| `java.nio.file.Path` | `"/path/to/file"` -> `Paths.get("/path/to/file")` -| `java.time.Duration` | `"PT3S"` -> `Duration.ofSeconds(3)` -| `java.time.Instant` | `"1970-01-01T00:00:00Z"` -> `Instant.ofEpochMilli(0)` -| `java.time.LocalDateTime` | `"2017-03-14T12:34:56.789"` -> `LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)` -| `java.time.LocalDate` | `"2017-03-14"` -> `LocalDate.of(2017, 3, 14)` -| `java.time.LocalTime` | `"12:34:56.789"` -> `LocalTime.of(12, 34, 56, 789_000_000)` -| `java.time.MonthDay` | `"--03-14"` -> `MonthDay.of(3, 14)` -| `java.time.OffsetDateTime` | `"2017-03-14T12:34:56.789Z"` -> `OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)` -| `java.time.OffsetTime` | `"12:34:56.789Z"` -> `OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)` -| `java.time.Period` | `"P2M6D"` -> `Period.of(0, 2, 6)` -| `java.time.YearMonth` | `"2017-03"` -> `YearMonth.of(2017, 3)` -| `java.time.Year` | `"2017"` -> `Year.of(2017)` -| `java.time.ZonedDateTime` | `"2017-03-14T12:34:56.789Z"` -> `ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)` -| `java.time.ZoneId` | `"Europe/Berlin"` -> `ZoneId.of("Europe/Berlin")` -| `java.time.ZoneOffset` | `"+02:30"` -> `ZoneOffset.ofHoursMinutes(2, 30)` -| `java.util.Currency` | `"JPY"` -> `Currency.getInstance("JPY")` -| `java.util.Locale` | `"en"` -> `new Locale("en")` -| `java.util.UUID` | `"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"` -> `UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")` -|=== - -[[writing-tests-parameterized-tests-argument-conversion-implicit-fallback]] -====== Fallback String-to-Object Conversion - -In addition to implicit conversion from strings to the target types listed in the above -table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a -`String` to a given target type if the target type declares exactly one suitable _factory -method_ or a _factory constructor_ as defined below. - -- __factory method__: a non-private, `static` method declared in the target type that - accepts a single `String` argument and returns an instance of the target type. The name - of the method can be arbitrary and need not follow any particular convention. -- __factory constructor__: a non-private constructor in the target type that accepts a - single `String` argument. Note that the target type must be declared as either a - top-level class or as a `static` nested class. - -NOTE: If multiple _factory methods_ are discovered, they will be ignored. If a _factory -method_ and a _factory constructor_ are discovered, the factory method will be used -instead of the constructor. - -For example, in the following `@ParameterizedTest` method, the `Book` argument will be -created by invoking the `Book.fromTitle(String)` factory method and passing `"42 Cats"` -as the title of the book. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example_Book] ----- - -[[writing-tests-parameterized-tests-argument-conversion-explicit]] -===== Explicit Conversion - -Instead of relying on implicit argument conversion you may explicitly specify an -`ArgumentConverter` to use for a certain parameter using the `@ConvertWith` annotation -like in the following example. Note that an implementation of `ArgumentConverter` must be -declared as either a top-level class or as a `static` nested class. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_conversion_example] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_ToStringArgumentConverter] ----- - -If the converter is only meant to convert one type to another, you can extend -`TypedArgumentConverter` to avoid boilerplate type checks. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_TypedArgumentConverter] ----- - -Explicit argument converters are meant to be implemented by test and extension authors. -Thus, `junit-jupiter-params` only provides a single explicit argument converter that may -also serve as a reference implementation: `JavaTimeArgumentConverter`. It is used via the -composed annotation `JavaTimeConversionPattern`. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=explicit_java_time_converter] ----- - -[[writing-tests-parameterized-tests-argument-aggregation]] -==== Argument Aggregation - -By default, each _argument_ provided to a `@ParameterizedTest` method corresponds to a -single method parameter. Consequently, argument sources which are expected to supply a -large number of arguments can lead to large method signatures. - -In such cases, an `{ArgumentsAccessor}` can be used instead of multiple parameters. Using -this API, you can access the provided arguments through a single argument passed to your -test method. In addition, type conversion is supported as discussed in -<>. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAccessor_example] ----- - -_An instance of `ArgumentsAccessor` is automatically injected into any parameter of type -`ArgumentsAccessor`._ - -[[writing-tests-parameterized-tests-argument-aggregation-custom]] -===== Custom Aggregators - -Apart from direct access to a `@ParameterizedTest` method's arguments using an -`ArgumentsAccessor`, JUnit Jupiter also supports the usage of custom, reusable -_aggregators_. - -To use a custom aggregator, implement the `{ArgumentsAggregator}` interface and register -it via the `@AggregateWith` annotation on a compatible parameter in the -`@ParameterizedTest` method. The result of the aggregation will then be provided as an -argument for the corresponding parameter when the parameterized test is invoked. Note -that an implementation of `ArgumentsAggregator` must be declared as either a top-level -class or as a `static` nested class. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example_PersonAggregator] ----- - -If you find yourself repeatedly declaring `@AggregateWith(MyTypeAggregator.class)` for -multiple parameterized test methods across your codebase, you may wish to create a custom -_composed annotation_ such as `@CsvToMyType` that is meta-annotated with -`@AggregateWith(MyTypeAggregator.class)`. The following example demonstrates this in -action with a custom `@CsvToPerson` annotation. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example] ----- - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example_CsvToPerson] ----- - - -[[writing-tests-parameterized-tests-display-names]] -==== Customizing Display Names - -By default, the display name of a parameterized test invocation contains the invocation -index and the `String` representation of all arguments for that specific invocation. -Each of them is preceded by the parameter name (unless the argument is only available via -an `ArgumentsAccessor` or `ArgumentAggregator`), if present in the bytecode (for Java, -test code must be compiled with the `-parameters` compiler flag). - -However, you can customize invocation display names via the `name` attribute of the -`@ParameterizedTest` annotation like in the following example. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=custom_display_names] ----- - -When executing the above method using the `ConsoleLauncher` you will see output similar to -the following. - -.... -Display name of container ✔ -├─ 1 ==> the rank of 'apple' is 1 ✔ -├─ 2 ==> the rank of 'banana' is 2 ✔ -└─ 3 ==> the rank of 'lemon, lime' is 3 ✔ -.... - -Please note that `name` is a `MessageFormat` pattern. Thus, a single quote (`'`) needs to -be represented as a doubled single quote (`''`) in order to be displayed. - -The following placeholders are supported within custom display names. - -[cols="20,80"] -|=== -| Placeholder | Description - -| `{displayName}` | the display name of the method -| `{index}` | the current invocation index (1-based) -| `{arguments}` | the complete, comma-separated arguments list -| `{argumentsWithNames}` | the complete, comma-separated arguments list with parameter names -| `{0}`, `{1}`, ... | an individual argument -|=== - -NOTE: When including arguments in display names, their string representations are truncated -if they exceed the configured maximum length. The limit is configurable via the -`junit.jupiter.params.displayname.argument.maxlength` configuration parameter and defaults -to 512 characters. - -When using `@MethodSource` or `@ArgumentsSource`, you can provide custom names for -arguments using the `{Named}` API. A custom name will be used if the argument is included -in the invocation display name, like in the example below. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=named_arguments] ----- - -.... -A parameterized test with named arguments ✔ -├─ 1: An important file ✔ -└─ 2: Another file ✔ -.... - -If you'd like to set a default name pattern for all parameterized tests in your project, -you can declare the `junit.jupiter.params.displayname.default` configuration parameter in -the `junit-platform.properties` file as demonstrated in the following example (see -<> for other options). - -[source,properties,indent=0] ----- -junit.jupiter.params.displayname.default = {index} ----- - -The display name for a parameterized test is determined according to the following -precedence rules: - -1. `name` attribute in `@ParameterizedTest`, if present -2. value of the `junit.jupiter.params.displayname.default` configuration parameter, if present -3. `DEFAULT_DISPLAY_NAME` constant defined in `@ParameterizedTest` - -[[writing-tests-parameterized-tests-lifecycle-interop]] -==== Lifecycle and Interoperability - -Each invocation of a parameterized test has the same lifecycle as a regular `@Test` -method. For example, `@BeforeEach` methods will be executed before each invocation. -Similar to <>, invocations will appear one by one in the -test tree of an IDE. You may at will mix regular `@Test` methods and `@ParameterizedTest` -methods within the same test class. - -You may use `ParameterResolver` extensions with `@ParameterizedTest` methods. However, -method parameters that are resolved by argument sources need to come first in the -argument list. Since a test class may contain regular tests as well as parameterized -tests with different parameter lists, values from argument sources are not resolved for -lifecycle methods (e.g. `@BeforeEach`) and test class constructors. - -[source,java,indent=0] ----- -include::{testDir}/example/ParameterizedTestDemo.java[tags=ParameterResolver_example] ----- - - -[[writing-tests-test-templates]] -=== Test Templates - -A `{TestTemplate}` method is not a regular test case but rather a template for test -cases. As such, it is designed to be invoked multiple times depending on the number of -invocation contexts returned by the registered providers. Thus, it must be used in -conjunction with a registered `{TestTemplateInvocationContextProvider}` extension. Each -invocation of a test template method behaves like the execution of a regular `@Test` -method with full support for the same lifecycle callbacks and extensions. Please refer to -<> for usage examples. - -NOTE: <> and <> are -built-in specializations of test templates. - -[[writing-tests-dynamic-tests]] -=== Dynamic Tests - -The standard `@Test` annotation in JUnit Jupiter described in -<> is very similar to the `@Test` annotation in JUnit 4. Both -describe methods that implement test cases. These test cases are static in the sense that -they are fully specified at compile time, and their behavior cannot be changed by -anything happening at runtime. _Assumptions provide a basic form of dynamic behavior but -are intentionally rather limited in their expressiveness._ - -In addition to these standard tests a completely new kind of test programming model has -been introduced in JUnit Jupiter. This new kind of test is a _dynamic test_ which is -generated at runtime by a factory method that is annotated with `@TestFactory`. - -In contrast to `@Test` methods, a `@TestFactory` method is not itself a test case but -rather a factory for test cases. Thus, a dynamic test is the product of a factory. -Technically speaking, a `@TestFactory` method must return a single `DynamicNode` or a -`Stream`, `Collection`, `Iterable`, `Iterator`, or array of `DynamicNode` instances. -Instantiable subclasses of `DynamicNode` are `DynamicContainer` and `DynamicTest`. -`DynamicContainer` instances are composed of a _display name_ and a list of dynamic child -nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes. -`DynamicTest` instances will be executed lazily, enabling dynamic and even -non-deterministic generation of test cases. - -Any `Stream` returned by a `@TestFactory` will be properly closed by calling -`stream.close()`, making it safe to use a resource such as `Files.lines()`. - -As with `@Test` methods, `@TestFactory` methods must not be `private` or `static` and may -optionally declare parameters to be resolved by `ParameterResolvers`. - -A `DynamicTest` is a test case generated at runtime. It is composed of a _display name_ -and an `Executable`. `Executable` is a `@FunctionalInterface` which means that the -implementations of dynamic tests can be provided as _lambda expressions_ or _method -references_. - -.Dynamic Test Lifecycle -WARNING: The execution lifecycle of a dynamic test is quite different than it is for a -standard `@Test` case. Specifically, there are no lifecycle callbacks for individual -dynamic tests. This means that `@BeforeEach` and `@AfterEach` methods and their -corresponding extension callbacks are executed for the `@TestFactory` method but not for -each _dynamic test_. In other words, if you access fields from the test instance within a -lambda expression for a dynamic test, those fields will not be reset by callback methods -or extensions between the execution of individual dynamic tests generated by the same -`@TestFactory` method. - -As of JUnit Jupiter {jupiter-version}, dynamic tests must always be created by factory -methods; however, this might be complemented by a registration facility in a later -release. - -[[writing-tests-dynamic-tests-examples]] -==== Dynamic Test Examples - -The following `DynamicTestsDemo` class demonstrates several examples of test factories -and dynamic tests. - -The first method returns an invalid return type. Since an invalid return type cannot be -detected at compile time, a `JUnitException` is thrown when it is detected at runtime. - -The next six methods demonstrate the generation of a `Collection`, `Iterable`, `Iterator`, -array, or `Stream` of `DynamicTest` instances. Most of these examples do not really -exhibit dynamic behavior but merely demonstrate the supported return types in principle. -However, `dynamicTestsFromStream()` and `dynamicTestsFromIntStream()` demonstrate how to -generate dynamic tests for a given set of strings or a range of input numbers. - -The next method is truly dynamic in nature. `generateRandomNumberOfTests()` implements an -`Iterator` that generates random numbers, a display name generator, and a test executor -and then provides all three to `DynamicTest.stream()`. Although the non-deterministic -behavior of `generateRandomNumberOfTests()` is of course in conflict with test -repeatability and should thus be used with care, it serves to demonstrate the -expressiveness and power of dynamic tests. - -The next method is similar to `generateRandomNumberOfTests()` in terms of flexibility; -however, `dynamicTestsFromStreamFactoryMethod()` generates a stream of dynamic tests from -an existing `Stream` via the `DynamicTest.stream()` factory method. - -For demonstration purposes, the `dynamicNodeSingleTest()` method generates a single -`DynamicTest` instead of a stream, and the `dynamicNodeSingleContainer()` method generates -a nested hierarchy of dynamic tests utilizing `DynamicContainer`. - -[source,java] ----- -include::{testDir}/example/DynamicTestsDemo.java[tags=user_guide] ----- - -[[writing-tests-dynamic-tests-uri-test-source]] -==== URI Test Sources for Dynamic Tests - -The JUnit Platform provides `TestSource`, a representation of the source of a test or -container used to navigate to its location by IDEs and build tools. - -The `TestSource` for a dynamic test or dynamic container can be constructed from a -`java.net.URI` which can be supplied via the `DynamicTest.dynamicTest(String, URI, -Executable)` or `DynamicContainer.dynamicContainer(String, URI, Stream)` factory method, -respectively. The `URI` will be converted to one of the following `TestSource` -implementations. - -`ClasspathResourceSource` :: - If the `URI` contains the `classpath` scheme -- for example, - `classpath:/test/foo.xml?line=20,column=2`. - -`DirectorySource` :: - If the `URI` represents a directory present in the file system. - -`FileSource` :: - If the `URI` represents a file present in the file system. - -`MethodSource` :: - If the `URI` contains the `method` scheme and the fully qualified method name (FQMN) -- - for example, `method:org.junit.Foo#bar(java.lang.String, java.lang.String[])`. Please - refer to the Javadoc for `DiscoverySelectors.selectMethod(String)` for the supported - formats for a FQMN. - -`ClassSource` :: - If the `URI` contains the `class` scheme and the fully qualified class name -- - for example, `class:org.junit.Foo?line=42`. - -`UriSource` :: - If none of the above `TestSource` implementations are applicable. - - -[[writing-tests-declarative-timeouts]] -=== Timeouts - -The `@Timeout` annotation allows one to declare that a test, test factory, test template, -or lifecycle method should fail if its execution time exceeds a given duration. The time -unit for the duration defaults to seconds but is configurable. - -The following example shows how `@Timeout` is applied to lifecycle and test methods. - -[source,java] ----- -include::{testDir}/example/TimeoutDemo.java[tags=user_guide] ----- - -To apply the same timeout to all test methods within a test class and all of its `@Nested` -classes, you can declare the `@Timeout` annotation at the class level. It will then be -applied to all test, test factory, and test template methods within that class and its -`@Nested` classes unless overridden by a `@Timeout` annotation on a specific method or -`@Nested` class. Please note that `@Timeout` annotations declared at the class level are -not applied to lifecycle methods. - -Declaring `@Timeout` on a `@TestFactory` method checks that the factory method returns -within the specified duration but does not verify the execution time of each individual -`DynamicTest` generated by the factory. Please use -`assertTimeout()` or `assertTimeoutPreemptively()` for that purpose. - -If `@Timeout` is present on a `@TestTemplate` method — for example, a `@RepeatedTest` or -`@ParameterizedTest` — each invocation will have the given timeout applied to it. - -[[writing-tests-declarative-timeouts-thread-mode]] -==== Thread mode - -The timeout can be applied using one of the following three thread modes: `SAME_THREAD`, -`SEPARATE_THREAD`, or `INFERRED`. - -When `SAME_THREAD` is used, the execution of the annotated method proceeds in the main -thread of the test. If the timeout is exceeded, the main thread is interrupted from -another thread. This is done to ensure interoperability with frameworks such as Spring -that make use of mechanisms that are sensitive to the currently running thread — for -example, `ThreadLocal` transaction management. - -On the contrary when `SEPARATE_THREAD` is used, like the `assertTimeoutPreemptively()` -assertion, the execution of the annotated method proceeds in a separate thread, this -can lead to undesirable side effects, see <>. - -When `INFERRED` (default) thread mode is used, the thread mode is resolved via the -`junit.jupiter.execution.timeout.thread.mode.default` configuration parameter. If the -provided configuration parameter is invalid or not present then `SAME_THREAD` is used as -fallback. - -[[writing-tests-declarative-timeouts-default-timeouts]] -==== Default Timeouts - -The following <> can be used to -specify default timeouts for all methods of a certain category unless they or an enclosing -test class is annotated with `@Timeout`: - -`junit.jupiter.execution.timeout.default`:: - Default timeout for all testable and lifecycle methods -`junit.jupiter.execution.timeout.testable.method.default`:: - Default timeout for all testable methods -`junit.jupiter.execution.timeout.test.method.default`:: - Default timeout for `@Test` methods -`junit.jupiter.execution.timeout.testtemplate.method.default`:: - Default timeout for `@TestTemplate` methods -`junit.jupiter.execution.timeout.testfactory.method.default`:: - Default timeout for `@TestFactory` methods -`junit.jupiter.execution.timeout.lifecycle.method.default`:: - Default timeout for all lifecycle methods -`junit.jupiter.execution.timeout.beforeall.method.default`:: - Default timeout for `@BeforeAll` methods -`junit.jupiter.execution.timeout.beforeeach.method.default`:: - Default timeout for `@BeforeEach` methods -`junit.jupiter.execution.timeout.aftereach.method.default`:: - Default timeout for `@AfterEach` methods -`junit.jupiter.execution.timeout.afterall.method.default`:: - Default timeout for `@AfterAll` methods - -More specific configuration parameters override less specific ones. For example, -`junit.jupiter.execution.timeout.test.method.default` overrides -`junit.jupiter.execution.timeout.testable.method.default` which overrides -`junit.jupiter.execution.timeout.default`. - -The values of such configuration parameters must be in the following, case-insensitive -format: ` [ns|μs|ms|s|m|h|d]`. The space between the number and the unit may be -omitted. Specifying no unit is equivalent to using seconds. - -.Example timeout configuration parameter values -[cols="20,80"] -|=== -| Parameter value | Equivalent annotation - -| `42` | `@Timeout(42)` -| `42 ns` | `@Timeout(value = 42, unit = NANOSECONDS)` -| `42 μs` | `@Timeout(value = 42, unit = MICROSECONDS)` -| `42 ms` | `@Timeout(value = 42, unit = MILLISECONDS)` -| `42 s` | `@Timeout(value = 42, unit = SECONDS)` -| `42 m` | `@Timeout(value = 42, unit = MINUTES)` -| `42 h` | `@Timeout(value = 42, unit = HOURS)` -| `42 d` | `@Timeout(value = 42, unit = DAYS)` -|=== - - -[[writing-tests-declarative-timeouts-polling]] -==== Using @Timeout for Polling Tests - -When dealing with asynchronous code, it is common to write tests that poll while waiting -for something to happen before performing any assertions. In some cases you can rewrite -the logic to use a `CountDownLatch` or another synchronization mechanism, but sometimes -that is not possible — for example, if the subject under test sends a message to a channel -in an external message broker and assertions cannot be performed until the message has -been successfully sent through the channel. Asynchronous tests like these require some -form of timeout to ensure they don't hang the test suite by executing indefinitely, as -would be the case if an asynchronous message never gets successfully delivered. - -By configuring a timeout for an asynchronous test that polls, you can ensure that the test -does not execute indefinitely. The following example demonstrates how to achieve this with -JUnit Jupiter's `@Timeout` annotation. This technique can be used to implement "poll -until" logic very easily. - -[source,java] ----- -include::{testDir}/example/PollingTimeoutDemo.java[tags=user_guide,indent=0] ----- - -NOTE: If you need more control over polling intervals and greater flexibility with -asynchronous tests, consider using a dedicated library such as -link:https://github.com/awaitility/awaitility[Awaitility]. - - -[[writing-tests-declarative-timeouts-mode]] -==== Disable @Timeout Globally -When stepping through your code in a debug session, a fixed timeout limit may influence -the result of the test, e.g. mark the test as failed although all assertions were met. - -JUnit Jupiter supports the `junit.jupiter.execution.timeout.mode` configuration parameter -to configure when timeouts are applied. There are three modes: `enabled`, `disabled`, -and `disabled_on_debug`. The default mode is `enabled`. -A VM runtime is considered to run in debug mode when one of its input parameters starts -with `-agentlib:jdwp` or `-Xrunjdwp`. -This heuristic is queried by the `disabled_on_debug` mode. - - -[[writing-tests-parallel-execution]] -=== Parallel Execution - -.Parallel test execution is an experimental feature -WARNING: You're invited to give it a try and provide feedback to the JUnit team so they -can improve and eventually <> this feature. - -By default, JUnit Jupiter tests are run sequentially in a single thread. Running tests in -parallel -- for example, to speed up execution -- is available as an opt-in feature since -version 5.3. To enable parallel execution, set the -`junit.jupiter.execution.parallel.enabled` configuration parameter to `true` -- for -example, in `junit-platform.properties` (see <> for other -options). - -Please note that enabling this property is only the first step required to execute tests -in parallel. If enabled, test classes and methods will still be executed sequentially by -default. Whether or not a node in the test tree is executed concurrently is controlled by -its execution mode. The following two modes are available. - -`SAME_THREAD`:: - Force execution in the same thread used by the parent. For example, when used on a test - method, the test method will be executed in the same thread as any `@BeforeAll` or - `@AfterAll` methods of the containing test class. - -`CONCURRENT`:: - Execute concurrently unless a resource lock forces execution in the same thread. - -By default, nodes in the test tree use the `SAME_THREAD` execution mode. You can change -the default by setting the `junit.jupiter.execution.parallel.mode.default` configuration -parameter. Alternatively, you can use the `{Execution}` annotation to change the -execution mode for the annotated element and its subelements (if any) which allows you to -activate parallel execution for individual test classes, one by one. - -[source,properties] -.Configuration parameters to execute all tests in parallel ----- -junit.jupiter.execution.parallel.enabled = true -junit.jupiter.execution.parallel.mode.default = concurrent ----- - -The default execution mode is applied to all nodes of the test tree with a few notable -exceptions, namely test classes that use the `Lifecycle.PER_CLASS` mode or a -`{MethodOrderer}` (except for `{MethodOrderer_Random}`). In the former case, test authors -have to ensure that the test class is thread-safe; in the latter, concurrent execution -might conflict with the configured execution order. Thus, in both cases, test methods in -such test classes are only executed concurrently if the `@Execution(CONCURRENT)` -annotation is present on the test class or method. - -All nodes of the test tree that are configured with the `CONCURRENT` execution mode will -be executed fully in parallel according to the provided -<> while observing the -declarative <> -mechanism. Please note that <> needs to be enabled -separately. - -In addition, you can configure the default execution mode for top-level classes by setting -the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter. By -combining both configuration parameters, you can configure classes to run in parallel but -their methods in the same thread: - -[source,properties] -.Configuration parameters to execute top-level classes in parallel but methods in same thread ----- -junit.jupiter.execution.parallel.enabled = true -junit.jupiter.execution.parallel.mode.default = same_thread -junit.jupiter.execution.parallel.mode.classes.default = concurrent ----- - -The opposite combination will run all methods within one class in parallel, but top-level -classes will run sequentially: - -[source,properties] -.Configuration parameters to execute top-level classes sequentially but their methods in parallel ----- -junit.jupiter.execution.parallel.enabled = true -junit.jupiter.execution.parallel.mode.default = concurrent -junit.jupiter.execution.parallel.mode.classes.default = same_thread ----- - -The following diagram illustrates how the execution of two top-level test classes `A` and -`B` with two test methods per class behaves for all four combinations of -`junit.jupiter.execution.parallel.mode.default` and -`junit.jupiter.execution.parallel.mode.classes.default` (see labels in first column). - -//// -Source: https://mermaidjs.github.io/mermaid-live-editor/#/view/eyJjb2RlIjoiZ2FudHRcbiAgICBkYXRlRm9ybWF0ICBZWVlZLU1NLUREXG5cbiAgICBzZWN0aW9uIChzYW1lX3RocmVhZCwgc2FtZV90aHJlYWQpXG4gICAgQS50ZXN0MSgpIDphc3MxLCAyMDE5LTAxLTAxLCAxZFxuICAgIEEudGVzdDIoKSA6YXNzMiwgYWZ0ZXIgYXNzMSwgMWRcbiAgICBCLnRlc3QxKCkgOmJzczEsIGFmdGVyIGFzczIsIDFkXG4gICAgQi50ZXN0MigpIDpic3MyLCBhZnRlciBic3MxLCAxZFxuXG4gICAgc2VjdGlvbiAoc2FtZV90aHJlYWQsIGNvbmN1cnJlbnQpXG4gICAgQS50ZXN0MSgpIDphc2MxLCAyMDE5LTAxLTAxLCAxZFxuICAgIEEudGVzdDIoKSA6YXNjMiwgYWZ0ZXIgYXNjMSwgMWRcbiAgICBCLnRlc3QxKCkgOmJzYzEsIDIwMTktMDEtMDEsIDFkXG4gICAgQi50ZXN0MigpIDpic2MyLCBhZnRlciBic2MxLCAxZFxuXG4gICAgc2VjdGlvbiAoY29uY3VycmVudCwgc2FtZV90aHJlYWQpXG4gICAgQS50ZXN0MSgpIDphY3MxLCAyMDE5LTAxLTAxLCAxZFxuICAgIEEudGVzdDIoKSA6YWNzMiwgMjAxOS0wMS0wMSwgMWRcbiAgICBCLnRlc3QxKCkgOmJjczEsIGFmdGVyIGFjczIsIDFkXG4gICAgQi50ZXN0MigpIDpiY3MyLCBhZnRlciBhY3MyLCAxZFxuXG4gICAgc2VjdGlvbiAoY29uY3VycmVudCwgY29uY3VycmVudClcbiAgICBBLnRlc3QxKCkgOmFjYzEsIDIwMTktMDEtMDEsIDFkXG4gICAgQS50ZXN0MigpIDphY2MyLCAyMDE5LTAxLTAxLCAxZFxuICAgIEIudGVzdDEoKSA6YmNjMSwgMjAxOS0wMS0wMSwgMWRcbiAgICBCLnRlc3QyKCkgOmJjYzIsIDIwMTktMDEtMDEsIDFkXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoibmV1dHJhbCIsImdhbnR0Ijp7ImxlZnRQYWRkaW5nIjoyMjUsImJhckdhcCI6NSwiZ3JpZExpbmVTdGFydFBhZGRpbmciOjEwLCJiYXJIZWlnaHQiOjMwLCJmb250U2l6ZSI6MTV9LCJ0aGVtZUNTUyI6Ii50YXNrVGV4dCwgLnNlY3Rpb25UaXRsZSB7IGZvbnQtZmFtaWx5OiAnT3BlbiBTYW5zJzsgZm9udC1zaXplOjE1cHggfSAuZ3JpZCAudGljayB0ZXh0IHsgZGlzcGxheTpub25lIH0gLmdyaWQgLnRpY2s6bnRoLWNoaWxkKDJuKzEpIHsgZGlzcGxheTpub25lIH0ifX0 - -gantt - dateFormat YYYY-MM-DD - - section (same_thread, same_thread) - A.test1() :ass1, 2019-01-01, 1d - A.test2() :ass2, after ass1, 1d - B.test1() :bss1, after ass2, 1d - B.test2() :bss2, after bss1, 1d - - section (same_thread, concurrent) - A.test1() :asc1, 2019-01-01, 1d - A.test2() :asc2, after asc1, 1d - B.test1() :bsc1, 2019-01-01, 1d - B.test2() :bsc2, after bsc1, 1d - - section (concurrent, same_thread) - A.test1() :acs1, 2019-01-01, 1d - A.test2() :acs2, 2019-01-01, 1d - B.test1() :bcs1, after acs2, 1d - B.test2() :bcs2, after acs2, 1d - - section (concurrent, concurrent) - A.test1() :acc1, 2019-01-01, 1d - A.test2() :acc2, 2019-01-01, 1d - B.test1() :bcc1, 2019-01-01, 1d - B.test2() :bcc2, 2019-01-01, 1d - -//// -image::writing-tests_execution_mode.svg[caption='',title='Default execution mode configuration combinations'] - -If the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter is -not explicitly set, the value for `junit.jupiter.execution.parallel.mode.default` will be -used instead. - -[[writing-tests-parallel-execution-config]] -==== Configuration - -Properties such as the desired parallelism and the maximum pool size can be configured -using a `{ParallelExecutionConfigurationStrategy}`. The JUnit Platform provides two -implementations out of the box: `dynamic` and `fixed`. Alternatively, you may implement a -`custom` strategy. - -To select a strategy, set the `junit.jupiter.execution.parallel.config.strategy` -configuration parameter to one of the following options. - -`dynamic`:: - Computes the desired parallelism based on the number of available processors/cores - multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor` - configuration parameter (defaults to `1`). - -`fixed`:: - Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism` - configuration parameter as the desired parallelism. - The optional `junit.jupiter.execution.parallel.config.fixed.max-pool-size` - configuration parameter can be used to limit the maximum number of threads. - -`custom`:: - Allows you to specify a custom `{ParallelExecutionConfigurationStrategy}` - implementation via the mandatory `junit.jupiter.execution.parallel.config.custom.class` - configuration parameter to determine the desired configuration. - -If no configuration strategy is set, JUnit Jupiter uses the `dynamic` configuration -strategy with a factor of `1`. Consequently, the desired parallelism will be equal to the -number of available processors/cores. - -.Parallelism alone does not imply maximum number of concurrent threads -NOTE: By default JUnit Jupiter does not guarantee that the number of concurrently -executing tests will not exceed the configured parallelism. For example, when using one -of the synchronization mechanisms described in the next section, the `ForkJoinPool` that -is used behind the scenes may spawn additional threads to ensure execution continues with -sufficient parallelism. -If you require such guarantees, with Java 9+, it is possible to limit the maximum number -of concurrent threads by controlling the maximum pool size of the `fixed` and `custom` -strategies. - -[[writing-tests-parallel-execution-config-properties]] -===== Relevant properties - -The following table lists relevant properties for configuring parallel execution. See -<> for details on how to set such properties. - -[cols="d,d,a,d"] -|=== -|Property |Description |Supported Values |Default Value - -| ```junit.jupiter.execution.parallel.enabled``` -| Enable parallel test execution -| - * `true` - * `false` -| ```false``` - -| ```junit.jupiter.execution.parallel.mode.default``` -| Default execution mode of nodes in the test tree -| - * `concurrent` - * `same_thread` -| ```same_thread``` - -| ```junit.jupiter.execution.parallel.mode.classes.default``` -| Default execution mode of top-level classes -| - * `concurrent` - * `same_thread` -| ```same_thread``` - -| ```junit.jupiter.execution.parallel.config.strategy``` -| Execution strategy for desired parallelism and maximum pool size -| - * `dynamic` - * `fixed` - * `custom` -| ```dynamic``` - -| ```junit.jupiter.execution.parallel.config.dynamic.factor``` -| Factor to be multiplied by the number of available processors/cores to determine the - desired parallelism for the ```dynamic``` configuration strategy -| a positive decimal number -| ```1.0``` - -| ```junit.jupiter.execution.parallel.config.fixed.parallelism``` -| Desired parallelism for the ```fixed``` configuration strategy -| a positive integer -| no default value - -| ```junit.jupiter.execution.parallel.config.fixed.max-pool-size``` -| Desired maximum pool size of the underlying fork-join pool for the ```fixed``` - configuration strategy -| a positive integer, must greater than or equal to `junit.jupiter.execution.parallel.config.fixed.parallelism` -| 256 + the value of `junit.jupiter.execution.parallel.config.fixed.parallelism` - -| ```junit.jupiter.execution.parallel.config.fixed.saturate``` -| Disable saturation of the underlying fork-join pool for the ```fixed``` configuration - strategy -| - * `true` - * `false` -| ```true``` - -| ```junit.jupiter.execution.parallel.config.custom.class``` -| Fully qualified class name of the _ParallelExecutionConfigurationStrategy_ to be - used for the ```custom``` configuration strategy -| for example, _org.example.CustomStrategy_ -| no default value -|=== - -[[writing-tests-parallel-execution-synchronization]] -==== Synchronization - -In addition to controlling the execution mode using the `{Execution}` annotation, JUnit -Jupiter provides another annotation-based declarative synchronization mechanism. The -`{ResourceLock}` annotation allows you to declare that a test class or method uses a -specific shared resource that requires synchronized access to ensure reliable test -execution. The shared resource is identified by a unique name which is a `String`. The -name can be user-defined or one of the predefined constants in `{Resources}`: -`SYSTEM_PROPERTIES`, `SYSTEM_OUT`, `SYSTEM_ERR`, `LOCALE`, or `TIME_ZONE`. - -If the tests in the following example were run in parallel _without_ the use of -{ResourceLock}, they would be _flaky_. Sometimes they would pass, and at other times they -would fail due to the inherent race condition of writing and then reading the same JVM -System Property. - -When access to shared resources is declared using the `{ResourceLock}` annotation, the -JUnit Jupiter engine uses this information to ensure that no conflicting tests are run in -parallel. - -[NOTE] -.Running tests in isolation -==== -If most of your test classes can be run in parallel without any synchronization but you -have some test classes that need to run in isolation, you can mark the latter with the -`{Isolated}` annotation. Tests in such classes are executed sequentially without any other -tests running at the same time. -==== - -In addition to the `String` that uniquely identifies the shared resource, you may specify -an access mode. Two tests that require `READ` access to a shared resource may run in -parallel with each other but not while any other test that requires `READ_WRITE` access -to the same shared resource is running. - -[source,java] ----- -include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide] ----- - - -[[writing-tests-built-in-extensions]] -=== Built-in Extensions - -While the JUnit team encourages reusable extensions to be packaged and maintained in -separate libraries, the JUnit Jupiter API artifact includes a few user-facing extension -implementations that are considered so generally useful that users shouldn't have to add -another dependency. - -[[writing-tests-built-in-extensions-TempDirectory]] -==== The TempDirectory Extension - -.`@TempDir` is an experimental feature -WARNING: You're invited to give it a try and provide feedback to the JUnit team so they -can improve and eventually <> this feature. - -The built-in `{TempDirectory}` extension is used to create and clean up a temporary -directory for an individual test or all tests in a test class. It is registered by -default. To use it, annotate a non-final, unassigned field of type `java.nio.file.Path` or -`java.io.File` with `{TempDir}` or add a parameter of type `java.nio.file.Path` or -`java.io.File` annotated with `@TempDir` to a lifecycle method or test method. - -For example, the following test declares a parameter annotated with `@TempDir` for a -single test method, creates and writes to a file in the temporary directory, and checks -its content. - -[source,java,indent=0] -.A test method that requires a temporary directory ----- -include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_parameter_injection] ----- - -You can inject multiple temporary directories by specifying multiple annotated parameters. - -[source,java,indent=0] -.A test method that requires multiple temporary directories ----- -include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_multiple_directories] ----- - -WARNING: To revert to the old behavior of using a single temporary directory for the -entire test class or method (depending on which level the annotation is used), you can set -the `junit.jupiter.tempdir.scope` configuration parameter to `per_context`. However, -please note that this option is deprecated and will be removed in a future release. - -`@TempDir` is not supported on constructor parameters. If you wish to retain a single -reference to a temp directory across lifecycle methods and the current test method, please -use field injection by annotating an instance field with `@TempDir`. - -The following example stores a _shared_ temporary directory in a `static` field. This -allows the same `sharedTempDir` to be used in all lifecycle methods and test methods of -the test class. For better isolation, you should use an instance field so that each test -method uses a separate directory. - -[source,java,indent=0] -.A test class that shares a temporary directory across test methods ----- -include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_field_injection] ----- - -The `@TempDir` annotation has an optional `cleanup` attribute that can be set to either -`NEVER`, `ON_SUCCESS`, or `ALWAYS`. If the cleanup mode is set to `NEVER`, temporary -directories are not deleted after a test completes. If it is set to `ON_SUCCESS`, -temporary directories are deleted only after a test completed successfully. - -The default cleanup mode is `ALWAYS`. You can use the -`junit.jupiter.tempdir.cleanup.mode.default` -<> to override this default. - -[source,java,indent=0] -.A test class with a temporary directory that doesn't get cleaned up ----- -include::{testDir}/example/TempDirCleanupModeDemo.java[tags=user_guide] ----- diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html deleted file mode 100644 index 2e79b9d5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-overview.html +++ /dev/null @@ -1,26 +0,0 @@ - - -

This document consists of three sections:

- -
-
Platform
-
The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. - It also defines the TestEngine API for developing a testing framework that runs on the - platform. Furthermore, the platform provides a Console Launcher to launch the platform - from the command line and a JUnit 4 based Runner for running any TestEngine on the - platform in a JUnit 4 based environment. -
-
Jupiter
-
JUnit Jupiter is the combination of the new programming model and extension model for - writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine - for running Jupiter based tests on the platform. -
-
Vintage
-
JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the - platform. -
-
- -

Already consulted the JUnit 5 User Guide?

- - diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css deleted file mode 100644 index 19fe4f0c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/javadoc/junit-stylesheet.css +++ /dev/null @@ -1,155 +0,0 @@ -/* - * CSS customizations for JUnit - */ - -@import url('https://fonts.googleapis.com/css?family=Fira+Mono:400,700|Open+Sans:300,300i,400,400i,700,700i'); - -body, .title { - color:#333333; -} - -body, div.block, .deprecationBlock, button { - font-family: 'Open Sans', Arial, Helvetica, sans-serif; -} - -.title { - font-weight: 300; - font-size: 32px; - margin-top: 0; -} - -a:link, a:visited { - text-decoration:none; - color:#dc524a; -} - -a[href]:hover, a[href]:focus { - text-decoration:none; - color:#b62b23; -} - -pre, code, tt, dt code, table tr td dt code { - font-family: "Fira Mono", monospace; -} - -.bar { - background-color:#25a162; -} - -.top-nav { - background-color:#25a162; -} - -.bottom-nav { - background-color:#25a162; -} - -.sub-nav { - background-color:#f5f5f5; -} - -.top-nav a:hover, .bottom-nav a:hover { - text-decoration:underline; - color:inherit; -} - -.nav-bar-cell1-rev { - background-color:#fff; - color:#dc524a; - border-radius: 6px; -} - -.index-nav { - background-color:#eee; -} - -body.class-declaration-page .summary h2, -body.class-declaration-page .details h2, -body.class-use-page h2, -body.module-declaration-page .block-list h2 { - font-style: italic; - padding:0; - margin:15px 0; -} -body.class-declaration-page .summary h3, -body.class-declaration-page .details h3, -body.class-declaration-page .summary .inherited-list h2, -div.details ul.block-list ul.block-list ul.block-list li.block-list h4, -ul.block-list ul.block-list ul.block-list li.block-list h3 { - background-color:#ddd; - border:1px solid #ddd; -} - -.constants-summary caption a:link, .constants-summary caption a:visited, -.use-summary caption a:link, .use-summary caption a:visited { - color:#fff; -} - -.overview-summary caption span, .member-summary caption span, .type-summary caption span, -.use-summary caption span, .constants-summary caption span, .deprecated-summary caption span, -.requires-summary caption span, .packages-summary caption span, .provides-summary caption span, -.uses-summary caption span, -.member-summary caption span.active-table-tab span, .packages-summary caption span.active-table-tab span, -.overview-summary caption span.active-table-tab span, .type-summary caption span.active-table-tab span, -div.table-tabs > button.active-table-tab -{ - background-color:#dc524a; - color: #fff; -} - -.ui-state-active, -.ui-widget-content .ui-state-active, -.ui-widget-header .ui-state-active, -a.ui-button:active, -.ui-button:active, -.ui-button.ui-state-active:hover { - /* Overrides the color of selection used in jQuery UI */ - background: #dc524a !important; - color: #fff !important; -} - -main a[href*="://"]::after, -main a[href*="://"]:hover::after, -main a[href*="://"]:focus::after { - background-image:url('data:image/svg+xml; utf8, \ - \ - \ - '); -} - -.member-summary caption span.table-tab span, .packages-summary caption span.table-tab span, -.overview-summary caption span.table-tab span, .type-summary caption span.table-tab span, -.ui-autocomplete-category, -div.table-tabs > button.table-tab { - background-color:#aaa; - color: #fff; -} - -th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .constants-summary th, -.packages-summary th { - background:#eee; -} - -.table-sub-heading-color { - background-color:#eee; -} - -.alt-color, .alt-color th { - background-color:#fff; -} - -.row-color, .row-color th { - background-color:#eee; -} - -.block { - margin:0 10px 5px 0; -} - -th.col-first, th.col-second, th.col-last, th.col-constructor-name, th.col-deprecated-item-name, .constants-summary th, -.packages-summary th, .overview-summary td, .member-summary td, .type-summary td, -.use-summary td, .constants-summary td, .deprecated-summary td, -.requires-summary td, .packages-summary td, .provides-summary td, .uses-summary td { - padding-left:7px; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java deleted file mode 100644 index b628febd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/Person.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.domain; - -import java.time.LocalDate; - -public class Person { - - public enum Gender { - F, M - } - - private String firstName; - private String lastName; - private Gender gender; - private LocalDate dateOfBirth; - - public Person(String firstName, String lastName) { - this.firstName = firstName; - this.lastName = lastName; - } - - public Person(String firstName, String lastName, Gender gender, LocalDate dateOfBirth) { - this(firstName, lastName); - this.gender = gender; - this.dateOfBirth = dateOfBirth; - } - - public String getFirstName() { - return firstName; - } - - public String getLastName() { - return lastName; - } - - public Gender getGender() { - return gender; - } - - public LocalDate getDateOfBirth() { - return dateOfBirth; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); - result = prime * result + ((lastName == null) ? 0 : lastName.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Person other = (Person) obj; - if (firstName == null) { - if (other.firstName != null) { - return false; - } - } - else if (!firstName.equals(other.firstName)) { - return false; - } - if (lastName == null) { - if (other.lastName != null) { - return false; - } - } - else if (!lastName.equals(other.lastName)) { - return false; - } - return true; - } - - @Override - public String toString() { - return "Person [firstName=" + firstName + ", lastName=" + lastName + "]"; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java deleted file mode 100644 index ace33c48..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/domain/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Demo domain model. - */ - -package example.domain; diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java deleted file mode 100644 index b907c2c5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebClient.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.registration; - -public class WebClient { - - public WebResponse get(String string) { - return new WebResponse(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java deleted file mode 100644 index 598239f4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.registration; - -public class WebResponse { - - public int getResponseStatus() { - return 200; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java deleted file mode 100644 index 80fefe78..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/WebServerExtension.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.registration; - -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -public class WebServerExtension implements BeforeAllCallback { - - @Override - public void beforeAll(ExtensionContext context) { - /* no-op for demo */ - } - - public String getServerUrl() { - return "https://example.org:8181"; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - public Builder enableSecurity(boolean b) { - return this; - } - - public WebServerExtension build() { - return new WebServerExtension(); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java deleted file mode 100644 index 2f71c663..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/registration/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Demo code for a WebServer extension. - */ - -package example.registration; diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java deleted file mode 100644 index 98291f6a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/Calculator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.util; - -public class Calculator { - - public int add(int a, int b) { - return a + b; - } - - public int subtract(int a, int b) { - return a - b; - } - - public int multiply(int a, int b) { - return a * b; - } - - public int divide(int a, int b) { - return a / b; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java deleted file mode 100644 index 88fb7313..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/ListWriter.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.util; - -import static java.util.Collections.singletonList; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -public class ListWriter { - - private final Path file; - - public ListWriter(Path file) { - this.file = file; - } - - public void write(String... items) throws IOException { - Files.write(file, singletonList(String.join(",", items))); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java deleted file mode 100644 index b622aa3e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/StringUtils.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.util; - -public class StringUtils { - - public static boolean isPalindrome(String candidate) { - int length = candidate.length(); - for (int i = 0; i < length / 2; i++) { - if (candidate.charAt(i) != candidate.charAt(length - (i + 1))) { - return false; - } - } - return true; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java deleted file mode 100644 index d31921f2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/main/java/example/util/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Demo utilities. - */ - -package example.util; diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java deleted file mode 100644 index a9cd383d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssertionsDemo.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// @formatter:off -// tag::user_guide[] -import static java.time.Duration.ofMillis; -import static java.time.Duration.ofMinutes; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTimeout; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.concurrent.CountDownLatch; - -import example.domain.Person; -import example.util.Calculator; - -import org.junit.jupiter.api.Test; - -class AssertionsDemo { - - private final Calculator calculator = new Calculator(); - - private final Person person = new Person("Jane", "Doe"); - - @Test - void standardAssertions() { - assertEquals(2, calculator.add(1, 1)); - assertEquals(4, calculator.multiply(2, 2), - "The optional failure message is now the last parameter"); - assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- " - + "to avoid constructing complex messages unnecessarily."); - } - - @Test - void groupedAssertions() { - // In a grouped assertion all assertions are executed, and all - // failures will be reported together. - assertAll("person", - () -> assertEquals("Jane", person.getFirstName()), - () -> assertEquals("Doe", person.getLastName()) - ); - } - - @Test - void dependentAssertions() { - // Within a code block, if an assertion fails the - // subsequent code in the same block will be skipped. - assertAll("properties", - () -> { - String firstName = person.getFirstName(); - assertNotNull(firstName); - - // Executed only if the previous assertion is valid. - assertAll("first name", - () -> assertTrue(firstName.startsWith("J")), - () -> assertTrue(firstName.endsWith("e")) - ); - }, - () -> { - // Grouped assertion, so processed independently - // of results of first name assertions. - String lastName = person.getLastName(); - assertNotNull(lastName); - - // Executed only if the previous assertion is valid. - assertAll("last name", - () -> assertTrue(lastName.startsWith("D")), - () -> assertTrue(lastName.endsWith("e")) - ); - } - ); - } - - @Test - void exceptionTesting() { - Exception exception = assertThrows(ArithmeticException.class, () -> - calculator.divide(1, 0)); - assertEquals("/ by zero", exception.getMessage()); - } - - @Test - void timeoutNotExceeded() { - // The following assertion succeeds. - assertTimeout(ofMinutes(2), () -> { - // Perform task that takes less than 2 minutes. - }); - } - - @Test - void timeoutNotExceededWithResult() { - // The following assertion succeeds, and returns the supplied object. - String actualResult = assertTimeout(ofMinutes(2), () -> { - return "a result"; - }); - assertEquals("a result", actualResult); - } - - @Test - void timeoutNotExceededWithMethod() { - // The following assertion invokes a method reference and returns an object. - String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting); - assertEquals("Hello, World!", actualGreeting); - } - - // end::user_guide[] - @extensions.ExpectToFail - // tag::user_guide[] - @Test - void timeoutExceeded() { - // The following assertion fails with an error message similar to: - // execution exceeded timeout of 10 ms by 91 ms - assertTimeout(ofMillis(10), () -> { - // Simulate task that takes more than 10 ms. - Thread.sleep(100); - }); - } - - // end::user_guide[] - @extensions.ExpectToFail - // tag::user_guide[] - @Test - void timeoutExceededWithPreemptiveTermination() { - // The following assertion fails with an error message similar to: - // execution timed out after 10 ms - assertTimeoutPreemptively(ofMillis(10), () -> { - // Simulate task that takes more than 10 ms. - new CountDownLatch(1).await(); - }); - } - - private static String greeting() { - return "Hello, World!"; - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java deleted file mode 100644 index 41438fea..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/AssumptionsDemo.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// @formatter:off -// tag::user_guide[] - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.Assumptions.assumingThat; - -import example.util.Calculator; - -import org.junit.jupiter.api.Test; - -class AssumptionsDemo { - - private final Calculator calculator = new Calculator(); - - @Test - void testOnlyOnCiServer() { - assumeTrue("CI".equals(System.getenv("ENV"))); - // remainder of test - } - - @Test - void testOnlyOnDeveloperWorkstation() { - assumeTrue("DEV".equals(System.getenv("ENV")), - () -> "Aborting test: not on developer workstation"); - // remainder of test - } - - @Test - void testInAllEnvironments() { - assumingThat("CI".equals(System.getenv("ENV")), - () -> { - // perform these assertions only on the CI server - assertEquals(2, calculator.divide(4, 2)); - }); - - // perform these assertions in all environments - assertEquals(42, calculator.multiply(6, 7)); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java deleted file mode 100644 index 146443c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ConditionalTestExecutionDemo.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import static org.junit.jupiter.api.condition.JRE.JAVA_10; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; -import static org.junit.jupiter.api.condition.JRE.JAVA_8; -import static org.junit.jupiter.api.condition.JRE.JAVA_9; -import static org.junit.jupiter.api.condition.OS.LINUX; -import static org.junit.jupiter.api.condition.OS.MAC; -import static org.junit.jupiter.api.condition.OS.WINDOWS; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; -import org.junit.jupiter.api.condition.DisabledIf; -import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; -import org.junit.jupiter.api.condition.DisabledInNativeImage; -import org.junit.jupiter.api.condition.DisabledOnJre; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.EnabledIf; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; -import org.junit.jupiter.api.condition.EnabledInNativeImage; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.EnabledOnOs; - -class ConditionalTestExecutionDemo { - - // tag::user_guide_os[] - @Test - @EnabledOnOs(MAC) - void onlyOnMacOs() { - // ... - } - - @TestOnMac - void testOnMac() { - // ... - } - - @Test - @EnabledOnOs({ LINUX, MAC }) - void onLinuxOrMac() { - // ... - } - - @Test - @DisabledOnOs(WINDOWS) - void notOnWindows() { - // ... - } - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @Test - @EnabledOnOs(MAC) - @interface TestOnMac { - } - // end::user_guide_os[] - - // tag::user_guide_architecture[] - @Test - @EnabledOnOs(architectures = "aarch64") - void onAarch64() { - // ... - } - - @Test - @DisabledOnOs(architectures = "x86_64") - void notOnX86_64() { - // ... - } - - @Test - @EnabledOnOs(value = MAC, architectures = "aarch64") - void onNewMacs() { - // ... - } - - @Test - @DisabledOnOs(value = MAC, architectures = "aarch64") - void notOnNewMacs() { - // ... - } - // end::user_guide_architecture[] - - // tag::user_guide_jre[] - @Test - @EnabledOnJre(JAVA_8) - void onlyOnJava8() { - // ... - } - - @Test - @EnabledOnJre({ JAVA_9, JAVA_10 }) - void onJava9Or10() { - // ... - } - - @Test - @EnabledForJreRange(min = JAVA_9, max = JAVA_11) - void fromJava9to11() { - // ... - } - - @Test - @EnabledForJreRange(min = JAVA_9) - void fromJava9toCurrentJavaFeatureNumber() { - // ... - } - - @Test - @EnabledForJreRange(max = JAVA_11) - void fromJava8To11() { - // ... - } - - @Test - @DisabledOnJre(JAVA_9) - void notOnJava9() { - // ... - } - - @Test - @DisabledForJreRange(min = JAVA_9, max = JAVA_11) - void notFromJava9to11() { - // ... - } - - @Test - @DisabledForJreRange(min = JAVA_9) - void notFromJava9toCurrentJavaFeatureNumber() { - // ... - } - - @Test - @DisabledForJreRange(max = JAVA_11) - void notFromJava8to11() { - // ... - } - // end::user_guide_jre[] - - // tag::user_guide_native[] - @Test - @EnabledInNativeImage - void onlyWithinNativeImage() { - // ... - } - - @Test - @DisabledInNativeImage - void neverWithinNativeImage() { - // ... - } - // end::user_guide_native[] - - // tag::user_guide_system_property[] - @Test - @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") - void onlyOn64BitArchitectures() { - // ... - } - - @Test - @DisabledIfSystemProperty(named = "ci-server", matches = "true") - void notOnCiServer() { - // ... - } - // end::user_guide_system_property[] - - // tag::user_guide_environment_variable[] - @Test - @EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server") - void onlyOnStagingServer() { - // ... - } - - @Test - @DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*") - void notOnDeveloperWorkstation() { - // ... - } - // end::user_guide_environment_variable[] - - // tag::user_guide_custom[] - @Test - @EnabledIf("customCondition") - void enabled() { - // ... - } - - @Test - @DisabledIf("customCondition") - void disabled() { - // ... - } - - boolean customCondition() { - return true; - } - // end::user_guide_custom[] - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java deleted file mode 100644 index a1d0eb31..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/CustomTestEngine.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; - -/** - * This is a no-op {@link TestEngine} that is only - * used to make examples compile. - */ -class CustomTestEngine implements TestEngine { - - @Override - public String getId() { - return "custom-test-engine"; - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - return new EngineDescriptor(UniqueId.forEngine(getId()), "Custom Test Engine"); - } - - @Override - public void execute(ExecutionRequest request) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java deleted file mode 100644 index a2453a2a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledClassDemo.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -@Disabled("Disabled until bug #99 has been fixed") -class DisabledClassDemo { - - @Test - void testWillBeSkipped() { - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java deleted file mode 100644 index e1a7f6c0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisabledTestsDemo.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -class DisabledTestsDemo { - - @Disabled("Disabled until bug #42 has been resolved") - @Test - void testWillBeSkipped() { - } - - @Test - void testWillBeExecuted() { - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java deleted file mode 100644 index c9ee6fed..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameDemo.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -@DisplayName("A special test case") -class DisplayNameDemo { - - @Test - @DisplayName("Custom test name containing spaces") - void testWithDisplayNameContainingSpaces() { - } - - @Test - @DisplayName("╯°□°)╯") - void testWithDisplayNameContainingSpecialCharacters() { - } - - @Test - @DisplayName("😱") - void testWithDisplayNameContainingEmoji() { - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java deleted file mode 100644 index f1483d15..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DisplayNameGeneratorDemo.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.IndicativeSentencesGeneration; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class DisplayNameGeneratorDemo { - - @Nested - @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) - class A_year_is_not_supported { - - @Test - void if_it_is_zero() { - } - - @DisplayName("A negative value for year is not supported by the leap year computation.") - @ParameterizedTest(name = "For example, year {0} is not supported.") - @ValueSource(ints = { -1, -4 }) - void if_it_is_negative(int year) { - } - - } - - @Nested - @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) - class A_year_is_a_leap_year { - - @Test - void if_it_is_divisible_by_4_but_not_by_100() { - } - - @ParameterizedTest(name = "Year {0} is a leap year.") - @ValueSource(ints = { 2016, 2020, 2048 }) - void if_it_is_one_of_the_following_years(int year) { - } - - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java deleted file mode 100644 index b3bc6dd0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DocumentationTestSuite.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import org.junit.platform.suite.api.ExcludeTags; -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; - -/** - *

Logging Configuration

- * - *

In order for our log4j2 configuration to be used in an IDE, you must - * set the following system property before running any tests — for - * example, in Run Configurations in Eclipse. - * - *

- * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
- * 
- * - * @since 5.0 - */ -@Suite -@SelectPackages("example") -@IncludeClassNamePatterns(".+(Tests|Demo)$") -@ExcludeTags("exclude") -class DocumentationTestSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java deleted file mode 100644 index 8a8d9bef..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/DynamicTestsDemo.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] - -import static example.util.StringUtils.isPalindrome; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.Named.named; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Random; -import java.util.function.Function; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import example.util.Calculator; - -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.function.ThrowingConsumer; - -// end::user_guide[] -// @formatter:off -// tag::user_guide[] -class DynamicTestsDemo { - - private final Calculator calculator = new Calculator(); - - // end::user_guide[] - @Tag("exclude") - // tag::user_guide[] - // This will result in a JUnitException! - @TestFactory - List dynamicTestsWithInvalidReturnType() { - return Arrays.asList("Hello"); - } - - @TestFactory - Collection dynamicTestsFromCollection() { - return Arrays.asList( - dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))), - dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) - ); - } - - @TestFactory - Iterable dynamicTestsFromIterable() { - return Arrays.asList( - dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))), - dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) - ); - } - - @TestFactory - Iterator dynamicTestsFromIterator() { - return Arrays.asList( - dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))), - dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) - ).iterator(); - } - - @TestFactory - DynamicTest[] dynamicTestsFromArray() { - return new DynamicTest[] { - dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))), - dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) - }; - } - - @TestFactory - Stream dynamicTestsFromStream() { - return Stream.of("racecar", "radar", "mom", "dad") - .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))); - } - - @TestFactory - Stream dynamicTestsFromIntStream() { - // Generates tests for the first 10 even integers. - return IntStream.iterate(0, n -> n + 2).limit(10) - .mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0))); - } - - @TestFactory - Stream generateRandomNumberOfTestsFromIterator() { - - // Generates random positive integers between 0 and 100 until - // a number evenly divisible by 7 is encountered. - Iterator inputGenerator = new Iterator() { - - Random random = new Random(); - // end::user_guide[] - { - // Use fixed seed to always produce the same number of tests for execution on the CI server - random = new Random(23); - } - // tag::user_guide[] - int current; - - @Override - public boolean hasNext() { - current = random.nextInt(100); - return current % 7 != 0; - } - - @Override - public Integer next() { - return current; - } - }; - - // Generates display names like: input:5, input:37, input:85, etc. - Function displayNameGenerator = (input) -> "input:" + input; - - // Executes tests based on the current input value. - ThrowingConsumer testExecutor = (input) -> assertTrue(input % 7 != 0); - - // Returns a stream of dynamic tests. - return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor); - } - - @TestFactory - Stream dynamicTestsFromStreamFactoryMethod() { - // Stream of palindromes to check - Stream inputStream = Stream.of("racecar", "radar", "mom", "dad"); - - // Generates display names like: racecar is a palindrome - Function displayNameGenerator = text -> text + " is a palindrome"; - - // Executes tests based on the current input value. - ThrowingConsumer testExecutor = text -> assertTrue(isPalindrome(text)); - - // Returns a stream of dynamic tests. - return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor); - } - - @TestFactory - Stream dynamicTestsFromStreamFactoryMethodWithNames() { - // Stream of palindromes to check - Stream> inputStream = Stream.of( - named("racecar is a palindrome", "racecar"), - named("radar is also a palindrome", "radar"), - named("mom also seems to be a palindrome", "mom"), - named("dad is yet another palindrome", "dad") - ); - - // Returns a stream of dynamic tests. - return DynamicTest.stream(inputStream, - text -> assertTrue(isPalindrome(text))); - } - - @TestFactory - Stream dynamicTestsWithContainers() { - return Stream.of("A", "B", "C") - .map(input -> dynamicContainer("Container " + input, Stream.of( - dynamicTest("not null", () -> assertNotNull(input)), - dynamicContainer("properties", Stream.of( - dynamicTest("length > 0", () -> assertTrue(input.length() > 0)), - dynamicTest("not empty", () -> assertFalse(input.isEmpty())) - )) - ))); - } - - @TestFactory - DynamicNode dynamicNodeSingleTest() { - return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop"))); - } - - @TestFactory - DynamicNode dynamicNodeSingleContainer() { - return dynamicContainer("palindromes", - Stream.of("racecar", "radar", "mom", "dad") - .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))) - )); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java deleted file mode 100644 index 2a898b33..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExampleTestCase.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -import example.util.Calculator; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; - -@TestMethodOrder(OrderAnnotation.class) -public class ExampleTestCase { - - private final Calculator calculator = new Calculator(); - - @Test - @Disabled("for demonstration purposes") - @Order(1) - void skippedTest() { - // skipped ... - } - - @Test - @Order(2) - void succeedingTest() { - assertEquals(42, calculator.multiply(6, 7)); - } - - @Test - @Order(3) - void abortedTest() { - assumeTrue("abc".contains("Z"), "abc does not contain Z"); - // aborted ... - } - - @Test - @Order(4) - void failingTest() { - // The following throws an ArithmeticException: "/ by zero" - calculator.divide(1, 0); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java deleted file mode 100644 index 024b07d2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalCustomConditionDemo.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide_external_custom_condition[] -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; - -class ExternalCustomConditionDemo { - - @Test - @EnabledIf("example.ExternalCondition#customCondition") - void enabled() { - // ... - } - -} - -class ExternalCondition { - - static boolean customCondition() { - return true; - } - -} -// end::user_guide_external_custom_condition[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java deleted file mode 100644 index 1d829936..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ExternalMethodSourceDemo.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::external_MethodSource_example[] -import java.util.stream.Stream; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -class ExternalMethodSourceDemo { - - @ParameterizedTest - @MethodSource("example.StringsProviders#tinyStrings") - void testWithExternalMethodSource(String tinyString) { - // test with tiny string - } -} - -class StringsProviders { - - static Stream tinyStrings() { - return Stream.of(".", "oo", "OOO"); - } -} -// end::external_MethodSource_example[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java deleted file mode 100644 index 1e7c78e6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/Fast.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.Tag; - -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Tag("fast") -public @interface Fast { -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java deleted file mode 100644 index af31b49d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/FastTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Tag("fast") -@Test -public @interface FastTest { -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java deleted file mode 100644 index 8fa21982..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/HamcrestAssertionsDemo.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import example.util.Calculator; - -import org.junit.jupiter.api.Test; - -class HamcrestAssertionsDemo { - - private final Calculator calculator = new Calculator(); - - @Test - void assertWithHamcrestMatcher() { - assertThat(calculator.subtract(4, 1), is(equalTo(3))); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java deleted file mode 100644 index 96fca01c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/IgnoredTestsDemo.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import org.junit.Ignore; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport; - -// @ExtendWith(IgnoreCondition.class) -@EnableJUnit4MigrationSupport -class IgnoredTestsDemo { - - @Ignore - @Test - void testWillBeIgnored() { - } - - @Test - void testWillBeExecuted() { - } -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java deleted file mode 100644 index d229e639..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnit4Tests.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import org.junit.Test; - -public class JUnit4Tests { - - @Test - public void standardJUnit4Test() { - // perform assertions - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java deleted file mode 100644 index 0bf0aeb0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformClassDemo.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; - -//end::user_guide[] -@SuppressWarnings("deprecation") -//tag::user_guide[] -@RunWith(org.junit.platform.runner.JUnitPlatform.class) -public class JUnitPlatformClassDemo { - - @Test - void succeedingTest() { - /* no-op */ - } - - // end::user_guide[] - @extensions.ExpectToFail - // tag::user_guide[] - @Test - void failingTest() { - fail("Failing for failing's sake."); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java deleted file mode 100644 index b563204f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -//tag::user_guide[] -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.SuiteDisplayName; -import org.junit.runner.RunWith; - -@RunWith(org.junit.platform.runner.JUnitPlatform.class) -@SuiteDisplayName("JUnit Platform Suite Demo") -@SelectPackages("example") -//end::user_guide[] -@SuppressWarnings("deprecation") -@org.junit.platform.suite.api.ExcludeTags("exclude") -//tag::user_guide[] -public class JUnitPlatformSuiteDemo { -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java deleted file mode 100644 index 45b35dbc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class MethodSourceParameterResolutionDemo { - - // @formatter:off - // tag::parameter_resolution_MethodSource_example[] - @RegisterExtension - static final IntegerResolver integerResolver = new IntegerResolver(); - - @ParameterizedTest - @MethodSource("factoryMethodWithArguments") - void testWithFactoryMethodWithArguments(String argument) { - assertTrue(argument.startsWith("2")); - } - - static Stream factoryMethodWithArguments(int quantity) { - return Stream.of( - arguments(quantity + " apples"), - arguments(quantity + " lemons") - ); - } - - static class IntegerResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) { - - return parameterContext.getParameter().getType() == int.class; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) { - - return 2; - } - - } - // end::parameter_resolution_MethodSource_example[] - // @formatter:on - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java deleted file mode 100644 index 77632abc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.junit.jupiter.api.Assertions.assertEquals; - -import example.util.Calculator; - -import org.junit.jupiter.api.Test; - -class MyFirstJUnitJupiterTests { - - private final Calculator calculator = new Calculator(); - - @Test - void addition() { - assertEquals(2, calculator.add(1, 1)); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java deleted file mode 100644 index b5e987da..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestClassOrder; - -@TestClassOrder(ClassOrderer.OrderAnnotation.class) -class OrderedNestedTestClassesDemo { - - @Nested - @Order(1) - class PrimaryTests { - - @Test - void test1() { - } - } - - @Nested - @Order(2) - class SecondaryTests { - - @Test - void test2() { - } - } -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java deleted file mode 100644 index 2ee01d33..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/OrderedTestsDemo.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; - -@TestMethodOrder(OrderAnnotation.class) -class OrderedTestsDemo { - - @Test - @Order(1) - void nullValues() { - // perform assertions against null values - } - - @Test - @Order(2) - void emptyValues() { - // perform assertions against empty values - } - - @Test - @Order(3) - void validValues() { - // perform assertions against valid values - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java deleted file mode 100644 index 3afd8872..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/ParameterizedTestDemo.java +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Named.named; -import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; -import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; - -import java.io.File; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.time.LocalDate; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.List; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import example.domain.Person; -import example.domain.Person.Gender; -import example.util.StringUtils; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.aggregator.AggregateWith; -import org.junit.jupiter.params.aggregator.ArgumentsAccessor; -import org.junit.jupiter.params.aggregator.ArgumentsAggregator; -import org.junit.jupiter.params.converter.ConvertWith; -import org.junit.jupiter.params.converter.JavaTimeConversionPattern; -import org.junit.jupiter.params.converter.SimpleArgumentConverter; -import org.junit.jupiter.params.converter.TypedArgumentConverter; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.jupiter.params.provider.CsvFileSource; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.EmptySource; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; - -class ParameterizedTestDemo { - - // tag::first_example[] - @ParameterizedTest - @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) - void palindromes(String candidate) { - assertTrue(StringUtils.isPalindrome(candidate)); - } - // end::first_example[] - - // tag::ValueSource_example[] - @ParameterizedTest - @ValueSource(ints = { 1, 2, 3 }) - void testWithValueSource(int argument) { - assertTrue(argument > 0 && argument < 4); - } - // end::ValueSource_example[] - - @Nested - class NullAndEmptySource_1 { - - // tag::NullAndEmptySource_example1[] - @ParameterizedTest - @NullSource - @EmptySource - @ValueSource(strings = { " ", " ", "\t", "\n" }) - void nullEmptyAndBlankStrings(String text) { - assertTrue(text == null || text.trim().isEmpty()); - } - // end::NullAndEmptySource_example1[] - } - - @Nested - class NullAndEmptySource_2 { - - // tag::NullAndEmptySource_example2[] - @ParameterizedTest - @NullAndEmptySource - @ValueSource(strings = { " ", " ", "\t", "\n" }) - void nullEmptyAndBlankStrings(String text) { - assertTrue(text == null || text.trim().isEmpty()); - } - // end::NullAndEmptySource_example2[] - } - - // tag::EnumSource_example[] - @ParameterizedTest - @EnumSource(ChronoUnit.class) - void testWithEnumSource(TemporalUnit unit) { - assertNotNull(unit); - } - // end::EnumSource_example[] - - // tag::EnumSource_example_autodetection[] - @ParameterizedTest - @EnumSource - void testWithEnumSourceWithAutoDetection(ChronoUnit unit) { - assertNotNull(unit); - } - // end::EnumSource_example_autodetection[] - - // tag::EnumSource_include_example[] - @ParameterizedTest - @EnumSource(names = { "DAYS", "HOURS" }) - void testWithEnumSourceInclude(ChronoUnit unit) { - assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit)); - } - // end::EnumSource_include_example[] - - // tag::EnumSource_exclude_example[] - @ParameterizedTest - @EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" }) - void testWithEnumSourceExclude(ChronoUnit unit) { - assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit)); - } - // end::EnumSource_exclude_example[] - - // tag::EnumSource_regex_example[] - @ParameterizedTest - @EnumSource(mode = MATCH_ALL, names = "^.*DAYS$") - void testWithEnumSourceRegex(ChronoUnit unit) { - assertTrue(unit.name().endsWith("DAYS")); - } - // end::EnumSource_regex_example[] - - // tag::simple_MethodSource_example[] - @ParameterizedTest - @MethodSource("stringProvider") - void testWithExplicitLocalMethodSource(String argument) { - assertNotNull(argument); - } - - static Stream stringProvider() { - return Stream.of("apple", "banana"); - } - // end::simple_MethodSource_example[] - - // tag::simple_MethodSource_without_value_example[] - @ParameterizedTest - @MethodSource - void testWithDefaultLocalMethodSource(String argument) { - assertNotNull(argument); - } - - static Stream testWithDefaultLocalMethodSource() { - return Stream.of("apple", "banana"); - } - // end::simple_MethodSource_without_value_example[] - - // tag::primitive_MethodSource_example[] - @ParameterizedTest - @MethodSource("range") - void testWithRangeMethodSource(int argument) { - assertNotEquals(9, argument); - } - - static IntStream range() { - return IntStream.range(0, 20).skip(10); - } - // end::primitive_MethodSource_example[] - - // @formatter:off - // tag::multi_arg_MethodSource_example[] - @ParameterizedTest - @MethodSource("stringIntAndListProvider") - void testWithMultiArgMethodSource(String str, int num, List list) { - assertEquals(5, str.length()); - assertTrue(num >=1 && num <=2); - assertEquals(2, list.size()); - } - - static Stream stringIntAndListProvider() { - return Stream.of( - arguments("apple", 1, Arrays.asList("a", "b")), - arguments("lemon", 2, Arrays.asList("x", "y")) - ); - } - // end::multi_arg_MethodSource_example[] - // @formatter:on - - // @formatter:off - // tag::CsvSource_example[] - @ParameterizedTest - @CsvSource({ - "apple, 1", - "banana, 2", - "'lemon, lime', 0xF1", - "strawberry, 700_000" - }) - void testWithCsvSource(String fruit, int rank) { - assertNotNull(fruit); - assertNotEquals(0, rank); - } - // end::CsvSource_example[] - // @formatter:on - - // tag::CsvFileSource_example[] - @ParameterizedTest - @CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1) - void testWithCsvFileSourceFromClasspath(String country, int reference) { - assertNotNull(country); - assertNotEquals(0, reference); - } - - @ParameterizedTest - @CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1) - void testWithCsvFileSourceFromFile(String country, int reference) { - assertNotNull(country); - assertNotEquals(0, reference); - } - - @ParameterizedTest(name = "[{index}] {arguments}") - @CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true) - void testWithCsvFileSourceAndHeaders(String country, int reference) { - assertNotNull(country); - assertNotEquals(0, reference); - } - // end::CsvFileSource_example[] - - // tag::ArgumentsSource_example[] - @ParameterizedTest - @ArgumentsSource(MyArgumentsProvider.class) - void testWithArgumentsSource(String argument) { - assertNotNull(argument); - } - - // end::ArgumentsSource_example[] - static - // tag::ArgumentsProvider_example[] - public class MyArgumentsProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of("apple", "banana").map(Arguments::of); - } - } - // end::ArgumentsProvider_example[] - - // tag::ParameterResolver_example[] - @BeforeEach - void beforeEach(TestInfo testInfo) { - // ... - } - - @ParameterizedTest - @ValueSource(strings = "apple") - void testWithRegularParameterResolver(String argument, TestReporter testReporter) { - testReporter.publishEntry("argument", argument); - } - - @AfterEach - void afterEach(TestInfo testInfo) { - // ... - } - // end::ParameterResolver_example[] - - // tag::implicit_conversion_example[] - @ParameterizedTest - @ValueSource(strings = "SECONDS") - void testWithImplicitArgumentConversion(ChronoUnit argument) { - assertNotNull(argument.name()); - } - // end::implicit_conversion_example[] - - // tag::implicit_fallback_conversion_example[] - @ParameterizedTest - @ValueSource(strings = "42 Cats") - void testWithImplicitFallbackArgumentConversion(Book book) { - assertEquals("42 Cats", book.getTitle()); - } - - // end::implicit_fallback_conversion_example[] - static - // tag::implicit_fallback_conversion_example_Book[] - public class Book { - - private final String title; - - private Book(String title) { - this.title = title; - } - - public static Book fromTitle(String title) { - return new Book(title); - } - - public String getTitle() { - return this.title; - } - } - // end::implicit_fallback_conversion_example_Book[] - - // @formatter:off - // tag::explicit_conversion_example[] - @ParameterizedTest - @EnumSource(ChronoUnit.class) - void testWithExplicitArgumentConversion( - @ConvertWith(ToStringArgumentConverter.class) String argument) { - - assertNotNull(ChronoUnit.valueOf(argument)); - } - - // end::explicit_conversion_example[] - static - // tag::explicit_conversion_example_ToStringArgumentConverter[] - public class ToStringArgumentConverter extends SimpleArgumentConverter { - - @Override - protected Object convert(Object source, Class targetType) { - assertEquals(String.class, targetType, "Can only convert to String"); - if (source instanceof Enum) { - return ((Enum) source).name(); - } - return String.valueOf(source); - } - } - // end::explicit_conversion_example_ToStringArgumentConverter[] - - static - // tag::explicit_conversion_example_TypedArgumentConverter[] - public class ToLengthArgumentConverter extends TypedArgumentConverter { - - protected ToLengthArgumentConverter() { - super(String.class, Integer.class); - } - - @Override - protected Integer convert(String source) { - return (source != null ? source.length() : 0); - } - - } - // end::explicit_conversion_example_TypedArgumentConverter[] - - // tag::explicit_java_time_converter[] - @ParameterizedTest - @ValueSource(strings = { "01.01.2017", "31.12.2017" }) - void testWithExplicitJavaTimeConverter( - @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) { - - assertEquals(2017, argument.getYear()); - } - // end::explicit_java_time_converter[] - // @formatter:on - - // @formatter:off - // tag::ArgumentsAccessor_example[] - @ParameterizedTest - @CsvSource({ - "Jane, Doe, F, 1990-05-20", - "John, Doe, M, 1990-10-22" - }) - void testWithArgumentsAccessor(ArgumentsAccessor arguments) { - Person person = new Person(arguments.getString(0), - arguments.getString(1), - arguments.get(2, Gender.class), - arguments.get(3, LocalDate.class)); - - if (person.getFirstName().equals("Jane")) { - assertEquals(Gender.F, person.getGender()); - } - else { - assertEquals(Gender.M, person.getGender()); - } - assertEquals("Doe", person.getLastName()); - assertEquals(1990, person.getDateOfBirth().getYear()); - } - // end::ArgumentsAccessor_example[] - // @formatter:on - - // @formatter:off - // tag::ArgumentsAggregator_example[] - @ParameterizedTest - @CsvSource({ - "Jane, Doe, F, 1990-05-20", - "John, Doe, M, 1990-10-22" - }) - void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) { - // perform assertions against person - } - - // end::ArgumentsAggregator_example[] - static - // tag::ArgumentsAggregator_example_PersonAggregator[] - public class PersonAggregator implements ArgumentsAggregator { - @Override - public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { - return new Person(arguments.getString(0), - arguments.getString(1), - arguments.get(2, Gender.class), - arguments.get(3, LocalDate.class)); - } - } - // end::ArgumentsAggregator_example_PersonAggregator[] - // @formatter:on - - // @formatter:off - // tag::ArgumentsAggregator_with_custom_annotation_example[] - @ParameterizedTest - @CsvSource({ - "Jane, Doe, F, 1990-05-20", - "John, Doe, M, 1990-10-22" - }) - void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { - // perform assertions against person - } - // end::ArgumentsAggregator_with_custom_annotation_example[] - - // tag::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @AggregateWith(PersonAggregator.class) - public @interface CsvToPerson { - } - // end::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] - // @formatter:on - - // tag::custom_display_names[] - @DisplayName("Display name of container") - @ParameterizedTest(name = "{index} ==> the rank of ''{0}'' is {1}") - @CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" }) - void testWithCustomDisplayNames(String fruit, int rank) { - } - // end::custom_display_names[] - - // @formatter:off - // tag::named_arguments[] - @DisplayName("A parameterized test with named arguments") - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("namedArguments") - void testWithNamedArguments(File file) { - } - - static Stream namedArguments() { - return Stream.of( - arguments(named("An important file", new File("path1"))), - arguments(named("Another file", new File("path2"))) - ); - } - // end::named_arguments[] - // @formatter:on -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java deleted file mode 100644 index a10b7cec..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/PollingTimeoutDemo.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; - -class PollingTimeoutDemo { - - // tag::user_guide[] - @Test - @Timeout(5) // Poll at most 5 seconds - void pollUntil() throws InterruptedException { - while (asynchronousResultNotAvailable()) { - Thread.sleep(250); // custom poll interval - } - // Obtain the asynchronous result and perform assertions - } - // end::user_guide[] - - private boolean asynchronousResultNotAvailable() { - return false; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java deleted file mode 100644 index ccf46845..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/RepeatedTestsDemo.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.logging.Logger; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.RepetitionInfo; -import org.junit.jupiter.api.TestInfo; - -class RepeatedTestsDemo { - - private Logger logger = // ... - // end::user_guide[] - Logger.getLogger(RepeatedTestsDemo.class.getName()); - // tag::user_guide[] - - @BeforeEach - void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { - int currentRepetition = repetitionInfo.getCurrentRepetition(); - int totalRepetitions = repetitionInfo.getTotalRepetitions(); - String methodName = testInfo.getTestMethod().get().getName(); - logger.info(String.format("About to execute repetition %d of %d for %s", // - currentRepetition, totalRepetitions, methodName)); - } - - @RepeatedTest(10) - void repeatedTest() { - // ... - } - - @RepeatedTest(5) - void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) { - assertEquals(5, repetitionInfo.getTotalRepetitions()); - } - - @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") - @DisplayName("Repeat!") - void customDisplayName(TestInfo testInfo) { - assertEquals("Repeat! 1/1", testInfo.getDisplayName()); - } - - @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) - @DisplayName("Details...") - void customDisplayNameWithLongPattern(TestInfo testInfo) { - assertEquals("Details... :: repetition 1 of 1", testInfo.getDisplayName()); - } - - @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}") - void repeatedTestInGerman() { - // ... - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java deleted file mode 100644 index be3d12b3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SharedResourcesDemo.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; -import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; -import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; - -import java.util.Properties; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ResourceLock; - -// tag::user_guide[] -@Execution(CONCURRENT) -class SharedResourcesDemo { - - private Properties backup; - - @BeforeEach - void backup() { - backup = new Properties(); - backup.putAll(System.getProperties()); - } - - @AfterEach - void restore() { - System.setProperties(backup); - } - - @Test - @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ) - void customPropertyIsNotSetByDefault() { - assertNull(System.getProperty("my.prop")); - } - - @Test - @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) - void canSetCustomPropertyToApple() { - System.setProperty("my.prop", "apple"); - assertEquals("apple", System.getProperty("my.prop")); - } - - @Test - @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) - void canSetCustomPropertyToBanana() { - System.setProperty("my.prop", "banana"); - assertEquals("banana", System.getProperty("my.prop")); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java deleted file mode 100644 index f3c711e7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SlowTests.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; - -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; - -@Disabled -class SlowTests { - - @Execution(SAME_THREAD) - @Test - void a() { - foo(); - } - - @Test - void b() { - foo(); - } - - @Test - void c() { - foo(); - } - - @Test - void d() { - foo(); - } - - @Test - void e() { - foo(); - } - - @Test - void f() { - foo(); - } - - @Test - void g() { - foo(); - } - - @Test - void h() { - foo(); - } - - @Test - void i() { - foo(); - } - - @Test - void j() { - foo(); - } - - @Test - void k() { - foo(); - } - - @Test - void l() { - foo(); - } - - @Test - void m() { - foo(); - } - - @Test - void n() { - foo(); - } - - @Test - void o() { - foo(); - } - - @Test - void p() { - foo(); - } - - @Execution(SAME_THREAD) - @Test - void q() { - foo(); - } - - @Test - void r() { - foo(); - } - - @Test - void s() { - foo(); - } - - private void foo() { - IntStream.range(1, 100_000_000).mapToDouble(i -> Math.pow(i, i)).map(Math::sqrt).max(); - } -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java deleted file mode 100644 index 4a6a660e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/StandardTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -class StandardTests { - - @BeforeAll - static void initAll() { - } - - @BeforeEach - void init() { - } - - @Test - void succeedingTest() { - } - - // end::user_guide[] - @extensions.ExpectToFail - // tag::user_guide[] - @Test - void failingTest() { - fail("a failing test"); - } - - @Test - @Disabled("for demonstration purposes") - void skippedTest() { - // not executed - } - - @Test - void abortedTest() { - assumeTrue("abc".contains("Z")); - fail("test should have been aborted"); - } - - @AfterEach - void tearDown() { - } - - @AfterAll - static void tearDownAll() { - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java deleted file mode 100644 index 617806b3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/SuiteDemo.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -//tag::user_guide[] -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.api.SuiteDisplayName; - -@Suite -@SuiteDisplayName("JUnit Platform Suite Demo") -@SelectPackages("example") -@IncludeClassNamePatterns(".*Tests") -//end::user_guide[] -@org.junit.platform.suite.api.ExcludeTags("exclude") -//tag::user_guide[] -class SuiteDemo { -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java deleted file mode 100644 index ebe9594b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TaggingDemo.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -@Tag("fast") -@Tag("model") -class TaggingDemo { - - @Test - @Tag("taxes") - void testingTaxCalculation() { - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java deleted file mode 100644 index 84c05b4b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirCleanupModeDemo.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; - -import java.nio.file.Path; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -// tag::user_guide[] -class TempDirCleanupModeDemo { - - @Test - void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { - // perform test - } -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java deleted file mode 100644 index 70ce5822..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TempDirectoryDemo.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import example.util.ListWriter; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -class TempDirectoryDemo { - - // tag::user_guide_parameter_injection[] - @Test - void writeItemsToFile(@TempDir Path tempDir) throws IOException { - Path file = tempDir.resolve("test.txt"); - - new ListWriter(file).write("a", "b", "c"); - - assertEquals(singletonList("a,b,c"), Files.readAllLines(file)); - } - // end::user_guide_parameter_injection[] - - // tag::user_guide_multiple_directories[] - @Test - void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException { - Path sourceFile = source.resolve("test.txt"); - new ListWriter(sourceFile).write("a", "b", "c"); - - Path targetFile = Files.copy(sourceFile, target.resolve("test.txt")); - - assertNotEquals(sourceFile, targetFile); - assertEquals(singletonList("a,b,c"), Files.readAllLines(targetFile)); - } - // end::user_guide_multiple_directories[] - - static - // tag::user_guide_field_injection[] - class SharedTempDirectoryDemo { - - @TempDir - static Path sharedTempDir; - - @Test - void writeItemsToFile() throws IOException { - Path file = sharedTempDir.resolve("test.txt"); - - new ListWriter(file).write("a", "b", "c"); - - assertEquals(singletonList("a,b,c"), Files.readAllLines(file)); - } - - @Test - void anotherTestThatUsesTheSameTempDir() { - // use sharedTempDir - } - - } - // end::user_guide_field_injection[] - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java deleted file mode 100644 index ac8f044e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestInfoDemo.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; - -@DisplayName("TestInfo Demo") -class TestInfoDemo { - - TestInfoDemo(TestInfo testInfo) { - assertEquals("TestInfo Demo", testInfo.getDisplayName()); - } - - @BeforeEach - void init(TestInfo testInfo) { - String displayName = testInfo.getDisplayName(); - assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); - } - - @Test - @DisplayName("TEST 1") - @Tag("my-tag") - void test1(TestInfo testInfo) { - assertEquals("TEST 1", testInfo.getDisplayName()); - assertTrue(testInfo.getTags().contains("my-tag")); - } - - @Test - void test2() { - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java deleted file mode 100644 index dbd78d94..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestReporterDemo.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; - -@Execution(ExecutionMode.SAME_THREAD) -// tag::user_guide[] -class TestReporterDemo { - - @Test - void reportSingleValue(TestReporter testReporter) { - testReporter.publishEntry("a status message"); - } - - @Test - void reportKeyValuePair(TestReporter testReporter) { - testReporter.publishEntry("a key", "a value"); - } - - @Test - void reportMultipleKeyValuePairs(TestReporter testReporter) { - Map values = new HashMap<>(); - values.put("user name", "dk38"); - values.put("award year", "1974"); - - testReporter.publishEntry(values); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java deleted file mode 100644 index 5f0c56a2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestTemplateDemo.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; - -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; - -class TestTemplateDemo { - - // tag::user_guide[] - final List fruits = Arrays.asList("apple", "banana", "lemon"); - - @TestTemplate - @ExtendWith(MyTestTemplateInvocationContextProvider.class) - void testTemplate(String fruit) { - assertTrue(fruits.contains(fruit)); - } - - // end::user_guide[] - static - // @formatter:off - // tag::user_guide[] - public class MyTestTemplateInvocationContextProvider - implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts( - ExtensionContext context) { - - return Stream.of(invocationContext("apple"), invocationContext("banana")); - } - - private TestTemplateInvocationContext invocationContext(String parameter) { - return new TestTemplateInvocationContext() { - @Override - public String getDisplayName(int invocationIndex) { - return parameter; - } - - @Override - public List getAdditionalExtensions() { - return Collections.singletonList(new ParameterResolver() { - @Override - public boolean supportsParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) { - return parameterContext.getParameter().getType().equals(String.class); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) { - return parameter; - } - }); - } - }; - } - } - // end::user_guide[] - // @formatter:on - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java deleted file mode 100644 index ab7b8339..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TestingAStackDemo.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::user_guide[] -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.EmptyStackException; -import java.util.Stack; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -@DisplayName("A stack") -class TestingAStackDemo { - - Stack stack; - - @Test - @DisplayName("is instantiated with new Stack()") - void isInstantiatedWithNew() { - new Stack<>(); - } - - @Nested - @DisplayName("when new") - class WhenNew { - - @BeforeEach - void createNewStack() { - stack = new Stack<>(); - } - - @Test - @DisplayName("is empty") - void isEmpty() { - assertTrue(stack.isEmpty()); - } - - @Test - @DisplayName("throws EmptyStackException when popped") - void throwsExceptionWhenPopped() { - assertThrows(EmptyStackException.class, stack::pop); - } - - @Test - @DisplayName("throws EmptyStackException when peeked") - void throwsExceptionWhenPeeked() { - assertThrows(EmptyStackException.class, stack::peek); - } - - @Nested - @DisplayName("after pushing an element") - class AfterPushing { - - String anElement = "an element"; - - @BeforeEach - void pushAnElement() { - stack.push(anElement); - } - - @Test - @DisplayName("it is no longer empty") - void isNotEmpty() { - assertFalse(stack.isEmpty()); - } - - @Test - @DisplayName("returns the element when popped and is empty") - void returnElementWhenPopped() { - assertEquals(anElement, stack.pop()); - assertTrue(stack.isEmpty()); - } - - @Test - @DisplayName("returns the element when peeked but remains not empty") - void returnElementWhenPeeked() { - assertEquals(anElement, stack.peek()); - assertFalse(stack.isEmpty()); - } - } - } -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java deleted file mode 100644 index 8700f23e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/TimeoutDemo.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.Timeout.ThreadMode; - -// tag::user_guide[] -class TimeoutDemo { - - @BeforeEach - @Timeout(5) - void setUp() { - // fails if execution time exceeds 5 seconds - } - - @Test - @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) - void failsIfExecutionTimeExceeds500Milliseconds() { - // fails if execution time exceeds 500 milliseconds - } - - @Test - @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD) - void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() { - // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java deleted file mode 100644 index 403e9084..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/UsingTheLauncherDemo.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -// tag::imports[] -import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; - -import java.io.PrintWriter; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.LauncherSession; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.core.LauncherConfig; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; -import org.junit.platform.launcher.core.LauncherFactory; -import org.junit.platform.launcher.listeners.SummaryGeneratingListener; -import org.junit.platform.launcher.listeners.TestExecutionSummary; -import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; -// end::imports[] - -/** - * @since 5.0 - */ -class UsingTheLauncherDemo { - - @org.junit.jupiter.api.Test - @SuppressWarnings("unused") - void discovery() { - // @formatter:off - // tag::discovery[] - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() - .selectors( - selectPackage("com.example.mytests"), - selectClass(MyTestClass.class) - ) - .filters( - includeClassNamePatterns(".*Tests") - ) - .build(); - - try (LauncherSession session = LauncherFactory.openSession()) { - TestPlan testPlan = session.getLauncher().discover(request); - - // ... discover additional test plans or execute tests - } - // end::discovery[] - // @formatter:on - } - - @org.junit.jupiter.api.Tag("exclude") - @org.junit.jupiter.api.Test - @SuppressWarnings("unused") - void execution() { - // @formatter:off - // tag::execution[] - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() - .selectors( - selectPackage("com.example.mytests"), - selectClass(MyTestClass.class) - ) - .filters( - includeClassNamePatterns(".*Tests") - ) - .build(); - - SummaryGeneratingListener listener = new SummaryGeneratingListener(); - - try (LauncherSession session = LauncherFactory.openSession()) { - Launcher launcher = session.getLauncher(); - // Register a listener of your choice - launcher.registerTestExecutionListeners(listener); - // Discover tests and build a test plan - TestPlan testPlan = launcher.discover(request); - // Execute test plan - launcher.execute(testPlan); - // Alternatively, execute the request directly - launcher.execute(request); - } - - TestExecutionSummary summary = listener.getSummary(); - // Do something with the summary... - - // end::execution[] - // @formatter:on - } - - @org.junit.jupiter.api.Test - void launcherConfig() { - Path reportsDir = Paths.get("target", "xml-reports"); - PrintWriter out = new PrintWriter(System.out); - // @formatter:off - // tag::launcherConfig[] - LauncherConfig launcherConfig = LauncherConfig.builder() - .enableTestEngineAutoRegistration(false) - .enableLauncherSessionListenerAutoRegistration(false) - .enableLauncherDiscoveryListenerAutoRegistration(false) - .enablePostDiscoveryFilterAutoRegistration(false) - .enableTestExecutionListenerAutoRegistration(false) - .addTestEngines(new CustomTestEngine()) - .addLauncherSessionListeners(new CustomLauncherSessionListener()) - .addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener()) - .addPostDiscoveryFilters(new CustomPostDiscoveryFilter()) - .addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out)) - .addTestExecutionListeners(new CustomTestExecutionListener()) - .build(); - - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() - .selectors(selectPackage("com.example.mytests")) - .build(); - - try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) { - session.getLauncher().execute(request); - } - // end::launcherConfig[] - // @formatter:on - } - -} - -class MyTestClass { -} - -class CustomTestExecutionListener implements TestExecutionListener { -} - -class CustomLauncherSessionListener implements LauncherSessionListener { -} - -class CustomLauncherDiscoveryListener implements LauncherDiscoveryListener { -} - -class CustomPostDiscoveryFilter implements PostDiscoveryFilter { - @Override - public FilterResult apply(TestDescriptor object) { - return FilterResult.included("includes everything"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java deleted file mode 100644 index a6eef448..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.callbacks; - -// tag::user_guide[] - -import static example.callbacks.Logger.afterAllMethod; -import static example.callbacks.Logger.afterEachMethod; -import static example.callbacks.Logger.beforeAllMethod; -import static example.callbacks.Logger.beforeEachMethod; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; - -/** - * Abstract base class for tests that use the database. - */ -abstract class AbstractDatabaseTests { - - @BeforeAll - static void createDatabase() { - beforeAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".createDatabase()"); - } - - @BeforeEach - void connectToDatabase() { - beforeEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".connectToDatabase()"); - } - - @AfterEach - void disconnectFromDatabase() { - afterEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".disconnectFromDatabase()"); - } - - @AfterAll - static void destroyDatabase() { - afterAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".destroyDatabase()"); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java deleted file mode 100644 index 03da5ff8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.callbacks; - -// tag::user_guide[] - -import static example.callbacks.Logger.afterEachMethod; -import static example.callbacks.Logger.beforeEachMethod; -import static example.callbacks.Logger.testMethod; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * Example of "broken" lifecycle method configuration. - * - *

Test data is inserted before the database connection has been opened. - * - *

Database connection is closed before deleting test data. - */ -@ExtendWith({ Extension1.class, Extension2.class }) -class BrokenLifecycleMethodConfigDemo { - - @BeforeEach - void connectToDatabase() { - beforeEachMethod(getClass().getSimpleName() + ".connectToDatabase()"); - } - - @BeforeEach - void insertTestDataIntoDatabase() { - beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()"); - } - - @Test - void testDatabaseFunctionality() { - testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()"); - } - - @AfterEach - void deleteTestDataFromDatabase() { - afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()"); - } - - @AfterEach - void disconnectFromDatabase() { - afterEachMethod(getClass().getSimpleName() + ".disconnectFromDatabase()"); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java deleted file mode 100644 index 792c7788..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.callbacks; - -// tag::user_guide[] - -import static example.callbacks.Logger.afterEachMethod; -import static example.callbacks.Logger.beforeAllMethod; -import static example.callbacks.Logger.beforeEachMethod; -import static example.callbacks.Logger.testMethod; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * Extension of {@link AbstractDatabaseTests} that inserts test data - * into the database (after the database connection has been opened) - * and deletes test data (before the database connection is closed). - */ -@ExtendWith({ Extension1.class, Extension2.class }) -class DatabaseTestsDemo extends AbstractDatabaseTests { - - @BeforeAll - static void beforeAll() { - beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".beforeAll()"); - } - - @BeforeEach - void insertTestDataIntoDatabase() { - beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()"); - } - - @Test - void testDatabaseFunctionality() { - testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()"); - } - - @AfterEach - void deleteTestDataFromDatabase() { - afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()"); - } - - @AfterAll - static void afterAll() { - beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()"); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java deleted file mode 100644 index f0f5e697..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension1.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.callbacks; - -// tag::user_guide[] - -import static example.callbacks.Logger.afterEachCallback; -import static example.callbacks.Logger.beforeEachCallback; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -public class Extension1 implements BeforeEachCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - beforeEachCallback(this); - } - - @Override - public void afterEach(ExtensionContext context) { - afterEachCallback(this); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java deleted file mode 100644 index a7cc878f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Extension2.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.callbacks; - -// tag::user_guide[] - -import static example.callbacks.Logger.afterEachCallback; -import static example.callbacks.Logger.beforeEachCallback; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -public class Extension2 implements BeforeEachCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - beforeEachCallback(this); - } - - @Override - public void afterEach(ExtensionContext context) { - afterEachCallback(this); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java deleted file mode 100644 index ab227125..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/callbacks/Logger.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.callbacks; - -import java.util.function.Supplier; - -import org.junit.jupiter.api.extension.Extension; - -class Logger { - - static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName()); - - static void beforeAllMethod(String text) { - log(() -> "@BeforeAll " + text); - } - - static void beforeEachCallback(Extension extension) { - log(() -> " " + extension.getClass().getSimpleName() + ".beforeEach()"); - } - - static void beforeEachMethod(String text) { - log(() -> " @BeforeEach " + text); - } - - static void testMethod(String text) { - log(() -> " @Test " + text); - } - - static void afterEachMethod(String text) { - log(() -> " @AfterEach " + text); - } - - static void afterEachCallback(Extension extension) { - log(() -> " " + extension.getClass().getSimpleName() + ".afterEach()"); - } - - static void afterAllMethod(String text) { - log(() -> "@AfterAll " + text); - } - - private static void log(Supplier supplier) { - // System.err.println(supplier.get()); - logger.info(supplier); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java deleted file mode 100644 index f72d80f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/ComparableContract.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.defaultmethods; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -// tag::user_guide[] -public interface ComparableContract> extends Testable { - - T createSmallerValue(); - - @Test - default void returnsZeroWhenComparedToItself() { - T value = createValue(); - assertEquals(0, value.compareTo(value)); - } - - @Test - default void returnsPositiveNumberWhenComparedToSmallerValue() { - T value = createValue(); - T smallerValue = createSmallerValue(); - assertTrue(value.compareTo(smallerValue) > 0); - } - - @Test - default void returnsNegativeNumberWhenComparedToLargerValue() { - T value = createValue(); - T smallerValue = createSmallerValue(); - assertTrue(smallerValue.compareTo(value) < 0); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java deleted file mode 100644 index 36e30258..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/EqualsContract.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.defaultmethods; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import org.junit.jupiter.api.Test; - -// tag::user_guide[] -public interface EqualsContract extends Testable { - - T createNotEqualValue(); - - @Test - default void valueEqualsItself() { - T value = createValue(); - assertEquals(value, value); - } - - @Test - default void valueDoesNotEqualNull() { - T value = createValue(); - assertFalse(value.equals(null)); - } - - @Test - default void valueDoesNotEqualDifferentValue() { - T value = createValue(); - T differentValue = createNotEqualValue(); - assertNotEquals(value, differentValue); - assertNotEquals(differentValue, value); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java deleted file mode 100644 index 1449f28f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/StringTests.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.defaultmethods; - -// tag::user_guide[] -class StringTests implements ComparableContract, EqualsContract { - - @Override - public String createValue() { - return "banana"; - } - - @Override - public String createSmallerValue() { - return "apple"; // 'a' < 'b' in "banana" - } - - @Override - public String createNotEqualValue() { - return "cherry"; - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java deleted file mode 100644 index 84bf397b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/defaultmethods/Testable.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.defaultmethods; - -// tag::user_guide[] -public interface Testable { - - T createValue(); - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java deleted file mode 100644 index f2010d1b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.exception; - -import java.io.IOException; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; - -// @formatter:off -// tag::user_guide[] -public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler { - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - - if (throwable instanceof IOException) { - return; - } - throw throwable; - } -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java deleted file mode 100644 index ca706586..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.exception; - -import java.io.IOException; - -import extensions.ExpectToFail; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -@ExtendWith(IgnoreIOExceptionExtension.class) -class IgnoreIOExceptionTests { - - @Test - void shouldSucceed() throws IOException { - throw new IOException("any"); - } - - @Test - @ExpectToFail - void shouldFail() { - throw new RuntimeException("any"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java deleted file mode 100644 index 3db997fe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.exception; - -import static example.exception.MultipleHandlersTestCase.ThirdExecutedHandler; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; - -// @formatter:off -// tag::user_guide[] -// Register handlers for @Test, @BeforeEach, @AfterEach as well as @BeforeAll and @AfterAll -@ExtendWith(ThirdExecutedHandler.class) -class MultipleHandlersTestCase { - - // Register handlers for @Test, @BeforeEach, @AfterEach only - @ExtendWith(SecondExecutedHandler.class) - @ExtendWith(FirstExecutedHandler.class) - @Test - void testMethod() { - } - - // end::user_guide[] - - static class FirstExecutedHandler implements TestExecutionExceptionHandler { - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - throw ex; - } - } - - static class SecondExecutedHandler implements LifecycleMethodExecutionExceptionHandler { - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - throw ex; - } - } - - static class ThirdExecutedHandler implements LifecycleMethodExecutionExceptionHandler { - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - throw ex; - } - } - // tag::user_guide[] -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java deleted file mode 100644 index ed4c7697..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.exception; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; - -// @formatter:off -// tag::user_guide[] -class RecordStateOnErrorExtension implements LifecycleMethodExecutionExceptionHandler { - - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during class setup"); - throw ex; - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during test setup"); - throw ex; - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during test cleanup"); - throw ex; - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during class cleanup"); - throw ex; - } - // end::user_guide[] - - private void memoryDumpForFurtherInvestigation(String error) { - - } - // tag::user_guide[] -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java deleted file mode 100644 index 4b9a904d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/Random.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.extensions; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.extension.ExtendWith; - -//tag::user_guide[] -@Target({ ElementType.FIELD, ElementType.PARAMETER }) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(RandomNumberExtension.class) -public @interface Random { -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java deleted file mode 100644 index d52af0a6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberDemo.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.extensions; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -@Disabled("RandomNumberExtension has not been implemented") -//tag::user_guide[] -class RandomNumberDemo { - - // use random number field in test methods and @BeforeEach - // or @AfterEach lifecycle methods - @Random - private int randomNumber1; - - RandomNumberDemo(@Random int randomNumber2) { - // use random number in constructor - } - - @BeforeEach - void beforeEach(@Random int randomNumber3) { - // use random number in @BeforeEach method - } - - @Test - void test(@Random int randomNumber4) { - // use random number in test method - } - -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java deleted file mode 100644 index 98f1d1ac..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/extensions/RandomNumberExtension.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.extensions; - -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -class RandomNumberExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return false; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return null; - } - - @Override - public void beforeAll(ExtensionContext context) { - } - - @Override - public void beforeEach(ExtensionContext context) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java deleted file mode 100644 index b12094f3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.interceptor; - -import java.lang.reflect.Method; -import java.util.concurrent.atomic.AtomicReference; - -import javax.swing.SwingUtilities; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; - -// @formatter:off -// tag::user_guide[] -public class SwingEdtInterceptor implements InvocationInterceptor { - - @Override - public void interceptTestMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext) throws Throwable { - - AtomicReference throwable = new AtomicReference<>(); - - SwingUtilities.invokeAndWait(() -> { - try { - invocation.proceed(); - } - catch (Throwable t) { - throwable.set(t); - } - }); - Throwable t = throwable.get(); - if (t != null) { - throw t; - } - } -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java deleted file mode 100644 index 973b4f19..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/DocumentationDemo.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.registration; - -import java.nio.file.Path; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.RegisterExtension; - -//tag::user_guide[] -class DocumentationDemo { - - static Path lookUpDocsDir() { - // return path to docs dir - // end::user_guide[] - return null; - // tag::user_guide[] - } - - @RegisterExtension - DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir()); - - @Test - void generateDocumentation() { - // use this.docs ... - } -} -//end::user_guide[] - -class DocumentationExtension implements AfterEachCallback { - - @SuppressWarnings("unused") - private final Path path; - - private DocumentationExtension(Path path) { - this.path = path; - } - - static DocumentationExtension forPath(Path path) { - return new DocumentationExtension(path); - } - - @Override - public void afterEach(ExtensionContext context) { - /* no-op for demo */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java deleted file mode 100644 index 25a6b279..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/registration/WebServerDemo.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.registration; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -// tag::user_guide[] -class WebServerDemo { - - // end::user_guide[] - // @formatter:off - // tag::user_guide[] - @RegisterExtension - static WebServerExtension server = WebServerExtension.builder() - .enableSecurity(false) - .build(); - // end::user_guide[] - // @formatter:on - // tag::user_guide[] - - @Test - void getProductList() { - WebClient webClient = new WebClient(); - String serverUrl = server.getServerUrl(); - // Use WebClient to connect to web server using serverUrl and verify response - assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java deleted file mode 100644 index 595be12b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.session; - -//tag::user_guide[] -import static java.net.InetAddress.getLoopbackAddress; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.InetSocketAddress; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import com.sun.net.httpserver.HttpServer; - -import org.junit.platform.launcher.LauncherSession; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestPlan; - -public class GlobalSetupTeardownListener implements LauncherSessionListener { - - private Fixture fixture; - - @Override - public void launcherSessionOpened(LauncherSession session) { - // Avoid setup for test discovery by delaying it until tests are about to be executed - session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() { - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - //end::user_guide[] - if (!testPlan.getConfigurationParameters().getBoolean("enableHttpServer").orElse(false)) { - // avoid starting multiple HTTP servers unnecessarily from UsingTheLauncherDemo - return; - } - //tag::user_guide[] - if (fixture == null) { - fixture = new Fixture(); - fixture.setUp(); - } - } - }); - } - - @Override - public void launcherSessionClosed(LauncherSession session) { - if (fixture != null) { - fixture.tearDown(); - fixture = null; - } - } - - static class Fixture { - - private HttpServer server; - private ExecutorService executorService; - - void setUp() { - try { - server = HttpServer.create(new InetSocketAddress(getLoopbackAddress(), 0), 0); - } - catch (IOException e) { - throw new UncheckedIOException("Failed to start HTTP server", e); - } - server.createContext("/test", exchange -> { - exchange.sendResponseHeaders(204, -1); - exchange.close(); - }); - executorService = Executors.newCachedThreadPool(); - server.setExecutor(executorService); - server.start(); // <1> - int port = server.getAddress().getPort(); - System.setProperty("http.server.host", getLoopbackAddress().getHostAddress()); // <2> - System.setProperty("http.server.port", String.valueOf(port)); // <3> - } - - void tearDown() { - server.stop(0); // <4> - executorService.shutdownNow(); - } - } - -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java deleted file mode 100644 index cbdf5367..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/session/HttpTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.session; - -//tag::user_guide[] -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; - -import org.junit.jupiter.api.Test; - -class HttpTests { - - @Test - void respondsWith204() throws Exception { - String host = System.getProperty("http.server.host"); // <1> - String port = System.getProperty("http.server.port"); // <2> - URL url = URI.create("http://" + host + ":" + port + "/test").toURL(); - - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - int responseCode = connection.getResponseCode(); // <3> - - assertEquals(204, responseCode); // <4> - } -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java deleted file mode 100644 index b24ab341..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testinterface; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -// @formatter:off -// tag::user_guide[] -class TestInterfaceDemo implements TestLifecycleLogger, - TimeExecutionLogger, TestInterfaceDynamicTestsDemo { - - @Test - void isEqualValue() { - assertEquals(1, "a".length(), "is always equal"); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java deleted file mode 100644 index 694192db..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testinterface; - -import static example.util.StringUtils.isPalindrome; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; - -// @formatter:off -// tag::user_guide[] -interface TestInterfaceDynamicTestsDemo { - - @TestFactory - default Stream dynamicTestsForPalindromes() { - return Stream.of("racecar", "radar", "mom", "dad") - .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java deleted file mode 100644 index b269bfb6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testinterface; - -import java.util.logging.Logger; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; - -// @formatter:off -// tag::user_guide[] -@TestInstance(Lifecycle.PER_CLASS) -interface TestLifecycleLogger { - - static final Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName()); - - @BeforeAll - default void beforeAllTests() { - logger.info("Before all tests"); - } - - @AfterAll - default void afterAllTests() { - logger.info("After all tests"); - } - - @BeforeEach - default void beforeEachTest(TestInfo testInfo) { - logger.info(() -> String.format("About to execute [%s]", - testInfo.getDisplayName())); - } - - @AfterEach - default void afterEachTest(TestInfo testInfo) { - logger.info(() -> String.format("Finished executing [%s]", - testInfo.getDisplayName())); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java deleted file mode 100644 index b3a53fea..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testinterface; - -import example.timing.TimingExtension; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.extension.ExtendWith; - -//tag::user_guide[] -@Tag("timed") -@ExtendWith(TimingExtension.class) -interface TimeExecutionLogger { -} -//end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java deleted file mode 100644 index 5df68019..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testkit; - -// @formatter:off -// tag::user_guide[] - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.io.StringWriter; -import java.io.Writer; - -import example.ExampleTestCase; - -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.opentest4j.TestAbortedException; - -class EngineTestKitAllEventsDemo { - - @Test - void verifyAllJupiterEvents() { - Writer writer = // create a java.io.Writer for debug output - // end::user_guide[] - // For the demo, we are swallowing the debug output. - new StringWriter(); - // tag::user_guide[] - - EngineTestKit.engine("junit-jupiter") // <1> - .selectors(selectClass(ExampleTestCase.class)) // <2> - .execute() // <3> - .allEvents() // <4> - .debug(writer) // <5> - .assertEventsMatchExactly( // <6> - event(engine(), started()), - event(container(ExampleTestCase.class), started()), - event(test("skippedTest"), skippedWithReason("for demonstration purposes")), - event(test("succeedingTest"), started()), - event(test("succeedingTest"), finishedSuccessfully()), - event(test("abortedTest"), started()), - event(test("abortedTest"), - abortedWithReason(instanceOf(TestAbortedException.class), - message(m -> m.contains("abc does not contain Z")))), - event(test("failingTest"), started()), - event(test("failingTest"), finishedWithFailure( - instanceOf(ArithmeticException.class), message("/ by zero"))), - event(container(ExampleTestCase.class), finishedSuccessfully()), - event(engine(), finishedSuccessfully())); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java deleted file mode 100644 index 40ea51ba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testkit; - -// @formatter:off -// tag::user_guide[] - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import example.ExampleTestCase; - -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineTestKit; - -class EngineTestKitFailedMethodDemo { - - @Test - void verifyJupiterMethodFailed() { - EngineTestKit.engine("junit-jupiter") // <1> - .selectors(selectClass(ExampleTestCase.class)) // <2> - .execute() // <3> - .testEvents() // <4> - .assertThatEvents().haveExactly(1, // <5> - event(test("failingTest"), - finishedWithFailure( - instanceOf(ArithmeticException.class), message("/ by zero")))); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java deleted file mode 100644 index 1d06770f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testkit; - -// @formatter:off -// tag::user_guide[] - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.test; - -import example.ExampleTestCase; - -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Events; - -class EngineTestKitSkippedMethodDemo { - - @Test - void verifyJupiterMethodWasSkipped() { - String methodName = "skippedTest"; - - Events testEvents = EngineTestKit // <5> - .engine("junit-jupiter") // <1> - .selectors(selectMethod(ExampleTestCase.class, methodName)) // <2> - .execute() // <3> - .testEvents(); // <4> - - testEvents.assertStatistics(stats -> stats.skipped(1)); // <6> - - testEvents.assertThatEvents() // <7> - .haveExactly(1, event(test(methodName), - skippedWithReason("for demonstration purposes"))); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java deleted file mode 100644 index b9576352..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.testkit; - -// @formatter:off -// tag::user_guide[] - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import example.ExampleTestCase; - -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineTestKit; - -class EngineTestKitStatisticsDemo { - - @Test - void verifyJupiterContainerStats() { - EngineTestKit - .engine("junit-jupiter") // <1> - .selectors(selectClass(ExampleTestCase.class)) // <2> - .execute() // <3> - .containerEvents() // <4> - .assertStatistics(stats -> stats.started(2).succeeded(2)); // <5> - } - - @Test - void verifyJupiterTestStats() { - EngineTestKit - .engine("junit-jupiter") // <1> - .selectors(selectClass(ExampleTestCase.class)) // <2> - .execute() // <3> - .testEvents() // <6> - .assertStatistics(stats -> - stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); // <7> - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java deleted file mode 100644 index b6b51d61..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtension.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.timing; - -// tag::user_guide[] -import java.lang.reflect.Method; -import java.util.logging.Logger; - -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; - -// end::user_guide[] -/** - * Simple extension that times the execution of test methods and - * logs the results at {@code INFO} level. - * - * @since 5.0 - */ -// @formatter:off -// tag::user_guide[] -public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { - - private static final Logger logger = Logger.getLogger(TimingExtension.class.getName()); - - private static final String START_TIME = "start time"; - - @Override - public void beforeTestExecution(ExtensionContext context) throws Exception { - getStore(context).put(START_TIME, System.currentTimeMillis()); - } - - @Override - public void afterTestExecution(ExtensionContext context) throws Exception { - Method testMethod = context.getRequiredTestMethod(); - long startTime = getStore(context).remove(START_TIME, long.class); - long duration = System.currentTimeMillis() - startTime; - - logger.info(() -> - String.format("Method [%s] took %s ms.", testMethod.getName(), duration)); - } - - private Store getStore(ExtensionContext context) { - return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); - } - -} -// end::user_guide[] -// @formatter:on diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java deleted file mode 100644 index ee84b196..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/example/timing/TimingExtensionTests.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example.timing; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * Tests that demonstrate the example {@link TimingExtension}. - * - * @since 5.0 - */ -// tag::user_guide[] -@ExtendWith(TimingExtension.class) -class TimingExtensionTests { - - @Test - void sleep20ms() throws Exception { - Thread.sleep(20); - } - - @Test - void sleep50ms() throws Exception { - Thread.sleep(50); - } - -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java deleted file mode 100644 index 8dd21a71..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/extensions/ExpectToFail.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package extensions; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(ExpectToFail.Extension.class) -public @interface ExpectToFail { - - class Extension implements TestExecutionExceptionHandler, AfterEachCallback { - - private static final String KEY = "exception"; - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { - getExceptionStore(context).put(KEY, throwable); - } - - @Override - public void afterEach(ExtensionContext context) throws Exception { - assertNotNull(getExceptionStore(context).get(KEY), "Test should have failed"); - } - - private Store getExceptionStore(ExtensionContext context) { - return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java deleted file mode 100644 index 6b9a143c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AbstractApiReportWriter.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.api.tools; - -import static java.lang.String.format; - -import java.io.PrintWriter; -import java.util.EnumSet; -import java.util.List; - -import org.apiguardian.api.API.Status; - -/** - * @since 1.0 - */ -abstract class AbstractApiReportWriter implements ApiReportWriter { - - private final ApiReport apiReport; - - AbstractApiReportWriter(ApiReport apiReport) { - this.apiReport = apiReport; - } - - @Override - public void printReportHeader(PrintWriter out) { - out.println(h1("@API Declarations")); - out.println(); - out.println(paragraph( - format("Discovered %d types with %s declarations.", this.apiReport.getTypes().size(), code("@API")))); - out.println(); - } - - @Override - public void printDeclarationInfo(PrintWriter out, EnumSet statuses) { - // @formatter:off - this.apiReport.getDeclarationsMap().entrySet().stream() - .filter(e -> statuses.contains(e.getKey())) - .forEach(e -> printDeclarationSection(statuses, e.getKey(), e.getValue(), out)); - // @formatter:on - } - - protected void printDeclarationSection(EnumSet statuses, Status status, List> types, - PrintWriter out) { - printDeclarationSectionHeader(statuses, status, types, out); - if (types.size() > 0) { - printDeclarationTableHeader(out); - types.forEach(type -> printDeclarationTableRow(type, out)); - printDeclarationTableFooter(out); - out.println(); - } - } - - protected void printDeclarationSectionHeader(EnumSet statuses, Status status, List> types, - PrintWriter out) { - if (statuses.size() < 2) { - // omit section header when only a single status is printed - return; - } - out.println(h2(format("@API(%s)", status))); - out.println(); - out.println(paragraph(format("Discovered %d " + code("@API(%s)") + " declarations.", types.size(), status))); - out.println(); - } - - protected abstract String h1(String header); - - protected abstract String h2(String header); - - protected abstract String code(String element); - - protected abstract String italic(String element); - - protected String paragraph(String element) { - return element; - } - - protected abstract void printDeclarationTableHeader(PrintWriter out); - - protected abstract void printDeclarationTableRow(Class type, PrintWriter out); - - protected abstract void printDeclarationTableFooter(PrintWriter out); - - protected String getKind(Class type) { - if (type.isAnnotation()) { - return "annotation"; - } - if (type.isEnum()) { - return "enum"; - } - if (type.isInterface()) { - return "interface"; - } - return "class"; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java deleted file mode 100644 index b21e63e8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReport.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.api.tools; - -import java.util.List; -import java.util.Map; - -import org.apiguardian.api.API.Status; - -/** - * @since 1.0 - */ -class ApiReport { - - private final List> types; - - private final Map>> declarationsMap; - - ApiReport(List> types, Map>> declarationsMap) { - this.types = types; - this.declarationsMap = declarationsMap; - } - - List> getTypes() { - return this.types; - } - - Map>> getDeclarationsMap() { - return this.declarationsMap; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java deleted file mode 100644 index 7bdb6c21..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportGenerator.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.api.tools; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -import io.github.classgraph.ClassGraph; -import io.github.classgraph.ClassInfoList; -import io.github.classgraph.ScanResult; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * @since 1.0 - */ -class ApiReportGenerator { - - public static void main(String... args) { - - // CAUTION: The output produced by this method is used to - // generate a table in the User Guide. - - PrintWriter writer = new PrintWriter(System.out, true); - ApiReportGenerator reportGenerator = new ApiReportGenerator(); - - // scan all types below "org.junit" package - ApiReport apiReport = reportGenerator.generateReport("org.junit"); - - // ApiReportWriter reportWriter = new MarkdownApiReportWriter(apiReport); - ApiReportWriter reportWriter = new AsciidocApiReportWriter(apiReport); - // ApiReportWriter reportWriter = new HtmlApiReportWriter(apiReport); - - // reportWriter.printReportHeader(writer); - - // Print report for all Usage enum constants - // reportWriter.printDeclarationInfo(writer, EnumSet.allOf(Usage.class)); - - // Print report only for a specific Status constant, defaults to EXPERIMENTAL - Status status = Status.EXPERIMENTAL; - if (args.length == 1) { - status = Status.valueOf(args[0]); - } - reportWriter.printDeclarationInfo(writer, EnumSet.of(status)); - } - - // ------------------------------------------------------------------------- - - ApiReport generateReport(String... packages) { - Logger logger = LoggerFactory.getLogger(ApiReportGenerator.class); - String EOL = System.lineSeparator(); - ClassGraph classGraph = new ClassGraph() // - .acceptPackages(packages) // - .disableNestedJarScanning() // - .enableAnnotationInfo(); // - String apiClasspath = System.getProperty("api.classpath"); - if (apiClasspath != null) { - classGraph = classGraph.overrideClasspath(apiClasspath); - } - - // Scan packages - try (ScanResult scanResult = classGraph.scan()) { - - // Collect names - ClassInfoList classesWithApiAnnotation = scanResult.getClassesWithAnnotation(API.class.getCanonicalName()); - List names = classesWithApiAnnotation.getNames(); - - logger.debug(() -> { - StringBuilder builder = new StringBuilder( - names.size() + " @API declarations (including meta) found in class-path:"); - builder.append(EOL); - scanResult.getClasspathURLs().forEach(e -> builder.append(e).append(EOL)); - return builder.toString(); - }); - - // Collect types - List> types = classesWithApiAnnotation.loadClasses(); - // only retain directly annotated types - types.removeIf(c -> !c.isAnnotationPresent(API.class)); - types.sort(Comparator.comparing(Class::getName)); - - logger.debug(() -> { - StringBuilder builder = new StringBuilder("Listing of all " + types.size() + " annotated types:"); - builder.append(EOL); - types.forEach(e -> builder.append(e).append(EOL)); - return builder.toString(); - }); - - // Build map - Map>> declarationsMap = new EnumMap<>(Status.class); - for (Status status : Status.values()) { - declarationsMap.put(status, new ArrayList<>()); - } - types.forEach(type -> declarationsMap.get(type.getAnnotation(API.class).status()).add(type)); - - // Create report - return new ApiReport(types, declarationsMap); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java deleted file mode 100644 index c231d1b4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/ApiReportWriter.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.api.tools; - -import java.io.PrintWriter; -import java.util.EnumSet; - -import org.apiguardian.api.API.Status; - -/** - * @since 1.0 - */ -interface ApiReportWriter { - - void printReportHeader(PrintWriter out); - - void printDeclarationInfo(PrintWriter out, EnumSet statuses); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java deleted file mode 100644 index 05748d36..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/AsciidocApiReportWriter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.api.tools; - -import java.io.PrintWriter; - -import org.apiguardian.api.API; - -/** - * @since 1.0 - */ -class AsciidocApiReportWriter extends AbstractApiReportWriter { - - private static final String ASCIIDOC_FORMAT = "| %-52s | %-42s | %-12s%n"; - - AsciidocApiReportWriter(ApiReport apiReport) { - super(apiReport); - } - - @Override - protected String h1(String header) { - return "= " + header; - } - - @Override - protected String h2(String header) { - return "== " + header; - } - - @Override - protected String code(String element) { - return "`" + element + "`"; - } - - @Override - protected String italic(String element) { - return "_" + element + "_"; - } - - @Override - protected void printDeclarationTableHeader(PrintWriter out) { - out.println("|==="); - out.printf(ASCIIDOC_FORMAT, "Package Name", "Type Name", "Since"); - out.println(); - } - - @Override - protected void printDeclarationTableRow(Class type, PrintWriter out) { - String packageName = type.getPackage().getName(); - String typeName = type.getCanonicalName(); - if (typeName.startsWith(packageName + '.')) { - typeName = typeName.substring(packageName.length() + 1); - } - out.printf(ASCIIDOC_FORMAT, // - code(packageName), // - code(typeName) + " " + italic("(" + getKind(type) + ")"), // - code(type.getAnnotation(API.class).since()) // - ); - } - - @Override - protected void printDeclarationTableFooter(PrintWriter out) { - out.println("|==="); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java deleted file mode 100644 index 48193368..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/HtmlApiReportWriter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.api.tools; - -import java.io.PrintWriter; - -import org.apiguardian.api.API; - -/** - * @since 1.0 - */ -class HtmlApiReportWriter extends AbstractApiReportWriter { - - private static final String HTML_HEADER_FORMAT = "\t%s%s%s%n"; - private static final String HTML_ROW_FORMAT = "\t%s%s%s%n"; - - HtmlApiReportWriter(ApiReport apiReport) { - super(apiReport); - } - - @Override - protected String h1(String header) { - return "

" + header + "

"; - } - - @Override - protected String h2(String header) { - return "

" + header + "

"; - } - - @Override - protected String code(String element) { - return "" + element + ""; - } - - @Override - protected String italic(String element) { - return "" + element + ""; - } - - @Override - protected String paragraph(String element) { - return "

" + element + "

"; - } - - @Override - protected void printDeclarationTableHeader(PrintWriter out) { - out.println(""); - out.printf(HTML_HEADER_FORMAT, "Package Name", "Type Name", "Since"); - } - - @Override - protected void printDeclarationTableRow(Class type, PrintWriter out) { - out.printf(HTML_ROW_FORMAT, // - code(type.getPackage().getName()), // - code(type.getSimpleName()) + " " + italic("(" + getKind(type) + ")"), // - code(type.getAnnotation(API.class).since()) // - ); - } - - @Override - protected void printDeclarationTableFooter(PrintWriter out) { - out.println("
"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java deleted file mode 100644 index 6294ac58..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/java/org/junit/api/tools/MarkdownApiReportWriter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.api.tools; - -import java.io.PrintWriter; -import java.nio.CharBuffer; - -import org.apiguardian.api.API; - -/** - * @since 1.0 - */ -class MarkdownApiReportWriter extends AbstractApiReportWriter { - - private static final String MARKDOWN_FORMAT = "%-52s | %-42s | %-12s | %-27s%n"; - - MarkdownApiReportWriter(ApiReport apiReport) { - super(apiReport); - } - - @Override - protected String h1(String header) { - return "# " + header; - } - - @Override - protected String h2(String header) { - return "## " + header; - } - - @Override - protected String code(String element) { - return "`" + element + "`"; - } - - @Override - protected String italic(String element) { - return "_" + element + "_"; - } - - @Override - protected void printDeclarationTableHeader(PrintWriter out) { - out.printf(MARKDOWN_FORMAT, "Package Name", "Type Name", "Since"); - out.printf(MARKDOWN_FORMAT, dashes(52), dashes(42), dashes(12), dashes(27)); - } - - private String dashes(int length) { - return CharBuffer.allocate(length).toString().replace('\0', '-'); - } - - @Override - protected void printDeclarationTableRow(Class type, PrintWriter out) { - out.printf(MARKDOWN_FORMAT, // - code(type.getPackage().getName()), // - code(type.getSimpleName()) + " " + italic("(" + getKind(type) + ")"), // - code(type.getAnnotation(API.class).since()) // - ); - } - - @Override - protected void printDeclarationTableFooter(PrintWriter out) { - /* no-op */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt deleted file mode 100644 index 33ddbe08..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/FibonacciCalculator.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package example - -class FibonacciCalculator { - - private val fibonacci = sequence { - yield(0) // 0th Fibonacci number - yield(1) // first Fibonacci number - var cur = 1 - var next = 1 - while (true) { - yield(next) // next Fibonacci number - val tmp = cur + next - cur = next - next = tmp - } - } - - fun fib(fibonacciNumber: Int) = - fibonacci.elementAt(fibonacciNumber) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt deleted file mode 100644 index 2870ae95..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package example - -// tag::user_guide[] - -import example.domain.Person -import example.util.Calculator -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.assertTimeout -import org.junit.jupiter.api.assertTimeoutPreemptively -import java.time.Duration - -class KotlinAssertionsDemo { - - private val person = Person("Jane", "Doe") - private val people = setOf(person, Person("John", "Doe")) - - @Test - fun `exception absence testing`() { - val calculator = Calculator() - val result = assertDoesNotThrow("Should not throw an exception") { - calculator.divide(0, 1) - } - assertEquals(0, result) - } - - @Test - fun `expected exception testing`() { - val calculator = Calculator() - val exception = assertThrows ("Should throw an exception") { - calculator.divide(1, 0) - } - assertEquals("/ by zero", exception.message) - } - - @Test - fun `grouped assertions`() { - assertAll( - "Person properties", - { assertEquals("Jane", person.firstName) }, - { assertEquals("Doe", person.lastName) } - ) - } - - @Test - fun `grouped assertions from a stream`() { - assertAll( - "People with first name starting with J", - people - .stream() - .map { - // This mapping returns Stream<() -> Unit> - { assertTrue(it.firstName.startsWith("J")) } - } - ) - } - - @Test - fun `grouped assertions from a collection`() { - assertAll( - "People with last name of Doe", - people.map { { assertEquals("Doe", it.lastName) } } - ) - } - - @Test - fun `timeout not exceeded testing`() { - val fibonacciCalculator = FibonacciCalculator() - val result = assertTimeout(Duration.ofMillis(1000)) { - fibonacciCalculator.fib(14) - } - assertEquals(377, result) - } - - // end::user_guide[] - @extensions.ExpectToFail - // tag::user_guide[] - @Test - fun `timeout exceeded with preemptive termination`() { - // The following assertion fails with an error message similar to: - // execution timed out after 10 ms - assertTimeoutPreemptively(Duration.ofMillis(10)) { - // Simulate task that takes more than 10 ms. - Thread.sleep(100) - } - } -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt deleted file mode 100644 index 52a390fe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package example.registration - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.RegisterExtension - -// tag::user_guide[] -class KotlinWebServerDemo { - - companion object { - @JvmStatic - @RegisterExtension - val server = WebServerExtension.builder() - .enableSecurity(false) - .build() - } - - @Test - fun getProductList() { - // Use WebClient to connect to web server using serverUrl and verify response - val webClient = WebClient() - val serverUrl = server.serverUrl - assertEquals(200, webClient.get("$serverUrl/products").responseStatus) - } -} -// end::user_guide[] diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener deleted file mode 100644 index f6d29762..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener +++ /dev/null @@ -1 +0,0 @@ -example.session.GlobalSetupTeardownListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties deleted file mode 100644 index 6f2ed6e7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/junit-platform.properties +++ /dev/null @@ -1,4 +0,0 @@ -junit.jupiter.execution.parallel.enabled=true -junit.jupiter.execution.parallel.mode.default=concurrent -junit.jupiter.execution.parallel.config.strategy=fixed -junit.jupiter.execution.parallel.config.fixed.parallelism=6 diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml deleted file mode 100644 index 9fb7a4a6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv b/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv deleted file mode 100644 index 7ebb4c54..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/documentation/src/test/resources/two-column.csv +++ /dev/null @@ -1,5 +0,0 @@ -COUNTRY, REFERENCE -Sweden, 1 -Poland, 2 -"United States of America", 3 -France, 700_000 diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradle.properties b/src/main/java/com/junit-team-junit5-da216b8/gradle.properties deleted file mode 100644 index 646efede..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/gradle.properties +++ /dev/null @@ -1,31 +0,0 @@ -group = org.junit -version = 5.9.3 - -jupiterGroup = org.junit.jupiter - -platformGroup = org.junit.platform -platformVersion = 1.9.3 - -vintageGroup = org.junit.vintage -vintageVersion = 5.9.3 - -defaultBuiltBy = JUnit Team - -# We need more metaspace due to apparent memory leak in Asciidoctor/JRuby -# The exports are needed due to https://github.com/diffplug/spotless/issues/834 -org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError \ - --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED -org.gradle.caching=true -org.gradle.parallel=true -org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21 - -# Test Distribution -gradle.internal.testdistribution.writeTraceFile=true - -# Omit automatic compile dependency on kotlin-stdlib -# https://kotlinlang.org/docs/gradle.html#dependency-on-the-standard-library -kotlin.stdlib.default.dependency=false diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml b/src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml deleted file mode 100644 index 3c1dc9df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/gradle/libs.versions.toml +++ /dev/null @@ -1,56 +0,0 @@ -[versions] -ant = "1.10.12" -apiguardian = "1.1.2" -asciidoctor-pdf = "1.5.3" -assertj = "3.23.1" -checkstyle = "9.0" -jacoco = "0.8.7" -jmh = "1.36" -junit4 = "4.13.2" -junit4Osgi = "4.13.2_1" -junit4Min = "4.12" -ktlint = "0.43.0" -log4j = "2.19.0" -opentest4j = "1.2.0" -openTestReporting = "0.1.0-M1" -surefire = "3.0.0-M7" -xmlunit = "2.9.0" - -[libraries] -ant = { module = "org.apache.ant:ant", version.ref = "ant" } -ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" } -ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" } -apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } -archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.0.1" } -assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } -bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" } -bnd = { module = "biz.aQute.bnd:biz.aQute.bndlib", version = "6.4.0" } -classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.153" } -commons-io = { module = "commons-io:commons-io", version = "2.11.0" } -groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.7" } -groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.14" } -hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } -jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } -jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } -jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } -joox = { module = "org.jooq:joox", version = "2.0.0" } -junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.6.4" } -log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } -log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } -maven = { module = "org.apache.maven:apache-maven", version = "3.8.6" } -mockito = { module = "org.mockito:mockito-junit-jupiter", version = "4.11.0" } -opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } -openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } -openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" } -picocli = { module = "info.picocli:picocli", version = "4.7.0" } -slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.6" } -spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } -univocity-parsers = { module = "com.univocity:univocity-parsers", version = "2.9.1" } -xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } -xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } - -[bundles] -ant = ["ant", "ant-junit", "ant-junitlauncher"] -log4j = ["log4j-core", "log4j-jul"] -xmlunit = ["xmlunit-assertj", "xmlunit-placeholders"] diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties b/src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index adb6acbd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip -networkTimeout=10000 -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradlew b/src/main/java/com/junit-team-junit5-da216b8/gradlew deleted file mode 100644 index 65dcd68d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/gradlew +++ /dev/null @@ -1,244 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/src/main/java/com/junit-team-junit5-da216b8/gradlew.bat b/src/main/java/com/junit-team-junit5-da216b8/gradlew.bat deleted file mode 100644 index 93e3f59f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md b/src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md deleted file mode 100644 index 907a361c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-bom/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# JUnit Bill of Materials (BOM) - -This module provides a Bill of Materials POM to ease dependency management using [Maven] -or [Gradle]. Please refer to the [User Guide] for details. - -[Maven]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies -[Gradle]: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:bom_import -[User Guide]: https://junit.org/junit5/docs/current/user-guide/#dependency-metadata-junit-bom diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts deleted file mode 100644 index fae23ee3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-bom/junit-bom.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -plugins { - `java-platform` - `publishing-conventions` -} - -description = "${rootProject.description} (Bill of Materials)" - -dependencies { - constraints { - val mavenizedProjects: List by rootProject.extra - mavenizedProjects.sorted() - .filter { it.name != "junit-platform-console-standalone" } - .forEach { api("${it.group}:${it.name}:${it.version}") } - } -} - -publishing.publications.named("maven") { - from(components["javaPlatform"]) - pom { - description.set("This Bill of Materials POM can be used to ease dependency management " + - "when referencing multiple JUnit artifacts using Gradle or Maven.") - withXml { - val filteredContent = asString().replace("\\s*compile".toRegex(), "") - asString().clear().append(filteredContent) - } - } -} - -tasks.withType().configureEach { - doLast { - val xml = destination.readText() - require(xml.indexOf("") == xml.lastIndexOf("")) { - "BOM must contain exactly one element but contained multiple:\n$destination" - } - require(xml.contains("")) { - "BOM must contain a element:\n$destination" - } - require(!xml.contains("")) { - "BOM must not contain elements:\n$destination" - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts deleted file mode 100644 index d28dec7b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - `kotlin-library-conventions` - `java-test-fixtures` -} - -description = "JUnit Jupiter API" - -dependencies { - api(platform(projects.junitBom)) - api(libs.opentest4j) - api(projects.junitPlatformCommons) - - compileOnlyApi(libs.apiguardian) - - compileOnly(kotlin("stdlib")) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -tasks { - jar { - bundle { - bnd(""" - Require-Capability:\ - org.junit.platform.engine;\ - filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;${rootProject.property("version")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("version")!!}}})))';\ - effective:=active - """) - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java deleted file mode 100644 index 52743ebe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @AfterAll} is used to signal that the annotated method should be - * executed after all tests in the current test class. - * - *

In contrast to {@link AfterEach @AfterEach} methods, {@code @AfterAll} - * methods are only executed once for a given test class. - * - *

Method Signatures

- * - *

{@code @AfterAll} methods must have a {@code void} return type and must be - * {@code static} by default. Consequently, {@code @AfterAll} methods are not - * supported in {@link Nested @Nested} test classes or as interface default - * methods unless the test class is annotated with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. - * However, beginning with Java 16 {@code @AfterAll} methods may be declared as - * {@code static} in {@link Nested @Nested} test classes, and the - * {@code Lifecycle.PER_CLASS} restriction no longer applies. {@code @AfterAll} - * methods may optionally declare parameters to be resolved by - * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. - * - *

Using {@code private} visibility for {@code @BeforeAll} methods is - * strongly discouraged and will be disallowed in a future release. - * - *

Inheritance and Execution Order

- * - *

{@code @AfterAll} methods are inherited from superclasses as long as - * they are not hidden (default mode with {@code static} modifier), - * overridden, or superseded (i.e., replaced based on - * signature only, irrespective of Java's visibility rules). Furthermore, - * {@code @AfterAll} methods from superclasses will be executed before - * {@code @AfterAll} methods in subclasses. - * - *

Similarly, {@code @AfterAll} methods declared in an interface are - * inherited as long as they are not hidden or overridden, - * and {@code @AfterAll} methods from an interface will be executed after - * {@code @AfterAll} methods in the class that implements the interface. - * - *

JUnit Jupiter does not guarantee the execution order of multiple - * {@code @AfterAll} methods that are declared within a single test class or - * test interface. While it may at times appear that these methods are invoked - * in alphabetical order, they are in fact sorted using an algorithm that is - * deterministic but intentionally non-obvious. - * - *

In addition, {@code @AfterAll} methods are in no way linked to - * {@code @BeforeAll} methods. Consequently, there are no guarantees with regard - * to their wrapping behavior. For example, given two - * {@code @BeforeAll} methods {@code createA()} and {@code createB()} as well as - * two {@code @AfterAll} methods {@code destroyA()} and {@code destroyB()}, the - * order in which the {@code @BeforeAll} methods are executed (e.g. - * {@code createA()} before {@code createB()}) does not imply any order for the - * seemingly corresponding {@code @AfterAll} methods. In other words, - * {@code destroyA()} might be called before or after - * {@code destroyB()}. The JUnit Team therefore recommends that developers - * declare at most one {@code @BeforeAll} method and at most one - * {@code @AfterAll} method per test class or test interface unless there are no - * dependencies between the {@code @BeforeAll} methods or between the - * {@code @AfterAll} methods. - * - *

Composition

- * - *

{@code @AfterAll} may be used as a meta-annotation in order to create - * a custom composed annotation that inherits the semantics of - * {@code @AfterAll}. - * - * @since 5.0 - * @see BeforeAll - * @see BeforeEach - * @see AfterEach - * @see Test - * @see TestFactory - * @see TestInstance - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -public @interface AfterAll { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java deleted file mode 100644 index 8dfd018f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @AfterEach} is used to signal that the annotated method should be - * executed after each {@code @Test}, - * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, - * and {@code @TestTemplate} method in the current test class. - * - *

Method Signatures

- * - *

{@code @AfterEach} methods must have a {@code void} return type and must - * not be {@code static}. Using {@code private} visibility is strongly - * discouraged and will be disallowed in a future release. - * They may optionally declare parameters to be resolved by - * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. - * - *

Inheritance and Execution Order

- * - *

{@code @AfterEach} methods are inherited from superclasses as long as they - * are not overridden or superseded (i.e., replaced based on - * signature only, irrespective of Java's visibility rules). Furthermore, - * {@code @AfterEach} methods from superclasses will be executed after - * {@code @AfterEach} methods in subclasses. - * - *

Similarly, {@code @AfterEach} methods declared as interface default - * methods are inherited as long as they are not overridden, and - * {@code @AfterEach} default methods will be executed after {@code @AfterEach} - * methods in the class that implements the interface. - * - *

JUnit Jupiter does not guarantee the execution order of multiple - * {@code @AfterEach} methods that are declared within a single test class or - * test interface. While it may at times appear that these methods are invoked - * in alphabetical order, they are in fact sorted using an algorithm that is - * deterministic but intentionally non-obvious. - * - *

In addition, {@code @AfterEach} methods are in no way linked to - * {@code @BeforeEach} methods. Consequently, there are no guarantees with - * regard to their wrapping behavior. For example, given two - * {@code @BeforeEach} methods {@code createA()} and {@code createB()} as well - * as two {@code @AfterEach} methods {@code destroyA()} and {@code destroyB()}, - * the order in which the {@code @BeforeEach} methods are executed (e.g. - * {@code createA()} before {@code createB()}) does not imply any order for the - * seemingly corresponding {@code @AfterEach} methods. In other words, - * {@code destroyA()} might be called before or after - * {@code destroyB()}. The JUnit Team therefore recommends that developers - * declare at most one {@code @BeforeEach} method and at most one - * {@code @AfterEach} method per test class or test interface unless there are - * no dependencies between the {@code @BeforeEach} methods or between the - * {@code @AfterEach} methods. - * - *

Composition

- * - *

{@code @AfterEach} may be used as a meta-annotation in order to create - * a custom composed annotation that inherits the semantics of - * {@code @AfterEach}. - * - * @since 5.0 - * @see BeforeEach - * @see BeforeAll - * @see AfterAll - * @see Test - * @see RepeatedTest - * @see TestFactory - * @see TestTemplate - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -public @interface AfterEach { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java deleted file mode 100644 index 601e9ecf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.opentest4j.MultipleFailuresError; - -/** - * {@code AssertAll} is a collection of utility methods that support asserting - * multiple conditions in tests at once. - * - * @since 5.0 - */ -class AssertAll { - - private AssertAll() { - /* no-op */ - } - - static void assertAll(Executable... executables) { - assertAll(null, executables); - } - - static void assertAll(String heading, Executable... executables) { - Preconditions.notEmpty(executables, "executables array must not be null or empty"); - Preconditions.containsNoNullElements(executables, "individual executables must not be null"); - assertAll(heading, Arrays.stream(executables)); - } - - static void assertAll(Collection executables) { - assertAll(null, executables); - } - - static void assertAll(String heading, Collection executables) { - Preconditions.notNull(executables, "executables collection must not be null"); - Preconditions.containsNoNullElements(executables, "individual executables must not be null"); - assertAll(heading, executables.stream()); - } - - static void assertAll(Stream executables) { - assertAll(null, executables); - } - - static void assertAll(String heading, Stream executables) { - Preconditions.notNull(executables, "executables stream must not be null"); - - List failures = executables // - .map(executable -> { - Preconditions.notNull(executable, "individual executables must not be null"); - try { - executable.execute(); - return null; - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - return t; - } - }) // - .filter(Objects::nonNull) // - .collect(Collectors.toList()); - - if (!failures.isEmpty()) { - MultipleFailuresError multipleFailuresError = new MultipleFailuresError(heading, failures); - failures.forEach(multipleFailuresError::addSuppressed); - throw multipleFailuresError; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java deleted file mode 100644 index 45d37399..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.jupiter.api.AssertionUtils.formatIndexes; -import static org.junit.platform.commons.util.ReflectionUtils.isArray; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * {@code AssertArrayEquals} is a collection of utility methods that support asserting - * array equality in tests. - * - * @since 5.0 - */ -class AssertArrayEquals { - - private AssertArrayEquals() { - /* no-op */ - } - - static void assertArrayEquals(boolean[] expected, boolean[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(boolean[] expected, boolean[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(boolean[] expected, boolean[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(char[] expected, char[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(char[] expected, char[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(char[] expected, char[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(byte[] expected, byte[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(byte[] expected, byte[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(byte[] expected, byte[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(short[] expected, short[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(short[] expected, short[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(short[] expected, short[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(int[] expected, int[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(int[] expected, int[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(int[] expected, int[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(long[] expected, long[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(long[] expected, long[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(long[] expected, long[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(float[] expected, float[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(float[] expected, float[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(float[] expected, float[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(float[] expected, float[] actual, float delta) { - assertArrayEquals(expected, actual, delta, (String) null); - } - - static void assertArrayEquals(float[] expected, float[] actual, float delta, String message) { - assertArrayEquals(expected, actual, delta, null, message); - } - - static void assertArrayEquals(float[] expected, float[] actual, float delta, Supplier messageSupplier) { - assertArrayEquals(expected, actual, delta, null, messageSupplier); - } - - static void assertArrayEquals(double[] expected, double[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(double[] expected, double[] actual, String message) { - assertArrayEquals(expected, actual, null, message); - } - - static void assertArrayEquals(double[] expected, double[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, null, messageSupplier); - } - - static void assertArrayEquals(double[] expected, double[] actual, double delta) { - assertArrayEquals(expected, actual, delta, (String) null); - } - - static void assertArrayEquals(double[] expected, double[] actual, double delta, String message) { - assertArrayEquals(expected, actual, delta, null, message); - } - - static void assertArrayEquals(double[] expected, double[] actual, double delta, Supplier messageSupplier) { - assertArrayEquals(expected, actual, delta, null, messageSupplier); - } - - static void assertArrayEquals(Object[] expected, Object[] actual) { - assertArrayEquals(expected, actual, (String) null); - } - - static void assertArrayEquals(Object[] expected, Object[] actual, String message) { - assertArrayEquals(expected, actual, new ArrayDeque<>(), message); - } - - static void assertArrayEquals(Object[] expected, Object[] actual, Supplier messageSupplier) { - assertArrayEquals(expected, actual, new ArrayDeque<>(), messageSupplier); - } - - private static void assertArrayEquals(boolean[] expected, boolean[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (expected[i] != actual[i]) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(char[] expected, char[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (expected[i] != actual[i]) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(byte[] expected, byte[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (expected[i] != actual[i]) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(short[] expected, short[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (expected[i] != actual[i]) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(int[] expected, int[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (expected[i] != actual[i]) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(long[] expected, long[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (expected[i] != actual[i]) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(float[] expected, float[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (!AssertionUtils.floatsAreEqual(expected[i], actual[i])) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(float[] expected, float[] actual, float delta, Deque indexes, - Object messageOrSupplier) { - - AssertionUtils.assertValidDelta(delta); - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (!AssertionUtils.floatsAreEqual(expected[i], actual[i], delta)) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(double[] expected, double[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (!AssertionUtils.doublesAreEqual(expected[i], actual[i])) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(double[] expected, double[] actual, double delta, Deque indexes, - Object messageOrSupplier) { - - AssertionUtils.assertValidDelta(delta); - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - if (!AssertionUtils.doublesAreEqual(expected[i], actual[i], delta)) { - failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); - } - } - } - - private static void assertArrayEquals(Object[] expected, Object[] actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == actual) { - return; - } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); - assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); - - for (int i = 0; i < expected.length; i++) { - Object expectedElement = expected[i]; - Object actualElement = actual[i]; - - if (expectedElement == actualElement) { - continue; - } - - indexes.addLast(i); - assertArrayElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier); - indexes.removeLast(); - } - } - - private static void assertArrayElementsEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { - - if (expected instanceof Object[] && actual instanceof Object[]) { - assertArrayEquals((Object[]) expected, (Object[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof byte[] && actual instanceof byte[]) { - assertArrayEquals((byte[]) expected, (byte[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof short[] && actual instanceof short[]) { - assertArrayEquals((short[]) expected, (short[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof int[] && actual instanceof int[]) { - assertArrayEquals((int[]) expected, (int[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof long[] && actual instanceof long[]) { - assertArrayEquals((long[]) expected, (long[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof char[] && actual instanceof char[]) { - assertArrayEquals((char[]) expected, (char[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof float[] && actual instanceof float[]) { - assertArrayEquals((float[]) expected, (float[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof double[] && actual instanceof double[]) { - assertArrayEquals((double[]) expected, (double[]) actual, indexes, messageOrSupplier); - } - else if (expected instanceof boolean[] && actual instanceof boolean[]) { - assertArrayEquals((boolean[]) expected, (boolean[]) actual, indexes, messageOrSupplier); - } - else if (!Objects.equals(expected, actual)) { - if (expected == null && isArray(actual)) { - failExpectedArrayIsNull(indexes, messageOrSupplier); - } - else if (isArray(expected) && actual == null) { - failActualArrayIsNull(indexes, messageOrSupplier); - } - else { - failArraysNotEqual(expected, actual, indexes, messageOrSupplier); - } - } - } - - private static void assertArraysNotNull(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == null) { - failExpectedArrayIsNull(indexes, messageOrSupplier); - } - if (actual == null) { - failActualArrayIsNull(indexes, messageOrSupplier); - } - } - - private static void failExpectedArrayIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("expected array was " + formatIndexes(indexes)) // - .buildAndThrow(); - } - - private static void failActualArrayIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("actual array was " + formatIndexes(indexes)) // - .buildAndThrow(); - } - - private static void assertArraysHaveSameLength(int expected, int actual, Deque indexes, - Object messageOrSupplier) { - - if (expected != actual) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("array lengths differ" + formatIndexes(indexes)) // - .expected(expected) // - .actual(actual) // - .buildAndThrow(); - } - } - - private static void failArraysNotEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { - - assertionFailure() // - .message(messageOrSupplier) // - .reason("array contents differ" + formatIndexes(indexes)) // - .expected(expected) // - .actual(actual) // - .buildAndThrow(); - } - - private static Deque nullSafeIndexes(Deque indexes, int newIndex) { - Deque result = (indexes != null ? indexes : new ArrayDeque<>()); - result.addLast(newIndex); - return result; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java deleted file mode 100644 index 2d424aa7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.Supplier; - -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.function.ThrowingSupplier; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.opentest4j.AssertionFailedError; - -/** - * {@code AssertDoesNotThrow} is a collection of utility methods that support - * explicitly asserting that a given code block does not throw an exception. - * - * @since 5.2 - */ -class AssertDoesNotThrow { - - private AssertDoesNotThrow() { - /* no-op */ - } - - static void assertDoesNotThrow(Executable executable) { - assertDoesNotThrow(executable, (Object) null); - } - - static void assertDoesNotThrow(Executable executable, String message) { - assertDoesNotThrow(executable, (Object) message); - } - - static void assertDoesNotThrow(Executable executable, Supplier messageSupplier) { - assertDoesNotThrow(executable, (Object) messageSupplier); - } - - private static void assertDoesNotThrow(Executable executable, Object messageOrSupplier) { - try { - executable.execute(); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - throw createAssertionFailedError(messageOrSupplier, t); - } - } - - static T assertDoesNotThrow(ThrowingSupplier supplier) { - return assertDoesNotThrow(supplier, (Object) null); - } - - static T assertDoesNotThrow(ThrowingSupplier supplier, String message) { - return assertDoesNotThrow(supplier, (Object) message); - } - - static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier messageSupplier) { - return assertDoesNotThrow(supplier, (Object) messageSupplier); - } - - private static T assertDoesNotThrow(ThrowingSupplier supplier, Object messageOrSupplier) { - try { - return supplier.get(); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - throw createAssertionFailedError(messageOrSupplier, t); - } - } - - private static AssertionFailedError createAssertionFailedError(Object messageOrSupplier, Throwable t) { - return assertionFailure() // - .message(messageOrSupplier) // - .reason("Unexpected exception thrown: " + t.getClass().getName() + buildSuffix(t.getMessage())) // - .cause(t) // - .build(); - } - - private static String buildSuffix(String message) { - return StringUtils.isNotBlank(message) ? ": " + message : ""; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java deleted file mode 100644 index 2a46cea3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; -import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; -import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; - -import java.util.function.Supplier; - -/** - * {@code AssertEquals} is a collection of utility methods that support asserting - * equality on objects and primitives in tests. - * - * @since 5.0 - */ -class AssertEquals { - - private AssertEquals() { - /* no-op */ - } - - static void assertEquals(byte expected, byte actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(byte expected, byte actual, String message) { - if (expected != actual) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(byte expected, byte actual, Supplier messageSupplier) { - if (expected != actual) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(char expected, char actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(char expected, char actual, String message) { - if (expected != actual) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(char expected, char actual, Supplier messageSupplier) { - if (expected != actual) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(double expected, double actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(double expected, double actual, String message) { - if (!doublesAreEqual(expected, actual)) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(double expected, double actual, Supplier messageSupplier) { - if (!doublesAreEqual(expected, actual)) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(double expected, double actual, double delta) { - assertEquals(expected, actual, delta, (String) null); - } - - static void assertEquals(double expected, double actual, double delta, String message) { - if (!doublesAreEqual(expected, actual, delta)) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(double expected, double actual, double delta, Supplier messageSupplier) { - if (!doublesAreEqual(expected, actual, delta)) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(float expected, float actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(float expected, float actual, String message) { - if (!floatsAreEqual(expected, actual)) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(float expected, float actual, Supplier messageSupplier) { - if (!floatsAreEqual(expected, actual)) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(float expected, float actual, float delta) { - assertEquals(expected, actual, delta, (String) null); - } - - static void assertEquals(float expected, float actual, float delta, String message) { - if (!floatsAreEqual(expected, actual, delta)) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(float expected, float actual, float delta, Supplier messageSupplier) { - if (!floatsAreEqual(expected, actual, delta)) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(short expected, short actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(short expected, short actual, String message) { - if (expected != actual) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(short expected, short actual, Supplier messageSupplier) { - if (expected != actual) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(int expected, int actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(int expected, int actual, String message) { - if (expected != actual) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(int expected, int actual, Supplier messageSupplier) { - if (expected != actual) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(long expected, long actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(long expected, long actual, String message) { - if (expected != actual) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(long expected, long actual, Supplier messageSupplier) { - if (expected != actual) { - failNotEqual(expected, actual, messageSupplier); - } - } - - static void assertEquals(Object expected, Object actual) { - assertEquals(expected, actual, (String) null); - } - - static void assertEquals(Object expected, Object actual, String message) { - if (!objectsAreEqual(expected, actual)) { - failNotEqual(expected, actual, message); - } - } - - static void assertEquals(Object expected, Object actual, Supplier messageSupplier) { - if (!objectsAreEqual(expected, actual)) { - failNotEqual(expected, actual, messageSupplier); - } - } - - private static void failNotEqual(Object expected, Object actual, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .expected(expected) // - .actual(actual) // - .buildAndThrow(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java deleted file mode 100644 index 5291a6ac..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.BooleanSupplier; -import java.util.function.Supplier; - -/** - * {@code AssertFalse} is a collection of utility methods that support asserting - * {@code false} in tests. - * - * @since 5.0 - */ -class AssertFalse { - - private AssertFalse() { - /* no-op */ - } - - static void assertFalse(boolean condition) { - assertFalse(condition, (String) null); - } - - static void assertFalse(boolean condition, String message) { - if (condition) { - failNotFalse(message); - } - } - - static void assertFalse(boolean condition, Supplier messageSupplier) { - if (condition) { - failNotFalse(messageSupplier); - } - } - - static void assertFalse(BooleanSupplier booleanSupplier) { - assertFalse(booleanSupplier.getAsBoolean(), (String) null); - } - - static void assertFalse(BooleanSupplier booleanSupplier, String message) { - assertFalse(booleanSupplier.getAsBoolean(), message); - } - - static void assertFalse(BooleanSupplier booleanSupplier, Supplier messageSupplier) { - assertFalse(booleanSupplier.getAsBoolean(), messageSupplier); - } - - private static void failNotFalse(Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .expected(false) // - .actual(true) // - .buildAndThrow(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java deleted file mode 100644 index 2f265f3f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.Supplier; - -/** - * {@code AssertInstanceOf} is a collection of utility methods that support - * asserting that an object is of an expected type — in other words, if it - * can be assigned to the expected type. - * - * @since 5.8 - */ -class AssertInstanceOf { - - private AssertInstanceOf() { - /* no-op */ - } - - static T assertInstanceOf(Class expectedType, Object actualValue) { - return assertInstanceOf(expectedType, actualValue, (Object) null); - } - - static T assertInstanceOf(Class expectedType, Object actualValue, String message) { - return assertInstanceOf(expectedType, actualValue, (Object) message); - } - - static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { - return assertInstanceOf(expectedType, actualValue, (Object) messageSupplier); - } - - private static T assertInstanceOf(Class expectedType, Object actualValue, Object messageOrSupplier) { - if (!expectedType.isInstance(actualValue)) { - assertionFailure() // - .message(messageOrSupplier) // - .reason(actualValue == null ? "Unexpected null value" : "Unexpected type") // - .expected(expectedType) // - .actual(actualValue == null ? null : actualValue.getClass()) // - .buildAndThrow(); - } - return expectedType.cast(actualValue); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java deleted file mode 100644 index b837c449..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.jupiter.api.AssertionUtils.formatIndexes; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; - -/** - * {@code AssertIterable} is a collection of utility methods that support asserting - * Iterable equality in tests. - * - * @since 5.0 - */ -class AssertIterableEquals { - - private AssertIterableEquals() { - /* no-op */ - } - - static void assertIterableEquals(Iterable expected, Iterable actual) { - assertIterableEquals(expected, actual, (String) null); - } - - static void assertIterableEquals(Iterable expected, Iterable actual, String message) { - assertIterableEquals(expected, actual, new ArrayDeque<>(), message); - } - - static void assertIterableEquals(Iterable expected, Iterable actual, Supplier messageSupplier) { - assertIterableEquals(expected, actual, new ArrayDeque<>(), messageSupplier); - } - - private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, - Object messageOrSupplier) { - assertIterableEquals(expected, actual, indexes, messageOrSupplier, new LinkedHashMap<>()); - } - - private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, - Object messageOrSupplier, Map investigatedElements) { - - if (expected == actual) { - return; - } - assertIterablesNotNull(expected, actual, indexes, messageOrSupplier); - - Iterator expectedIterator = expected.iterator(); - Iterator actualIterator = actual.iterator(); - - int processed = 0; - while (expectedIterator.hasNext() && actualIterator.hasNext()) { - Object expectedElement = expectedIterator.next(); - Object actualElement = actualIterator.next(); - - indexes.addLast(processed); - - assertIterableElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier, - investigatedElements); - - indexes.removeLast(); - processed++; - } - - assertIteratorsAreEmpty(expectedIterator, actualIterator, processed, indexes, messageOrSupplier); - } - - private static void assertIterableElementsEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier, Map investigatedElements) { - - // If both are equal, we don't need to check recursively. - if (Objects.equals(expected, actual)) { - return; - } - - // If both are iterables, we need to check whether they contain the same elements. - if (expected instanceof Iterable && actual instanceof Iterable) { - - Pair pair = new Pair(expected, actual); - - // Before comparing their elements, we check whether we have already checked this pair. - Status status = investigatedElements.get(pair); - - // If we've already determined that both contain the same elements, we don't need to check them again. - if (status == Status.CONTAIN_SAME_ELEMENTS) { - return; - } - - // If the pair is already under investigation, we fail in order to avoid infinite recursion. - if (status == Status.UNDER_INVESTIGATION) { - indexes.removeLast(); - failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); - } - - // Otherwise, we put the pair under investigation and recurse. - investigatedElements.put(pair, Status.UNDER_INVESTIGATION); - - assertIterableEquals((Iterable) expected, (Iterable) actual, indexes, messageOrSupplier, - investigatedElements); - - // If we reach this point, we've checked that the two iterables contain the same elements so we store this information - // in case we come across the same pair again. - investigatedElements.put(pair, Status.CONTAIN_SAME_ELEMENTS); - } - - // Otherwise, they are neither equal nor iterables, so we fail. - else { - assertIterablesNotNull(expected, actual, indexes, messageOrSupplier); - failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); - } - } - - private static void assertIterablesNotNull(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == null) { - failExpectedIterableIsNull(indexes, messageOrSupplier); - } - if (actual == null) { - failActualIterableIsNull(indexes, messageOrSupplier); - } - } - - private static void failExpectedIterableIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("expected iterable was " + formatIndexes(indexes)) // - .buildAndThrow(); - } - - private static void failActualIterableIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("actual iterable was " + formatIndexes(indexes)) // - .buildAndThrow(); - } - - private static void assertIteratorsAreEmpty(Iterator expected, Iterator actual, int processed, - Deque indexes, Object messageOrSupplier) { - - if (expected.hasNext() || actual.hasNext()) { - AtomicInteger expectedCount = new AtomicInteger(processed); - expected.forEachRemaining(e -> expectedCount.incrementAndGet()); - - AtomicInteger actualCount = new AtomicInteger(processed); - actual.forEachRemaining(e -> actualCount.incrementAndGet()); - - assertionFailure() // - .message(messageOrSupplier) // - .reason("iterable lengths differ" + formatIndexes(indexes)) // - .expected(expectedCount.get()) // - .actual(actualCount.get()) // - .buildAndThrow(); - } - } - - private static void failIterablesNotEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { - - assertionFailure() // - .message(messageOrSupplier) // - .reason("iterable contents differ" + formatIndexes(indexes)) // - .expected(expected) // - .actual(actual) // - .buildAndThrow(); - } - - private final static class Pair { - private final Object left; - private final Object right; - - public Pair(Object left, Object right) { - this.left = left; - this.right = right; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Pair that = (Pair) o; - return Objects.equals(this.left, that.left) // - && Objects.equals(this.right, that.right); - } - - @Override - public int hashCode() { - return Objects.hash(left, right); - } - } - - private enum Status { - UNDER_INVESTIGATION, CONTAIN_SAME_ELEMENTS - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java deleted file mode 100644 index 03e79081..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.lang.String.format; -import static java.lang.String.join; -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.platform.commons.util.Preconditions.condition; -import static org.junit.platform.commons.util.Preconditions.notNull; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.List; -import java.util.regex.PatternSyntaxException; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -/** - * {@code AssertLinesMatch} is a collection of utility methods that support asserting - * lines of {@link String} equality or {@link java.util.regex.Pattern}-match in tests. - * - * @since 5.0 - */ -class AssertLinesMatch { - - private AssertLinesMatch() { - /* no-op */ - } - - private static final int MAX_SNIPPET_LENGTH = 21; - - static void assertLinesMatch(List expectedLines, List actualLines) { - assertLinesMatch(expectedLines, actualLines, (Object) null); - } - - static void assertLinesMatch(List expectedLines, List actualLines, String message) { - assertLinesMatch(expectedLines, actualLines, (Object) message); - } - - static void assertLinesMatch(Stream expectedLines, Stream actualLines) { - assertLinesMatch(expectedLines, actualLines, (Object) null); - } - - static void assertLinesMatch(Stream expectedLines, Stream actualLines, String message) { - assertLinesMatch(expectedLines, actualLines, (Object) message); - } - - static void assertLinesMatch(Stream expectedLines, Stream actualLines, Object messageOrSupplier) { - notNull(expectedLines, "expectedLines must not be null"); - notNull(actualLines, "actualLines must not be null"); - - // trivial case: same stream instance - if (expectedLines == actualLines) { - return; - } - - List expectedListOfStrings = expectedLines.collect(Collectors.toList()); - List actualListOfStrings = actualLines.collect(Collectors.toList()); - assertLinesMatch(expectedListOfStrings, actualListOfStrings, messageOrSupplier); - } - - static void assertLinesMatch(List expectedLines, List actualLines, Object messageOrSupplier) { - notNull(expectedLines, "expectedLines must not be null"); - notNull(actualLines, "actualLines must not be null"); - - // trivial case: same list instance - if (expectedLines == actualLines) { - return; - } - - new LinesMatcher(expectedLines, actualLines, messageOrSupplier).assertLinesMatch(); - } - - private static class LinesMatcher { - - private final List expectedLines; - private final List actualLines; - private final Object messageOrSupplier; - - LinesMatcher(List expectedLines, List actualLines, Object messageOrSupplier) { - this.expectedLines = expectedLines; - this.actualLines = actualLines; - this.messageOrSupplier = messageOrSupplier; - } - - void assertLinesMatch() { - int expectedSize = expectedLines.size(); - int actualSize = actualLines.size(); - - // trivial case: when expecting more than actual lines available, something is wrong - if (expectedSize > actualSize) { - fail("expected %d lines, but only got %d", expectedSize, actualSize); - } - - // simple case: both list are equally sized, compare them line-by-line - if (expectedSize == actualSize) { - if (IntStream.range(0, expectedSize).allMatch(i -> matches(expectedLines.get(i), actualLines.get(i)))) { - return; - } - // else fall-through to "with fast-forward" matching - } - - assertLinesMatchWithFastForward(); - } - - void assertLinesMatchWithFastForward() { - Deque expectedDeque = new ArrayDeque<>(expectedLines); - Deque actualDeque = new ArrayDeque<>(actualLines); - - main: while (!expectedDeque.isEmpty()) { - String expectedLine = expectedDeque.pop(); - int expectedLineNumber = expectedLines.size() - expectedDeque.size(); // 1-based line number - // trivial case: no more actual lines available - if (actualDeque.isEmpty()) { - fail("expected line #%d:`%s` not found - actual lines depleted", expectedLineNumber, - snippet(expectedLine)); - } - - String actualLine = actualDeque.peek(); - // trivial case: take the fast path when they match - if (matches(expectedLine, actualLine)) { - actualDeque.pop(); - continue; // main - } - - // fast-forward marker found in expected line: fast-forward actual line... - if (isFastForwardLine(expectedLine)) { - int fastForwardLimit = parseFastForwardLimit(expectedLine); - int actualRemaining = actualDeque.size(); - - // trivial case: fast-forward marker was in last expected line - if (expectedDeque.isEmpty()) { - // no limit given or perfect match? we're done. - if (fastForwardLimit == Integer.MAX_VALUE || fastForwardLimit == actualRemaining) { - return; - } - fail("terminal fast-forward(%d) error: fast-forward(%d) expected", fastForwardLimit, - actualRemaining); - } - - // fast-forward limit was given: use it - if (fastForwardLimit != Integer.MAX_VALUE) { - if (actualRemaining < fastForwardLimit) { - fail("fast-forward(%d) error: not enough actual lines remaining (%s)", fastForwardLimit, - actualRemaining); - } - // fast-forward now: actualDeque.pop(fastForwardLimit) - for (int i = 0; i < fastForwardLimit; i++) { - actualDeque.pop(); - } - continue; // main - } - - // peek next expected line - expectedLine = expectedDeque.peek(); - // fast-forward "unlimited": until next match - while (true) { - if (actualDeque.isEmpty()) { - fail("fast-forward(∞) didn't find: `%s`", snippet(expectedLine)); - } - if (matches(expectedLine, actualDeque.peek())) { - continue main; - } - actualDeque.pop(); - } - } - - int actualLineNumber = actualLines.size() - actualDeque.size() + 1; // 1-based line number - fail("expected line #%d doesn't match actual line #%d%n" + "\texpected: `%s`%n" + "\t actual: `%s`", - expectedLineNumber, actualLineNumber, expectedLine, actualLine); - } - - // after math - if (!actualDeque.isEmpty()) { - fail("more actual lines than expected: %d", actualDeque.size()); - } - } - - String snippet(String line) { - if (line.length() <= MAX_SNIPPET_LENGTH) { - return line; - } - return line.substring(0, MAX_SNIPPET_LENGTH - 5) + "[...]"; - } - - void fail(String format, Object... args) { - String newLine = System.lineSeparator(); - assertionFailure() // - .message(messageOrSupplier) // - .reason(format(format, args)) // - .expected(join(newLine, expectedLines)) // - .actual(join(newLine, actualLines)) // - .includeValuesInMessage(false) // - .buildAndThrow(); - } - } - - static boolean isFastForwardLine(String line) { - line = line.trim(); - return line.length() >= 4 && line.startsWith(">>") && line.endsWith(">>"); - } - - static int parseFastForwardLimit(String fastForwardLine) { - fastForwardLine = fastForwardLine.trim(); - String text = fastForwardLine.substring(2, fastForwardLine.length() - 2).trim(); - try { - int limit = Integer.parseInt(text); - condition(limit > 0, () -> format("fast-forward(%d) limit must be greater than zero", limit)); - return limit; - } - catch (NumberFormatException e) { - return Integer.MAX_VALUE; - } - } - - static boolean matches(String expectedLine, String actualLine) { - notNull(expectedLine, "expected line must not be null"); - notNull(actualLine, "actual line must not be null"); - if (expectedLine.equals(actualLine)) { - return true; - } - try { - return actualLine.matches(expectedLine); - } - catch (PatternSyntaxException ignore) { - return false; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java deleted file mode 100644 index 5a37b059..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; -import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; -import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; - -import java.util.function.Supplier; - -/** - * {@code AssertNotEquals} is a collection of utility methods that support asserting - * inequality in objects and primitive values in tests. - * - * @since 5.0 - */ -class AssertNotEquals { - - private AssertNotEquals() { - /* no-op */ - } - - /** - * @since 5.4 - */ - static void assertNotEquals(byte unexpected, byte actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(byte unexpected, byte actual, String message) { - if (unexpected == actual) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { - if (unexpected == actual) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(short unexpected, short actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(short unexpected, short actual, String message) { - if (unexpected == actual) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { - if (unexpected == actual) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(int unexpected, int actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(int unexpected, int actual, String message) { - if (unexpected == actual) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { - if (unexpected == actual) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(long unexpected, long actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(long unexpected, long actual, String message) { - if (unexpected == actual) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { - if (unexpected == actual) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(float unexpected, float actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(float unexpected, float actual, String message) { - if (floatsAreEqual(unexpected, actual)) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { - if (floatsAreEqual(unexpected, actual)) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(float unexpected, float actual, float delta) { - assertNotEquals(unexpected, actual, delta, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(float unexpected, float actual, float delta, String message) { - if (floatsAreEqual(unexpected, actual, delta)) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { - if (floatsAreEqual(unexpected, actual, delta)) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(double unexpected, double actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(double unexpected, double actual, String message) { - if (doublesAreEqual(unexpected, actual)) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { - if (doublesAreEqual(unexpected, actual)) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(double unexpected, double actual, double delta) { - assertNotEquals(unexpected, actual, delta, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(double unexpected, double actual, double delta, String message) { - if (doublesAreEqual(unexpected, actual, delta)) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(double unexpected, double actual, double delta, Supplier messageSupplier) { - if (doublesAreEqual(unexpected, actual, delta)) { - failEqual(actual, messageSupplier); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(char unexpected, char actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - /** - * @since 5.4 - */ - static void assertNotEquals(char unexpected, char actual, String message) { - if (unexpected == actual) { - failEqual(actual, message); - } - } - - /** - * @since 5.4 - */ - static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { - if (unexpected == actual) { - failEqual(actual, messageSupplier); - } - } - - static void assertNotEquals(Object unexpected, Object actual) { - assertNotEquals(unexpected, actual, (String) null); - } - - static void assertNotEquals(Object unexpected, Object actual, String message) { - if (objectsAreEqual(unexpected, actual)) { - failEqual(actual, message); - } - } - - static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { - if (objectsAreEqual(unexpected, actual)) { - failEqual(actual, messageSupplier); - } - } - - private static void failEqual(Object actual, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("expected: not equal but was: <" + actual + ">") // - .buildAndThrow(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java deleted file mode 100644 index 43787ab8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.Supplier; - -/** - * {@code AssertNotNull} is a collection of utility methods that support asserting - * that there is an object. - * - * @since 5.0 - */ -class AssertNotNull { - - private AssertNotNull() { - /* no-op */ - } - - static void assertNotNull(Object actual) { - assertNotNull(actual, (String) null); - } - - static void assertNotNull(Object actual, String message) { - if (actual == null) { - failNull(message); - } - } - - static void assertNotNull(Object actual, Supplier messageSupplier) { - if (actual == null) { - failNull(messageSupplier); - } - } - - private static void failNull(Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("expected: not ") // - .buildAndThrow(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java deleted file mode 100644 index 66f795c7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.Supplier; - -/** - * {@code AssertNotSame} is a collection of utility methods that support asserting - * two objects are not the same. - * - * @since 5.0 - */ -class AssertNotSame { - - private AssertNotSame() { - /* no-op */ - } - - static void assertNotSame(Object unexpected, Object actual) { - assertNotSame(unexpected, actual, (String) null); - } - - static void assertNotSame(Object unexpected, Object actual, String message) { - if (unexpected == actual) { - failSame(actual, message); - } - } - - static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { - if (unexpected == actual) { - failSame(actual, messageSupplier); - } - } - - private static void failSame(Object actual, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("expected: not same but was: <" + actual + ">") // - .buildAndThrow(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java deleted file mode 100644 index 53ceb4ce..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.Supplier; - -/** - * {@code AssertNull} is a collection of utility methods that support asserting - * there is no object. - * - * @since 5.0 - */ -class AssertNull { - - private AssertNull() { - /* no-op */ - } - - static void assertNull(Object actual) { - assertNull(actual, (String) null); - } - - static void assertNull(Object actual, String message) { - if (actual != null) { - failNotNull(actual, message); - } - } - - static void assertNull(Object actual, Supplier messageSupplier) { - if (actual != null) { - failNotNull(actual, messageSupplier); - } - } - - private static void failNotNull(Object actual, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .expected(null) // - .actual(actual) // - .buildAndThrow(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java deleted file mode 100644 index feafa362..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.Supplier; - -/** - * {@code AssertSame} is a collection of utility methods that support asserting - * two objects are the same. - * - * @since 5.0 - */ -class AssertSame { - - private AssertSame() { - /* no-op */ - } - - static void assertSame(Object expected, Object actual) { - assertSame(expected, actual, (String) null); - } - - static void assertSame(Object expected, Object actual, String message) { - if (expected != actual) { - failNotSame(expected, actual, message); - } - } - - static void assertSame(Object expected, Object actual, Supplier messageSupplier) { - if (expected != actual) { - failNotSame(expected, actual, messageSupplier); - } - } - - private static void failNotSame(Object expected, Object actual, Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .expected(expected) // - .actual(actual) // - .buildAndThrow(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java deleted file mode 100644 index b6157064..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.lang.String.format; -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; - -import java.util.function.Supplier; - -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.util.UnrecoverableExceptions; - -/** - * {@code AssertThrows} is a collection of utility methods that support asserting - * an exception of an expected type is thrown. - * - * @since 5.0 - */ -class AssertThrows { - - private AssertThrows() { - /* no-op */ - } - - static T assertThrows(Class expectedType, Executable executable) { - return assertThrows(expectedType, executable, (Object) null); - } - - static T assertThrows(Class expectedType, Executable executable, String message) { - return assertThrows(expectedType, executable, (Object) message); - } - - static T assertThrows(Class expectedType, Executable executable, - Supplier messageSupplier) { - - return assertThrows(expectedType, executable, (Object) messageSupplier); - } - - @SuppressWarnings("unchecked") - private static T assertThrows(Class expectedType, Executable executable, - Object messageOrSupplier) { - - try { - executable.execute(); - } - catch (Throwable actualException) { - if (expectedType.isInstance(actualException)) { - return (T) actualException; - } - else { - UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); - throw assertionFailure() // - .message(messageOrSupplier) // - .expected(expectedType) // - .actual(actualException.getClass()) // - .reason("Unexpected exception type thrown") // - .cause(actualException) // - .build(); - } - } - throw assertionFailure() // - .message(messageOrSupplier) // - .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // - .build(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java deleted file mode 100644 index 8a669388..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.lang.String.format; -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; - -import java.util.function.Supplier; - -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.util.UnrecoverableExceptions; - -/** - * {@code AssertThrowsExactly} is a collection of utility methods that support asserting - * an exception of an exact type is thrown. - * - * @since 5.8 - */ -class AssertThrowsExactly { - - private AssertThrowsExactly() { - /* no-op */ - } - - static T assertThrowsExactly(Class expectedType, Executable executable) { - return assertThrowsExactly(expectedType, executable, (Object) null); - } - - static T assertThrowsExactly(Class expectedType, Executable executable, String message) { - return assertThrowsExactly(expectedType, executable, (Object) message); - } - - static T assertThrowsExactly(Class expectedType, Executable executable, - Supplier messageSupplier) { - - return assertThrowsExactly(expectedType, executable, (Object) messageSupplier); - } - - @SuppressWarnings("unchecked") - private static T assertThrowsExactly(Class expectedType, Executable executable, - Object messageOrSupplier) { - - try { - executable.execute(); - } - catch (Throwable actualException) { - if (expectedType.equals(actualException.getClass())) { - return (T) actualException; - } - else { - UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); - throw assertionFailure() // - .message(messageOrSupplier) // - .expected(expectedType) // - .actual(actualException.getClass()) // - .reason("Unexpected exception type thrown") // - .cause(actualException) // - .build(); - } - } - - throw assertionFailure() // - .message(messageOrSupplier) // - .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // - .build(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java deleted file mode 100644 index bfd1f668..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; - -import java.time.Duration; -import java.util.function.Supplier; - -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.function.ThrowingSupplier; - -/** - * {@code AssertTimeout} is a collection of utility methods that support asserting - * the execution of the code under test did not take longer than the timeout duration. - * - * @since 5.0 - */ -class AssertTimeout { - - private AssertTimeout() { - /* no-op */ - } - - static void assertTimeout(Duration timeout, Executable executable) { - assertTimeout(timeout, executable, (String) null); - } - - static void assertTimeout(Duration timeout, Executable executable, String message) { - assertTimeout(timeout, () -> { - executable.execute(); - return null; - }, message); - } - - static void assertTimeout(Duration timeout, Executable executable, Supplier messageSupplier) { - assertTimeout(timeout, () -> { - executable.execute(); - return null; - }, messageSupplier); - } - - static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { - return assertTimeout(timeout, supplier, (Object) null); - } - - static T assertTimeout(Duration timeout, ThrowingSupplier supplier, String message) { - return assertTimeout(timeout, supplier, (Object) message); - } - - static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { - return assertTimeout(timeout, supplier, (Object) messageSupplier); - } - - private static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { - long timeoutInMillis = timeout.toMillis(); - long start = System.currentTimeMillis(); - T result = null; - try { - result = supplier.get(); - } - catch (Throwable ex) { - throwAsUncheckedException(ex); - } - - long timeElapsed = System.currentTimeMillis() - start; - if (timeElapsed > timeoutInMillis) { - assertionFailure() // - .message(messageOrSupplier) // - .reason("execution exceeded timeout of " + timeoutInMillis + " ms by " - + (timeElapsed - timeoutInMillis) + " ms") // - .buildAndThrow(); - } - return result; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java deleted file mode 100644 index 4ff96b71..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; -import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; - -import java.time.Duration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.function.ThrowingSupplier; -import org.junit.platform.commons.JUnitException; -import org.opentest4j.AssertionFailedError; - -/** - * {@code AssertTimeout} is a collection of utility methods that support asserting - * the execution of the code under test did not take longer than the timeout duration - * using a preemptive approach. - * - * @since 5.9.1 - */ -class AssertTimeoutPreemptively { - - static void assertTimeoutPreemptively(Duration timeout, Executable executable) { - assertTimeoutPreemptively(timeout, executable, (String) null); - } - - static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { - assertTimeoutPreemptively(timeout, () -> { - executable.execute(); - return null; - }, message); - } - - static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { - assertTimeoutPreemptively(timeout, () -> { - executable.execute(); - return null; - }, messageSupplier); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, - AssertTimeoutPreemptively::createAssertionFailure); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { - return assertTimeoutPreemptively(timeout, supplier, messageSupplier, - AssertTimeoutPreemptively::createAssertionFailure); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier, Assertions.TimeoutFailureFactory failureFactory) throws E { - AtomicReference threadReference = new AtomicReference<>(); - ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); - - try { - Future future = submitTask(supplier, threadReference, executorService); - return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, - failureFactory); - } - finally { - executorService.shutdownNow(); - } - } - - private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, - ExecutorService executorService) { - return executorService.submit(() -> { - try { - threadReference.set(Thread.currentThread()); - return supplier.get(); - } - catch (Throwable throwable) { - throw throwAsUncheckedException(throwable); - } - }); - } - - private static T resolveFutureAndHandleException(Future future, Duration timeout, - Supplier messageSupplier, Supplier threadSupplier, - Assertions.TimeoutFailureFactory failureFactory) throws E { - try { - return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); - } - catch (TimeoutException ex) { - Thread thread = threadSupplier.get(); - ExecutionTimeoutException cause = null; - if (thread != null) { - cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); - cause.setStackTrace(thread.getStackTrace()); - } - throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); - } - catch (ExecutionException ex) { - throw throwAsUncheckedException(ex.getCause()); - } - catch (Throwable ex) { - throw throwAsUncheckedException(ex); - } - } - - private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, - Throwable cause) { - return assertionFailure() // - .message(messageSupplier) // - .reason("execution timed out after " + timeout.toMillis() + " ms") // - .cause(cause) // - .build(); - } - - private static class ExecutionTimeoutException extends JUnitException { - - private static final long serialVersionUID = 1L; - - ExecutionTimeoutException(String message) { - super(message); - } - } - - /** - * The thread factory used for preemptive timeout. - *

- * The factory creates threads with meaningful names, helpful for debugging purposes. - */ - private static class TimeoutThreadFactory implements ThreadFactory { - private static final AtomicInteger threadNumber = new AtomicInteger(1); - - public Thread newThread(Runnable r) { - return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java deleted file mode 100644 index cf4f9427..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; - -import java.util.function.BooleanSupplier; -import java.util.function.Supplier; - -/** - * {@code AssertTrue} is a collection of utility methods that support asserting - * {@code true} in tests. - * - * @since 5.0 - */ -class AssertTrue { - - private AssertTrue() { - /* no-op */ - } - - static void assertTrue(boolean condition) { - assertTrue(condition, (String) null); - } - - static void assertTrue(boolean condition, String message) { - if (!condition) { - failNotTrue(message); - } - } - - static void assertTrue(boolean condition, Supplier messageSupplier) { - if (!condition) { - failNotTrue(messageSupplier); - } - } - - static void assertTrue(BooleanSupplier booleanSupplier) { - assertTrue(booleanSupplier.getAsBoolean(), (String) null); - } - - static void assertTrue(BooleanSupplier booleanSupplier, String message) { - assertTrue(booleanSupplier.getAsBoolean(), message); - } - - static void assertTrue(BooleanSupplier booleanSupplier, Supplier messageSupplier) { - assertTrue(booleanSupplier.getAsBoolean(), messageSupplier); - } - - private static void failNotTrue(Object messageOrSupplier) { - assertionFailure() // - .message(messageOrSupplier) // - .expected(true) // - .actual(false) // - .buildAndThrow(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java deleted file mode 100644 index 8d0341d3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; - -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.StringUtils; -import org.opentest4j.AssertionFailedError; - -/** - * Builder for {@link AssertionFailedError AssertionFailedErrors}. - *

- * Using this builder ensures consistency in how failure message are formatted - * within JUnit Jupiter and for custom user-defined assertions. - * - * @since 5.9 - * @see AssertionFailedError - */ -@API(status = STABLE, since = "5.9") -public class AssertionFailureBuilder { - - private Object message; - private Throwable cause; - private boolean mismatch; - private Object expected; - private Object actual; - private String reason; - private boolean includeValuesInMessage = true; - - /** - * Create a new {@code AssertionFailureBuilder}. - */ - public static AssertionFailureBuilder assertionFailure() { - return new AssertionFailureBuilder(); - } - - private AssertionFailureBuilder() { - } - - /** - * Set the user-defined message of the assertion. - *

- * The {@code message} may be passed as a {@link Supplier} or plain - * {@link String}. If any other type is passed, it is converted to - * {@code String} as per {@link StringUtils#nullSafeToString(Object)}. - * - * @param message the user-defined failure message; may be {@code null} - * @return this builder for method chaining - */ - public AssertionFailureBuilder message(Object message) { - this.message = message; - return this; - } - - /** - * Set the reason why the assertion failed. - * - * @param reason the failure reason; may be {@code null} - * @return this builder for method chaining - */ - public AssertionFailureBuilder reason(String reason) { - this.reason = reason; - return this; - } - - /** - * Set the cause of the assertion failure. - * - * @param cause the failure cause; may be {@code null} - * @return this builder for method chaining - */ - public AssertionFailureBuilder cause(Throwable cause) { - this.cause = cause; - return this; - } - - /** - * Set the expected value of the assertion. - * - * @param expected the expected value; may be {@code null} - * @return this builder for method chaining - */ - public AssertionFailureBuilder expected(Object expected) { - this.mismatch = true; - this.expected = expected; - return this; - } - - /** - * Set the actual value of the assertion. - * - * @param actual the actual value; may be {@code null} - * @return this builder for method chaining - */ - public AssertionFailureBuilder actual(Object actual) { - this.mismatch = true; - this.actual = actual; - return this; - } - - /** - * Set whether to include the actual and expected values in the generated - * failure message. - * - * @param includeValuesInMessage whether to include the actual and expected - * values - * @return this builder for method chaining - */ - public AssertionFailureBuilder includeValuesInMessage(boolean includeValuesInMessage) { - this.includeValuesInMessage = includeValuesInMessage; - return this; - } - - /** - * Build the {@link AssertionFailedError AssertionFailedError} and throw it. - * - * @throws AssertionFailedError always - */ - public void buildAndThrow() throws AssertionFailedError { - throw build(); - } - - /** - * Build the {@link AssertionFailedError AssertionFailedError} without - * throwing it. - * - * @return the built assertion failure - */ - public AssertionFailedError build() { - String reason = nullSafeGet(this.reason); - if (mismatch && includeValuesInMessage) { - reason = (reason == null ? "" : reason + ", ") + formatValues(expected, actual); - } - String message = nullSafeGet(this.message); - if (reason != null) { - message = buildPrefix(message) + reason; - } - return mismatch // - ? new AssertionFailedError(message, expected, actual, cause) // - : new AssertionFailedError(message, cause); - } - - private static String nullSafeGet(Object messageOrSupplier) { - if (messageOrSupplier == null) { - return null; - } - if (messageOrSupplier instanceof Supplier) { - Object message = ((Supplier) messageOrSupplier).get(); - return StringUtils.nullSafeToString(message); - } - return StringUtils.nullSafeToString(messageOrSupplier); - } - - private static String buildPrefix(String message) { - return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); - } - - private static String formatValues(Object expected, Object actual) { - String expectedString = toString(expected); - String actualString = toString(actual); - if (expectedString.equals(actualString)) { - return String.format("expected: %s but was: %s", formatClassAndValue(expected, expectedString), - formatClassAndValue(actual, actualString)); - } - return String.format("expected: <%s> but was: <%s>", expectedString, actualString); - } - - private static String formatClassAndValue(Object value, String valueString) { - // If the value is null, return instead of null. - if (value == null) { - return ""; - } - String classAndHash = getClassName(value) + toHash(value); - // if it's a class, there's no need to repeat the class name contained in the valueString. - return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); - } - - private static String toString(Object obj) { - if (obj instanceof Class) { - return getCanonicalName((Class) obj); - } - return StringUtils.nullSafeToString(obj); - } - - private static String toHash(Object obj) { - return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); - } - - private static String getClassName(Object obj) { - return (obj == null ? "null" - : obj instanceof Class ? getCanonicalName((Class) obj) : obj.getClass().getName()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java deleted file mode 100644 index 48a74da2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.util.stream.Collectors.joining; - -import java.util.Deque; -import java.util.function.Supplier; - -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.opentest4j.AssertionFailedError; - -/** - * {@code AssertionUtils} is a collection of utility methods that are common to - * all assertion implementations. - * - * @since 5.0 - */ -class AssertionUtils { - - private AssertionUtils() { - /* no-op */ - } - - static void fail() { - throw new AssertionFailedError(); - } - - static void fail(String message) { - throw new AssertionFailedError(message); - } - - static void fail(String message, Throwable cause) { - throw new AssertionFailedError(message, cause); - } - - static void fail(Throwable cause) { - throw new AssertionFailedError(null, cause); - } - - static void fail(Supplier messageSupplier) { - throw new AssertionFailedError(nullSafeGet(messageSupplier)); - } - - static String nullSafeGet(Supplier messageSupplier) { - return (messageSupplier != null ? messageSupplier.get() : null); - } - - static String getCanonicalName(Class clazz) { - try { - String canonicalName = clazz.getCanonicalName(); - return (canonicalName != null ? canonicalName : clazz.getName()); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - return clazz.getName(); - } - } - - static String formatIndexes(Deque indexes) { - if (indexes == null || indexes.isEmpty()) { - return ""; - } - String indexesString = indexes.stream().map(Object::toString).collect(joining("][", "[", "]")); - return " at index " + indexesString; - } - - static boolean floatsAreEqual(float value1, float value2, float delta) { - assertValidDelta(delta); - return floatsAreEqual(value1, value2) || Math.abs(value1 - value2) <= delta; - } - - static void assertValidDelta(float delta) { - if (Float.isNaN(delta) || delta < 0.0) { - failIllegalDelta(String.valueOf(delta)); - } - } - - static void assertValidDelta(double delta) { - if (Double.isNaN(delta) || delta < 0.0) { - failIllegalDelta(String.valueOf(delta)); - } - } - - static boolean floatsAreEqual(float value1, float value2) { - return Float.floatToIntBits(value1) == Float.floatToIntBits(value2); - } - - static boolean doublesAreEqual(double value1, double value2, double delta) { - assertValidDelta(delta); - return doublesAreEqual(value1, value2) || Math.abs(value1 - value2) <= delta; - } - - static boolean doublesAreEqual(double value1, double value2) { - return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2); - } - - static boolean objectsAreEqual(Object obj1, Object obj2) { - if (obj1 == null) { - return (obj2 == null); - } - return obj1.equals(obj2); - } - - private static void failIllegalDelta(String delta) { - fail("positive delta expected but was: <" + delta + ">"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java deleted file mode 100644 index 1240caa5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java +++ /dev/null @@ -1,3625 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.time.Duration; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.function.BooleanSupplier; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.function.ThrowingSupplier; -import org.opentest4j.MultipleFailuresError; - -/** - * {@code Assertions} is a collection of utility methods that support asserting - * conditions in tests. - * - *

Unless otherwise noted, a failed assertion will throw an - * {@link org.opentest4j.AssertionFailedError} or a subclass thereof. - * - *

Object Equality

- * - *

Assertion methods comparing two objects for equality, such as the - * {@code assertEquals(expected, actual)} and {@code assertNotEquals(unexpected, actual)} - * variants, are only intended to test equality for an (un-)expected value - * and an actual value. They are not designed for testing whether a class correctly - * implements {@link Object#equals(Object)}. For example, {@code assertEquals()} - * might immediately return {@code true} when provided the same object for the - * expected and actual values, without calling {@code equals(Object)} at all. - * Tests that aim to verify the {@code equals(Object)} implementation should instead - * be written to explicitly verify the {@link Object#equals(Object)} contract by - * using {@link #assertTrue(boolean) assertTrue()} or {@link #assertFalse(boolean) - * assertFalse()} — for example, {@code assertTrue(expected.equals(actual))}, - * {@code assertTrue(actual.equals(expected))}, {@code assertFalse(expected.equals(null))}, - * etc. - * - *

Kotlin Support

- * - *

Additional Kotlin assertions can be - * found as top-level functions in the {@link org.junit.jupiter.api} - * package. - * - *

Preemptive Timeouts

- * - *

The various {@code assertTimeoutPreemptively()} methods in this class - * execute the provided {@code executable} or {@code supplier} in a different - * thread than that of the calling code. This behavior can lead to undesirable - * side effects if the code that is executed within the {@code executable} or - * {@code supplier} relies on {@link ThreadLocal} storage. - * - *

One common example of this is the transactional testing support in the Spring - * Framework. Specifically, Spring's testing support binds transaction state to - * the current thread (via a {@code ThreadLocal}) before a test method is invoked. - * Consequently, if an {@code executable} or {@code supplier} provided to - * {@code assertTimeoutPreemptively()} invokes Spring-managed components that - * participate in transactions, any actions taken by those components will not be - * rolled back with the test-managed transaction. On the contrary, such actions - * will be committed to the persistent store (e.g., relational database) even - * though the test-managed transaction is rolled back. - * - *

Similar side effects may be encountered with other frameworks that rely on - * {@code ThreadLocal} storage. - * - *

Extensibility

- * - *

Although it is technically possible to extend this class, extension is - * strongly discouraged. The JUnit Team highly recommends that the methods - * defined in this class be used via static imports. - * - * @since 5.0 - * @see org.opentest4j.AssertionFailedError - * @see Assumptions - */ -@API(status = STABLE, since = "5.0") -public class Assertions { - - /** - * Protected constructor allowing subclassing but not direct instantiation. - * - * @since 5.3 - */ - @API(status = STABLE, since = "5.3") - protected Assertions() { - /* no-op */ - } - - // --- fail ---------------------------------------------------------------- - - /** - * Fail the test without a failure message. - * - *

Although failing with an explicit failure message is recommended, - * this method may be useful when maintaining legacy code. - * - *

See Javadoc for {@link #fail(String)} for an explanation of this method's - * generic return type {@code V}. - */ - public static V fail() { - AssertionUtils.fail(); - return null; // appeasing the compiler: this line will never be executed. - } - - /** - * Fail the test with the given failure {@code message}. - * - *

The generic return type {@code V} allows this method to be used - * directly as a single-statement lambda expression, thereby avoiding the - * need to implement a code block with an explicit return value. Since this - * method throws an {@link org.opentest4j.AssertionFailedError} before its - * return statement, this method never actually returns a value to its caller. - * The following example demonstrates how this may be used in practice. - * - *

{@code
-	 * Stream.of().map(entry -> fail("should not be called"));
-	 * }
- */ - public static V fail(String message) { - AssertionUtils.fail(message); - return null; // appeasing the compiler: this line will never be executed. - } - - /** - * Fail the test with the given failure {@code message} as well - * as the underlying {@code cause}. - * - *

See Javadoc for {@link #fail(String)} for an explanation of this method's - * generic return type {@code V}. - */ - public static V fail(String message, Throwable cause) { - AssertionUtils.fail(message, cause); - return null; // appeasing the compiler: this line will never be executed. - } - - /** - * Fail the test with the given underlying {@code cause}. - * - *

See Javadoc for {@link #fail(String)} for an explanation of this method's - * generic return type {@code V}. - */ - public static V fail(Throwable cause) { - AssertionUtils.fail(cause); - return null; // appeasing the compiler: this line will never be executed. - } - - /** - * Fail the test with the failure message retrieved from the - * given {@code messageSupplier}. - * - *

See Javadoc for {@link #fail(String)} for an explanation of this method's - * generic return type {@code V}. - */ - public static V fail(Supplier messageSupplier) { - AssertionUtils.fail(messageSupplier); - return null; // appeasing the compiler: this line will never be executed. - } - - // --- assertTrue ---------------------------------------------------------- - - /** - * Assert that the supplied {@code condition} is {@code true}. - */ - public static void assertTrue(boolean condition) { - AssertTrue.assertTrue(condition); - } - - /** - * Assert that the supplied {@code condition} is {@code true}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertTrue(boolean condition, Supplier messageSupplier) { - AssertTrue.assertTrue(condition, messageSupplier); - } - - /** - * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. - */ - public static void assertTrue(BooleanSupplier booleanSupplier) { - AssertTrue.assertTrue(booleanSupplier); - } - - /** - * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertTrue(BooleanSupplier booleanSupplier, String message) { - AssertTrue.assertTrue(booleanSupplier, message); - } - - /** - * Assert that the supplied {@code condition} is {@code true}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertTrue(boolean condition, String message) { - AssertTrue.assertTrue(condition, message); - } - - /** - * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertTrue(BooleanSupplier booleanSupplier, Supplier messageSupplier) { - AssertTrue.assertTrue(booleanSupplier, messageSupplier); - } - - // --- assertFalse --------------------------------------------------------- - - /** - * Assert that the supplied {@code condition} is {@code false}. - */ - public static void assertFalse(boolean condition) { - AssertFalse.assertFalse(condition); - } - - /** - * Assert that the supplied {@code condition} is {@code false}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertFalse(boolean condition, String message) { - AssertFalse.assertFalse(condition, message); - } - - /** - * Assert that the supplied {@code condition} is {@code false}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertFalse(boolean condition, Supplier messageSupplier) { - AssertFalse.assertFalse(condition, messageSupplier); - } - - /** - * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. - */ - public static void assertFalse(BooleanSupplier booleanSupplier) { - AssertFalse.assertFalse(booleanSupplier); - } - - /** - * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertFalse(BooleanSupplier booleanSupplier, String message) { - AssertFalse.assertFalse(booleanSupplier, message); - } - - /** - * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertFalse(BooleanSupplier booleanSupplier, Supplier messageSupplier) { - AssertFalse.assertFalse(booleanSupplier, messageSupplier); - } - - // --- assertNull ---------------------------------------------------------- - - /** - * Assert that {@code actual} is {@code null}. - */ - public static void assertNull(Object actual) { - AssertNull.assertNull(actual); - } - - /** - * Assert that {@code actual} is {@code null}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertNull(Object actual, String message) { - AssertNull.assertNull(actual, message); - } - - /** - * Assert that {@code actual} is {@code null}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertNull(Object actual, Supplier messageSupplier) { - AssertNull.assertNull(actual, messageSupplier); - } - - // --- assertNotNull ------------------------------------------------------- - - /** - * Assert that {@code actual} is not {@code null}. - */ - public static void assertNotNull(Object actual) { - AssertNotNull.assertNotNull(actual); - } - - /** - * Assert that {@code actual} is not {@code null}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertNotNull(Object actual, String message) { - AssertNotNull.assertNotNull(actual, message); - } - - /** - * Assert that {@code actual} is not {@code null}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertNotNull(Object actual, Supplier messageSupplier) { - AssertNotNull.assertNotNull(actual, messageSupplier); - } - - // --- assertEquals -------------------------------------------------------- - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(short expected, short actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(short expected, Short actual) { - AssertEquals.assertEquals((Short) expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(Short expected, short actual) { - AssertEquals.assertEquals(expected, (Short) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Short expected, Short actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(short expected, short actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(short expected, Short actual, String message) { - AssertEquals.assertEquals((Short) expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(Short expected, short actual, String message) { - AssertEquals.assertEquals(expected, (Short) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Short expected, Short actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(short expected, short actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(short expected, Short actual, Supplier messageSupplier) { - AssertEquals.assertEquals((Short) expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(Short expected, short actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, (Short) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Short expected, Short actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(byte expected, byte actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(byte expected, Byte actual) { - AssertEquals.assertEquals((Byte) expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(Byte expected, byte actual) { - AssertEquals.assertEquals(expected, (Byte) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Byte expected, Byte actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(byte expected, byte actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(byte expected, Byte actual, String message) { - AssertEquals.assertEquals((Byte) expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(Byte expected, byte actual, String message) { - AssertEquals.assertEquals(expected, (Byte) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Byte expected, Byte actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(byte expected, byte actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(byte expected, Byte actual, Supplier messageSupplier) { - AssertEquals.assertEquals((Byte) expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(Byte expected, byte actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, (Byte) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Byte expected, Byte actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(int expected, int actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(int expected, Integer actual) { - AssertEquals.assertEquals((Integer) expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(Integer expected, int actual) { - AssertEquals.assertEquals(expected, (Integer) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Integer expected, Integer actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(int expected, int actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(int expected, Integer actual, String message) { - AssertEquals.assertEquals((Integer) expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(Integer expected, int actual, String message) { - AssertEquals.assertEquals(expected, (Integer) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Integer expected, Integer actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(int expected, int actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(int expected, Integer actual, Supplier messageSupplier) { - AssertEquals.assertEquals((Integer) expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(Integer expected, int actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, (Integer) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Integer expected, Integer actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(long expected, long actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(long expected, Long actual) { - AssertEquals.assertEquals((Long) expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(Long expected, long actual) { - AssertEquals.assertEquals(expected, (Long) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Long expected, Long actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(long expected, long actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(long expected, Long actual, String message) { - AssertEquals.assertEquals((Long) expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(Long expected, long actual, String message) { - AssertEquals.assertEquals(expected, (Long) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Long expected, Long actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(long expected, long actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(long expected, Long actual, Supplier messageSupplier) { - AssertEquals.assertEquals((Long) expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(Long expected, long actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, (Long) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Long expected, Long actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - */ - public static void assertEquals(float expected, float actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - */ - public static void assertEquals(float expected, Float actual) { - AssertEquals.assertEquals((Float) expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - */ - public static void assertEquals(Float expected, float actual) { - AssertEquals.assertEquals(expected, (Float) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Float expected, Float actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(float expected, float actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(float expected, Float actual, String message) { - AssertEquals.assertEquals((Float) expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(Float expected, float actual, String message) { - AssertEquals.assertEquals(expected, (Float) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Float expected, Float actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(float expected, float actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(float expected, Float actual, Supplier messageSupplier) { - AssertEquals.assertEquals((Float) expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(Float expected, float actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, (Float) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Float expected, Float actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - */ - public static void assertEquals(float expected, float actual, float delta) { - AssertEquals.assertEquals(expected, actual, delta); - } - - /** - * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(float expected, float actual, float delta, String message) { - AssertEquals.assertEquals(expected, actual, delta, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(float expected, float actual, float delta, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, delta, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - */ - public static void assertEquals(double expected, double actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - */ - public static void assertEquals(double expected, Double actual) { - AssertEquals.assertEquals((Double) expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - */ - public static void assertEquals(Double expected, double actual) { - AssertEquals.assertEquals(expected, (Double) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Double expected, Double actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(double expected, double actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(double expected, Double actual, String message) { - AssertEquals.assertEquals((Double) expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(Double expected, double actual, String message) { - AssertEquals.assertEquals(expected, (Double) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Double expected, Double actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(double expected, double actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(double expected, Double actual, Supplier messageSupplier) { - AssertEquals.assertEquals((Double) expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(Double expected, double actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, (Double) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Double expected, Double actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - */ - public static void assertEquals(double expected, double actual, double delta) { - AssertEquals.assertEquals(expected, actual, delta); - } - - /** - * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(double expected, double actual, double delta, String message) { - AssertEquals.assertEquals(expected, actual, delta, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(double expected, double actual, double delta, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, delta, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(char expected, char actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(char expected, Character actual) { - AssertEquals.assertEquals((Character) expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - */ - public static void assertEquals(Character expected, char actual) { - AssertEquals.assertEquals(expected, (Character) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Character expected, Character actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(char expected, char actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(char expected, Character actual, String message) { - AssertEquals.assertEquals((Character) expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertEquals(Character expected, char actual, String message) { - AssertEquals.assertEquals(expected, (Character) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Character expected, Character actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(char expected, char actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(char expected, Character actual, Supplier messageSupplier) { - AssertEquals.assertEquals((Character) expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertEquals(Character expected, char actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, (Character) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertEquals(Character expected, Character actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If both are {@code null}, they are considered equal. - * - * @see Object#equals(Object) - */ - public static void assertEquals(Object expected, Object actual) { - AssertEquals.assertEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If both are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - * - * @see Object#equals(Object) - */ - public static void assertEquals(Object expected, Object actual, String message) { - AssertEquals.assertEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are equal. - *

If both are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @see Object#equals(Object) - */ - public static void assertEquals(Object expected, Object actual, Supplier messageSupplier) { - AssertEquals.assertEquals(expected, actual, messageSupplier); - } - - // --- assertArrayEquals --------------------------------------------------- - - /** - * Assert that {@code expected} and {@code actual} boolean arrays are equal. - *

If both are {@code null}, they are considered equal. - */ - public static void assertArrayEquals(boolean[] expected, boolean[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} boolean arrays are equal. - *

If both are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(boolean[] expected, boolean[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} boolean arrays are equal. - *

If both are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(boolean[] expected, boolean[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} char arrays are equal. - *

If both are {@code null}, they are considered equal. - */ - public static void assertArrayEquals(char[] expected, char[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} char arrays are equal. - *

If both are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(char[] expected, char[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} char arrays are equal. - *

If both are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(char[] expected, char[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} byte arrays are equal. - *

If both are {@code null}, they are considered equal. - */ - public static void assertArrayEquals(byte[] expected, byte[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} byte arrays are equal. - *

If both are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(byte[] expected, byte[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} byte arrays are equal. - *

If both are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(byte[] expected, byte[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} short arrays are equal. - *

If both are {@code null}, they are considered equal. - */ - public static void assertArrayEquals(short[] expected, short[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} short arrays are equal. - *

If both are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(short[] expected, short[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} short arrays are equal. - *

If both are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(short[] expected, short[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} int arrays are equal. - *

If both are {@code null}, they are considered equal. - */ - public static void assertArrayEquals(int[] expected, int[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} int arrays are equal. - *

If both are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(int[] expected, int[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} int arrays are equal. - *

If both are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(int[] expected, int[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} long arrays are equal. - *

If both are {@code null}, they are considered equal. - */ - public static void assertArrayEquals(long[] expected, long[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} long arrays are equal. - *

If both are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(long[] expected, long[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} long arrays are equal. - *

If both are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(long[] expected, long[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} float arrays are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - */ - public static void assertArrayEquals(float[] expected, float[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} float arrays are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(float[] expected, float[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} float arrays are equal. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(float[] expected, float[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - */ - public static void assertArrayEquals(float[] expected, float[] actual, float delta) { - AssertArrayEquals.assertArrayEquals(expected, actual, delta); - } - - /** - * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(float[] expected, float[] actual, float delta, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); - } - - /** - * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and - * {@link Float#compare(float, float)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(float[] expected, float[] actual, float delta, - Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} double arrays are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - */ - public static void assertArrayEquals(double[] expected, double[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} double arrays are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(double[] expected, double[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} double arrays are equal. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(double[] expected, double[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - */ - public static void assertArrayEquals(double[] expected, double[] actual, double delta) { - AssertArrayEquals.assertArrayEquals(expected, actual, delta); - } - - /** - * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

Fails with the supplied failure {@code message}. - */ - public static void assertArrayEquals(double[] expected, double[] actual, double delta, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); - } - - /** - * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. - *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and - * {@link Double#compare(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertArrayEquals(double[] expected, double[] actual, double delta, - Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} object arrays are deeply equal. - *

If both are {@code null}, they are considered equal. - *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. - *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. - * - * @see Objects#equals(Object, Object) - * @see Arrays#deepEquals(Object[], Object[]) - */ - public static void assertArrayEquals(Object[] expected, Object[] actual) { - AssertArrayEquals.assertArrayEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} object arrays are deeply equal. - *

If both are {@code null}, they are considered equal. - *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. - *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. - *

Fails with the supplied failure {@code message}. - * - * @see Objects#equals(Object, Object) - * @see Arrays#deepEquals(Object[], Object[]) - */ - public static void assertArrayEquals(Object[] expected, Object[] actual, String message) { - AssertArrayEquals.assertArrayEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} object arrays are deeply equal. - *

If both are {@code null}, they are considered equal. - *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. - *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @see Objects#equals(Object, Object) - * @see Arrays#deepEquals(Object[], Object[]) - */ - public static void assertArrayEquals(Object[] expected, Object[] actual, Supplier messageSupplier) { - AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); - } - - // --- assertIterableEquals -------------------------------------------- - - /** - * Assert that {@code expected} and {@code actual} iterables are deeply equal. - *

Similarly to the check for deep equality in {@link #assertArrayEquals(Object[], Object[])}, - * if two iterables are encountered (including {@code expected} and {@code actual}) then their - * iterators must return equal elements in the same order as each other. Note: - * this means that the iterables do not need to be of the same type. Example:

{@code
-	 * import static java.util.Arrays.asList;
-	 *  . . .
-	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
-	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
-	 * assertIterableEquals(i0, i1); // Passes
-	 * }
- *

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. - * - * @see Objects#equals(Object, Object) - * @see Arrays#deepEquals(Object[], Object[]) - * @see #assertArrayEquals(Object[], Object[]) - */ - public static void assertIterableEquals(Iterable expected, Iterable actual) { - AssertIterableEquals.assertIterableEquals(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} iterables are deeply equal. - *

Similarly to the check for deep equality in - * {@link #assertArrayEquals(Object[], Object[], String)}, if two iterables are encountered - * (including {@code expected} and {@code actual}) then their iterators must return equal - * elements in the same order as each other. Note: this means that the iterables - * do not need to be of the same type. Example:

{@code
-	 * import static java.util.Arrays.asList;
-	 *  . . .
-	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
-	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
-	 * assertIterableEquals(i0, i1); // Passes
-	 * }
- *

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. - *

Fails with the supplied failure {@code message}. - * - * @see Objects#equals(Object, Object) - * @see Arrays#deepEquals(Object[], Object[]) - * @see #assertArrayEquals(Object[], Object[], String) - */ - public static void assertIterableEquals(Iterable expected, Iterable actual, String message) { - AssertIterableEquals.assertIterableEquals(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} iterables are deeply equal. - *

Similarly to the check for deep equality in - * {@link #assertArrayEquals(Object[], Object[], Supplier)}, if two iterables are encountered - * (including {@code expected} and {@code actual}) then their iterators must return equal - * elements in the same order as each other. Note: this means that the iterables - * do not need to be of the same type. Example:

{@code
-	 * import static java.util.Arrays.asList;
-	 *  . . .
-	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
-	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
-	 * assertIterableEquals(i0, i1); // Passes
-	 * }
- *

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - * - * @see Objects#equals(Object, Object) - * @see Arrays#deepEquals(Object[], Object[]) - * @see #assertArrayEquals(Object[], Object[], Supplier) - */ - public static void assertIterableEquals(Iterable expected, Iterable actual, - Supplier messageSupplier) { - AssertIterableEquals.assertIterableEquals(expected, actual, messageSupplier); - } - - // --- assertLinesMatch ---------------------------------------------------- - - /** - * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} - * list. - * - *

This method differs from other assertions that effectively only check {@link String#equals(Object)}, - * in that it uses the following staged matching algorithm: - * - *

For each pair of expected and actual lines do - *

    - *
  1. check if {@code expected.equals(actual)} - if yes, continue with next pair
  2. - *
  3. otherwise treat {@code expected} as a regular expression and check via - * {@link String#matches(String)} - if yes, continue with next pair
  4. - *
  5. otherwise check if {@code expected} line is a fast-forward marker, if yes apply - * fast-forward actual lines accordingly (see below) and goto 1.
  6. - *
- * - *

A valid fast-forward marker is an expected line that starts and ends with the literal - * {@code >>} and contains at least 4 characters. Examples: - *

    - *
  • {@code >>>>}
    {@code >> stacktrace >>}
    {@code >> single line, non Integer.parse()-able comment >>} - *
    Skip arbitrary number of actual lines, until first matching subsequent expected line is found. Any - * character between the fast-forward literals are discarded.
  • - *
  • {@code ">> 21 >>"} - *
    Skip strictly 21 lines. If they can't be skipped for any reason, an assertion error is raised.
  • - *
- * - *

Here is an example showing all three kinds of expected line formats: - *

{@code
-	 * ls -la /
-	 * total [\d]+
-	 * drwxr-xr-x  0 root root   512 Jan  1  1970 .
-	 * drwxr-xr-x  0 root root   512 Jan  1  1970 ..
-	 * drwxr-xr-x  0 root root   512 Apr  5 07:45 bin
-	 * >> 4 >>
-	 * -rwxr-xr-x  1 root root [\d]+ Jan  1  1970 init
-	 * >> M A N Y  M O R E  E N T R I E S >>
-	 * drwxr-xr-x  0 root root   512 Sep 22  2017 var
-	 * }
- *

Fails with a generated failure message describing the difference. - */ - public static void assertLinesMatch(List expectedLines, List actualLines) { - AssertLinesMatch.assertLinesMatch(expectedLines, actualLines); - } - - /** - * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} - * list. - * - *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. - * - *

Fails with the supplied failure {@code message} and the generated message. - * - * @see #assertLinesMatch(List, List) - */ - public static void assertLinesMatch(List expectedLines, List actualLines, String message) { - AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); - } - - /** - * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} - * list. - * - *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. - * - *

If necessary, a custom failure message will be retrieved lazily from the supplied - * {@code messageSupplier}. Fails with the custom failure message prepended to - * a generated failure message describing the difference. - * - * @see #assertLinesMatch(List, List) - */ - public static void assertLinesMatch(List expectedLines, List actualLines, - Supplier messageSupplier) { - AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); - } - - /** - * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} - * stream. - * - *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. - * - *

Note: An implementation of this method may consume all lines of both streams eagerly and - * delegate the evaluation to {@link #assertLinesMatch(List, List)}. - * - * @since 5.7 - * @see #assertLinesMatch(List, List) - */ - public static void assertLinesMatch(Stream expectedLines, Stream actualLines) { - AssertLinesMatch.assertLinesMatch(expectedLines, actualLines); - } - - /** - * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} - * stream. - * - *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. - * - *

Fails with the supplied failure {@code message} and the generated message. - * - *

Note: An implementation of this method may consume all lines of both streams eagerly and - * delegate the evaluation to {@link #assertLinesMatch(List, List)}. - * - * @since 5.7 - * @see #assertLinesMatch(List, List) - */ - public static void assertLinesMatch(Stream expectedLines, Stream actualLines, String message) { - AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); - } - - /** - * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} - * stream. - * - *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. - * - *

If necessary, a custom failure message will be retrieved lazily from the supplied - * {@code messageSupplier}. Fails with the custom failure message prepended to - * a generated failure message describing the difference. - * - *

Note: An implementation of this method may consume all lines of both streams eagerly and - * delegate the evaluation to {@link #assertLinesMatch(List, List)}. - * - * @since 5.7 - * @see #assertLinesMatch(List, List) - */ - public static void assertLinesMatch(Stream expectedLines, Stream actualLines, - Supplier messageSupplier) { - AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); - } - - // --- assertNotEquals ----------------------------------------------------- - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, byte actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, Byte actual) { - AssertNotEquals.assertNotEquals((Byte) unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, byte actual) { - AssertNotEquals.assertNotEquals(unexpected, (Byte) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, Byte actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, byte actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, Byte actual, String message) { - AssertNotEquals.assertNotEquals((Byte) unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, byte actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, Byte actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, Byte actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals((Byte) unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, byte actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, Byte actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, short actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, Short actual) { - AssertNotEquals.assertNotEquals((Short) unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, short actual) { - AssertNotEquals.assertNotEquals(unexpected, (Short) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, Short actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, short actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, Short actual, String message) { - AssertNotEquals.assertNotEquals((Short) unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, short actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, (Short) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, Short actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, Short actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals((Short) unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, short actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, (Short) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, Short actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, int actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, Integer actual) { - AssertNotEquals.assertNotEquals((Integer) unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, int actual) { - AssertNotEquals.assertNotEquals(unexpected, (Integer) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, Integer actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, int actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, Integer actual, String message) { - AssertNotEquals.assertNotEquals((Integer) unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, int actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, Integer actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, Integer actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals((Integer) unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, int actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, Integer actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, long actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, Long actual) { - AssertNotEquals.assertNotEquals((Long) unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, long actual) { - AssertNotEquals.assertNotEquals(unexpected, (Long) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, Long actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, long actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, Long actual, String message) { - AssertNotEquals.assertNotEquals((Long) unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, long actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, (Long) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, Long actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, Long actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals((Long) unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, long actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, (Long) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, Long actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, Float actual) { - AssertNotEquals.assertNotEquals((Float) unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, float actual) { - AssertNotEquals.assertNotEquals(unexpected, (Float) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, Float actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, Float actual, String message) { - AssertNotEquals.assertNotEquals((Float) unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, float actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, (Float) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, Float actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, Float actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals((Float) unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, float actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, (Float) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, Float actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal - * within the given {@code delta}. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, float delta) { - AssertNotEquals.assertNotEquals(unexpected, actual, delta); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal - * within the given {@code delta}. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, float delta, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal - * within the given {@code delta}. - * - *

Inequality imposed by this method is consistent with - * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, Double actual) { - AssertNotEquals.assertNotEquals((Double) unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, double actual) { - AssertNotEquals.assertNotEquals(unexpected, (Double) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, Double actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, Double actual, String message) { - AssertNotEquals.assertNotEquals((Double) unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, double actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, (Double) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, Double actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, Double actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals((Double) unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, double actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, (Double) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, Double actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal - * within the given {@code delta}. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, double delta) { - AssertNotEquals.assertNotEquals(unexpected, actual, delta); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal - * within the given {@code delta}. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, double delta, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal - * within the given {@code delta}. - * - *

Inequality imposed by this method is consistent with - * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, double delta, - Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, char actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, Character actual) { - AssertNotEquals.assertNotEquals((Character) unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, char actual) { - AssertNotEquals.assertNotEquals(unexpected, (Character) actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, Character actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, char actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, Character actual, String message) { - AssertNotEquals.assertNotEquals((Character) unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, char actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, (Character) actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, Character actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, Character actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals((Character) unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, char actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, (Character) actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, Character actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails if both are {@code null}. - * - * @see Object#equals(Object) - */ - public static void assertNotEquals(Object unexpected, Object actual) { - AssertNotEquals.assertNotEquals(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails if both are {@code null}. - * - *

Fails with the supplied failure {@code message}. - * - * @see Object#equals(Object) - */ - public static void assertNotEquals(Object unexpected, Object actual, String message) { - AssertNotEquals.assertNotEquals(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} are not equal. - * - *

Fails if both are {@code null}. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @see Object#equals(Object) - */ - public static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { - AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); - } - - // --- assertSame ---------------------------------------------------------- - - /** - * Assert that {@code expected} and {@code actual} refer to the same object. - */ - public static void assertSame(Object expected, Object actual) { - AssertSame.assertSame(expected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} refer to the same object. - *

Fails with the supplied failure {@code message}. - */ - public static void assertSame(Object expected, Object actual, String message) { - AssertSame.assertSame(expected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} refer to the same object. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertSame(Object expected, Object actual, Supplier messageSupplier) { - AssertSame.assertSame(expected, actual, messageSupplier); - } - - // --- assertNotSame ------------------------------------------------------- - - /** - * Assert that {@code expected} and {@code actual} do not refer to the same object. - */ - public static void assertNotSame(Object unexpected, Object actual) { - AssertNotSame.assertNotSame(unexpected, actual); - } - - /** - * Assert that {@code expected} and {@code actual} do not refer to the same object. - *

Fails with the supplied failure {@code message}. - */ - public static void assertNotSame(Object unexpected, Object actual, String message) { - AssertNotSame.assertNotSame(unexpected, actual, message); - } - - /** - * Assert that {@code expected} and {@code actual} do not refer to the same object. - *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. - */ - public static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { - AssertNotSame.assertNotSame(unexpected, actual, messageSupplier); - } - - // --- assertAll ----------------------------------------------------------- - - /** - * Assert that all supplied {@code executables} do not throw - * exceptions. - * - *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this - * method's exception handling semantics. - * - * @see #assertAll(String, Executable...) - * @see #assertAll(Collection) - * @see #assertAll(String, Collection) - * @see #assertAll(Stream) - * @see #assertAll(String, Stream) - */ - public static void assertAll(Executable... executables) throws MultipleFailuresError { - AssertAll.assertAll(executables); - } - - /** - * Assert that all supplied {@code executables} do not throw - * exceptions. - * - *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this - * method's exception handling semantics. - * - * @see #assertAll(Executable...) - * @see #assertAll(Collection) - * @see #assertAll(Stream) - * @see #assertAll(String, Collection) - * @see #assertAll(String, Stream) - */ - public static void assertAll(String heading, Executable... executables) throws MultipleFailuresError { - AssertAll.assertAll(heading, executables); - } - - /** - * Assert that all supplied {@code executables} do not throw - * exceptions. - * - *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this - * method's exception handling semantics. - * - * @see #assertAll(Executable...) - * @see #assertAll(String, Executable...) - * @see #assertAll(String, Collection) - * @see #assertAll(Stream) - * @see #assertAll(String, Stream) - */ - public static void assertAll(Collection executables) throws MultipleFailuresError { - AssertAll.assertAll(executables); - } - - /** - * Assert that all supplied {@code executables} do not throw - * exceptions. - * - *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this - * method's exception handling semantics. - * - * @see #assertAll(Executable...) - * @see #assertAll(String, Executable...) - * @see #assertAll(Collection) - * @see #assertAll(Stream) - * @see #assertAll(String, Stream) - */ - public static void assertAll(String heading, Collection executables) throws MultipleFailuresError { - AssertAll.assertAll(heading, executables); - } - - /** - * Assert that all supplied {@code executables} do not throw - * exceptions. - * - *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this - * method's exception handling semantics. - * - * @see #assertAll(Executable...) - * @see #assertAll(String, Executable...) - * @see #assertAll(Collection) - * @see #assertAll(String, Collection) - * @see #assertAll(String, Stream) - */ - public static void assertAll(Stream executables) throws MultipleFailuresError { - AssertAll.assertAll(executables); - } - - /** - * Assert that all supplied {@code executables} do not throw - * exceptions. - * - *

If any supplied {@link Executable} throws an exception (i.e., a {@link Throwable} - * or any subclass thereof), all remaining {@code executables} will still be executed, - * and all exceptions will be aggregated and reported in a {@link MultipleFailuresError}. - * In addition, all aggregated exceptions will be added as {@linkplain - * Throwable#addSuppressed(Throwable) suppressed exceptions} to the - * {@code MultipleFailuresError}. However, if an {@code executable} throws an - * unrecoverable exception — for example, an {@link OutOfMemoryError} - * — execution will halt immediately, and the unrecoverable exception will be - * rethrown as is but masked as an unchecked exception. - * - *

The supplied {@code heading} will be included in the message string for the - * {@code MultipleFailuresError}. - * - * @see #assertAll(Executable...) - * @see #assertAll(String, Executable...) - * @see #assertAll(Collection) - * @see #assertAll(String, Collection) - * @see #assertAll(Stream) - */ - public static void assertAll(String heading, Stream executables) throws MultipleFailuresError { - AssertAll.assertAll(heading, executables); - } - - // --- assert exceptions --------------------------------------------------- - - // --- executable --- - - /** - * Assert that execution of the supplied {@code executable} throws - * an exception of exactly the {@code expectedType} and return the exception. - * - *

If no exception is thrown, or if an exception of a different type is - * thrown, this method will fail. - * - *

If you do not want to perform additional checks on the exception instance, - * ignore the return value. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - public static T assertThrowsExactly(Class expectedType, Executable executable) { - return AssertThrowsExactly.assertThrowsExactly(expectedType, executable); - } - - /** - * Assert that execution of the supplied {@code executable} throws - * an exception of exactly the {@code expectedType} and return the exception. - * - *

If no exception is thrown, or if an exception of a different type is - * thrown, this method will fail. - * - *

If you do not want to perform additional checks on the exception instance, - * ignore the return value. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - public static T assertThrowsExactly(Class expectedType, Executable executable, - String message) { - return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, message); - } - - /** - * Assert that execution of the supplied {@code executable} throws - * an exception of exactly the {@code expectedType} and return the exception. - * - *

If no exception is thrown, or if an exception of a different type is - * thrown, this method will fail. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - *

If you do not want to perform additional checks on the exception instance, - * ignore the return value. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - public static T assertThrowsExactly(Class expectedType, Executable executable, - Supplier messageSupplier) { - return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, messageSupplier); - } - - /** - * Assert that execution of the supplied {@code executable} throws - * an exception of the {@code expectedType} and return the exception. - * - *

If no exception is thrown, or if an exception of a different type is - * thrown, this method will fail. - * - *

If you do not want to perform additional checks on the exception instance, - * ignore the return value. - */ - public static T assertThrows(Class expectedType, Executable executable) { - return AssertThrows.assertThrows(expectedType, executable); - } - - /** - * Assert that execution of the supplied {@code executable} throws - * an exception of the {@code expectedType} and return the exception. - * - *

If no exception is thrown, or if an exception of a different type is - * thrown, this method will fail. - * - *

If you do not want to perform additional checks on the exception instance, - * ignore the return value. - * - *

Fails with the supplied failure {@code message}. - */ - public static T assertThrows(Class expectedType, Executable executable, String message) { - return AssertThrows.assertThrows(expectedType, executable, message); - } - - /** - * Assert that execution of the supplied {@code executable} throws - * an exception of the {@code expectedType} and return the exception. - * - *

If no exception is thrown, or if an exception of a different type is - * thrown, this method will fail. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - *

If you do not want to perform additional checks on the exception instance, - * ignore the return value. - */ - public static T assertThrows(Class expectedType, Executable executable, - Supplier messageSupplier) { - return AssertThrows.assertThrows(expectedType, executable, messageSupplier); - } - - // --- executable --- - - /** - * Assert that execution of the supplied {@code executable} does - * not throw any kind of {@linkplain Throwable exception}. - * - *

Usage Note

- *

Although any exception thrown from a test method will cause the test - * to fail, there are certain use cases where it can be beneficial - * to explicitly assert that an exception is not thrown for a given code - * block within a test method. - * - * @since 5.2 - */ - @API(status = STABLE, since = "5.2") - public static void assertDoesNotThrow(Executable executable) { - AssertDoesNotThrow.assertDoesNotThrow(executable); - } - - /** - * Assert that execution of the supplied {@code executable} does - * not throw any kind of {@linkplain Throwable exception}. - * - *

Usage Note

- *

Although any exception thrown from a test method will cause the test - * to fail, there are certain use cases where it can be beneficial - * to explicitly assert that an exception is not thrown for a given code - * block within a test method. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.2 - */ - @API(status = STABLE, since = "5.2") - public static void assertDoesNotThrow(Executable executable, String message) { - AssertDoesNotThrow.assertDoesNotThrow(executable, message); - } - - /** - * Assert that execution of the supplied {@code executable} does - * not throw any kind of {@linkplain Throwable exception}. - * - *

Usage Note

- *

Although any exception thrown from a test method will cause the test - * to fail, there are certain use cases where it can be beneficial - * to explicitly assert that an exception is not thrown for a given code - * block within a test method. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.2 - */ - @API(status = STABLE, since = "5.2") - public static void assertDoesNotThrow(Executable executable, Supplier messageSupplier) { - AssertDoesNotThrow.assertDoesNotThrow(executable, messageSupplier); - } - - // --- supplier --- - - /** - * Assert that execution of the supplied {@code supplier} does - * not throw any kind of {@linkplain Throwable exception}. - * - *

If the assertion passes, the {@code supplier}'s result will be returned. - * - *

Usage Note

- *

Although any exception thrown from a test method will cause the test - * to fail, there are certain use cases where it can be beneficial - * to explicitly assert that an exception is not thrown for a given code - * block within a test method. - * - * @since 5.2 - */ - @API(status = STABLE, since = "5.2") - public static T assertDoesNotThrow(ThrowingSupplier supplier) { - return AssertDoesNotThrow.assertDoesNotThrow(supplier); - } - - /** - * Assert that execution of the supplied {@code supplier} does - * not throw any kind of {@linkplain Throwable exception}. - * - *

If the assertion passes, the {@code supplier}'s result will be returned. - * - *

Fails with the supplied failure {@code message}. - * - *

Usage Note

- *

Although any exception thrown from a test method will cause the test - * to fail, there are certain use cases where it can be beneficial - * to explicitly assert that an exception is not thrown for a given code - * block within a test method. - * - * @since 5.2 - */ - @API(status = STABLE, since = "5.2") - public static T assertDoesNotThrow(ThrowingSupplier supplier, String message) { - return AssertDoesNotThrow.assertDoesNotThrow(supplier, message); - } - - /** - * Assert that execution of the supplied {@code supplier} does - * not throw any kind of {@linkplain Throwable exception}. - * - *

If the assertion passes, the {@code supplier}'s result will be returned. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - *

Usage Note

- *

Although any exception thrown from a test method will cause the test - * to fail, there are certain use cases where it can be beneficial - * to explicitly assert that an exception is not thrown for a given code - * block within a test method. - * - * @since 5.2 - */ - @API(status = STABLE, since = "5.2") - public static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier messageSupplier) { - return AssertDoesNotThrow.assertDoesNotThrow(supplier, messageSupplier); - } - - // --- assertTimeout ------------------------------------------------------- - - // --- executable --- - - /** - * Assert that execution of the supplied {@code executable} - * completes before the given {@code timeout} is exceeded. - * - *

Note: the {@code executable} will be executed in the same thread as that - * of the calling code. Consequently, execution of the {@code executable} will - * not be preemptively aborted if the timeout is exceeded. - * - * @see #assertTimeout(Duration, Executable, String) - * @see #assertTimeout(Duration, Executable, Supplier) - * @see #assertTimeout(Duration, ThrowingSupplier) - * @see #assertTimeout(Duration, ThrowingSupplier, String) - * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeoutPreemptively(Duration, Executable) - */ - public static void assertTimeout(Duration timeout, Executable executable) { - AssertTimeout.assertTimeout(timeout, executable); - } - - /** - * Assert that execution of the supplied {@code executable} - * completes before the given {@code timeout} is exceeded. - * - *

Note: the {@code executable} will be executed in the same thread as that - * of the calling code. Consequently, execution of the {@code executable} will - * not be preemptively aborted if the timeout is exceeded. - * - *

Fails with the supplied failure {@code message}. - * - * @see #assertTimeout(Duration, Executable) - * @see #assertTimeout(Duration, Executable, Supplier) - * @see #assertTimeout(Duration, ThrowingSupplier) - * @see #assertTimeout(Duration, ThrowingSupplier, String) - * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - */ - public static void assertTimeout(Duration timeout, Executable executable, String message) { - AssertTimeout.assertTimeout(timeout, executable, message); - } - - /** - * Assert that execution of the supplied {@code executable} - * completes before the given {@code timeout} is exceeded. - * - *

Note: the {@code executable} will be executed in the same thread as that - * of the calling code. Consequently, execution of the {@code executable} will - * not be preemptively aborted if the timeout is exceeded. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @see #assertTimeout(Duration, Executable) - * @see #assertTimeout(Duration, Executable, String) - * @see #assertTimeout(Duration, ThrowingSupplier) - * @see #assertTimeout(Duration, ThrowingSupplier, String) - * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - */ - public static void assertTimeout(Duration timeout, Executable executable, Supplier messageSupplier) { - AssertTimeout.assertTimeout(timeout, executable, messageSupplier); - } - - // --- supplier --- - - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

If the assertion passes then the {@code supplier}'s result is returned. - * - *

Note: the {@code supplier} will be executed in the same thread as that - * of the calling code. Consequently, execution of the {@code supplier} will - * not be preemptively aborted if the timeout is exceeded. - * - * @see #assertTimeout(Duration, Executable) - * @see #assertTimeout(Duration, Executable, String) - * @see #assertTimeout(Duration, Executable, Supplier) - * @see #assertTimeout(Duration, ThrowingSupplier, String) - * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeoutPreemptively(Duration, Executable) - */ - public static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { - return AssertTimeout.assertTimeout(timeout, supplier); - } - - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

If the assertion passes then the {@code supplier}'s result is returned. - * - *

Note: the {@code supplier} will be executed in the same thread as that - * of the calling code. Consequently, execution of the {@code supplier} will - * not be preemptively aborted if the timeout is exceeded. - * - *

Fails with the supplied failure {@code message}. - * - * @see #assertTimeout(Duration, Executable) - * @see #assertTimeout(Duration, Executable, String) - * @see #assertTimeout(Duration, Executable, Supplier) - * @see #assertTimeout(Duration, ThrowingSupplier) - * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - */ - public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, String message) { - return AssertTimeout.assertTimeout(timeout, supplier, message); - } - - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

If the assertion passes then the {@code supplier}'s result is returned. - * - *

Note: the {@code supplier} will be executed in the same thread as that - * of the calling code. Consequently, execution of the {@code supplier} will - * not be preemptively aborted if the timeout is exceeded. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @see #assertTimeout(Duration, Executable) - * @see #assertTimeout(Duration, Executable, String) - * @see #assertTimeout(Duration, Executable, Supplier) - * @see #assertTimeout(Duration, ThrowingSupplier) - * @see #assertTimeout(Duration, ThrowingSupplier, String) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - */ - public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { - return AssertTimeout.assertTimeout(timeout, supplier, messageSupplier); - } - - // --- executable - preemptively --- - - /** - * Assert that execution of the supplied {@code executable} - * completes before the given {@code timeout} is exceeded. - * - *

Note: the {@code executable} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code executable} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * - * @see #assertTimeoutPreemptively(Duration, Executable, String) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeout(Duration, Executable) - */ - public static void assertTimeoutPreemptively(Duration timeout, Executable executable) { - AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable); - } - - /** - * Assert that execution of the supplied {@code executable} - * completes before the given {@code timeout} is exceeded. - * - *

Note: the {@code executable} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code executable} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * - *

Fails with the supplied failure {@code message}. - * - * @see #assertTimeoutPreemptively(Duration, Executable) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeout(Duration, Executable, String) - */ - public static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { - AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, message); - } - - /** - * Assert that execution of the supplied {@code executable} - * completes before the given {@code timeout} is exceeded. - * - *

Note: the {@code executable} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code executable} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @see #assertTimeoutPreemptively(Duration, Executable) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeout(Duration, Executable, Supplier) - */ - public static void assertTimeoutPreemptively(Duration timeout, Executable executable, - Supplier messageSupplier) { - AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, messageSupplier); - } - - // --- supplier - preemptively --- - - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

If the assertion passes then the {@code supplier}'s result is returned. - * - *

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * - * @see #assertTimeoutPreemptively(Duration, Executable) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeout(Duration, Executable) - */ - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier); - } - - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

If the assertion passes then the {@code supplier}'s result is returned. - * - *

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * - *

Fails with the supplied failure {@code message}. - * - * @see #assertTimeoutPreemptively(Duration, Executable) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) - * @see #assertTimeout(Duration, Executable, String) - */ - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, message); - } - - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

If the assertion passes then the {@code supplier}'s result is returned. - * - *

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @see #assertTimeoutPreemptively(Duration, Executable) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) - * @see #assertTimeout(Duration, Executable, Supplier) - */ - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { - return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier); - } - - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

If the assertion passes then the {@code supplier}'s result is returned. - * - *

In the case the assertion does not pass, the supplied - * {@link TimeoutFailureFactory} is invoked to create an exception which is - * then thrown. - * - *

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @see #assertTimeoutPreemptively(Duration, Executable) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) - * @see #assertTimeout(Duration, Executable, Supplier) - */ - @API(status = INTERNAL, since = "5.9.1") - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { - return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); - } - - // --- assertInstanceOf ---------------------------------------------------- - - /** - * Assert that the supplied {@code actualValue} is an instance of the - * {@code expectedType}. - * - *

Like the {@code instanceof} operator a {@code null} value is not - * considered to be of the {@code expectedType} and does not pass the assertion. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - public static T assertInstanceOf(Class expectedType, Object actualValue) { - return AssertInstanceOf.assertInstanceOf(expectedType, actualValue); - } - - /** - * Assert that the supplied {@code actualValue} is an instance of the - * {@code expectedType}. - * - *

Like the {@code instanceof} operator a {@code null} value is not - * considered to be of the {@code expectedType} and does not pass the assertion. - * - *

Fails with the supplied failure {@code message}. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - public static T assertInstanceOf(Class expectedType, Object actualValue, String message) { - return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, message); - } - - /** - * Assert that the supplied {@code actualValue} is an instance of the - * {@code expectedType}. - * - *

Like the {@code instanceof} operator a {@code null} value is not - * considered to be of the {@code expectedType} and does not pass the assertion. - * - *

If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - public static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { - return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); - } - - /** - * Factory for timeout failures. - * - * @param The type of error or exception created - * @since 5.9.1 - * @see Assertions#assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) - */ - @API(status = INTERNAL, since = "5.9.1") - public interface TimeoutFailureFactory { - - /** - * Create a failure for the given timeout, message, and cause. - * - * @return timeout failure; never {@code null} - */ - T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java deleted file mode 100644 index d0448a89..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.function.BooleanSupplier; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.StringUtils; -import org.opentest4j.TestAbortedException; - -/** - * {@code Assumptions} is a collection of utility methods that support - * conditional test execution based on assumptions. - * - *

In direct contrast to failed {@linkplain Assertions assertions}, - * failed assumptions do not result in a test failure; rather, - * a failed assumption results in a test being aborted. - * - *

Assumptions are typically used whenever it does not make sense to - * continue execution of a given test method — for example, if the - * test depends on something that does not exist in the current runtime - * environment. - * - *

Although it is technically possible to extend this class, extension is - * strongly discouraged. The JUnit Team highly recommends that the methods - * defined in this class be used via static imports. - * - * @since 5.0 - * @see TestAbortedException - * @see Assertions - */ -@API(status = STABLE, since = "5.0") -public class Assumptions { - - /** - * Protected constructor allowing subclassing but not direct instantiation. - * - * @since 5.3 - */ - protected Assumptions() { - /* no-op */ - } - - // --- assumeTrue ---------------------------------------------------------- - - /** - * Validate the given assumption. - * - * @param assumption the assumption to validate - * @throws TestAbortedException if the assumption is not {@code true} - */ - public static void assumeTrue(boolean assumption) throws TestAbortedException { - assumeTrue(assumption, "assumption is not true"); - } - - /** - * Validate the given assumption. - * - * @param assumptionSupplier the supplier of the assumption to validate - * @throws TestAbortedException if the assumption is not {@code true} - */ - public static void assumeTrue(BooleanSupplier assumptionSupplier) throws TestAbortedException { - assumeTrue(assumptionSupplier.getAsBoolean(), "assumption is not true"); - } - - /** - * Validate the given assumption. - * - * @param assumptionSupplier the supplier of the assumption to validate - * @param message the message to be included in the {@code TestAbortedException} - * if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code true} - */ - public static void assumeTrue(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException { - assumeTrue(assumptionSupplier.getAsBoolean(), message); - } - - /** - * Validate the given assumption. - * - * @param assumption the assumption to validate - * @param messageSupplier the supplier of the message to be included in - * the {@code TestAbortedException} if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code true} - */ - public static void assumeTrue(boolean assumption, Supplier messageSupplier) throws TestAbortedException { - if (!assumption) { - throwAssumptionFailed(messageSupplier.get()); - } - } - - /** - * Validate the given assumption. - * - * @param assumption the assumption to validate - * @param message the message to be included in the {@code TestAbortedException} - * if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code true} - */ - public static void assumeTrue(boolean assumption, String message) throws TestAbortedException { - if (!assumption) { - throwAssumptionFailed(message); - } - } - - /** - * Validate the given assumption. - * - * @param assumptionSupplier the supplier of the assumption to validate - * @param messageSupplier the supplier of the message to be included in - * the {@code TestAbortedException} if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code true} - */ - public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier messageSupplier) - throws TestAbortedException { - - assumeTrue(assumptionSupplier.getAsBoolean(), messageSupplier); - } - - // --- assumeFalse --------------------------------------------------------- - - /** - * Validate the given assumption. - * - * @param assumption the assumption to validate - * @throws TestAbortedException if the assumption is not {@code false} - */ - public static void assumeFalse(boolean assumption) throws TestAbortedException { - assumeFalse(assumption, "assumption is not false"); - } - - /** - * Validate the given assumption. - * - * @param assumptionSupplier the supplier of the assumption to validate - * @throws TestAbortedException if the assumption is not {@code false} - */ - public static void assumeFalse(BooleanSupplier assumptionSupplier) throws TestAbortedException { - assumeFalse(assumptionSupplier.getAsBoolean(), "assumption is not false"); - } - - /** - * Validate the given assumption. - * - * @param assumptionSupplier the supplier of the assumption to validate - * @param message the message to be included in the {@code TestAbortedException} - * if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code false} - */ - public static void assumeFalse(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException { - assumeFalse(assumptionSupplier.getAsBoolean(), message); - } - - /** - * Validate the given assumption. - * - * @param assumption the assumption to validate - * @param messageSupplier the supplier of the message to be included in - * the {@code TestAbortedException} if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code false} - */ - public static void assumeFalse(boolean assumption, Supplier messageSupplier) throws TestAbortedException { - if (assumption) { - throwAssumptionFailed(messageSupplier.get()); - } - } - - /** - * Validate the given assumption. - * - * @param assumption the assumption to validate - * @param message the message to be included in the {@code TestAbortedException} - * if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code false} - */ - public static void assumeFalse(boolean assumption, String message) throws TestAbortedException { - if (assumption) { - throwAssumptionFailed(message); - } - } - - /** - * Validate the given assumption. - * - * @param assumptionSupplier the supplier of the assumption to validate - * @param messageSupplier the supplier of the message to be included in - * the {@code TestAbortedException} if the assumption is invalid - * @throws TestAbortedException if the assumption is not {@code false} - */ - public static void assumeFalse(BooleanSupplier assumptionSupplier, Supplier messageSupplier) - throws TestAbortedException { - - assumeFalse(assumptionSupplier.getAsBoolean(), messageSupplier); - } - - // --- assumingThat -------------------------------------------------------- - - /** - * Execute the supplied {@link Executable}, but only if the supplied - * assumption is valid. - * - *

Unlike the other assumption methods, this method will not abort the test. - * If the assumption is invalid, this method does nothing. If the assumption is - * valid and the {@code executable} throws an exception, it will be treated like - * a regular test failure. That exception will be rethrown as is - * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked - * exception. - * - * @param assumptionSupplier the supplier of the assumption to validate - * @param executable the block of code to execute if the assumption is valid - * @see #assumingThat(boolean, Executable) - */ - public static void assumingThat(BooleanSupplier assumptionSupplier, Executable executable) { - assumingThat(assumptionSupplier.getAsBoolean(), executable); - } - - /** - * Execute the supplied {@link Executable}, but only if the supplied - * assumption is valid. - * - *

Unlike the other assumption methods, this method will not abort the test. - * If the assumption is invalid, this method does nothing. If the assumption is - * valid and the {@code executable} throws an exception, it will be treated like - * a regular test failure. That exception will be rethrown as is - * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked - * exception. - * - * @param assumption the assumption to validate - * @param executable the block of code to execute if the assumption is valid - * @see #assumingThat(BooleanSupplier, Executable) - */ - public static void assumingThat(boolean assumption, Executable executable) { - if (assumption) { - try { - executable.execute(); - } - catch (Throwable t) { - ExceptionUtils.throwAsUncheckedException(t); - } - } - } - - // --- abort --------------------------------------------------------------- - - /** - * Abort the test without a message. - * - *

Although aborting with an explicit message is recommended, this may be - * useful when maintaining legacy code. - * - *

See Javadoc for {@link #abort(String)} for an explanation of this - * method's generic return type {@code V}. - * - * @throws TestAbortedException always - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - public static V abort() { - throw new TestAbortedException(); - } - - /** - * Abort the test with the given {@code message}. - * - *

The generic return type {@code V} allows this method to be used - * directly as a single-statement lambda expression, thereby avoiding the - * need to implement a code block with an explicit return value. Since this - * method throws a {@link TestAbortedException} before its return statement, - * this method never actually returns a value to its caller. The following - * example demonstrates how this may be used in practice. - * - *

{@code
-	 * Stream.of().map(entry -> abort("assumption not met"));
-	 * }
- * - * @param message the message to be included in the {@code TestAbortedException} - * @throws TestAbortedException always - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - public static V abort(String message) { - throw new TestAbortedException(message); - } - - /** - * Abort the test with the supplied message. - * - *

See Javadoc for {@link #abort(String)} for an explanation of this - * method's generic return type {@code V}. - * - * @param messageSupplier the supplier of the message to be included in the - * {@code TestAbortedException} - * @throws TestAbortedException always - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - public static V abort(Supplier messageSupplier) { - throw new TestAbortedException(messageSupplier.get()); - } - - private static void throwAssumptionFailed(String message) { - throw new TestAbortedException( - StringUtils.isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java deleted file mode 100644 index 30eba9b7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @BeforeAll} is used to signal that the annotated method should be - * executed before all tests in the current test class. - * - *

In contrast to {@link BeforeEach @BeforeEach} methods, {@code @BeforeAll} - * methods are only executed once for a given test class. - * - *

Method Signatures

- * - *

{@code @BeforeAll} methods must have a {@code void} return type and must - * be {@code static} by default. Consequently, {@code @BeforeAll} methods are - * not supported in {@link Nested @Nested} test classes or as interface - * default methods unless the test class is annotated with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. - * However, beginning with Java 16 {@code @BeforeAll} methods may be declared as - * {@code static} in {@link Nested @Nested} test classes, and the - * {@code Lifecycle.PER_CLASS} restriction no longer applies. {@code @BeforeAll} - * methods may optionally declare parameters to be resolved by - * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. - * - *

Using {@code private} visibility for {@code @BeforeAll} methods is - * strongly discouraged and will be disallowed in a future release. - * - *

Inheritance and Execution Order

- * - *

{@code @BeforeAll} methods are inherited from superclasses as long as - * they are not hidden (default mode with {@code static} modifier), - * overridden, or superseded (i.e., replaced based on - * signature only, irrespective of Java's visibility rules). Furthermore, - * {@code @BeforeAll} methods from superclasses will be executed before - * {@code @BeforeAll} methods in subclasses. - * - *

Similarly, {@code @BeforeAll} methods declared in an interface are - * inherited as long as they are not hidden or overridden, - * and {@code @BeforeAll} methods from an interface will be executed before - * {@code @BeforeAll} methods in the class that implements the interface. - * - *

JUnit Jupiter does not guarantee the execution order of multiple - * {@code @BeforeAll} methods that are declared within a single test class or - * test interface. While it may at times appear that these methods are invoked - * in alphabetical order, they are in fact sorted using an algorithm that is - * deterministic but intentionally non-obvious. - * - *

In addition, {@code @BeforeAll} methods are in no way linked to - * {@code @AfterAll} methods. Consequently, there are no guarantees with regard - * to their wrapping behavior. For example, given two - * {@code @BeforeAll} methods {@code createA()} and {@code createB()} as well as - * two {@code @AfterAll} methods {@code destroyA()} and {@code destroyB()}, the - * order in which the {@code @BeforeAll} methods are executed (e.g. - * {@code createA()} before {@code createB()}) does not imply any order for the - * seemingly corresponding {@code @AfterAll} methods. In other words, - * {@code destroyA()} might be called before or after - * {@code destroyB()}. The JUnit Team therefore recommends that developers - * declare at most one {@code @BeforeAll} method and at most one - * {@code @AfterAll} method per test class or test interface unless there are no - * dependencies between the {@code @BeforeAll} methods or between the - * {@code @AfterAll} methods. - * - *

Composition

- * - *

{@code @BeforeAll} may be used as a meta-annotation in order to create - * a custom composed annotation that inherits the semantics of - * {@code @BeforeAll}. - * - * @since 5.0 - * @see AfterAll - * @see BeforeEach - * @see AfterEach - * @see Test - * @see TestFactory - * @see TestInstance - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -public @interface BeforeAll { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java deleted file mode 100644 index 37ca2498..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @BeforeEach} is used to signal that the annotated method should be - * executed before each {@code @Test}, - * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, - * and {@code @TestTemplate} method in the current test class. - * - *

Method Signatures

- * - *

{@code @BeforeEach} methods must have a {@code void} return type and must - * not be {@code static}. Using {@code private} visibility is strongly - * discouraged and will be disallowed in a future release. - * They may optionally declare parameters to be resolved by - * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. - * - *

Inheritance and Execution Order

- * - *

{@code @BeforeEach} methods are inherited from superclasses as long as they - * are not overridden or superseded (i.e., replaced based on - * signature only, irrespective of Java's visibility rules). Furthermore, - * {@code @BeforeEach} methods from superclasses will be executed before - * {@code @BeforeEach} methods in subclasses. - * - *

Similarly, {@code @BeforeEach} methods declared as interface default - * methods are inherited as long as they are not overridden, and - * {@code @BeforeEach} default methods will be executed before {@code @BeforeEach} - * methods in the class that implements the interface. - * - *

JUnit Jupiter does not guarantee the execution order of multiple - * {@code @BeforeEach} methods that are declared within a single test class or - * test interface. While it may at times appear that these methods are invoked - * in alphabetical order, they are in fact sorted using an algorithm that is - * deterministic but intentionally non-obvious. - * - *

In addition, {@code @BeforeEach} methods are in no way linked to - * {@code @AfterEach} methods. Consequently, there are no guarantees with regard - * to their wrapping behavior. For example, given two - * {@code @BeforeEach} methods {@code createA()} and {@code createB()} as well - * as two {@code @AfterEach} methods {@code destroyA()} and {@code destroyB()}, - * the order in which the {@code @BeforeEach} methods are executed (e.g. - * {@code createA()} before {@code createB()}) does not imply any order for the - * seemingly corresponding {@code @AfterEach} methods. In other words, - * {@code destroyA()} might be called before or after - * {@code destroyB()}. The JUnit Team therefore recommends that developers - * declare at most one {@code @BeforeEach} method and at most one - * {@code @AfterEach} method per test class or test interface unless there are - * no dependencies between the {@code @BeforeEach} methods or between the - * {@code @AfterEach} methods. - * - *

Composition

- * - *

{@code @BeforeEach} may be used as a meta-annotation in order to create - * a custom composed annotation that inherits the semantics of - * {@code @BeforeEach}. - * - * @since 5.0 - * @see AfterEach - * @see BeforeAll - * @see AfterAll - * @see Test - * @see RepeatedTest - * @see TestFactory - * @see TestTemplate - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -public @interface BeforeEach { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java deleted file mode 100644 index 5ad667d1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code ClassDescriptor} encapsulates functionality for a given {@link Class}. - * - * @since 5.8 - * @see ClassOrdererContext - */ -@API(status = EXPERIMENTAL, since = "5.8") -public interface ClassDescriptor { - - /** - * Get the class for this descriptor. - * - * @return the class; never {@code null} - */ - Class getTestClass(); - - /** - * Get the display name for this descriptor's {@link #getTestClass() class}. - * - * @return the display name for this descriptor's class; never {@code null} - * or blank - */ - String getDisplayName(); - - /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the {@link Class} for - * this descriptor. - * - * @param annotationType the annotation type to search for; never {@code null} - * @return {@code true} if the annotation is present or meta-present - * @see #findAnnotation(Class) - * @see #findRepeatableAnnotations(Class) - */ - boolean isAnnotated(Class annotationType); - - /** - * Find the first annotation of {@code annotationType} that is either - * present or meta-present on the {@link Class} for - * this descriptor. - * - * @param the annotation type - * @param annotationType the annotation type to search for; never {@code null} - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty - * @see #isAnnotated(Class) - * @see #findRepeatableAnnotations(Class) - */ - Optional findAnnotation(Class annotationType); - - /** - * Find all repeatable {@linkplain Annotation annotations} of - * {@code annotationType} that are either present or - * meta-present on the {@link Class} for this descriptor. - * - * @param the annotation type - * @param annotationType the repeatable annotation type to search for; never - * {@code null} - * @return the list of all such annotations found; neither {@code null} nor - * mutable, but potentially empty - * @see #isAnnotated(Class) - * @see #findAnnotation(Class) - * @see java.lang.annotation.Repeatable - */ - List findRepeatableAnnotations(Class annotationType); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java deleted file mode 100644 index 04017c02..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.util.Comparator.comparingInt; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * {@code ClassOrderer} defines the API for ordering top-level test classes and - * {@link Nested @Nested} test classes. - * - *

In this context, the term "test class" refers to any class containing methods - * annotated with {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, - * {@code @TestFactory}, or {@code @TestTemplate}. - * - *

Top-level test classes will be ordered relative to each other; whereas, - * {@code @Nested} test classes will be ordered relative to other {@code @Nested} - * test classes sharing the same {@linkplain Class#getEnclosingClass() enclosing - * class}. - * - *

A {@link ClassOrderer} can be configured globally for the entire - * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration - * parameter (see the User Guide for details) or locally for - * {@link Nested @Nested} test classes via the {@link TestClassOrder @TestClassOrder} - * annotation. - * - *

Built-in Implementations

- * - *

JUnit Jupiter provides the following built-in {@code ClassOrderer} - * implementations. - * - *

    - *
  • {@link ClassOrderer.ClassName}
  • - *
  • {@link ClassOrderer.DisplayName}
  • - *
  • {@link ClassOrderer.OrderAnnotation}
  • - *
  • {@link ClassOrderer.Random}
  • - *
- * - * @since 5.8 - * @see TestClassOrder - * @see ClassOrdererContext - * @see #orderClasses(ClassOrdererContext) - * @see MethodOrderer - */ -@API(status = EXPERIMENTAL, since = "5.8") -public interface ClassOrderer { - - /** - * Property name used to set the default class orderer class name: {@value} - * - *

Supported Values

- * - *

Supported values include fully qualified class names for types that - * implement {@link org.junit.jupiter.api.ClassOrderer}. - * - *

If not specified, test classes are not ordered unless test classes are - * annotated with {@link TestClassOrder @TestClassOrder}. - * - * @since 5.8 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testclass.order.default"; - - /** - * Order the classes encapsulated in the supplied {@link ClassOrdererContext}. - * - *

The classes to order or sort are made indirectly available via - * {@link ClassOrdererContext#getClassDescriptors()}. Since this method - * has a {@code void} return type, the list of class descriptors must be - * modified directly. - * - *

For example, a simplified implementation of the {@link ClassOrderer.Random} - * {@code ClassOrderer} might look like the following. - * - *

-	 * public void orderClasses(ClassOrdererContext context) {
-	 *     Collections.shuffle(context.getClassDescriptors());
-	 * }
-	 * 
- * - * @param context the {@code ClassOrdererContext} containing the - * {@linkplain ClassDescriptor class descriptors} to order; never {@code null} - */ - void orderClasses(ClassOrdererContext context); - - /** - * {@code ClassOrderer} that sorts classes alphanumerically based on their - * fully qualified names using {@link String#compareTo(String)}. - */ - class ClassName implements ClassOrderer { - - public ClassName() { - } - - /** - * Sort the classes encapsulated in the supplied - * {@link ClassOrdererContext} alphanumerically based on their fully - * qualified names. - */ - @Override - public void orderClasses(ClassOrdererContext context) { - context.getClassDescriptors().sort(comparator); - } - - private static final Comparator comparator = Comparator.comparing( - descriptor -> descriptor.getTestClass().getName()); - } - - /** - * {@code ClassOrderer} that sorts classes alphanumerically based on their - * display names using {@link String#compareTo(String)} - */ - class DisplayName implements ClassOrderer { - - public DisplayName() { - } - - /** - * Sort the classes encapsulated in the supplied - * {@link ClassOrdererContext} alphanumerically based on their display - * names. - */ - @Override - public void orderClasses(ClassOrdererContext context) { - context.getClassDescriptors().sort(comparator); - } - - private static final Comparator comparator = Comparator.comparing( - ClassDescriptor::getDisplayName); - } - - /** - * {@code ClassOrderer} that sorts classes based on the {@link Order @Order} - * annotation. - * - *

Any classes that are assigned the same order value will be sorted - * arbitrarily adjacent to each other. - * - *

Any classes not annotated with {@code @Order} will be assigned the - * {@linkplain Order#DEFAULT default order} value which will effectively cause them - * to appear at the end of the sorted list, unless certain classes are assigned - * an explicit order value greater than the default order value. Any classes - * assigned an explicit order value greater than the default order value will - * appear after non-annotated classes in the sorted list. - */ - class OrderAnnotation implements ClassOrderer { - - public OrderAnnotation() { - } - - /** - * Sort the classes encapsulated in the supplied - * {@link ClassOrdererContext} based on the {@link Order @Order} - * annotation. - */ - @Override - public void orderClasses(ClassOrdererContext context) { - context.getClassDescriptors().sort(comparingInt(OrderAnnotation::getOrder)); - } - - private static int getOrder(ClassDescriptor descriptor) { - return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT); - } - } - - /** - * {@code ClassOrderer} that orders classes pseudo-randomly. - * - *

Custom Seed

- * - *

By default, the random seed used for ordering classes is the - * value returned by {@link System#nanoTime()} during static initialization - * of this class. In order to support repeatable builds, the value of the - * default random seed is logged at {@code CONFIG} level. In addition, a - * custom seed (potentially the default seed from the previous test plan - * execution) may be specified via the {@value Random#RANDOM_SEED_PROPERTY_NAME} - * configuration parameter which can be supplied via the {@code Launcher} - * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit - * Platform configuration file (i.e., a file named {@code junit-platform.properties} - * in the root of the class path). Consult the User Guide for further information. - * - * @see Random#RANDOM_SEED_PROPERTY_NAME - * @see java.util.Random - */ - class Random implements ClassOrderer { - - private static final Logger logger = LoggerFactory.getLogger(Random.class); - - /** - * Default seed, which is generated during initialization of this class - * via {@link System#nanoTime()} for reproducibility of tests. - */ - private static final long DEFAULT_SEED; - - static { - DEFAULT_SEED = System.nanoTime(); - logger.config(() -> "ClassOrderer.Random default seed: " + DEFAULT_SEED); - } - - /** - * Property name used to set the random seed used by this - * {@code ClassOrderer}: {@value} - * - *

The same property is used by {@link MethodOrderer.Random} for - * consistency between the two random orderers. - * - *

Supported Values

- * - *

Supported values include any string that can be converted to a - * {@link Long} via {@link Long#valueOf(String)}. - * - *

If not specified or if the specified value cannot be converted to - * a {@link Long}, the default random seed will be used (see the - * {@linkplain Random class-level Javadoc} for details). - * - * @see MethodOrderer.Random - */ - public static final String RANDOM_SEED_PROPERTY_NAME = MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME; - - public Random() { - } - - /** - * Order the classes encapsulated in the supplied - * {@link ClassOrdererContext} pseudo-randomly. - */ - @Override - public void orderClasses(ClassOrdererContext context) { - Collections.shuffle(context.getClassDescriptors(), - new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED))); - } - - private Optional getCustomSeed(ClassOrdererContext context) { - return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> { - Long seed = null; - try { - seed = Long.valueOf(configurationParameter); - logger.config( - () -> String.format("Using custom seed for configuration parameter [%s] with value [%s].", - RANDOM_SEED_PROPERTY_NAME, configurationParameter)); - } - catch (NumberFormatException ex) { - logger.warn(ex, - () -> String.format( - "Failed to convert configuration parameter [%s] with value [%s] to a long. " - + "Using default seed [%s] as fallback.", - RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); - } - return seed; - }); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java deleted file mode 100644 index 1ec2e9b8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code ClassOrdererContext} encapsulates the context in which - * a {@link ClassOrderer} will be invoked. - * - * @since 5.8 - * @see ClassOrderer - * @see ClassDescriptor - */ -@API(status = EXPERIMENTAL, since = "5.8") -public interface ClassOrdererContext { - - /** - * Get the list of {@linkplain ClassDescriptor class descriptors} to - * order. - * - * @return the list of class descriptors; never {@code null} - */ - List getClassDescriptors(); - - /** - * Get the configuration parameter stored under the specified {@code key}. - * - *

If no such key is present in the {@code ConfigurationParameters} for - * the JUnit Platform, an attempt will be made to look up the value as a - * JVM system property. If no such system property exists, an attempt will - * be made to look up the value in the JUnit Platform properties file. - * - * @param key the key to look up; never {@code null} or blank - * @return an {@code Optional} containing the value; never {@code null} - * but potentially empty - * - * @see System#getProperty(String) - * @see org.junit.platform.engine.ConfigurationParameters - */ - Optional getConfigurationParameter(String key); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java deleted file mode 100644 index 9a771705..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @Disabled} is used to signal that the annotated test class or - * test method is currently disabled and should not be executed. - * - *

{@code @Disabled} may optionally be declared with a {@linkplain #value - * reason} to document why the annotated test class or test method is disabled. - * - *

When applied at the class level, all test methods within that class - * are automatically disabled as well. - * - *

When applied at the method level, the presence of this annotation does not - * prevent the test class from being instantiated. Rather, it prevents the - * execution of the test method and method-level lifecycle callbacks such as - * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding - * extension APIs. - * - * @since 5.0 - * @see #value - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.extension.ExecutionCondition - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -public @interface Disabled { - - /** - * The reason this annotated test class or test method is disabled. - */ - String value() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java deleted file mode 100644 index e99ee34c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @DisplayName} is used to declare a {@linkplain #value custom display - * name} for the annotated test class or test method. - * - *

Display names are typically used for test reporting in IDEs and build - * tools and may contain spaces, special characters, and even emoji. - * - * @since 5.0 - * @see Test - * @see Tag - * @see TestInfo - * @see DisplayNameGeneration - * @see DisplayNameGenerator - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -public @interface DisplayName { - - /** - * Custom display name for the annotated class or method. - * - * @return a custom display name; never blank or consisting solely of - * whitespace - */ - String value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java deleted file mode 100644 index bb0e1bf9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @DisplayNameGeneration} is used to declare a custom display name - * generator for the annotated test class. - * - *

This annotation is inherited from superclasses and implemented - * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() - * enclosing classes} for {@link Nested @Nested} test classes. - * - *

As an alternative to {@code @DisplayNameGeneration}, a global - * {@link DisplayNameGenerator} can be configured for the entire test suite via - * the {@value DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME} configuration parameter. See - * the User Guide for details. Note, however, that a {@code @DisplayNameGeneration} - * declaration always overrides a global {@code DisplayNameGenerator}. - * - * @since 5.4 - * @see DisplayName - * @see DisplayNameGenerator - * @see IndicativeSentencesGeneration - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@API(status = STABLE, since = "5.7") -public @interface DisplayNameGeneration { - - /** - * Custom display name generator. - * - * @return custom display name generator class - */ - Class value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java deleted file mode 100644 index 7d62049a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.support.ModifierSupport.isStatic; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * {@code DisplayNameGenerator} defines the SPI for generating display names - * programmatically. - * - *

Display names are typically used for test reporting in IDEs and build - * tools and may contain spaces, special characters, and even emoji. - * - *

Concrete implementations must have a default constructor. - * - *

A {@link DisplayNameGenerator} can be configured globally for the - * entire test suite via the {@value #DEFAULT_GENERATOR_PROPERTY_NAME} - * configuration parameter (see the User Guide for details) or locally - * for a test class via the {@link DisplayNameGeneration @DisplayNameGeneration} - * annotation. - * - *

Built-in Implementations

- *
    - *
  • {@link Standard}
  • - *
  • {@link Simple}
  • - *
  • {@link ReplaceUnderscores}
  • - *
  • {@link IndicativeSentences}
  • - *
- * - * @since 5.4 - * @see DisplayName @DisplayName - * @see DisplayNameGeneration @DisplayNameGeneration - */ -@API(status = STABLE, since = "5.7") -public interface DisplayNameGenerator { - - /** - * Property name used to set the default display name generator class name: - * {@value} - * - *

Supported Values

- * - *

Supported values include fully qualified class names for types that - * implement {@link DisplayNameGenerator}. - * - *

If not specified, the default is - * {@link DisplayNameGenerator.Standard}. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_GENERATOR_PROPERTY_NAME = "junit.jupiter.displayname.generator.default"; - - /** - * Generate a display name for the given top-level or {@code static} nested test class. - * - * @param testClass the class to generate a name for; never {@code null} - * @return the display name for the class; never {@code null} or blank - */ - String generateDisplayNameForClass(Class testClass); - - /** - * Generate a display name for the given {@link Nested @Nested} inner test class. - * - * @param nestedClass the class to generate a name for; never {@code null} - * @return the display name for the nested class; never {@code null} or blank - */ - String generateDisplayNameForNestedClass(Class nestedClass); - - /** - * Generate a display name for the given method. - * - * @implNote The class instance supplied as {@code testClass} may differ from - * the class returned by {@code testMethod.getDeclaringClass()} — for - * example, when a test method is inherited from a superclass. - * - * @param testClass the class the test method is invoked on; never {@code null} - * @param testMethod method to generate a display name for; never {@code null} - * @return the display name for the test; never {@code null} or blank - */ - String generateDisplayNameForMethod(Class testClass, Method testMethod); - - /** - * Generate a string representation of the formal parameters of the supplied - * method, consisting of the {@linkplain Class#getSimpleName() simple names} - * of the parameter types, separated by commas, and enclosed in parentheses. - * - * @param method the method from to extract the parameter types from; never - * {@code null} - * @return a string representation of all parameter types of the supplied - * method or {@code "()"} if the method declares no parameters - */ - static String parameterTypesAsString(Method method) { - Preconditions.notNull(method, "Method must not be null"); - return '(' + ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes()) + ')'; - } - - /** - * Standard {@code DisplayNameGenerator}. - * - *

This implementation matches the standard display name generation - * behavior in place since JUnit Jupiter 5.0 was released. - */ - class Standard implements DisplayNameGenerator { - - static final DisplayNameGenerator INSTANCE = new Standard(); - - public Standard() { - } - - @Override - public String generateDisplayNameForClass(Class testClass) { - String name = testClass.getName(); - int lastDot = name.lastIndexOf('.'); - return name.substring(lastDot + 1); - } - - @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return nestedClass.getSimpleName(); - } - - @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return testMethod.getName() + parameterTypesAsString(testMethod); - } - } - - /** - * Simple {@code DisplayNameGenerator} that removes trailing parentheses - * for methods with no parameters. - * - *

This generator extends the functionality of {@link Standard} by - * removing parentheses ({@code '()'}) found at the end of method names - * with no parameters. - */ - class Simple extends Standard { - - static final DisplayNameGenerator INSTANCE = new Simple(); - - public Simple() { - } - - @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - String displayName = testMethod.getName(); - if (hasParameters(testMethod)) { - displayName += ' ' + parameterTypesAsString(testMethod); - } - return displayName; - } - - private static boolean hasParameters(Method method) { - return method.getParameterCount() > 0; - } - - } - - /** - * {@code DisplayNameGenerator} that replaces underscores with spaces. - * - *

This generator extends the functionality of {@link Simple} by - * replacing all underscores ({@code '_'}) found in class and method names - * with spaces ({@code ' '}). - */ - class ReplaceUnderscores extends Simple { - - static final DisplayNameGenerator INSTANCE = new ReplaceUnderscores(); - - public ReplaceUnderscores() { - } - - @Override - public String generateDisplayNameForClass(Class testClass) { - return replaceUnderscores(super.generateDisplayNameForClass(testClass)); - } - - @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass)); - } - - @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod)); - } - - private static String replaceUnderscores(String name) { - return name.replace('_', ' '); - } - - } - - /** - * {@code DisplayNameGenerator} that generates complete sentences. - * - *

This generator generates display names that build up complete sentences - * by concatenating the names of the test and the enclosing classes. The - * sentence fragments are concatenated using a separator. The separator and - * the display name generator for individual sentence fragments can be configured - * via the {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} - * annotation. - * - * @since 5.7 - */ - @API(status = EXPERIMENTAL, since = "5.7") - class IndicativeSentences implements DisplayNameGenerator { - - static final DisplayNameGenerator INSTANCE = new IndicativeSentences(); - - public IndicativeSentences() { - } - - @Override - public String generateDisplayNameForClass(Class testClass) { - return getGeneratorFor(testClass).generateDisplayNameForClass(testClass); - } - - @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return getSentenceBeginning(nestedClass); - } - - @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return getSentenceBeginning(testClass) + getFragmentSeparator(testClass) - + getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod); - } - - private String getSentenceBeginning(Class testClass) { - Class enclosingClass = testClass.getEnclosingClass(); - boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); - Optional displayName = findAnnotation(testClass, DisplayName.class)// - .map(DisplayName::value).map(String::trim); - - if (topLevelTestClass) { - if (displayName.isPresent()) { - return displayName.get(); - } - Class generatorClass = findDisplayNameGeneration(testClass)// - .map(DisplayNameGeneration::value)// - .filter(not(IndicativeSentences.class))// - .orElse(null); - if (generatorClass != null) { - return getDisplayNameGenerator(generatorClass).generateDisplayNameForClass(testClass); - } - return generateDisplayNameForClass(testClass); - } - - // Only build prefix based on the enclosing class if the enclosing - // class is also configured to use the IndicativeSentences generator. - boolean buildPrefix = findDisplayNameGeneration(enclosingClass)// - .map(DisplayNameGeneration::value)// - .filter(IndicativeSentences.class::equals)// - .isPresent(); - - String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : ""); - - return prefix + displayName.orElseGet( - () -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass)); - } - - /** - * Get the sentence fragment separator. - * - *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} - * is present (searching enclosing classes if not found locally), the - * configured {@link IndicativeSentencesGeneration#separator() separator} - * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_SEPARATOR} - * will be used. - * - * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} - * @return the sentence fragment separator - */ - private static String getFragmentSeparator(Class testClass) { - return findIndicativeSentencesGeneration(testClass)// - .map(IndicativeSentencesGeneration::separator)// - .orElse(IndicativeSentencesGeneration.DEFAULT_SEPARATOR); - } - - /** - * Get the display name generator to use for the supplied test class. - * - *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} - * is present (searching enclosing classes if not found locally), the - * configured {@link IndicativeSentencesGeneration#generator() generator} - * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_GENERATOR} - * will be used. - * - * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} - * @return the {@code DisplayNameGenerator} instance to use - */ - private static DisplayNameGenerator getGeneratorFor(Class testClass) { - return findIndicativeSentencesGeneration(testClass)// - .map(IndicativeSentencesGeneration::generator)// - .filter(not(IndicativeSentences.class))// - .map(DisplayNameGenerator::getDisplayNameGenerator)// - .orElseGet(() -> getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR)); - } - - /** - * Find the first {@code DisplayNameGeneration} annotation that is either - * directly present, meta-present, or indirectly present - * on the supplied {@code testClass} or on an enclosing class. - * - * @param testClass the test class on which to find the annotation; never {@code null} - * @return an {@code Optional} containing the annotation, potentially empty if not found - */ - private static Optional findDisplayNameGeneration(Class testClass) { - return findAnnotation(testClass, DisplayNameGeneration.class, true); - } - - /** - * Find the first {@code IndicativeSentencesGeneration} annotation that is either - * directly present, meta-present, or indirectly present - * on the supplied {@code testClass} or on an enclosing class. - * - * @param testClass the test class on which to find the annotation; never {@code null} - * @return an {@code Optional} containing the annotation, potentially empty if not found - */ - private static Optional findIndicativeSentencesGeneration(Class testClass) { - return findAnnotation(testClass, IndicativeSentencesGeneration.class, true); - } - - private static Predicate> not(Class clazz) { - return ((Predicate>) clazz::equals).negate(); - } - - } - - /** - * Return the {@code DisplayNameGenerator} instance corresponding to the - * given {@code Class}. - * - * @param generatorClass the generator's {@code Class}; never {@code null}, - * has to be a {@code DisplayNameGenerator} implementation - * @return a {@code DisplayNameGenerator} implementation instance - */ - static DisplayNameGenerator getDisplayNameGenerator(Class generatorClass) { - Preconditions.notNull(generatorClass, "Class must not be null"); - Preconditions.condition(DisplayNameGenerator.class.isAssignableFrom(generatorClass), - "Class must be a DisplayNameGenerator implementation"); - if (generatorClass == Standard.class) { - return Standard.INSTANCE; - } - if (generatorClass == Simple.class) { - return Simple.INSTANCE; - } - if (generatorClass == ReplaceUnderscores.class) { - return ReplaceUnderscores.INSTANCE; - } - if (generatorClass == IndicativeSentences.class) { - return IndicativeSentences.INSTANCE; - } - return (DisplayNameGenerator) ReflectionUtils.newInstance(generatorClass); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java deleted file mode 100644 index dcc48186..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.net.URI; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; - -/** - * A {@code DynamicContainer} is a container generated at runtime. - * - *

It is composed of a {@linkplain DynamicNode#getDisplayName display name} - * and an {@link Iterable} or {@link Stream} of {@link DynamicNode DynamicNodes}. - * - *

Instances of {@code DynamicContainer} must be generated by factory methods - * annotated with {@link TestFactory @TestFactory}. - * - * @since 5.0 - * @see #dynamicContainer(String, Iterable) - * @see #dynamicContainer(String, Stream) - * @see TestFactory - * @see DynamicTest - */ -@API(status = MAINTAINED, since = "5.3") -public class DynamicContainer extends DynamicNode { - - /** - * Factory for creating a new {@code DynamicContainer} for the supplied display - * name and collection of dynamic nodes. - * - *

The collection of dynamic nodes must not contain {@code null} elements. - * - * @param displayName the display name for the dynamic container; never - * {@code null} or blank - * @param dynamicNodes collection of dynamic nodes to execute; - * never {@code null} - * @see #dynamicContainer(String, Stream) - */ - public static DynamicContainer dynamicContainer(String displayName, Iterable dynamicNodes) { - return dynamicContainer(displayName, null, StreamSupport.stream(dynamicNodes.spliterator(), false)); - } - - /** - * Factory for creating a new {@code DynamicContainer} for the supplied display - * name and stream of dynamic nodes. - * - *

The stream of dynamic nodes must not contain {@code null} elements. - * - * @param displayName the display name for the dynamic container; never - * {@code null} or blank - * @param dynamicNodes stream of dynamic nodes to execute; - * never {@code null} - * @see #dynamicContainer(String, Iterable) - */ - public static DynamicContainer dynamicContainer(String displayName, Stream dynamicNodes) { - return dynamicContainer(displayName, null, dynamicNodes); - } - - /** - * Factory for creating a new {@code DynamicContainer} for the supplied display - * name, custom test source {@link URI}, and stream of dynamic nodes. - * - *

The stream of dynamic nodes must not contain {@code null} elements. - * - * @param displayName the display name for the dynamic container; never - * {@code null} or blank - * @param testSourceUri a custom test source URI for the dynamic container; - * may be {@code null} if the framework should generate the test source based - * on the {@code @TestFactory} method - * @param dynamicNodes stream of dynamic nodes to execute; never {@code null} - * @since 5.3 - * @see #dynamicContainer(String, Iterable) - */ - public static DynamicContainer dynamicContainer(String displayName, URI testSourceUri, - Stream dynamicNodes) { - - return new DynamicContainer(displayName, testSourceUri, dynamicNodes); - } - - private final Stream children; - - private DynamicContainer(String displayName, URI testSourceUri, Stream children) { - super(displayName, testSourceUri); - Preconditions.notNull(children, "children must not be null"); - this.children = children; - } - - /** - * Get the {@link Stream} of {@link DynamicNode DynamicNodes} associated - * with this {@code DynamicContainer}. - */ - public Stream getChildren() { - return children; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java deleted file mode 100644 index c1a15177..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.net.URI; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@code DynamicNode} serves as the abstract base class for a container or a - * test case generated at runtime. - * - * @since 5.0 - * @see DynamicTest - * @see DynamicContainer - */ -@API(status = MAINTAINED, since = "5.3") -public abstract class DynamicNode { - - private final String displayName; - - /** Custom test source {@link URI} associated with this node; potentially {@code null}. */ - private final URI testSourceUri; - - DynamicNode(String displayName, URI testSourceUri) { - this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank"); - this.testSourceUri = testSourceUri; - } - - /** - * Get the display name of this {@code DynamicNode}. - * - * @return the display name - */ - public String getDisplayName() { - return this.displayName; - } - - /** - * Get the custom test source {@link URI} of this {@code DynamicNode}. - * - * @return an {@code Optional} containing the custom test source {@link URI}; - * never {@code null} but potentially empty - * @since 5.3 - */ - public Optional getTestSourceUri() { - return Optional.ofNullable(testSourceUri); - } - - @Override - public String toString() { - return new ToStringBuilder(this) // - .append("displayName", displayName) // - .append("testSourceUri", testSourceUri) // - .toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java deleted file mode 100644 index 93595166..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.util.Spliterator.ORDERED; -import static java.util.Spliterators.spliteratorUnknownSize; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.net.URI; -import java.util.Iterator; -import java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.function.ThrowingConsumer; -import org.junit.platform.commons.util.Preconditions; - -/** - * A {@code DynamicTest} is a test case generated at runtime. - * - *

It is composed of a {@linkplain DynamicNode#getDisplayName display name} - * and an {@link #getExecutable Executable}. - * - *

Instances of {@code DynamicTest} must be generated by factory methods - * annotated with {@link TestFactory @TestFactory}. - * - *

Note that dynamic tests are quite different from standard {@link Test @Test} - * cases since callbacks such as {@link BeforeEach @BeforeEach} and - * {@link AfterEach @AfterEach} methods are not executed for dynamic tests. - * - * @since 5.0 - * @see #dynamicTest(String, Executable) - * @see #stream(Iterator, Function, ThrowingConsumer) - * @see Test - * @see TestFactory - * @see DynamicContainer - * @see Executable - */ -@API(status = MAINTAINED, since = "5.3") -public class DynamicTest extends DynamicNode { - - /** - * Factory for creating a new {@code DynamicTest} for the supplied display - * name and executable code block. - * - * @param displayName the display name for the dynamic test; never - * {@code null} or blank - * @param executable the executable code block for the dynamic test; - * never {@code null} - * @see #stream(Iterator, Function, ThrowingConsumer) - */ - public static DynamicTest dynamicTest(String displayName, Executable executable) { - return new DynamicTest(displayName, null, executable); - } - - /** - * Factory for creating a new {@code DynamicTest} for the supplied display - * name, custom test source {@link URI}, and executable code block. - * - * @param displayName the display name for the dynamic test; never - * {@code null} or blank - * @param testSourceUri a custom test source URI for the dynamic test; may - * be {@code null} if the framework should generate the test source based on - * the {@code @TestFactory} method - * @param executable the executable code block for the dynamic test; - * never {@code null} - * @since 5.3 - * @see #stream(Iterator, Function, ThrowingConsumer) - */ - public static DynamicTest dynamicTest(String displayName, URI testSourceUri, Executable executable) { - return new DynamicTest(displayName, testSourceUri, executable); - } - - /** - * Generate a stream of dynamic tests based on the given generator and test - * executor. - * - *

Use this method when the set of dynamic tests is nondeterministic in - * nature or when the input comes from an existing {@link Iterator}. See - * {@link #stream(Stream, Function, ThrowingConsumer)} as an alternative. - * - *

The given {@code inputGenerator} is responsible for generating - * input values. A {@link DynamicTest} will be added to the resulting - * stream for each dynamically generated input value, using the given - * {@code displayNameGenerator} and {@code testExecutor}. - * - * @param inputGenerator an {@code Iterator} that serves as a dynamic - * input generator; never {@code null} - * @param displayNameGenerator a function that generates a display name - * based on an input value; never {@code null} - * @param testExecutor a consumer that executes a test based on an input - * value; never {@code null} - * @param the type of input generated by the {@code inputGenerator} - * and used by the {@code displayNameGenerator} and {@code testExecutor} - * @return a stream of dynamic tests based on the given generator and - * executor; never {@code null} - * @see #dynamicTest(String, Executable) - * @see #stream(Stream, Function, ThrowingConsumer) - */ - public static Stream stream(Iterator inputGenerator, - Function displayNameGenerator, ThrowingConsumer testExecutor) { - - Preconditions.notNull(inputGenerator, "inputGenerator must not be null"); - - return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), - displayNameGenerator, testExecutor); - } - - /** - * Generate a stream of dynamic tests based on the given input stream and - * test executor. - * - *

Use this method when the set of dynamic tests is nondeterministic in - * nature or when the input comes from an existing {@link Stream}. See - * {@link #stream(Iterator, Function, ThrowingConsumer)} as an alternative. - * - *

The given {@code inputStream} is responsible for supplying input values. - * A {@link DynamicTest} will be added to the resulting stream for each - * dynamically supplied input value, using the given {@code displayNameGenerator} - * and {@code testExecutor}. - * - * @param inputStream a {@code Stream} that supplies dynamic input values; - * never {@code null} - * @param displayNameGenerator a function that generates a display name - * based on an input value; never {@code null} - * @param testExecutor a consumer that executes a test based on an input - * value; never {@code null} - * @param the type of input supplied by the {@code inputStream} - * and used by the {@code displayNameGenerator} and {@code testExecutor} - * @return a stream of dynamic tests based on the given generator and - * executor; never {@code null} - * @since 5.7 - * @see #dynamicTest(String, Executable) - * @see #stream(Iterator, Function, ThrowingConsumer) - */ - @API(status = MAINTAINED, since = "5.7") - public static Stream stream(Stream inputStream, - Function displayNameGenerator, ThrowingConsumer testExecutor) { - - Preconditions.notNull(inputStream, "inputStream must not be null"); - Preconditions.notNull(displayNameGenerator, "displayNameGenerator must not be null"); - Preconditions.notNull(testExecutor, "testExecutor must not be null"); - - return inputStream // - .map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input))); - } - - /** - * Generate a stream of dynamic tests based on the given generator and test - * executor. - * - *

Use this method when the set of dynamic tests is nondeterministic in - * nature or when the input comes from an existing {@link Iterator}. See - * {@link #stream(Stream, ThrowingConsumer)} as an alternative. - * - *

The given {@code inputGenerator} is responsible for generating - * input values and display names. A {@link DynamicTest} will be added to - * the resulting stream for each dynamically generated input value, - * using the given {@code testExecutor}. - * - * @param inputGenerator an {@code Iterator} with {@code Named} values - * that serves as a dynamic input generator; never {@code null} - * @param testExecutor a consumer that executes a test based on an input - * value; never {@code null} - * @param the type of input generated by the {@code inputGenerator} - * and used by the {@code testExecutor} - * @return a stream of dynamic tests based on the given generator and - * executor; never {@code null} - * @since 5.8 - * - * @see #dynamicTest(String, Executable) - * @see #stream(Stream, ThrowingConsumer) - * @see Named - */ - @API(status = MAINTAINED, since = "5.8") - public static Stream stream(Iterator> inputGenerator, - ThrowingConsumer testExecutor) { - Preconditions.notNull(inputGenerator, "inputGenerator must not be null"); - - return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), testExecutor); - } - - /** - * Generate a stream of dynamic tests based on the given input stream and - * test executor. - * - *

Use this method when the set of dynamic tests is nondeterministic in - * nature or when the input comes from an existing {@link Stream}. See - * {@link #stream(Iterator, ThrowingConsumer)} as an alternative. - * - *

The given {@code inputStream} is responsible for supplying input values - * and display names. A {@link DynamicTest} will be added to the resulting stream for - * each dynamically supplied input value, using the given {@code testExecutor}. - * - * @param inputStream a {@code Stream} that supplies dynamic {@code Named} - * input values; never {@code null} - * @param testExecutor a consumer that executes a test based on an input - * value; never {@code null} - * @param the type of input supplied by the {@code inputStream} - * and used by the {@code displayNameGenerator} and {@code testExecutor} - * @return a stream of dynamic tests based on the given generator and - * executor; never {@code null} - * @since 5.8 - * - * @see #dynamicTest(String, Executable) - * @see #stream(Iterator, ThrowingConsumer) - * @see Named - */ - @API(status = MAINTAINED, since = "5.8") - public static Stream stream(Stream> inputStream, - ThrowingConsumer testExecutor) { - Preconditions.notNull(inputStream, "inputStream must not be null"); - Preconditions.notNull(testExecutor, "testExecutor must not be null"); - - return inputStream // - .map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload()))); - } - - private final Executable executable; - - private DynamicTest(String displayName, URI testSourceUri, Executable executable) { - super(displayName, testSourceUri); - this.executable = Preconditions.notNull(executable, "executable must not be null"); - } - - /** - * Get the {@code executable} code block associated with this {@code DynamicTest}. - */ - public Executable getExecutable() { - return this.executable; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java deleted file mode 100644 index 6925a2b6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; - -/** - * {@code @IndicativeSentencesGeneration} is used to register the - * {@link IndicativeSentences} display name generator and configure it. - * - *

The {@link #separator} for sentence fragments and the display name - * {@link #generator} for sentence fragments are configurable. If this annotation - * is declared without any attributes — for example, - * {@code @IndicativeSentencesGeneration} or {@code @IndicativeSentencesGeneration()} - * — the default configuration will be used. - * - *

This annotation is inherited from superclasses and implemented - * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() - * enclosing classes} for {@link Nested @Nested} test classes. - * - * @since 5.7 - * @see DisplayName - * @see DisplayNameGenerator - * @see DisplayNameGenerator.IndicativeSentences - * @see DisplayNameGeneration - */ -@DisplayNameGeneration(IndicativeSentences.class) -@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@API(status = EXPERIMENTAL, since = "5.7") -public @interface IndicativeSentencesGeneration { - - String DEFAULT_SEPARATOR = ", "; - - Class DEFAULT_GENERATOR = DisplayNameGenerator.Standard.class; - - /** - * Custom separator for sentence fragments. - * - *

Defaults to {@value #DEFAULT_SEPARATOR}. - */ - String separator() default DEFAULT_SEPARATOR; - - /** - * Custom display name generator to use for sentence fragments. - * - *

Defaults to {@link DisplayNameGenerator.Standard}. - */ - Class generator() default DisplayNameGenerator.Standard.class; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java deleted file mode 100644 index 20550832..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code MethodDescriptor} encapsulates functionality for a given {@link Method}. - * - * @since 5.4 - * @see MethodOrdererContext - */ -@API(status = STABLE, since = "5.7") -public interface MethodDescriptor { - - /** - * Get the method for this descriptor. - * - * @return the method; never {@code null} - */ - Method getMethod(); - - /** - * Get the display name for this descriptor's {@link #getMethod() method}. - * - * @return the display name for this descriptor's method; never {@code null} - * or blank - * @since 5.7 - */ - @API(status = EXPERIMENTAL, since = "5.7") - String getDisplayName(); - - /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the {@link Method} for - * this descriptor. - * - * @param annotationType the annotation type to search for; never {@code null} - * @return {@code true} if the annotation is present or meta-present - * @see #findAnnotation(Class) - * @see #findRepeatableAnnotations(Class) - */ - boolean isAnnotated(Class annotationType); - - /** - * Find the first annotation of {@code annotationType} that is either - * present or meta-present on the {@link Method} for - * this descriptor. - * - * @param the annotation type - * @param annotationType the annotation type to search for; never {@code null} - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty - * @see #isAnnotated(Class) - * @see #findRepeatableAnnotations(Class) - */ - Optional findAnnotation(Class annotationType); - - /** - * Find all repeatable {@linkplain Annotation annotations} of - * {@code annotationType} that are either present or - * meta-present on the {@link Method} for this descriptor. - * - * @param the annotation type - * @param annotationType the repeatable annotation type to search for; never - * {@code null} - * @return the list of all such annotations found; neither {@code null} nor - * mutable, but potentially empty - * @see #isAnnotated(Class) - * @see #findAnnotation(Class) - * @see java.lang.annotation.Repeatable - */ - List findRepeatableAnnotations(Class annotationType); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java deleted file mode 100644 index 7818020d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.util.Comparator.comparingInt; -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Comparator; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassUtils; - -/** - * {@code MethodOrderer} defines the API for ordering the test methods - * in a given test class. - * - *

In this context, the term "test method" refers to any method annotated with - * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, - * {@code @TestFactory}, or {@code @TestTemplate}. - * - *

A {@link MethodOrderer} can be configured globally for the entire - * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration - * parameter (see the User Guide for details) or locally for a test - * class via the {@link TestMethodOrder @TestMethodOrder} annotation. - * - *

Built-in Implementations

- * - *

JUnit Jupiter provides the following built-in {@code MethodOrderer} - * implementations. - * - *

    - *
  • {@link MethodName}
  • - *
  • {@link OrderAnnotation}
  • - *
  • {@link Random}
  • - *
- * - * @since 5.4 - * @see TestMethodOrder - * @see MethodOrdererContext - * @see #orderMethods(MethodOrdererContext) - * @see ClassOrderer - */ -@API(status = STABLE, since = "5.7") -public interface MethodOrderer { - - /** - * Property name used to set the default method orderer class name: {@value} - * - *

Supported Values

- * - *

Supported values include fully qualified class names for types that - * implement {@link org.junit.jupiter.api.MethodOrderer}. - * - *

If not specified, test methods will be ordered using an algorithm that - * is deterministic but intentionally non-obvious. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testmethod.order.default"; - - /** - * Order the methods encapsulated in the supplied {@link MethodOrdererContext}. - * - *

The methods to order or sort are made indirectly available via - * {@link MethodOrdererContext#getMethodDescriptors()}. Since this method - * has a {@code void} return type, the list of method descriptors must be - * modified directly. - * - *

For example, a simplified implementation of the {@link Random} - * {@code MethodOrderer} might look like the following. - * - *

-	 * public void orderMethods(MethodOrdererContext context) {
-	 *     Collections.shuffle(context.getMethodDescriptors());
-	 * }
-	 * 
- * - * @param context the {@code MethodOrdererContext} containing the - * {@linkplain MethodDescriptor method descriptors} to order; never {@code null} - * @see #getDefaultExecutionMode() - */ - void orderMethods(MethodOrdererContext context); - - /** - * Get the default {@link ExecutionMode} for the test class - * configured with this {@link MethodOrderer}. - * - *

This method is guaranteed to be invoked after - * {@link #orderMethods(MethodOrdererContext)} which allows implementations - * of this method to determine the appropriate return value programmatically, - * potentially based on actions that were taken in {@code orderMethods()}. - * - *

Defaults to {@link ExecutionMode#SAME_THREAD SAME_THREAD}, since - * ordered methods are typically sorted in a fashion that would conflict - * with concurrent execution. - * - *

In case the ordering does not conflict with concurrent execution, - * implementations should return an empty {@link Optional} to signal that - * the engine should decide which execution mode to use. - * - *

Can be overridden via an explicit - * {@link org.junit.jupiter.api.parallel.Execution @Execution} declaration - * on the test class or in concrete implementations of the - * {@code MethodOrderer} API. - * - * @return the default {@code ExecutionMode}; never {@code null} but - * potentially empty - * @see #orderMethods(MethodOrdererContext) - */ - default Optional getDefaultExecutionMode() { - return Optional.of(ExecutionMode.SAME_THREAD); - } - - /** - * {@code MethodOrderer} that sorts methods alphanumerically based on their - * names using {@link String#compareTo(String)}. - * - *

If two methods have the same name, {@code String} representations of - * their formal parameter lists will be used as a fallback for comparing the - * methods. - * - * @since 5.4 - * @deprecated as of JUnit Jupiter 5.7 in favor of {@link MethodOrderer.MethodName}; - * to be removed in 6.0 - */ - @API(status = DEPRECATED, since = "5.7") - @Deprecated - class Alphanumeric extends MethodName { - - public Alphanumeric() { - } - } - - /** - * {@code MethodOrderer} that sorts methods alphanumerically based on their - * names using {@link String#compareTo(String)}. - * - *

If two methods have the same name, {@code String} representations of - * their formal parameter lists will be used as a fallback for comparing the - * methods. - * - * @since 5.7 - */ - @API(status = EXPERIMENTAL, since = "5.7") - class MethodName implements MethodOrderer { - - public MethodName() { - } - - /** - * Sort the methods encapsulated in the supplied - * {@link MethodOrdererContext} alphanumerically based on their names - * and formal parameter lists. - */ - @Override - public void orderMethods(MethodOrdererContext context) { - context.getMethodDescriptors().sort(comparator); - } - - private static final Comparator comparator = Comparator. // - comparing(descriptor -> descriptor.getMethod().getName())// - .thenComparing(descriptor -> parameterList(descriptor.getMethod())); - - private static String parameterList(Method method) { - return ClassUtils.nullSafeToString(method.getParameterTypes()); - } - } - - /** - * {@code MethodOrderer} that sorts methods alphanumerically based on their - * display names using {@link String#compareTo(String)} - * - * @since 5.7 - */ - @API(status = EXPERIMENTAL, since = "5.7") - class DisplayName implements MethodOrderer { - - public DisplayName() { - } - - /** - * Sort the methods encapsulated in the supplied - * {@link MethodOrdererContext} alphanumerically based on their display - * names. - */ - @Override - public void orderMethods(MethodOrdererContext context) { - context.getMethodDescriptors().sort(comparator); - } - - private static final Comparator comparator = Comparator.comparing( - MethodDescriptor::getDisplayName); - } - - /** - * {@code MethodOrderer} that sorts methods based on the {@link Order @Order} - * annotation. - * - *

Any methods that are assigned the same order value will be sorted - * arbitrarily adjacent to each other. - * - *

Any methods not annotated with {@code @Order} will be assigned the - * {@linkplain Order#DEFAULT default order} value which will effectively cause them - * to appear at the end of the sorted list, unless certain methods are assigned - * an explicit order value greater than the default order value. Any methods - * assigned an explicit order value greater than the default order value will - * appear after non-annotated methods in the sorted list. - */ - class OrderAnnotation implements MethodOrderer { - - public OrderAnnotation() { - } - - /** - * Sort the methods encapsulated in the supplied - * {@link MethodOrdererContext} based on the {@link Order @Order} - * annotation. - */ - @Override - public void orderMethods(MethodOrdererContext context) { - context.getMethodDescriptors().sort(comparingInt(OrderAnnotation::getOrder)); - } - - private static int getOrder(MethodDescriptor descriptor) { - return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT); - } - } - - /** - * {@code MethodOrderer} that orders methods pseudo-randomly. - * - *

Custom Seed

- * - *

By default, the random seed used for ordering methods is the - * value returned by {@link System#nanoTime()} during static initialization - * of this class. In order to support repeatable builds, the value of the - * default random seed is logged at {@code CONFIG} level. In addition, a - * custom seed (potentially the default seed from the previous test plan - * execution) may be specified via the {@value ClassOrderer.Random#RANDOM_SEED_PROPERTY_NAME} - * configuration parameter which can be supplied via the {@code Launcher} - * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit - * Platform configuration file (i.e., a file named {@code junit-platform.properties} - * in the root of the class path). Consult the User Guide for further information. - * - * @see Random#RANDOM_SEED_PROPERTY_NAME - * @see java.util.Random - */ - class Random implements MethodOrderer { - - private static final Logger logger = LoggerFactory.getLogger(Random.class); - - /** - * Default seed, which is generated during initialization of this class - * via {@link System#nanoTime()} for reproducibility of tests. - */ - private static final long DEFAULT_SEED; - - static { - DEFAULT_SEED = System.nanoTime(); - logger.config(() -> "MethodOrderer.Random default seed: " + DEFAULT_SEED); - } - - /** - * Property name used to set the random seed used by this - * {@code MethodOrderer}: {@value} - * - *

The same property is used by {@link ClassOrderer.Random} for - * consistency between the two random orderers. - * - *

Supported Values

- * - *

Supported values include any string that can be converted to a - * {@link Long} via {@link Long#valueOf(String)}. - * - *

If not specified or if the specified value cannot be converted to - * a {@link Long}, the default random seed will be used (see the - * {@linkplain Random class-level Javadoc} for details). - * - * @see ClassOrderer.Random - */ - public static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed"; - - public Random() { - } - - /** - * Order the methods encapsulated in the supplied - * {@link MethodOrdererContext} pseudo-randomly. - */ - @Override - public void orderMethods(MethodOrdererContext context) { - Collections.shuffle(context.getMethodDescriptors(), - new java.util.Random(getCustomSeed(context).orElse(DEFAULT_SEED))); - } - - private Optional getCustomSeed(MethodOrdererContext context) { - return context.getConfigurationParameter(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> { - Long seed = null; - try { - seed = Long.valueOf(configurationParameter); - logger.config( - () -> String.format("Using custom seed for configuration parameter [%s] with value [%s].", - RANDOM_SEED_PROPERTY_NAME, configurationParameter)); - } - catch (NumberFormatException ex) { - logger.warn(ex, - () -> String.format( - "Failed to convert configuration parameter [%s] with value [%s] to a long. " - + "Using default seed [%s] as fallback.", - RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); - } - return seed; - }); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java deleted file mode 100644 index cb585b70..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code MethodOrdererContext} encapsulates the context in which - * a {@link MethodOrderer} will be invoked. - * - * @since 5.4 - * @see MethodOrderer - * @see MethodDescriptor - */ -@API(status = STABLE, since = "5.7") -public interface MethodOrdererContext { - - /** - * Get the test class for this context. - * - * @return the test class; never {@code null} - */ - Class getTestClass(); - - /** - * Get the list of {@linkplain MethodDescriptor method descriptors} to - * order. - * - * @return the list of method descriptors; never {@code null} - */ - List getMethodDescriptors(); - - /** - * Get the configuration parameter stored under the specified {@code key}. - * - *

If no such key is present in the {@code ConfigurationParameters} for - * the JUnit Platform, an attempt will be made to look up the value as a - * JVM system property. If no such system property exists, an attempt will - * be made to look up the value in the JUnit Platform properties file. - * - * @param key the key to look up; never {@code null} or blank - * @return an {@code Optional} containing the value; never {@code null} - * but potentially empty - * - * @see System#getProperty(String) - * @see org.junit.platform.engine.ConfigurationParameters - */ - Optional getConfigurationParameter(String key); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java deleted file mode 100644 index 6dee03e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@code Named} is a container that associates a name with a given payload. - * - * @param the type of the payload - * - * @since 5.8 - */ -@API(status = STABLE, since = "5.8") -public interface Named { - - /** - * Factory method for creating an instance of {@code Named} based on a - * {@code name} and a {@code payload}. - * - * @param name the name associated with the payload; never {@code null} or - * blank - * @param payload the object that serves as the payload; may be {@code null} - * depending on the use case - * @param the type of the payload - * @return an instance of {@code Named}; never {@code null} - * @see #named(String, java.lang.Object) - */ - static Named of(String name, T payload) { - Preconditions.notBlank(name, "name must not be null or blank"); - - return new Named() { - @Override - public String getName() { - return name; - } - - @Override - public T getPayload() { - return payload; - } - - @Override - public String toString() { - return name; - } - }; - } - - /** - * Factory method for creating an instance of {@code Named} based on a - * {@code name} and a {@code payload}. - * - *

This method is an alias for {@link Named#of} and is - * intended to be used when statically imported — for example, via: - * {@code import static org.junit.jupiter.api.Named.named;} - * - * @param name the name associated with the payload; never {@code null} or - * blank - * @param payload the object that serves as the payload; may be {@code null} - * depending on the use case - * @param the type of the payload - * @return an instance of {@code Named}; never {@code null} - */ - static Named named(String name, T payload) { - return of(name, payload); - } - - /** - * Get the name of the payload. - * - * @return the name of the payload; never {@code null} or blank - */ - String getName(); - - /** - * Get the payload. - * - * @return the payload; may be {@code null} depending on the use case - */ - T getPayload(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java deleted file mode 100644 index 1627010a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestInstance.Lifecycle; - -/** - * {@code @Nested} is used to signal that the annotated class is a nested, - * non-static test class (i.e., an inner class) that can share - * setup and state with an instance of its {@linkplain Class#getEnclosingClass() - * enclosing class}. The enclosing class may be a top-level test class or - * another {@code @Nested} test class, and nesting can be arbitrarily deep. - * - *

{@code @Nested} test classes may be ordered via - * {@link TestClassOrder @TestClassOrder} or a global {@link ClassOrderer}. - * - *

Test Instance Lifecycle

- * - *
    - *
  • A {@code @Nested} test class can be configured with its own - * {@link Lifecycle} mode which may differ from that of an enclosing test - * class.
  • - *
  • A {@code @Nested} test class cannot change the {@link Lifecycle} - * mode of an enclosing test class.
  • - *
- * - * @since 5.0 - * @see Test - * @see TestInstance - * @see TestClassOrder - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -public @interface Nested { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java deleted file mode 100644 index 5b48253f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @Order} is an annotation that is used to configure the - * {@linkplain #value order} in which the annotated element (i.e., field, - * method, or class) should be evaluated or executed relative to other elements - * of the same category. - * - *

When used with - * {@link org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension} or - * {@link org.junit.jupiter.api.extension.ExtendWith @ExtendWith}, - * the category applies to extension fields. When used with - * {@link MethodOrderer.OrderAnnotation}, the category applies to test methods. - * When used with {@link ClassOrderer.OrderAnnotation}, the category applies to - * test classes. - * - *

If {@code @Order} is not explicitly declared on an element, the - * {@link #DEFAULT} order value will be assigned to the element. - * - * @since 5.4 - * @see MethodOrderer.OrderAnnotation - * @see ClassOrderer.OrderAnnotation - * @see org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension - * @see org.junit.jupiter.api.extension.ExtendWith @ExtendWith - */ -@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.9") -public @interface Order { - - /** - * Default order value for elements not explicitly annotated with {@code @Order}, - * equal to the value of {@code Integer.MAX_VALUE / 2}. - * - * @since 5.6 - * @see Order#value - */ - int DEFAULT = Integer.MAX_VALUE / 2; - - /** - * The order value for the annotated element (i.e., field, method, or class). - * - *

Elements are ordered based on priority where a lower value has greater - * priority than a higher value. For example, {@link Integer#MAX_VALUE} has - * the lowest priority. - * - * @see #DEFAULT - */ - int value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java deleted file mode 100644 index 298de922..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @RepeatedTest} is used to signal that the annotated method is a - * test template method that should be repeated a {@linkplain #value - * specified number of times} with a configurable {@linkplain #name display - * name}. - * - *

Each invocation of the repeated test behaves like the execution of a - * regular {@link Test @Test} method with full support for the same lifecycle - * callbacks and extensions. In addition, the current repetition and total - * number of repetitions can be accessed by having the {@link RepetitionInfo} - * injected. - * - *

{@code @RepeatedTest} methods must not be {@code private} or {@code static} - * and must return {@code void}. - * - *

{@code @RepeatedTest} methods may optionally declare parameters to be - * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver - * ParameterResolvers}. - * - *

{@code @RepeatedTest} may also be used as a meta-annotation in order to - * create a custom composed annotation that inherits the semantics - * of {@code @RepeatedTest}. - * - *

Test Execution Order

- * - *

By default, test methods will be ordered using an algorithm that is - * deterministic but intentionally nonobvious. This ensures that subsequent runs - * of a test suite execute test methods in the same order, thereby allowing for - * repeatable builds. In this context, a test method is any instance - * method that is directly annotated or meta-annotated with {@code @Test}, - * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or - * {@code @TestTemplate}. - * - *

Although true unit tests typically should not rely on the order - * in which they are executed, there are times when it is necessary to enforce - * a specific test method execution order — for example, when writing - * integration tests or functional tests where the sequence of - * the tests is important, especially in conjunction with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. - * - *

To control the order in which test methods are executed, annotate your - * test class or test interface with {@link TestMethodOrder @TestMethodOrder} - * and specify the desired {@link MethodOrderer} implementation. - * - * @since 5.0 - * @see DisplayName - * @see RepetitionInfo - * @see TestTemplate - * @see TestInfo - * @see Test - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -@TestTemplate -public @interface RepeatedTest { - - /** - * Placeholder for the {@linkplain TestInfo#getDisplayName display name} of - * a {@code @RepeatedTest} method: {displayName} - */ - String DISPLAY_NAME_PLACEHOLDER = "{displayName}"; - - /** - * Placeholder for the current repetition count of a {@code @RepeatedTest} - * method: {currentRepetition} - */ - String CURRENT_REPETITION_PLACEHOLDER = "{currentRepetition}"; - - /** - * Placeholder for the total number of repetitions of a {@code @RepeatedTest} - * method: {totalRepetitions} - */ - String TOTAL_REPETITIONS_PLACEHOLDER = "{totalRepetitions}"; - - /** - * Short display name pattern for a repeated test: {@value} - * - * @see #CURRENT_REPETITION_PLACEHOLDER - * @see #TOTAL_REPETITIONS_PLACEHOLDER - * @see #LONG_DISPLAY_NAME - */ - String SHORT_DISPLAY_NAME = "repetition " + CURRENT_REPETITION_PLACEHOLDER + " of " + TOTAL_REPETITIONS_PLACEHOLDER; - - /** - * Long display name pattern for a repeated test: {@value} - * - * @see #DISPLAY_NAME_PLACEHOLDER - * @see #SHORT_DISPLAY_NAME - */ - String LONG_DISPLAY_NAME = DISPLAY_NAME_PLACEHOLDER + " :: " + SHORT_DISPLAY_NAME; - - /** - * The number of repetitions. - * - * @return the number of repetitions; must be greater than zero - */ - int value(); - - /** - * The display name for each repetition of the repeated test. - * - *

Supported placeholders

- *
    - *
  • {@link #DISPLAY_NAME_PLACEHOLDER}
  • - *
  • {@link #CURRENT_REPETITION_PLACEHOLDER}
  • - *
  • {@link #TOTAL_REPETITIONS_PLACEHOLDER}
  • - *
- * - *

Defaults to {@link #SHORT_DISPLAY_NAME}, resulting in - * names such as {@code "repetition 1 of 2"}, {@code "repetition 2 of 2"}, - * etc. - * - *

Can be set to {@link #LONG_DISPLAY_NAME}, resulting in - * names such as {@code "myRepeatedTest() :: repetition 1 of 2"}, - * {@code "myRepeatedTest() :: repetition 2 of 2"}, etc. - * - *

Alternatively, you can provide a custom display name, optionally - * using the aforementioned placeholders. - * - * @return a custom display name; never blank or consisting solely of - * whitespace - * @see #SHORT_DISPLAY_NAME - * @see #LONG_DISPLAY_NAME - * @see #DISPLAY_NAME_PLACEHOLDER - * @see #CURRENT_REPETITION_PLACEHOLDER - * @see #TOTAL_REPETITIONS_PLACEHOLDER - * @see TestInfo#getDisplayName() - */ - String name() default SHORT_DISPLAY_NAME; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java deleted file mode 100644 index 89f5d1f0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code RepetitionInfo} is used to inject information about the current - * repetition of a repeated test into {@code @RepeatedTest}, {@code @BeforeEach}, - * and {@code @AfterEach} methods. - * - *

If a method parameter is of type {@code RepetitionInfo}, JUnit will - * supply an instance of {@code RepetitionInfo} corresponding to the current - * repeated test as the value for the parameter. - * - *

WARNING: {@code RepetitionInfo} cannot be injected into - * a {@code @BeforeEach} or {@code @AfterEach} method if the corresponding test - * method is not a {@code @RepeatedTest}. Any attempt to do so will result in a - * {@link org.junit.jupiter.api.extension.ParameterResolutionException - * ParameterResolutionException}. - * - * @since 5.0 - * @see RepeatedTest - * @see TestInfo - */ -@API(status = STABLE, since = "5.0") -public interface RepetitionInfo { - - /** - * Get the current repetition of the corresponding - * {@link RepeatedTest @RepeatedTest} method. - */ - int getCurrentRepetition(); - - /** - * Get the total number of repetitions of the corresponding - * {@link RepeatedTest @RepeatedTest} method. - * - * @see RepeatedTest#value - */ - int getTotalRepetitions(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java deleted file mode 100644 index 0def1ed3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @Tag} is a {@linkplain Repeatable repeatable} annotation that is - * used to declare a tag for the annotated test class or test method. - * - *

Tags are used to filter which tests are executed for a given test - * plan. For example, a development team may tag tests with values such as - * {@code "fast"}, {@code "slow"}, {@code "ci-server"}, etc. and then supply a - * list of tags to be included in or excluded from the current test plan, - * potentially dependent on the current environment. - * - *

Syntax Rules for Tags

- *
    - *
  • A tag must not be blank.
  • - *
  • A trimmed tag must not contain whitespace.
  • - *
  • A trimmed tag must not contain ISO control characters.
  • - *
  • A trimmed tag must not contain any of the following - * reserved characters. - *
      - *
    • {@code ,}: comma
    • - *
    • {@code (}: left parenthesis
    • - *
    • {@code )}: right parenthesis
    • - *
    • {@code &}: ampersand
    • - *
    • {@code |}: vertical bar
    • - *
    • {@code !}: exclamation point
    • - *
    - *
  • - *
- * - * @since 5.0 - * @see Tags - * @see Test - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@Repeatable(Tags.class) -@API(status = STABLE, since = "5.0") -public @interface Tag { - - /** - * The tag. - * - *

Note: the tag will first be {@linkplain String#trim() trimmed}. If the - * supplied tag is syntactically invalid after trimming, the error will be - * logged as a warning, and the invalid tag will be effectively ignored. See - * {@linkplain Tag Syntax Rules for Tags}. - */ - String value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java deleted file mode 100644 index 0d6526ec..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @Tags} is a container for one or more {@link Tag @Tag} declarations. - * - *

Note, however, that use of the {@code @Tags} container is completely - * optional since {@code @Tag} is a {@linkplain java.lang.annotation.Repeatable - * repeatable} annotation. - * - * @since 5.0 - * @see Tag - * @see java.lang.annotation.Repeatable - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@API(status = STABLE, since = "5.0") -public @interface Tags { - - /** - * An array of one or more {@link Tag Tags}. - */ - Tag[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java deleted file mode 100644 index 53c520bd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.platform.commons.annotation.Testable; - -/** - * {@code @Test} is used to signal that the annotated method is a - * test method. - * - *

{@code @Test} methods must not be {@code private} or {@code static} - * and must not return a value. - * - *

{@code @Test} methods may optionally declare parameters to be - * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver - * ParameterResolvers}. - * - *

{@code @Test} may also be used as a meta-annotation in order to create - * a custom composed annotation that inherits the semantics of - * {@code @Test}. - * - *

Test Execution Order

- * - *

By default, test methods will be ordered using an algorithm that is - * deterministic but intentionally nonobvious. This ensures that subsequent runs - * of a test suite execute test methods in the same order, thereby allowing for - * repeatable builds. In this context, a test method is any instance - * method that is directly annotated or meta-annotated with {@code @Test}, - * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or - * {@code @TestTemplate}. - * - *

Although true unit tests typically should not rely on the order - * in which they are executed, there are times when it is necessary to enforce - * a specific test method execution order — for example, when writing - * integration tests or functional tests where the sequence of - * the tests is important, especially in conjunction with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. - * - *

To control the order in which test methods are executed, annotate your - * test class or test interface with {@link TestMethodOrder @TestMethodOrder} - * and specify the desired {@link MethodOrderer} implementation. - * - * @since 5.0 - * @see RepeatedTest - * @see org.junit.jupiter.params.ParameterizedTest - * @see TestTemplate - * @see TestFactory - * @see TestInfo - * @see DisplayName - * @see Tag - * @see BeforeAll - * @see AfterAll - * @see BeforeEach - * @see AfterEach - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -@Testable -public @interface Test { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java deleted file mode 100644 index 15eedf56..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @TestClassOrder} is a type-level annotation that is used to configure - * a {@link #value ClassOrderer} for the {@link Nested @Nested} test classes of - * the annotated test class. - * - *

If {@code @TestClassOrder} is not explicitly declared on a test class, - * inherited from a parent class, declared on a test interface implemented by - * a test class, or inherited from an {@linkplain Class#getEnclosingClass() enclosing - * class}, {@code @Nested} test classes will be executed in arbitrary order. - * - *

As an alternative to {@code @TestClassOrder}, a global {@link ClassOrderer} - * can be configured for the entire test suite via the - * {@value ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME} configuration parameter. See - * the User Guide for details. Note, however, that a {@code @TestClassOrder} - * declaration always overrides a global {@code ClassOrderer}. - * - *

Example Usage

- * - *

The following demonstrates how to guarantee that {@code @Nested} test classes - * are executed in the order specified via the {@link Order @Order} annotation. - * - *

- * {@literal @}TestClassOrder(ClassOrderer.OrderAnnotation.class)
- * class OrderedNestedTests {
- *
- *     {@literal @}Nested
- *     {@literal @}Order(1)
- *     class PrimaryTests {
- *         // {@literal @}Test methods ...
- *     }
- *
- *     {@literal @}Nested
- *     {@literal @}Order(2)
- *     class SecondaryTests {
- *         // {@literal @}Test methods ...
- *     }
- * }
- * 
- * - * @since 5.8 - * @see ClassOrderer - * @see TestMethodOrder - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@API(status = EXPERIMENTAL, since = "5.8") -public @interface TestClassOrder { - - /** - * The {@link ClassOrderer} to use. - * - * @see ClassOrderer - * @see ClassOrderer.ClassName - * @see ClassOrderer.DisplayName - * @see ClassOrderer.OrderAnnotation - * @see ClassOrderer.Random - */ - Class value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java deleted file mode 100644 index 96b1f524..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.platform.commons.annotation.Testable; - -/** - * {@code @TestFactory} is used to signal that the annotated method is a - * test factory method. - * - *

In contrast to {@link Test @Test} methods, a test factory is not itself - * a test case but rather a factory for test cases. - * - *

{@code @TestFactory} methods must not be {@code private} or {@code static} - * and must return a {@code Stream}, {@code Collection}, {@code Iterable}, - * {@code Iterator}, or array of {@link DynamicNode} instances. Supported - * subclasses of {@code DynamicNode} include {@link DynamicContainer} and - * {@link DynamicTest}. Dynamic tests will be executed lazily, - * enabling dynamic and even non-deterministic generation of test cases. - * - *

Any {@code Stream} returned by a {@code @TestFactory} will be properly - * closed by calling {@code stream.close()}, making it safe to use a resource - * such as {@code Files.lines()} as the initial source of the stream. - * - *

{@code @TestFactory} methods may optionally declare parameters to be - * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver - * ParameterResolvers}. - * - *

Test Execution Order

- * - *

By default, test methods will be ordered using an algorithm that is - * deterministic but intentionally nonobvious. This ensures that subsequent runs - * of a test suite execute test methods in the same order, thereby allowing for - * repeatable builds. In this context, a test method is any instance - * method that is directly annotated or meta-annotated with {@code @Test}, - * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or - * {@code @TestTemplate}. - * - *

Although true unit tests typically should not rely on the order - * in which they are executed, there are times when it is necessary to enforce - * a specific test method execution order — for example, when writing - * integration tests or functional tests where the sequence of - * the tests is important, especially in conjunction with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. - * - *

To control the order in which test methods are executed, annotate your - * test class or test interface with {@link TestMethodOrder @TestMethodOrder} - * and specify the desired {@link MethodOrderer} implementation. - * - * @since 5.0 - * @see Test - * @see DynamicNode - * @see DynamicTest - * @see DynamicContainer - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = MAINTAINED, since = "5.3") -@Testable -public @interface TestFactory { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java deleted file mode 100644 index c7949d1c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; - -/** - * {@code TestInfo} is used to inject information about the current test or - * container into to {@code @Test}, {@code @RepeatedTest}, - * {@code @ParameterizedTest}, {@code @TestFactory}, {@code @BeforeEach}, - * {@code @AfterEach}, {@code @BeforeAll}, and {@code @AfterAll} methods. - * - *

If a method parameter is of type {@link TestInfo}, JUnit will supply - * an instance of {@code TestInfo} corresponding to the current test or - * container as the value for the parameter. - * - * @since 5.0 - * @see Test - * @see RepeatedTest - * @see TestFactory - * @see BeforeEach - * @see AfterEach - * @see BeforeAll - * @see AfterAll - * @see DisplayName - * @see Tag - */ -@API(status = STABLE, since = "5.0") -public interface TestInfo { - - /** - * Get the display name of the current test or container. - * - *

The display name is either a default name or a custom name configured - * via {@link DisplayName @DisplayName}. - * - *

Default Display Names

- * - *

If the context in which {@code TestInfo} is used is at the container - * level, the default display name is generated based on the name of the - * test class. For top-level and {@link Nested @Nested} test classes, the - * default display name is the {@linkplain Class#getSimpleName simple name} - * of the class. For {@code static} nested test classes, the default display - * name is the default display name for the enclosing class concatenated with - * the {@linkplain Class#getSimpleName simple name} of the {@code static} - * nested class, separated by a dollar sign ({@code $}). For example, the - * default display names for the following test classes are - * {@code TopLevelTests}, {@code NestedTests}, and {@code TopLevelTests$StaticTests}. - * - *

-	 *   class TopLevelTests {
-	 *
-	 *      {@literal @}Nested
-	 *      class NestedTests {}
-	 *
-	 *      static class StaticTests {}
-	 *   }
-	 * 
- * - *

If the context in which {@code TestInfo} is used is at the test level, - * the default display name is the name of the test method concatenated with - * a comma-separated list of {@linkplain Class#getSimpleName simple names} - * of the parameter types in parentheses. For example, the default display - * name for the following test method is {@code testUser(TestInfo, User)}. - * - *

-	 *   {@literal @}Test
-	 *   void testUser(TestInfo testInfo, {@literal @}Mock User user) {}
-	 * 
- * - *

Note that display names are typically used for test reporting in IDEs - * and build tools and may contain spaces, special characters, and even emoji. - * - * @return the display name of the test or container; never {@code null} or blank - */ - String getDisplayName(); - - /** - * Get the set of all tags for the current test or container. - * - *

Tags may be declared directly on the test element or inherited - * from an outer context. - */ - Set getTags(); - - /** - * Get the {@link Class} associated with the current test or container, if available. - */ - Optional> getTestClass(); - - /** - * Get the {@link Method} associated with the current test or container, if available. - */ - Optional getTestMethod(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java deleted file mode 100644 index 0e6ae375..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @TestInstance} is a type-level annotation that is used to configure - * the {@linkplain Lifecycle lifecycle} of test instances for the annotated - * test class or test interface. - * - *

If {@code @TestInstance} is not explicitly declared on a test class or - * on a test interface implemented by a test class, the lifecycle mode will - * implicitly default to {@link Lifecycle#PER_METHOD PER_METHOD}. Note, however, - * that an explicit lifecycle mode is inherited within a test class - * hierarchy. In addition, the default lifecycle mode may be overridden - * via the {@value Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME} configuration - * parameter which can be supplied via the {@code Launcher} API, build tools - * (e.g., Gradle and Maven), a JVM system property, or the JUnit Platform - * configuration file (i.e., a file named {@code junit-platform.properties} in - * the root of the class path). Consult the User Guide for further information. - * - *

Use Cases

- *

Setting the test instance lifecycle mode to {@link Lifecycle#PER_CLASS - * PER_CLASS} enables the following features. - *

    - *
  • Shared test instance state between test methods in a given test class - * as well as between non-static {@link BeforeAll @BeforeAll} and - * {@link AfterAll @AfterAll} methods in the test class.
  • - *
  • Declaration of non-static {@code @BeforeAll} and {@code @AfterAll} methods - * in {@link Nested @Nested} test classes. Beginning with Java 16, {@code @BeforeAll} - * and {@code @AfterAll} methods may be declared as {@code static} in - * {@link Nested @Nested} test classes with either lifecycle mode.
  • - *
  • Declaration of {@code @BeforeAll} and {@code @AfterAll} on interface - * {@code default} methods.
  • - *
  • Simplified declaration of non-static {@code @BeforeAll} and {@code @AfterAll} - * methods in test classes implemented with the Kotlin programming language.
  • - *
- * - *

{@code @TestInstance} may also be used as a meta-annotation in order to - * create a custom composed annotation that inherits the semantics - * of {@code @TestInstance}. - * - * @since 5.0 - * @see Nested @Nested - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -@API(status = STABLE, since = "5.0") -public @interface TestInstance { - - /** - * Enumeration of test instance lifecycle modes. - * - * @see #PER_METHOD - * @see #PER_CLASS - */ - enum Lifecycle { - - /** - * When using this mode, a new test instance will be created once per - * test class. - * - * @see #PER_METHOD - */ - PER_CLASS, - - /** - * When using this mode, a new test instance will be created for each - * test method, test factory method, or test template method. - * - *

This mode is analogous to the behavior found in JUnit versions 1 - * through 4. - * - * @see #PER_CLASS - */ - PER_METHOD; - - /** - * Property name used to set the default test instance lifecycle mode: - * {@value} - * - *

Supported Values

- * - *

Supported values include names of enum constants defined in - * {@link org.junit.jupiter.api.TestInstance.Lifecycle}, ignoring case. - * - *

If not specified, the default is "per_method" which corresponds to - * {@code @TestInstance(Lifecycle.PER_METHOD)}. - * - * @since 5.0 - * @see org.junit.jupiter.api.TestInstance - */ - @API(status = STABLE, since = "5.9") - public static final String DEFAULT_LIFECYCLE_PROPERTY_NAME = "junit.jupiter.testinstance.lifecycle.default"; - - } - - /** - * The test instance lifecycle mode to use. - */ - Lifecycle value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java deleted file mode 100644 index 2e6a571e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @TestMethodOrder} is a type-level annotation that is used to configure - * a {@link #value MethodOrderer} for the test methods of the annotated - * test class or test interface. - * - *

In this context, the term "test method" refers to any method annotated with - * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, - * {@code @TestFactory}, or {@code @TestTemplate}. - * - *

If {@code @TestMethodOrder} is not explicitly declared on a test class, - * inherited from a parent class, or declared on a test interface implemented by - * a test class, test methods will be ordered using a default algorithm that is - * deterministic but intentionally nonobvious. - * - *

As an alternative to {@code @TestMethodOrder}, a global {@link MethodOrderer} - * can be configured for the entire test suite via the - * {@value MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME} configuration parameter. See - * the User Guide for details. Note, however, that a {@code @TestClassOrder} - * declaration always overrides a global {@code ClassOrderer}. - * - *

Example Usage

- * - *

The following demonstrates how to guarantee that test methods are executed - * in the order specified via the {@link Order @Order} annotation. - * - *

- * {@literal @}TestMethodOrder(MethodOrderer.OrderAnnotation.class)
- * class OrderedTests {
- *
- *     {@literal @}Test
- *     {@literal @}Order(1)
- *     void nullValues() {}
- *
- *     {@literal @}Test
- *     {@literal @}Order(2)
- *     void emptyValues() {}
- *
- *     {@literal @}Test
- *     {@literal @}Order(3)
- *     void validValues() {}
- * }
- * 
- * - * @since 5.4 - * @see MethodOrderer - * @see TestClassOrder - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@API(status = STABLE, since = "5.7") -public @interface TestMethodOrder { - - /** - * The {@link MethodOrderer} to use. - * - * @see MethodOrderer - * @see MethodOrderer.MethodName - * @see MethodOrderer.DisplayName - * @see MethodOrderer.OrderAnnotation - * @see MethodOrderer.Random - */ - Class value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java deleted file mode 100644 index 6b5b349b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Collections; -import java.util.Map; - -import org.apiguardian.api.API; - -/** - * Parameters of type {@code TestReporter} can be injected into - * {@link BeforeEach @BeforeEach} and {@link AfterEach @AfterEach} lifecycle - * methods as well as methods annotated with {@link Test @Test}, - * {@link RepeatedTest @RepeatedTest}, - * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}, - * {@link TestFactory @TestFactory}, etc. - * - *

Within such methods the injected {@code TestReporter} can be used to - * publish report entries for the current container or test to the - * reporting infrastructure. - * - * @since 5.0 - * @see #publishEntry(Map) - * @see #publishEntry(String, String) - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface TestReporter { - - /** - * Publish the supplied map of key-value pairs as a report entry. - * - * @param map the key-value pairs to be published; never {@code null}; - * keys and values within entries in the map also must not be - * {@code null} or blank - * @see #publishEntry(String, String) - * @see #publishEntry(String) - */ - void publishEntry(Map map); - - /** - * Publish the supplied key-value pair as a report entry. - * - * @param key the key of the entry to publish; never {@code null} or blank - * @param value the value of the entry to publish; never {@code null} or blank - * @see #publishEntry(Map) - * @see #publishEntry(String) - */ - default void publishEntry(String key, String value) { - this.publishEntry(Collections.singletonMap(key, value)); - } - - /** - * Publish the supplied value as a report entry. - * - *

This method delegates to {@link #publishEntry(String, String)}, - * supplying {@code "value"} as the key and the supplied {@code value} - * argument as the value. - * - * @param value the value to be published; never {@code null} or blank - * @since 5.3 - * @see #publishEntry(Map) - * @see #publishEntry(String, String) - */ - @API(status = STABLE, since = "5.3") - default void publishEntry(String value) { - this.publishEntry("value", value); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java deleted file mode 100644 index 24f8208a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.platform.commons.annotation.Testable; - -/** - * {@code @TestTemplate} is used to signal that the annotated method is a - * test template method. - * - *

In contrast to {@link Test @Test} methods, a test template is not itself - * a test case but rather a template for test cases. As such, it is designed to - * be invoked multiple times depending on the number of {@linkplain - * org.junit.jupiter.api.extension.TestTemplateInvocationContext invocation - * contexts} returned by the registered {@linkplain - * org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider - * providers}. Must be used together with at least one provider. Otherwise, - * execution will fail. - * - *

Each invocation of a test template method behaves like the execution of - * a regular {@link Test @Test} method with full support for the same lifecycle - * callbacks and extensions. - * - *

{@code @TestTemplate} methods must not be {@code private} or {@code static} - * and must return {@code void}. - * - *

{@code @TestTemplate} methods may optionally declare parameters to be - * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver - * ParameterResolvers}. - * - *

{@code @TestTemplate} may also be used as a meta-annotation in order to - * create a custom composed annotation that inherits the semantics - * of {@code @TestTemplate}. - * - *

Test Execution Order

- * - *

By default, test methods will be ordered using an algorithm that is - * deterministic but intentionally nonobvious. This ensures that subsequent runs - * of a test suite execute test methods in the same order, thereby allowing for - * repeatable builds. In this context, a test method is any instance - * method that is directly annotated or meta-annotated with {@code @Test}, - * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or - * {@code @TestTemplate}. - * - *

Although true unit tests typically should not rely on the order - * in which they are executed, there are times when it is necessary to enforce - * a specific test method execution order — for example, when writing - * integration tests or functional tests where the sequence of - * the tests is important, especially in conjunction with - * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. - * - *

To control the order in which test methods are executed, annotate your - * test class or test interface with {@link TestMethodOrder @TestMethodOrder} - * and specify the desired {@link MethodOrderer} implementation. - * - * @since 5.0 - * @see Test - * @see org.junit.jupiter.api.extension.TestTemplateInvocationContext - * @see org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.0") -@Testable -public @interface TestTemplate { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java deleted file mode 100644 index 18f0d34c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.concurrent.TimeUnit; - -import org.apiguardian.api.API; - -/** - * {@code @Timeout} is used to define a timeout for a method or all testable - * methods within one class and its {@link Nested @Nested} classes. - * - *

This annotation may also be used on lifecycle methods annotated with - * {@link BeforeAll @BeforeAll}, {@link BeforeEach @BeforeEach}, - * {@link AfterEach @AfterEach}, or {@link AfterAll @AfterAll}. - * - *

Applying this annotation to a test class has the same effect as applying - * it to all testable methods, i.e. all methods annotated or meta-annotated with - * {@link Test @Test}, {@link TestFactory @TestFactory}, or - * {@link TestTemplate @TestTemplate}, but not to its lifecycle methods. - * - *

Default Timeouts

- * - *

If this annotation is not present, no timeout will be used unless a - * default timeout is defined via one of the following configuration parameters: - * - *

- *
{@value #DEFAULT_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for all testable and lifecycle methods
- *
{@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for all testable methods
- *
{@value #DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for {@link Test @Test} methods
- *
{@value #DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for {@link TestTemplate @TestTemplate} methods
- *
{@value DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for {@link TestFactory @TestFactory} methods
- *
{@value DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for all lifecycle methods
- *
{@value #DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for {@link BeforeAll @BeforeAll} methods
- *
{@value #DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for {@link BeforeEach @BeforeEach} methods
- *
{@value #DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for {@link AfterEach @AfterEach} methods
- *
{@value #DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME}
- *
Default timeout for {@link AfterAll @AfterAll} methods
- *
- * - *

More specific configuration parameters override less specific ones. For - * example, {@value #DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME} - * overrides {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} - * which overrides {@value #DEFAULT_TIMEOUT_PROPERTY_NAME}. - * - *

Supported Values

- * - *

Values for timeouts must be in the following, case-insensitive format: - * {@code [ns|μs|ms|s|m|h|d]}. The space between the number and the - * unit may be omitted. Specifying no unit is equivalent to using seconds. - * - * - * - * - * - * - * - * - * - * - * - * - *
Timeout configuration via configuration parameter vs. annotation
Value Equivalent annotation
{@code 42} {@code @Timeout(42)}
{@code 42 ns} {@code @Timeout(value = 42, unit = NANOSECONDS)}
{@code 42 μs} {@code @Timeout(value = 42, unit = MICROSECONDS)}
{@code 42 ms} {@code @Timeout(value = 42, unit = MILLISECONDS)}
{@code 42 s} {@code @Timeout(value = 42, unit = SECONDS)}
{@code 42 m} {@code @Timeout(value = 42, unit = MINUTES)}
{@code 42 h} {@code @Timeout(value = 42, unit = HOURS)}
{@code 42 d} {@code @Timeout(value = 42, unit = DAYS)}
- * - *

Disabling Timeouts

- * - *

You may use the {@value #TIMEOUT_MODE_PROPERTY_NAME} configuration - * parameter to explicitly enable or disable timeouts. - * - *

Supported values: - *

    - *
  • {@code enabled}: enables timeouts - *
  • {@code disabled}: disables timeouts - *
  • {@code disabled_on_debug}: disables timeouts while debugging - *
- * - * @since 5.5 - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@API(status = STABLE, since = "5.7") -public @interface Timeout { - - /** - * Property name used to set the default timeout for all testable and - * lifecycle methods: {@value}. - * - *

The value of this property will be used unless overridden by a more - * specific property or a {@link Timeout @Timeout} - * annotation present on the method or on an enclosing test class (for - * testable methods). - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.default"; - - /** - * Property name used to set the default timeout for all testable methods: - * {@value}. - * - *

The value of this property will be used unless overridden by a more - * specific property or a {@link Timeout @Timeout} - * annotation present on the testable method or on an enclosing test class. - * - *

This property overrides the {@value #DEFAULT_TIMEOUT_PROPERTY_NAME} - * property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testable.method.default"; - - /** - * Property name used to set the default timeout for all {@link Test @Test} - * methods: {@value}. - * - *

The value of this property will be used unless overridden by a - * {@link Timeout @Timeout} annotation present on the {@link Test @Test} - * method or on an enclosing test class. - * - *

This property overrides the - * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.test.method.default"; - - /** - * Property name used to set the default timeout for all - * {@link TestTemplate @TestTemplate} methods: {@value}. - * - *

The value of this property will be used unless overridden by a - * {@link Timeout @Timeout} annotation present on the - * {@link TestTemplate @TestTemplate} method or on an enclosing test class. - * - *

This property overrides the - * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testtemplate.method.default"; - - /** - * Property name used to set the default timeout for all - * {@link TestFactory @TestFactory} methods: {@value}. - * - *

The value of this property will be used unless overridden by a - * {@link Timeout @Timeout} annotation present on the - * {@link TestFactory @TestFactory} method or on an enclosing test class. - * - *

This property overrides the - * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testfactory.method.default"; - - /** - * Property name used to set the default timeout for all lifecycle methods: - * {@value}. - * - *

The value of this property will be used unless overridden by a more - * specific property or a {@link Timeout @Timeout} annotation present on the - * lifecycle method. - * - *

This property overrides the {@value #DEFAULT_TIMEOUT_PROPERTY_NAME} - * property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.lifecycle.method.default"; - - /** - * Property name used to set the default timeout for all - * {@link BeforeAll @BeforeAll} methods: {@value}. - * - *

The value of this property will be used unless overridden by a - * {@link Timeout @Timeout} annotation present on the - * {@link BeforeAll @BeforeAll} method. - * - *

This property overrides the - * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.beforeall.method.default"; - - /** - * Property name used to set the default timeout for all - * {@link BeforeEach @BeforeEach} methods: {@value}. - * - *

The value of this property will be used unless overridden by a - * {@link Timeout @Timeout} annotation present on the - * {@link BeforeEach @BeforeEach} method. - * - *

This property overrides the - * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.beforeeach.method.default"; - - /** - * Property name used to set the default timeout for all - * {@link AfterEach @AfterEach} methods: {@value}. - * - *

The value of this property will be used unless overridden by a - * {@link Timeout @Timeout} annotation present on the - * {@link AfterEach @AfterEach} method. - * - *

This property overrides the - * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.aftereach.method.default"; - - /** - * Property name used to set the default timeout for all - * {@link AfterAll @AfterAll} methods: {@value}. - * - *

The value of this property will be used unless overridden by a - * {@link Timeout @Timeout} annotation present on the - * {@link AfterAll @AfterAll} method. - * - *

This property overrides the - * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. - * - *

Please refer to the class - * description for the definition of supported values. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.9") - String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.afterall.method.default"; - - /** - * Property used to determine if timeouts are applied to tests: {@value}. - * - *

The value of this property will be used to toggle whether - * {@link Timeout @Timeout} is applied to tests.

- * - *

Supported timeout mode values:

- *
    - *
  • {@code enabled}: enables timeouts - *
  • {@code disabled}: disables timeouts - *
  • {@code disabled_on_debug}: disables timeouts while debugging - *
- * - *

If not specified, the default is {@code enabled}. - * - * @since 5.6 - */ - @API(status = STABLE, since = "5.9") - String TIMEOUT_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.mode"; - - /** - * Property name used to set the default thread mode for all testable and lifecycle - * methods: "junit.jupiter.execution.timeout.thread.mode.default". - * - *

The value of this property will be used unless overridden by a {@link Timeout @Timeout} - * annotation present on the method or on an enclosing test class (for testable methods). - * - *

The supported values are {@code SAME_THREAD} or {@code SEPARATE_THREAD}, if none is provided - * {@code SAME_THREAD} is used as default. - * - * @since 5.9 - */ - @API(status = EXPERIMENTAL, since = "5.9") - String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.thread.mode.default"; - - /** - * The duration of this timeout. - * - * @return timeout duration; must be a positive number - */ - long value(); - - /** - * The time unit of this timeout. - * - * @return time unit - * @see TimeUnit - */ - TimeUnit unit() default TimeUnit.SECONDS; - - /** - * The thread mode of this timeout. - * - * @return thread mode - * @since 5.9 - * @see ThreadMode - */ - @API(status = EXPERIMENTAL, since = "5.9") - ThreadMode threadMode() default ThreadMode.INFERRED; - - /** - * {@code ThreadMode} is use to define whether the test code should be executed in the thread - * of the calling code or in a separated thread. - * - * @since 5.9 - */ - @API(status = EXPERIMENTAL, since = "5.9") - enum ThreadMode { - /** - * The thread mode is determined using the parameter configured in property - * {@value Timeout#DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME}. - */ - INFERRED, - - /** - * The test code is executed in the thread of the calling code. - */ - SAME_THREAD, - - /** - * The test code is executed in a different thread than that of the calling code. Furthermore, - * execution of the test code will be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - */ - SEPARATE_THREAD, - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java deleted file mode 100644 index 0a82b367..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.annotation.Annotation; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * Base class for OS-based {@link ExecutionCondition} implementations. - * - * @since 5.9 - */ -abstract class AbstractOsBasedExecutionCondition implements ExecutionCondition { - - static final String CURRENT_ARCHITECTURE = System.getProperty("os.arch"); - static final String CURRENT_OS = System.getProperty("os.name"); - - private final Class annotationType; - - AbstractOsBasedExecutionCondition(Class annotationType) { - this.annotationType = annotationType; - } - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return findAnnotation(context.getElement(), this.annotationType) // - .map(this::evaluateExecutionCondition) // - .orElseGet(this::enabledByDefault); - } - - abstract ConditionEvaluationResult evaluateExecutionCondition(A annotation); - - String createReason(boolean enabled, boolean osSpecified, boolean archSpecified) { - StringBuilder reason = new StringBuilder() // - .append(enabled ? "Enabled" : "Disabled") // - .append(osSpecified ? " on operating system: " : " on architecture: "); - - if (osSpecified && archSpecified) { - reason.append(String.format("%s (%s)", CURRENT_OS, CURRENT_ARCHITECTURE)); - } - else if (osSpecified) { - reason.append(CURRENT_OS); - } - else { - reason.append(CURRENT_ARCHITECTURE); - } - - return reason.toString(); - } - - private ConditionEvaluationResult enabledByDefault() { - String reason = String.format("@%s is not present", this.annotationType.getSimpleName()); - return enabled(reason); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java deleted file mode 100644 index 5463f23c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static java.lang.String.format; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; - -import java.lang.annotation.Annotation; -import java.lang.annotation.Repeatable; -import java.lang.reflect.AnnotatedElement; -import java.util.Optional; - -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.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * Abstract base class for {@link ExecutionCondition} implementations that support - * {@linkplain Repeatable repeatable} annotations. - * - * @param the type of repeatable annotation supported by this {@code ExecutionCondition} - * @since 5.6 - */ -abstract class AbstractRepeatableAnnotationCondition implements ExecutionCondition { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final Class annotationType; - - AbstractRepeatableAnnotationCondition(Class annotationType) { - this.annotationType = annotationType; - } - - @Override - public final ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - Optional optionalElement = context.getElement(); - if (optionalElement.isPresent()) { - AnnotatedElement annotatedElement = optionalElement.get(); - // @formatter:off - return findRepeatableAnnotations(annotatedElement, this.annotationType).stream() - .map(annotation -> { - ConditionEvaluationResult result = evaluate(annotation); - logResult(annotation, annotatedElement, result); - return result; - }) - .filter(ConditionEvaluationResult::isDisabled) - .findFirst() - .orElse(getNoDisabledConditionsEncounteredResult()); - // @formatter:on - } - return getNoDisabledConditionsEncounteredResult(); - } - - protected abstract ConditionEvaluationResult evaluate(A annotation); - - protected abstract ConditionEvaluationResult getNoDisabledConditionsEncounteredResult(); - - private void logResult(A annotation, AnnotatedElement annotatedElement, ConditionEvaluationResult result) { - logger.trace(() -> format("Evaluation of %s on [%s] resulted in: %s", annotation, annotatedElement, result)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java deleted file mode 100644 index a4f7ac18..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.annotation.Annotation; -import java.util.function.Function; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; - -abstract class BooleanExecutionCondition implements ExecutionCondition { - - private final Class annotationType; - private final String enabledReason; - private final String disabledReason; - private final Function customDisabledReason; - - BooleanExecutionCondition(Class annotationType, String enabledReason, String disabledReason, - Function customDisabledReason) { - this.annotationType = annotationType; - this.enabledReason = enabledReason; - this.disabledReason = disabledReason; - this.customDisabledReason = customDisabledReason; - } - - abstract boolean isEnabled(A annotation); - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return findAnnotation(context.getElement(), annotationType) // - .map(annotation -> isEnabled(annotation) ? enabled(enabledReason) - : disabled(disabledReason, customDisabledReason.apply(annotation))) // - .orElseGet(this::enabledByDefault); - } - - private ConditionEvaluationResult enabledByDefault() { - String reason = String.format("@%s is not present", annotationType.getSimpleName()); - return enabled(reason); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java deleted file mode 100644 index 57341bfa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @DisabledForJreRange} is used to signal that the annotated test class or - * test method is only disabled for a specific range of Java Runtime - * Environment (JRE) versions from {@link #min} to {@link #max}. - * - *

When applied at the class level, all test methods within that class will - * be disabled on the same specified JRE versions. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - *

This annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.6 - * @see JRE - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(DisabledForJreRangeCondition.class) -@API(status = STABLE, since = "5.6") -public @interface DisabledForJreRange { - - /** - * Java Runtime Environment version which is used as the lower boundary - * for the version range that determines if the annotated class or method - * should be disabled. - * - *

Defaults to {@link JRE#JAVA_8 JAVA_8}, as this is the lowest - * supported JRE version. - * - * @see JRE - */ - JRE min() default JRE.JAVA_8; - - /** - * Java Runtime Environment version which is used as the upper boundary - * for the version range that determines if the annotated class or method - * should be disabled. - * - *

Defaults to {@link JRE#OTHER OTHER}, as this will always be the highest - * possible version. - * - * @see JRE - */ - JRE max() default JRE.OTHER; - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java deleted file mode 100644 index 90e157d1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; - -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link DisabledForJreRange @DisabledForJreRange}. - * - * @since 5.6 - * @see DisabledForJreRange - */ -class DisabledForJreRangeCondition extends BooleanExecutionCondition { - - DisabledForJreRangeCondition() { - super(DisabledForJreRange.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, - DisabledForJreRange::disabledReason); - } - - @Override - boolean isEnabled(DisabledForJreRange annotation) { - JRE min = annotation.min(); - JRE max = annotation.max(); - - Preconditions.condition((min != JRE.JAVA_8 || max != JRE.OTHER), - "You must declare a non-default value for min or max in @DisabledForJreRange"); - Preconditions.condition(max.compareTo(min) >= 0, - "@DisabledForJreRange.min must be less than or equal to @DisabledForJreRange.max"); - - return !JRE.isCurrentVersionWithinRange(min, max); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java deleted file mode 100644 index 8ab454fb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @DisabledIf} is used to signal that the annotated test class or test - * method is disabled only if the provided - * {@linkplain #value() condition} evaluates to {@code true}. - * - *

When applied at the class level, all test methods within that class will - * be disabled on the same condition. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - * This annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.7 - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(DisabledIfCondition.class) -@API(status = STABLE, since = "5.7") -public @interface DisabledIf { - - /** - * The name of a method within the test class or in an external class to use - * as a condition for the test's or container's execution. - * - *

Condition methods must be static if located outside the test class or - * if {@code @DisabledIf} is used at the class level. - * - *

A condition method in an external class must be referenced by its - * fully qualified method name — for example, - * {@code com.example.Conditions#isEncryptionSupported}. - */ - String value(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - */ - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java deleted file mode 100644 index 1998f884..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import org.junit.jupiter.api.extension.ExecutionCondition; - -/** - * {@link ExecutionCondition} for {@link DisabledIf @DisabledIf}. - * - * @since 5.7 - * @see DisabledIf - */ -class DisabledIfCondition extends MethodBasedCondition { - - DisabledIfCondition() { - super(DisabledIf.class, DisabledIf::value, DisabledIf::disabledReason); - } - - @Override - protected boolean isEnabled(boolean methodResult) { - return !methodResult; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java deleted file mode 100644 index a98c31bb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @DisabledIfEnvironmentVariable} is used to signal that the annotated test - * class or test method is disabled if the value of the specified - * {@linkplain #named environment variable} matches the specified - * {@linkplain #matches regular expression}. - * - *

When declared at the class level, the result will apply to all test methods - * within that class as well. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

If the specified environment variable is undefined, the presence of this - * annotation will have no effect on whether or not the class or method - * is disabled. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable - * repeatable} annotation. Consequently, this annotation may be declared multiple - * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., - * test interface, test class, or test method). Specifically, this annotation will - * be found if it is directly present, indirectly present, or meta-present on a - * given element. - * - * @since 5.1 - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(DisabledIfEnvironmentVariables.class) -@ExtendWith(DisabledIfEnvironmentVariableCondition.class) -@API(status = STABLE, since = "5.1") -public @interface DisabledIfEnvironmentVariable { - - /** - * The name of the environment variable to retrieve. - * - * @return the environment variable name; never blank - * @see System#getenv(String) - */ - String named(); - - /** - * A regular expression that will be used to match against the retrieved - * value of the {@link #named} environment variable. - * - * @return the regular expression; never blank - * @see String#matches(String) - * @see java.util.regex.Pattern - */ - String matches(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java deleted file mode 100644 index de9a8515..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static java.lang.String.format; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable}. - * - * @since 5.1 - * @see DisabledIfEnvironmentVariable - */ -class DisabledIfEnvironmentVariableCondition - extends AbstractRepeatableAnnotationCondition { - - private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( - "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - - DisabledIfEnvironmentVariableCondition() { - super(DisabledIfEnvironmentVariable.class); - } - - @Override - protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { - return ENABLED; - } - - @Override - protected ConditionEvaluationResult evaluate(DisabledIfEnvironmentVariable annotation) { - String name = annotation.named().trim(); - String regex = annotation.matches(); - Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); - Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); - String actual = getEnvironmentVariable(name); - - // Nothing to match against? - if (actual == null) { - return enabled(format("Environment variable [%s] does not exist", name)); - } - - if (actual.matches(regex)) { - return disabled(format("Environment variable [%s] with value [%s] matches regular expression [%s]", name, - actual, regex), annotation.disabledReason()); - } - // else - return enabled(format("Environment variable [%s] with value [%s] does not match regular expression [%s]", name, - actual, regex)); - } - - /** - * Get the value of the named environment variable. - * - *

The default implementation delegates to - * {@link System#getenv(String)}. Can be overridden in a subclass for - * testing purposes. - */ - protected String getEnvironmentVariable(String name) { - return System.getenv(name); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java deleted file mode 100644 index deef2a4a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @DisabledIfEnvironmentVariables} is a container for one or more - * {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable} declarations. - * - *

Note, however, that use of the {@code @DisabledIfEnvironmentVariables} container - * is completely optional since {@code @DisabledIfEnvironmentVariable} is a {@linkplain - * java.lang.annotation.Repeatable repeatable} annotation. - * - * @since 5.6 - * @see DisabledIfEnvironmentVariable - * @see java.lang.annotation.Repeatable - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.6") -public @interface DisabledIfEnvironmentVariables { - - /** - * An array of one or more {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable} - * declarations. - */ - DisabledIfEnvironmentVariable[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java deleted file mode 100644 index ff6877bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @DisabledIfSystemProperties} is a container for one or more - * {@link DisabledIfSystemProperty @DisabledIfSystemProperty} declarations. - * - *

Note, however, that use of the {@code @DisabledIfSystemProperties} container - * is completely optional since {@code @DisabledIfSystemProperty} is a {@linkplain - * java.lang.annotation.Repeatable repeatable} annotation. - * - * @since 5.6 - * @see DisabledIfSystemProperty - * @see java.lang.annotation.Repeatable - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.6") -public @interface DisabledIfSystemProperties { - - /** - * An array of one or more {@link DisabledIfSystemProperty @DisabledIfSystemProperty} - * declarations. - */ - DisabledIfSystemProperty[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java deleted file mode 100644 index c8bfe4fc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @DisabledIfSystemProperty} is used to signal that the annotated test - * class or test method is disabled if the value of the specified - * {@linkplain #named system property} matches the specified - * {@linkplain #matches regular expression}. - * - *

When declared at the class level, the result will apply to all test methods - * within that class as well. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

If the specified system property is undefined, the presence of this - * annotation will have no effect on whether or not the class or method - * is disabled. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable - * repeatable} annotation. Consequently, this annotation may be declared multiple - * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., - * test interface, test class, or test method). Specifically, this annotation will - * be found if it is directly present, indirectly present, or meta-present on a - * given element. - * - * @since 5.1 - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(DisabledIfSystemProperties.class) -@ExtendWith(DisabledIfSystemPropertyCondition.class) -@API(status = STABLE, since = "5.1") -public @interface DisabledIfSystemProperty { - - /** - * The name of the JVM system property to retrieve. - * - * @return the system property name; never blank - * @see System#getProperty(String) - */ - String named(); - - /** - * A regular expression that will be used to match against the retrieved - * value of the {@link #named} JVM system property. - * - * @return the regular expression; never blank - * @see String#matches(String) - * @see java.util.regex.Pattern - */ - String matches(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java deleted file mode 100644 index c2e17250..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static java.lang.String.format; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link DisabledIfSystemProperty @DisabledIfSystemProperty}. - * - * @since 5.1 - * @see DisabledIfSystemProperty - */ -class DisabledIfSystemPropertyCondition extends AbstractRepeatableAnnotationCondition { - - private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( - "No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - - DisabledIfSystemPropertyCondition() { - super(DisabledIfSystemProperty.class); - } - - @Override - protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { - return ENABLED; - } - - @Override - protected ConditionEvaluationResult evaluate(DisabledIfSystemProperty annotation) { - String name = annotation.named().trim(); - String regex = annotation.matches(); - Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); - Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); - String actual = System.getProperty(name); - - // Nothing to match against? - if (actual == null) { - return enabled(format("System property [%s] does not exist", name)); - } - - if (actual.matches(regex)) { - return disabled( - format("System property [%s] with value [%s] matches regular expression [%s]", name, actual, regex), - annotation.disabledReason()); - } - // else - return enabled( - format("System property [%s] with value [%s] does not match regular expression [%s]", name, actual, regex)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java deleted file mode 100644 index a96399e7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @DisabledInNativeImage} is used to signal that the annotated test class - * or test method is only disabled when executing within a GraalVM native - * image. - * - *

When applied at the class level, all test methods within that class will - * be disabled within a native image. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Technical Details

- * - *

JUnit detects whether tests are executing within a GraalVM native image by - * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} - * system property (see - * org.graalvm.nativeimage.ImageInfo - * for details). The GraalVM compiler sets the property to {@code buildtime} while - * compiling a native image; the property is set to {@code runtime} while a native - * image is executing; and the Gradle and Maven plug-ins in the GraalVM - * Native Build Tools - * project set the property to {@code agent} while executing tests with the GraalVM - * tracing agent. - * - * @since 5.9.1 - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@DisabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // - disabledReason = "Currently executing within a GraalVM native image") -@API(status = STABLE, since = "5.9.1") -public @interface DisabledInNativeImage { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java deleted file mode 100644 index ef1b5d8a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @DisabledOnJre} is used to signal that the annotated test class or - * test method is disabled on one or more specified Java - * Runtime Environment (JRE) {@linkplain #value versions}. - * - *

When applied at the class level, all test methods within that class - * will be disabled on the same specified JRE versions. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.1 - * @see JRE - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(DisabledOnJreCondition.class) -@API(status = STABLE, since = "5.1") -public @interface DisabledOnJre { - - /** - * Java Runtime Environment versions on which the annotated class or - * method should be disabled. - * - * @see JRE - */ - JRE[] value(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java deleted file mode 100644 index a2bacd66..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; - -import java.util.Arrays; - -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link DisabledOnJre @DisabledOnJre}. - * - * @since 5.1 - * @see DisabledOnJre - */ -class DisabledOnJreCondition extends BooleanExecutionCondition { - - DisabledOnJreCondition() { - super(DisabledOnJre.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, DisabledOnJre::disabledReason); - } - - @Override - boolean isEnabled(DisabledOnJre annotation) { - JRE[] versions = annotation.value(); - Preconditions.condition(versions.length > 0, "You must declare at least one JRE in @DisabledOnJre"); - return Arrays.stream(versions).noneMatch(JRE::isCurrentVersion); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java deleted file mode 100644 index 5f1e08d7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @DisabledOnOs} is used to signal that the annotated test class or - * test method is disabled on one or more specified - * {@linkplain #value operating systems} or on one or more specified - * {@linkplain #architectures architectures} - * - *

If operating systems and architectures are specified, the annotated - * test class or test method is disabled if both conditions apply. - * - *

When applied at the class level, all test methods within that class - * will be disabled on the same specified operating systems, architectures, or - * the specified combinations of both. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.1 - * @see OS - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(DisabledOnOsCondition.class) -@API(status = STABLE, since = "5.1") -public @interface DisabledOnOs { - - /** - * Operating systems on which the annotated class or method should be - * disabled. - * - * @see OS - */ - OS[] value() default {}; - - /** - * Architectures on which the annotated class or method should be disabled. - * - *

Each architecture will be compared to the value returned from - * {@code System.getProperty("os.arch")}, ignoring case. - * - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - String[] architectures() default {}; - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java deleted file mode 100644 index 90ec2089..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import java.util.Arrays; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link DisabledOnOs @DisabledOnOs}. - * - * @since 5.1 - * @see DisabledOnOs - */ -class DisabledOnOsCondition extends AbstractOsBasedExecutionCondition { - - DisabledOnOsCondition() { - super(DisabledOnOs.class); - } - - @Override - ConditionEvaluationResult evaluateExecutionCondition(DisabledOnOs annotation) { - boolean osSpecified = annotation.value().length > 0; - boolean archSpecified = annotation.architectures().length > 0; - Preconditions.condition(osSpecified || archSpecified, - "You must declare at least one OS or architecture in @DisabledOnOs"); - - boolean enabled = isEnabledBasedOnOs(annotation) || isEnabledBasedOnArchitecture(annotation); - String reason = createReason(enabled, osSpecified, archSpecified); - - return enabled ? ConditionEvaluationResult.enabled(reason) - : ConditionEvaluationResult.disabled(reason, annotation.disabledReason()); - } - - private boolean isEnabledBasedOnOs(DisabledOnOs annotation) { - OS[] operatingSystems = annotation.value(); - if (operatingSystems.length == 0) { - return false; - } - return Arrays.stream(operatingSystems).noneMatch(OS::isCurrentOs); - } - - private boolean isEnabledBasedOnArchitecture(DisabledOnOs annotation) { - String[] architectures = annotation.architectures(); - if (architectures.length == 0) { - return false; - } - return Arrays.stream(architectures).noneMatch(CURRENT_ARCHITECTURE::equalsIgnoreCase); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java deleted file mode 100644 index 9e3154d8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @EnabledForJreRange} is used to signal that the annotated test class or - * test method is only enabled for a specific range of Java Runtime - * Environment (JRE) versions from {@link #min} to {@link #max}. - * - *

When applied at the class level, all test methods within that class will - * be enabled on the same specified JRE versions. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - *

This annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.6 - * @see JRE - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(EnabledForJreRangeCondition.class) -@API(status = STABLE, since = "5.6") -public @interface EnabledForJreRange { - - /** - * Java Runtime Environment version which should be used as the lower boundary - * for the version range that determines if the annotated class or method - * should be enabled. - * - *

Defaults to {@link JRE#JAVA_8 JAVA_8}, as this is the lowest - * supported JRE version. - * - * @see JRE - */ - JRE min() default JRE.JAVA_8; - - /** - * Java Runtime Environment version which should be used as the upper boundary - * for the version range that determines if the annotated class or method - * should be enabled. - * - *

Defaults to {@link JRE#OTHER OTHER}, as this will always be the highest - * possible version. - * - * @see JRE - */ - JRE max() default JRE.OTHER; - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java deleted file mode 100644 index 5a7c9e54..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; - -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link EnabledForJreRange @EnabledForJreRange}. - * - * @since 5.6 - * @see EnabledForJreRange - */ -class EnabledForJreRangeCondition extends BooleanExecutionCondition { - - EnabledForJreRangeCondition() { - super(EnabledForJreRange.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, - EnabledForJreRange::disabledReason); - } - - @Override - boolean isEnabled(EnabledForJreRange annotation) { - JRE min = annotation.min(); - JRE max = annotation.max(); - - Preconditions.condition((min != JRE.JAVA_8 || max != JRE.OTHER), - "You must declare a non-default value for min or max in @EnabledForJreRange"); - Preconditions.condition(max.compareTo(min) >= 0, - "@EnabledForJreRange.min must be less than or equal to @EnabledForJreRange.max"); - - return JRE.isCurrentVersionWithinRange(min, max); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java deleted file mode 100644 index 18e8a839..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @EnabledIf} is used to signal that the annotated test class or test - * method is enabled only if the provided - * {@linkplain #value() condition} evaluates to {@code true}. - * - *

When applied at the class level, all test methods within that class will - * be enabled on the same condition. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - * This annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.7 - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(EnabledIfCondition.class) -@API(status = STABLE, since = "5.7") -public @interface EnabledIf { - - /** - * The name of a method within the test class or in an external class to use - * as a condition for the test's or container's execution. - * - *

Condition methods must be static if located outside the test class or - * if {@code @EnabledIf} is used at the class level. - * - *

A condition method in an external class must be referenced by its - * fully qualified method name — for example, - * {@code com.example.Conditions#isEncryptionSupported}. - */ - String value(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - */ - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java deleted file mode 100644 index 3ebaa594..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import org.junit.jupiter.api.extension.ExecutionCondition; - -/** - * {@link ExecutionCondition} for {@link EnabledIf @EnabledIf}. - * - * @since 5.7 - * @see EnabledIf - */ -class EnabledIfCondition extends MethodBasedCondition { - - EnabledIfCondition() { - super(EnabledIf.class, EnabledIf::value, EnabledIf::disabledReason); - } - - @Override - protected boolean isEnabled(boolean methodResult) { - return methodResult; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java deleted file mode 100644 index d7cab6c3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @EnabledIfEnvironmentVariable} is used to signal that the annotated test - * class or test method is only enabled if the value of the specified - * {@linkplain #named environment variable} matches the specified - * {@linkplain #matches regular expression}. - * - *

When declared at the class level, the result will apply to all test methods - * within that class as well. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

If the specified environment variable is undefined, the annotated class or - * method will be disabled. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable - * repeatable} annotation. Consequently, this annotation may be declared multiple - * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., - * test interface, test class, or test method). Specifically, this annotation will - * be found if it is directly present, indirectly present, or meta-present on a - * given element. - * - * @since 5.1 - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(EnabledIfEnvironmentVariables.class) -@ExtendWith(EnabledIfEnvironmentVariableCondition.class) -@API(status = STABLE, since = "5.1") -public @interface EnabledIfEnvironmentVariable { - - /** - * The name of the environment variable to retrieve. - * - * @return the environment variable name; never blank - * @see System#getenv(String) - */ - String named(); - - /** - * A regular expression that will be used to match against the retrieved - * value of the {@link #named} environment variable. - * - * @return the regular expression; never blank - * @see String#matches(String) - * @see java.util.regex.Pattern - */ - String matches(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java deleted file mode 100644 index 13848432..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static java.lang.String.format; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable}. - * - * @since 5.1 - * @see EnabledIfEnvironmentVariable - */ -class EnabledIfEnvironmentVariableCondition - extends AbstractRepeatableAnnotationCondition { - - private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( - "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - - EnabledIfEnvironmentVariableCondition() { - super(EnabledIfEnvironmentVariable.class); - } - - @Override - protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { - return ENABLED; - } - - @Override - protected ConditionEvaluationResult evaluate(EnabledIfEnvironmentVariable annotation) { - - String name = annotation.named().trim(); - String regex = annotation.matches(); - Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); - Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); - String actual = getEnvironmentVariable(name); - - // Nothing to match against? - if (actual == null) { - return disabled(format("Environment variable [%s] does not exist", name), annotation.disabledReason()); - } - if (actual.matches(regex)) { - return enabled(format("Environment variable [%s] with value [%s] matches regular expression [%s]", name, - actual, regex)); - } - return disabled(format("Environment variable [%s] with value [%s] does not match regular expression [%s]", name, - actual, regex), annotation.disabledReason()); - } - - /** - * Get the value of the named environment variable. - * - *

The default implementation delegates to - * {@link System#getenv(String)}. Can be overridden in a subclass for - * testing purposes. - */ - protected String getEnvironmentVariable(String name) { - return System.getenv(name); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java deleted file mode 100644 index 3589ed58..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @EnabledIfEnvironmentVariables} is a container for one or more - * {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable} declarations. - * - *

Note, however, that use of the {@code @EnabledIfEnvironmentVariables} container - * is completely optional since {@code @EnabledIfEnvironmentVariable} is a {@linkplain - * java.lang.annotation.Repeatable repeatable} annotation. - * - * @since 5.6 - * @see EnabledIfEnvironmentVariable - * @see java.lang.annotation.Repeatable - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.6") -public @interface EnabledIfEnvironmentVariables { - - /** - * An array of one or more {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable} - * declarations. - */ - EnabledIfEnvironmentVariable[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java deleted file mode 100644 index f33bdfae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @EnabledIfSystemProperties} is a container for one or more - * {@link EnabledIfSystemProperty @EnabledIfSystemProperty} declarations. - * - *

Note, however, that use of the {@code @EnabledIfSystemProperties} container - * is completely optional since {@code @EnabledIfSystemProperty} is a {@linkplain - * java.lang.annotation.Repeatable repeatable} annotation. - * - * @since 5.6 - * @see EnabledIfSystemProperty - * @see java.lang.annotation.Repeatable - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.6") -public @interface EnabledIfSystemProperties { - - /** - * An array of one or more {@link EnabledIfSystemProperty @EnabledIfSystemProperty} - * declarations. - */ - EnabledIfSystemProperty[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java deleted file mode 100644 index cd658e4e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @EnabledIfSystemProperty} is used to signal that the annotated test - * class or test method is only enabled if the value of the specified - * {@linkplain #named system property} matches the specified - * {@linkplain #matches regular expression}. - * - *

When declared at the class level, the result will apply to all test methods - * within that class as well. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

If the specified system property is undefined, the annotated class or - * method will be disabled. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

As of JUnit Jupiter 5.6, this annotation is a {@linkplain Repeatable - * repeatable} annotation. Consequently, this annotation may be declared multiple - * times on an {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., - * test interface, test class, or test method). Specifically, this annotation will - * be found if it is directly present, indirectly present, or meta-present on a - * given element. - * - * @since 5.1 - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(EnabledIfSystemProperties.class) -@ExtendWith(EnabledIfSystemPropertyCondition.class) -@API(status = STABLE, since = "5.1") -public @interface EnabledIfSystemProperty { - - /** - * The name of the JVM system property to retrieve. - * - * @return the system property name; never blank - * @see System#getProperty(String) - */ - String named(); - - /** - * A regular expression that will be used to match against the retrieved - * value of the {@link #named} JVM system property. - * - * @return the regular expression; never blank - * @see String#matches(String) - * @see java.util.regex.Pattern - */ - String matches(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java deleted file mode 100644 index af8c7cb9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static java.lang.String.format; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link EnabledIfSystemProperty @EnabledIfSystemProperty}. - * - * @since 5.1 - * @see EnabledIfSystemProperty - */ -class EnabledIfSystemPropertyCondition extends AbstractRepeatableAnnotationCondition { - - private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( - "No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - - EnabledIfSystemPropertyCondition() { - super(EnabledIfSystemProperty.class); - } - - @Override - protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { - return ENABLED; - } - - @Override - protected ConditionEvaluationResult evaluate(EnabledIfSystemProperty annotation) { - - String name = annotation.named().trim(); - String regex = annotation.matches(); - Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); - Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); - String actual = System.getProperty(name); - - // Nothing to match against? - if (actual == null) { - return disabled(format("System property [%s] does not exist", name), annotation.disabledReason()); - } - if (actual.matches(regex)) { - return enabled( - format("System property [%s] with value [%s] matches regular expression [%s]", name, actual, regex)); - } - return disabled( - format("System property [%s] with value [%s] does not match regular expression [%s]", name, actual, regex), - annotation.disabledReason()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java deleted file mode 100644 index 33641509..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @EnabledInNativeImage} is used to signal that the annotated test class - * or test method is only enabled when executing within a GraalVM native - * image. - * - *

When applied at the class level, all test methods within that class will - * be enabled within a native image. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Technical Details

- * - *

JUnit detects whether tests are executing within a GraalVM native image by - * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} - * system property (see - * org.graalvm.nativeimage.ImageInfo - * for details). The GraalVM compiler sets the property to {@code buildtime} while - * compiling a native image; the property is set to {@code runtime} while a native - * image is executing; and the Gradle and Maven plug-ins in the GraalVM - * Native Build Tools - * project set the property to {@code agent} while executing tests with the GraalVM - * tracing agent. - * - * @since 5.9.1 - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@EnabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // - disabledReason = "Not currently executing within a GraalVM native image") -@API(status = STABLE, since = "5.9.1") -public @interface EnabledInNativeImage { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java deleted file mode 100644 index c5ca85ea..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @EnabledOnJre} is used to signal that the annotated test class or - * test method is only enabled on one or more specified Java - * Runtime Environment (JRE) {@linkplain #value versions}. - * - *

When applied at the class level, all test methods within that class - * will be enabled on the same specified JRE versions. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.1 - * @see JRE - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(EnabledOnJreCondition.class) -@API(status = STABLE, since = "5.1") -public @interface EnabledOnJre { - - /** - * Java Runtime Environment versions on which the annotated class or - * method should be enabled. - * - * @see JRE - */ - JRE[] value(); - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java deleted file mode 100644 index 2bd86535..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import java.util.Arrays; - -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link EnabledOnJre @EnabledOnJre}. - * - * @since 5.1 - * @see EnabledOnJre - */ -class EnabledOnJreCondition extends BooleanExecutionCondition { - - static final String ENABLED_ON_CURRENT_JRE = // - "Enabled on JRE version: " + System.getProperty("java.version"); - - static final String DISABLED_ON_CURRENT_JRE = // - "Disabled on JRE version: " + System.getProperty("java.version"); - - EnabledOnJreCondition() { - super(EnabledOnJre.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, EnabledOnJre::disabledReason); - } - - @Override - boolean isEnabled(EnabledOnJre annotation) { - JRE[] versions = annotation.value(); - Preconditions.condition(versions.length > 0, "You must declare at least one JRE in @EnabledOnJre"); - return Arrays.stream(versions).anyMatch(JRE::isCurrentVersion); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java deleted file mode 100644 index 2805fc40..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @EnabledOnOs} is used to signal that the annotated test class or - * test method is only enabled on one or more specified - * {@linkplain #value operating systems} or one or more specified - * {@linkplain #architectures architectures}. - * - *

If operating systems and architectures are specified, the annotated - * test class or test method is enabled if both conditions apply. - * - *

When applied at the class level, all test methods within that class - * will be enabled on the same specified operating systems, architectures, or - * the specified combinations of both. - * - *

If a test method is disabled via this annotation, that does not prevent - * the test class from being instantiated. Rather, it prevents the execution of - * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} - * methods, {@code @AfterEach} methods, and corresponding extension APIs. - * - *

This annotation may be used as a meta-annotation in order to create a - * custom composed annotation that inherits the semantics of this - * annotation. - * - *

Warning

- * - *

As of JUnit Jupiter 5.1, this annotation can only be declared once on an - * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test - * interface, test class, or test method). If this annotation is directly - * present, indirectly present, or meta-present multiple times on a given - * element, only the first such annotation discovered by JUnit will be used; - * any additional declarations will be silently ignored. Note, however, that - * this annotation may be used in conjunction with other {@code @Enabled*} or - * {@code @Disabled*} annotations in this package. - * - * @since 5.1 - * @see OS - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.Disabled - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@ExtendWith(EnabledOnOsCondition.class) -@API(status = STABLE, since = "5.1") -public @interface EnabledOnOs { - - /** - * Operating systems on which the annotated class or method should be - * enabled. - * - * @see OS - */ - OS[] value() default {}; - - /** - * Architectures on which the annotated class or method should be enabled. - * - *

Each architecture will be compared to the value returned from - * {@code System.getProperty("os.arch")}, ignoring case. - * - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - String[] architectures() default {}; - - /** - * Custom reason to provide if the test or container is disabled. - * - *

If a custom reason is supplied, it will be combined with the default - * reason for this annotation. If a custom reason is not supplied, the default - * reason will be used. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - String disabledReason() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java deleted file mode 100644 index 092342f7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import java.util.Arrays; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ExecutionCondition} for {@link EnabledOnOs @EnabledOnOs}. - * - * @since 5.1 - * @see EnabledOnOs - */ -class EnabledOnOsCondition extends AbstractOsBasedExecutionCondition { - - EnabledOnOsCondition() { - super(EnabledOnOs.class); - } - - @Override - ConditionEvaluationResult evaluateExecutionCondition(EnabledOnOs annotation) { - boolean osSpecified = annotation.value().length > 0; - boolean archSpecified = annotation.architectures().length > 0; - Preconditions.condition(osSpecified || archSpecified, - "You must declare at least one OS or architecture in @EnabledOnOs"); - - boolean enabled = isEnabledBasedOnOs(annotation) && isEnabledBasedOnArchitecture(annotation); - String reason = createReason(enabled, osSpecified, archSpecified); - - return enabled ? ConditionEvaluationResult.enabled(reason) - : ConditionEvaluationResult.disabled(reason, annotation.disabledReason()); - } - - private boolean isEnabledBasedOnOs(EnabledOnOs annotation) { - OS[] operatingSystems = annotation.value(); - if (operatingSystems.length == 0) { - return true; - } - return Arrays.stream(operatingSystems).anyMatch(OS::isCurrentOs); - } - - private boolean isEnabledBasedOnArchitecture(EnabledOnOs annotation) { - String[] architectures = annotation.architectures(); - if (architectures.length == 0) { - return true; - } - return Arrays.stream(architectures).anyMatch(CURRENT_ARCHITECTURE::equalsIgnoreCase); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java deleted file mode 100644 index b42bc90e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/JRE.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.reflect.Method; -import java.util.EnumSet; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; - -/** - * Enumeration of Java Runtime Environment (JRE) versions. - * - *

If the current JRE version cannot be detected — for example, if the - * {@code java.version} JVM system property is undefined — then none of - * the constants defined in this enum will be considered to be the - * {@linkplain #isCurrentVersion current JRE version}. - * - * @since 5.1 - * @see #JAVA_8 - * @see #JAVA_9 - * @see #JAVA_10 - * @see #JAVA_11 - * @see #JAVA_12 - * @see #JAVA_13 - * @see #JAVA_14 - * @see #OTHER - * @see EnabledOnJre - * @see DisabledOnJre - * @see EnabledForJreRange - * @see DisabledForJreRange - */ -@API(status = STABLE, since = "5.1") -public enum JRE { - - /** - * Java 8. - */ - JAVA_8, - - /** - * Java 9. - */ - JAVA_9, - - /** - * Java 10. - */ - JAVA_10, - - /** - * Java 11. - */ - JAVA_11, - - /** - * Java 12. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - JAVA_12, - - /** - * Java 13. - * - * @since 5.4 - */ - @API(status = STABLE, since = "5.4") - JAVA_13, - - /** - * Java 14. - * - * @since 5.5 - */ - @API(status = STABLE, since = "5.5") - JAVA_14, - - /** - * Java 15. - * - * @since 5.6 - */ - @API(status = STABLE, since = "5.6") - JAVA_15, - - /** - * Java 16. - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - JAVA_16, - - /** - * Java 17. - * - * @since 5.7.1 - */ - @API(status = STABLE, since = "5.7.1") - JAVA_17, - - /** - * Java 18. - * - * @since 5.8.1 - */ - @API(status = STABLE, since = "5.8.1") - JAVA_18, - - /** - * Java 19. - * - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - JAVA_19, - - /** - * Java 20. - * - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - JAVA_20, - - /** - * Java 20. - * - * @since 5.9.2 - */ - @API(status = STABLE, since = "5.9.2") - JAVA_21, - - /** - * A JRE version other than {@link #JAVA_8}, {@link #JAVA_9}, - * {@link #JAVA_10}, {@link #JAVA_11}, {@link #JAVA_12}, - * {@link #JAVA_13}, {@link #JAVA_14}, {@link #JAVA_15}, - * {@link #JAVA_16}, {@link #JAVA_17}, {@link #JAVA_18}, - * {@link #JAVA_19}, {@link #JAVA_20}, or {@link #JAVA_21}. - */ - OTHER; - - private static final Logger logger = LoggerFactory.getLogger(JRE.class); - - private static final JRE CURRENT_VERSION = determineCurrentVersion(); - - private static JRE determineCurrentVersion() { - String javaVersion = System.getProperty("java.version"); - boolean javaVersionIsBlank = StringUtils.isBlank(javaVersion); - - if (javaVersionIsBlank) { - logger.debug( - () -> "JVM system property 'java.version' is undefined. It is therefore not possible to detect Java 8."); - } - - if (!javaVersionIsBlank && javaVersion.startsWith("1.8")) { - return JAVA_8; - } - - try { - // java.lang.Runtime.version() is a static method available on Java 9+ - // that returns an instance of java.lang.Runtime.Version which has the - // following method: public int major() - Method versionMethod = Runtime.class.getMethod("version"); - Object version = ReflectionUtils.invokeMethod(versionMethod, null); - Method majorMethod = version.getClass().getMethod("major"); - int major = (int) ReflectionUtils.invokeMethod(majorMethod, version); - switch (major) { - case 9: - return JAVA_9; - case 10: - return JAVA_10; - case 11: - return JAVA_11; - case 12: - return JAVA_12; - case 13: - return JAVA_13; - case 14: - return JAVA_14; - case 15: - return JAVA_15; - case 16: - return JAVA_16; - case 17: - return JAVA_17; - case 18: - return JAVA_18; - case 19: - return JAVA_19; - case 20: - return JAVA_20; - case 21: - return JAVA_21; - default: - return OTHER; - } - } - catch (Exception ex) { - logger.debug(ex, () -> "Failed to determine the current JRE version via java.lang.Runtime.Version."); - } - - // null signals that the current JRE version is "unknown" - return null; - } - - /** - * @return {@code true} if this {@code JRE} is known to be the - * Java Runtime Environment version for the currently executing JVM - */ - public boolean isCurrentVersion() { - return this == CURRENT_VERSION; - } - - /** - * @return the {@link JRE} for the currently executing JVM - * - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - public static JRE currentVersion() { - return CURRENT_VERSION; - } - - static boolean isCurrentVersionWithinRange(JRE min, JRE max) { - return EnumSet.range(min, max).contains(CURRENT_VERSION); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java deleted file mode 100644 index 40330479..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static java.lang.String.format; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; -import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Supplier; - -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.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; - -/** - * @since 5.7 - */ -abstract class MethodBasedCondition implements ExecutionCondition { - - private final Class annotationType; - private final Function methodName; - private final Function customDisabledReason; - - MethodBasedCondition(Class annotationType, Function methodName, - Function customDisabledReason) { - this.annotationType = annotationType; - this.methodName = methodName; - this.customDisabledReason = customDisabledReason; - } - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - Optional annotation = findAnnotation(context.getElement(), this.annotationType); - return annotation // - .map(this.methodName) // - .map(methodName -> getConditionMethod(methodName, context)) // - .map(method -> invokeConditionMethod(method, context)) // - .map(methodResult -> buildConditionEvaluationResult(methodResult, annotation.get())) // - .orElseGet(this::enabledByDefault); - } - - private Method getConditionMethod(String fullyQualifiedMethodName, ExtensionContext context) { - if (!fullyQualifiedMethodName.contains("#")) { - return findMethod(context.getRequiredTestClass(), fullyQualifiedMethodName); - } - String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); - String className = methodParts[0]; - String methodName = methodParts[1]; - Class clazz = ReflectionUtils.tryToLoadClass(className).getOrThrow( - cause -> new JUnitException(format("Could not load class [%s]", className), cause)); - return findMethod(clazz, methodName); - } - - private Method findMethod(Class clazz, String methodName) { - return ReflectionUtils.findMethod(clazz, methodName) // - .orElseGet(() -> ReflectionUtils.getRequiredMethod(clazz, methodName, ExtensionContext.class)); - } - - private boolean invokeConditionMethod(Method method, ExtensionContext context) { - Preconditions.condition(method.getReturnType() == boolean.class, - () -> format("Method [%s] must return a boolean", method)); - Preconditions.condition(acceptsExtensionContextOrNoArguments(method), - () -> format("Method [%s] must accept either an ExtensionContext or no arguments", method)); - - Object testInstance = context.getTestInstance().orElse(null); - if (method.getParameterCount() == 0) { - return (boolean) ReflectionUtils.invokeMethod(method, testInstance); - } - return (boolean) ReflectionUtils.invokeMethod(method, testInstance, context); - } - - private boolean acceptsExtensionContextOrNoArguments(Method method) { - int parameterCount = method.getParameterCount(); - return parameterCount == 0 || (parameterCount == 1 && method.getParameterTypes()[0] == ExtensionContext.class); - } - - private ConditionEvaluationResult buildConditionEvaluationResult(boolean methodResult, A annotation) { - Supplier defaultReason = () -> format("@%s(\"%s\") evaluated to %s", - this.annotationType.getSimpleName(), this.methodName.apply(annotation), methodResult); - if (isEnabled(methodResult)) { - return enabled(defaultReason.get()); - } - String customReason = this.customDisabledReason.apply(annotation); - return StringUtils.isNotBlank(customReason) ? disabled(customReason) : disabled(defaultReason.get()); - } - - protected abstract boolean isEnabled(boolean methodResult); - - private ConditionEvaluationResult enabledByDefault() { - return enabled(format("@%s is not present", this.annotationType.getSimpleName())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java deleted file mode 100644 index 5acb383e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Locale; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.StringUtils; - -/** - * Enumeration of common operating systems used for testing Java applications. - * - *

If the current operating system cannot be detected — for example, - * if the {@code os.name} JVM system property is undefined — then none - * of the constants defined in this enum will be considered to be the - * {@linkplain #isCurrentOs current operating system}. - * - * @since 5.1 - * @see #AIX - * @see #FREEBSD - * @see #LINUX - * @see #MAC - * @see #OPENBSD - * @see #SOLARIS - * @see #WINDOWS - * @see #OTHER - * @see EnabledOnOs - * @see DisabledOnOs - */ -@API(status = STABLE, since = "5.1") -public enum OS { - - /** - * IBM AIX operating system. - * - * @since 5.3 - */ - @API(status = STABLE, since = "5.3") - AIX, - - /** - * FreeBSD operating system. - * - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - FREEBSD, - - /** - * Linux-based operating system. - */ - LINUX, - - /** - * Apple Macintosh operating system (e.g., macOS). - */ - MAC, - - /** - * OpenBSD operating system. - * - * @since 5.9 - */ - @API(status = STABLE, since = "5.9") - OPENBSD, - - /** - * Oracle Solaris operating system. - */ - SOLARIS, - - /** - * Microsoft Windows operating system. - */ - WINDOWS, - - /** - * An operating system other than {@link #AIX}, {@link #FREEBSD}, {@link #LINUX}, - * {@link #MAC}, {@link #OPENBSD}, {@link #SOLARIS}, or {@link #WINDOWS}. - */ - OTHER; - - private static final Logger logger = LoggerFactory.getLogger(OS.class); - - private static final OS CURRENT_OS = determineCurrentOs(); - - /** - * Get the current operating system. - * - * @since 5.9 - */ - @API(status = EXPERIMENTAL, since = "5.9") - public static OS current() { - return CURRENT_OS; - } - - private static OS determineCurrentOs() { - return parse(System.getProperty("os.name")); - } - - static OS parse(String osName) { - if (StringUtils.isBlank(osName)) { - logger.debug( - () -> "JVM system property 'os.name' is undefined. It is therefore not possible to detect the current OS."); - - // null signals that the current OS is "unknown" - return null; - } - - osName = osName.toLowerCase(Locale.ENGLISH); - - if (osName.contains("aix")) { - return AIX; - } - if (osName.contains("freebsd")) { - return FREEBSD; - } - if (osName.contains("linux")) { - return LINUX; - } - if (osName.contains("mac")) { - return MAC; - } - if (osName.contains("openbsd")) { - return OPENBSD; - } - if (osName.contains("sunos") || osName.contains("solaris")) { - return SOLARIS; - } - if (osName.contains("win")) { - return WINDOWS; - } - return OTHER; - } - - /** - * @return {@code true} if this {@code OS} is known to be the - * operating system on which the current JVM is executing - */ - public boolean isCurrentOs() { - return this == CURRENT_OS; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java deleted file mode 100644 index 14d1f7fb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Annotation-based conditions for enabling or disabling tests in JUnit Jupiter. - */ - -package org.junit.jupiter.api.condition; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java deleted file mode 100644 index cdf4c267..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code AfterAllCallback} defines the API for {@link Extension Extensions} - * that wish to provide additional behavior to test containers once - * after all tests in the container have been executed. - * - *

Concrete implementations often implement {@link BeforeAllCallback} as well. - * - *

Extensions that implement {@code AfterAllCallback} must be registered at - * the class level. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - *

Wrapping Behavior

- * - *

JUnit Jupiter guarantees wrapping behavior for multiple - * registered extensions that implement lifecycle callbacks such as - * {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link BeforeEachCallback}, {@link AfterEachCallback}, - * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. - * - *

That means that, given two extensions {@code Extension1} and - * {@code Extension2} with {@code Extension1} registered before - * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} - * are guaranteed to execute before any "before" callbacks implemented by - * {@code Extension2}. Similarly, given the two same two extensions registered - * in the same order, any "after" callbacks implemented by {@code Extension1} - * are guaranteed to execute after any "after" callbacks implemented by - * {@code Extension2}. {@code Extension1} is therefore said to wrap - * {@code Extension2}. - * - * @since 5.0 - * @see org.junit.jupiter.api.AfterAll - * @see BeforeAllCallback - * @see BeforeEachCallback - * @see AfterEachCallback - * @see BeforeTestExecutionCallback - * @see AfterTestExecutionCallback - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface AfterAllCallback extends Extension { - - /** - * Callback that is invoked once after all tests in the current - * container. - * - * @param context the current extension context; never {@code null} - */ - void afterAll(ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java deleted file mode 100644 index 6c26cc11..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code AfterEachCallback} defines the API for {@link Extension Extensions} - * that wish to provide additional behavior to tests after an individual test - * and any user-defined teardown methods (e.g., - * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods) for that test - * have been executed. - * - *

Concrete implementations often implement {@link BeforeEachCallback} as well. - * If you do not wish to have your callbacks wrapped around user-defined - * setup and teardown methods, implement {@link BeforeTestExecutionCallback} and - * {@link AfterTestExecutionCallback} instead of {@link BeforeEachCallback} and - * {@link AfterEachCallback}. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - *

Wrapping Behavior

- * - *

JUnit Jupiter guarantees wrapping behavior for multiple - * registered extensions that implement lifecycle callbacks such as - * {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link BeforeEachCallback}, {@link AfterEachCallback}, - * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. - * - *

That means that, given two extensions {@code Extension1} and - * {@code Extension2} with {@code Extension1} registered before - * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} - * are guaranteed to execute before any "before" callbacks implemented by - * {@code Extension2}. Similarly, given the two same two extensions registered - * in the same order, any "after" callbacks implemented by {@code Extension1} - * are guaranteed to execute after any "after" callbacks implemented by - * {@code Extension2}. {@code Extension1} is therefore said to wrap - * {@code Extension2}. - * - * @since 5.0 - * @see org.junit.jupiter.api.AfterEach - * @see BeforeEachCallback - * @see BeforeTestExecutionCallback - * @see AfterTestExecutionCallback - * @see BeforeAllCallback - * @see AfterAllCallback - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface AfterEachCallback extends Extension { - - /** - * Callback that is invoked after an individual test and any - * user-defined teardown methods for that test have been executed. - * - * @param context the current extension context; never {@code null} - */ - void afterEach(ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java deleted file mode 100644 index 817744a1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code AfterTestExecutionCallback} defines the API for {@link Extension - * Extensions} that wish to provide additional behavior to tests - * immediately after an individual test has been executed but - * before any user-defined teardown methods (e.g., - * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods) have been executed - * for that test. - * - *

Concrete implementations often implement {@link BeforeTestExecutionCallback} - * as well. If you wish to have your callbacks wrapped around user-defined - * setup and teardown methods, implement {@link BeforeEachCallback} and - * {@link AfterEachCallback} instead of {@link BeforeTestExecutionCallback} and - * {@link AfterTestExecutionCallback}. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - *

Wrapping Behavior

- * - *

JUnit Jupiter guarantees wrapping behavior for multiple - * registered extensions that implement lifecycle callbacks such as - * {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link BeforeEachCallback}, {@link AfterEachCallback}, - * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. - * - *

That means that, given two extensions {@code Extension1} and - * {@code Extension2} with {@code Extension1} registered before - * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} - * are guaranteed to execute before any "before" callbacks implemented by - * {@code Extension2}. Similarly, given the two same two extensions registered - * in the same order, any "after" callbacks implemented by {@code Extension1} - * are guaranteed to execute after any "after" callbacks implemented by - * {@code Extension2}. {@code Extension1} is therefore said to wrap - * {@code Extension2}. - * - * @since 5.0 - * @see org.junit.jupiter.api.Test - * @see BeforeTestExecutionCallback - * @see BeforeEachCallback - * @see AfterEachCallback - * @see BeforeAllCallback - * @see AfterAllCallback - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface AfterTestExecutionCallback extends Extension { - - /** - * Callback that is invoked immediately after an individual test has - * been executed but before any user-defined teardown methods have been - * executed for that test. - * - * @param context the current extension context; never {@code null} - */ - void afterTestExecution(ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java deleted file mode 100644 index ae78aa2d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code BeforeAllCallback} defines the API for {@link Extension Extensions} - * that wish to provide additional behavior to test containers once - * before all tests in the container have been executed. - * - *

Concrete implementations often implement {@link AfterAllCallback} as well. - * - *

Extensions that implement {@code BeforeAllCallback} must be registered at - * the class level. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - *

Wrapping Behavior

- * - *

JUnit Jupiter guarantees wrapping behavior for multiple - * registered extensions that implement lifecycle callbacks such as - * {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link BeforeEachCallback}, {@link AfterEachCallback}, - * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. - * - *

That means that, given two extensions {@code Extension1} and - * {@code Extension2} with {@code Extension1} registered before - * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} - * are guaranteed to execute before any "before" callbacks implemented by - * {@code Extension2}. Similarly, given the two same two extensions registered - * in the same order, any "after" callbacks implemented by {@code Extension1} - * are guaranteed to execute after any "after" callbacks implemented by - * {@code Extension2}. {@code Extension1} is therefore said to wrap - * {@code Extension2}. - * - * @since 5.0 - * @see org.junit.jupiter.api.BeforeAll - * @see AfterAllCallback - * @see BeforeEachCallback - * @see AfterEachCallback - * @see BeforeTestExecutionCallback - * @see AfterTestExecutionCallback - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface BeforeAllCallback extends Extension { - - /** - * Callback that is invoked once before all tests in the current - * container. - * - * @param context the current extension context; never {@code null} - */ - void beforeAll(ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java deleted file mode 100644 index 2ba5b378..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code BeforeEachCallback} defines the API for {@link Extension Extensions} - * that wish to provide additional behavior to tests before an individual test - * and any user-defined setup methods (e.g., - * {@link org.junit.jupiter.api.BeforeEach @BeforeEach} methods) for that test - * have been executed. - * - *

Concrete implementations often implement {@link AfterEachCallback} as well. - * If you do not wish to have your callbacks wrapped around user-defined - * setup and teardown methods, implement {@link BeforeTestExecutionCallback} and - * {@link AfterTestExecutionCallback} instead of {@link BeforeEachCallback} and - * {@link AfterEachCallback}. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - *

Wrapping Behavior

- * - *

JUnit Jupiter guarantees wrapping behavior for multiple - * registered extensions that implement lifecycle callbacks such as - * {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link BeforeEachCallback}, {@link AfterEachCallback}, - * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. - * - *

That means that, given two extensions {@code Extension1} and - * {@code Extension2} with {@code Extension1} registered before - * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} - * are guaranteed to execute before any "before" callbacks implemented by - * {@code Extension2}. Similarly, given the two same two extensions registered - * in the same order, any "after" callbacks implemented by {@code Extension1} - * are guaranteed to execute after any "after" callbacks implemented by - * {@code Extension2}. {@code Extension1} is therefore said to wrap - * {@code Extension2}. - * - * @since 5.0 - * @see org.junit.jupiter.api.BeforeEach - * @see AfterEachCallback - * @see BeforeTestExecutionCallback - * @see AfterTestExecutionCallback - * @see BeforeAllCallback - * @see AfterAllCallback - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface BeforeEachCallback extends Extension { - - /** - * Callback that is invoked before an individual test and any - * user-defined setup methods for that test have been executed. - * - * @param context the current extension context; never {@code null} - */ - void beforeEach(ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java deleted file mode 100644 index 9a250881..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code BeforeTestExecutionCallback} defines the API for {@link Extension - * Extensions} that wish to provide additional behavior to tests - * immediately before an individual test is executed but after - * any user-defined setup methods (e.g., - * {@link org.junit.jupiter.api.BeforeEach @BeforeEach} methods) have been - * executed for that test. - * - *

Concrete implementations often implement {@link AfterTestExecutionCallback} - * as well. If you wish to have your callbacks wrapped around user-defined - * setup and teardown methods, implement {@link BeforeEachCallback} and - * {@link AfterEachCallback} instead of {@link BeforeTestExecutionCallback} and - * {@link AfterTestExecutionCallback}. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - *

Wrapping Behavior

- * - *

JUnit Jupiter guarantees wrapping behavior for multiple - * registered extensions that implement lifecycle callbacks such as - * {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link BeforeEachCallback}, {@link AfterEachCallback}, - * {@link BeforeTestExecutionCallback}, and {@link AfterTestExecutionCallback}. - * - *

That means that, given two extensions {@code Extension1} and - * {@code Extension2} with {@code Extension1} registered before - * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} - * are guaranteed to execute before any "before" callbacks implemented by - * {@code Extension2}. Similarly, given the two same two extensions registered - * in the same order, any "after" callbacks implemented by {@code Extension1} - * are guaranteed to execute after any "after" callbacks implemented by - * {@code Extension2}. {@code Extension1} is therefore said to wrap - * {@code Extension2}. - * - * @since 5.0 - * @see org.junit.jupiter.api.Test - * @see AfterTestExecutionCallback - * @see BeforeEachCallback - * @see AfterEachCallback - * @see BeforeAllCallback - * @see AfterAllCallback - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface BeforeTestExecutionCallback extends Extension { - - /** - * Callback that is invoked immediately before an individual test is - * executed but after any user-defined setup methods have been executed - * for that test. - * - * @param context the current extension context; never {@code null} - */ - void beforeTestExecution(ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java deleted file mode 100644 index e01c5ac6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * The result of evaluating an {@link ExecutionCondition}. - * - * @since 5.0 - */ -@API(status = STABLE, since = "5.0") -public class ConditionEvaluationResult { - - /** - * Factory for creating enabled results. - * - * @param reason the reason why the container or test should be enabled - * @return an enabled {@code ConditionEvaluationResult} with the given reason - */ - public static ConditionEvaluationResult enabled(String reason) { - return new ConditionEvaluationResult(true, reason); - } - - /** - * Factory for creating disabled results. - * - * @param reason the reason why the container or test should be disabled - * @return a disabled {@code ConditionEvaluationResult} with the given reason - */ - public static ConditionEvaluationResult disabled(String reason) { - return new ConditionEvaluationResult(false, reason); - } - - /** - * Factory for creating disabled results with custom reasons - * added by the user. - * - * @param reason the default reason why the container or test should be disabled - * @param customReason the custom reason why the container or test should be disabled - * @return a disabled {@code ConditionEvaluationResult} with the given reasons - * @since 5.7 - */ - @API(status = STABLE, since = "5.7") - public static ConditionEvaluationResult disabled(String reason, String customReason) { - if (StringUtils.isBlank(customReason)) { - return disabled(reason); - } - return disabled(String.format("%s ==> %s", reason, customReason)); - } - - private final boolean enabled; - - private final Optional reason; - - private ConditionEvaluationResult(boolean enabled, String reason) { - this.enabled = enabled; - this.reason = Optional.ofNullable(reason); - } - - /** - * Whether the container or test should be disabled. - * - * @return {@code true} if the container or test should be disabled - */ - public boolean isDisabled() { - return !this.enabled; - } - - /** - * Get the reason why the container or test should be enabled or disabled, - * if available. - */ - public Optional getReason() { - return this.reason; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("enabled", this.enabled) - .append("reason", this.reason.orElse("")) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java deleted file mode 100644 index a040ae80..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.function.Executable; - -/** - * {@code DynamicTestInvocationContext} represents the context of a - * single invocation of a {@linkplain org.junit.jupiter.api.DynamicTest - * dynamic test}. - * - * @since 5.8 - * @see org.junit.jupiter.api.DynamicTest - */ -@API(status = EXPERIMENTAL, since = "5.8") -public interface DynamicTestInvocationContext { - - /** - * Get the {@code Executable} of this dynamic test invocation context. - * - * @return the executable of the dynamic test invocation, never {@code null} - */ - Executable getExecutable(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java deleted file mode 100644 index 7cfad263..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import org.apiguardian.api.API; - -/** - * {@code ExecutableInvoker} allows invoking methods and constructors - * with support for dynamic resolution of parameters via - * {@link ParameterResolver ParameterResolvers}. - * - * @since 5.9 - */ -@API(status = EXPERIMENTAL, since = "5.9") -public interface ExecutableInvoker { - - /** - * Invoke the supplied {@code static} method with dynamic parameter resolution. - * - * @param method the method to invoke and resolve parameters for - * @see #invoke(Method, Object) - */ - default Object invoke(Method method) { - return invoke(method, null); - } - - /** - * Invoke the supplied method with dynamic parameter resolution. - * - * @param method the method to invoke and resolve parameters for - * @param target the target on which the executable will be invoked; - * can be {@code null} for {@code static} methods - */ - Object invoke(Method method, Object target); - - /** - * Invoke the supplied top-level constructor with dynamic parameter resolution. - * - * @param constructor the constructor to invoke and resolve parameters for - * @see #invoke(Constructor, Object) - */ - default T invoke(Constructor constructor) { - return invoke(constructor, null); - } - - /** - * Invoke the supplied constructor with the supplied outer instance and - * dynamic parameter resolution. - * - *

Use this method when invoking the constructor for an inner class. - * - * @param constructor the constructor to invoke and resolve parameters for - * @param outerInstance the outer instance to supply as the first argument - * to the constructor; must be {@code null} for top-level classes - * or {@code static} nested classes - */ - T invoke(Constructor constructor, Object outerInstance); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java deleted file mode 100644 index 9747a1e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code ExecutionCondition} defines the {@link Extension} API for - * programmatic, conditional test execution. - * - *

An {@code ExecutionCondition} is - * {@linkplain #evaluateExecutionCondition(ExtensionContext) evaluated} - * to determine if a given container or test should be executed based on the - * supplied {@link ExtensionContext}. - * - *

If an {@code ExecutionCondition} {@linkplain ConditionEvaluationResult#disabled - * disables} a test method, that does not prevent the test class from being - * instantiated. Rather, it prevents the execution of the test method and - * method-level lifecycle callbacks such as {@code @BeforeEach} methods, - * {@code @AfterEach} methods, and corresponding extension APIs. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - * @since 5.0 - * @see org.junit.jupiter.api.Disabled - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledInNativeImage - * @see org.junit.jupiter.api.condition.DisabledInNativeImage - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface ExecutionCondition extends Extension { - - /** - * Evaluate this condition for the supplied {@link ExtensionContext}. - * - *

An {@linkplain ConditionEvaluationResult#enabled enabled} result - * indicates that the container or test should be executed; whereas, a - * {@linkplain ConditionEvaluationResult#disabled disabled} result - * indicates that the container or test should not be executed. - * - * @param context the current extension context; never {@code null} - * @return the result of evaluating this condition; never {@code null} - */ - ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java deleted file mode 100644 index dbee47c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ExtendWith} is a {@linkplain Repeatable repeatable} annotation - * that is used to register {@linkplain Extension extensions} for the annotated - * test class, test interface, test method, parameter, or field. - * - *

Annotated parameters are supported in test class constructors, in test - * methods, and in {@code @BeforeAll}, {@code @AfterAll}, {@code @BeforeEach}, - * and {@code @AfterEach} lifecycle methods. - * - *

{@code @ExtendWith} fields may be either {@code static} or non-static. - * - *

Inheritance

- * - *

{@code @ExtendWith} fields are inherited from superclasses as long as they - * are not hidden or overridden. Furthermore, {@code @ExtendWith} - * fields from superclasses will be registered before {@code @ExtendWith} fields - * in subclasses. - * - *

Registration Order

- * - *

When {@code @ExtendWith} is present on a test class, test interface, or - * test method or on a parameter in a test method or lifecycle method, the - * corresponding extensions will be registered in the order in which the - * {@code @ExtendWith} annotations are discovered. For example, if a test class - * is annotated with {@code @ExtendWith(A.class)} and then with - * {@code @ExtendWith(B.class)}, extension {@code A} will be registered before - * extension {@code B}. - * - *

By default, if multiple extensions are registered on fields via - * {@code @ExtendWith}, they will be ordered using an algorithm that is - * deterministic but intentionally nonobvious. This ensures that subsequent runs - * of a test suite execute extensions in the same order, thereby allowing for - * repeatable builds. However, there are times when extensions need to be - * registered in an explicit order. To achieve that, you can annotate - * {@code @ExtendWith} fields with {@link org.junit.jupiter.api.Order @Order}. - * Any {@code @ExtendWith} field not annotated with {@code @Order} will be - * ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order - * value. Note that {@code @RegisterExtension} fields can also be ordered with - * {@code @Order}, relative to {@code @ExtendWith} fields and other - * {@code @RegisterExtension} fields. - * - *

Supported Extension APIs

- * - *
    - *
  • {@link ExecutionCondition}
  • - *
  • {@link InvocationInterceptor}
  • - *
  • {@link BeforeAllCallback}
  • - *
  • {@link AfterAllCallback}
  • - *
  • {@link BeforeEachCallback}
  • - *
  • {@link AfterEachCallback}
  • - *
  • {@link BeforeTestExecutionCallback}
  • - *
  • {@link AfterTestExecutionCallback}
  • - *
  • {@link TestInstanceFactory}
  • - *
  • {@link TestInstancePostProcessor}
  • - *
  • {@link TestInstancePreDestroyCallback}
  • - *
  • {@link ParameterResolver}
  • - *
  • {@link TestExecutionExceptionHandler}
  • - *
  • {@link TestTemplateInvocationContextProvider}
  • - *
- * - * @since 5.0 - * @see RegisterExtension - * @see Extension - */ -@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@Repeatable(Extensions.class) -@API(status = STABLE, since = "5.0") -public @interface ExtendWith { - - /** - * An array of one or more {@link Extension} classes to register. - */ - Class[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java deleted file mode 100644 index fa1d255e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * Marker interface for all extensions. - * - *

An {@code Extension} can be registered declaratively via - * {@link ExtendWith @ExtendWith}, programmatically via - * {@link RegisterExtension @RegisterExtension}, or automatically via - * the {@link java.util.ServiceLoader} mechanism. For details on the latter, - * consult the User Guide. - * - *

Constructor Requirements

- * - *

Extension implementations must have a default constructor if - * registered via {@code @ExtendWith} or the {@code ServiceLoader}. When - * registered via {@code @ExtendWith} the default constructor is not required - * to be {@code public}. When registered via the {@code ServiceLoader} the - * default constructor must be {@code public}. When registered via - * {@code @RegisterExtension} the extension's constructors typically must be - * {@code public} unless the extension provides {@code static} factory methods - * or a builder API as an alternative to constructors. - * - * @since 5.0 - */ -@API(status = STABLE, since = "5.0") -public interface Extension { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java deleted file mode 100644 index f4204b49..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * Thrown if an error is encountered regarding the configuration of an - * extension. - * - * @since 5.0 - */ -@API(status = STABLE, since = "5.0") -public class ExtensionConfigurationException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public ExtensionConfigurationException(String message) { - super(message); - } - - public ExtensionConfigurationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java deleted file mode 100644 index e58f585f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java +++ /dev/null @@ -1,713 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@code ExtensionContext} encapsulates the context in which the - * current test or container is being executed. - * - *

{@link Extension Extensions} are provided an instance of - * {@code ExtensionContext} to perform their work. - * - * @since 5.0 - * @see Store - * @see Namespace - */ -@API(status = STABLE, since = "5.0") -public interface ExtensionContext { - - /** - * Get the parent extension context, if available. - * - * @return an {@code Optional} containing the parent; never {@code null} but - * potentially empty - * @see #getRoot() - */ - Optional getParent(); - - /** - * Get the root {@code ExtensionContext}. - * - * @return the root extension context; never {@code null} but potentially - * this {@code ExtensionContext} - * @see #getParent() - */ - ExtensionContext getRoot(); - - /** - * Get the unique ID of the current test or container. - * - * @return the unique ID of the test or container; never {@code null} or blank - */ - String getUniqueId(); - - /** - * Get the display name for the current test or container. - * - *

The display name is either a default name or a custom name configured - * via {@link org.junit.jupiter.api.DisplayName @DisplayName}. - * - *

For details on default display names consult the Javadoc for - * {@link org.junit.jupiter.api.TestInfo#getDisplayName()}. - * - *

Note that display names are typically used for test reporting in IDEs - * and build tools and may contain spaces, special characters, and even emoji. - * - * @return the display name of the test or container; never {@code null} or blank - */ - String getDisplayName(); - - /** - * Get the set of all tags for the current test or container. - * - *

Tags may be declared directly on the test element or inherited - * from an outer context. - * - * @return the set of tags for the test or container; never {@code null} but - * potentially empty - */ - Set getTags(); - - /** - * Get the {@link AnnotatedElement} corresponding to the current extension - * context, if available. - * - *

For example, if the current extension context encapsulates a test - * class, test method, test factory method, or test template method, the - * annotated element will be the corresponding {@link Class} or {@link Method} - * reference. - * - *

Favor this method over more specific methods whenever the - * {@code AnnotatedElement} API suits the task at hand — for example, - * when looking up annotations regardless of concrete element type. - * - * @return an {@code Optional} containing the {@code AnnotatedElement}; - * never {@code null} but potentially empty - * @see #getTestClass() - * @see #getTestMethod() - */ - Optional getElement(); - - /** - * Get the {@link Class} associated with the current test or container, - * if available. - * - * @return an {@code Optional} containing the class; never {@code null} but - * potentially empty - * @see #getRequiredTestClass() - */ - Optional> getTestClass(); - - /** - * Get the required {@link Class} associated with the current test - * or container. - * - *

Use this method as an alternative to {@link #getTestClass()} for use - * cases in which the test class is required to be present. - * - * @return the test class; never {@code null} - * @throws PreconditionViolationException if the test class is not present - * in this {@code ExtensionContext} - */ - default Class getRequiredTestClass() { - return Preconditions.notNull(getTestClass().orElse(null), - "Illegal state: required test class is not present in the current ExtensionContext"); - } - - /** - * Get the {@link Lifecycle} of the {@linkplain #getTestInstance() test - * instance} associated with the current test or container, if available. - * - * @return an {@code Optional} containing the test instance {@code Lifecycle}; - * never {@code null} but potentially empty - * @since 5.1 - * @see org.junit.jupiter.api.TestInstance {@code @TestInstance} - */ - @API(status = STABLE, since = "5.1") - Optional getTestInstanceLifecycle(); - - /** - * Get the test instance associated with the current test or container, - * if available. - * - * @return an {@code Optional} containing the test instance; never - * {@code null} but potentially empty - * @see #getRequiredTestInstance() - * @see #getTestInstances() - */ - Optional getTestInstance(); - - /** - * Get the required test instance associated with the current test - * or container. - * - *

Use this method as an alternative to {@link #getTestInstance()} for use - * cases in which the test instance is required to be present. - * - * @return the test instance; never {@code null} - * @throws PreconditionViolationException if the test instance is not present - * in this {@code ExtensionContext} - * - * @see #getRequiredTestInstances() - */ - default Object getRequiredTestInstance() { - return Preconditions.notNull(getTestInstance().orElse(null), - "Illegal state: required test instance is not present in the current ExtensionContext"); - } - - /** - * Get the test instances associated with the current test or container, - * if available. - * - *

While top-level tests only have a single test instance, nested tests - * have one additional instance for each enclosing test class. - * - * @return an {@code Optional} containing the test instances; never - * {@code null} but potentially empty - * @since 5.4 - * @see #getRequiredTestInstances() - */ - @API(status = STABLE, since = "5.7") - Optional getTestInstances(); - - /** - * Get the required test instances associated with the current test - * or container. - * - *

Use this method as an alternative to {@link #getTestInstances()} for use - * cases in which the test instances are required to be present. - * - * @return the test instances; never {@code null} - * @throws PreconditionViolationException if the test instances are not present - * in this {@code ExtensionContext} - * @since 5.4 - */ - @API(status = STABLE, since = "5.7") - default TestInstances getRequiredTestInstances() { - return Preconditions.notNull(getTestInstances().orElse(null), - "Illegal state: required test instances are not present in the current ExtensionContext"); - } - - /** - * Get the {@link Method} associated with the current test, if available. - * - * @return an {@code Optional} containing the method; never {@code null} but - * potentially empty - * @see #getRequiredTestMethod() - */ - Optional getTestMethod(); - - /** - * Get the required {@link Method} associated with the current test - * or container. - * - *

Use this method as an alternative to {@link #getTestMethod()} for use - * cases in which the test method is required to be present. - * - * @return the test method; never {@code null} - * @throws PreconditionViolationException if the test method is not present - * in this {@code ExtensionContext} - */ - default Method getRequiredTestMethod() { - return Preconditions.notNull(getTestMethod().orElse(null), - "Illegal state: required test method is not present in the current ExtensionContext"); - } - - /** - * Get the exception that was thrown during execution of the test or container - * associated with this {@code ExtensionContext}, if available. - * - *

This method is typically used for logging and tracing purposes. If you - * wish to actually handle an exception thrown during test execution, - * implement the {@link TestExecutionExceptionHandler} API. - * - *

Unlike the exception passed to a {@code TestExecutionExceptionHandler}, - * an execution exception returned by this method can be any - * exception thrown during the invocation of a {@code @Test} method, its - * surrounding {@code @BeforeEach} and {@code @AfterEach} methods, or a - * test-level {@link Extension}. Similarly, if this {@code ExtensionContext} - * represents a test class, the execution exception returned by - * this method can be any exception thrown in a {@code @BeforeAll} or - * {@code AfterAll} method or a class-level {@link Extension}. - * - *

Note, however, that this method will never return an exception - * swallowed by a {@code TestExecutionExceptionHandler}. Furthermore, if - * multiple exceptions have been thrown during test execution, the exception - * returned by this method will be the first such exception with all - * additional exceptions {@linkplain Throwable#addSuppressed(Throwable) - * suppressed} in the first one. - * - * @return an {@code Optional} containing the exception thrown; never - * {@code null} but potentially empty if test execution has not (yet) - * resulted in an exception - */ - Optional getExecutionException(); - - /** - * Get the configuration parameter stored under the specified {@code key}. - * - *

If no such key is present in the {@code ConfigurationParameters} for - * the JUnit Platform, an attempt will be made to look up the value as a - * JVM system property. If no such system property exists, an attempt will - * be made to look up the value in the JUnit Platform properties file. - * - * @param key the key to look up; never {@code null} or blank - * @return an {@code Optional} containing the value; never {@code null} - * but potentially empty - * - * @since 5.1 - * @see System#getProperty(String) - * @see org.junit.platform.engine.ConfigurationParameters - */ - @API(status = STABLE, since = "5.1") - Optional getConfigurationParameter(String key); - - /** - * Get and transform the configuration parameter stored under the specified - * {@code key} using the specified {@code transformer}. - * - *

If no such key is present in the {@code ConfigurationParameters} for - * the JUnit Platform, an attempt will be made to look up the value as a - * JVM system property. If no such system property exists, an attempt will - * be made to look up the value in the JUnit Platform properties file. - * - *

In case the transformer throws an exception, it will be wrapped in a - * {@link org.junit.platform.commons.JUnitException} with a helpful message. - * - * @param key the key to look up; never {@code null} or blank - * @param transformer the transformer to apply in case a value is found; - * never {@code null} - * @return an {@code Optional} containing the value; never {@code null} - * but potentially empty - * - * @since 5.7 - * @see System#getProperty(String) - * @see org.junit.platform.engine.ConfigurationParameters - */ - @API(status = EXPERIMENTAL, since = "5.7") - Optional getConfigurationParameter(String key, Function transformer); - - /** - * Publish a map of key-value pairs to be consumed by an - * {@code org.junit.platform.engine.EngineExecutionListener} in order to - * supply additional information to the reporting infrastructure. - * - * @param map the key-value pairs to be published; never {@code null}; - * keys and values within entries in the map also must not be - * {@code null} or blank - * @see #publishReportEntry(String, String) - * @see #publishReportEntry(String) - * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished - */ - void publishReportEntry(Map map); - - /** - * Publish the specified key-value pair to be consumed by an - * {@code org.junit.platform.engine.EngineExecutionListener} in order to - * supply additional information to the reporting infrastructure. - * - * @param key the key of the published pair; never {@code null} or blank - * @param value the value of the published pair; never {@code null} or blank - * @see #publishReportEntry(Map) - * @see #publishReportEntry(String) - * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished - */ - default void publishReportEntry(String key, String value) { - this.publishReportEntry(Collections.singletonMap(key, value)); - } - - /** - * Publish the specified value to be consumed by an - * {@code org.junit.platform.engine.EngineExecutionListener} in order to - * supply additional information to the reporting infrastructure. - * - *

This method delegates to {@link #publishReportEntry(String, String)}, - * supplying {@code "value"} as the key and the supplied {@code value} - * argument as the value. - * - * @param value the value to be published; never {@code null} or blank - * @since 5.3 - * @see #publishReportEntry(Map) - * @see #publishReportEntry(String, String) - * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished - */ - @API(status = STABLE, since = "5.3") - default void publishReportEntry(String value) { - this.publishReportEntry("value", value); - } - - /** - * Get the {@link Store} for the supplied {@link Namespace}. - * - *

Use {@code getStore(Namespace.GLOBAL)} to get the default, global {@link Namespace}. - * - *

A store is bound to its extension context lifecycle. When an extension - * context lifecycle ends it closes its associated store. All stored values - * that are instances of {@link ExtensionContext.Store.CloseableResource} are - * notified by invoking their {@code close()} methods. - * - * @param namespace the {@code Namespace} to get the store for; never {@code null} - * @return the store in which to put and get objects for other invocations - * working in the same namespace; never {@code null} - * @see Namespace#GLOBAL - */ - Store getStore(Namespace namespace); - - /** - * Get the {@link ExecutionMode} associated with the current test or container. - * - * @return the {@code ExecutionMode} of the test; never {@code null} - * - * @since 5.8.1 - * @see org.junit.jupiter.api.parallel.ExecutionMode {@code @ExecutionMode} - */ - @API(status = STABLE, since = "5.8.1") - ExecutionMode getExecutionMode(); - - /** - * Get an {@link ExecutableInvoker} to invoke methods and constructors - * with support for dynamic resolution of parameters. - * - * @since 5.9 - */ - @API(status = EXPERIMENTAL, since = "5.9") - ExecutableInvoker getExecutableInvoker(); - - /** - * {@code Store} provides methods for extensions to save and retrieve data. - */ - interface Store { - - /** - * Classes implementing this interface indicate that they want to {@link #close} - * some underlying resource or resources when the enclosing {@link Store Store} - * is closed. - * - *

Note that the {@code CloseableResource} API is only honored for - * objects stored within an extension context {@link Store Store}. - * - *

The resources stored in a {@link Store Store} are closed in the - * inverse order they were added in. - * - * @since 5.1 - */ - @API(status = STABLE, since = "5.1") - interface CloseableResource { - - /** - * Close underlying resources. - * - * @throws Throwable any throwable will be caught and rethrown - */ - void close() throws Throwable; - - } - - /** - * Get the value that is stored under the supplied {@code key}. - * - *

If no value is stored in the current {@link ExtensionContext} - * for the supplied {@code key}, ancestors of the context will be queried - * for a value with the same {@code key} in the {@code Namespace} used - * to create this store. - * - *

For greater type safety, consider using {@link #get(Object, Class)} - * instead. - * - * @param key the key; never {@code null} - * @return the value; potentially {@code null} - * @see #get(Object, Class) - * @see #getOrDefault(Object, Class, Object) - */ - Object get(Object key); - - /** - * Get the value of the specified required type that is stored under - * the supplied {@code key}. - * - *

If no value is stored in the current {@link ExtensionContext} - * for the supplied {@code key}, ancestors of the context will be queried - * for a value with the same {@code key} in the {@code Namespace} used - * to create this store. - * - * @param key the key; never {@code null} - * @param requiredType the required type of the value; never {@code null} - * @param the value type - * @return the value; potentially {@code null} - * @see #get(Object) - * @see #getOrDefault(Object, Class, Object) - */ - V get(Object key, Class requiredType); - - /** - * Get the value of the specified required type that is stored under - * the supplied {@code key}, or the supplied {@code defaultValue} if no - * value is found for the supplied {@code key} in this store or in an - * ancestor. - * - *

If no value is stored in the current {@link ExtensionContext} - * for the supplied {@code key}, ancestors of the context will be queried - * for a value with the same {@code key} in the {@code Namespace} used - * to create this store. - * - * @param key the key; never {@code null} - * @param requiredType the required type of the value; never {@code null} - * @param defaultValue the default value - * @param the value type - * @return the value; potentially {@code null} - * @since 5.5 - * @see #get(Object, Class) - */ - @API(status = STABLE, since = "5.5") - default V getOrDefault(Object key, Class requiredType, V defaultValue) { - V value = get(key, requiredType); - return (value != null ? value : defaultValue); - } - - /** - * Get the object of type {@code type} that is present in this - * {@code Store} (keyed by {@code type}); and otherwise invoke - * the default constructor for {@code type} to generate the object, - * store it, and return it. - * - *

This method is a shortcut for the following, where {@code X} is - * the type of object we wish to retrieve from the store. - * - *

-		 * X x = store.getOrComputeIfAbsent(X.class, key -> new X(), X.class);
-		 * // Equivalent to:
-		 * // X x = store.getOrComputeIfAbsent(X.class);
-		 * 
- * - *

See {@link #getOrComputeIfAbsent(Object, Function, Class)} for - * further details. - * - *

If {@code type} implements {@link ExtensionContext.Store.CloseableResource} - * the {@code close()} method will be invoked on the stored object when - * the store is closed. - * - * @param type the type of object to retrieve; never {@code null} - * @param the key and value type - * @return the object; never {@code null} - * @since 5.1 - * @see #getOrComputeIfAbsent(Object, Function) - * @see #getOrComputeIfAbsent(Object, Function, Class) - * @see CloseableResource - */ - @API(status = STABLE, since = "5.1") - default V getOrComputeIfAbsent(Class type) { - return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); - } - - /** - * Get the value that is stored under the supplied {@code key}. - * - *

If no value is stored in the current {@link ExtensionContext} - * for the supplied {@code key}, ancestors of the context will be queried - * for a value with the same {@code key} in the {@code Namespace} used - * to create this store. If no value is found for the supplied {@code key}, - * a new value will be computed by the {@code defaultCreator} (given - * the {@code key} as input), stored, and returned. - * - *

For greater type safety, consider using - * {@link #getOrComputeIfAbsent(Object, Function, Class)} instead. - * - *

If the created value is an instance of {@link ExtensionContext.Store.CloseableResource} - * the {@code close()} method will be invoked on the stored object when - * the store is closed. - * - * @param key the key; never {@code null} - * @param defaultCreator the function called with the supplied {@code key} - * to create a new value; never {@code null} - * @param the key type - * @param the value type - * @return the value; potentially {@code null} - * @see #getOrComputeIfAbsent(Class) - * @see #getOrComputeIfAbsent(Object, Function, Class) - * @see CloseableResource - */ - Object getOrComputeIfAbsent(K key, Function defaultCreator); - - /** - * Get the value of the specified required type that is stored under the - * supplied {@code key}. - * - *

If no value is stored in the current {@link ExtensionContext} - * for the supplied {@code key}, ancestors of the context will be queried - * for a value with the same {@code key} in the {@code Namespace} used - * to create this store. If no value is found for the supplied {@code key}, - * a new value will be computed by the {@code defaultCreator} (given - * the {@code key} as input), stored, and returned. - * - *

If {@code requiredType} implements {@link ExtensionContext.Store.CloseableResource} - * the {@code close()} method will be invoked on the stored object when - * the store is closed. - * - * @param key the key; never {@code null} - * @param defaultCreator the function called with the supplied {@code key} - * to create a new value; never {@code null} - * @param requiredType the required type of the value; never {@code null} - * @param the key type - * @param the value type - * @return the value; potentially {@code null} - * @see #getOrComputeIfAbsent(Class) - * @see #getOrComputeIfAbsent(Object, Function) - * @see CloseableResource - */ - V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); - - /** - * Store a {@code value} for later retrieval under the supplied {@code key}. - * - *

A stored {@code value} is visible in child {@link ExtensionContext - * ExtensionContexts} for the store's {@code Namespace} unless they - * overwrite it. - * - *

If the {@code value} is an instance of {@link ExtensionContext.Store.CloseableResource} - * the {@code close()} method will be invoked on the stored object when - * the store is closed. - * - * @param key the key under which the value should be stored; never - * {@code null} - * @param value the value to store; may be {@code null} - * @see CloseableResource - */ - void put(Object key, Object value); - - /** - * Remove the value that was previously stored under the supplied {@code key}. - * - *

The value will only be removed in the current {@link ExtensionContext}, - * not in ancestors. In addition, the {@link CloseableResource} API will not - * be honored for values that are manually removed via this method. - * - *

For greater type safety, consider using {@link #remove(Object, Class)} - * instead. - * - * @param key the key; never {@code null} - * @return the previous value or {@code null} if no value was present - * for the specified key - * @see #remove(Object, Class) - */ - Object remove(Object key); - - /** - * Remove the value of the specified required type that was previously stored - * under the supplied {@code key}. - * - *

The value will only be removed in the current {@link ExtensionContext}, - * not in ancestors. In addition, the {@link CloseableResource} API will not - * be honored for values that are manually removed via this method. - * - * @param key the key; never {@code null} - * @param requiredType the required type of the value; never {@code null} - * @param the value type - * @return the previous value or {@code null} if no value was present - * for the specified key - * @see #remove(Object) - */ - V remove(Object key, Class requiredType); - - } - - /** - * A {@code Namespace} is used to provide a scope for data saved by - * extensions within a {@link Store}. - * - *

Storing data in custom namespaces allows extensions to avoid accidentally - * mixing data between extensions or across different invocations within the - * lifecycle of a single extension. - */ - class Namespace { - - /** - * The default, global namespace which allows access to stored data from - * all extensions. - */ - public static final Namespace GLOBAL = Namespace.create(new Object()); - - /** - * Create a namespace which restricts access to data to all extensions - * which use the same sequence of {@code parts} for creating a namespace. - * - *

The order of the {@code parts} is significant. - * - *

Internally the {@code parts} are compared using {@link Object#equals(Object)}. - */ - public static Namespace create(Object... parts) { - Preconditions.notEmpty(parts, "parts array must not be null or empty"); - Preconditions.containsNoNullElements(parts, "individual parts must not be null"); - return new Namespace(new ArrayList<>(Arrays.asList(parts))); - } - - private final List parts; - - private Namespace(List parts) { - this.parts = parts; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Namespace that = (Namespace) o; - return this.parts.equals(that.parts); - } - - @Override - public int hashCode() { - return this.parts.hashCode(); - } - - /** - * Create a new namespace by appending the supplied {@code parts} to the - * existing sequence of parts in this namespace. - * - * @return new namespace; never {@code null} - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - public Namespace append(Object... parts) { - Preconditions.notEmpty(parts, "parts array must not be null or empty"); - Preconditions.containsNoNullElements(parts, "individual parts must not be null"); - ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); - newParts.addAll(this.parts); - Collections.addAll(newParts, parts); - return new Namespace(newParts); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java deleted file mode 100644 index 957c0ed6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.platform.commons.JUnitException; - -/** - * Thrown if an error is encountered regarding the use of an - * {@link ExtensionContext} or {@link Store}. - * - * @since 5.0 - */ -@API(status = STABLE, since = "5.0") -public class ExtensionContextException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public ExtensionContextException(String message) { - super(message); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java deleted file mode 100644 index a683dea9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @Extensions} is a container for one or more {@code @ExtendWith} - * declarations. - * - *

Note, however, that use of the {@code @Extensions} container is completely - * optional since {@code @ExtendWith} is a {@linkplain java.lang.annotation.Repeatable - * repeatable} annotation. - * - * @since 5.0 - * @see ExtendWith - * @see java.lang.annotation.Repeatable - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@API(status = STABLE, since = "5.0") -public @interface Extensions { - - /** - * An array of one or more {@link ExtendWith @ExtendWith} declarations. - */ - ExtendWith[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java deleted file mode 100644 index 5d4bbdb2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestTemplate; - -/** - * {@code InvocationInterceptor} defines the API for {@link Extension - * Extensions} that wish to intercept calls to test code. - * - *

Invocation Contract

- * - *

Each method in this class must call {@link Invocation#proceed()} or {@link - * Invocation#skip()} exactly once on the supplied invocation. Otherwise, the - * enclosing test or container will be reported as failed. - * - *

The default implementation calls {@link Invocation#proceed() - * proceed()} on the supplied {@linkplain Invocation invocation}. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - * @since 5.5 - * @see Invocation - * @see ReflectiveInvocationContext - * @see ExtensionContext - */ -@API(status = EXPERIMENTAL, since = "5.5") -public interface InvocationInterceptor extends Extension { - - /** - * Intercept the invocation of a test class constructor. - * - *

Note that the test class may not have been initialized - * (static initialization) when this method is invoked. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @param the result type - * @return the result of the invocation; never {@code null} - * @throws Throwable in case of failure - */ - default T interceptTestClassConstructor(Invocation invocation, - ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) - throws Throwable { - return invocation.proceed(); - } - - /** - * Intercept the invocation of a {@link BeforeAll @BeforeAll} method. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - */ - default void interceptBeforeAllMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); - } - - /** - * Intercept the invocation of a {@link BeforeEach @BeforeEach} method. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - */ - default void interceptBeforeEachMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); - } - - /** - * Intercept the invocation of a {@link Test @Test} method. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - */ - default void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); - } - - /** - * Intercept the invocation of a {@link TestFactory @TestFactory} method, - * such as a {@link org.junit.jupiter.api.RepeatedTest @RepeatedTest} or - * {@code @ParameterizedTest} method. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @param the result type - * @return the result of the invocation; potentially {@code null} - * @throws Throwable in case of failures - */ - default T interceptTestFactoryMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - return invocation.proceed(); - } - - /** - * Intercept the invocation of a {@link TestTemplate @TestTemplate} method. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - */ - default void interceptTestTemplateMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); - } - - /** - * Intercept the invocation of a {@link DynamicTest}. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - * @deprecated use {@link #interceptDynamicTest(Invocation, DynamicTestInvocationContext, ExtensionContext)} instead - */ - @Deprecated - @API(status = DEPRECATED, since = "5.8") - default void interceptDynamicTest(Invocation invocation, ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); - } - - /** - * Intercept the invocation of a {@link DynamicTest}. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - */ - @API(status = EXPERIMENTAL, since = "5.8") - default void interceptDynamicTest(Invocation invocation, DynamicTestInvocationContext invocationContext, - ExtensionContext extensionContext) throws Throwable { - // by default call the old interceptDynamicTest(Invocation, ExtensionContext) method so that existing extensions still work - interceptDynamicTest(invocation, extensionContext); - } - - /** - * Intercept the invocation of an {@link AfterEach @AfterEach} method. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - */ - default void interceptAfterEachMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); - } - - /** - * Intercept the invocation of an {@link AfterAll @AfterAll} method. - * - * @param invocation the invocation that is being intercepted; never - * {@code null} - * @param invocationContext the context of the invocation that is being - * intercepted; never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @throws Throwable in case of failures - */ - default void interceptAfterAllMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); - } - - /** - * An invocation that returns a result and may throw a {@link Throwable}. - * - *

This interface is not intended to be implemented by clients. - * - * @param the result type - * @since 5.5 - */ - @API(status = EXPERIMENTAL, since = "5.5") - interface Invocation { - - /** - * Proceed with this invocation. - * - * @return the result of this invocation; potentially {@code null}. - * @throws Throwable in case the invocation failed - */ - T proceed() throws Throwable; - - /** - * Explicitly skip this invocation. - * - *

This allows to bypass the check that {@link #proceed()} must be - * called at least once. The default implementation does nothing. - */ - @API(status = EXPERIMENTAL, since = "5.6") - default void skip() { - // do nothing - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java deleted file mode 100644 index 7b7eb210..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * {@code LifecycleMethodExecutionExceptionHandler} defines the API for - * {@link Extension Extensions} that wish to handle exceptions thrown during - * the execution of {@code @BeforeAll}, {@code @BeforeEach}, {@code @AfterEach}, - * and {@code @AfterAll} lifecycle methods. - * - *

Common use cases include swallowing an exception if it's anticipated, - * logging errors, or rolling back a transaction in certain error scenarios. - * - *

Implementations of this extension API must be registered at the class level - * if exceptions thrown from {@code @BeforeAll} or {@code @AfterAll} methods are - * to be handled. When registered at the test level, only exceptions thrown from - * {@code @BeforeEach} or {@code @AfterEach} methods will be handled. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on constructor - * requirements. - * - *

Implementation Guidelines

- * - *

An implementation of an exception handler method defined in this API must - * perform one of the following. - * - *

    - *
  1. Rethrow the supplied {@code Throwable} as is, which is the default implementation.
  2. - *
  3. Swallow the supplied {@code Throwable}, thereby preventing propagation.
  4. - *
  5. Throw a new exception, potentially wrapping the supplied {@code Throwable}.
  6. - *
- * - *

If the supplied {@code Throwable} is swallowed by a handler method, subsequent - * handler methods for the same lifecycle will not be invoked; otherwise, the - * corresponding handler method of the next registered - * {@code LifecycleMethodExecutionExceptionHandler} (if there is one) will be - * invoked with any {@link Throwable} thrown by the previous handler. - * - * @since 5.5 - * @see TestExecutionExceptionHandler - */ -@API(status = EXPERIMENTAL, since = "5.5") -public interface LifecycleMethodExecutionExceptionHandler extends Extension { - - /** - * Handle the supplied {@link Throwable} that was thrown during execution of - * a {@code @BeforeAll} lifecycle method. - * - *

Please refer to the class-level Javadoc for - * Implementation Guidelines. - * - * @param context the current extension context; never {@code null} - * @param throwable the {@code Throwable} to handle; never {@code null} - */ - default void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - - throw throwable; - } - - /** - * Handle the supplied {@link Throwable} that was thrown during execution of - * a {@code @BeforeEach} lifecycle method. - * - *

Please refer to the class-level Javadoc for - * Implementation Guidelines. - * - * @param context the current extension context; never {@code null} - * @param throwable the {@code Throwable} to handle; never {@code null} - */ - default void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - - throw throwable; - } - - /** - * Handle the supplied {@link Throwable} that was thrown during execution of - * a {@code @AfterEach} lifecycle method. - * - *

Please refer to the class-level Javadoc for - * Implementation Guidelines. - * - * @param context the current extension context; never {@code null} - * @param throwable the {@code Throwable} to handle; never {@code null} - */ - default void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - - throw throwable; - } - - /** - * Handle the supplied {@link Throwable} that was thrown during execution of - * a {@code @AfterAll} lifecycle method. - * - *

Please refer to the class-level Javadoc for - * Implementation Guidelines. - * - * @param context the current extension context; never {@code null} - * @param throwable the {@code Throwable} to handle; never {@code null} - */ - default void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - - throw throwable; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java deleted file mode 100644 index 1bce9ffb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Executable; -import java.lang.reflect.Parameter; -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code ParameterContext} encapsulates the context in which an - * {@link #getDeclaringExecutable Executable} will be invoked for a given - * {@link #getParameter Parameter}. - * - *

A {@code ParameterContext} is used to support parameter resolution via - * a {@link ParameterResolver}. - * - * @since 5.0 - * @see ParameterResolver - * @see java.lang.reflect.Parameter - * @see java.lang.reflect.Executable - * @see java.lang.reflect.Method - * @see java.lang.reflect.Constructor - */ -@API(status = STABLE, since = "5.0") -public interface ParameterContext { - - /** - * Get the {@link Parameter} for this context. - * - *

WARNING

- *

When searching for annotations on the parameter in this context, - * favor {@link #isAnnotated(Class)}, {@link #findAnnotation(Class)}, and - * {@link #findRepeatableAnnotations(Class)} over methods in the - * {@link Parameter} API due to a bug in {@code javac} on JDK versions prior - * to JDK 9. - * - * @return the parameter; never {@code null} - * @see #getIndex() - */ - Parameter getParameter(); - - /** - * Get the index of the {@link Parameter} for this context within the - * parameter list of the {@link #getDeclaringExecutable Executable} that - * declares the parameter. - * - * @return the index of the parameter - * @see #getParameter() - * @see Executable#getParameters() - */ - int getIndex(); - - /** - * Get the {@link Executable} (i.e., the {@link java.lang.reflect.Method} or - * {@link java.lang.reflect.Constructor}) that declares the {@code Parameter} - * for this context. - * - * @return the declaring {@code Executable}; never {@code null} - * @see Parameter#getDeclaringExecutable() - */ - default Executable getDeclaringExecutable() { - return getParameter().getDeclaringExecutable(); - } - - /** - * Get the target on which the {@link #getDeclaringExecutable Executable} - * that declares the {@link #getParameter Parameter} for this context will - * be invoked, if available. - * - * @return an {@link Optional} containing the target on which the - * {@code Executable} will be invoked; never {@code null} but will be - * empty if the {@code Executable} is a constructor or a - * {@code static} method. - */ - Optional getTarget(); - - /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the {@link Parameter} for - * this context. - * - *

WARNING

- *

Favor the use of this method over directly invoking - * {@link Parameter#isAnnotationPresent(Class)} due to a bug in {@code javac} - * on JDK versions prior to JDK 9. - * - * @param annotationType the annotation type to search for; never {@code null} - * @return {@code true} if the annotation is present or meta-present - * @since 5.1.1 - * @see #findAnnotation(Class) - * @see #findRepeatableAnnotations(Class) - */ - boolean isAnnotated(Class annotationType); - - /** - * Find the first annotation of {@code annotationType} that is either - * present or meta-present on the {@link Parameter} for - * this context. - * - *

WARNING

- *

Favor the use of this method over directly invoking annotation lookup - * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK - * versions prior to JDK 9. - * - * @param the annotation type - * @param annotationType the annotation type to search for; never {@code null} - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty - * @since 5.1.1 - * @see #isAnnotated(Class) - * @see #findRepeatableAnnotations(Class) - */ - Optional findAnnotation(Class annotationType); - - /** - * Find all repeatable {@linkplain Annotation annotations} of - * {@code annotationType} that are either present or - * meta-present on the {@link Parameter} for this context. - * - *

WARNING

- *

Favor the use of this method over directly invoking annotation lookup - * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK - * versions prior to JDK 9. - * - * @param the annotation type - * @param annotationType the repeatable annotation type to search for; never - * {@code null} - * @return the list of all such annotations found; neither {@code null} nor - * mutable, but potentially empty - * @since 5.1.1 - * @see #isAnnotated(Class) - * @see #findAnnotation(Class) - * @see java.lang.annotation.Repeatable - */ - List findRepeatableAnnotations(Class annotationType); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java deleted file mode 100644 index 5e34e823..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * Thrown if an error is encountered in the configuration or execution of a - * {@link ParameterResolver}. - * - * @since 5.0 - * @see ParameterResolver - */ -@API(status = STABLE, since = "5.0") -public class ParameterResolutionException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public ParameterResolutionException(String message) { - super(message); - } - - public ParameterResolutionException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java deleted file mode 100644 index 3ce4fdb4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.reflect.Parameter; - -import org.apiguardian.api.API; - -/** - * {@code ParameterResolver} defines the API for {@link Extension Extensions} - * that wish to dynamically resolve arguments for {@linkplain Parameter parameters} - * at runtime. - * - *

If a constructor for a test class or a - * {@link org.junit.jupiter.api.Test @Test}, - * {@link org.junit.jupiter.api.BeforeEach @BeforeEach}, - * {@link org.junit.jupiter.api.AfterEach @AfterEach}, - * {@link org.junit.jupiter.api.BeforeAll @BeforeAll}, or - * {@link org.junit.jupiter.api.AfterAll @AfterAll} method declares a parameter, - * an argument for the parameter must be resolved at runtime by a - * {@code ParameterResolver}. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - * @since 5.0 - * @see #supportsParameter(ParameterContext, ExtensionContext) - * @see #resolveParameter(ParameterContext, ExtensionContext) - * @see ParameterContext - * @see TestInstanceFactory - * @see TestInstancePostProcessor - * @see TestInstancePreDestroyCallback - */ -@API(status = STABLE, since = "5.0") -public interface ParameterResolver extends Extension { - - /** - * Determine if this resolver supports resolution of an argument for the - * {@link Parameter} in the supplied {@link ParameterContext} for the supplied - * {@link ExtensionContext}. - * - *

The {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} - * in which the parameter is declared can be retrieved via - * {@link ParameterContext#getDeclaringExecutable()}. - * - * @param parameterContext the context for the parameter for which an argument should - * be resolved; never {@code null} - * @param extensionContext the extension context for the {@code Executable} - * about to be invoked; never {@code null} - * @return {@code true} if this resolver can resolve an argument for the parameter - * @see #resolveParameter - * @see ParameterContext - */ - boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException; - - /** - * Resolve an argument for the {@link Parameter} in the supplied {@link ParameterContext} - * for the supplied {@link ExtensionContext}. - * - *

This method is only called by the framework if {@link #supportsParameter} - * previously returned {@code true} for the same {@link ParameterContext} - * and {@link ExtensionContext}. - * - *

The {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} - * in which the parameter is declared can be retrieved via - * {@link ParameterContext#getDeclaringExecutable()}. - * - * @param parameterContext the context for the parameter for which an argument should - * be resolved; never {@code null} - * @param extensionContext the extension context for the {@code Executable} - * about to be invoked; never {@code null} - * @return the resolved argument for the parameter; may only be {@code null} if the - * parameter type is not a primitive - * @see #supportsParameter - * @see ParameterContext - */ - Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java deleted file mode 100644 index 90666658..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.reflect.Executable; -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code ReflectiveInvocationContext} encapsulates the context of - * a reflective invocation of an executable (method or constructor). - * - *

This interface is not intended to be implemented by clients. - * - * @since 5.5 - */ -@API(status = EXPERIMENTAL, since = "5.5") -public interface ReflectiveInvocationContext { - - /** - * Get the target class of this invocation context. - * - *

If this invocation context represents an instance method, this - * method returns the class of the object the method will be invoked on, - * not the class it is declared in. Otherwise, if this invocation - * represents a static method or constructor, this method returns the - * class the method or constructor is declared in. - * - * @return the target class of this invocation context; never - * {@code null} - */ - Class getTargetClass(); - - /** - * Get the method or constructor of this invocation context. - * - * @return the executable of this invocation context; never {@code null} - */ - T getExecutable(); - - /** - * Get the arguments of the executable in this invocation context. - * - * @return the arguments of the executable in this invocation context; - * immutable and never {@code null} - */ - List getArguments(); - - /** - * Get the target object of this invocation context, if available. - * - *

If this invocation context represents an instance method, this - * method returns the object the method will be invoked on. Otherwise, - * if this invocation context represents a static method or - * constructor, this method returns {@link Optional#empty() empty()}. - * - * @return the target of the executable of this invocation context; never - * {@code null} but potentially empty - */ - Optional getTarget(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java deleted file mode 100644 index c3f89728..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @RegisterExtension} is used to register an {@link Extension} via a - * field in a test class. - * - *

In contrast to {@link ExtendWith @ExtendWith} which is used to register - * extensions declaratively, {@code @RegisterExtension} can be used to - * register an extension programmatically — for example, in order - * to pass arguments to the extension's constructor, {@code static} factory - * method, or builder API. - * - *

{@code @RegisterExtension} fields must not be {@code null} (when evaluated) - * but may be either {@code static} or non-static. - * - *

Static Fields

- * - *

If a {@code @RegisterExtension} field is {@code static}, the extension - * will be registered after extensions that are registered at the class level - * via {@code @ExtendWith}. Such static extensions are not limited in - * which extension APIs they can implement. Extensions registered via static - * fields may therefore implement class-level and instance-level extension APIs - * such as {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link TestInstanceFactory}, {@link TestInstancePostProcessor} and - * {@link TestInstancePreDestroyCallback} as well as method-level extension APIs - * such as {@link BeforeEachCallback}, etc. - * - *

Instance Fields

- * - *

If a {@code @RegisterExtension} field is non-static (i.e., an instance - * field), the extension will be registered after the test class has been - * instantiated and after all {@link TestInstancePostProcessor - * TestInstancePostProcessors} have been given a chance to post-process the - * test instance (potentially injecting the instance of the extension to be - * used into the annotated field). Thus, if such an instance extension - * implements class-level or instance-level extension APIs such as - * {@link BeforeAllCallback}, {@link AfterAllCallback}, - * {@link TestInstanceFactory}, or {@link TestInstancePostProcessor} those APIs - * will not be honored. By default, an instance extension will be registered - * after extensions that are registered at the method level via - * {@code @ExtendWith}; however, if the test class is configured with - * {@link org.junit.jupiter.api.TestInstance.Lifecycle @TestInstance(Lifecycle.PER_CLASS)} - * semantics, an instance extension will be registered before extensions - * that are registered at the method level via {@code @ExtendWith}. - * - *

Inheritance

- * - *

{@code @RegisterExtension} fields are inherited from superclasses as long - * as they are not hidden or overridden. Furthermore, - * {@code @RegisterExtension} fields from superclasses will be registered before - * {@code @RegisterExtension} fields in subclasses. - * - *

Registration Order

- * - *

By default, if multiple extensions are registered via - * {@code @RegisterExtension}, they will be ordered using an algorithm that is - * deterministic but intentionally nonobvious. This ensures that subsequent runs - * of a test suite execute extensions in the same order, thereby allowing for - * repeatable builds. However, there are times when extensions need to be - * registered in an explicit order. To achieve that, you can annotate - * {@code @RegisterExtension} fields with {@link org.junit.jupiter.api.Order @Order}. - * Any {@code @RegisterExtension} field not annotated with {@code @Order} will be - * ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order - * value. Note that {@code @ExtendWith} fields can also be ordered with - * {@code @Order}, relative to {@code @RegisterExtension} fields and other - * {@code @ExtendWith} fields. - * - *

Example Usage

- * - *

In the following example, the {@code docs} field in the test class is - * initialized programmatically by supplying a custom {@code lookUpDocsDir()} - * method to a {@code static} factory method in the {@code DocumentationExtension}. - * The configured {@code DocumentationExtension} will be automatically registered - * as an extension. In addition, test methods can access the instance of the - * extension via the {@code docs} field if necessary. - * - *

- * class DocumentationTests {
- *
- *     static Path lookUpDocsDir() {
- *         // return path to docs dir
- *     }
- *
- *     {@literal @}RegisterExtension
- *     DocumentationExtension docs =
- *         DocumentationExtension.forPath(lookUpDocsDir());
- *
- *     {@literal @}Test
- *     void generateDocumentation() {
- *         // use docs ...
- *     }
- * }
- * - *

Supported Extension APIs

- * - *
    - *
  • {@link ExecutionCondition}
  • - *
  • {@link InvocationInterceptor}
  • - *
  • {@link BeforeAllCallback}
  • - *
  • {@link AfterAllCallback}
  • - *
  • {@link BeforeEachCallback}
  • - *
  • {@link AfterEachCallback}
  • - *
  • {@link BeforeTestExecutionCallback}
  • - *
  • {@link AfterTestExecutionCallback}
  • - *
  • {@link TestInstanceFactory}
  • - *
  • {@link TestInstancePostProcessor}
  • - *
  • {@link TestInstancePreDestroyCallback}
  • - *
  • {@link ParameterResolver}
  • - *
  • {@link TestExecutionExceptionHandler}
  • - *
  • {@link TestTemplateInvocationContextProvider}
  • - *
  • {@link TestWatcher}
  • - *
- * - * @since 5.1 - * @see ExtendWith @ExtendWith - * @see Extension - * @see org.junit.jupiter.api.Order @Order - */ -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.1") -public @interface RegisterExtension { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java deleted file mode 100644 index cfd03f74..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code TestExecutionExceptionHandler} defines the API for {@link Extension - * Extensions} that wish to handle exceptions thrown during test execution. - * - *

In this context, test execution refers to the physical - * invocation of a {@code @Test} method and not to any test-level extensions - * or callbacks. - * - *

Common use cases include swallowing an exception if it's anticipated - * or rolling back a transaction in certain error scenarios. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - * @since 5.0 - * @see LifecycleMethodExecutionExceptionHandler - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface TestExecutionExceptionHandler extends Extension { - - /** - * Handle the supplied {@link Throwable throwable}. - * - *

Implementors must perform one of the following. - *

    - *
  1. Swallow the supplied {@code throwable}, thereby preventing propagation.
  2. - *
  3. Rethrow the supplied {@code throwable} as is.
  4. - *
  5. Throw a new exception, potentially wrapping the supplied {@code throwable}.
  6. - *
- * - *

If the supplied {@code throwable} is swallowed, subsequent - * {@code TestExecutionExceptionHandlers} will not be invoked; otherwise, - * the next registered {@code TestExecutionExceptionHandler} (if there is - * one) will be invoked with any {@link Throwable} thrown by this handler. - * - *

Note that the {@link ExtensionContext#getExecutionException() execution - * exception} in the supplied {@code ExtensionContext} will not - * contain the {@code Throwable} thrown during invocation of the corresponding - * {@code @Test} method. - * - * @param context the current extension context; never {@code null} - * @param throwable the {@code Throwable} to handle; never {@code null} - */ - void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java deleted file mode 100644 index beb98895..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code TestInstanceFactory} defines the API for {@link Extension - * Extensions} that wish to {@linkplain #createTestInstance create} test instances. - * - *

Common use cases include creating test instances with special construction - * requirements or acquiring the test instance from a dependency injection - * framework. - * - *

Extensions that implement {@code TestInstanceFactory} must be registered - * at the class level. - * - *

Warning

- * - *

Only one {@code TestInstanceFactory} is allowed to be registered for any - * given test class. Registering multiple factories for any single test class - * will result in an exception being thrown for all tests in that class, in any - * subclass, and in any nested class. Note that any {@code TestInstanceFactory} - * registered in a {@linkplain Class#getSuperclass() superclass} or - * {@linkplain Class#getEnclosingClass() enclosing} class (i.e., in the case of - * a {@code @Nested} test class) is inherited. It is therefore the - * user's responsibility to ensure that only a single {@code TestInstanceFactory} - * is registered for any specific test class. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - * @since 5.3 - * @see #createTestInstance(TestInstanceFactoryContext, ExtensionContext) - * @see TestInstanceFactoryContext - * @see TestInstancePostProcessor - * @see TestInstancePreDestroyCallback - * @see ParameterResolver - */ -@FunctionalInterface -@API(status = STABLE, since = "5.7") -public interface TestInstanceFactory extends Extension { - - /** - * Callback for creating a test instance for the supplied context. - * - *

Note: the {@code ExtensionContext} supplied to a - * {@code TestInstanceFactory} will always return an empty - * {@link java.util.Optional} value from - * {@link ExtensionContext#getTestInstance() getTestInstance()} since the - * test instance cannot exist before it has been created by a - * {@code TestInstanceFactory} or the framework itself. - * - * @param factoryContext the context for the test class to be instantiated; - * never {@code null} - * @param extensionContext the current extension context; never {@code null} - * @return the test instance; never {@code null} - * @throws TestInstantiationException if an error occurs while creating the - * test instance - */ - Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) - throws TestInstantiationException; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java deleted file mode 100644 index 99af882d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code TestInstanceFactoryContext} encapsulates the context in which - * a {@linkplain #getTestClass test class} is to be instantiated by a - * {@link TestInstanceFactory}. - * - * @since 5.3 - * @see TestInstanceFactory - */ -@API(status = STABLE, since = "5.7") -public interface TestInstanceFactoryContext { - - /** - * Get the test class for this context. - * - * @return the test class to be instantiated; never {@code null} - */ - Class getTestClass(); - - /** - * Get the instance of the outer class, if available. - * - *

The returned {@link Optional} will be empty unless the - * current {@linkplain #getTestClass() test class} is a - * {@link org.junit.jupiter.api.Nested @Nested} test class. - * - * @return an {@code Optional} containing the outer test instance; never - * {@code null} but potentially empty - * @see org.junit.jupiter.api.Nested - */ - Optional getOuterInstance(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java deleted file mode 100644 index a2ec718e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code TestInstancePostProcessor} defines the API for {@link Extension - * Extensions} that wish to post-process test instances. - * - *

Common use cases include injecting dependencies into the test - * instance, invoking custom initialization methods on the test instance, - * etc. - * - *

Extensions that implement {@code TestInstancePostProcessor} must be - * registered at the class level. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - * @since 5.0 - * @see #postProcessTestInstance(Object, ExtensionContext) - * @see TestInstancePreDestroyCallback - * @see TestInstanceFactory - * @see ParameterResolver - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface TestInstancePostProcessor extends Extension { - - /** - * Callback for post-processing the supplied test instance. - * - *

Note: the {@code ExtensionContext} supplied to a - * {@code TestInstancePostProcessor} will always return an empty - * {@link java.util.Optional} value from {@link ExtensionContext#getTestInstance() - * getTestInstance()}. A {@code TestInstancePostProcessor} should therefore - * only attempt to process the supplied {@code testInstance}. - * - * @param testInstance the instance to post-process; never {@code null} - * @param context the current extension context; never {@code null} - */ - void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java deleted file mode 100644 index 0bad10b8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestInstance.Lifecycle; - -/** - * {@code TestInstancePreConstructCallback} defines the API for {@link Extension - * Extensions} that wish to be invoked prior to creation of test instances. - * - *

This extension is a symmetric counterpart to {@link TestInstancePreDestroyCallback}. - * The use cases for this extension may include preparing context-sensitive arguments - * that are injected into the instance's constructor. - * - *

Extensions that implement {@code TestInstancePreConstructCallback} must be - * registered at the class level if the test class is configured with - * {@link Lifecycle @TestInstance(Lifecycle.PER_CLASS)} semantics. If the test - * class is configured with - * {@link Lifecycle @TestInstance(Lifecycle.PER_METHOD)} semantics, - * {@code TestInstancePreConstructCallback} extensions may be registered at the - * class level or at the method level. In the latter case, the extension will - * only be applied to the test method for which it is registered. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on constructor - * requirements. - * - * @since 5.9 - * @see TestInstancePreDestroyCallback - * @see TestInstanceFactory - * @see ParameterResolver - */ -@FunctionalInterface -@API(status = EXPERIMENTAL, since = "5.9") -public interface TestInstancePreConstructCallback extends Extension { - - /** - * Callback invoked prior to test instances being constructed. - * - * @param factoryContext the context for the test instance about to be instantiated; - * never {@code null} - * @param context the current extension context; never {@code null} - */ - void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java deleted file mode 100644 index 1feab185..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.TestInstance.Lifecycle; - -/** - * {@code TestInstancePreDestroyCallback} defines the API for {@link Extension - * Extensions} that wish to process test instances after they have been - * used in tests but before they are destroyed. - * - *

Common use cases include releasing resources that have been created for - * the test instance, invoking custom clean-up methods on the test instance, etc. - * - *

Extensions that implement {@code TestInstancePreDestroyCallback} must be - * registered at the class level if the test class is configured with - * {@link Lifecycle @TestInstance(Lifecycle.PER_CLASS)} - * semantics. If the test class is configured with - * {@link Lifecycle @TestInstance(Lifecycle.PER_METHOD)} - * semantics, {@code TestInstancePreDestroyCallback} extensions may be registered - * at the class level or at the method level. In the latter case, the - * {@code TestInstancePreDestroyCallback} extension will only be applied to the - * test method for which it is registered. - * - *

A symmetric {@link TestInstancePreConstructCallback} extension defines a callback - * hook that is invoked prior to any test class instances being constructed. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on constructor - * requirements. - * - * @since 5.6 - * @see #preDestroyTestInstance(ExtensionContext) - * @see TestInstancePostProcessor - * @see TestInstancePreConstructCallback - * @see TestInstanceFactory - * @see ParameterResolver - */ -@FunctionalInterface -@API(status = STABLE, since = "5.7") -public interface TestInstancePreDestroyCallback extends Extension { - - /** - * Callback for processing test instances before they are destroyed. - * - *

Contrary to {@link TestInstancePostProcessor#postProcessTestInstance} - * this method is only called once for each {@link ExtensionContext} even if - * there are multiple test instances about to be destroyed in case of - * {@link Nested @Nested} tests. Please use the provided - * {@link #preDestroyTestInstances(ExtensionContext, Consumer)} utility - * method to ensure that all test instances are handled. - * - * @param context the current extension context; never {@code null} - * @see ExtensionContext#getTestInstance() - * @see ExtensionContext#getRequiredTestInstance() - * @see ExtensionContext#getTestInstances() - * @see ExtensionContext#getRequiredTestInstances() - * @see #preDestroyTestInstances(ExtensionContext, Consumer) - */ - void preDestroyTestInstance(ExtensionContext context) throws Exception; - - /** - * Utility method for processing all test instances of an - * {@link ExtensionContext} that are not present in any of its parent - * contexts. - * - *

This method should be called in order to implement this interface - * correctly since it ensures that the right test instances are processed - * regardless of the used {@linkplain Lifecycle lifecycle}. The supplied - * callback is called once per test instance that is about to be destroyed - * starting with the innermost one. - * - *

This method is intended to be called from an implementation of - * {@link #preDestroyTestInstance(ExtensionContext)} like this: - * - *

{@code class MyExtension implements TestInstancePreDestroyCallback {
-	 *    @Override
-	 *    public void preDestroyTestInstance(ExtensionContext context) {
-	 *        TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> {
-	 *            // custom logic that processes testInstance
-	 *        });
-	 *    }
-	 *}}
- * - * @param context the current extension context; never {@code null} - * @param callback the callback to be invoked for every test instance of the - * current extension context that is about to be destroyed; never - * {@code null} - * @since 5.7.1 - */ - @API(status = EXPERIMENTAL, since = "5.7.1") - static void preDestroyTestInstances(ExtensionContext context, Consumer callback) { - List destroyedInstances = new ArrayList<>(context.getRequiredTestInstances().getAllInstances()); - for (Optional current = context.getParent(); current.isPresent(); current = current.get().getParent()) { - current.get().getTestInstances().map(TestInstances::getAllInstances).ifPresent( - destroyedInstances::removeAll); - } - Collections.reverse(destroyedInstances); - destroyedInstances.forEach(callback); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java deleted file mode 100644 index d2bab806..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code TestInstances} encapsulates the test instances of a test. - * - *

While top-level tests only have a single test instance, nested tests - * have one additional instance for each enclosing test class. - * - * @since 5.4 - * @see ExtensionContext#getTestInstances() - * @see ExtensionContext#getRequiredTestInstances() - */ -@API(status = STABLE, since = "5.7") -public interface TestInstances { - - /** - * Get the innermost test instance. - * - *

The innermost instance is the one closest to the test method. - * - * @return the innermost test instance; never {@code null} - */ - Object getInnermostInstance(); - - /** - * Get the enclosing test instances, excluding the innermost test instance, - * ordered from outermost to innermost. - * - * @return the enclosing test instances; never {@code null} or containing - * {@code null}, but potentially empty - */ - List getEnclosingInstances(); - - /** - * Get all test instances, ordered from outermost to innermost. - * - * @return all test instances; never {@code null}, containing {@code null}, - * or empty - */ - List getAllInstances(); - - /** - * Find the first test instance that is an instance of the supplied required - * type, checking from innermost to outermost. - * - * @param requiredType the type to search for - * @return the first test instance of the required type; never {@code null} - * but potentially empty - */ - Optional findInstance(Class requiredType); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java deleted file mode 100644 index e8c5920b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * Thrown if an error is encountered during the execution of - * a {@link TestInstanceFactory}. - * - * @since 5.3 - */ -@API(status = EXPERIMENTAL, since = "5.3") -public class TestInstantiationException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public TestInstantiationException(String message) { - super(message); - } - - public TestInstantiationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java deleted file mode 100644 index 268b5c47..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static java.util.Collections.emptyList; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; - -import org.apiguardian.api.API; - -/** - * {@code TestTemplateInvocationContext} represents the context of a - * single invocation of a {@linkplain org.junit.jupiter.api.TestTemplate test - * template}. - * - *

Each context is provided by a {@link TestTemplateInvocationContextProvider}. - * - * @since 5.0 - * @see org.junit.jupiter.api.TestTemplate - * @see TestTemplateInvocationContextProvider - */ -@API(status = STABLE, since = "5.0") -public interface TestTemplateInvocationContext { - - /** - * Get the display name for this invocation. - * - *

The supplied {@code invocationIndex} is incremented by the framework - * with each test invocation. Thus, in the case of multiple active - * {@linkplain TestTemplateInvocationContextProvider providers}, only the - * first active provider receives indices starting with {@code 1}. - * - *

The default implementation returns the supplied {@code invocationIndex} - * wrapped in brackets — for example, {@code [1]}, {@code [42]}, etc. - * - * @param invocationIndex the index of this invocation (1-based). - * @return the display name for this invocation; never {@code null} or blank - */ - default String getDisplayName(int invocationIndex) { - return "[" + invocationIndex + "]"; - } - - /** - * Get the additional {@linkplain Extension extensions} for this invocation. - * - *

The extensions provided by this method will only be used for this - * invocation of the test template. Thus, it does not make sense to return - * an extension that acts solely on the container level (e.g. - * {@link BeforeAllCallback}). - * - *

The default implementation returns an empty list. - * - * @return the additional extensions for this invocation; never {@code null} - * or containing {@code null} elements, but potentially empty - */ - default List getAdditionalExtensions() { - return emptyList(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java deleted file mode 100644 index f7e0d61b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.stream.Stream; - -import org.apiguardian.api.API; - -/** - * {@code TestTemplateInvocationContextProvider} defines the API for - * {@link Extension Extensions} that wish to provide one or multiple contexts - * for the invocation of a - * {@link org.junit.jupiter.api.TestTemplate @TestTemplate} method. - * - *

This extension point makes it possible to execute a test template in - * different contexts — for example, with different parameters, by - * preparing the test class instance differently, or multiple times without - * modifying the context. - * - *

This interface defines two methods: {@link #supportsTestTemplate} and - * {@link #provideTestTemplateInvocationContexts}. The former is called by the - * framework to determine whether this extension wants to act on a test template - * that is about to be executed. If so, the latter is called and must return a - * {@link Stream} of {@link TestTemplateInvocationContext} instances. Otherwise, - * this provider is ignored for the execution of the current test template. - * - *

A provider that has returned {@code true} from its {@link #supportsTestTemplate} - * method is called active. When multiple providers are active for a - * test template method, the {@code Streams} returned by their - * {@link #provideTestTemplateInvocationContexts} methods will be chained, and - * the test template method will be invoked using the contexts of all active - * providers. - * - *

Constructor Requirements

- * - *

Consult the documentation in {@link Extension} for details on - * constructor requirements. - * - * @since 5.0 - * @see org.junit.jupiter.api.TestTemplate - * @see TestTemplateInvocationContext - */ -@API(status = STABLE, since = "5.0") -public interface TestTemplateInvocationContextProvider extends Extension { - - /** - * Determine if this provider supports providing invocation contexts for the - * test template method represented by the supplied {@code context}. - * - * @param context the extension context for the test template method about - * to be invoked; never {@code null} - * @return {@code true} if this provider can provide invocation contexts - * @see #provideTestTemplateInvocationContexts - * @see ExtensionContext - */ - boolean supportsTestTemplate(ExtensionContext context); - - /** - * Provide {@linkplain TestTemplateInvocationContext invocation contexts} - * for the test template method represented by the supplied {@code context}. - * - *

This method is only called by the framework if {@link #supportsTestTemplate} - * previously returned {@code true} for the same {@link ExtensionContext}; - * this method is allowed to return an empty {@code Stream} but not {@code null}. - * - *

The returned {@code Stream} will be properly closed by calling - * {@link Stream#close()}, making it safe to use a resource such as - * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. - * - * @param context the extension context for the test template method about - * to be invoked; never {@code null} - * @return a {@code Stream} of {@code TestTemplateInvocationContext} - * instances for the invocation of the test template method; never {@code null} - * @see #supportsTestTemplate - * @see ExtensionContext - */ - Stream provideTestTemplateInvocationContexts(ExtensionContext context); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java deleted file mode 100644 index a720af0e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * {@code TestWatcher} defines the API for {@link Extension Extensions} that - * wish to process test results. - * - *

The methods in this API are called after a test has been skipped or - * executed. Any {@link ExtensionContext.Store.CloseableResource CloseableResource} - * objects stored in the {@link ExtensionContext.Store Store} of the supplied - * {@link ExtensionContext} will have already been closed before - * methods in this API are invoked. - * - *

Please note that this API is currently only used to report the results of - * {@link org.junit.jupiter.api.Test @Test} methods and - * {@link org.junit.jupiter.api.TestTemplate @TestTemplate} methods (e.g., - * {@code @RepeatedTest} and {@code @ParameterizedTest}). Moreover, if there is a - * failure at the class level — for example, an exception thrown by a - * {@code @BeforeAll} method — no test results will be reported. - * - *

Extensions implementing this API can be registered at any level. - * - *

Exception Handling

- * - *

In contrast to other {@link Extension} APIs, a {@code TestWatcher} is not - * permitted to adversely influence the execution of tests. Consequently, any - * exception thrown by a method in the {@code TestWatcher} API will be logged at - * {@code WARNING} level and will not be allowed to propagate or fail test - * execution. - * - * @since 5.4 - */ -@API(status = STABLE, since = "5.7") -public interface TestWatcher extends Extension { - - /** - * Invoked after a disabled test has been skipped. - * - *

The default implementation does nothing. Concrete implementations can - * override this method as appropriate. - * - * @param context the current extension context; never {@code null} - * @param reason the reason the test is disabled; never {@code null} but - * potentially empty - */ - default void testDisabled(ExtensionContext context, Optional reason) { - /* no-op */ - } - - /** - * Invoked after a test has completed successfully. - * - *

The default implementation does nothing. Concrete implementations can - * override this method as appropriate. - * - * @param context the current extension context; never {@code null} - */ - default void testSuccessful(ExtensionContext context) { - /* no-op */ - } - - /** - * Invoked after a test has been aborted. - * - *

The default implementation does nothing. Concrete implementations can - * override this method as appropriate. - * - * @param context the current extension context; never {@code null} - * @param cause the throwable responsible for the test being aborted; may be {@code null} - */ - default void testAborted(ExtensionContext context, Throwable cause) { - /* no-op */ - } - - /** - * Invoked after a test has failed. - * - *

The default implementation does nothing. Concrete implementations can - * override this method as appropriate. - * - * @param context the current extension context; never {@code null} - * @param cause the throwable that caused test failure; may be {@code null} - */ - default void testFailed(ExtensionContext context, Throwable cause) { - /* no-op */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java deleted file mode 100644 index 68f070a2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * JUnit Jupiter API for writing extensions. - */ - -package org.junit.jupiter.api.extension; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java deleted file mode 100644 index 0346fc0b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension.support; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@link ParameterResolver} adapter which resolves a parameter based on its exact type. - * - * @param the type of the parameter supported by this {@code ParameterResolver} - * @since 5.6 - */ -@API(status = EXPERIMENTAL, since = "5.6") -public abstract class TypeBasedParameterResolver implements ParameterResolver { - - private final Type supportedParameterType; - - public TypeBasedParameterResolver() { - this.supportedParameterType = enclosedTypeOfParameterResolver(); - } - - @Override - public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return this.supportedParameterType.equals(getParameterType(parameterContext)); - } - - @Override - public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException; - - private Type getParameterType(ParameterContext parameterContext) { - return parameterContext.getParameter().getParameterizedType(); - } - - private Type enclosedTypeOfParameterResolver() { - ParameterizedType typeBasedParameterResolverSuperclass = findTypeBasedParameterResolverSuperclass(getClass()); - Preconditions.notNull(typeBasedParameterResolverSuperclass, - () -> String.format( - "Failed to discover parameter type supported by %s; " - + "potentially caused by lacking parameterized type in class declaration.", - getClass().getName())); - return typeBasedParameterResolverSuperclass.getActualTypeArguments()[0]; - } - - private ParameterizedType findTypeBasedParameterResolverSuperclass(Class clazz) { - Class superclass = clazz.getSuperclass(); - - // Abort? - if (superclass == null || superclass == Object.class) { - return null; - } - - Type genericSuperclass = clazz.getGenericSuperclass(); - if (genericSuperclass instanceof ParameterizedType) { - Type rawType = ((ParameterizedType) genericSuperclass).getRawType(); - if (rawType == TypeBasedParameterResolver.class) { - return (ParameterizedType) genericSuperclass; - } - } - return findTypeBasedParameterResolverSuperclass(superclass); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java deleted file mode 100644 index 3d4156a3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * JUnit Jupiter API support for writing extensions. - */ - -package org.junit.jupiter.api.extension.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java deleted file mode 100644 index c6309581..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.function; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code Executable} is a functional interface that can be used to - * implement any generic block of code that potentially throws a - * {@link Throwable}. - * - *

The {@code Executable} interface is similar to {@link java.lang.Runnable}, - * except that an {@code Executable} can throw any kind of exception. - * - *

Rationale for throwing {@code Throwable} instead of {@code Exception}

- * - *

Although Java applications typically throw exceptions that are instances - * of {@link java.lang.Exception}, {@link java.lang.RuntimeException}, - * {@link java.lang.Error}, or {@link java.lang.AssertionError} (in testing - * scenarios), there may be use cases where an {@code Executable} needs to - * explicitly throw a {@code Throwable}. In order to support such specialized - * use cases, {@link #execute()} is declared to throw {@code Throwable}. - * - * @since 5.0 - * @see org.junit.jupiter.api.Assertions#assertAll(Executable...) - * @see org.junit.jupiter.api.Assertions#assertAll(String, Executable...) - * @see org.junit.jupiter.api.Assertions#assertThrows(Class, Executable) - * @see org.junit.jupiter.api.Assumptions#assumingThat(java.util.function.BooleanSupplier, Executable) - * @see org.junit.jupiter.api.DynamicTest#dynamicTest(String, Executable) - * @see ThrowingConsumer - * @see ThrowingSupplier - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface Executable { - - void execute() throws Throwable; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java deleted file mode 100644 index a5f74507..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.function; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code ThrowingConsumer} is a functional interface that can be used to - * implement any generic block of code that consumes an argument and - * potentially throws a {@link Throwable}. - * - *

The {@code ThrowingConsumer} interface is similar to - * {@link java.util.function.Consumer}, except that a {@code ThrowingConsumer} - * can throw any kind of exception, including checked exceptions. - * - *

Rationale for throwing {@code Throwable} instead of {@code Exception}

- * - *

Although Java applications typically throw exceptions that are instances - * of {@link java.lang.Exception}, {@link java.lang.RuntimeException}, - * {@link java.lang.Error}, or {@link java.lang.AssertionError} (in testing - * scenarios), there may be use cases where a {@code ThrowingConsumer} needs to - * explicitly throw a {@code Throwable}. In order to support such specialized - * use cases, {@link #accept} is declared to throw {@code Throwable}. - * - * @param the type of argument consumed - * @since 5.0 - * @see java.util.function.Consumer - * @see org.junit.jupiter.api.DynamicTest#stream - * @see Executable - * @see ThrowingSupplier - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface ThrowingConsumer { - - /** - * Consume the supplied argument, potentially throwing an exception. - * - * @param t the argument to consume - */ - void accept(T t) throws Throwable; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java deleted file mode 100644 index 573dffe7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.function; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * {@code ThrowingSupplier} is a functional interface that can be used to - * implement any generic block of code that returns an object and - * potentially throws a {@link Throwable}. - * - *

The {@code ThrowingSupplier} interface is similar to - * {@link java.util.function.Supplier}, except that a {@code ThrowingSupplier} - * can throw any kind of exception, including checked exceptions. - * - *

Rationale for throwing {@code Throwable} instead of {@code Exception}

- * - *

Although Java applications typically throw exceptions that are instances - * of {@link Exception}, {@link RuntimeException}, - * {@link Error}, or {@link AssertionError} (in testing - * scenarios), there may be use cases where a {@code ThrowingSupplier} needs to - * explicitly throw a {@code Throwable}. In order to support such specialized - * use cases, {@link #get} is declared to throw {@code Throwable}. - * - * @param the type of argument supplied - * @since 5.0 - * @see java.util.function.Supplier - * @see org.junit.jupiter.api.Assertions#assertTimeout(java.time.Duration, ThrowingSupplier) - * @see org.junit.jupiter.api.Assertions#assertTimeoutPreemptively(java.time.Duration, ThrowingSupplier) - * @see Executable - * @see ThrowingConsumer - */ -@FunctionalInterface -@API(status = STABLE, since = "5.0") -public interface ThrowingSupplier { - - /** - * Get a result, potentially throwing an exception. - * - * @return a result - */ - T get() throws Throwable; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java deleted file mode 100644 index aaebdbdb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional interfaces used within JUnit Jupiter. - */ - -package org.junit.jupiter.api.function; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java deleted file mode 100644 index 59472e28..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.io; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * Enumeration of cleanup modes for {@link TempDir @TempDir}. - * - *

When a test with a temporary directory completes, it might be useful in - * some cases to be able to view the contents of the temporary directory used by - * the test. {@code CleanupMode} allows you to control how a {@code TempDir} - * is cleaned up. - * - * @since 5.9 - * @see TempDir - */ -@API(status = EXPERIMENTAL, since = "5.9") -public enum CleanupMode { - - /** - * Use the default cleanup mode. - * - * @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME - */ - DEFAULT, - - /** - * Always clean up a temporary directory after the test has completed. - */ - ALWAYS, - - /** - * Only clean up a temporary directory if the test completed successfully. - */ - ON_SUCCESS, - - /** - * Never clean up a temporary directory after the test has completed. - */ - NEVER; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java deleted file mode 100644 index 8d0e6dad..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.io; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.io.File; -import java.io.IOException; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.nio.file.Path; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ParameterResolutionException; - -/** - * {@code @TempDir} can be used to annotate a field in a test class or a - * parameter in a lifecycle method or test method of type {@link Path} or - * {@link File} that should be resolved into a temporary directory. - * - *

Please note that {@code @TempDir} is not supported on constructor - * parameters. Please use field injection instead by annotating an instance - * field with {@code @TempDir}. - * - *

Creation

- * - *

The temporary directory is only created if a field in a test class or a - * parameter in a lifecycle method or test method is annotated with - * {@code @TempDir}. If the field type or parameter type is neither {@link Path} - * nor {@link File}, if a field is declared as {@code final}, or if the temporary - * directory cannot be created, an {@link ExtensionConfigurationException} or a - * {@link ParameterResolutionException} will be thrown as appropriate. In - * addition, a {@code ParameterResolutionException} will be thrown for a - * constructor parameter annotated with {@code @TempDir}. - * - *

Scope

- * - *

By default, a separate temporary directory is created for every - * declaration of the {@code @TempDir} annotation. If you want to share a - * temporary directory across all tests in a test class, you should declare the - * annotation on a {@code static} field or on a parameter of a - * {@link org.junit.jupiter.api.BeforeAll @BeforeAll} method. - * - *

Old behavior

- * - *

You can revert to the old behavior of using a single temporary directory - * by setting the {@value #SCOPE_PROPERTY_NAME} configuration parameter to - * {@code per_context}. In that case, the scope of the temporary directory - * depends on where the first {@code @TempDir} annotation is encountered when - * executing a test class. The temporary directory will be shared by all tests - * in a class when the annotation is present on a {@code static} field or on a - * parameter of a {@link org.junit.jupiter.api.BeforeAll @BeforeAll} method. - * Otherwise — for example, when {@code @TempDir} is only used on instance - * fields or on parameters in test, - * {@link org.junit.jupiter.api.BeforeEach @BeforeEach}, or - * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods — each test - * will use its own temporary directory. - * - *

Clean Up

- * - *

By default, when the end of the scope of a temporary directory is reached, - * — when the test method or class has finished execution — JUnit will - * attempt to clean up the temporary directory by recursively deleting all files - * and directories in the temporary directory and, finally, the temporary directory - * itself. In case deletion of a file or directory fails, an {@link IOException} - * will be thrown that will cause the test or test class to fail. - * - *

The {@link #cleanup} attribute allows you to configure the {@link CleanupMode}. - * If the cleanup mode is set to {@link CleanupMode#NEVER NEVER}, the temporary - * directory will not be cleaned up after the test completes. If the cleanup mode is - * set to {@link CleanupMode#ON_SUCCESS ON_SUCCESS}, the temporary directory will - * only be cleaned up if the test completes successfully. By default, the - * {@link CleanupMode#ALWAYS ALWAYS} clean up mode will be used, but this can be - * configured globally by setting the {@value #DEFAULT_CLEANUP_MODE_PROPERTY_NAME} - * configuration parameter. - * - * @since 5.4 - */ -@Target({ ElementType.FIELD, ElementType.PARAMETER }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = EXPERIMENTAL, since = "5.4") -public @interface TempDir { - - /** - * Property name used to set the scope of temporary directories created via - * {@link org.junit.jupiter.api.io.TempDir @TempDir} annotation: {@value} - * - *

Supported Values

- *
    - *
  • {@code per_context}: creates a single temporary directory for the - * entire test class or method, depending on where it's first declared - *
  • {@code per_declaration}: creates separate temporary directories for - * each declaration site of the {@code @TempDir} annotation. - *
- * - *

If not specified, the default is {@code per_declaration}. - * - * @since 5.8 - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - @API(status = DEPRECATED, since = "5.9") - String SCOPE_PROPERTY_NAME = "junit.jupiter.tempdir.scope"; - - /** - * The name of the configuration parameter that is used to configure the - * default {@link CleanupMode}. - * - *

If this configuration parameter is not set, {@link CleanupMode#ALWAYS} - * will be used as the default. - * - * @since 5.9 - */ - @API(status = EXPERIMENTAL, since = "5.9") - String DEFAULT_CLEANUP_MODE_PROPERTY_NAME = "junit.jupiter.tempdir.cleanup.mode.default"; - - /** - * How the temporary directory gets cleaned up after the test completes. - * - * @since 5.9 - */ - @API(status = EXPERIMENTAL, since = "5.9") - CleanupMode cleanup() default CleanupMode.DEFAULT; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java deleted file mode 100644 index 7e893893..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * IO-related support in JUnit Jupiter. - */ - -package org.junit.jupiter.api.io; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java deleted file mode 100644 index 38e623dd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * JUnit Jupiter API for writing tests. - */ - -package org.junit.jupiter.api; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java deleted file mode 100644 index 86d051c6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.parallel; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @Execution} is used to configure the parallel execution - * {@linkplain #value mode} of a test class or test method. - * - *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} - * within class hierarchies. - * - *

Default Execution Mode

- * - *

If this annotation is not present, {@link ExecutionMode#SAME_THREAD} is - * used unless a default execution mode is defined via one of the following - * configuration parameters: - * - *

- *
{@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME}
- *
Default execution mode for all classes and tests
- *
{@value #DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME}
- *
Default execution mode for top-level classes
- *
- * - *

{@value #DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME} - * overrides {@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME} for top-level - * classes - * - * @see Isolated - * @see ResourceLock - * @since 5.3 - */ -@API(status = EXPERIMENTAL, since = "5.3") -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Inherited -public @interface Execution { - - /** - * Property name used to set the default test execution mode: {@value} - * - *

This setting is only effective if parallel execution is enabled. - * - *

Supported Values

- * - *

Supported values include names of enum constants defined in - * {@link ExecutionMode}, ignoring case. - * - *

If not specified, the default is "same_thread" which corresponds to - * {@code @Execution(ExecutionMode.SAME_THREAD)}. - * - * @since 5.4 - */ - @API(status = EXPERIMENTAL, since = "5.9") - String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = "junit.jupiter.execution.parallel.mode.default"; - - /** - * Property name used to set the default test execution mode for top-level - * classes: {@value} - * - *

This setting is only effective if parallel execution is enabled. - * - *

Supported Values

- * - *

Supported values include names of enum constants defined in - * {@link ExecutionMode}, ignoring case. - * - *

If not specified, it will be resolved into the same value as - * {@link #DEFAULT_EXECUTION_MODE_PROPERTY_NAME}. - * - * @since 5.4 - */ - @API(status = EXPERIMENTAL, since = "5.9") - String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = "junit.jupiter.execution.parallel.mode.classes.default"; - - /** - * The required/preferred execution mode. - * - * @see ExecutionMode - */ - ExecutionMode value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java deleted file mode 100644 index b0b7752a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.parallel; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * Supported execution modes for parallel test execution. - * - * @since 5.3 - * @see #SAME_THREAD - * @see #CONCURRENT - */ -@API(status = EXPERIMENTAL, since = "5.3") -public enum ExecutionMode { - - /** - * Force execution in same thread as the parent node. - * - * @see #CONCURRENT - */ - SAME_THREAD, - - /** - * Allow concurrent execution with any other node. - * - * @see #SAME_THREAD - */ - CONCURRENT - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java deleted file mode 100644 index 5dc97602..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.parallel; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @Isolated} is used to declare that the annotated test class should be - * executed in isolation from other test classes. - * - *

When a test class is run in isolation, no other test class is executed - * concurrently. This can be used to enable parallel test execution for the - * entire test suite while running some tests in isolation (e.g. if they modify - * some global resource). - * - * @since 5.7 - * @see ExecutionMode - * @see ResourceLock - */ -@API(status = EXPERIMENTAL, since = "5.7") -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@ResourceLock(Resources.GLOBAL) -public @interface Isolated { - - /** - * The reason this test class needs to run in isolation. - * - *

The supplied string is currently not reported in any way but can be - * used for documentation purposes. - */ - String value() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java deleted file mode 100644 index 30378f53..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.parallel; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * The access mode required by a test class or method for a given resource. - * - * @since 5.3 - * @see ResourceLock - */ -@API(status = EXPERIMENTAL, since = "5.3") -public enum ResourceAccessMode { - - /** - * Require read and write access to the resource. - */ - READ_WRITE, - - /** - * Require only read access to the resource. - */ - READ - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java deleted file mode 100644 index 4f7f97d4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.parallel; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ResourceLock} is used to declare that the annotated test class or test - * method requires access to a shared resource identified by a key. - * - *

The resource key is specified via {@link #value}. In addition, - * {@link #mode} allows you to specify whether the annotated test class or test - * method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE} or only - * {@link ResourceAccessMode#READ READ} access to the resource. In the former case, - * execution of the annotated element will occur while no other test class or - * test method that uses the shared resource is being executed. In the latter case, - * the annotated element may be executed concurrently with other test classes or - * methods that also require {@code READ} access but not at the same time as any - * other test that requires {@code READ_WRITE} access. - * - *

This annotation can be repeated to declare the use of multiple shared resources. - * - *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} - * within class hierarchies. - * - * @see Isolated - * @see Resources - * @see ResourceAccessMode - * @see ResourceLocks - * @since 5.3 - */ -@API(status = EXPERIMENTAL, since = "5.3") -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Inherited -@Repeatable(ResourceLocks.class) -public @interface ResourceLock { - - /** - * The resource key. - * - * @see Resources - */ - String value(); - - /** - * The resource access mode. - * - *

Defaults to {@link ResourceAccessMode#READ_WRITE READ_WRITE}. - * - * @see ResourceAccessMode - */ - ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java deleted file mode 100644 index f15bfbc3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.parallel; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ResourceLocks} is a container for one or more - * {@link ResourceLock @ResourceLock} declarations. - * - *

Note, however, that use of the {@code @ResourceLocks} container is - * completely optional since {@code @ResourceLock} is a - * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. - * - *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} - * within class hierarchies. - * - * @see ResourceLock - * @since 5.3 - */ -@API(status = EXPERIMENTAL, since = "5.3") -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Inherited -public @interface ResourceLocks { - - /** - * An array of one or more {@linkplain ResourceLock @ResourceLock} declarations. - */ - ResourceLock[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java deleted file mode 100644 index ccdbaefe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.parallel; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * Common resource names for synchronizing test execution. - * - * @since 5.3 - * @see ResourceLock - */ -@API(status = EXPERIMENTAL, since = "5.3") -public class Resources { - - /** - * Represents Java's system properties: {@value} - * - * @see System#getProperties() - * @see System#setProperties(java.util.Properties) - */ - public static final String SYSTEM_PROPERTIES = "java.lang.System.properties"; - - /** - * Represents the standard output stream of the current process: {@value} - * - * @see System#out - * @see System#setOut(java.io.PrintStream) - */ - public static final String SYSTEM_OUT = "java.lang.System.out"; - - /** - * Represents the standard error stream of the current process: {@value} - * - * @see System#err - * @see System#setErr(java.io.PrintStream) - */ - public static final String SYSTEM_ERR = "java.lang.System.err"; - - /** - * Represents the default locale for the current instance of the JVM: - * {@value} - * - * @since 5.4 - * @see java.util.Locale#setDefault(java.util.Locale) - */ - @API(status = EXPERIMENTAL, since = "5.4") - public static final String LOCALE = "java.util.Locale.default"; - - /** - * Represents the default time zone for the current instance of the JVM: - * {@value} - * - * @since 5.4 - * @see java.util.TimeZone#setDefault(java.util.TimeZone) - */ - @API(status = EXPERIMENTAL, since = "5.4") - public static final String TIME_ZONE = "java.util.TimeZone.default"; - - /** - * Represents the global resource lock: {@value} - * - * @since 5.8 - * @see Isolated - * @see org.junit.platform.engine.support.hierarchical.ExclusiveResource - */ - @API(status = EXPERIMENTAL, since = "5.8") - public static final String GLOBAL = "org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY"; - - private Resources() { - /* no-op */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java deleted file mode 100644 index 16b34edf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * JUnit Jupiter API for influencing parallel test execution. - */ - -package org.junit.jupiter.api.parallel; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt deleted file mode 100644 index d0f5af1e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -@file:API(status = STABLE, since = "5.7") - -package org.junit.jupiter.api - -import org.apiguardian.api.API -import org.apiguardian.api.API.Status.EXPERIMENTAL -import org.apiguardian.api.API.Status.STABLE -import org.junit.jupiter.api.function.Executable -import org.junit.jupiter.api.function.ThrowingSupplier -import java.time.Duration -import java.util.function.Supplier -import java.util.stream.Stream - -/** - * @see Assertions.fail - */ -fun fail(message: String?, throwable: Throwable? = null): Nothing = - Assertions.fail(message, throwable) - -/** - * @see Assertions.fail - */ -fun fail(message: (() -> String)?): Nothing = - Assertions.fail(message) - -/** - * @see Assertions.fail - */ -fun fail(throwable: Throwable?): Nothing = - Assertions.fail(throwable) - -/** - * [Stream] of functions to be executed. - */ -private typealias ExecutableStream = Stream<() -> Unit> -private fun ExecutableStream.convert() = map { Executable(it) } - -/** - * @see Assertions.assertAll - */ -fun assertAll(executables: ExecutableStream) = - Assertions.assertAll(executables.convert()) - -/** - * @see Assertions.assertAll - */ -fun assertAll(heading: String?, executables: ExecutableStream) = - Assertions.assertAll(heading, executables.convert()) - -/** - * [Collection] of functions to be executed. - */ -private typealias ExecutableCollection = Collection<() -> Unit> -private fun ExecutableCollection.convert() = map { Executable(it) } - -/** - * @see Assertions.assertAll - */ -fun assertAll(executables: ExecutableCollection) = - Assertions.assertAll(executables.convert()) - -/** - * @see Assertions.assertAll - */ -fun assertAll(heading: String?, executables: ExecutableCollection) = - Assertions.assertAll(heading, executables.convert()) - -/** - * @see Assertions.assertAll - */ -fun assertAll(vararg executables: () -> Unit) = - assertAll(executables.toList().stream()) - -/** - * @see Assertions.assertAll - */ -fun assertAll(heading: String?, vararg executables: () -> Unit) = - assertAll(heading, executables.toList().stream()) - -/** - * Example usage: - * ```kotlin - * val exception = assertThrows { - * throw IllegalArgumentException("Talk to a duck") - * } - * assertEquals("Talk to a duck", exception.message) - * ``` - * @see Assertions.assertThrows - */ -inline fun assertThrows(executable: () -> Unit): T { - val throwable: Throwable? = try { - executable() - } catch (caught: Throwable) { - caught - } as? Throwable - - return Assertions.assertThrows(T::class.java) { - if (throwable != null) { - throw throwable - } - } -} - -/** - * Example usage: - * ```kotlin - * val exception = assertThrows("Should throw an Exception") { - * throw IllegalArgumentException("Talk to a duck") - * } - * assertEquals("Talk to a duck", exception.message) - * ``` - * @see Assertions.assertThrows - */ -inline fun assertThrows(message: String, executable: () -> Unit): T = - assertThrows({ message }, executable) - -/** - * Example usage: - * ```kotlin - * val exception = assertThrows({ "Should throw an Exception" }) { - * throw IllegalArgumentException("Talk to a duck") - * } - * assertEquals("Talk to a duck", exception.message) - * ``` - * @see Assertions.assertThrows - */ -inline fun assertThrows(noinline message: () -> String, executable: () -> Unit): T { - val throwable: Throwable? = try { - executable() - } catch (caught: Throwable) { - caught - } as? Throwable - - return Assertions.assertThrows( - T::class.java, - Executable { - if (throwable != null) { - throw throwable - } - }, - Supplier(message) - ) -} - -/** - * Example usage: - * ```kotlin - * val result = assertDoesNotThrow { - * // Code block that is expected to not throw an exception - * } - * ``` - * @see Assertions.assertDoesNotThrow - * @param R the result type of the [executable] - */ -@API(status = EXPERIMENTAL, since = "5.5") -inline fun assertDoesNotThrow(executable: () -> R): R = - Assertions.assertDoesNotThrow(evaluateAndWrap(executable)) - -/** - * Example usage: - * ```kotlin - * val result = assertDoesNotThrow("Should not throw an exception") { - * // Code block that is expected to not throw an exception - * } - * ``` - * @see Assertions.assertDoesNotThrow - * @param R the result type of the [executable] - */ -@API(status = EXPERIMENTAL, since = "5.5") -inline fun assertDoesNotThrow(message: String, executable: () -> R): R = - assertDoesNotThrow({ message }, executable) - -/** - * Example usage: - * ```kotlin - * val result = assertDoesNotThrow({ "Should not throw an exception" }) { - * // Code block that is expected to not throw an exception - * } - * ``` - * @see Assertions.assertDoesNotThrow - * @param R the result type of the [executable] - */ -@API(status = EXPERIMENTAL, since = "5.5") -inline fun assertDoesNotThrow(noinline message: () -> String, executable: () -> R): R = - Assertions.assertDoesNotThrow( - evaluateAndWrap(executable), - Supplier(message) - ) - -@PublishedApi -internal inline fun evaluateAndWrap(executable: () -> R): ThrowingSupplier = try { - val result = executable() - ThrowingSupplier { result } -} catch (throwable: Throwable) { - ThrowingSupplier { throw throwable } -} - -/** - * Example usage: - * ```kotlin - * val result = assertTimeout(Duration.seconds(1)) { - * // Code block that is being timed. - * } - * ``` - * @see Assertions.assertTimeout - * @paramR the result of the [executable]. - */ -@API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeout(timeout: Duration, executable: () -> R): R = - Assertions.assertTimeout(timeout, executable) - -/** - * Example usage: - * ```kotlin - * val result = assertTimeout(Duration.seconds(1), "Should only take one second") { - * // Code block that is being timed. - * } - * ``` - * @see Assertions.assertTimeout - * @paramR the result of the [executable]. - */ -@API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeout(timeout: Duration, message: String, executable: () -> R): R = - Assertions.assertTimeout(timeout, executable, message) - -/** - * Example usage: - * ```kotlin - * val result = assertTimeout(Duration.seconds(1), { "Should only take one second" }) { - * // Code block that is being timed. - * } - * ``` - * @see Assertions.assertTimeout - * @paramR the result of the [executable]. - */ -@API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeout(timeout: Duration, message: () -> String, executable: () -> R): R = - Assertions.assertTimeout(timeout, executable, message) - -/** - * Example usage: - * ```kotlin - * val result = assertTimeoutPreemptively(Duration.seconds(1)) { - * // Code block that is being timed. - * } - * ``` - * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. - */ -@API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, executable: () -> R): R = - Assertions.assertTimeoutPreemptively(timeout, executable) - -/** - * Example usage: - * ```kotlin - * val result = assertTimeoutPreemptively(Duration.seconds(1), "Should only take one second") { - * // Code block that is being timed. - * } - * ``` - * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. - */ -@API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, message: String, executable: () -> R): R = - Assertions.assertTimeoutPreemptively(timeout, executable, message) - -/** - * Example usage: - * ```kotlin - * val result = assertTimeoutPreemptively(Duration.seconds(1), { "Should only take one second" }) { - * // Code block that is being timed. - * } - * ``` - * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. - */ -@API(status = EXPERIMENTAL, since = "5.5") -fun assertTimeoutPreemptively(timeout: Duration, message: () -> String, executable: () -> R): R = - Assertions.assertTimeoutPreemptively(timeout, executable, message) diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java deleted file mode 100644 index b6856c78..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Defines JUnit Jupiter API for writing tests. - */ -module org.junit.jupiter.api { - requires static transitive org.apiguardian.api; - requires transitive org.junit.platform.commons; - requires transitive org.opentest4j; - - exports org.junit.jupiter.api; - exports org.junit.jupiter.api.condition; - exports org.junit.jupiter.api.extension; - exports org.junit.jupiter.api.function; - exports org.junit.jupiter.api.io; - exports org.junit.jupiter.api.parallel; - - opens org.junit.jupiter.api.condition to org.junit.platform.commons; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java deleted file mode 100644 index 836aabab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -public class ExtensionContextParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - return ExtensionContext.class.equals(parameterContext.getParameter().getType()); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - return extensionContext; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java deleted file mode 100644 index ef33de69..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.fixtures; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * {@code @TrackLogRecords} registers an extension that tracks log records - * logged via JUnit's logging facade for JUL. - * - *

Log records are tracked on a per-method basis (e.g., for a single - * test method). - * - *

Test methods can gain access to the {@link LogRecordListener} managed by - * the extension by having an instance of {@code LogRecordListener} injected as - * a method parameter. - * - * @since 5.1 - * @see LoggerFactory - * @see LogRecordListener - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(TrackLogRecords.Extension.class) -public @interface TrackLogRecords { - - class Extension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { - - @Override - public void beforeEach(ExtensionContext context) { - LoggerFactory.addListener(getListener(context)); - } - - @Override - public void afterEach(ExtensionContext context) { - LoggerFactory.removeListener(getListener(context)); - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - boolean isTestMethodLevel = extensionContext.getTestMethod().isPresent(); - boolean isListener = parameterContext.getParameter().getType() == LogRecordListener.class; - return isTestMethodLevel && isListener; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return getListener(extensionContext); - } - - private LogRecordListener getListener(ExtensionContext context) { - return getStore(context).getOrComputeIfAbsent(LogRecordListener.class); - } - - private Store getStore(ExtensionContext context) { - return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts deleted file mode 100644 index e15a42bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/junit-jupiter-engine.gradle.kts +++ /dev/null @@ -1,53 +0,0 @@ -import org.gradle.api.tasks.PathSensitivity.RELATIVE - -plugins { - `kotlin-library-conventions` - `testing-conventions` - groovy - `java-test-fixtures` -} - -description = "JUnit Jupiter Engine" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformEngine) - api(projects.junitJupiterApi) - - compileOnlyApi(libs.apiguardian) - - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteEngine) - testImplementation(projects.junitPlatformTestkit) - testImplementation(testFixtures(projects.junitPlatformCommons)) - testImplementation(kotlin("stdlib")) - testImplementation(libs.junit4) - testImplementation(libs.kotlinx.coroutines) - testImplementation(libs.groovy4) - - osgiVerification(projects.junitPlatformLauncher) -} - -tasks { - test { - inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) - systemProperty("developmentVersion", version) - } -} - -tasks { - jar { - bundle { - bnd(""" - Provide-Capability:\ - org.junit.platform.engine;\ - org.junit.platform.engine='junit-jupiter';\ - version:Version="${'$'}{version_cleanup;${project.version}}" - Require-Capability:\ - org.junit.platform.launcher;\ - filter:='(&(org.junit.platform.launcher=junit-platform-launcher)(version>=${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}}})))';\ - effective:=active - """) - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java deleted file mode 100644 index f99d61d8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME; -import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME; -import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME; -import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; -import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_PROPERTY_NAME; -import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.util.ClassNamePatternFilterUtils; -import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy; - -/** - * Collection of constants related to the {@link JupiterTestEngine}. - * - * @since 5.0 - * @see org.junit.platform.engine.ConfigurationParameters - */ -@API(status = STABLE, since = "5.0") -public final class Constants { - - /** - * Property name used to provide patterns for deactivating conditions: {@value} - * - *

Pattern Matching Syntax

- * - *

If the property value consists solely of an asterisk ({@code *}), all - * conditions will be deactivated. Otherwise, the property value will be treated - * as a comma-separated list of patterns where each individual pattern will be - * matched against the fully qualified class name (FQCN) of each registered - * condition. Any dot ({@code .}) in a pattern will match against a dot ({@code .}) - * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match - * against one or more characters in a FQCN. All other characters in a pattern - * will be matched one-to-one against a FQCN. - * - *

Examples

- * - *
    - *
  • {@code *}: deactivates all conditions. - *
  • {@code org.junit.*}: deactivates every condition under the {@code org.junit} - * base package and any of its subpackages. - *
  • {@code *.MyCondition}: deactivates every condition whose simple class name is - * exactly {@code MyCondition}. - *
  • {@code *System*}: deactivates every condition whose FQCN contains - * {@code System}. - *
  • {@code *System*, *Dev*}: deactivates every condition whose FQCN contains - * {@code System} or {@code Dev}. - *
  • {@code org.example.MyCondition, org.example.TheirCondition}: deactivates - * conditions whose FQCN is exactly {@code org.example.MyCondition} or - * {@code org.example.TheirCondition}. - *
- * - * @see #DEACTIVATE_ALL_CONDITIONS_PATTERN - * @see org.junit.jupiter.api.extension.ExecutionCondition - */ - public static final String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = JupiterConfiguration.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; - - /** - * Wildcard pattern which signals that all conditions should be deactivated: {@value} - * - * @see #DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME - * @see org.junit.jupiter.api.extension.ExecutionCondition - */ - public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; - - /** - * Property name used to set the default display name generator class name: {@value} - * - * @see DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME - */ - public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; - - /** - * Property name used to enable auto-detection and registration of extensions via - * Java's {@link java.util.ServiceLoader} mechanism: {@value} - * - *

The default behavior is not to perform auto-detection. - */ - public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; - - /** - * Property name used to set the default test instance lifecycle mode: {@value} - * - * @see TestInstance.Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME - */ - public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; - - /** - * Property name used to enable parallel test execution: {@value} - * - *

By default, tests are executed sequentially in a single thread. - * - * @since 5.3 - */ - @API(status = EXPERIMENTAL, since = "5.3") - public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; - - /** - * Property name used to set the default test execution mode: {@value} - * - * @see Execution#DEFAULT_EXECUTION_MODE_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.4") - public static final String DEFAULT_PARALLEL_EXECUTION_MODE = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; - - /** - * Property name used to set the default test execution mode for top-level - * classes: {@value} - * - * @see Execution#DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; - - static final String PARALLEL_CONFIG_PREFIX = "junit.jupiter.execution.parallel.config."; - - /** - * Property name used to select the - * {@link ParallelExecutionConfigurationStrategy}: {@value} - * - *

Potential values: {@code dynamic} (default), {@code fixed}, or - * {@code custom}. - * - * @since 5.3 - */ - @API(status = EXPERIMENTAL, since = "5.3") - public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + CONFIG_STRATEGY_PROPERTY_NAME; - - /** - * Property name used to set the desired parallelism for the {@code fixed} - * configuration strategy: {@value} - * - *

No default value; must be a positive integer. - * - * @since 5.3 - */ - @API(status = EXPERIMENTAL, since = "5.3") - public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; - - /** - * Property name used to configure the maximum pool size of the underlying - * fork-join pool for the {@code fixed} configuration strategy: {@value} - * - *

Value must be an integer and greater than or equal to - * {@value #PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME}; defaults to - * {@code 256 + fixed.parallelism}. - * - *

Note: This property only takes affect on Java 9+. - * - * @since 5.10 - */ - @API(status = EXPERIMENTAL, since = "5.10") - public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME; - - /** - * Property name used to disable saturation of the underlying fork-join pool - * for the {@code fixed} configuration strategy: {@value} - * - *

When set to {@code false} the underlying fork-join pool will reject - * additional tasks if all available workers are busy and the maximum - * pool-size would be exceeded. - - *

Value must either {@code true} or {@code false}; defaults to {@code true}. - * - *

Note: This property only takes affect on Java 9+. - * - * @since 5.10 - */ - @API(status = EXPERIMENTAL, since = "5.10") - public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + CONFIG_FIXED_SATURATE_PROPERTY_NAME; - - /** - * Property name used to set the factor to be multiplied with the number of - * available processors/cores to determine the desired parallelism for the - * {@code dynamic} configuration strategy: {@value} - * - *

Value must be a positive decimal number; defaults to {@code 1}. - * - * @since 5.3 - */ - @API(status = EXPERIMENTAL, since = "5.3") - public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME; - - /** - * Property name used to specify the fully qualified class name of the - * {@link ParallelExecutionConfigurationStrategy} to be used for the - * {@code custom} configuration strategy: {@value} - * - * @since 5.3 - */ - @API(status = EXPERIMENTAL, since = "5.3") - public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX - + CONFIG_CUSTOM_CLASS_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all testable and - * lifecycle methods: {@value}. - * - * @see Timeout#DEFAULT_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all testable methods: {@value}. - * - * @see Timeout#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all - * {@link Test @Test} methods: {@value}. - * - * @see Timeout#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all - * {@link TestTemplate @TestTemplate} methods: {@value}. - * - * @see Timeout#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all - * {@link TestFactory @TestFactory} methods: {@value}. - * - * @see Timeout#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all lifecycle methods: {@value}. - * - * @see Timeout#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all - * {@link BeforeAll @BeforeAll} methods: {@value}. - * - * @see Timeout#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all - * {@link BeforeEach @BeforeEach} methods: {@value}. - * - * @see Timeout#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all - * {@link AfterEach @AfterEach} methods: {@value}. - * - * @see Timeout#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property name used to set the default timeout for all - * {@link AfterAll @AfterAll} methods: {@value}. - * - * @see Timeout#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.5") - public static final String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; - - /** - * Property used to determine if timeouts are applied to tests: {@value}. - * - * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "5.6") - public static final String TIMEOUT_MODE_PROPERTY_NAME = Timeout.TIMEOUT_MODE_PROPERTY_NAME; - - /** - * Property name used to set the default method orderer class name: {@value} - * - * @see MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME - */ - @API(status = STABLE, since = "5.9") - public static final String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; - - /** - * Property name used to set the default class orderer class name: {@value} - * - * @see ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME - */ - @API(status = STABLE, since = "5.9") - public static final String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; - - /** - * Property name used to set the scope of temporary directories created via - * {@link TempDir @TempDir} annotation: {@value} - * - * @see TempDir#SCOPE_PROPERTY_NAME - */ - @Deprecated - @API(status = DEPRECATED, since = "5.8") - @SuppressWarnings("deprecation") - public static final String TEMP_DIR_SCOPE_PROPERTY_NAME = TempDir.SCOPE_PROPERTY_NAME; - - /** - * Property name used to set the default timeout thread mode. - * - * @since 5.9 - * @see Timeout - * @see Timeout.ThreadMode - */ - @API(status = EXPERIMENTAL, since = "5.9") - public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; - - private Constants() { - /* no-op */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java deleted file mode 100644 index dbd799b7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.jupiter.engine.config.CachingJupiterConfiguration; -import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.config.PrefixedConfigurationParameters; -import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService; -import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine; -import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * The JUnit Jupiter {@link org.junit.platform.engine.TestEngine TestEngine}. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public final class JupiterTestEngine extends HierarchicalTestEngine { - - @Override - public String getId() { - return JupiterEngineDescriptor.ENGINE_ID; - } - - /** - * Returns {@code org.junit.jupiter} as the group ID. - */ - @Override - public Optional getGroupId() { - return Optional.of("org.junit.jupiter"); - } - - /** - * Returns {@code junit-jupiter-engine} as the artifact ID. - */ - @Override - public Optional getArtifactId() { - return Optional.of("junit-jupiter-engine"); - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - JupiterConfiguration configuration = new CachingJupiterConfiguration( - new DefaultJupiterConfiguration(discoveryRequest.getConfigurationParameters())); - JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration); - new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); - return engineDescriptor; - } - - @Override - protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) { - JupiterConfiguration configuration = getJupiterConfiguration(request); - if (configuration.isParallelExecutionEnabled()) { - return new ForkJoinPoolHierarchicalTestExecutorService(new PrefixedConfigurationParameters( - request.getConfigurationParameters(), Constants.PARALLEL_CONFIG_PREFIX)); - } - return super.createExecutorService(request); - } - - @Override - protected JupiterEngineExecutionContext createExecutionContext(ExecutionRequest request) { - return new JupiterEngineExecutionContext(request.getEngineExecutionListener(), - getJupiterConfiguration(request)); - } - - /** - * @since 5.4 - */ - @Override - protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) { - return JupiterThrowableCollectorFactory::createThrowableCollector; - } - - private JupiterConfiguration getJupiterConfiguration(ExecutionRequest request) { - JupiterEngineDescriptor engineDescriptor = (JupiterEngineDescriptor) request.getRootTestDescriptor(); - return engineDescriptor.getConfiguration(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java deleted file mode 100644 index ebf54734..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.api.io.TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; - -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.parallel.ExecutionMode; - -/** - * Caching implementation of the {@link JupiterConfiguration} API. - * - * @since 5.4 - */ -@API(status = INTERNAL, since = "5.4") -public class CachingJupiterConfiguration implements JupiterConfiguration { - - private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); - private final JupiterConfiguration delegate; - - public CachingJupiterConfiguration(JupiterConfiguration delegate) { - this.delegate = delegate; - } - - @Override - public Optional getRawConfigurationParameter(String key) { - return delegate.getRawConfigurationParameter(key); - } - - @Override - public Optional getRawConfigurationParameter(String key, Function transformer) { - return delegate.getRawConfigurationParameter(key, transformer); - } - - @Override - public boolean isParallelExecutionEnabled() { - return (boolean) cache.computeIfAbsent(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, - key -> delegate.isParallelExecutionEnabled()); - } - - @Override - public boolean isExtensionAutoDetectionEnabled() { - return (boolean) cache.computeIfAbsent(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME, - key -> delegate.isExtensionAutoDetectionEnabled()); - } - - @Override - public ExecutionMode getDefaultExecutionMode() { - return (ExecutionMode) cache.computeIfAbsent(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, - key -> delegate.getDefaultExecutionMode()); - } - - @Override - public ExecutionMode getDefaultClassesExecutionMode() { - return (ExecutionMode) cache.computeIfAbsent(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, - key -> delegate.getDefaultClassesExecutionMode()); - } - - @Override - public TestInstance.Lifecycle getDefaultTestInstanceLifecycle() { - return (TestInstance.Lifecycle) cache.computeIfAbsent(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, - key -> delegate.getDefaultTestInstanceLifecycle()); - } - - @SuppressWarnings("unchecked") - @Override - public Predicate getExecutionConditionFilter() { - return (Predicate) cache.computeIfAbsent(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, - key -> delegate.getExecutionConditionFilter()); - } - - @Override - public DisplayNameGenerator getDefaultDisplayNameGenerator() { - return (DisplayNameGenerator) cache.computeIfAbsent(DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME, - key -> delegate.getDefaultDisplayNameGenerator()); - } - - @SuppressWarnings("unchecked") - @Override - public Optional getDefaultTestMethodOrderer() { - return (Optional) cache.computeIfAbsent(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, - key -> delegate.getDefaultTestMethodOrderer()); - } - - @SuppressWarnings("unchecked") - @Override - public Optional getDefaultTestClassOrderer() { - return (Optional) cache.computeIfAbsent(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, - key -> delegate.getDefaultTestClassOrderer()); - } - - @Override - public CleanupMode getDefaultTempDirCleanupMode() { - return (CleanupMode) cache.computeIfAbsent(DEFAULT_CLEANUP_MODE_PROPERTY_NAME, - key -> delegate.getDefaultTempDirCleanupMode()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java deleted file mode 100644 index ae8a4944..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; -import static org.junit.jupiter.api.io.TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; - -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.platform.commons.util.ClassNamePatternFilterUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * Default implementation of the {@link JupiterConfiguration} API. - * - * @since 5.4 - */ -@API(status = INTERNAL, since = "5.4") -public class DefaultJupiterConfiguration implements JupiterConfiguration { - - private static final EnumConfigurationParameterConverter executionModeConverter = // - new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode"); - - private static final EnumConfigurationParameterConverter lifecycleConverter = // - new EnumConfigurationParameterConverter<>(Lifecycle.class, "test instance lifecycle mode"); - - private static final InstantiatingConfigurationParameterConverter displayNameGeneratorConverter = // - new InstantiatingConfigurationParameterConverter<>(DisplayNameGenerator.class, "display name generator"); - - private static final InstantiatingConfigurationParameterConverter methodOrdererConverter = // - new InstantiatingConfigurationParameterConverter<>(MethodOrderer.class, "method orderer"); - - private static final InstantiatingConfigurationParameterConverter classOrdererConverter = // - new InstantiatingConfigurationParameterConverter<>(ClassOrderer.class, "class orderer"); - - private static final EnumConfigurationParameterConverter cleanupModeConverter = // - new EnumConfigurationParameterConverter<>(CleanupMode.class, "cleanup mode"); - - private final ConfigurationParameters configurationParameters; - - public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) { - this.configurationParameters = Preconditions.notNull(configurationParameters, - "ConfigurationParameters must not be null"); - } - - @Override - public Optional getRawConfigurationParameter(String key) { - return configurationParameters.get(key); - } - - @Override - public Optional getRawConfigurationParameter(String key, Function transformer) { - return configurationParameters.get(key, transformer); - } - - @Override - public boolean isParallelExecutionEnabled() { - return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false); - } - - @Override - public boolean isExtensionAutoDetectionEnabled() { - return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false); - } - - @Override - public ExecutionMode getDefaultExecutionMode() { - return executionModeConverter.get(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, - ExecutionMode.SAME_THREAD); - } - - @Override - public ExecutionMode getDefaultClassesExecutionMode() { - return executionModeConverter.get(configurationParameters, DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, - getDefaultExecutionMode()); - } - - @Override - public Lifecycle getDefaultTestInstanceLifecycle() { - return lifecycleConverter.get(configurationParameters, DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, - Lifecycle.PER_METHOD); - } - - @Override - public Predicate getExecutionConditionFilter() { - return ClassNamePatternFilterUtils.excludeMatchingClasses( - configurationParameters.get(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME).orElse(null)); - } - - @Override - public DisplayNameGenerator getDefaultDisplayNameGenerator() { - return displayNameGeneratorConverter.get(configurationParameters, DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME) // - .orElseGet(() -> DisplayNameGenerator.getDisplayNameGenerator(DisplayNameGenerator.Standard.class)); - } - - @Override - public Optional getDefaultTestMethodOrderer() { - return methodOrdererConverter.get(configurationParameters, DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME); - } - - @Override - public Optional getDefaultTestClassOrderer() { - return classOrdererConverter.get(configurationParameters, DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME); - } - - @Override - public CleanupMode getDefaultTempDirCleanupMode() { - return cleanupModeConverter.get(configurationParameters, DEFAULT_CLEANUP_MODE_PROPERTY_NAME, ALWAYS); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java deleted file mode 100644 index 75fd3f99..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Locale; -import java.util.Optional; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * @since 5.4 - */ -@API(status = INTERNAL, since = "5.8") -public class EnumConfigurationParameterConverter> { - - private static final Logger logger = LoggerFactory.getLogger(EnumConfigurationParameterConverter.class); - - private final Class enumType; - private final String enumDisplayName; - - public EnumConfigurationParameterConverter(Class enumType, String enumDisplayName) { - this.enumType = enumType; - this.enumDisplayName = enumDisplayName; - } - - E get(ConfigurationParameters configParams, String key, E defaultValue) { - Preconditions.notNull(configParams, "ConfigurationParameters must not be null"); - - return get(key, configParams::get, defaultValue); - } - - public E get(String key, Function> lookup, E defaultValue) { - - Optional value = lookup.apply(key); - - if (value.isPresent()) { - String constantName = null; - try { - constantName = value.get().trim().toUpperCase(Locale.ROOT); - E result = Enum.valueOf(enumType, constantName); - logger.config(() -> String.format("Using %s '%s' set via the '%s' configuration parameter.", - enumDisplayName, result, key)); - return result; - } - catch (Exception ex) { - // local copy necessary for use in lambda expression - String constant = constantName; - logger.warn(() -> String.format( - "Invalid %s '%s' set via the '%s' configuration parameter. " - + "Falling back to the %s default value.", - enumDisplayName, constant, key, defaultValue.name())); - } - } - - return defaultValue; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java deleted file mode 100644 index 7f77d471..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import java.util.Optional; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * @since 5.5 - */ -class InstantiatingConfigurationParameterConverter { - - private static final Logger logger = LoggerFactory.getLogger(InstantiatingConfigurationParameterConverter.class); - - private final Class clazz; - private final String name; - - public InstantiatingConfigurationParameterConverter(Class clazz, String name) { - this.clazz = clazz; - this.name = name; - } - - Optional get(ConfigurationParameters configurationParameters, String key) { - // @formatter:off - return configurationParameters.get(key) - .map(String::trim) - .filter(className -> !className.isEmpty()) - .flatMap(className -> newInstance(className, key)); - // @formatter:on - } - - private Optional newInstance(String className, String key) { - // @formatter:off - return ReflectionUtils.tryToLoadClass(className) - .andThenTry(ReflectionUtils::newInstance) - .andThenTry(this.clazz::cast) - .ifSuccess(generator -> logSuccessMessage(className, key)) - .ifFailure(cause -> logFailureMessage(className, key, cause)) - .toOptional(); - // @formatter:on - } - - private void logFailureMessage(String className, String key, Exception cause) { - logger.warn(cause, - () -> String.format("Failed to load default %s class '%s' set via the '%s' configuration parameter." - + " Falling back to default behavior.", - this.name, className, key)); - } - - private void logSuccessMessage(String className, String key) { - logger.config(() -> String.format("Using default %s '%s' set via the '%s' configuration parameter.", this.name, - className, key)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java deleted file mode 100644 index d0027c15..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; - -/** - * @since 5.4 - */ -@API(status = INTERNAL, since = "5.4") -public interface JupiterConfiguration { - - String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate"; - String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; - String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; - String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; - String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; - String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; - String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; - String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; - String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; - - Optional getRawConfigurationParameter(String key); - - Optional getRawConfigurationParameter(String key, Function transformer); - - boolean isParallelExecutionEnabled(); - - boolean isExtensionAutoDetectionEnabled(); - - ExecutionMode getDefaultExecutionMode(); - - ExecutionMode getDefaultClassesExecutionMode(); - - TestInstance.Lifecycle getDefaultTestInstanceLifecycle(); - - Predicate getExecutionConditionFilter(); - - DisplayNameGenerator getDefaultDisplayNameGenerator(); - - Optional getDefaultTestMethodOrderer(); - - Optional getDefaultTestClassOrderer(); - - CleanupMode getDefaultTempDirCleanupMode(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java deleted file mode 100644 index ede68088..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Configuration specific to the JUnit Jupiter test engine. - */ - -package org.junit.jupiter.engine.config; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java deleted file mode 100644 index 90b6a932..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toCollection; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.ExtensionValuesStore; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.hierarchical.Node; - -/** - * @since 5.0 - */ -abstract class AbstractExtensionContext implements ExtensionContext, AutoCloseable { - - private final ExtensionContext parent; - private final EngineExecutionListener engineExecutionListener; - private final T testDescriptor; - private final Set tags; - private final JupiterConfiguration configuration; - private final ExtensionValuesStore valuesStore; - private final ExecutableInvoker executableInvoker; - - AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, - JupiterConfiguration configuration, ExecutableInvoker executableInvoker) { - this.executableInvoker = executableInvoker; - - Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); - Preconditions.notNull(configuration, "JupiterConfiguration must not be null"); - - this.parent = parent; - this.engineExecutionListener = engineExecutionListener; - this.testDescriptor = testDescriptor; - this.configuration = configuration; - this.valuesStore = createStore(parent); - - // @formatter:off - this.tags = testDescriptor.getTags().stream() - .map(TestTag::getName) - .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); - // @formatter:on - } - - private ExtensionValuesStore createStore(ExtensionContext parent) { - ExtensionValuesStore parentStore = null; - if (parent != null) { - parentStore = ((AbstractExtensionContext) parent).valuesStore; - } - return new ExtensionValuesStore(parentStore); - } - - @Override - public void close() { - this.valuesStore.closeAllStoredCloseableValues(); - } - - @Override - public String getUniqueId() { - return getTestDescriptor().getUniqueId().toString(); - } - - @Override - public String getDisplayName() { - return getTestDescriptor().getDisplayName(); - } - - @Override - public void publishReportEntry(Map values) { - this.engineExecutionListener.reportingEntryPublished(this.testDescriptor, ReportEntry.from(values)); - } - - @Override - public Optional getParent() { - return Optional.ofNullable(this.parent); - } - - @Override - public ExtensionContext getRoot() { - if (this.parent != null) { - return this.parent.getRoot(); - } - return this; - } - - protected T getTestDescriptor() { - return this.testDescriptor; - } - - @Override - public Store getStore(Namespace namespace) { - Preconditions.notNull(namespace, "Namespace must not be null"); - return new NamespaceAwareStore(this.valuesStore, namespace); - } - - @Override - public Set getTags() { - // return modifiable copy - return new LinkedHashSet<>(this.tags); - } - - @Override - public Optional getConfigurationParameter(String key) { - return this.configuration.getRawConfigurationParameter(key); - } - - @Override - public Optional getConfigurationParameter(String key, Function transformer) { - return this.configuration.getRawConfigurationParameter(key, transformer); - } - - @Override - public ExecutionMode getExecutionMode() { - return toJupiterExecutionMode(getPlatformExecutionMode()); - } - - @Override - public ExecutableInvoker getExecutableInvoker() { - return executableInvoker; - } - - protected abstract Node.ExecutionMode getPlatformExecutionMode(); - - private ExecutionMode toJupiterExecutionMode(Node.ExecutionMode mode) { - switch (mode) { - case CONCURRENT: - return ExecutionMode.CONCURRENT; - case SAME_THREAD: - return ExecutionMode.SAME_THREAD; - } - throw new JUnitException("Unknown ExecutionMode: " + mode); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java deleted file mode 100644 index d1fed951..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java +++ /dev/null @@ -1,524 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.stream.Collectors.joining; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromConstructorParameters; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromExecutableParameters; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromFields; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods; -import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; -import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; -import org.junit.jupiter.api.extension.TestInstanceFactory; -import org.junit.jupiter.api.extension.TestInstanceFactoryContext; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.api.extension.TestInstancePreConstructCallback; -import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.api.extension.TestInstantiationException; -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; -import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; -import org.junit.jupiter.engine.execution.DefaultTestInstances; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall.VoidMethodInterceptorCall; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.execution.TestInstancesProvider; -import org.junit.jupiter.engine.extension.ExtensionRegistrar; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * {@link TestDescriptor} for tests based on Java classes. - * - * @since 5.5 - */ -@API(status = INTERNAL, since = "5.5") -public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor { - - private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); - - private final Class testClass; - protected final Set tags; - protected final Lifecycle lifecycle; - - private ExecutionMode defaultChildExecutionMode; - private TestInstanceFactory testInstanceFactory; - private List beforeAllMethods; - private List afterAllMethods; - - ClassBasedTestDescriptor(UniqueId uniqueId, Class testClass, Supplier displayNameSupplier, - JupiterConfiguration configuration) { - super(uniqueId, testClass, displayNameSupplier, ClassSource.from(testClass), configuration); - - this.testClass = testClass; - this.tags = getTags(testClass); - this.lifecycle = getTestInstanceLifecycle(testClass, configuration); - this.defaultChildExecutionMode = (this.lifecycle == Lifecycle.PER_CLASS ? ExecutionMode.SAME_THREAD : null); - } - - // --- TestDescriptor ------------------------------------------------------ - - public final Class getTestClass() { - return this.testClass; - } - - public abstract List> getEnclosingTestClasses(); - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public String getLegacyReportingName() { - return this.testClass.getName(); - } - - // --- Node ---------------------------------------------------------------- - - @Override - protected Optional getExplicitExecutionMode() { - return getExecutionModeFromAnnotation(getTestClass()); - } - - @Override - protected Optional getDefaultChildExecutionMode() { - return Optional.ofNullable(this.defaultChildExecutionMode); - } - - public void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode) { - this.defaultChildExecutionMode = defaultChildExecutionMode; - } - - @Override - public Set getExclusiveResources() { - return getExclusiveResourcesFromAnnotation(getTestClass()); - } - - @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( - context.getExtensionRegistry(), this.testClass); - - // Register extensions from static fields here, at the class level but - // after extensions registered via @ExtendWith. - registerExtensionsFromFields(registry, this.testClass, null); - - // Resolve the TestInstanceFactory at the class level in order to fail - // the entire class in case of configuration errors (e.g., more than - // one factory registered per class). - this.testInstanceFactory = resolveTestInstanceFactory(registry); - - if (this.testInstanceFactory == null) { - registerExtensionsFromConstructorParameters(registry, this.testClass); - } - - this.beforeAllMethods = findBeforeAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD); - this.afterAllMethods = findAfterAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD); - - this.beforeAllMethods.forEach(method -> registerExtensionsFromExecutableParameters(registry, method)); - // Since registerBeforeEachMethodAdapters() and registerAfterEachMethodAdapters() also - // invoke registerExtensionsFromExecutableParameters(), we invoke those methods before - // invoking registerExtensionsFromExecutableParameters() for @AfterAll methods, - // thereby ensuring proper registration order for extensions registered via @ExtendWith - // on parameters in lifecycle methods. - registerBeforeEachMethodAdapters(registry); - registerAfterEachMethodAdapters(registry); - this.afterAllMethods.forEach(method -> registerExtensionsFromExecutableParameters(registry, method)); - - ThrowableCollector throwableCollector = createThrowableCollector(); - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); - ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(), - context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), throwableCollector, - executableInvoker); - - // @formatter:off - return context.extend() - .withTestInstancesProvider(testInstancesProvider(context, extensionContext)) - .withExtensionRegistry(registry) - .withExtensionContext(extensionContext) - .withThrowableCollector(throwableCollector) - .build(); - // @formatter:on - } - - @Override - public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) { - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - if (isPerClassLifecycle(context)) { - // Eagerly load test instance for BeforeAllCallbacks, if necessary, - // and store the instance in the ExtensionContext. - ClassExtensionContext extensionContext = (ClassExtensionContext) context.getExtensionContext(); - throwableCollector.execute(() -> { - TestInstances testInstances = context.getTestInstancesProvider().getTestInstances( - context.getExtensionRegistry(), throwableCollector); - extensionContext.setTestInstances(testInstances); - }); - } - - if (throwableCollector.isEmpty()) { - context.beforeAllCallbacksExecuted(true); - invokeBeforeAllCallbacks(context); - - if (throwableCollector.isEmpty()) { - context.beforeAllMethodsExecuted(true); - invokeBeforeAllMethods(context); - } - } - - throwableCollector.assertEmpty(); - - return context; - } - - @Override - public void after(JupiterEngineExecutionContext context) { - - ThrowableCollector throwableCollector = context.getThrowableCollector(); - Throwable previousThrowable = throwableCollector.getThrowable(); - - if (context.beforeAllMethodsExecuted()) { - invokeAfterAllMethods(context); - } - - if (context.beforeAllCallbacksExecuted()) { - invokeAfterAllCallbacks(context); - } - - if (isPerClassLifecycle(context) && context.getExtensionContext().getTestInstance().isPresent()) { - invokeTestInstancePreDestroyCallbacks(context); - } - - // If the previous Throwable was not null when this method was called, - // that means an exception was already thrown either before or during - // the execution of this Node. If an exception was already thrown, any - // later exceptions were added as suppressed exceptions to that original - // exception unless a more severe exception occurred in the meantime. - if (previousThrowable != throwableCollector.getThrowable()) { - throwableCollector.assertEmpty(); - } - } - - private TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registry) { - List factories = registry.getExtensions(TestInstanceFactory.class); - - if (factories.size() == 1) { - return factories.get(0); - } - - if (factories.size() > 1) { - String factoryNames = factories.stream()// - .map(factory -> factory.getClass().getName())// - .collect(joining(", ")); - - String errorMessage = String.format( - "The following TestInstanceFactory extensions were registered for test class [%s], but only one is permitted: %s", - testClass.getName(), factoryNames); - - throw new ExtensionConfigurationException(errorMessage); - } - - return null; - } - - private TestInstancesProvider testInstancesProvider(JupiterEngineExecutionContext parentExecutionContext, - ClassExtensionContext extensionContext) { - - return (registry, registrar, throwableCollector) -> extensionContext.getTestInstances().orElseGet( - () -> instantiateAndPostProcessTestInstance(parentExecutionContext, extensionContext, registry, registrar, - throwableCollector)); - } - - private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecutionContext parentExecutionContext, - ExtensionContext extensionContext, ExtensionRegistry registry, ExtensionRegistrar registrar, - ThrowableCollector throwableCollector) { - - TestInstances instances = instantiateTestClass(parentExecutionContext, registry, registrar, extensionContext, - throwableCollector); - throwableCollector.execute(() -> { - invokeTestInstancePostProcessors(instances.getInnermostInstance(), registry, extensionContext); - // In addition, we register extensions from instance fields here since the - // best time to do that is immediately following test class instantiation - // and post processing. - registerExtensionsFromFields(registrar, this.testClass, instances.getInnermostInstance()); - }); - return instances; - } - - protected abstract TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, - ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, - ThrowableCollector throwableCollector); - - protected TestInstances instantiateTestClass(Optional outerInstances, ExtensionRegistry registry, - ExtensionContext extensionContext) { - - Optional outerInstance = outerInstances.map(TestInstances::getInnermostInstance); - invokeTestInstancePreConstructCallbacks(new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), - registry, extensionContext); - Object instance = this.testInstanceFactory != null // - ? invokeTestInstanceFactory(outerInstance, extensionContext) // - : invokeTestClassConstructor(outerInstance, registry, extensionContext); - return outerInstances.map(instances -> DefaultTestInstances.of(instances, instance)).orElse( - DefaultTestInstances.of(instance)); - } - - private Object invokeTestInstanceFactory(Optional outerInstance, ExtensionContext extensionContext) { - Object instance; - - try { - instance = this.testInstanceFactory.createTestInstance( - new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), extensionContext); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - - if (throwable instanceof TestInstantiationException) { - throw (TestInstantiationException) throwable; - } - - String message = String.format("TestInstanceFactory [%s] failed to instantiate test class [%s]", - this.testInstanceFactory.getClass().getName(), this.testClass.getName()); - if (StringUtils.isNotBlank(throwable.getMessage())) { - message += ": " + throwable.getMessage(); - } - throw new TestInstantiationException(message, throwable); - } - - if (!this.testClass.isInstance(instance)) { - String testClassName = this.testClass.getName(); - Class instanceClass = (instance == null ? null : instance.getClass()); - String instanceClassName = (instanceClass == null ? "null" : instanceClass.getName()); - - // If the test instance was loaded via a different ClassLoader, append - // the identity hash codes to the type names to help users disambiguate - // between otherwise identical "fully qualified class names". - if (testClassName.equals(instanceClassName)) { - testClassName += "@" + Integer.toHexString(System.identityHashCode(this.testClass)); - instanceClassName += "@" + Integer.toHexString(System.identityHashCode(instanceClass)); - } - String message = String.format( - "TestInstanceFactory [%s] failed to return an instance of [%s] and instead returned an instance of [%s].", - this.testInstanceFactory.getClass().getName(), testClassName, instanceClassName); - - throw new TestInstantiationException(message); - } - - return instance; - } - - private Object invokeTestClassConstructor(Optional outerInstance, ExtensionRegistry registry, - ExtensionContext extensionContext) { - - Constructor constructor = ReflectionUtils.getDeclaredConstructor(this.testClass); - return executableInvoker.invoke(constructor, outerInstance, extensionContext, registry, - InvocationInterceptor::interceptTestClassConstructor); - } - - private void invokeTestInstancePreConstructCallbacks(TestInstanceFactoryContext factoryContext, - ExtensionRegistry registry, ExtensionContext context) { - registry.stream(TestInstancePreConstructCallback.class).forEach( - extension -> executeAndMaskThrowable(() -> extension.preConstructTestInstance(factoryContext, context))); - } - - private void invokeTestInstancePostProcessors(Object instance, ExtensionRegistry registry, - ExtensionContext context) { - - registry.stream(TestInstancePostProcessor.class).forEach( - extension -> executeAndMaskThrowable(() -> extension.postProcessTestInstance(instance, context))); - } - - private void executeAndMaskThrowable(Executable executable) { - try { - executable.execute(); - } - catch (Throwable throwable) { - ExceptionUtils.throwAsUncheckedException(throwable); - } - } - - private void invokeBeforeAllCallbacks(JupiterEngineExecutionContext context) { - ExtensionRegistry registry = context.getExtensionRegistry(); - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - for (BeforeAllCallback callback : registry.getExtensions(BeforeAllCallback.class)) { - throwableCollector.execute(() -> callback.beforeAll(extensionContext)); - if (throwableCollector.isNotEmpty()) { - break; - } - } - } - - private void invokeBeforeAllMethods(JupiterEngineExecutionContext context) { - ExtensionRegistry registry = context.getExtensionRegistry(); - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - Object testInstance = extensionContext.getTestInstance().orElse(null); - - for (Method method : this.beforeAllMethods) { - throwableCollector.execute(() -> { - try { - executableInvoker.invoke(method, testInstance, extensionContext, registry, - ReflectiveInterceptorCall.ofVoidMethod(InvocationInterceptor::interceptBeforeAllMethod)); - } - catch (Throwable throwable) { - invokeBeforeAllMethodExecutionExceptionHandlers(registry, extensionContext, throwable); - } - }); - if (throwableCollector.isNotEmpty()) { - break; - } - } - } - - private void invokeBeforeAllMethodExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, - Throwable throwable) { - - invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, - (handler, handledThrowable) -> handler.handleBeforeAllMethodExecutionException(context, handledThrowable)); - } - - private void invokeAfterAllMethods(JupiterEngineExecutionContext context) { - ExtensionRegistry registry = context.getExtensionRegistry(); - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - Object testInstance = extensionContext.getTestInstance().orElse(null); - - this.afterAllMethods.forEach(method -> throwableCollector.execute(() -> { - try { - executableInvoker.invoke(method, testInstance, extensionContext, registry, - ReflectiveInterceptorCall.ofVoidMethod(InvocationInterceptor::interceptAfterAllMethod)); - } - catch (Throwable throwable) { - invokeAfterAllMethodExecutionExceptionHandlers(registry, extensionContext, throwable); - } - })); - } - - private void invokeAfterAllMethodExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, - Throwable throwable) { - - invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, - (handler, handledThrowable) -> handler.handleAfterAllMethodExecutionException(context, handledThrowable)); - } - - private void invokeAfterAllCallbacks(JupiterEngineExecutionContext context) { - ExtensionRegistry registry = context.getExtensionRegistry(); - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - registry.getReversedExtensions(AfterAllCallback.class)// - .forEach(extension -> throwableCollector.execute(() -> extension.afterAll(extensionContext))); - } - - private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext context) { - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - context.getExtensionRegistry().getReversedExtensions(TestInstancePreDestroyCallback.class).forEach( - extension -> throwableCollector.execute(() -> extension.preDestroyTestInstance(extensionContext))); - } - - private boolean isPerClassLifecycle(JupiterEngineExecutionContext context) { - return context.getExtensionContext().getTestInstanceLifecycle().orElse( - Lifecycle.PER_METHOD) == Lifecycle.PER_CLASS; - } - - private void registerBeforeEachMethodAdapters(ExtensionRegistrar registrar) { - List beforeEachMethods = findBeforeEachMethods(this.testClass); - registerMethodsAsExtensions(beforeEachMethods, registrar, this::synthesizeBeforeEachMethodAdapter); - } - - private void registerAfterEachMethodAdapters(ExtensionRegistrar registrar) { - // Make a local copy since findAfterEachMethods() returns an immutable list. - List afterEachMethods = new ArrayList<>(findAfterEachMethods(this.testClass)); - - // Since the bottom-up ordering of afterEachMethods will later be reversed when the - // synthesized AfterEachMethodAdapters are executed within TestMethodTestDescriptor, - // we have to reverse the afterEachMethods list to put them in top-down order before - // we register them as synthesized extensions. - Collections.reverse(afterEachMethods); - - registerMethodsAsExtensions(afterEachMethods, registrar, this::synthesizeAfterEachMethodAdapter); - } - - private void registerMethodsAsExtensions(List methods, ExtensionRegistrar registrar, - Function extensionSynthesizer) { - - methods.forEach(method -> { - registerExtensionsFromExecutableParameters(registrar, method); - registrar.registerSyntheticExtension(extensionSynthesizer.apply(method), method); - }); - } - - private BeforeEachMethodAdapter synthesizeBeforeEachMethodAdapter(Method method) { - return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry, - InvocationInterceptor::interceptBeforeEachMethod); - } - - private AfterEachMethodAdapter synthesizeAfterEachMethodAdapter(Method method) { - return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry, - InvocationInterceptor::interceptAfterEachMethod); - } - - private void invokeMethodInExtensionContext(Method method, ExtensionContext context, ExtensionRegistry registry, - VoidMethodInterceptorCall interceptorCall) { - TestInstances testInstances = context.getRequiredTestInstances(); - Object target = testInstances.findInstance(this.testClass).orElseThrow( - () -> new JUnitException("Failed to find instance for method: " + method.toGenericString())); - - executableInvoker.invoke(method, target, context, registry, - ReflectiveInterceptorCall.ofVoidMethod(interceptorCall)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java deleted file mode 100644 index 57d40a86..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.support.hierarchical.Node; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * @since 5.0 - */ -final class ClassExtensionContext extends AbstractExtensionContext { - - private final Lifecycle lifecycle; - - private final ThrowableCollector throwableCollector; - - private TestInstances testInstances; - - /** - * Create a new {@code ClassExtensionContext} with {@link Lifecycle#PER_METHOD}. - * - * @see #ClassExtensionContext(ExtensionContext, EngineExecutionListener, ClassBasedTestDescriptor, - * Lifecycle, JupiterConfiguration, ThrowableCollector, ExecutableInvoker) - */ - ClassExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, - ClassBasedTestDescriptor testDescriptor, JupiterConfiguration configuration, - ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) { - - this(parent, engineExecutionListener, testDescriptor, Lifecycle.PER_METHOD, configuration, throwableCollector, - executableInvoker); - } - - ClassExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, - ClassBasedTestDescriptor testDescriptor, Lifecycle lifecycle, JupiterConfiguration configuration, - ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) { - - super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); - - this.lifecycle = lifecycle; - this.throwableCollector = throwableCollector; - } - - @Override - public Optional getElement() { - return Optional.of(getTestDescriptor().getTestClass()); - } - - @Override - public Optional> getTestClass() { - return Optional.of(getTestDescriptor().getTestClass()); - } - - @Override - public Optional getTestInstanceLifecycle() { - return Optional.of(this.lifecycle); - } - - @Override - public Optional getTestInstance() { - return getTestInstances().map(TestInstances::getInnermostInstance); - } - - @Override - public Optional getTestInstances() { - return Optional.ofNullable(testInstances); - } - - void setTestInstances(TestInstances testInstances) { - this.testInstances = testInstances; - } - - @Override - public Optional getTestMethod() { - return Optional.empty(); - } - - @Override - public Optional getExecutionException() { - return Optional.ofNullable(this.throwableCollector.getThrowable()); - } - - @Override - protected Node.ExecutionMode getPlatformExecutionMode() { - return getTestDescriptor().getExecutionMode(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java deleted file mode 100644 index 22961298..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.Collections.emptyList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistrar; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * {@link TestDescriptor} for tests based on Java classes. - * - *

Default Display Names

- * - *

The default display name for a top-level or nested static test class is - * the fully qualified name of the class with the package name and leading dot - * (".") removed. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class ClassTestDescriptor extends ClassBasedTestDescriptor { - - public static final String SEGMENT_TYPE = "class"; - - public ClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { - super(uniqueId, testClass, createDisplayNameSupplierForClass(testClass, configuration), configuration); - } - - // --- TestDescriptor ------------------------------------------------------ - - @Override - public Set getTags() { - // return modifiable copy - return new LinkedHashSet<>(this.tags); - } - - @Override - public List> getEnclosingTestClasses() { - return emptyList(); - } - - // --- Node ---------------------------------------------------------------- - - @Override - public ExecutionMode getExecutionMode() { - return getExplicitExecutionMode().orElseGet( - () -> JupiterTestDescriptor.toExecutionMode(configuration.getDefaultClassesExecutionMode())); - } - - @Override - protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, - ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, - ThrowableCollector throwableCollector) { - return instantiateTestClass(Optional.empty(), registry, extensionContext); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java deleted file mode 100644 index f6e272bc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import org.junit.jupiter.api.extension.DynamicTestInvocationContext; -import org.junit.jupiter.api.function.Executable; - -/** - * Default implementation of the {@link DynamicTestInvocationContext} API. - * - * @since 5.8 - */ -class DefaultDynamicTestInvocationContext implements DynamicTestInvocationContext { - - private final Executable executable; - - DefaultDynamicTestInvocationContext(Executable executable) { - this.executable = executable; - } - - @Override - public Executable getExecutable() { - return this.executable; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java deleted file mode 100644 index affdf5b5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.util.Optional; - -import org.junit.jupiter.api.extension.TestInstanceFactoryContext; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Default implementation of the {@link TestInstanceFactoryContext} API. - * - * @since 5.3 - */ -class DefaultTestInstanceFactoryContext implements TestInstanceFactoryContext { - - private final Class testClass; - private final Optional outerInstance; - - DefaultTestInstanceFactoryContext(Class testClass, Optional outerInstance) { - this.testClass = testClass; - this.outerInstance = outerInstance; - } - - @Override - public Class getTestClass() { - return this.testClass; - } - - @Override - public Optional getOuterInstance() { - return this.outerInstance; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("testClass", this.testClass) - .append("outerInstance", this.outerInstance) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java deleted file mode 100644 index 7e2b3130..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.function.Supplier; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.DisplayNameGenerator.Simple; -import org.junit.jupiter.api.DisplayNameGenerator.Standard; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; - -/** - * Collection of utilities for working with display names. - * - * @since 5.4 - * @see DisplayName - * @see DisplayNameGenerator - * @see DisplayNameGeneration - */ -final class DisplayNameUtils { - - private static final Logger logger = LoggerFactory.getLogger(DisplayNameUtils.class); - - /** - * Pre-defined standard display name generator instance. - */ - private static final DisplayNameGenerator standardGenerator = DisplayNameGenerator.getDisplayNameGenerator( - Standard.class); - - /** - * Pre-defined simple display name generator instance. - */ - private static final DisplayNameGenerator simpleGenerator = DisplayNameGenerator.getDisplayNameGenerator( - Simple.class); - - /** - * Pre-defined display name generator instance replacing underscores. - */ - private static final DisplayNameGenerator replaceUnderscoresGenerator = DisplayNameGenerator.getDisplayNameGenerator( - ReplaceUnderscores.class); - - /** - * Pre-defined display name generator instance producing indicative sentences. - */ - private static final DisplayNameGenerator indicativeSentencesGenerator = DisplayNameGenerator.getDisplayNameGenerator( - IndicativeSentences.class); - - static String determineDisplayName(AnnotatedElement element, Supplier displayNameSupplier) { - Preconditions.notNull(element, "Annotated element must not be null"); - Optional displayNameAnnotation = findAnnotation(element, DisplayName.class); - if (displayNameAnnotation.isPresent()) { - String displayName = displayNameAnnotation.get().value().trim(); - - // TODO [#242] Replace logging with precondition check once we have a proper mechanism for - // handling validation exceptions during the TestEngine discovery phase. - if (StringUtils.isBlank(displayName)) { - logger.warn(() -> String.format( - "Configuration error: @DisplayName on [%s] must be declared with a non-empty value.", element)); - } - else { - return displayName; - } - } - // else let a 'DisplayNameGenerator' generate a display name - return displayNameSupplier.get(); - } - - static String determineDisplayNameForMethod(Class testClass, Method testMethod, - JupiterConfiguration configuration) { - DisplayNameGenerator generator = getDisplayNameGenerator(testClass, configuration); - return determineDisplayName(testMethod, () -> generator.generateDisplayNameForMethod(testClass, testMethod)); - } - - static Supplier createDisplayNameSupplierForClass(Class testClass, JupiterConfiguration configuration) { - return () -> getDisplayNameGenerator(testClass, configuration).generateDisplayNameForClass(testClass); - } - - static Supplier createDisplayNameSupplierForNestedClass(Class testClass, - JupiterConfiguration configuration) { - return () -> getDisplayNameGenerator(testClass, configuration).generateDisplayNameForNestedClass(testClass); - } - - private static DisplayNameGenerator getDisplayNameGenerator(Class testClass, - JupiterConfiguration configuration) { - Preconditions.notNull(testClass, "Test class must not be null"); - - return AnnotationUtils.findAnnotation(testClass, DisplayNameGeneration.class, true) // - .map(DisplayNameGeneration::value) // - .map(displayNameGeneratorClass -> { - if (displayNameGeneratorClass == Standard.class) { - return standardGenerator; - } - if (displayNameGeneratorClass == Simple.class) { - return simpleGenerator; - } - if (displayNameGeneratorClass == ReplaceUnderscores.class) { - return replaceUnderscoresGenerator; - } - if (displayNameGeneratorClass == IndicativeSentences.class) { - return indicativeSentencesGenerator; - } - return ReflectionUtils.newInstance(displayNameGeneratorClass); - }) // - .orElseGet(configuration::getDefaultDisplayNameGenerator); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java deleted file mode 100644 index a72981df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.createDynamicDescriptor; - -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicContainer; -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; - -/** - * {@link TestDescriptor} for a {@link DynamicContainer}. - * - * @since 5.0 - */ -class DynamicContainerTestDescriptor extends DynamicNodeTestDescriptor { - - private final DynamicContainer dynamicContainer; - private final TestSource testSource; - private final DynamicDescendantFilter dynamicDescendantFilter; - - DynamicContainerTestDescriptor(UniqueId uniqueId, int index, DynamicContainer dynamicContainer, - TestSource testSource, DynamicDescendantFilter dynamicDescendantFilter, - JupiterConfiguration configuration) { - - super(uniqueId, index, dynamicContainer, testSource, configuration); - this.dynamicContainer = dynamicContainer; - this.testSource = testSource; - this.dynamicDescendantFilter = dynamicDescendantFilter; - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, - DynamicTestExecutor dynamicTestExecutor) throws Exception { - - AtomicInteger index = new AtomicInteger(1); - try (Stream children = dynamicContainer.getChildren()) { - // @formatter:off - children.map(child -> { - Preconditions.notNull(child, "individual dynamic node must not be null"); - return toDynamicDescriptor(index.getAndIncrement(), child); - }) - .filter(Optional::isPresent) - .map(Optional::get) - .forEachOrdered(dynamicTestExecutor::execute); - // @formatter:on - } - return context; - } - - private Optional toDynamicDescriptor(int index, DynamicNode childNode) { - return createDynamicDescriptor(this, childNode, index, testSource, dynamicDescendantFilter, configuration); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java deleted file mode 100644 index 48e45e0b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.HashSet; -import java.util.Set; -import java.util.function.BiPredicate; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; - -/** - * Filter for dynamic descendants of {@link TestDescriptor TestDescriptors} that - * implement {@link Filterable}. - * - * @since 5.1 - * @see Filterable - */ -@API(status = INTERNAL, since = "5.1") -public class DynamicDescendantFilter implements BiPredicate { - - private final Set allowedUniqueIds = new HashSet<>(); - private final Set allowedIndices = new HashSet<>(); - private Mode mode = Mode.EXPLICIT; - - public void allowUniqueIdPrefix(UniqueId uniqueId) { - if (this.mode == Mode.EXPLICIT) { - this.allowedUniqueIds.add(uniqueId); - } - } - - public void allowIndex(Set indices) { - if (this.mode == Mode.EXPLICIT) { - this.allowedIndices.addAll(indices); - } - } - - public void allowAll() { - this.mode = Mode.ALLOW_ALL; - this.allowedUniqueIds.clear(); - this.allowedIndices.clear(); - } - - @Override - public boolean test(UniqueId uniqueId, Integer index) { - return isEverythingAllowed() // - || isUniqueIdAllowed(uniqueId) // - || allowedIndices.contains(index); - } - - private boolean isEverythingAllowed() { - return allowedUniqueIds.isEmpty() && allowedIndices.isEmpty(); - } - - private boolean isUniqueIdAllowed(UniqueId uniqueId) { - return allowedUniqueIds.stream().anyMatch(allowedUniqueId -> isPrefixOrViceVersa(uniqueId, allowedUniqueId)); - } - - private boolean isPrefixOrViceVersa(UniqueId currentUniqueId, UniqueId allowedUniqueId) { - return allowedUniqueId.hasPrefix(currentUniqueId) || currentUniqueId.hasPrefix(allowedUniqueId); - } - - public DynamicDescendantFilter withoutIndexFiltering() { - return new WithoutIndexFiltering(); - } - - private enum Mode { - EXPLICIT, ALLOW_ALL - } - - private class WithoutIndexFiltering extends DynamicDescendantFilter { - - @Override - public boolean test(UniqueId uniqueId, Integer index) { - return isEverythingAllowed() || isUniqueIdAllowed(uniqueId); - } - - @Override - public DynamicDescendantFilter withoutIndexFiltering() { - return this; - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java deleted file mode 100644 index 92ec7b24..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.support.hierarchical.Node; - -class DynamicExtensionContext extends AbstractExtensionContext { - - DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, - DynamicNodeTestDescriptor testDescriptor, JupiterConfiguration configuration, - ExecutableInvoker executableInvoker) { - super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); - } - - @Override - public Optional getElement() { - return Optional.empty(); - } - - @Override - public Optional> getTestClass() { - return Optional.empty(); - } - - @Override - public Optional getTestInstanceLifecycle() { - return Optional.empty(); - } - - @Override - public Optional getTestInstance() { - return Optional.empty(); - } - - @Override - public Optional getTestInstances() { - return Optional.empty(); - } - - @Override - public Optional getTestMethod() { - return Optional.empty(); - } - - @Override - public Optional getExecutionException() { - return Optional.empty(); - } - - @Override - protected Node.ExecutionMode getPlatformExecutionMode() { - return getTestDescriptor().getExecutionMode(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java deleted file mode 100644 index bf5bde6c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; - -/** - * Base {@link TestDescriptor} for a {@link DynamicNode}. - * - * @since 5.0.3 - */ -abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor { - - private final int index; - - DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, TestSource testSource, - JupiterConfiguration configuration) { - super(uniqueId, dynamicNode.getDisplayName(), testSource, configuration); - this.index = index; - } - - @Override - public String getLegacyReportingName() { - // @formatter:off - return getParent() - .map(TestDescriptor::getLegacyReportingName) - .orElseGet(this::getDisplayName) - + "[" + index + "]"; - // @formatter:on - } - - @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - DynamicExtensionContext extensionContext = new DynamicExtensionContext(context.getExtensionContext(), - context.getExecutionListener(), this, context.getConfiguration(), new DefaultExecutableInvoker(context)); - // @formatter:off - return context.extend() - .withExtensionContext(extensionContext) - .build(); - // @formatter:on - } - - @Override - public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { - return SkipResult.doNotSkip(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java deleted file mode 100644 index 3fc75d00..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.extension.DynamicTestInvocationContext; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.InvocationInterceptorChain; -import org.junit.jupiter.engine.execution.InvocationInterceptorChain.InterceptorCall; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; - -/** - * {@link TestDescriptor} for a {@link DynamicTest}. - * - * @since 5.0 - */ -class DynamicTestTestDescriptor extends DynamicNodeTestDescriptor { - - private static final InvocationInterceptorChain interceptorChain = new InvocationInterceptorChain(); - - private DynamicTest dynamicTest; - - DynamicTestTestDescriptor(UniqueId uniqueId, int index, DynamicTest dynamicTest, TestSource source, - JupiterConfiguration configuration) { - super(uniqueId, index, dynamicTest, source, configuration); - this.dynamicTest = dynamicTest; - } - - @Override - public Type getType() { - return Type.TEST; - } - - @Override - public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, - DynamicTestExecutor dynamicTestExecutor) { - InvocationInterceptor.Invocation invocation = () -> { - dynamicTest.getExecutable().execute(); - return null; - }; - DynamicTestInvocationContext dynamicTestInvocationContext = new DefaultDynamicTestInvocationContext( - dynamicTest.getExecutable()); - ExtensionContext extensionContext = context.getExtensionContext(); - ExtensionRegistry extensionRegistry = context.getExtensionRegistry(); - interceptorChain.invoke(invocation, extensionRegistry, InterceptorCall.ofVoid( - (interceptor, wrappedInvocation) -> interceptor.interceptDynamicTest(wrappedInvocation, - dynamicTestInvocationContext, extensionContext))); - return context; - } - - /** - * Avoid an {@link OutOfMemoryError} by releasing the reference to this - * descriptor's {@link DynamicTest} which holds a reference to the user-supplied - * {@link Executable} which may potentially consume large amounts of memory - * on the heap. - * - * @since 5.5 - * @see Issue 1865 - */ - @Override - public void after(JupiterEngineExecutionContext context) throws Exception { - super.after(context); - this.dynamicTest = null; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java deleted file mode 100644 index b444f632..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; -import static org.junit.platform.commons.util.ReflectionUtils.findFields; -import static org.junit.platform.commons.util.ReflectionUtils.getDeclaredConstructor; -import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Executable; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.engine.extension.ExtensionRegistrar; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Collection of utilities for working with extensions and the extension registry. - * - * @since 5.1 - * @see ExtensionRegistrar - * @see MutableExtensionRegistry - * @see ExtendWith - * @see RegisterExtension - */ -final class ExtensionUtils { - - private ExtensionUtils() { - /* no-op */ - } - - /** - * Populate a new {@link MutableExtensionRegistry} from extension types declared via - * {@link ExtendWith @ExtendWith} on the supplied {@link AnnotatedElement}. - * - * @param parentRegistry the parent extension registry to set in the newly - * created registry; never {@code null} - * @param annotatedElement the annotated element on which to search for - * declarations of {@code @ExtendWith}; never {@code null} - * - * @return the new extension registry; never {@code null} - * @since 5.0 - */ - static MutableExtensionRegistry populateNewExtensionRegistryFromExtendWithAnnotation( - MutableExtensionRegistry parentRegistry, AnnotatedElement annotatedElement) { - - Preconditions.notNull(parentRegistry, "Parent ExtensionRegistry must not be null"); - Preconditions.notNull(annotatedElement, "AnnotatedElement must not be null"); - - return MutableExtensionRegistry.createRegistryFrom(parentRegistry, streamExtensionTypes(annotatedElement)); - } - - /** - * Register extensions using the supplied registrar from fields in the supplied - * class that are annotated with {@link ExtendWith @ExtendWith} or - * {@link RegisterExtension @RegisterExtension}. - * - *

The extensions will be sorted according to {@link Order @Order} semantics - * prior to registration. - * - * @param registrar the registrar with which to register the extensions; never {@code null} - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param instance the instance of the supplied class; may be {@code null} - * when searching for {@code static} fields in the class - */ - static void registerExtensionsFromFields(ExtensionRegistrar registrar, Class clazz, Object instance) { - Preconditions.notNull(registrar, "ExtensionRegistrar must not be null"); - Preconditions.notNull(clazz, "Class must not be null"); - - Predicate predicate = (instance == null ? ReflectionUtils::isStatic : ReflectionUtils::isNotStatic); - - findFields(clazz, predicate, TOP_DOWN).stream()// - .sorted(orderComparator)// - .forEach(field -> { - List> extensionTypes = streamExtensionTypes(field).collect(toList()); - boolean isExtendWithPresent = !extensionTypes.isEmpty(); - boolean isRegisterExtensionPresent = isAnnotated(field, RegisterExtension.class); - if (isExtendWithPresent) { - extensionTypes.forEach(registrar::registerExtension); - } - if (isRegisterExtensionPresent) { - tryToReadFieldValue(field, instance).ifSuccess(value -> { - Preconditions.condition(value instanceof Extension, () -> String.format( - "Failed to register extension via @RegisterExtension field [%s]: field value's type [%s] must implement an [%s] API.", - field, (value != null ? value.getClass().getName() : null), Extension.class.getName())); - - if (isExtendWithPresent) { - Class valueType = value.getClass(); - extensionTypes.forEach(extensionType -> { - Preconditions.condition(!extensionType.equals(valueType), - () -> String.format("Failed to register extension via field [%s]. " - + "The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, " - + "but only one registration of a given extension type is permitted.", - field, valueType.getName())); - }); - } - - registrar.registerExtension((Extension) value, field); - }); - } - }); - } - - /** - * Register extensions using the supplied registrar from parameters in the - * declared constructor of the supplied class that are annotated with - * {@link ExtendWith @ExtendWith}. - * - * @param registrar the registrar with which to register the extensions; never {@code null} - * @param clazz the class in which to find the declared constructor; never {@code null} - * @since 5.8 - */ - static void registerExtensionsFromConstructorParameters(ExtensionRegistrar registrar, Class clazz) { - registerExtensionsFromExecutableParameters(registrar, getDeclaredConstructor(clazz)); - } - - /** - * Register extensions using the supplied registrar from parameters in the - * supplied {@link Executable} (i.e., a {@link java.lang.reflect.Constructor} - * or {@link java.lang.reflect.Method}) that are annotated with - * {@link ExtendWith @ExtendWith}. - * - * @param registrar the registrar with which to register the extensions; never {@code null} - * @param executable the constructor or method whose parameters should be searched; never {@code null} - * @since 5.8 - */ - static void registerExtensionsFromExecutableParameters(ExtensionRegistrar registrar, Executable executable) { - Preconditions.notNull(registrar, "ExtensionRegistrar must not be null"); - Preconditions.notNull(executable, "Executable must not be null"); - - AtomicInteger index = new AtomicInteger(); - - // @formatter:off - Arrays.stream(executable.getParameters()) - .map(parameter -> findRepeatableAnnotations(parameter, index.getAndIncrement(), ExtendWith.class)) - .flatMap(ExtensionUtils::streamExtensionTypes) - .forEach(registrar::registerExtension); - // @formatter:on - } - - /** - * @since 5.8 - */ - private static Stream> streamExtensionTypes(AnnotatedElement annotatedElement) { - return streamExtensionTypes(findRepeatableAnnotations(annotatedElement, ExtendWith.class)); - } - - /** - * @since 5.8 - */ - private static Stream> streamExtensionTypes(List extendWithAnnotations) { - return extendWithAnnotations.stream().map(ExtendWith::value).flatMap(Arrays::stream); - } - - /** - * @since 5.4 - */ - private static final Comparator orderComparator = // - Comparator.comparingInt(ExtensionUtils::getOrder); - - /** - * @since 5.4 - */ - private static int getOrder(Field field) { - return findAnnotation(field, Order.class).map(Order::value).orElse(Order.DEFAULT); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java deleted file mode 100644 index a9ca7ad1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; - -/** - * {@code Filterable} is implemented by - * {@link org.junit.platform.engine.TestDescriptor TestDescriptors} that may - * register dynamic tests during execution and support selective test execution. - * - * @since 5.1 - * @see DynamicDescendantFilter - */ -@API(status = INTERNAL, since = "5.1") -public interface Filterable { - - DynamicDescendantFilter getDynamicDescendantFilter(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java deleted file mode 100644 index 3a87753c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.toExecutionMode; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.engine.support.hierarchical.Node; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class JupiterEngineDescriptor extends EngineDescriptor implements Node { - - public static final String ENGINE_ID = "junit-jupiter"; - private final JupiterConfiguration configuration; - - public JupiterEngineDescriptor(UniqueId uniqueId, JupiterConfiguration configuration) { - super(uniqueId, "JUnit Jupiter"); - this.configuration = configuration; - } - - public JupiterConfiguration getConfiguration() { - return configuration; - } - - @Override - public ExecutionMode getExecutionMode() { - return toExecutionMode(configuration.getDefaultExecutionMode()); - } - - @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( - context.getConfiguration()); - EngineExecutionListener executionListener = context.getExecutionListener(); - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); - ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionListener, this, - context.getConfiguration(), executableInvoker); - - // @formatter:off - return context.extend() - .withExtensionRegistry(extensionRegistry) - .withExtensionContext(extensionContext) - .build(); - // @formatter:on - } - - @Override - public void cleanUp(JupiterEngineExecutionContext context) throws Exception { - context.close(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java deleted file mode 100644 index 26eb83f7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.support.hierarchical.Node; - -/** - * @since 5.0 - */ -final class JupiterEngineExtensionContext extends AbstractExtensionContext { - - JupiterEngineExtensionContext(EngineExecutionListener engineExecutionListener, - JupiterEngineDescriptor testDescriptor, JupiterConfiguration configuration, - ExecutableInvoker executableInvoker) { - - super(null, engineExecutionListener, testDescriptor, configuration, executableInvoker); - } - - @Override - public Optional getElement() { - return Optional.empty(); - } - - @Override - public Optional> getTestClass() { - return Optional.empty(); - } - - @Override - public Optional getTestInstanceLifecycle() { - return Optional.empty(); - } - - @Override - public Optional getTestInstance() { - return Optional.empty(); - } - - @Override - public Optional getTestInstances() { - return Optional.empty(); - } - - @Override - public Optional getTestMethod() { - return Optional.empty(); - } - - @Override - public Optional getExecutionException() { - return Optional.empty(); - } - - @Override - protected Node.ExecutionMode getPlatformExecutionMode() { - return getTestDescriptor().getExecutionMode(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java deleted file mode 100644 index 5492e8a6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toSet; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayName; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; - -import java.lang.reflect.AnnotatedElement; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ResourceAccessMode; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.ConditionEvaluator; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; -import org.junit.platform.engine.support.hierarchical.Node; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public abstract class JupiterTestDescriptor extends AbstractTestDescriptor - implements Node { - - private static final Logger logger = LoggerFactory.getLogger(JupiterTestDescriptor.class); - - private static final ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); - - final JupiterConfiguration configuration; - - JupiterTestDescriptor(UniqueId uniqueId, AnnotatedElement element, Supplier displayNameSupplier, - TestSource source, JupiterConfiguration configuration) { - this(uniqueId, determineDisplayName(element, displayNameSupplier), source, configuration); - } - - JupiterTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, - JupiterConfiguration configuration) { - super(uniqueId, displayName, source); - this.configuration = configuration; - } - - // --- TestDescriptor ------------------------------------------------------ - - static Set getTags(AnnotatedElement element) { - // @formatter:off - return findRepeatableAnnotations(element, Tag.class).stream() - .map(Tag::value) - .filter(tag -> { - boolean isValid = TestTag.isValid(tag); - if (!isValid) { - // TODO [#242] Replace logging with precondition check once we have a proper mechanism for - // handling validation exceptions during the TestEngine discovery phase. - // - // As an alternative to a precondition check here, we could catch any - // PreconditionViolationException thrown by TestTag::create. - logger.warn(() -> String.format( - "Configuration error: invalid tag syntax in @Tag(\"%s\") declaration on [%s]. Tag will be ignored.", - tag, element)); - } - return isValid; - }) - .map(TestTag::create) - .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); - // @formatter:on - } - - /** - * Invoke exception handlers for the supplied {@code Throwable} one-by-one - * until none are left or the throwable to handle has been swallowed. - */ - void invokeExecutionExceptionHandlers(Class handlerType, ExtensionRegistry registry, - Throwable throwable, ExceptionHandlerInvoker handlerInvoker) { - - invokeExecutionExceptionHandlers(registry.getReversedExtensions(handlerType), throwable, handlerInvoker); - } - - private void invokeExecutionExceptionHandlers(List exceptionHandlers, Throwable throwable, - ExceptionHandlerInvoker handlerInvoker) { - - // No handlers left? - if (exceptionHandlers.isEmpty()) { - ExceptionUtils.throwAsUncheckedException(throwable); - } - - try { - // Invoke next available handler - handlerInvoker.invoke(exceptionHandlers.remove(0), throwable); - } - catch (Throwable handledThrowable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(handledThrowable); - invokeExecutionExceptionHandlers(exceptionHandlers, handledThrowable, handlerInvoker); - } - } - - // --- Node ---------------------------------------------------------------- - - @Override - public ExecutionMode getExecutionMode() { - Optional executionMode = getExplicitExecutionMode(); - if (executionMode.isPresent()) { - return executionMode.get(); - } - Optional parent = getParent(); - while (parent.isPresent() && parent.get() instanceof JupiterTestDescriptor) { - JupiterTestDescriptor jupiterParent = (JupiterTestDescriptor) parent.get(); - executionMode = jupiterParent.getExplicitExecutionMode(); - if (executionMode.isPresent()) { - return executionMode.get(); - } - executionMode = jupiterParent.getDefaultChildExecutionMode(); - if (executionMode.isPresent()) { - return executionMode.get(); - } - parent = jupiterParent.getParent(); - } - return toExecutionMode(configuration.getDefaultExecutionMode()); - } - - Optional getExplicitExecutionMode() { - return Optional.empty(); - } - - Optional getDefaultChildExecutionMode() { - return Optional.empty(); - } - - Optional getExecutionModeFromAnnotation(AnnotatedElement element) { - // @formatter:off - return findAnnotation(element, Execution.class) - .map(Execution::value) - .map(JupiterTestDescriptor::toExecutionMode); - // @formatter:on - } - - public static ExecutionMode toExecutionMode(org.junit.jupiter.api.parallel.ExecutionMode mode) { - switch (mode) { - case CONCURRENT: - return ExecutionMode.CONCURRENT; - case SAME_THREAD: - return ExecutionMode.SAME_THREAD; - } - throw new JUnitException("Unknown ExecutionMode: " + mode); - } - - Set getExclusiveResourcesFromAnnotation(AnnotatedElement element) { - // @formatter:off - return findRepeatableAnnotations(element, ResourceLock.class).stream() - .map(resource -> new ExclusiveResource(resource.value(), toLockMode(resource.mode()))) - .collect(toSet()); - // @formatter:on - } - - private static LockMode toLockMode(ResourceAccessMode mode) { - switch (mode) { - case READ: - return LockMode.READ; - case READ_WRITE: - return LockMode.READ_WRITE; - } - throw new JUnitException("Unknown ResourceAccessMode: " + mode); - } - - @Override - public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) throws Exception { - context.getThrowableCollector().assertEmpty(); - ConditionEvaluationResult evaluationResult = conditionEvaluator.evaluate(context.getExtensionRegistry(), - context.getConfiguration(), context.getExtensionContext()); - return toSkipResult(evaluationResult); - } - - private SkipResult toSkipResult(ConditionEvaluationResult evaluationResult) { - if (evaluationResult.isDisabled()) { - return SkipResult.skip(evaluationResult.getReason().orElse("")); - } - return SkipResult.doNotSkip(); - } - - /** - * Must be overridden and return a new context so cleanUp() does not accidentally close the parent context. - */ - @Override - public abstract JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception; - - @Override - public void cleanUp(JupiterEngineExecutionContext context) throws Exception { - context.close(); - } - - /** - * @since 5.5 - */ - @FunctionalInterface - interface ExceptionHandlerInvoker { - - /** - * Invoke the supplied {@code exceptionHandler} with the supplied {@code throwable}. - */ - void invoke(E exceptionHandler, Throwable throwable) throws Throwable; - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java deleted file mode 100644 index 9adb38ff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedMethods; -import static org.junit.platform.commons.util.ReflectionUtils.returnsVoid; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; - -/** - * Collection of utilities for working with test lifecycle methods. - * - * @since 5.0 - */ -final class LifecycleMethodUtils { - - private LifecycleMethodUtils() { - /* no-op */ - } - - static List findBeforeAllMethods(Class testClass, boolean requireStatic) { - return findMethodsAndAssertStaticAndNonPrivate(testClass, requireStatic, BeforeAll.class, - HierarchyTraversalMode.TOP_DOWN); - } - - static List findAfterAllMethods(Class testClass, boolean requireStatic) { - return findMethodsAndAssertStaticAndNonPrivate(testClass, requireStatic, AfterAll.class, - HierarchyTraversalMode.BOTTOM_UP); - } - - static List findBeforeEachMethods(Class testClass) { - return findMethodsAndAssertNonStaticAndNonPrivate(testClass, BeforeEach.class, HierarchyTraversalMode.TOP_DOWN); - } - - static List findAfterEachMethods(Class testClass) { - return findMethodsAndAssertNonStaticAndNonPrivate(testClass, AfterEach.class, HierarchyTraversalMode.BOTTOM_UP); - } - - private static List findMethodsAndAssertStaticAndNonPrivate(Class testClass, boolean requireStatic, - Class annotationType, HierarchyTraversalMode traversalMode) { - - List methods = findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode); - if (requireStatic) { - methods.forEach(method -> assertStatic(annotationType, method)); - } - return methods; - } - - private static List findMethodsAndAssertNonStaticAndNonPrivate(Class testClass, - Class annotationType, HierarchyTraversalMode traversalMode) { - - List methods = findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode); - methods.forEach(method -> assertNonStatic(annotationType, method)); - return methods; - } - - private static List findMethodsAndCheckVoidReturnType(Class testClass, - Class annotationType, HierarchyTraversalMode traversalMode) { - - List methods = findAnnotatedMethods(testClass, annotationType, traversalMode); - methods.forEach(method -> assertVoid(annotationType, method)); - return methods; - } - - private static void assertStatic(Class annotationType, Method method) { - if (ReflectionUtils.isNotStatic(method)) { - throw new JUnitException(String.format( - "@%s method '%s' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).", - annotationType.getSimpleName(), method.toGenericString())); - } - } - - private static void assertNonStatic(Class annotationType, Method method) { - if (ReflectionUtils.isStatic(method)) { - throw new JUnitException(String.format("@%s method '%s' must not be static.", - annotationType.getSimpleName(), method.toGenericString())); - } - } - - private static void assertVoid(Class annotationType, Method method) { - if (!returnsVoid(method)) { - throw new JUnitException(String.format("@%s method '%s' must not return a value.", - annotationType.getSimpleName(), method.toGenericString())); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java deleted file mode 100644 index bb0d3907..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod; - -import java.lang.reflect.Method; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestWatcher; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource; - -/** - * Base class for {@link TestDescriptor TestDescriptors} based on Java methods. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor { - - private static final Logger logger = LoggerFactory.getLogger(MethodBasedTestDescriptor.class); - - private final Class testClass; - private final Method testMethod; - - /** - * Set of method-level tags; does not contain tags from parent. - */ - private final Set tags; - - MethodBasedTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod, - configuration); - } - - MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, displayName, MethodSource.from(testClass, testMethod), configuration); - - this.testClass = Preconditions.notNull(testClass, "Class must not be null"); - this.testMethod = testMethod; - this.tags = getTags(testMethod); - } - - @Override - public final Set getTags() { - // return modifiable copy - Set allTags = new LinkedHashSet<>(this.tags); - getParent().ifPresent(parentDescriptor -> allTags.addAll(parentDescriptor.getTags())); - return allTags; - } - - @Override - public Set getExclusiveResources() { - return getExclusiveResourcesFromAnnotation(getTestMethod()); - } - - @Override - protected Optional getExplicitExecutionMode() { - return getExecutionModeFromAnnotation(getTestMethod()); - } - - public final Class getTestClass() { - return this.testClass; - } - - public final Method getTestMethod() { - return this.testMethod; - } - - @Override - public String getLegacyReportingName() { - return String.format("%s(%s)", testMethod.getName(), - ClassUtils.nullSafeToString(Class::getSimpleName, testMethod.getParameterTypes())); - } - - /** - * Invoke {@link TestWatcher#testDisabled(ExtensionContext, Optional)} on each - * registered {@link TestWatcher}, in registration order. - * - * @since 5.4 - */ - @Override - public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) { - if (context != null) { - invokeTestWatchers(context, false, - watcher -> watcher.testDisabled(context.getExtensionContext(), result.getReason())); - } - } - - /** - * @since 5.4 - */ - protected void invokeTestWatchers(JupiterEngineExecutionContext context, boolean reverseOrder, - Consumer callback) { - - ExtensionRegistry registry = context.getExtensionRegistry(); - - List watchers = reverseOrder // - ? registry.getReversedExtensions(TestWatcher.class) - : registry.getExtensions(TestWatcher.class); - - watchers.forEach(watcher -> { - try { - callback.accept(watcher); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - ExtensionContext extensionContext = context.getExtensionContext(); - logger.warn(throwable, - () -> String.format("Failed to invoke TestWatcher [%s] for method [%s] with display name [%s]", - watcher.getClass().getName(), - ReflectionUtils.getFullyQualifiedMethodName(extensionContext.getRequiredTestClass(), - extensionContext.getRequiredTestMethod()), - getDisplayName())); - } - }); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java deleted file mode 100644 index 22db33b3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.support.hierarchical.Node; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * @since 5.0 - */ -final class MethodExtensionContext extends AbstractExtensionContext { - - private final ThrowableCollector throwableCollector; - - private TestInstances testInstances; - - MethodExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, - TestMethodTestDescriptor testDescriptor, JupiterConfiguration configuration, - ThrowableCollector throwableCollector, ExecutableInvoker executableInvoker) { - - super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); - - this.throwableCollector = throwableCollector; - } - - @Override - public Optional getElement() { - return Optional.of(getTestDescriptor().getTestMethod()); - } - - @Override - public Optional> getTestClass() { - return Optional.of(getTestDescriptor().getTestClass()); - } - - @Override - public Optional getTestInstanceLifecycle() { - return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); - } - - @Override - public Optional getTestInstance() { - return getTestInstances().map(TestInstances::getInnermostInstance); - } - - @Override - public Optional getTestInstances() { - return Optional.ofNullable(this.testInstances); - } - - void setTestInstances(TestInstances testInstances) { - this.testInstances = testInstances; - } - - @Override - public Optional getTestMethod() { - return Optional.of(getTestDescriptor().getTestMethod()); - } - - @Override - public Optional getExecutionException() { - return Optional.ofNullable(this.throwableCollector.getThrowable()); - } - - @Override - protected Node.ExecutionMode getPlatformExecutionMode() { - return getTestDescriptor().getExecutionMode(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java deleted file mode 100644 index fd461ba4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.net.URI; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.support.descriptor.MethodSource; - -/** - * Jupiter internal support for creating {@link MethodSource} from {@link URI}. - * - * @since 5.5 - * @see MethodSource - * @see MethodSelector - */ -class MethodSourceSupport { - - static final String METHOD_SCHEME = "method"; - - /** - * Create a new {@code MethodSource} from the supplied {@link URI}. - * - *

The supplied {@link URI} should be of the form {@code method:} - * where FQMN is the fully qualified method name. See - * {@link DiscoverySelectors#selectMethod(String)} for the supported formats. - * - *

The {@link URI#getSchemeSpecificPart() scheme-specific part} - * component of the {@code URI} will be used as fully qualified class name. - * The {@linkplain URI#getFragment() fragment} component of the {@code URI} - * will be used to retrieve the method name and method parameter types. - * - * @param uri the {@code URI} for the method; never {@code null} - * @return a new {@code MethodSource}; never {@code null} - * @since 5.5 - * @see #METHOD_SCHEME - * @see DiscoverySelectors#selectMethod(String) - */ - static MethodSource from(URI uri) { - Preconditions.notNull(uri, "URI must not be null"); - Preconditions.condition(METHOD_SCHEME.equals(uri.getScheme()), - () -> "URI [" + uri + "] must have [" + METHOD_SCHEME + "] scheme"); - String schemeSpecificPart = Preconditions.notNull(uri.getSchemeSpecificPart(), - () -> "Invalid method URI (scheme-specific part must not be null). Please consult the Javadoc of " - + DiscoverySelectors.class.getName() - + "#selectMethod(String) for details on the supported formats."); - String fragment = Preconditions.notNull(uri.getFragment(), - () -> "Invalid method URI (fragment must not be null). Please consult the Javadoc of " - + DiscoverySelectors.class.getName() - + "#selectMethod(String) for details on the supported formats."); - - String fullyQualifiedMethodName = schemeSpecificPart + "#" + fragment; - String[] methodSpec = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); - return MethodSource.from(methodSpec[0], methodSpec[1], methodSpec[2]); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java deleted file mode 100644 index f750e2f5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.Collections.emptyList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistrar; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * {@link TestDescriptor} for tests based on nested (but not static) Java classes. - * - *

Default Display Names

- * - *

The default display name for a non-static nested test class is the simple - * name of the class. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class NestedClassTestDescriptor extends ClassBasedTestDescriptor { - - public static final String SEGMENT_TYPE = "nested-class"; - - public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { - super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration); - } - - // --- TestDescriptor ------------------------------------------------------ - - @Override - public final Set getTags() { - // return modifiable copy - Set allTags = new LinkedHashSet<>(this.tags); - getParent().ifPresent(parentDescriptor -> allTags.addAll(parentDescriptor.getTags())); - return allTags; - } - - @Override - public List> getEnclosingTestClasses() { - TestDescriptor parent = getParent().orElse(null); - if (parent instanceof ClassBasedTestDescriptor) { - ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent; - List> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses()); - result.add(parentClassDescriptor.getTestClass()); - return result; - } - return emptyList(); - } - - // --- Node ---------------------------------------------------------------- - - @Override - protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, - ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, - ThrowableCollector throwableCollector) { - - // Extensions registered for nested classes and below are not to be used for instantiating and initializing outer classes - ExtensionRegistry extensionRegistryForOuterInstanceCreation = parentExecutionContext.getExtensionRegistry(); - TestInstances outerInstances = parentExecutionContext.getTestInstancesProvider().getTestInstances( - extensionRegistryForOuterInstanceCreation, registrar, throwableCollector); - return instantiateTestClass(Optional.of(outerInstances), registry, extensionContext); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java deleted file mode 100644 index 671cc6b6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.MethodSourceSupport.METHOD_SCHEME; -import static org.junit.platform.engine.support.descriptor.ClassSource.CLASS_SCHEME; -import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; - -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Iterator; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.DynamicContainer; -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.CollectionUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; -import org.junit.platform.engine.support.descriptor.UriSource; - -/** - * {@link org.junit.platform.engine.TestDescriptor TestDescriptor} for - * {@link org.junit.jupiter.api.TestFactory @TestFactory} methods. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implements Filterable { - - public static final String SEGMENT_TYPE = "test-factory"; - public static final String DYNAMIC_CONTAINER_SEGMENT_TYPE = "dynamic-container"; - public static final String DYNAMIC_TEST_SEGMENT_TYPE = "dynamic-test"; - - private static final ReflectiveInterceptorCall interceptorCall = InvocationInterceptor::interceptTestFactoryMethod; - private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); - - private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); - - public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); - } - - // --- Filterable ---------------------------------------------------------- - - @Override - public DynamicDescendantFilter getDynamicDescendantFilter() { - return dynamicDescendantFilter; - } - - // --- TestDescriptor ------------------------------------------------------ - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public boolean mayRegisterTests() { - return true; - } - - // --- Node ---------------------------------------------------------------- - - @Override - protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { - ExtensionContext extensionContext = context.getExtensionContext(); - - context.getThrowableCollector().execute(() -> { - Object instance = extensionContext.getRequiredTestInstance(); - Object testFactoryMethodResult = executableInvoker.invoke(getTestMethod(), instance, extensionContext, - context.getExtensionRegistry(), interceptorCall); - TestSource defaultTestSource = getSource().orElseThrow( - () -> new JUnitException("Illegal state: TestSource must be present")); - try (Stream dynamicNodeStream = toDynamicNodeStream(testFactoryMethodResult)) { - int index = 1; - Iterator iterator = dynamicNodeStream.iterator(); - while (iterator.hasNext()) { - DynamicNode dynamicNode = iterator.next(); - Optional descriptor = createDynamicDescriptor(this, dynamicNode, index, - defaultTestSource, getDynamicDescendantFilter(), configuration); - descriptor.ifPresent(dynamicTestExecutor::execute); - index++; - } - } - catch (ClassCastException ex) { - throw invalidReturnTypeException(ex); - } - dynamicTestExecutor.awaitFinished(); - }); - } - - @SuppressWarnings("unchecked") - private Stream toDynamicNodeStream(Object testFactoryMethodResult) { - if (testFactoryMethodResult instanceof DynamicNode) { - return Stream.of((DynamicNode) testFactoryMethodResult); - } - try { - return (Stream) CollectionUtils.toStream(testFactoryMethodResult); - } - catch (PreconditionViolationException ex) { - throw invalidReturnTypeException(ex); - } - } - - private JUnitException invalidReturnTypeException(Throwable cause) { - String message = String.format( - "@TestFactory method [%s] must return a single %2$s or a Stream, Collection, Iterable, Iterator, or array of %2$s.", - getTestMethod().toGenericString(), DynamicNode.class.getName()); - return new JUnitException(message, cause); - } - - static Optional createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node, - int index, TestSource defaultTestSource, DynamicDescendantFilter dynamicDescendantFilter, - JupiterConfiguration configuration) { - - UniqueId uniqueId; - Supplier descriptorCreator; - Optional customTestSource = node.getTestSourceUri().map(TestFactoryTestDescriptor::fromUri); - TestSource source = customTestSource.orElse(defaultTestSource); - - if (node instanceof DynamicTest) { - DynamicTest test = (DynamicTest) node; - uniqueId = parent.getUniqueId().append(DYNAMIC_TEST_SEGMENT_TYPE, "#" + index); - descriptorCreator = () -> new DynamicTestTestDescriptor(uniqueId, index, test, source, configuration); - } - else { - DynamicContainer container = (DynamicContainer) node; - uniqueId = parent.getUniqueId().append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#" + index); - descriptorCreator = () -> new DynamicContainerTestDescriptor(uniqueId, index, container, source, - dynamicDescendantFilter.withoutIndexFiltering(), configuration); - } - if (dynamicDescendantFilter.test(uniqueId, index - 1)) { - JupiterTestDescriptor descriptor = descriptorCreator.get(); - descriptor.setParent(parent); - return Optional.of(descriptor); - } - return Optional.empty(); - } - - /** - * @since 5.3 - */ - static TestSource fromUri(URI uri) { - Preconditions.notNull(uri, "URI must not be null"); - if (CLASSPATH_SCHEME.equals(uri.getScheme())) { - return ClasspathResourceSource.from(uri); - } - if (CLASS_SCHEME.equals(uri.getScheme())) { - return ClassSource.from(uri); - } - if (METHOD_SCHEME.equals(uri.getScheme())) { - return MethodSourceSupport.from(uri); - } - return UriSource.from(uri); - } - - /** - * Override {@link TestMethodTestDescriptor#nodeSkipped} as a no-op, since - * the {@code TestWatcher} API is not supported for {@code @TestFactory} - * containers. - * - * @since 5.4 - */ - @Override - public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) { - /* no-op */ - } - - /** - * Override {@link TestMethodTestDescriptor#nodeFinished} as a no-op, since - * the {@code TestWatcher} API is not supported for {@code @TestFactory} - * containers. - * - * @since 5.4 - */ - @Override - public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor, - TestExecutionResult result) { - - /* no-op */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java deleted file mode 100644 index 8aae97f9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.Preconditions; - -/** - * Collection of utilities for retrieving the test instance lifecycle mode. - * - * @since 5.0 - * @see TestInstance - * @see TestInstance.Lifecycle - */ -@API(status = INTERNAL, since = "5.0") -public final class TestInstanceLifecycleUtils { - - private TestInstanceLifecycleUtils() { - /* no-op */ - } - - static TestInstance.Lifecycle getTestInstanceLifecycle(Class testClass, JupiterConfiguration configuration) { - Preconditions.notNull(testClass, "testClass must not be null"); - Preconditions.notNull(configuration, "configuration must not be null"); - - // @formatter:off - return AnnotationUtils.findAnnotation(testClass, TestInstance.class) - .map(TestInstance::value) - .orElseGet(configuration::getDefaultTestInstanceLifecycle); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java deleted file mode 100644 index c5c784b0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromExecutableParameters; -import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; - -import java.lang.reflect.Method; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; -import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.api.extension.TestWatcher; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; -import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * {@link TestDescriptor} for {@link org.junit.jupiter.api.Test @Test} methods. - * - *

Default Display Names

- * - *

The default display name for a test method is the name of the method - * concatenated with a comma-separated list of parameter types in parentheses. - * The names of parameter types are retrieved using {@link Class#getSimpleName()}. - * For example, the default display name for the following test method is - * {@code testUser(TestInfo, User)}. - * - *

- *   {@literal @}Test
- *   void testUser(TestInfo testInfo, {@literal @}Mock User user) { ... }
- * 
- * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class TestMethodTestDescriptor extends MethodBasedTestDescriptor { - - public static final String SEGMENT_TYPE = "method"; - private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); - private static final ReflectiveInterceptorCall defaultInterceptorCall = ReflectiveInterceptorCall.ofVoidMethod( - InvocationInterceptor::interceptTestMethod); - - private final ReflectiveInterceptorCall interceptorCall; - - public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); - this.interceptorCall = defaultInterceptorCall; - } - - TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, - JupiterConfiguration configuration, ReflectiveInterceptorCall interceptorCall) { - super(uniqueId, displayName, testClass, testMethod, configuration); - this.interceptorCall = interceptorCall; - } - - @Override - public Type getType() { - return Type.TEST; - } - - // --- Node ---------------------------------------------------------------- - - @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { - MutableExtensionRegistry registry = populateNewExtensionRegistry(context); - ThrowableCollector throwableCollector = createThrowableCollector(); - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); - MethodExtensionContext extensionContext = new MethodExtensionContext(context.getExtensionContext(), - context.getExecutionListener(), this, context.getConfiguration(), throwableCollector, executableInvoker); - throwableCollector.execute(() -> { - TestInstances testInstances = context.getTestInstancesProvider().getTestInstances(registry, - throwableCollector); - extensionContext.setTestInstances(testInstances); - }); - - // @formatter:off - return context.extend() - .withExtensionRegistry(registry) - .withExtensionContext(extensionContext) - .withThrowableCollector(throwableCollector) - .build(); - // @formatter:on - } - - protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { - MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( - context.getExtensionRegistry(), getTestMethod()); - registerExtensionsFromExecutableParameters(registry, getTestMethod()); - return registry; - } - - @Override - public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, - DynamicTestExecutor dynamicTestExecutor) { - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - // @formatter:off - invokeBeforeEachCallbacks(context); - if (throwableCollector.isEmpty()) { - invokeBeforeEachMethods(context); - if (throwableCollector.isEmpty()) { - invokeBeforeTestExecutionCallbacks(context); - if (throwableCollector.isEmpty()) { - invokeTestMethod(context, dynamicTestExecutor); - } - invokeAfterTestExecutionCallbacks(context); - } - invokeAfterEachMethods(context); - } - invokeAfterEachCallbacks(context); - // @formatter:on - - return context; - } - - @Override - public void cleanUp(JupiterEngineExecutionContext context) throws Exception { - if (isPerMethodLifecycle(context) && context.getExtensionContext().getTestInstance().isPresent()) { - invokeTestInstancePreDestroyCallbacks(context); - } - context.getThrowableCollector().execute(() -> super.cleanUp(context)); - context.getThrowableCollector().assertEmpty(); - } - - private boolean isPerMethodLifecycle(JupiterEngineExecutionContext context) { - return context.getExtensionContext().getTestInstanceLifecycle().orElse( - Lifecycle.PER_CLASS) == Lifecycle.PER_METHOD; - } - - private void invokeBeforeEachCallbacks(JupiterEngineExecutionContext context) { - invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeEachCallback.class, context, - (callback, extensionContext) -> callback.beforeEach(extensionContext)); - } - - private void invokeBeforeEachMethods(JupiterEngineExecutionContext context) { - ExtensionRegistry registry = context.getExtensionRegistry(); - invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeEachMethodAdapter.class, context, - (adapter, extensionContext) -> { - try { - adapter.invokeBeforeEachMethod(extensionContext, registry); - } - catch (Throwable throwable) { - invokeBeforeEachExecutionExceptionHandlers(extensionContext, registry, throwable); - } - }); - } - - private void invokeBeforeEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry, - Throwable throwable) { - - invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, - (handler, handledThrowable) -> handler.handleBeforeEachMethodExecutionException(context, handledThrowable)); - } - - private void invokeBeforeTestExecutionCallbacks(JupiterEngineExecutionContext context) { - invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeTestExecutionCallback.class, context, - (callback, extensionContext) -> callback.beforeTestExecution(extensionContext)); - } - - private void invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(Class type, - JupiterEngineExecutionContext context, CallbackInvoker callbackInvoker) { - - ExtensionRegistry registry = context.getExtensionRegistry(); - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - for (T callback : registry.getExtensions(type)) { - throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext)); - if (throwableCollector.isNotEmpty()) { - break; - } - } - } - - protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - throwableCollector.execute(() -> { - try { - Method testMethod = getTestMethod(); - Object instance = extensionContext.getRequiredTestInstance(); - executableInvoker.invoke(testMethod, instance, extensionContext, context.getExtensionRegistry(), - interceptorCall); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - invokeTestExecutionExceptionHandlers(context.getExtensionRegistry(), extensionContext, throwable); - } - }); - } - - private void invokeTestExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, - Throwable throwable) { - - invokeExecutionExceptionHandlers(TestExecutionExceptionHandler.class, registry, throwable, - (handler, handledThrowable) -> handler.handleTestExecutionException(context, handledThrowable)); - } - - private void invokeAfterTestExecutionCallbacks(JupiterEngineExecutionContext context) { - invokeAllAfterMethodsOrCallbacks(AfterTestExecutionCallback.class, context, - (callback, extensionContext) -> callback.afterTestExecution(extensionContext)); - } - - private void invokeAfterEachMethods(JupiterEngineExecutionContext context) { - ExtensionRegistry registry = context.getExtensionRegistry(); - invokeAllAfterMethodsOrCallbacks(AfterEachMethodAdapter.class, context, (adapter, extensionContext) -> { - try { - adapter.invokeAfterEachMethod(extensionContext, registry); - } - catch (Throwable throwable) { - invokeAfterEachExecutionExceptionHandlers(extensionContext, registry, throwable); - } - }); - } - - private void invokeAfterEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry, - Throwable throwable) { - - invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, - (handler, handledThrowable) -> handler.handleAfterEachMethodExecutionException(context, handledThrowable)); - } - - private void invokeAfterEachCallbacks(JupiterEngineExecutionContext context) { - invokeAllAfterMethodsOrCallbacks(AfterEachCallback.class, context, - (callback, extensionContext) -> callback.afterEach(extensionContext)); - } - - private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext context) { - invokeAllAfterMethodsOrCallbacks(TestInstancePreDestroyCallback.class, context, - TestInstancePreDestroyCallback::preDestroyTestInstance); - } - - private void invokeAllAfterMethodsOrCallbacks(Class type, - JupiterEngineExecutionContext context, CallbackInvoker callbackInvoker) { - - ExtensionRegistry registry = context.getExtensionRegistry(); - ExtensionContext extensionContext = context.getExtensionContext(); - ThrowableCollector throwableCollector = context.getThrowableCollector(); - - registry.getReversedExtensions(type).forEach(callback -> { - throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext)); - }); - } - - /** - * Invoke {@link TestWatcher#testSuccessful testSuccessful()}, - * {@link TestWatcher#testAborted testAborted()}, or - * {@link TestWatcher#testFailed testFailed()} on each - * registered {@link TestWatcher} according to the status of the supplied - * {@link TestExecutionResult}, in reverse registration order. - * - * @since 5.4 - */ - @Override - public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor, - TestExecutionResult result) { - - if (context != null) { - ExtensionContext extensionContext = context.getExtensionContext(); - TestExecutionResult.Status status = result.getStatus(); - - invokeTestWatchers(context, true, watcher -> { - switch (status) { - case SUCCESSFUL: - watcher.testSuccessful(extensionContext); - break; - case ABORTED: - watcher.testAborted(extensionContext, result.getThrowable().orElse(null)); - break; - case FAILED: - watcher.testFailed(extensionContext, result.getThrowable().orElse(null)); - break; - } - }); - } - } - - /** - * @since 5.5 - */ - @FunctionalInterface - private interface CallbackInvoker { - - void invoke(T t, ExtensionContext context) throws Throwable; - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java deleted file mode 100644 index 5a3506f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.support.hierarchical.Node; - -/** - * @since 5.0 - */ -final class TestTemplateExtensionContext extends AbstractExtensionContext { - - private final TestInstances testInstances; - - TestTemplateExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, - TestTemplateTestDescriptor testDescriptor, JupiterConfiguration configuration, TestInstances testInstances, - ExecutableInvoker executableInvoker) { - - super(parent, engineExecutionListener, testDescriptor, configuration, executableInvoker); - this.testInstances = testInstances; - } - - @Override - public Optional getElement() { - return Optional.of(getTestDescriptor().getTestMethod()); - } - - @Override - public Optional> getTestClass() { - return Optional.of(getTestDescriptor().getTestClass()); - } - - @Override - public Optional getTestInstanceLifecycle() { - return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); - } - - @Override - public Optional getTestInstance() { - return getTestInstances().map(TestInstances::getInnermostInstance); - } - - @Override - public Optional getTestInstances() { - return Optional.ofNullable(this.testInstances); - } - - @Override - public Optional getTestMethod() { - return Optional.of(getTestDescriptor().getTestMethod()); - } - - @Override - public Optional getExecutionException() { - return Optional.empty(); - } - - @Override - protected Node.ExecutionMode getPlatformExecutionMode() { - return getTestDescriptor().getExecutionMode(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java deleted file mode 100644 index 4d522615..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.Collections.emptySet; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.lang.reflect.Method; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource; - -/** - * {@link TestDescriptor} for a {@link org.junit.jupiter.api.TestTemplate @TestTemplate} - * invocation. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescriptor { - - public static final String SEGMENT_TYPE = "test-template-invocation"; - private static final ReflectiveInterceptorCall interceptorCall = ReflectiveInterceptorCall.ofVoidMethod( - InvocationInterceptor::interceptTestTemplateMethod); - - private TestTemplateInvocationContext invocationContext; - private final int index; - - TestTemplateInvocationTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, - TestTemplateInvocationContext invocationContext, int index, JupiterConfiguration configuration) { - super(uniqueId, invocationContext.getDisplayName(index), testClass, templateMethod, configuration, - interceptorCall); - this.invocationContext = invocationContext; - this.index = index; - } - - @Override - public Set getExclusiveResources() { - // @ResourceLock annotations are already collected and returned by the enclosing container - return emptySet(); - } - - @Override - public String getLegacyReportingName() { - return super.getLegacyReportingName() + "[" + index + "]"; - } - - @Override - protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { - MutableExtensionRegistry registry = super.populateNewExtensionRegistry(context); - invocationContext.getAdditionalExtensions().forEach( - extension -> registry.registerExtension(extension, invocationContext)); - return registry; - } - - @Override - public void after(JupiterEngineExecutionContext context) { - // forget invocationContext so it can be garbage collected - invocationContext = null; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java deleted file mode 100644 index 885a07d9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; - -/** - * {@link TestDescriptor} for {@link org.junit.jupiter.api.TestTemplate @TestTemplate} - * methods. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implements Filterable { - - public static final String SEGMENT_TYPE = "test-template"; - private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); - - public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, templateMethod, configuration); - } - - // --- Filterable ---------------------------------------------------------- - - @Override - public DynamicDescendantFilter getDynamicDescendantFilter() { - return dynamicDescendantFilter; - } - - // --- TestDescriptor ------------------------------------------------------ - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public boolean mayRegisterTests() { - return true; - } - - // --- Node ---------------------------------------------------------------- - - @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception { - MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( - context.getExtensionRegistry(), getTestMethod()); - - // The test instance should be properly maintained by the enclosing class's ExtensionContext. - TestInstances testInstances = context.getExtensionContext().getTestInstances().orElse(null); - - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); - ExtensionContext extensionContext = new TestTemplateExtensionContext(context.getExtensionContext(), - context.getExecutionListener(), this, context.getConfiguration(), testInstances, executableInvoker); - - // @formatter:off - return context.extend() - .withExtensionRegistry(registry) - .withExtensionContext(extensionContext) - .build(); - // @formatter:on - } - - @Override - public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, - DynamicTestExecutor dynamicTestExecutor) throws Exception { - - ExtensionContext extensionContext = context.getExtensionContext(); - List providers = validateProviders(extensionContext, - context.getExtensionRegistry()); - AtomicInteger invocationIndex = new AtomicInteger(); - // @formatter:off - providers.stream() - .flatMap(provider -> provider.provideTestTemplateInvocationContexts(extensionContext)) - .map(invocationContext -> createInvocationTestDescriptor(invocationContext, invocationIndex.incrementAndGet())) - .filter(Optional::isPresent) - .map(Optional::get) - .forEach(invocationTestDescriptor -> execute(dynamicTestExecutor, invocationTestDescriptor)); - // @formatter:on - validateWasAtLeastInvokedOnce(invocationIndex.get(), providers); - return context; - } - - private List validateProviders(ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry) { - - // @formatter:off - List providers = extensionRegistry.stream(TestTemplateInvocationContextProvider.class) - .filter(provider -> provider.supportsTestTemplate(extensionContext)) - .collect(toList()); - // @formatter:on - - return Preconditions.notEmpty(providers, - () -> String.format("You must register at least one %s that supports @TestTemplate method [%s]", - TestTemplateInvocationContextProvider.class.getSimpleName(), getTestMethod())); - } - - private Optional createInvocationTestDescriptor(TestTemplateInvocationContext invocationContext, - int index) { - UniqueId uniqueId = getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); - if (getDynamicDescendantFilter().test(uniqueId, index - 1)) { - return Optional.of(new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(), - invocationContext, index, configuration)); - } - return Optional.empty(); - } - - private void execute(DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { - testDescriptor.setParent(this); - dynamicTestExecutor.execute(testDescriptor); - } - - private void validateWasAtLeastInvokedOnce(int invocationIndex, - List providers) { - - Preconditions.condition(invocationIndex > 0, - () -> "None of the supporting " + TestTemplateInvocationContextProvider.class.getSimpleName() + "s " - + providers.stream().map(provider -> provider.getClass().getSimpleName()).collect( - joining(", ", "[", "]")) - + " provided a non-empty stream"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java deleted file mode 100644 index e3e0028c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Test descriptors used within the JUnit Jupiter test engine. - */ - -package org.junit.jupiter.engine.descriptor; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java deleted file mode 100644 index 31666b8c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.util.List; -import java.util.Optional; - -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; - -/** - * Abstract base class for wrappers for test descriptors based on annotated - * elements. - * - * @since 5.8 - */ -abstract class AbstractAnnotatedDescriptorWrapper { - - private final TestDescriptor testDescriptor; - private final E annotatedElement; - - AbstractAnnotatedDescriptorWrapper(TestDescriptor testDescriptor, E annotatedElement) { - this.testDescriptor = testDescriptor; - this.annotatedElement = annotatedElement; - } - - E getAnnotatedElement() { - return this.annotatedElement; - } - - TestDescriptor getTestDescriptor() { - return this.testDescriptor; - } - - public final String getDisplayName() { - return this.testDescriptor.getDisplayName(); - } - - public final boolean isAnnotated(Class annotationType) { - Preconditions.notNull(annotationType, "annotationType must not be null"); - return AnnotationUtils.isAnnotated(getAnnotatedElement(), annotationType); - } - - public final Optional findAnnotation(Class annotationType) { - Preconditions.notNull(annotationType, "annotationType must not be null"); - return AnnotationUtils.findAnnotation(getAnnotatedElement(), annotationType); - } - - public final List findRepeatableAnnotations(Class annotationType) { - Preconditions.notNull(annotationType, "annotationType must not be null"); - return AnnotationUtils.findRepeatableAnnotations(getAnnotatedElement(), annotationType); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java deleted file mode 100644 index e19e5c96..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static java.util.stream.Collectors.toCollection; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestDescriptor; - -/** - * Abstract base class for {@linkplain TestDescriptor.Visitor visitors} that - * order children nodes. - * - * @param the parent container type to search in for matching children - * @param the type of children (containers or tests) to order - * @param the wrapper type for the children to order - * @since 5.8 - */ -abstract class AbstractOrderingVisitor> - implements TestDescriptor.Visitor { - - private static final Logger logger = LoggerFactory.getLogger(AbstractOrderingVisitor.class); - - @SuppressWarnings("unchecked") - protected void doWithMatchingDescriptor(Class parentTestDescriptorType, TestDescriptor testDescriptor, - Consumer action, Function errorMessageBuilder) { - - if (parentTestDescriptorType.isInstance(testDescriptor)) { - PARENT parentTestDescriptor = (PARENT) testDescriptor; - try { - action.accept(parentTestDescriptor); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - logger.error(t, () -> errorMessageBuilder.apply(parentTestDescriptor)); - } - } - } - - protected void orderChildrenTestDescriptors(TestDescriptor parentTestDescriptor, Class matchingChildrenType, - Function descriptorWrapperFactory, DescriptorWrapperOrderer descriptorWrapperOrderer) { - - Set children = parentTestDescriptor.getChildren(); - - List matchingDescriptorWrappers = children.stream()// - .filter(matchingChildrenType::isInstance)// - .map(matchingChildrenType::cast)// - .map(descriptorWrapperFactory)// - .collect(toCollection(ArrayList::new)); - - // If there are no children to order, abort early. - if (matchingDescriptorWrappers.isEmpty()) { - return; - } - - if (descriptorWrapperOrderer.canOrderWrappers()) { - List nonMatchingTestDescriptors = children.stream()// - .filter(childTestDescriptor -> !matchingChildrenType.isInstance(childTestDescriptor))// - .collect(Collectors.toList()); - - // Make a local copy for later validation - Set originalWrappers = new LinkedHashSet<>(matchingDescriptorWrappers); - - descriptorWrapperOrderer.orderWrappers(matchingDescriptorWrappers); - - int difference = matchingDescriptorWrappers.size() - originalWrappers.size(); - if (difference > 0) { - descriptorWrapperOrderer.logDescriptorsAddedWarning(difference); - } - else if (difference < 0) { - descriptorWrapperOrderer.logDescriptorsRemovedWarning(difference); - } - - Set orderedTestDescriptors = matchingDescriptorWrappers.stream()// - .filter(originalWrappers::contains)// - .map(AbstractAnnotatedDescriptorWrapper::getTestDescriptor)// - .collect(toCollection(LinkedHashSet::new)); - - // There is currently no way to removeAll or addAll children at once, so we - // first remove them all and then add them all back. - Stream.concat(orderedTestDescriptors.stream(), nonMatchingTestDescriptors.stream())// - .forEach(parentTestDescriptor::removeChild); - - // If we are ordering children of type ClassBasedTestDescriptor, that means we - // are ordering top-level classes or @Nested test classes. Thus, the - // nonMatchingTestDescriptors list is either empty (for top-level classes) or - // contains only local test methods (for @Nested classes) which must be executed - // before tests in @Nested test classes. So we add the test methods before adding - // the @Nested test classes. - if (matchingChildrenType == ClassBasedTestDescriptor.class) { - Stream.concat(nonMatchingTestDescriptors.stream(), orderedTestDescriptors.stream())// - .forEach(parentTestDescriptor::addChild); - } - // Otherwise, we add the ordered descriptors before the non-matching descriptors, - // which is the case when we are ordering test methods. In other words, local - // test methods always get added before @Nested test classes. - else { - Stream.concat(orderedTestDescriptors.stream(), nonMatchingTestDescriptors.stream())// - .forEach(parentTestDescriptor::addChild); - } - } - - // Recurse through the children in order to support ordering for @Nested test classes. - matchingDescriptorWrappers.forEach(descriptorWrapper -> { - TestDescriptor newParentTestDescriptor = descriptorWrapper.getTestDescriptor(); - DescriptorWrapperOrderer newDescriptorWrapperOrderer = getDescriptorWrapperOrderer(descriptorWrapperOrderer, - descriptorWrapper); - - orderChildrenTestDescriptors(newParentTestDescriptor, matchingChildrenType, descriptorWrapperFactory, - newDescriptorWrapperOrderer); - }); - } - - /** - * Get the {@link DescriptorWrapperOrderer} for the supplied {@link AbstractAnnotatedDescriptorWrapper}. - * - *

The default implementation returns the supplied {@code DescriptorWrapperOrderer}. - * - * @return a new {@code DescriptorWrapperOrderer} or the one supplied as an argument - */ - protected DescriptorWrapperOrderer getDescriptorWrapperOrderer( - DescriptorWrapperOrderer inheritedDescriptorWrapperOrderer, - AbstractAnnotatedDescriptorWrapper descriptorWrapper) { - - return inheritedDescriptorWrapperOrderer; - } - - protected class DescriptorWrapperOrderer { - - private final Consumer> orderingAction; - - private final MessageGenerator descriptorsAddedMessageGenerator; - - private final MessageGenerator descriptorsRemovedMessageGenerator; - - DescriptorWrapperOrderer(Consumer> orderingAction, - MessageGenerator descriptorsAddedMessageGenerator, - MessageGenerator descriptorsRemovedMessageGenerator) { - - this.orderingAction = orderingAction; - this.descriptorsAddedMessageGenerator = descriptorsAddedMessageGenerator; - this.descriptorsRemovedMessageGenerator = descriptorsRemovedMessageGenerator; - } - - private boolean canOrderWrappers() { - return this.orderingAction != null; - } - - private void orderWrappers(List wrappers) { - this.orderingAction.accept(wrappers); - } - - private void logDescriptorsAddedWarning(int number) { - logger.warn(() -> this.descriptorsAddedMessageGenerator.generateMessage(number)); - } - - private void logDescriptorsRemovedWarning(int number) { - logger.warn(() -> this.descriptorsRemovedMessageGenerator.generateMessage(Math.abs(number))); - } - - } - - @FunctionalInterface - protected interface MessageGenerator { - - String generateMessage(int number); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java deleted file mode 100644 index 0b8ea6d0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import java.lang.reflect.AnnotatedElement; -import java.util.List; -import java.util.function.Consumer; - -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.TestClassOrder; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.TestDescriptor; - -/** - * @since 5.8 - */ -class ClassOrderingVisitor - extends AbstractOrderingVisitor { - - private final JupiterConfiguration configuration; - - ClassOrderingVisitor(JupiterConfiguration configuration) { - this.configuration = configuration; - } - - @Override - public void visit(TestDescriptor testDescriptor) { - ClassOrderer globalClassOrderer = this.configuration.getDefaultTestClassOrderer().orElse(null); - doWithMatchingDescriptor(JupiterEngineDescriptor.class, testDescriptor, - descriptor -> orderContainedClasses(descriptor, globalClassOrderer), - descriptor -> "Failed to order classes"); - } - - private void orderContainedClasses(JupiterEngineDescriptor jupiterEngineDescriptor, ClassOrderer classOrderer) { - orderChildrenTestDescriptors(// - jupiterEngineDescriptor, // - ClassBasedTestDescriptor.class, // - DefaultClassDescriptor::new, // - createDescriptorWrapperOrderer(classOrderer)); - } - - @Override - protected DescriptorWrapperOrderer getDescriptorWrapperOrderer( - DescriptorWrapperOrderer inheritedDescriptorWrapperOrderer, - AbstractAnnotatedDescriptorWrapper descriptorWrapper) { - - AnnotatedElement annotatedElement = descriptorWrapper.getAnnotatedElement(); - return AnnotationUtils.findAnnotation(annotatedElement, TestClassOrder.class)// - .map(TestClassOrder::value)// - . map(ReflectionUtils::newInstance)// - .map(this::createDescriptorWrapperOrderer)// - .orElse(inheritedDescriptorWrapperOrderer); - } - - private DescriptorWrapperOrderer createDescriptorWrapperOrderer(ClassOrderer classOrderer) { - Consumer> orderingAction = classOrderer == null ? null : // - classDescriptors -> classOrderer.orderClasses( - new DefaultClassOrdererContext(classDescriptors, this.configuration)); - - MessageGenerator descriptorsAddedMessageGenerator = number -> String.format( - "ClassOrderer [%s] added %s ClassDescriptor(s) which will be ignored.", nullSafeToString(classOrderer), - number); - MessageGenerator descriptorsRemovedMessageGenerator = number -> String.format( - "ClassOrderer [%s] removed %s ClassDescriptor(s) which will be retained with arbitrary ordering.", - nullSafeToString(classOrderer), number); - - return new DescriptorWrapperOrderer(orderingAction, descriptorsAddedMessageGenerator, - descriptorsRemovedMessageGenerator); - } - - private static String nullSafeToString(ClassOrderer classOrderer) { - return (classOrderer != null ? classOrderer.getClass().getName() : ""); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java deleted file mode 100644 index fd986367..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static java.util.function.Predicate.isEqual; -import static java.util.stream.Collectors.toCollection; -import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; -import static org.junit.platform.commons.support.ReflectionSupport.findNestedClasses; -import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; -import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; -import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; -import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.discovery.NestedClassSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.support.discovery.SelectorResolver; - -/** - * @since 5.5 - */ -class ClassSelectorResolver implements SelectorResolver { - - private static final IsTestClassWithTests isTestClassWithTests = new IsTestClassWithTests(); - private static final IsNestedTestClass isNestedTestClass = new IsNestedTestClass(); - - private final Predicate classNameFilter; - private final JupiterConfiguration configuration; - - ClassSelectorResolver(Predicate classNameFilter, JupiterConfiguration configuration) { - this.classNameFilter = classNameFilter; - this.configuration = configuration; - } - - @Override - public Resolution resolve(ClassSelector selector, Context context) { - Class testClass = selector.getJavaClass(); - if (isTestClassWithTests.test(testClass)) { - // Nested tests are never filtered out - if (classNameFilter.test(testClass.getName())) { - return toResolution( - context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass)))); - } - } - else if (isNestedTestClass.test(testClass)) { - return toResolution(context.addToParent(() -> DiscoverySelectors.selectClass(testClass.getEnclosingClass()), - parent -> Optional.of(newNestedClassTestDescriptor(parent, testClass)))); - } - return unresolved(); - } - - @Override - public Resolution resolve(NestedClassSelector selector, Context context) { - if (isNestedTestClass.test(selector.getNestedClass())) { - return toResolution(context.addToParent(() -> selectClass(selector.getEnclosingClasses()), - parent -> Optional.of(newNestedClassTestDescriptor(parent, selector.getNestedClass())))); - } - return unresolved(); - } - - @Override - public Resolution resolve(UniqueIdSelector selector, Context context) { - UniqueId uniqueId = selector.getUniqueId(); - UniqueId.Segment lastSegment = uniqueId.getLastSegment(); - if (ClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { - String className = lastSegment.getValue(); - return ReflectionUtils.tryToLoadClass(className).toOptional().filter(isTestClassWithTests).map( - testClass -> toResolution( - context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass))))).orElse( - unresolved()); - } - if (NestedClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { - String simpleClassName = lastSegment.getValue(); - return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { - if (parent instanceof ClassBasedTestDescriptor) { - Class parentTestClass = ((ClassBasedTestDescriptor) parent).getTestClass(); - return ReflectionUtils.findNestedClasses(parentTestClass, - isNestedTestClass.and( - where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst().flatMap( - testClass -> Optional.of(newNestedClassTestDescriptor(parent, testClass))); - } - return Optional.empty(); - })); - } - return unresolved(); - } - - private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class testClass) { - return new ClassTestDescriptor( - parent.getUniqueId().append(ClassTestDescriptor.SEGMENT_TYPE, testClass.getName()), testClass, - configuration); - } - - private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { - return new NestedClassTestDescriptor( - parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), testClass, - configuration); - } - - private Resolution toResolution(Optional testDescriptor) { - return testDescriptor.map(it -> { - Class testClass = it.getTestClass(); - List> testClasses = new ArrayList<>(it.getEnclosingTestClasses()); - testClasses.add(testClass); - // @formatter:off - return Resolution.match(Match.exact(it, () -> { - Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream() - .map(method -> selectMethod(testClasses, method)); - Stream nestedClasses = findNestedClasses(testClass, isNestedTestClass).stream() - .map(nestedClass -> DiscoverySelectors.selectNestedClass(testClasses, nestedClass)); - return Stream.concat(methods, nestedClasses).collect(toCollection((Supplier>) LinkedHashSet::new)); - })); - // @formatter:on - }).orElse(unresolved()); - } - - private DiscoverySelector selectClass(List> classes) { - if (classes.size() == 1) { - return DiscoverySelectors.selectClass(classes.get(0)); - } - int lastIndex = classes.size() - 1; - return DiscoverySelectors.selectNestedClass(classes.subList(0, lastIndex), classes.get(lastIndex)); - } - - private DiscoverySelector selectMethod(List> classes, Method method) { - if (classes.size() == 1) { - return DiscoverySelectors.selectMethod(classes.get(0), method); - } - int lastIndex = classes.size() - 1; - return DiscoverySelectors.selectNestedMethod(classes.subList(0, lastIndex), classes.get(lastIndex), method); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java deleted file mode 100644 index b11c1b02..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import org.junit.jupiter.api.ClassDescriptor; -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Default implementation of {@link ClassDescriptor}, backed by - * a {@link ClassBasedTestDescriptor}. - * - * @since 5.8 - */ -class DefaultClassDescriptor extends AbstractAnnotatedDescriptorWrapper> implements ClassDescriptor { - - DefaultClassDescriptor(ClassBasedTestDescriptor testDescriptor) { - super(testDescriptor, testDescriptor.getTestClass()); - } - - @Override - public final Class getTestClass() { - return getAnnotatedElement(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("class", getTestClass().toGenericString()).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java deleted file mode 100644 index 2c8c8743..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.ClassDescriptor; -import org.junit.jupiter.api.ClassOrdererContext; -import org.junit.jupiter.engine.config.JupiterConfiguration; - -/** - * Default implementation of {@link ClassOrdererContext}. - * - * @since 5.8 - */ -class DefaultClassOrdererContext implements ClassOrdererContext { - - private final List classDescriptors; - private final JupiterConfiguration configuration; - - DefaultClassOrdererContext(List classDescriptors, JupiterConfiguration configuration) { - this.classDescriptors = classDescriptors; - this.configuration = configuration; - } - - @Override - public List getClassDescriptors() { - return this.classDescriptors; - } - - @Override - public Optional getConfigurationParameter(String key) { - return this.configuration.getRawConfigurationParameter(key); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java deleted file mode 100644 index a8cdd12c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.MethodDescriptor; -import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Default implementation of {@link MethodDescriptor}, backed by - * a {@link MethodBasedTestDescriptor}. - * - * @since 5.4 - */ -class DefaultMethodDescriptor extends AbstractAnnotatedDescriptorWrapper implements MethodDescriptor { - - DefaultMethodDescriptor(MethodBasedTestDescriptor testDescriptor) { - super(testDescriptor, testDescriptor.getTestMethod()); - } - - @Override - public final Method getMethod() { - return getAnnotatedElement(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("method", getMethod().toGenericString()).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java deleted file mode 100644 index fd51d4b7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.MethodDescriptor; -import org.junit.jupiter.api.MethodOrdererContext; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Default implementation of {@link MethodOrdererContext}. - * - * @since 5.4 - */ -class DefaultMethodOrdererContext implements MethodOrdererContext { - - private final Class testClass; - private final List methodDescriptors; - private final JupiterConfiguration configuration; - - DefaultMethodOrdererContext(Class testClass, List methodDescriptors, - JupiterConfiguration configuration) { - - this.testClass = testClass; - this.methodDescriptors = methodDescriptors; - this.configuration = configuration; - } - - @Override - public final Class getTestClass() { - return this.testClass; - } - - @Override - public List getMethodDescriptors() { - return this.methodDescriptors; - } - - @Override - public Optional getConfigurationParameter(String key) { - return this.configuration.getRawConfigurationParameter(key); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("testClass", this.testClass.getName()).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java deleted file mode 100644 index 42771f96..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; - -/** - * {@code DiscoverySelectorResolver} resolves {@link TestDescriptor TestDescriptors} - * for containers and tests selected by DiscoverySelectors with the help of the - * {@code JavaElementsResolver}. - * - *

This class is the only public entry point into the discovery package. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class DiscoverySelectorResolver { - - // @formatter:off - private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() - .addClassContainerSelectorResolver(new IsTestClassWithTests()) - .addSelectorResolver(context -> new ClassSelectorResolver(context.getClassNameFilter(), context.getEngineDescriptor().getConfiguration())) - .addSelectorResolver(context -> new MethodSelectorResolver(context.getEngineDescriptor().getConfiguration())) - .addTestDescriptorVisitor(context -> new ClassOrderingVisitor(context.getEngineDescriptor().getConfiguration())) - .addTestDescriptorVisitor(context -> new MethodOrderingVisitor(context.getEngineDescriptor().getConfiguration())) - .addTestDescriptorVisitor(context -> TestDescriptor::prune) - .build(); - // @formatter:on - - public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescriptor engineDescriptor) { - resolver.resolve(request, engineDescriptor); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java deleted file mode 100644 index 648ad453..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.0 - */ -class MethodFinder { - - // Pattern: methodName(comma-separated list of parameter type names) - private static final Pattern METHOD_PATTERN = Pattern.compile("(.+)\\((.*)\\)"); - - Optional findMethod(String methodSpecPart, Class clazz) { - Matcher matcher = METHOD_PATTERN.matcher(methodSpecPart); - - Preconditions.condition(matcher.matches(), - () -> String.format("Method [%s] does not match pattern [%s]", methodSpecPart, METHOD_PATTERN)); - - String methodName = matcher.group(1); - String parameterTypeNames = matcher.group(2); - return ReflectionUtils.findMethod(clazz, methodName, parameterTypeNames); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java deleted file mode 100644 index e1e7c8af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; - -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; - -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; -import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; -import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.TestDescriptor; - -/** - * @since 5.5 - */ -class MethodOrderingVisitor - extends AbstractOrderingVisitor { - - private final JupiterConfiguration configuration; - - MethodOrderingVisitor(JupiterConfiguration configuration) { - this.configuration = configuration; - } - - @Override - public void visit(TestDescriptor testDescriptor) { - doWithMatchingDescriptor(ClassBasedTestDescriptor.class, testDescriptor, - descriptor -> orderContainedMethods(descriptor, descriptor.getTestClass()), - descriptor -> "Failed to order methods for " + descriptor.getTestClass()); - } - - /** - * @since 5.4 - */ - private void orderContainedMethods(ClassBasedTestDescriptor classBasedTestDescriptor, Class testClass) { - findAnnotation(testClass, TestMethodOrder.class)// - .map(TestMethodOrder::value)// - . map(ReflectionUtils::newInstance)// - .map(Optional::of)// - .orElseGet(configuration::getDefaultTestMethodOrderer)// - .ifPresent(methodOrderer -> { - - Consumer> orderingAction = methodDescriptors -> methodOrderer.orderMethods( - new DefaultMethodOrdererContext(testClass, methodDescriptors, this.configuration)); - - MessageGenerator descriptorsAddedMessageGenerator = number -> String.format( - "MethodOrderer [%s] added %s MethodDescriptor(s) for test class [%s] which will be ignored.", - methodOrderer.getClass().getName(), number, testClass.getName()); - MessageGenerator descriptorsRemovedMessageGenerator = number -> String.format( - "MethodOrderer [%s] removed %s MethodDescriptor(s) for test class [%s] which will be retained with arbitrary ordering.", - methodOrderer.getClass().getName(), number, testClass.getName()); - - DescriptorWrapperOrderer descriptorWrapperOrderer = new DescriptorWrapperOrderer(orderingAction, - descriptorsAddedMessageGenerator, descriptorsRemovedMessageGenerator); - - orderChildrenTestDescriptors(classBasedTestDescriptor, // - MethodBasedTestDescriptor.class, // - DefaultMethodDescriptor::new, // - descriptorWrapperOrderer); - - // Note: MethodOrderer#getDefaultExecutionMode() is guaranteed - // to be invoked after MethodOrderer#orderMethods(). - methodOrderer.getDefaultExecutionMode()// - .map(JupiterTestDescriptor::toExecutionMode)// - .ifPresent(classBasedTestDescriptor::setDefaultChildExecutionMode); - }); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java deleted file mode 100644 index 62698001..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.matches; -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; -import org.junit.jupiter.engine.descriptor.Filterable; -import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; -import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; -import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; -import org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod; -import org.junit.jupiter.engine.discovery.predicates.IsTestMethod; -import org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.NestedMethodSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.support.discovery.SelectorResolver; - -/** - * @since 5.5 - */ -class MethodSelectorResolver implements SelectorResolver { - - private static final Logger logger = LoggerFactory.getLogger(MethodSelectorResolver.class); - private static final MethodFinder methodFinder = new MethodFinder(); - private static final Predicate> testClassPredicate = new IsTestClassWithTests().or( - new IsNestedTestClass()); - - protected final JupiterConfiguration configuration; - - MethodSelectorResolver(JupiterConfiguration configuration) { - this.configuration = configuration; - } - - @Override - public Resolution resolve(MethodSelector selector, Context context) { - return resolve(context, emptyList(), selector.getJavaClass(), selector::getJavaMethod, Match::exact); - } - - @Override - public Resolution resolve(NestedMethodSelector selector, Context context) { - return resolve(context, selector.getEnclosingClasses(), selector.getNestedClass(), selector::getMethod, - Match::exact); - } - - private Resolution resolve(Context context, List> enclosingClasses, Class testClass, - Supplier methodSupplier, - BiFunction>, Match> matchFactory) { - if (!testClassPredicate.test(testClass)) { - return unresolved(); - } - Method method = methodSupplier.get(); - // @formatter:off - Set matches = Arrays.stream(MethodType.values()) - .map(methodType -> methodType.resolve(enclosingClasses, testClass, method, context, configuration)) - .filter(Optional::isPresent) - .map(Optional::get) - .map(testDescriptor -> matchFactory.apply(testDescriptor, expansionCallback(testDescriptor))) - .collect(toSet()); - // @formatter:on - if (matches.size() > 1) { - logger.warn(() -> { - Stream testDescriptors = matches.stream().map(Match::getTestDescriptor); - return String.format( - "Possible configuration error: method [%s] resulted in multiple TestDescriptors %s. " - + "This is typically the result of annotating a method with multiple competing annotations " - + "such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.", - method.toGenericString(), testDescriptors.map(d -> d.getClass().getName()).collect(toList())); - }); - } - return matches.isEmpty() ? unresolved() : matches(matches); - } - - @Override - public Resolution resolve(UniqueIdSelector selector, Context context) { - UniqueId uniqueId = selector.getUniqueId(); - // @formatter:off - return Arrays.stream(MethodType.values()) - .map(methodType -> methodType.resolveUniqueIdIntoTestDescriptor(uniqueId, context, configuration)) - .filter(Optional::isPresent) - .map(Optional::get) - .map(testDescriptor -> { - boolean exactMatch = uniqueId.equals(testDescriptor.getUniqueId()); - if (testDescriptor instanceof Filterable) { - Filterable filterable = (Filterable) testDescriptor; - if (exactMatch) { - filterable.getDynamicDescendantFilter().allowAll(); - } - else { - filterable.getDynamicDescendantFilter().allowUniqueIdPrefix(uniqueId); - } - } - return Resolution.match(exactMatch ? Match.exact(testDescriptor) : Match.partial(testDescriptor, expansionCallback(testDescriptor))); - }) - .findFirst() - .orElse(unresolved()); - // @formatter:on - } - - @Override - public Resolution resolve(IterationSelector selector, Context context) { - if (selector.getParentSelector() instanceof MethodSelector) { - MethodSelector methodSelector = (MethodSelector) selector.getParentSelector(); - return resolve(context, emptyList(), methodSelector.getJavaClass(), methodSelector::getJavaMethod, - (testDescriptor, childSelectorsSupplier) -> { - if (testDescriptor instanceof Filterable) { - Filterable filterable = (Filterable) testDescriptor; - filterable.getDynamicDescendantFilter().allowIndex(selector.getIterationIndices()); - } - return Match.partial(testDescriptor, childSelectorsSupplier); - }); - } - return unresolved(); - } - - private Supplier> expansionCallback(TestDescriptor testDescriptor) { - return () -> { - if (testDescriptor instanceof Filterable) { - Filterable filterable = (Filterable) testDescriptor; - filterable.getDynamicDescendantFilter().allowAll(); - } - return emptySet(); - }; - } - - private enum MethodType { - - TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { - @Override - protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration); - } - }, - - TEST_FACTORY(new IsTestFactoryMethod(), TestFactoryTestDescriptor.SEGMENT_TYPE, - TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE, - TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE) { - @Override - protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration); - } - }, - - TEST_TEMPLATE(new IsTestTemplateMethod(), TestTemplateTestDescriptor.SEGMENT_TYPE, - TestTemplateInvocationTestDescriptor.SEGMENT_TYPE) { - @Override - protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestTemplateTestDescriptor(uniqueId, testClass, method, configuration); - } - }; - - private final Predicate methodPredicate; - private final String segmentType; - private final Set dynamicDescendantSegmentTypes; - - MethodType(Predicate methodPredicate, String segmentType, String... dynamicDescendantSegmentTypes) { - this.methodPredicate = methodPredicate; - this.segmentType = segmentType; - this.dynamicDescendantSegmentTypes = new LinkedHashSet<>(Arrays.asList(dynamicDescendantSegmentTypes)); - } - - private Optional resolve(List> enclosingClasses, Class testClass, Method method, - Context context, JupiterConfiguration configuration) { - if (!methodPredicate.test(method)) { - return Optional.empty(); - } - return context.addToParent(() -> selectClass(enclosingClasses, testClass), // - parent -> Optional.of( - createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); - } - - private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { - if (enclosingClasses.isEmpty()) { - return DiscoverySelectors.selectClass(testClass); - } - return DiscoverySelectors.selectNestedClass(enclosingClasses, testClass); - } - - private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniqueId, Context context, - JupiterConfiguration configuration) { - UniqueId.Segment lastSegment = uniqueId.getLastSegment(); - if (segmentType.equals(lastSegment.getType())) { - return context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { - String methodSpecPart = lastSegment.getValue(); - Class testClass = ((ClassBasedTestDescriptor) parent).getTestClass(); - // @formatter:off - return methodFinder.findMethod(methodSpecPart, testClass) - .filter(methodPredicate) - .map(method -> createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration)); - // @formatter:on - }); - } - if (dynamicDescendantSegmentTypes.contains(lastSegment.getType())) { - return resolveUniqueIdIntoTestDescriptor(uniqueId.removeLastSegment(), context, configuration); - } - return Optional.empty(); - } - - private UniqueId createUniqueId(Method method, TestDescriptor parent) { - String methodId = String.format("%s(%s)", method.getName(), - ClassUtils.nullSafeToString(method.getParameterTypes())); - return parent.getUniqueId().append(segmentType, methodId); - } - - protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration); - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java deleted file mode 100644 index 0426627d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Internal classes for test discovery within the JUnit Jupiter test engine. - * Contains resolvers for Java elements. - */ - -package org.junit.jupiter.engine.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java deleted file mode 100644 index c46822be..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; - -import java.util.function.Predicate; - -import org.apiguardian.api.API; - -/** - * Test if a class is a non-private inner class (i.e., a non-static nested class). - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class IsInnerClass implements Predicate> { - - @Override - public boolean test(Class candidate) { - // Do not collapse into a single return statement. - if (isPrivate(candidate)) { - return false; - } - if (!isInnerClass(candidate)) { - return false; - } - - return true; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java deleted file mode 100644 index ceaa9f3d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; - -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.Nested; - -/** - * Test if a class is a JUnit Jupiter {@link Nested @Nested} test class. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class IsNestedTestClass implements Predicate> { - - private static final IsInnerClass isInnerClass = new IsInnerClass(); - - @Override - public boolean test(Class candidate) { - //please do not collapse into single return - if (!isInnerClass.test(candidate)) { - return false; - } - return isAnnotated(candidate, Nested.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java deleted file mode 100644 index 525e9aaf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; -import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; - -import java.util.function.Predicate; - -import org.apiguardian.api.API; - -/** - * Test if a class is a potential top-level JUnit Jupiter test container, even if - * it does not contain tests. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class IsPotentialTestContainer implements Predicate> { - - @Override - public boolean test(Class candidate) { - // Please do not collapse the following into a single statement. - if (isPrivate(candidate)) { - return false; - } - if (isAbstract(candidate)) { - return false; - } - if (candidate.isLocalClass()) { - return false; - } - if (candidate.isAnonymousClass()) { - return false; - } - return !isInnerClass(candidate); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java deleted file mode 100644 index edec7724..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.lang.reflect.Method; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Test if a class is a JUnit Jupiter test class containing executable tests, - * test factories, test templates, or nested tests. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.1") -public class IsTestClassWithTests implements Predicate> { - - private static final IsTestMethod isTestMethod = new IsTestMethod(); - - private static final IsTestFactoryMethod isTestFactoryMethod = new IsTestFactoryMethod(); - - private static final IsTestTemplateMethod isTestTemplateMethod = new IsTestTemplateMethod(); - - public static final Predicate isTestOrTestFactoryOrTestTemplateMethod = isTestMethod.or( - isTestFactoryMethod).or(isTestTemplateMethod); - - private static final IsPotentialTestContainer isPotentialTestContainer = new IsPotentialTestContainer(); - - private static final IsNestedTestClass isNestedTestClass = new IsNestedTestClass(); - - @Override - public boolean test(Class candidate) { - return isPotentialTestContainer.test(candidate) - && (hasTestOrTestFactoryOrTestTemplateMethods(candidate) || hasNestedTests(candidate)); - } - - private boolean hasTestOrTestFactoryOrTestTemplateMethods(Class candidate) { - return ReflectionUtils.isMethodPresent(candidate, isTestOrTestFactoryOrTestTemplateMethod); - } - - private boolean hasNestedTests(Class candidate) { - return !ReflectionUtils.findNestedClasses(candidate, isNestedTestClass).isEmpty(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java deleted file mode 100644 index 186d5ab6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestFactory; - -/** - * Test if a method is a JUnit Jupiter {@link TestFactory @TestFactory} method. - * - *

NOTE: this predicate does not check if a candidate method - * has an appropriate return type for a {@code @TestFactory} method. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class IsTestFactoryMethod extends IsTestableMethod { - - public IsTestFactoryMethod() { - super(TestFactory.class, false); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java deleted file mode 100644 index 9d30765d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.Test; - -/** - * Test if a method is a JUnit Jupiter {@link Test @Test} method. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class IsTestMethod extends IsTestableMethod { - - public IsTestMethod() { - super(Test.class, true); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java deleted file mode 100644 index 70a4391a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestTemplate; - -/** - * Test if a method is a JUnit Jupiter {@link TestTemplate @TestTemplate} method. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class IsTestTemplateMethod extends IsTestableMethod { - - public IsTestTemplateMethod() { - super(TestTemplate.class, true); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java deleted file mode 100644 index e8906f0d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; -import static org.junit.platform.commons.util.ReflectionUtils.isStatic; -import static org.junit.platform.commons.util.ReflectionUtils.returnsVoid; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.function.Predicate; - -/** - * @since 5.0 - */ -abstract class IsTestableMethod implements Predicate { - - private final Class annotationType; - private final boolean mustReturnVoid; - - IsTestableMethod(Class annotationType, boolean mustReturnVoid) { - this.annotationType = annotationType; - this.mustReturnVoid = mustReturnVoid; - } - - @Override - public boolean test(Method candidate) { - // Please do not collapse the following into a single statement. - if (isStatic(candidate)) { - return false; - } - if (isPrivate(candidate)) { - return false; - } - if (isAbstract(candidate)) { - return false; - } - if (returnsVoid(candidate) != this.mustReturnVoid) { - return false; - } - - return isAnnotated(candidate, this.annotationType); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java deleted file mode 100644 index 579eb486..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Internal predicate classes used by test discovery within the JUnit Jupiter test engine. - */ - -package org.junit.jupiter.engine.discovery.predicates; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java deleted file mode 100644 index 2b9dff68..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; - -/** - * Functional interface for registering an {@link AfterEach @AfterEach} method - * as a pseudo-extension. - * - * @since 5.0 - */ -@FunctionalInterface -@API(status = INTERNAL, since = "5.0") -public interface AfterEachMethodAdapter extends Extension { - - void invokeAfterEachMethod(ExtensionContext context, ExtensionRegistry registry) throws Throwable; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java deleted file mode 100644 index 709e35e8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; - -/** - * Functional interface for registering a {@link BeforeEach @BeforeEach} method - * as a pseudo-extension. - * - * @since 5.0 - */ -@FunctionalInterface -@API(status = INTERNAL, since = "5.0") -public interface BeforeEachMethodAdapter extends Extension { - - void invokeBeforeEachMethod(ExtensionContext context, ExtensionRegistry registry) throws Throwable; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java deleted file mode 100644 index ddafc0a4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.JUnitException; - -/** - * Thrown if an error is encountered while evaluating an - * {@link ExecutionCondition}. - * - * @since 5.0 - * @see ConditionEvaluator - */ -class ConditionEvaluationException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public ConditionEvaluationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java deleted file mode 100644 index fb5e0ff2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static java.lang.String.format; -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.StringUtils; - -/** - * {@code ConditionEvaluator} evaluates {@link ExecutionCondition} extensions. - * - * @since 5.0 - * @see ExecutionCondition - */ -@API(status = INTERNAL, since = "5.0") -public class ConditionEvaluator { - - private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); - - private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( - "No 'disabled' conditions encountered"); - - /** - * Evaluate all {@link ExecutionCondition} extensions registered for the - * supplied {@link ExtensionContext}. - * - * @param context the current {@code ExtensionContext} - * @return the first disabled {@code ConditionEvaluationResult}, - * or a default enabled {@code ConditionEvaluationResult} if no - * disabled conditions are encountered - */ - public ConditionEvaluationResult evaluate(ExtensionRegistry extensionRegistry, JupiterConfiguration configuration, - ExtensionContext context) { - - // @formatter:off - return extensionRegistry.stream(ExecutionCondition.class) - .filter(configuration.getExecutionConditionFilter()) - .map(condition -> evaluate(condition, context)) - .filter(ConditionEvaluationResult::isDisabled) - .findFirst() - .orElse(ENABLED); - // @formatter:on - } - - private ConditionEvaluationResult evaluate(ExecutionCondition condition, ExtensionContext context) { - try { - ConditionEvaluationResult result = condition.evaluateExecutionCondition(context); - logResult(condition.getClass(), result, context); - return result; - } - catch (Exception ex) { - throw evaluationException(condition.getClass(), ex); - } - } - - private void logResult(Class conditionType, ConditionEvaluationResult result, ExtensionContext context) { - logger.trace(() -> format("Evaluation of condition [%s] on [%s] resulted in: %s", conditionType.getName(), - context.getElement().get(), result)); - } - - private ConditionEvaluationException evaluationException(Class conditionType, Exception ex) { - String cause = StringUtils.isNotBlank(ex.getMessage()) ? ": " + ex.getMessage() : ""; - return new ConditionEvaluationException( - format("Failed to evaluate condition [%s]%s", conditionType.getName(), cause), ex); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java deleted file mode 100644 index 4100d667..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static java.util.Collections.unmodifiableList; - -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.platform.commons.util.ReflectionUtils; - -class ConstructorInvocation implements Invocation, ReflectiveInvocationContext> { - - private final Constructor constructor; - private final Object[] arguments; - - ConstructorInvocation(Constructor constructor, Object[] arguments) { - this.constructor = constructor; - this.arguments = arguments; - } - - @Override - public Class getTargetClass() { - return this.constructor.getDeclaringClass(); - } - - @Override - public Constructor getExecutable() { - return this.constructor; - } - - @Override - public List getArguments() { - return unmodifiableList(Arrays.asList(this.arguments)); - } - - @Override - public Optional getTarget() { - return Optional.empty(); - } - - @Override - public T proceed() { - return ReflectionUtils.newInstance(this.constructor, this.arguments); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java deleted file mode 100644 index 2276cc94..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameters; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.9 - */ -@API(status = INTERNAL, since = "5.9") -public class DefaultExecutableInvoker implements ExecutableInvoker { - - private final ExtensionContext extensionContext; - private final ExtensionRegistry extensionRegistry; - - public DefaultExecutableInvoker(ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { - this.extensionContext = extensionContext; - this.extensionRegistry = extensionRegistry; - } - - public DefaultExecutableInvoker(JupiterEngineExecutionContext context) { - this(context.getExtensionContext(), context.getExtensionRegistry()); - } - - @Override - public T invoke(Constructor constructor, Object outerInstance) { - Object[] arguments = resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance), - extensionContext, extensionRegistry); - return ReflectionUtils.newInstance(constructor, arguments); - } - - @Override - public Object invoke(Method method, Object target) { - Object[] arguments = resolveParameters(method, Optional.ofNullable(target), extensionContext, - extensionRegistry); - return ReflectionUtils.invokeMethod(method, target, arguments); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java deleted file mode 100644 index 900289b3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Parameter; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * @since 5.0 - */ -class DefaultParameterContext implements ParameterContext { - - private final Parameter parameter; - private final int index; - private final Optional target; - - DefaultParameterContext(Parameter parameter, int index, Optional target) { - Preconditions.condition(index >= 0, "index must be greater than or equal to zero"); - this.parameter = Preconditions.notNull(parameter, "parameter must not be null"); - this.index = index; - this.target = Preconditions.notNull(target, "target must not be null"); - } - - @Override - public Parameter getParameter() { - return this.parameter; - } - - @Override - public int getIndex() { - return this.index; - } - - @Override - public Optional getTarget() { - return this.target; - } - - @Override - public boolean isAnnotated(Class annotationType) { - return AnnotationUtils.isAnnotated(this.parameter, this.index, annotationType); - } - - @Override - public Optional findAnnotation(Class annotationType) { - return AnnotationUtils.findAnnotation(this.parameter, this.index, annotationType); - } - - @Override - public List findRepeatableAnnotations(Class annotationType) { - return AnnotationUtils.findRepeatableAnnotations(this.parameter, this.index, annotationType); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("parameter", this.parameter) - .append("index", this.index) - .append("target", this.target) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java deleted file mode 100644 index dc611f69..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.platform.commons.util.Preconditions; - -@API(status = INTERNAL, since = "5.4") -public class DefaultTestInstances implements TestInstances { - - public static DefaultTestInstances of(Object instance) { - return new DefaultTestInstances(Collections.singletonList(instance)); - } - - public static DefaultTestInstances of(TestInstances testInstances, Object instance) { - List allInstances = new ArrayList<>(testInstances.getAllInstances()); - allInstances.add(instance); - return new DefaultTestInstances(Collections.unmodifiableList(allInstances)); - } - - private final List instances; - - private DefaultTestInstances(List instances) { - this.instances = Preconditions.notEmpty(instances, "instances must not be empty"); - } - - @Override - public Object getInnermostInstance() { - return instances.get(instances.size() - 1); - } - - @Override - public List getEnclosingInstances() { - return instances.subList(0, instances.size() - 1); - } - - @Override - public List getAllInstances() { - return instances; - } - - @Override - public Optional findInstance(Class requiredType) { - Preconditions.notNull(requiredType, "requiredType must not be null"); - ListIterator iterator = instances.listIterator(instances.size()); - while (iterator.hasPrevious()) { - Object instance = iterator.previous(); - if (requiredType.isInstance(instance)) { - return Optional.of(requiredType.cast(instance)); - } - } - return Optional.empty(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java deleted file mode 100644 index 133e3d6b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; -import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; -import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo; - -import java.util.Comparator; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; -import org.junit.jupiter.api.extension.ExtensionContextException; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * {@code ExtensionValuesStore} is used inside implementations of - * {@link ExtensionContext} to store and retrieve values. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class ExtensionValuesStore { - - private static final Comparator REVERSE_INSERT_ORDER = Comparator. comparing( - it -> it.order).reversed(); - - private final AtomicInteger insertOrderSequence = new AtomicInteger(); - private final ConcurrentMap storedValues = new ConcurrentHashMap<>(4); - private final ExtensionValuesStore parentStore; - - public ExtensionValuesStore(ExtensionValuesStore parentStore) { - this.parentStore = parentStore; - } - - /** - * Close all values that implement {@link CloseableResource}. - * - * @implNote Only close values stored in this instance. This implementation - * does not close values in parent stores. - */ - public void closeAllStoredCloseableValues() { - ThrowableCollector throwableCollector = createThrowableCollector(); - storedValues.values().stream() // - .filter(storedValue -> storedValue.evaluateSafely() instanceof CloseableResource) // - .sorted(REVERSE_INSERT_ORDER) // - .map(storedValue -> (CloseableResource) storedValue.evaluate()) // - .forEach(resource -> throwableCollector.execute(resource::close)); - throwableCollector.assertEmpty(); - } - - Object get(Namespace namespace, Object key) { - StoredValue storedValue = getStoredValue(new CompositeKey(namespace, key)); - return (storedValue != null ? storedValue.evaluate() : null); - } - - T get(Namespace namespace, Object key, Class requiredType) { - Object value = get(namespace, key); - return castToRequiredType(key, value, requiredType); - } - - Object getOrComputeIfAbsent(Namespace namespace, K key, Function defaultCreator) { - CompositeKey compositeKey = new CompositeKey(namespace, key); - StoredValue storedValue = getStoredValue(compositeKey); - if (storedValue == null) { - StoredValue newValue = storedValue(new MemoizingSupplier(() -> defaultCreator.apply(key))); - storedValue = Optional.ofNullable(storedValues.putIfAbsent(compositeKey, newValue)).orElse(newValue); - } - return storedValue.evaluate(); - } - - V getOrComputeIfAbsent(Namespace namespace, K key, Function defaultCreator, Class requiredType) { - Object value = getOrComputeIfAbsent(namespace, key, defaultCreator); - return castToRequiredType(key, value, requiredType); - } - - void put(Namespace namespace, Object key, Object value) { - storedValues.put(new CompositeKey(namespace, key), storedValue(() -> value)); - } - - private StoredValue storedValue(Supplier value) { - return new StoredValue(insertOrderSequence.getAndIncrement(), value); - } - - Object remove(Namespace namespace, Object key) { - StoredValue previous = storedValues.remove(new CompositeKey(namespace, key)); - return (previous != null ? previous.evaluate() : null); - } - - T remove(Namespace namespace, Object key, Class requiredType) { - Object value = remove(namespace, key); - return castToRequiredType(key, value, requiredType); - } - - private StoredValue getStoredValue(CompositeKey compositeKey) { - StoredValue storedValue = storedValues.get(compositeKey); - if (storedValue != null) { - return storedValue; - } - if (parentStore != null) { - return parentStore.getStoredValue(compositeKey); - } - return null; - } - - @SuppressWarnings("unchecked") - private T castToRequiredType(Object key, Object value, Class requiredType) { - if (value == null) { - return null; - } - if (isAssignableTo(value, requiredType)) { - if (requiredType.isPrimitive()) { - return (T) getWrapperType(requiredType).cast(value); - } - return requiredType.cast(value); - } - // else - throw new ExtensionContextException( - String.format("Object stored under key [%s] is not of required type [%s]", key, requiredType.getName())); - } - - private static class CompositeKey { - - private final Namespace namespace; - private final Object key; - - private CompositeKey(Namespace namespace, Object key) { - this.namespace = namespace; - this.key = key; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CompositeKey that = (CompositeKey) o; - return this.namespace.equals(that.namespace) && this.key.equals(that.key); - } - - @Override - public int hashCode() { - return Objects.hash(namespace, key); - } - - } - - private static class StoredValue { - - private final int order; - private final Supplier supplier; - - public StoredValue(int order, Supplier supplier) { - this.order = order; - this.supplier = supplier; - } - - private Object evaluateSafely() { - try { - return evaluate(); - } - catch (RuntimeException e) { - return null; - } - } - - private Object evaluate() { - return supplier.get(); - } - - } - - private static class MemoizingSupplier implements Supplier { - - private static final Object NO_VALUE_SET = new Object(); - - private final Lock lock = new ReentrantLock(); - private final Supplier delegate; - private volatile Object value = NO_VALUE_SET; - - private MemoizingSupplier(Supplier delegate) { - this.delegate = delegate; - } - - @Override - public Object get() { - if (value == NO_VALUE_SET) { - computeValue(); - } - if (value instanceof Failure) { - throw ((Failure) value).exception; - } - return value; - } - - private void computeValue() { - lock.lock(); - try { - if (value == NO_VALUE_SET) { - value = delegate.get(); - } - } - catch (RuntimeException e) { - value = new Failure(e); - } - finally { - lock.unlock(); - } - } - - private static class Failure { - - private final RuntimeException exception; - - public Failure(RuntimeException exception) { - this.exception = exception; - } - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java deleted file mode 100644 index b0a1fdb2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameters; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.jupiter.engine.extension.ExtensionRegistry; - -/** - * {@code InterceptingExecutableInvoker} encapsulates the invocation of a - * {@link java.lang.reflect.Executable} (i.e., method or constructor), - * including support for dynamic resolution of method parameters via - * {@link ParameterResolver ParameterResolvers}. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class InterceptingExecutableInvoker { - - private static final InvocationInterceptorChain interceptorChain = new InvocationInterceptorChain(); - - /** - * Invoke the supplied constructor with the supplied outer instance and - * dynamic parameter resolution. - * - * @param constructor the constructor to invoke and resolve parameters for - * @param outerInstance the outer instance to supply as the first argument - * to the constructor; empty, for top-level classes - * @param extensionContext the current {@code ExtensionContext} - * @param extensionRegistry the {@code ExtensionRegistry} to retrieve - * {@code ParameterResolvers} from - * @param interceptorCall the call for intercepting this constructor - * invocation via all registered {@linkplain InvocationInterceptor - * interceptors} - */ - public T invoke(Constructor constructor, Optional outerInstance, ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall, T> interceptorCall) { - - Object[] arguments = resolveParameters(constructor, Optional.empty(), outerInstance, extensionContext, - extensionRegistry); - ConstructorInvocation invocation = new ConstructorInvocation<>(constructor, arguments); - return invoke(invocation, invocation, extensionContext, extensionRegistry, interceptorCall); - } - - /** - * Invoke the supplied method with dynamic parameter resolution. - * - * @param method the method to invoke and resolve parameters for - * @param target the target on which the executable will be invoked, - * potentially wrapped in an {@link Optional}; can be {@code null} or an - * empty {@code Optional} for a {@code static} method - * @param extensionContext the current {@code ExtensionContext} - * @param extensionRegistry the {@code ExtensionRegistry} to retrieve - * {@code ParameterResolvers} from - * @param interceptorCall the call for intercepting this method invocation - * via all registered {@linkplain InvocationInterceptor interceptors} - */ - public T invoke(Method method, Object target, ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall interceptorCall) { - - @SuppressWarnings("unchecked") - Optional optionalTarget = (target instanceof Optional ? (Optional) target - : Optional.ofNullable(target)); - Object[] arguments = resolveParameters(method, optionalTarget, extensionContext, extensionRegistry); - MethodInvocation invocation = new MethodInvocation<>(method, optionalTarget, arguments); - return invoke(invocation, invocation, extensionContext, extensionRegistry, interceptorCall); - } - - private T invoke(Invocation originalInvocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall call) { - return interceptorChain.invoke(originalInvocation, extensionRegistry, (interceptor, - wrappedInvocation) -> call.apply(interceptor, wrappedInvocation, invocationContext, extensionContext)); - } - - public interface ReflectiveInterceptorCall { - - T apply(InvocationInterceptor interceptor, Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable; - - static ReflectiveInterceptorCall ofVoidMethod(VoidMethodInterceptorCall call) { - return ((interceptorChain, invocation, invocationContext, extensionContext) -> { - call.apply(interceptorChain, invocation, invocationContext, extensionContext); - return null; - }); - } - - interface VoidMethodInterceptorCall { - void apply(InvocationInterceptor interceptor, Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java deleted file mode 100644 index f622a386..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static java.util.stream.Collectors.joining; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.List; -import java.util.ListIterator; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ExceptionUtils; - -@API(status = INTERNAL, since = "5.5") -public class InvocationInterceptorChain { - - public T invoke(Invocation invocation, ExtensionRegistry extensionRegistry, InterceptorCall call) { - List interceptors = extensionRegistry.getExtensions(InvocationInterceptor.class); - if (interceptors.isEmpty()) { - return proceed(invocation); - } - return chainAndInvoke(invocation, call, interceptors); - } - - private T chainAndInvoke(Invocation invocation, InterceptorCall call, - List interceptors) { - - ValidatingInvocation validatingInvocation = new ValidatingInvocation<>(invocation, interceptors); - Invocation chainedInvocation = chainInterceptors(validatingInvocation, call, interceptors); - T result = proceed(chainedInvocation); - validatingInvocation.verifyInvokedAtLeastOnce(); - return result; - } - - private Invocation chainInterceptors(Invocation invocation, InterceptorCall call, - List interceptors) { - - Invocation result = invocation; - ListIterator iterator = interceptors.listIterator(interceptors.size()); - while (iterator.hasPrevious()) { - InvocationInterceptor interceptor = iterator.previous(); - result = new InterceptedInvocation<>(result, call, interceptor); - } - return result; - } - - private T proceed(Invocation invocation) { - try { - return invocation.proceed(); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(t); - } - } - - @FunctionalInterface - public interface InterceptorCall { - - T apply(InvocationInterceptor interceptor, Invocation invocation) throws Throwable; - - static InterceptorCall ofVoid(VoidInterceptorCall call) { - return ((interceptorChain, invocation) -> { - call.apply(interceptorChain, invocation); - return null; - }); - } - - } - - @FunctionalInterface - public interface VoidInterceptorCall { - - void apply(InvocationInterceptor interceptor, Invocation invocation) throws Throwable; - - } - - private static class InterceptedInvocation implements Invocation { - - private final Invocation invocation; - private final InterceptorCall call; - private final InvocationInterceptor interceptor; - - InterceptedInvocation(Invocation invocation, InterceptorCall call, InvocationInterceptor interceptor) { - this.invocation = invocation; - this.call = call; - this.interceptor = interceptor; - } - - @Override - public T proceed() throws Throwable { - return call.apply(interceptor, invocation); - } - - @Override - public void skip() { - invocation.skip(); - } - } - - private static class ValidatingInvocation implements Invocation { - - private static final Logger logger = LoggerFactory.getLogger(ValidatingInvocation.class); - - private final AtomicBoolean invokedOrSkipped = new AtomicBoolean(); - private final Invocation delegate; - private final List interceptors; - - ValidatingInvocation(Invocation delegate, List interceptors) { - this.delegate = delegate; - this.interceptors = interceptors; - } - - @Override - public T proceed() throws Throwable { - markInvokedOrSkipped(); - return delegate.proceed(); - } - - @Override - public void skip() { - logger.debug(() -> "The invocation is skipped"); - markInvokedOrSkipped(); - delegate.skip(); - } - - private void markInvokedOrSkipped() { - if (!invokedOrSkipped.compareAndSet(false, true)) { - fail("Chain of InvocationInterceptors called invocation multiple times instead of just once"); - } - } - - void verifyInvokedAtLeastOnce() { - if (!invokedOrSkipped.get()) { - fail("Chain of InvocationInterceptors never called invocation"); - } - } - - private void fail(String prefix) { - String commaSeparatedInterceptorClasses = interceptors.stream().map(Object::getClass).map( - Class::getName).collect(joining(", ")); - throw new JUnitException(prefix + ": " + commaSeparatedInterceptorClasses); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java deleted file mode 100644 index a31c9c02..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class JupiterEngineExecutionContext implements EngineExecutionContext { - - private static final Logger logger = LoggerFactory.getLogger(JupiterEngineExecutionContext.class); - - private final State state; - - // The following is not "cloneable" State. - private boolean beforeAllCallbacksExecuted = false; - private boolean beforeAllMethodsExecuted = false; - - public JupiterEngineExecutionContext(EngineExecutionListener executionListener, - JupiterConfiguration configuration) { - this(new State(executionListener, configuration)); - } - - private JupiterEngineExecutionContext(State state) { - this.state = state; - } - - public void close() throws Exception { - ExtensionContext extensionContext = getExtensionContext(); - if (extensionContext instanceof AutoCloseable) { - try { - ((AutoCloseable) extensionContext).close(); - } - catch (Exception e) { - logger.error(e, () -> "Caught exception while closing extension context: " + extensionContext); - throw e; - } - } - } - - public EngineExecutionListener getExecutionListener() { - return this.state.executionListener; - } - - public JupiterConfiguration getConfiguration() { - return this.state.configuration; - } - - public TestInstancesProvider getTestInstancesProvider() { - return this.state.testInstancesProvider; - } - - public MutableExtensionRegistry getExtensionRegistry() { - return this.state.extensionRegistry; - } - - public ExtensionContext getExtensionContext() { - return this.state.extensionContext; - } - - public ThrowableCollector getThrowableCollector() { - return this.state.throwableCollector; - } - - /** - * Track that an attempt was made to execute {@code BeforeAllCallback} extensions. - * - * @since 5.3 - */ - public void beforeAllCallbacksExecuted(boolean beforeAllCallbacksExecuted) { - this.beforeAllCallbacksExecuted = beforeAllCallbacksExecuted; - } - - /** - * @return {@code true} if an attempt was made to execute {@code BeforeAllCallback} - * extensions - * @since 5.3 - */ - public boolean beforeAllCallbacksExecuted() { - return beforeAllCallbacksExecuted; - } - - /** - * Track that an attempt was made to execute {@code @BeforeAll} methods. - */ - public void beforeAllMethodsExecuted(boolean beforeAllMethodsExecuted) { - this.beforeAllMethodsExecuted = beforeAllMethodsExecuted; - } - - /** - * @return {@code true} if an attempt was made to execute {@code @BeforeAll} - * methods - */ - public boolean beforeAllMethodsExecuted() { - return this.beforeAllMethodsExecuted; - } - - public Builder extend() { - return new Builder(this.state); - } - - private static final class State implements Cloneable { - - final EngineExecutionListener executionListener; - final JupiterConfiguration configuration; - TestInstancesProvider testInstancesProvider; - MutableExtensionRegistry extensionRegistry; - ExtensionContext extensionContext; - ThrowableCollector throwableCollector; - - State(EngineExecutionListener executionListener, JupiterConfiguration configuration) { - this.executionListener = executionListener; - this.configuration = configuration; - } - - @Override - public State clone() { - try { - return (State) super.clone(); - } - catch (CloneNotSupportedException e) { - throw new JUnitException("State could not be cloned", e); - } - } - - } - - public static class Builder { - - private State originalState; - private State newState = null; - - private Builder(State originalState) { - this.originalState = originalState; - } - - public Builder withTestInstancesProvider(TestInstancesProvider testInstancesProvider) { - newState().testInstancesProvider = testInstancesProvider; - return this; - } - - public Builder withExtensionRegistry(MutableExtensionRegistry extensionRegistry) { - newState().extensionRegistry = extensionRegistry; - return this; - } - - public Builder withExtensionContext(ExtensionContext extensionContext) { - newState().extensionContext = extensionContext; - return this; - } - - public Builder withThrowableCollector(ThrowableCollector throwableCollector) { - newState().throwableCollector = throwableCollector; - return this; - } - - public JupiterEngineExecutionContext build() { - if (newState != null) { - originalState = newState; - newState = null; - } - return new JupiterEngineExecutionContext(originalState); - } - - private State newState() { - if (newState == null) { - this.newState = originalState.clone(); - } - return newState; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java deleted file mode 100644 index a8bb6ae7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static java.util.Collections.unmodifiableList; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.platform.commons.util.ReflectionUtils; - -class MethodInvocation implements Invocation, ReflectiveInvocationContext { - - private final Method method; - private final Optional target; - private final Object[] arguments; - - MethodInvocation(Method method, Optional target, Object[] arguments) { - this.method = method; - this.target = target; - this.arguments = arguments; - } - - @Override - public Class getTargetClass() { - return this.target.> map(Object::getClass).orElseGet(this.method::getDeclaringClass); - } - - @Override - @SuppressWarnings("unchecked") - public Optional getTarget() { - return this.target; - } - - @Override - public Method getExecutable() { - return this.method; - } - - @Override - public List getArguments() { - return unmodifiableList(Arrays.asList(this.arguments)); - } - - @Override - @SuppressWarnings("unchecked") - public T proceed() { - return (T) ReflectionUtils.invokeMethod(this.method, this.target.orElse(null), this.arguments); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java deleted file mode 100644 index 1b563045..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class NamespaceAwareStore implements Store { - - private final ExtensionValuesStore valuesStore; - private final Namespace namespace; - - public NamespaceAwareStore(ExtensionValuesStore valuesStore, Namespace namespace) { - this.valuesStore = valuesStore; - this.namespace = namespace; - } - - @Override - public Object get(Object key) { - Preconditions.notNull(key, "key must not be null"); - return this.valuesStore.get(this.namespace, key); - } - - @Override - public T get(Object key, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return this.valuesStore.get(this.namespace, key, requiredType); - } - - @Override - public Object getOrComputeIfAbsent(K key, Function defaultCreator) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); - return this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator); - } - - @Override - public V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType); - } - - @Override - public void put(Object key, Object value) { - Preconditions.notNull(key, "key must not be null"); - this.valuesStore.put(this.namespace, key, value); - } - - @Override - public Object remove(Object key) { - Preconditions.notNull(key, "key must not be null"); - return this.valuesStore.remove(this.namespace, key); - } - - @Override - public T remove(Object key, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return this.valuesStore.remove(this.namespace, key, requiredType); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java deleted file mode 100644 index 87e121a3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.UnrecoverableExceptions; - -/** - * {@code ParameterResolutionUtils} provides support for dynamic resolution - * of executable parameters via {@link ParameterResolver ParameterResolvers}. - * - * @since 5.9 - */ -@API(status = INTERNAL, since = "5.9") -public class ParameterResolutionUtils { - - private static final Logger logger = LoggerFactory.getLogger(ParameterResolutionUtils.class); - - /** - * Resolve the array of parameters for the supplied method and target. - * - * @param method the method for which to resolve parameters - * @param target an {@code Optional} containing the target on which the - * executable will be invoked; never {@code null} but should be empty for - * static methods and constructors - * @param extensionContext the current {@code ExtensionContext} - * @param extensionRegistry the {@code ExtensionRegistry} to retrieve - * {@code ParameterResolvers} from - * @return the array of Objects to be used as parameters in the executable - * invocation; never {@code null} though potentially empty - */ - public static Object[] resolveParameters(Method method, Optional target, ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry) { - - return resolveParameters(method, target, Optional.empty(), extensionContext, extensionRegistry); - } - - /** - * Resolve the array of parameters for the supplied executable, target, and - * outer instance. - * - * @param executable the executable for which to resolve parameters - * @param target an {@code Optional} containing the target on which the - * executable will be invoked; never {@code null} but should be empty for - * static methods and constructors - * @param outerInstance the outer instance that will be supplied as the - * first argument to a constructor for an inner class; should be {@code null} - * for methods and constructors for top-level or static classes - * @param extensionContext the current {@code ExtensionContext} - * @param extensionRegistry the {@code ExtensionRegistry} to retrieve - * {@code ParameterResolvers} from - * @return the array of Objects to be used as parameters in the executable - * invocation; never {@code null} though potentially empty - */ - public static Object[] resolveParameters(Executable executable, Optional target, - Optional outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { - - Preconditions.notNull(target, "target must not be null"); - - Parameter[] parameters = executable.getParameters(); - Object[] values = new Object[parameters.length]; - int start = 0; - - // Ensure that the outer instance is resolved as the first parameter if - // the executable is a constructor for an inner class. - if (outerInstance.isPresent()) { - values[0] = outerInstance.get(); - start = 1; - } - - // Resolve remaining parameters dynamically - for (int i = start; i < parameters.length; i++) { - ParameterContext parameterContext = new DefaultParameterContext(parameters[i], i, target); - values[i] = resolveParameter(parameterContext, executable, extensionContext, extensionRegistry); - } - return values; - } - - private static Object resolveParameter(ParameterContext parameterContext, Executable executable, - ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { - - try { - // @formatter:off - List matchingResolvers = extensionRegistry.stream(ParameterResolver.class) - .filter(resolver -> resolver.supportsParameter(parameterContext, extensionContext)) - .collect(toList()); - // @formatter:on - - if (matchingResolvers.isEmpty()) { - throw new ParameterResolutionException( - String.format("No ParameterResolver registered for parameter [%s] in %s [%s].", - parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); - } - - if (matchingResolvers.size() > 1) { - // @formatter:off - String resolvers = matchingResolvers.stream() - .map(StringUtils::defaultToString) - .collect(joining(", ")); - // @formatter:on - throw new ParameterResolutionException( - String.format("Discovered multiple competing ParameterResolvers for parameter [%s] in %s [%s]: %s", - parameterContext.getParameter(), asLabel(executable), executable.toGenericString(), resolvers)); - } - - ParameterResolver resolver = matchingResolvers.get(0); - Object value = resolver.resolveParameter(parameterContext, extensionContext); - validateResolvedType(parameterContext.getParameter(), value, executable, resolver); - - logger.trace(() -> String.format( - "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] in %s [%s].", - resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), - parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); - - return value; - } - catch (ParameterResolutionException ex) { - throw ex; - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - - String message = String.format("Failed to resolve parameter [%s] in %s [%s]", - parameterContext.getParameter(), asLabel(executable), executable.toGenericString()); - - if (StringUtils.isNotBlank(throwable.getMessage())) { - message += ": " + throwable.getMessage(); - } - - throw new ParameterResolutionException(message, throwable); - } - } - - private static void validateResolvedType(Parameter parameter, Object value, Executable executable, - ParameterResolver resolver) { - - Class type = parameter.getType(); - - // Note: null is permissible as a resolved value but only for non-primitive types. - if (!isAssignableTo(value, type)) { - String message; - if (value == null && type.isPrimitive()) { - message = String.format( - "ParameterResolver [%s] resolved a null value for parameter [%s] " - + "in %s [%s], but a primitive of type [%s] is required.", - resolver.getClass().getName(), parameter, asLabel(executable), executable.toGenericString(), - type.getName()); - } - else { - message = String.format( - "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] " - + "in %s [%s], but a value assignment compatible with [%s] is required.", - resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameter, - asLabel(executable), executable.toGenericString(), type.getName()); - } - - throw new ParameterResolutionException(message); - } - } - - private static String asLabel(Executable executable) { - return executable instanceof Constructor ? "constructor" : "method"; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java deleted file mode 100644 index 1f5819a6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.extension.ExtensionRegistrar; -import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * @since 5.0 - */ -@FunctionalInterface -@API(status = INTERNAL, since = "5.0") -public interface TestInstancesProvider { - - default TestInstances getTestInstances(MutableExtensionRegistry extensionRegistry, - ThrowableCollector throwableCollector) { - return getTestInstances(extensionRegistry, extensionRegistry, throwableCollector); - } - - TestInstances getTestInstances(ExtensionRegistry extensionRegistry, ExtensionRegistrar extensionRegistrar, - ThrowableCollector throwableCollector); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java deleted file mode 100644 index 028111e0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Internal classes for test execution within the JUnit Jupiter test engine. - */ - -package org.junit.jupiter.engine.execution; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java deleted file mode 100644 index 6279cac6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.reflect.AnnotatedElement; - -import org.junit.jupiter.api.Disabled; -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.util.StringUtils; - -/** - * {@link ExecutionCondition} that supports the {@code @Disabled} annotation. - * - * @since 5.0 - * @see Disabled - * @see #evaluateExecutionCondition(ExtensionContext) - */ -class DisabledCondition implements ExecutionCondition { - - private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( - "@Disabled is not present"); - - /** - * Containers/tests are disabled if {@code @Disabled} is present on the test - * class or method. - */ - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - AnnotatedElement element = context.getElement().orElse(null); - return findAnnotation(element, Disabled.class) // - .map(annotation -> toResult(element, annotation)) // - .orElse(ENABLED); - } - - private ConditionEvaluationResult toResult(AnnotatedElement element, Disabled annotation) { - String value = annotation.value(); - String reason = StringUtils.isNotBlank(value) ? value : element + " is @Disabled"; - return ConditionEvaluationResult.disabled(reason); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java deleted file mode 100644 index e555aecd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.Extension; - -/** - * An {@code ExtensionRegistrar} is used to register extensions. - * - * @since 5.5 - */ -@API(status = INTERNAL, since = "5.5") -public interface ExtensionRegistrar { - - /** - * Instantiate an extension of the given type using its default constructor - * and register it in the registry. - * - *

A new {@link Extension} should not be registered if an extension of the - * given type already exists in the registry or a parent registry. - * - * @param extensionType the type of extension to register - * @since 5.8 - */ - void registerExtension(Class extensionType); - - /** - * Register the supplied {@link Extension}, without checking if an extension - * of that type has already been registered. - * - *

Semantics for Source

- * - *

If an extension is registered declaratively via - * {@link org.junit.jupiter.api.extension.ExtendWith @ExtendWith}, the - * {@code source} and the {@code extension} should be the same object. - * However, if an extension is registered programmatically via - * {@link org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension}, - * the {@code source} object should be the {@link java.lang.reflect.Field} - * that is annotated with {@code @RegisterExtension}. Similarly, if an - * extension is registered programmatically as a lambda expression - * or method reference, the {@code source} object should be the underlying - * {@link java.lang.reflect.Method} that implements the extension API. - * - * @param extension the extension to register; never {@code null} - * @param source the source of the extension; never {@code null} - */ - void registerExtension(Extension extension, Object source); - - /** - * Register the supplied {@link Extension} as a synthetic extension, - * without checking if an extension of that type has already been registered. - * - * @param extension the extension to register; never {@code null} - * @param source the source of the extension; never {@code null} - * @since 5.8 - * @see #registerExtension(Extension, Object) - */ - void registerSyntheticExtension(Extension extension, Object source); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java deleted file mode 100644 index d859d6b8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.stream.Collectors.toCollection; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.Extension; - -/** - * An {@code ExtensionRegistry} holds all registered extensions (i.e. - * instances of {@link Extension}) for a given - * {@link org.junit.platform.engine.support.hierarchical.Node}. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public interface ExtensionRegistry { - - /** - * Stream all {@code Extensions} of the specified type that are present - * in this registry or one of its ancestors. - * - * @param extensionType the type of {@link Extension} to stream - * @see #getReversedExtensions(Class) - * @see #getExtensions(Class) - */ - Stream stream(Class extensionType); - - /** - * Get all {@code Extensions} of the specified type that are present - * in this registry or one of its ancestors. - * - * @param extensionType the type of {@link Extension} to get - * @see #getReversedExtensions(Class) - * @see #stream(Class) - */ - default List getExtensions(Class extensionType) { - return stream(extensionType).collect(toCollection(ArrayList::new)); - } - - /** - * Get all {@code Extensions} of the specified type that are present - * in this registry or one of its ancestors, in reverse order. - * - * @param extensionType the type of {@link Extension} to get - * @see #getExtensions(Class) - * @see #stream(Class) - */ - default List getReversedExtensions(Class extensionType) { - List extensions = getExtensions(extensionType); - Collections.reverse(extensions); - return extensions; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java deleted file mode 100644 index 2856cdc3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.stream.Stream.concat; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.ServiceLoader; -import java.util.Set; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassLoaderUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Default, mutable implementation of {@link ExtensionRegistry}. - * - *

A registry has a reference to its parent registry, and all lookups are - * performed first in the current registry itself and then recursively in its - * ancestors. - * - * @since 5.5 - */ -@API(status = INTERNAL, since = "5.5") -public class MutableExtensionRegistry implements ExtensionRegistry, ExtensionRegistrar { - - private static final Logger logger = LoggerFactory.getLogger(MutableExtensionRegistry.class); - - private static final List DEFAULT_STATELESS_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(// - new DisabledCondition(), // - new TimeoutExtension(), // - new RepeatedTestExtension(), // - new TestInfoParameterResolver(), // - new TestReporterParameterResolver())); - - /** - * Factory for creating and populating a new root registry with the default - * extensions. - * - *

If the {@link org.junit.jupiter.engine.Constants#EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME} - * configuration parameter has been set to {@code true}, extensions will be - * auto-detected using Java's {@link ServiceLoader} mechanism and automatically - * registered after the default extensions. - * - * @param configuration configuration parameters used to retrieve the extension - * auto-detection flag; never {@code null} - * @return a new {@code ExtensionRegistry}; never {@code null} - */ - public static MutableExtensionRegistry createRegistryWithDefaultExtensions(JupiterConfiguration configuration) { - MutableExtensionRegistry extensionRegistry = new MutableExtensionRegistry(null); - - DEFAULT_STATELESS_EXTENSIONS.forEach(extensionRegistry::registerDefaultExtension); - - extensionRegistry.registerDefaultExtension(new TempDirectory(configuration)); - - if (configuration.isExtensionAutoDetectionEnabled()) { - registerAutoDetectedExtensions(extensionRegistry); - } - - return extensionRegistry; - } - - private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry) { - ServiceLoader.load(Extension.class, ClassLoaderUtils.getDefaultClassLoader())// - .forEach(extensionRegistry::registerAutoDetectedExtension); - } - - /** - * Factory for creating and populating a new registry from a list of - * extension types and a parent registry. - * - * @param parentRegistry the parent registry - * @param extensionTypes the types of extensions to be registered in - * the new registry - * @return a new {@code ExtensionRegistry}; never {@code null} - */ - public static MutableExtensionRegistry createRegistryFrom(MutableExtensionRegistry parentRegistry, - Stream> extensionTypes) { - - Preconditions.notNull(parentRegistry, "parentRegistry must not be null"); - - MutableExtensionRegistry registry = new MutableExtensionRegistry(parentRegistry); - extensionTypes.forEach(registry::registerExtension); - return registry; - } - - private final MutableExtensionRegistry parent; - - private final Set> registeredExtensionTypes = new LinkedHashSet<>(); - - private final List registeredExtensions = new ArrayList<>(); - - private MutableExtensionRegistry(MutableExtensionRegistry parent) { - this.parent = parent; - } - - @Override - public Stream stream(Class extensionType) { - if (this.parent == null) { - return streamLocal(extensionType); - } - return concat(this.parent.stream(extensionType), streamLocal(extensionType)); - } - - /** - * Stream all {@code Extensions} of the specified type that are present - * in this registry. - * - *

Extensions in ancestors are ignored. - * - * @param extensionType the type of {@link Extension} to stream - * @see #getReversedExtensions(Class) - */ - private Stream streamLocal(Class extensionType) { - // @formatter:off - return this.registeredExtensions.stream() - .filter(extensionType::isInstance) - .map(extensionType::cast); - // @formatter:on - } - - @Override - public void registerExtension(Class extensionType) { - if (!isAlreadyRegistered(extensionType)) { - registerLocalExtension(ReflectionUtils.newInstance(extensionType)); - } - } - - /** - * Determine if the supplied type is already registered in this registry or in a - * parent registry. - */ - private boolean isAlreadyRegistered(Class extensionType) { - return (this.registeredExtensionTypes.contains(extensionType) - || (this.parent != null && this.parent.isAlreadyRegistered(extensionType))); - } - - @Override - public void registerExtension(Extension extension, Object source) { - Preconditions.notNull(source, "source must not be null"); - registerExtension("local", extension, source); - } - - @Override - public void registerSyntheticExtension(Extension extension, Object source) { - registerExtension("synthetic", extension, source); - } - - private void registerDefaultExtension(Extension extension) { - registerExtension("default", extension); - } - - private void registerAutoDetectedExtension(Extension extension) { - registerExtension("auto-detected", extension); - } - - private void registerLocalExtension(Extension extension) { - registerExtension("local", extension); - } - - private void registerExtension(String category, Extension extension) { - registerExtension(category, extension, null); - } - - private void registerExtension(String category, Extension extension, Object source) { - Preconditions.notBlank(category, "category must not be null or blank"); - Preconditions.notNull(extension, "Extension must not be null"); - - logger.trace( - () -> String.format("Registering %s extension [%s]%s", category, extension, buildSourceInfo(source))); - - this.registeredExtensions.add(extension); - this.registeredExtensionTypes.add(extension.getClass()); - } - - private String buildSourceInfo(Object source) { - if (source == null) { - return ""; - } - if (source instanceof Member) { - Member member = (Member) source; - Object type = (member instanceof Method ? "method" : "field"); - source = String.format("%s %s.%s", type, member.getDeclaringClass().getName(), member.getName()); - } - return " from source [" + source + "]"; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java deleted file mode 100644 index 1b2657fd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.RepeatedTest.CURRENT_REPETITION_PLACEHOLDER; -import static org.junit.jupiter.api.RepeatedTest.DISPLAY_NAME_PLACEHOLDER; -import static org.junit.jupiter.api.RepeatedTest.TOTAL_REPETITIONS_PLACEHOLDER; - -import org.junit.jupiter.api.RepeatedTest; - -/** - * Display name formatter for a {@link RepeatedTest @RepeatedTest}. - * - * @since 5.0 - */ -class RepeatedTestDisplayNameFormatter { - - private final String pattern; - private final String displayName; - - RepeatedTestDisplayNameFormatter(String pattern, String displayName) { - this.pattern = pattern; - this.displayName = displayName; - } - - String format(int currentRepetition, int totalRepetitions) { - return this.pattern// - .replace(DISPLAY_NAME_PLACEHOLDER, this.displayName)// - .replace(CURRENT_REPETITION_PLACEHOLDER, String.valueOf(currentRepetition))// - .replace(TOTAL_REPETITIONS_PLACEHOLDER, String.valueOf(totalRepetitions)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java deleted file mode 100644 index ce561b58..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; - -import java.lang.reflect.Method; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@code TestTemplateInvocationContextProvider} that supports the - * {@link RepeatedTest @RepeatedTest} annotation. - * - * @since 5.0 - */ -class RepeatedTestExtension implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return isAnnotated(context.getTestMethod(), RepeatedTest.class); - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - Method testMethod = context.getRequiredTestMethod(); - String displayName = context.getDisplayName(); - RepeatedTest repeatedTest = AnnotationUtils.findAnnotation(testMethod, RepeatedTest.class).get(); - int totalRepetitions = totalRepetitions(repeatedTest, testMethod); - RepeatedTestDisplayNameFormatter formatter = displayNameFormatter(repeatedTest, testMethod, displayName); - - // @formatter:off - return IntStream - .rangeClosed(1, totalRepetitions) - .mapToObj(repetition -> new RepeatedTestInvocationContext(repetition, totalRepetitions, formatter)); - // @formatter:on - } - - private int totalRepetitions(RepeatedTest repeatedTest, Method method) { - int repetitions = repeatedTest.value(); - Preconditions.condition(repetitions > 0, () -> String.format( - "Configuration error: @RepeatedTest on method [%s] must be declared with a positive 'value'.", method)); - return repetitions; - } - - private RepeatedTestDisplayNameFormatter displayNameFormatter(RepeatedTest repeatedTest, Method method, - String displayName) { - String pattern = Preconditions.notBlank(repeatedTest.name().trim(), () -> String.format( - "Configuration error: @RepeatedTest on method [%s] must be declared with a non-empty name.", method)); - return new RepeatedTestDisplayNameFormatter(pattern, displayName); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java deleted file mode 100644 index 1ec271b7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.Collections.singletonList; - -import java.util.List; - -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; - -/** - * {@code TestTemplateInvocationContext} for a {@link org.junit.jupiter.api.RepeatedTest @RepeatedTest}. - * - * @since 5.0 - */ -class RepeatedTestInvocationContext implements TestTemplateInvocationContext { - - private final int currentRepetition; - private final int totalRepetitions; - private final RepeatedTestDisplayNameFormatter formatter; - - public RepeatedTestInvocationContext(int currentRepetition, int totalRepetitions, - RepeatedTestDisplayNameFormatter formatter) { - - this.currentRepetition = currentRepetition; - this.totalRepetitions = totalRepetitions; - this.formatter = formatter; - } - - @Override - public String getDisplayName(int invocationIndex) { - return this.formatter.format(this.currentRepetition, this.totalRepetitions); - } - - @Override - public List getAdditionalExtensions() { - return singletonList(new RepetitionInfoParameterResolver(this.currentRepetition, this.totalRepetitions)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java deleted file mode 100644 index be104eed..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import org.junit.jupiter.api.RepetitionInfo; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@link ParameterResolver} that resolves the {@link RepetitionInfo} for - * the currently executing {@code @RepeatedTest}. - * - * @since 5.0 - */ -class RepetitionInfoParameterResolver implements ParameterResolver { - - private final int currentRepetition; - private final int totalRepetitions; - - public RepetitionInfoParameterResolver(int currentRepetition, int totalRepetitions) { - this.currentRepetition = currentRepetition; - this.totalRepetitions = totalRepetitions; - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return (parameterContext.getParameter().getType() == RepetitionInfo.class); - } - - @Override - public RepetitionInfo resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return new DefaultRepetitionInfo(this.currentRepetition, this.totalRepetitions); - } - - private static class DefaultRepetitionInfo implements RepetitionInfo { - - private final int currentRepetition; - private final int totalRepetitions; - - DefaultRepetitionInfo(int currentRepetition, int totalRepetitions) { - this.currentRepetition = currentRepetition; - this.totalRepetitions = totalRepetitions; - } - - @Override - public int getCurrentRepetition() { - return this.currentRepetition; - } - - @Override - public int getTotalRepetitions() { - return this.totalRepetitions; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("currentRepetition", this.currentRepetition) - .append("totalRepetitions", this.totalRepetitions) - .toString(); - // @formatter:on - } - - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java deleted file mode 100644 index e620151d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.function.Supplier; - -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.platform.commons.util.UnrecoverableExceptions; - -/** - * @since 5.5 - */ -class SameThreadTimeoutInvocation implements Invocation { - - private final Invocation delegate; - private final TimeoutDuration timeout; - private final ScheduledExecutorService executor; - private final Supplier descriptionSupplier; - - SameThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, ScheduledExecutorService executor, - Supplier descriptionSupplier) { - this.delegate = delegate; - this.timeout = timeout; - this.executor = executor; - this.descriptionSupplier = descriptionSupplier; - } - - @Override - public T proceed() throws Throwable { - InterruptTask interruptTask = new InterruptTask(Thread.currentThread()); - ScheduledFuture future = executor.schedule(interruptTask, timeout.getValue(), timeout.getUnit()); - Throwable failure = null; - T result = null; - try { - result = delegate.proceed(); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - failure = t; - } - finally { - boolean cancelled = future.cancel(false); - if (!cancelled) { - future.get(); - } - if (interruptTask.executed) { - Thread.interrupted(); - failure = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, failure); - } - } - if (failure != null) { - throw failure; - } - return result; - } - - static class InterruptTask implements Runnable { - - private final Thread thread; - private volatile boolean executed; - - InterruptTask(Thread thread) { - this.thread = thread; - } - - @Override - public void run() { - executed = true; - thread.interrupt(); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java deleted file mode 100644 index 9453577a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; - -import java.util.concurrent.TimeoutException; -import java.util.function.Supplier; - -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; - -/** - * @since 5.9 - */ -class SeparateThreadTimeoutInvocation implements Invocation { - - private final Invocation delegate; - private final TimeoutDuration timeout; - private final Supplier descriptionSupplier; - - SeparateThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, - Supplier descriptionSupplier) { - this.delegate = delegate; - this.timeout = timeout; - this.descriptionSupplier = descriptionSupplier; - } - - @Override - public T proceed() throws Throwable { - return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier, - (__, messageSupplier, cause) -> { - TimeoutException exception = TimeoutExceptionFactory.create(messageSupplier.get(), timeout, null); - exception.initCause(cause); - return exception; - }); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java deleted file mode 100644 index 624c9adb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.nio.file.FileVisitResult.CONTINUE; -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.api.io.CleanupMode.DEFAULT; -import static org.junit.jupiter.api.io.CleanupMode.NEVER; -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Parameter; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.function.Predicate; - -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.engine.config.EnumConfigurationParameterConverter; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * {@code TempDirectory} is a JUnit Jupiter extension that creates and cleans - * up temporary directories if field in a test class or a parameter in a - * lifecycle method or test method is annotated with {@code @TempDir}. - * - *

Consult the Javadoc for {@link TempDir} for details on the contract. - * - * @since 5.4 - * @see TempDir - * @see Files#createTempDirectory - */ -class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { - - static final Namespace NAMESPACE = Namespace.create(TempDirectory.class); - private static final String KEY = "temp.dir"; - private static final String TEMP_DIR_PREFIX = "junit"; - - // for testing purposes - static final String FILE_OPERATIONS_KEY = "file.operations"; - - private final JupiterConfiguration configuration; - - public TempDirectory(JupiterConfiguration configuration) { - this.configuration = configuration; - } - - /** - * Perform field injection for non-private, {@code static} fields (i.e., - * class fields) of type {@link Path} or {@link File} that are annotated with - * {@link TempDir @TempDir}. - */ - @Override - public void beforeAll(ExtensionContext context) { - injectStaticFields(context, context.getRequiredTestClass()); - } - - /** - * Perform field injection for non-private, non-static fields (i.e., - * instance fields) of type {@link Path} or {@link File} that are annotated - * with {@link TempDir @TempDir}. - */ - @Override - public void beforeEach(ExtensionContext context) { - context.getRequiredTestInstances().getAllInstances() // - .forEach(instance -> injectInstanceFields(context, instance)); - } - - private void injectStaticFields(ExtensionContext context, Class testClass) { - injectFields(context, null, testClass, ReflectionUtils::isStatic); - } - - private void injectInstanceFields(ExtensionContext context, Object instance) { - injectFields(context, instance, instance.getClass(), ReflectionUtils::isNotStatic); - } - - private void injectFields(ExtensionContext context, Object testInstance, Class testClass, - Predicate predicate) { - - findAnnotatedFields(testClass, TempDir.class, predicate).forEach(field -> { - assertNonFinalField(field); - assertSupportedType("field", field.getType()); - - try { - CleanupMode cleanupMode = determineCleanupModeForField(field); - makeAccessible(field).set(testInstance, getPathOrFile(field, field.getType(), cleanupMode, context)); - } - catch (Throwable t) { - ExceptionUtils.throwAsUncheckedException(t); - } - }); - } - - /** - * Determine if the {@link Parameter} in the supplied {@link ParameterContext} - * is annotated with {@link TempDir @TempDir}. - */ - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - boolean annotated = parameterContext.isAnnotated(TempDir.class); - if (annotated && parameterContext.getDeclaringExecutable() instanceof Constructor) { - throw new ParameterResolutionException( - "@TempDir is not supported on constructor parameters. Please use field injection instead."); - } - return annotated; - } - - /** - * Resolve the current temporary directory for the {@link Parameter} in the - * supplied {@link ParameterContext}. - */ - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - Class parameterType = parameterContext.getParameter().getType(); - assertSupportedType("parameter", parameterType); - CleanupMode cleanupMode = determineCleanupModeForParameter(parameterContext); - return getPathOrFile(parameterContext.getParameter(), parameterType, cleanupMode, extensionContext); - } - - private CleanupMode determineCleanupModeForField(Field field) { - TempDir tempDir = findAnnotation(field, TempDir.class).orElseThrow( - () -> new JUnitException("Field " + field + " must be annotated with @TempDir")); - return determineCleanupMode(tempDir); - } - - private CleanupMode determineCleanupModeForParameter(ParameterContext parameterContext) { - TempDir tempDir = parameterContext.findAnnotation(TempDir.class).orElseThrow(() -> new JUnitException( - "Parameter " + parameterContext.getParameter() + " must be annotated with @TempDir")); - return determineCleanupMode(tempDir); - } - - private CleanupMode determineCleanupMode(TempDir tempDir) { - CleanupMode cleanupMode = tempDir.cleanup(); - return cleanupMode == DEFAULT ? this.configuration.getDefaultTempDirCleanupMode() : cleanupMode; - } - - private void assertNonFinalField(Field field) { - if (ReflectionUtils.isFinal(field)) { - throw new ExtensionConfigurationException("@TempDir field [" + field + "] must not be declared as final."); - } - } - - private void assertSupportedType(String target, Class type) { - if (type != Path.class && type != File.class) { - throw new ExtensionConfigurationException("Can only resolve @TempDir " + target + " of type " - + Path.class.getName() + " or " + File.class.getName() + " but was: " + type.getName()); - } - } - - private Object getPathOrFile(AnnotatedElement sourceElement, Class type, CleanupMode cleanupMode, - ExtensionContext extensionContext) { - Namespace namespace = getScope(extensionContext) == Scope.PER_DECLARATION // - ? NAMESPACE.append(sourceElement) // - : NAMESPACE; - Path path = extensionContext.getStore(namespace) // - .getOrComputeIfAbsent(KEY, __ -> createTempDir(cleanupMode, extensionContext), CloseablePath.class) // - .get(); - - return (type == Path.class) ? path : path.toFile(); - } - - @SuppressWarnings("deprecation") - private Scope getScope(ExtensionContext context) { - return context.getRoot().getStore(NAMESPACE).getOrComputeIfAbsent( // - Scope.class, // - __ -> new EnumConfigurationParameterConverter<>(Scope.class, "@TempDir scope") // - .get(TempDir.SCOPE_PROPERTY_NAME, context::getConfigurationParameter, Scope.PER_DECLARATION), // - Scope.class // - ); - } - - static CloseablePath createTempDir(CleanupMode cleanupMode, ExtensionContext executionContext) { - try { - return new CloseablePath(Files.createTempDirectory(TEMP_DIR_PREFIX), cleanupMode, executionContext); - } - catch (Exception ex) { - throw new ExtensionConfigurationException("Failed to create default temp directory", ex); - } - } - - static class CloseablePath implements CloseableResource { - - private static final Logger logger = LoggerFactory.getLogger(CloseablePath.class); - - private final Path dir; - private final CleanupMode cleanupMode; - private final ExtensionContext executionContext; - - CloseablePath(Path dir, CleanupMode cleanupMode, ExtensionContext executionContext) { - this.dir = dir; - this.cleanupMode = cleanupMode; - this.executionContext = executionContext; - } - - Path get() { - return dir; - } - - @Override - public void close() throws IOException { - if (cleanupMode == NEVER - || (cleanupMode == ON_SUCCESS && executionContext.getExecutionException().isPresent())) { - logger.info(() -> "Skipping cleanup of temp dir " + dir + " due to cleanup mode configuration."); - return; - } - - FileOperations fileOperations = executionContext.getStore(NAMESPACE) // - .getOrDefault(FILE_OPERATIONS_KEY, FileOperations.class, FileOperations.DEFAULT); - - SortedMap failures = deleteAllFilesAndDirectories(fileOperations); - if (!failures.isEmpty()) { - throw createIOExceptionWithAttachedFailures(failures); - } - } - - private SortedMap deleteAllFilesAndDirectories(FileOperations fileOperations) - throws IOException { - if (Files.notExists(dir)) { - return Collections.emptySortedMap(); - } - - SortedMap failures = new TreeMap<>(); - Set retriedPaths = new HashSet<>(); - resetPermissions(dir); - Files.walkFileTree(dir, new SimpleFileVisitor() { - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { - if (!dir.equals(CloseablePath.this.dir)) { - resetPermissions(dir); - } - return CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - // IOException includes `AccessDeniedException` thrown by non-readable or non-executable flags - resetPermissionsAndTryToDeleteAgain(file, exc); - return CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) { - return deleteAndContinue(file); - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - return deleteAndContinue(dir); - } - - private FileVisitResult deleteAndContinue(Path path) { - try { - fileOperations.delete(path); - } - catch (NoSuchFileException ignore) { - // ignore - } - catch (DirectoryNotEmptyException exception) { - failures.put(path, exception); - } - catch (IOException exception) { - // IOException includes `AccessDeniedException` thrown by non-readable or non-executable flags - resetPermissionsAndTryToDeleteAgain(path, exception); - } - return CONTINUE; - } - - private void resetPermissionsAndTryToDeleteAgain(Path path, IOException exception) { - boolean notYetRetried = retriedPaths.add(path); - if (notYetRetried) { - try { - resetPermissions(path); - if (Files.isDirectory(path)) { - Files.walkFileTree(path, this); - } - else { - fileOperations.delete(path); - } - } - catch (Exception suppressed) { - exception.addSuppressed(suppressed); - failures.put(path, exception); - } - } - else { - failures.put(path, exception); - } - } - }); - return failures; - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private static void resetPermissions(Path path) { - File file = path.toFile(); - file.setReadable(true); - file.setWritable(true); - if (Files.isDirectory(path)) { - file.setExecutable(true); - } - } - - private IOException createIOExceptionWithAttachedFailures(SortedMap failures) { - Path emptyPath = Paths.get(""); - String joinedPaths = failures.keySet().stream() // - .map(this::tryToDeleteOnExit) // - .map(this::relativizeSafely) // - .map(path -> emptyPath.equals(path) ? "" : path.toString()) // - .collect(joining(", ")); - IOException exception = new IOException("Failed to delete temp directory " + dir.toAbsolutePath() - + ". The following paths could not be deleted (see suppressed exceptions for details): " - + joinedPaths); - failures.values().forEach(exception::addSuppressed); - return exception; - } - - private Path tryToDeleteOnExit(Path path) { - try { - path.toFile().deleteOnExit(); - } - catch (UnsupportedOperationException ignore) { - } - return path; - } - - private Path relativizeSafely(Path path) { - try { - return dir.relativize(path); - } - catch (IllegalArgumentException e) { - return path; - } - } - } - - enum Scope { - - PER_CONTEXT, - - PER_DECLARATION - - } - - interface FileOperations { - - FileOperations DEFAULT = Files::delete; - - void delete(Path path) throws IOException; - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java deleted file mode 100644 index 1fdd3c7b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@link ParameterResolver} that resolves the {@link TestInfo} for - * the currently executing test. - * - * @since 5.0 - */ -class TestInfoParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return (parameterContext.getParameter().getType() == TestInfo.class); - } - - @Override - public TestInfo resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return new DefaultTestInfo(extensionContext); - } - - private static class DefaultTestInfo implements TestInfo { - - private final String displayName; - private final Set tags; - private final Optional> testClass; - private final Optional testMethod; - - DefaultTestInfo(ExtensionContext extensionContext) { - this.displayName = extensionContext.getDisplayName(); - this.tags = extensionContext.getTags(); - this.testClass = extensionContext.getTestClass(); - this.testMethod = extensionContext.getTestMethod(); - } - - @Override - public String getDisplayName() { - return this.displayName; - } - - @Override - public Set getTags() { - return this.tags; - } - - @Override - public Optional> getTestClass() { - return this.testClass; - } - - @Override - public Optional getTestMethod() { - return this.testMethod; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("displayName", this.displayName) - .append("tags", this.tags) - .append("testClass", nullSafeGet(this.testClass)) - .append("testMethod", nullSafeGet(this.testMethod)) - .toString(); - // @formatter:on - } - - private static Object nullSafeGet(Optional optional) { - return optional != null ? optional.orElse(null) : null; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java deleted file mode 100644 index ffa5cf06..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * {@link ParameterResolver} that injects a {@link TestReporter}. - * - * @since 5.0 - */ -class TestReporterParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return (parameterContext.getParameter().getType() == TestReporter.class); - } - - @Override - public TestReporter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return extensionContext::publishReportEntry; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java deleted file mode 100644 index 1391d4cd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; -import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; - -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Timeout.ThreadMode; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * @since 5.5 - */ -class TimeoutConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(TimeoutConfiguration.class); - - private final TimeoutDurationParser parser = new TimeoutDurationParser(); - private final Map> cache = new ConcurrentHashMap<>(); - private final AtomicReference> threadMode = new AtomicReference<>(); - private final ExtensionContext extensionContext; - - TimeoutConfiguration(ExtensionContext extensionContext) { - this.extensionContext = extensionContext; - } - - Optional getDefaultTestMethodTimeout() { - return parseOrDefault(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTestableMethodTimeout); - } - - Optional getDefaultTestTemplateMethodTimeout() { - return parseOrDefault(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, - this::getDefaultTestableMethodTimeout); - } - - Optional getDefaultTestFactoryMethodTimeout() { - return parseOrDefault(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTestableMethodTimeout); - } - - Optional getDefaultBeforeAllMethodTimeout() { - return parseOrDefault(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); - } - - Optional getDefaultBeforeEachMethodTimeout() { - return parseOrDefault(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); - } - - Optional getDefaultAfterEachMethodTimeout() { - return parseOrDefault(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); - } - - Optional getDefaultAfterAllMethodTimeout() { - return parseOrDefault(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); - } - - private Optional getDefaultTestableMethodTimeout() { - return parseOrDefault(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTimeout); - } - - private Optional getDefaultLifecycleMethodTimeout() { - return parseOrDefault(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTimeout); - } - - private Optional getDefaultTimeout() { - return parseTimeoutDuration(DEFAULT_TIMEOUT_PROPERTY_NAME); - } - - private Optional parseOrDefault(String propertyName, - Supplier> defaultSupplier) { - Optional timeoutConfiguration = parseTimeoutDuration(propertyName); - return timeoutConfiguration.isPresent() ? timeoutConfiguration : defaultSupplier.get(); - } - - private Optional parseTimeoutDuration(String propertyName) { - return cache.computeIfAbsent(propertyName, key -> extensionContext.getConfigurationParameter(key).map(value -> { - try { - return parser.parse(value); - } - catch (Exception e) { - logger.warn(e, - () -> String.format("Ignored invalid timeout '%s' set via the '%s' configuration parameter.", value, - key)); - return null; - } - })); - } - - Optional getDefaultTimeoutThreadMode() { - if (threadMode.get() != null) { - return threadMode.get(); - } - else { - Optional configuredThreadMode = parseTimeoutThreadModeConfiguration(); - threadMode.set(configuredThreadMode); - return configuredThreadMode; - } - } - - private Optional parseTimeoutThreadModeConfiguration() { - return extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME).map(value -> { - try { - ThreadMode threadMode = ThreadMode.valueOf(value.toUpperCase()); - if (threadMode == ThreadMode.INFERRED) { - logger.warn(() -> String.format( - "Invalid timeout thread mode '%s', only %s and %s can be used as configuration parameter for %s.", - value, SAME_THREAD, SEPARATE_THREAD, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)); - return null; - } - return threadMode; - } - catch (Exception e) { - logger.warn(e, - () -> String.format("Invalid timeout thread mode '%s' set via the '%s' configuration parameter.", - value, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)); - return null; - } - }); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java deleted file mode 100644 index eb058aef..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Timeout; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.5 - */ -class TimeoutDuration { - - static TimeoutDuration from(Timeout timeout) { - return new TimeoutDuration(timeout.value(), timeout.unit()); - } - - private final long value; - private final TimeUnit unit; - - TimeoutDuration(long value, TimeUnit unit) { - Preconditions.condition(value > 0, () -> "timeout duration must be a positive number: " + value); - this.value = value; - this.unit = Preconditions.notNull(unit, "timeout unit must not be null"); - } - - public long getValue() { - return value; - } - - public TimeUnit getUnit() { - return unit; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TimeoutDuration that = (TimeoutDuration) o; - return value == that.value && unit == that.unit; - } - - @Override - public int hashCode() { - return Objects.hash(value, unit); - } - - @Override - public String toString() { - String label = unit.name().toLowerCase(); - if (value == 1 && label.endsWith("s")) { - label = label.substring(0, label.length() - 1); - } - return value + " " + label; - } - - public Duration toDuration() { - return Duration.of(value, toChronoUnit()); - } - - private ChronoUnit toChronoUnit() { - switch (unit) { - case NANOSECONDS: - return ChronoUnit.NANOS; - case MICROSECONDS: - return ChronoUnit.MICROS; - case MILLISECONDS: - return ChronoUnit.MILLIS; - case SECONDS: - return ChronoUnit.SECONDS; - case MINUTES: - return ChronoUnit.MINUTES; - case HOURS: - return ChronoUnit.HOURS; - case DAYS: - return ChronoUnit.DAYS; - default: - throw new JUnitException("Could not map TimeUnit " + unit + " to ChronoUnit"); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java deleted file mode 100644 index 9412983b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.HOURS; -import static java.util.concurrent.TimeUnit.MICROSECONDS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static java.util.regex.Pattern.CASE_INSENSITIVE; -import static java.util.regex.Pattern.UNICODE_CASE; - -import java.time.format.DateTimeParseException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * @since 5.5 - */ -class TimeoutDurationParser { - - private static final Pattern PATTERN = Pattern.compile("([1-9]\\d*) ?((?:[nμm]?s)|m|h|d)?", - CASE_INSENSITIVE | UNICODE_CASE); - private static final Map UNITS_BY_ABBREVIATION; - - static { - Map unitsByAbbreviation = new HashMap<>(); - unitsByAbbreviation.put("ns", NANOSECONDS); - unitsByAbbreviation.put("μs", MICROSECONDS); - unitsByAbbreviation.put("ms", MILLISECONDS); - unitsByAbbreviation.put("s", SECONDS); - unitsByAbbreviation.put("m", MINUTES); - unitsByAbbreviation.put("h", HOURS); - unitsByAbbreviation.put("d", DAYS); - UNITS_BY_ABBREVIATION = Collections.unmodifiableMap(unitsByAbbreviation); - } - - TimeoutDuration parse(CharSequence text) throws DateTimeParseException { - Matcher matcher = PATTERN.matcher(text); - if (matcher.matches()) { - long value = Long.parseLong(matcher.group(1)); - String unitAbbreviation = matcher.group(2); - TimeUnit unit = unitAbbreviation == null ? SECONDS - : UNITS_BY_ABBREVIATION.get(unitAbbreviation.toLowerCase(Locale.ENGLISH)); - return new TimeoutDuration(value, unit); - } - throw new DateTimeParseException("Timeout duration is not in the expected format ( [ns|μs|ms|s|m|h|d])", - text, 0); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java deleted file mode 100644 index 82c39e5d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import java.util.concurrent.TimeoutException; - -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.9 - */ -class TimeoutExceptionFactory { - - private TimeoutExceptionFactory() { - } - - static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration, Throwable failure) { - String message = String.format("%s timed out after %s", - Preconditions.notNull(methodSignature, "method signature must not be null"), - Preconditions.notNull(timeoutDuration, "timeout duration must not be null")); - TimeoutException timeoutException = new TimeoutException(message); - if (failure != null) { - timeoutException.addSuppressed(failure); - } - return timeoutException; - } - - static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration) { - return create(methodSignature, timeoutDuration, null); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java deleted file mode 100644 index 9efe2255..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.function.Function; - -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.Timeout.ThreadMode; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; -import org.junit.platform.commons.support.AnnotationSupport; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.RuntimeUtils; - -/** - * @since 5.5 - */ -class TimeoutExtension implements BeforeAllCallback, BeforeEachCallback, InvocationInterceptor { - - private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(Timeout.class); - private static final String TESTABLE_METHOD_TIMEOUT_KEY = "testable_method_timeout_from_annotation"; - private static final String TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY = "testable_method_timeout_thread_mode_from_annotation"; - private static final String GLOBAL_TIMEOUT_CONFIG_KEY = "global_timeout_config"; - private static final String ENABLED_MODE_VALUE = "enabled"; - private static final String DISABLED_MODE_VALUE = "disabled"; - private static final String DISABLED_ON_DEBUG_MODE_VALUE = "disabled_on_debug"; - - @Override - public void beforeAll(ExtensionContext context) { - readAndStoreTimeoutSoChildrenInheritIt(context); - } - - @Override - public void beforeEach(ExtensionContext context) { - readAndStoreTimeoutSoChildrenInheritIt(context); - } - - private void readAndStoreTimeoutSoChildrenInheritIt(ExtensionContext context) { - readTimeoutFromAnnotation(context.getElement()).ifPresent( - timeout -> context.getStore(NAMESPACE).put(TESTABLE_METHOD_TIMEOUT_KEY, timeout)); - readTimeoutThreadModeFromAnnotation(context.getElement()).ifPresent( - timeoutThreadMode -> context.getStore(NAMESPACE).put(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, - timeoutThreadMode)); - } - - @Override - public void interceptBeforeAllMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - - interceptLifecycleMethod(invocation, invocationContext, extensionContext, - TimeoutConfiguration::getDefaultBeforeAllMethodTimeout); - } - - @Override - public void interceptBeforeEachMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - - interceptLifecycleMethod(invocation, invocationContext, extensionContext, - TimeoutConfiguration::getDefaultBeforeEachMethodTimeout); - } - - @Override - public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext) throws Throwable { - - interceptTestableMethod(invocation, invocationContext, extensionContext, - TimeoutConfiguration::getDefaultTestMethodTimeout); - } - - @Override - public void interceptTestTemplateMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - - interceptTestableMethod(invocation, invocationContext, extensionContext, - TimeoutConfiguration::getDefaultTestTemplateMethodTimeout); - } - - @Override - public T interceptTestFactoryMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - - return interceptTestableMethod(invocation, invocationContext, extensionContext, - TimeoutConfiguration::getDefaultTestFactoryMethodTimeout); - } - - @Override - public void interceptAfterEachMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - - interceptLifecycleMethod(invocation, invocationContext, extensionContext, - TimeoutConfiguration::getDefaultAfterEachMethodTimeout); - } - - @Override - public void interceptAfterAllMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - - interceptLifecycleMethod(invocation, invocationContext, extensionContext, - TimeoutConfiguration::getDefaultAfterAllMethodTimeout); - } - - private void interceptLifecycleMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, - TimeoutProvider defaultTimeoutProvider) throws Throwable { - - TimeoutDuration timeout = readTimeoutFromAnnotation(Optional.of(invocationContext.getExecutable())).orElse( - null); - intercept(invocation, invocationContext, extensionContext, timeout, defaultTimeoutProvider); - } - - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private Optional readTimeoutFromAnnotation(Optional element) { - return AnnotationSupport.findAnnotation(element, Timeout.class).map(TimeoutDuration::from); - } - - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private Optional readTimeoutThreadModeFromAnnotation(Optional element) { - return AnnotationSupport.findAnnotation(element, Timeout.class).map(Timeout::threadMode); - } - - private T interceptTestableMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, - TimeoutProvider defaultTimeoutProvider) throws Throwable { - - TimeoutDuration timeout = extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_KEY, - TimeoutDuration.class); - return intercept(invocation, invocationContext, extensionContext, timeout, defaultTimeoutProvider); - } - - private T intercept(Invocation invocation, ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext, TimeoutDuration explicitTimeout, TimeoutProvider defaultTimeoutProvider) - throws Throwable { - - TimeoutDuration timeout = explicitTimeout == null ? getDefaultTimeout(extensionContext, defaultTimeoutProvider) - : explicitTimeout; - return decorate(invocation, invocationContext, extensionContext, timeout).proceed(); - } - - private TimeoutDuration getDefaultTimeout(ExtensionContext extensionContext, - TimeoutProvider defaultTimeoutProvider) { - - return defaultTimeoutProvider.apply(getGlobalTimeoutConfiguration(extensionContext)).orElse(null); - } - - private TimeoutConfiguration getGlobalTimeoutConfiguration(ExtensionContext extensionContext) { - ExtensionContext root = extensionContext.getRoot(); - return root.getStore(NAMESPACE).getOrComputeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, - key -> new TimeoutConfiguration(root), TimeoutConfiguration.class); - } - - private Invocation decorate(Invocation invocation, ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext, TimeoutDuration timeout) { - - if (timeout == null || isTimeoutDisabled(extensionContext)) { - return invocation; - } - - ThreadMode threadMode = resolveTimeoutThreadMode(extensionContext); - return new TimeoutInvocationFactory(extensionContext.getRoot().getStore(NAMESPACE)).create(threadMode, - new TimeoutInvocationParameters<>(invocation, timeout, - () -> describe(invocationContext, extensionContext))); - } - - private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext) { - ThreadMode annotationThreadMode = getAnnotationThreadMode(extensionContext); - if (annotationThreadMode == null || annotationThreadMode == ThreadMode.INFERRED) { - return getGlobalTimeoutConfiguration(extensionContext).getDefaultTimeoutThreadMode().orElse(SAME_THREAD); - } - return annotationThreadMode; - } - - private ThreadMode getAnnotationThreadMode(ExtensionContext extensionContext) { - return extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, ThreadMode.class); - } - - private String describe(ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { - Method method = invocationContext.getExecutable(); - Optional> testClass = extensionContext.getTestClass(); - if (testClass.isPresent() && invocationContext.getTargetClass().equals(testClass.get())) { - return String.format("%s(%s)", method.getName(), ClassUtils.nullSafeToString(method.getParameterTypes())); - } - return ReflectionUtils.getFullyQualifiedMethodName(invocationContext.getTargetClass(), method); - } - - /** - * Determine if timeouts are disabled for the supplied extension context. - */ - private boolean isTimeoutDisabled(ExtensionContext extensionContext) { - Optional mode = extensionContext.getConfigurationParameter(TIMEOUT_MODE_PROPERTY_NAME); - return mode.map(this::isTimeoutDisabled).orElse(false); - } - - /** - * Determine if timeouts are disabled for the supplied mode. - */ - private boolean isTimeoutDisabled(String mode) { - switch (mode) { - case ENABLED_MODE_VALUE: - return false; - case DISABLED_MODE_VALUE: - return true; - case DISABLED_ON_DEBUG_MODE_VALUE: - return RuntimeUtils.isDebugMode(); - default: - throw new ExtensionConfigurationException("Unsupported timeout mode: " + mode); - } - } - - @FunctionalInterface - private interface TimeoutProvider extends Function> { - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java deleted file mode 100644 index 08f6851f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Timeout.ThreadMode; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.9 - */ -class TimeoutInvocationFactory { - - private final Store store; - - TimeoutInvocationFactory(Store store) { - this.store = Preconditions.notNull(store, "store must not be null"); - } - - Invocation create(ThreadMode threadMode, TimeoutInvocationParameters timeoutInvocationParameters) { - Preconditions.notNull(threadMode, "thread mode must not be null"); - Preconditions.condition(threadMode != ThreadMode.INFERRED, "thread mode must not be INFERRED"); - Preconditions.notNull(timeoutInvocationParameters, "timeout invocation parameters must not be null"); - if (threadMode == ThreadMode.SEPARATE_THREAD) { - return new SeparateThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), - timeoutInvocationParameters.getTimeoutDuration(), timeoutInvocationParameters.getDescriptionSupplier()); - } - return new SameThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), - timeoutInvocationParameters.getTimeoutDuration(), getThreadExecutorForSameThreadInvocation(), - timeoutInvocationParameters.getDescriptionSupplier()); - } - - private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() { - return store.getOrComputeIfAbsent(SingleThreadExecutorResource.class).get(); - } - - private static abstract class ExecutorResource implements CloseableResource { - - protected final ScheduledExecutorService executor; - - ExecutorResource(ScheduledExecutorService executor) { - this.executor = executor; - } - - ScheduledExecutorService get() { - return executor; - } - - @Override - public void close() throws Throwable { - executor.shutdown(); - boolean terminated = executor.awaitTermination(5, TimeUnit.SECONDS); - if (!terminated) { - executor.shutdownNow(); - throw new JUnitException("Scheduled executor could not be stopped in an orderly manner"); - } - } - } - - static class SingleThreadExecutorResource extends ExecutorResource { - - @SuppressWarnings("unused") - SingleThreadExecutorResource() { - super(Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread thread = new Thread(runnable, "junit-jupiter-timeout-watcher"); - thread.setPriority(Thread.MAX_PRIORITY); - return thread; - })); - } - } - - static class TimeoutInvocationParameters { - - private final Invocation invocation; - private final TimeoutDuration timeout; - private final Supplier descriptionSupplier; - - TimeoutInvocationParameters(Invocation invocation, TimeoutDuration timeout, - Supplier descriptionSupplier) { - this.invocation = Preconditions.notNull(invocation, "invocation must not be null"); - this.timeout = Preconditions.notNull(timeout, "timeout must not be null"); - this.descriptionSupplier = Preconditions.notNull(descriptionSupplier, - "description supplier must not be null"); - } - - public Invocation getInvocation() { - return invocation; - } - - public TimeoutDuration getTimeoutDuration() { - return timeout; - } - - public Supplier getDescriptionSupplier() { - return descriptionSupplier; - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java deleted file mode 100644 index ff29b09a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Test extensions specific to the JUnit Jupiter test engine. - */ - -package org.junit.jupiter.engine.extension; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java deleted file mode 100644 index 17af1fec..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Core package for the JUnit Jupiter test engine. - */ - -package org.junit.jupiter.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java deleted file mode 100644 index 49a80ddb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.support; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; - -/** - * Factory for creating {@link ThrowableCollector ThrowableCollectors} within - * the JUnit Jupiter test engine. - * - * @since 5.4 - * @see ThrowableCollector - */ -@API(status = INTERNAL, since = "5.4") -public class JupiterThrowableCollectorFactory { - - /** - * Create a new {@link ThrowableCollector} that treats instances of the - * OTA's {@link org.opentest4j.TestAbortedException} and JUnit 4's - * {@code org.junit.AssumptionViolatedException} as aborting. - */ - public static ThrowableCollector createThrowableCollector() { - return new OpenTest4JAndJUnit4AwareThrowableCollector(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java deleted file mode 100644 index 5df8ff6a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.support; - -import java.util.function.Predicate; -import java.util.function.Supplier; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; -import org.opentest4j.TestAbortedException; - -/** - * Specialization of {@link ThrowableCollector} that treats instances of the - * OTA's {@link org.opentest4j.TestAbortedException} and JUnit 4's - * {@code org.junit.AssumptionViolatedException} as aborting. - * - * @since 5.4 - * @see ThrowableCollector - * @see org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector - */ -class OpenTest4JAndJUnit4AwareThrowableCollector extends ThrowableCollector { - - private static final Logger logger = LoggerFactory.getLogger(OpenTest4JAndJUnit4AwareThrowableCollector.class); - - private static final String ASSUMPTION_VIOLATED_EXCEPTION = "org.junit.internal.AssumptionViolatedException"; - - private static final String COMMON_FAILURE_MESSAGE = "Failed to load class " + ASSUMPTION_VIOLATED_EXCEPTION - + ": only supporting " + TestAbortedException.class.getName() + " for aborted execution."; - - private static final Predicate abortedExecutionPredicate = createAbortedExecutionPredicate(); - - OpenTest4JAndJUnit4AwareThrowableCollector() { - super(abortedExecutionPredicate); - } - - private static Predicate createAbortedExecutionPredicate() { - Predicate otaPredicate = TestAbortedException.class::isInstance; - - // Additionally support JUnit 4's AssumptionViolatedException? - try { - Class clazz = ReflectionUtils.tryToLoadClass(ASSUMPTION_VIOLATED_EXCEPTION).get(); - if (clazz != null) { - return otaPredicate.or(clazz::isInstance); - } - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - Supplier messageSupplier = (throwable instanceof NoClassDefFoundError) - ? () -> COMMON_FAILURE_MESSAGE + " Note that " + ASSUMPTION_VIOLATED_EXCEPTION - + " requires that Hamcrest is on the classpath." - : () -> COMMON_FAILURE_MESSAGE; - logger.debug(throwable, messageSupplier); - } - - // Else just OTA's TestAbortedException - return otaPredicate; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java deleted file mode 100644 index 6eecfd0f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Internal support classes for the JUnit Jupiter test engine. - */ - -package org.junit.jupiter.engine.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine deleted file mode 100644 index 581900d5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine +++ /dev/null @@ -1 +0,0 @@ -org.junit.jupiter.engine.JupiterTestEngine \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java deleted file mode 100644 index 946b1dd3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Provides the JUnit Jupiter {@linkplain org.junit.platform.engine.TestEngine} - * implementation. - * - * @since 5.0 - * @uses org.junit.jupiter.api.extension.Extension - * @provides org.junit.platform.engine.TestEngine The {@code JupiterTestEngine} - * runs Jupiter based tests on the platform. - */ -module org.junit.jupiter.engine { - requires static org.apiguardian.api; - requires org.junit.jupiter.api; - requires org.junit.platform.commons; - requires org.junit.platform.engine; - requires org.opentest4j; - - // exports org.junit.jupiter.engine; // Constants... - - uses org.junit.jupiter.api.extension.Extension; - - provides org.junit.platform.engine.TestEngine - with org.junit.jupiter.engine.JupiterTestEngine; - - opens org.junit.jupiter.engine.extension to org.junit.platform.commons; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy deleted file mode 100644 index 7554f942..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.api - -import java.util.function.Supplier -import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.PrimitiveAndWrapperTypeHelpers.* - -class GroovyAssertEqualsTests { - - Supplier supplier = { '' } - - @Test - void "null references can be passed to assertEquals"() { - Object null1 = null - Object null2 = null - - assertEquals(null1, null) - assertEquals(null, null2) - assertEquals(null1, null2) - } - - @Test - void "integers can be passed to assertEquals"() { - assertEquals(i(42), i(42)) - assertEquals(i(42), I(42)) - assertEquals(I(42), i(42)) - assertEquals(I(42), I(42)) - - assertEquals(i(42), i(42), '') - assertEquals(i(42), I(42), '') - assertEquals(I(42), i(42), '') - assertEquals(I(42), I(42), '') - - assertEquals(i(42), i(42), supplier) - assertEquals(i(42), I(42), supplier) - assertEquals(I(42), i(42), supplier) - assertEquals(I(42), I(42), supplier) - } - - @Test - void "floats can be passed to assertEquals"() { - assertEquals(f(42), f(42)) - assertEquals(f(42), F(42)) - assertEquals(F(42), f(42)) - assertEquals(F(42), F(42)) - - assertEquals(f(42), f(42), '') - assertEquals(f(42), F(42), '') - assertEquals(F(42), f(42), '') - assertEquals(F(42), F(42), '') - - assertEquals(f(42), f(42), supplier) - assertEquals(f(42), F(42), supplier) - assertEquals(F(42), f(42), supplier) - assertEquals(F(42), F(42), supplier) - } - - @Test - void "floats can be passed to assertEquals with delta"() { - assertEquals(f(42), f(42), 0.01f) - assertEquals(f(42), F(42), 0.01f) - assertEquals(F(42), f(42), 0.01f) - assertEquals(F(42), F(42), 0.01f) - - assertEquals(f(42), f(42), 0.01f, '') - assertEquals(f(42), F(42), 0.01f, '') - assertEquals(F(42), f(42), 0.01f, '') - assertEquals(F(42), F(42), 0.01f, '') - - assertEquals(f(42), f(42), 0.01f, supplier) - assertEquals(f(42), F(42), 0.01f, supplier) - assertEquals(F(42), f(42), 0.01f, supplier) - assertEquals(F(42), F(42), 0.01f, supplier) - } - - @Test - void "bytes can be passed to assertEquals"() { - assertEquals(b(42), b(42)) - assertEquals(b(42), B(42)) - assertEquals(B(42), b(42)) - assertEquals(B(42), B(42)) - - assertEquals(b(42), b(42), '') - assertEquals(b(42), B(42), '') - assertEquals(B(42), b(42), '') - assertEquals(B(42), B(42), '') - - assertEquals(b(42), b(42), supplier) - assertEquals(b(42), B(42), supplier) - assertEquals(B(42), b(42), supplier) - assertEquals(B(42), B(42), supplier) - } - - @Test - void "doubles can be passed to assertEquals"() { - assertEquals(d(42), d(42)) - assertEquals(d(42), D(42)) - assertEquals(D(42), d(42)) - assertEquals(D(42), D(42)) - - assertEquals(d(42), d(42), '') - assertEquals(d(42), D(42), '') - assertEquals(D(42), d(42), '') - assertEquals(D(42), D(42), '') - - assertEquals(d(42), d(42), supplier) - assertEquals(d(42), D(42), supplier) - assertEquals(D(42), d(42), supplier) - assertEquals(D(42), D(42), supplier) - } - - @Test - void "doubles can be passed to assertEquals with delta"() { - assertEquals(d(42), d(42), 0.01d) - assertEquals(d(42), D(42), 0.01d) - assertEquals(D(42), d(42), 0.01d) - assertEquals(D(42), D(42), 0.01d) - - assertEquals(d(42), d(42), 0.01d, '') - assertEquals(d(42), D(42), 0.01d, '') - assertEquals(D(42), d(42), 0.01d, '') - assertEquals(D(42), D(42), 0.01d, '') - - assertEquals(d(42), d(42), 0.01d, supplier) - assertEquals(d(42), D(42), 0.01d, supplier) - assertEquals(D(42), d(42), 0.01d, supplier) - assertEquals(D(42), D(42), 0.01d, supplier) - } - - @Test - void "chars can be passed to assertEquals"() { - assertEquals(c(42), c(42)) - assertEquals(c(42), C(42)) - assertEquals(C(42), c(42)) - assertEquals(C(42), C(42)) - - assertEquals(c(42), c(42), '') - assertEquals(c(42), C(42), '') - assertEquals(C(42), c(42), '') - assertEquals(C(42), C(42), '') - - assertEquals(c(42), c(42), supplier) - assertEquals(c(42), C(42), supplier) - assertEquals(C(42), c(42), supplier) - assertEquals(C(42), C(42), supplier) - } - - @Test - void "longs can be passed to assertEquals"() { - assertEquals(l(42), l(42)) - assertEquals(l(42), L(42)) - assertEquals(L(42), l(42)) - assertEquals(L(42), L(42)) - - assertEquals(l(42), l(42), '') - assertEquals(l(42), L(42), '') - assertEquals(L(42), l(42), '') - assertEquals(L(42), L(42), '') - - assertEquals(l(42), l(42), supplier) - assertEquals(l(42), L(42), supplier) - assertEquals(L(42), l(42), supplier) - assertEquals(L(42), L(42), supplier) - } - - @Test - void "shorts can be passed to assertEquals"() { - assertEquals(s(42), s(42)) - assertEquals(s(42), S(42)) - assertEquals(S(42), s(42)) - assertEquals(S(42), S(42)) - - assertEquals(s(42), s(42), '') - assertEquals(s(42), S(42), '') - assertEquals(S(42), s(42), '') - assertEquals(S(42), S(42), '') - - assertEquals(s(42), s(42), supplier) - assertEquals(s(42), S(42), supplier) - assertEquals(S(42), s(42), supplier) - assertEquals(S(42), S(42), supplier) - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy deleted file mode 100644 index bf95310e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.api - -import org.opentest4j.AssertionFailedError - -import java.util.function.Supplier - -import static org.junit.jupiter.api.Assertions.assertNotEquals -import static org.junit.jupiter.api.Assertions.assertThrows -import static org.junit.jupiter.api.PrimitiveAndWrapperTypeHelpers.* - -class GroovyAssertNotEqualsTests { - - Supplier supplier = { '' } - - @Test - void "null references can be passed to assertNotEquals"() { - Object null1 = null - Object null2 = null - - assertThrows(AssertionFailedError, { assertNotEquals(null1, null) } ) - assertThrows(AssertionFailedError, { assertNotEquals(null, null2) } ) - assertThrows(AssertionFailedError, { assertNotEquals(null1, null2) } ) - } - - @Test - void "integers can be passed to assertNotEquals"() { - assertNotEquals(i(42), i(2)) - assertNotEquals(i(42), I(2)) - assertNotEquals(I(42), i(2)) - assertNotEquals(I(42), I(2)) - - assertNotEquals(i(42), i(2), '') - assertNotEquals(i(42), I(2), '') - assertNotEquals(I(42), i(2), '') - assertNotEquals(I(42), I(2), '') - - assertNotEquals(i(42), i(2), supplier) - assertNotEquals(i(42), I(2), supplier) - assertNotEquals(I(42), i(2), supplier) - assertNotEquals(I(42), I(2), supplier) - } - - @Test - void "floats can be passed to assertNotEquals"() { - assertNotEquals(f(42), f(2)) - assertNotEquals(f(42), F(2)) - assertNotEquals(F(42), f(2)) - assertNotEquals(F(42), F(2)) - - assertNotEquals(f(42), f(2), '') - assertNotEquals(f(42), F(2), '') - assertNotEquals(F(42), f(2), '') - assertNotEquals(F(42), F(2), '') - - assertNotEquals(f(42), f(2), supplier) - assertNotEquals(f(42), F(2), supplier) - assertNotEquals(F(42), f(2), supplier) - assertNotEquals(F(42), F(2), supplier) - } - - @Test - void "floats can be passed to assertNotEquals with delta"() { - assertNotEquals(f(42), f(2), 0.01f) - assertNotEquals(f(42), F(2), 0.01f) - assertNotEquals(F(42), f(2), 0.01f) - assertNotEquals(F(42), F(2), 0.01f) - - assertNotEquals(f(42), f(2), 0.01f, '') - assertNotEquals(f(42), F(2), 0.01f, '') - assertNotEquals(F(42), f(2), 0.01f, '') - assertNotEquals(F(42), F(2), 0.01f, '') - - assertNotEquals(f(42), f(2), 0.01f, supplier) - assertNotEquals(f(42), F(2), 0.01f, supplier) - assertNotEquals(F(42), f(2), 0.01f, supplier) - assertNotEquals(F(42), F(2), 0.01f, supplier) - } - - @Test - void "bytes can be passed to assertNotEquals"() { - assertNotEquals(b(42), b(2)) - assertNotEquals(b(42), B(2)) - assertNotEquals(B(42), b(2)) - assertNotEquals(B(42), B(2)) - - assertNotEquals(b(42), b(2), '') - assertNotEquals(b(42), B(2), '') - assertNotEquals(B(42), b(2), '') - assertNotEquals(B(42), B(2), '') - - assertNotEquals(b(42), b(2), supplier) - assertNotEquals(b(42), B(2), supplier) - assertNotEquals(B(42), b(2), supplier) - assertNotEquals(B(42), B(2), supplier) - } - - @Test - void "doubles can be passed to assertNotEquals"() { - assertNotEquals(d(42), d(2)) - assertNotEquals(d(42), D(2)) - assertNotEquals(D(42), d(2)) - assertNotEquals(D(42), D(2)) - - assertNotEquals(d(42), d(2), '') - assertNotEquals(d(42), D(2), '') - assertNotEquals(D(42), d(2), '') - assertNotEquals(D(42), D(2), '') - - assertNotEquals(d(42), d(2), supplier) - assertNotEquals(d(42), D(2), supplier) - assertNotEquals(D(42), d(2), supplier) - assertNotEquals(D(42), D(2), supplier) - } - - @Test - void "doubles can be passed to assertNotEquals with delta"() { - assertNotEquals(d(42), d(2), 0.01d) - assertNotEquals(d(42), D(2), 0.01d) - assertNotEquals(D(42), d(2), 0.01d) - assertNotEquals(D(42), D(2), 0.01d) - - assertNotEquals(d(42), d(2), 0.01d, '') - assertNotEquals(d(42), D(2), 0.01d, '') - assertNotEquals(D(42), d(2), 0.01d, '') - assertNotEquals(D(42), D(2), 0.01d, '') - - assertNotEquals(d(42), d(2), 0.01d, supplier) - assertNotEquals(d(42), D(2), 0.01d, supplier) - assertNotEquals(D(42), d(2), 0.01d, supplier) - assertNotEquals(D(42), D(2), 0.01d, supplier) - } - - @Test - void "chars can be passed to assertNotEquals"() { - assertNotEquals(c(42), c(2)) - assertNotEquals(c(42), C(2)) - assertNotEquals(C(42), c(2)) - assertNotEquals(C(42), C(2)) - - assertNotEquals(c(42), c(2), '') - assertNotEquals(c(42), C(2), '') - assertNotEquals(C(42), c(2), '') - assertNotEquals(C(42), C(2), '') - - assertNotEquals(c(42), c(2), supplier) - assertNotEquals(c(42), C(2), supplier) - assertNotEquals(C(42), c(2), supplier) - assertNotEquals(C(42), C(2), supplier) - } - - @Test - void "longs can be passed to assertNotEquals"() { - assertNotEquals(l(42), l(2)) - assertNotEquals(l(42), L(2)) - assertNotEquals(L(42), l(2)) - assertNotEquals(L(42), L(2)) - - assertNotEquals(l(42), l(2), '') - assertNotEquals(l(42), L(2), '') - assertNotEquals(L(42), l(2), '') - assertNotEquals(L(42), L(2), '') - - assertNotEquals(l(42), l(2), supplier) - assertNotEquals(l(42), L(2), supplier) - assertNotEquals(L(42), l(2), supplier) - assertNotEquals(L(42), L(2), supplier) - } - - @Test - void "shorts can be passed to assertNotEquals"() { - assertNotEquals(s(42), s(2)) - assertNotEquals(s(42), S(2)) - assertNotEquals(S(42), s(2)) - assertNotEquals(S(42), S(2)) - - assertNotEquals(s(42), s(2), '') - assertNotEquals(s(42), S(2), '') - assertNotEquals(S(42), s(2), '') - assertNotEquals(S(42), S(2), '') - - assertNotEquals(s(42), s(2), supplier) - assertNotEquals(s(42), S(2), supplier) - assertNotEquals(S(42), s(2), supplier) - assertNotEquals(S(42), S(2), supplier) - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy deleted file mode 100644 index a33d8031..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.api - -class PrimitiveAndWrapperTypeHelpers { - - static char c(int number) { - return (char) number - } - - static Character C(int number) { - return Character.valueOf((char) number) - } - - static byte b(int number) { - return (byte) number - } - - static Byte B(int number) { - return Byte.valueOf((byte) number) - } - - static double d(int number) { - return (double) number - } - - static Double D(int number) { - return Double.valueOf((double) number) - } - - static float f(int number) { - return (float) number - } - - static Float F(int number) { - return Float.valueOf((float) number) - } - - static long l(int number) { - return (long) number - } - - static Long L(int number) { - return Long.valueOf( (long) number) - } - - static short s(int number) { - return (short) number - } - - static Short S(int number) { - return Short.valueOf( (short) number) - } - - static int i(int number) { - return number - } - - static Integer I(int number) { - return Integer.valueOf( number) - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java deleted file mode 100644 index 3b10c68b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java +++ /dev/null @@ -1,28 +0,0 @@ - -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Simple test case that is used to verify proper support for classpath scanning - * within the default package. - * - * @since 5.0 - */ -@Disabled("Only used reflectively by other tests") -class DefaultPackageTestCase { - - @Test - void test() { - // do nothing - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java deleted file mode 100644 index 95ab9959..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/example/B_TestCase.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package example; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; - -/** - * @since 5.8 - */ -public class B_TestCase { - - public static List callSequence; - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - if (callSequence != null) { - callSequence.add(testInfo.getTestClass().get().getName()); - } - } - - @Test - void a() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java deleted file mode 100644 index 470fb7be..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; - -/** - * Test suite for the JUnit Jupiter programming model, extension model, and - * {@code TestEngine} implementation. - * - *

Logging Configuration

- * - *

In order for our log4j2 configuration to be used in an IDE, you must - * set the following system property before running any tests — for - * example, in Run Configurations in Eclipse. - * - *

- * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
- * 
- * - * @since 5.0 - */ -@Suite -@SelectPackages("org.junit.jupiter") -@IncludeClassNamePatterns(".*Tests?") -@IncludeEngines("junit-jupiter") -class JupiterTestSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java deleted file mode 100644 index ac8d045e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.stream.Stream; - -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.PreconditionViolationException; -import org.opentest4j.AssertionFailedError; -import org.opentest4j.MultipleFailuresError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertAllAssertionsTests { - - @Test - void assertAllWithNullExecutableArray() { - assertPrecondition("executables array must not be null or empty", () -> assertAll((Executable[]) null)); - } - - @Test - void assertAllWithNullExecutableCollection() { - assertPrecondition("executables collection must not be null", () -> assertAll((Collection) null)); - } - - @Test - void assertAllWithNullExecutableStream() { - assertPrecondition("executables stream must not be null", () -> assertAll((Stream) null)); - } - - @Test - void assertAllWithNullInExecutableArray() { - assertPrecondition("individual executables must not be null", () -> assertAll((Executable) null)); - } - - @Test - void assertAllWithNullInExecutableCollection() { - assertPrecondition("individual executables must not be null", () -> assertAll(asList((Executable) null))); - } - - @Test - void assertAllWithNullInExecutableStream() { - assertPrecondition("individual executables must not be null", () -> assertAll(Stream.of((Executable) null))); - } - - @Test - void assertAllWithExecutablesThatDoNotThrowExceptions() { - // @formatter:off - assertAll( - () -> assertTrue(true), - () -> assertFalse(false) - ); - assertAll("heading", - () -> assertTrue(true), - () -> assertFalse(false) - ); - assertAll(asList( - () -> assertTrue(true), - () -> assertFalse(false) - )); - assertAll("heading", asList( - () -> assertTrue(true), - () -> assertFalse(false) - )); - assertAll(Stream.of( - () -> assertTrue(true), - () -> assertFalse(false) - )); - assertAll("heading", Stream.of( - () -> assertTrue(true), - () -> assertFalse(false) - )); - // @formatter:on - } - - @Test - void assertAllWithExecutablesThatThrowAssertionErrors() { - // @formatter:off - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> - assertAll( - () -> assertFalse(true), - () -> assertFalse(true) - ) - ); - // @formatter:on - - assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); - } - - @Test - void assertAllWithCollectionOfExecutablesThatThrowAssertionErrors() { - // @formatter:off - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> - assertAll(asList( - () -> assertFalse(true), - () -> assertFalse(true) - )) - ); - // @formatter:on - - assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); - } - - @Test - void assertAllWithStreamOfExecutablesThatThrowAssertionErrors() { - // @formatter:off - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> - assertAll(Stream.of( - () -> assertFalse(true), - () -> assertFalse(true) - )) - ); - // @formatter:on - - assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); - } - - @Test - void assertAllWithExecutableThatThrowsThrowable() { - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { - throw new EnigmaThrowable(); - })); - - assertExpectedExceptionTypes(multipleFailuresError, EnigmaThrowable.class); - } - - @Test - void assertAllWithExecutableThatThrowsCheckedException() { - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { - throw new IOException(); - })); - - assertExpectedExceptionTypes(multipleFailuresError, IOException.class); - } - - @Test - void assertAllWithExecutableThatThrowsRuntimeException() { - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { - throw new IllegalStateException(); - })); - - assertExpectedExceptionTypes(multipleFailuresError, IllegalStateException.class); - } - - @Test - void assertAllWithExecutableThatThrowsError() { - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, - () -> assertAll(AssertionTestUtils::recurseIndefinitely)); - - assertExpectedExceptionTypes(multipleFailuresError, StackOverflowError.class); - } - - @Test - void assertAllWithExecutableThatThrowsUnrecoverableException() { - OutOfMemoryError outOfMemoryError = assertThrows(OutOfMemoryError.class, - () -> assertAll(AssertionTestUtils::runOutOfMemory)); - - assertEquals("boom", outOfMemoryError.getMessage()); - } - - @Test - void assertAllWithParallelStream() { - Executable executable = () -> { - throw new RuntimeException(); - }; - MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, - () -> assertAll(Stream.generate(() -> executable).parallel().limit(100))); - - assertThat(multipleFailuresError.getFailures()).hasSize(100).doesNotContainNull(); - } - - private void assertPrecondition(String msg, Executable executable) { - PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, executable); - assertMessageEquals(exception, msg); - } - - @SafeVarargs - static void assertExpectedExceptionTypes(MultipleFailuresError multipleFailuresError, - Class... exceptionTypes) { - - assertNotNull(multipleFailuresError, "MultipleFailuresError"); - List failures = multipleFailuresError.getFailures(); - assertEquals(exceptionTypes.length, failures.size(), "number of failures"); - - // Verify that exceptions are also present as suppressed exceptions. - // https://github.com/junit-team/junit5/issues/1602 - Throwable[] suppressed = multipleFailuresError.getSuppressed(); - assertEquals(exceptionTypes.length, suppressed.length, "number of suppressed exceptions"); - - for (int i = 0; i < exceptionTypes.length; i++) { - assertEquals(exceptionTypes[i], failures.get(i).getClass(), "exception type [" + i + "]"); - assertEquals(exceptionTypes[i], suppressed[i].getClass(), "suppressed exception type [" + i + "]"); - } - } - - @SuppressWarnings("serial") - private static class EnigmaThrowable extends Throwable { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java deleted file mode 100644 index 5900e356..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java +++ /dev/null @@ -1,1977 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertArrayEqualsAssertionsTests { - - @Test - void assertArrayEqualsWithNulls() { - assertArrayEquals((boolean[]) null, (boolean[]) null); - assertArrayEquals((char[]) null, (char[]) null); - assertArrayEquals((byte[]) null, (byte[]) null); - assertArrayEquals((int[]) null, (int[]) null); - assertArrayEquals((long[]) null, (long[]) null); - assertArrayEquals((float[]) null, (float[]) null); - assertArrayEquals((double[]) null, (double[]) null); - assertArrayEquals((Object[]) null, (Object[]) null); - } - - @Test - void assertArrayEqualsBooleanArrays() { - assertArrayEquals(new boolean[] {}, new boolean[] {}); - assertArrayEquals(new boolean[] {}, new boolean[] {}, "message"); - assertArrayEquals(new boolean[] {}, new boolean[] {}, () -> "message"); - assertArrayEquals(new boolean[] { true }, new boolean[] { true }); - assertArrayEquals(new boolean[] { false, true, false, false }, new boolean[] { false, true, false, false }); - } - - @Test - void assertArrayEqualsBooleanArrayVsNull() { - try { - assertArrayEquals(null, new boolean[] { true, false }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new boolean[] { true, false }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsBooleanArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new boolean[] { true, false }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new boolean[] { true, false }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsBooleanArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new boolean[] { true, false }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new boolean[] { true, false }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsBooleanArraysOfDifferentLength() { - try { - assertArrayEquals(new boolean[] { true, false }, new boolean[] { true, false, true }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertArrayEqualsBooleanArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new boolean[] { true, false, false }, new boolean[] { true }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <1>"); - } - } - - @Test - void assertArrayEqualsBooleanArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new boolean[] { true }, new boolean[] { true, false }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <1> but was: <2>"); - } - } - - @Test - void assertArrayEqualsDifferentBooleanArrays() { - try { - assertArrayEquals(new boolean[] { true, false, false }, new boolean[] { true, false, true }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [2], expected: but was: "); - } - } - - @Test - void assertArrayEqualsDifferentBooleanArraysAndMessage() { - try { - assertArrayEquals(new boolean[] { true, true }, new boolean[] { false, true }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [0], expected: but was: "); - } - } - - @Test - void assertArrayEqualsDifferentBooleanArraysAndMessageSupplier() { - try { - assertArrayEquals(new boolean[] { false, false, false }, new boolean[] { false, true, true }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: but was: "); - } - } - - @Test - void assertArrayEqualsCharArrays() { - assertArrayEquals(new char[] {}, new char[] {}); - assertArrayEquals(new char[] {}, new char[] {}, "message"); - assertArrayEquals(new char[] {}, new char[] {}, () -> "message"); - assertArrayEquals(new char[] { 'a' }, new char[] { 'a' }); - assertArrayEquals(new char[] { 'j', 'u', 'n', 'i', 't' }, new char[] { 'j', 'u', 'n', 'i', 't' }); - } - - @Test - void assertArrayEqualsCharArrayVsNull() { - try { - assertArrayEquals(null, new char[] { 'a', 'z' }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new char[] { 'a', 'z' }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsCharArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new char[] { 'a', 'b', 'z' }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new char[] { 'a', 'b', 'z' }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsCharArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new char[] { 'z', 'x', 'y' }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new char[] { 'z', 'x', 'y' }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsCharArraysOfDifferentLength() { - try { - assertArrayEquals(new char[] { 'q', 'w', 'e' }, new char[] { 'q', 'w' }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <2>"); - } - } - - @Test - void assertArrayEqualsCharArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new char[] { 'a', 's', 'd' }, new char[] { 'd' }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <1>"); - } - } - - @Test - void assertArrayEqualsCharArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new char[] { 'q' }, new char[] { 't', 'u' }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <1> but was: <2>"); - } - } - - @Test - void assertArrayEqualsDifferentCharArrays() { - try { - assertArrayEquals(new char[] { 'a', 'b', 'c' }, new char[] { 'a', 'b', 'a' }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [2], expected: but was: "); - } - } - - @Test - void assertArrayEqualsDifferentCharArraysAndMessage() { - try { - assertArrayEquals(new char[] { 'z', 'x', 'c', 'v' }, new char[] { 'x', 'x', 'c', 'v' }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [0], expected: but was: "); - } - } - - @Test - void assertArrayEqualsDifferentCharArraysAndMessageSupplier() { - try { - assertArrayEquals(new char[] { 'r', 't', 'y' }, new char[] { 'r', 'y', 'u' }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: but was: "); - } - } - - @Test - void assertArrayEqualsByteArrays() { - assertArrayEquals(new byte[] {}, new byte[] {}); - assertArrayEquals(new byte[] {}, new byte[] {}, "message"); - assertArrayEquals(new byte[] {}, new byte[] {}, () -> "message"); - assertArrayEquals(new byte[] { 42 }, new byte[] { 42 }); - assertArrayEquals(new byte[] { 1, 2, 3, 42 }, new byte[] { 1, 2, 3, 42 }); - } - - @Test - void assertArrayEqualsByteArrayVsNull() { - try { - assertArrayEquals(null, new byte[] { 7, 8, 9 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new byte[] { 7, 8, 9 }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsByteArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new byte[] { 9, 8, 7 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new byte[] { 9, 8, 7 }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsByteArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new byte[] { 10, 20, 30 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new byte[] { 10, 20, 30 }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsByteArraysOfDifferentLength() { - try { - assertArrayEquals(new byte[] { 1, 2, 100 }, new byte[] { 1, 2, 100, 101 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <4>"); - } - } - - @Test - void assertArrayEqualsByteArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new byte[] { 1, 2 }, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <9>"); - } - } - - @Test - void assertArrayEqualsByteArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new byte[] { 88, 99 }, new byte[] { 99, 88, 77 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertArrayEqualsDifferentByteArrays() { - try { - assertArrayEquals(new byte[] { 12, 13, 12, 13 }, new byte[] { 12, 13, 12, 14 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [3], expected: <13> but was: <14>"); - } - } - - @Test - void assertArrayEqualsDifferentByteArraysAndMessage() { - try { - assertArrayEquals(new byte[] { 1, 2, 3, 4, 5 }, new byte[] { 1, 2, 3, 5, 5 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [3], expected: <4> but was: <5>"); - } - } - - @Test - void assertArrayEqualsDifferentByteArraysAndMessageSupplier() { - try { - assertArrayEquals(new byte[] { 127, 126, -128, +127 }, new byte[] { 127, 126, -128, -127 }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [3], expected: <127> but was: <-127>"); - } - } - - @Test - void assertArrayEqualsShortArrays() { - assertArrayEquals(new short[] {}, new short[] {}); - assertArrayEquals(new short[] {}, new short[] {}, "message"); - assertArrayEquals(new short[] {}, new short[] {}, () -> "message"); - assertArrayEquals(new short[] { 999 }, new short[] { 999 }); - assertArrayEquals(new short[] { 111, 222, 333, 444, 999 }, new short[] { 111, 222, 333, 444, 999 }); - } - - @Test - void assertArrayEqualsShortArrayVsNull() { - try { - assertArrayEquals(null, new short[] { 5, 10, 12 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new short[] { 5, 10, 12 }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsShortArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new short[] { 128, 129, 130 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new short[] { -129, -130, -131 }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsShortArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new short[] { 1, 2, 3, 4 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new short[] { -2000, 1, 2, 3, 4 }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsShortArraysOfDifferentLength() { - try { - assertArrayEquals(new short[] { 1, 2, 3, 4, 5, 6 }, new short[] { 1, 2, 3 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <6> but was: <3>"); - } - } - - @Test - void assertArrayEqualsShortArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new short[] { 1, 2, 3, 10_000 }, new short[] { 10_000, 1, 2, 3, 4, 5, 6, 7 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <8>"); - } - } - - @Test - void assertArrayEqualsShortArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new short[] { 150, 151 }, new short[] { 150, 151, 152 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertArrayEqualsDifferentShortArrays() { - try { - assertArrayEquals(new short[] { 10, 100, 1000, 10000 }, new short[] { 1, 10, 100, 1000 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [0], expected: <10> but was: <1>"); - } - } - - @Test - void assertArrayEqualsDifferentShortArraysAndMessage() { - try { - assertArrayEquals(new short[] { 1, 2, 100, -200 }, new short[] { 1, 2, 100, -500 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [3], expected: <-200> but was: <-500>"); - } - } - - @Test - void assertArrayEqualsDifferentShortArraysAndMessageSupplier() { - try { - assertArrayEquals(new short[] { 1000, 2000, +3000, 42 }, new short[] { 1000, 2000, -3000, 42 }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [2], expected: <3000> but was: <-3000>"); - } - } - - @Test - void assertArrayEqualsIntArrays() { - assertArrayEquals(new int[] {}, new int[] {}); - assertArrayEquals(new int[] {}, new int[] {}, "message"); - assertArrayEquals(new int[] {}, new int[] {}, () -> "message"); - assertArrayEquals(new int[] { Integer.MAX_VALUE }, new int[] { Integer.MAX_VALUE }); - assertArrayEquals(new int[] { 1, 2, 3, 4, 5, 99_999 }, new int[] { 1, 2, 3, 4, 5, 99_999 }); - } - - @Test - void assertArrayEqualsIntArrayVsNull() { - try { - assertArrayEquals(null, new int[] { Integer.MIN_VALUE, 2, 10 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new int[] { Integer.MIN_VALUE, 2, 10 }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsIntArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new int[] { 99_999, 88_888, 1 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new int[] { 99_999, 77_7777, 2 }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsIntArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new int[] { 1, 10, 100, 1000, 10000, 100000 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new int[] { 100000, 10000, 1000, 100, 10, 1 }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsIntArraysOfDifferentLength() { - try { - assertArrayEquals(new int[] { 1, 2, 3, Integer.MIN_VALUE, 4 }, new int[] { 1, Integer.MAX_VALUE, 2 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <5> but was: <3>"); - } - } - - @Test - void assertArrayEqualsIntArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new int[] { 100_000, 200_000, 1, 2 }, new int[] { 1, 2, 3 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <3>"); - } - } - - @Test - void assertArrayEqualsIntArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new int[] { Integer.MAX_VALUE, Integer.MIN_VALUE }, new int[] { 1, 2, 3 }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertArrayEqualsDifferentIntArrays() { - try { - assertArrayEquals(new int[] { Integer.MIN_VALUE, 1, 2, 10 }, new int[] { Integer.MIN_VALUE, 1, 10, 10 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [2], expected: <2> but was: <10>"); - } - } - - @Test - void assertArrayEqualsDifferentIntArraysAndMessage() { - try { - assertArrayEquals(new int[] { 9, 10, 100, 100_000, 7 }, new int[] { 9, 10, 100, 100_000, 200_000 }, - "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [4], expected: <7> but was: <200000>"); - } - } - - @Test - void assertArrayEqualsDifferentIntArraysAndMessageSupplier() { - try { - assertArrayEquals(new int[] { 1, Integer.MIN_VALUE, 2 }, new int[] { 1, Integer.MAX_VALUE, 2 }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, - "array contents differ at index [1], expected: <-2147483648> but was: <2147483647>"); - } - } - - @Test - void assertArrayEqualsLongArrays() { - assertArrayEquals(new long[] {}, new long[] {}); - assertArrayEquals(new long[] {}, new long[] {}, "message"); - assertArrayEquals(new long[] {}, new long[] {}, () -> "message"); - assertArrayEquals(new long[] { Long.MAX_VALUE }, new long[] { Long.MAX_VALUE }); - assertArrayEquals(new long[] { Long.MIN_VALUE, 10, 20, 30 }, new long[] { Long.MIN_VALUE, 10, 20, 30 }); - } - - @Test - void assertArrayEqualsLongArrayVsNull() { - try { - assertArrayEquals(null, new long[] { Long.MAX_VALUE, 2, 10 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new long[] { Long.MAX_VALUE, 2, 10 }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsLongArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new long[] { 42, 4242, 424242, 4242424242L }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new long[] { 4242424242L, 424242, 4242, 42 }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsLongArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new long[] { 12345678910L, 10, 9, 8 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new long[] { 8, 9, 10, 12345678910L }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsLongArraysOfDifferentLength() { - try { - assertArrayEquals(new long[] { 1, 2, 3, Long.MIN_VALUE, 4 }, new long[] { 1, Long.MAX_VALUE, 2 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <5> but was: <3>"); - } - } - - @Test - void assertArrayEqualsLongArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new long[] { 100_000L, 200_000L, 1L, 2L }, new long[] { 1L, 2L, 3L }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <3>"); - } - } - - @Test - void assertArrayEqualsLongArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new long[] { Long.MAX_VALUE, Long.MIN_VALUE }, new long[] { 1L, 2L, 42L }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertArrayEqualsDifferentLongArrays() { - try { - assertArrayEquals(new long[] { Long.MIN_VALUE, 17, 18L, 19 }, new long[] { Long.MIN_VALUE, 17, 18, 20 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [3], expected: <19> but was: <20>"); - } - } - - @Test - void assertArrayEqualsDifferentLongArraysAndMessage() { - try { - assertArrayEquals(new long[] { 6, 5, 4, 3, 2, Long.MIN_VALUE }, new long[] { 6, 5, 4, 3, 2, 1 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, - "array contents differ at index [5], expected: <-9223372036854775808> but was: <1>"); - } - } - - @Test - void assertArrayEqualsDifferentLongArraysAndMessageSupplier() { - try { - assertArrayEquals(new long[] { 42, -9999L, 2 }, new long[] { 42L, +9999L, 2L }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: <-9999> but was: <9999>"); - } - } - - @Test - void assertArrayEqualsFloatArrays() { - assertArrayEquals(new float[] {}, new float[] {}); - assertArrayEquals(new float[] {}, new float[] {}, "message"); - assertArrayEquals(new float[] {}, new float[] {}, () -> "message"); - assertArrayEquals(new float[] { Float.MAX_VALUE }, new float[] { Float.MAX_VALUE }); - assertArrayEquals(new float[] { Float.MIN_VALUE, 5F, 5.5F, 1.00F }, - new float[] { Float.MIN_VALUE, 5F, 5.5F, 1.00F }); - - assertArrayEquals(new float[] { Float.NaN }, new float[] { Float.NaN }); - assertArrayEquals(new float[] { 10.18F, Float.NaN, 42.9F }, new float[] { 10.18F, Float.NaN, 42.9F }); - } - - @Test - void assertArrayEqualsFloatArrayVsNull() { - try { - assertArrayEquals(null, new float[] { Float.MAX_VALUE, 4.2F, 9.0F }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new float[] { Float.MIN_VALUE, 2.3F, 10.10F }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsFloatArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new float[] { 42.42F, 42.4242F, 19.20F }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new float[] { 11.101F, 12.101F, 99.9F }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsFloatArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new float[] { 5F, 6F, 7.77F, 8.88F }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new float[] { 1F, 1.1F, 1.11F, 1.111F }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsFloatArraysOfDifferentLength() { - try { - assertArrayEquals(new float[] { Float.MIN_VALUE, 1F, 2F, 3F }, new float[] { Float.MAX_VALUE, 7.1F }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <2>"); - } - } - - @Test - void assertArrayEqualsFloatArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new float[] { 19.1F, 12.77F, 18.F }, new float[] { .9F, .8F, 5.123F, .10F }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <4>"); - } - } - - @Test - void assertArrayEqualsFloatArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new float[] { 1.1F, 1.2F, 1.3F }, new float[] { 1F, 2F, 3F, 4F, 5F, 6F }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <6>"); - } - } - - @Test - void assertArrayEqualsDifferentFloatArrays() { - try { - assertArrayEquals(new float[] { 5.5F, 6.5F, 7.5F, 8.5F }, new float[] { 5.5F, 6.5F, 7.4F, 8.5F }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [2], expected: <7.5> but was: <7.4>"); - } - - try { - assertArrayEquals(new float[] { 1.0F, 2.0F, 3.0F, Float.NaN }, new float[] { 1.0F, 2.0F, 3.0F, 4.0F }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [3], expected: but was: <4.0>"); - } - } - - @Test - void assertArrayEqualsDifferentFloatArraysAndMessage() { - try { - assertArrayEquals(new float[] { 1.9F, 0.5F, 0.4F, 0.3F }, new float[] { 1.9F, 0.5F, 0.4F, -0.333F }, - "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [3], expected: <0.3> but was: <-0.333>"); - } - } - - @Test - void assertArrayEqualsDifferentFloatArraysAndMessageSupplier() { - try { - assertArrayEquals(new float[] { 0.3F, 0.9F, 8F }, new float[] { 0.3F, Float.MIN_VALUE, 8F }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.9> but was: <1.4E-45>"); - } - } - - @Test - void assertArrayEqualsDeltaFloatArrays() { - assertArrayEquals(new float[] {}, new float[] {}, 0.001F); - assertArrayEquals(new float[] {}, new float[] {}, 0.001F, "message"); - assertArrayEquals(new float[] {}, new float[] {}, 0.001F, () -> "message"); - assertArrayEquals(new float[] { Float.MAX_VALUE }, new float[] { Float.MAX_VALUE }, 0.0001F); - assertArrayEquals(new float[] { Float.MIN_VALUE, 2.111F, 2.521F, 1.01F }, - new float[] { Float.MIN_VALUE, 2.119F, 2.523F, 1.01001F }, 0.01F); - - assertArrayEquals(new float[] { Float.NaN }, new float[] { Float.NaN }, 0.1F); - assertArrayEquals(new float[] { 10.18F, Float.NaN, 42.9F }, new float[] { 10.98F, Float.NaN, 43.9F }, 1F); - } - - @Test - void assertArrayEqualsDeltaFloatArraysThrowsForIllegalDelta() { - try { - assertArrayEquals(new float[] {}, new float[] {}, -0.5F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); - } - try { - assertArrayEquals(new float[] {}, new float[] {}, Float.NaN); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: "); - } - - try { - assertArrayEquals(new float[] { 12.9F, 7F, 13F }, new float[] { 12.9F, 7F, 13F }, -0.5F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); - } - try { - assertArrayEquals(new float[] { 1.11F, 1.11F, 9F }, new float[] { 1.11F, 1.11F, 9F, 10F }, Float.NaN); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: "); - } - } - - @Test - void assertArrayEqualsDeltaFloatArrayVsNull() { - try { - assertArrayEquals(null, new float[] { Float.MAX_VALUE, 4.2F, 9.0F }, 0.001F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new float[] { Float.MIN_VALUE, 2.3F, 10.10F }, null, 0.01F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDeltaFloatArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new float[] { 42.42F, 42.4242F, 19.20F }, 0.0001F, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new float[] { 11.101F, 12.101F, 99.9F }, null, 0.01F, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDeltaFloatArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new float[] { 5F, 6F, 7.77F, 8.88F }, 0.1F, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new float[] { 1F, 1.1F, 1.11F, 1.111F }, null, 0.1F, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDeltaFloatArraysOfDifferentLength() { - try { - assertArrayEquals(new float[] { Float.MIN_VALUE, 1F, 2F, 3F }, new float[] { Float.MAX_VALUE, 7.1F }, 0.1F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <2>"); - } - } - - @Test - void assertArrayEqualsDeltaFloatArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new float[] { 19.1F, 12.77F }, new float[] { .9F, .8F, 5.123F }, 0.1F, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertArrayEqualsDeltaFloatArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new float[] { 1.1F, 1.2F, 1.3F }, new float[] { 1F, 2F, 3F, 4F }, 0.1F, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <4>"); - } - } - - @Test - void assertArrayEqualsDeltaDifferentFloatArrays() { - try { - assertArrayEquals(new float[] { 5.6F, 3.2F, 9.1F, 0.5F }, new float[] { 5.55F, 3.3F, 9.201F, 0.51F }, 0.1F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [2], expected: <9.1> but was: <9.201>"); - } - - try { - assertArrayEquals(new float[] { 1.0F, 2.0F, 3.0F, Float.NaN }, new float[] { 1.5F, 1.5F, 2.9F, 4.0F }, - 0.5F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [3], expected: but was: <4.0>"); - } - } - - @Test - void assertArrayEqualsDeltaDifferentFloatArraysAndMessage() { - try { - assertArrayEquals(new float[] { 1.91F, 0.5F, .4F, 0.3F }, new float[] { 2F, 0.509F, .499F, -0.333F }, 0.1F, - "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [3], expected: <0.3> but was: <-0.333>"); - } - } - - @Test - void assertArrayEqualsDeltaDifferentFloatArraysAndMessageSupplier() { - try { - assertArrayEquals(new float[] { 0.3F, 0.9F, 8F }, new float[] { 0.6F, Float.MIN_VALUE, 8.4F }, 0.5F, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.9> but was: <1.4E-45>"); - } - } - - @Test - void assertArrayEqualsDoubleArrays() { - assertArrayEquals(new double[] {}, new double[] {}); - assertArrayEquals(new double[] {}, new double[] {}, "message"); - assertArrayEquals(new double[] {}, new double[] {}, () -> "message"); - assertArrayEquals(new double[] { Double.MAX_VALUE }, new double[] { Double.MAX_VALUE }); - assertArrayEquals(new double[] { Double.MIN_VALUE, 2.1, 5.5, 1.0 }, - new double[] { Double.MIN_VALUE, 2.1, 5.5, 1.0 }); - - assertArrayEquals(new double[] { Double.NaN }, new double[] { Double.NaN }); - assertArrayEquals(new double[] { 1.2, 10.8, Double.NaN, 42.9 }, new double[] { 1.2, 10.8, Double.NaN, 42.9 }); - } - - @Test - void assertArrayEqualsDoubleArrayVsNull() { - try { - assertArrayEquals(null, new double[] { Double.MAX_VALUE, 17.4, 98.7654321 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new double[] { Double.MIN_VALUE, 93.0, 92.000001 }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDoubleArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new double[] { 33.3, 34.9, 20.1, 11.0011 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new double[] { 44.4, 20.19, 11.3, 0.11 }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDoubleArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new double[] { 1.2, 1.3, 1.4, 2.2002 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new double[] { 13.13, 43.33, 100 }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDoubleArraysOfDifferentLength() { - try { - assertArrayEquals(new double[] { Double.MIN_VALUE, 1.0, 2.0, 3.0 }, - new double[] { Double.MAX_VALUE, 1.1, 1.0 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <3>"); - } - } - - @Test - void assertArrayEqualsDoubleArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new double[] { 11.1, 99.1, 2 }, new double[] { .9, .1, .0, .1, .3 }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <5>"); - } - } - - @Test - void assertArrayEqualsDoubleArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new double[] { 1.15D, 2.2, 2.3 }, new double[] { 1.15D, 1.15D }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <2>"); - } - } - - @Test - void assertArrayEqualsDifferentDoubleArrays() { - try { - assertArrayEquals(new double[] { 1.17, 1.19, 1.21, 5 }, new double[] { 1.17, 1.00019, 1.21, 5 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [1], expected: <1.19> but was: <1.00019>"); - } - - try { - assertArrayEquals(new double[] { 0.1, 0.2, 0.3, 0.4, 0.5 }, - new double[] { 0.1, 0.2, 0.3, 0.4, Double.NaN }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [4], expected: <0.5> but was: "); - } - } - - @Test - void assertArrayEqualsDifferentDoubleArraysAndMessage() { - try { - assertArrayEquals(new double[] { 1.01, 9.031, .123, 4.23 }, new double[] { 1.01, 9.099, .123, 4.23 }, - "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: <9.031> but was: <9.099>"); - } - } - - @Test - void assertArrayEqualsDifferentDoubleArraysAndMessageSupplier() { - try { - assertArrayEquals(new double[] { 0.7, .1, 8 }, new double[] { 0.7, Double.MIN_VALUE, 8 }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.1> but was: <4.9E-324>"); - } - } - - @Test - void assertArrayEqualsDeltaDoubleArrays() { - assertArrayEquals(new double[] {}, new double[] {}, 0.5); - assertArrayEquals(new double[] {}, new double[] {}, 0.5, "message"); - assertArrayEquals(new double[] {}, new double[] {}, 0.5, () -> "message"); - assertArrayEquals(new double[] { Double.MAX_VALUE, 0.1 }, new double[] { Double.MAX_VALUE, 0.2 }, 0.2); - assertArrayEquals(new double[] { Double.MIN_VALUE, 3.1, 1.3, 2.7 }, - new double[] { Double.MIN_VALUE, 3.4, 1.7, 2.4 }, 0.5); - - assertArrayEquals(new double[] { Double.NaN }, new double[] { Double.NaN }, 0.01); - assertArrayEquals(new double[] { 1.2, 1.8, Double.NaN, 4.9 }, new double[] { 1.25, 1.7, Double.NaN, 4.8 }, 0.2); - } - - @Test - void assertArrayEqualsDeltaDoubleArraysThrowsForIllegalDelta() { - try { - assertArrayEquals(new double[] {}, new double[] {}, -0.5F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); - } - try { - assertArrayEquals(new double[] {}, new double[] {}, Float.NaN); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: "); - } - - try { - assertArrayEquals(new double[] { 1.2, 1.3, 10 }, new double[] { 1.2, 1.3, 10 }, -0.5F); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); - } - try { - assertArrayEquals(new double[] { 0.1, 10 }, new double[] { 0.1, 10, 11 }, Float.NaN); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "positive delta expected but was: "); - } - } - - @Test - void assertArrayEqualsDeltaDoubleArrayVsNull() { - try { - assertArrayEquals(null, new double[] { Double.MAX_VALUE, 11.1, 12.12 }, 0.5); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new double[] { Double.MIN_VALUE, 90, 91.9 }, null, 0.1); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDeltaDoubleArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new double[] { 33.3, 34.9, 20.1, 11.0011 }, 0.1, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new double[] { 44.4, 20.19, 11.3, 0.11 }, null, 0.5, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDeltaDoubleArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new double[] { 1.2, 1.3, 1.4, 2.2002 }, 1, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new double[] { 13.13, 43.33, 100 }, null, 1.5, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsDeltaDoubleArraysOfDifferentLength() { - try { - assertArrayEquals(new double[] { Double.MIN_VALUE, 2.0, 3.0, 4.0 }, - new double[] { Double.MAX_VALUE, 2.1, 3.1 }, 0.001); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <3>"); - } - } - - @Test - void assertArrayEqualsDeltaDoubleArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new double[] { 1.1, 99.1, 3.1 }, new double[] { .9, .1, .0, .1, .3 }, 0.1, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <5>"); - } - } - - @Test - void assertArrayEqualsDeltaDoubleArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new double[] { 1.77D, 2.1, 3 }, new double[] { 8.8, 0.11 }, 1, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <2>"); - } - } - - @Test - void assertArrayEqualsDeltaDifferentDoubleArrays() { - try { - assertArrayEquals(new double[] { 1.12, 2.92, 1.201 }, new double[] { 1.1201, 2.94, 1.201 }, 0.01); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [1], expected: <2.92> but was: <2.94>"); - } - - try { - assertArrayEquals(new double[] { 0.6, 0.12, 19.9, 5.5 }, new double[] { 1.0, 0.42, 20, Double.NaN }, 0.5); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [3], expected: <5.5> but was: "); - } - } - - @Test - void assertArrayEqualsDeltaDifferentDoubleArraysAndMessage() { - try { - assertArrayEquals(new double[] { 1.01, 9.031, .123, 4.23 }, new double[] { 1.1, 9.231, .13, 4.3 }, 0.1, - "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: <9.031> but was: <9.231>"); - } - } - - @Test - void assertArrayEqualsDeltaDifferentDoubleArraysAndMessageSupplier() { - try { - assertArrayEquals(new double[] { 0.7, 0.3001, 8 }, new double[] { 0.7, 0.4002, 8 }, 0.1, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.3001> but was: <0.4002>"); - } - } - - @Test - void assertArrayEqualsObjectArrays() { - Object[] array = { "a", 'b', 1, 2 }; - assertArrayEquals(array, array); - - assertArrayEquals(new Object[] {}, new Object[] {}); - assertArrayEquals(new Object[] {}, new Object[] {}, "message"); - assertArrayEquals(new Object[] {}, new Object[] {}, () -> "message"); - assertArrayEquals(new Object[] { "abc" }, new Object[] { "abc" }); - assertArrayEquals(new Object[] { "abc", 1, 2L, 3D }, new Object[] { "abc", 1, 2L, 3D }); - - assertArrayEquals(new Object[] { new Object[] { new Object[] {} } }, - new Object[] { new Object[] { new Object[] {} } }); - - assertArrayEquals( - new Object[] { null, new Object[] { null, new Object[] { null, null } }, null, new Object[] { null } }, - new Object[] { null, new Object[] { null, new Object[] { null, null } }, null, new Object[] { null } }); - - assertArrayEquals(new Object[] { "a", new Object[] { new Object[] { "b", new Object[] { "c", "d" } } }, "e" }, - new Object[] { "a", new Object[] { new Object[] { "b", new Object[] { "c", "d" } } }, "e" }); - - assertArrayEquals( - new Object[] { new Object[] { 1 }, new Object[] { 2 }, - new Object[] { new Object[] { 3, new Object[] { 4 } } } }, - new Object[] { new Object[] { 1 }, new Object[] { 2 }, - new Object[] { new Object[] { 3, new Object[] { 4 } } } }); - - assertArrayEquals( - new Object[] { new Object[] { - new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { "abc" } } } } } } }, - new Object[] { new Object[] { - new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { "abc" } } } } } } }); - - assertArrayEquals( - new Object[] { null, new Object[] { null, Double.NaN, new Object[] { Float.NaN, null, new Object[] {} } } }, - new Object[] { null, - new Object[] { null, Double.NaN, new Object[] { Float.NaN, null, new Object[] {} } } }); - - assertArrayEquals( - new Object[] { new String("a"), Integer.valueOf(1), new Object[] { Double.parseDouble("1.1"), "b" } }, - new Object[] { new String("a"), Integer.valueOf(1), new Object[] { Double.parseDouble("1.1"), "b" } }); - - assertArrayEquals( - new Object[] { 1, 2, - new Object[] { 3, new int[] { 4, 5 }, new long[] { 6 }, - new Object[] { new Object[] { new int[] { 7 } } } }, - new int[] { 8 }, new Object[] { new long[] { 9 } } }, - new Object[] { 1, 2, - new Object[] { 3, new int[] { 4, 5 }, new long[] { 6 }, - new Object[] { new Object[] { new int[] { 7 } } } }, - new int[] { 8 }, new Object[] { new long[] { 9 } } }); - - assertArrayEquals( - new Object[] { "a", new char[] { 'b', 'c' }, new int[] { 'd' }, - new Object[] { new Object[] { new String[] { "ef" }, new Object[] { new String[] { "ghi" } } } } }, - new Object[] { "a", new char[] { 'b', 'c' }, new int[] { 'd' }, - new Object[] { new Object[] { new String[] { "ef" }, new Object[] { new String[] { "ghi" } } } } }); - } - - @Test - void assertArrayEqualsObjectArrayVsNull() { - try { - assertArrayEquals(null, new Object[] { "a", "b", 1, new Object() }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was "); - } - - try { - assertArrayEquals(new Object[] { 'a', 1, new Object(), 10L }, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsNestedObjectArrayVsNull() { - try { - assertArrayEquals(// - new Object[] { new Object[] {}, 1, "2", new Object[] { '3', new Object[] { null } } }, // - new Object[] { new Object[] {}, 1, "2", new Object[] { '3', new Object[] { new Object[] { "4" } } } }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected array was at index [3][1][0]"); - } - - try { - assertArrayEquals( - new Object[] { 1, 2, new Object[] { 3, new Object[] { "4", new Object[] { 5, new Object[] { 6 } } } }, - "7" }, - new Object[] { 1, 2, new Object[] { 3, new Object[] { "4", new Object[] { 5, null } } }, "7" }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual array was at index [2][1][1][1]"); - } - } - - @Test - void assertArrayEqualsObjectArrayVsNullAndMessage() { - try { - assertArrayEquals(null, new Object[] { 'a', "b", 10, 20D }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new Object[] { "hello", 42 }, null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsNestedObjectArrayVsNullAndMessage() { - try { - assertArrayEquals(new Object[] { 1, new Object[] { 2, 3, new Object[] { 4, 5, new Object[] { null } } } }, - new Object[] { 1, new Object[] { 2, 3, new Object[] { 4, 5, new Object[] { new Object[] { 6 } } } } }, - "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was at index [1][2][2][0]"); - } - - try { - assertArrayEquals( - new Object[] { 1, new Object[] { 2, new Object[] { 3, new Object[] { new Object[] { 4 } } } } }, - new Object[] { 1, new Object[] { 2, new Object[] { 3, new Object[] { null } } } }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was at index [1][1][1][0]"); - } - } - - @Test - void assertArrayEqualsObjectArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(null, new Object[] { 42, "42", new float[] { 42F }, 42D }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was "); - } - - try { - assertArrayEquals(new Object[] { new Object[] { "a" }, new Object() }, null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was "); - } - } - - @Test - void assertArrayEqualsNestedObjectArrayVsNullAndMessageSupplier() { - try { - assertArrayEquals(new Object[] { "1", "2", "3", new Object[] { "4", new Object[] { null } } }, - new Object[] { "1", "2", "3", new Object[] { "4", new Object[] { new int[] { 5 } } } }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected array was at index [3][1][0]"); - } - - try { - assertArrayEquals( - new Object[] { 1, 2, new Object[] { "3", new Object[] { '4', new Object[] { 5, 6, new long[] {} } } } }, - new Object[] { 1, 2, new Object[] { "3", new Object[] { '4', new Object[] { 5, 6, null } } } }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual array was at index [2][1][1][2]"); - } - } - - @Test - void assertArrayEqualsObjectArraysOfDifferentLength() { - try { - assertArrayEquals(new Object[] { 'a', "b", 'c' }, new Object[] { 'a', "b", 'c', 1 }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <4>"); - } - } - - @Test - void assertArrayEqualsNestedObjectArraysOfDifferentLength() { - try { - assertArrayEquals( - new Object[] { "a", new Object[] { "b", new Object[] { "c", "d", new Object[] { "e", 1, 2, 3 } } } }, - new Object[] { "a", - new Object[] { "b", new Object[] { "c", "d", new Object[] { "e", 1, 2, 3, 4, 5 } } } }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ at index [1][1][2], expected: <4> but was: <6>"); - } - - try { - assertArrayEquals( - new Object[] { new Object[] { - new Object[] { new Object[] { new Object[] { new Object[] { new char[] { 'a' } } } } } } }, - new Object[] { new Object[] { new Object[] { - new Object[] { new Object[] { new Object[] { new char[] { 'a', 'b' } } } } } } }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array lengths differ at index [0][0][0][0][0][0], expected: <1> but was: <2>"); - } - } - - @Test - void assertArrayEqualsObjectArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(new Object[] { 'a', 1 }, new Object[] { 'a', 1, new Object() }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertArrayEqualsNestedObjectArraysOfDifferentLengthAndMessage() { - try { - assertArrayEquals(// - new Object[] { 'a', 1, new Object[] { 2, 3 } }, // - new Object[] { 'a', 1, new Object[] { 2, 3, 4, 5 } }, // - "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ at index [2], expected: <2> but was: <4>"); - } - } - - @Test - void assertArrayEqualsObjectArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(new Object[] { "a", "b", "c" }, new Object[] { "a", "b", "c", "d", "e", "f" }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <6>"); - } - } - - @Test - void assertArrayEqualsNestedObjectArraysOfDifferentLengthAndMessageSupplier() { - try { - assertArrayEquals(// - new Object[] { "a", new Object[] { 1, 2, 3, new double[] { 4.0, 5.1, 6.1 }, 7 } }, // - new Object[] { "a", new Object[] { 1, 2, 3, new double[] { 4.0, 5.1, 6.1, 7.0 }, 8 } }, // - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array lengths differ at index [1][3], expected: <3> but was: <4>"); - } - } - - @Test - void assertArrayEqualsDifferentObjectArrays() { - try { - assertArrayEquals(new Object[] { 1L, "2", '3', 4, 5D }, new Object[] { 1L, "2", '9', 4, 5D }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [2], expected: <3> but was: <9>"); - } - - try { - assertArrayEquals(new Object[] { "a", 10, 11, 12, Double.NaN }, new Object[] { "a", 10, 11, 12, 13.55D }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [4], expected: but was: <13.55>"); - } - } - - @Test - void assertArrayEqualsDifferentNestedObjectArrays() { - try { - assertArrayEquals( - new Object[] { 1, 2, new Object[] { 3, new Object[] { 4, new boolean[] { false, true } } } }, - new Object[] { 1, 2, new Object[] { 3, new Object[] { 4, new boolean[] { true, false } } } }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [2][1][1][0], expected: but was: "); - } - - try { - assertArrayEquals(new Object[] { 1, 2, 3, new Object[] { new Object[] { 4, new Object[] { 5 } } } }, - new Object[] { 1, 2, 3, new Object[] { new Object[] { 4, new Object[] { new Object[] {} } } } }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "array contents differ at index [3][0][1][0], expected: <5> but was: <[]>"); - } - } - - @Test - void assertArrayEqualsDifferentObjectArraysAndMessage() { - try { - assertArrayEquals(new Object[] { 1.1D, 2L, "3" }, new Object[] { 1D, 2L, "3" }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [0], expected: <1.1> but was: <1.0>"); - } - } - - @Test - void assertArrayEqualsDifferentNestedObjectArraysAndMessage() { - try { - assertArrayEquals(new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new Object[] { "2", '1' } } }, - new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new Object[] { "99", '1' } } }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>"); - } - - try { - assertArrayEquals(new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new String[] { "2", "1" } } }, - new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new String[] { "99", "1" } } }, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>"); - } - } - - @Test - void assertArrayEqualsDifferentObjectArraysAndMessageSupplier() { - try { - assertArrayEquals(new Object[] { "one", 1L, Double.MIN_VALUE, "abc" }, - new Object[] { "one", 1L, 42.42, "abc" }, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [2], expected: <4.9E-324> but was: <42.42>"); - } - } - - @Test - void assertArrayEqualsDifferentNestedObjectArraysAndMessageSupplier() { - try { - assertArrayEquals( - new Object[] { "one", 1L, new Object[] { "a", 'b', new Object[] { 1, new Object[] { 2, 3 } } }, "abc" }, - new Object[] { "one", 1L, new Object[] { "a", 'b', new Object[] { 1, new Object[] { 2, 4 } } }, "abc" }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [2][2][1][1], expected: <3> but was: <4>"); - } - - try { - assertArrayEquals( - new Object[] { "j", new String[] { "a" }, new int[] { 42 }, "ab", new Object[] { 1, new int[] { 3 } } }, - new Object[] { "j", new String[] { "a" }, new int[] { 42 }, "ab", new Object[] { 1, new int[] { 5 } } }, - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "array contents differ at index [4][1][0], expected: <3> but was: <5>"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java deleted file mode 100644 index 77bc9ce4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.io.IOException; -import java.util.concurrent.FutureTask; - -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.function.ThrowingSupplier; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.2 - */ -class AssertDoesNotThrowAssertionsTests { - - private static final Executable nix = () -> { - }; - - private static final ThrowingSupplier something = () -> "enigma"; - - @Test - void assertDoesNotThrowWithMethodReferenceForNonVoidReturnType() { - FutureTask future = new FutureTask<>(() -> { - return "foo"; - }); - future.run(); - - String result; - - // Current compiler's type inference: does NOT compile since the compiler - // cannot figure out which overloaded variant of assertDoesNotThrow() to - // invoke (i.e., Executable vs. ThrowingSupplier). - // - // result = assertDoesNotThrow(future::get); - - // Explicitly as an Executable - assertDoesNotThrow((Executable) future::get); - - // Explicitly as a ThrowingSupplier - result = assertDoesNotThrow((ThrowingSupplier) future::get); - assertEquals("foo", result); - } - - @Test - void assertDoesNotThrowWithMethodReferenceForVoidReturnType() { - var foo = new Foo(); - - // Note: the following does not compile since the compiler cannot properly - // perform type inference for a method reference for an overloaded method - // that has a void return type such as Foo.overloaded(...), IFF the - // compiler is simultaneously trying to pick which overloaded variant - // of assertDoesNotThrow() to invoke. - // - // assertDoesNotThrow(foo::overloaded); - - // Current compiler's type inference - assertDoesNotThrow(foo::normalMethod); - - // Explicitly as an Executable - assertDoesNotThrow((Executable) foo::normalMethod); - assertDoesNotThrow((Executable) foo::overloaded); - } - - // --- executable ---------------------------------------------------------- - - @Test - void assertDoesNotThrowAnythingWithExecutable() { - assertDoesNotThrow(nix); - } - - @Test - void assertDoesNotThrowAnythingWithExecutableAndMessage() { - assertDoesNotThrow(nix, "message"); - } - - @Test - void assertDoesNotThrowAnythingWithExecutableAndMessageSupplier() { - assertDoesNotThrow(nix, () -> "message"); - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsACheckedException() { - try { - assertDoesNotThrow((Executable) () -> { - throw new IOException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsACheckedExceptionWithMessage() { - String message = "Checked exception message"; - try { - assertDoesNotThrow((Executable) () -> { - throw new IOException(message); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName() + ": " + message); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsARuntimeException() { - try { - assertDoesNotThrow((Executable) () -> { - throw new IllegalStateException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Unexpected exception thrown: " + IllegalStateException.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsARuntimeExceptionWithMessage() { - String message = "Runtime exception message"; - try { - assertDoesNotThrow((Executable) () -> { - throw new IllegalStateException(message); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, - "Unexpected exception thrown: " + IllegalStateException.class.getName() + ": " + message); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsAnError() { - try { - assertDoesNotThrow((Executable) AssertionTestUtils::recurseIndefinitely); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Unexpected exception thrown: " + StackOverflowError.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageString() { - try { - assertDoesNotThrow((Executable) () -> { - throw new IllegalStateException(); - }, "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, - "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageWithMessageString() { - String message = "Runtime exception message"; - try { - assertDoesNotThrow((Executable) () -> { - throw new IllegalStateException(message); - }, "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " - + IllegalStateException.class.getName() + ": " + message); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageSupplier() { - try { - assertDoesNotThrow((Executable) () -> { - throw new IllegalStateException(); - }, () -> "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, - "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageWithMessageSupplier() { - String message = "Runtime exception message"; - try { - assertDoesNotThrow((Executable) () -> { - throw new IllegalStateException(message); - }, () -> "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " - + IllegalStateException.class.getName() + ": " + message); - } - } - - // --- supplier ------------------------------------------------------------ - - @Test - void assertDoesNotThrowAnythingWithSupplier() { - assertEquals("enigma", assertDoesNotThrow(something)); - } - - @Test - void assertDoesNotThrowAnythingWithSupplierAndMessage() { - assertEquals("enigma", assertDoesNotThrow(something, "message")); - } - - @Test - void assertDoesNotThrowAnythingWithSupplierAndMessageSupplier() { - assertEquals("enigma", assertDoesNotThrow(something, () -> "message")); - } - - @Test - void assertDoesNotThrowWithSupplierThatThrowsACheckedException() { - try { - assertDoesNotThrow((ThrowingSupplier) () -> { - throw new IOException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithSupplierThatThrowsARuntimeException() { - try { - assertDoesNotThrow((ThrowingSupplier) () -> { - throw new IllegalStateException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Unexpected exception thrown: " + IllegalStateException.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithSupplierThatThrowsAnError() { - try { - assertDoesNotThrow((ThrowingSupplier) () -> { - throw new StackOverflowError(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Unexpected exception thrown: " + StackOverflowError.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithSupplierThatThrowsAnExceptionWithMessageString() { - try { - assertDoesNotThrow((ThrowingSupplier) () -> { - throw new IllegalStateException(); - }, "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, - "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); - } - } - - @Test - void assertDoesNotThrowWithSupplierThatThrowsAnExceptionWithMessageSupplier() { - try { - assertDoesNotThrow((ThrowingSupplier) () -> { - throw new IllegalStateException(); - }, () -> "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, - "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); - } - } - - // ------------------------------------------------------------------------- - - private static class Foo { - - void normalMethod() { - } - - void overloaded() { - } - - @SuppressWarnings("unused") - void overloaded(int i) { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java deleted file mode 100644 index 8c09c72c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java +++ /dev/null @@ -1,771 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.function.Executable; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertEqualsAssertionsTests { - - @Test - void assertEqualsByte() { - byte expected = 1; - byte actual = 1; - assertEquals(expected, actual); - assertEquals(expected, actual, "message"); - assertEquals(expected, actual, () -> "message"); - } - - @Test - void assertEqualsByteWithUnequalValues() { - byte expected = 1; - byte actual = 2; - try { - assertEquals(expected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, expected, actual); - } - } - - @Test - void assertEqualsByteWithUnequalValuesAndMessage() { - byte expected = 1; - byte actual = 2; - try { - assertEquals(expected, actual, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, expected, actual); - } - } - - @Test - void assertEqualsByteWithUnequalValuesAndMessageSupplier() { - byte expected = 1; - byte actual = 2; - try { - assertEquals(expected, actual, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, expected, actual); - } - } - - @Test - void assertEqualsShort() { - short expected = 1; - short actual = 1; - assertEquals(expected, actual); - assertEquals(expected, actual, "message"); - assertEquals(expected, actual, () -> "message"); - } - - @Test - void assertEqualsShortWithUnequalValues() { - short expected = 1; - short actual = 2; - try { - assertEquals(expected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, expected, actual); - } - } - - @Test - void assertEqualsShortWithUnequalValuesAndMessage() { - short expected = 1; - short actual = 2; - try { - assertEquals(expected, actual, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, expected, actual); - } - } - - @Test - void assertEqualsShortWithUnequalValuesAndMessageSupplier() { - short expected = 1; - short actual = 2; - try { - assertEquals(expected, actual, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, expected, actual); - } - } - - @Test - void assertEqualsInt() { - assertEquals(1, 1); - assertEquals(1, 1, "message"); - assertEquals(1, 1, () -> "message"); - } - - @Test - void assertEqualsIntWithUnequalValues() { - try { - assertEquals(1, 2); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, 1, 2); - } - } - - @Test - void assertEqualsIntWithUnequalValuesAndMessage() { - try { - assertEquals(1, 2, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, 1, 2); - } - } - - @Test - void assertEqualsIntWithUnequalValuesAndMessageSupplier() { - try { - assertEquals(1, 2, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, 1, 2); - } - } - - @Test - void assertEqualsLong() { - assertEquals(1L, 1L); - assertEquals(1L, 1L, "message"); - assertEquals(1L, 1L, () -> "message"); - } - - @Test - void assertEqualsLongWithUnequalValues() { - try { - assertEquals(1L, 2L); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, 1L, 2L); - } - } - - @Test - void assertEqualsLongWithUnequalValuesAndMessage() { - try { - assertEquals(1L, 2L, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, 1L, 2L); - } - } - - @Test - void assertEqualsLongWithUnequalValuesAndMessageSupplier() { - try { - assertEquals(1L, 2L, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1> but was: <2>"); - assertExpectedAndActualValues(ex, 1L, 2L); - } - } - - @Test - void assertEqualsChar() { - assertEquals('a', 'a'); - assertEquals('a', 'a', "message"); - assertEquals('a', 'a', () -> "message"); - } - - @Test - void assertEqualsCharWithUnequalValues() { - try { - assertEquals('a', 'b'); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, 'a', 'b'); - } - } - - @Test - void assertEqualsCharWithUnequalValuesAndMessage() { - try { - assertEquals('a', 'b', "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, 'a', 'b'); - } - } - - @Test - void assertEqualsCharWithUnequalValuesAndMessageSupplier() { - try { - assertEquals('a', 'b', () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, 'a', 'b'); - } - } - - @Test - void assertEqualsFloat() { - assertEquals(1.0f, 1.0f); - assertEquals(1.0f, 1.0f, "message"); - assertEquals(1.0f, 1.0f, () -> "message"); - assertEquals(Float.NaN, Float.NaN); - assertEquals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - assertEquals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - assertEquals(Float.MIN_VALUE, Float.MIN_VALUE); - assertEquals(Float.MAX_VALUE, Float.MAX_VALUE); - assertEquals(Float.MIN_NORMAL, Float.MIN_NORMAL); - assertEquals(Double.NaN, Float.NaN); - } - - @Test - void assertEqualsFloatWithUnequalValues() { - try { - assertEquals(1.0f, 1.1f); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: <1.0> but was: <1.1>"); - assertExpectedAndActualValues(ex, 1.0f, 1.1f); - } - } - - @Test - void assertEqualsFloatWithUnequalValuesAndMessage() { - try { - assertEquals(1.0f, 1.1f, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); - assertExpectedAndActualValues(ex, 1.0f, 1.1f); - } - } - - @Test - void assertEqualsFloatWithUnequalValuesAndMessageSupplier() { - try { - assertEquals(1.0f, 1.1f, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); - assertExpectedAndActualValues(ex, 1.0f, 1.1f); - } - } - - @Test - void assertEqualsFloatWithDelta() { - assertEquals(0.0f, 0.0f, 0.1f); - assertEquals(0.0f, 0.0f, 0.1f, "message"); - assertEquals(0.0f, 0.0f, 0.1f, () -> "message"); - assertEquals(0.56f, 0.6f, 0.05f); - assertEquals(0.01f, 0.011f, 0.002f); - assertEquals(Float.NaN, Float.NaN, 0.5f); - assertEquals(0.1f, 0.1f, 0.0f); - } - - @Test - void assertEqualsFloatWithIllegalDelta() { - AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1f, 0.2f, -0.9f)); - assertMessageEndsWith(e1, "positive delta expected but was: <-0.9>"); - - AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(.0f, .0f, -10.5f)); - assertMessageEndsWith(e2, "positive delta expected but was: <-10.5>"); - - AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(4.5f, 4.6f, Float.NaN)); - assertMessageEndsWith(e3, "positive delta expected but was: "); - } - - @Test - void assertEqualsFloatWithDeltaWithUnequalValues() { - AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.5f, 0.2f, 0.2f)); - assertMessageEndsWith(e1, "expected: <0.5> but was: <0.2>"); - - AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1f, 0.2f, 0.000001f)); - assertMessageEndsWith(e2, "expected: <0.1> but was: <0.2>"); - - AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(100.0f, 50.0f, 10.0f)); - assertMessageEndsWith(e3, "expected: <100.0> but was: <50.0>"); - - AssertionFailedError e4 = assertThrows(AssertionFailedError.class, () -> assertEquals(-3.5f, -3.3f, 0.01f)); - assertMessageEndsWith(e4, "expected: <-3.5> but was: <-3.3>"); - - AssertionFailedError e5 = assertThrows(AssertionFailedError.class, () -> assertEquals(+0.0f, -0.001f, .00001f)); - assertMessageEndsWith(e5, "expected: <0.0> but was: <-0.001>"); - } - - @Test - void assertEqualsFloatWithDeltaWithUnequalValuesAndMessage() { - Executable assertion = () -> assertEquals(0.5f, 0.45f, 0.03f, "message"); - - AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); - - assertMessageStartsWith(e, "message"); - assertMessageEndsWith(e, "expected: <0.5> but was: <0.45>"); - assertExpectedAndActualValues(e, 0.5f, 0.45f); - } - - @Test - void assertEqualsFloatWithDeltaWithUnequalValuesAndMessageSupplier() { - Executable assertion = () -> assertEquals(0.5f, 0.45f, 0.03f, () -> "message"); - - AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); - - assertMessageStartsWith(e, "message"); - assertMessageEndsWith(e, "expected: <0.5> but was: <0.45>"); - assertExpectedAndActualValues(e, 0.5f, 0.45f); - } - - @Test - void assertEqualsDouble() { - assertEquals(1.0d, 1.0d); - assertEquals(1.0d, 1.0d, "message"); - assertEquals(1.0d, 1.0d, () -> "message"); - assertEquals(Double.NaN, Double.NaN); - assertEquals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); - assertEquals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - assertEquals(Double.MIN_VALUE, Double.MIN_VALUE); - assertEquals(Double.MAX_VALUE, Double.MAX_VALUE); - assertEquals(Double.MIN_NORMAL, Double.MIN_NORMAL); - } - - @Test - void assertEqualsDoubleWithUnequalValues() { - try { - assertEquals(1.0d, 1.1d); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: <1.0> but was: <1.1>"); - assertExpectedAndActualValues(ex, 1.0d, 1.1d); - } - } - - @Test - void assertEqualsDoubleWithUnequalValuesAndMessage() { - try { - assertEquals(1.0d, 1.1d, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); - assertExpectedAndActualValues(ex, 1.0d, 1.1d); - } - } - - @Test - void assertEqualsDoubleWithUnequalValuesAndMessageSupplier() { - try { - assertEquals(1.0d, 1.1d, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); - assertExpectedAndActualValues(ex, 1.0d, 1.1d); - } - } - - @Test - void assertEqualsDoubleWithDelta() { - assertEquals(0.0d, 0.0d, 0.1d); - assertEquals(0.0d, 0.0d, 0.1d, "message"); - assertEquals(0.0d, 0.0d, 0.1d, () -> "message"); - assertEquals(0.42d, 0.24d, 0.19d); - assertEquals(0.02d, 0.011d, 0.01d); - assertEquals(Double.NaN, Double.NaN, 0.2d); - assertEquals(0.001d, 0.001d, 0.0d); - } - - @Test - void assertEqualsDoubleWithIllegalDelta() { - AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(1.1d, 1.11d, -0.5d)); - assertMessageEndsWith(e1, "positive delta expected but was: <-0.5>"); - - AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(.55d, .56d, -10.5d)); - assertMessageEndsWith(e2, "positive delta expected but was: <-10.5>"); - - AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(1.1d, 1.1d, Double.NaN)); - assertMessageEndsWith(e3, "positive delta expected but was: "); - } - - @Test - void assertEqualsDoubleWithDeltaWithUnequalValues() { - AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(9.9d, 9.7d, 0.1d)); - assertMessageEndsWith(e1, "expected: <9.9> but was: <9.7>"); - assertExpectedAndActualValues(e1, 9.9d, 9.7d); - - AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1d, 0.05d, 0.001d)); - assertMessageEndsWith(e2, "expected: <0.1> but was: <0.05>"); - assertExpectedAndActualValues(e2, 0.1d, 0.05d); - - AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(17.11d, 15.11d, 1.1d)); - assertMessageEndsWith(e3, "expected: <17.11> but was: <15.11>"); - assertExpectedAndActualValues(e3, 17.11d, 15.11d); - - AssertionFailedError e4 = assertThrows(AssertionFailedError.class, () -> assertEquals(-7.2d, -5.9d, 1.1d)); - assertMessageEndsWith(e4, "expected: <-7.2> but was: <-5.9>"); - assertExpectedAndActualValues(e4, -7.2d, -5.9d); - - AssertionFailedError e5 = assertThrows(AssertionFailedError.class, () -> assertEquals(+0.0d, -0.001d, .00001d)); - assertMessageEndsWith(e5, "expected: <0.0> but was: <-0.001>"); - assertExpectedAndActualValues(e5, +0.0d, -0.001d); - } - - @Test - void assertEqualsDoubleWithDeltaWithUnequalValuesAndMessage() { - Executable assertion = () -> assertEquals(42.42d, 42.4d, 0.001d, "message"); - - AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); - - assertMessageStartsWith(e, "message"); - assertMessageEndsWith(e, "expected: <42.42> but was: <42.4>"); - assertExpectedAndActualValues(e, 42.42d, 42.4d); - } - - @Test - void assertEqualsDoubleWithDeltaWithUnequalValuesAndMessageSupplier() { - Executable assertion = () -> assertEquals(0.9d, 10.12d, 5.001d, () -> "message"); - - AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); - - assertMessageStartsWith(e, "message"); - assertMessageEndsWith(e, "expected: <0.9> but was: <10.12>"); - assertExpectedAndActualValues(e, 0.9d, 10.12d); - } - - @Test - void assertEqualsWithNullReferences() { - Object null1 = null; - Object null2 = null; - - assertEquals(null1, null); - assertEquals(null, null2); - assertEquals(null1, null2); - } - - @Test - void assertEqualsWithSameObject() { - Object foo = new Object(); - assertEquals(foo, foo); - assertEquals(foo, foo, "message"); - assertEquals(foo, foo, () -> "message"); - } - - @Test - void assertEqualsWithEquivalentStrings() { - assertEquals(new String("foo"), new String("foo")); - } - - @Test - void assertEqualsWithNullVsObject() { - try { - assertEquals(null, "foo"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, null, "foo"); - } - } - - @Test - void assertEqualsWithObjectVsNull() { - try { - assertEquals("foo", null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, "foo", null); - } - } - - @Test - void assertEqualsWithObjectWithNullStringReturnedFromToStringVsNull() { - try { - assertEquals("null", null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "expected: java.lang.String@"); - assertMessageEndsWith(ex, " but was: "); - assertExpectedAndActualValues(ex, "null", null); - } - } - - @Test - void assertEqualsWithNullVsObjectWithNullStringReturnedFromToString() { - try { - assertEquals(null, "null"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "expected: but was: java.lang.String@"); - assertMessageEndsWith(ex, ""); - assertExpectedAndActualValues(ex, null, "null"); - } - } - - @Test - void assertEqualsWithNullVsObjectAndMessageSupplier() { - try { - assertEquals(null, "foo", () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageEndsWith(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, null, "foo"); - } - } - - @Test - void assertEqualsWithObjectVsNullAndMessageSupplier() { - try { - assertEquals("foo", null, () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageEndsWith(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, "foo", null); - } - } - - @Test - void assertEqualsInvokesEqualsMethodForIdenticalObjects() { - Object obj = new EqualsThrowsException(); - assertThrows(NumberFormatException.class, () -> assertEquals(obj, obj)); - } - - @Test - void assertEqualsWithUnequalObjectWhoseToStringImplementationThrowsAnException() { - try { - assertEquals(new ToStringThrowsException(), "foo"); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "expected: <" + ToStringThrowsException.class.getName() + "@"); - assertMessageEndsWith(ex, "but was: "); - } - } - - // ------------------------------------------------------------------------- - - @Nested - class MixedBoxedAndUnboxedPrimitivesTests { - - @Test - void bytes() { - byte primitive = (byte) 42; - Byte wrapper = Byte.valueOf("42"); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, () -> "message"); - } - - @Test - void shorts() { - short primitive = (short) 42; - Short wrapper = Short.valueOf("42"); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, () -> "message"); - } - - @Test - void integers() { - int primitive = 42; - Integer wrapper = Integer.valueOf("42"); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, () -> "message"); - } - - @Test - void longs() { - long primitive = 42L; - Long wrapper = Long.valueOf("42"); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, () -> "message"); - } - - @Test - void floats() { - float primitive = 42.0f; - Float wrapper = Float.valueOf("42.0"); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, 0.0f); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, 0.0f, "message"); - assertEquals(primitive, wrapper, () -> "message"); - assertEquals(primitive, wrapper, 0.0f, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, 0.0f); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, 0.0f, "message"); - assertEquals(wrapper, primitive, () -> "message"); - assertEquals(wrapper, primitive, 0.0f, () -> "message"); - } - - @Test - void doubles() { - double primitive = 42.0d; - Double wrapper = Double.valueOf("42.0"); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, 0.0d); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, 0.0d, "message"); - assertEquals(primitive, wrapper, () -> "message"); - assertEquals(primitive, wrapper, 0.0d, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, 0.0d); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, 0.0d, "message"); - assertEquals(wrapper, primitive, () -> "message"); - assertEquals(wrapper, primitive, 0.0d, () -> "message"); - } - - @Test - void booleans() { - boolean primitive = true; - Boolean wrapper = Boolean.valueOf("true"); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, () -> "message"); - } - - @Test - void chars() { - char primitive = 'a'; - Character wrapper = Character.valueOf('a'); - - assertEquals(primitive, wrapper); - assertEquals(primitive, wrapper, "message"); - assertEquals(primitive, wrapper, () -> "message"); - - assertEquals(wrapper, primitive); - assertEquals(wrapper, primitive, "message"); - assertEquals(wrapper, primitive, () -> "message"); - } - - } - - // ------------------------------------------------------------------------- - - private static class EqualsThrowsException { - - @Override - public boolean equals(Object obj) { - throw new NumberFormatException(); - } - } - - private static class ToStringThrowsException { - - @Override - public String toString() { - throw new NumberFormatException(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java deleted file mode 100644 index ee2f2f61..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertFalse; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertFalseAssertionsTests { - - @Test - void assertFalseWithBooleanFalse() { - assertFalse(false); - assertFalse(false, "test"); - assertFalse(false, () -> "test"); - } - - @Test - void assertFalseWithBooleanSupplierFalse() { - assertFalse(() -> false); - assertFalse(() -> false, "test"); - assertFalse(() -> false, () -> "test"); - } - - @Test - void assertFalseWithBooleanFalseAndMessageSupplier() { - assertFalse(false, () -> "test"); - } - - @Test - void assertFalseWithBooleanSupplierFalseAndMessageSupplier() { - assertFalse(() -> false, () -> "test"); - } - - @Test - void assertFalseWithBooleanTrueAndDefaultMessageWithExpectedAndActualValues() { - try { - assertFalse(true); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, false, true); - } - } - - @Test - void assertFalseWithBooleanTrueAndString() { - try { - assertFalse(true, "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, false, true); - } - } - - @Test - void assertFalseWithBooleanSupplierTrueAndString() { - try { - assertFalse(() -> true, "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, false, true); - } - } - - @Test - void assertFalseWithBooleanTrueAndMessageSupplier() { - try { - assertFalse(true, () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, false, true); - } - } - - @Test - void assertFalseWithBooleanSupplierTrueAndMessageSupplier() { - try { - assertFalse(() -> true, () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, false, true); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java deleted file mode 100644 index 70d5882b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.IOException; - -import org.junit.jupiter.api.function.Executable; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions#assertInstanceOf(Class, Object)}. - * - * @since 5.8 - */ -class AssertInstanceOfAssertionsTests { - - @Test - void assertInstanceOfFailsNullValue() { - assertInstanceOfFails(String.class, null, "null value"); - } - - @Test - void assertInstanceOfFailsWrongTypeValue() { - assertInstanceOfFails(String.class, 1, "type"); - } - - @Test - void assertInstanceOfFailsWrongExceptionValue() { - assertInstanceOfFails(RuntimeException.class, new IOException(), "type"); - } - - @Test - void assertInstanceOfFailsSuperTypeExceptionValue() { - assertInstanceOfFails(IllegalArgumentException.class, new RuntimeException(), "type"); - } - - private static class BaseClass { - } - - private static class SubClass extends BaseClass { - - } - - @Test - void assertInstanceOfFailsSuperTypeValue() { - assertInstanceOfFails(SubClass.class, new BaseClass(), "type"); - } - - @Test - void assertInstanceOfSucceedsSameTypeValue() { - assertInstanceOfSucceeds(String.class, "indeed a String"); - assertInstanceOfSucceeds(BaseClass.class, new BaseClass()); - assertInstanceOfSucceeds(SubClass.class, new SubClass()); - } - - @Test - void assertInstanceOfSucceedsExpectSuperClassOfValue() { - assertInstanceOfSucceeds(CharSequence.class, "indeed a CharSequence"); - assertInstanceOfSucceeds(BaseClass.class, new SubClass()); - } - - @Test - void assertInstanceOfSucceedsSameTypeExceptionValue() { - assertInstanceOfSucceeds(UnsupportedOperationException.class, new UnsupportedOperationException()); - } - - @Test - void assertInstanceOfSucceedsExpectSuperClassOfExceptionValue() { - assertInstanceOfSucceeds(RuntimeException.class, new IllegalArgumentException("is a RuntimeException")); - } - - private void assertInstanceOfSucceeds(Class expectedType, Object actualValue) { - T res = assertInstanceOf(expectedType, actualValue); - assertSame(res, actualValue); - res = assertInstanceOf(expectedType, actualValue, "extra"); - assertSame(res, actualValue); - res = assertInstanceOf(expectedType, actualValue, () -> "extra"); - assertSame(res, actualValue); - } - - private void assertInstanceOfFails(Class expectedType, Object actualValue, String unexpectedSort) { - String valueType = actualValue == null ? "null" : actualValue.getClass().getCanonicalName(); - String expectedMessage = String.format("Unexpected %s, expected: <%s> but was: <%s>", unexpectedSort, - expectedType.getCanonicalName(), valueType); - - assertThrowsWithMessage(expectedMessage, () -> assertInstanceOf(expectedType, actualValue)); - assertThrowsWithMessage("extra ==> " + expectedMessage, - () -> assertInstanceOf(expectedType, actualValue, "extra")); - assertThrowsWithMessage("extra ==> " + expectedMessage, - () -> assertInstanceOf(expectedType, actualValue, () -> "extra")); - } - - private void assertThrowsWithMessage(String expectedMessage, Executable executable) { - assertEquals(expectedMessage, assertThrows(AssertionFailedError.class, executable).getMessage()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java deleted file mode 100644 index 843f2af7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java +++ /dev/null @@ -1,478 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.IterableFactory.listOf; -import static org.junit.jupiter.api.IterableFactory.setOf; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertIterableEqualsAssertionsTests { - - @Test - void assertIterableEqualsEqualToSelf() { - List list = listOf("a", 'b', 1, 2); - assertIterableEquals(list, list); - assertIterableEquals(list, list, "message"); - assertIterableEquals(list, list, () -> "message"); - - Set set = setOf("a", 'b', 1, 2); - assertIterableEquals(set, set); - } - - @Test - void assertIterableEqualsEqualObjectsOfSameType() { - assertIterableEquals(listOf(), listOf()); - assertIterableEquals(listOf("abc"), listOf("abc")); - assertIterableEquals(listOf("abc", 1, 2L, 3D), listOf("abc", 1, 2L, 3D)); - assertIterableEquals(setOf(), setOf()); - assertIterableEquals(setOf("abc"), setOf("abc")); - assertIterableEquals(setOf("abc", 1, 2L, 3D), setOf("abc", 1, 2L, 3D)); - } - - @Test - void assertIterableEqualsNestedIterables() { - assertIterableEquals(listOf(listOf(listOf())), listOf(listOf(listOf()))); - assertIterableEquals(setOf(setOf(setOf())), setOf(setOf(setOf()))); - } - - @Test - void assertIterableEqualsNestedIterablesWithNull() { - assertIterableEquals(listOf(null, listOf(null, listOf(null, null)), null, listOf((List) null)), - listOf(null, listOf(null, listOf(null, null)), null, listOf((List) null))); - assertIterableEquals(setOf(null, setOf(null, setOf(null, null)), null, setOf((Set) null)), - setOf(null, setOf(null, setOf(null, null)), null, setOf((Set) null))); - } - - @Test - void assertIterableEqualsNestedIterablesWithStrings() { - assertIterableEquals(listOf("a", listOf(listOf("b", listOf("c", "d"))), "e"), - listOf("a", listOf(listOf("b", listOf("c", "d"))), "e")); - assertIterableEquals(setOf("a", setOf(setOf("b", setOf("c", "d"))), "e"), - setOf("a", setOf(setOf("b", setOf("c", "d"))), "e")); - } - - @Test - void assertIterableEqualsNestedIterablesWithIntegers() { - assertIterableEquals(listOf(listOf(1), listOf(2), listOf(listOf(3, listOf(4)))), - listOf(listOf(1), listOf(2), listOf(listOf(3, listOf(4))))); - assertIterableEquals(setOf(setOf(1), setOf(2), setOf(setOf(3, setOf(4)))), - setOf(setOf(1), setOf(2), setOf(setOf(3, setOf(4))))); - assertIterableEquals(listOf(listOf(1), listOf(listOf(1))), setOf(setOf(1), setOf(setOf(1)))); - } - - @Test - void assertIterableEqualsNestedIterablesWithDeeplyNestedObject() { - assertIterableEquals(listOf(listOf(listOf(listOf(listOf(listOf(listOf("abc"))))))), - listOf(listOf(listOf(listOf(listOf(listOf(listOf("abc")))))))); - assertIterableEquals(setOf(setOf(setOf(setOf(setOf(setOf(setOf("abc"))))))), - setOf(setOf(setOf(setOf(setOf(setOf(setOf("abc")))))))); - } - - @Test - void assertIterableEqualsNestedIterablesWithNaN() { - assertIterableEquals(listOf(null, listOf(null, Double.NaN, listOf(Float.NaN, null, listOf()))), - listOf(null, listOf(null, Double.NaN, listOf(Float.NaN, null, listOf())))); - assertIterableEquals(setOf(null, setOf(null, Double.NaN, setOf(Float.NaN, null, setOf()))), - setOf(null, setOf(null, Double.NaN, setOf(Float.NaN, null, setOf())))); - } - - @Test - void assertIterableEqualsNestedIterablesWithObjectsOfDifferentTypes() { - assertIterableEquals(listOf(new String("a"), Integer.valueOf(1), listOf(Double.parseDouble("1.1"), "b")), - listOf(new String("a"), Integer.valueOf(1), listOf(Double.parseDouble("1.1"), "b"))); - assertIterableEquals(setOf(new String("a"), Integer.valueOf(1), setOf(Double.parseDouble("1.1"), "b")), - setOf(new String("a"), Integer.valueOf(1), setOf(Double.parseDouble("1.1"), "b"))); - } - - @Test - void assertIterableEqualsNestedIterablesOfMixedSubtypes() { - assertIterableEquals( - listOf(1, 2, listOf(3, setOf(4, 5), setOf(6L), listOf(listOf(setOf(7)))), setOf(8), listOf(setOf(9L))), - listOf(1, 2, listOf(3, setOf(4, 5), setOf(6L), listOf(listOf(setOf(7)))), setOf(8), listOf(setOf(9L)))); - - assertIterableEquals( - listOf("a", setOf('b', 'c'), setOf((int) 'd'), listOf(listOf(listOf("ef"), listOf(listOf("ghi"))))), - setOf("a", listOf('b', 'c'), listOf((int) 'd'), setOf(setOf(setOf("ef"), setOf(setOf("ghi")))))); - } - - @Test - void assertIterableEqualsIterableVsNull() { - try { - assertIterableEquals(null, listOf("a", "b", 1, listOf())); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected iterable was "); - } - - try { - assertIterableEquals(listOf('a', 1, new Object(), 10L), null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual iterable was "); - } - } - - @Test - void assertIterableEqualsNestedIterableVsNull() { - try { - assertIterableEquals(listOf(listOf(), 1, "2", setOf('3', listOf((List) null))), - listOf(listOf(), 1, "2", setOf('3', listOf(listOf("4"))))); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected iterable was at index [3][1][0]"); - } - - try { - assertIterableEquals(setOf(1, 2, listOf(3, listOf("4", setOf(5, setOf(6)))), "7"), - setOf(1, 2, listOf(3, listOf("4", setOf(5, null))), "7")); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "actual iterable was at index [2][1][1][1]"); - } - } - - @Test - void assertIterableEqualsIterableVsNullAndMessage() { - try { - assertIterableEquals(null, listOf('a', "b", 10, 20D), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected iterable was "); - } - - try { - assertIterableEquals(listOf("hello", 42), null, "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual iterable was "); - } - } - - @Test - void assertIterableEqualsNestedIterableVsNullAndMessage() { - try { - assertIterableEquals(listOf(1, listOf(2, 3, listOf(4, 5, listOf((List) null)))), - listOf(1, listOf(2, 3, listOf(4, 5, listOf(listOf(6))))), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected iterable was at index [1][2][2][0]"); - } - - try { - assertIterableEquals(listOf(1, listOf(2, listOf(3, listOf(listOf(4))))), - listOf(1, listOf(2, listOf(3, listOf((List) null)))), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual iterable was at index [1][1][1][0]"); - } - } - - @Test - void assertIterableEqualsIterableVsNullAndMessageSupplier() { - try { - assertIterableEquals(null, setOf(42, "42", listOf(42F), 42D), () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected iterable was "); - } - - try { - assertIterableEquals(listOf(listOf("a"), listOf()), null, () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual iterable was "); - } - } - - @Test - void assertIterableEqualsNestedIterableVsNullAndMessageSupplier() { - try { - assertIterableEquals(listOf("1", "2", "3", listOf("4", listOf((List) null))), - listOf("1", "2", "3", listOf("4", listOf(listOf(5)))), () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "expected iterable was at index [3][1][0]"); - } - - try { - assertIterableEquals(setOf(1, 2, setOf("3", setOf('4', setOf(5, 6, setOf())))), - setOf(1, 2, setOf("3", setOf('4', setOf(5, 6, null)))), () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "actual iterable was at index [2][1][1][2]"); - } - } - - @Test - void assertIterableEqualsIterablesOfDifferentLength() { - try { - assertIterableEquals(listOf('a', "b", 'c'), listOf('a', "b", 'c', 1)); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "iterable lengths differ, expected: <3> but was: <4>"); - } - } - - @Test - void assertIterableEqualsNestedIterablesOfDifferentLength() { - try { - assertIterableEquals(listOf("a", setOf("b", listOf("c", "d", setOf("e", 1, 2, 3)))), - listOf("a", setOf("b", listOf("c", "d", setOf("e", 1, 2, 3, 4, 5))))); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "iterable lengths differ at index [1][1][2], expected: <4> but was: <6>"); - } - - try { - assertIterableEquals(listOf(listOf(listOf(listOf(listOf(listOf(listOf('a'))))))), - listOf(listOf(listOf(listOf(listOf(listOf(listOf('a', 'b')))))))); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "iterable lengths differ at index [0][0][0][0][0][0], expected: <1> but was: <2>"); - } - } - - @Test - void assertIterableEqualsIterablesOfDifferentLengthAndMessage() { - try { - assertIterableEquals(setOf('a', 1), setOf('a', 1, new Object()), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable lengths differ, expected: <2> but was: <3>"); - } - } - - @Test - void assertIterableEqualsNestedIterablesOfDifferentLengthAndMessage() { - try { - assertIterableEquals(listOf('a', 1, listOf(2, 3)), listOf('a', 1, listOf(2, 3, 4, 5)), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable lengths differ at index [2], expected: <2> but was: <4>"); - } - } - - @Test - void assertIterableEqualsIterablesOfDifferentLengthAndMessageSupplier() { - try { - assertIterableEquals(setOf("a", "b", "c"), setOf("a", "b", "c", "d", "e", "f"), () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable lengths differ, expected: <3> but was: <6>"); - } - } - - @Test - void assertIterableEqualsNestedIterablesOfDifferentLengthAndMessageSupplier() { - try { - assertIterableEquals(listOf("a", setOf(1, 2, 3, listOf(4.0, 5.1, 6.1), 7)), - listOf("a", setOf(1, 2, 3, listOf(4.0, 5.1, 6.1, 7.0), 8)), () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable lengths differ at index [1][3], expected: <3> but was: <4>"); - } - } - - @Test - void assertIterableEqualsDifferentIterables() { - try { - assertIterableEquals(listOf(1L, "2", '3', 4, 5D), listOf(1L, "2", '9', 4, 5D)); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "iterable contents differ at index [2], expected: <3> but was: <9>"); - } - - try { - assertIterableEquals(listOf("a", 10, 11, 12, Double.NaN), listOf("a", 10, 11, 12, 13.55D)); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "iterable contents differ at index [4], expected: but was: <13.55>"); - } - } - - @Test - void assertIterableEqualsDifferentNestedIterables() { - try { - assertIterableEquals(listOf(1, 2, listOf(3, listOf(4, listOf(false, true)))), - listOf(1, 2, listOf(3, listOf(4, listOf(true, false))))); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, - "iterable contents differ at index [2][1][1][0], expected: but was: "); - } - - List differentElement = listOf(); - try { - assertIterableEquals(listOf(1, 2, 3, listOf(listOf(4, listOf(5)))), - listOf(1, 2, 3, listOf(listOf(4, listOf(differentElement))))); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, - "iterable contents differ at index [3][0][1][0], expected: <5> but was: <" + differentElement + ">"); - } - } - - @Test - void assertIterableEqualsDifferentIterablesAndMessage() { - try { - assertIterableEquals(listOf(1.1D, 2L, "3"), listOf(1D, 2L, "3"), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable contents differ at index [0], expected: <1.1> but was: <1.0>"); - } - } - - @Test - void assertIterableEqualsDifferentNestedIterablesAndMessage() { - try { - assertIterableEquals(listOf(9, 8, '6', listOf(5, 4, "3", listOf("2", '1'))), - listOf(9, 8, '6', listOf(5, 4, "3", listOf("99", '1'))), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>"); - } - - try { - assertIterableEquals(listOf(9, 8, '6', listOf(5, 4, "3", listOf("2", "1"))), - listOf(9, 8, '6', listOf(5, 4, "3", listOf("99", "1"))), "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>"); - } - } - - @Test - void assertIterableEqualsDifferentIterablesAndMessageSupplier() { - try { - assertIterableEquals(setOf("one", 1L, Double.MIN_VALUE, "abc"), setOf("one", 1L, 42.42, "abc"), - () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable contents differ at index [2], expected: <4.9E-324> but was: <42.42>"); - } - } - - @Test - void assertIterableEqualsDifferentNestedIterablesAndMessageSupplier() { - try { - assertIterableEquals(setOf("one", 1L, setOf("a", 'b', setOf(1, setOf(2, 3))), "abc"), - setOf("one", 1L, setOf("a", 'b', setOf(1, setOf(2, 4))), "abc"), () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable contents differ at index [2][2][1][1], expected: <3> but was: <4>"); - } - - try { - assertIterableEquals(listOf("j", listOf("a"), setOf(42), "ab", setOf(1, listOf(3))), - listOf("j", listOf("a"), setOf(42), "ab", setOf(1, listOf(5))), () -> "message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "message"); - assertMessageEndsWith(ex, "iterable contents differ at index [4][1][0], expected: <3> but was: <5>"); - } - } - - @Test - // https://github.com/junit-team/junit5/issues/2157 - void assertIterableEqualsWithListOfPath() { - var expected = listOf(Path.of("1")); - var actual = listOf(Path.of("1")); - assertDoesNotThrow(() -> assertIterableEquals(expected, actual)); - } - - @Test - void assertIterableEqualsThrowsStackOverflowErrorForInterlockedRecursiveStructures() { - var expected = new ArrayList<>(); - var actual = new ArrayList<>(); - actual.add(expected); - expected.add(actual); - assertThrows(StackOverflowError.class, () -> assertIterableEquals(expected, actual)); - } - - @Test - // https://github.com/junit-team/junit5/issues/2915 - void assertIterableEqualsWithDifferentListOfPath() { - try { - var expected = listOf(Path.of("1").resolve("2")); - var actual = listOf(Path.of("1").resolve("3")); - assertIterableEquals(expected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "iterable contents differ at index [0][1], expected: <2> but was: <3>"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java deleted file mode 100644 index 97263cd6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertLinesMatch.isFastForwardLine; -import static org.junit.jupiter.api.AssertLinesMatch.parseFastForwardLimit; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Random; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.junit.platform.commons.PreconditionViolationException; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertLinesMatchAssertionsTests { - - @Test - void assertLinesMatchEmptyLists() { - assertLinesMatch(Collections.emptyList(), new ArrayList<>()); - } - - @Test - void assertLinesMatchSameListInstance() { - List list = List.of("first line", "second line", "third line", "last line"); - assertLinesMatch(list, list); - } - - @Test - void assertLinesMatchPlainEqualLists() { - List expected = List.of("first line", "second line", "third line", "last line"); - List actual = List.of("first line", "second line", "third line", "last line"); - assertLinesMatch(expected, actual); - } - - @Test - void assertLinesMatchUsingRegexPatterns() { - List expected = List.of("^first.+line", "second\\s*line", "th.rd l.ne", "last line$"); - List actual = List.of("first line", "second line", "third line", "last line"); - assertLinesMatch(expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerAtEndOfExpectedLines() { - List expected = List.of("first line", ">> ignore all following lines >>"); - List actual = List.of("first line", "I", "II", "III", "IV", "V", "VI", "last line"); - assertLinesMatch(expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarker() { - List expected = List.of("first line", ">> skip lines until next matches >>", "V", "last line"); - List actual = List.of("first line", "I", "II", "III", "IV", "V", "last line"); - assertLinesMatch(expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerWithLimit1() { - List expected = List.of("first line", ">> 1 >>", "last line"); - List actual = List.of("first line", "skipped", "last line"); - assertLinesMatch(expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerWithLimit3() { - List expected = Collections.singletonList(">> 3 >>"); - List actual = List.of("first line", "skipped", "last line"); - assertLinesMatch(expected, actual); - } - - @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) - void assertLinesMatchWithNullFails() { - assertThrows(PreconditionViolationException.class, () -> assertLinesMatch((List) null, (List) null)); - assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(null, Collections.emptyList())); - assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(Collections.emptyList(), null)); - } - - @Test - void assertLinesMatchWithNullElementsFails() { - var list = List.of("1", "2", "3"); - var withNullElement = Arrays.asList("1", null, "3"); // List.of() doesn't permit null values. - assertDoesNotThrow(() -> assertLinesMatch(withNullElement, withNullElement)); - var e1 = assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(withNullElement, list)); - assertEquals("expected line must not be null", e1.getMessage()); - var e2 = assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(list, withNullElement)); - assertEquals("actual line must not be null", e2.getMessage()); - } - - private void assertError(AssertionFailedError error, String expectedMessage, List expectedLines, - List actualLines) { - assertEquals(expectedMessage, error.getMessage()); - assertEquals(String.join(System.lineSeparator(), expectedLines), error.getExpected().getStringRepresentation()); - assertEquals(String.join(System.lineSeparator(), actualLines), error.getActual().getStringRepresentation()); - } - - @Test - void assertLinesMatchMoreExpectedThanActualAvailableFails() { - var expected = List.of("first line", "second line", "third line"); - var actual = List.of("first line", "third line"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - assertError(error, "expected 3 lines, but only got 2", expected, actual); - } - - @Test - void assertLinesMatchFailsWithDescriptiveErrorMessage() { - var expected = List.of("first line", "second line", "third line"); - var actual = List.of("first line", "sec0nd line", "third line"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - var expectedMessage = String.join(System.lineSeparator(), List.of( // - "expected line #2 doesn't match actual line #2", // - "\texpected: `second line`", // - "\t actual: `sec0nd line`")); - assertError(error, expectedMessage, expected, actual); - } - - @Test - void assertLinesMatchMoreActualLinesThenExpectedFails() { - var expected = List.of("first line", "second line", "third line"); - var actual = List.of("first line", "second line", "third line", "last line"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - assertError(error, "more actual lines than expected: 1", expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerWithTooLowLimitFails() { - var expected = List.of("first line", ">> 1 >>"); - var actual = List.of("first line", "skipped", "last line"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - assertError(error, "terminal fast-forward(1) error: fast-forward(2) expected", expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerWithTooHighLimitFails() { - var expected = List.of("first line", ">> 100 >>"); - var actual = List.of("first line", "skipped", "last line"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - assertError(error, "terminal fast-forward(100) error: fast-forward(2) expected", expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerWithTooHighLimitAndFollowingLineFails() { - /* - * It is important here that the line counts are expected <= actual, that the - * fast-forward exceeds the available actual lines and that it is not a - * terminal fast-forward. - */ - var expected = List.of("first line", ">> 3 >>", "not present"); - var actual = List.of("first line", "first skipped", "second skipped"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - assertError(error, "fast-forward(3) error: not enough actual lines remaining (2)", expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerWithoutMatchingNextLineFails() { - var expected = List.of("first line", ">> fails, because next line is >>", "not present"); - var actual = List.of("first line", "skipped", "last line"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - assertError(error, "fast-forward(∞) didn't find: `not present`", expected, actual); - } - - @Test - void assertLinesMatchUsingFastForwardMarkerWithExtraExpectLineFails() { - var expected = List.of("first line", ">> fails, because final line is missing >>", "last line", "not present"); - var actual = List.of("first line", "first skipped", "second skipped", "last line"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); - assertError(error, "expected line #4:`not present` not found - actual lines depleted", expected, actual); - } - - @Test - void assertLinesMatchIsFastForwardLine() { - assertAll("valid fast-forward lines", // - () -> assertTrue(isFastForwardLine(">>>>")), () -> assertTrue(isFastForwardLine(">> >>")), - () -> assertTrue(isFastForwardLine(">> stacktrace >>")), - () -> assertTrue(isFastForwardLine(">> single line, non Integer.parse()-able comment >>")), - () -> assertTrue(isFastForwardLine(">>9>>")), () -> assertTrue(isFastForwardLine(">> 9 >>")), - () -> assertTrue(isFastForwardLine(">> -9 >>")), () -> assertTrue(isFastForwardLine(" >> 9 >> ")), - () -> assertTrue(isFastForwardLine(" >> 9 >> "))); - } - - @Test - void assertLinesMatchParseFastForwardLimit() { - assertAll("valid fast-forward limits", // - () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">>>>")), - () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> >>")), - () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> stacktrace >>")), - () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> non Integer.parse()-able comment >>")), - () -> assertEquals(9, parseFastForwardLimit(">>9>>")), - () -> assertEquals(9, parseFastForwardLimit(">> 9 >>")), - () -> assertEquals(9, parseFastForwardLimit(" >> 9 >> ")), - () -> assertEquals(9, parseFastForwardLimit(" >> 9 >> "))); - Throwable error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>0>>")); - assertMessageEquals(error, "fast-forward(0) limit must be greater than zero"); - error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>-1>>")); - assertMessageEquals(error, "fast-forward(-1) limit must be greater than zero"); - error = assertThrows(PreconditionViolationException.class, () -> parseFastForwardLimit(">>-2147483648>>")); - assertMessageEquals(error, "fast-forward(-2147483648) limit must be greater than zero"); - } - - @Test - void assertLinesMatchMatches() { - Random random = new Random(); - assertAll("do match", // - () -> assertTrue( - AssertLinesMatch.matches("duration: [\\d]+ ms", "duration: " + random.nextInt(1000) + " ms")), - () -> assertTrue(AssertLinesMatch.matches("123", "123")), - () -> assertTrue(AssertLinesMatch.matches(".*", "123")), - () -> assertTrue(AssertLinesMatch.matches("\\d+", "123"))); - assertAll("don't match", // - () -> assertFalse( - AssertLinesMatch.matches("duration: [\\d]+ ms", "duration: " + random.nextGaussian() + " ms")), - () -> assertFalse(AssertLinesMatch.matches("12", "123")), - () -> assertFalse(AssertLinesMatch.matches("..+", "1")), - () -> assertFalse(AssertLinesMatch.matches("\\d\\d+", "1"))); - } - - @Test - void largeListsThatDoNotMatchAreTruncated() { - var expected = IntStream.range(1, 999).boxed().map(Object::toString).collect(Collectors.toList()); - var actual = IntStream.range(0, 1000).boxed().map(Object::toString).collect(Collectors.toList()); - var error = assertThrows(AssertionFailedError.class, - () -> assertLinesMatch(expected, actual, "custom message")); - var expectedMessage = String.join(System.lineSeparator(), List.of( // - "custom message ==> expected line #1 doesn't match actual line #1", // - "\texpected: `1`", // - "\t actual: `0`")); - assertError(error, expectedMessage, expected, actual); - } - - /** - * @since 5.5 - */ - @Nested - class WithCustomFailureMessage { - @Test - void simpleStringMessage() { - String message = "XXX"; - var expected = List.of("a", "b", "c"); - var actual = List.of("a", "d", "c"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual, message)); - var expectedMessage = String.join(System.lineSeparator(), List.of( // - message + " ==> expected line #2 doesn't match actual line #2", // - "\texpected: `b`", // - "\t actual: `d`")); - assertError(error, expectedMessage, expected, actual); - } - - @Test - void stringSupplierWithMultiLineMessage() { - var message = "XXX\nYYY"; - Supplier supplier = () -> message; - var expected = List.of("a", "b", "c"); - var actual = List.of("a", "d", "c"); - var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual, supplier)); - var expectedMessage = String.join(System.lineSeparator(), List.of( // - message + " ==> expected line #2 doesn't match actual line #2", // - "\texpected: `b`", // - "\t actual: `d`")); - assertError(error, expectedMessage, expected, actual); - } - } - - @Nested - class WithStreamsOfStrings { - @Test - void assertLinesMatchEmptyStreams() { - assertLinesMatch(Stream.empty(), Stream.empty()); - } - - @Test - void assertLinesMatchSameListInstance() { - Stream stream = Stream.of("first line", "second line", "third line", "last line"); - assertLinesMatch(stream, stream); - } - - @Test - void assertLinesMatchPlainEqualLists() { - var expected = """ - first line - second line - third line - last line - """; - var actual = """ - first line - second line - third line - last line - """; - assertLinesMatch(expected.lines(), actual.lines()); - } - - @Test - void assertLinesMatchUsingRegexPatterns() { - var expected = """ - ^first.+line - second\\s*line - th.rd l.ne - last line$ - """; - var actual = """ - first line - second line - third line - last line - """; - assertLinesMatch(expected.lines(), actual.lines()); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java deleted file mode 100644 index 1606c6be..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java +++ /dev/null @@ -1,768 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertNotEqualsAssertionsTests { - - @Nested - class AssertNotEqualsByte { - - @Test - void assertNotEqualsByte() { - byte unexpected = 1; - byte actual = 2; - assertNotEquals(unexpected, actual); - assertNotEquals(unexpected, actual, "message"); - assertNotEquals(unexpected, actual, () -> "message"); - } - - @Test - void withEqualValues() { - byte unexpected = 1; - byte actual = 1; - try { - assertNotEquals(unexpected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessage() { - byte unexpected = 1; - byte actual = 1; - try { - assertNotEquals(unexpected, actual, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - byte unexpected = 1; - byte actual = 1; - try { - assertNotEquals(unexpected, actual, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - } - - @Nested - class AssertNotEqualsShort { - - @Test - void assertNotEqualsShort() { - short unexpected = 1; - short actual = 2; - assertNotEquals(unexpected, actual); - assertNotEquals(unexpected, actual, "message"); - assertNotEquals(unexpected, actual, () -> "message"); - } - - @Test - void withEqualValues() { - short unexpected = 1; - short actual = 1; - try { - assertNotEquals(unexpected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessage() { - short unexpected = 1; - short actual = 1; - try { - assertNotEquals(unexpected, actual, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - short unexpected = 1; - short actual = 1; - try { - assertNotEquals(unexpected, actual, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - } - - @Nested - class AssertNotEqualsChar { - - @Test - void assertNotEqualsChar() { - char unexpected = 'a'; - char actual = 'b'; - assertNotEquals(unexpected, actual); - assertNotEquals(unexpected, actual, "message"); - assertNotEquals(unexpected, actual, () -> "message"); - } - - @Test - void withEqualValues() { - char unexpected = 'a'; - char actual = 'a'; - try { - assertNotEquals(unexpected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: "); - } - } - - @Test - void withEqualValuesWithMessage() { - char unexpected = 'a'; - char actual = 'a'; - try { - assertNotEquals(unexpected, actual, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: "); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - char unexpected = 'a'; - char actual = 'a'; - try { - assertNotEquals(unexpected, actual, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: "); - } - } - - } - - @Nested - class AssertNotEqualsInt { - - @Test - void assertNotEqualsInt() { - int unexpected = 1; - int actual = 2; - assertNotEquals(unexpected, actual); - assertNotEquals(unexpected, actual, "message"); - assertNotEquals(unexpected, actual, () -> "message"); - } - - @Test - void withEqualValues() { - int unexpected = 1; - int actual = 1; - try { - assertNotEquals(unexpected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessage() { - int unexpected = 1; - int actual = 1; - try { - assertNotEquals(unexpected, actual, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - int unexpected = 1; - int actual = 1; - try { - assertNotEquals(unexpected, actual, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - } - - @Nested - class AssertNotEqualsLong { - - @Test - void assertNotEqualsLong() { - long unexpected = 1L; - long actual = 2L; - assertNotEquals(unexpected, actual); - assertNotEquals(unexpected, actual, "message"); - assertNotEquals(unexpected, actual, () -> "message"); - } - - @Test - void withEqualValues() { - long unexpected = 1L; - long actual = 1L; - try { - assertNotEquals(unexpected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessage() { - long unexpected = 1L; - long actual = 1L; - try { - assertNotEquals(unexpected, actual, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - long unexpected = 1L; - long actual = 1L; - try { - assertNotEquals(unexpected, actual, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1>"); - } - } - - } - - @Nested - class AssertNotEqualsFloatWithoutDelta { - - @Test - void assertNotEqualsFloat() { - float unexpected = 1.0f; - float actual = 2.0f; - assertNotEquals(unexpected, actual); - assertNotEquals(unexpected, actual, "message"); - assertNotEquals(unexpected, actual, () -> "message"); - } - - @Test - void assertNotEqualsForTwoNaNFloat() { - try { - assertNotEquals(Float.NaN, Float.NaN); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: "); - } - } - - @Test - void assertNotEqualsForPositiveInfinityFloat() { - try { - assertNotEquals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: "); - } - } - - @Test - void assertNotEqualsForNegativeInfinityFloat() { - try { - assertNotEquals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <-Infinity>"); - } - } - - @Test - void withEqualValues() { - float unexpected = 1.0f; - float actual = 1.0f; - try { - assertNotEquals(unexpected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1.0>"); - } - } - - @Test - void withEqualValuesWithMessage() { - float unexpected = 1.0f; - float actual = 1.0f; - try { - assertNotEquals(unexpected, actual, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - float unexpected = 1.0f; - float actual = 1.0f; - try { - assertNotEquals(unexpected, actual, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); - } - } - - } - - @Nested - class AssertNotEqualsFloatWithDelta { - - @Test - void assertNotEqualsFloat() { - assertNotEquals(1.0f, 1.5f, 0.4f); - assertNotEquals(1.0f, 1.5f, 0.4f, "message"); - assertNotEquals(1.0f, 1.5f, 0.4f, () -> "message"); - } - - @Test - void withEqualValues() { - float unexpected = 1.0f; - float actual = 1.5f; - float delta = 0.5f; - try { - assertNotEquals(unexpected, actual, delta); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1.5>"); - } - } - - @Test - void withEqualValuesWithMessage() { - float unexpected = 1.0f; - float actual = 1.5f; - float delta = 0.5f; - try { - assertNotEquals(unexpected, actual, delta, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - float unexpected = 1.0f; - float actual = 1.5f; - float delta = 0.5f; - try { - assertNotEquals(unexpected, actual, delta, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); - } - } - - } - - @Nested - class AssertNotEqualsDoubleWithoutDelta { - - @Test - void assertNotEqualsDouble() { - double unexpected = 1.0d; - double actual = 2.0d; - assertNotEquals(unexpected, actual); - assertNotEquals(unexpected, actual, "message"); - assertNotEquals(unexpected, actual, () -> "message"); - } - - @Test - void assertNotEqualsForTwoNaNDouble() { - try { - assertNotEquals(Double.NaN, Double.NaN); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: "); - } - } - - @Test - void withEqualValues() { - double unexpected = 1.0d; - double actual = 1.0d; - try { - assertNotEquals(unexpected, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1.0>"); - } - } - - @Test - void withEqualValuesWithMessage() { - double unexpected = 1.0d; - double actual = 1.0d; - try { - assertNotEquals(unexpected, actual, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - double unexpected = 1.0d; - double actual = 1.0d; - try { - assertNotEquals(unexpected, actual, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); - } - } - - } - - @Nested - class AssertNotEqualsDoubleWithDelta { - - @Test - void assertNotEqualsDouble() { - assertNotEquals(1.0d, 1.5d, 0.4d); - assertNotEquals(1.0d, 1.5d, 0.4d, "message"); - assertNotEquals(1.0d, 1.5d, 0.4d, () -> "message"); - } - - @Test - void withEqualValues() { - double unexpected = 1.0d; - double actual = 1.5d; - double delta = 0.5d; - try { - assertNotEquals(unexpected, actual, delta); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not equal but was: <1.5>"); - } - } - - @Test - void withEqualValuesWithMessage() { - double unexpected = 1.0d; - double actual = 1.5d; - double delta = 0.5d; - try { - assertNotEquals(unexpected, actual, delta, "custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); - } - } - - @Test - void withEqualValuesWithMessageSupplier() { - double unexpected = 1.0d; - double actual = 1.5d; - double delta = 0.5d; - try { - assertNotEquals(unexpected, actual, delta, () -> "custom message from supplier"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "custom message from supplier"); - assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); - } - } - - } - - @Nested - class AssertNotEqualsObject { - - @Test - void assertNotEqualsWithNullVsObject() { - assertNotEquals(null, "foo"); - } - - @Test - void assertNotEqualsWithObjectVsNull() { - assertNotEquals("foo", null); - } - - @Test - void assertNotEqualsWithDifferentObjects() { - assertNotEquals(new Object(), new Object()); - assertNotEquals(new Object(), new Object(), "message"); - assertNotEquals(new Object(), new Object(), () -> "message"); - } - - @Test - void assertNotEqualsWithNullVsObjectAndMessageSupplier() { - assertNotEquals(null, "foo", () -> "test"); - } - - @Test - void assertNotEqualsWithEquivalentStringsAndMessage() { - try { - assertNotEquals(new String("foo"), new String("foo"), "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageEndsWith(ex, "expected: not equal but was: "); - } - } - - @Test - void assertNotEqualsWithEquivalentStringsAndMessageSupplier() { - try { - assertNotEquals(new String("foo"), new String("foo"), () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageEndsWith(ex, "expected: not equal but was: "); - } - } - - @Test - void assertNotEqualsInvokesEqualsMethodForIdenticalObjects() { - Object obj = new EqualsThrowsExceptionClass(); - assertThrows(NumberFormatException.class, () -> assertNotEquals(obj, obj)); - } - - } - - // ------------------------------------------------------------------------- - - @Nested - class MixedBoxedAndUnboxedPrimitivesTests { - - @Test - void bytes() { - byte primitive = (byte) 42; - Byte wrapper = Byte.valueOf("99"); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - } - - @Test - void shorts() { - short primitive = (short) 42; - Short wrapper = Short.valueOf("99"); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - } - - @Test - void integers() { - int primitive = 42; - Integer wrapper = Integer.valueOf("99"); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - } - - @Test - void longs() { - long primitive = 42L; - Long wrapper = Long.valueOf("99"); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - } - - @Test - void floats() { - float primitive = 42.0f; - Float wrapper = Float.valueOf("99.0"); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, 0.0f); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, 0.0f, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - assertNotEquals(primitive, wrapper, 0.0f, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, 0.0f); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, 0.0f, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - assertNotEquals(wrapper, primitive, 0.0f, () -> "message"); - } - - @Test - void doubles() { - double primitive = 42.0d; - Double wrapper = Double.valueOf("99.0"); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, 0.0d); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, 0.0d, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - assertNotEquals(primitive, wrapper, 0.0d, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, 0.0d); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, 0.0d, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - assertNotEquals(wrapper, primitive, 0.0d, () -> "message"); - } - - @Test - void booleans() { - boolean primitive = true; - Boolean wrapper = Boolean.valueOf("false"); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - } - - @Test - void chars() { - char primitive = 'a'; - Character wrapper = Character.valueOf('z'); - - assertNotEquals(primitive, wrapper); - assertNotEquals(primitive, wrapper, "message"); - assertNotEquals(primitive, wrapper, () -> "message"); - - assertNotEquals(wrapper, primitive); - assertNotEquals(wrapper, primitive, "message"); - assertNotEquals(wrapper, primitive, () -> "message"); - } - - } - - // ------------------------------------------------------------------------- - - private static class EqualsThrowsExceptionClass { - - @Override - public boolean equals(Object obj) { - throw new NumberFormatException(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java deleted file mode 100644 index bf234197..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertNotNullAssertionsTests { - - @Test - void assertNotNullWithNonNullObject() { - assertNotNull("foo"); - assertNotNull("foo", "message"); - assertNotNull("foo", () -> "message"); - } - - @Test - void assertNotNullWithNonNullObjectAndMessageSupplier() { - assertNotNull("foo", () -> "should not fail"); - } - - @Test - @SuppressWarnings("unused") - void assertNotNullWithNull() { - try { - assertNotNull(null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not "); - } - } - - @Test - @SuppressWarnings("unused") - void assertNotNullWithNullAndMessageSupplier() { - try { - assertNotNull(null, () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageEndsWith(ex, "expected: not "); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java deleted file mode 100644 index 4429f33b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertNotSame; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertNotSameAssertionsTests { - - @Test - void assertNotSameWithDifferentObjects() { - assertNotSame(new Object(), new Object()); - assertNotSame(new Object(), new Object(), "message"); - assertNotSame(new Object(), new Object(), () -> "message"); - } - - @Test - void assertNotSameWithDifferentObjectsAndMessageSupplier() { - assertNotSame(new Object(), new Object(), () -> "should not fail"); - } - - @Test - void assertNotSameWithObjectVsNull() { - assertNotSame(new Object(), null); - } - - @Test - void assertNotSameWithNullVsObject() { - assertNotSame(null, new Object()); - } - - @Test - void assertNotSameWithTwoNulls() { - try { - assertNotSame(null, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: not same but was: "); - } - } - - @Test - void assertNotSameWithSameObjectAndMessage() { - try { - Object foo = new Object(); - assertNotSame(foo, foo, "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageContains(ex, "expected: not same but was: "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageContains(ex, "expected: not same but was: "message"); - } - - @Test - void assertNullWithNullAndMessageSupplier() { - assertNull(null, () -> "test"); - } - - @Test - @SuppressWarnings("unused") - void assertNullWithNonNullObject() { - try { - assertNull("foo"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, null, "foo"); - } - } - - @Test - void assertNullWithNonNullObjectWithNullStringReturnedFromToString() { - assertNullWithNonNullObjectWithNullStringReturnedFromToString(null); - } - - @Test - void assertNullWithNonNullObjectWithNullStringReturnedFromToStringAndMessageSupplier() { - assertNullWithNonNullObjectWithNullStringReturnedFromToString(() -> "boom"); - } - - @SuppressWarnings("unused") - private void assertNullWithNonNullObjectWithNullStringReturnedFromToString(Supplier messageSupplier) { - String actual = "null"; - try { - if (messageSupplier == null) { - assertNull(actual); - } - else { - assertNull(actual, messageSupplier); - } - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Should look something like: - // expected: but was: java.lang.String@264b3504 - String prefix = (messageSupplier != null ? messageSupplier.get() + " ==> " : ""); - assertMessageMatches(ex, prefix + "expected: but was: java\\.lang\\.String@.+"); - assertExpectedAndActualValues(ex, null, actual); - } - } - - @Test - void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString() { - assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(null); - } - - @Test - void assertNullWithNonNullObjectWithNullReferenceReturnedFromToStringAndMessageSupplier() { - assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(() -> "boom"); - } - - @SuppressWarnings("unused") - private void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(Supplier messageSupplier) { - Object actual = new NullToString(); - try { - if (messageSupplier == null) { - assertNull(actual); - } - else { - assertNull(actual, messageSupplier); - } - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Should look something like: - // expected: but was: org.junit.jupiter.api.AssertNullAssertionsTests$NullToString@4e7912d8 - String prefix = (messageSupplier != null ? messageSupplier.get() + " ==> " : ""); - assertMessageMatches(ex, prefix - + "expected: but was: org\\.junit\\.jupiter\\.api\\.AssertNullAssertionsTests\\$NullToString@.+"); - assertExpectedAndActualValues(ex, null, actual); - } - } - - @Test - @SuppressWarnings("unused") - void assertNullWithNonNullObjectAndMessage() { - try { - assertNull("foo", "a message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "a message ==> expected: but was: "); - assertExpectedAndActualValues(ex, null, "foo"); - } - } - - @Test - @SuppressWarnings("unused") - void assertNullWithNonNullObjectAndMessageSupplier() { - try { - assertNull("foo", () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, null, "foo"); - } - } - - private static class NullToString { - - @Override - public String toString() { - return null; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java deleted file mode 100644 index e9ab08de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertSame; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertSameAssertionsTests { - - @Test - void assertSameWithTwoNulls() { - assertSame(null, null); - assertSame(null, null, () -> "should not fail"); - } - - @Test - void assertSameWithSameObject() { - Object foo = new Object(); - assertSame(foo, foo); - assertSame(foo, foo, "message"); - assertSame(foo, foo, () -> "message"); - } - - @Test - void assertSameWithObjectVsNull() { - Object expected = new Object(); - try { - assertSame(expected, null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageContains(ex, "expected: "); - assertExpectedAndActualValues(ex, expected, null); - } - } - - @Test - void assertSameWithNullVsObject() { - Object actual = new Object(); - try { - assertSame(null, actual); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "test"); - assertMessageContains(ex, "expected: java.lang.String@"); - assertMessageContains(ex, "but was: java.lang.String@"); - assertExpectedAndActualValues(ex, expected, actual); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java deleted file mode 100644 index 7a807db6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.IOException; -import java.io.Serial; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -import org.junit.jupiter.api.function.Executable; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -@SuppressWarnings("ExcessiveLambdaUsage") -class AssertThrowsAssertionsTests { - - private static final Executable nix = () -> { - }; - - @Test - void assertThrowsWithMethodReferenceForNonVoidReturnType() { - FutureTask future = new FutureTask<>(() -> { - throw new RuntimeException("boom"); - }); - future.run(); - - ExecutionException exception = assertThrows(ExecutionException.class, future::get); - assertEquals("boom", exception.getCause().getMessage()); - } - - @Test - void assertThrowsWithMethodReferenceForVoidReturnType() { - var object = new Object(); - IllegalMonitorStateException exception; - - exception = assertThrows(IllegalMonitorStateException.class, object::notify); - assertNotNull(exception); - - // Note that Object.wait(...) is an overloaded method with a void return type - exception = assertThrows(IllegalMonitorStateException.class, object::wait); - assertNotNull(exception); - } - - @Test - void assertThrowsWithExecutableThatThrowsThrowable() { - EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { - throw new EnigmaThrowable(); - }); - assertNotNull(enigmaThrowable); - } - - @Test - void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { - EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { - throw new EnigmaThrowable(); - }, "message"); - assertNotNull(enigmaThrowable); - } - - @Test - void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { - EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { - throw new EnigmaThrowable(); - }, () -> "message"); - assertNotNull(enigmaThrowable); - } - - @Test - void assertThrowsWithExecutableThatThrowsCheckedException() { - IOException exception = assertThrows(IOException.class, () -> { - throw new IOException(); - }); - assertNotNull(exception); - } - - @Test - void assertThrowsWithExecutableThatThrowsRuntimeException() { - IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, () -> { - throw new IllegalStateException(); - }); - assertNotNull(illegalStateException); - } - - @Test - void assertThrowsWithExecutableThatThrowsError() { - StackOverflowError stackOverflowError = assertThrows(StackOverflowError.class, - AssertionTestUtils::recurseIndefinitely); - assertNotNull(stackOverflowError); - } - - @Test - void assertThrowsWithExecutableThatDoesNotThrowAnException() { - try { - assertThrows(IllegalStateException.class, nix); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Expected java.lang.IllegalStateException to be thrown, but nothing was thrown."); - } - } - - @Test - void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageString() { - try { - assertThrows(IOException.class, nix, "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionError ex) { - assertMessageEquals(ex, - "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); - } - } - - @Test - void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() { - try { - assertThrows(IOException.class, nix, () -> "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionError ex) { - assertMessageEquals(ex, - "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { - try { - assertThrows(IllegalStateException.class, () -> { - throw new NumberFormatException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { - try { - assertThrows(IllegalStateException.class, () -> { - throw new NumberFormatException(); - }, "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Should look something like this: - // Custom message ==> Unexpected exception type thrown, expected: but was: - assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { - try { - assertThrows(IllegalStateException.class, () -> { - throw new NumberFormatException(); - }, () -> "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Should look something like this: - // Custom message ==> Unexpected exception type thrown, expected: but was: - assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); - } - } - - @Test - @SuppressWarnings("serial") - void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { - try { - assertThrows(IllegalStateException.class, () -> { - throw new NumberFormatException() { - }; - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - // As of the time of this writing, the class name of the above anonymous inner - // class is org.junit.jupiter.api.AssertionsAssertThrowsTests$2; however, hard - // coding "$2" is fragile. So we just check for the presence of the "$" - // appended to this class's name. - assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); - assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { - try { - assertThrows(IllegalStateException.class, () -> { - throw new LocalException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - // The following verifies that the canonical name is used (i.e., "." instead of "$"). - assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); - assertThat(ex).hasCauseInstanceOf(LocalException.class); - } - } - - @Test - @SuppressWarnings("unchecked") - void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoader() throws Exception { - try (EnigmaClassLoader enigmaClassLoader = new EnigmaClassLoader()) { - - // Load expected exception type from different class loader - Class enigmaThrowableClass = (Class) enigmaClassLoader.loadClass( - EnigmaThrowable.class.getName()); - - try { - assertThrows(enigmaThrowableClass, () -> { - throw new EnigmaThrowable(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Example Output: - // - // Unexpected exception type thrown, - // expected: - // but was: - - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - // The presence of the "@" sign is sufficient to indicate that the hash was - // generated to disambiguate between the two identical class names. - assertMessageContains(ex, "expected: loadClass(String name) throws ClassNotFoundException { - return (EnigmaThrowable.class.getName().equals(name) ? findClass(name) : super.loadClass(name)); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java deleted file mode 100644 index 79c1f328..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrowsExactly; - -import java.io.IOException; -import java.io.Serial; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -import org.junit.jupiter.api.function.Executable; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.8 - */ -@SuppressWarnings("ExcessiveLambdaUsage") -class AssertThrowsExactlyAssertionsTests { - - private static final Executable nix = () -> { - }; - - @Test - void assertThrowsExactlyTheSpecifiedExceptionClass() { - var actual = assertThrowsExactly(EnigmaThrowable.class, () -> { - throw new EnigmaThrowable(); - }); - assertNotNull(actual); - } - - @Test - void assertThrowsExactlyWithTheExpectedChildException() { - try { - assertThrowsExactly(RuntimeException.class, () -> { - throw new Exception(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseExactlyInstanceOf(Exception.class); - } - } - - @Test - void assertThrowsExactlyWithTheExpectedParentException() { - try { - assertThrowsExactly(RuntimeException.class, () -> { - throw new NumberFormatException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); - } - } - - @Test - void assertThrowsWithMethodReferenceForNonVoidReturnType() { - FutureTask future = new FutureTask<>(() -> { - throw new RuntimeException("boom"); - }); - future.run(); - - ExecutionException exception = assertThrowsExactly(ExecutionException.class, future::get); - assertEquals("boom", exception.getCause().getMessage()); - } - - @Test - void assertThrowsWithMethodReferenceForVoidReturnType() { - var object = new Object(); - IllegalMonitorStateException exception; - - exception = assertThrowsExactly(IllegalMonitorStateException.class, object::notify); - assertNotNull(exception); - - // Note that Object.wait(...) is an overloaded method with a void return type - exception = assertThrowsExactly(IllegalMonitorStateException.class, object::wait); - assertNotNull(exception); - } - - @Test - void assertThrowsWithExecutableThatThrowsThrowable() { - EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { - throw new EnigmaThrowable(); - }); - assertNotNull(enigmaThrowable); - } - - @Test - void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { - EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { - throw new EnigmaThrowable(); - }, "message"); - assertNotNull(enigmaThrowable); - } - - @Test - void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { - EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { - throw new EnigmaThrowable(); - }, () -> "message"); - assertNotNull(enigmaThrowable); - } - - @Test - void assertThrowsWithExecutableThatThrowsCheckedException() { - IOException exception = assertThrowsExactly(IOException.class, () -> { - throw new IOException(); - }); - assertNotNull(exception); - } - - @Test - void assertThrowsWithExecutableThatThrowsRuntimeException() { - IllegalStateException illegalStateException = assertThrowsExactly(IllegalStateException.class, () -> { - throw new IllegalStateException(); - }); - assertNotNull(illegalStateException); - } - - @Test - void assertThrowsWithExecutableThatThrowsError() { - StackOverflowError stackOverflowError = assertThrowsExactly(StackOverflowError.class, - AssertionTestUtils::recurseIndefinitely); - assertNotNull(stackOverflowError); - } - - @Test - void assertThrowsWithExecutableThatDoesNotThrowAnException() { - try { - assertThrowsExactly(IllegalStateException.class, nix); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "Expected java.lang.IllegalStateException to be thrown, but nothing was thrown."); - } - } - - @Test - void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageString() { - try { - assertThrowsExactly(IOException.class, nix, "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionError ex) { - assertMessageEquals(ex, - "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); - } - } - - @Test - void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() { - try { - assertThrowsExactly(IOException.class, nix, () -> "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionError ex) { - assertMessageEquals(ex, - "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { - try { - assertThrowsExactly(IllegalStateException.class, () -> { - throw new NumberFormatException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { - try { - assertThrowsExactly(IllegalStateException.class, () -> { - throw new NumberFormatException(); - }, "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Should look something like this: - // Custom message ==> Unexpected exception type thrown, expected: but was: - assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { - try { - assertThrowsExactly(IllegalStateException.class, () -> { - throw new NumberFormatException(); - }, () -> "Custom message"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Should look something like this: - // Custom message ==> Unexpected exception type thrown, expected: but was: - assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - assertMessageContains(ex, "but was: "); - assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); - } - } - - @Test - @SuppressWarnings("serial") - void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { - try { - assertThrowsExactly(IllegalStateException.class, () -> { - throw new NumberFormatException() { - }; - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - // As of the time of this writing, the class name of the above anonymous inner - // class is org.junit.jupiter.api.AssertThrowsExactlyAssertionsTests$2; however, hard - // coding "$2" is fragile. So we just check for the presence of the "$" - // appended to this class's name. - assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); - assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); - } - } - - @Test - void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { - try { - assertThrowsExactly(IllegalStateException.class, () -> { - throw new LocalException(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - assertMessageContains(ex, "expected: "); - // The following verifies that the canonical name is used (i.e., "." instead of "$"). - assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); - assertThat(ex).hasCauseExactlyInstanceOf(LocalException.class); - } - } - - @Test - @SuppressWarnings("unchecked") - void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoader() throws Exception { - try (EnigmaClassLoader enigmaClassLoader = new EnigmaClassLoader()) { - - // Load expected exception type from different class loader - Class enigmaThrowableClass = (Class) enigmaClassLoader.loadClass( - EnigmaThrowable.class.getName()); - - try { - assertThrowsExactly(enigmaThrowableClass, () -> { - throw new EnigmaThrowable(); - }); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - // Example Output: - // - // Unexpected exception type thrown, - // expected: - // but was: - - assertMessageStartsWith(ex, "Unexpected exception type thrown, "); - // The presence of the "@" sign is sufficient to indicate that the hash was - // generated to disambiguate between the two identical class names. - assertMessageContains(ex, "expected: loadClass(String name) throws ClassNotFoundException { - return (EnigmaThrowable.class.getName().equals(name) ? findClass(name) : super.loadClass(name)); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java deleted file mode 100644 index 65ef3620..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.time.Duration.ofMillis; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTimeout; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.util.ExceptionUtils; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for {@link AssertTimeout}. - * - * @since 5.0 - */ -class AssertTimeoutAssertionsTests { - - private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); - - private final Executable nix = () -> { - }; - - // --- executable ---------------------------------------------------------- - - @Test - void assertTimeoutForExecutableThatCompletesBeforeTheTimeout() { - changed.get().set(false); - assertTimeout(ofMillis(500), () -> changed.get().set(true)); - assertTrue(changed.get().get(), "should have executed in the same thread"); - assertTimeout(ofMillis(500), nix, "message"); - assertTimeout(ofMillis(500), nix, () -> "message"); - } - - @Test - void assertTimeoutForExecutableThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, () -> assertTimeout(ofMillis(500), () -> { - throw new RuntimeException("not this time"); - })); - assertMessageEquals(exception, "not this time"); - } - - @Test - void assertTimeoutForExecutableThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, - () -> assertTimeout(ofMillis(500), () -> fail("enigma"))); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeout(ofMillis(10), this::nap)); - assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); - } - - @Test - void assertTimeoutWithMessageForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeout(ofMillis(10), this::nap, "Tempus Fugit")); - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); - } - - @Test - void assertTimeoutWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeout(ofMillis(10), this::nap, () -> "Tempus" + " " + "Fugit")); - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); - } - - // --- supplier ------------------------------------------------------------ - - @Test - void assertTimeoutForSupplierThatCompletesBeforeTheTimeout() { - changed.get().set(false); - String result = assertTimeout(ofMillis(500), () -> { - changed.get().set(true); - return "Tempus Fugit"; - }); - assertTrue(changed.get().get(), "should have executed in the same thread"); - assertEquals("Tempus Fugit", result); - assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", "message")); - assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", () -> "message")); - } - - @Test - void assertTimeoutForSupplierThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, () -> { - assertTimeout(ofMillis(500), () -> { - ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); - return "Tempus Fugit"; - }); - }); - assertMessageEquals(exception, "not this time"); - } - - @Test - void assertTimeoutForSupplierThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { - assertTimeout(ofMillis(500), () -> { - fail("enigma"); - return "Tempus Fugit"; - }); - }); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeout(ofMillis(10), () -> { - nap(); - return "Tempus Fugit"; - }); - }); - assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); - } - - @Test - void assertTimeoutWithMessageForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeout(ofMillis(10), () -> { - nap(); - return "Tempus Fugit"; - }, "Tempus Fugit"); - }); - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); - } - - @Test - void assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeout(ofMillis(10), () -> { - nap(); - return "Tempus Fugit"; - }, () -> "Tempus" + " " + "Fugit"); - }); - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); - } - - /** - * Take a nap for 100 milliseconds. - */ - private void nap() throws InterruptedException { - long start = System.currentTimeMillis(); - // workaround for imprecise clocks (yes, Windows, I'm talking about you) - do { - Thread.sleep(100); - } while (System.currentTimeMillis() - start < 100); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java deleted file mode 100644 index c1570e6a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.time.Duration.ofMillis; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.OS.WINDOWS; - -import java.time.Duration; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.util.ExceptionUtils; -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for {@link AssertTimeoutPreemptively}. - * - * @since 5.0 - */ -class AssertTimeoutPreemptivelyAssertionsTests { - - private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); - private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, - ____) -> new TimeoutException(); - - private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); - - private final Executable nix = () -> { - }; - - // --- executable ---------------------------------------------------------- - - @Test - void assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { - changed.get().set(false); - assertTimeoutPreemptively(ofMillis(500), () -> changed.get().set(true)); - assertFalse(changed.get().get(), "should have executed in a different thread"); - assertTimeoutPreemptively(ofMillis(500), nix, "message"); - assertTimeoutPreemptively(ofMillis(500), nix, () -> "message"); - } - - @Test - void assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, - () -> assertTimeoutPreemptively(ofMillis(500), () -> { - throw new RuntimeException("not this time"); - })); - assertMessageEquals(exception, "not this time"); - } - - @Test - void assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"))); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt)); - assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, "Tempus Fugit")); - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, - () -> "Tempus" + " " + "Fugit")); - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { - assertTimeoutPreemptively(ofMillis(500), nix, () -> "Tempus" + " " + "Fugit"); - } - - // --- supplier ------------------------------------------------------------ - - @Test - void assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { - changed.get().set(false); - String result = assertTimeoutPreemptively(ofMillis(500), () -> { - changed.get().set(true); - return "Tempus Fugit"; - }); - assertFalse(changed.get().get(), "should have executed in a different thread"); - assertEquals("Tempus Fugit", result); - assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", "message")); - assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", () -> "message")); - } - - @Test - void assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, () -> { - assertTimeoutPreemptively(ofMillis(500), () -> { - ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); - return "Tempus Fugit"; - }); - }); - assertMessageEquals(exception, "not this time"); - } - - @Test - void assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(ofMillis(500), () -> { - fail("enigma"); - return "Tempus Fugit"; - }); - }); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }); - }); - - assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, "Tempus Fugit"); - }); - - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, () -> "Tempus" + " " + "Fugit"); - }); - - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { - AtomicReference threadName = new AtomicReference<>(""); - assertTimeoutPreemptively(ofMillis(1000), () -> threadName.set(Thread.currentThread().getName())); - assertTrue(threadName.get().startsWith("junit-timeout-thread-"), - "Thread name does not match the expected prefix"); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { - assertThrows(TimeoutException.class, () -> Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, - () -> Assertions.assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", - TIMEOUT_EXCEPTION_FACTORY)); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, - () -> Assertions.assertTimeoutPreemptively(ofMillis(500), - () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", - TIMEOUT_EXCEPTION_FACTORY)); - assertMessageEquals(exception, ":("); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() - throws Exception { - var result = Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", - () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY); - - assertThat(result).isEqualTo("Tempus Fugit"); - } - - private void waitForInterrupt() { - try { - assertFalse(Thread.interrupted(), "Already interrupted"); - new CountDownLatch(1).await(); - } - catch (InterruptedException ignore) { - // ignore - } - } - - /** - * Assert the given stack trace elements contain an element with the given class name and method name. - */ - private static void assertStackTraceContains(StackTraceElement[] stackTrace, String className, String methodName) { - assertThat(stackTrace).anySatisfy(element -> { - assertThat(element.getClassName()).endsWith(className); - assertThat(element.getMethodName()).isEqualTo(methodName); - }); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java deleted file mode 100644 index edbda5c9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class AssertTrueAssertionsTests { - - @Test - void assertTrueWithBooleanTrue() { - assertTrue(true); - assertTrue(true, "test"); - assertTrue(true, () -> "test"); - } - - @Test - void assertTrueWithBooleanSupplierTrue() { - assertTrue(() -> true); - assertTrue(() -> true, "test"); - assertTrue(() -> true, () -> "test"); - } - - @Test - void assertTrueWithBooleanTrueAndMessageSupplier() { - assertTrue(true, () -> "test"); - } - - @Test - void assertTrueWithBooleanSupplierTrueAndMessageSupplier() { - assertTrue(() -> true, () -> "test"); - } - - @Test - void assertTrueWithBooleanFalseAndDefaultMessageWithExpectedAndActualValues() { - try { - assertTrue(false); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "expected: but was: "); - assertExpectedAndActualValues(ex, true, false); - } - } - - @Test - void assertTrueWithBooleanFalseAndString() { - try { - assertTrue(false, "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, true, false); - } - } - - @Test - void assertTrueWithBooleanFalseAndMessageSupplier() { - try { - assertTrue(false, () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, true, false); - } - } - - @Test - void assertTrueWithBooleanSupplierFalseAndString() { - try { - assertTrue(() -> false, "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, true, false); - } - } - - @Test - void assertTrueWithBooleanSupplierFalseAndMessageSupplier() { - try { - assertTrue(() -> false, () -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test ==> expected: but was: "); - assertExpectedAndActualValues(ex, true, false); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java deleted file mode 100644 index 3db35842..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import java.io.Serializable; -import java.util.Objects; - -import org.opentest4j.AssertionFailedError; -import org.opentest4j.ValueWrapper; - -class AssertionTestUtils { - - private AssertionTestUtils() { - /* no-op */ - } - - static void expectAssertionFailedError() { - throw new AssertionError("Should have thrown an " + AssertionFailedError.class.getName()); - } - - static void assertEmptyMessage(Throwable ex) throws AssertionError { - if (!ex.getMessage().isEmpty()) { - throw new AssertionError("Exception message should be empty, but was [" + ex.getMessage() + "]."); - } - } - - static void assertMessageEquals(Throwable ex, String msg) throws AssertionError { - if (!msg.equals(ex.getMessage())) { - throw new AssertionError("Exception message should be [" + msg + "], but was [" + ex.getMessage() + "]."); - } - } - - static void assertMessageMatches(Throwable ex, String regex) throws AssertionError { - if (!ex.getMessage().matches(regex)) { - throw new AssertionError("Exception message should match regular expression [" + regex + "], but was [" - + ex.getMessage() + "]."); - } - } - - static void assertMessageStartsWith(Throwable ex, String msg) throws AssertionError { - if (!ex.getMessage().startsWith(msg)) { - throw new AssertionError( - "Exception message should start with [" + msg + "], but was [" + ex.getMessage() + "]."); - } - } - - static void assertMessageEndsWith(Throwable ex, String msg) throws AssertionError { - if (!ex.getMessage().endsWith(msg)) { - throw new AssertionError( - "Exception message should end with [" + msg + "], but was [" + ex.getMessage() + "]."); - } - } - - static void assertMessageContains(Throwable ex, String msg) throws AssertionError { - if (!ex.getMessage().contains(msg)) { - throw new AssertionError( - "Exception message should contain [" + msg + "], but was [" + ex.getMessage() + "]."); - } - } - - static void assertExpectedAndActualValues(AssertionFailedError ex, Object expected, Object actual) - throws AssertionError { - if (!wrapsEqualValue(ex.getExpected(), expected)) { - throw new AssertionError("Expected value in AssertionFailedError should equal [" - + ValueWrapper.create(expected) + "], but was [" + ex.getExpected() + "]."); - } - if (!wrapsEqualValue(ex.getActual(), actual)) { - throw new AssertionError("Actual value in AssertionFailedError should equal [" + ValueWrapper.create(actual) - + "], but was [" + ex.getActual() + "]."); - } - } - - static boolean wrapsEqualValue(ValueWrapper wrapper, Object value) { - if (value == null || value instanceof Serializable) { - return Objects.equals(value, wrapper.getValue()); - } - return wrapper.getIdentityHashCode() == System.identityHashCode(value) - && Objects.equals(wrapper.getStringRepresentation(), String.valueOf(value)) - && Objects.equals(wrapper.getType(), value.getClass()); - } - - static void recurseIndefinitely() { - // simulate infinite recursion - throw new StackOverflowError(); - } - - static void runOutOfMemory() { - // simulate running out of memory - throw new OutOfMemoryError("boom"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java deleted file mode 100644 index badeff93..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.abort; -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.Assumptions.assumingThat; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.junit.jupiter.api.function.Executable; -import org.opentest4j.TestAbortedException; - -/** - * Unit tests for JUnit Jupiter {@link Assumptions}. - * - * @since 5.0 - */ -class AssumptionsTests { - - // --- assumeTrue ---------------------------------------------------- - - @Test - void assumeTrueWithBooleanTrue() { - String foo = null; - try { - assumeTrue(true); - assumeTrue(true, "message"); - assumeTrue(true, () -> "message"); - foo = "foo"; - } - finally { - assertNotNull(foo); - } - } - - @Test - void assumeTrueWithBooleanSupplierTrue() { - String foo = null; - try { - assumeTrue(() -> true); - assumeTrue(() -> true, "message"); - assumeTrue(() -> true, () -> "message"); - foo = "foo"; - } - finally { - assertNotNull(foo); - } - } - - @Test - void assumeTrueWithBooleanFalse() { - assertAssumptionFailure("assumption is not true", () -> assumeTrue(false)); - } - - @Test - void assumeTrueWithBooleanSupplierFalse() { - assertAssumptionFailure("assumption is not true", () -> assumeTrue(() -> false)); - } - - @Test - void assumeTrueWithBooleanFalseAndStringMessage() { - assertAssumptionFailure("test", () -> assumeTrue(false, "test")); - } - - @Test - void assumeTrueWithBooleanFalseAndNullStringMessage() { - assertAssumptionFailure(null, () -> assumeTrue(false, (String) null)); - } - - @Test - void assumeTrueWithBooleanSupplierFalseAndStringMessage() { - assertAssumptionFailure("test", () -> assumeTrue(() -> false, "test")); - } - - @Test - void assumeTrueWithBooleanSupplierFalseAndMessageSupplier() { - assertAssumptionFailure("test", () -> assumeTrue(() -> false, () -> "test")); - } - - @Test - void assumeTrueWithBooleanFalseAndMessageSupplier() { - assertAssumptionFailure("test", () -> assumeTrue(false, () -> "test")); - } - - // --- assumeFalse ---------------------------------------------------- - - @Test - void assumeFalseWithBooleanFalse() { - String foo = null; - try { - assumeFalse(false); - assumeFalse(false, "message"); - assumeFalse(false, () -> "message"); - foo = "foo"; - } - finally { - assertNotNull(foo); - } - } - - @Test - void assumeFalseWithBooleanSupplierFalse() { - String foo = null; - try { - assumeFalse(() -> false); - assumeFalse(() -> false, "message"); - assumeFalse(() -> false, () -> "message"); - foo = "foo"; - } - finally { - assertNotNull(foo); - } - } - - @Test - void assumeFalseWithBooleanTrue() { - assertAssumptionFailure("assumption is not false", () -> assumeFalse(true)); - } - - @Test - void assumeFalseWithBooleanSupplierTrue() { - assertAssumptionFailure("assumption is not false", () -> assumeFalse(() -> true)); - } - - @Test - void assumeFalseWithBooleanTrueAndStringMessage() { - assertAssumptionFailure("test", () -> assumeFalse(true, "test")); - } - - @Test - void assumeFalseWithBooleanSupplierTrueAndMessage() { - assertAssumptionFailure("test", () -> assumeFalse(() -> true, "test")); - } - - @Test - void assumeFalseWithBooleanSupplierTrueAndMessageSupplier() { - assertAssumptionFailure("test", () -> assumeFalse(() -> true, () -> "test")); - } - - @Test - void assumeFalseWithBooleanTrueAndMessageSupplier() { - assertAssumptionFailure("test", () -> assumeFalse(true, () -> "test")); - } - - // --- assumingThat -------------------------------------------------- - - @Test - void assumingThatWithBooleanTrue() { - List list = new ArrayList<>(); - assumingThat(true, () -> list.add("test")); - assertEquals(1, list.size()); - assertEquals("test", list.get(0)); - } - - @Test - void assumingThatWithBooleanSupplierTrue() { - List list = new ArrayList<>(); - assumingThat(() -> true, () -> list.add("test")); - assertEquals(1, list.size()); - assertEquals("test", list.get(0)); - } - - @Test - void assumingThatWithBooleanFalse() { - List list = new ArrayList<>(); - assumingThat(false, () -> list.add("test")); - assertEquals(0, list.size()); - } - - @Test - void assumingThatWithBooleanSupplierFalse() { - List list = new ArrayList<>(); - assumingThat(() -> false, () -> list.add("test")); - assertEquals(0, list.size()); - } - - @Test - void assumingThatWithFailingExecutable() { - assertThrows(EnigmaThrowable.class, () -> assumingThat(true, () -> { - throw new EnigmaThrowable(); - })); - } - - // --- abort --------------------------------------------------------- - - @Test - void abortWithNoArguments() { - assertTestAbortedException(null, Assumptions::abort); - } - - @Test - void abortWithStringMessage() { - assertTestAbortedException("test", () -> abort("test")); - } - - @Test - void abortWithStringSupplier() { - assertTestAbortedException("test", () -> abort(() -> "test")); - } - - // ------------------------------------------------------------------- - - private static void assertAssumptionFailure(String msg, Executable executable) { - assertTestAbortedException(msg == null ? "Assumption failed" : "Assumption failed: " + msg, executable); - } - - private static void assertTestAbortedException(String expectedMessage, Executable executable) { - try { - executable.execute(); - expectTestAbortedException(); - } - catch (Throwable ex) { - assertTrue(ex instanceof TestAbortedException); - assertMessageEquals(ex, expectedMessage); - } - } - - private static void expectTestAbortedException() { - throw new AssertionError("Should have thrown a " + TestAbortedException.class.getName()); - } - - private static void assertMessageEquals(Throwable t, String expectedMessage) throws AssertionError { - if (!Objects.equals(expectedMessage, t.getMessage())) { - throw new AssertionError("Message in TestAbortedException should be [" + expectedMessage + "], but was [" - + t.getMessage() + "]."); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java deleted file mode 100644 index 427ebdfc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; - -/** - * @since 5.8 - */ -@DisplayNameGeneration(ReplaceUnderscores.class) -class DisplayNameGenerationInheritanceTestCase { - - @Nested - class InnerNestedTestCase { - - @Test - void this_is_a_test() { - } - } - - static class StaticNestedTestCase { - - @Test - void this_is_a_test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java deleted file mode 100644 index a02a9adf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.lang.reflect.Method; -import java.util.EmptyStackException; -import java.util.Stack; - -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.engine.TestDescriptor; - -/** - * Check generated display names. - * - * @see DisplayName - * @see DisplayNameGenerator - * @see DisplayNameGeneration - * @since 5.4 - */ -class DisplayNameGenerationTests extends AbstractJupiterTestEngineTests { - - @Test - void standardGenerator() { - check(DefaultStyleTestCase.class, // - "CONTAINER: DisplayNameGenerationTests$DefaultStyleTestCase", // - "TEST: @DisplayName prevails", // - "TEST: test()", // - "TEST: test(TestInfo)", // - "TEST: testUsingCamelCaseStyle()", // - "TEST: testUsingCamelCase_and_also_UnderScores()", // - "TEST: testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact(TestInfo)", // - "TEST: test_with_underscores()" // - ); - } - - @Test - void simpleGenerator() { - check(SimpleStyleTestCase.class, // - "CONTAINER: DisplayNameGenerationTests$SimpleStyleTestCase", // - "TEST: @DisplayName prevails", // - "TEST: test", // - "TEST: test (TestInfo)", // - "TEST: testUsingCamelCaseStyle", // - "TEST: testUsingCamelCase_and_also_UnderScores", // - "TEST: testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact (TestInfo)", // - "TEST: test_with_underscores" // - ); - } - - @Test - void underscoreGenerator() { - var expectedDisplayNames = new String[] { // - "", // - "TEST: @DisplayName prevails", // - "TEST: test", // - "TEST: test (TestInfo)", // - "TEST: test with underscores", // - "TEST: testUsingCamelCase and also UnderScores", // - "TEST: testUsingCamelCase and also UnderScores keepingParameterTypeNamesIntact (TestInfo)", // - "TEST: testUsingCamelCaseStyle" // - }; - - expectedDisplayNames[0] = "CONTAINER: DisplayNameGenerationTests$UnderscoreStyleTestCase"; - check(UnderscoreStyleTestCase.class, expectedDisplayNames); - - expectedDisplayNames[0] = "CONTAINER: DisplayNameGenerationTests$UnderscoreStyleInheritedFromSuperClassTestCase"; - check(UnderscoreStyleInheritedFromSuperClassTestCase.class, expectedDisplayNames); - } - - @Test - void indicativeSentencesGeneratorOnStaticNestedClass() { - check(IndicativeStyleTestCase.class, // - "CONTAINER: DisplayNameGenerationTests$IndicativeStyleTestCase", // - "TEST: @DisplayName prevails", // - "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test", // - "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test (TestInfo)", // - "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test with underscores", // - "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCase and also UnderScores", // - "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCase and also UnderScores keepingParameterTypeNamesIntact (TestInfo)", // - "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCaseStyle" // - ); - } - - @Test - void indicativeSentencesGeneratorOnTopLevelClass() { - check(IndicativeSentencesTopLevelTestCase.class, // - "CONTAINER: IndicativeSentencesTopLevelTestCase", // - "CONTAINER: IndicativeSentencesTopLevelTestCase -> A year is a leap year", // - "TEST: IndicativeSentencesTopLevelTestCase -> A year is a leap year -> if it is divisible by 4 but not by 100" // - ); - } - - @Test - void indicativeSentencesGeneratorOnNestedClass() { - check(IndicativeSentencesNestedTestCase.class, // - "CONTAINER: IndicativeSentencesNestedTestCase", // - "CONTAINER: A year is a leap year", // - "TEST: A year is a leap year -> if it is divisible by 4 but not by 100" // - ); - } - - @Test - void noNameGenerator() { - check(NoNameStyleTestCase.class, // - "CONTAINER: nn", // - "TEST: @DisplayName prevails", // - "TEST: nn", // - "TEST: nn", // - "TEST: nn", // - "TEST: nn", // - "TEST: nn", // - "TEST: nn" // - ); - } - - @Test - void checkDisplayNameGeneratedForTestingAStackDemo() { - check(StackTestCase.class, // - "CONTAINER: A stack", // - "TEST: is instantiated using its noarg constructor", // - "CONTAINER: A new stack", // - "TEST: throws an EmptyStackException when peeked", // - "TEST: throws an EmptyStackException when popped", // - "TEST: is empty", // - "CONTAINER: After pushing an element to an empty stack", // - "TEST: peek returns that element without removing it from the stack", // - "TEST: pop returns that element and leaves an empty stack", // - "TEST: the stack is no longer empty" // - ); - } - - @Test - void checkDisplayNameGeneratedForIndicativeGeneratorTestCase() { - check(IndicativeGeneratorTestCase.class, // - "CONTAINER: A stack", // - "TEST: A stack, is instantiated with new constructor", // - "CONTAINER: A stack, when new", // - "TEST: A stack, when new, throws EmptyStackException when peeked", // - "CONTAINER: A stack, when new, after pushing an element to an empty stack", // - "TEST: A stack, when new, after pushing an element to an empty stack, is no longer empty" // - ); - } - - @Test - void checkDisplayNameGeneratedForIndicativeGeneratorWithCustomSeparatorTestCase() { - check(IndicativeGeneratorWithCustomSeparatorTestCase.class, // - "CONTAINER: A stack", // - "TEST: A stack >> is instantiated with new constructor", // - "CONTAINER: A stack >> when new", // - "TEST: A stack >> when new >> throws EmptyStackException when peeked", // - "CONTAINER: A stack >> when new >> after pushing an element to an empty stack", // - "TEST: A stack >> when new >> after pushing an element to an empty stack >> is no longer empty" // - ); - } - - @Test - void displayNameGenerationInheritance() { - check(DisplayNameGenerationInheritanceTestCase.InnerNestedTestCase.class, // - "CONTAINER: DisplayNameGenerationInheritanceTestCase", // - "CONTAINER: InnerNestedTestCase", // - "TEST: this is a test"// - ); - - check(DisplayNameGenerationInheritanceTestCase.StaticNestedTestCase.class, // - "CONTAINER: DisplayNameGenerationInheritanceTestCase$StaticNestedTestCase", // - "TEST: this_is_a_test()"// - ); - } - - @Test - void indicativeSentencesGenerationInheritance() { - check(IndicativeSentencesGenerationInheritanceTestCase.InnerNestedTestCase.class, // - "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase", // - "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase -> InnerNestedTestCase", // - "TEST: IndicativeSentencesGenerationInheritanceTestCase -> InnerNestedTestCase -> this is a test"// - ); - - check(IndicativeSentencesGenerationInheritanceTestCase.StaticNestedTestCase.class, // - "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase$StaticNestedTestCase", // - "TEST: this_is_a_test()"// - ); - } - - private void check(Class testClass, String... expectedDisplayNames) { - var request = request().selectors(selectClass(testClass)).build(); - var descriptors = discoverTests(request).getDescendants(); - assertThat(descriptors).map(this::describe).containsExactlyInAnyOrder(expectedDisplayNames); - } - - private String describe(TestDescriptor descriptor) { - return descriptor.getType() + ": " + descriptor.getDisplayName(); - } - - // ------------------------------------------------------------------- - - static class NoNameGenerator implements DisplayNameGenerator { - - @Override - public String generateDisplayNameForClass(Class testClass) { - return "nn"; - } - - @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return "nn"; - } - - @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return "nn"; - } - } - - @DisplayNameGeneration(NoNameGenerator.class) - static abstract class AbstractTestCase { - @Test - void test() { - } - - @Test - void test(TestInfo testInfo) { - testInfo.getDisplayName(); - } - - @Test - void testUsingCamelCaseStyle() { - } - - @Test - void testUsingCamelCase_and_also_UnderScores() { - } - - @Test - void testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact(TestInfo testInfo) { - testInfo.getDisplayName(); - } - - @Test - void test_with_underscores() { - } - - @DisplayName("@DisplayName prevails") - @Test - void testDisplayNamePrevails() { - } - } - - @DisplayNameGeneration(DisplayNameGenerator.Standard.class) - static class DefaultStyleTestCase extends AbstractTestCase { - } - - @DisplayNameGeneration(DisplayNameGenerator.Simple.class) - static class SimpleStyleTestCase extends AbstractTestCase { - } - - @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) - static class UnderscoreStyleTestCase extends AbstractTestCase { - } - - @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) - static class IndicativeStyleTestCase extends AbstractTestCase { - } - - @DisplayNameGeneration(NoNameGenerator.class) - static class NoNameStyleTestCase extends AbstractTestCase { - } - - // No annotation here! @DisplayNameGeneration is inherited from super class - static class UnderscoreStyleInheritedFromSuperClassTestCase extends UnderscoreStyleTestCase { - } - - // ------------------------------------------------------------------- - - @DisplayName("A stack") - @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) - static class StackTestCase { - - Stack stack; - - @Test - void is_instantiated_using_its_noarg_constructor() { - new Stack<>(); - } - - @Nested - class A_new_stack { - - @BeforeEach - void createNewStack() { - stack = new Stack<>(); - } - - @Test - void is_empty() { - assertTrue(stack.isEmpty()); - } - - @Test - void throws_an_EmptyStackException_when_popped() { - assertThrows(EmptyStackException.class, () -> stack.pop()); - } - - @Test - void throws_an_EmptyStackException_when_peeked() { - assertThrows(EmptyStackException.class, () -> stack.peek()); - } - - @Nested - class After_pushing_an_element_to_an_empty_stack { - - String anElement = "an element"; - - @BeforeEach - void pushAnElement() { - stack.push(anElement); - } - - @Test - void the_stack_is_no_longer_empty() { - assertFalse(stack.isEmpty()); - } - - @Test - void pop_returns_that_element_and_leaves_an_empty_stack() { - assertEquals(anElement, stack.pop()); - assertTrue(stack.isEmpty()); - } - - @Test - void peek_returns_that_element_without_removing_it_from_the_stack() { - assertEquals(anElement, stack.peek()); - assertFalse(stack.isEmpty()); - } - } - } - } - - // ------------------------------------------------------------------- - - @DisplayName("A stack") - @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) - static class IndicativeGeneratorTestCase { - - Stack stack; - - @Test - void is_instantiated_with_new_constructor() { - new Stack<>(); - } - - @Nested - class when_new { - - @BeforeEach - void create_with_new_stack() { - stack = new Stack<>(); - } - - @Test - void throws_EmptyStackException_when_peeked() { - assertThrows(EmptyStackException.class, () -> stack.peek()); - } - - @Nested - class after_pushing_an_element_to_an_empty_stack { - - String anElement = "an element"; - - @BeforeEach - void push_an_element() { - stack.push(anElement); - } - - @Test - void is_no_longer_empty() { - assertFalse(stack.isEmpty()); - } - } - } - } - - // ------------------------------------------------------------------- - - @DisplayName("A stack") - @IndicativeSentencesGeneration(separator = " >> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) - static class IndicativeGeneratorWithCustomSeparatorTestCase { - - Stack stack; - - @Test - void is_instantiated_with_new_constructor() { - new Stack<>(); - } - - @Nested - class when_new { - - @BeforeEach - void create_with_new_stack() { - stack = new Stack<>(); - } - - @Test - void throws_EmptyStackException_when_peeked() { - assertThrows(EmptyStackException.class, () -> stack.peek()); - } - - @Nested - class after_pushing_an_element_to_an_empty_stack { - - String anElement = "an element"; - - @BeforeEach - void push_an_element() { - stack.push(anElement); - } - - @Test - void is_no_longer_empty() { - assertFalse(stack.isEmpty()); - } - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java deleted file mode 100644 index 91a49501..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static java.util.Collections.emptyIterator; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.function.ThrowingConsumer; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.support.ReflectionSupport; -import org.opentest4j.AssertionFailedError; - -/** - * @since 5.0 - */ -class DynamicTestTests { - - private static final Executable nix = () -> { - }; - - private final List assertedValues = new ArrayList<>(); - - @Test - void streamFromStreamPreconditions() { - ThrowingConsumer testExecutor = input -> { - }; - Function displayNameGenerator = Object::toString; - - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream((Stream) null, displayNameGenerator, testExecutor)); - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream(Stream.empty(), null, testExecutor)); - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream(Stream.empty(), displayNameGenerator, null)); - } - - @Test - void streamFromIteratorPreconditions() { - ThrowingConsumer testExecutor = input -> { - }; - Function displayNameGenerator = Object::toString; - - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream((Iterator) null, displayNameGenerator, testExecutor)); - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream(emptyIterator(), null, testExecutor)); - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream(emptyIterator(), displayNameGenerator, null)); - } - - @Test - void streamFromStreamWithNamesPreconditions() { - ThrowingConsumer testExecutor = input -> { - }; - - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream((Stream>) null, testExecutor)); - assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(Stream.empty(), null)); - } - - @Test - void streamFromIteratorWithNamesPreconditions() { - ThrowingConsumer testExecutor = input -> { - }; - - assertThrows(PreconditionViolationException.class, - () -> DynamicTest.stream((Iterator>) null, testExecutor)); - assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(emptyIterator(), null)); - } - - @Test - void streamFromStream() throws Throwable { - Stream stream = DynamicTest.stream(Stream.of("foo", "bar", "baz"), String::toUpperCase, - this::throwingConsumer); - assertStream(stream); - } - - @Test - void streamFromIterator() throws Throwable { - Stream stream = DynamicTest.stream(List.of("foo", "bar", "baz").iterator(), String::toUpperCase, - this::throwingConsumer); - assertStream(stream); - } - - @Test - void streamFromStreamWithNames() throws Throwable { - Stream stream = DynamicTest.stream( - Stream.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")), this::throwingConsumer); - assertStream(stream); - } - - @Test - void streamFromIteratorWithNames() throws Throwable { - Stream stream = DynamicTest.stream( - List.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")).iterator(), - this::throwingConsumer); - assertStream(stream); - } - - private void assertStream(Stream stream) throws Throwable { - List dynamicTests = stream.collect(Collectors.toList()); - - assertThat(dynamicTests).extracting(DynamicTest::getDisplayName).containsExactly("FOO", "BAR", "BAZ"); - - assertThat(assertedValues).isEmpty(); - - dynamicTests.get(0).getExecutable().execute(); - assertThat(assertedValues).containsExactly("foo"); - - dynamicTests.get(1).getExecutable().execute(); - assertThat(assertedValues).containsExactly("foo", "bar"); - - Throwable t = assertThrows(Throwable.class, () -> dynamicTests.get(2).getExecutable().execute()); - assertThat(t).hasMessage("Baz!"); - assertThat(assertedValues).containsExactly("foo", "bar"); - } - - private void throwingConsumer(String str) throws Throwable { - if ("baz".equals(str)) { - throw new Throwable("Baz!"); - } - this.assertedValues.add(str); - } - - @Test - void reflectiveOperationsThrowingAssertionFailedError() { - Throwable t48 = assertThrows(AssertionFailedError.class, - () -> dynamicTest("1 == 48", this::assert1Equals48Directly).getExecutable().execute()); - assertThat(t48).hasMessage("expected: <1> but was: <48>"); - - Throwable t49 = assertThrows(AssertionFailedError.class, () -> dynamicTest("1 == 49", - this::assert1Equals49ReflectivelyAndUnwrapInvocationTargetException).getExecutable().execute()); - assertThat(t49).hasMessage("expected: <1> but was: <49>"); - } - - @Test - void reflectiveOperationThrowingInvocationTargetException() { - Throwable t50 = assertThrows(InvocationTargetException.class, - () -> dynamicTest("1 == 50", this::assert1Equals50Reflectively).getExecutable().execute()); - assertThat(t50.getCause()).hasMessage("expected: <1> but was: <50>"); - } - - @Test - void sourceUriIsNotPresentByDefault() { - DynamicTest test = dynamicTest("foo", nix); - assertThat(test.getTestSourceUri()).isNotPresent(); - assertThat(test.toString()).isEqualTo("DynamicTest [displayName = 'foo', testSourceUri = null]"); - DynamicContainer container = dynamicContainer("bar", Stream.of(test)); - assertThat(container.getTestSourceUri()).isNotPresent(); - assertThat(container.toString()).isEqualTo("DynamicContainer [displayName = 'bar', testSourceUri = null]"); - } - - @Test - void sourceUriIsReturnedWhenSupplied() { - URI testSourceUri = URI.create("any://test"); - DynamicTest test = dynamicTest("foo", testSourceUri, nix); - URI containerSourceUri = URI.create("other://container"); - DynamicContainer container = dynamicContainer("bar", containerSourceUri, Stream.of(test)); - - assertThat(test.getTestSourceUri().get()).isSameAs(testSourceUri); - assertThat(test.toString()).isEqualTo("DynamicTest [displayName = 'foo', testSourceUri = any://test]"); - assertThat(container.getTestSourceUri().get()).isSameAs(containerSourceUri); - assertThat(container.toString()).isEqualTo( - "DynamicContainer [displayName = 'bar', testSourceUri = other://container]"); - } - - private void assert1Equals48Directly() { - Assertions.assertEquals(1, 48); - } - - private void assert1Equals49ReflectivelyAndUnwrapInvocationTargetException() throws Throwable { - Method method = Assertions.class.getMethod("assertEquals", int.class, int.class); - ReflectionSupport.invokeMethod(method, null, 1, 49); - } - - private void assert1Equals50Reflectively() throws Throwable { - Method method = Assertions.class.getMethod("assertEquals", int.class, int.class); - method.invoke(null, 1, 50); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java deleted file mode 100644 index c3ce9611..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -/** - * This is a top-level type in order to avoid issues with - * {@link Class#getCanonicalName()} when using different class - * loaders in tests. - * - * @since 5.0 - */ -@SuppressWarnings("serial") -class EnigmaThrowable extends Throwable { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java deleted file mode 100644 index fa2edce6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import static org.junit.jupiter.api.AssertionTestUtils.assertEmptyMessage; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; -import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; -import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.opentest4j.AssertionFailedError; - -/** - * Unit tests for JUnit Jupiter {@link Assertions}. - * - * @since 5.0 - */ -class FailAssertionsTests { - - @Test - void failWithoutArgument() { - try { - fail(); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertEmptyMessage(ex); - } - } - - @Test - void failWithString() { - try { - fail("test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test"); - } - } - - @Test - void failWithMessageSupplier() { - try { - fail(() -> "test"); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "test"); - } - } - - @Test - void failWithNullString() { - try { - fail((String) null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertEmptyMessage(ex); - } - } - - @Test - void failWithNullMessageSupplier() { - try { - fail((Supplier) null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertEmptyMessage(ex); - } - } - - @Test - void failWithStringAndThrowable() { - try { - fail("message", new Throwable("cause")); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "message"); - Throwable cause = ex.getCause(); - assertMessageContains(cause, "cause"); - } - } - - @Test - void failWithThrowable() { - try { - fail(new Throwable("cause")); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertEmptyMessage(ex); - Throwable cause = ex.getCause(); - assertMessageContains(cause, "cause"); - } - } - - @Test - void failWithStringAndNullThrowable() { - try { - fail("message", (Throwable) null); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertMessageEquals(ex, "message"); - if (ex.getCause() != null) { - throw new AssertionError("Cause should have been null"); - } - } - } - - @Test - void failWithNullStringAndThrowable() { - try { - fail((String) null, new Throwable("cause")); - expectAssertionFailedError(); - } - catch (AssertionFailedError ex) { - assertEmptyMessage(ex); - Throwable cause = ex.getCause(); - assertMessageContains(cause, "cause"); - } - } - - @Test - void failUsableAsAnExpression() { - // @formatter:off - long count = Stream.empty() - .peek(element -> fail("peek should never be called")) - .filter(element -> fail("filter should never be called", new Throwable("cause"))) - .map(element -> fail(new Throwable("map should never be called"))) - .sorted((e1, e2) -> fail(() -> "sorted should never be called")) - .count(); - // @formatter:on - assertEquals(0L, count); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java deleted file mode 100644 index 1a506ee0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; - -/** - * @since 5.8 - */ -@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) -class IndicativeSentencesGenerationInheritanceTestCase { - - @Nested - class InnerNestedTestCase { - - @Test - void this_is_a_test() { - } - } - - static class StaticNestedTestCase { - - @Test - void this_is_a_test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java deleted file mode 100644 index 8b6db62d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; - -/** - * This test case declares {@link IndicativeSentencesGeneration} on a test class - * that is nested directly within a top-level test class. - * - * @see IndicativeSentencesTopLevelTestCase - * @since 5.8 - */ -class IndicativeSentencesNestedTestCase { - - @Nested - @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) - class A_year_is_a_leap_year { - - @Test - void if_it_is_divisible_by_4_but_not_by_100() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java deleted file mode 100644 index d1cdbcdb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; - -/** - * This test case declares {@link IndicativeSentencesGeneration} on a top-level - * test class that contains a nested test class. - * - * @see IndicativeSentencesNestedTestCase - * @since 5.8 - */ -@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) -class IndicativeSentencesTopLevelTestCase { - - @Nested - class A_year_is_a_leap_year { - - @Test - void if_it_is_divisible_by_4_but_not_by_100() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java deleted file mode 100644 index 0bf31285..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api; - -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -final class IterableFactory { - - static List listOf(Object... objects) { - return Arrays.asList(objects); - } - - static Set setOf(Object... objects) { - return new LinkedHashSet<>(listOf(objects)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java deleted file mode 100644 index 7e02a7d3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; -import static org.junit.platform.commons.util.ReflectionUtils.newInstance; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * Abstract base class for unit testing a concrete {@link ExecutionCondition} - * implementation. - * - *

WARNING: this abstract base class currently does not - * support tests in {@code @Nested} test classes within the - * {@linkplain #getTestClass() test class}, since {@link #beforeEach(TestInfo)} - * instantiates the test class using the no-args default constructor. - * - * @since 5.1 - */ -@TestInstance(Lifecycle.PER_CLASS) -abstract class AbstractExecutionConditionTests { - - private final ExtensionContext context = mock(); - - private ConditionEvaluationResult result; - - @BeforeAll - void ensureAllTestMethodsAreCovered() { - Predicate isTestMethod = method -> method.isAnnotationPresent(Test.class); - - List methodsToTest = findMethods(getTestClass(), isTestMethod).stream()// - .map(Method::getName).sorted().collect(toList()); - - List localTestMethods = findMethods(getClass(), isTestMethod).stream()// - .map(Method::getName).sorted().collect(toList()); - - assertThat(localTestMethods).isEqualTo(methodsToTest); - } - - @BeforeEach - void beforeEach(TestInfo testInfo) { - when(this.context.getElement()).thenReturn(method(testInfo)); - when(this.context.getTestInstance()).thenReturn(Optional.of(newInstance(getTestClass()))); - doReturn(getTestClass()).when(this.context).getRequiredTestClass(); - } - - protected abstract ExecutionCondition getExecutionCondition(); - - protected abstract Class getTestClass(); - - protected void evaluateCondition() { - this.result = getExecutionCondition().evaluateExecutionCondition(this.context); - } - - protected void assertEnabled() { - assertTrue(!this.result.isDisabled(), "Should be enabled"); - } - - protected void assertDisabled() { - assertTrue(this.result.isDisabled(), "Should be disabled"); - } - - protected void assertReasonContains(String text) { - assertThat(this.result.getReason()).hasValueSatisfying(reason -> assertThat(reason).contains(text)); - } - - protected void assertCustomDisabledReasonIs(String text) { - if (this.result.isDisabled()) { - assertThat(this.result.getReason()).hasValueSatisfying( - reason -> assertThat(reason).contains(" ==> " + text)); - } - } - - private Optional method(TestInfo testInfo) { - return method(getTestClass(), testInfo.getTestMethod().get().getName()); - } - - private Optional method(Class testClass, String methodName) { - return Optional.of(findMethod(testClass, methodName).get()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java deleted file mode 100644 index 9ccce353..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DisabledForJreRange}. - * - *

Note that test method names MUST match the test method names in - * {@link DisabledForJreRangeIntegrationTests}. - * - * @since 5.6 - */ -class DisabledForJreRangeConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new DisabledForJreRangeCondition(); - } - - @Override - protected Class getTestClass() { - return DisabledForJreRangeIntegrationTests.class; - } - - /** - * @see DisabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@DisabledForJreRange is not present"); - } - - /** - * @see DisabledForJreRangeIntegrationTests#defaultValues() - */ - @Test - void defaultValues() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(this::evaluateCondition)// - .withMessageContaining("You must declare a non-default value for min or max in @DisabledForJreRange"); - } - - /** - * @see DisabledForJreRangeIntegrationTests#java17() - */ - @Test - void java17() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava17()); - } - - /** - * @see DisabledForJreRangeIntegrationTests#java18to19() - */ - @Test - void java18to19() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava18() || onJava19()); - assertCustomDisabledReasonIs("Disabled on some JRE"); - } - - /** - * @see DisabledForJreRangeIntegrationTests#javaMax18() - */ - @Test - void javaMax18() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() - || onJava14() || onJava15() || onJava16() || onJava17() || onJava18()); - } - - /** - * @see DisabledForJreRangeIntegrationTests#javaMin18() - */ - @Test - void javaMin18() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(!(onJava17())); - } - - /** - * @see DisabledForJreRangeIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertDisabledOnCurrentJreIf( - !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() - || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); - } - - private void assertDisabledOnCurrentJreIf(boolean condition) { - if (condition) { - assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); - } - else { - assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java deleted file mode 100644 index bcca45f9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.OTHER; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link DisabledForJreRange}. - * - * @since 5.6 - */ -class DisabledForJreRangeIntegrationTests { - - @Test - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @DisabledForJreRange - void defaultValues() { - fail("should result in a configuration exception"); - } - - @Test - @DisabledForJreRange(min = JAVA_17, max = JAVA_17) - void java17() { - assertFalse(onJava17()); - } - - @Test - @DisabledForJreRange(min = JAVA_18, max = JAVA_19, disabledReason = "Disabled on some JRE") - void java18to19() { - assertFalse(onJava18() || onJava19()); - } - - @Test - @DisabledForJreRange(max = JAVA_18) - void javaMax18() { - assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18()); - } - - @Test - @DisabledForJreRange(min = JAVA_18) - void javaMin18() { - assertFalse(onJava18() || onJava19()); - assertTrue(onJava17()); - } - - @Test - @DisabledForJreRange(min = OTHER, max = OTHER) - void other() { - assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java deleted file mode 100644 index fc1524d8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; - -/** - * Unit tests for {@link DisabledIf}. - * - *

Note that test method names MUST match the test method names in - * {@link DisabledIfIntegrationTests}. - * - * @since 5.7 - */ -public class DisabledIfConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new DisabledIfCondition(); - } - - @Override - protected Class getTestClass() { - return DisabledIfIntegrationTests.class; - } - - /** - * @see DisabledIfIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@DisabledIf is not present"); - } - - /** - * @see DisabledIfIntegrationTests#disabledBecauseStaticConditionMethodReturnsTrue() - */ - @Test - void disabledBecauseStaticConditionMethodReturnsTrue() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("Disabled for some reason"); - } - - /** - * @see DisabledIfIntegrationTests#enabledBecauseStaticConditionMethodReturnsFalse() - */ - @Test - void enabledBecauseStaticConditionMethodReturnsFalse() { - evaluateCondition(); - assertEnabled(); - assertReasonContains(""" - @DisabledIf("staticMethodThatReturnsFalse") evaluated to false"""); - } - - /** - * @see DisabledIfIntegrationTests#disabledBecauseConditionMethodReturnsTrue() - */ - @Test - void disabledBecauseConditionMethodReturnsTrue() { - evaluateCondition(); - assertDisabled(); - assertReasonContains(""" - @DisabledIf("methodThatReturnsTrue") evaluated to true"""); - } - - /** - * @see DisabledIfIntegrationTests#enabledBecauseConditionMethodReturnsFalse() - */ - @Test - void enabledBecauseConditionMethodReturnsFalse() { - evaluateCondition(); - assertEnabled(); - assertReasonContains(""" - @DisabledIf("methodThatReturnsFalse") evaluated to false"""); - } - - /** - * @see DisabledIfIntegrationTests.ExternalConditionMethod#disabledBecauseStaticExternalConditionMethodReturnsTrue() - */ - @Test - void disabledBecauseStaticExternalConditionMethodReturnsTrue() { - evaluateCondition(); - assertDisabled(); - assertReasonContains(""" - @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") evaluated to true"""); - } - - /** - * @see DisabledIfIntegrationTests.ExternalConditionMethod#enabledBecauseStaticExternalConditionMethodReturnsFalse() - */ - @Test - void enabledBecauseStaticExternalConditionMethodReturnsFalse() { - evaluateCondition(); - assertEnabled(); - assertReasonContains( - """ - @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") evaluated to false"""); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java deleted file mode 100644 index 07a3e782..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.ENIGMA; -import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY1; -import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY2; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DisabledIfEnvironmentVariableCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link DisabledIfEnvironmentVariableIntegrationTests}. - * - * @since 5.1 - */ -class DisabledIfEnvironmentVariableConditionTests extends AbstractExecutionConditionTests { - - /** - * Stubbed subclass of {@link DisabledIfEnvironmentVariableCondition}. - */ - private ExecutionCondition condition = new DisabledIfEnvironmentVariableCondition() { - - @Override - protected String getEnvironmentVariable(String name) { - return KEY1.equals(name) ? ENIGMA : null; - } - }; - - @Override - protected ExecutionCondition getExecutionCondition() { - return this.condition; - } - - @Override - protected Class getTestClass() { - return DisabledIfEnvironmentVariableIntegrationTests.class; - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains( - "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#blankNamedAttribute() - */ - @Test - void blankNamedAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#blankMatchesAttribute() - */ - @Test - void blankMatchesAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableMatchesExactly() - */ - @Test - void disabledBecauseEnvironmentVariableMatchesExactly() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("matches regular expression"); - assertCustomDisabledReasonIs("That's an enigma"); - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() - */ - @Test - void disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() { - this.condition = new DisabledIfEnvironmentVariableCondition() { - - @Override - protected String getEnvironmentVariable(String name) { - return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; - } - }; - - evaluateCondition(); - assertDisabled(); - assertReasonContains("matches regular expression"); - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableMatchesPattern() - */ - @Test - void disabledBecauseEnvironmentVariableMatchesPattern() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("matches regular expression"); - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableDoesNotMatch() - */ - @Test - void enabledBecauseEnvironmentVariableDoesNotMatch() { - evaluateCondition(); - assertEnabled(); - assertReasonContains( - "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableDoesNotExist() - */ - @Test - void enabledBecauseEnvironmentVariableDoesNotExist() { - evaluateCondition(); - assertEnabled(); - assertReasonContains( - "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java deleted file mode 100644 index cbc06823..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.fail; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link DisabledIfEnvironmentVariable}. - * - * @since 5.1 - */ -@Disabled("Disabled since the required environment variables are not set") -// Tests (except those with intentional configuration errors) will pass if you set -// the following environment variables: -// DisabledIfEnvironmentVariableTests.key1 = enigma -// DisabledIfEnvironmentVariableTests.key2 = enigma -class DisabledIfEnvironmentVariableIntegrationTests { - - static final String KEY1 = "DisabledIfEnvironmentVariableTests.key1"; - static final String KEY2 = "DisabledIfEnvironmentVariableTests.key2"; - static final String ENIGMA = "enigma"; - static final String BOGUS = "bogus"; - - @Test - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @DisabledIfEnvironmentVariable(named = " ", matches = ENIGMA) - void blankNamedAttribute() { - } - - @Test - @DisabledIfEnvironmentVariable(named = KEY1, matches = " ") - void blankMatchesAttribute() { - } - - @Test - @DisabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA, disabledReason = "That's an enigma") - void disabledBecauseEnvironmentVariableMatchesExactly() { - fail("should be disabled"); - } - - @Test - @DisabledIfEnvironmentVariable(named = KEY1, matches = BOGUS) - @CustomDisabled - void disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() { - fail("should be disabled"); - } - - @Test - @DisabledIfEnvironmentVariable(named = KEY1, matches = ".*e.+gma$") - void disabledBecauseEnvironmentVariableMatchesPattern() { - fail("should be disabled"); - } - - @Test - @DisabledIfEnvironmentVariable(named = KEY1, matches = BOGUS) - void enabledBecauseEnvironmentVariableDoesNotMatch() { - assertNotEquals(BOGUS, System.getenv(KEY1)); - } - - @Test - @DisabledIfEnvironmentVariable(named = BOGUS, matches = "doesn't matter") - void enabledBecauseEnvironmentVariableDoesNotExist() { - assertNull(System.getenv(BOGUS)); - } - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @DisabledIfEnvironmentVariable(named = KEY2, matches = ENIGMA) - @interface CustomDisabled { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java deleted file mode 100644 index c783d322..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link DisabledIf}. - * - * @since 5.7 - */ -public class DisabledIfIntegrationTests { - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @DisabledIf(value = "staticMethodThatReturnsTrue", disabledReason = "Disabled for some reason") - void disabledBecauseStaticConditionMethodReturnsTrue() { - fail("Should be disabled"); - } - - @Test - @DisabledIf("staticMethodThatReturnsFalse") - void enabledBecauseStaticConditionMethodReturnsFalse() { - } - - @Test - @DisabledIf("methodThatReturnsTrue") - void disabledBecauseConditionMethodReturnsTrue() { - fail("Should be disabled"); - } - - @Test - @DisabledIf("methodThatReturnsFalse") - void enabledBecauseConditionMethodReturnsFalse() { - } - - @Test - @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") - void disabledBecauseStaticExternalConditionMethodReturnsTrue() { - fail("Should be disabled"); - } - - @Test - @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") - void enabledBecauseStaticExternalConditionMethodReturnsFalse() { - } - - @Nested - @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") - class ConditionallyDisabledClass { - - @Test - void disabledBecauseConditionMethodReturnsTrue() { - fail("Should be disabled"); - } - - } - - // ------------------------------------------------------------------------- - - @SuppressWarnings("unused") - private static boolean staticMethodThatReturnsTrue() { - return true; - } - - @SuppressWarnings("unused") - private static boolean staticMethodThatReturnsFalse() { - return false; - } - - @SuppressWarnings("unused") - private boolean methodThatReturnsTrue() { - return true; - } - - @SuppressWarnings("unused") - private boolean methodThatReturnsFalse() { - return false; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java deleted file mode 100644 index d7be1177..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DisabledIfSystemPropertyCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link DisabledIfSystemPropertyIntegrationTests}. - * - * @since 5.1 - */ -class DisabledIfSystemPropertyConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new DisabledIfSystemPropertyCondition(); - } - - @Override - protected Class getTestClass() { - return DisabledIfSystemPropertyIntegrationTests.class; - } - - @BeforeAll - static void setSystemProperties() { - DisabledIfSystemPropertyIntegrationTests.setSystemProperties(); - } - - @AfterAll - static void clearSystemProperties() { - DisabledIfSystemPropertyIntegrationTests.clearSystemProperties(); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#blankNamedAttribute() - */ - @Test - void blankNamedAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#blankMatchesAttribute() - */ - @Test - void blankMatchesAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyMatchesExactly() - */ - @Test - void disabledBecauseSystemPropertyMatchesExactly() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("matches regular expression"); - assertCustomDisabledReasonIs("That's an enigma"); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() - */ - @Test - void disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("matches regular expression"); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyMatchesPattern() - */ - @Test - void disabledBecauseSystemPropertyMatchesPattern() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("matches regular expression"); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyDoesNotMatch() - */ - @Test - void enabledBecauseSystemPropertyDoesNotMatch() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyDoesNotExist() - */ - @Test - void enabledBecauseSystemPropertyDoesNotExist() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java deleted file mode 100644 index 7dd479d3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.fail; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link DisabledIfSystemProperty}. - * - * @since 5.1 - */ -class DisabledIfSystemPropertyIntegrationTests { - - private static final String KEY1 = "DisabledIfSystemPropertyTests.key1"; - private static final String KEY2 = "DisabledIfSystemPropertyTests.key2"; - private static final String ENIGMA = "enigma"; - private static final String BOGUS = "bogus"; - - @BeforeAll - static void setSystemProperties() { - System.setProperty(KEY1, ENIGMA); - System.setProperty(KEY2, ENIGMA); - } - - @AfterAll - static void clearSystemProperties() { - System.clearProperty(KEY1); - System.clearProperty(KEY2); - } - - @Test - void enabledBecauseAnnotationIsNotPresent() { - // no-op - } - - @Test - @Disabled("Only used in a unit test via reflection") - @DisabledIfSystemProperty(named = " ", matches = ENIGMA) - void blankNamedAttribute() { - fail("should be disabled"); - } - - @Test - @Disabled("Only used in a unit test via reflection") - @DisabledIfSystemProperty(named = KEY1, matches = " ") - void blankMatchesAttribute() { - fail("should be disabled"); - } - - @Test - @DisabledIfSystemProperty(named = KEY1, matches = ENIGMA, disabledReason = "That's an enigma") - void disabledBecauseSystemPropertyMatchesExactly() { - fail("should be disabled"); - } - - @Test - @DisabledIfSystemProperty(named = KEY1, matches = BOGUS) - @CustomDisabled - void disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() { - fail("should be disabled"); - } - - @Test - @DisabledIfSystemProperty(named = KEY1, matches = ".*e.+gma$") - void disabledBecauseSystemPropertyMatchesPattern() { - fail("should be disabled"); - } - - @Test - @DisabledIfSystemProperty(named = KEY1, matches = BOGUS) - void enabledBecauseSystemPropertyDoesNotMatch() { - assertNotEquals(BOGUS, System.getProperty(KEY1)); - } - - @Test - @DisabledIfSystemProperty(named = BOGUS, matches = "doesn't matter") - void enabledBecauseSystemPropertyDoesNotExist() { - assertNull(System.getProperty(BOGUS)); - } - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @DisabledIfSystemProperty(named = KEY2, matches = ENIGMA) - @interface CustomDisabled { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java deleted file mode 100644 index 35fa2e52..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DisabledOnJreCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link DisabledOnJreIntegrationTests}. - * - * @since 5.1 - */ -class DisabledOnJreConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new DisabledOnJreCondition(); - } - - @Override - protected Class getTestClass() { - return DisabledOnJreIntegrationTests.class; - } - - /** - * @see DisabledOnJreIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@DisabledOnJre is not present"); - } - - /** - * @see DisabledOnJreIntegrationTests#missingJreDeclaration() - */ - @Test - void missingJreDeclaration() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("You must declare at least one JRE"); - } - - /** - * @see DisabledOnJreIntegrationTests#disabledOnAllJavaVersions() - */ - @Test - void disabledOnAllJavaVersions() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(true); - assertCustomDisabledReasonIs("Disabled on every JRE"); - } - - /** - * @see DisabledOnJreIntegrationTests#java8() - */ - @Test - void java8() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava8()); - } - - /** - * @see DisabledOnJreIntegrationTests#java9() - */ - @Test - void java9() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava9()); - } - - /** - * @see DisabledOnJreIntegrationTests#java10() - */ - @Test - void java10() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava10()); - } - - /** - * @see DisabledOnJreIntegrationTests#java11() - */ - @Test - void java11() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava11()); - } - - /** - * @see DisabledOnJreIntegrationTests#java12() - */ - @Test - void java12() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava12()); - } - - /** - * @see DisabledOnJreIntegrationTests#java13() - */ - @Test - void java13() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava13()); - } - - /** - * @see DisabledOnJreIntegrationTests#java14() - */ - @Test - void java14() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava14()); - } - - /** - * @see DisabledOnJreIntegrationTests#java15() - */ - @Test - void java15() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava15()); - } - - /** - * @see DisabledOnJreIntegrationTests#java16() - */ - @Test - void java16() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava16()); - } - - /** - * @see DisabledOnJreIntegrationTests#java17() - */ - @Test - void java17() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava17()); - } - - /** - * @see DisabledOnJreIntegrationTests#java18() - */ - @Test - void java18() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava18()); - } - - /** - * @see DisabledOnJreIntegrationTests#java19() - */ - @Test - void java19() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava19()); - } - - /** - * @see DisabledOnJreIntegrationTests#java20() - */ - @Test - void java20() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava20()); - } - - /** - * @see DisabledOnJreIntegrationTests#java21() - */ - @Test - void java21() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava21()); - } - - /** - * @see DisabledOnJreIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertDisabledOnCurrentJreIf( - !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() - || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); - } - - private void assertDisabledOnCurrentJreIf(boolean condition) { - if (condition) { - assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); - } - else { - assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java deleted file mode 100644 index 12ece2e5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; -import static org.junit.jupiter.api.condition.JRE.JAVA_10; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; -import static org.junit.jupiter.api.condition.JRE.JAVA_12; -import static org.junit.jupiter.api.condition.JRE.JAVA_13; -import static org.junit.jupiter.api.condition.JRE.JAVA_14; -import static org.junit.jupiter.api.condition.JRE.JAVA_15; -import static org.junit.jupiter.api.condition.JRE.JAVA_16; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.JAVA_20; -import static org.junit.jupiter.api.condition.JRE.JAVA_21; -import static org.junit.jupiter.api.condition.JRE.JAVA_8; -import static org.junit.jupiter.api.condition.JRE.JAVA_9; -import static org.junit.jupiter.api.condition.JRE.OTHER; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link DisabledOnJre}. - * - * @since 5.1 - */ -class DisabledOnJreIntegrationTests { - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @DisabledOnJre({}) - void missingJreDeclaration() { - } - - @Test - @DisabledOnJre(value = { JAVA_8, JAVA_9, JAVA_10, JAVA_11, JAVA_12, JAVA_13, JAVA_14, JAVA_15, JAVA_16, JAVA_17, - JAVA_18, JAVA_19, JAVA_20, JAVA_21, OTHER }, disabledReason = "Disabled on every JRE") - void disabledOnAllJavaVersions() { - fail("should be disabled"); - } - - @Test - @DisabledOnJre(JAVA_8) - void java8() { - assertFalse(onJava8()); - } - - @Test - @DisabledOnJre(JAVA_9) - void java9() { - assertFalse(onJava9()); - } - - @Test - @DisabledOnJre(JAVA_10) - void java10() { - assertFalse(onJava10()); - } - - @Test - @DisabledOnJre(JAVA_11) - void java11() { - assertFalse(onJava11()); - } - - @Test - @DisabledOnJre(JAVA_12) - void java12() { - assertFalse(onJava12()); - } - - @Test - @DisabledOnJre(JAVA_13) - void java13() { - assertFalse(onJava13()); - } - - @Test - @DisabledOnJre(JAVA_14) - void java14() { - assertFalse(onJava14()); - } - - @Test - @DisabledOnJre(JAVA_15) - void java15() { - assertFalse(onJava15()); - } - - @Test - @DisabledOnJre(JAVA_16) - void java16() { - assertFalse(onJava16()); - } - - @Test - @DisabledOnJre(JAVA_17) - void java17() { - assertFalse(onJava17()); - } - - @Test - @DisabledOnJre(JAVA_18) - void java18() { - assertFalse(onJava18()); - } - - @Test - @DisabledOnJre(JAVA_19) - void java19() { - assertFalse(onJava19()); - } - - @Test - @DisabledOnJre(JAVA_20) - void java20() { - assertFalse(onJava20()); - } - - @Test - @DisabledOnJre(JAVA_21) - void java21() { - assertFalse(onJava21()); - } - - @Test - @DisabledOnJre(OTHER) - void other() { - assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java deleted file mode 100644 index bab7f53d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DisabledOnOsCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link DisabledOnOsIntegrationTests}. - * - * @since 5.1 - */ -class DisabledOnOsConditionTests extends AbstractExecutionConditionTests { - - private static final String OS_NAME = System.getProperty("os.name"); - private static final String ARCH = System.getProperty("os.arch"); - - @Override - protected ExecutionCondition getExecutionCondition() { - return new DisabledOnOsCondition(); - } - - @Override - protected Class getTestClass() { - return DisabledOnOsIntegrationTests.class; - } - - /** - * @see DisabledOnOsIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@DisabledOnOs is not present"); - } - - /** - * @see DisabledOnOsIntegrationTests#missingOsAndArchitectureDeclaration() - */ - @Test - void missingOsAndArchitectureDeclaration() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("You must declare at least one OS or architecture"); - } - - /** - * @see DisabledOnOsIntegrationTests#disabledOnEveryOs() - */ - @Test - void disabledOnEveryOs() { - evaluateCondition(); - assertDisabled(); - assertReasonContains(String.format("Disabled on operating system: %s ==> Disabled on every OS", OS_NAME)); - } - - /** - * @see DisabledOnOsIntegrationTests#aix() - */ - @Test - void aix() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onAix()); - } - - /** - * @see DisabledOnOsIntegrationTests#freebsd() - */ - @Test - void freebsd() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onFreebsd()); - } - - /** - * @see DisabledOnOsIntegrationTests#linux() - */ - @Test - void linux() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onLinux()); - } - - /** - * @see DisabledOnOsIntegrationTests#macOs() - */ - @Test - void macOs() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onMac()); - } - - /** - * @see DisabledOnOsIntegrationTests#macOsWithComposedAnnotation() - */ - @Test - void macOsWithComposedAnnotation() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onMac()); - } - - /** - * @see DisabledOnOsIntegrationTests#openbsd() - */ - @Test - void openbsd() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onOpenbsd()); - } - - /** - * @see DisabledOnOsIntegrationTests#windows() - */ - @Test - void windows() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onWindows()); - } - - /** - * @see DisabledOnOsIntegrationTests#solaris() - */ - @Test - void solaris() { - evaluateCondition(); - assertDisabledOnCurrentOsIf(onSolaris()); - } - - /** - * @see DisabledOnOsIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertDisabledOnCurrentOsIf( - !(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows())); - } - - /** - * @see DisabledOnOsIntegrationTests#architectureX86_64() - */ - @Test - void architectureX86_64() { - evaluateCondition(); - assertDisabledOnCurrentArchitectureIf(onArchitecture("x86_64")); - } - - /** - * @see DisabledOnOsIntegrationTests#architectureAarch64() - */ - @Test - void architectureAarch64() { - evaluateCondition(); - assertDisabledOnCurrentArchitectureIf(onArchitecture("aarch64")); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureX86_64WithMacOs() - */ - @Test - void architectureX86_64WithMacOs() { - evaluateCondition(); - assertDisabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("x86_64")); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureX86_64WithWindows() - */ - @Test - void architectureX86_64WithWindows() { - evaluateCondition(); - assertDisabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("x86_64")); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureX86_64WithLinux() - */ - @Test - void architectureX86_64WithLinux() { - evaluateCondition(); - assertDisabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("x86_64")); - } - - /** - * @see EnabledOnOsIntegrationTests#aarch64WithMacOs() - */ - @Test - void aarch64WithMacOs() { - evaluateCondition(); - assertDisabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("aarch64")); - } - - /** - * @see EnabledOnOsIntegrationTests#aarch64WithWindows() - */ - @Test - void aarch64WithWindows() { - evaluateCondition(); - assertDisabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("aarch64")); - } - - /** - * @see EnabledOnOsIntegrationTests#aarch64WithLinux() - */ - @Test - void aarch64WithLinux() { - evaluateCondition(); - assertDisabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("aarch64")); - } - - private void assertDisabledOnCurrentOsIf(boolean condition) { - if (condition) { - assertDisabled(); - assertReasonContains(String.format("Disabled on operating system: %s", OS_NAME)); - } - else { - assertEnabled(); - assertReasonContains(String.format("Enabled on operating system: %s", OS_NAME)); - } - } - - private void assertDisabledOnCurrentArchitectureIf(boolean condition) { - if (condition) { - assertDisabled(); - assertReasonContains(String.format("Disabled on architecture: %s", ARCH)); - } - else { - assertEnabled(); - assertReasonContains(String.format("Enabled on architecture: %s", ARCH)); - } - } - - private void assertDisabledOnCurrentOsAndArchitectureIf(boolean condition) { - if (condition) { - assertDisabled(); - assertReasonContains(String.format("Disabled on operating system: %s (%s)", OS_NAME, ARCH)); - } - else { - assertEnabled(); - assertReasonContains(String.format("Enabled on operating system: %s (%s)", OS_NAME, ARCH)); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java deleted file mode 100644 index d5a27e7e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; -import static org.junit.jupiter.api.condition.OS.AIX; -import static org.junit.jupiter.api.condition.OS.FREEBSD; -import static org.junit.jupiter.api.condition.OS.LINUX; -import static org.junit.jupiter.api.condition.OS.MAC; -import static org.junit.jupiter.api.condition.OS.OPENBSD; -import static org.junit.jupiter.api.condition.OS.OTHER; -import static org.junit.jupiter.api.condition.OS.SOLARIS; -import static org.junit.jupiter.api.condition.OS.WINDOWS; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link DisabledOnOs}. - * - * @since 5.1 - */ -class DisabledOnOsIntegrationTests { - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @DisabledOnOs({}) - void missingOsAndArchitectureDeclaration() { - } - - @Test - @DisabledOnOs(value = { AIX, FREEBSD, LINUX, MAC, OPENBSD, WINDOWS, SOLARIS, - OTHER }, disabledReason = "Disabled on every OS") - void disabledOnEveryOs() { - fail("should be disabled"); - } - - @Test - @DisabledOnOs(AIX) - void aix() { - assertFalse(onAix()); - } - - @Test - @DisabledOnOs(FREEBSD) - void freebsd() { - assertFalse(onFreebsd()); - } - - @Test - @DisabledOnOs(LINUX) - void linux() { - assertFalse(onLinux()); - } - - @Test - @DisabledOnOs(MAC) - void macOs() { - assertFalse(onMac()); - } - - @Test - @DisabledOnMac - void macOsWithComposedAnnotation() { - assertFalse(onMac()); - } - - @Test - @DisabledOnOs(OPENBSD) - void openbsd() { - assertFalse(onOpenbsd()); - } - - @Test - @DisabledOnOs(WINDOWS) - void windows() { - assertFalse(onWindows()); - } - - @Test - @DisabledOnOs(SOLARIS) - void solaris() { - assertFalse(onSolaris()); - } - - @Test - @DisabledOnOs(OTHER) - void other() { - assertTrue(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows()); - } - - @Test - @DisabledOnOs(architectures = "x86_64") - void architectureX86_64() { - assertFalse(onArchitecture("x_86_64")); - } - - @Test - @DisabledOnOs(architectures = "aarch64") - void architectureAarch64() { - assertFalse(onArchitecture("aarch64")); - } - - @Test - @DisabledOnOs(value = MAC, architectures = "x86_64") - void architectureX86_64WithMacOs() { - assertFalse(onMac() && onArchitecture("x_86_64")); - } - - @Test - @DisabledOnOs(value = WINDOWS, architectures = "x86_64") - void architectureX86_64WithWindows() { - assertFalse(onWindows() && onArchitecture("x86_64")); - } - - @Test - @DisabledOnOs(value = LINUX, architectures = "x86_64") - void architectureX86_64WithLinux() { - assertFalse(onLinux() && onArchitecture("x86_64")); - } - - @Test - @DisabledOnOs(value = MAC, architectures = "aarch64") - void aarch64WithMacOs() { - assertFalse(onMac() && onArchitecture("aarch64")); - } - - @Test - @DisabledOnOs(value = WINDOWS, architectures = "aarch64") - void aarch64WithWindows() { - assertFalse(onWindows() && onArchitecture("aarch64")); - } - - @Test - @DisabledOnOs(value = LINUX, architectures = "aarch64") - void aarch64WithLinux() { - assertFalse(onLinux() && onArchitecture("aarch64")); - } - // ------------------------------------------------------------------------- - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @DisabledOnOs(MAC) - @interface DisabledOnMac { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java deleted file mode 100644 index f8946952..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link EnabledForJreRange}. - * - *

Note that test method names MUST match the test method names in - * {@link EnabledForJreRangeIntegrationTests}. - * - * @since 5.6 - */ -class EnabledForJreRangeConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new EnabledForJreRangeCondition(); - } - - @Override - protected Class getTestClass() { - return EnabledForJreRangeIntegrationTests.class; - } - - /** - * @see EnabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@EnabledForJreRange is not present"); - } - - /** - * @see EnabledForJreRangeIntegrationTests#defaultValues() - */ - @Test - void defaultValues() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(this::evaluateCondition)// - .withMessageContaining("You must declare a non-default value for min or max in @EnabledForJreRange"); - } - - /** - * @see EnabledForJreRangeIntegrationTests#java17() - */ - @Test - void java17() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava17()); - } - - /** - * @see EnabledForJreRangeIntegrationTests#java18to19() - */ - @Test - void java18to19() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava18() || onJava19()); - } - - /** - * @see EnabledForJreRangeIntegrationTests#javaMax18() - */ - @Test - void javaMax18() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() - || onJava14() || onJava15() || onJava16() || onJava17() || onJava18()); - } - - /** - * @see EnabledForJreRangeIntegrationTests#javaMin18() - */ - @Test - void javaMin18() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(!(onJava17())); - } - - /** - * @see EnabledForJreRangeIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertEnabledOnCurrentJreIf( - !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() - || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); - } - - private void assertEnabledOnCurrentJreIf(boolean condition) { - if (condition) { - assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); - } - else { - assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java deleted file mode 100644 index 5007240e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.OTHER; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link EnabledForJreRange}. - * - * @since 5.6 - */ -class EnabledForJreRangeIntegrationTests { - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @EnabledForJreRange - void defaultValues() { - fail("should result in a configuration exception"); - } - - @Test - @EnabledForJreRange(min = JAVA_17, max = JAVA_17) - void java17() { - assertTrue(onJava17()); - } - - @Test - @EnabledForJreRange(min = JAVA_18, max = JAVA_19) - void java18to19() { - assertTrue(onJava18() || onJava19()); - assertFalse(onJava17()); - } - - @Test - @EnabledForJreRange(max = JAVA_18) - void javaMax18() { - assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18()); - assertFalse(onJava19()); - } - - @Test - @EnabledForJreRange(min = JAVA_18) - void javaMin18() { - assertTrue(onJava18() || onJava19() || onJava20() || onJava21()); - assertFalse(onJava17()); - } - - @Test - @EnabledForJreRange(min = OTHER, max = OTHER) - void other() { - assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java deleted file mode 100644 index ec249667..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; - -/** - * Unit tests for {@link EnabledIf}. - * - *

Note that test method names MUST match the test method names in - * {@link EnabledIfIntegrationTests}. - * - * @since 5.7 - */ -public class EnabledIfConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new EnabledIfCondition(); - } - - @Override - protected Class getTestClass() { - return EnabledIfIntegrationTests.class; - } - - /** - * @see EnabledIfIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@EnabledIf is not present"); - } - - /** - * @see EnabledIfIntegrationTests#enabledBecauseStaticConditionMethodReturnsTrue() - */ - @Test - void enabledBecauseStaticConditionMethodReturnsTrue() { - evaluateCondition(); - assertEnabled(); - assertReasonContains(""" - @EnabledIf("staticMethodThatReturnsTrue") evaluated to true"""); - } - - /** - * @see EnabledIfIntegrationTests#disabledBecauseStaticConditionMethodReturnsFalse() - */ - @Test - void disabledBecauseStaticConditionMethodReturnsFalse() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("Disabled for some reason"); - } - - /** - * @see EnabledIfIntegrationTests#enabledBecauseConditionMethodReturnsTrue() - */ - @Test - void enabledBecauseConditionMethodReturnsTrue() { - evaluateCondition(); - assertEnabled(); - assertReasonContains(""" - @EnabledIf("methodThatReturnsTrue") evaluated to true"""); - } - - /** - * @see EnabledIfIntegrationTests#disabledBecauseConditionMethodReturnsFalse() - */ - @Test - void disabledBecauseConditionMethodReturnsFalse() { - evaluateCondition(); - assertDisabled(); - assertReasonContains(""" - @EnabledIf("methodThatReturnsFalse") evaluated to false"""); - } - - /** - * @see EnabledIfIntegrationTests.ExternalConditionMethod#enabledBecauseStaticExternalConditionMethodReturnsTrue() - */ - @Test - void enabledBecauseStaticExternalConditionMethodReturnsTrue() { - evaluateCondition(); - assertEnabled(); - assertReasonContains(""" - @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") evaluated to true"""); - } - - /** - * @see EnabledIfIntegrationTests.ExternalConditionMethod#disabledBecauseStaticExternalConditionMethodReturnsFalse() - */ - @Test - void disabledBecauseStaticExternalConditionMethodReturnsFalse() { - evaluateCondition(); - assertDisabled(); - assertReasonContains( - """ - @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") evaluated to false"""); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java deleted file mode 100644 index c90c28c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.BOGUS; -import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.ENIGMA; -import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY1; -import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY2; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link EnabledIfEnvironmentVariableCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link EnabledIfEnvironmentVariableIntegrationTests}. - * - * @since 5.1 - */ -class EnabledIfEnvironmentVariableConditionTests extends AbstractExecutionConditionTests { - - /** - * Stubbed subclass of {@link EnabledIfEnvironmentVariableCondition}. - */ - private ExecutionCondition condition = new EnabledIfEnvironmentVariableCondition() { - - @Override - protected String getEnvironmentVariable(String name) { - return KEY1.equals(name) ? ENIGMA : null; - } - }; - - @Override - protected ExecutionCondition getExecutionCondition() { - return condition; - } - - @Override - protected Class getTestClass() { - return EnabledIfEnvironmentVariableIntegrationTests.class; - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains( - "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#blankNamedAttribute() - */ - @Test - void blankNamedAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#blankMatchesAttribute() - */ - @Test - void blankMatchesAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableMatchesExactly() - */ - @Test - void enabledBecauseEnvironmentVariableMatchesExactly() { - evaluateCondition(); - assertEnabled(); - assertReasonContains( - "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseBothEnvironmentVariablesMatchExactly() - */ - @Test - void enabledBecauseBothEnvironmentVariablesMatchExactly() { - this.condition = new EnabledIfEnvironmentVariableCondition() { - - @Override - protected String getEnvironmentVariable(String name) { - return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; - } - }; - - evaluateCondition(); - assertEnabled(); - assertReasonContains( - "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableMatchesPattern() - */ - @Test - void enabledBecauseEnvironmentVariableMatchesPattern() { - evaluateCondition(); - assertEnabled(); - assertReasonContains( - "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableDoesNotMatch() - */ - @Test - void disabledBecauseEnvironmentVariableDoesNotMatch() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("does not match regular expression"); - assertCustomDisabledReasonIs("Not bogus"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() - */ - @Test - void disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() { - this.condition = new EnabledIfEnvironmentVariableCondition() { - - @Override - protected String getEnvironmentVariable(String name) { - return KEY1.equals(name) ? ENIGMA : KEY2.equals(name) ? BOGUS : null; - } - }; - - evaluateCondition(); - assertDisabled(); - assertReasonContains("does not match regular expression"); - } - - /** - * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableDoesNotExist() - */ - @Test - void disabledBecauseEnvironmentVariableDoesNotExist() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("does not exist"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java deleted file mode 100644 index 2e0224c3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link EnabledIfEnvironmentVariable}. - * - * @since 5.1 - */ -@Disabled("Disabled since the required environment variables are not set") -// Tests (except those with intentional configuration errors) will pass if you set -// the following environment variables: -// EnabledIfEnvironmentVariableTests.key1 = enigma -// EnabledIfEnvironmentVariableTests.key2 = enigma -class EnabledIfEnvironmentVariableIntegrationTests { - - static final String KEY1 = "EnabledIfEnvironmentVariableTests.key1"; - static final String KEY2 = "EnabledIfEnvironmentVariableTests.key2"; - static final String ENIGMA = "enigma"; - static final String PUZZLE = "puzzle"; - static final String BOGUS = "bogus"; - - @Test - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @EnabledIfEnvironmentVariable(named = " ", matches = ENIGMA) - void blankNamedAttribute() { - } - - @Test - @EnabledIfEnvironmentVariable(named = KEY1, matches = " ") - void blankMatchesAttribute() { - } - - @Test - @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) - void enabledBecauseEnvironmentVariableMatchesExactly() { - assertEquals(ENIGMA, System.getenv(KEY1)); - } - - @Test - @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) - @EnabledIfEnvironmentVariable(named = KEY2, matches = ENIGMA) - void enabledBecauseBothEnvironmentVariablesMatchExactly() { - assertEquals(ENIGMA, System.getenv(KEY1)); - assertEquals(ENIGMA, System.getenv(KEY2)); - } - - @Test - @EnabledIfEnvironmentVariable(named = KEY1, matches = ".*e.+ma$") - void enabledBecauseEnvironmentVariableMatchesPattern() { - assertEquals(ENIGMA, System.getenv(KEY1)); - } - - @Test - @EnabledIfEnvironmentVariable(named = KEY1, matches = BOGUS, disabledReason = "Not bogus") - void disabledBecauseEnvironmentVariableDoesNotMatch() { - fail("should be disabled"); - } - - @Test - @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) - @CustomEnabled - void disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() { - fail("should be disabled"); - } - - @Test - @EnabledIfEnvironmentVariable(named = BOGUS, matches = "doesn't matter") - void disabledBecauseEnvironmentVariableDoesNotExist() { - fail("should be disabled"); - } - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @EnabledIfEnvironmentVariable(named = KEY2, matches = PUZZLE) - @interface CustomEnabled { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java deleted file mode 100644 index 60eac3ad..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link EnabledIf}. - * - * @since 5.7 - */ -public class EnabledIfIntegrationTests { - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @EnabledIf("staticMethodThatReturnsTrue") - void enabledBecauseStaticConditionMethodReturnsTrue() { - } - - @Test - @EnabledIf(value = "staticMethodThatReturnsFalse", disabledReason = "Disabled for some reason") - void disabledBecauseStaticConditionMethodReturnsFalse() { - fail("Should be disabled"); - } - - @Test - @EnabledIf("methodThatReturnsTrue") - void enabledBecauseConditionMethodReturnsTrue() { - } - - @Test - @EnabledIf("methodThatReturnsFalse") - void disabledBecauseConditionMethodReturnsFalse() { - fail("Should be disabled"); - } - - @Test - @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") - void enabledBecauseStaticExternalConditionMethodReturnsTrue() { - } - - @Test - @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") - void disabledBecauseStaticExternalConditionMethodReturnsFalse() { - fail("Should be disabled"); - } - - @Nested - @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") - class ConditionallyDisabledClass { - - @Test - void disabledBecauseConditionMethodReturnsFalse() { - fail("Should be disabled"); - } - - } - - // ------------------------------------------------------------------------- - - @SuppressWarnings("unused") - private static boolean staticMethodThatReturnsTrue() { - return true; - } - - @SuppressWarnings("unused") - private static boolean staticMethodThatReturnsFalse() { - return false; - } - - @SuppressWarnings("unused") - private boolean methodThatReturnsTrue() { - return true; - } - - @SuppressWarnings("unused") - private boolean methodThatReturnsFalse() { - return false; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java deleted file mode 100644 index 399687dd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link EnabledIfSystemPropertyCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link EnabledIfSystemPropertyIntegrationTests}. - * - * @since 5.1 - */ -class EnabledIfSystemPropertyConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new EnabledIfSystemPropertyCondition(); - } - - @Override - protected Class getTestClass() { - return EnabledIfSystemPropertyIntegrationTests.class; - } - - @BeforeAll - static void setSystemProperties() { - EnabledIfSystemPropertyIntegrationTests.setSystemProperties(); - } - - @AfterAll - static void clearSystemProperties() { - EnabledIfSystemPropertyIntegrationTests.clearSystemProperties(); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#blankNamedAttribute() - */ - @Test - void blankNamedAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'named' attribute must not be blank"); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#blankMatchesAttribute() - */ - @Test - void blankMatchesAttribute() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("The 'matches' attribute must not be blank"); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyMatchesExactly() - */ - @Test - void enabledBecauseSystemPropertyMatchesExactly() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseBothSystemPropertiesMatchExactly() - */ - @Test - void enabledBecauseBothSystemPropertiesMatchExactly() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyMatchesPattern() - */ - @Test - void enabledBecauseSystemPropertyMatchesPattern() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyDoesNotMatch() - */ - @Test - void disabledBecauseSystemPropertyDoesNotMatch() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("does not match regular expression"); - assertCustomDisabledReasonIs("Not bogus"); - } - - @Test - void disabledBecauseSystemPropertyForComposedAnnotationDoesNotMatch() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("does not match regular expression"); - } - - /** - * @see EnabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyDoesNotExist() - */ - @Test - void disabledBecauseSystemPropertyDoesNotExist() { - evaluateCondition(); - assertDisabled(); - assertReasonContains("does not exist"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java deleted file mode 100644 index 2a6010e3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link EnabledIfSystemProperty}. - * - * @since 5.1 - */ -class EnabledIfSystemPropertyIntegrationTests { - - private static final String KEY1 = "EnabledIfSystemPropertyTests.key1"; - private static final String KEY2 = "EnabledIfSystemPropertyTests.key2"; - private static final String ENIGMA = "enigma"; - private static final String BOGUS = "bogus"; - - @BeforeAll - static void setSystemProperties() { - System.setProperty(KEY1, ENIGMA); - System.setProperty(KEY2, ENIGMA); - } - - @AfterAll - static void clearSystemProperties() { - System.clearProperty(KEY1); - System.clearProperty(KEY2); - } - - @Test - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @EnabledIfSystemProperty(named = " ", matches = ENIGMA) - void blankNamedAttribute() { - fail("should be disabled"); - } - - @Test - @Disabled("Only used in a unit test via reflection") - @EnabledIfSystemProperty(named = KEY1, matches = " ") - void blankMatchesAttribute() { - fail("should be disabled"); - } - - @Test - @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) - void enabledBecauseSystemPropertyMatchesExactly() { - assertEquals(ENIGMA, System.getProperty(KEY1)); - } - - @Test - @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) - @EnabledIfSystemProperty(named = KEY2, matches = ENIGMA) - void enabledBecauseBothSystemPropertiesMatchExactly() { - assertEquals(ENIGMA, System.getProperty(KEY1)); - assertEquals(ENIGMA, System.getProperty(KEY2)); - } - - @Test - @EnabledIfSystemProperty(named = KEY1, matches = ".*en.+gma$") - void enabledBecauseSystemPropertyMatchesPattern() { - assertEquals(ENIGMA, System.getProperty(KEY1)); - } - - @Test - @EnabledIfSystemProperty(named = KEY1, matches = BOGUS, disabledReason = "Not bogus") - void disabledBecauseSystemPropertyDoesNotMatch() { - fail("should be disabled"); - } - - @Test - @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) - @CustomEnabled - void disabledBecauseSystemPropertyForComposedAnnotationDoesNotMatch() { - fail("should be disabled"); - } - - @Test - @EnabledIfSystemProperty(named = BOGUS, matches = "doesn't matter") - void disabledBecauseSystemPropertyDoesNotExist() { - fail("should be disabled"); - } - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @EnabledIfSystemProperty(named = KEY2, matches = BOGUS) - @interface CustomEnabled { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java deleted file mode 100644 index 9c4bb4c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava10; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava11; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava12; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava13; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava14; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava15; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava16; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava17; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava18; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava19; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava20; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava21; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava8; -import static org.junit.jupiter.api.condition.EnabledOnJreIntegrationTests.onJava9; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link EnabledOnJreCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link EnabledOnJreIntegrationTests}. - * - * @since 5.1 - */ -class EnabledOnJreConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new EnabledOnJreCondition(); - } - - @Override - protected Class getTestClass() { - return EnabledOnJreIntegrationTests.class; - } - - /** - * @see EnabledOnJreIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@EnabledOnJre is not present"); - } - - /** - * @see EnabledOnJreIntegrationTests#missingJreDeclaration() - */ - @Test - void missingJreDeclaration() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("You must declare at least one JRE"); - } - - /** - * @see EnabledOnJreIntegrationTests#enabledOnAllJavaVersions() - */ - @Test - void enabledOnAllJavaVersions() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(true); - } - - /** - * @see EnabledOnJreIntegrationTests#java8() - */ - @Test - void java8() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava8()); - } - - /** - * @see EnabledOnJreIntegrationTests#java9() - */ - @Test - void java9() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava9()); - } - - /** - * @see EnabledOnJreIntegrationTests#java10() - */ - @Test - void java10() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava10()); - } - - /** - * @see EnabledOnJreIntegrationTests#java11() - */ - @Test - void java11() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava11()); - } - - /** - * @see EnabledOnJreIntegrationTests#java12() - */ - @Test - void java12() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava12()); - } - - /** - * @see EnabledOnJreIntegrationTests#java13() - */ - @Test - void java13() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava13()); - } - - /** - * @see EnabledOnJreIntegrationTests#java14() - */ - @Test - void java14() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava14()); - } - - /** - * @see EnabledOnJreIntegrationTests#java15() - */ - @Test - void java15() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava15()); - } - - /** - * @see EnabledOnJreIntegrationTests#java16() - */ - @Test - void java16() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava16()); - } - - /** - * @see EnabledOnJreIntegrationTests#java17() - */ - @Test - void java17() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava17()); - } - - /** - * @see EnabledOnJreIntegrationTests#java18() - */ - @Test - void java18() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava18()); - } - - /** - * @see EnabledOnJreIntegrationTests#java19() - */ - @Test - void java19() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava19()); - } - - /** - * @see EnabledOnJreIntegrationTests#java20() - */ - @Test - void java20() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava20()); - } - - /** - * @see EnabledOnJreIntegrationTests#java21() - */ - @Test - void java21() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava21()); - } - - /** - * @see EnabledOnJreIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertEnabledOnCurrentJreIf( - !(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() - || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21())); - assertCustomDisabledReasonIs("Disabled on almost every JRE"); - } - - private void assertEnabledOnCurrentJreIf(boolean condition) { - if (condition) { - assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); - } - else { - assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java deleted file mode 100644 index a7a41448..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.condition.JRE.JAVA_10; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; -import static org.junit.jupiter.api.condition.JRE.JAVA_12; -import static org.junit.jupiter.api.condition.JRE.JAVA_13; -import static org.junit.jupiter.api.condition.JRE.JAVA_14; -import static org.junit.jupiter.api.condition.JRE.JAVA_15; -import static org.junit.jupiter.api.condition.JRE.JAVA_16; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.JAVA_20; -import static org.junit.jupiter.api.condition.JRE.JAVA_21; -import static org.junit.jupiter.api.condition.JRE.JAVA_8; -import static org.junit.jupiter.api.condition.JRE.JAVA_9; -import static org.junit.jupiter.api.condition.JRE.OTHER; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link EnabledOnJre}. - * - * @since 5.1 - */ -class EnabledOnJreIntegrationTests { - - private static final String JAVA_VERSION = System.getProperty("java.version"); - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @EnabledOnJre({}) - void missingJreDeclaration() { - } - - @Test - @EnabledOnJre({ JAVA_8, JAVA_9, JAVA_10, JAVA_11, JAVA_12, JAVA_13, JAVA_14, JAVA_15, JAVA_16, JAVA_17, JAVA_18, - JAVA_19, JAVA_20, JAVA_21, OTHER }) - void enabledOnAllJavaVersions() { - } - - @Test - @EnabledOnJre(JAVA_8) - void java8() { - assertTrue(onJava8()); - } - - @Test - @EnabledOnJre(JAVA_9) - void java9() { - assertTrue(onJava9()); - } - - @Test - @EnabledOnJre(JAVA_10) - void java10() { - assertTrue(onJava10()); - } - - @Test - @EnabledOnJre(JAVA_11) - void java11() { - assertTrue(onJava11()); - } - - @Test - @EnabledOnJre(JAVA_12) - void java12() { - assertTrue(onJava12()); - } - - @Test - @EnabledOnJre(JAVA_13) - void java13() { - assertTrue(onJava13()); - } - - @Test - @EnabledOnJre(JAVA_14) - void java14() { - assertTrue(onJava14()); - } - - @Test - @EnabledOnJre(JAVA_15) - void java15() { - assertTrue(onJava15()); - } - - @Test - @EnabledOnJre(JAVA_16) - void java16() { - assertTrue(onJava16()); - } - - @Test - @EnabledOnJre(JAVA_17) - void java17() { - assertTrue(onJava17()); - } - - @Test - @EnabledOnJre(JAVA_18) - void java18() { - assertTrue(onJava18()); - } - - @Test - @EnabledOnJre(JAVA_19) - void java19() { - assertTrue(onJava19()); - } - - @Test - @EnabledOnJre(JAVA_20) - void java20() { - assertTrue(onJava20()); - } - - @Test - @EnabledOnJre(JAVA_21) - void java21() { - assertTrue(onJava21()); - } - - @Test - @EnabledOnJre(value = OTHER, disabledReason = "Disabled on almost every JRE") - void other() { - assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); - } - - static boolean onJava8() { - return JAVA_VERSION.startsWith("1.8"); - } - - static boolean onJava9() { - return JAVA_VERSION.startsWith("9"); - } - - static boolean onJava10() { - return JAVA_VERSION.startsWith("10"); - } - - static boolean onJava11() { - return JAVA_VERSION.startsWith("11"); - } - - static boolean onJava12() { - return JAVA_VERSION.startsWith("12"); - } - - static boolean onJava13() { - return JAVA_VERSION.startsWith("13"); - } - - static boolean onJava14() { - return JAVA_VERSION.startsWith("14"); - } - - static boolean onJava15() { - return JAVA_VERSION.startsWith("15"); - } - - static boolean onJava16() { - return JAVA_VERSION.startsWith("16"); - } - - static boolean onJava17() { - return JAVA_VERSION.startsWith("17"); - } - - static boolean onJava18() { - return JAVA_VERSION.startsWith("18"); - } - - static boolean onJava19() { - return JAVA_VERSION.startsWith("19"); - } - - static boolean onJava20() { - return JAVA_VERSION.startsWith("20"); - } - - static boolean onJava21() { - return JAVA_VERSION.startsWith("21"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java deleted file mode 100644 index 267e5e69..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; -import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link EnabledOnOsCondition}. - * - *

Note that test method names MUST match the test method names in - * {@link EnabledOnOsIntegrationTests}. - * - * @since 5.1 - */ -class EnabledOnOsConditionTests extends AbstractExecutionConditionTests { - - private static final String OS_NAME = System.getProperty("os.name"); - private static final String ARCH = System.getProperty("os.arch"); - - @Override - protected ExecutionCondition getExecutionCondition() { - return new EnabledOnOsCondition(); - } - - @Override - protected Class getTestClass() { - return EnabledOnOsIntegrationTests.class; - } - - /** - * @see EnabledOnOsIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@EnabledOnOs is not present"); - } - - /** - * @see EnabledOnOsIntegrationTests#missingOsAndArchitectureDeclaration() - */ - @Test - void missingOsAndArchitectureDeclaration() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("You must declare at least one OS or architecture"); - } - - /** - * @see EnabledOnOsIntegrationTests#enabledOnEveryOs() - */ - @Test - void enabledOnEveryOs() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(true); - } - - /** - * @see EnabledOnOsIntegrationTests#aix() - */ - @Test - void aix() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onAix()); - } - - /** - * @see EnabledOnOsIntegrationTests#freebsd() - */ - @Test - void freebsd() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onFreebsd()); - } - - /** - * @see EnabledOnOsIntegrationTests#linux() - */ - @Test - void linux() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onLinux()); - } - - /** - * @see EnabledOnOsIntegrationTests#macOs() - */ - @Test - void macOs() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onMac()); - } - - /** - * @see EnabledOnOsIntegrationTests#macOsWithComposedAnnotation() - */ - @Test - void macOsWithComposedAnnotation() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onMac()); - } - - /** - * @see EnabledOnOsIntegrationTests#openbsd() - */ - @Test - void openbsd() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onOpenbsd()); - } - - /** - * @see EnabledOnOsIntegrationTests#windows() - */ - @Test - void windows() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onWindows()); - } - - /** - * @see EnabledOnOsIntegrationTests#solaris() - */ - @Test - void solaris() { - evaluateCondition(); - assertEnabledOnCurrentOsIf(onSolaris()); - } - - /** - * @see EnabledOnOsIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertEnabledOnCurrentOsIf( - !(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows())); - assertCustomDisabledReasonIs("Disabled on almost every OS"); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureX86_64() - */ - @Test - void architectureX86_64() { - evaluateCondition(); - assertEnabledOnCurrentArchitectureIf(onArchitecture("x86_64")); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureAarch64() - */ - @Test - void architectureAarch64() { - evaluateCondition(); - assertEnabledOnCurrentArchitectureIf(onArchitecture("aarch64")); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureX86_64WithMacOs() - */ - @Test - void architectureX86_64WithMacOs() { - evaluateCondition(); - assertEnabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("x86_64")); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureX86_64WithWindows() - */ - @Test - void architectureX86_64WithWindows() { - evaluateCondition(); - assertEnabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("x86_64")); - } - - /** - * @see EnabledOnOsIntegrationTests#architectureX86_64WithLinux() - */ - @Test - void architectureX86_64WithLinux() { - evaluateCondition(); - assertEnabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("x86_64")); - } - - /** - * @see EnabledOnOsIntegrationTests#aarch64WithMacOs() - */ - @Test - void aarch64WithMacOs() { - evaluateCondition(); - assertEnabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("aarch64")); - } - - /** - * @see EnabledOnOsIntegrationTests#aarch64WithWindows() - */ - @Test - void aarch64WithWindows() { - evaluateCondition(); - assertEnabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("aarch64")); - } - - /** - * @see EnabledOnOsIntegrationTests#aarch64WithLinux() - */ - @Test - void aarch64WithLinux() { - evaluateCondition(); - assertEnabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("aarch64")); - } - - private void assertEnabledOnCurrentOsIf(boolean condition) { - if (condition) { - assertEnabled(); - assertReasonContains(String.format("Enabled on operating system: %s", OS_NAME)); - } - else { - assertDisabled(); - assertReasonContains(String.format("Disabled on operating system: %s", OS_NAME)); - } - } - - private void assertEnabledOnCurrentArchitectureIf(boolean condition) { - if (condition) { - assertEnabled(); - assertReasonContains(String.format("Enabled on architecture: %s", ARCH)); - } - else { - assertDisabled(); - assertReasonContains(String.format("Disabled on architecture: %s", ARCH)); - } - } - - private void assertEnabledOnCurrentOsAndArchitectureIf(boolean condition) { - if (condition) { - assertEnabled(); - assertReasonContains(String.format("Enabled on operating system: %s (%s)", OS_NAME, ARCH)); - } - else { - assertDisabled(); - assertReasonContains(String.format("Disabled on operating system: %s (%s)", OS_NAME, ARCH)); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java deleted file mode 100644 index 5e7b15c6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.condition.OS.AIX; -import static org.junit.jupiter.api.condition.OS.FREEBSD; -import static org.junit.jupiter.api.condition.OS.LINUX; -import static org.junit.jupiter.api.condition.OS.MAC; -import static org.junit.jupiter.api.condition.OS.OPENBSD; -import static org.junit.jupiter.api.condition.OS.OTHER; -import static org.junit.jupiter.api.condition.OS.SOLARIS; -import static org.junit.jupiter.api.condition.OS.WINDOWS; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Locale; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link EnabledOnOs}. - * - * @since 5.1 - */ -class EnabledOnOsIntegrationTests { - - private static final String ARCH = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); - private static final String OS_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @EnabledOnOs({}) - void missingOsAndArchitectureDeclaration() { - } - - @Test - @EnabledOnOs({ AIX, FREEBSD, LINUX, MAC, OPENBSD, WINDOWS, SOLARIS, OTHER }) - void enabledOnEveryOs() { - } - - @Test - @EnabledOnOs(AIX) - void aix() { - assertTrue(onAix()); - } - - @Test - @EnabledOnOs(FREEBSD) - void freebsd() { - assertTrue(onFreebsd()); - } - - @Test - @EnabledOnOs(LINUX) - void linux() { - assertTrue(onLinux()); - } - - @Test - @EnabledOnOs(MAC) - void macOs() { - assertTrue(onMac()); - } - - @Test - @EnabledOnMac - void macOsWithComposedAnnotation() { - assertTrue(onMac()); - } - - @Test - @EnabledOnOs(OPENBSD) - void openbsd() { - assertTrue(onOpenbsd()); - } - - @Test - @EnabledOnOs(WINDOWS) - void windows() { - assertTrue(onWindows()); - } - - @Test - @EnabledOnOs(SOLARIS) - void solaris() { - assertTrue(onSolaris()); - } - - @Test - @EnabledOnOs(value = OTHER, disabledReason = "Disabled on almost every OS") - void other() { - assertFalse(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows()); - } - - @Test - @EnabledOnOs(architectures = "x86_64") - void architectureX86_64() { - assertFalse(onArchitecture("x_86_64")); - } - - @Test - @EnabledOnOs(architectures = "aarch64") - void architectureAarch64() { - assertTrue(onArchitecture("aarch64")); - } - - @Test - @EnabledOnOs(value = MAC, architectures = "x86_64") - void architectureX86_64WithMacOs() { - assertTrue(onMac()); - assertTrue(onArchitecture("x86_64")); - } - - @Test - @EnabledOnOs(value = WINDOWS, architectures = "x86_64") - void architectureX86_64WithWindows() { - assertTrue(onWindows()); - assertTrue(onArchitecture("x86_64")); - } - - @Test - @EnabledOnOs(value = LINUX, architectures = "x86_64") - void architectureX86_64WithLinux() { - assertTrue(onLinux()); - assertTrue(onArchitecture("x86_64")); - } - - @Test - @EnabledOnOs(value = MAC, architectures = "aarch64") - void aarch64WithMacOs() { - assertTrue(onMac()); - assertTrue(onArchitecture("aarch64")); - } - - @Test - @EnabledOnOs(value = WINDOWS, architectures = "aarch64") - void aarch64WithWindows() { - assertTrue(onWindows()); - assertTrue(onArchitecture("aarch64")); - } - - @Test - @EnabledOnOs(value = LINUX, architectures = "aarch64") - void aarch64WithLinux() { - assertTrue(onLinux()); - assertTrue(onArchitecture("aarch64")); - } - - static boolean onAix() { - return OS_NAME.contains("aix"); - } - - static boolean onArchitecture(String arch) { - return ARCH.contains(arch); - } - - static boolean onFreebsd() { - return OS_NAME.contains("freebsd"); - } - - static boolean onLinux() { - return OS_NAME.contains("linux"); - } - - static boolean onMac() { - return OS_NAME.contains("mac"); - } - - static boolean onOpenbsd() { - return OS_NAME.contains("openbsd"); - } - - static boolean onSolaris() { - return OS_NAME.contains("solaris"); - } - - static boolean onWindows() { - return OS_NAME.contains("windows"); - } - - // ------------------------------------------------------------------------- - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @EnabledOnOs(MAC) - @interface EnabledOnMac { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java deleted file mode 100644 index f915146c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.JAVA_20; -import static org.junit.jupiter.api.condition.JRE.JAVA_21; -import static org.junit.jupiter.api.condition.JRE.OTHER; - -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link JRE} - * - * @since 5.7 - */ -public class JRETests { - - @Test - @EnabledOnJre(JAVA_17) - void java17() { - assertEquals(JAVA_17, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_18) - void java18() { - assertEquals(JAVA_18, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_19) - void java19() { - assertEquals(JAVA_19, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_20) - void java20() { - assertEquals(JAVA_20, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_21) - void java21() { - assertEquals(JAVA_21, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(OTHER) - void other() { - assertEquals(OTHER, JRE.currentVersion()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java deleted file mode 100644 index 1175c870..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -public class StaticConditionMethods { - - public static boolean returnsTrue() { - return true; - } - - public static boolean returnsFalse() { - return false; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java deleted file mode 100644 index f96e1b63..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.reportEntry; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; - -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; - -public class CloseableResourceIntegrationTests extends AbstractJupiterTestEngineTests { - - @Test - void closesCloseableResourcesInReverseInsertOrder() { - executeTestsForClass(TestCase.class).allEvents().reportingEntryPublished() // - .assertEventsMatchExactly( // - reportEntry(Map.of("3", "closed")), // - reportEntry(Map.of("2", "closed")), // - reportEntry(Map.of("1", "closed"))); - } - - @ExtendWith(ExtensionContextParameterResolver.class) - static class TestCase { - @Test - void closesCloseableResourcesInExtensionContext(ExtensionContext extensionContext) { - ExtensionContext.Store store = extensionContext.getStore(GLOBAL); - store.put("foo", reportEntryOnClose(extensionContext, "1")); - store.put("bar", reportEntryOnClose(extensionContext, "2")); - store.put("baz", reportEntryOnClose(extensionContext, "3")); - } - - private ExtensionContext.Store.CloseableResource reportEntryOnClose(ExtensionContext extensionContext, - String key) { - return () -> extensionContext.publishReportEntry(Map.of(key, "closed")); - } - } - - @Test - void exceptionsDuringCloseAreReportedAsSuppressed() { - executeTestsForClass(ExceptionInCloseableResourceTestCase.class).testEvents() // - .assertEventsMatchLoosely(event( // - test(), // - finishedWithFailure( // - message("Exception in test"), // - suppressed(0, message("Exception in onClose"))))); - } - - @ExtendWith(ThrowingOnCloseExtension.class) - static class ExceptionInCloseableResourceTestCase { - - @Test - void test() { - throw new RuntimeException("Exception in test"); - } - - } - - static class ThrowingOnCloseExtension implements BeforeEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - context.getStore(GLOBAL).put("throwingResource", (ExtensionContext.Store.CloseableResource) () -> { - throw new RuntimeException("Exception in onClose"); - }); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java deleted file mode 100644 index 82a96d16..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * @since 5.9 - */ -public class ExecutableInvokerIntegrationTests extends AbstractJupiterTestEngineTests { - - @Test - void invokeConstructorViaExtensionContext() { - EngineExecutionResults results = executeTestsForClass(ExecuteConstructorTwiceTestCase.class); - - assertEquals(1, results.testEvents().succeeded().count()); - assertEquals(2, ExecuteConstructorTwiceTestCase.constructorInvocations); - } - - @Test - void invokeMethodViaExtensionContext() { - EngineExecutionResults results = executeTestsForClass(ExecuteTestsTwiceTestCase.class); - - assertEquals(1, results.testEvents().succeeded().count()); - assertEquals(2, ExecuteTestsTwiceTestCase.testInvocations); - } - - @ExtendWith(ExecuteTestsTwiceExtension.class) - static class ExecuteTestsTwiceTestCase { - - static int testInvocations = 0; - - @Test - void testWithResolvedParameter(TestInfo testInfo) { - assertNotNull(testInfo); - testInvocations++; - } - - } - - @ExtendWith(ExecuteConstructorTwiceExtension.class) - static class ExecuteConstructorTwiceTestCase { - - static int constructorInvocations = 0; - - public ExecuteConstructorTwiceTestCase(TestInfo testInfo) { - assertNotNull(testInfo); - constructorInvocations++; - } - - @Test - void test() { - - } - - } - - static class ExecuteTestsTwiceExtension implements AfterTestExecutionCallback { - - @Override - public void afterTestExecution(ExtensionContext context) { - context.getExecutableInvoker() // - .invoke(context.getRequiredTestMethod(), context.getRequiredTestInstance()); - } - - } - - static class ExecuteConstructorTwiceExtension implements BeforeAllCallback { - - @Override - public void beforeAll(ExtensionContext context) throws Exception { - context.getExecutableInvoker() // - .invoke(context.getRequiredTestClass().getConstructor(TestInfo.class)); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java deleted file mode 100644 index 1ac243cf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import static java.util.function.Predicate.not; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.platform.commons.util.FunctionUtils.where; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Proxy; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for extension composability in JUnit Jupiter. - * - *

The purpose of these tests is to ensure that a concrete extension - * (a.k.a., the kitchen sink extension) is able to implement all extension - * APIs supported by JUnit Jupiter without any naming conflicts or - * ambiguities with regard to method names or method signatures. - * - * @since 5.0 - * @see KitchenSinkExtension - */ -class ExtensionComposabilityTests { - - @Test - void ensureJupiterExtensionApisAreComposable() { - - // 1) Find all existing top-level Extension APIs - List> extensionApis = ReflectionUtils.findAllClassesInPackage(Extension.class.getPackage().getName(), - this::isExtensionApi, name -> true); - - // 2) Determine which methods we expect the kitchen sink to implement... - - // @formatter:off - List expectedMethods = extensionApis.stream() - .map(Class::getDeclaredMethods) - .flatMap(Arrays::stream) - .filter(not(Method::isSynthetic)) - .filter(not(where(Method::getModifiers, Modifier::isStatic))) - .collect(toList()); - - List expectedMethodSignatures = expectedMethods.stream() - .map(this::methodSignature) - .sorted() - .collect(toList()); - - List expectedMethodNames = expectedMethods.stream() - .map(Method::getName) - .distinct() - .sorted() - .collect(toList()); - // @formatter:on - - // 3) Dynamically implement all Extension APIs - Object dynamicKitchenSinkExtension = Proxy.newProxyInstance(getClass().getClassLoader(), - extensionApis.toArray(Class[]::new), (proxy, method, args) -> null); - - // 4) Determine what ended up in the kitchen sink... - - // @formatter:off - List actualMethods = Arrays.stream(dynamicKitchenSinkExtension.getClass().getDeclaredMethods()) - .filter(ReflectionUtils::isNotStatic) - .collect(toList()); - - List actualMethodSignatures = actualMethods.stream() - .map(this::methodSignature) - .distinct() - .sorted() - .collect(toList()); - - List actualMethodNames = actualMethods.stream() - .map(Method::getName) - .distinct() - .sorted() - .collect(toList()); - // @formatter:on - - // 5) Remove methods from java.lang.Object - actualMethodSignatures.remove("equals(Object)"); - actualMethodSignatures.remove("hashCode()"); - actualMethodSignatures.remove("toString()"); - actualMethodNames.remove("equals"); - actualMethodNames.remove("hashCode"); - actualMethodNames.remove("toString"); - - // 6) Verify our expectations - - // @formatter:off - assertAll( - () -> assertThat(actualMethodSignatures).isEqualTo(expectedMethodSignatures), - () -> assertThat(actualMethodNames).isEqualTo(expectedMethodNames) - ); - // @formatter:on - } - - private boolean isExtensionApi(Class candidate) { - return candidate.isInterface() && (candidate != Extension.class) && Extension.class.isAssignableFrom(candidate); - } - - private String methodSignature(Method method) { - return String.format("%s(%s)", method.getName(), - ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java deleted file mode 100644 index 1740f609..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension; - -import java.util.Optional; -import java.util.stream.Stream; - -/** - * Kitchen Sink extension that implements every extension API - * supported by JUnit Jupiter. - * - *

This extension should never actually be registered for any tests. - * Rather, its sole purpose is to help ensure (via visual inspection) - * that a concrete extension is able to implement all extension APIs - * supported by JUnit Jupiter without any naming conflicts or - * ambiguities with regard to method names or method signatures. - * {@link ExtensionComposabilityTests}, on the other hand, serves - * the same purpose in a dynamic and automated fashion. - * - * @since 5.0 - * @see ExtensionComposabilityTests - */ -// @formatter:off -public class KitchenSinkExtension implements - - // Lifecycle Callbacks - BeforeAllCallback, - BeforeEachCallback, - BeforeTestExecutionCallback, - TestExecutionExceptionHandler, - AfterTestExecutionCallback, - AfterEachCallback, - AfterAllCallback, - - // Lifecycle methods exception handling - LifecycleMethodExecutionExceptionHandler, - - // Dependency Injection - TestInstancePreConstructCallback, - TestInstanceFactory, - TestInstancePostProcessor, - TestInstancePreDestroyCallback, - ParameterResolver, - - // Conditional Test Execution - ExecutionCondition, - - // @TestTemplate - TestTemplateInvocationContextProvider, - - // Miscellaneous - TestWatcher, - InvocationInterceptor - -// @formatter:on -{ - - // --- Lifecycle Callbacks ------------------------------------------------- - - @Override - public void beforeAll(ExtensionContext context) { - } - - @Override - public void beforeEach(ExtensionContext context) { - } - - @Override - public void beforeTestExecution(ExtensionContext context) { - } - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { - } - - @Override - public void afterTestExecution(ExtensionContext context) { - } - - @Override - public void afterEach(ExtensionContext context) { - } - - @Override - public void afterAll(ExtensionContext context) { - } - - // --- Lifecycle methods exception handling - - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { - } - - // --- Dependency Injection ------------------------------------------------ - - @Override - public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) { - } - - @Override - public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { - return null; - } - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - } - - @Override - public void preDestroyTestInstance(ExtensionContext context) { - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return false; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return null; - } - - // --- Conditional Test Execution ------------------------------------------ - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return null; - } - - // --- @TestTemplate ------------------------------------------------------- - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return false; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return null; - } - - // --- TestWatcher --------------------------------------------------------- - - @Override - public void testDisabled(ExtensionContext context, Optional reason) { - } - - @Override - public void testSuccessful(ExtensionContext context) { - } - - @Override - public void testAborted(ExtensionContext context, Throwable cause) { - } - - @Override - public void testFailed(ExtensionContext context, Throwable cause) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java deleted file mode 100644 index 40a333ef..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.extension.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.6 - */ -class TypeBasedParameterResolverTests { - - private final ParameterResolver basicTypeBasedParameterResolver = new BasicTypeBasedParameterResolver(); - private final ParameterResolver subClassedBasicTypeBasedParameterResolver = new SubClassedBasicTypeBasedParameterResolver(); - private final ParameterResolver parametrizedTypeBasedParameterResolver = new ParameterizedTypeBasedParameterResolver(); - - @Test - void missingTypeTypeBasedParameterResolver() { - PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - MissingTypeTypeBasedParameterResolver::new); - assertEquals( - "Failed to discover parameter type supported by " + MissingTypeTypeBasedParameterResolver.class.getName() - + "; potentially caused by lacking parameterized type in class declaration.", - exception.getMessage()); - } - - @Test - void supportsParameterForBasicTypes() { - Parameter parameter1 = findParameterOfMethod("methodWithBasicTypeParameter", String.class); - assertTrue(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); - assertTrue(subClassedBasicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); - - Parameter parameter2 = findParameterOfMethod("methodWithObjectParameter", Object.class); - assertFalse(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter2), null)); - } - - @Test - void supportsParameterForParameterizedTypes() { - Parameter parameter1 = findParameterOfMethod("methodWithParameterizedTypeParameter", Map.class); - assertTrue(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); - - Parameter parameter3 = findParameterOfMethod("methodWithAnotherParameterizedTypeParameter", Map.class); - assertFalse(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter3), null)); - } - - @Test - void resolve() { - ExtensionContext extensionContext = extensionContext(); - ParameterContext parameterContext = parameterContext( - findParameterOfMethod("methodWithBasicTypeParameter", String.class)); - assertEquals("Displaying TestAnnotation", - basicTypeBasedParameterResolver.resolveParameter(parameterContext, extensionContext)); - - Parameter parameter2 = findParameterOfMethod("methodWithParameterizedTypeParameter", Map.class); - assertEquals(Map.of("ids", List.of(1, 42)), - parametrizedTypeBasedParameterResolver.resolveParameter(parameterContext(parameter2), extensionContext)); - } - - private static ParameterContext parameterContext(Parameter parameter) { - ParameterContext parameterContext = mock(); - when(parameterContext.getParameter()).thenReturn(parameter); - return parameterContext; - } - - private static ExtensionContext extensionContext() { - ExtensionContext extensionContext = mock(); - when(extensionContext.getDisplayName()).thenReturn("Displaying"); - return extensionContext; - } - - private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionUtils.findMethod(Sample.class, methodName, parameterTypes).get(); - return method.getParameters()[0]; - } - - // ------------------------------------------------------------------------- - - @SuppressWarnings("rawtypes") - static class MissingTypeTypeBasedParameterResolver extends TypeBasedParameterResolver { - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return "enigma"; - } - } - - static class BasicTypeBasedParameterResolver extends TypeBasedParameterResolver { - - @Override - public String resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - Class parameterAnnotation = parameterContext.getParameter().getAnnotations()[0].annotationType(); - return String.format("%s %s", extensionContext.getDisplayName(), parameterAnnotation.getSimpleName()); - } - } - - static class SubClassedBasicTypeBasedParameterResolver extends BasicTypeBasedParameterResolver { - } - - static class ParameterizedTypeBasedParameterResolver - extends TypeBasedParameterResolver>> { - - @Override - public Map> resolveParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) throws ParameterResolutionException { - return Map.of("ids", List.of(1, 42)); - } - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @interface TestAnnotation { - } - - static class Sample { - - void methodWithBasicTypeParameter(@TestAnnotation String string) { - } - - void methodWithObjectParameter(Object nothing) { - } - - void methodWithParameterizedTypeParameter(Map> map) { - } - - void methodWithAnotherParameterizedTypeParameter(Map> nothing) { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java deleted file mode 100644 index 0efa28c9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.subpackage; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.opentest4j.AssertionFailedError; - -/** - * Tests which verify that {@link Assertions} can be subclassed. - * - * @since 5.3 - */ -class SubclassedAssertionsTests extends Assertions { - - @Test - void assertTrueWithBooleanTrue() { - assertTrue(true); - assertTrue(true, "test"); - assertTrue(true, () -> "test"); - } - - @Test - void assertFalseWithBooleanTrue() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertFalse(true)); - assertEquals("expected: but was: ", error.getMessage()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java deleted file mode 100644 index 0d5cc0bb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.subpackage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; -import org.opentest4j.TestAbortedException; - -/** - * Tests which verify that {@link Assumptions} can be subclassed. - * - * @since 5.3 - */ -class SubclassedAssumptionsTests extends Assumptions { - - @Test - void assumeTrueWithBooleanTrue() { - String foo = null; - try { - assumeTrue(true); - foo = "foo"; - } - finally { - assertEquals("foo", foo); - } - } - - @Test - void assumeFalseWithBooleanTrue() { - TestAbortedException exception = assertThrows(TestAbortedException.class, () -> assumeFalse(true)); - assertEquals("Assumption failed: assumption is not false", exception.getMessage()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java deleted file mode 100644 index 69264f90..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.util.Set; - -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.EngineTestKit; - -/** - * Abstract base class for tests involving the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -public abstract class AbstractJupiterTestEngineTests { - - private final JupiterTestEngine engine = new JupiterTestEngine(); - - protected EngineExecutionResults executeTestsForClass(Class testClass) { - return executeTests(selectClass(testClass)); - } - - protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { - return executeTests(request().selectors(selectors).build()); - } - - protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) { - return EngineTestKit.execute(this.engine, request); - } - - protected TestDescriptor discoverTests(DiscoverySelector... selectors) { - return discoverTests(request().selectors(selectors).build()); - } - - protected TestDescriptor discoverTests(LauncherDiscoveryRequest request) { - return engine.discover(request, UniqueId.forEngine(engine.getId())); - } - - protected UniqueId discoverUniqueId(Class clazz, String methodName) { - TestDescriptor engineDescriptor = discoverTests(selectMethod(clazz, methodName)); - Set descendants = engineDescriptor.getDescendants(); - // @formatter:off - TestDescriptor testDescriptor = descendants.stream() - .skip(descendants.size() - 1) - .findFirst() - .orElseGet(() -> fail("no descendants")); - // @formatter:on - return testDescriptor.getUniqueId(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java deleted file mode 100644 index 38bef70a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.engine.kotlin.ArbitraryNamingKotlinTestCase.METHOD_NAME; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.kotlin.ArbitraryNamingKotlinTestCase; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests for JVM languages that allow special characters - * in method names (e.g., Kotlin, Groovy, etc.) which are forbidden in - * Java source code. - * - * @since 5.1 - */ -class AtypicalJvmMethodNameTests extends AbstractJupiterTestEngineTests { - - @Test - void kotlinTestWithMethodNameContainingSpecialCharacters() { - EngineExecutionResults executionResults = executeTestsForClass(ArbitraryNamingKotlinTestCase.class); - assertThat(executionResults.testEvents().started().count()).isEqualTo(2); - - TestDescriptor testDescriptor1 = executionResults.testEvents().succeeded().list().get(0).getTestDescriptor(); - assertAll(// - () -> assertEquals(METHOD_NAME + "()", testDescriptor1.getDisplayName()), // - () -> assertEquals(METHOD_NAME + "()", testDescriptor1.getLegacyReportingName())); - - TestDescriptor testDescriptor2 = executionResults.testEvents().succeeded().list().get(1).getTestDescriptor(); - assertAll(// - () -> assertEquals("test name ends with parentheses()()", testDescriptor2.getDisplayName()), // - () -> assertEquals("test name ends with parentheses()()", testDescriptor2.getLegacyReportingName())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java deleted file mode 100644 index 2cbbf64a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * Integration tests that verify support for {@link BeforeAll} and {@link AfterAll} - * when used as meta-annotations in the {@link JupiterTestEngine}. - * - * @since 5.0 - * @see BeforeEachAndAfterEachComposedAnnotationTests - */ -class BeforeAllAndAfterAllComposedAnnotationTests extends AbstractJupiterTestEngineTests { - - private static final List methodsInvoked = new ArrayList<>(); - - @Test - void beforeAllAndAfterAllAsMetaAnnotations() { - executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); - - assertThat(methodsInvoked).containsExactly("beforeAll", "test", "afterAll"); - } - - static class TestCase { - - @CustomBeforeAll - static void beforeAll() { - methodsInvoked.add("beforeAll"); - } - - @Test - void test() { - methodsInvoked.add("test"); - } - - @CustomAfterAll - static void afterAll() { - methodsInvoked.add("afterAll"); - } - - } - - @BeforeAll - @Retention(RetentionPolicy.RUNTIME) - private @interface CustomBeforeAll { - } - - @AfterAll - @Retention(RetentionPolicy.RUNTIME) - private @interface CustomAfterAll { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java deleted file mode 100644 index e6c38048..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Integration tests that verify support for {@link BeforeEach} and {@link AfterEach} - * when used as meta-annotations in the {@link JupiterTestEngine}. - * - * @since 5.0 - * @see BeforeAllAndAfterAllComposedAnnotationTests - */ -class BeforeEachAndAfterEachComposedAnnotationTests extends AbstractJupiterTestEngineTests { - - private static final List methodsInvoked = new ArrayList<>(); - - @Test - void beforeEachAndAfterEachAsMetaAnnotations() { - executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); - - assertThat(methodsInvoked).containsExactly("beforeEach", "test", "afterEach"); - } - - static class TestCase { - - @CustomBeforeEach - void beforeEach() { - methodsInvoked.add("beforeEach"); - } - - @Test - void test() { - methodsInvoked.add("test"); - } - - @CustomAfterEach - void afterEach() { - methodsInvoked.add("afterEach"); - } - - } - - @BeforeEach - @Retention(RetentionPolicy.RUNTIME) - private @interface CustomBeforeEach { - } - - @AfterEach - @Retention(RetentionPolicy.RUNTIME) - private @interface CustomAfterEach { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java deleted file mode 100644 index 7dd37e1e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; -import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.support.hierarchical.Node; -import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; - -class DefaultExecutionModeTests extends AbstractJupiterTestEngineTests { - - @Test - void defaultExecutionModeIsReadFromConfigurationParameter() { - assertUsesExpectedExecutionMode(null, SAME_THREAD); - assertUsesExpectedExecutionMode(SAME_THREAD, SAME_THREAD); - assertUsesExpectedExecutionMode(CONCURRENT, CONCURRENT); - } - - private void assertUsesExpectedExecutionMode(ExecutionMode defaultExecutionMode, - ExecutionMode expectedExecutionMode) { - var engineDescriptor = discoverTestsWithDefaultExecutionMode(TestCase.class, defaultExecutionMode); - assertExecutionModeRecursively(engineDescriptor, expectedExecutionMode); - } - - @Test - void annotationOverridesDefaultExecutionModeToConcurrentForAllDescendants() { - assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, null, CONCURRENT); - assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, SAME_THREAD, CONCURRENT); - assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, CONCURRENT, CONCURRENT); - } - - @Test - void annotationOverridesDefaultExecutionModeToSameThreadForAllDescendants() { - assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, null, SAME_THREAD); - assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, SAME_THREAD, - SAME_THREAD); - assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, CONCURRENT, SAME_THREAD); - } - - private void assertUsesExpectedExecutionModeForTestClassAndItsDescendants(Class testClass, - ExecutionMode defaultExecutionMode, ExecutionMode expectedExecutionMode) { - var engineDescriptor = discoverTestsWithDefaultExecutionMode(testClass, defaultExecutionMode); - engineDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, expectedExecutionMode)); - } - - private void assertExecutionModeRecursively(TestDescriptor testDescriptor, ExecutionMode expectedExecutionMode) { - assertExecutionMode(testDescriptor, expectedExecutionMode); - testDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, expectedExecutionMode)); - } - - @Test - void methodsInTestClassesWithInstancePerClassHaveExecutionModeSameThread() { - var engineDescriptor = discoverTestsWithDefaultExecutionMode(SimpleTestInstancePerClassTestCase.class, - CONCURRENT); - var classDescriptor = getOnlyElement(engineDescriptor.getChildren()); - classDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, SAME_THREAD)); - } - - @Test - void methodsInNestedTestClassesWithInstancePerClassInHierarchyHaveExecutionModeSameThread() { - var engineDescriptor = discoverTestsWithDefaultExecutionMode(OuterTestCase.class, CONCURRENT); - var outerTestCaseClassDescriptor = firstChild(engineDescriptor, ClassTestDescriptor.class); - var outerTestMethodDescriptor = firstChild(outerTestCaseClassDescriptor, TestMethodTestDescriptor.class); - var level1NestedClassDescriptor = firstChild(outerTestCaseClassDescriptor, NestedClassTestDescriptor.class); - var level1TestMethodDescriptor = firstChild(level1NestedClassDescriptor, TestMethodTestDescriptor.class); - var level2NestedClassDescriptor = firstChild(level1NestedClassDescriptor, NestedClassTestDescriptor.class); - var level2TestMethodDescriptor = firstChild(level2NestedClassDescriptor, TestMethodTestDescriptor.class); - var level3NestedClassDescriptor = firstChild(level2NestedClassDescriptor, NestedClassTestDescriptor.class); - var level3TestMethodDescriptor = firstChild(level3NestedClassDescriptor, TestMethodTestDescriptor.class); - - assertExecutionMode(outerTestCaseClassDescriptor, CONCURRENT); - assertExecutionMode(outerTestMethodDescriptor, CONCURRENT); - assertExecutionMode(level1NestedClassDescriptor, CONCURRENT); - assertExecutionMode(level1TestMethodDescriptor, CONCURRENT); - assertExecutionMode(level2NestedClassDescriptor, CONCURRENT); - assertExecutionMode(level2TestMethodDescriptor, SAME_THREAD); - assertExecutionMode(level3NestedClassDescriptor, SAME_THREAD); - assertExecutionMode(level3TestMethodDescriptor, SAME_THREAD); - } - - private JupiterEngineDescriptor discoverTestsWithDefaultExecutionMode(Class testClass, - ExecutionMode executionMode) { - LauncherDiscoveryRequestBuilder request = request().selectors(selectClass(testClass)); - if (executionMode != null) { - request.configurationParameter(Constants.DEFAULT_PARALLEL_EXECUTION_MODE, executionMode.name()); - } - return (JupiterEngineDescriptor) discoverTests(request.build()); - } - - private static void assertExecutionMode(TestDescriptor testDescriptor, ExecutionMode expectedExecutionMode) { - assertThat(((Node) testDescriptor).getExecutionMode()) // - .describedAs("ExecutionMode for %s", testDescriptor) // - .isEqualTo(expectedExecutionMode); - } - - @SuppressWarnings("unchecked") - private T firstChild(TestDescriptor engineDescriptor, Class testDescriptorClass) { - return (T) engineDescriptor.getChildren().stream() // - .filter(testDescriptorClass::isInstance) // - .findFirst() // - .orElseGet(() -> fail("No child of type " + testDescriptorClass + " found")); - } - - static class TestCase { - - @Test - void test() { - } - - @Nested - class NestedTestCase { - - @Test - void test() { - } - - } - - } - - @TestInstance(PER_CLASS) - static class SimpleTestInstancePerClassTestCase extends TestCase { - } - - @Execution(org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT) - static class ConcurrentTestCase extends TestCase { - } - - @Execution(org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD) - static class SameThreadTestCase extends TestCase { - } - - static class OuterTestCase { - @Nested - class LevelOne { - @Nested - @TestInstance(PER_CLASS) - class LevelTwo { - @Nested - class LevelThree { - @Test - void test() { - } - } - - @Test - void test() { - } - } - - @Test - void test() { - } - } - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java deleted file mode 100644 index 45d86c7c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.engine.execution.injection.sample.DoubleParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests that verify support for selecting and executing default - * methods from interfaces in conjunction with the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class DefaultMethodTests extends AbstractJupiterTestEngineTests { - - private static boolean beforeAllInvoked; - private static boolean afterAllInvoked; - private static boolean defaultMethodInvoked; - private static boolean overriddenDefaultMethodInvoked; - private static boolean localMethodInvoked; - - @BeforeEach - void resetFlags() { - beforeAllInvoked = false; - afterAllInvoked = false; - defaultMethodInvoked = false; - overriddenDefaultMethodInvoked = false; - localMethodInvoked = false; - } - - @Test - void executeTestCaseWithDefaultMethodFromInterfaceSelectedByFullyQualifedMethodName() { - String fqmn = TestCaseWithDefaultMethod.class.getName() + "#test"; - LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); - EngineExecutionResults executionResults = executeTests(request); - - // @formatter:off - assertAll( - () -> assertTrue(beforeAllInvoked, "@BeforeAll static method invoked from interface"), - () -> assertTrue(afterAllInvoked, "@AfterAll static method invoked from interface"), - () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), - () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), - () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), - () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") - ); - // @formatter:on - } - - @Test - void executeTestCaseWithDefaultMethodFromGenericInterfaceSelectedByFullyQualifedMethodName() { - String fqmn = GenericTestCaseWithDefaultMethod.class.getName() + "#test(" + Long.class.getName() + ")"; - LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); - EngineExecutionResults executionResults = executeTests(request); - - // @formatter:off - assertAll( - () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), - () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), - () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), - () -> assertFalse(localMethodInvoked, "local @Test method should not have been invoked from class"), - () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), - () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), - () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") - ); - // @formatter:on - } - - @Test - void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByFullyQualifedMethodName() { - - String fqmn = GenericTestCaseWithDefaultMethod.class.getName() + "#test(" + Double.class.getName() + ")"; - LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); - EngineExecutionResults executionResults = executeTests(request); - - // @formatter:off - assertAll( - () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), - () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), - () -> assertFalse(defaultMethodInvoked, "default @Test method should not have been invoked from interface"), - () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), - () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), - () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), - () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") - ); - // @formatter:on - } - - @Test - void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByClass() { - Class clazz = GenericTestCaseWithDefaultMethod.class; - LauncherDiscoveryRequest request = request().selectors(selectClass(clazz)).build(); - EngineExecutionResults executionResults = executeTests(request); - - // @formatter:off - assertAll( - () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), - () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), - () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), - () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), - () -> assertEquals(2, executionResults.testEvents().started().count(), "# tests started"), - () -> assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"), - () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") - ); - // @formatter:on - } - - @Test - void executeTestCaseWithOverriddenGenericDefaultMethodSelectedByClass() { - Class clazz = GenericTestCaseWithOverriddenDefaultMethod.class; - LauncherDiscoveryRequest request = request().selectors(selectClass(clazz)).build(); - EngineExecutionResults executionResults = executeTests(request); - - // @formatter:off - assertAll( - () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), - () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), - () -> assertFalse(defaultMethodInvoked, "default @Test method should not have been invoked from interface"), - () -> assertTrue(overriddenDefaultMethodInvoked, "overridden default @Test method invoked from interface"), - () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), - // If defaultMethodInvoked is false and the following ends up being - // 3 instead of 2, that means that the overriding method gets invoked - // twice: once as itself and a second time "as" the default method which - // should not have been "discovered" since it is overridden. - () -> assertEquals(2, executionResults.testEvents().started().count(), "# tests started"), - () -> assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"), - () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") - ); - // @formatter:on - } - - // ------------------------------------------------------------------------- - - interface TestInterface { - - @BeforeAll - static void beforeAll() { - beforeAllInvoked = true; - } - - @Test - default void test() { - defaultMethodInvoked = true; - } - - @AfterAll - static void afterAll() { - afterAllInvoked = true; - } - - } - - static class TestCaseWithDefaultMethod implements TestInterface { - } - - @ExtendWith({ LongParameterResolver.class, DoubleParameterResolver.class }) - @TestInstance(Lifecycle.PER_CLASS) - interface GenericTestInterface { - - @BeforeAll - default void beforeAll() { - beforeAllInvoked = true; - } - - @Test - default void test(N number) { - defaultMethodInvoked = true; - assertThat(number.intValue()).isEqualTo(42); - } - - @AfterAll - default void afterAll() { - afterAllInvoked = true; - } - - } - - static class GenericTestCaseWithDefaultMethod implements GenericTestInterface { - - @Test - void test(Double number) { - localMethodInvoked = true; - assertThat(number).isEqualTo(42.0); - } - - } - - static class GenericTestCaseWithOverriddenDefaultMethod implements GenericTestInterface { - - @Test - @Override - public void test(Long number) { - overriddenDefaultMethodInvoked = true; - assertThat(number.intValue()).isEqualTo(42); - } - - @Test - void test(Double number) { - localMethodInvoked = true; - assertThat(number).isEqualTo(42.0); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java deleted file mode 100644 index 63f43367..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.test; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests that verify support for {@link Disabled @Disabled} in the - * {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class DisabledTests extends AbstractJupiterTestEngineTests { - - @Test - void executeTestsWithDisabledTestClass() { - EngineExecutionResults results = executeTestsForClass(DisabledTestClassTestCase.class); - - results.containerEvents().assertStatistics(stats -> stats.skipped(1)); - results.testEvents().assertStatistics(stats -> stats.started(0)); - } - - @Test - void executeTestsWithDisabledTestMethods() throws Exception { - String methodName = "disabledTest"; - Method method = DisabledTestMethodsTestCase.class.getDeclaredMethod(methodName); - - executeTestsForClass(DisabledTestMethodsTestCase.class).testEvents()// - .assertStatistics(stats -> stats.skipped(1).started(1).finished(1).aborted(0).succeeded(1).failed(0))// - .skipped().assertEventsMatchExactly( - event(test(methodName), skippedWithReason(method + " is @Disabled"))); - } - - // ------------------------------------------------------------------- - - @Disabled - static class DisabledTestClassTestCase { - - @Test - void disabledTest() { - fail("this should be @Disabled"); - } - } - - static class DisabledTestMethodsTestCase { - - @Test - void enabledTest() { - } - - @Test - @Disabled - void disabledTest() { - fail("this should be @Disabled"); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java deleted file mode 100644 index 87dcf40c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Fail.fail; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE; -import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.displayName; -import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Event; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests for {@link TestFactory @TestFactory}, {@link DynamicTest}, - * and {@link org.junit.jupiter.api.DynamicContainer}. - * - * @since 5.0 - */ -class DynamicNodeGenerationTests extends AbstractJupiterTestEngineTests { - - @Test - void testFactoryMethodsAreCorrectlyDiscoveredForClassSelector() { - LauncherDiscoveryRequest request = request().selectors(selectClass(MyDynamicTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(13); - } - - @Test - void testFactoryMethodIsCorrectlyDiscoveredForMethodSelector() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyDynamicTestCase.class, "dynamicStream")).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(2); - } - - @Test - void dynamicTestsAreExecutedFromStream() { - EngineExecutionResults executionResults = executeTests(selectMethod(MyDynamicTestCase.class, "dynamicStream")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("dynamicStream"), started()), // - event(dynamicTestRegistered("dynamic-test:#1")), // - event(test("dynamic-test:#1", "succeedingTest"), started()), // - event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container("dynamicStream"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void dynamicTestsAreExecutedFromCollection() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "dynamicCollection")); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertAll( // - () -> assertEquals(3, containers.started().count(), "# container started"), - () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), - () -> assertEquals(2, tests.started().count(), "# tests started"), - () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), - () -> assertEquals(1, tests.failed().count(), "# tests failed"), - () -> assertEquals(3, containers.finished().count(), "# container finished")); - } - - @Test - void dynamicTestsAreExecutedFromIterator() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "dynamicIterator")); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertAll( // - () -> assertEquals(3, containers.started().count(), "# container started"), - () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), - () -> assertEquals(2, tests.started().count(), "# tests started"), - () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), - () -> assertEquals(1, tests.failed().count(), "# tests failed"), - () -> assertEquals(3, containers.finished().count(), "# container finished")); - } - - @Test - void dynamicTestsAreExecutedFromIterable() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "dynamicIterable")); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - // @TestFactory methods are counted as both container and test - assertAll( // - () -> assertEquals(3, containers.started().count(), "# container started"), - () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), - () -> assertEquals(2, tests.started().count(), "# tests started"), - () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), - () -> assertEquals(1, tests.failed().count(), "# tests failed"), - () -> assertEquals(3, containers.finished().count(), "# container finished")); - } - - @Test - void singleDynamicTestIsExecutedWhenDiscoveredByUniqueId() { - UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "dynamicStream") // - .append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); - - EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("dynamicStream"), started()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container("dynamicStream"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void singleDynamicTestIsExecutedWhenDiscoveredByIterationIndex() { - var methodSelector = selectMethod(MyDynamicTestCase.class, "dynamicStream"); - - EngineExecutionResults executionResults = executeTests(selectIteration(methodSelector, 1)); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("dynamicStream"), started()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container("dynamicStream"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void multipleDynamicTestsAreExecutedWhenDiscoveredByIterationIndexAndUniqueId() { - UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "threeTests") // - .append(DYNAMIC_TEST_SEGMENT_TYPE, "#3"); - - var methodSelector = selectMethod(MyDynamicTestCase.class, "threeTests"); - - EngineExecutionResults executionResults = executeTests(selectIteration(methodSelector, 1), - selectUniqueId(uniqueId)); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("threeTests"), started()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "two"), started()), // - event(test("dynamic-test:#2", "two"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamic-test:#3")), // - event(test("dynamic-test:#3", "three"), started()), // - event(test("dynamic-test:#3", "three"), finishedSuccessfully()), // - event(container("threeTests"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void dynamicContainersAreExecutedFromIterable() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "dynamicContainerWithIterable")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("dynamicContainerWithIterable"), started()), // - event(dynamicTestRegistered("dynamic-container:#1")), // - event(container("dynamic-container:#1"), started()), // - event(dynamicTestRegistered("dynamic-test:#1")), // - event(test("dynamic-test:#1", "succeedingTest"), started()), // - event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container("dynamic-container:#1"), finishedSuccessfully()), // - event(container("dynamicContainerWithIterable"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertAll( // - () -> assertEquals(4, containers.started().count(), "# container started"), - () -> assertEquals(1, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), - () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), - () -> assertEquals(2, tests.started().count(), "# tests started"), - () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), - () -> assertEquals(1, tests.failed().count(), "# tests failed"), - () -> assertEquals(4, containers.finished().count(), "# container finished")); - } - - @Test - void singleDynamicTestInNestedDynamicContainerIsExecutedWhenDiscoveredByUniqueId() { - UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "twoNestedContainersWithTwoTestsEach") // - .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1") // - .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1") // - .append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); - - EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("twoNestedContainersWithTwoTestsEach"), started()), // - event(dynamicTestRegistered(displayName("a"))), // - event(container(displayName("a")), started()), // - event(dynamicTestRegistered(displayName("a1"))), // - event(container(displayName("a1")), started()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container(displayName("a1")), finishedSuccessfully()), // - event(container(displayName("a")), finishedSuccessfully()), // - event(container("twoNestedContainersWithTwoTestsEach"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void allDynamicTestInNestedDynamicContainerAreExecutedWhenContainerIsDiscoveredByUniqueId() { - UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "twoNestedContainersWithTwoTestsEach") // - .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#2") // - .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1"); - - EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("twoNestedContainersWithTwoTestsEach"), started()), // - event(dynamicTestRegistered(displayName("b"))), // - event(container(displayName("b")), started()), // - event(dynamicTestRegistered(displayName("b1"))), // - event(container(displayName("b1")), started()), // - event(dynamicTestRegistered("dynamic-test:#1")), // - event(test("dynamic-test:#1", "succeedingTest"), started()), // - event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container(displayName("b1")), finishedSuccessfully()), // - event(container(displayName("b")), finishedSuccessfully()), // - event(container("twoNestedContainersWithTwoTestsEach"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void nestedDynamicContainersAreExecuted() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "nestedDynamicContainers")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("nestedDynamicContainers"), started()), // - event(dynamicTestRegistered(displayName("gift wrap"))), // - event(container(displayName("gift wrap")), started()), // - event(dynamicTestRegistered(displayName("box"))), // - event(container(displayName("box")), started()), // - event(dynamicTestRegistered("dynamic-test:#1")), // - event(test("dynamic-test:#1", "succeedingTest"), started()), // - event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container(displayName("box")), finishedSuccessfully()), // - event(container(displayName("gift wrap")), finishedSuccessfully()), // - event(container("nestedDynamicContainers"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertAll( // - () -> assertEquals(5, containers.started().count(), "# container started"), - () -> assertEquals(2, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), - () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), - () -> assertEquals(2, tests.started().count(), "# tests started"), - () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), - () -> assertEquals(1, tests.failed().count(), "# tests failed"), - () -> assertEquals(5, containers.finished().count(), "# container finished")); - } - - @Test - void legacyReportingNames() { - Events dynamicRegistrations = executeTests(selectMethod(MyDynamicTestCase.class, "nestedDynamicContainers"))// - .allEvents().dynamicallyRegistered(); - - // @formatter:off - Stream legacyReportingNames = dynamicRegistrations - .map(Event::getTestDescriptor) - .map(TestDescriptor::getLegacyReportingName); - assertThat(legacyReportingNames) - .containsExactly("nestedDynamicContainers()[1]", "nestedDynamicContainers()[1][1]", - "nestedDynamicContainers()[1][1][1]", "nestedDynamicContainers()[1][1][2]"); - // @formatter:on - } - - @Test - void dynamicContainersAreExecutedFromExceptionThrowingStream() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "dynamicContainerWithExceptionThrowingStream")); - - assertTrue(MyDynamicTestCase.exceptionThrowingStreamClosed.get(), "stream should be closed"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("dynamicContainerWithExceptionThrowingStream"), started()), // - event(dynamicTestRegistered("dynamic-container:#1")), // - event(container("dynamic-container:#1"), started()), // - event(dynamicTestRegistered("dynamic-test:#1")), // - event(test("dynamic-test:#1", "succeedingTest"), started()), // - event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container("dynamic-container:#1"), - finishedWithFailure(instanceOf(ArrayIndexOutOfBoundsException.class))), // - event(container("dynamicContainerWithExceptionThrowingStream"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertAll( // - () -> assertEquals(4, containers.started().count(), "# container started"), - () -> assertEquals(1, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), - () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), - () -> assertEquals(2, tests.started().count(), "# tests started"), - () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), - () -> assertEquals(1, tests.failed().count(), "# tests failed"), - () -> assertEquals(4, containers.finished().count(), "# container finished")); - } - - @Test - void dynamicContainersChildrenMustNotBeNull() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "dynamicContainerWithNullChildren")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("dynamicContainerWithNullChildren"), started()), // - event(dynamicTestRegistered("dynamic-container:#1")), // - event(container("dynamic-container:#1"), started()), // - event(container("dynamic-container:#1"), // - finishedWithFailure(message("individual dynamic node must not be null"))), // - event(container("dynamicContainerWithNullChildren"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void testFactoryMethodsMayReturnSingleDynamicContainer() { - EngineExecutionResults executionResults = executeTests( - selectMethod(MyDynamicTestCase.class, "singleContainer")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("singleContainer"), started()), // - event(dynamicTestRegistered("dynamic-container:#1")), // - event(container("dynamic-container:#1"), started()), // - event(dynamicTestRegistered("dynamic-test:#1")), // - event(test("dynamic-test:#1", "succeedingTest"), started()), // - event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamic-test:#2")), // - event(test("dynamic-test:#2", "failingTest"), started()), // - event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // - event(container("dynamic-container:#1"), finishedSuccessfully()), // - event(container("singleContainer"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void testFactoryMethodsMayReturnSingleDynamicTest() { - EngineExecutionResults executionResults = executeTests(selectMethod(MyDynamicTestCase.class, "singleTest")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MyDynamicTestCase.class), started()), // - event(container("singleTest"), started()), // - event(dynamicTestRegistered("dynamic-test:#1")), // - event(test("dynamic-test:#1", "succeedingTest"), started()), // - event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // - event(container("singleTest"), finishedSuccessfully()), // - event(container(MyDynamicTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - static class MyDynamicTestCase { - - private static final List list = Arrays.asList( - dynamicTest("succeedingTest", () -> assertTrue(true, "succeeding")), - dynamicTest("failingTest", () -> fail("failing"))); - - private static final AtomicBoolean exceptionThrowingStreamClosed = new AtomicBoolean(false); - - @TestFactory - Collection dynamicCollection() { - return list; - } - - @TestFactory - Stream dynamicStream() { - return list.stream(); - } - - @TestFactory - Iterator dynamicIterator() { - return list.iterator(); - } - - @TestFactory - Iterable dynamicIterable() { - return this::dynamicIterator; - } - - @TestFactory - Iterable dynamicContainerWithIterable() { - return singleton(dynamicContainer("box", list)); - } - - @TestFactory - Iterable nestedDynamicContainers() { - return singleton(dynamicContainer("gift wrap", singleton(dynamicContainer("box", list)))); - } - - @TestFactory - Stream twoNestedContainersWithTwoTestsEach() { - return Stream.of( // - dynamicContainer("a", singleton(dynamicContainer("a1", list))), // - dynamicContainer("b", singleton(dynamicContainer("b1", list))) // - ); - } - - @TestFactory - Iterable dynamicContainerWithExceptionThrowingStream() { - // @formatter:off - return singleton(dynamicContainer("box", - IntStream.rangeClosed(0, 100) - .mapToObj(list::get) - .onClose(() -> exceptionThrowingStreamClosed.set(true)))); - // @formatter:on - } - - @TestFactory - Iterable dynamicContainerWithNullChildren() { - return singleton(dynamicContainer("box", singleton(null))); - } - - @TestFactory - DynamicNode singleContainer() { - return dynamicContainer("box", list); - } - - @TestFactory - DynamicNode singleTest() { - return dynamicTest("succeedingTest", () -> assertTrue(true)); - } - - @TestFactory - Stream threeTests() { - return Stream.of( // - dynamicTest("one", () -> assertTrue(true)), // - dynamicTest("two", () -> assertTrue(true)), // - dynamicTest("three", () -> assertTrue(true)) // - ); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java deleted file mode 100644 index b1662d65..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; - -import java.io.IOException; -import java.util.Optional; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; -import org.opentest4j.AssertionFailedError; -import org.opentest4j.TestAbortedException; - -/** - * Integration tests that verify correct exception handling in the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class ExceptionHandlingTests extends AbstractJupiterTestEngineTests { - - @Test - void failureInTestMethodIsRegistered() { - EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "failingTest")); - Events tests = executionResults.testEvents(); - - tests.assertStatistics(stats -> stats.started(1).failed(1)); - - tests.failed().assertEventsMatchExactly( // - event(test("failingTest"), - finishedWithFailure(instanceOf(AssertionFailedError.class), message("always fails")))); - } - - @Test - void uncheckedExceptionInTestMethodIsRegistered() { - EngineExecutionResults executionResults = executeTests( - selectMethod(FailureTestCase.class, "testWithUncheckedException")); - Events tests = executionResults.testEvents(); - - tests.assertStatistics(stats -> stats.started(1).failed(1)); - - tests.failed().assertEventsMatchExactly( // - event(test("testWithUncheckedException"), - finishedWithFailure(instanceOf(RuntimeException.class), message("unchecked")))); - } - - @Test - void checkedExceptionInTestMethodIsRegistered() { - EngineExecutionResults executionResults = executeTests( - selectMethod(FailureTestCase.class, "testWithCheckedException")); - Events tests = executionResults.testEvents(); - - tests.assertStatistics(stats -> stats.started(1).failed(1)); - - tests.failed().assertEventsMatchExactly( // - event(test("testWithCheckedException"), - finishedWithFailure(instanceOf(IOException.class), message("checked")))); - } - - @Test - void checkedExceptionInBeforeEachIsRegistered() { - FailureTestCase.exceptionToThrowInBeforeEach = Optional.of(new IOException("checked")); - - EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); - Events tests = executionResults.testEvents(); - - tests.assertStatistics(stats -> stats.started(1).failed(1)); - - tests.failed().assertEventsMatchExactly( - event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked")))); - } - - @Test - void checkedExceptionInAfterEachIsRegistered() { - FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); - - EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); - Events tests = executionResults.testEvents(); - - tests.assertStatistics(stats -> stats.started(1).failed(1)); - - tests.failed().assertEventsMatchExactly( - event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked")))); - } - - @Test - void checkedExceptionInAfterEachIsSuppressedByExceptionInTest() { - Class testClass = FailureTestCase.class; - - FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); - - EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "testWithUncheckedException")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("testWithUncheckedException"), started()), // - event(test("testWithUncheckedException"), // - finishedWithFailure( // - instanceOf(RuntimeException.class), // - message("unchecked"), // - suppressed(0, instanceOf(IOException.class), message("checked")))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void exceptionInAfterEachTakesPrecedenceOverFailedAssumptionInTest() { - FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); - - EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "abortedTest")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(FailureTestCase.class), started()), // - event(test("abortedTest"), started()), // - event(test("abortedTest"), // - finishedWithFailure(instanceOf(IOException.class), message("checked"), // - suppressed(0, instanceOf(TestAbortedException.class)))), // - event(container(FailureTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void checkedExceptionInBeforeAllIsRegistered() { - Class testClass = FailureTestCase.class; - - FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new IOException("checked")); - - EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void checkedExceptionInAfterAllIsRegistered() { - Class testClass = FailureTestCase.class; - - FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked")); - - EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("succeedingTest"), started()), // - event(test("succeedingTest"), finishedSuccessfully()), // - event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void exceptionInAfterAllCallbackDoesNotHideExceptionInBeforeAllCallback() { - Class testClass = TestCaseWithThrowingBeforeAllAndAfterAllCallbacks.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), finishedWithFailure( // - message("beforeAll callback"), // - suppressed(0, message("afterAll callback")))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void exceptionsInConstructorAndAfterAllCallbackAreReportedWhenTestInstancePerMethodIsUsed() { - Class testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("test"), started()), // - event(test("test"), finishedWithFailure(message("constructor"))), // - event(container(testClass), finishedWithFailure(message("afterAll callback"))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void exceptionInConstructorPreventsExecutionOfAfterAllCallbacksWhenTestInstancePerClassIsUsed() { - Class testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), finishedWithFailure(message("constructor"))), - event(engine(), finishedSuccessfully())); - } - - @Test - void failureInAfterAllTakesPrecedenceOverTestAbortedExceptionInBeforeAll() { - FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new TestAbortedException("aborted")); - FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked")); - - EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(FailureTestCase.class), started()), // - event(container(FailureTestCase.class), - finishedWithFailure(instanceOf(IOException.class), message("checked"), - suppressed(0, instanceOf(TestAbortedException.class), message("aborted")))), // - event(engine(), finishedSuccessfully())); - } - - @AfterEach - void cleanUpExceptions() { - FailureTestCase.exceptionToThrowInBeforeAll = Optional.empty(); - FailureTestCase.exceptionToThrowInAfterAll = Optional.empty(); - FailureTestCase.exceptionToThrowInBeforeEach = Optional.empty(); - FailureTestCase.exceptionToThrowInAfterEach = Optional.empty(); - } - - // ------------------------------------------------------------------------- - - static class FailureTestCase { - - static Optional exceptionToThrowInBeforeAll = Optional.empty(); - static Optional exceptionToThrowInAfterAll = Optional.empty(); - static Optional exceptionToThrowInBeforeEach = Optional.empty(); - static Optional exceptionToThrowInAfterEach = Optional.empty(); - - @BeforeAll - static void beforeAll() throws Throwable { - if (exceptionToThrowInBeforeAll.isPresent()) { - throw exceptionToThrowInBeforeAll.get(); - } - } - - @AfterAll - static void afterAll() throws Throwable { - if (exceptionToThrowInAfterAll.isPresent()) { - throw exceptionToThrowInAfterAll.get(); - } - } - - @BeforeEach - void beforeEach() throws Throwable { - if (exceptionToThrowInBeforeEach.isPresent()) { - throw exceptionToThrowInBeforeEach.get(); - } - } - - @AfterEach - void afterEach() throws Throwable { - if (exceptionToThrowInAfterEach.isPresent()) { - throw exceptionToThrowInAfterEach.get(); - } - } - - @Test - void succeedingTest() { - } - - @Test - void failingTest() { - Assertions.fail("always fails"); - } - - @Test - void testWithUncheckedException() { - throw new RuntimeException("unchecked"); - } - - @Test - void testWithCheckedException() throws IOException { - throw new IOException("checked"); - } - - @Test - void abortedTest() { - assumeFalse(true, "abortedTest"); - } - - } - - @TestInstance(PER_METHOD) - @ExtendWith(ThrowingAfterAllCallback.class) - static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle { - TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle() { - throw new IllegalStateException("constructor"); - } - - @Test - void test() { - } - - } - - @TestInstance(PER_CLASS) - @ExtendWith(ThrowingAfterAllCallback.class) - static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle { - TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle() { - throw new IllegalStateException("constructor"); - } - - @Test - void test() { - } - } - - @ExtendWith(ThrowingBeforeAllCallback.class) - @ExtendWith(ThrowingAfterAllCallback.class) - static class TestCaseWithThrowingBeforeAllAndAfterAllCallbacks { - @Test - void test() { - } - } - - static class ThrowingBeforeAllCallback implements BeforeAllCallback { - @Override - public void beforeAll(ExtensionContext context) { - throw new IllegalStateException("beforeAll callback"); - } - } - - static class ThrowingAfterAllCallback implements AfterAllCallback { - @Override - public void afterAll(ExtensionContext context) { - throw new IllegalStateException("afterAll callback"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java deleted file mode 100644 index 726eb885..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import org.junit.Assume; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests that verify support for failed assumptions in the - * {@link JupiterTestEngine}. - * - * @since 5.4 - */ -class FailedAssumptionsTests extends AbstractJupiterTestEngineTests { - - @Test - void testAbortedExceptionInBeforeAll() { - EngineExecutionResults results = executeTestsForClass(TestAbortedExceptionInBeforeAllTestCase.class); - - results.containerEvents().assertStatistics(stats -> stats.aborted(1)); - results.testEvents().assertStatistics(stats -> stats.started(0)); - } - - @Test - void assumptionViolatedExceptionInBeforeAll() { - EngineExecutionResults results = executeTestsForClass(AssumptionViolatedExceptionInBeforeAllTestCase.class); - - results.containerEvents().assertStatistics(stats -> stats.aborted(1)); - results.testEvents().assertStatistics(stats -> stats.started(0)); - } - - // ------------------------------------------------------------------- - - static class TestAbortedExceptionInBeforeAllTestCase { - - @BeforeAll - static void beforeAll() { - Assumptions.assumeTrue(false); - } - - @Test - void test() { - } - } - - static class AssumptionViolatedExceptionInBeforeAllTestCase { - - @BeforeAll - static void beforeAll() { - Assume.assumeTrue(false); - } - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java deleted file mode 100644 index 37dc1649..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests that verify proper handling of invalid configuration for - * lifecycle methods in conjunction with the {@link JupiterTestEngine}. - * - *

In general, configuration errors should not be thrown until the - * execution phase, thereby giving all containers a chance to execute. - * - * @since 5.0 - */ -class InvalidLifecycleMethodConfigurationTests extends AbstractJupiterTestEngineTests { - - @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticBeforeAllDeclaration() { - assertContainerFailed(TestCaseWithInvalidNonStaticBeforeAllMethod.class); - } - - @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticAfterAllDeclaration() { - assertContainerFailed(TestCaseWithInvalidNonStaticAfterAllMethod.class); - } - - @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidStaticBeforeEachDeclaration() { - assertContainerFailed(TestCaseWithInvalidStaticBeforeEachMethod.class); - } - - @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidStaticAfterEachDeclaration() { - assertContainerFailed(TestCaseWithInvalidStaticAfterEachMethod.class); - } - - private void assertContainerFailed(Class invalidTestClass) { - EngineExecutionResults executionResults = executeTests(selectClass(TestCase.class), - selectClass(invalidTestClass)); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - // @formatter:off - assertAll( - () -> assertEquals(3, containers.started().count(), "# containers started"), - () -> assertEquals(1, tests.started().count(), "# tests started"), - () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), - () -> assertEquals(0, tests.failed().count(), "# tests failed"), - () -> assertEquals(3, containers.finished().count(), "# containers finished"), - () -> assertEquals(1, containers.failed().count(), "# containers failed") - ); - // @formatter:on - } - - // ------------------------------------------------------------------------- - - static class TestCase { - - @Test - void test() { - } - } - - static class TestCaseWithInvalidNonStaticBeforeAllMethod { - - // must be static - @BeforeAll - void beforeAll() { - } - - @Test - void test() { - } - } - - static class TestCaseWithInvalidNonStaticAfterAllMethod { - - // must be static - @AfterAll - void afterAll() { - } - - @Test - void test() { - } - } - - static class TestCaseWithInvalidStaticBeforeEachMethod { - - // must NOT be static - @BeforeEach - static void beforeEach() { - } - - @Test - void test() { - } - } - - static class TestCaseWithInvalidStaticAfterEachMethod { - - // must NOT be static - @AfterEach - static void afterEach() { - } - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java deleted file mode 100644 index 5358e434..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -/** - * Basic assertions regarding {@link org.junit.platform.engine.TestEngine} - * functionality in JUnit Jupiter. - * - * @since 5.0 - */ -class JupiterTestEngineBasicTests { - - private final JupiterTestEngine jupiter = new JupiterTestEngine(); - - @Test - void id() { - assertEquals("junit-jupiter", jupiter.getId()); - } - - @Test - void groupId() { - assertEquals("org.junit.jupiter", jupiter.getGroupId().orElseThrow()); - } - - @Test - void artifactId() { - assertEquals("junit-jupiter-engine", jupiter.getArtifactId().orElseThrow()); - } - - @Test - void version() { - assertThat(jupiter.getVersion().orElseThrow()).isIn( // - System.getProperty("developmentVersion"), // with Test Distribution - "DEVELOPMENT" // without Test Distribution - ); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java deleted file mode 100644 index f2a584fe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingAndSupersedingTests.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.subpackage.SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase; - -/** - * Integration tests that explicitly demonstrate the overriding and superseding - * rules for lifecycle methods in the {@link JupiterTestEngine}. - * - * @since 5.9 - */ -class LifecycleMethodOverridingAndSupersedingTests { - - @Nested - @DisplayName("A package-private lifecycle super-method can be overridden by") - class PackagePrivateSuperClassTests { - - @Nested - @DisplayName("a protected lifecycle method in the derived class") - class ProtectedExtendsPackagePrivateLifecycleMethod - extends SuperClassWithPackagePrivateLifecycleMethodTestCase { - - @Override - @BeforeEach - protected void beforeEach() { - } - - } - - @Nested - @DisplayName("a package-private lifecycle method in the derived class") - class PackagePrivateExtendsPackagePrivateLifecycleMethod - extends SuperClassWithPackagePrivateLifecycleMethodTestCase { - - @Override - @BeforeEach - void beforeEach() { - } - - } - - @Nested - @DisplayName("a public lifecycle method in the derived class") - class PublicExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodTestCase { - - @Override - @BeforeEach - public void beforeEach() { - } - - } - } - - @Nested - @DisplayName("A package-private lifecycle super-method from a different package can be superseded by") - class PackagePrivateSuperClassInDifferentPackageTests { - - @Nested - @DisplayName("a protected lifecycle method in the derived class") - class ProtectedExtendsPackagePrivateLifecycleMethod - extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { - - // @Override - @BeforeEach - protected void beforeEach() { - } - - } - - @Nested - @DisplayName("a package-private lifecycle method in the derived class") - class PackagePrivateExtendsPackagePrivateLifecycleMethod - extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { - - // @Override - @BeforeEach - void beforeEach() { - } - - } - - @Nested - @DisplayName("a public lifecycle method in the derived class") - class PublicExtendsPackagePrivateLifecycleMethod - extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { - - // @Override - @BeforeEach - public void beforeEach() { - } - - } - } - - @Nested - @DisplayName("A protected lifecycle super-method can be overridden by") - class ProtectedSuperClassTests { - - @Nested - @DisplayName("a protected lifecycle method in the derived class") - class ProtectedExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { - - @Override - @BeforeEach - protected void beforeEach() { - } - - } - - @Nested - @DisplayName("a public lifecycle method in the derived class") - class PublicExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { - - @Override - @BeforeEach - public void beforeEach() { - } - - } - } - - @Nested - @DisplayName("A public lifecycle super-method can be overridden by") - class PublicSuperClassTests { - - @Nested - @DisplayName("a public lifecycle method in the derived class") - class PublicExtendsPackagePrivate extends SuperClassWithPublicLifecycleMethodTestCase { - - @Override - @BeforeEach - public void beforeEach() { - } - - } - } - -} - -// ------------------------------------------------------------------------- - -class SuperClassWithPackagePrivateLifecycleMethodTestCase { - - @BeforeEach - void beforeEach() { - fail(); - } - - @Test - void test() { - } - -} - -class SuperClassWithProtectedLifecycleMethodTestCase { - - @BeforeEach - protected void beforeEach() { - fail(); - } - - @Test - void test() { - } - -} - -class SuperClassWithPublicLifecycleMethodTestCase { - - @BeforeEach - public void beforeEach() { - fail(); - } - - @Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java deleted file mode 100644 index f8ded865..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.RepetitionInfo; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; - -/** - * Integration tests that verify the correct behavior for methods annotated - * with multiple testable annotations simultaneously. - * - * @since 5.0 - */ -@TrackLogRecords -class MultipleTestableAnnotationsTests extends AbstractJupiterTestEngineTests { - - @Test - void testAndRepeatedTest(LogRecordListener listener) { - discoverTests(request().selectors(selectClass(TestCase.class)).build()); - - // @formatter:off - assertTrue(listener.stream(Level.WARNING) - .map(LogRecord::getMessage) - .anyMatch(m -> m.matches("Possible configuration error: method .+ resulted in multiple TestDescriptors .+"))); - // @formatter:on - } - - static class TestCase { - - @Test - @RepeatedTest(1) - void testAndRepeatedTest(RepetitionInfo repetitionInfo) { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java deleted file mode 100644 index 2788b157..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.util.Throwables.getRootCause; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass; -import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedClass; -import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedSiblingClass; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests that verify support for {@linkplain Nested nested contexts} - * in the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class NestedTestClassesTests extends AbstractJupiterTestEngineTests { - - @Test - void nestedTestsAreCorrectlyDiscovered() { - LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithNesting.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(5, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void nestedTestsAreExecuted() { - EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithNesting.class); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(3, tests.started().count(), "# tests started"); - assertEquals(2, tests.succeeded().count(), "# tests succeeded"); - assertEquals(1, tests.failed().count(), "# tests failed"); - - assertEquals(3, containers.started().count(), "# containers started"); - assertEquals(3, containers.finished().count(), "# containers finished"); - } - - @Test - void doublyNestedTestsAreCorrectlyDiscovered() { - LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithDoubleNesting.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(8, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void doublyNestedTestsAreExecuted() { - EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithDoubleNesting.class); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(5, tests.started().count(), "# tests started"); - assertEquals(3, tests.succeeded().count(), "# tests succeeded"); - assertEquals(2, tests.failed().count(), "# tests failed"); - - assertEquals(4, containers.started().count(), "# containers started"); - assertEquals(4, containers.finished().count(), "# containers finished"); - - assertAll("before each counts", // - () -> assertEquals(5, TestCaseWithDoubleNesting.beforeTopCount), - () -> assertEquals(4, TestCaseWithDoubleNesting.beforeNestedCount), - () -> assertEquals(2, TestCaseWithDoubleNesting.beforeDoublyNestedCount)); - - assertAll("after each counts", // - () -> assertEquals(5, TestCaseWithDoubleNesting.afterTopCount), - () -> assertEquals(4, TestCaseWithDoubleNesting.afterNestedCount), - () -> assertEquals(2, TestCaseWithDoubleNesting.afterDoublyNestedCount)); - - } - - @Test - void inheritedNestedTestsAreExecuted() { - EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithInheritedNested.class); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(3, tests.started().count(), "# tests started"); - assertEquals(2, tests.succeeded().count(), "# tests succeeded"); - assertEquals(1, tests.failed().count(), "# tests failed"); - - assertEquals(4, containers.started().count(), "# containers started"); - assertEquals(4, containers.finished().count(), "# containers finished"); - } - - @Test - void extendedNestedTestsAreExecuted() { - EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithExtendedNested.class); - executionResults.allEvents().debug(); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(6, tests.started().count(), "# tests started"); - assertEquals(4, tests.succeeded().count(), "# tests succeeded"); - assertEquals(2, tests.failed().count(), "# tests failed"); - - assertEquals(8, containers.started().count(), "# containers started"); - assertEquals(8, containers.finished().count(), "# containers finished"); - } - - @Test - void deeplyNestedInheritedMethodsAreExecutedWhenSelectedViaUniqueId() { - EngineExecutionResults executionResults = executeTests(selectUniqueId( - "[engine:junit-jupiter]/[class:org.junit.jupiter.engine.NestedTestClassesTests$TestCaseWithExtendedNested]/[nested-class:ConcreteInner1]/[nested-class:NestedInAbstractClass]/[nested-class:SecondLevelInherited]/[method:test()]"), - selectUniqueId( - "[engine:junit-jupiter]/[class:org.junit.jupiter.engine.NestedTestClassesTests$TestCaseWithExtendedNested]/[nested-class:ConcreteInner2]/[nested-class:NestedInAbstractClass]/[nested-class:SecondLevelInherited]/[method:test()]")); - executionResults.allEvents().debug(); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(2, tests.started().count(), "# tests started"); - assertEquals(2, tests.succeeded().count(), "# tests succeeded"); - assertEquals(0, tests.failed().count(), "# tests failed"); - - assertEquals(8, containers.started().count(), "# containers started"); - assertEquals(8, containers.finished().count(), "# containers finished"); - } - - /** - * @since 1.6 - */ - @Test - void recursiveNestedTestClassHierarchiesAreNotExecuted() { - assertNestedCycle(OuterClass.class, RecursiveNestedClass.class, OuterClass.class); - assertNestedCycle(NestedClass.class, RecursiveNestedClass.class, OuterClass.class); - assertNestedCycle(RecursiveNestedClass.class, RecursiveNestedClass.class, OuterClass.class); - } - - /** - * NOTE: We do not actually support this as a feature, but we currently only - * check for cycles if a class is selected. Thus, the tests in this method - * pass, since the selection of a particular method does not result in a - * lookup for nested test classes. - * - * @since 1.6 - */ - @Test - void individualMethodsWithinRecursiveNestedTestClassHierarchiesAreExecuted() { - EngineExecutionResults executionResults = executeTests(selectMethod(OuterClass.class, "outer")); - executionResults.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); - - executionResults = executeTests(selectMethod(NestedClass.class, "nested")); - executionResults.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3)); - executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); - - executionResults = executeTests(selectMethod(RecursiveNestedClass.class, "nested")); - executionResults.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); - executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); - - executionResults = executeTests(selectMethod(RecursiveNestedSiblingClass.class, "nested")); - executionResults.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); - executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - private void assertNestedCycle(Class start, Class from, Class to) { - assertThatExceptionOfType(JUnitException.class)// - .isThrownBy(() -> executeTestsForClass(start))// - .withCauseExactlyInstanceOf(JUnitException.class)// - .satisfies(ex -> assertThat(getRootCause(ex)).hasMessageMatching( - String.format("Detected cycle in inner class hierarchy between .+%s and .+%s", from.getSimpleName(), - to.getSimpleName()))); - } - - // ------------------------------------------------------------------- - - static class TestCaseWithNesting { - - @Test - void someTest() { - } - - @Nested - class NestedTestCase { - - @Test - void successful() { - } - - @Test - void failing() { - Assertions.fail("Something went horribly wrong"); - } - } - } - - static class TestCaseWithDoubleNesting { - - static int beforeTopCount = 0; - static int beforeNestedCount = 0; - static int beforeDoublyNestedCount = 0; - - static int afterTopCount = 0; - static int afterNestedCount = 0; - static int afterDoublyNestedCount = 0; - - @BeforeEach - void beforeTop() { - beforeTopCount++; - } - - @AfterEach - void afterTop() { - afterTopCount++; - } - - @Test - void someTest() { - } - - @Nested - class NestedTestCase { - - @BeforeEach - void beforeNested() { - beforeNestedCount++; - } - - @AfterEach - void afterNested() { - afterNestedCount++; - } - - @Test - void successful() { - } - - @Test - void failing() { - Assertions.fail("Something went horribly wrong"); - } - - @Nested - class DoublyNestedTestCase { - - @BeforeEach - void beforeDoublyNested() { - beforeDoublyNestedCount++; - } - - @BeforeEach - void afterDoublyNested() { - afterDoublyNestedCount++; - } - - @Test - void successful() { - } - - @Test - void failing() { - Assertions.fail("Something went horribly wrong"); - } - } - } - } - - interface InterfaceWithNestedClass { - - @Nested - class NestedInInterface { - - @Test - void notExecutedByImplementingClass() { - Assertions.fail("class in interface is static and should have been filtered out"); - } - } - - } - - static abstract class AbstractSuperClass implements InterfaceWithNestedClass { - - @Nested - class NestedInAbstractClass { - - @Test - void successful() { - } - - @Test - void failing() { - Assertions.fail("something went wrong"); - } - - @Nested - class SecondLevelInherited { - @Test - void test() { - } - } - } - } - - static class TestCaseWithInheritedNested extends AbstractSuperClass { - // empty on purpose - } - - static class TestCaseWithExtendedNested { - @Nested - class ConcreteInner1 extends AbstractSuperClass { - } - - @Nested - class ConcreteInner2 extends AbstractSuperClass { - } - } - - static class AbstractOuterClass { - } - - static class OuterClass extends AbstractOuterClass { - - @Test - void outer() { - } - - @Nested - class NestedClass { - - @Test - void nested() { - } - - @Nested - class RecursiveNestedClass extends OuterClass { - - @Test - void nested() { - } - } - - @Nested - // sibling of OuterClass due to common super type - class RecursiveNestedSiblingClass extends AbstractOuterClass { - - @Test - void nested() { - } - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java deleted file mode 100644 index 529026ec..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -class NestedWithInheritanceTests extends SuperClass { - - static List lifecycleInvokingClassNames; - - static String OUTER = NestedWithInheritanceTests.class.getSimpleName(); - static String NESTED = NestedClass.class.getSimpleName(); - static String NESTEDNESTED = NestedClass.NestedNestedClass.class.getSimpleName(); - - @Nested - class NestedClass extends SuperClass { - - @Test - public void test() { - assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED); - } - - @Nested - class NestedNestedClass extends SuperClass { - - @Test - public void test() { - assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED, NESTEDNESTED); - } - } - - } - -} - -class SuperClass { - - @BeforeAll - static void setup() { - NestedWithInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); - } - - @BeforeEach - public void beforeEach() { - String invokingClass = this.getClass().getSimpleName(); - NestedWithInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java deleted file mode 100644 index 8b69ac20..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -class NestedWithSeparateInheritanceTests extends SuperClass1 { - - static List lifecycleInvokingClassNames; - - static String OUTER = NestedWithSeparateInheritanceTests.class.getSimpleName(); - static String NESTED = NestedClass.class.getSimpleName(); - static String NESTEDNESTED = NestedClass.NestedNestedClass.class.getSimpleName(); - - @Nested - class NestedClass extends SuperClass2 { - - @Test - public void test() { - assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED); - } - - @Nested - class NestedNestedClass extends SuperClass3 { - - @Test - public void test() { - assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED, NESTEDNESTED); - } - } - - } - -} - -class SuperClass1 { - - @BeforeAll - static void setup() { - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); - } - - @BeforeEach - public void beforeEach() { - String invokingClass = this.getClass().getSimpleName(); - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); - } - -} - -class SuperClass2 { - - @BeforeAll - static void setup() { - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); - } - - @BeforeEach - public void beforeEach() { - String invokingClass = this.getClass().getSimpleName(); - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); - } - -} - -class SuperClass3 { - - @BeforeAll - static void setup() { - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); - } - - @BeforeEach - public void beforeEach() { - String invokingClass = this.getClass().getSimpleName(); - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java deleted file mode 100644 index 8f44af79..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; - -class NonVoidTestableMethodIntegrationTests { - - @Test - void valid() { - } - - @Test - int invalidMethodReturningPrimitive() { - fail("This method should never have been called."); - return 1; - } - - @Test - String invalidMethodReturningObject() { - fail("This method should never have been called."); - return ""; - } - - @RepeatedTest(3) - int invalidMethodVerifyingTestTemplateMethod() { - fail("This method should never have been called."); - return 1; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java deleted file mode 100644 index 6f17f367..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; - -import java.util.Optional; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.testkit.engine.Event; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests for support of overloaded test methods in conjunction with - * the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class OverloadedTestMethodTests extends AbstractJupiterTestEngineTests { - - @Test - void executeTestCaseWithOverloadedMethodsAndThenRerunOnlyOneOfTheMethodsSelectedByUniqueId() { - Events tests = executeTestsForClass(TestCase.class).testEvents(); - - tests.assertStatistics(stats -> stats.started(2).succeeded(2).failed(0)); - - Optional first = tests.succeeded().filter( - event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); - assertTrue(first.isPresent()); - TestIdentifier testIdentifier = TestIdentifier.from(first.get().getTestDescriptor()); - UniqueId uniqueId = testIdentifier.getUniqueIdObject(); - - tests = executeTests(selectUniqueId(uniqueId)).testEvents(); - - tests.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); - - first = tests.succeeded().filter( - event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); - assertTrue(first.isPresent()); - } - - @Test - void executeTestCaseWithOverloadedMethodsWithSingleMethodThatAcceptsArgumentsSelectedByFullyQualifedMethodName() { - String fqmn = TestCase.class.getName() + "#test(" + TestInfo.class.getName() + ")"; - Events tests = executeTests(selectMethod(fqmn)).testEvents(); - - tests.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); - - Optional first = tests.succeeded().stream().filter( - event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); - assertTrue(first.isPresent()); - } - - static class TestCase { - - @Test - void test() { - } - - @Test - void test(TestInfo testInfo) { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java deleted file mode 100644 index f5a05db3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.Test; - -class RecordTests extends AbstractJupiterTestEngineTests { - - @Test - void recordsAreTestClasses() { - executeTestsForClass(TestCase.class).testEvents() // - .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); - } - - record TestCase() { - - @Test - void succeedingTest() { - assertTrue(true); - } - - @Test - void failingTest() { - fail("always fails"); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java deleted file mode 100644 index ca909b8d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static java.util.Collections.emptyMap; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestReporter; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 5.0 - */ -class ReportingTests extends AbstractJupiterTestEngineTests { - - @Test - void reportEntriesArePublished() { - executeTestsForClass(MyReportingTestCase.class).testEvents().assertStatistics(stats -> stats // - .started(2) // - .succeeded(2) // - .failed(0) // - .reportingEntryPublished(7)); - } - - static class MyReportingTestCase { - - @BeforeEach - void beforeEach(TestReporter reporter) { - reporter.publishEntry("@BeforeEach"); - } - - @AfterEach - void afterEach(TestReporter reporter) { - reporter.publishEntry("@AfterEach"); - } - - @Test - void succeedingTest(TestReporter reporter) { - reporter.publishEntry(emptyMap()); - reporter.publishEntry("user name", "dk38"); - reporter.publishEntry("message"); - } - - @Test - void invalidReportData(TestReporter reporter) { - - // Maps - Map map = new HashMap<>(); - - map.put("key", null); - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); - - map.clear(); - map.put(null, "value"); - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); - - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((Map) null)); - - // Key-Value pair - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(null, "bar")); - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry("foo", null)); - - // Value - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((String) null)); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java deleted file mode 100644 index e1e0f7dc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.Test; - -class SealedClassTests extends AbstractJupiterTestEngineTests { - - @Test - void sealedTestClassesAreTestClasses() { - executeTestsForClass(TestCase.class).testEvents() // - .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); - } - - sealed - abstract static class AbstractTestCase - permits TestCase - { - - @Test - void succeedingTest() { - assertTrue(true); - } - - @Test - void failingTest() { - fail("always fails"); - } - } - - static final class TestCase extends AbstractTestCase { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java deleted file mode 100644 index 230d5ebc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; -import org.opentest4j.TestAbortedException; - -/** - * Tests for discovery and execution of standard test cases for the - * {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class StandardTestClassTests extends AbstractJupiterTestEngineTests { - - @Test - void standardTestClassIsCorrectlyDiscovered() { - LauncherDiscoveryRequest request = request().selectors(selectClass(MyStandardTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(1 /*class*/ + 6 /*methods*/, engineDescriptor.getDescendants().size(), - "# resolved test descriptors"); - } - - @Test - void moreThanOneTestClassIsCorrectlyDiscovered() { - LauncherDiscoveryRequest request = request().selectors(selectClass(SecondOfTwoTestCases.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(1 /*class*/ + 3 /*methods*/, engineDescriptor.getDescendants().size(), - "# resolved test descriptors"); - } - - @Test - void moreThanOneTestClassIsExecuted() { - LauncherDiscoveryRequest request = request().selectors(selectClass(FirstOfTwoTestCases.class), - selectClass(SecondOfTwoTestCases.class)).build(); - - EngineExecutionResults executionResults = executeTests(request); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(6, tests.started().count(), "# tests started"); - assertEquals(5, tests.succeeded().count(), "# tests succeeded"); - assertEquals(1, tests.failed().count(), "# tests failed"); - - assertEquals(3, containers.started().count(), "# containers started"); - assertEquals(3, containers.finished().count(), "# containers finished"); - } - - @Test - void allTestsInClassAreRunWithBeforeEachAndAfterEachMethods() { - EngineExecutionResults executionResults = executeTestsForClass(MyStandardTestCase.class); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(2, containers.started().count(), "# containers started"); - assertEquals(2, containers.finished().count(), "# containers finished"); - - assertEquals(6, tests.started().count(), "# tests started"); - assertEquals(2, tests.succeeded().count(), "# tests succeeded"); - assertEquals(3, tests.aborted().count(), "# tests aborted"); - assertEquals(1, tests.failed().count(), "# tests failed"); - - assertEquals(6, MyStandardTestCase.countBefore1, "# before1 calls"); - assertEquals(6, MyStandardTestCase.countBefore2, "# before2 calls"); - assertEquals(6, MyStandardTestCase.countAfter, "# after each calls"); - } - - @Test - void testsFailWhenBeforeEachFails() { - EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithFailingBefore.class); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(2, tests.started().count(), "# tests started"); - assertEquals(0, tests.succeeded().count(), "# tests succeeded"); - assertEquals(2, tests.failed().count(), "# tests failed"); - - assertEquals(2, containers.started().count(), "# containers started"); - assertEquals(2, containers.finished().count(), "# containers finished"); - - assertEquals(2, TestCaseWithFailingBefore.countBefore, "# before each calls"); - } - - @Test - void testsFailWhenAfterEachFails() { - EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithFailingAfter.class); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - assertEquals(1, tests.started().count(), "# tests started"); - assertEquals(0, tests.succeeded().count(), "# tests succeeded"); - assertEquals(1, tests.failed().count(), "# tests failed"); - - assertEquals(2, containers.started().count(), "# containers started"); - assertEquals(2, containers.finished().count(), "# containers finished"); - - assertTrue(TestCaseWithFailingAfter.testExecuted, "test executed?"); - } - - static class MyStandardTestCase { - - static int countBefore1 = 0; - static int countBefore2 = 0; - static int countAfter = 0; - - @BeforeEach - void before1() { - countBefore1++; - } - - @BeforeEach - void before2() { - countBefore2++; - } - - @AfterEach - void after() { - countAfter++; - } - - @Test - void succeedingTest1() { - assertTrue(true); - } - - @Test - void succeedingTest2() { - assertTrue(true); - } - - @Test - void failingTest() { - fail("always fails"); - } - - @Test - @DisplayName("Test aborted via the OTA's TestAbortedException") - void testAbortedOpenTest4J() { - throw new TestAbortedException("aborted!"); - } - - @Test - @DisplayName("Test aborted via JUnit 4's AssumptionViolatedException") - void testAbortedJUnit4() { - throw new org.junit.AssumptionViolatedException("aborted!"); - } - - @Test - @DisplayName("Test aborted via JUnit 4's legacy, deprecated AssumptionViolatedException") - @SuppressWarnings("deprecation") - void testAbortedJUnit4Legacy() { - throw new org.junit.internal.AssumptionViolatedException("aborted!"); - } - - } - - static class FirstOfTwoTestCases { - - @Test - void succeedingTest1() { - assertTrue(true); - } - - @Test - void succeedingTest2() { - assertTrue(true); - } - - @Test - void failingTest() { - fail("always fails"); - } - - } - - static class SecondOfTwoTestCases { - - @Test - void succeedingTest1() { - assertTrue(true); - } - - @Test - void succeedingTest2() { - assertTrue(true); - } - - @Test - void succeedingTest3() { - assertTrue(true); - } - - } - - static class TestCaseWithFailingBefore { - - static int countBefore = 0; - - @BeforeEach - void before() { - countBefore++; - throw new RuntimeException("Problem during setup"); - } - - @Test - void test1() { - } - - @Test - void test2() { - } - - } - - static class TestCaseWithFailingAfter { - - static boolean testExecuted = false; - - @AfterEach - void after() { - throw new RuntimeException("Problem during 'after'"); - } - - @Test - void test1() { - testExecuted = true; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java deleted file mode 100644 index 42648602..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; - -/** - * Integration tests that verify support for {@code static} {@link BeforeAll} and - * {@link AfterAll} methods in {@link Nested} tests on Java 16+. - * - * @since 5.9 - * @see BeforeAllAndAfterAllComposedAnnotationTests - */ -class StaticNestedBeforeAllAndAfterAllMethodsTests extends AbstractJupiterTestEngineTests { - - private static final List methodsInvoked = new ArrayList<>(); - - @DisplayName("static @BeforeAll and @AfterAll methods in @Nested test class") - @Test - void staticBeforeAllAndAfterAllMethodsInNestedTestClass() { - executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - - assertThat(methodsInvoked).containsExactly(// - "@BeforeAll: top-level", // - "@Test: top-level", // - "@BeforeAll: nested", // - "@Test: nested", // - "@AfterAll: nested", // - "@AfterAll: top-level"// - ); - } - - static class TestCase { - - @BeforeAll - static void beforeAll() { - methodsInvoked.add("@BeforeAll: top-level"); - } - - @Test - void test() { - methodsInvoked.add("@Test: top-level"); - } - - @AfterAll - static void afterAll() { - methodsInvoked.add("@AfterAll: top-level"); - } - - @Nested - // Lifecycle.PER_METHOD is the default, but we declare it here in order - // to be very explicit about what we are testing, namely static lifecycle - // methods in an inner class WITHOUT Lifecycle.PER_CLASS semantics. - @TestInstance(Lifecycle.PER_METHOD) - class NestedTestCase { - - @BeforeAll - static void beforeAllInner() { - methodsInvoked.add("@BeforeAll: nested"); - } - - @Test - void test() { - methodsInvoked.add("@Test: nested"); - } - - @AfterAll - static void afterAllInner() { - methodsInvoked.add("@AfterAll: nested"); - } - - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java deleted file mode 100644 index 12aa4566..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests for test class hierarchy support in the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class TestClassInheritanceTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @BeforeEach - void initStatics() { - callSequence.clear(); - LocalTestCase.countBeforeInvoked = 0; - LocalTestCase.countAfterInvoked = 0; - AbstractTestCase.countSuperBeforeInvoked = 0; - AbstractTestCase.countSuperAfterInvoked = 0; - } - - @Test - void executeAllTestsInClass() { - EngineExecutionResults executionResults = executeTestsForClass(LocalTestCase.class); - - assertEquals(6, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(1, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(2, executionResults.testEvents().failed().count(), "# tests failed"); - - assertEquals(6, LocalTestCase.countBeforeInvoked, "# before calls"); - assertEquals(6, LocalTestCase.countAfterInvoked, "# after calls"); - assertEquals(6, AbstractTestCase.countSuperBeforeInvoked, "# super before calls"); - assertEquals(6, AbstractTestCase.countSuperAfterInvoked, "# super after calls"); - } - - @Test - void executeSingleTest() { - EngineExecutionResults executionResults = executeTests(selectMethod(LocalTestCase.class, "alwaysPasses")); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestDeclaredInSuperClass() { - EngineExecutionResults executionResults = executeTests(selectMethod(LocalTestCase.class, "superTest")); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - - assertEquals(1, LocalTestCase.countBeforeInvoked, "# after calls"); - assertEquals(1, LocalTestCase.countAfterInvoked, "# after calls"); - assertEquals(1, AbstractTestCase.countSuperBeforeInvoked, "# super before calls"); - assertEquals(1, AbstractTestCase.countSuperAfterInvoked, "# super after calls"); - - } - - @Test - void executeTestWithExceptionThrownInAfterMethod() { - EngineExecutionResults executionResults = executeTests( - selectMethod(LocalTestCase.class, "throwExceptionInAfterMethod")); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void beforeAndAfterMethodsInTestClassHierarchy() { - EngineExecutionResults executionResults = executeTestsForClass(TestCase3.class); - - // @formatter:off - assertAll( - () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), - () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), - () -> assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"), - () -> assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"), - () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") - ); - // @formatter:on - - // @formatter:off - assertEquals(asList( - "beforeAll1", - "beforeAll2", - "beforeAll3", - "beforeEach1", - "beforeEach2", - "beforeEach3", - "test3", - "afterEach3", - "afterEach2", - "afterEach1", - "afterAll3", - "afterAll2", - "afterAll1" - ), callSequence, "wrong call sequence"); - // @formatter:on - } - - // ------------------------------------------------------------------- - - private static abstract class AbstractTestCase { - - static int countSuperBeforeInvoked = 0; - static int countSuperAfterInvoked = 0; - - @BeforeEach - void superBefore() { - countSuperBeforeInvoked++; - } - - @AfterEach - void superAfter() { - countSuperAfterInvoked++; - } - - @Test - void superTest() { - /* no-op */ - } - } - - static class LocalTestCase extends AbstractTestCase { - - boolean throwExceptionInAfterMethod = false; - - static int countBeforeInvoked = 0; - static int countAfterInvoked = 0; - - @BeforeEach - void before() { - countBeforeInvoked++; - // Reset state, since the test instance is retained across all test methods; - // otherwise, after() always throws an exception. - this.throwExceptionInAfterMethod = false; - } - - @AfterEach - void after() { - countAfterInvoked++; - if (this.throwExceptionInAfterMethod) { - throw new RuntimeException("Exception thrown from @AfterEach method"); - } - } - - @Test - void otherTest() { - /* no-op */ - } - - @Test - void throwExceptionInAfterMethod() { - this.throwExceptionInAfterMethod = true; - } - - @Test - void alwaysPasses() { - /* no-op */ - } - - @Test - void aborted() { - assumeTrue(false); - } - - @Test - void alwaysFails() { - fail("#fail"); - } - } - - static class TestCase1 { - - @BeforeAll - static void beforeAll1() { - callSequence.add("beforeAll1"); - } - - @BeforeEach - void beforeEach1() { - callSequence.add("beforeEach1"); - } - - @AfterEach - void afterEach1() { - callSequence.add("afterEach1"); - } - - @AfterAll - static void afterAll1() { - callSequence.add("afterAll1"); - } - } - - static class TestCase2 extends TestCase1 { - - @BeforeAll - static void beforeAll2() { - callSequence.add("beforeAll2"); - } - - @BeforeEach - void beforeEach2() { - callSequence.add("beforeEach2"); - } - - @AfterEach - void afterEach2() { - callSequence.add("afterEach2"); - } - - @AfterAll - static void afterAll2() { - callSequence.add("afterAll2"); - } - } - - static class TestCase3 extends TestCase2 { - - @BeforeAll - static void beforeAll3() { - callSequence.add("beforeAll3"); - } - - @BeforeEach - void beforeEach3() { - callSequence.add("beforeEach3"); - } - - @Test - void test3() { - callSequence.add("test3"); - } - - @AfterEach - void afterEach3() { - callSequence.add("afterEach3"); - } - - @AfterAll - static void afterAll3() { - callSequence.add("afterAll3"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java deleted file mode 100644 index 87260717..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests for {@link TestInstance @TestInstance} lifecycle - * configuration support, not to be confused with {@link TestInstanceLifecycleTests}. - * - *

Specifically, this class tests custom lifecycle configuration specified - * via {@code @TestInstance} as well as via {@link ConfigurationParameters} - * supplied to the {@link Launcher} or via a JVM system property. - * - * @since 5.0 - * @see TestInstanceLifecycleTests - */ -class TestInstanceLifecycleConfigurationTests extends AbstractJupiterTestEngineTests { - - private static final String KEY = Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; - - private static final List methodsInvoked = new ArrayList<>(); - - @BeforeEach - @AfterEach - void reset() { - methodsInvoked.clear(); - System.clearProperty(KEY); - } - - @Test - void instancePerMethodUsingStandardDefaultConfiguration() { - performAssertions(AssumedInstancePerTestMethodTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); - } - - @Test - void instancePerClassConfiguredViaExplicitAnnotationDeclaration() { - performAssertions(ExplicitInstancePerClassTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); - } - - @Test - void instancePerClassConfiguredViaSystemProperty() { - Class testClass = AssumedInstancePerClassTestCase.class; - - // Should fail by default... - performAssertions(testClass, 2, 1, 0); - - // Should pass with the system property set - System.setProperty(KEY, PER_CLASS.name()); - performAssertions(testClass, 2, 0, 1, "beforeAll", "test", "afterAll"); - } - - @Test - void instancePerClassConfiguredViaConfigParam() { - Class testClass = AssumedInstancePerClassTestCase.class; - - // Should fail by default... - performAssertions(testClass, 2, 1, 0); - - // Should pass with the config param - performAssertions(testClass, singletonMap(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); - } - - @Test - void instancePerClassConfiguredViaConfigParamThatOverridesSystemProperty() { - Class testClass = AssumedInstancePerClassTestCase.class; - - // Should fail with system property - System.setProperty(KEY, PER_METHOD.name()); - performAssertions(testClass, 2, 1, 0); - - // Should pass with the config param - performAssertions(testClass, singletonMap(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); - } - - @Test - void instancePerMethodConfiguredViaExplicitAnnotationDeclarationThatOverridesSystemProperty() { - System.setProperty(KEY, PER_CLASS.name()); - performAssertions(ExplicitInstancePerTestMethodTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); - } - - @Test - void instancePerMethodConfiguredViaExplicitAnnotationDeclarationThatOverridesConfigParam() { - Class testClass = ExplicitInstancePerTestMethodTestCase.class; - performAssertions(testClass, singletonMap(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); - } - - private void performAssertions(Class testClass, int containers, int containersFailed, int tests, - String... methods) { - - performAssertions(testClass, emptyMap(), containers, containersFailed, tests, methods); - } - - private void performAssertions(Class testClass, Map configParams, int numContainers, - int numFailedContainers, int numTests, String... methods) { - - // @formatter:off - EngineExecutionResults executionResults = executeTests( - request() - .selectors(selectClass(testClass)) - .configurationParameters(configParams) - .build() - ); - // @formatter:on - - executionResults.containerEvents().assertStatistics(// - stats -> stats.started(numContainers).finished(numContainers).failed(numFailedContainers)); - executionResults.testEvents().assertStatistics(// - stats -> stats.started(numTests).finished(numTests)); - - assertEquals(Arrays.asList(methods), methodsInvoked); - } - - // ------------------------------------------------------------------------- - - @TestInstance(PER_METHOD) - static class ExplicitInstancePerTestMethodTestCase { - - @BeforeAll - static void beforeAll() { - methodsInvoked.add("beforeAll"); - } - - @Test - void test() { - methodsInvoked.add("test"); - } - - @AfterAll - static void afterAllStatic() { - methodsInvoked.add("afterAll"); - } - - } - - /** - * "per-method" lifecycle mode is assumed since the {@code @BeforeAll} and - * {@code @AfterAll} methods are static, even though there is no explicit - * {@code @TestInstance} declaration. - */ - static class AssumedInstancePerTestMethodTestCase { - - @BeforeAll - static void beforeAll() { - methodsInvoked.add("beforeAll"); - } - - @Test - void test() { - methodsInvoked.add("test"); - } - - @AfterAll - static void afterAllStatic() { - methodsInvoked.add("afterAll"); - } - - } - - @TestInstance(PER_CLASS) - static class ExplicitInstancePerClassTestCase { - - @BeforeAll - void beforeAll(TestInfo testInfo) { - methodsInvoked.add("beforeAll"); - } - - @Test - void test() { - methodsInvoked.add("test"); - } - - @AfterAll - void afterAll(TestInfo testInfo) { - methodsInvoked.add("afterAll"); - } - - } - - /** - * "per-class" lifecycle mode is assumed since the {@code @BeforeAll} and - * {@code @AfterAll} methods are non-static, even though there is no - * explicit {@code @TestInstance} declaration. - */ - static class AssumedInstancePerClassTestCase { - - @BeforeAll - void beforeAll(TestInfo testInfo) { - methodsInvoked.add("beforeAll"); - } - - @Test - void test() { - methodsInvoked.add("test"); - } - - @AfterAll - void afterAll(TestInfo testInfo) { - methodsInvoked.add("afterAll"); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java deleted file mode 100644 index 50d059df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.engine.kotlin.InstancePerClassKotlinTestCase; -import org.junit.jupiter.engine.kotlin.InstancePerMethodKotlinTestCase; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Kotlin-specific integration tests for {@link TestInstance @TestInstance} - * lifecycle support. - * - * @since 5.1 - * @see TestInstanceLifecycleConfigurationTests - * @see TestInstanceLifecycleTests - */ -class TestInstanceLifecycleKotlinTests extends AbstractJupiterTestEngineTests { - - @Test - void instancePerClassCanBeUsedForKotlinTestClasses() { - Class testClass = InstancePerClassKotlinTestCase.class; - InstancePerClassKotlinTestCase.TEST_INSTANCES.clear(); - - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertThat(executionResults.testEvents().finished().count()).isEqualTo(2); - assertThat(InstancePerClassKotlinTestCase.TEST_INSTANCES.keySet()).hasSize(1); - assertThat(getOnlyElement(InstancePerClassKotlinTestCase.TEST_INSTANCES.values())) // - .containsEntry("beforeAll", 1) // - .containsEntry("beforeEach", 2) // - .containsEntry("test", 2) // - .containsEntry("afterEach", 2) // - .containsEntry("afterAll", 1); - } - - @Test - void instancePerMethodIsDefaultForKotlinTestClasses() { - Class testClass = InstancePerMethodKotlinTestCase.class; - InstancePerMethodKotlinTestCase.TEST_INSTANCES.clear(); - - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertThat(executionResults.testEvents().finished().count()).isEqualTo(2); - List instances = new ArrayList<>(InstancePerMethodKotlinTestCase.TEST_INSTANCES.keySet()); - assertThat(instances) // - .hasSize(3) // - .extracting(o -> (Object) o.getClass()) // - .containsExactly(InstancePerMethodKotlinTestCase.Companion.getClass(), // - InstancePerMethodKotlinTestCase.class, // - InstancePerMethodKotlinTestCase.class); - assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(0))) // - .containsEntry("beforeAll", 1) // - .containsEntry("afterAll", 1); - assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(1))) // - .containsEntry("beforeEach", 1) // - .containsEntry("test", 1) // - .containsEntry("afterEach", 1); - assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(2))) // - .containsEntry("beforeEach", 1) // - .containsEntry("test", 1) // - .containsEntry("afterEach", 1); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java deleted file mode 100644 index 91548496..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java +++ /dev/null @@ -1,1067 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static java.lang.String.join; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.jupiter.engine.execution.DefaultTestInstances; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests for {@link TestInstance @TestInstance} lifecycle support. - * - * @since 5.0 - * @see TestInstanceLifecycleConfigurationTests - * @see TestInstanceLifecycleKotlinTests - */ -class TestInstanceLifecycleTests extends AbstractJupiterTestEngineTests { - - private static final Map, List> lifecyclesMap = new LinkedHashMap<>(); - private static final Map instanceMap = new LinkedHashMap<>(); - private static final List testsInvoked = new ArrayList<>(); - private static final Map, Integer> instanceCount = new LinkedHashMap<>(); - - private static int beforeAllCount; - private static int afterAllCount; - private static int beforeEachCount; - private static int afterEachCount; - - @BeforeEach - void init() { - lifecyclesMap.clear(); - instanceMap.clear(); - testsInvoked.clear(); - instanceCount.clear(); - beforeAllCount = 0; - afterAllCount = 0; - beforeEachCount = 0; - afterEachCount = 0; - } - - @Test - void instancePerMethod() { - Class testClass = InstancePerMethodTestCase.class; - int containers = 3; - int tests = 3; - Map.Entry, Integer>[] instances = instanceCounts(entry(InstancePerMethodTestCase.class, 3)); - int allMethods = 1; - int eachMethods = 3; - - performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); - - String containerExecutionConditionKey = executionConditionKey(testClass, null); - String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); - String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); - String beforeAllCallbackKey = beforeAllCallbackKey(testClass); - String afterAllCallbackKey = afterAllCallbackKey(testClass); - String testTemplateKey = testTemplateKey(testClass, "singletonTest"); - String testExecutionConditionKey1 = executionConditionKey(testClass, testsInvoked.get(0)); - String beforeEachCallbackKey1 = beforeEachCallbackKey(testClass, testsInvoked.get(0)); - String afterEachCallbackKey1 = afterEachCallbackKey(testClass, testsInvoked.get(0)); - String testExecutionConditionKey2 = executionConditionKey(testClass, testsInvoked.get(1)); - String beforeEachCallbackKey2 = beforeEachCallbackKey(testClass, testsInvoked.get(1)); - String afterEachCallbackKey2 = afterEachCallbackKey(testClass, testsInvoked.get(1)); - String testExecutionConditionKey3 = executionConditionKey(testClass, testsInvoked.get(2)); - String beforeEachCallbackKey3 = beforeEachCallbackKey(testClass, testsInvoked.get(2)); - String afterEachCallbackKey3 = afterEachCallbackKey(testClass, testsInvoked.get(2)); - - // @formatter:off - assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( - containerExecutionConditionKey, - beforeAllCallbackKey, - postProcessTestInstanceKey, - preDestroyCallbackTestInstanceKey, - testTemplateKey, - testExecutionConditionKey1, - beforeEachCallbackKey1, - afterEachCallbackKey1, - testExecutionConditionKey2, - beforeEachCallbackKey2, - afterEachCallbackKey2, - testExecutionConditionKey3, - beforeEachCallbackKey3, - afterEachCallbackKey3, - afterAllCallbackKey - ); - // @formatter:on - - assertNull(instanceMap.get(containerExecutionConditionKey)); - assertNull(instanceMap.get(beforeAllCallbackKey)); - assertNull(instanceMap.get(afterAllCallbackKey)); - - TestInstances testInstances = instanceMap.get(beforeEachCallbackKey1); - assertNotNull(testInstances.getInnermostInstance()); - assertSame(testInstances, instanceMap.get(afterEachCallbackKey1)); - assertSame(testInstances, instanceMap.get(testExecutionConditionKey1)); - - testInstances = instanceMap.get(beforeEachCallbackKey2); - assertNotNull(testInstances.getInnermostInstance()); - assertSame(testInstances, instanceMap.get(afterEachCallbackKey2)); - assertSame(testInstances, instanceMap.get(testExecutionConditionKey2)); - - testInstances = instanceMap.get(beforeEachCallbackKey3); - assertNotNull(testInstances.getInnermostInstance()); - assertSame(testInstances, instanceMap.get(afterEachCallbackKey3)); - assertSame(testInstances, instanceMap.get(testExecutionConditionKey3)); - assertSame(testInstances.getInnermostInstance(), - instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); - assertSame(testInstances.getInnermostInstance(), - instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); - - assertThat(lifecyclesMap.keySet()).containsExactly(testClass); - assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); - } - - @Test - void instancePerClass() { - instancePerClass(InstancePerClassTestCase.class, instanceCounts(entry(InstancePerClassTestCase.class, 1))); - } - - @Test - void instancePerClassWithInheritedLifecycleMode() { - instancePerClass(SubInstancePerClassTestCase.class, - instanceCounts(entry(SubInstancePerClassTestCase.class, 1))); - } - - private void instancePerClass(Class testClass, Map.Entry, Integer>[] instances) { - int containers = 3; - int tests = 3; - int allMethods = 2; - int eachMethods = 3; - - performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); - - String containerExecutionConditionKey = executionConditionKey(testClass, null); - String testTemplateKey = testTemplateKey(testClass, "singletonTest"); - String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); - String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); - String beforeAllCallbackKey = beforeAllCallbackKey(testClass); - String afterAllCallbackKey = afterAllCallbackKey(testClass); - String testExecutionConditionKey1 = executionConditionKey(testClass, testsInvoked.get(0)); - String beforeEachCallbackKey1 = beforeEachCallbackKey(testClass, testsInvoked.get(0)); - String afterEachCallbackKey1 = afterEachCallbackKey(testClass, testsInvoked.get(0)); - String testExecutionConditionKey2 = executionConditionKey(testClass, testsInvoked.get(1)); - String beforeEachCallbackKey2 = beforeEachCallbackKey(testClass, testsInvoked.get(1)); - String afterEachCallbackKey2 = afterEachCallbackKey(testClass, testsInvoked.get(1)); - String testExecutionConditionKey3 = executionConditionKey(testClass, testsInvoked.get(2)); - String beforeEachCallbackKey3 = beforeEachCallbackKey(testClass, testsInvoked.get(2)); - String afterEachCallbackKey3 = afterEachCallbackKey(testClass, testsInvoked.get(2)); - - // @formatter:off - assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( - postProcessTestInstanceKey, - preDestroyCallbackTestInstanceKey, - containerExecutionConditionKey, - beforeAllCallbackKey, - testTemplateKey, - testExecutionConditionKey1, - beforeEachCallbackKey1, - afterEachCallbackKey1, - testExecutionConditionKey2, - beforeEachCallbackKey2, - afterEachCallbackKey2, - testExecutionConditionKey3, - beforeEachCallbackKey3, - afterEachCallbackKey3, - afterAllCallbackKey - ); - // @formatter:on - - TestInstances testInstances = instanceMap.get(beforeAllCallbackKey); - assertNotNull(testInstances.getInnermostInstance()); - assertSame(testInstances, instanceMap.get(afterAllCallbackKey)); - assertSame(testInstances, instanceMap.get(testExecutionConditionKey1)); - assertSame(testInstances, instanceMap.get(beforeEachCallbackKey1)); - assertSame(testInstances, instanceMap.get(afterEachCallbackKey1)); - assertSame(testInstances, instanceMap.get(testExecutionConditionKey2)); - assertSame(testInstances, instanceMap.get(beforeEachCallbackKey2)); - assertSame(testInstances, instanceMap.get(afterEachCallbackKey2)); - assertSame(testInstances, instanceMap.get(testExecutionConditionKey3)); - assertSame(testInstances, instanceMap.get(beforeEachCallbackKey3)); - assertSame(testInstances, instanceMap.get(afterEachCallbackKey3)); - assertSame(testInstances.getInnermostInstance(), - instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); - assertSame(testInstances.getInnermostInstance(), - instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); - - assertNull(instanceMap.get(containerExecutionConditionKey)); - - assertThat(lifecyclesMap.keySet()).containsExactly(testClass); - assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); - } - - @Test - void instancePerMethodWithNestedTestClass() { - Class testClass = InstancePerMethodOuterTestCase.class; - Class nestedTestClass = InstancePerMethodOuterTestCase.NestedInstancePerMethodTestCase.class; - int containers = 4; - int tests = 4; - Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 4), entry(nestedTestClass, 3)); - int allMethods = 1; - int eachMethods = 3; - - performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); - - String containerExecutionConditionKey = executionConditionKey(testClass, null); - String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); - String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); - String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); - String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); - String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); - String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); - String beforeAllCallbackKey = beforeAllCallbackKey(testClass); - String afterAllCallbackKey = afterAllCallbackKey(testClass); - String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); - String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); - String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); - String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); - String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); - String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.get(0)); - String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); - String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); - String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); - String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); - String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); - String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); - String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); - String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); - - // @formatter:off - assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( - containerExecutionConditionKey, - nestedTestTemplateKey, - nestedContainerExecutionConditionKey, - postProcessTestInstanceKey, - nestedPostProcessTestInstanceKey, - preDestroyCallbackTestInstanceKey, - nestedPreDestroyCallbackTestInstanceKey, - beforeAllCallbackKey, - afterAllCallbackKey, - outerTestExecutionConditionKey, - beforeEachCallbackKey, - afterEachCallbackKey, - nestedBeforeAllCallbackKey, - nestedAfterAllCallbackKey, - nestedExecutionConditionKey1, - nestedBeforeEachCallbackKey1, - nestedAfterEachCallbackKey1, - nestedExecutionConditionKey2, - nestedBeforeEachCallbackKey2, - nestedAfterEachCallbackKey2, - nestedExecutionConditionKey3, - nestedBeforeEachCallbackKey3, - nestedAfterEachCallbackKey3 - ); - // @formatter:on - - assertNull(instanceMap.get(containerExecutionConditionKey)); - assertNull(instanceMap.get(beforeAllCallbackKey)); - assertNull(instanceMap.get(afterAllCallbackKey)); - assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); - assertNull(instanceMap.get(nestedBeforeAllCallbackKey)); - assertNull(instanceMap.get(nestedAfterAllCallbackKey)); - - TestInstances outerInstances = instanceMap.get(beforeEachCallbackKey); - assertNotNull(outerInstances.getInnermostInstance()); - assertSame(outerInstances, instanceMap.get(afterEachCallbackKey)); - assertSame(outerInstances, instanceMap.get(outerTestExecutionConditionKey)); - - TestInstances nestedInstances1 = instanceMap.get(nestedBeforeEachCallbackKey1); - assertNotNull(nestedInstances1.getInnermostInstance()); - assertNotSame(outerInstances.getInnermostInstance(), nestedInstances1.getInnermostInstance()); - assertSame(nestedInstances1, instanceMap.get(nestedAfterEachCallbackKey1)); - assertSame(nestedInstances1, instanceMap.get(nestedExecutionConditionKey1)); - - TestInstances nestedInstances2 = instanceMap.get(nestedBeforeEachCallbackKey2); - assertNotNull(nestedInstances2.getInnermostInstance()); - assertNotSame(outerInstances.getInnermostInstance(), nestedInstances2.getInnermostInstance()); - assertNotSame(nestedInstances1.getInnermostInstance(), nestedInstances2.getInnermostInstance()); - assertSame(nestedInstances2, instanceMap.get(nestedAfterEachCallbackKey2)); - assertSame(nestedInstances2, instanceMap.get(nestedExecutionConditionKey2)); - - TestInstances nestedInstances3 = instanceMap.get(nestedPostProcessTestInstanceKey); - assertNotNull(nestedInstances3.getInnermostInstance()); - assertNotSame(outerInstances.getInnermostInstance(), nestedInstances3.getInnermostInstance()); - assertNotSame(nestedInstances1.getInnermostInstance(), nestedInstances3.getInnermostInstance()); - assertSame(nestedInstances3.getInnermostInstance(), - instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); - assertSame(nestedInstances3.getInnermostInstance(), - instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); - assertSame(nestedInstances3.getInnermostInstance(), - instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); - - Object outerInstance1 = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); - Object outerInstance2 = instanceMap.get(nestedExecutionConditionKey2).findInstance(testClass).get(); - Object outerInstance3 = instanceMap.get(nestedExecutionConditionKey3).findInstance(testClass).get(); - assertNotSame(outerInstance1, outerInstance2); - assertNotSame(outerInstance1, outerInstance3); - assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance1, - nestedInstances1.getInnermostInstance()); - assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance2, - nestedInstances2.getInnermostInstance()); - assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance3, - nestedInstances3.getInnermostInstance()); - - // The last tracked instance stored under postProcessTestInstanceKey - // is only created in order to instantiate the nested test class for - // test2(). - assertSame(outerInstance3, instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); - - assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); - assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); - assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); - } - - @Test - void instancePerClassWithNestedTestClass() { - Class testClass = InstancePerClassOuterTestCase.class; - Class nestedTestClass = InstancePerClassOuterTestCase.NestedInstancePerClassTestCase.class; - int containers = 4; - int tests = 4; - Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 1), entry(nestedTestClass, 1)); - int allMethods = 2; - int eachMethods = 3; - - performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); - - String containerExecutionConditionKey = executionConditionKey(testClass, null); - String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); - String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); - String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); - String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); - String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); - String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); - String beforeAllCallbackKey = beforeAllCallbackKey(testClass); - String afterAllCallbackKey = afterAllCallbackKey(testClass); - String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); - String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); - String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); - String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); - String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); - String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.get(0)); - String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); - String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); - String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); - String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); - String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); - String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); - String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); - String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); - - // @formatter:off - assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( - containerExecutionConditionKey, - nestedTestTemplateKey, - nestedContainerExecutionConditionKey, - postProcessTestInstanceKey, - nestedPostProcessTestInstanceKey, - preDestroyCallbackTestInstanceKey, - nestedPreDestroyCallbackTestInstanceKey, - beforeAllCallbackKey, - afterAllCallbackKey, - outerTestExecutionConditionKey, - beforeEachCallbackKey, - afterEachCallbackKey, - nestedBeforeAllCallbackKey, - nestedAfterAllCallbackKey, - nestedExecutionConditionKey1, - nestedBeforeEachCallbackKey1, - nestedAfterEachCallbackKey1, - nestedExecutionConditionKey2, - nestedBeforeEachCallbackKey2, - nestedAfterEachCallbackKey2, - nestedExecutionConditionKey3, - nestedBeforeEachCallbackKey3, - nestedAfterEachCallbackKey3 - ); - // @formatter:on - - Object instance = instanceMap.get(postProcessTestInstanceKey).getInnermostInstance(); - assertNotNull(instance); - assertNull(instanceMap.get(containerExecutionConditionKey)); - assertSame(instance, instanceMap.get(beforeAllCallbackKey).getInnermostInstance()); - assertSame(instance, instanceMap.get(afterAllCallbackKey).getInnermostInstance()); - assertSame(instance, instanceMap.get(outerTestExecutionConditionKey).getInnermostInstance()); - assertSame(instance, instanceMap.get(beforeEachCallbackKey).getInnermostInstance()); - assertSame(instance, instanceMap.get(afterEachCallbackKey).getInnermostInstance()); - assertSame(instance, instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); - - Object nestedInstance = instanceMap.get(nestedPostProcessTestInstanceKey).getInnermostInstance(); - assertNotNull(nestedInstance); - assertNotSame(instance, nestedInstance); - assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); - assertSame(nestedInstance, instanceMap.get(nestedBeforeAllCallbackKey).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterAllCallbackKey).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey1).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey1).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey1).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey2).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey2).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey2).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey3).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); - - Object outerInstance = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); - assertSame(outerInstance, instance); - assertSame(outerInstance, instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); - assertSame(outerInstance, instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); - - assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedBeforeEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedAfterEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedBeforeEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedAfterEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedBeforeEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedAfterEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - - assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); - assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); - assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); - } - - @Test - void instancePerMethodOnOuterTestClassWithInstancePerClassOnNestedTestClass() { - Class testClass = MixedLifecyclesOuterTestCase.class; - Class nestedTestClass = MixedLifecyclesOuterTestCase.NestedInstancePerClassTestCase.class; - int containers = 4; - int tests = 4; - Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 2), entry(nestedTestClass, 1)); - int allMethods = 1; - int eachMethods = 7; - - performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); - - String containerExecutionConditionKey = executionConditionKey(testClass, null); - String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); - String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); - String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); - String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); - String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); - String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); - String beforeAllCallbackKey = beforeAllCallbackKey(testClass); - String afterAllCallbackKey = afterAllCallbackKey(testClass); - String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); - String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); - String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); - String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); - String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); - String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.get(0)); - String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); - String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); - String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); - String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); - String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); - String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); - String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); - String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); - - // @formatter:off - assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( - containerExecutionConditionKey, - nestedTestTemplateKey, - nestedContainerExecutionConditionKey, - postProcessTestInstanceKey, - nestedPostProcessTestInstanceKey, - preDestroyCallbackTestInstanceKey, - nestedPreDestroyCallbackTestInstanceKey, - beforeAllCallbackKey, - afterAllCallbackKey, - outerTestExecutionConditionKey, - beforeEachCallbackKey, - afterEachCallbackKey, - nestedBeforeAllCallbackKey, - nestedAfterAllCallbackKey, - nestedExecutionConditionKey1, - nestedBeforeEachCallbackKey1, - nestedAfterEachCallbackKey1, - nestedExecutionConditionKey2, - nestedBeforeEachCallbackKey2, - nestedAfterEachCallbackKey2, - nestedExecutionConditionKey3, - nestedBeforeEachCallbackKey3, - nestedAfterEachCallbackKey3 - ); - // @formatter:on - - assertNull(instanceMap.get(containerExecutionConditionKey)); - assertNull(instanceMap.get(beforeAllCallbackKey)); - assertNull(instanceMap.get(afterAllCallbackKey)); - - TestInstances outerInstances = instanceMap.get(beforeEachCallbackKey); - assertSame(outerInstances, instanceMap.get(afterEachCallbackKey)); - assertSame(outerInstances, instanceMap.get(outerTestExecutionConditionKey)); - - Object nestedInstance = instanceMap.get(nestedPostProcessTestInstanceKey).getInnermostInstance(); - assertNotNull(nestedInstance); - assertNotSame(outerInstances.getInnermostInstance(), nestedInstance); - assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); - assertSame(nestedInstance, instanceMap.get(nestedBeforeAllCallbackKey).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterAllCallbackKey).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey1).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey1).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey1).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey2).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey2).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey2).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey3).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); - assertSame(nestedInstance, instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); - - // The last tracked instance stored under postProcessTestInstanceKey - // is only created in order to instantiate the nested test class. - Object outerInstance = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); - assertEquals(outerInstances.getInnermostInstance().getClass(), outerInstance.getClass()); - assertNotSame(outerInstances.getInnermostInstance(), outerInstance); - assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedBeforeEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedAfterEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedBeforeEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedAfterEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedBeforeEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - assertThat(instanceMap.get(nestedAfterEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, - nestedInstance); - - assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); - assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); - assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); - } - - private void performAssertions(Class testClass, int numContainers, int numTests, - Map.Entry, Integer>[] instanceCountEntries, int allMethods, int eachMethods) { - - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - executionResults.containerEvents().assertStatistics( - stats -> stats.started(numContainers).finished(numContainers)); - executionResults.testEvents().assertStatistics(stats -> stats.started(numTests).finished(numTests)); - - // @formatter:off - assertAll( - () -> assertThat(instanceCount).describedAs("instance count").contains(instanceCountEntries), - () -> assertEquals(allMethods, beforeAllCount, "@BeforeAll count"), - () -> assertEquals(allMethods, afterAllCount, "@AfterAll count"), - () -> assertEquals(eachMethods, beforeEachCount, "@BeforeEach count"), - () -> assertEquals(eachMethods, afterEachCount, "@AfterEach count") - ); - // @formatter:on - } - - @SafeVarargs - @SuppressWarnings("varargs") - private final Map.Entry, Integer>[] instanceCounts(Map.Entry, Integer>... entries) { - return entries; - } - - private static void incrementInstanceCount(Class testClass) { - instanceCount.compute(testClass, (key, value) -> value == null ? 1 : value + 1); - } - - private static String executionConditionKey(Class testClass, String testMethod) { - return concat(ExecutionCondition.class, testClass, testMethod); - } - - private static String postProcessTestInstanceKey(Class testClass) { - return concat(TestInstancePostProcessor.class, testClass); - } - - private static String preDestroyCallbackTestInstanceKey(Class testClass) { - return concat(TestInstancePreDestroyCallback.class, testClass); - } - - private static String beforeAllCallbackKey(Class testClass) { - return concat(BeforeAllCallback.class, testClass); - } - - private static String afterAllCallbackKey(Class testClass) { - return concat(AfterAllCallback.class, testClass); - } - - private static String beforeEachCallbackKey(Class testClass, String testMethod) { - return concat(BeforeEachCallback.class, testClass, testMethod); - } - - private static String afterEachCallbackKey(Class testClass, String testMethod) { - return concat(AfterEachCallback.class, testClass, testMethod); - } - - private static String testTemplateKey(Class testClass, String testMethod) { - return concat(TestTemplateInvocationContextProvider.class, testClass, testMethod); - } - - private static String concat(Class c1, Class c2, String str) { - return concat(c1.getSimpleName(), c2.getSimpleName(), str); - } - - private static String concat(Class c1, Class c2) { - return concat(c1.getSimpleName(), c2.getSimpleName()); - } - - private static String concat(String... args) { - return join(".", args); - } - - // ------------------------------------------------------------------------- - - @ExtendWith(InstanceTrackingExtension.class) - // The following is commented out b/c it's the default. - // @TestInstance(Lifecycle.PER_METHOD) - static class InstancePerMethodTestCase { - - InstancePerMethodTestCase() { - incrementInstanceCount(InstancePerMethodTestCase.class); - } - - @BeforeAll - static void beforeAllStatic(TestInfo testInfo) { - assertNotNull(testInfo); - beforeAllCount++; - } - - @BeforeEach - void beforeEach() { - beforeEachCount++; - } - - @Test - void test1(TestInfo testInfo) { - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @Test - void test2(TestInfo testInfo) { - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @SingletonTest - void singletonTest(TestInfo testInfo) { - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @AfterEach - void afterEach() { - afterEachCount++; - } - - @AfterAll - static void afterAllStatic(TestInfo testInfo) { - assertNotNull(testInfo); - afterAllCount++; - } - - } - - @TestInstance(Lifecycle.PER_CLASS) - static class InstancePerClassTestCase extends InstancePerMethodTestCase { - - InstancePerClassTestCase() { - incrementInstanceCount(InstancePerClassTestCase.class); - } - - @BeforeAll - void beforeAll(TestInfo testInfo) { - assertNotNull(testInfo); - beforeAllCount++; - } - - @AfterAll - void afterAll(TestInfo testInfo) { - assertNotNull(testInfo); - afterAllCount++; - } - - } - - static class SubInstancePerClassTestCase extends InstancePerClassTestCase { - SubInstancePerClassTestCase() { - incrementInstanceCount(SubInstancePerClassTestCase.class); - } - } - - @ExtendWith(InstanceTrackingExtension.class) - // The following is commented out b/c it's the default. - // @TestInstance(Lifecycle.PER_METHOD) - static class InstancePerMethodOuterTestCase { - - InstancePerMethodOuterTestCase() { - incrementInstanceCount(InstancePerMethodOuterTestCase.class); - } - - @BeforeAll - static void beforeAll(TestInfo testInfo) { - assertNotNull(testInfo); - beforeAllCount++; - } - - @Test - void outerTest() { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - } - - @AfterAll - static void afterAll(TestInfo testInfo) { - assertNotNull(testInfo); - afterAllCount++; - } - - @Nested - // The following is commented out b/c it's the default. - // @TestInstance(Lifecycle.PER_METHOD) - class NestedInstancePerMethodTestCase { - - NestedInstancePerMethodTestCase() { - incrementInstanceCount(NestedInstancePerMethodTestCase.class); - } - - @BeforeEach - void beforeEach() { - beforeEachCount++; - } - - @Test - void test1(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @Test - void test2(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @SingletonTest - void singletonTest(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @AfterEach - void afterEach() { - afterEachCount++; - } - } - } - - @ExtendWith(InstanceTrackingExtension.class) - @TestInstance(Lifecycle.PER_CLASS) - static class InstancePerClassOuterTestCase { - - InstancePerClassOuterTestCase() { - incrementInstanceCount(InstancePerClassOuterTestCase.class); - } - - @BeforeAll - static void beforeAll(TestInfo testInfo) { - assertNotNull(testInfo); - beforeAllCount++; - } - - @Test - void outerTest() { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - } - - @AfterAll - static void afterAll(TestInfo testInfo) { - assertNotNull(testInfo); - afterAllCount++; - } - - @Nested - @TestInstance(Lifecycle.PER_CLASS) - class NestedInstancePerClassTestCase { - - NestedInstancePerClassTestCase() { - incrementInstanceCount(NestedInstancePerClassTestCase.class); - } - - @BeforeAll - void beforeAll(TestInfo testInfo) { - assertNotNull(testInfo); - beforeAllCount++; - } - - @BeforeEach - void beforeEach() { - beforeEachCount++; - } - - @Test - void test1(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @Test - void test2(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @SingletonTest - void singletonTest(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @AfterEach - void afterEach() { - afterEachCount++; - } - - @AfterAll - void afterAll(TestInfo testInfo) { - assertNotNull(testInfo); - afterAllCount++; - } - } - } - - @ExtendWith(InstanceTrackingExtension.class) - // The following is commented out b/c it's the default. - // @TestInstance(Lifecycle.PER_METHOD) - static class MixedLifecyclesOuterTestCase { - - MixedLifecyclesOuterTestCase() { - incrementInstanceCount(MixedLifecyclesOuterTestCase.class); - } - - @BeforeEach - void beforeEach() { - beforeEachCount++; - } - - @Test - void outerTest() { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - } - - @AfterEach - void afterEach() { - afterEachCount++; - } - - @Nested - @TestInstance(Lifecycle.PER_CLASS) - class NestedInstancePerClassTestCase { - - NestedInstancePerClassTestCase() { - incrementInstanceCount(NestedInstancePerClassTestCase.class); - } - - @BeforeAll - void beforeAll(TestInfo testInfo) { - assertNotNull(testInfo); - beforeAllCount++; - } - - @BeforeEach - void beforeEach() { - beforeEachCount++; - } - - @Test - void test1(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @Test - void test2(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @SingletonTest - void singletonTest(TestInfo testInfo) { - assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); - testsInvoked.add(testInfo.getTestMethod().get().getName()); - } - - @AfterEach - void afterEach() { - afterEachCount++; - } - - @AfterAll - void afterAll(TestInfo testInfo) { - assertNotNull(testInfo); - afterAllCount++; - } - } - } - - // Intentionally not implementing BeforeTestExecutionCallback, AfterTestExecutionCallback, - // and TestExecutionExceptionHandler, since they are analogous to BeforeEachCallback and - // AfterEachCallback with regard to instance scope and Lifecycle. - static class InstanceTrackingExtension - implements ExecutionCondition, TestInstancePostProcessor, TestInstancePreDestroyCallback, BeforeAllCallback, - AfterAllCallback, BeforeEachCallback, AfterEachCallback, TestTemplateInvocationContextProvider { - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - trackLifecycle(context); - String testMethod = context.getTestMethod().map(Method::getName).orElse(null); - if (testMethod == null) { - assertThat(context.getTestInstance()).isNotPresent(); - assertThat(instanceCount.getOrDefault(context.getRequiredTestClass(), 0)).isEqualTo(0); - } - instanceMap.put(executionConditionKey(context.getRequiredTestClass(), testMethod), - context.getTestInstances().orElse(null)); - - return ConditionEvaluationResult.enabled("enigma"); - } - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - trackLifecycle(context); - assertThat(context.getTestInstance()).isNotPresent(); - assertNotNull(testInstance); - instanceMap.put(postProcessTestInstanceKey(context.getRequiredTestClass()), - DefaultTestInstances.of(testInstance)); - } - - @Override - public void preDestroyTestInstance(ExtensionContext context) { - trackLifecycle(context); - assertThat(context.getTestInstance()).isPresent(); - instanceMap.put(preDestroyCallbackTestInstanceKey(context.getRequiredTestClass()), - DefaultTestInstances.of(context.getTestInstance().get())); - } - - @Override - public void beforeAll(ExtensionContext context) { - trackLifecycle(context); - instanceMap.put(beforeAllCallbackKey(context.getRequiredTestClass()), - context.getTestInstances().orElse(null)); - } - - @Override - public void afterAll(ExtensionContext context) { - trackLifecycle(context); - instanceMap.put(afterAllCallbackKey(context.getRequiredTestClass()), - context.getTestInstances().orElse(null)); - } - - @Override - public void beforeEach(ExtensionContext context) { - trackLifecycle(context); - instanceMap.put( - beforeEachCallbackKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), - context.getRequiredTestInstances()); - } - - @Override - public void afterEach(ExtensionContext context) { - trackLifecycle(context); - instanceMap.put( - afterEachCallbackKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), - context.getRequiredTestInstances()); - } - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - trackLifecycle(context); - return isAnnotated(context.getTestMethod(), SingletonTest.class); - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - trackLifecycle(context); - instanceMap.put(testTemplateKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), - context.getTestInstances().orElse(null)); - - return Stream.of(new TestTemplateInvocationContext() { - }); - } - - private static void trackLifecycle(ExtensionContext context) { - assertThat(context.getRoot().getTestInstanceLifecycle()).isEmpty(); - lifecyclesMap.computeIfAbsent(context.getRequiredTestClass(), clazz -> new ArrayList<>()).add( - context.getTestInstanceLifecycle().orElse(null)); - } - - } - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @TestTemplate - @interface SingletonTest { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java deleted file mode 100644 index 5b63eee0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java +++ /dev/null @@ -1,792 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.displayName; -import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Event; -import org.junit.platform.testkit.engine.Events; -import org.opentest4j.AssertionFailedError; - -/** - * @since 5.0 - */ -class TestTemplateInvocationTests extends AbstractJupiterTestEngineTests { - - @Test - void templateWithSingleRegisteredExtensionIsInvoked() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithSingleRegisteredExtension")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithSingleRegisteredExtension"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), - finishedWithFailure(message("invocation is expected to fail"))), // - event(container("templateWithSingleRegisteredExtension"), finishedSuccessfully()))); - } - - @Test - void parentRelationshipIsEstablished() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithSingleRegisteredExtension")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - TestDescriptor templateMethodDescriptor = findTestDescriptor(executionResults, - container("templateWithSingleRegisteredExtension")); - TestDescriptor invocationDescriptor = findTestDescriptor(executionResults, test("test-template-invocation:#1")); - assertThat(invocationDescriptor.getParent()).hasValue(templateMethodDescriptor); - } - - @Test - void beforeAndAfterEachMethodsAreExecutedAroundInvocation() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(TestTemplateTestClassWithBeforeAndAfterEach.class, "testTemplateWithTwoInvocations")).build(); - - executeTests(request); - - assertThat(TestTemplateTestClassWithBeforeAndAfterEach.lifecycleEvents).containsExactly( - "beforeAll:TestTemplateInvocationTests$TestTemplateTestClassWithBeforeAndAfterEach", "beforeEach:[1]", - "afterEach:[1]", "beforeEach:[2]", "afterEach:[2]", - "afterAll:TestTemplateInvocationTests$TestTemplateTestClassWithBeforeAndAfterEach"); - } - - @Test - void templateWithTwoRegisteredExtensionsIsInvoked() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithTwoRegisteredExtensions")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithTwoRegisteredExtensions"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1]")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), - finishedWithFailure(message("invocation is expected to fail"))), // - event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // - event(test("test-template-invocation:#2"), started()), // - event(test("test-template-invocation:#2"), - finishedWithFailure(message("invocation is expected to fail"))), // - event(container("templateWithTwoRegisteredExtensions"), finishedSuccessfully()))); - } - - @Test - void legacyReportingNames() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithTwoRegisteredExtensions")).build(); - - EngineExecutionResults results = executeTests(request); - Events events = results.allEvents(); - - events.assertStatistics(stats -> stats.dynamicallyRegistered(2)); - // events.dynamicallyRegistered().debug(); - // results.testEvents().dynamicallyRegistered().debug(); - // results.containerEvents().dynamicallyRegistered().debug(); - - // @formatter:off - Stream legacyReportingNames = events.dynamicallyRegistered() - .map(Event::getTestDescriptor) - .map(TestDescriptor::getLegacyReportingName); - // @formatter:off - assertThat(legacyReportingNames).containsExactly("templateWithTwoRegisteredExtensions()[1]", - "templateWithTwoRegisteredExtensions()[2]"); - } - - @Test - void templateWithTwoInvocationsFromSingleExtensionIsInvoked() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithTwoInvocationsFromSingleExtension")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithTwoInvocationsFromSingleExtension"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1]")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), - finishedWithFailure(message("invocation is expected to fail"))), // - event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // - event(test("test-template-invocation:#2"), started()), // - event(test("test-template-invocation:#2"), - finishedWithFailure(message("invocation is expected to fail"))), // - event(container("templateWithTwoInvocationsFromSingleExtension"), finishedSuccessfully()))); - } - - @Test - void singleInvocationIsExecutedWhenDiscoveredByUniqueId() { - UniqueId uniqueId = discoverUniqueId(MyTestTemplateTestCase.class, - "templateWithTwoInvocationsFromSingleExtension") // - .append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); - - EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithTwoInvocationsFromSingleExtension"), started()), // - event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // - event(test("test-template-invocation:#2"), started()), // - event(test("test-template-invocation:#2"), - finishedWithFailure(message("invocation is expected to fail"))), // - event(container("templateWithTwoInvocationsFromSingleExtension"), finishedSuccessfully()))); - } - - @Test - void templateWithDisabledInvocationsIsSkipped() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithDisabledInvocations")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithDisabledInvocations"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1")), // - event(test("test-template-invocation:#1"), skippedWithReason("always disabled")), // - event(container("templateWithDisabledInvocations"), finishedSuccessfully()))); - } - - @Test - void disabledTemplateIsSkipped() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "disabledTemplate")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("disabledTemplate"), skippedWithReason("always disabled")))); - } - - @Test - void templateWithCustomizedDisplayNamesIsInvoked() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithCustomizedDisplayNames")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithCustomizedDisplayNames"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1"), - displayName("1 --> templateWithCustomizedDisplayNames()")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), - finishedWithFailure(message("invocation is expected to fail"))), // - event(container("templateWithCustomizedDisplayNames"), finishedSuccessfully()))); - } - - @Test - void templateWithDynamicParameterResolverIsInvoked() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(MyTestTemplateTestCase.class, - "templateWithDynamicParameterResolver", "java.lang.String")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithDynamicParameterResolver"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1] foo")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), finishedWithFailure(message("foo"))), // - event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2] bar")), // - event(test("test-template-invocation:#2"), started()), // - event(test("test-template-invocation:#2"), finishedWithFailure(message("bar"))), // - event(container("templateWithDynamicParameterResolver"), finishedSuccessfully()))); - } - - @Test - void contextParameterResolverCanResolveConstructorArguments() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCaseWithConstructor.class, "template", "java.lang.String")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("template"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1] foo")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), finishedSuccessfully()), // - event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2] bar")), // - event(test("test-template-invocation:#2"), started()), // - event(test("test-template-invocation:#2"), finishedSuccessfully()), // - event(container("template"), finishedSuccessfully()))); - } - - @Test - void templateWithDynamicTestInstancePostProcessorIsInvoked() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithDynamicTestInstancePostProcessor")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithDynamicTestInstancePostProcessor"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), finishedWithFailure(message("foo"))), // - event(dynamicTestRegistered("test-template-invocation:#2")), // - event(test("test-template-invocation:#2"), started()), // - event(test("test-template-invocation:#2"), finishedWithFailure(message("bar"))), // - event(container("templateWithDynamicTestInstancePostProcessor"), finishedSuccessfully()))); - } - - @Test - void lifecycleCallbacksAreExecutedForInvocation() { - LauncherDiscoveryRequest request = request().selectors( - selectClass(TestTemplateTestClassWithDynamicLifecycleCallbacks.class)).build(); - - executeTests(request); - - // @formatter:off - assertThat(TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents).containsExactly( - "beforeEach", - "beforeTestExecution", - "testTemplate:foo", - "handleTestExecutionException", - "afterTestExecution", - "afterEach", - "beforeEach", - "beforeTestExecution", - "testTemplate:bar", - "afterTestExecution", - "afterEach"); - // @formatter:on - } - - @Test - void extensionIsAskedForSupportBeforeItMustProvide() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithWrongParameterType", int.class.getName())).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithWrongParameterType"), started()), // - event(container("templateWithWrongParameterType"), finishedWithFailure(message(s -> s.startsWith( - "You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [")))))); - } - - @Test - void templateWithSupportingProviderButNoInvocationsReportsFailure() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithSupportingProviderButNoInvocations")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithSupportingProviderButNoInvocations"), started()), // - event(container("templateWithSupportingProviderButNoInvocations"), - finishedWithFailure(message("None of the supporting TestTemplateInvocationContextProviders [" - + InvocationContextProviderThatSupportsEverythingButProvidesNothing.class.getSimpleName() - + "] provided a non-empty stream"))))); - } - - @Test - void templateWithCloseableStream() { - LauncherDiscoveryRequest request = request().selectors( - selectMethod(MyTestTemplateTestCase.class, "templateWithCloseableStream")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - assertThat(InvocationContextProviderWithCloseableStream.streamClosed.get()).describedAs( - "streamClosed").isTrue(); - - executionResults.allEvents().assertEventsMatchExactly( // - wrappedInContainerEvents(MyTestTemplateTestCase.class, // - event(container("templateWithCloseableStream"), started()), // - event(dynamicTestRegistered("test-template-invocation:#1")), // - event(test("test-template-invocation:#1"), started()), // - event(test("test-template-invocation:#1"), finishedSuccessfully()), // - event(container("templateWithCloseableStream"), finishedSuccessfully()))); - } - - private TestDescriptor findTestDescriptor(EngineExecutionResults executionResults, Condition condition) { - // @formatter:off - return executionResults.allEvents() - .filter(condition::matches) - .findAny() - .map(Event::getTestDescriptor) - .orElseThrow(() -> new AssertionFailedError("Could not find event for condition: " + condition)); - // @formatter:on - } - - @SafeVarargs - @SuppressWarnings({ "unchecked", "varargs", "rawtypes" }) - private final Condition[] wrappedInContainerEvents(Class clazz, - Condition... wrappedConditions) { - - List> conditions = new ArrayList<>(); - conditions.add(event(engine(), started())); - conditions.add(event(container(clazz), started())); - conditions.addAll(asList(wrappedConditions)); - conditions.add(event(container(clazz), finishedSuccessfully())); - conditions.add(event(engine(), finishedSuccessfully())); - return conditions.toArray(new Condition[0]); - } - - static class MyTestTemplateTestCase { - - @TestTemplate - void templateWithoutRegisteredExtension() { - } - - @ExtendWith(SingleInvocationContextProvider.class) - @TestTemplate - void templateWithSingleRegisteredExtension() { - fail("invocation is expected to fail"); - } - - @ExtendWith({ SingleInvocationContextProvider.class, - AnotherInvocationContextProviderWithASingleInvocation.class }) - @TestTemplate - void templateWithTwoRegisteredExtensions() { - fail("invocation is expected to fail"); - } - - @ExtendWith(TwoInvocationsContextProvider.class) - @TestTemplate - void templateWithTwoInvocationsFromSingleExtension() { - fail("invocation is expected to fail"); - } - - @ExtendWith({ SingleInvocationContextProviderWithDisabledInvocations.class }) - @TestTemplate - void templateWithDisabledInvocations() { - fail("this is never called"); - } - - @ExtendWith(AlwaysDisabledExecutionCondition.class) - @TestTemplate - void disabledTemplate() { - fail("this is never called"); - } - - @ExtendWith(InvocationContextProviderWithCustomizedDisplayNames.class) - @TestTemplate - void templateWithCustomizedDisplayNames() { - fail("invocation is expected to fail"); - } - - @ExtendWith(StringParameterResolvingInvocationContextProvider.class) - @TestTemplate - void templateWithDynamicParameterResolver(String parameter) { - fail(parameter); - } - - @ExtendWith(StringParameterResolvingInvocationContextProvider.class) - @TestTemplate - void templateWithWrongParameterType(int parameter) { - fail("never called: " + parameter); - } - - String parameterInstanceVariable; - - @ExtendWith(StringParameterInjectingInvocationContextProvider.class) - @TestTemplate - void templateWithDynamicTestInstancePostProcessor() { - fail(parameterInstanceVariable); - } - - @ExtendWith(InvocationContextProviderThatSupportsEverythingButProvidesNothing.class) - @TestTemplate - void templateWithSupportingProviderButNoInvocations() { - fail("never called"); - } - - @ExtendWith(InvocationContextProviderWithCloseableStream.class) - @TestTemplate - void templateWithCloseableStream() { - } - } - - @ExtendWith(StringParameterResolvingInvocationContextProvider.class) - static class MyTestTemplateTestCaseWithConstructor { - - private final String constructorParameter; - - MyTestTemplateTestCaseWithConstructor(String constructorParameter) { - this.constructorParameter = constructorParameter; - } - - @TestTemplate - void template(String parameter) { - assertEquals(constructorParameter, parameter); - } - } - - static class TestTemplateTestClassWithBeforeAndAfterEach { - - private static List lifecycleEvents = new ArrayList<>(); - - @BeforeAll - static void beforeAll(TestInfo testInfo) { - lifecycleEvents.add("beforeAll:" + testInfo.getDisplayName()); - } - - @AfterAll - static void afterAll(TestInfo testInfo) { - lifecycleEvents.add("afterAll:" + testInfo.getDisplayName()); - } - - @BeforeEach - void beforeEach(TestInfo testInfo) { - lifecycleEvents.add("beforeEach:" + testInfo.getDisplayName()); - } - - @AfterEach - void afterEach(TestInfo testInfo) { - lifecycleEvents.add("afterEach:" + testInfo.getDisplayName()); - } - - @ExtendWith(TwoInvocationsContextProvider.class) - @TestTemplate - void testTemplateWithTwoInvocations() { - fail("invocation is expected to fail"); - } - } - - static class TestTemplateTestClassWithDynamicLifecycleCallbacks { - - private static List lifecycleEvents = new ArrayList<>(); - - @ExtendWith(InvocationContextProviderWithDynamicLifecycleCallbacks.class) - @TestTemplate - void testTemplate(TestInfo testInfo) { - lifecycleEvents.add("testTemplate:" + testInfo.getDisplayName()); - assertEquals("bar", testInfo.getDisplayName()); - } - } - - private static class SingleInvocationContextProvider implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(emptyTestTemplateInvocationContext()); - } - } - - private static class SingleInvocationContextProviderWithDisabledInvocations - extends SingleInvocationContextProvider { - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(new TestTemplateInvocationContext() { - @Override - public List getAdditionalExtensions() { - return singletonList(new AlwaysDisabledExecutionCondition()); - } - }); - } - } - - private static class AnotherInvocationContextProviderWithASingleInvocation - implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(emptyTestTemplateInvocationContext()); - } - } - - private static class TwoInvocationsContextProvider implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext()); - } - } - - private static class AlwaysDisabledExecutionCondition implements ExecutionCondition { - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return ConditionEvaluationResult.disabled("always disabled"); - } - } - - private static class InvocationContextProviderWithCustomizedDisplayNames - implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream. generate(() -> new TestTemplateInvocationContext() { - - @Override - public String getDisplayName(int invocationIndex) { - return invocationIndex + " --> " + context.getDisplayName(); - } - }).limit(1); - } - } - - private static class StringParameterResolvingInvocationContextProvider - implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - // @formatter:off - return context.getTestMethod() - .map(Method::getParameterTypes) - .map(Arrays::stream) - .map(parameters -> parameters.anyMatch(Predicate.isEqual(String.class))) - .orElse(false); - // @formatter:on - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(createContext("foo"), createContext("bar")); - } - - private TestTemplateInvocationContext createContext(String argument) { - return new TestTemplateInvocationContext() { - - @Override - public String getDisplayName(int invocationIndex) { - return TestTemplateInvocationContext.super.getDisplayName(invocationIndex) + " " + argument; - } - - @Override - public List getAdditionalExtensions() { - return singletonList(new ParameterResolver() { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) throws ParameterResolutionException { - return true; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) throws ParameterResolutionException { - return argument; - } - }); - } - }; - } - } - - private static class StringParameterInjectingInvocationContextProvider - implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(createContext("foo"), createContext("bar")); - } - - private TestTemplateInvocationContext createContext(String argument) { - return new TestTemplateInvocationContext() { - - @Override - public String getDisplayName(int invocationIndex) { - return TestTemplateInvocationContext.super.getDisplayName(invocationIndex) + " " + argument; - } - - @Override - public List getAdditionalExtensions() { - return singletonList((TestInstancePostProcessor) (testInstance, context) -> { - Field field = testInstance.getClass().getDeclaredField("parameterInstanceVariable"); - field.setAccessible(true); - field.set(testInstance, argument); - }); - } - }; - } - } - - private static class InvocationContextProviderWithDynamicLifecycleCallbacks - implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(createContext("foo"), createContext("bar")); - } - - private TestTemplateInvocationContext createContext(String argument) { - return new TestTemplateInvocationContext() { - - @Override - public String getDisplayName(int invocationIndex) { - return argument; - } - - @Override - public List getAdditionalExtensions() { - return singletonList(new LifecycleCallbackExtension()); - } - }; - } - - private static class LifecycleCallbackExtension implements BeforeEachCallback, BeforeTestExecutionCallback, - TestExecutionExceptionHandler, AfterTestExecutionCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("beforeEach"); - } - - @Override - public void beforeTestExecution(ExtensionContext context) { - TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("beforeTestExecution"); - } - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { - TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("handleTestExecutionException"); - throw new AssertionError(throwable); - } - - @Override - public void afterTestExecution(ExtensionContext context) { - TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("afterTestExecution"); - } - - @Override - public void afterEach(ExtensionContext context) { - TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("afterEach"); - } - } - } - - private static class InvocationContextProviderThatSupportsEverythingButProvidesNothing - implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.empty(); - } - } - - private static class InvocationContextProviderWithCloseableStream implements TestTemplateInvocationContextProvider { - - private static AtomicBoolean streamClosed = new AtomicBoolean(false); - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(emptyTestTemplateInvocationContext()).onClose(() -> streamClosed.set(true)); - } - } - - private static TestTemplateInvocationContext emptyTestTemplateInvocationContext() { - return new TestTemplateInvocationContext() { - }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java deleted file mode 100644 index 0bc75500..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * @since 5.0 - */ -@ExtendWith(NumberResolver.class) -abstract class AbstractNonGenericTests { - - @Test - void mA() { - BridgeMethodTests.sequence.add("mA()"); - } - - @Test - void test(Number value) { - BridgeMethodTests.sequence.add("A.test(Number)"); - Assertions.assertEquals(42, value); - } - - static class B extends AbstractNonGenericTests { - - @Test - void mB() { - BridgeMethodTests.sequence.add("mB()"); - } - - @Test - void test(Byte value) { - BridgeMethodTests.sequence.add("B.test(Byte)"); - Assertions.assertEquals(123, value.intValue()); - } - - } - - static class C extends B { - - @Test - void mC() { - BridgeMethodTests.sequence.add("mC()"); - } - - @Override - @Test - void test(Byte value) { - BridgeMethodTests.sequence.add("C.test(Byte)"); - Assertions.assertEquals(123, value.intValue()); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java deleted file mode 100644 index afecac33..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * @since 5.0 - */ -@ExtendWith(NumberResolver.class) -abstract class AbstractNumberTests { - - @Test - void test(N number) { - BridgeMethodTests.sequence.add("test(N)"); - assertNotNull(number); - assertEquals(123, number.intValue()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java deleted file mode 100644 index 13150e88..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.bridge.NumberTestGroup.ByteTestCase; -import org.junit.jupiter.engine.bridge.NumberTestGroup.ShortTestCase; - -/** - * @since 5.0 - */ -class BridgeMethodTests extends AbstractJupiterTestEngineTests { - - static List sequence = new ArrayList<>(); - - @Test - void childrenHaveBridgeMethods() throws Exception { - assertFalse(ChildWithBridgeMethods.class.getMethod("anotherBeforeEach").isBridge()); - assertFalse(ChildWithBridgeMethods.class.getMethod("anotherAfterEach").isBridge()); - assertTrue(ChildWithBridgeMethods.class.getMethod("beforeEach").isBridge()); - assertTrue(ChildWithBridgeMethods.class.getMethod("afterEach").isBridge()); - - assertTrue(ByteTestCase.class.getDeclaredMethod("test", Number.class).isBridge()); - assertFalse(ByteTestCase.class.getDeclaredMethod("test", Byte.class).isBridge()); - - assertTrue(ShortTestCase.class.getDeclaredMethod("test", Number.class).isBridge()); - assertFalse(ShortTestCase.class.getDeclaredMethod("test", Short.class).isBridge()); - } - - @Test - void childHasNoBridgeMethods() throws Exception { - assertFalse(ChildWithoutBridgeMethods.class.getMethod("anotherBeforeEach").isBridge()); - assertFalse(ChildWithoutBridgeMethods.class.getMethod("anotherAfterEach").isBridge()); - assertFalse(ChildWithoutBridgeMethods.class.getMethod("beforeEach").isBridge()); - assertFalse(ChildWithoutBridgeMethods.class.getMethod("afterEach").isBridge()); - } - - @Test - void compareMethodExecutionSequenceOrder() { - String withoutBridgeMethods = execute(1, ChildWithoutBridgeMethods.class); - String withBridgeMethods = execute(1, ChildWithBridgeMethods.class); - assertEquals(withoutBridgeMethods, withBridgeMethods); - } - - @TestFactory - List ensureSingleTestMethodsExecute() { - return Arrays.asList( // - dynamicTest("Byte", // - () -> assertEquals("[test(Byte) BEGIN, test(N), test(Byte) END, test(Long) BEGIN, test(Long) END]", // - execute(2, ByteTestCase.class))), - dynamicTest("Short", // - () -> assertEquals("[test(Long) BEGIN, test(Long) END, test(Short) BEGIN, test(N), test(Short) END]", // - execute(2, ShortTestCase.class)))); - } - - @Test - void inheritedNonGenericMethodsAreExecuted() { - String b = execute(4, AbstractNonGenericTests.B.class); - assertAll("Missing expected test(s) in sequence: " + b, // - () -> assertTrue(b.contains("A.test(Number)")), // - () -> assertTrue(b.contains("mA()")), // - () -> assertTrue(b.contains("mB()")), // - () -> assertTrue(b.contains("B.test(Byte)")) // - ); - String c = execute(5, AbstractNonGenericTests.C.class); - assertAll("Missing expected test(s) in sequence: " + c, // - () -> assertTrue(c.contains("A.test(Number)")), // - () -> assertTrue(c.contains("mA()")), // - () -> assertTrue(c.contains("mB()")), // - () -> assertTrue(c.contains("mC()")), // - () -> assertTrue(c.contains("C.test(Byte)")) // - ); - } - - private String execute(int expectedTestFinishedCount, Class testClass) { - sequence.clear(); - executeTestsForClass(testClass).testEvents()// - .assertStatistics(stats -> stats.started(expectedTestFinishedCount)); - return sequence.toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java deleted file mode 100644 index bd3cb864..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -// modifier "public" is necessary for creating bridge methods by the compiler -public class ChildWithBridgeMethods extends PackagePrivateParent { - - @BeforeEach - public void anotherBeforeEach() { - BridgeMethodTests.sequence.add("child.anotherBeforeEach()"); - } - - @Test - void test() { - BridgeMethodTests.sequence.add("child.test()"); - } - - @AfterEach - public void anotherAfterEach() { - BridgeMethodTests.sequence.add("child.anotherAfterEach()"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java deleted file mode 100644 index 2a516912..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -// modifier "public" is *not* present for not creating bridge methods by the compiler -class ChildWithoutBridgeMethods extends PackagePrivateParent { - - @BeforeEach - public void anotherBeforeEach() { - BridgeMethodTests.sequence.add("child.anotherBeforeEach()"); - } - - @Test - void test() { - BridgeMethodTests.sequence.add("child.test()"); - } - - @AfterEach - public void anotherAfterEach() { - BridgeMethodTests.sequence.add("child.anotherAfterEach()"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java deleted file mode 100644 index 114d6a61..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * @since 5.0 - */ -class NumberResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - - return Number.class.isAssignableFrom(parameterContext.getParameter().getType()); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - - Class type = parameterContext.getParameter().getType(); - if (type == Number.class) { - return 42; - } - try { - return type.getMethod("valueOf", String.class).invoke(null, "123"); - } - catch (Exception e) { - throw new AssertionError("Could not resolve number type: " + type, e); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java deleted file mode 100644 index 5f2146f6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -interface NumberTestGroup { - - class ByteTestCase extends AbstractNumberTests { - - @Test - @Override - void test(Byte number) { - BridgeMethodTests.sequence.add("test(Byte) BEGIN"); - super.test(number); - BridgeMethodTests.sequence.add("test(Byte) END"); - } - - @Test - void test(Long number) { - BridgeMethodTests.sequence.add("test(Long) BEGIN"); - assertNotNull(number); - assertEquals(123, number.intValue()); - BridgeMethodTests.sequence.add("test(Long) END"); - } - } - - class ShortTestCase extends AbstractNumberTests { - - @Test - @Override - void test(Short number) { - BridgeMethodTests.sequence.add("test(Short) BEGIN"); - super.test(number); - BridgeMethodTests.sequence.add("test(Short) END"); - } - - @Test - void test(Long number) { - BridgeMethodTests.sequence.add("test(Long) BEGIN"); - assertNotNull(number); - assertEquals(123, number.intValue()); - BridgeMethodTests.sequence.add("test(Long) END"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java deleted file mode 100644 index 909139a0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.bridge; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; - -/** - * @since 5.0 - */ -class PackagePrivateParent { - - @BeforeAll - static void beforeAll() { - BridgeMethodTests.sequence.add("static parent.beforeAll()"); - } - - @AfterAll - static void afterAll() { - BridgeMethodTests.sequence.add("static parent.afterAll()"); - } - - @BeforeEach - public void beforeEach() { - BridgeMethodTests.sequence.add("parent.beforeEach()"); - } - - @AfterEach - public void afterEach() { - BridgeMethodTests.sequence.add("parent.afterEach()"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java deleted file mode 100644 index cb4f88f7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.io.CleanupMode.NEVER; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.only; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.util.Optional; -import java.util.function.Predicate; - -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; - -/** - * Unit tests for {@link CachingJupiterConfiguration}. - */ -class CachingJupiterConfigurationTests { - - private final JupiterConfiguration delegate = mock(); - private final JupiterConfiguration cache = new CachingJupiterConfiguration(delegate); - - @Test - void cachesDefaultExecutionMode() { - when(delegate.getDefaultExecutionMode()).thenReturn(ExecutionMode.CONCURRENT); - - assertThat(cache.getDefaultExecutionMode()).isEqualTo(ExecutionMode.CONCURRENT); - assertThat(cache.getDefaultExecutionMode()).isEqualTo(ExecutionMode.CONCURRENT); - - verify(delegate, only()).getDefaultExecutionMode(); - } - - @Test - void cachesDefaultTestInstanceLifecycle() { - when(delegate.getDefaultTestInstanceLifecycle()).thenReturn(Lifecycle.PER_CLASS); - - assertThat(cache.getDefaultTestInstanceLifecycle()).isEqualTo(Lifecycle.PER_CLASS); - assertThat(cache.getDefaultTestInstanceLifecycle()).isEqualTo(Lifecycle.PER_CLASS); - - verify(delegate, only()).getDefaultTestInstanceLifecycle(); - } - - @Test - void cachesExecutionConditionFilter() { - Predicate predicate = executionCondition -> true; - when(delegate.getExecutionConditionFilter()).thenReturn(predicate); - - assertThat(cache.getExecutionConditionFilter()).isSameAs(predicate); - assertThat(cache.getExecutionConditionFilter()).isSameAs(predicate); - - verify(delegate, only()).getExecutionConditionFilter(); - } - - @Test - void cachesExtensionAutoDetectionEnabled() { - when(delegate.isExtensionAutoDetectionEnabled()).thenReturn(true); - - assertThat(cache.isExtensionAutoDetectionEnabled()).isTrue(); - assertThat(cache.isExtensionAutoDetectionEnabled()).isTrue(); - - verify(delegate, only()).isExtensionAutoDetectionEnabled(); - } - - @Test - void cachesParallelExecutionEnabled() { - when(delegate.isParallelExecutionEnabled()).thenReturn(true); - - assertThat(cache.isParallelExecutionEnabled()).isTrue(); - assertThat(cache.isParallelExecutionEnabled()).isTrue(); - - verify(delegate, only()).isParallelExecutionEnabled(); - } - - @Test - void cachesDefaultDisplayNameGenerator() { - CustomDisplayNameGenerator customDisplayNameGenerator = new CustomDisplayNameGenerator(); - when(delegate.getDefaultDisplayNameGenerator()).thenReturn(customDisplayNameGenerator); - - // call `cache.getDefaultDisplayNameGenerator()` twice to verify the delegate method is called only once. - assertThat(cache.getDefaultDisplayNameGenerator()).isSameAs(customDisplayNameGenerator); - assertThat(cache.getDefaultDisplayNameGenerator()).isSameAs(customDisplayNameGenerator); - - verify(delegate, only()).getDefaultDisplayNameGenerator(); - } - - @Test - void cachesDefaultTestMethodOrderer() { - final Optional methodOrderer = Optional.of(new MethodOrderer.MethodName()); - when(delegate.getDefaultTestMethodOrderer()).thenReturn(methodOrderer); - - // call `cache.getDefaultTestMethodOrderer()` twice to verify the delegate method is called only once. - assertThat(cache.getDefaultTestMethodOrderer()).isSameAs(methodOrderer); - assertThat(cache.getDefaultTestMethodOrderer()).isSameAs(methodOrderer); - - verify(delegate, only()).getDefaultTestMethodOrderer(); - } - - @Test - void cachesDefaultTempDirCleanupMode() { - when(delegate.getDefaultTempDirCleanupMode()).thenReturn(NEVER); - - // call `cache.getDefaultTempStrategyDirCleanupMode()` twice to verify the delegate method is called only once. - assertThat(cache.getDefaultTempDirCleanupMode()).isSameAs(NEVER); - assertThat(cache.getDefaultTempDirCleanupMode()).isSameAs(NEVER); - - verify(delegate, only()).getDefaultTempDirCleanupMode(); - } - - @Test - void doesNotCacheRawParameters() { - when(delegate.getRawConfigurationParameter("foo")).thenReturn(Optional.of("bar")).thenReturn( - Optional.of("baz")); - - assertThat(cache.getRawConfigurationParameter("foo")).contains("bar"); - assertThat(cache.getRawConfigurationParameter("foo")).contains("baz"); - - verify(delegate, times(2)).getRawConfigurationParameter("foo"); - verifyNoMoreInteractions(delegate); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java deleted file mode 100644 index 2592fe25..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Optional; - -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.engine.Constants; -import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.ConfigurationParameters; - -class DefaultJupiterConfigurationTests { - - private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; - - @Test - void getDefaultTestInstanceLifecyclePreconditions() { - PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> new DefaultJupiterConfiguration(null)); - assertThat(exception).hasMessage("ConfigurationParameters must not be null"); - } - - @Test - void getDefaultTestInstanceLifecycleWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); - Lifecycle lifecycle = configuration.getDefaultTestInstanceLifecycle(); - assertThat(lifecycle).isEqualTo(PER_METHOD); - } - - @Test - void getDefaultTempDirCleanupModeWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); - CleanupMode cleanupMode = configuration.getDefaultTempDirCleanupMode(); - assertThat(cleanupMode).isEqualTo(ALWAYS); - } - - @Test - void getDefaultTestInstanceLifecycleWithConfigParamSet() { - assertAll(// - () -> assertDefaultConfigParam(null, PER_METHOD), // - () -> assertDefaultConfigParam("", PER_METHOD), // - () -> assertDefaultConfigParam("bogus", PER_METHOD), // - () -> assertDefaultConfigParam(PER_METHOD.name(), PER_METHOD), // - () -> assertDefaultConfigParam(PER_METHOD.name().toLowerCase(), PER_METHOD), // - () -> assertDefaultConfigParam(" " + PER_METHOD.name() + " ", PER_METHOD), // - () -> assertDefaultConfigParam(PER_CLASS.name(), PER_CLASS), // - () -> assertDefaultConfigParam(PER_CLASS.name().toLowerCase(), PER_CLASS), // - () -> assertDefaultConfigParam(" " + PER_CLASS.name() + " ", Lifecycle.PER_CLASS) // - ); - } - - @Test - void shouldGetDefaultDisplayNameGeneratorWithConfigParamSet() { - ConfigurationParameters parameters = mock(); - String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; - when(parameters.get(key)).thenReturn(Optional.of(CustomDisplayNameGenerator.class.getName())); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); - - DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); - - assertThat(defaultDisplayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); - } - - @Test - void shouldGetStandardAsDefaultDisplayNameGeneratorWithoutConfigParamSet() { - ConfigurationParameters parameters = mock(); - String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; - when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); - - DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); - - assertThat(defaultDisplayNameGenerator).isInstanceOf(DisplayNameGenerator.Standard.class); - } - - @Test - void shouldGetNothingAsDefaultTestMethodOrderWithoutConfigParamSet() { - ConfigurationParameters parameters = mock(); - String key = Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; - when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); - - final Optional defaultTestMethodOrder = configuration.getDefaultTestMethodOrderer(); - - assertThat(defaultTestMethodOrder).isEmpty(); - } - - private void assertDefaultConfigParam(String configValue, Lifecycle expected) { - ConfigurationParameters configParams = mock(); - when(configParams.get(KEY)).thenReturn(Optional.ofNullable(configValue)); - Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams).getDefaultTestInstanceLifecycle(); - assertThat(lifecycle).isEqualTo(expected); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java deleted file mode 100644 index 5848cda9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.config; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.engine.Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * @since 5.5 - */ -@TrackLogRecords -class InstantiatingConfigurationParameterConverterTests { - - private static final String KEY = DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; - - @Test - void shouldInstantiateConfiguredClass(LogRecordListener listener) { - - ConfigurationParameters configurationParameters = mock(); - when(configurationParameters.get(KEY)).thenReturn(Optional.of(CustomDisplayNameGenerator.class.getName())); - - InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( - DisplayNameGenerator.class, "display name generator"); - DisplayNameGenerator displayNameGenerator = converter.get(configurationParameters, KEY).orElseThrow(); - - assertThat(displayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); - assertExpectedLogMessage(listener, Level.CONFIG, - "Using default display name generator " - + "'org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator' set via the " - + "'junit.jupiter.displayname.generator.default' configuration parameter."); - } - - @Test - void shouldReturnEmptyOptionalIfNoConfigurationFound() { - ConfigurationParameters configurationParameters = mock(); - when(configurationParameters.get(KEY)).thenReturn(Optional.empty()); - - InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( - DisplayNameGenerator.class, "display name generator"); - Optional displayNameGenerator = converter.get(configurationParameters, KEY); - - assertThat(displayNameGenerator).isEmpty(); - } - - @Test - void shouldReturnEmptyOptionalIfConfigurationIsBlank() { - ConfigurationParameters configurationParameters = mock(); - when(configurationParameters.get(KEY)).thenReturn(Optional.of("")); - - InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( - DisplayNameGenerator.class, "display name generator"); - Optional displayNameGenerator = converter.get(configurationParameters, KEY); - - assertThat(displayNameGenerator).isEmpty(); - } - - @Test - void shouldTrimAndInstantiateConfiguredClass(LogRecordListener listener) { - ConfigurationParameters configurationParameters = mock(); - String classNameWithSpaces = " " + CustomDisplayNameGenerator.class.getName() + " "; - when(configurationParameters.get(KEY)).thenReturn(Optional.of(classNameWithSpaces)); - - InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( - DisplayNameGenerator.class, "display name generator"); - DisplayNameGenerator displayNameGenerator = converter.get(configurationParameters, KEY).orElseThrow(); - - assertThat(displayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); - assertExpectedLogMessage(listener, Level.CONFIG, - "Using default display name generator " - + "'org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator' set via the " - + "'junit.jupiter.displayname.generator.default' configuration parameter."); - } - - @Test - void shouldReturnEmptyOptionalIfNoClassFound(LogRecordListener listener) { - ConfigurationParameters configurationParameters = mock(); - when(configurationParameters.get(KEY)).thenReturn(Optional.of("random-string")); - - InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( - DisplayNameGenerator.class, "display name generator"); - Optional displayNameGenerator = converter.get(configurationParameters, KEY); - - assertThat(displayNameGenerator).isEmpty(); - assertExpectedLogMessage(listener, Level.WARNING, - "Failed to load default display name generator " - + "class 'random-string' set via the 'junit.jupiter.displayname.generator.default' " - + "configuration parameter. Falling back to default behavior."); - } - - @Test - void shouldReturnEmptyOptionalIfClassFoundIsNotATypeOfExpectedType(LogRecordListener listener) { - ConfigurationParameters configurationParameters = mock(); - when(configurationParameters.get(KEY)).thenReturn(Optional.of(Object.class.getName())); - - InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( - DisplayNameGenerator.class, "display name generator"); - Optional displayNameGenerator = converter.get(configurationParameters, KEY); - - assertThat(displayNameGenerator).isEmpty(); - assertExpectedLogMessage(listener, Level.WARNING, - "Failed to load default display name generator class 'java.lang.Object' " - + "set via the 'junit.jupiter.displayname.generator.default' configuration parameter. " - + "Falling back to default behavior."); - } - - @Test - void shouldReturnEmptyOptionalIfClassNameIsNotFullyQualified(LogRecordListener listener) { - ConfigurationParameters configurationParameters = mock(); - when(configurationParameters.get(KEY)).thenReturn( - Optional.of(CustomDisplayNameGenerator.class.getSimpleName())); - - InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( - DisplayNameGenerator.class, "display name generator"); - Optional displayNameGenerator = converter.get(configurationParameters, KEY); - - assertThat(displayNameGenerator).isEmpty(); - assertExpectedLogMessage(listener, Level.WARNING, - "Failed to load default display name generator class 'CustomDisplayNameGenerator' " - + "set via the 'junit.jupiter.displayname.generator.default' configuration parameter. " - + "Falling back to default behavior."); - } - - private void assertExpectedLogMessage(LogRecordListener listener, Level level, String expectedMessage) { - // @formatter:off - assertTrue(listener.stream(level) - .map(LogRecord::getMessage) - .anyMatch(expectedMessage::equals)); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java deleted file mode 100644 index 40ef487c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.DisplayNameGenerator; - -public class CustomDisplayNameGenerator implements DisplayNameGenerator { - - @Override - public String generateDisplayNameForClass(Class testClass) { - return "class-display-name"; - } - - @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return "nested-class-display-name"; - } - - @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return "method-display-name"; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java deleted file mode 100644 index 022f93af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Method; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.logging.LogRecordListener; - -/** - * Unit tests for {@link DisplayNameUtils}. - * - * @since 5.5 - */ -class DisplayNameUtilsTests { - - @Nested - class ClassDisplayNameTests { - - @Test - void shouldGetDisplayNameFromDisplayNameAnnotation() { - - String displayName = DisplayNameUtils.determineDisplayName(MyTestCase.class, () -> "default-name"); - - assertThat(displayName).isEqualTo("my-test-case"); - } - - @Test - @TrackLogRecords - void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationWithBlankStringPresent( - LogRecordListener listener) { - - String displayName = DisplayNameUtils.determineDisplayName(BlankDisplayNameTestCase.class, - () -> "default-name"); - - assertThat(displayName).isEqualTo("default-name"); - assertThat(firstWarningLogRecord(listener).getMessage()).isEqualTo( - "Configuration error: @DisplayName on [class org.junit.jupiter.engine.descriptor.DisplayNameUtilsTests$BlankDisplayNameTestCase] must be declared with a non-empty value."); - } - - @Test - void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationPresent() { - - String displayName = DisplayNameUtils.determineDisplayName(NotDisplayNameTestCase.class, - () -> "default-name"); - - assertThat(displayName).isEqualTo("default-name"); - } - - @Nested - class ClassDisplayNameSupplierTests { - - private JupiterConfiguration configuration = mock(); - - @Test - void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass( - StandardDisplayNameTestCase.class, configuration); - - String name = StandardDisplayNameTestCase.class.getName(); - String expectedClassName = name.substring(name.lastIndexOf(".") + 1); - assertThat(displayName.get()).isEqualTo(expectedClassName); - } - - @Test - void shouldGetUnderscoreDisplayNameFromDisplayNameGenerationAnnotation() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass( - Underscore_DisplayName_TestCase.class, configuration); - - assertThat(displayName.get()).isEqualTo("DisplayNameUtilsTests$Underscore DisplayName TestCase"); - } - - @Test - void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass(MyTestCase.class, - configuration); - - assertThat(displayName.get()).isEqualTo("class-display-name"); - } - } - } - - @Nested - class NestedClassDisplayNameTests { - - private JupiterConfiguration configuration = mock(); - - @Test - void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( - StandardDisplayNameTestCase.class, configuration); - - assertThat(displayName.get()).isEqualTo(StandardDisplayNameTestCase.class.getSimpleName()); - } - - @Test - void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( - NestedTestCase.class, configuration); - - assertThat(displayName.get()).isEqualTo("nested-class-display-name"); - } - } - - @Nested - class MethodDisplayNameTests { - - private JupiterConfiguration configuration = mock(); - - @Test - void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() throws Exception { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Method method = MyTestCase.class.getDeclaredMethod("test1"); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(StandardDisplayNameTestCase.class, - method, configuration); - - assertThat(displayName).isEqualTo("test1()"); - } - - @Test - void shouldGetDisplayNameFromDefaultNameGenerator() throws Exception { - Method method = MyTestCase.class.getDeclaredMethod("test1"); - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - - String displayName = DisplayNameUtils.determineDisplayNameForMethod(NotDisplayNameTestCase.class, method, - configuration); - - assertThat(displayName).isEqualTo("method-display-name"); - } - } - - private LogRecord firstWarningLogRecord(LogRecordListener listener) throws AssertionError { - return listener.stream(DisplayNameUtils.class, Level.WARNING).findFirst().orElseThrow( - () -> new AssertionError("Failed to find warning log record")); - } - - @DisplayName("my-test-case") - @DisplayNameGeneration(value = CustomDisplayNameGenerator.class) - static class MyTestCase { - - void test1() { - } - - } - - @DisplayName("") - static class BlankDisplayNameTestCase { - } - - @DisplayNameGeneration(value = DisplayNameGenerator.Standard.class) - static class StandardDisplayNameTestCase { - } - - @DisplayNameGeneration(value = DisplayNameGenerator.ReplaceUnderscores.class) - static class Underscore_DisplayName_TestCase { - } - - static class NotDisplayNameTestCase { - } - - @Nested - class NestedTestCase { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java deleted file mode 100644 index af140f29..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.DefaultTestInstances; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; - -/** - * Unit tests for concrete implementations of {@link ExtensionContext}: - * {@link JupiterEngineExtensionContext}, {@link ClassExtensionContext}, and - * {@link MethodExtensionContext}. - * - * @since 5.0 - * @see org.junit.jupiter.engine.execution.ExtensionValuesStoreTests - */ -public class ExtensionContextTests { - - private final JupiterConfiguration configuration = mock(); - - @BeforeEach - void setUp() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); - when(configuration.getDefaultClassesExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); - } - - @Test - void fromJupiterEngineDescriptor() { - JupiterEngineDescriptor engineTestDescriptor = new JupiterEngineDescriptor( - UniqueId.root("engine", "junit-jupiter"), configuration); - - JupiterEngineExtensionContext engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor, - configuration, null); - - // @formatter:off - assertAll("engineContext", - () -> assertThat(engineContext.getElement()).isEmpty(), - () -> assertThat(engineContext.getTestClass()).isEmpty(), - () -> assertThat(engineContext.getTestInstance()).isEmpty(), - () -> assertThat(engineContext.getTestMethod()).isEmpty(), - () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestClass()), - () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestInstance()), - () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestMethod()), - () -> assertThat(engineContext.getDisplayName()).isEqualTo(engineTestDescriptor.getDisplayName()), - () -> assertThat(engineContext.getParent()).isEmpty(), - () -> assertThat(engineContext.getRoot()).isSameAs(engineContext), - () -> assertThat(engineContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) - ); - // @formatter:on - } - - @Test - @SuppressWarnings("resource") - void fromClassTestDescriptor() { - NestedClassTestDescriptor nestedClassDescriptor = nestedClassDescriptor(); - ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); - - ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, - configuration, null, null); - - // @formatter:off - assertAll("outerContext", - () -> assertThat(outerExtensionContext.getElement()).contains(OuterClass.class), - () -> assertThat(outerExtensionContext.getTestClass()).contains(OuterClass.class), - () -> assertThat(outerExtensionContext.getTestInstance()).isEmpty(), - () -> assertThat(outerExtensionContext.getTestMethod()).isEmpty(), - () -> assertThat(outerExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), - () -> assertThrows(PreconditionViolationException.class, () -> outerExtensionContext.getRequiredTestInstance()), - () -> assertThrows(PreconditionViolationException.class, () -> outerExtensionContext.getRequiredTestMethod()), - () -> assertThat(outerExtensionContext.getDisplayName()).isEqualTo(outerClassDescriptor.getDisplayName()), - () -> assertThat(outerExtensionContext.getParent()).isEmpty(), - () -> assertThat(outerExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) - ); - // @formatter:on - - ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, - nestedClassDescriptor, configuration, null, null); - assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext); - } - - @Test - @SuppressWarnings("resource") - void tagsCanBeRetrievedInExtensionContext() { - NestedClassTestDescriptor nestedClassDescriptor = nestedClassDescriptor(); - ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); - TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); - outerClassDescriptor.addChild(methodTestDescriptor); - - ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, - configuration, null, null); - - assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag"); - assertThat(outerExtensionContext.getRoot()).isSameAs(outerExtensionContext); - - ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, - nestedClassDescriptor, configuration, null, null); - assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag"); - assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext); - - MethodExtensionContext methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null, - methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null); - methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); - assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag"); - assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext); - } - - @Test - @SuppressWarnings("resource") - void fromMethodTestDescriptor() { - TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); - ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor); - JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(UniqueId.forEngine("junit-jupiter"), - configuration); - engineDescriptor.addChild(classTestDescriptor); - - Object testInstance = new OuterClass(); - Method testMethod = methodTestDescriptor.getTestMethod(); - - JupiterEngineExtensionContext engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, - configuration, null); - ClassExtensionContext classExtensionContext = new ClassExtensionContext(engineExtensionContext, null, - classTestDescriptor, configuration, null, null); - MethodExtensionContext methodExtensionContext = new MethodExtensionContext(classExtensionContext, null, - methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null); - methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance)); - - // @formatter:off - assertAll("methodContext", - () -> assertThat(methodExtensionContext.getElement()).contains(testMethod), - () -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClass.class), - () -> assertThat(methodExtensionContext.getTestInstance()).contains(testInstance), - () -> assertThat(methodExtensionContext.getTestMethod()).contains(testMethod), - () -> assertThat(methodExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), - () -> assertThat(methodExtensionContext.getRequiredTestInstance()).isEqualTo(testInstance), - () -> assertThat(methodExtensionContext.getRequiredTestMethod()).isEqualTo(testMethod), - () -> assertThat(methodExtensionContext.getDisplayName()).isEqualTo(methodTestDescriptor.getDisplayName()), - () -> assertThat(methodExtensionContext.getParent()).contains(classExtensionContext), - () -> assertThat(methodExtensionContext.getRoot()).isSameAs(engineExtensionContext), - () -> assertThat(methodExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) - ); - // @formatter:on - } - - @Test - @SuppressWarnings("resource") - void reportEntriesArePublishedToExecutionContext() { - ClassTestDescriptor classTestDescriptor = outerClassDescriptor(null); - EngineExecutionListener engineExecutionListener = Mockito.spy(EngineExecutionListener.class); - ExtensionContext extensionContext = new ClassExtensionContext(null, engineExecutionListener, - classTestDescriptor, configuration, null, null); - - Map map1 = Collections.singletonMap("key", "value"); - Map map2 = Collections.singletonMap("other key", "other value"); - - extensionContext.publishReportEntry(map1); - extensionContext.publishReportEntry(map2); - extensionContext.publishReportEntry("3rd key", "third value"); - extensionContext.publishReportEntry("status message"); - - ArgumentCaptor entryCaptor = ArgumentCaptor.forClass(ReportEntry.class); - Mockito.verify(engineExecutionListener, Mockito.times(4)).reportingEntryPublished( - ArgumentMatchers.eq(classTestDescriptor), entryCaptor.capture()); - - ReportEntry reportEntry1 = entryCaptor.getAllValues().get(0); - ReportEntry reportEntry2 = entryCaptor.getAllValues().get(1); - ReportEntry reportEntry3 = entryCaptor.getAllValues().get(2); - ReportEntry reportEntry4 = entryCaptor.getAllValues().get(3); - - assertEquals(map1, reportEntry1.getKeyValuePairs()); - assertEquals(map2, reportEntry2.getKeyValuePairs()); - assertEquals("third value", reportEntry3.getKeyValuePairs().get("3rd key")); - assertEquals("status message", reportEntry4.getKeyValuePairs().get("value")); - } - - @Test - @SuppressWarnings("resource") - void usingStore() { - TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); - ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor); - ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, configuration, null, - null); - MethodExtensionContext childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor, - configuration, new OpenTest4JAwareThrowableCollector(), null); - childContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); - - ExtensionContext.Store childStore = childContext.getStore(Namespace.GLOBAL); - ExtensionContext.Store parentStore = parentContext.getStore(Namespace.GLOBAL); - - final Object key1 = "key 1"; - final String value1 = "a value"; - childStore.put(key1, value1); - assertEquals(value1, childStore.get(key1)); - assertEquals(value1, childStore.remove(key1)); - assertNull(childStore.get(key1)); - - childStore.put(key1, value1); - assertEquals(value1, childStore.get(key1)); - assertEquals(value1, childStore.remove(key1, String.class)); - assertNull(childStore.get(key1)); - - final Object key2 = "key 2"; - final String value2 = "other value"; - assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2)); - assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2, String.class)); - assertEquals(value2, childStore.get(key2)); - assertEquals(value2, childStore.get(key2, String.class)); - - final Object parentKey = "parent key"; - final String parentValue = "parent value"; - parentStore.put(parentKey, parentValue); - assertEquals(parentValue, childStore.getOrComputeIfAbsent(parentKey, k -> "a different value")); - assertEquals(parentValue, childStore.get(parentKey)); - } - - @TestFactory - Stream configurationParameter() throws Exception { - JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters()); - String key = "123"; - Optional expected = Optional.of(key); - - UniqueId engineUniqueId = UniqueId.parse("[engine:junit-jupiter]"); - JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(engineUniqueId, configuration); - - UniqueId classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]"); - ClassTestDescriptor classTestDescriptor = new ClassTestDescriptor(classUniqueId, getClass(), configuration); - - Method method = getClass().getDeclaredMethod("configurationParameter"); - UniqueId methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); - TestMethodTestDescriptor methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, getClass(), method, - configuration); - - return Stream.of( // - (ExtensionContext) new JupiterEngineExtensionContext(null, engineDescriptor, echo, null), // - new ClassExtensionContext(null, null, classTestDescriptor, echo, null, null), // - new MethodExtensionContext(null, null, methodTestDescriptor, echo, null, null) // - ).map(context -> dynamicTest(context.getClass().getSimpleName(), - () -> assertEquals(expected, context.getConfigurationParameter(key)))); - } - - private NestedClassTestDescriptor nestedClassDescriptor() { - return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"), OuterClass.NestedClass.class, - configuration); - } - - private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) { - ClassTestDescriptor classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"), - OuterClass.class, configuration); - if (child != null) { - classTestDescriptor.addChild(child); - } - return classTestDescriptor; - } - - private TestMethodTestDescriptor methodDescriptor() { - try { - return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClass.class, - OuterClass.class.getDeclaredMethod("aMethod"), configuration); - } - catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - @Tag("outer-tag") - static class OuterClass { - - @Tag("nested-tag") - class NestedClass { - } - - @Tag("method-tag") - void aMethod() { - } - } - - private static class EchoParameters implements ConfigurationParameters { - - @Override - public Optional get(String key) { - return Optional.of(key); - } - - @Override - public Optional getBoolean(String key) { - throw new UnsupportedOperationException("getBoolean(String) should not be called"); - } - - @Override - @SuppressWarnings("deprecation") - public int size() { - throw new UnsupportedOperationException("size() should not be called"); - } - - @Override - public Set keySet() { - throw new UnsupportedOperationException("keySet() should not be called"); - - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java deleted file mode 100644 index ecd65438..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.JupiterTestDescriptorTests.StaticTestCase.StaticTestCaseLevel2; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.MethodSource; - -/** - * Unit tests for {@link ClassTestDescriptor}, {@link NestedClassTestDescriptor}, - * and {@link TestMethodTestDescriptor}. - * - * @since 5.0 - * @see org.junit.jupiter.engine.descriptor.LifecycleMethodUtilsTests - */ -class JupiterTestDescriptorTests { - - private static final UniqueId uniqueId = UniqueId.root("enigma", "foo"); - - private final JupiterConfiguration configuration = mock(); - - @BeforeEach - void setUp() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); - } - - @Test - void constructFromClass() { - ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); - - assertEquals(TestCase.class, descriptor.getTestClass()); - assertThat(descriptor.getTags()).containsExactly(TestTag.create("inherited-class-level-tag"), - TestTag.create("classTag1"), TestTag.create("classTag2")); - } - - @Test - void constructFromClassWithInvalidBeforeAllDeclaration() { - // Note: if we can instantiate the descriptor, then the invalid configuration - // will not be reported during the test engine discovery phase. - ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidBeforeAllMethod.class, - configuration); - - assertEquals(TestCaseWithInvalidBeforeAllMethod.class, descriptor.getTestClass()); - } - - @Test - void constructFromClassWithInvalidAfterAllDeclaration() { - // Note: if we can instantiate the descriptor, then the invalid configuration - // will not be reported during the test engine discovery phase. - ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidAfterAllMethod.class, - configuration); - - assertEquals(TestCaseWithInvalidAfterAllMethod.class, descriptor.getTestClass()); - } - - @Test - void constructFromClassWithInvalidBeforeEachDeclaration() { - // Note: if we can instantiate the descriptor, then the invalid configuration - // will not be reported during the test engine discovery phase. - ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidBeforeEachMethod.class, - configuration); - - assertEquals(TestCaseWithInvalidBeforeEachMethod.class, descriptor.getTestClass()); - } - - @Test - void constructFromClassWithInvalidAfterEachDeclaration() { - // Note: if we can instantiate the descriptor, then the invalid configuration - // will not be reported during the test engine discovery phase. - ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidAfterEachMethod.class, - configuration); - - assertEquals(TestCaseWithInvalidAfterEachMethod.class, descriptor.getTestClass()); - } - - @Test - void constructFromMethod() throws Exception { - Class testClass = TestCase.class; - Method testMethod = testClass.getDeclaredMethod("test"); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, - configuration); - - assertEquals(uniqueId, descriptor.getUniqueId()); - assertEquals(testMethod, descriptor.getTestMethod()); - assertEquals("test()", descriptor.getDisplayName(), "display name:"); - assertEquals("test()", descriptor.getLegacyReportingName(), "legacy name:"); - } - - @Test - void constructFromMethodWithAnnotations() throws Exception { - JupiterTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); - Method testMethod = TestCase.class.getDeclaredMethod("foo"); - TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); - classDescriptor.addChild(methodDescriptor); - - assertEquals(testMethod, methodDescriptor.getTestMethod()); - assertEquals("custom test name", methodDescriptor.getDisplayName(), "display name:"); - assertEquals("foo()", methodDescriptor.getLegacyReportingName(), "legacy name:"); - - List tags = methodDescriptor.getTags().stream().map(TestTag::getName).collect(toList()); - assertThat(tags).containsExactlyInAnyOrder("inherited-class-level-tag", "classTag1", "classTag2", "methodTag1", - "methodTag2"); - } - - @Test - void constructFromMethodWithCustomTestAnnotation() throws Exception { - Method testMethod = TestCase.class.getDeclaredMethod("customTestAnnotation"); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); - - assertEquals(testMethod, descriptor.getTestMethod()); - assertEquals("custom name", descriptor.getDisplayName(), "display name:"); - assertEquals("customTestAnnotation()", descriptor.getLegacyReportingName(), "legacy name:"); - assertThat(descriptor.getTags()).containsExactly(TestTag.create("custom-tag")); - } - - @Test - void constructFromMethodWithParameters() throws Exception { - Method testMethod = TestCase.class.getDeclaredMethod("test", String.class, BigDecimal.class); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); - - assertEquals(testMethod, descriptor.getTestMethod()); - assertEquals("test(String, BigDecimal)", descriptor.getDisplayName(), "display name"); - assertEquals("test(String, BigDecimal)", descriptor.getLegacyReportingName(), "legacy name"); - } - - @Test - void constructFromMethodWithPrimitiveArrayParameter() throws Exception { - Method testMethod = TestCase.class.getDeclaredMethod("test", int[].class); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); - - assertEquals(testMethod, descriptor.getTestMethod()); - assertEquals("test(int[])", descriptor.getDisplayName(), "display name"); - assertEquals("test(int[])", descriptor.getLegacyReportingName(), "legacy name"); - } - - @Test - void constructFromMethodWithObjectArrayParameter() throws Exception { - Method testMethod = TestCase.class.getDeclaredMethod("test", String[].class); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); - - assertEquals(testMethod, descriptor.getTestMethod()); - assertEquals("test(String[])", descriptor.getDisplayName(), "display name"); - assertEquals("test(String[])", descriptor.getLegacyReportingName(), "legacy name"); - } - - @Test - void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exception { - Method testMethod = TestCase.class.getDeclaredMethod("test", int[][][][][].class); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); - - assertEquals(testMethod, descriptor.getTestMethod()); - assertEquals("test(int[][][][][])", descriptor.getDisplayName(), "display name"); - assertEquals("test(int[][][][][])", descriptor.getLegacyReportingName(), "legacy name"); - } - - @Test - void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Exception { - Method testMethod = TestCase.class.getDeclaredMethod("test", String[][][][][].class); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); - - assertEquals(testMethod, descriptor.getTestMethod()); - assertEquals("test(String[][][][][])", descriptor.getDisplayName(), "display name"); - assertEquals("test(String[][][][][])", descriptor.getLegacyReportingName(), "legacy name"); - } - - @Test - void constructFromInheritedMethod() throws Exception { - Method testMethod = ConcreteTestCase.class.getMethod("theTest"); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, ConcreteTestCase.class, testMethod, - configuration); - - assertEquals(testMethod, descriptor.getTestMethod()); - - Optional sourceOptional = descriptor.getSource(); - assertThat(sourceOptional).containsInstanceOf(MethodSource.class); - - MethodSource methodSource = (MethodSource) sourceOptional.orElseThrow(); - assertEquals(ConcreteTestCase.class.getName(), methodSource.getClassName()); - assertEquals("theTest", methodSource.getMethodName()); - } - - @Test - void shouldTakeCustomMethodNameDescriptorFromConfigurationIfPresent() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - - ClassBasedTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, getClass(), configuration); - assertEquals("class-display-name", descriptor.getDisplayName()); - assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); - assertEquals("nested-class-display-name", descriptor.getDisplayName()); - assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); - - descriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); - assertEquals("class-display-name", descriptor.getDisplayName()); - assertEquals(StaticTestCase.class.getName(), descriptor.getLegacyReportingName()); - - descriptor = new ClassTestDescriptor(uniqueId, StaticTestCaseLevel2.class, configuration); - assertEquals("class-display-name", descriptor.getDisplayName()); - assertEquals(StaticTestCaseLevel2.class.getName(), descriptor.getLegacyReportingName()); - } - - @Test - void defaultDisplayNamesForTestClasses() { - ClassBasedTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, getClass(), configuration); - assertEquals(getClass().getSimpleName(), descriptor.getDisplayName()); - assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); - assertEquals(NestedTestCase.class.getSimpleName(), descriptor.getDisplayName()); - assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); - - descriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); - String staticDisplayName = getClass().getSimpleName() + "$" + StaticTestCase.class.getSimpleName(); - assertEquals(staticDisplayName, descriptor.getDisplayName()); - assertEquals(StaticTestCase.class.getName(), descriptor.getLegacyReportingName()); - - descriptor = new ClassTestDescriptor(uniqueId, StaticTestCaseLevel2.class, configuration); - staticDisplayName += "$" + StaticTestCaseLevel2.class.getSimpleName(); - assertEquals(staticDisplayName, descriptor.getDisplayName()); - assertEquals(StaticTestCaseLevel2.class.getName(), descriptor.getLegacyReportingName()); - } - - @Test - void enclosingClassesAreDerivedFromParent() { - ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, - configuration); - ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, - configuration); - assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); - assertThat(nestedDescriptor.getEnclosingTestClasses()).isEmpty(); - - parentDescriptor.addChild(nestedDescriptor); - assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); - assertThat(nestedDescriptor.getEnclosingTestClasses()).containsExactly(StaticTestCase.class); - } - - // ------------------------------------------------------------------------- - - @Test - @DisplayName("custom name") - @Tag(" custom-tag ") - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @interface CustomTestAnnotation { - } - - @Tag("inherited-class-level-tag") - private static abstract class AbstractTestCase { - } - - @Tag("classTag1") - @Tag("classTag2") - @DisplayName("custom class name") - @SuppressWarnings("unused") - private static class TestCase extends AbstractTestCase { - - void test() { - } - - void test(String txt, BigDecimal sum) { - } - - void test(int[] nums) { - } - - void test(int[][][][][] nums) { - } - - void test(String[] info) { - } - - void test(String[][][][][] info) { - } - - @Test - @DisplayName("custom test name") - @Tag("methodTag1") - @Tag("methodTag2") - @Tag("tag containing whitespace") - void foo() { - } - - @CustomTestAnnotation - void customTestAnnotation() { - } - - } - - private static class TestCaseWithInvalidBeforeAllMethod { - - // must be static - @BeforeAll - void beforeAll() { - } - - @Test - void test() { - } - - } - - private static class TestCaseWithInvalidAfterAllMethod { - - // must be static - @AfterAll - void afterAll() { - } - - @Test - void test() { - } - - } - - private static class TestCaseWithInvalidBeforeEachMethod { - - // must NOT be static - @BeforeEach - static void beforeEach() { - } - - @Test - void test() { - } - - } - - private static class TestCaseWithInvalidAfterEachMethod { - - // must NOT be static - @AfterEach - static void afterEach() { - } - - @Test - void test() { - } - - } - - @Nested - class NestedTestCase { - } - - static class StaticTestCase { - - static class StaticTestCaseLevel2 { - } - } - - private abstract static class AbstractTestBase { - - @Test - public void theTest() { - } - } - - private static class ConcreteTestCase extends AbstractTestBase { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java deleted file mode 100644 index a77ec6ab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods; -import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods; - -import java.lang.reflect.Method; -import java.util.List; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.platform.commons.JUnitException; - -/** - * Unit tests for {@link LifecycleMethodUtils}. - * - * @since 5.0 - */ -class LifecycleMethodUtilsTests { - - @Test - void findNonVoidBeforeAllMethodsWithStandardLifecycle() { - JUnitException exception = assertThrows(JUnitException.class, - () -> findBeforeAllMethods(TestCaseWithNonVoidLifecyleMethods.class, true)); - assertEquals( - "@BeforeAll method 'java.lang.Double org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.cc()' must not return a value.", - exception.getMessage()); - } - - @Test - void findNonVoidAfterAllMethodsWithStandardLifecycle() { - JUnitException exception = assertThrows(JUnitException.class, - () -> findAfterAllMethods(TestCaseWithNonVoidLifecyleMethods.class, true)); - assertEquals( - "@AfterAll method 'java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.dd()' must not return a value.", - exception.getMessage()); - } - - @Test - void findNonVoidBeforeEachMethodsWithStandardLifecycle() { - JUnitException exception = assertThrows(JUnitException.class, - () -> findBeforeEachMethods(TestCaseWithNonVoidLifecyleMethods.class)); - assertEquals( - "@BeforeEach method 'java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.aa()' must not return a value.", - exception.getMessage()); - } - - @Test - void findNonVoidAfterEachMethodsWithStandardLifecycle() { - JUnitException exception = assertThrows(JUnitException.class, - () -> findAfterEachMethods(TestCaseWithNonVoidLifecyleMethods.class)); - assertEquals( - "@AfterEach method 'int org.junit.jupiter.engine.descriptor.TestCaseWithNonVoidLifecyleMethods.bb()' must not return a value.", - exception.getMessage()); - } - - @Test - void findBeforeEachMethodsWithStandardLifecycle() { - List methods = findBeforeEachMethods(TestCaseWithStandardLifecycle.class); - - assertThat(namesOf(methods)).containsExactlyInAnyOrder("nine", "ten"); - } - - @Test - void findAfterEachMethodsWithStandardLifecycle() { - List methods = findAfterEachMethods(TestCaseWithStandardLifecycle.class); - - assertThat(namesOf(methods)).containsExactlyInAnyOrder("eleven", "twelve"); - } - - @Test - void findBeforeAllMethodsWithStandardLifecycleAndWithoutRequiringStatic() { - List methods = findBeforeAllMethods(TestCaseWithStandardLifecycle.class, false); - - assertThat(namesOf(methods)).containsExactly("one"); - } - - @Test - void findBeforeAllMethodsWithStandardLifecycleAndRequiringStatic() { - JUnitException exception = assertThrows(JUnitException.class, - () -> findBeforeAllMethods(TestCaseWithStandardLifecycle.class, true)); - assertEquals( - "@BeforeAll method 'void org.junit.jupiter.engine.descriptor.TestCaseWithStandardLifecycle.one()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).", - exception.getMessage()); - } - - @Test - void findBeforeAllMethodsWithLifeCyclePerClassAndRequiringStatic() { - List methods = findBeforeAllMethods(TestCaseWithLifecyclePerClass.class, false); - - assertThat(namesOf(methods)).containsExactlyInAnyOrder("three", "four"); - } - - @Test - void findAfterAllMethodsWithStandardLifecycleAndWithoutRequiringStatic() { - List methods = findAfterAllMethods(TestCaseWithStandardLifecycle.class, false); - - assertThat(namesOf(methods)).containsExactlyInAnyOrder("five", "six"); - } - - @Test - void findAfterAllMethodsWithStandardLifecycleAndRequiringStatic() { - assertThrows(JUnitException.class, () -> findAfterAllMethods(TestCaseWithStandardLifecycle.class, true)); - } - - @Test - void findAfterAllMethodsWithLifeCyclePerClassAndRequiringStatic() { - List methods = findAfterAllMethods(TestCaseWithLifecyclePerClass.class, false); - - assertThat(namesOf(methods)).containsExactlyInAnyOrder("seven", "eight"); - } - - private static List namesOf(List methods) { - return methods.stream().map(Method::getName).collect(toList()); - } - -} - -class TestCaseWithStandardLifecycle { - - @BeforeAll - void one() { - } - - @BeforeEach - void nine() { - } - - @BeforeEach - void ten() { - } - - @AfterEach - void eleven() { - } - - @AfterEach - void twelve() { - } - - @AfterAll - void five() { - } - - @AfterAll - void six() { - } - -} - -@TestInstance(Lifecycle.PER_CLASS) -class TestCaseWithLifecyclePerClass { - - @BeforeAll - void three() { - } - - @BeforeAll - void four() { - } - - @AfterAll - void seven() { - } - - @AfterAll - void eight() { - } - -} - -class TestCaseWithNonVoidLifecyleMethods { - - @BeforeEach - String aa() { - return null; - } - - @AfterEach - int bb() { - return 1; - } - - @BeforeAll - Double cc() { - return null; - } - - @AfterAll - String dd() { - return ""; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java deleted file mode 100644 index 7718e771..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.File; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Optional; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; -import org.junit.platform.engine.support.descriptor.DirectorySource; -import org.junit.platform.engine.support.descriptor.FilePosition; -import org.junit.platform.engine.support.descriptor.FileSource; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.engine.support.descriptor.UriSource; -import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; - -/** - * Unit tests for {@link TestFactoryTestDescriptor}. - * - * @since 5.0 - */ -class TestFactoryTestDescriptorTests { - - /** - * @since 5.3 - */ - @Nested - class TestSources { - - @Test - void classpathResourceSourceFromUriWithFilePosition() { - FilePosition position = FilePosition.from(42, 21); - URI uri = URI.create("classpath:/test.js?line=42&column=21"); - TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); - - assertThat(testSource).isInstanceOf(ClasspathResourceSource.class); - ClasspathResourceSource source = (ClasspathResourceSource) testSource; - assertThat(source.getClasspathResourceName()).isEqualTo("test.js"); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void fileSourceFromUriWithFilePosition() { - File file = new File("src/test/resources/log4j2-test.xml"); - assertThat(file).isFile(); - - FilePosition position = FilePosition.from(42, 21); - URI uri = URI.create(file.toURI().toString() + "?line=42&column=21"); - TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); - - assertThat(testSource).isInstanceOf(FileSource.class); - FileSource source = (FileSource) testSource; - assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath()); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void directorySourceFromUri() { - File file = new File("src/test/resources"); - assertThat(file).isDirectory(); - - URI uri = file.toURI(); - TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); - - assertThat(testSource).isInstanceOf(DirectorySource.class); - DirectorySource source = (DirectorySource) testSource; - assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath()); - } - - @Test - void defaultUriSourceFromUri() { - File file = new File("src/test/resources"); - assertThat(file).isDirectory(); - - URI uri = URI.create("https://example.com?foo=bar&line=42"); - TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); - - assertThat(testSource).isInstanceOf(UriSource.class); - assertThat(testSource.getClass().getSimpleName()).isEqualTo("DefaultUriSource"); - UriSource source = (UriSource) testSource; - assertThat(source.getUri()).isEqualTo(uri); - } - - @Test - void methodSourceFromUri() { - URI uri = URI.create("method:org.junit.Foo#bar(java.lang.String,%20java.lang.String[])"); - TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); - - assertThat(testSource).isInstanceOf(MethodSource.class); - assertThat(testSource.getClass().getSimpleName()).isEqualTo("MethodSource"); - MethodSource source = (MethodSource) testSource; - assertThat(source.getClassName()).isEqualTo("org.junit.Foo"); - assertThat(source.getMethodName()).isEqualTo("bar"); - assertThat(source.getMethodParameterTypes()).isEqualTo("java.lang.String, java.lang.String[]"); - } - } - - @Nested - class Streams { - - private JupiterEngineExecutionContext context; - private ExtensionContext extensionContext; - private TestFactoryTestDescriptor descriptor; - private boolean isClosed; - private JupiterConfiguration jupiterConfiguration; - - @BeforeEach - void before() throws Exception { - jupiterConfiguration = mock(); - when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - - extensionContext = mock(); - isClosed = false; - - context = new JupiterEngineExecutionContext(null, null) // - .extend() // - .withThrowableCollector(new OpenTest4JAwareThrowableCollector()) // - .withExtensionContext(extensionContext) // - .withExtensionRegistry(mock()) // - .build(); - - Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); - descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class, - testMethod, jupiterConfiguration); - when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); - } - - @Test - void streamsFromTestFactoriesShouldBeClosed() { - Stream dynamicTestStream = Stream.empty(); - prepareMockForTestInstanceWithCustomStream(dynamicTestStream); - - descriptor.invokeTestMethod(context, mock()); - - assertTrue(isClosed); - } - - @Test - void streamsFromTestFactoriesShouldBeClosedWhenTheyThrow() { - Stream integerStream = Stream.of(1, 2); - prepareMockForTestInstanceWithCustomStream(integerStream); - - descriptor.invokeTestMethod(context, mock()); - - assertTrue(isClosed); - } - - private void prepareMockForTestInstanceWithCustomStream(Stream stream) { - Stream mockStream = stream.onClose(() -> isClosed = true); - when(extensionContext.getRequiredTestInstance()).thenReturn(new CustomStreamTestCase(mockStream)); - } - - } - - private static class CustomStreamTestCase { - - private final Stream mockStream; - - CustomStreamTestCase(Stream mockStream) { - this.mockStream = mockStream; - } - - @TestFactory - Stream customStream() { - return mockStream; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java deleted file mode 100644 index 46fab2de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; -import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Optional; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * Unit tests for {@link TestInstanceLifecycleUtils}. - * - *

NOTE: it doesn't make sense to unit test the JVM system property fallback - * support in this test class since that feature is a concrete implementation - * detail of {@code LauncherConfigurationParameters} which necessitates an - * integration test via the {@code Launcher} API. - * - * @since 5.0 - */ -class TestInstanceLifecycleUtilsTests { - - private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; - - @Test - void getTestInstanceLifecyclePreconditions() { - PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> getTestInstanceLifecycle(null, new DefaultJupiterConfiguration(mock()))); - assertThat(exception).hasMessage("testClass must not be null"); - - exception = assertThrows(PreconditionViolationException.class, - () -> getTestInstanceLifecycle(getClass(), null)); - assertThat(exception).hasMessage("configuration must not be null"); - } - - @Test - void getTestInstanceLifecycleWithNoConfigParamSet() { - Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(mock())); - assertThat(lifecycle).isEqualTo(PER_METHOD); - } - - @Test - void getTestInstanceLifecycleWithConfigParamSet() { - ConfigurationParameters configParams = mock(); - when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); - Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(configParams)); - assertThat(lifecycle).isEqualTo(PER_CLASS); - } - - @Test - void getTestInstanceLifecycleWithLocalConfigThatOverridesCustomDefaultSetViaConfigParam() { - ConfigurationParameters configParams = mock(); - when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); - Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, new DefaultJupiterConfiguration(configParams)); - assertThat(lifecycle).isEqualTo(PER_METHOD); - } - - @Test - void getTestInstanceLifecycleFromMetaAnnotationWithNoConfigParamSet() { - Class testClass = BaseMetaAnnotatedTestCase.class; - Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); - assertThat(lifecycle).isEqualTo(PER_CLASS); - } - - @Test - void getTestInstanceLifecycleFromSpecializedClassWithNoConfigParamSet() { - Class testClass = SpecializedTestCase.class; - Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); - assertThat(lifecycle).isEqualTo(PER_CLASS); - } - - @TestInstance(Lifecycle.PER_METHOD) - private static class TestCase { - } - - @Inherited - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - @TestInstance(Lifecycle.PER_CLASS) - private @interface PerClassLifeCycle { - } - - @PerClassLifeCycle - private static class BaseMetaAnnotatedTestCase { - } - - private static class SpecializedTestCase extends BaseMetaAnnotatedTestCase { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java deleted file mode 100644 index b00060eb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.engine.UniqueId; - -class TestTemplateInvocationTestDescriptorTests { - - @Test - void invocationsDoNotDeclareExclusiveResources() throws Exception { - Class testClass = MyTestCase.class; - Method testTemplateMethod = testClass.getDeclaredMethod("testTemplate"); - JupiterConfiguration configuration = mock(); - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - TestTemplateTestDescriptor parent = new TestTemplateTestDescriptor(UniqueId.root("segment", "template"), - testClass, testTemplateMethod, configuration); - TestTemplateInvocationContext invocationContext = mock(); - when(invocationContext.getDisplayName(anyInt())).thenReturn("invocation"); - - TestTemplateInvocationTestDescriptor testDescriptor = new TestTemplateInvocationTestDescriptor( - parent.getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "1"), testClass, - testTemplateMethod, invocationContext, 1, configuration); - - assertThat(parent.getExclusiveResources()).hasSize(1); - assertThat(testDescriptor.getExclusiveResources()).isEmpty(); - } - - static class MyTestCase { - @TestTemplate - @ResourceLock("a") - void testTemplate() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java deleted file mode 100644 index f9649dda..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Set; - -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; - -/** - * Unit tests for {@link TestTemplateTestDescriptor}. - * - * @since 5.0 - */ -class TestTemplateTestDescriptorTests { - private JupiterConfiguration jupiterConfiguration = mock(); - - @Test - void inheritsTagsFromParent() throws Exception { - UniqueId rootUniqueId = UniqueId.root("segment", "template"); - UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); - AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, - singleton(TestTag.create("foo"))); - - when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - - TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( - parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); - parent.addChild(testDescriptor); - - assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"), - TestTag.create("baz")); - } - - @Test - void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exception { - UniqueId rootUniqueId = UniqueId.root("segment", "template"); - UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); - AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, - singleton(TestTag.create("foo"))); - - when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - - TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( - parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); - parent.addChild(testDescriptor); - - assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name"); - } - - @Test - void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exception { - UniqueId rootUniqueId = UniqueId.root("segment", "template"); - UniqueId parentUniqueId = rootUniqueId.append("class", "myClass"); - AbstractTestDescriptor parent = containerTestDescriptorWithTags(parentUniqueId, - singleton(TestTag.create("foo"))); - - when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - - TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( - parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); - parent.addChild(testDescriptor); - - assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()"); - } - - private AbstractTestDescriptor containerTestDescriptorWithTags(UniqueId uniqueId, Set tags) { - return new AbstractTestDescriptor(uniqueId, "testDescriptor with tags") { - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public Set getTags() { - return tags; - } - }; - } - - static class MyTestCase { - - @Tag("bar") - @Tag("baz") - @TestTemplate - void testTemplate() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java deleted file mode 100644 index 25091d05..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor.subpackage; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -public class Class1WithTestCases { - - @Test - void test1() { - - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java deleted file mode 100644 index 2732b274..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor.subpackage; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -public class Class2WithTestCases { - - @Test - void test2() { - - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java deleted file mode 100644 index 139f38e6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor.subpackage; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -public class ClassWithStaticInnerTestCases { - - public static class ShouldBeDiscovered { - - @Test - void test1() { - } - } - - @SuppressWarnings("unused") - private static class ShouldNotBeDiscovered { - - @Test - void test2() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java deleted file mode 100644 index bb0d49fe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor.subpackage; - -/** - * @since 5.0 - */ -public class ClassWithoutTestCases { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java deleted file mode 100644 index a71d0665..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ /dev/null @@ -1,900 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static java.util.Collections.singleton; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE; -import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.engineId; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForClass; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForMethod; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestFactoryMethod; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTopLevelClass; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; -import static org.junit.platform.engine.SelectorResolutionResult.Status.RESOLVED; -import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; -import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.engine.discovery.PackageNameFilter.excludePackageNames; -import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.descriptor.DynamicDescendantFilter; -import org.junit.jupiter.engine.descriptor.Filterable; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; -import org.junit.jupiter.engine.descriptor.subpackage.Class1WithTestCases; -import org.junit.jupiter.engine.descriptor.subpackage.Class2WithTestCases; -import org.junit.jupiter.engine.descriptor.subpackage.ClassWithStaticInnerTestCases; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.SelectorResolutionResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; -import org.mockito.ArgumentCaptor; - -/** - * @since 5.0 - */ -class DiscoverySelectorResolverTests { - - private final JupiterConfiguration configuration = mock(); - private final LauncherDiscoveryListener discoveryListener = mock(); - private final JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(engineId(), configuration); - - @BeforeEach - void setUp() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); - } - - @Test - void nonTestClassResolution() { - resolve(request().selectors(selectClass(NonTestClass.class))); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - } - - @Test - void doesNotAttemptToResolveMethodsForNonTestClasses() { - var methodSelector = selectMethod(NonTestClass.class, "doesNotExist"); - resolve(request().selectors(methodSelector)); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - assertUnresolved(methodSelector); - } - - @Test - void abstractClassResolution() { - resolve(request().selectors(selectClass(AbstractTestClass.class))); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - assertUnresolved(selectClass(AbstractTestClass.class)); - } - - @Test - void singleClassResolution() { - ClassSelector selector = selectClass(MyTestClass.class); - - resolve(request().selectors(selector)); - - assertEquals(4, engineDescriptor.getDescendants().size()); - assertUniqueIdsForMyTestClass(uniqueIds()); - } - - @Test - void classResolutionForNonexistentClass() { - ClassSelector selector = selectClass("org.example.DoesNotExist"); - - resolve(request().selectors(selector)); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - var result = verifySelectorProcessed(selector); - assertThat(result.getStatus()).isEqualTo(FAILED); - assertThat(result.getThrowable().get()).hasMessageContaining("Could not load class with name"); - } - - @Test - void duplicateClassSelectorOnlyResolvesOnce() { - resolve(request().selectors( // - selectClass(MyTestClass.class), // - selectClass(MyTestClass.class) // - )); - - assertEquals(4, engineDescriptor.getDescendants().size()); - assertUniqueIdsForMyTestClass(uniqueIds()); - } - - @Test - void twoClassesResolution() { - ClassSelector selector1 = selectClass(MyTestClass.class); - ClassSelector selector2 = selectClass(YourTestClass.class); - - resolve(request().selectors(selector1, selector2)); - - assertEquals(7, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertUniqueIdsForMyTestClass(uniqueIds); - assertThat(uniqueIds).contains(uniqueIdForClass(YourTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(YourTestClass.class, "test3()")); - assertThat(uniqueIds).contains(uniqueIdForMethod(YourTestClass.class, "test4()")); - } - - private void assertUniqueIdsForMyTestClass(List uniqueIds) { - assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); - assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test2()")); - assertThat(uniqueIds).contains(uniqueIdForTestFactoryMethod(MyTestClass.class, "dynamicTest()")); - } - - @Test - void classResolutionOfStaticNestedClass() { - ClassSelector selector = selectClass(OtherTestClass.NestedTestClass.class); - - resolve(request().selectors(selector)); - - assertEquals(3, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); - assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test6()")); - } - - @Test - void methodResolution() throws NoSuchMethodException { - Method test1 = MyTestClass.class.getDeclaredMethod("test1"); - MethodSelector selector = selectMethod(test1.getDeclaringClass(), test1); - - resolve(request().selectors(selector)); - - assertEquals(2, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); - } - - @Test - void methodResolutionFromInheritedMethod() throws NoSuchMethodException { - MethodSelector selector = selectMethod(HerTestClass.class, MyTestClass.class.getDeclaredMethod("test1")); - - resolve(request().selectors(selector)); - - assertEquals(2, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test1()")); - } - - @Test - void resolvingSelectorOfNonTestMethodResolvesNothing() throws NoSuchMethodException { - Method notATest = MyTestClass.class.getDeclaredMethod("notATest"); - MethodSelector selector = selectMethod(notATest.getDeclaringClass(), notATest); - - resolve(request().selectors(selector)); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - } - - @Test - void methodResolutionForNonexistentClass() { - String className = "org.example.DoesNotExist"; - String methodName = "bogus"; - MethodSelector selector = selectMethod(className, methodName, ""); - - resolve(request().selectors(selector)); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - var result = verifySelectorProcessed(selector); - assertThat(result.getStatus()).isEqualTo(FAILED); - assertThat(result.getThrowable().get())// - .isInstanceOf(PreconditionViolationException.class)// - .hasMessageStartingWith("Could not load class with name: " + className); - } - - @Test - void methodResolutionForNonexistentMethod() { - MethodSelector selector = selectMethod(MyTestClass.class, "bogus", ""); - - resolve(request().selectors(selector)); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - var result = verifySelectorProcessed(selector); - assertThat(result.getStatus()).isEqualTo(FAILED); - assertThat(result.getThrowable().get()).hasMessageContaining("Could not find method"); - } - - @Test - void classResolutionByUniqueId() { - UniqueIdSelector selector = selectUniqueId(uniqueIdForClass(MyTestClass.class).toString()); - - resolve(request().selectors(selector)); - - assertEquals(4, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertUniqueIdsForMyTestClass(uniqueIds); - } - - @Test - void staticNestedClassResolutionByUniqueId() { - UniqueIdSelector selector = selectUniqueId(uniqueIdForClass(OtherTestClass.NestedTestClass.class).toString()); - - resolve(request().selectors(selector)); - - assertEquals(3, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); - assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test6()")); - } - - @Test - void methodOfInnerClassByUniqueId() { - UniqueIdSelector selector = selectUniqueId( - uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()").toString()); - - resolve(request().selectors(selector)); - - assertEquals(2, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); - } - - @Test - void resolvingUniqueIdWithUnknownSegmentTypeResolvesNothing() { - UniqueId uniqueId = engineId().append("bogus", "enigma"); - UniqueIdSelector selector = selectUniqueId(uniqueId); - - resolve(request().selectors(selector)); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - assertUnresolved(selector); - } - - @Test - void resolvingUniqueIdOfNonTestMethodResolvesNothing() { - UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "notATest()")); - - resolve(request().selectors(selector)); - - assertThat(engineDescriptor.getDescendants()).isEmpty(); - assertUnresolved(selector); - } - - @Test - void methodResolutionByUniqueIdWithMissingMethodName() { - UniqueId uniqueId = uniqueIdForMethod(getClass(), "()"); - - resolve(request().selectors(selectUniqueId(uniqueId))); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - var result = verifySelectorProcessed(selectUniqueId(uniqueId)); - assertThat(result.getStatus()).isEqualTo(FAILED); - assertThat(result.getThrowable().get())// - .isInstanceOf(PreconditionViolationException.class)// - .hasMessageStartingWith("Method [()] does not match pattern"); - } - - @Test - void methodResolutionByUniqueIdWithMissingParameters() { - UniqueId uniqueId = uniqueIdForMethod(getClass(), "methodName"); - - resolve(request().selectors(selectUniqueId(uniqueId))); - - assertThat(engineDescriptor.getDescendants()).isEmpty(); - var result = verifySelectorProcessed(selectUniqueId(uniqueId)); - assertThat(result.getStatus()).isEqualTo(FAILED); - assertThat(result.getThrowable().get())// - .isInstanceOf(PreconditionViolationException.class)// - .hasMessageStartingWith("Method [methodName] does not match pattern"); - } - - @Test - void methodResolutionByUniqueIdWithBogusParameters() { - UniqueId uniqueId = uniqueIdForMethod(getClass(), "methodName(java.lang.String, junit.foo.Enigma)"); - - resolve(request().selectors(selectUniqueId(uniqueId))); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - var result = verifySelectorProcessed(selectUniqueId(uniqueId)); - assertThat(result.getStatus()).isEqualTo(FAILED); - assertThat(result.getThrowable().get())// - .isInstanceOf(JUnitException.class)// - .hasMessage("Failed to load parameter type [%s] for method [%s] in class [%s].", "junit.foo.Enigma", - "methodName", getClass().getName()); - } - - @Test - void methodResolutionByUniqueId() { - UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test1()").toString()); - - resolve(request().selectors(selector)); - - assertEquals(2, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); - } - - @Test - void methodResolutionByUniqueIdFromInheritedClass() { - UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(HerTestClass.class, "test1()").toString()); - - resolve(request().selectors(selector)); - - assertEquals(2, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - - assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test1()")); - } - - @Test - void methodResolutionByUniqueIdWithParams() { - UniqueIdSelector selector = selectUniqueId( - uniqueIdForMethod(HerTestClass.class, "test7(java.lang.String)").toString()); - - resolve(request().selectors(selector)); - - assertEquals(2, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test7(java.lang.String)")); - - } - - @Test - void resolvingUniqueIdWithWrongParamsResolvesNothing() { - UniqueId uniqueId = uniqueIdForMethod(HerTestClass.class, "test7(java.math.BigDecimal)"); - - resolve(request().selectors(selectUniqueId(uniqueId))); - - assertTrue(engineDescriptor.getDescendants().isEmpty()); - assertUnresolved(selectUniqueId(uniqueId)); - } - - @Test - void twoMethodResolutionsByUniqueId() { - UniqueIdSelector selector1 = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test1()").toString()); - UniqueIdSelector selector2 = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test2()").toString()); - - // adding same selector twice should have no effect - resolve(request().selectors(selector1, selector2, selector2)); - - assertEquals(3, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); - assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test2()")); - - TestDescriptor classFromMethod1 = descriptorByUniqueId( - uniqueIdForMethod(MyTestClass.class, "test1()")).getParent().get(); - TestDescriptor classFromMethod2 = descriptorByUniqueId( - uniqueIdForMethod(MyTestClass.class, "test2()")).getParent().get(); - - assertEquals(classFromMethod1, classFromMethod2); - assertSame(classFromMethod1, classFromMethod2); - } - - @Test - void packageResolutionUsingExplicitBasePackage() { - PackageSelector selector = selectPackage("org.junit.jupiter.engine.descriptor.subpackage"); - - resolve(request().selectors(selector)); - - assertEquals(6, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); - assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); - assertThat(uniqueIds).contains( - uniqueIdForMethod(ClassWithStaticInnerTestCases.ShouldBeDiscovered.class, "test1()")); - } - - @Test - void packageResolutionUsingDefaultPackage() throws Exception { - resolve(request().selectors(selectPackage(""))); - - // 150 is completely arbitrary. The actual number is likely much higher. - assertThat(engineDescriptor.getDescendants().size())// - .describedAs("Too few test descriptors in classpath")// - .isGreaterThan(150); - - List uniqueIds = uniqueIds(); - assertThat(uniqueIds)// - .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// - .contains(uniqueIdForClass(ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get())); - assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); - assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); - } - - @Test - void classpathResolution() throws Exception { - Path classpath = Paths.get( - DiscoverySelectorResolverTests.class.getProtectionDomain().getCodeSource().getLocation().toURI()); - - List selectors = selectClasspathRoots(singleton(classpath)); - - resolve(request().selectors(selectors)); - - // 150 is completely arbitrary. The actual number is likely much higher. - assertThat(engineDescriptor.getDescendants().size())// - .describedAs("Too few test descriptors in classpath")// - .isGreaterThan(150); - - List uniqueIds = uniqueIds(); - assertThat(uniqueIds)// - .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// - .contains(uniqueIdForClass(ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get())); - assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); - assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); - assertThat(uniqueIds).contains( - uniqueIdForMethod(ClassWithStaticInnerTestCases.ShouldBeDiscovered.class, "test1()")); - } - - @Test - void classpathResolutionForJarFiles() throws Exception { - URL jarUrl = getClass().getResource("/jupiter-testjar.jar"); - Path path = Paths.get(jarUrl.toURI()); - List selectors = selectClasspathRoots(singleton(path)); - - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try (URLClassLoader classLoader = new URLClassLoader(new URL[] { jarUrl })) { - Thread.currentThread().setContextClassLoader(classLoader); - - resolve(request().selectors(selectors)); - - assertThat(uniqueIds()) // - .contains(uniqueIdForTopLevelClass("com.example.project.FirstTest")) // - .contains(uniqueIdForTopLevelClass("com.example.project.SecondTest")); - } - finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - } - - @Test - void nestedTestResolutionFromBaseClass() { - ClassSelector selector = selectClass(TestCaseWithNesting.class); - - resolve(request().selectors(selector)); - - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).hasSize(6); - - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.class, "testA()")); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); - assertThat(uniqueIds).contains( - uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); - } - - @Test - void nestedTestResolutionFromNestedTestClass() { - ClassSelector selector = selectClass(TestCaseWithNesting.NestedTestCase.class); - - resolve(request().selectors(selector)); - - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).hasSize(5); - - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); - assertThat(uniqueIds).contains( - uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); - } - - @Test - void nestedTestResolutionFromUniqueId() { - UniqueIdSelector selector = selectUniqueId( - uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class).toString()); - - resolve(request().selectors(selector)); - - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).hasSize(4); - - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); - assertThat(uniqueIds).contains( - uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); - } - - @Test - void doubleNestedTestResolutionFromClass() { - ClassSelector selector = selectClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class); - - resolve(request().selectors(selector)); - - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).hasSize(4); - - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); - assertThat(uniqueIds).contains( - uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); - } - - @Test - void methodResolutionInDoubleNestedTestClass() throws NoSuchMethodException { - MethodSelector selector = selectMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, - TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class.getDeclaredMethod("testC")); - - resolve(request().selectors(selector)); - - assertEquals(4, engineDescriptor.getDescendants().size()); - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); - assertThat(uniqueIds).contains( - uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); - } - - @Test - void nestedTestResolutionFromUniqueIdToMethod() { - UniqueIdSelector selector = selectUniqueId( - uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()").toString()); - - resolve(request().selectors(selector)); - - List uniqueIds = uniqueIds(); - assertThat(uniqueIds).hasSize(3); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); - assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); - assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); - } - - @Test - void testFactoryMethodResolutionByUniqueId() { - Class clazz = MyTestClass.class; - UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); - - resolve(request().selectors(selectUniqueId(factoryUid))); - - assertThat(engineDescriptor.getDescendants()).hasSize(2); - assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); - } - - @Test - void testTemplateMethodResolutionByUniqueId() { - Class clazz = TestClassWithTemplate.class; - UniqueId templateUid = uniqueIdForTestTemplateMethod(clazz, "testTemplate()"); - - resolve(request().selectors(selectUniqueId(templateUid))); - - assertThat(engineDescriptor.getDescendants()).hasSize(2); - assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); - } - - @Test - void resolvingDynamicTestByUniqueIdResolvesUpToParentTestFactory() { - Class clazz = MyTestClass.class; - UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); - UniqueId dynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); - UniqueId differentDynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); - - resolve(request().selectors(selectUniqueId(dynamicTestUid))); - - assertThat(engineDescriptor.getDescendants()).hasSize(2); - assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); - TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - - TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); - assertThat(dynamicDescendantFilter.test(dynamicTestUid, 42)).isTrue(); - assertThat(dynamicDescendantFilter.test(differentDynamicTestUid, 42)).isFalse(); - - assertAllSelectorsResolved(); - } - - @Test - void resolvingDynamicContainerByUniqueIdResolvesUpToParentTestFactory() { - Class clazz = MyTestClass.class; - UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); - UniqueId dynamicContainerUid = factoryUid.append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1"); - UniqueId differentDynamicContainerUid = factoryUid.append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#2"); - UniqueId dynamicTestUid = dynamicContainerUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); - UniqueId differentDynamicTestUid = dynamicContainerUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); - - resolve(request().selectors(selectUniqueId(dynamicTestUid))); - - assertThat(engineDescriptor.getDescendants()).hasSize(2); - assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); - TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - - TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); - assertThat(dynamicDescendantFilter.test(dynamicTestUid, 42)).isTrue(); - assertThat(dynamicDescendantFilter.test(differentDynamicContainerUid, 42)).isFalse(); - assertThat(dynamicDescendantFilter.test(differentDynamicTestUid, 42)).isFalse(); - - assertAllSelectorsResolved(); - } - - @Test - void resolvingDynamicTestByUniqueIdAndTestFactoryByMethodSelectorResolvesTestFactory() { - Class clazz = MyTestClass.class; - UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); - UniqueId dynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); - - resolve(request().selectors(selectUniqueId(dynamicTestUid), selectMethod(clazz, "dynamicTest"))); - - assertThat(engineDescriptor.getDescendants()).hasSize(2); - assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); - TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); - assertThat(dynamicDescendantFilter.test(UniqueId.root("foo", "bar"), 42)).isTrue(); - } - - private DynamicDescendantFilter getDynamicDescendantFilter(TestDescriptor testDescriptor) { - assertThat(testDescriptor).isInstanceOf(JupiterTestDescriptor.class); - return ((Filterable) testDescriptor).getDynamicDescendantFilter(); - } - - @Test - void resolvingTestTemplateInvocationByUniqueIdResolvesOnlyUpToParentTestTemplate() { - Class clazz = TestClassWithTemplate.class; - UniqueId templateUid = uniqueIdForTestTemplateMethod(clazz, "testTemplate()"); - UniqueId invocationUid = templateUid.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); - - resolve(request().selectors(selectUniqueId(invocationUid))); - - assertThat(engineDescriptor.getDescendants()).hasSize(2); - assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); - } - - @Test - void includingPackageNameFilterExcludesClassesInNonMatchingPackages() { - resolve(request().selectors(selectClass(MatchingClass.class)).filters( - includePackageNames("org.junit.jupiter.engine.unknown"))); - - assertThat(engineDescriptor.getDescendants()).isEmpty(); - } - - @Test - void includingPackageNameFilterIncludesClassesInMatchingPackages() { - resolve(request().selectors(selectClass(MatchingClass.class)).filters( - includePackageNames("org.junit.jupiter.engine"))); - - assertThat(engineDescriptor.getDescendants()).hasSize(3); - } - - @Test - void excludingPackageNameFilterExcludesClassesInMatchingPackages() { - resolve(request().selectors(selectClass(MatchingClass.class)).filters( - excludePackageNames("org.junit.jupiter.engine"))); - - assertThat(engineDescriptor.getDescendants()).isEmpty(); - } - - @Test - void excludingPackageNameFilterIncludesClassesInNonMatchingPackages() { - resolve(request().selectors(selectClass(MatchingClass.class)).filters( - excludePackageNames("org.junit.jupiter.engine.unknown"))); - - assertThat(engineDescriptor.getDescendants()).hasSize(3); - } - - @Test - void classNamePatternFilterExcludesNonMatchingClasses() { - resolve(request().selectors(selectClass(MatchingClass.class), selectClass(OtherClass.class)).filters( - includeClassNamePatterns(".*MatchingClass"))); - - assertThat(engineDescriptor.getDescendants()).hasSize(3); - } - - private void resolve(LauncherDiscoveryRequestBuilder builder) { - new DiscoverySelectorResolver().resolveSelectors(builder.build(), engineDescriptor); - } - - private TestDescriptor descriptorByUniqueId(UniqueId uniqueId) { - return engineDescriptor.getDescendants().stream().filter( - d -> d.getUniqueId().equals(uniqueId)).findFirst().get(); - } - - private List uniqueIds() { - return engineDescriptor.getDescendants().stream().map(TestDescriptor::getUniqueId).collect(toList()); - } - - private LauncherDiscoveryRequestBuilder request() { - return LauncherDiscoveryRequestBuilder.request() // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .listeners(discoveryListener); - } - - private void assertAllSelectorsResolved() { - ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); - verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-jupiter")), any(), - resultCaptor.capture()); - assertThat(resultCaptor.getAllValues()) // - .flatExtracting(SelectorResolutionResult::getStatus) // - .allMatch(Predicate.isEqual(RESOLVED)); - } - - private void assertUnresolved(DiscoverySelector selector) { - var result = verifySelectorProcessed(selector); - assertThat(result.getStatus()).isEqualTo(UNRESOLVED); - } - - private SelectorResolutionResult verifySelectorProcessed(DiscoverySelector selector) { - ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); - verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-jupiter")), eq(selector), - resultCaptor.capture()); - return resultCaptor.getValue(); - } - -} - -// ----------------------------------------------------------------------------- - -class NonTestClass { -} - -abstract class AbstractTestClass { - - @Test - void test() { - } -} - -class MyTestClass { - - @Test - void test1() { - } - - @Test - void test2() { - } - - void notATest() { - } - - @TestFactory - Stream dynamicTest() { - return new ArrayList().stream(); - } -} - -class YourTestClass { - - @Test - void test3() { - } - - @Test - void test4() { - } -} - -class HerTestClass extends MyTestClass { - - @Test - void test7(String param) { - } -} - -class OtherTestClass { - - static class NestedTestClass { - - @Test - void test5() { - } - - @Test - void test6() { - } - } -} - -class TestCaseWithNesting { - - @Test - void testA() { - } - - @Nested - class NestedTestCase { - - @Test - void testB() { - } - - @Nested - class DoubleNestedTestCase { - - @Test - void testC() { - } - } - } -} - -class TestClassWithTemplate { - - @TestTemplate - void testTemplate() { - } -} - -class MatchingClass { - @Nested - class NestedClass { - @Test - void test() { - } - } -} - -class OtherClass { - @Test - void test() { - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java deleted file mode 100644 index 3ab385d7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.Method; -import java.util.List; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.launcher.LauncherDiscoveryRequest; - -/** - * Test correct test discovery in simple test classes for the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class DiscoveryTests extends AbstractJupiterTestEngineTests { - - @Test - void discoverTestClass() { - LauncherDiscoveryRequest request = request().selectors(selectClass(LocalTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void doNotDiscoverAbstractTestClass() { - LauncherDiscoveryRequest request = request().selectors(selectClass(AbstractTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverMethodByUniqueId() { - LauncherDiscoveryRequest request = request().selectors( - selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test1()"))).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverMethodByUniqueIdForOverloadedMethod() { - LauncherDiscoveryRequest request = request().selectors( - selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test4()"))).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { - LauncherDiscoveryRequest request = request().selectors(selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod( - LocalTestCase.class, "test4(" + TestInfo.class.getName() + ")"))).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverMethodByMethodReference() throws NoSuchMethodException { - Method testMethod = LocalTestCase.class.getDeclaredMethod("test3", new Class[0]); - - LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, testMethod)).build(); - TestDescriptor engineDescriptor = discoverTests(request); - assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverMultipleMethodsOfSameClass() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, "test1"), - selectMethod(LocalTestCase.class, "test2")).build(); - - TestDescriptor engineDescriptor = discoverTests(request); - - assertThat(engineDescriptor.getChildren()).hasSize(1); - TestDescriptor classDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(classDescriptor.getChildren()).hasSize(2); - } - - @Test - void discoverCompositeSpec() { - LauncherDiscoveryRequest spec = request().selectors( - selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test2()")), - selectClass(LocalTestCase.class)).build(); - - TestDescriptor engineDescriptor = discoverTests(spec); - assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverTestTemplateMethodByUniqueId() { - LauncherDiscoveryRequest spec = request().selectors( - selectUniqueId(uniqueIdForTestTemplateMethod(TestTemplateClass.class, "testTemplate()"))).build(); - - TestDescriptor engineDescriptor = discoverTests(spec); - assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverTestTemplateMethodByMethodSelector() { - LauncherDiscoveryRequest spec = request().selectors( - selectMethod(TestTemplateClass.class, "testTemplate")).build(); - - TestDescriptor engineDescriptor = discoverTests(spec); - assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); - } - - @Test - void discoverDeeplyNestedTestMethodByNestedMethodSelector() throws Exception { - var selector = selectNestedMethod( - List.of(TestCaseWithExtendedNested.class, TestCaseWithExtendedNested.ConcreteInner1.class), - AbstractSuperClass.NestedInAbstractClass.class, - AbstractSuperClass.NestedInAbstractClass.class.getDeclaredMethod("test")); - LauncherDiscoveryRequest spec = request().selectors(selector).build(); - - TestDescriptor engineDescriptor = discoverTests(spec); - - ClassTestDescriptor topLevelClassDescriptor = (ClassTestDescriptor) getOnlyElement( - engineDescriptor.getChildren()); - assertThat(topLevelClassDescriptor.getTestClass()).isEqualTo(TestCaseWithExtendedNested.class); - - NestedClassTestDescriptor firstLevelNestedClassDescriptor = (NestedClassTestDescriptor) getOnlyElement( - topLevelClassDescriptor.getChildren()); - assertThat(firstLevelNestedClassDescriptor.getTestClass()).isEqualTo( - TestCaseWithExtendedNested.ConcreteInner1.class); - - NestedClassTestDescriptor secondLevelNestedClassDescriptor = (NestedClassTestDescriptor) getOnlyElement( - firstLevelNestedClassDescriptor.getChildren()); - assertThat(secondLevelNestedClassDescriptor.getTestClass()).isEqualTo( - AbstractSuperClass.NestedInAbstractClass.class); - - TestMethodTestDescriptor methodDescriptor = (TestMethodTestDescriptor) getOnlyElement( - secondLevelNestedClassDescriptor.getChildren()); - assertThat(methodDescriptor.getTestMethod().getName()).isEqualTo("test"); - } - - // ------------------------------------------------------------------- - - private static abstract class AbstractTestCase { - - @Test - void abstractTest() { - - } - } - - static class LocalTestCase { - - @Test - void test1() { - } - - @Test - void test2() { - } - - @Test - void test3() { - } - - @Test - void test4() { - } - - @Test - void test4(TestInfo testInfo) { - } - - @CustomTestAnnotation - void customTestAnnotation() { - /* no-op */ - } - - } - - @Test - @Retention(RetentionPolicy.RUNTIME) - @interface CustomTestAnnotation { - } - - static class TestTemplateClass { - - @TestTemplate - void testTemplate() { - } - - } - - static abstract class AbstractSuperClass { - @Nested - class NestedInAbstractClass { - @Test - void test() { - } - } - } - - static class TestCaseWithExtendedNested { - @Nested - class ConcreteInner1 extends AbstractSuperClass { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java deleted file mode 100644 index 99a41166..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -class IsInnerClassTests { - - private final Predicate> isInnerClass = new IsInnerClass(); - - @Test - void innerClassEvaluatesToTrue() { - assertThat(isInnerClass).accepts(InnerClassesTestCase.InnerClass.class); - } - - @Test - void staticNestedClassEvaluatesToFalse() { - assertThat(isInnerClass).rejects(InnerClassesTestCase.StaticNestedClass.class); - } - - @Test - void privateInnerClassEvaluatesToFalse() { - assertThat(isInnerClass).rejects(InnerClassesTestCase.PrivateInnerClass.class); - } - - private static class InnerClassesTestCase { - - class InnerClass { - } - - static class StaticNestedClass { - } - - private class PrivateInnerClass { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java deleted file mode 100644 index 9ce907e8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -class IsNestedTestClassTests { - - private final Predicate> isNestedTestClass = new IsNestedTestClass(); - - @Test - void innerClassEvaluatesToTrue() { - assertThat(isNestedTestClass).accepts(NestedClassesTestCase.InnerClass.class); - } - - @Test - void staticNestedClassEvaluatesToFalse() { - assertThat(isNestedTestClass).rejects(NestedClassesTestCase.StaticNestedClass.class); - } - - @Test - void privateNestedClassEvaluatesToFalse() { - assertThat(isNestedTestClass).rejects(NestedClassesTestCase.PrivateInnerClass.class); - } - - private static class NestedClassesTestCase { - - @Nested - class InnerClass { - } - - @Nested - static class StaticNestedClass { - } - - @Nested - private class PrivateInnerClass { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java deleted file mode 100644 index 09fee4aa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -class IsPotentialTestContainerTests { - - private final IsPotentialTestContainer isPotentialTestContainer = new IsPotentialTestContainer(); - - @Test - void staticClassEvaluatesToTrue() { - assertTrue(isPotentialTestContainer.test(StaticClass.class)); - } - - @Test - void privateStaticClassEvaluatesToFalse() { - assertFalse(isPotentialTestContainer.test(PrivateStaticClass.class)); - } - - @Test - void abstractClassEvaluatesToFalse() { - assertFalse(isPotentialTestContainer.test(AbstractClass.class)); - } - - @Test - void localClassEvaluatesToFalse() { - - class LocalClass { - } - - assertFalse(isPotentialTestContainer.test(LocalClass.class)); - } - - @Test - void anonymousClassEvaluatesToFalse() { - - Object object = new Object() { - @Override - public String toString() { - return ""; - } - }; - - assertFalse(isPotentialTestContainer.test(object.getClass())); - } - - private static class PrivateStaticClass { - } - - static class StaticClass { - } - -} - -abstract class AbstractClass { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java deleted file mode 100644 index 53537e9a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Predicate; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestTemplate; - -/** - * Unit tests for {@link IsTestClassWithTests}. - * - * @since 5.0 - */ -class IsTestClassWithTestsTests { - - private final Predicate> isTestClassWithTests = new IsTestClassWithTests(); - - @Test - void classWithTestMethodEvaluatesToTrue() { - assertTrue(isTestClassWithTests.test(ClassWithTestMethod.class)); - } - - @Test - void classWithTestFactoryEvaluatesToTrue() { - assertTrue(isTestClassWithTests.test(ClassWithTestFactory.class)); - } - - @Test - void classWithTestTemplateEvaluatesToTrue() { - assertTrue(isTestClassWithTests.test(ClassWithTestTemplate.class)); - } - - @Test - void classWithNestedTestClassEvaluatesToTrue() { - assertTrue(isTestClassWithTests.test(ClassWithNestedTestClass.class)); - } - - @Test - void staticTestClassEvaluatesToTrue() { - assertTrue(isTestClassWithTests.test(StaticTestCase.class)); - } - - // ------------------------------------------------------------------------- - - @Test - void privateClassWithTestMethodEvaluatesToFalse() { - assertFalse(isTestClassWithTests.test(PrivateClassWithTestMethod.class)); - } - - @Test - void privateClassWithTestFactoryEvaluatesToFalse() { - assertFalse(isTestClassWithTests.test(PrivateClassWithTestFactory.class)); - } - - @Test - void privateClassWithTestTemplateEvaluatesToFalse() { - assertFalse(isTestClassWithTests.test(PrivateClassWithTestTemplate.class)); - } - - @Test - void privateClassWithNestedTestCasesEvaluatesToFalse() { - assertFalse(isTestClassWithTests.test(PrivateClassWithNestedTestClass.class)); - } - - @Test - void privateStaticTestClassEvaluatesToFalse() { - assertFalse(isTestClassWithTests.test(PrivateStaticTestCase.class)); - } - - /** - * @see https://github.com/junit-team/junit5/issues/2249 - */ - @Test - void recursiveHierarchies() { - assertTrue(isTestClassWithTests.test(OuterClass.class)); - assertFalse(isTestClassWithTests.test(OuterClass.RecursiveInnerClass.class)); - } - - // ------------------------------------------------------------------------- - - private class PrivateClassWithTestMethod { - - @Test - void test() { - } - - } - - private class PrivateClassWithTestFactory { - - @TestFactory - Collection factory() { - return new ArrayList<>(); - } - - } - - private class PrivateClassWithTestTemplate { - - @TestTemplate - void template(int a) { - } - - } - - private class PrivateClassWithNestedTestClass { - - @Nested - class InnerClass { - - @Test - void first() { - } - - @Test - void second() { - } - - } - } - - // ------------------------------------------------------------------------- - - static class StaticTestCase { - - @Test - void test() { - } - } - - private static class PrivateStaticTestCase { - - @Test - void test() { - } - } - - static class OuterClass { - - @Nested - class InnerClass { - - @Test - void test() { - } - } - - // Intentionally commented out so that RecursiveInnerClass is NOT a candidate test class - // @Nested - class RecursiveInnerClass extends OuterClass { - } - } - -} - -// ----------------------------------------------------------------------------- - -class ClassWithTestMethod { - - @Test - void test() { - } - -} - -class ClassWithTestFactory { - - @TestFactory - Collection factory() { - return new ArrayList<>(); - } - -} - -class ClassWithTestTemplate { - - @TestTemplate - void template(int a) { - } - -} - -class ClassWithNestedTestClass { - - @Nested - class InnerClass { - - @Test - void first() { - } - - @Test - void second() { - } - - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java deleted file mode 100644 index e814cee4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link IsTestFactoryMethod}. - * - * @since 5.0 - */ -class IsTestFactoryMethodTests { - - private static final Predicate isTestFactoryMethod = new IsTestFactoryMethod(); - - @Test - void factoryMethodReturningCollectionOfDynamicTests() { - assertThat(isTestFactoryMethod).accepts(method("dynamicTestsFactory")); - } - - @Test - void bogusFactoryMethodReturningVoid() { - assertThat(isTestFactoryMethod).rejects(method("bogusVoidFactory")); - } - - // TODO [#949] Enable test once IsTestFactoryMethod properly checks return type. - @Disabled("Disabled until IsTestFactoryMethod properly checks return type") - @Test - void bogusFactoryMethodReturningObject() { - assertThat(isTestFactoryMethod).rejects(method("bogusObjectFactory")); - } - - // TODO [#949] Enable test once IsTestFactoryMethod properly checks return type. - @Disabled("Disabled until IsTestFactoryMethod properly checks return type") - @Test - void bogusFactoryMethodReturningCollectionOfStrings() { - assertThat(isTestFactoryMethod).rejects(method("bogusStringsFactory")); - } - - private static Method method(String name) { - return ReflectionUtils.findMethod(ClassWithTestFactoryMethods.class, name).get(); - } - - private static class ClassWithTestFactoryMethods { - - @TestFactory - Collection dynamicTestsFactory() { - return new ArrayList<>(); - } - - @TestFactory - void bogusVoidFactory() { - } - - @TestFactory - Object bogusObjectFactory() { - return new Object(); - } - - @TestFactory - Collection bogusStringsFactory() { - return new ArrayList<>(); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java deleted file mode 100644 index f4d211f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.reflect.Method; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link IsTestMethod}. - * - * @since 5.0 - */ -class IsTestMethodTests { - - private static final Predicate isTestMethod = new IsTestMethod(); - - @Test - void publicTestMethod() { - Method method = method("publicTestMethod"); - // Ensure that somebody doesn't accidentally delete the public modifier again. - assertTrue(ReflectionUtils.isPublic(method)); - assertThat(isTestMethod).accepts(method); - } - - @Test - void publicTestMethodWithArgument() { - Method method = method("publicTestMethodWithArgument", TestInfo.class); - // Ensure that somebody doesn't accidentally delete the public modifier again. - assertTrue(ReflectionUtils.isPublic(method)); - assertThat(isTestMethod).accepts(method); - } - - @Test - void protectedTestMethod() { - assertThat(isTestMethod).accepts(method("protectedTestMethod")); - } - - @Test - void packageVisibleTestMethod() { - assertThat(isTestMethod).accepts(method("packageVisibleTestMethod")); - } - - @Test - void bogusAbstractTestMethod() { - assertThat(isTestMethod).rejects(abstractMethod("bogusAbstractTestMethod")); - } - - @Test - void bogusStaticTestMethod() { - assertThat(isTestMethod).rejects(method("bogusStaticTestMethod")); - } - - @Test - void bogusPrivateTestMethod() { - assertThat(isTestMethod).rejects(method("bogusPrivateTestMethod")); - } - - @Test - void bogusTestMethodReturningObject() { - assertThat(isTestMethod).rejects(method("bogusTestMethodReturningObject")); - } - - @Test - void bogusTestMethodReturningVoidReference() { - assertThat(isTestMethod).rejects(method("bogusTestMethodReturningVoidReference")); - } - - @Test - void bogusTestMethodReturningPrimitive() { - assertThat(isTestMethod).rejects(method("bogusTestMethodReturningPrimitive")); - } - - private static Method method(String name, Class... parameterTypes) { - return ReflectionUtils.findMethod(ClassWithTestMethods.class, name, parameterTypes).get(); - } - - private Method abstractMethod(String name) { - return ReflectionUtils.findMethod(AbstractClassWithAbstractTestMethod.class, name).get(); - } - - private static abstract class AbstractClassWithAbstractTestMethod { - - @Test - abstract void bogusAbstractTestMethod(); - - } - - private static class ClassWithTestMethods { - - @Test - static void bogusStaticTestMethod() { - } - - @Test - private void bogusPrivateTestMethod() { - } - - @Test - String bogusTestMethodReturningObject() { - return ""; - } - - @Test - Void bogusTestMethodReturningVoidReference() { - return null; - } - - @Test - int bogusTestMethodReturningPrimitive() { - return 0; - } - - @Test - public void publicTestMethod() { - } - - @Test - public void publicTestMethodWithArgument(TestInfo info) { - } - - @Test - protected void protectedTestMethod() { - } - - @Test - void packageVisibleTestMethod() { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java deleted file mode 100644 index a260e0d6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery.predicates; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestTemplate; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link IsTestTemplateMethod}. - * - * @since 5.0 - */ -class IsTestTemplateMethodTests { - - private static final IsTestTemplateMethod isTestTemplateMethod = new IsTestTemplateMethod(); - - @Test - void testTemplateMethodReturningVoid() { - assertThat(isTestTemplateMethod).accepts(method("templateReturningVoid")); - } - - @Test - void bogusTestTemplateMethodReturningObject() { - assertThat(isTestTemplateMethod).rejects(method("bogusTemplateReturningObject")); - } - - private static Method method(String name) { - return ReflectionUtils.findMethod(ClassWithTestTemplateMethods.class, name).get(); - } - - private static class ClassWithTestTemplateMethods { - - @TestTemplate - void templateReturningVoid() { - } - - @TestTemplate - String bogusTemplateReturningObject() { - return ""; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java deleted file mode 100644 index 78678233..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.ConfigurableParameterResolver; -import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.ConstructorInjectionTestCase; -import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.MethodSource; -import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.NumberParameterResolver; -import static org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.StringParameterResolver; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.9 - */ -abstract class AbstractExecutableInvokerTests { - - private static final String ENIGMA = "enigma"; - - protected final MethodSource instance = mock(); - protected Method method; - - protected final ExtensionContext extensionContext = mock(); - - private final JupiterConfiguration configuration = mock(); - - protected final MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( - configuration); - - @Test - void constructorInjection() { - register(new StringParameterResolver(), new NumberParameterResolver()); - - Class outerClass = ConstructorInjectionTestCase.class; - Constructor constructor = ReflectionUtils.getDeclaredConstructor(outerClass); - ConstructorInjectionTestCase outer = invokeConstructor(constructor, null); - - assertNotNull(outer); - assertEquals(ENIGMA, outer.str); - - Class innerClass = ConstructorInjectionTestCase.NestedTestCase.class; - Constructor innerConstructor = ReflectionUtils.getDeclaredConstructor( - innerClass); - ConstructorInjectionTestCase.NestedTestCase inner = invokeConstructor(innerConstructor, outer); - - assertNotNull(inner); - assertEquals(42, inner.num); - } - - @Test - void resolveArgumentsViaParameterResolver() { - testMethodWithASingleStringParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo("argument"); - - invokeMethod(); - - verify(instance).singleStringParameter("argument"); - } - - private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) { - register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> argument)); - } - - private void testMethodWithASingleStringParameter() { - this.method = ReflectionUtils.findMethod(this.instance.getClass(), "singleStringParameter", String.class).get(); - } - - private void register(ParameterResolver... resolvers) { - for (ParameterResolver resolver : resolvers) { - extensionRegistry.registerExtension(resolver, this); - } - } - - abstract void invokeMethod(); - - abstract T invokeConstructor(Constructor constructor, Object outerInstance); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java deleted file mode 100644 index ab54aade..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import java.lang.reflect.Constructor; - -/** - * Unit tests for {@link DefaultExecutableInvoker}. - * - * @since 5.9 - */ -class DefaultExecutableInvokerTests extends AbstractExecutableInvokerTests { - - @Override - void invokeMethod() { - newInvoker().invoke(this.method, this.instance); - } - - @Override - T invokeConstructor(Constructor constructor, Object outerInstance) { - return newInvoker().invoke(constructor, outerInstance); - } - - private DefaultExecutableInvoker newInvoker() { - return new DefaultExecutableInvoker(this.extensionContext, this.extensionRegistry); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java deleted file mode 100644 index d3881a3d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; - -class DefaultTestInstancesTests { - - @Test - void topLevelClass() { - DefaultTestInstances instances = DefaultTestInstances.of(this); - - assertThat(instances.getInnermostInstance()).isSameAs(this); - assertThat(instances.getAllInstances()).containsExactly(this); - assertThat(instances.getEnclosingInstances()).isEmpty(); - assertThat(instances.findInstance(Object.class)).contains(this); - assertThat(instances.findInstance(String.class)).isEmpty(); - } - - @Test - void nestedLevelClass() { - DefaultTestInstancesTests outermost = this; - Nested innermost = new Nested(); - DefaultTestInstances instances = DefaultTestInstances.of(DefaultTestInstances.of(outermost), innermost); - - assertThat(instances.getInnermostInstance()).isSameAs(innermost); - assertThat(instances.getAllInstances()).containsExactly(outermost, innermost); - assertThat(instances.getEnclosingInstances()).containsExactly(outermost); - assertThat(instances.findInstance(Object.class)).contains(innermost); - assertThat(instances.findInstance(Nested.class)).contains(innermost); - assertThat(instances.findInstance(DefaultTestInstancesTests.class)).contains(outermost); - assertThat(instances.findInstance(String.class)).isEmpty(); - } - - class Nested { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java deleted file mode 100644 index c03c6581..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; - -/** - * Integration tests for dynamic tests. - * - * @since 5.5 - */ -class DynamicTestIntegrationTests { - - private static final int TEN_MB = 10 * 1024 * 1024; - - /** - * Without the fix in {@code DynamicTestTestDescriptor}, setting the - * {@code -mx200m} VM argument will cause an {@link OutOfMemoryError} before - * the 200 limit is reached. - * - * @see Issue 1865 - */ - @TestFactory - Stream generateDynamicTestsThatReferenceLargeAmountsOfMemory() { - return Stream.generate(() -> new byte[TEN_MB])// - // The lambda Executable in the following line *must* reference - // the `bytes` array in order to hold onto the allocated memory. - .map(bytes -> dynamicTest("test", () -> assertNotNull(bytes)))// - .limit(200); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java deleted file mode 100644 index f09fb699..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; - -/** - * Concurrency tests for {@link NamespaceAwareStore} and {@link ExtensionValuesStore}. - * - * @since 5.0 - */ -class ExtensionContextStoreConcurrencyTests { - - private final AtomicInteger count = new AtomicInteger(); - - @Test - void concurrentAccessToDefaultStoreWithoutParentStore() { - // Run the actual test 100 times "for good measure". - IntStream.range(1, 100).forEach(i -> { - Store store = reset(); - // Simulate 100 extensions interacting concurrently with the Store. - IntStream.range(1, 100).parallel().forEach(j -> store.getOrComputeIfAbsent("key", this::newValue)); - assertEquals(1, count.get(), () -> "number of times newValue() was invoked in run #" + i); - }); - } - - private String newValue(String key) { - count.incrementAndGet(); - return "value"; - } - - private Store reset() { - count.set(0); - return new NamespaceAwareStore(new ExtensionValuesStore(null), Namespace.GLOBAL); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java deleted file mode 100644 index 16f40334..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.ExtensionContextException; - -/** - * Unit tests for {@link NamespaceAwareStore} and {@link ExtensionValuesStore}. - * - * @since 5.5 - * @see ExtensionContextStoreConcurrencyTests - * @see ExtensionValuesStoreTests - */ -class ExtensionContextStoreTests { - - private static final String KEY = "key"; - private static final String VALUE = "value"; - - private final ExtensionValuesStore parentStore = new ExtensionValuesStore(null); - private final ExtensionValuesStore localStore = new ExtensionValuesStore(parentStore); - private final Store store = new NamespaceAwareStore(localStore, Namespace.GLOBAL); - - @Test - void getOrDefaultWithNoValuePresent() { - assertThat(store.get(KEY)).isNull(); - - assertThat(store.getOrDefault(KEY, boolean.class, true)).isTrue(); - assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); - } - - @Test - void getOrDefaultRequestingIncompatibleType() { - localStore.put(Namespace.GLOBAL, KEY, VALUE); - assertThat(store.get(KEY)).isEqualTo(VALUE); - - Exception exception = assertThrows(ExtensionContextException.class, - () -> store.getOrDefault(KEY, boolean.class, true)); - assertThat(exception).hasMessageContaining("is not of required type"); - } - - @Test - void getOrDefaultWithValueInLocalStore() { - localStore.put(Namespace.GLOBAL, KEY, VALUE); - assertThat(store.get(KEY)).isEqualTo(VALUE); - - assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); - } - - @Test - void getOrDefaultWithValueInParentStore() { - parentStore.put(Namespace.GLOBAL, KEY, VALUE); - assertThat(store.get(KEY)).isEqualTo(VALUE); - - assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); - } - - @Test - void getOrComputeIfAbsentWithFailingCreator() { - var invocations = new AtomicInteger(); - - var e1 = assertThrows(RuntimeException.class, () -> store.getOrComputeIfAbsent(KEY, __ -> { - invocations.incrementAndGet(); - throw new RuntimeException(); - })); - var e2 = assertThrows(RuntimeException.class, () -> store.get(KEY)); - assertSame(e1, e2); - - assertDoesNotThrow(localStore::closeAllStoredCloseableValues); - assertThat(invocations).hasValue(1); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java deleted file mode 100644 index 04d6fc00..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionValuesStoreTests.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; - -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContextException; - -/** - * Unit tests for {@link ExtensionValuesStore}. - * - * @since 5.0 - * @see org.junit.jupiter.engine.descriptor.ExtensionContextTests - */ -public class ExtensionValuesStoreTests { - - private final Object key = "key"; - private final Object value = "value"; - - private final Namespace namespace = Namespace.create("ns"); - - private final ExtensionValuesStore grandParentStore = new ExtensionValuesStore(null); - private final ExtensionValuesStore parentStore = new ExtensionValuesStore(grandParentStore); - private final ExtensionValuesStore store = new ExtensionValuesStore(parentStore); - - @Nested - class StoringValuesTests { - - @Test - void getWithUnknownKeyReturnsNull() { - assertNull(store.get(namespace, "unknown key")); - } - - @Test - void putAndGetWithSameKey() { - store.put(namespace, key, value); - assertEquals(value, store.get(namespace, key)); - } - - @Test - void valueCanBeReplaced() { - store.put(namespace, key, value); - - Object newValue = new Object(); - store.put(namespace, key, newValue); - - assertEquals(newValue, store.get(namespace, key)); - } - - @Test - void valueIsComputedIfAbsent() { - assertNull(store.get(namespace, key)); - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> value)); - assertEquals(value, store.get(namespace, key)); - } - - @Test - void valueIsNotComputedIfPresentLocally() { - store.put(namespace, key, value); - - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); - assertEquals(value, store.get(namespace, key)); - } - - @Test - void valueIsNotComputedIfPresentInParent() { - parentStore.put(namespace, key, value); - - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, k -> "a different value")); - assertEquals(value, store.get(namespace, key)); - } - - @Test - void valueIsNotComputedIfPresentInGrandParent() { - grandParentStore.put(namespace, key, value); - - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, k -> "a different value")); - assertEquals(value, store.get(namespace, key)); - } - - @Test - void nullIsAValidValueToPut() { - store.put(namespace, key, null); - - assertNull(store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); - assertNull(store.get(namespace, key)); - } - - @Test - void keysCanBeRemoved() { - store.put(namespace, key, value); - assertEquals(value, store.remove(namespace, key)); - - assertNull(store.get(namespace, key)); - assertEquals("a different value", - store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); - } - - @Test - void sameKeyWithDifferentNamespaces() { - Object value1 = createObject("value1"); - Namespace namespace1 = Namespace.create("ns1"); - - Object value2 = createObject("value2"); - Namespace namespace2 = Namespace.create("ns2"); - - store.put(namespace1, key, value1); - store.put(namespace2, key, value2); - - assertEquals(value1, store.get(namespace1, key)); - assertEquals(value2, store.get(namespace2, key)); - } - - @Test - void valueIsComputedIfAbsentInDifferentNamespace() { - Namespace namespace1 = Namespace.create("ns1"); - Namespace namespace2 = Namespace.create("ns2"); - - assertEquals(value, store.getOrComputeIfAbsent(namespace1, key, innerKey -> value)); - assertEquals(value, store.get(namespace1, key)); - - assertNull(store.get(namespace2, key)); - } - - @Test - void keyIsOnlyRemovedInGivenNamespace() { - Namespace namespace1 = Namespace.create("ns1"); - Namespace namespace2 = Namespace.create("ns2"); - - Object value1 = createObject("value1"); - Object value2 = createObject("value2"); - - store.put(namespace1, key, value1); - store.put(namespace2, key, value2); - store.remove(namespace1, key); - - assertNull(store.get(namespace1, key)); - assertEquals(value2, store.get(namespace2, key)); - } - - @Test - void getWithTypeSafetyAndInvalidRequiredTypeThrowsException() { - Integer key = 42; - String value = "enigma"; - store.put(namespace, key, value); - - Exception exception = assertThrows(ExtensionContextException.class, - () -> store.get(namespace, key, Number.class)); - assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", - exception.getMessage()); - } - - @Test - void getWithTypeSafety() { - Integer key = 42; - String value = "enigma"; - store.put(namespace, key, value); - - // The fact that we can declare this as a String suffices for testing the required type. - String requiredTypeValue = store.get(namespace, key, String.class); - assertEquals(value, requiredTypeValue); - } - - @Test - void getWithTypeSafetyAndPrimitiveValueType() { - String key = "enigma"; - int value = 42; - store.put(namespace, key, value); - - // The fact that we can declare this as an int/Integer suffices for testing the required type. - int requiredInt = store.get(namespace, key, int.class); - Integer requiredInteger = store.get(namespace, key, Integer.class); - assertEquals(value, requiredInt); - assertEquals(value, requiredInteger.intValue()); - } - - @Test - void getNullValueWithTypeSafety() { - store.put(namespace, key, null); - - // The fact that we can declare this as a String suffices for testing the required type. - String requiredTypeValue = store.get(namespace, key, String.class); - assertNull(requiredTypeValue); - } - - @Test - void getOrComputeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { - String key = "pi"; - Float value = 3.14f; - - // Store a Float... - store.put(namespace, key, value); - - // But declare that our function creates a String... - Function defaultCreator = k -> "enigma"; - - Exception exception = assertThrows(ExtensionContextException.class, - () -> store.getOrComputeIfAbsent(namespace, key, defaultCreator, String.class)); - assertEquals("Object stored under key [pi] is not of required type [java.lang.String]", - exception.getMessage()); - } - - @Test - void getOrComputeIfAbsentWithTypeSafety() { - Integer key = 42; - String value = "enigma"; - - // The fact that we can declare this as a String suffices for testing the required type. - String computedValue = store.getOrComputeIfAbsent(namespace, key, k -> value, String.class); - assertEquals(value, computedValue); - } - - @Test - void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { - String key = "enigma"; - int value = 42; - - // The fact that we can declare this as an int/Integer suffices for testing the required type. - int computedInt = store.getOrComputeIfAbsent(namespace, key, k -> value, int.class); - Integer computedInteger = store.getOrComputeIfAbsent(namespace, key, k -> value, Integer.class); - assertEquals(value, computedInt); - assertEquals(value, computedInteger.intValue()); - } - - @Test - void removeWithTypeSafetyAndInvalidRequiredTypeThrowsException() { - Integer key = 42; - String value = "enigma"; - store.put(namespace, key, value); - - Exception exception = assertThrows(ExtensionContextException.class, - () -> store.remove(namespace, key, Number.class)); - assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", - exception.getMessage()); - } - - @Test - void removeWithTypeSafety() { - Integer key = 42; - String value = "enigma"; - store.put(namespace, key, value); - - // The fact that we can declare this as a String suffices for testing the required type. - String removedValue = store.remove(namespace, key, String.class); - assertEquals(value, removedValue); - assertNull(store.get(namespace, key)); - } - - @Test - void removeWithTypeSafetyAndPrimitiveValueType() { - String key = "enigma"; - int value = 42; - store.put(namespace, key, value); - - // The fact that we can declare this as an int suffices for testing the required type. - int requiredInt = store.remove(namespace, key, int.class); - assertEquals(value, requiredInt); - - store.put(namespace, key, value); - // The fact that we can declare this as an Integer suffices for testing the required type. - Integer requiredInteger = store.get(namespace, key, Integer.class); - assertEquals(value, requiredInteger.intValue()); - } - - @Test - void removeNullValueWithTypeSafety() { - Integer key = 42; - store.put(namespace, key, null); - - // The fact that we can declare this as a String suffices for testing the required type. - String removedValue = store.remove(namespace, key, String.class); - assertNull(removedValue); - assertNull(store.get(namespace, key)); - } - - @Test - void simulateRaceConditionInGetOrComputeIfAbsent() throws Exception { - int threads = 10; - AtomicInteger counter = new AtomicInteger(); - ExtensionValuesStore localStore = new ExtensionValuesStore(null); - - List values = executeConcurrently(threads, // - () -> localStore.getOrComputeIfAbsent(namespace, key, it -> counter.incrementAndGet())); - - assertEquals(1, counter.get()); - assertThat(values).hasSize(threads).containsOnly(1); - } - } - - @Nested - class InheritedValuesTests { - - @Test - void valueFromParentIsVisible() { - parentStore.put(namespace, key, value); - assertEquals(value, store.get(namespace, key)); - } - - @Test - void valueFromParentCanBeOverriddenInChild() { - parentStore.put(namespace, key, value); - - Object otherValue = new Object(); - store.put(namespace, key, otherValue); - assertEquals(otherValue, store.get(namespace, key)); - - assertEquals(value, parentStore.get(namespace, key)); - } - } - - @Nested - class CompositeNamespaceTests { - - @Test - void namespacesEqualForSamePartsSequence() { - Namespace ns1 = Namespace.create("part1", "part2"); - Namespace ns2 = Namespace.create("part1", "part2"); - - assertEquals(ns1, ns2); - } - - @Test - void orderOfNamespacePartsDoesMatter() { - Namespace ns1 = Namespace.create("part1", "part2"); - Namespace ns2 = Namespace.create("part2", "part1"); - - assertNotEquals(ns1, ns2); - } - - @Test - void additionNamespacePartMakesADifference() { - - Namespace ns1 = Namespace.create("part1", "part2"); - Namespace ns2 = Namespace.create("part1"); - Namespace ns3 = Namespace.create("part1", "part2"); - - Object value2 = createObject("value2"); - - parentStore.put(ns1, key, value); - parentStore.put(ns2, key, value2); - - assertEquals(value, store.get(ns1, key)); - assertEquals(value, store.get(ns3, key)); - assertEquals(value2, store.get(ns2, key)); - } - - } - - private Object createObject(final String display) { - return new Object() { - - @Override - public String toString() { - return display; - } - }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java deleted file mode 100644 index 54431766..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.util.Optional; - -import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; - -/** - * Unit tests for {@link InterceptingExecutableInvoker}. - * - * @since 5.0 - */ -class InterceptingExecutableInvokerTests extends AbstractExecutableInvokerTests { - - @Override - void invokeMethod() { - newInvoker().invoke(this.method, this.instance, this.extensionContext, this.extensionRegistry, - passthroughInterceptor()); - } - - @Override - T invokeConstructor(Constructor constructor, Object outerInstance) { - return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), extensionContext, extensionRegistry, - passthroughInterceptor()); - } - - private InterceptingExecutableInvoker newInvoker() { - return new InterceptingExecutableInvoker(); - } - - private static ReflectiveInterceptorCall passthroughInterceptor() { - return (interceptor, invocation, invocationContext, extensionContext) -> invocation.proceed(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java deleted file mode 100644 index d6e619d8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.withSettings; - -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.engine.EngineExecutionListener; - -/** - * Unit tests for {@link JupiterEngineExecutionContext}. - * - * @since 5.0 - */ -class JupiterEngineExecutionContextTests { - - private final JupiterConfiguration configuration = mock(); - - private final EngineExecutionListener engineExecutionListener = mock(); - - private final JupiterEngineExecutionContext originalContext = new JupiterEngineExecutionContext( - engineExecutionListener, configuration); - - @Test - void executionListenerIsHandedOnWhenContextIsExtended() { - assertSame(engineExecutionListener, originalContext.getExecutionListener()); - JupiterEngineExecutionContext newContext = originalContext.extend().build(); - assertSame(engineExecutionListener, newContext.getExecutionListener()); - } - - @Test - void extendWithAllAttributes() { - ExtensionContext extensionContext = mock(); - MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( - configuration); - TestInstancesProvider testInstancesProvider = mock(); - JupiterEngineExecutionContext newContext = originalContext.extend() // - .withExtensionContext(extensionContext) // - .withExtensionRegistry(extensionRegistry) // - .withTestInstancesProvider(testInstancesProvider) // - .build(); - - assertSame(extensionContext, newContext.getExtensionContext()); - assertSame(extensionRegistry, newContext.getExtensionRegistry()); - assertSame(testInstancesProvider, newContext.getTestInstancesProvider()); - } - - @Test - void canOverrideAttributeWhenContextIsExtended() { - ExtensionContext extensionContext = mock(); - MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( - configuration); - TestInstancesProvider testInstancesProvider = mock(); - ExtensionContext newExtensionContext = mock(); - - JupiterEngineExecutionContext newContext = originalContext.extend() // - .withExtensionContext(extensionContext) // - .withExtensionRegistry(extensionRegistry) // - .withTestInstancesProvider(testInstancesProvider) // - .build() // - .extend() // - .withExtensionContext(newExtensionContext) // - .build(); - - assertSame(newExtensionContext, newContext.getExtensionContext()); - assertSame(extensionRegistry, newContext.getExtensionRegistry()); - assertSame(testInstancesProvider, newContext.getTestInstancesProvider()); - } - - @Test - @TrackLogRecords - void closeAttemptExceptionWillBeThrownDownTheCallStack(LogRecordListener logRecordListener) throws Exception { - ExtensionContext failingExtensionContext = mock(ExtensionContext.class, - withSettings().extraInterfaces(AutoCloseable.class)); - Exception expectedException = new Exception("test message"); - doThrow(expectedException).when(((AutoCloseable) failingExtensionContext)).close(); - - JupiterEngineExecutionContext newContext = originalContext.extend() // - .withExtensionContext(failingExtensionContext) // - .build(); - - Exception actualException = assertThrows(Exception.class, newContext::close); - - assertSame(expectedException, actualException); - assertThat(logRecordListener.stream(JupiterEngineExecutionContext.class, Level.SEVERE)) // - .extracting(LogRecord::getMessage) // - .containsOnly("Caught exception while closing extension context: " + failingExtensionContext); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java deleted file mode 100644 index 1c371f6f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link ParameterResolutionUtils}. - * - * @since 5.9 - */ -class ParameterResolutionUtilsTests { - - private static final String ENIGMA = "enigma"; - - private final MethodSource instance = mock(); - private Method method; - - private final ExtensionContext extensionContext = mock(); - - private final JupiterConfiguration configuration = mock(); - - private final MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( - configuration); - - @Test - void resolveConstructorArguments() { - register(new StringParameterResolver()); - - Class topLevelClass = ConstructorInjectionTestCase.class; - Object[] arguments = resolveConstructorParameters(topLevelClass, null); - - assertThat(arguments).containsExactly(ENIGMA); - } - - @Test - void resolveNestedConstructorArguments() { - register(new NumberParameterResolver()); - - Class outerClass = ConstructorInjectionTestCase.class; - ConstructorInjectionTestCase outer = ReflectionUtils.newInstance(outerClass, "str"); - - Class innerClass = ConstructorInjectionTestCase.NestedTestCase.class; - Object[] arguments = resolveConstructorParameters(innerClass, outer); - - assertThat(arguments).containsExactly(outer, 42); - } - - @Test - void resolveConstructorArgumentsWithMissingResolver() { - Constructor constructor = ReflectionUtils.getDeclaredConstructor( - ConstructorInjectionTestCase.class); - - Exception exception = assertThrows(ParameterResolutionException.class, - () -> ParameterResolutionUtils.resolveParameters(constructor, Optional.empty(), Optional.empty(), - extensionContext, extensionRegistry)); - - assertThat(exception.getMessage())// - .contains("No ParameterResolver registered for parameter [java.lang.String")// - .contains("in constructor")// - .contains(ConstructorInjectionTestCase.class.getName()); - } - - @Test - void resolvingArgumentsForMethodsWithoutParameterDoesNotDependOnParameterResolvers() { - testMethodWithNoParameters(); - throwDuringParameterResolution(new RuntimeException("boom!")); - - Object[] arguments = resolveMethodParameters(); - - assertThat(arguments).isEmpty(); - } - - @Test - void resolveArgumentsViaParameterResolver() { - testMethodWithASingleStringParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo("argument"); - - Object[] arguments = resolveMethodParameters(); - - assertThat(arguments).containsExactly("argument"); - } - - @Test - void resolveMultipleArguments() { - testMethodWith("multipleParameters", String.class, Integer.class, Double.class); - register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> { - switch (parameterContext.getIndex()) { - case 0: - return "0"; - case 1: - return 1; - default: - return 2.0; - } - })); - - Object[] arguments = resolveMethodParameters(); - - assertThat(arguments).containsExactly("0", 1, 2.0); - } - - @Test - void onlyConsiderParameterResolversThatSupportAParticularParameter() { - testMethodWithASingleStringParameter(); - thereIsAParameterResolverThatDoesNotSupportThisParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo("something"); - - Object[] arguments = resolveMethodParameters(); - - assertThat(arguments).containsExactly("something"); - } - - @Test - void passContextInformationToParameterResolverMethods() { - anyTestMethodWithAtLeastOneParameter(); - ArgumentRecordingParameterResolver extension = new ArgumentRecordingParameterResolver(); - register(extension); - - resolveMethodParameters(); - - assertSame(extensionContext, extension.supportsArguments.extensionContext); - assertEquals(0, extension.supportsArguments.parameterContext.getIndex()); - assertSame(instance, extension.supportsArguments.parameterContext.getTarget().get()); - assertSame(extensionContext, extension.resolveArguments.extensionContext); - assertEquals(0, extension.resolveArguments.parameterContext.getIndex()); - assertSame(instance, extension.resolveArguments.parameterContext.getTarget().get()); - assertThat(extension.resolveArguments.parameterContext.toString())// - .contains("parameter", String.class.getTypeName(), "index", "0", "target", "Mock"); - } - - @Test - void resolvingArgumentsForMethodsWithPrimitiveTypesIsSupported() { - testMethodWithASinglePrimitiveIntParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo(42); - - Object[] arguments = resolveMethodParameters(); - - assertThat(arguments).containsExactly(42); - } - - @Test - void nullIsAViableArgumentIfAReferenceTypeParameterIsExpected() { - testMethodWithASingleStringParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo(null); - - Object[] arguments = resolveMethodParameters(); - - assertThat(arguments).hasSize(1); - assertNull(arguments[0]); - } - - @Test - void reportThatNullIsNotAViableArgumentIfAPrimitiveTypeIsExpected() { - testMethodWithASinglePrimitiveIntParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo(null); - - ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, - this::resolveMethodParameters); - - // @formatter:off - assertThat(caught.getMessage()) - .contains("in method") - .contains("resolved a null value for parameter [int") - .contains("but a primitive of type [int] is required."); - // @formatter:on - } - - @Test - void reportIfThereIsNoParameterResolverThatSupportsTheParameter() { - testMethodWithASingleStringParameter(); - - ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, - this::resolveMethodParameters); - - assertThat(caught.getMessage()).contains("parameter [java.lang.String").contains("in method"); - } - - @Test - void reportIfThereAreMultipleParameterResolversThatSupportTheParameter() { - testMethodWithASingleStringParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo("one"); - thereIsAParameterResolverThatResolvesTheParameterTo("two"); - - ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, - this::resolveMethodParameters); - - String className = Pattern.quote(ConfigurableParameterResolver.class.getName()); - - // @formatter:off - assertThat(caught.getMessage()) - .matches("Discovered multiple competing ParameterResolvers for parameter \\[java.lang.String .+?\\] " + - "in method .+: " + className + "@.+, " + className + "@.+"); - // @formatter:on - } - - @Test - void reportTypeMismatchBetweenParameterAndResolvedParameter() { - testMethodWithASingleStringParameter(); - thereIsAParameterResolverThatResolvesTheParameterTo(BigDecimal.ONE); - - ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, - this::resolveMethodParameters); - - // @formatter:off - assertThat(caught.getMessage()) - .contains("resolved a value of type [java.math.BigDecimal] for parameter [java.lang.String") - .contains("in method") - .contains("but a value assignment compatible with [java.lang.String] is required."); - // @formatter:on - } - - @Test - void wrapAllExceptionsThrownDuringParameterResolutionIntoAParameterResolutionException() { - anyTestMethodWithAtLeastOneParameter(); - IllegalArgumentException cause = anyExceptionButParameterResolutionException(); - throwDuringParameterResolution(cause); - - ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, - this::resolveMethodParameters); - - assertSame(cause, caught.getCause(), () -> "cause should be present"); - assertThat(caught.getMessage())// - .matches("^Failed to resolve parameter \\[java.lang.String .+?\\] in method \\[.+?\\]$"); - } - - @Test - void exceptionMessageContainsMessageFromExceptionThrownDuringParameterResolution() { - anyTestMethodWithAtLeastOneParameter(); - RuntimeException cause = new RuntimeException("boom!"); - throwDuringParameterResolution(cause); - - ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, - this::resolveMethodParameters); - - assertSame(cause, caught.getCause(), () -> "cause should be present"); - assertThat(caught.getMessage())// - .matches("^Failed to resolve parameter \\[java.lang.String .+?\\] in method \\[.+?\\]: boom!$"); - } - - @Test - void doNotWrapThrownExceptionIfItIsAlreadyAParameterResolutionException() { - anyTestMethodWithAtLeastOneParameter(); - ParameterResolutionException cause = new ParameterResolutionException("custom message"); - throwDuringParameterResolution(cause); - - ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, - this::resolveMethodParameters); - - assertSame(cause, caught); - } - - private IllegalArgumentException anyExceptionButParameterResolutionException() { - return new IllegalArgumentException(); - } - - private void throwDuringParameterResolution(RuntimeException parameterResolutionException) { - register(ConfigurableParameterResolver.onAnyCallThrow(parameterResolutionException)); - } - - private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) { - register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> argument)); - } - - private void thereIsAParameterResolverThatDoesNotSupportThisParameter() { - register(ConfigurableParameterResolver.withoutSupport()); - } - - private void anyTestMethodWithAtLeastOneParameter() { - testMethodWithASingleStringParameter(); - } - - private void testMethodWithNoParameters() { - testMethodWith("noParameter"); - } - - private void testMethodWithASingleStringParameter() { - testMethodWith("singleStringParameter", String.class); - } - - private void testMethodWithASinglePrimitiveIntParameter() { - testMethodWith("primitiveParameterInt", int.class); - } - - private void testMethodWith(String methodName, Class... parameterTypes) { - this.method = ReflectionUtils.findMethod(this.instance.getClass(), methodName, parameterTypes).get(); - } - - private void register(ParameterResolver... resolvers) { - for (ParameterResolver resolver : resolvers) { - extensionRegistry.registerExtension(resolver, this); - } - } - - private Object[] resolveConstructorParameters(Class clazz, Object outerInstance) { - Constructor constructor = ReflectionUtils.getDeclaredConstructor(clazz); - return ParameterResolutionUtils.resolveParameters(constructor, Optional.empty(), - Optional.ofNullable(outerInstance), extensionContext, extensionRegistry); - } - - private Object[] resolveMethodParameters() { - return ParameterResolutionUtils.resolveParameters(this.method, Optional.of(this.instance), - this.extensionContext, this.extensionRegistry); - } - - // ------------------------------------------------------------------------- - - static class ArgumentRecordingParameterResolver implements ParameterResolver { - - ArgumentRecordingParameterResolver.Arguments supportsArguments; - ArgumentRecordingParameterResolver.Arguments resolveArguments; - - static class Arguments { - - final ParameterContext parameterContext; - final ExtensionContext extensionContext; - - Arguments(ParameterContext parameterContext, ExtensionContext extensionContext) { - this.parameterContext = parameterContext; - this.extensionContext = extensionContext; - } - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - supportsArguments = new ArgumentRecordingParameterResolver.Arguments(parameterContext, extensionContext); - return true; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - resolveArguments = new ArgumentRecordingParameterResolver.Arguments(parameterContext, extensionContext); - return null; - } - } - - static class ConfigurableParameterResolver implements ParameterResolver { - - static ParameterResolver onAnyCallThrow(RuntimeException runtimeException) { - return new ConfigurableParameterResolver(parameterContext -> { - throw runtimeException; - }, parameterContext -> { - throw runtimeException; - }); - } - - static ParameterResolver supportsAndResolvesTo(Function resolve) { - return new ConfigurableParameterResolver(parameterContext -> true, resolve); - } - - static ParameterResolver withoutSupport() { - return new ConfigurableParameterResolver(parameterContext -> false, parameter -> { - throw new UnsupportedOperationException(); - }); - } - - private final Predicate supports; - private final Function resolve; - - private ConfigurableParameterResolver(Predicate supports, - Function resolve) { - this.supports = supports; - this.resolve = resolve; - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return supports.test(parameterContext); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return resolve.apply(parameterContext); - } - } - - @SuppressWarnings("unused") - interface MethodSource { - - void noParameter(); - - void singleStringParameter(String parameter); - - void primitiveParameterInt(int parameter); - - void multipleParameters(String first, Integer second, Double third); - } - - static class StringParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == String.class; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return ENIGMA; - } - } - - static class NumberParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == Number.class; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return 42; - } - } - - static class ConstructorInjectionTestCase { - - final String str; - - @SuppressWarnings("unused") - ConstructorInjectionTestCase(String str) { - this.str = str; - } - - class NestedTestCase { - - final Number num; - - @SuppressWarnings("unused") - NestedTestCase(Number num) { - this.num = num; - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java deleted file mode 100644 index a5184a84..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.execution.injection.sample.PrimitiveArrayParameterResolver; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Event; - -/** - * Integration tests for {@link UniqueId#parse(String)} for methods - * with array type parameters. - * - * @see #810 - * @see org.junit.platform.engine.UniqueIdTests - * - * @since 5.0 - */ -class UniqueIdParsingForArrayParameterIntegrationTests extends AbstractJupiterTestEngineTests { - - @Test - void executeTestsForPrimitiveArrayMethodInjectionCases() { - EngineExecutionResults executionResults = executeTestsForClass(PrimitiveArrayMethodInjectionTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - UniqueId uniqueId = executionResults.allEvents() - .map(Event::getTestDescriptor) - .distinct() - .skip(2) - .map(TestDescriptor::getUniqueId) - .findFirst() - .orElseThrow(AssertionError::new); - // @formatter:on - - assertThat(UniqueId.parse(uniqueId.toString())).isEqualTo(uniqueId); - } - - @ExtendWith(PrimitiveArrayParameterResolver.class) - static class PrimitiveArrayMethodInjectionTestCase { - - @Test - void primitiveArray(int... ints) { - assertArrayEquals(new int[] { 1, 2, 3 }, ints); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java deleted file mode 100644 index 01d89a57..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @since 5.0 - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface CustomAnnotation { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java deleted file mode 100644 index b24aad8b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.0 - */ -public class CustomAnnotationParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - // We invoke parameterContext.isAnnotated() instead of parameterContext.getParameter().isAnnotationPresent() - // in order to verify support for the convenience method in the ParameterContext API. - return parameterContext.isAnnotated(CustomAnnotation.class); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return ReflectionUtils.newInstance(parameterContext.getParameter().getType()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java deleted file mode 100644 index e9df48a0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import java.util.Date; - -/** - * @since 5.0 - */ -public class CustomType { - - private final Date date = new Date(); - - @Override - public String toString() { - return "CustomType: " + this.date; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java deleted file mode 100644 index 29078266..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * @since 5.0 - */ -public class CustomTypeParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType().equals(CustomType.class); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return new CustomType(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java deleted file mode 100644 index 3f4ee2c5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * Example {@link ParameterResolver} that always resolves a {@link Double} - * parameter to {@code 42.0}. - * - * @since 5.0 - */ -public class DoubleParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == Double.class; - } - - @Override - public Double resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return 42.0; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java deleted file mode 100644 index 9b796003..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * Example {@link ParameterResolver} that always resolves a {@link Long} - * parameter to {@code 42}. - * - *

This resolver also attempts to support generic parameter type - * declarations if the generic type is defined at the class level in a - * superclass or interface (extended or implemented by the test class) and - * is assignable from {@link Long}. - * - * @since 5.0 - */ -public class LongParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - // Exact match? - if (parameterContext.getParameter().getType() == Long.class) { - return true; - } - - Type typeInMethod = parameterContext.getParameter().getParameterizedType(); - - // Type variables in parameterized class - for (TypeVariable typeVariable : parameterContext.getDeclaringExecutable().getDeclaringClass().getTypeParameters()) { - boolean namesMatch = typeInMethod.getTypeName().equals(typeVariable.getName()); - boolean typesAreCompatible = typeVariable.getBounds().length == 1 && // - typeVariable.getBounds()[0] instanceof Class && // - ((Class) typeVariable.getBounds()[0]).isAssignableFrom(Long.class); - - if (namesMatch && typesAreCompatible) { - return true; - } - } - - return false; - - } - - @Override - public Long resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return 42L; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java deleted file mode 100644 index ce4f0f6f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; - -/** - * @since 5.6 - */ -public class MapOfListsTypeBasedParameterResolver extends TypeBasedParameterResolver>> { - - @Override - public Map> resolveParameter(ParameterContext parameterContext, - ExtensionContext extensionContext) { - - return Map.of("ids", List.of(1, 42)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java deleted file mode 100644 index d8f94f09..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Map; -import java.util.TreeMap; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * Example {@link ParameterResolver} that resolves {@code Map} types. - * - * @since 5.0 - */ -public class MapOfStringsParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - Type type = parameterContext.getParameter().getParameterizedType(); - if (!(type instanceof ParameterizedType)) { - return false; - } - Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); - if (actualTypeArguments.length != 2) { - return false; - } - return actualTypeArguments[0] == String.class && actualTypeArguments[1] == String.class; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - Map map = new TreeMap<>(); - map.put("key", "value"); - return map; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java deleted file mode 100644 index c44c9dd2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * Example {@link ParameterResolver} that always resolves an - * {@link Integer} or {@code int} parameter to a {@code null} value. - * - * @since 5.0 - */ -public class NullIntegerParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return Integer.class == parameterContext.getParameter().getType() - || int.class == parameterContext.getParameter().getType(); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return null; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java deleted file mode 100644 index f1c826ae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * This is a non-realistic {@link ParameterResolver} that claims to - * resolve any {@link Number}, when in fact it always resolves an {@link Integer}. - * - *

This may appear nonsensical; however, there are use cases for which a - * resolver may think that it can support a particular parameter only to - * discover later that the actual resolved value is not assignment compatible. - * - *

For example, consider the case with Spring: the {@code SpringExtension} can - * theoretically resolve any type of {@code ApplicationContext}, but if the - * required parameter type is {@code WebApplicationContext} and the user - * neglects to annotate the test class with {@code @WebAppConfiguration} then - * the {@code ApplicationContext} loaded by Spring's testing support will in - * fact be an {@code ApplicationContext} but not a {@code WebApplicationContext}. - * Since Spring does not determine this in advance, such a scenario would lead to - * an {@link IllegalArgumentException} with the message "argument type mismatch" - * when JUnit attempts to invoke the test method. - * - * @since 5.0 - */ -public class NumberParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return Number.class.isAssignableFrom(parameterContext.getParameter().getType()); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return 42; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java deleted file mode 100644 index 2c6ce89a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * Example {@link ParameterResolver} that resolves arrays of primitive integers. - * - * @since 5.0 - */ -public class PrimitiveArrayParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return int[].class == parameterContext.getParameter().getType(); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return new int[] { 1, 2, 3 }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java deleted file mode 100644 index 9893412b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.execution.injection.sample; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * Example {@link ParameterResolver} that resolves primitive integers. - * - * @since 5.0 - */ -public class PrimitiveIntegerParameterResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return int.class == parameterContext.getParameter().getType(); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return 42; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java deleted file mode 100644 index 57a128e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; - -/** - * Integration tests that verify support for {@link BeforeAll}, {@link AfterAll}, - * {@link BeforeAllCallback}, and {@link AfterAllCallback} in the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class BeforeAndAfterAllTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - private static Optional actualExceptionInAfterAllCallback; - - @Test - void beforeAllAndAfterAllCallbacks() { - // @formatter:off - assertBeforeAllAndAfterAllCallbacks(TopLevelTestCase.class, - "fooBeforeAllCallback", - "barBeforeAllCallback", - "beforeAllMethod-1", - "test-1", - "afterAllMethod-1", - "barAfterAllCallback", - "fooAfterAllCallback" - ); - // @formatter:on - - assertThat(actualExceptionInAfterAllCallback).isEmpty(); - } - - @Test - void beforeAllAndAfterAllCallbacksInSubclass() { - // @formatter:off - assertBeforeAllAndAfterAllCallbacks(SecondLevelTestCase.class, - "fooBeforeAllCallback", - "barBeforeAllCallback", - "bazBeforeAllCallback", - "beforeAllMethod-1", - "beforeAllMethod-2", - "test-2", - "afterAllMethod-2", - "afterAllMethod-1", - "bazAfterAllCallback", - "barAfterAllCallback", - "fooAfterAllCallback" - ); - // @formatter:on - - assertThat(actualExceptionInAfterAllCallback).isEmpty(); - } - - @Test - void beforeAllAndAfterAllCallbacksInSubSubclass() { - // @formatter:off - assertBeforeAllAndAfterAllCallbacks(ThirdLevelTestCase.class, - "fooBeforeAllCallback", - "barBeforeAllCallback", - "bazBeforeAllCallback", - "quuxBeforeAllCallback", - "beforeAllMethod-1", - "beforeAllMethod-2", - "beforeAllMethod-3", - "test-3", - "afterAllMethod-3", - "afterAllMethod-2", - "afterAllMethod-1", - "quuxAfterAllCallback", - "bazAfterAllCallback", - "barAfterAllCallback", - "fooAfterAllCallback" - ); - // @formatter:on - - assertThat(actualExceptionInAfterAllCallback).isEmpty(); - } - - @Test - void beforeAllAndAfterAllCallbacksInSubSubclassWithStaticMethodHiding() { - // @formatter:off - assertBeforeAllAndAfterAllCallbacks(ThirdLevelStaticHidingTestCase.class, - "fooBeforeAllCallback", - "barBeforeAllCallback", - "bazBeforeAllCallback", - "quuxBeforeAllCallback", - "beforeAllMethod-1-hidden", - "beforeAllMethod-2-hidden", - "beforeAllMethod-3", - "test-3", - // The @AfterAll methods are executed as 1/2/3 due to - // the "stable" method sort order on the Platform. - "afterAllMethod-1-hidden", - "afterAllMethod-2-hidden", - "afterAllMethod-3", - "quuxAfterAllCallback", - "bazAfterAllCallback", - "barAfterAllCallback", - "fooAfterAllCallback" - ); - // @formatter:on - - assertThat(actualExceptionInAfterAllCallback).isEmpty(); - } - - @Test - void beforeAllMethodThrowsAnException() { - // @formatter:off - assertBeforeAllAndAfterAllCallbacks(ExceptionInBeforeAllMethodTestCase.class, 0, 0, - "fooBeforeAllCallback", - "beforeAllMethod", // throws an exception. - // test should not get invoked. - "afterAllMethod", - "fooAfterAllCallback" - ); - // @formatter:on - - assertThat(actualExceptionInAfterAllCallback).containsInstanceOf(EnigmaException.class); - } - - @Test - void beforeAllCallbackThrowsAnException() { - // @formatter:off - assertBeforeAllAndAfterAllCallbacks(ExceptionInBeforeAllCallbackTestCase.class, 0, 0, - "fooBeforeAllCallback", - "exceptionThrowingBeforeAllCallback", // throws an exception. - // beforeAllMethod should not get invoked. - // test should not get invoked. - // afterAllMethod should not get invoked. - "fooAfterAllCallback" - ); - // @formatter:on - - assertThat(actualExceptionInAfterAllCallback).containsInstanceOf(EnigmaException.class); - } - - private void assertBeforeAllAndAfterAllCallbacks(Class testClass, String... expectedCalls) { - assertBeforeAllAndAfterAllCallbacks(testClass, 1, 1, expectedCalls); - } - - private void assertBeforeAllAndAfterAllCallbacks(Class testClass, int testsStarted, int testsSuccessful, - String... expectedCalls) { - - callSequence.clear(); - - executeTestsForClass(testClass).testEvents()// - .assertStatistics(stats -> stats.started(testsStarted).succeeded(testsSuccessful)); - - assertEquals(asList(expectedCalls), callSequence, () -> "wrong call sequence for " + testClass.getName()); - } - - // ------------------------------------------------------------------------- - - // Must NOT be private; otherwise, the @Test method gets discovered but never executed. - @ExtendWith({ FooClassLevelCallbacks.class, BarClassLevelCallbacks.class }) - static class TopLevelTestCase { - - @BeforeAll - static void beforeAll1() { - callSequence.add("beforeAllMethod-1"); - } - - @AfterAll - static void afterAll1() { - callSequence.add("afterAllMethod-1"); - } - - @Test - void test() { - callSequence.add("test-1"); - } - } - - // Must NOT be private; otherwise, the @Test method gets discovered but never executed. - @ExtendWith(BazClassLevelCallbacks.class) - static class SecondLevelTestCase extends TopLevelTestCase { - - @BeforeAll - static void beforeAll2() { - callSequence.add("beforeAllMethod-2"); - } - - @AfterAll - static void afterAll2() { - callSequence.add("afterAllMethod-2"); - } - - @Test - @Override - void test() { - callSequence.add("test-2"); - } - } - - @ExtendWith(QuuxClassLevelCallbacks.class) - static class ThirdLevelTestCase extends SecondLevelTestCase { - - @BeforeAll - static void beforeAll3() { - callSequence.add("beforeAllMethod-3"); - } - - @AfterAll - static void afterAll3() { - callSequence.add("afterAllMethod-3"); - } - - @Test - @Override - void test() { - callSequence.add("test-3"); - } - } - - @ExtendWith(QuuxClassLevelCallbacks.class) - static class ThirdLevelStaticHidingTestCase extends SecondLevelTestCase { - - @BeforeAll - static void beforeAll1() { - callSequence.add("beforeAllMethod-1-hidden"); - } - - @BeforeAll - static void beforeAll2() { - callSequence.add("beforeAllMethod-2-hidden"); - } - - @BeforeAll - static void beforeAll3() { - callSequence.add("beforeAllMethod-3"); - } - - @AfterAll - static void afterAll1() { - callSequence.add("afterAllMethod-1-hidden"); - } - - @AfterAll - static void afterAll2() { - callSequence.add("afterAllMethod-2-hidden"); - } - - @AfterAll - static void afterAll3() { - callSequence.add("afterAllMethod-3"); - } - - @Test - @Override - void test() { - callSequence.add("test-3"); - } - } - - @ExtendWith(FooClassLevelCallbacks.class) - static class ExceptionInBeforeAllMethodTestCase { - - @BeforeAll - static void beforeAll() { - callSequence.add("beforeAllMethod"); - throw new EnigmaException("@BeforeAll"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterAll - static void afterAll() { - callSequence.add("afterAllMethod"); - } - } - - @ExtendWith({ FooClassLevelCallbacks.class, ExceptionThrowingBeforeAllCallback.class }) - static class ExceptionInBeforeAllCallbackTestCase { - - @BeforeAll - static void beforeAll() { - callSequence.add("beforeAllMethod"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterAll - static void afterAll() { - callSequence.add("afterAllMethod"); - } - } - - // ------------------------------------------------------------------------- - - static class FooClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { - - @Override - public void beforeAll(ExtensionContext context) { - callSequence.add("fooBeforeAllCallback"); - } - - @Override - public void afterAll(ExtensionContext context) { - callSequence.add("fooAfterAllCallback"); - actualExceptionInAfterAllCallback = context.getExecutionException(); - } - } - - static class BarClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { - - @Override - public void beforeAll(ExtensionContext context) { - callSequence.add("barBeforeAllCallback"); - } - - @Override - public void afterAll(ExtensionContext context) { - callSequence.add("barAfterAllCallback"); - } - } - - static class BazClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { - - @Override - public void beforeAll(ExtensionContext context) { - callSequence.add("bazBeforeAllCallback"); - } - - @Override - public void afterAll(ExtensionContext context) { - callSequence.add("bazAfterAllCallback"); - } - } - - static class QuuxClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { - - @Override - public void beforeAll(ExtensionContext context) { - callSequence.add("quuxBeforeAllCallback"); - } - - @Override - public void afterAll(ExtensionContext context) { - callSequence.add("quuxAfterAllCallback"); - } - } - - static class ExceptionThrowingBeforeAllCallback implements BeforeAllCallback { - - @Override - public void beforeAll(ExtensionContext context) { - callSequence.add("exceptionThrowingBeforeAllCallback"); - throw new EnigmaException("BeforeAllCallback"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java deleted file mode 100644 index 93f03748..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java +++ /dev/null @@ -1,524 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests that verify support for {@link BeforeEach}, {@link AfterEach}, - * {@link BeforeEachCallback}, and {@link AfterEachCallback} in the {@link JupiterTestEngine}. - * - * @since 5.0 - * @see BeforeAndAfterTestExecutionCallbackTests - */ -class BeforeAndAfterEachTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - private static final List beforeEachMethodCallSequence = new ArrayList<>(); - - private static Optional actualExceptionInAfterEachCallback; - - @BeforeEach - void resetCallSequence() { - callSequence.clear(); - beforeEachMethodCallSequence.clear(); - actualExceptionInAfterEachCallback = null; - } - - @Test - void beforeEachAndAfterEachCallbacks() { - Events testEvents = executeTestsForClass(OuterTestCase.class).testEvents(); - - assertEquals(2, testEvents.started().count(), "# tests started"); - assertEquals(2, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(0, testEvents.failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - - // OuterTestCase - "fooBeforeEachCallback", - "barBeforeEachCallback", - "beforeEachMethod", - "testOuter", - "afterEachMethod", - "barAfterEachCallback", - "fooAfterEachCallback", - - // InnerTestCase - "fooBeforeEachCallback", - "barBeforeEachCallback", - "fizzBeforeEachCallback", - "beforeEachMethod", - "beforeEachInnerMethod", - "testInner", - "afterEachInnerMethod", - "afterEachMethod", - "fizzAfterEachCallback", - "barAfterEachCallback", - "fooAfterEachCallback" - - ), callSequence, "wrong call sequence"); - // @formatter:on - } - - @Test - void beforeEachAndAfterEachCallbacksDeclaredOnSuperclassAndSubclass() { - Events testEvents = executeTestsForClass(ChildTestCase.class).testEvents(); - - assertEquals(1, testEvents.started().count(), "# tests started"); - assertEquals(1, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(0, testEvents.failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "fooBeforeEachCallback", - "barBeforeEachCallback", - "testChild", - "barAfterEachCallback", - "fooAfterEachCallback" - ), callSequence, "wrong call sequence"); - // @formatter:on - } - - @Test - void beforeEachAndAfterEachCallbacksDeclaredOnInterfaceAndClass() { - Events testEvents = executeTestsForClass(TestInterfaceTestCase.class).testEvents(); - - assertEquals(2, testEvents.started().count(), "# tests started"); - assertEquals(2, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(0, testEvents.failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - - // Test Interface - "fooBeforeEachCallback", - "barBeforeEachCallback", - "defaultTestMethod", - "barAfterEachCallback", - "fooAfterEachCallback", - - // Test Class - "fooBeforeEachCallback", - "barBeforeEachCallback", - "localTestMethod", - "barAfterEachCallback", - "fooAfterEachCallback" - - ), callSequence, "wrong call sequence"); - // @formatter:on - } - - @Test - void beforeEachCallbackThrowsAnException() { - Events testEvents = executeTestsForClass(ExceptionInBeforeEachCallbackTestCase.class).testEvents(); - - assertEquals(1, testEvents.started().count(), "# tests started"); - assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(1, testEvents.failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "fooBeforeEachCallback", - "exceptionThrowingBeforeEachCallback", // throws an exception. - // barBeforeEachCallback should not get invoked. - // beforeEachMethod should not get invoked. - // test should not get invoked. - // afterEachMethod should not get invoked. - "barAfterEachCallback", - "fooAfterEachCallback" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); - } - - @Test - void afterEachCallbackThrowsAnException() { - Events testEvents = executeTestsForClass(ExceptionInAfterEachCallbackTestCase.class).testEvents(); - - assertEquals(1, testEvents.started().count(), "# tests started"); - assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(1, testEvents.failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "fooBeforeEachCallback", - "barBeforeEachCallback", - "beforeEachMethod", - "test", - "afterEachMethod", - "barAfterEachCallback", - "exceptionThrowingAfterEachCallback", // throws an exception. - "fooAfterEachCallback" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); - } - - @Test - void beforeEachMethodThrowsAnException() { - Events testEvents = executeTestsForClass(ExceptionInBeforeEachMethodTestCase.class).testEvents(); - - assertEquals(1, testEvents.started().count(), "# tests started"); - assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(1, testEvents.failed().count(), "# tests failed"); - - // Since the JVM does not guarantee the order in which methods are - // returned via reflection (and since JUnit Jupiter does not yet - // support ordering of @BeforeEach methods), we have to figure out - // which @BeforeEach method got executed first in order to determine - // the expected call sequence. - - // @formatter:off - List list1 = asList( - "fooBeforeEachCallback", - "beforeEachMethod1", // throws an exception. - // "beforeEachMethod2" should not get invoked - // test should not get invoked. - "afterEachMethod", - "fooAfterEachCallback" - ); - List list2 = asList( - "fooBeforeEachCallback", - "beforeEachMethod2", - "beforeEachMethod1", // throws an exception. - // test should not get invoked. - "afterEachMethod", - "fooAfterEachCallback" - ); - // @formatter:on - - List expected = beforeEachMethodCallSequence.get(0).equals("beforeEachMethod1") ? list1 : list2; - - assertEquals(expected, callSequence, "wrong call sequence"); - - assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); - } - - @Test - void afterEachMethodThrowsAnException() { - Events testEvents = executeTestsForClass(ExceptionInAfterEachMethodTestCase.class).testEvents(); - - assertEquals(1, testEvents.started().count(), "# tests started"); - assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(1, testEvents.failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "fooBeforeEachCallback", - "beforeEachMethod", - "test", - "afterEachMethod", // throws an exception. - "fooAfterEachCallback" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); - } - - @Test - void testMethodThrowsAnException() { - Events testEvents = executeTestsForClass(ExceptionInTestMethodTestCase.class).testEvents(); - - assertEquals(1, testEvents.started().count(), "# tests started"); - assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); - assertEquals(0, testEvents.skipped().count(), "# tests skipped"); - assertEquals(0, testEvents.aborted().count(), "# tests aborted"); - assertEquals(1, testEvents.failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "fooBeforeEachCallback", - "beforeEachMethod", - "test", // throws an exception. - "afterEachMethod", - "fooAfterEachCallback" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); - } - - // ------------------------------------------------------------------------- - - @ExtendWith(FooMethodLevelCallbacks.class) - static class ParentTestCase { - } - - @ExtendWith(BarMethodLevelCallbacks.class) - static class ChildTestCase extends ParentTestCase { - - @Test - void test() { - callSequence.add("testChild"); - } - } - - @ExtendWith(FooMethodLevelCallbacks.class) - private interface TestInterface { - - @Test - default void defaultTest() { - callSequence.add("defaultTestMethod"); - } - } - - @ExtendWith(BarMethodLevelCallbacks.class) - static class TestInterfaceTestCase implements TestInterface { - - @Test - void localTest() { - callSequence.add("localTestMethod"); - } - } - - @ExtendWith({ FooMethodLevelCallbacks.class, BarMethodLevelCallbacks.class }) - static class OuterTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void testOuter() { - callSequence.add("testOuter"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - - @Nested - @ExtendWith(FizzMethodLevelCallbacks.class) - class InnerTestCase { - - @BeforeEach - void beforeEachInnerMethod() { - callSequence.add("beforeEachInnerMethod"); - } - - @Test - void testInner() { - callSequence.add("testInner"); - } - - @AfterEach - void afterEachInnerMethod() { - callSequence.add("afterEachInnerMethod"); - } - } - } - - @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingBeforeEachCallback.class, - BarMethodLevelCallbacks.class }) - static class ExceptionInBeforeEachCallbackTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingAfterEachCallback.class, - BarMethodLevelCallbacks.class }) - static class ExceptionInAfterEachCallbackTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - @ExtendWith(FooMethodLevelCallbacks.class) - static class ExceptionInBeforeEachMethodTestCase { - - @BeforeEach - void beforeEach1() { - beforeEachMethodCallSequence.add("beforeEachMethod1"); - callSequence.add("beforeEachMethod1"); - throw new EnigmaException("@BeforeEach"); - } - - @BeforeEach - void beforeEach2() { - beforeEachMethodCallSequence.add("beforeEachMethod2"); - callSequence.add("beforeEachMethod2"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - @ExtendWith(FooMethodLevelCallbacks.class) - static class ExceptionInAfterEachMethodTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - throw new EnigmaException("@AfterEach"); - } - } - - @ExtendWith(FooMethodLevelCallbacks.class) - static class ExceptionInTestMethodTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test() { - callSequence.add("test"); - throw new EnigmaException("@Test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - // ------------------------------------------------------------------------- - - static class FooMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - callSequence.add("fooBeforeEachCallback"); - } - - @Override - public void afterEach(ExtensionContext context) { - callSequence.add("fooAfterEachCallback"); - actualExceptionInAfterEachCallback = context.getExecutionException(); - } - } - - static class BarMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - callSequence.add("barBeforeEachCallback"); - } - - @Override - public void afterEach(ExtensionContext context) { - callSequence.add("barAfterEachCallback"); - } - } - - static class FizzMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - callSequence.add("fizzBeforeEachCallback"); - } - - @Override - public void afterEach(ExtensionContext context) { - callSequence.add("fizzAfterEachCallback"); - } - } - - static class ExceptionThrowingBeforeEachCallback implements BeforeEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - callSequence.add("exceptionThrowingBeforeEachCallback"); - throw new EnigmaException("BeforeEachCallback"); - } - } - - static class ExceptionThrowingAfterEachCallback implements AfterEachCallback { - - @Override - public void afterEach(ExtensionContext context) { - callSequence.add("exceptionThrowingAfterEachCallback"); - throw new EnigmaException("AfterEachCallback"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java deleted file mode 100644 index a06ceed8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests that verify support for {@link BeforeTestExecutionCallback}, - * {@link AfterTestExecutionCallback}, {@link BeforeEach}, and {@link AfterEach} - * in the {@link JupiterTestEngine}. - * - * @since 5.0 - * @see BeforeAndAfterEachTests - */ -class BeforeAndAfterTestExecutionCallbackTests extends AbstractJupiterTestEngineTests { - - private static List callSequence = new ArrayList<>(); - - private static Optional actualExceptionInAfterTestExecution; - - @BeforeEach - void resetCallSequence() { - callSequence.clear(); - actualExceptionInAfterTestExecution = null; - } - - @Test - void beforeAndAfterTestExecutionCallbacks() { - EngineExecutionResults executionResults = executeTestsForClass(OuterTestCase.class); - - assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - - // OuterTestCase - "beforeEachMethodOuter", - "fooBeforeTestExecutionCallback", - "barBeforeTestExecutionCallback", - "testOuter", - "barAfterTestExecutionCallback", - "fooAfterTestExecutionCallback", - "afterEachMethodOuter", - - // InnerTestCase - "beforeEachMethodOuter", - "beforeEachMethodInner", - "fooBeforeTestExecutionCallback", - "barBeforeTestExecutionCallback", - "fizzBeforeTestExecutionCallback", - "testInner", - "fizzAfterTestExecutionCallback", - "barAfterTestExecutionCallback", - "fooAfterTestExecutionCallback", - "afterEachMethodInner", - "afterEachMethodOuter" - - ), callSequence, "wrong call sequence"); - // @formatter:on - } - - @Test - void beforeAndAfterTestExecutionCallbacksDeclaredOnSuperclassAndSubclass() { - EngineExecutionResults executionResults = executeTestsForClass(ChildTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "fooBeforeTestExecutionCallback", - "barBeforeTestExecutionCallback", - "testChild", - "barAfterTestExecutionCallback", - "fooAfterTestExecutionCallback" - ), callSequence, "wrong call sequence"); - // @formatter:on - } - - @Test - void beforeAndAfterTestExecutionCallbacksDeclaredOnInterfaceAndClass() { - EngineExecutionResults executionResults = executeTestsForClass(TestInterfaceTestCase.class); - - assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - - // Test Interface - "fooBeforeTestExecutionCallback", - "barBeforeTestExecutionCallback", - "defaultTestMethod", - "barAfterTestExecutionCallback", - "fooAfterTestExecutionCallback", - - // Test Class - "fooBeforeTestExecutionCallback", - "barBeforeTestExecutionCallback", - "localTestMethod", - "barAfterTestExecutionCallback", - "fooAfterTestExecutionCallback" - - ), callSequence, "wrong call sequence"); - // @formatter:on - } - - @Test - void beforeEachMethodThrowsAnException() { - EngineExecutionResults executionResults = executeTestsForClass(ExceptionInBeforeEachMethodTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "beforeEachMethod", // throws an exception. - // fooBeforeTestExecutionCallback should not get invoked. - // test should not get invoked. - // fooAfterTestExecutionCallback should not get invoked. - "afterEachMethod" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertNull(actualExceptionInAfterTestExecution, - "test exception (fooAfterTestExecutionCallback should not have been called)"); - } - - @Test - void beforeTestExecutionCallbackThrowsAnException() { - EngineExecutionResults executionResults = executeTestsForClass( - ExceptionInBeforeTestExecutionCallbackTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "beforeEachMethod", - "fooBeforeTestExecutionCallback", - "exceptionThrowingBeforeTestExecutionCallback", // throws an exception. - // barBeforeTestExecutionCallback should not get invoked. - // test() should not get invoked. - "barAfterTestExecutionCallback", - "fooAfterTestExecutionCallback", - "afterEachMethod" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertNotNull(actualExceptionInAfterTestExecution, "test exception"); - assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); - assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); - } - - @Test - void afterTestExecutionCallbackThrowsAnException() { - EngineExecutionResults executionResults = executeTestsForClass( - ExceptionInAfterTestExecutionCallbackTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "beforeEachMethod", - "fooBeforeTestExecutionCallback", - "barBeforeTestExecutionCallback", - "test", - "barAfterTestExecutionCallback", - "exceptionThrowingAfterTestExecutionCallback", // throws an exception. - "fooAfterTestExecutionCallback", - "afterEachMethod" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertNotNull(actualExceptionInAfterTestExecution, "test exception"); - assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); - assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); - } - - @Test - void testMethodThrowsAnException() { - EngineExecutionResults executionResults = executeTestsForClass(ExceptionInTestMethodTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - assertEquals(asList( - "beforeEachMethod", - "fooBeforeTestExecutionCallback", - "test", // throws an exception. - "fooAfterTestExecutionCallback", - "afterEachMethod" - ), callSequence, "wrong call sequence"); - // @formatter:on - - assertNotNull(actualExceptionInAfterTestExecution, "test exception"); - assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); - assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); - } - - // ------------------------------------------------------------------------- - - @ExtendWith(FooTestExecutionCallbacks.class) - static class ParentTestCase { - } - - @ExtendWith(BarTestExecutionCallbacks.class) - static class ChildTestCase extends ParentTestCase { - - @Test - void test() { - callSequence.add("testChild"); - } - } - - @ExtendWith(FooTestExecutionCallbacks.class) - private interface TestInterface { - - @Test - default void defaultTest() { - callSequence.add("defaultTestMethod"); - } - } - - @ExtendWith(BarTestExecutionCallbacks.class) - static class TestInterfaceTestCase implements TestInterface { - - @Test - void localTest() { - callSequence.add("localTestMethod"); - } - } - - @ExtendWith({ FooTestExecutionCallbacks.class, BarTestExecutionCallbacks.class }) - static class OuterTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethodOuter"); - } - - @Test - void testOuter() { - callSequence.add("testOuter"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethodOuter"); - } - - @Nested - @ExtendWith(FizzTestExecutionCallbacks.class) - class InnerTestCase { - - @BeforeEach - void beforeInnerMethod() { - callSequence.add("beforeEachMethodInner"); - } - - @Test - void testInner() { - callSequence.add("testInner"); - } - - @AfterEach - void afterInnerMethod() { - callSequence.add("afterEachMethodInner"); - } - } - } - - @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingBeforeTestExecutionCallback.class, - BarTestExecutionCallbacks.class }) - static class ExceptionInBeforeTestExecutionCallbackTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingAfterTestExecutionCallback.class, - BarTestExecutionCallbacks.class }) - static class ExceptionInAfterTestExecutionCallbackTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - @ExtendWith(FooTestExecutionCallbacks.class) - static class ExceptionInBeforeEachMethodTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - throw new EnigmaException("@BeforeEach"); - } - - @Test - void test() { - callSequence.add("test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - @ExtendWith(FooTestExecutionCallbacks.class) - static class ExceptionInTestMethodTestCase { - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test() { - callSequence.add("test"); - throw new EnigmaException("@Test"); - } - - @AfterEach - void afterEach() { - callSequence.add("afterEachMethod"); - } - } - - // ------------------------------------------------------------------------- - - static class FooTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { - - @Override - public void beforeTestExecution(ExtensionContext context) { - callSequence.add("fooBeforeTestExecutionCallback"); - } - - @Override - public void afterTestExecution(ExtensionContext context) { - callSequence.add("fooAfterTestExecutionCallback"); - actualExceptionInAfterTestExecution = context.getExecutionException(); - } - } - - static class BarTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { - - @Override - public void beforeTestExecution(ExtensionContext context) { - callSequence.add("barBeforeTestExecutionCallback"); - } - - @Override - public void afterTestExecution(ExtensionContext context) { - callSequence.add("barAfterTestExecutionCallback"); - } - } - - static class FizzTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { - - @Override - public void beforeTestExecution(ExtensionContext context) { - callSequence.add("fizzBeforeTestExecutionCallback"); - } - - @Override - public void afterTestExecution(ExtensionContext context) { - callSequence.add("fizzAfterTestExecutionCallback"); - } - } - - static class ExceptionThrowingBeforeTestExecutionCallback implements BeforeTestExecutionCallback { - - @Override - public void beforeTestExecution(ExtensionContext context) { - callSequence.add("exceptionThrowingBeforeTestExecutionCallback"); - throw new EnigmaException("BeforeTestExecutionCallback"); - } - } - - static class ExceptionThrowingAfterTestExecutionCallback implements AfterTestExecutionCallback { - - @Override - public void afterTestExecution(ExtensionContext context) { - callSequence.add("exceptionThrowingAfterTestExecutionCallback"); - throw new EnigmaException("AfterTestExecutionCallback"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java deleted file mode 100644 index 7d955760..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.nio.file.Files.deleteIfExists; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; -import static org.junit.jupiter.api.io.CleanupMode.NEVER; -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.util.Optional; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.execution.ExtensionValuesStore; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; - -/** - * Integration tests for cleanup of the {@link TempDirectory} when the {@link CleanupMode} is - * set to {@link CleanupMode#ALWAYS}, {@link CleanupMode#NEVER}, or {@link CleanupMode#ON_SUCCESS}. - * - * @since 5.9 - * - * @see TempDir - * @see CleanupMode - */ -class CloseablePathCleanupTests extends AbstractJupiterTestEngineTests { - - private final ExtensionContext extensionContext = mock(); - - private TempDirectory.CloseablePath closeablePath; - - @BeforeEach - void setUpExtensionContext() { - var store = new NamespaceAwareStore(new ExtensionValuesStore(null), Namespace.GLOBAL); - when(extensionContext.getStore(any())).thenReturn(store); - } - - @AfterEach - void cleanupTempDirectory() throws IOException { - deleteIfExists(closeablePath.get()); - } - - /** - * Ensure a closeable path is cleaned up for a cleanup mode of ALWAYS. - */ - @Test - void always() throws IOException { - closeablePath = TempDirectory.createTempDir(ALWAYS, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).doesNotExist(); - } - - /** - * Ensure a closeable path is not cleaned up for a cleanup mode of NEVER. - */ - @Test - void never() throws IOException { - closeablePath = TempDirectory.createTempDir(NEVER, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).exists(); - } - - /** - * Ensure a closeable path is not cleaned up for a cleanup mode of ON_SUCCESS, if there is a TestAbortedException. - */ - @Test - void onSuccessWithException() throws IOException { - when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); - - closeablePath = TempDirectory.createTempDir(ON_SUCCESS, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).exists(); - } - - /** - * Ensure a closeable path is cleaned up for a cleanup mode of ON_SUCCESS, if there is no exception. - */ - @Test - void onSuccessWithNoException() throws IOException { - when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); - - closeablePath = TempDirectory.createTempDir(ON_SUCCESS, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).doesNotExist(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java deleted file mode 100644 index db1823b1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -@SuppressWarnings("serial") -class EnigmaException extends RuntimeException { - - EnigmaException(String message) { - super(message); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java deleted file mode 100644 index 811bde21..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.stream.IntStream; - -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; - -/** - * @since 5.5 - */ -class EventuallyInterruptibleInvocation implements Invocation { - - @Override - public Void proceed() { - while (!Thread.currentThread().isInterrupted()) { - assertThat(IntStream.range(1, 1_000_000).sum()).isGreaterThan(0); - } - return null; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java deleted file mode 100644 index 33422f04..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.engine.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.jupiter.engine.extension.sub.AlwaysDisabledCondition; -import org.junit.jupiter.engine.extension.sub.AnotherAlwaysDisabledCondition; -import org.junit.jupiter.engine.extension.sub.SystemPropertyCondition; -import org.junit.jupiter.engine.extension.sub.SystemPropertyCondition.SystemProperty; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests that verify support for the {@link ExecutionCondition} - * extension point in the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class ExecutionConditionTests extends AbstractJupiterTestEngineTests { - - private static final String FOO = "DisabledTests.foo"; - private static final String BAR = "DisabledTests.bar"; - private static final String BOGUS = "DisabledTests.bogus"; - private static final String DEACTIVATE = "*AnotherAlwaysDisable*, org.junit.jupiter.engine.extension.sub.AlwaysDisable*"; - - @BeforeEach - public void setUp() { - System.setProperty(FOO, BAR); - } - - @AfterEach - public void tearDown() { - System.clearProperty(FOO); - } - - @Test - void conditionWorksOnContainer() { - EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithExecutionConditionOnClass.class); - - executionResults.containerEvents().assertStatistics(stats -> stats.skipped(1)); - executionResults.testEvents().assertStatistics(stats -> stats.started(0)); - } - - @Test - void conditionWorksOnTest() { - Events tests = executeTestsForClass(TestCaseWithExecutionConditionOnMethods.class).testEvents(); - - tests.assertStatistics(stats -> stats.started(2).succeeded(2).skipped(3)); - } - - @Test - void overrideConditionsUsingFullyQualifiedClassName() { - String deactivatePattern = SystemPropertyCondition.class.getName() + "," + DEACTIVATE; - assertExecutionConditionOverride(deactivatePattern, 1, 1); - assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); - } - - @Test - void overrideConditionsUsingStar() { - // "*" should deactivate DisabledCondition and SystemPropertyCondition - String deactivatePattern = "*"; - assertExecutionConditionOverride(deactivatePattern, 2, 2); - assertExecutionConditionOverride(deactivatePattern, 5, 2, 3); - } - - @Test - void overrideConditionsUsingStarPlusSimpleClassName() { - // DisabledCondition should remain activated - String deactivatePattern = "*" + SystemPropertyCondition.class.getSimpleName() + ", " + DEACTIVATE; - assertExecutionConditionOverride(deactivatePattern, 1, 1); - assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); - } - - @Test - void overrideConditionsUsingPackageNamePlusDotStar() { - // DisabledCondition should remain activated - String deactivatePattern = DEACTIVATE + ", " + SystemPropertyCondition.class.getPackage().getName() + ".*"; - assertExecutionConditionOverride(deactivatePattern, 1, 1); - assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); - } - - @Test - void overrideConditionsUsingMultipleWildcards() { - // DisabledCondition should remain activated - String deactivatePattern = "org.junit.jupiter.*.System*Condition" + "," + DEACTIVATE; - assertExecutionConditionOverride(deactivatePattern, 1, 1); - assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); - } - - @Test - void deactivateAllConditions() { - // DisabledCondition should remain activated - String deactivatePattern = "org.junit.jupiter.*.System*Condition" + ", " + DEACTIVATE; - assertExecutionConditionOverride(deactivatePattern, 1, 1); - assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); - } - - private void assertExecutionConditionOverride(String deactivatePattern, int testStartedCount, int testFailedCount) { - // @formatter:off - LauncherDiscoveryRequest request = request() - .selectors(selectClass(TestCaseWithExecutionConditionOnClass.class)) - .configurationParameter(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, deactivatePattern) - .build(); - // @formatter:on - - EngineExecutionResults executionResults = executeTests(request); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - containers.assertStatistics(stats -> stats.skipped(0).started(2)); - tests.assertStatistics(stats -> stats.started(testStartedCount).failed(testFailedCount)); - } - - private void assertExecutionConditionOverride(String deactivatePattern, int started, int succeeded, int failed) { - // @formatter:off - LauncherDiscoveryRequest request = request() - .selectors(selectClass(TestCaseWithExecutionConditionOnMethods.class)) - .configurationParameter(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, deactivatePattern) - .build(); - // @formatter:on - - executeTests(request).testEvents().assertStatistics( - stats -> stats.started(started).succeeded(succeeded).failed(failed)); - } - - // ------------------------------------------------------------------- - - @SystemProperty(key = FOO, value = BOGUS) - @DeactivatedConditions - static class TestCaseWithExecutionConditionOnClass { - - @Test - void disabledTest() { - fail("this should be disabled"); - } - - @Test - @Disabled - void atDisabledTest() { - fail("this should be @Disabled"); - } - } - - static class TestCaseWithExecutionConditionOnMethods { - - @Test - void enabledTest() { - } - - @Test - @Disabled - @DeactivatedConditions - void atDisabledTest() { - fail("this should be @Disabled"); - } - - @Test - @SystemProperty(key = FOO, value = BAR) - void systemPropertyEnabledTest() { - } - - @Test - @DeactivatedConditions - @SystemProperty(key = FOO, value = BOGUS) - void systemPropertyWithIncorrectValueTest() { - fail("this should be disabled"); - } - - @Test - @DeactivatedConditions - @SystemProperty(key = BOGUS, value = "doesn't matter") - void systemPropertyNotSetTest() { - fail("this should be disabled"); - } - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @ExtendWith({ AlwaysDisabledCondition.class, AnotherAlwaysDisabledCondition.class }) - @interface DeactivatedConditions { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java deleted file mode 100644 index f87d38cb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContextParameterResolver; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; - -class ExtensionContextExecutionTests extends AbstractJupiterTestEngineTests { - - @Test - @ExtendWith(ExtensionContextParameterResolver.class) - void extensionContextHierarchy(ExtensionContext methodExtensionContext) { - assertThat(methodExtensionContext).isNotNull(); - assertThat(methodExtensionContext.getElement()).containsInstanceOf(Method.class); - - Optional classExtensionContext = methodExtensionContext.getParent(); - assertThat(classExtensionContext).isNotEmpty(); - assertThat(classExtensionContext.orElse(null).getElement()).contains(ExtensionContextExecutionTests.class); - - Optional engineExtensionContext = classExtensionContext.orElse(null).getParent(); - assertThat(engineExtensionContext).isNotEmpty(); - assertThat(engineExtensionContext.orElse(null).getElement()).isEmpty(); - - assertThat(engineExtensionContext.orElse(null).getParent()).isEmpty(); - } - - @Test - void twoTestClassesCanShareStateViaEngineExtensionContext() { - Parent.counter.set(0); - - executeTests(selectClass(A.class), selectClass(B.class)).testEvents()// - .assertStatistics(stats -> stats.started(2)); - - assertThat(Parent.counter).hasValue(1); - } - - @ExtendWith(OnlyIncrementCounterOnce.class) - static class Parent { - static final AtomicInteger counter = new AtomicInteger(0); - - @Test - void test() { - } - } - - static class A extends Parent { - } - - static class B extends Parent { - } - - static class OnlyIncrementCounterOnce implements BeforeAllCallback { - @Override - public void beforeAll(ExtensionContext context) { - ExtensionContext.Store store = getRoot(context).getStore(ExtensionContext.Namespace.GLOBAL); - store.getOrComputeIfAbsent("counter", key -> Parent.counter.incrementAndGet()); - } - - private ExtensionContext getRoot(ExtensionContext context) { - return context.getParent().map(this::getRoot).orElse(context); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java deleted file mode 100644 index 5b8a208d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java +++ /dev/null @@ -1,928 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; -import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.List; -import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Integration tests that verify support for extension registration via - * {@link ExtendWith @ExtendWith} on parameters and fields. - * - * @since 5.8 - */ -class ExtensionRegistrationViaParametersAndFieldsTests extends AbstractJupiterTestEngineTests { - - @Test - void constructorParameter() { - assertOneTestSucceeded(ConstructorParameterTestCase.class); - } - - @Test - void constructorParameterForNestedTestClass() { - assertTestsSucceeded(NestedConstructorParameterTestCase.class, 2); - } - - @Test - void beforeAllMethodParameter() { - assertOneTestSucceeded(BeforeAllParameterTestCase.class); - } - - @Test - void afterAllMethodParameter() { - assertOneTestSucceeded(AfterAllParameterTestCase.class); - } - - @Test - void beforeEachMethodParameter() { - assertOneTestSucceeded(BeforeEachParameterTestCase.class); - } - - @Test - void afterEachMethodParameter() { - assertOneTestSucceeded(AfterEachParameterTestCase.class); - } - - @Test - void testMethodParameter() { - assertOneTestSucceeded(TestMethodParameterTestCase.class); - } - - @Test - void testFactoryMethodParameter() { - assertTestsSucceeded(TestFactoryMethodParameterTestCase.class, 2); - } - - @Test - void testTemplateMethodParameter() { - assertTestsSucceeded(TestTemplateMethodParameterTestCase.class, 2); - } - - @Test - void staticField() { - assertOneTestSucceeded(StaticFieldTestCase.class); - } - - @Test - void instanceField() { - assertOneTestSucceeded(InstanceFieldTestCase.class); - } - - @Test - void fieldsWithTestInstancePerClass() { - assertOneTestSucceeded(TestInstancePerClassFieldTestCase.class); - } - - @Test - @TrackLogRecords - void multipleRegistrationsViaField(LogRecordListener listener) { - assertOneTestSucceeded(MultipleRegistrationsViaFieldTestCase.class); - assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension"); - } - - @Test - void duplicateRegistrationViaField() { - Class testClass = DuplicateRegistrationViaFieldTestCase.class; - String expectedMessage = "Failed to register extension via field " - + "[org.junit.jupiter.api.extension.Extension " - + "org.junit.jupiter.engine.extension.ExtensionRegistrationViaParametersAndFieldsTests$DuplicateRegistrationViaFieldTestCase.dummy]. " - + "The field registers an extension of type [org.junit.jupiter.engine.extension.DummyExtension] " - + "via @RegisterExtension and @ExtendWith, but only one registration of a given extension type is permitted."; - - executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, - finishedWithFailure(instanceOf(PreconditionViolationException.class), message(expectedMessage))); - } - - @Test - @TrackLogRecords - void registrationOrder(LogRecordListener listener) { - assertOneTestSucceeded(AllInOneWithTestInstancePerMethodTestCase.class); - assertThat(getRegisteredLocalExtensions(listener))// - .containsExactly(// - "ClassLevelExtension2", // @RegisterExtension on static field - "StaticField2", // @ExtendWith on static field - "ClassLevelExtension1", // @RegisterExtension on static field - "StaticField1", // @ExtendWith on static field - "ConstructorParameter", // @ExtendWith on parameter in constructor - "BeforeAllParameter", // @ExtendWith on parameter in static @BeforeAll method - "BeforeEachParameter", // @ExtendWith on parameter in @BeforeEach method - "AfterEachParameter", // @ExtendWith on parameter in @AfterEach method - "AfterAllParameter", // @ExtendWith on parameter in static @AfterAll method - "TestParameter", // @ExtendWith on parameter in @Test method - "InstanceLevelExtension1", // @RegisterExtension on instance field - "InstanceField1", // @ExtendWith on instance field - "InstanceLevelExtension2", // @RegisterExtension on instance field - "InstanceField2" // @ExtendWith on instance field - ); - - listener.clear(); - assertOneTestSucceeded(AllInOneWithTestInstancePerClassTestCase.class); - assertThat(getRegisteredLocalExtensions(listener))// - .containsExactly(// - "ClassLevelExtension2", // @RegisterExtension on static field - "StaticField2", // @ExtendWith on static field - "ClassLevelExtension1", // @RegisterExtension on static field - "StaticField1", // @ExtendWith on static field - "ConstructorParameter", // @ExtendWith on parameter in constructor - "BeforeAllParameter", // @ExtendWith on parameter in static @BeforeAll method - "BeforeEachParameter", // @ExtendWith on parameter in @BeforeEach method - "AfterEachParameter", // @ExtendWith on parameter in @AfterEach method - "AfterAllParameter", // @ExtendWith on parameter in static @AfterAll method - "InstanceLevelExtension1", // @RegisterExtension on instance field - "InstanceField1", // @ExtendWith on instance field - "InstanceLevelExtension2", // @RegisterExtension on instance field - "InstanceField2", // @ExtendWith on instance field - "TestParameter" // @ExtendWith on parameter in @Test method - ); - } - - private List getRegisteredLocalExtensions(LogRecordListener listener) { - // @formatter:off - return listener.stream(MutableExtensionRegistry.class, Level.FINER) - .map(LogRecord::getMessage) - .filter(message -> message.contains("local extension")) - .map(message -> { - message = message.replaceAll("from source .+", ""); - int indexOfDollarSign = message.indexOf("$"); - int indexOfAtSign = message.indexOf("@"); - int endIndex = (indexOfDollarSign > 1 ? indexOfDollarSign : indexOfAtSign); - return message.substring(message.lastIndexOf('.') + 1, endIndex); - }) - .collect(toList()); - // @formatter:on - } - - private void assertOneTestSucceeded(Class testClass) { - assertTestsSucceeded(testClass, 1); - } - - private void assertTestsSucceeded(Class testClass, int expected) { - executeTestsForClass(testClass).testEvents().assertStatistics( - stats -> stats.started(expected).succeeded(expected).skipped(0).aborted(0).failed(0)); - } - - // ------------------------------------------------------------------- - - /** - * The {@link MagicParameter.Extension} is first registered for the constructor - * and then used for lifecycle and test methods. - */ - @ExtendWith(LongParameterResolver.class) - static class ConstructorParameterTestCase { - - ConstructorParameterTestCase(@MagicParameter("constructor") String text) { - assertThat(text).isEqualTo("ConstructorParameterTestCase-0-constructor"); - } - - @BeforeEach - void beforeEach(String text, TestInfo testInfo) { - assertThat(text).isEqualTo("beforeEach-0-enigma"); - assertThat(testInfo).isNotNull(); - } - - @Test - void test(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-enigma"); - } - - /** - * Redeclaring {@code @MagicParameter} should not result in a - * {@link ParameterResolutionException}. - */ - @AfterEach - void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-method"); - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the constructor - * and then used for lifecycle and test methods. - */ - @Nested - @ExtendWith(LongParameterResolver.class) - class NestedConstructorParameterTestCase { - - NestedConstructorParameterTestCase(TestInfo testInfo, @MagicParameter("constructor") String text) { - assertThat(testInfo).isNotNull(); - // Index is 2 instead of 1, since constructors for non-static nested classes - // receive a reference to the enclosing instance as the first argument: this$0 - assertThat(text).isEqualTo("NestedConstructorParameterTestCase-2-constructor"); - } - - @BeforeEach - void beforeEach(String text, TestInfo testInfo) { - assertThat(text).isEqualTo("beforeEach-0-enigma"); - assertThat(testInfo).isNotNull(); - } - - @Test - void test(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-enigma"); - } - - /** - * Redeclaring {@code @MagicParameter} should not result in a - * {@link ParameterResolutionException}. - */ - @AfterEach - void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-method"); - } - - @Nested - class DoublyNestedConstructorParameterTestCase { - - DoublyNestedConstructorParameterTestCase(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - // Index is 2 instead of 1, since constructors for non-static nested classes - // receive a reference to the enclosing instance as the first argument: this$0 - assertThat(text).isEqualTo("DoublyNestedConstructorParameterTestCase-2-enigma"); - } - - @BeforeEach - void beforeEach(String text, TestInfo testInfo) { - assertThat(text).isEqualTo("beforeEach-0-enigma"); - assertThat(testInfo).isNotNull(); - } - - @Test - void test(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-enigma"); - } - - /** - * Redeclaring {@code @MagicParameter} should not result in a - * {@link ParameterResolutionException}. - */ - @AfterEach - void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-method"); - } - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the {@code @BeforeAll} - * method and then used for other lifecycle methods and test methods. - */ - @ExtendWith(LongParameterResolver.class) - static class BeforeAllParameterTestCase { - - @BeforeAll - static void beforeAll(@MagicParameter("method") String text, TestInfo testInfo) { - assertThat(text).isEqualTo("beforeAll-0-method"); - assertThat(testInfo).isNotNull(); - } - - @BeforeEach - void beforeEach(String text, TestInfo testInfo) { - assertThat(text).isEqualTo("beforeEach-0-enigma"); - assertThat(testInfo).isNotNull(); - } - - @Test - void test(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-enigma"); - } - - /** - * Redeclaring {@code @MagicParameter} should not result in a - * {@link ParameterResolutionException}. - */ - @AfterEach - void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-method"); - } - - @AfterAll - static void afterAll(String text, TestInfo testInfo) { - assertThat(text).isEqualTo("afterAll-0-enigma"); - assertThat(testInfo).isNotNull(); - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the {@code @AfterAll} - * method, but that registration occurs before the test method is invoked, which - * allows the string parameters in the after-each and test methods to be resolved - * by the {@link MagicParameter.Extension} as well. - */ - @ExtendWith(LongParameterResolver.class) - static class AfterAllParameterTestCase { - - @Test - void test(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-enigma"); - } - - @AfterEach - void afterEach(Long number, TestInfo testInfo, String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-enigma"); - } - - @AfterAll - static void afterAll(Long number, TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterAll-2-method"); - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the {@code @BeforeEach} - * method and then used for other lifecycle methods and test methods. - */ - @ExtendWith(LongParameterResolver.class) - static class BeforeEachParameterTestCase { - - @BeforeEach - void beforeEach(@MagicParameter("method") String text, TestInfo testInfo) { - assertThat(text).isEqualTo("beforeEach-0-method"); - assertThat(testInfo).isNotNull(); - } - - @Test - void test(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-enigma"); - } - - /** - * Redeclaring {@code @MagicParameter} should not result in a - * {@link ParameterResolutionException}. - */ - @AfterEach - void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-method"); - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the {@code @AfterEach} - * method, but that registration occurs before the test method is invoked, which - * allows the test method's parameter to be resolved by the {@link MagicParameter.Extension} - * as well. - */ - @ExtendWith(LongParameterResolver.class) - static class AfterEachParameterTestCase { - - @Test - void test(TestInfo testInfo, String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-enigma"); - } - - @AfterEach - void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-method"); - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the {@code @Test} - * method and then used for after-each lifecycle methods. - */ - @ExtendWith(LongParameterResolver.class) - static class TestMethodParameterTestCase { - - @Test - void test(TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("test-1-method"); - } - - @AfterEach - void afterEach(Long number, TestInfo testInfo, String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-enigma"); - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the {@code @TestFactory} - * method and then used for after-each lifecycle methods. - */ - @ExtendWith(LongParameterResolver.class) - static class TestFactoryMethodParameterTestCase { - - @TestFactory - Stream testFactory(TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("testFactory-1-method"); - - return IntStream.of(2, 4).mapToObj(num -> dynamicTest("" + num, () -> assertTrue(num % 2 == 0))); - } - - @AfterEach - void afterEach(Long number, TestInfo testInfo, String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-enigma"); - } - - } - - /** - * The {@link MagicParameter.Extension} is first registered for the {@code @TestTemplate} - * method and then used for after-each lifecycle methods. - */ - @ExtendWith(LongParameterResolver.class) - static class TestTemplateMethodParameterTestCase { - - @TestTemplate - @ExtendWith(TwoInvocationsContextProvider.class) - void testTemplate(TestInfo testInfo, @MagicParameter("method") String text) { - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("testTemplate-1-method"); - } - - @AfterEach - void afterEach(Long number, TestInfo testInfo, String text) { - assertThat(number).isEqualTo(42L); - assertThat(testInfo).isNotNull(); - assertThat(text).isEqualTo("afterEach-2-enigma"); - } - - } - - private static class TwoInvocationsContextProvider implements TestTemplateInvocationContextProvider { - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext()); - } - - private static TestTemplateInvocationContext emptyTestTemplateInvocationContext() { - return new TestTemplateInvocationContext() { - }; - } - } - - static class MultipleRegistrationsViaFieldTestCase { - - @ExtendWith(LongParameterResolver.class) - @RegisterExtension - Extension dummy = new DummyExtension(); - - @Test - void test() { - } - } - - static class DuplicateRegistrationViaFieldTestCase { - - @ExtendWith(DummyExtension.class) - @RegisterExtension - Extension dummy = new DummyExtension(); - - @Test - void test() { - } - } - - /** - * The {@link MagicField.Extension} is registered via a static field. - */ - static class StaticFieldTestCase { - - @MagicField - private static String staticField1; - - @MagicField - static String staticField2; - - @BeforeAll - static void beforeAll() { - assertThat(staticField1).isEqualTo("beforeAll - staticField1"); - assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - } - - @Test - void test() { - assertThat(staticField1).isEqualTo("beforeAll - staticField1"); - assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - } - } - - /** - * The {@link MagicField.Extension} is registered via an instance field. - */ - static class InstanceFieldTestCase { - - @MagicField - String instanceField1; - - @MagicField - private String instanceField2; - - @Test - void test() { - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); - } - } - - /** - * The {@link MagicField.Extension} is registered via a static field and - * an instance field. - */ - @TestInstance(Lifecycle.PER_CLASS) - static class TestInstancePerClassFieldTestCase { - - @MagicField - static String staticField; - - @MagicField - String instanceField; - - @BeforeAll - void beforeAll() { - assertThat(staticField).isEqualTo("beforeAll - staticField"); - assertThat(instanceField).isNull(); - } - - @Test - void test() { - assertThat(staticField).isEqualTo("beforeAll - staticField"); - assertThat(instanceField).isEqualTo("beforeEach - instanceField"); - } - } - - @TestInstance(Lifecycle.PER_METHOD) - static class AllInOneWithTestInstancePerMethodTestCase { - - @StaticField1 - @Order(Integer.MAX_VALUE) - static String staticField1; - - @StaticField2 - @ExtendWith(StaticField2.Extension.class) - @Order(3) - static String staticField2; - - @RegisterExtension - private static Extension classLevelExtension1 = new ClassLevelExtension1(); - - @RegisterExtension - @Order(1) - static Extension classLevelExtension2 = new ClassLevelExtension2(); - - @InstanceField1 - @Order(2) - String instanceField1; - - @InstanceField2 - @ExtendWith(InstanceField2.Extension.class) - String instanceField2; - - @RegisterExtension - @Order(1) - private Extension instanceLevelExtension1 = new InstanceLevelExtension1(); - - @RegisterExtension - @Order(3) - Extension instanceLevelExtension2 = new InstanceLevelExtension2(); - - AllInOneWithTestInstancePerMethodTestCase(@ConstructorParameter String text) { - assertThat(text).isEqualTo("enigma"); - } - - @BeforeAll - static void beforeAll(@ExtendWith(BeforeAllParameter.Extension.class) @BeforeAllParameter String text) { - assertThat(text).isEqualTo("enigma"); - assertThat(staticField1).isEqualTo("beforeAll - staticField1"); - assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - } - - @BeforeEach - void beforeEach(@BeforeEachParameter String text) { - assertThat(text).isEqualTo("enigma"); - assertThat(staticField1).isEqualTo("beforeAll - staticField1"); - assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); - } - - @Test - void test(@TestParameter String text) { - assertThat(text).isEqualTo("enigma"); - assertThat(staticField1).isEqualTo("beforeAll - staticField1"); - assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); - } - - @AfterEach - void afterEach(@AfterEachParameter String text) { - assertThat(text).isEqualTo("enigma"); - assertThat(staticField1).isEqualTo("beforeAll - staticField1"); - assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); - } - - @AfterAll - static void afterAll(@AfterAllParameter String text) { - assertThat(text).isEqualTo("enigma"); - assertThat(staticField1).isEqualTo("beforeAll - staticField1"); - assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - } - - } - - @TestInstance(Lifecycle.PER_CLASS) - static class AllInOneWithTestInstancePerClassTestCase extends AllInOneWithTestInstancePerMethodTestCase { - - AllInOneWithTestInstancePerClassTestCase(@ConstructorParameter String text) { - super(text); - } - } - -} - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(MagicParameter.Extension.class) -@interface MagicParameter { - - String value(); - - class Extension implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == String.class; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - String text = parameterContext.findAnnotation(MagicParameter.class)// - .map(MagicParameter::value)// - .orElse("enigma"); - Executable declaringExecutable = parameterContext.getDeclaringExecutable(); - String name = declaringExecutable instanceof Constructor - ? declaringExecutable.getDeclaringClass().getSimpleName() - : declaringExecutable.getName(); - return String.format("%s-%d-%s", name, parameterContext.getIndex(), text); - } - } -} - -@SuppressWarnings("unused") -class BaseParameterExtension implements ParameterResolver { - - private final Class annotationType; - - @SuppressWarnings("unchecked") - BaseParameterExtension() { - Type genericSuperclass = getClass().getGenericSuperclass(); - this.annotationType = (Class) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; - } - - @Override - public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.isAnnotated(this.annotationType) - && parameterContext.getParameter().getType() == String.class; - } - - @Override - public final Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return "enigma"; - } -} - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(ConstructorParameter.Extension.class) -@interface ConstructorParameter { - class Extension extends BaseParameterExtension { - } -} - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -// Intentionally NOT annotated as follows -// @ExtendWith(BeforeAllParameter.Extension.class) -@interface BeforeAllParameter { - class Extension extends BaseParameterExtension { - } -} - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(AfterAllParameter.Extension.class) -@interface AfterAllParameter { - class Extension extends BaseParameterExtension { - } -} - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(BeforeEachParameter.Extension.class) -@interface BeforeEachParameter { - class Extension extends BaseParameterExtension { - } -} - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(AfterEachParameter.Extension.class) -@interface AfterEachParameter { - class Extension extends BaseParameterExtension { - } -} - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(TestParameter.Extension.class) -@interface TestParameter { - class Extension extends BaseParameterExtension { - } -} - -class DummyExtension implements Extension { -} - -class BaseFieldExtension implements BeforeAllCallback, BeforeEachCallback { - - private final Class annotationType; - - @SuppressWarnings("unchecked") - BaseFieldExtension() { - Type genericSuperclass = getClass().getGenericSuperclass(); - this.annotationType = (Class) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; - } - - @Override - public final void beforeAll(ExtensionContext context) throws Exception { - injectFields("beforeAll", context.getRequiredTestClass(), null, ReflectionUtils::isStatic); - } - - @Override - public final void beforeEach(ExtensionContext context) throws Exception { - injectFields("beforeEach", context.getRequiredTestClass(), context.getRequiredTestInstance(), - ReflectionUtils::isNotStatic); - } - - private void injectFields(String trigger, Class testClass, Object instance, Predicate predicate) { - findAnnotatedFields(testClass, this.annotationType, predicate).forEach(field -> { - try { - makeAccessible(field).set(instance, trigger + " - " + field.getName()); - } - catch (Throwable t) { - ExceptionUtils.throwAsUncheckedException(t); - } - }); - } -} - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(MagicField.Extension.class) -@interface MagicField { - class Extension extends BaseFieldExtension { - } -} - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(InstanceField1.Extension.class) -@interface InstanceField1 { - class Extension extends BaseFieldExtension { - } -} - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -// Intentionally NOT annotated as follows -// @ExtendWith(InstanceField2.Extension.class) -@interface InstanceField2 { - class Extension extends BaseFieldExtension { - } -} - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@ExtendWith(StaticField1.Extension.class) -@interface StaticField1 { - class Extension extends BaseFieldExtension { - } -} - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -// Intentionally NOT annotated as follows -// @ExtendWith(StaticField2.Extension.class) -@interface StaticField2 { - class Extension extends BaseFieldExtension { - } -} - -class ClassLevelExtension1 implements Extension { -} - -class ClassLevelExtension2 implements Extension { -} - -class InstanceLevelExtension1 implements Extension { -} - -class InstanceLevelExtension2 implements Extension { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java deleted file mode 100644 index cbe91c6e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryFrom; -import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.jupiter.engine.config.JupiterConfiguration; - -/** - * Tests for the {@link MutableExtensionRegistry}. - * - * @since 5.0 - */ -class ExtensionRegistryTests { - - private static final int NUM_DEFAULT_EXTENSIONS = 6; - - private final JupiterConfiguration configuration = mock(); - - private MutableExtensionRegistry registry = createRegistryWithDefaultExtensions(configuration); - - @Test - void newRegistryWithoutParentHasDefaultExtensions() { - List extensions = registry.getExtensions(Extension.class); - - assertEquals(NUM_DEFAULT_EXTENSIONS, extensions.size()); - assertDefaultGlobalExtensionsAreRegistered(); - } - - @Test - void newRegistryWithoutParentHasDefaultExtensionsPlusAutodetectedExtensionsLoadedViaServiceLoader() { - - when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); - registry = createRegistryWithDefaultExtensions(configuration); - - List extensions = registry.getExtensions(Extension.class); - - assertEquals(NUM_DEFAULT_EXTENSIONS + 1, extensions.size()); - assertDefaultGlobalExtensionsAreRegistered(3); - - assertExtensionRegistered(registry, ServiceLoaderExtension.class); - assertEquals(3, countExtensions(registry, BeforeAllCallback.class)); - } - - @Test - void registerExtensionByImplementingClass() { - registry.registerExtension(MyExtension.class); - - assertExtensionRegistered(registry, MyExtension.class); - - registry.registerExtension(MyExtension.class); - registry.registerExtension(MyExtension.class); - registry.registerExtension(MyExtension.class); - - assertEquals(1, registry.getExtensions(MyExtension.class).size()); - assertExtensionRegistered(registry, MyExtension.class); - assertEquals(1, countExtensions(registry, MyExtensionApi.class)); - - registry.registerExtension(YourExtension.class); - assertExtensionRegistered(registry, YourExtension.class); - assertEquals(2, countExtensions(registry, MyExtensionApi.class)); - } - - @Test - void registerExtensionThatImplementsMultipleExtensionApis() { - registry.registerExtension(MultipleExtension.class); - - assertExtensionRegistered(registry, MultipleExtension.class); - - assertEquals(1, countExtensions(registry, MyExtensionApi.class)); - assertEquals(1, countExtensions(registry, AnotherExtensionApi.class)); - } - - @Test - void extensionsAreInheritedFromParent() { - MutableExtensionRegistry parent = registry; - parent.registerExtension(MyExtension.class); - - MutableExtensionRegistry child = createRegistryFrom(parent, Stream.of(YourExtension.class)); - assertExtensionRegistered(child, MyExtension.class); - assertExtensionRegistered(child, YourExtension.class); - assertEquals(2, countExtensions(child, MyExtensionApi.class)); - - ExtensionRegistry grandChild = createRegistryFrom(child, Stream.empty()); - assertExtensionRegistered(grandChild, MyExtension.class); - assertExtensionRegistered(grandChild, YourExtension.class); - assertEquals(2, countExtensions(grandChild, MyExtensionApi.class)); - } - - @Test - void registeringSameExtensionImplementationInParentAndChildDoesNotResultInDuplicate() { - MutableExtensionRegistry parent = registry; - parent.registerExtension(MyExtension.class); - assertEquals(1, countExtensions(parent, MyExtensionApi.class)); - - MutableExtensionRegistry child = createRegistryFrom(parent, Stream.of(MyExtension.class, YourExtension.class)); - assertExtensionRegistered(child, MyExtension.class); - assertExtensionRegistered(child, YourExtension.class); - assertEquals(2, countExtensions(child, MyExtensionApi.class)); - - ExtensionRegistry grandChild = createRegistryFrom(child, Stream.of(MyExtension.class, YourExtension.class)); - assertExtensionRegistered(grandChild, MyExtension.class); - assertExtensionRegistered(grandChild, YourExtension.class); - assertEquals(2, countExtensions(grandChild, MyExtensionApi.class)); - } - - @Test - void canStreamOverRegisteredExtension() { - registry.registerExtension(MyExtension.class); - - AtomicBoolean hasRun = new AtomicBoolean(false); - - registry.getExtensions(MyExtensionApi.class).forEach(extension -> { - assertEquals(MyExtension.class.getName(), extension.getClass().getName()); - hasRun.set(true); - }); - - assertTrue(hasRun.get()); - } - - private long countExtensions(ExtensionRegistry registry, Class extensionType) { - return registry.stream(extensionType).count(); - } - - private void assertExtensionRegistered(ExtensionRegistry registry, Class extensionType) { - assertFalse(registry.getExtensions(extensionType).isEmpty(), - () -> extensionType.getSimpleName() + " should be present"); - } - - private void assertDefaultGlobalExtensionsAreRegistered() { - assertDefaultGlobalExtensionsAreRegistered(2); - } - - private void assertDefaultGlobalExtensionsAreRegistered(long bacCount) { - assertExtensionRegistered(registry, DisabledCondition.class); - assertExtensionRegistered(registry, TempDirectory.class); - assertExtensionRegistered(registry, TimeoutExtension.class); - assertExtensionRegistered(registry, RepeatedTestExtension.class); - assertExtensionRegistered(registry, TestInfoParameterResolver.class); - assertExtensionRegistered(registry, TestReporterParameterResolver.class); - - assertEquals(bacCount, countExtensions(registry, BeforeAllCallback.class)); - assertEquals(2, countExtensions(registry, BeforeEachCallback.class)); - assertEquals(3, countExtensions(registry, ParameterResolver.class)); - assertEquals(1, countExtensions(registry, ExecutionCondition.class)); - assertEquals(1, countExtensions(registry, TestTemplateInvocationContextProvider.class)); - assertEquals(1, countExtensions(registry, InvocationInterceptor.class)); - } - - // ------------------------------------------------------------------------- - - interface MyExtensionApi extends Extension { - - void doNothing(String test); - } - - interface AnotherExtensionApi extends Extension { - - void doMore(); - } - - static class MyExtension implements MyExtensionApi { - - @Override - public void doNothing(String test) { - } - } - - static class YourExtension implements MyExtensionApi { - - @Override - public void doNothing(String test) { - } - } - - static class MultipleExtension implements MyExtensionApi, AnotherExtensionApi { - - @Override - public void doNothing(String test) { - } - - @Override - public void doMore() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java deleted file mode 100644 index c04636c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.stream.Stream; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.extension.DynamicTestInvocationContext; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -class InvocationInterceptorTests extends AbstractJupiterTestEngineTests { - - @Test - void failsTestWhenInterceptorChainDoesNotCallInvocation() { - var results = executeTestsForClass(InvocationIgnoringInterceptorTestCase.class); - - var tests = results.testEvents().assertStatistics(stats -> stats.failed(1).succeeded(0)); - tests.failed().assertEventsMatchExactly( - event(test("test"), finishedWithFailure(instanceOf(JUnitException.class), - message(it -> it.startsWith("Chain of InvocationInterceptors never called invocation"))))); - } - - static class InvocationIgnoringInterceptorTestCase { - @RegisterExtension - Extension interceptor = new InvocationInterceptor() { - @Override - public void interceptTestMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { - // do nothing - } - }; - - @Test - void test() { - // never called - } - } - - @Test - void successTestWhenInterceptorChainSkippedInvocation() { - var results = executeTestsForClass(InvocationSkippedTestCase.class); - - results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(1)); - } - - static class InvocationSkippedTestCase { - @RegisterExtension - Extension interceptor = new InvocationInterceptor() { - @Override - public void interceptTestMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { - invocation.skip(); - } - }; - - @Test - void test() { - fail("should not be called"); - } - } - - @Test - void failsTestWhenInterceptorChainCallsInvocationMoreThanOnce() { - var results = executeTestsForClass(DoubleInvocationInterceptorTestCase.class); - - var tests = results.testEvents().assertStatistics(stats -> stats.failed(1).succeeded(0)); - tests.failed().assertEventsMatchExactly( - event(test("test"), finishedWithFailure(instanceOf(JUnitException.class), message(it -> it.startsWith( - "Chain of InvocationInterceptors called invocation multiple times instead of just once"))))); - } - - static class DoubleInvocationInterceptorTestCase { - @RegisterExtension - Extension interceptor = new InvocationInterceptor() { - @Override - public void interceptTestMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - invocation.proceed(); - invocation.proceed(); - } - }; - - @Test - void test() { - // called twice - } - } - - @TestFactory - Stream callsInterceptors() { - var results = executeTestsForClass(TestCaseWithThreeInterceptors.class); - - results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(3)); - return Arrays.stream(InvocationType.values()) // - .map(invocationType -> dynamicTest(invocationType.name(), () -> { - assertThat(getEvents(results, EnumSet.of(invocationType)).distinct()) // - .containsExactly("before:foo", "before:bar", "before:baz", "test", "after:baz", "after:bar", - "after:foo"); - })); - } - - private Stream getEvents(EngineExecutionResults results, EnumSet types) { - return results.allEvents().reportingEntryPublished() // - .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) // - .map(ReportEntry::getKeyValuePairs) // - .filter(map -> map.keySet().stream().map(InvocationType::valueOf).anyMatch(types::contains)) // - .flatMap(map -> map.values().stream()); - } - - @ExtendWith({ FooInvocationInterceptor.class, BarInvocationInterceptor.class, BazInvocationInterceptor.class }) - static class TestCaseWithThreeInterceptors { - - public TestCaseWithThreeInterceptors(TestReporter reporter) { - publish(reporter, InvocationType.CONSTRUCTOR); - } - - @BeforeAll - static void beforeAll(TestReporter reporter) { - publish(reporter, InvocationType.BEFORE_ALL); - } - - @BeforeEach - void beforeEach(TestReporter reporter) { - publish(reporter, InvocationType.BEFORE_EACH); - } - - @Test - void test(TestReporter reporter) { - publish(reporter, InvocationType.TEST_METHOD); - } - - @RepeatedTest(1) - void testTemplate(TestReporter reporter) { - publish(reporter, InvocationType.TEST_TEMPLATE_METHOD); - } - - @TestFactory - DynamicTest testFactory(TestReporter reporter) { - publish(reporter, InvocationType.TEST_FACTORY_METHOD); - return dynamicTest("dynamicTest", () -> { - publish(reporter, InvocationType.DYNAMIC_TEST); - }); - } - - @AfterEach - void afterEach(TestReporter reporter) { - publish(reporter, InvocationType.AFTER_EACH); - } - - @AfterAll - static void afterAll(TestReporter reporter) { - publish(reporter, InvocationType.AFTER_ALL); - } - - static void publish(TestReporter reporter, InvocationType type) { - reporter.publishEntry(type.name(), "test"); - } - - } - - enum InvocationType { - BEFORE_ALL, - CONSTRUCTOR, - BEFORE_EACH, - TEST_METHOD, - TEST_TEMPLATE_METHOD, - TEST_FACTORY_METHOD, - DYNAMIC_TEST, - AFTER_EACH, - AFTER_ALL - } - - abstract static class ReportingInvocationInterceptor implements InvocationInterceptor { - private final Class testClass = TestCaseWithThreeInterceptors.class; - private final String name; - - ReportingInvocationInterceptor(String name) { - this.name = name; - } - - @Override - public void interceptBeforeAllMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertThat(invocationContext.getTarget()).isEmpty(); - assertEquals(testClass.getDeclaredMethod("beforeAll", TestReporter.class), - invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - reportAndProceed(invocation, extensionContext, InvocationType.BEFORE_ALL); - } - - @Override - public T interceptTestClassConstructor(Invocation invocation, - ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertEquals(testClass.getDeclaredConstructor(TestReporter.class), invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - return reportAndProceed(invocation, extensionContext, InvocationType.CONSTRUCTOR); - } - - @Override - public void interceptBeforeEachMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); - assertEquals(testClass.getDeclaredMethod("beforeEach", TestReporter.class), - invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - reportAndProceed(invocation, extensionContext, InvocationType.BEFORE_EACH); - } - - @Override - public void interceptTestMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); - assertEquals(testClass.getDeclaredMethod("test", TestReporter.class), invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - reportAndProceed(invocation, extensionContext, InvocationType.TEST_METHOD); - } - - @Override - public void interceptTestTemplateMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); - assertEquals(testClass.getDeclaredMethod("testTemplate", TestReporter.class), - invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - reportAndProceed(invocation, extensionContext, InvocationType.TEST_TEMPLATE_METHOD); - } - - @Override - public T interceptTestFactoryMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); - assertEquals(testClass.getDeclaredMethod("testFactory", TestReporter.class), - invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - return reportAndProceed(invocation, extensionContext, InvocationType.TEST_FACTORY_METHOD); - } - - @Override - public void interceptDynamicTest(Invocation invocation, DynamicTestInvocationContext invocationContext, - ExtensionContext extensionContext) throws Throwable { - assertThat(invocationContext.getExecutable()).isNotNull(); - assertThat(extensionContext.getUniqueId()).isNotBlank(); - assertThat(extensionContext.getElement()).isEmpty(); - assertThat(extensionContext.getParent().flatMap(ExtensionContext::getTestMethod)).contains( - testClass.getDeclaredMethod("testFactory", TestReporter.class)); - reportAndProceed(invocation, extensionContext, InvocationType.DYNAMIC_TEST); - } - - @Override - public void interceptAfterEachMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); - assertEquals(testClass.getDeclaredMethod("afterEach", TestReporter.class), - invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - reportAndProceed(invocation, extensionContext, InvocationType.AFTER_EACH); - } - - @Override - public void interceptAfterAllMethod(Invocation invocation, - ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) - throws Throwable { - assertEquals(testClass, invocationContext.getTargetClass()); - assertThat(invocationContext.getTarget()).isEmpty(); - assertEquals(testClass.getDeclaredMethod("afterAll", TestReporter.class), - invocationContext.getExecutable()); - assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); - reportAndProceed(invocation, extensionContext, InvocationType.AFTER_ALL); - } - - private T reportAndProceed(Invocation invocation, ExtensionContext extensionContext, InvocationType type) - throws Throwable { - extensionContext.publishReportEntry(type.name(), "before:" + name); - try { - return invocation.proceed(); - } - finally { - extensionContext.publishReportEntry(type.name(), "after:" + name); - } - } - } - - static class FooInvocationInterceptor extends ReportingInvocationInterceptor { - FooInvocationInterceptor() { - super("foo"); - } - } - - static class BarInvocationInterceptor extends ReportingInvocationInterceptor { - BarInvocationInterceptor() { - super("bar"); - } - } - - static class BazInvocationInterceptor extends ReportingInvocationInterceptor { - BazInvocationInterceptor() { - super("baz"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java deleted file mode 100644 index 4214dcfb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Tests that verify the support for lifecycle method execution exception handling - * via {@link LifecycleMethodExecutionExceptionHandler} - * - * @since 5.5 - */ -class LifecycleMethodExecutionExceptionHandlerTests extends AbstractJupiterTestEngineTests { - - private static List handlerCalls = new ArrayList<>(); - private static boolean throwExceptionBeforeAll; - private static boolean throwExceptionBeforeEach; - private static boolean throwExceptionAfterEach; - private static boolean throwExceptionAfterAll; - - @BeforeEach - void resetStatics() { - throwExceptionBeforeAll = true; - throwExceptionBeforeEach = true; - throwExceptionAfterEach = true; - throwExceptionAfterAll = true; - handlerCalls.clear(); - - SwallowExceptionHandler.beforeAllCalls = 0; - SwallowExceptionHandler.beforeEachCalls = 0; - SwallowExceptionHandler.afterEachCalls = 0; - SwallowExceptionHandler.afterAllCalls = 0; - - RethrowExceptionHandler.beforeAllCalls = 0; - RethrowExceptionHandler.beforeEachCalls = 0; - RethrowExceptionHandler.afterEachCalls = 0; - RethrowExceptionHandler.afterAllCalls = 0; - - ConvertExceptionHandler.beforeAllCalls = 0; - ConvertExceptionHandler.beforeEachCalls = 0; - ConvertExceptionHandler.afterEachCalls = 0; - ConvertExceptionHandler.afterAllCalls = 0; - - UnrecoverableExceptionHandler.beforeAllCalls = 0; - UnrecoverableExceptionHandler.beforeEachCalls = 0; - UnrecoverableExceptionHandler.afterEachCalls = 0; - UnrecoverableExceptionHandler.afterAllCalls = 0; - - ShouldNotBeCalledHandler.beforeAllCalls = 0; - ShouldNotBeCalledHandler.beforeEachCalls = 0; - ShouldNotBeCalledHandler.afterEachCalls = 0; - ShouldNotBeCalledHandler.afterAllCalls = 0; - } - - @Test - void classLevelExceptionHandlersRethrowException() { - LauncherDiscoveryRequest request = request().selectors(selectClass(RethrowingTestCase.class)).build(); - EngineExecutionResults executionResults = executeTests(request); - - assertEquals(1, RethrowExceptionHandler.beforeAllCalls, "Exception should handled in @BeforeAll"); - assertEquals(1, RethrowExceptionHandler.afterAllCalls, "Exception should handled in @AfterAll"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(RethrowingTestCase.class), started()), // - event(container(RethrowingTestCase.class), finishedWithFailure(instanceOf(RuntimeException.class))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void testLevelExceptionHandlersRethrowException() { - throwExceptionBeforeAll = false; - throwExceptionAfterAll = false; - LauncherDiscoveryRequest request = request().selectors(selectClass(RethrowingTestCase.class)).build(); - EngineExecutionResults executionResults = executeTests(request); - - assertEquals(1, RethrowExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); - assertEquals(1, RethrowExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(RethrowingTestCase.class), started()), // - event(test("aTest"), started()), // - event(test("aTest"), finishedWithFailure(instanceOf(RuntimeException.class))), // - event(container(RethrowingTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void classLevelExceptionHandlersConvertException() { - LauncherDiscoveryRequest request = request().selectors(selectClass(ConvertingTestCase.class)).build(); - EngineExecutionResults executionResults = executeTests(request); - - assertEquals(1, ConvertExceptionHandler.beforeAllCalls, "Exception should handled in @BeforeAll"); - assertEquals(1, ConvertExceptionHandler.afterAllCalls, "Exception should handled in @AfterAll"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(ConvertingTestCase.class), started()), // - event(container(ConvertingTestCase.class), finishedWithFailure(instanceOf(IOException.class))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void testLevelExceptionHandlersConvertException() { - throwExceptionBeforeAll = false; - throwExceptionAfterAll = false; - LauncherDiscoveryRequest request = request().selectors(selectClass(ConvertingTestCase.class)).build(); - EngineExecutionResults executionResults = executeTests(request); - - assertEquals(1, ConvertExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); - assertEquals(1, ConvertExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(ConvertingTestCase.class), started()), // - event(test("aTest"), started()), // - event(test("aTest"), finishedWithFailure(instanceOf(IOException.class))), // - event(container(ConvertingTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void exceptionHandlersSwallowException() { - LauncherDiscoveryRequest request = request().selectors(selectClass(SwallowingTestCase.class)).build(); - EngineExecutionResults executionResults = executeTests(request); - - assertEquals(1, SwallowExceptionHandler.beforeAllCalls, "Exception should be handled in @BeforeAll"); - assertEquals(1, SwallowExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); - assertEquals(1, SwallowExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); - assertEquals(1, SwallowExceptionHandler.afterAllCalls, "Exception should be handled in @AfterAll"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(SwallowingTestCase.class), started()), // - event(test("aTest"), started()), // - event(test("aTest"), finishedSuccessfully()), // - event(container(SwallowingTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void perClassLifecycleMethodsAreHandled() { - LauncherDiscoveryRequest request = request().selectors(selectClass(PerClassLifecycleTestCase.class)).build(); - EngineExecutionResults executionResults = executeTests(request); - assertEquals(2, SwallowExceptionHandler.beforeAllCalls, "Exception should be handled in @BeforeAll"); - assertEquals(1, SwallowExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); - assertEquals(1, SwallowExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); - assertEquals(2, SwallowExceptionHandler.afterAllCalls, "Exception should be handled in @AfterAll"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(PerClassLifecycleTestCase.class), started()), // - event(test("aTest"), started()), // - event(test("aTest"), finishedSuccessfully()), // - event(container(PerClassLifecycleTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void multipleHandlersAreCalledInOrder() { - LauncherDiscoveryRequest request = request().selectors(selectClass(MultipleHandlersTestCase.class)).build(); - EngineExecutionResults executionResults = executeTests(request); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(MultipleHandlersTestCase.class), started()), // - event(test("aTest"), started()), // - event(test("aTest"), finishedSuccessfully()), // - event(test("aTest2"), started()), // - event(test("aTest2"), finishedSuccessfully()), // - event(container(MultipleHandlersTestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); // - - assertEquals(Arrays.asList( - // BeforeAll chain (class level only) - "RethrowExceptionBeforeAll", "SwallowExceptionBeforeAll", - // BeforeEach chain for aTest (test + class level) - "ConvertExceptionBeforeEach", "RethrowExceptionBeforeEach", "SwallowExceptionBeforeEach", - // AfterEach chain for aTest (test + class level) - "ConvertExceptionAfterEach", "RethrowExceptionAfterEach", "SwallowExceptionAfterEach", - // BeforeEach chain for aTest2 (class level only) - "RethrowExceptionBeforeEach", "SwallowExceptionBeforeEach", - // AfterEach chain for aTest2 (class level only) - "RethrowExceptionAfterEach", "SwallowExceptionAfterEach", - // AfterAll chain (class level only) - "RethrowExceptionAfterAll", "SwallowExceptionAfterAll" // - ), handlerCalls, "Wrong order of handler calls"); - } - - @Test - void unrecoverableExceptionsAreNotPropagatedInBeforeAll() { - throwExceptionBeforeAll = true; - throwExceptionBeforeEach = false; - throwExceptionAfterEach = false; - throwExceptionAfterAll = false; - - boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); - assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); - assertEquals(1, UnrecoverableExceptionHandler.beforeAllCalls, "Exception should be handled in @BeforeAll"); - assertEquals(0, ShouldNotBeCalledHandler.beforeAllCalls, "Exception should not propagate in @BeforeAll"); - } - - @Test - void unrecoverableExceptionsAreNotPropagatedInBeforeEach() { - throwExceptionBeforeAll = false; - throwExceptionBeforeEach = true; - throwExceptionAfterEach = false; - throwExceptionAfterAll = false; - - boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); - assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); - assertEquals(1, UnrecoverableExceptionHandler.beforeEachCalls, "Exception should be handled in @BeforeEach"); - assertEquals(0, ShouldNotBeCalledHandler.beforeEachCalls, "Exception should not propagate in @BeforeEach"); - } - - @Test - void unrecoverableExceptionsAreNotPropagatedInAfterEach() { - throwExceptionBeforeAll = false; - throwExceptionBeforeEach = false; - throwExceptionAfterEach = true; - throwExceptionAfterAll = false; - - boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); - assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); - assertEquals(1, UnrecoverableExceptionHandler.afterEachCalls, "Exception should be handled in @AfterEach"); - assertEquals(0, ShouldNotBeCalledHandler.afterEachCalls, "Exception should not propagate in @AfterEach"); - } - - @Test - void unrecoverableExceptionsAreNotPropagatedInAfterAll() { - throwExceptionBeforeAll = false; - throwExceptionBeforeEach = false; - throwExceptionAfterEach = false; - throwExceptionAfterAll = true; - - boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); - assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); - assertEquals(1, UnrecoverableExceptionHandler.afterAllCalls, "Exception should be handled in @AfterAll"); - assertEquals(0, ShouldNotBeCalledHandler.afterAllCalls, "Exception should not propagate in @AfterAll"); - } - - private boolean executeThrowingOutOfMemoryException() { - LauncherDiscoveryRequest request = request().selectors( - selectClass(UnrecoverableExceptionTestCase.class)).build(); - try { - executeTests(request); - } - catch (OutOfMemoryError expected) { - return true; - } - return false; - } - - // ------------------------------------------ - - static class BaseTestCase { - @BeforeAll - static void throwBeforeAll() { - if (throwExceptionBeforeAll) { - throw new RuntimeException("BeforeAllEx"); - } - } - - @BeforeEach - void throwBeforeEach() { - if (throwExceptionBeforeEach) { - throw new RuntimeException("BeforeEachEx"); - } - } - - @Test - void aTest() { - } - - @AfterEach - void throwAfterEach() { - if (throwExceptionAfterEach) { - throw new RuntimeException("AfterEachEx"); - } - } - - @AfterAll - static void throwAfterAll() { - if (throwExceptionAfterAll) { - throw new RuntimeException("AfterAllEx"); - } - } - } - - @ExtendWith(RethrowExceptionHandler.class) - static class RethrowingTestCase extends BaseTestCase { - } - - @ExtendWith(ConvertExceptionHandler.class) - static class ConvertingTestCase extends BaseTestCase { - } - - @ExtendWith(SwallowExceptionHandler.class) - static class SwallowingTestCase extends BaseTestCase { - } - - @ExtendWith(ShouldNotBeCalledHandler.class) - @ExtendWith(UnrecoverableExceptionHandler.class) - static class UnrecoverableExceptionTestCase extends BaseTestCase { - } - - @ExtendWith(ShouldNotBeCalledHandler.class) - @ExtendWith(SwallowExceptionHandler.class) - @ExtendWith(RethrowExceptionHandler.class) - @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - static class MultipleHandlersTestCase extends BaseTestCase { - - @Override - @ExtendWith(ConvertExceptionHandler.class) - @Order(1) - @Test - void aTest() { - } - - @Order(2) - @Test - void aTest2() { - } - } - - @ExtendWith(SwallowExceptionHandler.class) - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - static class PerClassLifecycleTestCase extends BaseTestCase { - - @BeforeAll - void beforeAll() { - throw new RuntimeException("nonStaticBeforeAllEx"); - } - - @AfterAll - void afterAll() { - throw new RuntimeException("nonStaticAfterAllEx"); - } - } - - // ------------------------------------------ - - static class RethrowExceptionHandler implements LifecycleMethodExecutionExceptionHandler { - static int beforeAllCalls = 0; - static int beforeEachCalls = 0; - static int afterEachCalls = 0; - static int afterAllCalls = 0; - - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - beforeAllCalls++; - handlerCalls.add("RethrowExceptionBeforeAll"); - throw throwable; - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - beforeEachCalls++; - handlerCalls.add("RethrowExceptionBeforeEach"); - throw throwable; - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - afterEachCalls++; - handlerCalls.add("RethrowExceptionAfterEach"); - throw throwable; - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - afterAllCalls++; - handlerCalls.add("RethrowExceptionAfterAll"); - throw throwable; - } - } - - static class SwallowExceptionHandler implements LifecycleMethodExecutionExceptionHandler { - static int beforeAllCalls = 0; - static int beforeEachCalls = 0; - static int afterEachCalls = 0; - static int afterAllCalls = 0; - - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { - beforeAllCalls++; - handlerCalls.add("SwallowExceptionBeforeAll"); - // Do not rethrow - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { - beforeEachCalls++; - handlerCalls.add("SwallowExceptionBeforeEach"); - // Do not rethrow - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { - afterEachCalls++; - handlerCalls.add("SwallowExceptionAfterEach"); - // Do not rethrow - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { - afterAllCalls++; - handlerCalls.add("SwallowExceptionAfterAll"); - // Do not rethrow - } - } - - static class ConvertExceptionHandler implements LifecycleMethodExecutionExceptionHandler { - static int beforeAllCalls = 0; - static int beforeEachCalls = 0; - static int afterEachCalls = 0; - static int afterAllCalls = 0; - - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - beforeAllCalls++; - handlerCalls.add("ConvertExceptionBeforeAll"); - throw new IOException(throwable); - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - beforeEachCalls++; - handlerCalls.add("ConvertExceptionBeforeEach"); - throw new IOException(throwable); - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - afterEachCalls++; - handlerCalls.add("ConvertExceptionAfterEach"); - throw new IOException(throwable); - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - afterAllCalls++; - handlerCalls.add("ConvertExceptionAfterAll"); - throw new IOException(throwable); - } - } - - static class UnrecoverableExceptionHandler implements LifecycleMethodExecutionExceptionHandler { - static int beforeAllCalls = 0; - static int beforeEachCalls = 0; - static int afterEachCalls = 0; - static int afterAllCalls = 0; - - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { - beforeAllCalls++; - handlerCalls.add("UnrecoverableExceptionBeforeAll"); - throw new OutOfMemoryError(); - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { - beforeEachCalls++; - handlerCalls.add("UnrecoverableExceptionBeforeEach"); - throw new OutOfMemoryError(); - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { - afterEachCalls++; - handlerCalls.add("UnrecoverableExceptionAfterEach"); - throw new OutOfMemoryError(); - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { - afterAllCalls++; - handlerCalls.add("UnrecoverableExceptionAfterAll"); - throw new OutOfMemoryError(); - } - } - - static class ShouldNotBeCalledHandler implements LifecycleMethodExecutionExceptionHandler { - static int beforeAllCalls = 0; - static int beforeEachCalls = 0; - static int afterEachCalls = 0; - static int afterAllCalls = 0; - - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - beforeAllCalls++; - handlerCalls.add("ShouldNotBeCalledBeforeAll"); - throw throwable; - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - ShouldNotBeCalledHandler.beforeEachCalls++; - handlerCalls.add("ShouldNotBeCalledBeforeEach"); - throw throwable; - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - afterEachCalls++; - handlerCalls.add("ShouldNotBeCalledAfterEach"); - throw throwable; - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) - throws Throwable { - afterAllCalls++; - handlerCalls.add("ShouldNotBeCalledAfterAll"); - throw throwable; - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java deleted file mode 100644 index 09026c6b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestClassOrder; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests for {@link ClassOrderer} support. - * - * @since 5.8 - */ -class OrderedClassTests { - - private static final List callSequence = Collections.synchronizedList(new ArrayList<>()); - - @BeforeEach - @AfterEach - void clearCallSequence() { - callSequence.clear(); - } - - @Test - void className() { - executeTests(ClassOrderer.ClassName.class)// - .assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence)// - .containsExactly("A_TestCase", "B_TestCase", "C_TestCase"); - } - - @Test - void classNameAcrossPackages() { - try { - example.B_TestCase.callSequence = callSequence; - - // @formatter:off - executeTests(ClassOrderer.ClassName.class, selectClass(B_TestCase.class), selectClass(example.B_TestCase.class)) - .assertStatistics(stats -> stats.succeeded(callSequence.size())); - // @formatter:on - - assertThat(callSequence)// - .containsExactly("example.B_TestCase", "B_TestCase"); - } - finally { - example.B_TestCase.callSequence = null; - } - } - - @Test - void displayName() { - executeTests(ClassOrderer.DisplayName.class)// - .assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence)// - .containsExactly("C_TestCase", "B_TestCase", "A_TestCase"); - } - - @Test - void orderAnnotation() { - executeTests(ClassOrderer.OrderAnnotation.class)// - .assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence)// - .containsExactly("A_TestCase", "C_TestCase", "B_TestCase"); - } - - @Test - void orderAnnotationOnNestedTestClassesWithGlobalConfig() { - executeTests(ClassOrderer.OrderAnnotation.class, selectClass(OuterWithGlobalConfig.class))// - .assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence)// - .containsExactly("Inner2", "Inner1", "Inner0", "Inner3"); - } - - @Test - @TrackLogRecords - void orderAnnotationOnNestedTestClassesWithLocalConfig(LogRecordListener listener) { - executeTests(ClassOrderer.class, selectClass(OuterWithLocalConfig.class))// - .assertStatistics(stats -> stats.succeeded(callSequence.size())); - - // Ensure that supplying the ClassOrderer interface instead of an implementation - // class results in a WARNING log message. This also lets us know the local - // config is used. - assertTrue(listener.stream(Level.WARNING)// - .map(LogRecord::getMessage)// - .anyMatch(m -> m.startsWith( - "Failed to load default class orderer class 'org.junit.jupiter.api.ClassOrderer'"))); - - assertThat(callSequence)// - .containsExactly("Inner2", "Inner1", "Inner1Inner1", "Inner1Inner0", "Inner0", "Inner3"); - } - - @Test - void random() { - executeTests(ClassOrderer.Random.class)// - .assertStatistics(stats -> stats.succeeded(callSequence.size())); - } - - private Events executeTests(Class classOrderer) { - return executeTests(classOrderer, selectClass(A_TestCase.class), selectClass(B_TestCase.class), - selectClass(C_TestCase.class)); - } - - private Events executeTests(Class classOrderer, DiscoverySelector... selectors) { - // @formatter:off - return EngineTestKit.engine("junit-jupiter") - .configurationParameter(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, classOrderer.getName()) - .selectors(selectors) - .execute() - .testEvents(); - // @formatter:on - } - - static abstract class BaseTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - var testClass = testInfo.getTestClass().get(); - - callSequence.add(testClass.getSimpleName()); - } - - @Test - void a() { - } - } - - @Order(2) - @DisplayName("Z") - static class A_TestCase extends BaseTestCase { - } - - static class B_TestCase extends BaseTestCase { - } - - @Order(10) - @DisplayName("A") - static class C_TestCase extends BaseTestCase { - } - - static class OuterWithGlobalConfig { - - @Nested - class Inner0 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - - @Nested - @Order(2) - class Inner1 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - - @Nested - @Order(1) - class Inner2 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - - @Nested - @Order(Integer.MAX_VALUE) - class Inner3 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - } - - @TestClassOrder(ClassOrderer.OrderAnnotation.class) - static class OuterWithLocalConfig { - - @Nested - class Inner0 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - - @Nested - @Order(2) - class Inner1 { - - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - - @Nested - @Order(2) - class Inner1Inner0 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - - @Nested - @Order(1) - class Inner1Inner1 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - } - - @Nested - @Order(1) - class Inner2 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - - @Nested - @Order(Integer.MAX_VALUE) - class Inner3 { - @Test - void test() { - callSequence.add(getClass().getSimpleName()); - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java deleted file mode 100644 index ed86fd2a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java +++ /dev/null @@ -1,745 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME; -import static org.junit.jupiter.api.Order.DEFAULT; -import static org.junit.jupiter.engine.Constants.DEFAULT_PARALLEL_EXECUTION_MODE; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.regex.Pattern; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.MethodDescriptor; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.MethodOrderer.MethodName; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.MethodOrderer.Random; -import org.junit.jupiter.api.MethodOrdererContext; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Events; -import org.mockito.Mockito; - -/** - * Integration tests that verify support for custom test method execution order - * in the {@link JupiterTestEngine}. - * - * @since 5.4 - */ -class OrderedMethodTests { - - private static final Set callSequence = Collections.synchronizedSet(new LinkedHashSet<>()); - private static final Set threadNames = Collections.synchronizedSet(new LinkedHashSet<>()); - - @BeforeEach - void clearCallSequence() { - callSequence.clear(); - threadNames.clear(); - } - - @Test - void alphanumeric() { - Class testClass = AlphanumericTestCase.class; - - // The name of the base class MUST start with a letter alphanumerically - // greater than "A" so that BaseTestCase comes after AlphanumericTestCase - // if methods are sorted by class name for the fallback ordering if two - // methods have the same name but different parameter lists. Note, however, - // that Alphanumeric actually does not order methods like that, but we want - // this check to remain in place to ensure that the ordering does not rely - // on the class names. - assertThat(testClass.getSuperclass().getName()).isGreaterThan(testClass.getName()); - - var tests = executeTestsInParallel(testClass); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence).containsExactly("$()", "AAA()", "AAA(org.junit.jupiter.api.TestInfo)", - "AAA(org.junit.jupiter.api.TestReporter)", "ZZ_Top()", "___()", "a1()", "a2()", "b()", "c()", "zzz()"); - assertThat(threadNames).hasSize(1); - } - - @Test - void methodName() { - Class testClass = MethodNameTestCase.class; - - // The name of the base class MUST start with a letter alphanumerically - // greater than "A" so that BaseTestCase comes after AlphanumericTestCase - // if methods are sorted by class name for the fallback ordering if two - // methods have the same name but different parameter lists. Note, however, - // that Alphanumeric actually does not order methods like that, but we want - // this check to remain in place to ensure that the ordering does not rely - // on the class names. - assertThat(testClass.getSuperclass().getName()).isLessThan(testClass.getName()); - - var tests = executeTestsInParallel(testClass); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence).containsExactly("$()", "AAA()", "AAA(org.junit.jupiter.api.TestInfo)", - "AAA(org.junit.jupiter.api.TestReporter)", "ZZ_Top()", "___()", "a1()", "a2()", "b()", "c()", "zzz()"); - assertThat(threadNames).hasSize(1); - } - - @Test - void displayName() { - var tests = executeTestsInParallel(DisplayNameTestCase.class); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence)// - .containsExactly("$", "AAA", "No_display_name_attribute_1_caps()", "No_display_name_attribute_2_caps()", - "ZZ_Top", "___", "a1", "a2", "b()", "no_display_name_attribute_1()", - "no_display_name_attribute_2()", "repetition 1 of 1", "⑦ϼ\uD83D\uDC69\u200D⚕\uD83E\uDDD8\u200D♂"); - assertThat(threadNames).hasSize(1); - } - - @Test - void orderAnnotation() { - assertOrderAnnotationSupport(OrderAnnotationTestCase.class); - } - - @Test - void orderAnnotationInNestedTestClass() { - assertOrderAnnotationSupport(OuterTestCase.class); - } - - @Test - void orderAnnotationWithNestedTestClass() { - var tests = executeTestsInParallel(OrderAnnotationWithNestedClassTestCase.class); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence)// - .containsExactly("test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "nestedTest1", - "nestedTest2"); - assertThat(threadNames).hasSize(1); - } - - private void assertOrderAnnotationSupport(Class testClass) { - var tests = executeTestsInParallel(testClass); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence)// - .containsExactly("test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8"); - assertThat(threadNames).hasSize(1); - } - - @Test - void random() { - var tests = executeTestsInParallel(RandomTestCase.class); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(threadNames).hasSize(1); - } - - @Test - void defaultOrderer() { - var tests = executeTestsInParallel(WithoutTestMethodOrderTestCase.class, OrderAnnotation.class); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - assertThat(callSequence).containsExactly("test1()", "test2()", "test3()"); - assertThat(threadNames).hasSize(1); - } - - @Test - @TrackLogRecords - void randomWithBogusSeedRepeatedly(LogRecordListener listener) { - var seed = "explode"; - var expectedMessagePattern = Pattern.compile( - "Failed to convert configuration parameter \\[" + Pattern.quote(Random.RANDOM_SEED_PROPERTY_NAME) - + "] with value \\[" + seed + "] to a long\\. Using default seed \\[\\d+] as fallback\\."); - - Set uniqueSequences = new HashSet<>(); - - for (var i = 0; i < 10; i++) { - callSequence.clear(); - listener.clear(); - - var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - uniqueSequences.add(String.join(",", callSequence)); - - // @formatter:off - assertThat(listener.stream(Random.class, Level.WARNING) - .map(LogRecord::getMessage)) - .anyMatch(expectedMessagePattern.asMatchPredicate()); - // @formatter:on - } - - assertThat(uniqueSequences).size().isEqualTo(1); - } - - @Test - @TrackLogRecords - void randomWithDifferentSeedConsecutively(LogRecordListener listener) { - Set uniqueSequences = new HashSet<>(); - - for (var i = 0; i < 10; i++) { - var seed = String.valueOf(i); - var expectedMessage = "Using custom seed for configuration parameter [" + Random.RANDOM_SEED_PROPERTY_NAME - + "] with value [" + seed + "]."; - callSequence.clear(); - listener.clear(); - - var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - uniqueSequences.add(String.join(",", callSequence)); - - // @formatter:off - assertThat(listener.stream(Random.class, Level.CONFIG) - .map(LogRecord::getMessage)) - .contains(expectedMessage); - // @formatter:on - - assertThat(threadNames).hasSize(i + 1); - } - - // We assume that at least 3 out of 10 are different... - assertThat(uniqueSequences).size().isGreaterThanOrEqualTo(3); - } - - @Test - @TrackLogRecords - void randomWithCustomSeed(LogRecordListener listener) { - var seed = "42"; - var expectedMessage = "Using custom seed for configuration parameter [" + Random.RANDOM_SEED_PROPERTY_NAME - + "] with value [" + seed + "]."; - - for (var i = 0; i < 10; i++) { - callSequence.clear(); - listener.clear(); - - var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - - // With a custom seed, the "randomness" must be the same for every iteration. - assertThat(callSequence).containsExactly("test2()", "test3()", "test4()", "repetition 1 of 1", "test1()"); - - // @formatter:off - assertThat(listener.stream(Random.class, Level.CONFIG) - .map(LogRecord::getMessage)) - .contains(expectedMessage); - // @formatter:on - } - - assertThat(threadNames).size().isGreaterThanOrEqualTo(3); - } - - @Test - @TrackLogRecords - void misbehavingMethodOrdererThatAddsElements(LogRecordListener listener) { - Class testClass = MisbehavingByAddingTestCase.class; - - executeTestsInParallel(testClass).assertStatistics(stats -> stats.succeeded(2)); - - assertThat(callSequence).containsExactlyInAnyOrder("test1()", "test2()"); - - var expectedMessage = "MethodOrderer [" + MisbehavingByAdding.class.getName() - + "] added 2 MethodDescriptor(s) for test class [" + testClass.getName() + "] which will be ignored."; - - assertExpectedLogMessage(listener, expectedMessage); - } - - @Test - @TrackLogRecords - void misbehavingMethodOrdererThatRemovesElements(LogRecordListener listener) { - Class testClass = MisbehavingByRemovingTestCase.class; - - executeTestsInParallel(testClass).assertStatistics(stats -> stats.succeeded(3)); - - assertThat(callSequence).containsExactlyInAnyOrder("test1()", "test2()", "test3()"); - - var expectedMessage = "MethodOrderer [" + MisbehavingByRemoving.class.getName() - + "] removed 2 MethodDescriptor(s) for test class [" + testClass.getName() - + "] which will be retained with arbitrary ordering."; - - assertExpectedLogMessage(listener, expectedMessage); - } - - private void assertExpectedLogMessage(LogRecordListener listener, String expectedMessage) { - // @formatter:off - assertThat(listener.stream(Level.WARNING) - .map(LogRecord::getMessage)) - .contains(expectedMessage); - // @formatter:on - } - - private Events executeTestsInParallel(Class testClass) { - return executeTestsInParallel(testClass, Random.class); - } - - private Events executeTestsInParallel(Class testClass, Class defaultOrderer) { - // @formatter:off - return EngineTestKit - .engine("junit-jupiter") - .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") - .configurationParameter(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent") - .configurationParameter(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, defaultOrderer.getName()) - .selectors(selectClass(testClass)) - .execute() - .testEvents(); - // @formatter:on - } - - private Events executeRandomTestCaseInParallelWithRandomSeed(String seed) { - var configurationParameters = Map.of(// - PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true", // - DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent", // - RANDOM_SEED_PROPERTY_NAME, seed // - ); - - // @formatter:off - return EngineTestKit - .engine("junit-jupiter") - .configurationParameters(configurationParameters) - .selectors(selectClass(RandomTestCase.class)) - .execute() - .testEvents(); - // @formatter:on - } - - // ------------------------------------------------------------------------- - - static class BaseTestCase { - - @Test - void AAA() { - } - - @Test - void c() { - } - - } - - @SuppressWarnings("unused") - @TestMethodOrder(MethodName.class) - static class MethodNameTestCase extends BaseTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - var method = testInfo.getTestMethod().orElseThrow(AssertionError::new); - var signature = String.format("%s(%s)", method.getName(), - ClassUtils.nullSafeToString(method.getParameterTypes())); - - callSequence.add(signature); - threadNames.add(Thread.currentThread().getName()); - } - - @TestFactory - DynamicTest b() { - return dynamicTest("dynamic", () -> { - }); - } - - @Test - void $() { - } - - @Test - void ___() { - } - - @Test - void AAA(TestReporter testReporter) { - } - - @Test - void AAA(TestInfo testInfo) { - } - - @Test - void ZZ_Top() { - } - - @Test - void a1() { - } - - @Test - void a2() { - } - - @RepeatedTest(1) - void zzz() { - } - } - - @SuppressWarnings({ "deprecation", "unused" }) - @TestMethodOrder(org.junit.jupiter.api.MethodOrderer.Alphanumeric.class) - static class AlphanumericTestCase extends BaseTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - var method = testInfo.getTestMethod().orElseThrow(AssertionError::new); - var signature = String.format("%s(%s)", method.getName(), - ClassUtils.nullSafeToString(method.getParameterTypes())); - - callSequence.add(signature); - threadNames.add(Thread.currentThread().getName()); - } - - @TestFactory - DynamicTest b() { - return dynamicTest("dynamic", () -> { - }); - } - - @Test - void $() { - } - - @Test - void ___() { - } - - @Test - void AAA(TestReporter testReporter) { - } - - @Test - void AAA(TestInfo testInfo) { - } - - @Test - void ZZ_Top() { - } - - @Test - void a1() { - } - - @Test - void a2() { - } - - @RepeatedTest(1) - void zzz() { - } - } - - @TestMethodOrder(MethodOrderer.DisplayName.class) - static class DisplayNameTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - callSequence.add(testInfo.getDisplayName()); - threadNames.add(Thread.currentThread().getName()); - } - - @TestFactory - DynamicTest b() { - return dynamicTest("dynamic", () -> { - }); - } - - @DisplayName("$") - @Test - void $() { - } - - @DisplayName("___") - @Test - void ___() { - } - - @DisplayName("AAA") - @Test - void AAA() { - } - - @DisplayName("ZZ_Top") - @Test - void ZZ_Top() { - } - - @DisplayName("a1") - @Test - void a1() { - } - - @DisplayName("a2") - @Test - void a2() { - } - - @DisplayName("zzz") - @RepeatedTest(1) - void zzz() { - } - - @Test - @DisplayName("⑦ϼ\uD83D\uDC69\u200D⚕\uD83E\uDDD8\u200D♂") - void special_characters() { - } - - @Test - void no_display_name_attribute_1() { - } - - @Test - void no_display_name_attribute_2() { - } - - @Test - void No_display_name_attribute_1_caps() { - } - - @Test - void No_display_name_attribute_2_caps() { - } - } - - @TestMethodOrder(OrderAnnotation.class) - static class OrderAnnotationTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - callSequence.add(testInfo.getDisplayName()); - threadNames.add(Thread.currentThread().getName()); - } - - @Test - @DisplayName("test8") - @Order(Integer.MAX_VALUE) - void maxInteger() { - } - - @Test - @DisplayName("test7") - @Order(DEFAULT + 1) - void defaultOrderValuePlusOne() { - } - - @Test - @DisplayName("test6") - // @Order(DEFAULT) - void defaultOrderValue() { - } - - @Test - @DisplayName("test3") - @Order(3) - void $() { - } - - @Test - @DisplayName("test5") - @Order(5) - void AAA() { - } - - @TestFactory - @DisplayName("test4") - @Order(4) - DynamicTest aaa() { - return dynamicTest("test4", () -> { - }); - } - - @Test - @DisplayName("test1") - @Order(1) - void zzz() { - } - - @RepeatedTest(value = 1, name = "{displayName}") - @DisplayName("test2") - @Order(2) - void ___() { - } - } - - static class OuterTestCase { - - @Nested - class NestedOrderAnnotationTestCase extends OrderAnnotationTestCase { - } - } - - @TestMethodOrder(Random.class) - static class RandomTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - callSequence.add(testInfo.getDisplayName()); - threadNames.add(Thread.currentThread().getName()); - } - - @Test - void test1() { - } - - @Test - void test2() { - } - - @Test - void test3() { - } - - @TestFactory - DynamicTest test4() { - return dynamicTest("dynamic", () -> { - }); - } - - @RepeatedTest(1) - void test5() { - } - } - - @TestMethodOrder(MisbehavingByAdding.class) - static class MisbehavingByAddingTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - callSequence.add(testInfo.getDisplayName()); - } - - @Test - void test1() { - } - - @Test - void test2() { - } - } - - @TestMethodOrder(MisbehavingByRemoving.class) - static class MisbehavingByRemovingTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - callSequence.add(testInfo.getDisplayName()); - } - - @Test - void test1() { - } - - @Test - void test2() { - } - - @Test - void test3() { - } - } - - static class OrderAnnotationWithNestedClassTestCase extends OrderAnnotationTestCase { - @Nested - @TestMethodOrder(OrderAnnotation.class) - class NestedTests { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - callSequence.add(testInfo.getDisplayName()); - } - - @Test - @Order(1) - @DisplayName("nestedTest1") - void nestedTest1() { - } - - @Test - @Order(2) - @DisplayName("nestedTest2") - void nestedTest2() { - } - } - } - - static class MisbehavingByAdding implements MethodOrderer { - - @Override - public void orderMethods(MethodOrdererContext context) { - context.getMethodDescriptors().add(mockMethodDescriptor()); - context.getMethodDescriptors().add(mockMethodDescriptor()); - } - - @SuppressWarnings("unchecked") - static T mockMethodDescriptor() { - return (T) Mockito.mock((Class) MethodDescriptor.class); - } - - } - - static class MisbehavingByRemoving implements MethodOrderer { - - @Override - public void orderMethods(MethodOrdererContext context) { - context.getMethodDescriptors().remove(0); - context.getMethodDescriptors().remove(0); - } - } - - static class WithoutTestMethodOrderTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - callSequence.add(testInfo.getDisplayName()); - threadNames.add(Thread.currentThread().getName()); - } - - @Test - @Order(2) - void test2() { - } - - @Test - @Order(3) - void test3() { - } - - @Test - @Order(1) - void test1() { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java deleted file mode 100644 index 6d74a431..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Order.DEFAULT; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; - -/** - * Integration tests that verify support for {@linkplain Order ordered} programmatic - * extension registration via {@link RegisterExtension @RegisterExtension} in the - * {@link JupiterTestEngine}. - * - * @since 5.4 - * @see ProgrammaticExtensionRegistrationTests - */ -class OrderedProgrammaticExtensionRegistrationTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - /** - * This method basically verifies the implementation of - * {@link java.lang.String#hashCode()} (which needn't really be tested) - * in order to make reasonable assumptions about how fields are sorted - * in {@link org.junit.platform.commons.util.ReflectionUtils#defaultFieldSorter(Field, Field)}. - * - *

In other words, this method is just a sanity check for the chosen - * field names in the test cases used in these tests. - */ - @BeforeAll - static void assertAssumptionsAboutDefaultOrderingAlgorithm() { - String fieldName1 = "extension1"; - String fieldName2 = "extension2"; - String fieldName3 = "extension3"; - - assertThat(fieldName1.hashCode()).isLessThan(fieldName2.hashCode()); - assertThat(fieldName2.hashCode()).isLessThan(fieldName3.hashCode()); - } - - @BeforeEach - void clearCallSequence() { - callSequence.clear(); - } - - @Test - void instanceLevelWithDefaultOrder() { - assertOutcome(DefaultOrderInstanceLevelExtensionRegistrationTestCase.class, 1, 2, 3); - } - - @Test - void instanceLevelWithExplicitOrder() { - assertOutcome(ExplicitOrderInstanceLevelExtensionRegistrationTestCase.class, 3, 2, 1); - } - - @Test - void instanceLevelWithDefaultOrderAndExplicitOrder() { - assertOutcome(DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationTestCase.class, 3, 1, 2); - } - - /** - * Verify that an "after" callback can be registered first relative to other - * non-annotated "after" callbacks. - * - * @since 5.6 - * @see gh-1924 - */ - @Test - void instanceLevelWithDefaultOrderPlusOneAndDefaultOrder() { - assertOutcome(DefaultOrderPlusOneAndDefaultOrderInstanceLevelExtensionRegistrationTestCase.class, 1, 3, 2); - } - - @Test - void instanceLevelWithDefaultOrderAndExplicitOrderWithTestInstancePerClassLifecycle() { - assertOutcome( - DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase.class, - 3, 1, 2); - } - - @Test - void classLevelWithDefaultOrderAndExplicitOrder() { - assertOutcome(DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, 3, 1, 2); - } - - @Test - void classLevelWithDefaultOrderAndExplicitOrderInheritedFromSuperclass() { - assertOutcome(InheritedDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, 3, 1, 2); - } - - @Test - void classLevelWithDefaultOrderShadowingOrderFromSuperclass() { - assertOutcome(DefaultOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, 1, - 2, 3); - } - - @Test - void classLevelWithExplicitOrderShadowingOrderFromSuperclass() { - assertOutcome(ExplicitOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class, - 3, 2, 1); - } - - @Test - void classLevelWithDefaultOrderAndExplicitOrderFromInterface() { - assertOutcome(DefaultOrderAndExplicitOrderExtensionRegistrationFromInterfaceTestCase.class, 3, 1, 2); - } - - private void assertOutcome(Class testClass, Integer... values) { - executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); - assertThat(callSequence).containsExactly(values); - } - - // ------------------------------------------------------------------- - - private static class AbstractTestCase { - - @Test - void test() { - } - - } - - static class DefaultOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { - - @RegisterExtension - Extension extension1 = new BeforeEachExtension(1); - - @RegisterExtension - Extension extension3 = new BeforeEachExtension(3); - - @RegisterExtension - Extension extension2 = new BeforeEachExtension(2); - - } - - static class ExplicitOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { - - @Order(3) - @RegisterExtension - Extension extension1 = new BeforeEachExtension(1); - - @Order(2) - @RegisterExtension - Extension extension2 = new BeforeEachExtension(2); - - @Order(1) - @RegisterExtension - Extension extension3 = new BeforeEachExtension(3); - - } - - static class DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { - - // @Order(3) - @RegisterExtension - Extension extension1 = new BeforeEachExtension(1); - - // @Order(2) - @RegisterExtension - Extension extension2 = new BeforeEachExtension(2); - - @Order(1) - @RegisterExtension - Extension extension3 = new BeforeEachExtension(3); - - } - - static class DefaultOrderPlusOneAndDefaultOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { - - @Order(DEFAULT + 1) - @RegisterExtension - Extension extension1 = new AfterEachExtension(1); - - @RegisterExtension - Extension extension2 = new AfterEachExtension(2); - - @RegisterExtension - Extension extension3 = new AfterEachExtension(3); - - } - - @TestInstance(PER_CLASS) - static class DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase - extends AbstractTestCase { - - // @Order(3) - @RegisterExtension - Extension extension1 = new BeforeEachExtension(1); - - // @Order(2) - @RegisterExtension - Extension extension2 = new BeforeEachExtension(2); - - @Order(1) - @RegisterExtension - Extension extension3 = new BeforeEachExtension(3); - - } - - static class DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase extends AbstractTestCase { - - // @Order(3) - @RegisterExtension - static Extension extension1 = new BeforeEachExtension(1); - - // @Order(2) - @RegisterExtension - static Extension extension2 = new BeforeEachExtension(2); - - @Order(1) - @RegisterExtension - static Extension extension3 = new BeforeEachExtension(3); - - } - - static class InheritedDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase - extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { - } - - static class DefaultOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase - extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { - - // @Order(1) - @RegisterExtension - static Extension extension3 = new BeforeEachExtension(3); - - } - - static class ExplicitOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase - extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { - - @Order(2) - @RegisterExtension - static Extension extension2 = new BeforeEachExtension(2); - - } - - interface DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface { - - // @Order(3) - @RegisterExtension - static Extension extension1 = new BeforeEachExtension(1); - - // @Order(2) - @RegisterExtension - static Extension extension2 = new BeforeEachExtension(2); - - @Order(1) - @RegisterExtension - static Extension extension3 = new BeforeEachExtension(3); - - } - - static class DefaultOrderAndExplicitOrderExtensionRegistrationFromInterfaceTestCase extends AbstractTestCase - implements DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface { - } - - private static class BeforeEachExtension implements BeforeEachCallback { - - private final int id; - - BeforeEachExtension(int id) { - this.id = id; - } - - @Override - public void beforeEach(ExtensionContext context) { - callSequence.add(this.id); - } - - } - - private static class AfterEachExtension implements AfterEachCallback { - - private final int id; - - AfterEachExtension(int id) { - this.id = id; - } - - @Override - public void afterEach(ExtensionContext context) { - callSequence.add(this.id); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java deleted file mode 100644 index 9809dc3a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.jupiter.engine.execution.injection.sample.CustomAnnotation; -import org.junit.jupiter.engine.execution.injection.sample.CustomAnnotationParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.CustomType; -import org.junit.jupiter.engine.execution.injection.sample.CustomTypeParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.MapOfListsTypeBasedParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.MapOfStringsParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.NullIntegerParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.NumberParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.PrimitiveArrayParameterResolver; -import org.junit.jupiter.engine.execution.injection.sample.PrimitiveIntegerParameterResolver; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests that verify support for {@link ParameterResolver} - * extensions in the {@link JupiterTestEngine}. - * - * @since 5.0 - */ -class ParameterResolverTests extends AbstractJupiterTestEngineTests { - - @Test - void constructorInjection() { - EngineExecutionResults executionResults = executeTestsForClass(ConstructorInjectionTestCase.class); - - assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void constructorInjectionWithAnnotatedParameter() { - EngineExecutionResults executionResults = executeTestsForClass( - AnnotatedParameterConstructorInjectionTestCase.class); - - assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestsForMethodInjectionCases() { - EngineExecutionResults executionResults = executeTestsForClass(MethodInjectionTestCase.class); - - assertEquals(7, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(6, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestsForNullValuedMethodInjectionCases() { - EngineExecutionResults executionResults = executeTestsForClass(NullMethodInjectionTestCase.class); - Events tests = executionResults.testEvents(); - - assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - Predicate expectations = s -> - s.contains("NullIntegerParameterResolver") && - s.contains("resolved a null value for parameter") && - s.contains("but a primitive of type [int] is required"); - - tests.failed().assertEventsMatchExactly( - event( - test("injectPrimitive"), - finishedWithFailure(instanceOf(ParameterResolutionException.class), message(expectations)) - )); - // @formatter:on - } - - @Test - void executeTestsForPrimitiveIntegerMethodInjectionCases() { - EngineExecutionResults executionResults = executeTestsForClass(PrimitiveIntegerMethodInjectionTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestsForPrimitiveArrayMethodInjectionCases() { - EngineExecutionResults executionResults = executeTestsForClass(PrimitiveArrayMethodInjectionTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestsForPotentiallyIncompatibleTypeMethodInjectionCases() { - EngineExecutionResults executionResults = executeTestsForClass( - PotentiallyIncompatibleTypeMethodInjectionTestCase.class); - Events tests = executionResults.testEvents(); - - assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); - - // @formatter:off - Predicate expectations = s -> - s.contains("NumberParameterResolver") && - s.contains("resolved a value of type [java.lang.Integer]") && - s.contains("but a value assignment compatible with [java.lang.Double] is required"); - - tests.failed().assertEventsMatchExactly( - event( - test("doubleParameterInjection"), - finishedWithFailure(instanceOf(ParameterResolutionException.class), message(expectations) - ))); - // @formatter:on - } - - @Test - void executeTestsForMethodInjectionInBeforeAndAfterEachMethods() { - EngineExecutionResults executionResults = executeTestsForClass(BeforeAndAfterMethodInjectionTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestsForMethodInjectionInBeforeAndAfterAllMethods() { - EngineExecutionResults executionResults = executeTestsForClass(BeforeAndAfterAllMethodInjectionTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestsForMethodWithExtendWithAnnotation() { - EngineExecutionResults executionResults = executeTestsForClass(ExtendWithOnMethodTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - @Test - void executeTestsForParameterizedTypesSelectingByClass() { - assertEventsForParameterizedTypes(executeTestsForClass(ParameterizedTypeTestCase.class)); - } - - @Test - void executeTestsForParameterizedTypesSelectingByFullyQualifiedMethodName() { - String fqmn = ReflectionUtils.getFullyQualifiedMethodName(ParameterizedTypeTestCase.class, "testMapOfStrings", - Map.class); - - assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); - } - - @Test - void executeTestsForTypeBasedParameterResolverTestCaseSelectingByClass() { - assertEventsForParameterizedTypes(executeTestsForClass(TypeBasedParameterResolverTestCase.class)); - } - - @Test - void executeTestsForTypeBasedParameterResolverTestCaseSelectingByFullyQualifiedMethodName() { - String fqmn = ReflectionUtils.getFullyQualifiedMethodName(TypeBasedParameterResolverTestCase.class, - "testMapOfLists", Map.class); - - assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); - } - - @Disabled("Disabled until a decision has been made regarding #956") - @Test - void executeTestsForParameterizedTypesSelectingByFullyQualifiedMethodNameContainingGenericInfo() throws Exception { - Method method = ParameterizedTypeTestCase.class.getDeclaredMethod("testMapOfStrings", Map.class); - String genericParameterTypeName = method.getGenericParameterTypes()[0].getTypeName(); - String fqmn = String.format("%s#%s(%s)", ParameterizedTypeTestCase.class.getName(), "testMapOfStrings", - genericParameterTypeName); - - assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); - } - - private void assertEventsForParameterizedTypes(EngineExecutionResults executionResults) { - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); - assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); - } - - // ------------------------------------------------------------------- - - @ExtendWith(CustomTypeParameterResolver.class) - static class ConstructorInjectionTestCase { - - private final TestInfo outerTestInfo; - private final CustomType outerCustomType; - - ConstructorInjectionTestCase(TestInfo testInfo, CustomType customType) { - this.outerTestInfo = testInfo; - this.outerCustomType = customType; - } - - @Test - void test() { - assertNotNull(this.outerTestInfo); - assertNotNull(this.outerCustomType); - } - - @Nested - class NestedTestCase { - - private final TestInfo innerTestInfo; - private final CustomType innerCustomType; - - NestedTestCase(TestInfo testInfo, CustomType customType) { - this.innerTestInfo = testInfo; - this.innerCustomType = customType; - } - - @Test - void test() { - assertNotNull(outerTestInfo); - assertNotNull(outerCustomType); - assertNotNull(this.innerTestInfo); - assertNotNull(this.innerCustomType); - } - } - } - - @ExtendWith(CustomAnnotationParameterResolver.class) - static class AnnotatedParameterConstructorInjectionTestCase { - - private final TestInfo outerTestInfo; - private final CustomType outerCustomType; - - AnnotatedParameterConstructorInjectionTestCase(TestInfo testInfo, @CustomAnnotation CustomType customType) { - this.outerTestInfo = testInfo; - this.outerCustomType = customType; - } - - @Test - void test() { - assertNotNull(this.outerTestInfo); - assertNotNull(this.outerCustomType); - } - - @Nested - // See https://github.com/junit-team/junit5/issues/1345 - class AnnotatedConstructorParameterNestedTestCase { - - private final TestInfo innerTestInfo; - private final CustomType innerCustomType; - - AnnotatedConstructorParameterNestedTestCase(TestInfo testInfo, @CustomAnnotation CustomType customType) { - this.innerTestInfo = testInfo; - this.innerCustomType = customType; - } - - @Test - void test() { - assertNotNull(outerTestInfo); - assertNotNull(outerCustomType); - assertNotNull(this.innerTestInfo); - assertNotNull(this.innerCustomType); - } - } - } - - @ExtendWith({ CustomTypeParameterResolver.class, CustomAnnotationParameterResolver.class }) - static class MethodInjectionTestCase { - - @Test - void parameterInjectionOfTestInfo(TestInfo testInfo) { - assertNotNull(testInfo); - } - - @Test - void parameterInjectionWithCompetingResolversFail(@CustomAnnotation CustomType customType) { - // should fail - } - - @Test - void parameterInjectionByType(CustomType customType) { - assertNotNull(customType); - } - - @Test - void parameterInjectionByAnnotation(@CustomAnnotation String value) { - assertNotNull(value); - } - - // some overloaded methods - - @Test - void overloadedName() { - assertTrue(true); - } - - @Test - void overloadedName(CustomType customType) { - assertNotNull(customType); - } - - @Test - void overloadedName(CustomType customType, @CustomAnnotation String value) { - assertNotNull(customType); - assertNotNull(value); - } - } - - @ExtendWith(NullIntegerParameterResolver.class) - static class NullMethodInjectionTestCase { - - @Test - void injectWrapper(Integer number) { - assertNull(number); - } - - @Test - void injectPrimitive(int number) { - // should never be invoked since an int cannot be null - } - } - - @ExtendWith(PrimitiveIntegerParameterResolver.class) - static class PrimitiveIntegerMethodInjectionTestCase { - - @Test - void intPrimitive(int i) { - assertEquals(42, i); - } - } - - @ExtendWith(PrimitiveArrayParameterResolver.class) - static class PrimitiveArrayMethodInjectionTestCase { - - @Test - void primitiveArray(int... ints) { - assertArrayEquals(new int[] { 1, 2, 3 }, ints); - } - } - - @ExtendWith(NumberParameterResolver.class) - static class PotentiallyIncompatibleTypeMethodInjectionTestCase { - - @Test - void numberParameterInjection(Number number) { - assertEquals(Integer.valueOf(42), number); - } - - @Test - void integerParameterInjection(Integer number) { - assertEquals(Integer.valueOf(42), number); - } - - /** - * This test must fail, since {@link Double} is a {@link Number} but not an {@link Integer}. - * @see NumberParameterResolver - */ - @Test - void doubleParameterInjection(Double number) { - /* no-op */ - } - } - - static class BeforeAndAfterMethodInjectionTestCase { - - @BeforeEach - void before(TestInfo testInfo) { - assertEquals("custom name", testInfo.getDisplayName()); - } - - @Test - @DisplayName("custom name") - void customNamedTest() { - } - - @AfterEach - void after(TestInfo testInfo) { - assertEquals("custom name", testInfo.getDisplayName()); - } - } - - @DisplayName("custom class name") - static class BeforeAndAfterAllMethodInjectionTestCase { - - @BeforeAll - static void beforeAll(TestInfo testInfo) { - assertEquals("custom class name", testInfo.getDisplayName()); - } - - @Test - void aTest() { - } - - @AfterAll - static void afterAll(TestInfo testInfo) { - assertEquals("custom class name", testInfo.getDisplayName()); - } - } - - static class ExtendWithOnMethodTestCase { - - /** - * This set-up / tear-down method is here to verify that {@code @BeforeEach} - * and {@code @AfterEach} methods are properly invoked using the same - * {@code ExtensionRegistry} as the one used for the corresponding - * {@code @Test} method. - * - * @see #523 - */ - @BeforeEach - @AfterEach - void setUpAndTearDown(CustomType customType, @CustomAnnotation String value) { - assertNotNull(customType); - assertNotNull(value); - } - - @Test - @ExtendWith(CustomTypeParameterResolver.class) - @ExtendWith(CustomAnnotationParameterResolver.class) - void testMethodWithExtensionAnnotation(CustomType customType, @CustomAnnotation String value) { - assertNotNull(customType); - assertNotNull(value); - } - } - - static class ParameterizedTypeTestCase { - - @Test - @ExtendWith(MapOfStringsParameterResolver.class) - void testMapOfStrings(Map map) { - assertNotNull(map); - assertEquals("value", map.get("key")); - } - } - - static class TypeBasedParameterResolverTestCase { - @Test - @ExtendWith(MapOfListsTypeBasedParameterResolver.class) - void testMapOfLists(Map> map) { - assertNotNull(map); - assertEquals(asList(1, 42), map.get("ids")); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java deleted file mode 100644 index 5ae50c4c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.allOf; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests that verify support for programmatic extension registration - * via {@link RegisterExtension @RegisterExtension} in the {@link JupiterTestEngine}. - * - * @since 5.1 - * @see OrderedProgrammaticExtensionRegistrationTests - */ -class ProgrammaticExtensionRegistrationTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @Test - void instanceLevel() { - assertOneTestSucceeded(InstanceLevelExtensionRegistrationTestCase.class); - } - - @Test - void instanceLevelWithInjectedExtension() { - assertOneTestSucceeded(InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase.class); - } - - @Test - void instanceLevelWithTestInstancePerClassLifecycle() { - assertOneTestSucceeded(InstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase.class); - } - - @Test - void classLevel() { - assertOneTestSucceeded(ClassLevelExtensionRegistrationTestCase.class); - } - - @Test - void classLevelFromSuperclass() { - assertOneTestSucceeded(SubClassLevelExtensionRegistrationTestCase.class); - } - - @Test - void classLevelFromInterface() { - assertOneTestSucceeded(ExtensionRegistrationFromInterfaceTestCase.class); - } - - @Test - void instanceLevelWithInheritedAndHiddenExtensions() { - callSequence.clear(); - Class testClass = InstanceLevelExtensionRegistrationParentTestCase.class; - String parent = testClass.getSimpleName(); - assertOneTestSucceeded(testClass); - assertThat(callSequence).containsExactly( // - parent + " :: extension1: before test", // - parent + " :: extension2: before test" // - ); - - callSequence.clear(); - testClass = InstanceLevelExtensionRegistrationChildTestCase.class; - String child = testClass.getSimpleName(); - assertOneTestSucceeded(testClass); - assertThat(callSequence).containsExactly( // - parent + " :: extension1: before test", // - child + " :: extension2: before test", // - child + " :: extension3: before test" // - ); - } - - @Test - void classLevelWithInheritedAndHiddenExtensions() { - callSequence.clear(); - Class testClass = ClassLevelExtensionRegistrationParentTestCase.class; - String parent = testClass.getSimpleName(); - assertOneTestSucceeded(testClass); - assertThat(callSequence).containsExactly( // - parent + " :: extension1: before test", // - parent + " :: extension2: before test" // - ); - - callSequence.clear(); - testClass = ClassLevelExtensionRegistrationChildTestCase.class; - String child = testClass.getSimpleName(); - assertOneTestSucceeded(testClass); - assertThat(callSequence).containsExactly( // - parent + " :: extension1: before test", // - child + " :: extension2: before test", // - child + " :: extension3: before test" // - ); - } - - /** - * @since 5.5 - */ - @Test - void instanceLevelWithFieldThatDoesNotImplementAnExtensionApi() { - callSequence.clear(); - assertOneTestSucceeded(InstanceLevelCustomExtensionApiTestCase.class); - assertThat(callSequence).containsExactly( // - CustomExtensionImpl.class.getSimpleName() + " :: before test", // - CustomExtensionImpl.class.getSimpleName() + " :: doSomething()" // - ); - } - - /** - * @since 5.5 - */ - @Test - void classLevelWithFieldThatDoesNotImplementAnExtensionApi() { - callSequence.clear(); - assertOneTestSucceeded(ClassLevelCustomExtensionApiTestCase.class); - assertThat(callSequence).containsExactly( // - CustomExtensionImpl.class.getSimpleName() + " :: before test", // - CustomExtensionImpl.class.getSimpleName() + " :: doSomething()" // - ); - } - - /** - * @since 5.5 - */ - @Test - void instanceLevelWithPrivateField() { - Class testClass = InstanceLevelExtensionRegistrationWithPrivateFieldTestCase.class; - executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); - } - - /** - * @since 5.5 - */ - @Test - void classLevelWithPrivateField() { - Class testClass = ClassLevelExtensionRegistrationWithPrivateFieldTestCase.class; - executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); - } - - @Test - void instanceLevelWithNullField() { - Class testClass = InstanceLevelExtensionRegistrationWithNullFieldTestCase.class; - - executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, finishedWithFailure( - instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, null)))); - } - - @Test - void classLevelWithNullField() { - Class testClass = ClassLevelExtensionRegistrationWithNullFieldTestCase.class; - - executeTestsForClass(testClass).containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure( - instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, null)))); - } - - /** - * @since 5.5 - */ - @Test - void instanceLevelWithNonExtensionFieldValue() { - Class testClass = InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCase.class; - - executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, finishedWithFailure( - instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, String.class)))); - } - - /** - * @since 5.5 - */ - @Test - void classLevelWithNonExtensionFieldValue() { - Class testClass = ClassLevelExtensionRegistrationWithNonExtensionFieldValueTestCase.class; - - executeTestsForClass(testClass).containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure( - instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, String.class)))); - } - - private String expectedMessage(Class testClass, Class valueType) { - return "Failed to register extension via @RegisterExtension field [" + field(testClass) - + "]: field value's type [" + (valueType != null ? valueType.getName() : null) + "] must implement an [" - + Extension.class.getName() + "] API."; - } - - private Field field(Class testClass) { - try { - return testClass.getDeclaredField("extension"); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - @Test - void propagatesCheckedExceptionThrownDuringInitializationOfStaticField() { - assertClassFails(ClassLevelExplosiveCheckedExceptionTestCase.class, - allOf(instanceOf(ExceptionInInitializerError.class), cause(instanceOf(Exception.class), message("boom")))); - } - - @Test - void propagatesUncheckedExceptionThrownDuringInitializationOfStaticField() { - assertClassFails(ClassLevelExplosiveUncheckedExceptionTestCase.class, allOf( - instanceOf(ExceptionInInitializerError.class), cause(instanceOf(RuntimeException.class), message("boom")))); - } - - @Test - void propagatesErrorThrownDuringInitializationOfStaticField() { - assertClassFails(ClassLevelExplosiveErrorTestCase.class, allOf(instanceOf(Error.class), message("boom"))); - } - - @Test - void propagatesCheckedExceptionThrownDuringInitializationOfInstanceField() { - assertTestFails(InstanceLevelExplosiveCheckedExceptionTestCase.class, - allOf(instanceOf(Exception.class), message("boom"))); - } - - @Test - void propagatesUncheckedExceptionThrownDuringInitializationOfInstanceField() { - assertTestFails(InstanceLevelExplosiveUncheckedExceptionTestCase.class, - allOf(instanceOf(RuntimeException.class), message("boom"))); - } - - @Test - void propagatesErrorThrownDuringInitializationOfInstanceField() { - assertTestFails(InstanceLevelExplosiveErrorTestCase.class, allOf(instanceOf(Error.class), message("boom"))); - } - - @Test - void storesExtensionInRegistryOfNestedTestMethods() { - var results = executeTestsForClass(TwoNestedClassesTestCase.class); - - results.testEvents().assertStatistics(stats -> stats.succeeded(4)); - } - - private void assertClassFails(Class testClass, Condition causeCondition) { - EngineExecutionResults executionResults = executeTestsForClass(testClass); - executionResults.containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure(causeCondition)); - } - - private void assertTestFails(Class testClass, Condition causeCondition) { - executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, - finishedWithFailure(causeCondition)); - } - - private void assertOneTestSucceeded(Class testClass) { - executeTestsForClass(testClass).testEvents().assertStatistics( - stats -> stats.started(1).succeeded(1).skipped(0).aborted(0).failed(0)); - } - - // ------------------------------------------------------------------- - - private static void assertWisdom(CrystalBall crystalBall, String wisdom, String useCase) { - assertNotNull(crystalBall, useCase); - assertEquals("Outlook good", wisdom, useCase); - } - - static class InstanceLevelExtensionRegistrationTestCase { - - @RegisterExtension - final CrystalBall crystalBall = new CrystalBall("Outlook good"); - - @BeforeEach - void beforeEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeEach"); - } - - @Test - void test(String wisdom) { - assertWisdom(crystalBall, wisdom, "@Test"); - } - - @AfterEach - void afterEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterEach"); - } - - } - - @ExtendWith(ExtensionInjector.class) - static class InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase { - - @RegisterExtension - protected CrystalBall crystalBall; // Injected by ExtensionInjector. - - @BeforeEach - void beforeEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeEach"); - } - - @Test - void test(String wisdom) { - assertWisdom(crystalBall, wisdom, "@Test"); - } - - @AfterEach - void afterEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterEach"); - } - - } - - @TestInstance(PER_CLASS) - static class InstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase { - - @RegisterExtension - final CrystalBall crystalBall = new CrystalBall("Outlook good"); - - @BeforeAll - void beforeAll(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeAll"); - } - - @BeforeEach - void beforeEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeEach"); - } - - @Test - void test(String wisdom) { - assertWisdom(crystalBall, wisdom, "@Test"); - } - - @AfterEach - void afterEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterEach"); - } - - @AfterAll - void afterAll(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterAll"); - } - - } - - static class ClassLevelExtensionRegistrationTestCase { - - @RegisterExtension - static final CrystalBall crystalBall = new CrystalBall("Outlook good"); - - @BeforeAll - static void beforeAll(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeAll"); - } - - @BeforeEach - void beforeEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeEach"); - } - - @Test - void test(String wisdom) { - assertWisdom(crystalBall, wisdom, "@Test"); - } - - @AfterEach - void afterEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterEach"); - } - - @AfterAll - static void afterAll(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterAll"); - } - - } - - static class SubClassLevelExtensionRegistrationTestCase extends ClassLevelExtensionRegistrationTestCase { - - @Test - @Override - void test(String wisdom) { - assertWisdom(crystalBall, wisdom, "Overridden @Test"); - } - - } - - interface ClassLevelExtensionRegistrationInterface { - - @RegisterExtension - static CrystalBall crystalBall = new CrystalBall("Outlook good"); - - @BeforeAll - static void beforeAll(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeAll"); - } - - @BeforeEach - default void beforeEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@BeforeEach"); - } - - @AfterEach - default void afterEach(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterEach"); - } - - @AfterAll - static void afterAll(String wisdom) { - assertWisdom(crystalBall, wisdom, "@AfterAll"); - } - - } - - static class ExtensionRegistrationFromInterfaceTestCase implements ClassLevelExtensionRegistrationInterface { - - @Test - void test(String wisdom) { - assertWisdom(crystalBall, wisdom, "@Test"); - } - - } - - private static class CrystalBall implements ParameterResolver { - - private final String wisdom; - - public CrystalBall(String wisdom) { - this.wisdom = wisdom; - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == String.class; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return this.wisdom; - } - - } - - static class ClassLevelExtensionRegistrationParentTestCase { - - @RegisterExtension - static BeforeEachCallback extension1 = context -> callSequence.add( - ClassLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension1: before " - + context.getRequiredTestMethod().getName()); - - @RegisterExtension - static BeforeEachCallback extension2 = context -> callSequence.add( - ClassLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension2: before " - + context.getRequiredTestMethod().getName()); - - @Test - void test() { - } - - } - - static class ClassLevelExtensionRegistrationChildTestCase extends ClassLevelExtensionRegistrationParentTestCase { - - // "Hides" ClassLevelExtensionRegistrationParentTestCase.extension2 - @RegisterExtension - static BeforeEachCallback extension2 = context -> callSequence.add( - ClassLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension2: before " - + context.getRequiredTestMethod().getName()); - - @RegisterExtension - static BeforeEachCallback extension3 = context -> callSequence.add( - ClassLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension3: before " - + context.getRequiredTestMethod().getName()); - - } - - static class InstanceLevelExtensionRegistrationParentTestCase { - - @RegisterExtension - BeforeEachCallback extension1 = context -> callSequence.add( - InstanceLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension1: before " - + context.getRequiredTestMethod().getName()); - - @RegisterExtension - BeforeEachCallback extension2 = context -> callSequence.add( - InstanceLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension2: before " - + context.getRequiredTestMethod().getName()); - - @Test - void test() { - } - - } - - static class InstanceLevelExtensionRegistrationChildTestCase - extends InstanceLevelExtensionRegistrationParentTestCase { - - // "Hides" InstanceLevelExtensionRegistrationParentTestCase.extension2 - @RegisterExtension - BeforeEachCallback extension2 = context -> callSequence.add( - InstanceLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension2: before " - + context.getRequiredTestMethod().getName()); - - @RegisterExtension - BeforeEachCallback extension3 = context -> callSequence.add( - InstanceLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension3: before " - + context.getRequiredTestMethod().getName()); - - } - - /** - * This interface intentionally does not implement a supported {@link Extension} API. - */ - interface CustomExtension { - - void doSomething(); - - } - - static class CustomExtensionImpl implements CustomExtension, BeforeEachCallback { - - @Override - public void doSomething() { - callSequence.add(getClass().getSimpleName() + " :: doSomething()"); - } - - @Override - public void beforeEach(ExtensionContext context) throws Exception { - callSequence.add(getClass().getSimpleName() + " :: before " + context.getRequiredTestMethod().getName()); - } - } - - static class InstanceLevelCustomExtensionApiTestCase { - - @RegisterExtension - CustomExtension extension = new CustomExtensionImpl(); - - @Test - void test() { - this.extension.doSomething(); - } - - } - - static class ClassLevelCustomExtensionApiTestCase { - - @RegisterExtension - static CustomExtension extension = new CustomExtensionImpl(); - - @Test - void test() { - extension.doSomething(); - } - - } - - static class AbstractTestCase { - - @Test - void test() { - } - - } - - static class InstanceLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { - - @RegisterExtension - private Extension extension = new Extension() { - }; - - } - - static class ClassLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { - - @RegisterExtension - private static Extension extension = new Extension() { - }; - - } - - static class InstanceLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { - - @RegisterExtension - Extension extension; - - } - - static class ClassLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { - - @RegisterExtension - static Extension extension; - - } - - static class InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { - - @RegisterExtension - Object extension = "not an extension type"; - - } - - static class ClassLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { - - @RegisterExtension - static Object extension = "not an extension type"; - - } - - static class ClassLevelExplosiveCheckedExceptionTestCase extends AbstractTestCase { - - @RegisterExtension - static Extension field = new ExplosiveExtension(new Exception("boom")); - - } - - static class ClassLevelExplosiveUncheckedExceptionTestCase extends AbstractTestCase { - - @RegisterExtension - static Extension field = new ExplosiveExtension(new RuntimeException("boom")); - - } - - static class ClassLevelExplosiveErrorTestCase extends AbstractTestCase { - - @RegisterExtension - static Extension field = new ExplosiveExtension(new Error("boom")); - - } - - static class InstanceLevelExplosiveCheckedExceptionTestCase extends AbstractTestCase { - - @RegisterExtension - Extension field = new ExplosiveExtension(new Exception("boom")); - - } - - static class InstanceLevelExplosiveUncheckedExceptionTestCase extends AbstractTestCase { - - @RegisterExtension - Extension field = new ExplosiveExtension(new RuntimeException("boom")); - - } - - static class InstanceLevelExplosiveErrorTestCase extends AbstractTestCase { - - @RegisterExtension - Extension field = new ExplosiveExtension(new Error("boom")); - - } - - static class ExplosiveExtension implements Extension { - - ExplosiveExtension(Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(t); - } - - } - - /** - * Mimics a dependency injection framework such as Spring, Guice, CDI, etc., - * where the instance of the extension registered via - * {@link RegisterExtension @RegisterExtension} is managed by the DI - * framework and injected into the test instance. - */ - private static class ExtensionInjector implements TestInstancePostProcessor { - - private static final Predicate isCrystalBall = field -> CrystalBall.class.isAssignableFrom( - field.getType()); - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - // @formatter:off - AnnotationUtils.findAnnotatedFields(testInstance.getClass(), RegisterExtension.class, isCrystalBall).stream() - .findFirst() - .ifPresent(field -> { - try { - ReflectionUtils.makeAccessible(field); - field.set(testInstance, new CrystalBall("Outlook good")); - } - catch (Throwable t) { - ExceptionUtils.throwAsUncheckedException(t); - } - }); - // @formatter:on - } - - } - - static class TwoNestedClassesTestCase { - - @RegisterExtension - Extension extension = new LongParameterResolver(); - - @Nested - class A { - - @Test - void first(Long n) { - assertEquals(42L, n); - } - - @Test - void second(Long n) { - assertEquals(42L, n); - } - - } - - @Nested - class B { - - @Test - void first(Long n) { - assertEquals(42L, n); - } - - @Test - void second(Long n) { - assertEquals(42L, n); - } - - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java deleted file mode 100644 index dc2d5329..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RandomlyOrderedTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Events; - -/** - * @since 5.8 - */ -class RandomlyOrderedTests { - - private static final Set callSequence = Collections.synchronizedSet(new LinkedHashSet<>()); - - @Test - void randomSeedForClassAndMethodOrderingIsDeterministic() { - IntStream.range(0, 20).forEach(i -> { - callSequence.clear(); - var tests = executeTests(1618034L); - - tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); - assertThat(callSequence).containsExactlyInAnyOrder("B_TestCase#b", "B_TestCase#c", "B_TestCase#a", - "C_TestCase#b", "C_TestCase#c", "C_TestCase#a", "A_TestCase#b", "A_TestCase#c", "A_TestCase#a"); - }); - } - - private Events executeTests(long randomSeed) { - // @formatter:off - return EngineTestKit - .engine("junit-jupiter") - .configurationParameter(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, ClassOrderer.Random.class.getName()) - .configurationParameter(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, MethodOrderer.Random.class.getName()) - .configurationParameter(MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME, String.valueOf(randomSeed)) - .selectors(selectClass(A_TestCase.class), selectClass(B_TestCase.class), selectClass(C_TestCase.class)) - .execute() - .testEvents(); - // @formatter:on - } - - abstract static class BaseTestCase { - - @BeforeEach - void trackInvocations(TestInfo testInfo) { - var testClass = testInfo.getTestClass().get(); - var testMethod = testInfo.getTestMethod().get(); - - callSequence.add(testClass.getSimpleName() + "#" + testMethod.getName()); - } - - @Test - void a() { - } - - @Test - void b() { - } - - @Test - void c() { - } - } - - static class A_TestCase extends BaseTestCase { - } - - static class B_TestCase extends BaseTestCase { - } - - static class C_TestCase extends BaseTestCase { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java deleted file mode 100644 index 44514c06..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.displayName; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.RepetitionInfo; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests for {@link RepeatedTest @RepeatedTest} and supporting - * infrastructure. - * - * @since 5.0 - */ -class RepeatedTestTests extends AbstractJupiterTestEngineTests { - - private static int fortyTwo = 0; - - @BeforeEach - @AfterEach - void beforeAndAfterEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { - if (testInfo.getTestMethod().get().getName().equals("repeatedOnce")) { - assertThat(repetitionInfo.getCurrentRepetition()).isEqualTo(1); - assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(1); - } - else if (testInfo.getTestMethod().get().getName().equals("repeatedFortyTwoTimes")) { - assertThat(repetitionInfo.getCurrentRepetition()).isBetween(1, 42); - assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(42); - } - } - - @AfterAll - static void afterAll() { - assertEquals(42, fortyTwo); - } - - @RepeatedTest(1) - void repeatedOnce(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); - } - - @RepeatedTest(42) - void repeatedFortyTwoTimes(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).matches("repetition \\d+ of 42"); - fortyTwo++; - } - - @RepeatedTest(1) - @DisplayName("Repeat!") - void customDisplayName(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); - } - - @RepeatedTest(1) - @DisplayName(" \t ") - void customDisplayNameWithBlankName(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); - } - - @RepeatedTest(value = 1, name = "{displayName}") - @DisplayName("Repeat!") - void customDisplayNameWithPatternIncludingDisplayName(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("Repeat!"); - } - - @RepeatedTest(value = 1, name = "#{currentRepetition}") - @DisplayName("Repeat!") - void customDisplayNameWithPatternIncludingCurrentRepetition(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("#1"); - } - - @RepeatedTest(value = 1, name = "Repetition #{currentRepetition} for {displayName}") - @DisplayName("Repeat!") - void customDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("Repetition #1 for Repeat!"); - } - - @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) - @DisplayName("Repeat!") - void customDisplayNameWithPredefinedLongPattern(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("Repeat! :: repetition 1 of 1"); - } - - @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") - @DisplayName("Repeat!") - void customDisplayNameWithPatternIncludingDisplayNameCurrentRepetitionAndTotalRepetitions(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("Repeat! 1/1"); - } - - @RepeatedTest(value = 1, name = "Repetition #{currentRepetition} for {displayName}") - void defaultDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo testInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo( - "Repetition #1 for defaultDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo)"); - } - - @RepeatedTest(value = 5, name = "{displayName}") - void injectRepetitionInfo(TestInfo testInfo, RepetitionInfo repetitionInfo) { - assertThat(testInfo.getDisplayName()).isEqualTo("injectRepetitionInfo(TestInfo, RepetitionInfo)"); - assertThat(repetitionInfo.getCurrentRepetition()).isBetween(1, 5); - assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(5); - } - - @RepeatedTest(1) - void failsContainerOnEmptyPattern() { - executeTest("testWithEmptyPattern").assertThatEvents() // - .haveExactly(1, event(container(), displayName("testWithEmptyPattern()"), // - finishedWithFailure(message(value -> value.contains("must be declared with a non-empty name"))))); - } - - @RepeatedTest(1) - void failsContainerOnBlankPattern() { - executeTest("testWithBlankPattern").assertThatEvents() // - .haveExactly(1, event(container(), displayName("testWithBlankPattern()"), // - finishedWithFailure(message(value -> value.contains("must be declared with a non-empty name"))))); - } - - @RepeatedTest(1) - void failsContainerOnNegativeRepeatCount() { - executeTest("negativeRepeatCount").assertThatEvents() // - .haveExactly(1, event(container(), displayName("negativeRepeatCount()"), // - finishedWithFailure(message(value -> value.contains("must be declared with a positive 'value'"))))); - } - - @RepeatedTest(1) - void failsContainerOnZeroRepeatCount() { - executeTest("zeroRepeatCount").assertThatEvents() // - .haveExactly(1, event(container(), displayName("zeroRepeatCount()"), // - finishedWithFailure(message(value -> value.contains("must be declared with a positive 'value'"))))); - } - - private Events executeTest(String methodName) { - return executeTests(selectMethod(TestCase.class, methodName)).allEvents(); - } - - static class TestCase { - - @RepeatedTest(value = 1, name = "") - void testWithEmptyPattern() { - } - - @RepeatedTest(value = 1, name = " \t ") - void testWithBlankPattern() { - } - - @RepeatedTest(-99) - void negativeRepeatCount() { - } - - @RepeatedTest(0) - void zeroRepeatCount() { - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java deleted file mode 100644 index 81630493..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeoutException; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.ThrowingConsumer; - -/** - * @since 5.5 - */ -class SameThreadTimeoutInvocationTests { - - @Test - void resetsInterruptFlag() { - var exception = assertThrows(TimeoutException.class, () -> withExecutor(executor -> { - var delegate = new EventuallyInterruptibleInvocation(); - var duration = new TimeoutDuration(1, NANOSECONDS); - var timeoutInvocation = new SameThreadTimeoutInvocation<>(delegate, duration, executor, () -> "execution"); - timeoutInvocation.proceed(); - })); - assertFalse(Thread.currentThread().isInterrupted()); - assertThat(exception).hasMessage("execution timed out after 1 nanosecond"); - } - - private void withExecutor(ThrowingConsumer consumer) throws Throwable { - ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - try { - consumer.accept(executor); - } - finally { - executor.shutdown(); - assertTrue(executor.awaitTermination(5, SECONDS)); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java deleted file mode 100644 index 8ea41562..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.condition.OS.WINDOWS; - -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout.ThreadMode; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.jupiter.engine.execution.ExtensionValuesStore; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; -import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; - -/** - * @since 5.9 - */ -@DisplayName("SeparateThreadTimeoutInvocation") -class SeparateThreadTimeoutInvocationTest { - - private static final long PREEMPTIVE_TIMEOUT_MILLIS = WINDOWS.isCurrentOs() ? 1000 : 100; - - @Test - @DisplayName("throws timeout exception when timeout duration is exceeded") - void throwsTimeoutException() { - AtomicReference threadName = new AtomicReference<>(); - var invocation = aSeparateThreadInvocation(() -> { - threadName.set(Thread.currentThread().getName()); - Thread.sleep(PREEMPTIVE_TIMEOUT_MILLIS * 2); - return null; - }); - - assertThatThrownBy(invocation::proceed) // - .hasMessage("method() timed out after " + PREEMPTIVE_TIMEOUT_MILLIS + " milliseconds") // - .isInstanceOf(TimeoutException.class) // - .hasRootCauseMessage("Execution timed out in thread " + threadName.get()); - } - - @Test - @DisplayName("executes invocation in a separate thread") - void runsInvocationUsingSeparateThread() throws Throwable { - var invocationThreadName = aSeparateThreadInvocation(() -> Thread.currentThread().getName()).proceed(); - assertThat(invocationThreadName).isNotEqualTo(Thread.currentThread().getName()); - } - - @Test - @DisplayName("throws invocation exception") - void shouldThrowInvocationException() { - var invocation = aSeparateThreadInvocation(() -> { - throw new RuntimeException("hi!"); - }); - assertThatThrownBy(invocation::proceed) // - .isInstanceOf(RuntimeException.class) // - .hasMessage("hi!"); - } - - private static SeparateThreadTimeoutInvocation aSeparateThreadInvocation(Invocation invocation) { - var namespace = ExtensionContext.Namespace.create(SeparateThreadTimeoutInvocationTest.class); - var store = new NamespaceAwareStore(new ExtensionValuesStore(null), namespace); - var parameters = new TimeoutInvocationParameters<>(invocation, - new TimeoutDuration(PREEMPTIVE_TIMEOUT_MILLIS, MILLISECONDS), () -> "method()"); - return (SeparateThreadTimeoutInvocation) new TimeoutInvocationFactory(store) // - .create(ThreadMode.SEPARATE_THREAD, parameters); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java deleted file mode 100644 index abaf97bd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * Demo extension for auto-detection of extensions loaded via Java's - * {@link java.util.ServiceLoader} mechanism. - * - * @since 5.0 - */ -public class ServiceLoaderExtension implements BeforeAllCallback { - - @Override - public void beforeAll(ExtensionContext context) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java deleted file mode 100644 index ddaaa214..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.nio.file.Files.deleteIfExists; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; -import static org.junit.jupiter.api.io.CleanupMode.NEVER; -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.io.IOException; -import java.nio.file.Path; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.launcher.LauncherDiscoveryRequest; - -/** - * Test that {@linkplain TempDir temporary directories} are not deleted with - * {@link CleanupMode#NEVER}, are deleted with {@link CleanupMode#ON_SUCCESS} - * but only if the test passes, and are always deleted with {@link CleanupMode#ALWAYS}. - * - * @see CleanupMode - * @see TempDir - * @since 5.9 - */ -class TempDirectoryCleanupTests extends AbstractJupiterTestEngineTests { - - @Nested - class TempDirFieldTests { - - private static Path defaultFieldDir; - private static Path neverFieldDir; - private static Path alwaysFieldDir; - private static Path onSuccessFailingFieldDir; - private static Path onSuccessPassingFieldDir; - - /** - * Ensure the cleanup mode defaults to ALWAYS for fields. - *

- * Expect the TempDir to be cleaned up. - */ - @Test - void cleanupModeDefaultField() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(DefaultFieldCase.class, "testDefaultField"))// - .build(); - executeTests(request); - - assertThat(defaultFieldDir).doesNotExist(); - } - - /** - * Ensure that a custom, global cleanup mode is used for fields. - *

- * Expect the TempDir NOT to be cleaned up if set to NEVER. - */ - @Test - void cleanupModeCustomDefaultField() { - LauncherDiscoveryRequest request = request()// - .configurationParameter(TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME, "never")// - .selectors(selectMethod(DefaultFieldCase.class, "testDefaultField"))// - .build(); - executeTests(request); - - assertThat(defaultFieldDir).exists(); - } - - /** - * Ensure that NEVER cleanup modes are obeyed for fields. - *

- * Expect the TempDir not to be cleaned up. - */ - @Test - void cleanupModeNeverField() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(NeverFieldCase.class, "testNeverField"))// - .build(); - executeTests(request); - - assertThat(neverFieldDir).exists(); - } - - /** - * Ensure that ALWAYS cleanup modes are obeyed for fields. - *

- * Expect the TempDir to be cleaned up. - */ - @Test - void cleanupModeAlwaysField() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(AlwaysFieldCase.class, "testAlwaysField"))// - .build(); - executeTests(request); - - assertThat(alwaysFieldDir).doesNotExist(); - } - - /** - * Ensure that ON_SUCCESS cleanup modes are obeyed for passing field tests. - *

- * Expect the TempDir to be cleaned up. - */ - @Test - void cleanupModeOnSuccessPassingField() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(OnSuccessPassingFieldCase.class, "testOnSuccessPassingField"))// - .build(); - executeTests(request); - - assertThat(onSuccessPassingFieldDir).doesNotExist(); - } - - /** - * Ensure that ON_SUCCESS cleanup modes are obeyed for failing field tests. - *

- * Expect the TempDir not to be cleaned up. - */ - @Test - void cleanupModeOnSuccessFailingField() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(OnSuccessFailingFieldCase.class, "testOnSuccessFailingField"))// - .build(); - executeTests(request); - - assertThat(onSuccessFailingFieldDir).exists(); - } - - @AfterAll - static void afterAll() throws IOException { - deleteIfExists(defaultFieldDir); - deleteIfExists(neverFieldDir); - deleteIfExists(alwaysFieldDir); - deleteIfExists(onSuccessFailingFieldDir); - deleteIfExists(onSuccessPassingFieldDir); - } - - // ------------------------------------------------------------------- - - static class DefaultFieldCase { - - @TempDir - Path defaultFieldDir; - - @Test - void testDefaultField() { - TempDirFieldTests.defaultFieldDir = defaultFieldDir; - } - } - - static class NeverFieldCase { - - @TempDir(cleanup = NEVER) - Path neverFieldDir; - - @Test - void testNeverField() { - TempDirFieldTests.neverFieldDir = neverFieldDir; - } - } - - static class AlwaysFieldCase { - - @TempDir(cleanup = ALWAYS) - Path alwaysFieldDir; - - @Test - void testAlwaysField() { - TempDirFieldTests.alwaysFieldDir = alwaysFieldDir; - } - } - - static class OnSuccessPassingFieldCase { - - @TempDir(cleanup = ON_SUCCESS) - Path onSuccessPassingFieldDir; - - @Test - void testOnSuccessPassingField() { - TempDirFieldTests.onSuccessPassingFieldDir = onSuccessPassingFieldDir; - } - } - - static class OnSuccessFailingFieldCase { - - @TempDir(cleanup = ON_SUCCESS) - Path onSuccessFailingFieldDir; - - @Test - void testOnSuccessFailingField() { - TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; - fail(); - } - } - - } - - @Nested - class TempDirParameterTests { - - private static Path defaultParameterDir; - private static Path neverParameterDir; - private static Path alwaysParameterDir; - private static Path onSuccessFailingParameterDir; - private static Path onSuccessPassingParameterDir; - - /** - * Ensure the cleanup mode defaults to ALWAYS for parameters. - *

- * Expect the TempDir to be cleaned up. - */ - @Test - void cleanupModeDefaultParameter() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(DefaultParameterCase.class, "testDefaultParameter", "java.nio.file.Path"))// - .build(); - executeTests(request); - - assertThat(defaultParameterDir).doesNotExist(); - } - - /** - * Ensure that a custom, global cleanup mode is used for parameters. - *

- * Expect the TempDir NOT to be cleaned up if set to NEVER. - */ - @Test - void cleanupModeCustomDefaultParameter() { - LauncherDiscoveryRequest request = request()// - .configurationParameter(TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME, "never")// - .selectors(selectMethod(DefaultParameterCase.class, "testDefaultParameter", "java.nio.file.Path"))// - .build(); - executeTests(request); - - assertThat(defaultParameterDir).exists(); - } - - /** - * Ensure that NEVER cleanup modes are obeyed for parameters. - *

- * Expect the TempDir not to be cleaned up. - */ - @Test - void cleanupModeNeverParameter() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(NeverParameterCase.class, "testNeverParameter", "java.nio.file.Path"))// - .build(); - executeTests(request); - - assertThat(neverParameterDir).exists(); - } - - /** - * Ensure that ALWAYS cleanup modes are obeyed for parameters. - *

- * Expect the TempDir to be cleaned up. - */ - @Test - void cleanupModeAlwaysParameter() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(AlwaysParameterCase.class, "testAlwaysParameter", "java.nio.file.Path"))// - .build(); - executeTests(request); - - assertThat(alwaysParameterDir).doesNotExist(); - } - - /** - * Ensure that ON_SUCCESS cleanup modes are obeyed for passing parameter tests. - *

- * Expect the TempDir to be cleaned up. - */ - @Test - void cleanupModeOnSuccessPassingParameter() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(OnSuccessPassingParameterCase.class, "testOnSuccessPassingParameter", - "java.nio.file.Path"))// - .build(); - executeTests(request); - - assertThat(onSuccessPassingParameterDir).doesNotExist(); - } - - /** - * Ensure that ON_SUCCESS cleanup modes are obeyed for failing parameter tests. - *

- * Expect the TempDir not to be cleaned up. - */ - @Test - void cleanupModeOnSuccessFailingParameter() { - LauncherDiscoveryRequest request = request()// - .selectors(selectMethod(OnSuccessFailingParameterCase.class, "testOnSuccessFailingParameter", - "java.nio.file.Path"))// - .build(); - executeTests(request); - - assertThat(onSuccessFailingParameterDir).exists(); - } - - @AfterAll - static void afterAll() throws IOException { - deleteIfExists(defaultParameterDir); - deleteIfExists(neverParameterDir); - deleteIfExists(alwaysParameterDir); - deleteIfExists(onSuccessFailingParameterDir); - deleteIfExists(onSuccessPassingParameterDir); - } - - // ------------------------------------------------------------------- - - static class DefaultParameterCase { - - @Test - void testDefaultParameter(@TempDir Path defaultParameterDir) { - TempDirParameterTests.defaultParameterDir = defaultParameterDir; - } - } - - static class NeverParameterCase { - - @Test - void testNeverParameter(@TempDir(cleanup = NEVER) Path neverParameterDir) { - TempDirParameterTests.neverParameterDir = neverParameterDir; - } - } - - static class AlwaysParameterCase { - - @Test - void testAlwaysParameter(@TempDir(cleanup = ALWAYS) Path alwaysParameterDir) { - TempDirParameterTests.alwaysParameterDir = alwaysParameterDir; - } - } - - static class OnSuccessPassingParameterCase { - - @Test - void testOnSuccessPassingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessPassingParameterDir) { - TempDirParameterTests.onSuccessPassingParameterDir = onSuccessPassingParameterDir; - } - } - - static class OnSuccessFailingParameterCase { - - @Test - void testOnSuccessFailingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessFailingParameterDir) { - TempDirParameterTests.onSuccessFailingParameterDir = onSuccessFailingParameterDir; - fail(); - } - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java deleted file mode 100644 index 60c9f383..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java +++ /dev/null @@ -1,1308 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Deque; -import java.util.LinkedList; -import java.util.function.Supplier; - -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests for the legacy behavior of the {@link TempDirectory} - * extension to create a single temp directory per context, i.e. test class or - * method. - * - * @since 5.4 - */ -@DisplayName("TempDirectory extension (per context)") -class TempDirectoryPerContextTests extends AbstractJupiterTestEngineTests { - - @SuppressWarnings("deprecation") - @Override - protected EngineExecutionResults executeTestsForClass(Class testClass) { - return executeTests(request() // - .selectors(selectClass(testClass)) // - .configurationParameter(TempDir.SCOPE_PROPERTY_NAME, TempDirectory.Scope.PER_CONTEXT.toString()) // - .build()); - } - - @BeforeEach - @AfterEach - void resetStaticVariables() { - BaseSharedTempDirFieldInjectionTestCase.staticTempDir = null; - BaseSharedTempDirParameterInjectionTestCase.tempDir = null; - BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.clear(); - BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.clear(); - } - - @Test - @DisplayName("does not prevent constructor parameter resolution") - void tempDirectoryDoesNotPreventConstructorParameterResolution() { - executeTestsForClass(TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("does not prevent user from deleting the temp dir within a test") - void tempDirectoryDoesNotPreventUserFromDeletingTempDir() { - executeTestsForClass(UserTempDirectoryDeletionDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("is capable of removing a read-only file") - void nonWritableFileDoesNotCauseFailure() { - executeTestsForClass(NonWritableFileDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("is capable of removing non-executable, non-writable, or non-readable directories and folders") - void nonMintPermissionsContentDoesNotCauseFailure() { - executeTestsForClass(NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(13).succeeded(13)); - } - - @Test - @DisplayName("is capable of removing a directory when its permissions have been changed") - void nonMintPermissionsDoNotCauseFailure() { - executeTestsForClass(NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(42).succeeded(42)); - } - - @Test - @DisabledOnOs(OS.WINDOWS) - @DisplayName("is capable of removing a read-only file in a read-only dir") - void readOnlyFileInReadOnlyDirDoesNotCauseFailure() { - executeTestsForClass(ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisabledOnOs(OS.WINDOWS) - @DisplayName("is capable of removing a read-only file in a dir in a read-only dir") - void readOnlyFileInNestedReadOnlyDirDoesNotCauseFailure() { - executeTestsForClass(ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("can be used via instance field inside nested test classes") - void canBeUsedViaInstanceFieldInsideNestedTestClasses() { - executeTestsForClass(TempDirUsageInsideNestedClassesTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(3).succeeded(3)); - } - - @Test - @DisplayName("can be used via static field inside nested test classes") - void canBeUsedViaStaticFieldInsideNestedTestClasses() { - executeTestsForClass(StaticTempDirUsageInsideNestedClassTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(2).succeeded(2)); - } - - @Test - @DisplayName("resolves java.io.File injection type") - void resolvesFileInstances() { - executeTestsForClass(FileInjectionTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Nested - @DisplayName("resolves shared temp dir") - @TestMethodOrder(OrderAnnotation.class) - class SharedTempDir { - - @Test - @DisplayName("when @TempDir is used on static field") - @Order(10) - void resolvesSharedTempDirWhenAnnotationIsUsedOnStaticField() { - assertSharedTempDirForFieldInjection(AnnotationOnStaticFieldTestCase.class); - } - - @Test - @DisplayName("when @TempDir is used on static field and @BeforeAll method parameter") - @Order(11) - void resolvesSharedTempDirWhenAnnotationIsUsedOnStaticFieldAndBeforeAllMethodParameter() { - assertSharedTempDirForFieldInjection(AnnotationOnStaticFieldAndBeforeAllMethodParameterTestCase.class); - } - - @Test - @DisplayName("when @TempDir is used on instance field and @BeforeAll method parameter") - @Order(14) - void resolvesSharedTempDirWhenAnnotationIsUsedOnInstanceFieldAndBeforeAllMethodParameter() { - assertSharedTempDirForFieldInjection(AnnotationOnInstanceFieldAndBeforeAllMethodParameterTestCase.class); - } - - @Test - @DisplayName("when @TempDir is used on instance field and @BeforeAll method parameter with @TestInstance(PER_CLASS)") - @Order(15) - void resolvesSharedTempDirWhenAnnotationIsUsedOnInstanceFieldAndBeforeAllMethodParameterWithTestInstancePerClass() { - assertSharedTempDirForFieldInjection( - AnnotationOnInstanceFieldAndBeforeAllMethodParameterWithTestInstancePerClassTestCase.class); - } - - @Test - @DisplayName("when @TempDir is used on @BeforeAll method parameter") - @Order(23) - void resolvesSharedTempDirWhenAnnotationIsUsedOnBeforeAllMethodParameter() { - assertSharedTempDirForParameterInjection(AnnotationOnBeforeAllMethodParameterTestCase.class); - } - - @Test - @DisplayName("when @TempDir is used on @BeforeAll method parameter with @TestInstance(PER_CLASS)") - @Order(24) - void resolvesSharedTempDirWhenAnnotationIsUsedOnBeforeAllMethodParameterWithTestInstancePerClass() { - assertSharedTempDirForParameterInjection( - AnnotationOnBeforeAllMethodParameterWithTestInstancePerClassTestCase.class); - } - - private void assertSharedTempDirForFieldInjection( - Class testClass) { - - assertSharedTempDirForParameterInjection(testClass, - () -> BaseSharedTempDirFieldInjectionTestCase.staticTempDir); - } - - private void assertSharedTempDirForParameterInjection( - Class testClass) { - - assertSharedTempDirForParameterInjection(testClass, - () -> BaseSharedTempDirParameterInjectionTestCase.tempDir); - } - - private void assertSharedTempDirForParameterInjection(Class testClass, Supplier staticTempDir) { - var results = executeTestsForClass(testClass); - - results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2)); - assertThat(staticTempDir.get()).isNotNull().doesNotExist(); - } - - } - - @Nested - @DisplayName("resolves separate temp dirs") - @TestMethodOrder(OrderAnnotation.class) - class SeparateTempDirs { - - @Test - @DisplayName("when @TempDir is used on instance field") - @Order(11) - void resolvesSeparateTempDirWhenAnnotationIsUsedOnInstanceField() { - assertSeparateTempDirsForFieldInjection( - SeparateTempDirsWhenUsedOnForEachLifecycleMethodsFieldInjectionTestCase.class); - assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getFirst()).doesNotExist(); - assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getLast()).doesNotExist(); - } - - @Test - @DisplayName("when @TempDir is used on instance field with @TestInstance(PER_CLASS)") - @Order(12) - void resolvesSeparateTempDirWhenAnnotationIsUsedOnInstanceFieldWithTestInstancePerClass() { - assertSeparateTempDirsForFieldInjection( - SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassFieldInjectionTestCase.class); - assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getFirst()).doesNotExist(); - assertThat(BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.getLast()).doesNotExist(); - } - - @Test - @DisplayName("when @TempDir is used on @BeforeEach/@AfterEach method parameters") - @Order(21) - void resolvesSeparateTempDirsWhenUsedOnForEachLifecycleMethods() { - assertSeparateTempDirsForParameterInjection( - SeparateTempDirsWhenUsedOnForEachLifecycleMethodsParameterInjectionTestCase.class); - assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getFirst()).doesNotExist(); - assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getLast()).doesNotExist(); - } - - @Test - @DisplayName("when @TempDir is used on @BeforeEach/@AfterEach method parameters with @TestInstance(PER_CLASS)") - @Order(22) - void resolvesSeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClass() { - assertSeparateTempDirsForParameterInjection( - SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassParameterInjectionTestCase.class); - assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getFirst()).doesNotExist(); - assertThat(BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.getLast()).doesNotExist(); - } - - @Test - @DisplayName("for @AfterAll method parameter when @TempDir is not used on constructor or @BeforeAll method parameter") - @Order(31) - void resolvesSeparateTempDirWhenAnnotationIsUsedOnAfterAllMethodParameterOnly() { - var results = executeTestsForClass(AnnotationOnAfterAllMethodParameterTestCase.class); - - results.testEvents().assertStatistics(stats -> stats.started(1).failed(0).succeeded(1)); - assertThat(AnnotationOnAfterAllMethodParameterTestCase.firstTempDir).isNotNull().doesNotExist(); - assertThat(AnnotationOnAfterAllMethodParameterTestCase.secondTempDir).isNotNull().doesNotExist(); - } - - } - - @Nested - @DisplayName("reports failure") - @TestMethodOrder(OrderAnnotation.class) - class Failures { - - @Test - @DisplayName("when @TempDir is used on static field of an unsupported type") - @Order(20) - void onlySupportsStaticFieldsOfTypePathAndFile() { - var results = executeTestsForClass(AnnotationOnStaticFieldWithUnsupportedTypeTestCase.class); - - assertSingleFailedContainer(results, ExtensionConfigurationException.class, - "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); - } - - @Test - @DisplayName("when @TempDir is used on instance field of an unsupported type") - @Order(21) - void onlySupportsInstanceFieldsOfTypePathAndFile() { - var results = executeTestsForClass(AnnotationOnInstanceFieldWithUnsupportedTypeTestCase.class); - - assertSingleFailedTest(results, ExtensionConfigurationException.class, - "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); - } - - @Test - @DisplayName("when @TempDir is used on parameter of an unsupported type") - @Order(22) - void onlySupportsParametersOfTypePathAndFile() { - var results = executeTestsForClass(InvalidTestCase.class); - - // @formatter:off - TempDirectoryPerContextTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), - message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), - cause( - instanceOf(ExtensionConfigurationException.class), - message("Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String"))); - // @formatter:on - } - - @Test - @DisplayName("when @TempDir is used on constructor parameter") - @Order(30) - void doesNotSupportTempDirAnnotationOnConstructorParameter() { - var results = executeTestsForClass(AnnotationOnConstructorParameterTestCase.class); - - assertSingleFailedTest(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); - } - - @Test - @DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)") - @Order(31) - void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerClass() { - var results = executeTestsForClass(AnnotationOnConstructorParameterWithTestInstancePerClassTestCase.class); - - assertSingleFailedContainer(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); - } - - } - - @Nested - @DisplayName("supports @TempDir") - @TestMethodOrder(OrderAnnotation.class) - class PrivateFields { - - @Test - @DisplayName("on private static field") - @Order(10) - void supportsPrivateInstanceFields() { - executeTestsForClass(AnnotationOnPrivateStaticFieldTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("on private instance field") - @Order(11) - void supportsPrivateStaticFields() { - executeTestsForClass(AnnotationOnPrivateInstanceFieldTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - } - - private static void assertSingleFailedContainer(EngineExecutionResults results, Class clazz, - String message) { - - assertSingleFailedContainer(results, instanceOf(clazz), message(actual -> actual.contains(message))); - } - - @SafeVarargs - @SuppressWarnings("varargs") - private static void assertSingleFailedContainer(EngineExecutionResults results, - Condition... conditions) { - - results.containerEvents()// - .assertStatistics(stats -> stats.started(2).failed(1).succeeded(1))// - .assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); - } - - private static void assertSingleFailedTest(EngineExecutionResults results, Class clazz, - String message) { - - assertSingleFailedTest(results, instanceOf(clazz), message(actual -> actual.contains(message))); - } - - @SafeVarargs - @SuppressWarnings("varargs") - private static void assertSingleFailedTest(EngineExecutionResults results, Condition... conditions) { - results.testEvents().assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); - results.testEvents().assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); - } - - private void assertSeparateTempDirsForFieldInjection( - Class testClass) { - - assertResolvesSeparateTempDirs(testClass, BaseSeparateTempDirsFieldInjectionTestCase.tempDirs); - } - - private void assertSeparateTempDirsForParameterInjection( - Class testClass) { - - assertResolvesSeparateTempDirs(testClass, BaseSeparateTempDirsParameterInjectionTestCase.tempDirs); - } - - private void assertResolvesSeparateTempDirs(Class testClass, Deque tempDirs) { - var results = executeTestsForClass(testClass); - - results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2)); - assertThat(tempDirs).hasSize(2); - } - - // ------------------------------------------------------------------------- - - static class BaseSharedTempDirFieldInjectionTestCase { - - static Path staticTempDir; - - @TempDir - Path tempDir; - - @BeforeEach - void beforeEach(@TempDir Path tempDir) { - if (BaseSharedTempDirFieldInjectionTestCase.staticTempDir != null) { - assertSame(BaseSharedTempDirFieldInjectionTestCase.staticTempDir, tempDir); - } - else { - BaseSharedTempDirFieldInjectionTestCase.staticTempDir = tempDir; - } - check(tempDir); - } - - @Test - void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @Test - void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @AfterEach - void afterEach(@TempDir Path tempDir) { - check(tempDir); - } - - void check(Path tempDir) { - assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir)// - .isNotNull()// - .isSameAs(tempDir)// - .isSameAs(this.tempDir); - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnStaticFieldTestCase extends BaseSharedTempDirFieldInjectionTestCase { - - @TempDir - static Path staticTempPath; - - @TempDir - static File staticTempFile; - - @Override - void check(Path tempDir) { - assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir)// - .isNotNull()// - .isSameAs(AnnotationOnStaticFieldTestCase.staticTempPath)// - .isSameAs(tempDir)// - .isSameAs(this.tempDir); - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnStaticFieldAndBeforeAllMethodParameterTestCase extends AnnotationOnStaticFieldTestCase { - - @BeforeAll - static void beforeAll(@TempDir Path tempDir) { - assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir).isNull(); - BaseSharedTempDirFieldInjectionTestCase.staticTempDir = tempDir; - assertThat(AnnotationOnStaticFieldTestCase.staticTempFile).isNotNull(); - assertThat(AnnotationOnStaticFieldTestCase.staticTempPath)// - .isNotNull()// - .isSameAs(tempDir); - assertThat(AnnotationOnStaticFieldTestCase.staticTempFile.toPath().toAbsolutePath())// - .isEqualTo(AnnotationOnStaticFieldTestCase.staticTempPath.toAbsolutePath()); - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnInstanceFieldAndBeforeAllMethodParameterTestCase - extends BaseSharedTempDirFieldInjectionTestCase { - - @BeforeAll - static void beforeAll(@TempDir Path tempDir) { - assertThat(BaseSharedTempDirFieldInjectionTestCase.staticTempDir).isNull(); - BaseSharedTempDirFieldInjectionTestCase.staticTempDir = tempDir; - assertTrue(Files.exists(tempDir)); - } - - } - - @TestInstance(PER_CLASS) - static class AnnotationOnInstanceFieldAndBeforeAllMethodParameterWithTestInstancePerClassTestCase - extends AnnotationOnInstanceFieldAndBeforeAllMethodParameterTestCase { - - } - - static class AnnotationOnPrivateInstanceFieldTestCase { - - @SuppressWarnings("unused") - @TempDir - private Path tempDir; - - @Test - void test() { - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnPrivateStaticFieldTestCase { - - @SuppressWarnings("unused") - @TempDir - private static Path tempDir; - - @Test - void test() { - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnStaticFieldWithUnsupportedTypeTestCase { - - @SuppressWarnings("unused") - @TempDir - static String tempDir; - - @Test - void test1() { - } - - } - - static class AnnotationOnInstanceFieldWithUnsupportedTypeTestCase { - - @SuppressWarnings("unused") - @TempDir - String tempDir; - - @Test - void test1() { - } - - } - - static class BaseSharedTempDirParameterInjectionTestCase { - - static Path tempDir; - - @BeforeEach - void beforeEach(@TempDir Path tempDir) { - check(tempDir); - } - - @Test - void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @Test - void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @AfterEach - void afterEach(@TempDir Path tempDir) { - check(tempDir); - } - - static void check(Path tempDir) { - assertThat(BaseSharedTempDirParameterInjectionTestCase.tempDir).isNotNull().isSameAs(tempDir); - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnConstructorParameterTestCase { - - AnnotationOnConstructorParameterTestCase(@SuppressWarnings("unused") @TempDir Path tempDir) { - // never called - } - - @Test - void test() { - // never called - } - - } - - @TestInstance(PER_CLASS) - static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase - extends AnnotationOnConstructorParameterTestCase { - - AnnotationOnConstructorParameterWithTestInstancePerClassTestCase(@TempDir Path tempDir) { - super(tempDir); - } - } - - static class AnnotationOnBeforeAllMethodParameterTestCase extends BaseSharedTempDirParameterInjectionTestCase { - - @BeforeAll - static void beforeAll(@TempDir Path tempDir) { - assertThat(BaseSharedTempDirParameterInjectionTestCase.tempDir).isNull(); - BaseSharedTempDirParameterInjectionTestCase.tempDir = tempDir; - check(tempDir); - } - } - - @TestInstance(PER_CLASS) - static class AnnotationOnBeforeAllMethodParameterWithTestInstancePerClassTestCase - extends BaseSharedTempDirParameterInjectionTestCase { - - @BeforeAll - void beforeAll(@TempDir Path tempDir) { - assertThat(BaseSharedTempDirParameterInjectionTestCase.tempDir).isNull(); - BaseSharedTempDirParameterInjectionTestCase.tempDir = tempDir; - check(tempDir); - } - } - - static class AnnotationOnAfterAllMethodParameterTestCase { - - static Path firstTempDir = null; - static Path secondTempDir = null; - - @Test - void test(@TempDir Path tempDir, TestInfo testInfo) throws Exception { - assertThat(firstTempDir).isNull(); - firstTempDir = tempDir; - writeFile(tempDir, testInfo); - } - - @AfterAll - static void afterAll(@TempDir Path tempDir) { - assertThat(firstTempDir).isNotNull(); - assertNotEquals(firstTempDir, tempDir); - secondTempDir = tempDir; - } - } - - static class BaseSeparateTempDirsFieldInjectionTestCase { - - static final Deque tempDirs = new LinkedList<>(); - - @TempDir - Path tempDir; - - @BeforeEach - void beforeEach(@TempDir Path tempDir) { - for (Path dir : tempDirs) { - assertThat(dir).doesNotExist(); - } - assertThat(tempDirs).doesNotContain(tempDir); - tempDirs.add(tempDir); - check(tempDir); - } - - @Test - void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @Test - void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @AfterEach - void afterEach(@TempDir Path tempDir) { - check(tempDir); - } - - void check(Path tempDir) { - assertThat(tempDirs.getLast())// - .isNotNull()// - .isSameAs(tempDir)// - .isSameAs(this.tempDir); - assertTrue(Files.exists(tempDir)); - } - - } - - static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsFieldInjectionTestCase - extends BaseSeparateTempDirsFieldInjectionTestCase { - } - - @TestInstance(PER_CLASS) - static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassFieldInjectionTestCase - extends SeparateTempDirsWhenUsedOnForEachLifecycleMethodsFieldInjectionTestCase { - } - - static class BaseSeparateTempDirsParameterInjectionTestCase { - - static final Deque tempDirs = new LinkedList<>(); - - @BeforeEach - void beforeEach(@TempDir Path tempDir) { - for (Path dir : tempDirs) { - assertThat(dir).doesNotExist(); - } - assertThat(tempDirs).doesNotContain(tempDir); - tempDirs.add(tempDir); - check(tempDir); - } - - @Test - void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @Test - void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { - check(tempDir); - writeFile(tempDir, testInfo); - } - - @AfterEach - void afterEach(@TempDir Path tempDir) { - check(tempDir); - } - - void check(Path tempDir) { - assertSame(tempDirs.getLast(), tempDir); - assertTrue(Files.exists(tempDir)); - } - } - - static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsParameterInjectionTestCase - extends BaseSeparateTempDirsParameterInjectionTestCase { - } - - @TestInstance(PER_CLASS) - static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePerClassParameterInjectionTestCase - extends SeparateTempDirsWhenUsedOnForEachLifecycleMethodsParameterInjectionTestCase { - } - - static class InvalidTestCase { - - @Test - void wrongParameterType(@SuppressWarnings("unused") @TempDir String ignored) { - fail("this should never be called"); - } - } - - static class FileInjectionTestCase { - - @TempDir - File fileTempDir; - - @TempDir - Path pathTempDir; - - @Test - void checkFile(@TempDir File tempDir, @TempDir Path ref) { - assertFileAndPathAreEqual(tempDir, ref); - assertFileAndPathAreEqual(this.fileTempDir, this.pathTempDir); - } - - private void assertFileAndPathAreEqual(File tempDir, Path ref) { - Path path = tempDir.toPath(); - assertEquals(ref.toAbsolutePath(), path.toAbsolutePath()); - assertTrue(Files.exists(path)); - } - - } - - private static void writeFile(Path tempDir, TestInfo testInfo) throws IOException { - Path file = tempDir.resolve(testInfo.getTestMethod().orElseThrow().getName() + ".txt"); - Files.write(file, testInfo.getDisplayName().getBytes()); - } - - // https://github.com/junit-team/junit5/issues/1748 - static class TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase { - - TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase(TestInfo testInfo) { - assertNotNull(testInfo); - } - - @Test - void test() { - } - - } - - // https://github.com/junit-team/junit5/issues/1801 - static class UserTempDirectoryDeletionDoesNotCauseFailureTestCase { - - @Test - void deleteTempDir(@TempDir Path tempDir) throws IOException { - Files.delete(tempDir); - assertThat(tempDir).doesNotExist(); - } - - } - - // https://github.com/junit-team/junit5/issues/2046 - static class NonWritableFileDoesNotCauseFailureTestCase { - - @Test - void createReadonlyFile(@TempDir Path tempDir) throws IOException { - // Removal of setWritable(false) files might fail (e.g. for Windows) - // The test verifies that @TempDir is capable of removing of such files - var path = Files.write(tempDir.resolve("test.txt"), new byte[0]); - assumeTrue(path.toFile().setWritable(false), - () -> "Unable to set file " + path + " readonly via .toFile().setWritable(false)"); - } - - } - - // https://github.com/junit-team/junit5/issues/2171 - static class ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase { - - @Test - void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { - File file = tempDir.toPath().resolve("file").toFile(); - assumeTrue(file.createNewFile()); - assumeTrue(tempDir.setReadOnly()); - assumeTrue(file.setReadOnly()); - } - - } - - // https://github.com/junit-team/junit5/issues/2171 - static class ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase { - - @Test - void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { - File file = tempDir.toPath().resolve("dir").resolve("file").toFile(); - assumeTrue(file.getParentFile().mkdirs()); - assumeTrue(file.createNewFile()); - assumeTrue(tempDir.setReadOnly()); - assumeTrue(file.getParentFile().setReadOnly()); - assumeTrue(file.setReadOnly()); - } - - } - - // https://github.com/junit-team/junit5/issues/2609 - @SuppressWarnings("ResultOfMethodCallIgnored") - static class NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase { - - @Test - void createFile(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile(); - } - - @Test - void createFolder(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile(); - } - - @Test - void createNonWritableFile(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - } - - @Test - void createNonReadableFile(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - } - - @Test - void createNonWritableDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - } - - @Test - void createNonReadableDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - } - - @Test - void createNonExecutableDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - } - - @Test - void createNonEmptyNonWritableDirectory(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - } - - @Test - void createNonEmptyNonReadableDirectory(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - } - - @Test - void createNonEmptyNonExecutableDirectory(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - } - - @Test - void createNonEmptyDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - } - - @Test - void createNonEmptyDirectoryWithNonWritableFile(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - } - - @Test - void createNonEmptyDirectoryWithNonReadableFile(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - } - } - - // https://github.com/junit-team/junit5/issues/2609 - @SuppressWarnings("ResultOfMethodCallIgnored") - static class NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase { - - @Nested - class NonWritable { - - @Test - void makeEmptyTempDirectoryNonWritable(@TempDir Path tempDir) { - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithFileNonWritable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFileNonWritable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFileNonWritable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonWritable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonWritable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - } - - @Nested - class NonReadable { - - @Test - void makeEmptyTempDirectoryNonReadable(@TempDir Path tempDir) { - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithFileNonReadable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFileNonReadable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFileNonReadable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonReadable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonReadable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - } - - @Nested - class NonExecutable { - - @Test - void makeEmptyTempDirectoryNonExecutable(@TempDir Path tempDir) { - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithFileNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFileNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFileNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonExecutableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonExecutableFolderNonExecutable(@TempDir Path tempDir) - throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonExecutable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonExecutable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - } - } - - // https://github.com/junit-team/junit5/issues/2079 - static class TempDirUsageInsideNestedClassesTestCase { - - @TempDir - File tempDir; - - @Test - void topLevel() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - } - - @Nested - class NestedTestClass { - - @Test - void nested() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - } - - @Nested - class EvenDeeperNestedTestClass { - - @Test - void deeplyNested() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - } - } - } - } - - static class StaticTempDirUsageInsideNestedClassTestCase { - - @TempDir - static File tempDir; - - static File initialTempDir; - - @Test - void topLevel() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - initialTempDir = tempDir; - } - - @Nested - class NestedTestClass { - - @Test - void nested() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - assertSame(initialTempDir, tempDir); - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java deleted file mode 100644 index c98e99e0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java +++ /dev/null @@ -1,1068 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; - -import java.io.File; -import java.io.IOException; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Stream; - -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.Constants; -import org.junit.jupiter.engine.extension.TempDirectory.FileOperations; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests for the new behavior of the {@link TempDirectory} extension - * to create separate temp directories for each {@link TempDir} declaration. - * - * @since 5.8 - */ -@DisplayName("TempDirectory extension (per declaration)") -class TempDirectoryPerDeclarationTests extends AbstractJupiterTestEngineTests { - - @BeforeEach - @AfterEach - void resetStaticVariables() { - AllPossibleDeclarationLocationsTestCase.tempDirs.clear(); - } - - @TestFactory - @DisplayName("resolves separate temp dirs for each annotation declaration") - Stream resolvesSeparateTempDirsForEachAnnotationDeclaration() { - return Arrays.stream(TestInstance.Lifecycle.values()).map( - lifecycle -> dynamicTest("with " + lifecycle + " lifecycle", () -> { - - var results = executeTests(request() // - .selectors(selectClass(AllPossibleDeclarationLocationsTestCase.class)) // - .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, - lifecycle.toString()).build()); - - results.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - - assertThat(AllPossibleDeclarationLocationsTestCase.tempDirs).hasSize(3); - - var classTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("class"); - assertThat(classTempDirs).containsOnlyKeys("staticField1", "staticField2", "beforeAll1", "beforeAll2", - "afterAll1", "afterAll2"); - assertThat(classTempDirs.values()).hasSize(6).doesNotHaveDuplicates(); - - var testATempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testA"); - assertThat(testATempDirs).containsOnlyKeys("staticField1", "staticField2", "instanceField1", - "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2"); - assertThat(testATempDirs.values()).hasSize(10).doesNotHaveDuplicates(); - - var testBTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testB"); - assertThat(testBTempDirs).containsOnlyKeys("staticField1", "staticField2", "instanceField1", - "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2"); - assertThat(testBTempDirs.values()).hasSize(10).doesNotHaveDuplicates(); - - assertThat(testATempDirs.get("staticField1")).isEqualTo(classTempDirs.get("staticField1")); - assertThat(testBTempDirs.get("staticField1")).isEqualTo(classTempDirs.get("staticField1")); - assertThat(testATempDirs.get("staticField2")).isEqualTo(classTempDirs.get("staticField2")); - assertThat(testBTempDirs.get("staticField2")).isEqualTo(classTempDirs.get("staticField2")); - - assertThat(testATempDirs.get("instanceField1")).isNotEqualTo(testBTempDirs.get("instanceField1")); - assertThat(testATempDirs.get("instanceField2")).isNotEqualTo(testBTempDirs.get("instanceField2")); - assertThat(testATempDirs.get("beforeEach1")).isNotEqualTo(testBTempDirs.get("beforeEach1")); - assertThat(testATempDirs.get("beforeEach2")).isNotEqualTo(testBTempDirs.get("beforeEach2")); - assertThat(testATempDirs.get("test1")).isNotEqualTo(testBTempDirs.get("test1")); - assertThat(testATempDirs.get("test2")).isNotEqualTo(testBTempDirs.get("test2")); - assertThat(testATempDirs.get("afterEach1")).isNotEqualTo(testBTempDirs.get("afterEach1")); - assertThat(testATempDirs.get("afterEach2")).isNotEqualTo(testBTempDirs.get("afterEach2")); - })); - } - - @Test - @DisplayName("does not prevent constructor parameter resolution") - void tempDirectoryDoesNotPreventConstructorParameterResolution() { - executeTestsForClass(TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("does not prevent user from deleting the temp dir within a test") - void tempDirectoryDoesNotPreventUserFromDeletingTempDir() { - executeTestsForClass(UserTempDirectoryDeletionDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("is capable of removing a read-only file") - void nonWritableFileDoesNotCauseFailure() { - executeTestsForClass(NonWritableFileDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("is capable of removing non-executable, non-writable, or non-readable directories and folders") - void nonMintPermissionsContentDoesNotCauseFailure() { - executeTestsForClass(NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(13).succeeded(13)); - } - - @Test - @DisplayName("is capable of removing a directory when its permissions have been changed") - void nonMintPermissionsDoNotCauseFailure() { - executeTestsForClass(NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(42).succeeded(42)); - } - - @Test - @DisabledOnOs(OS.WINDOWS) - @DisplayName("is capable of removing a read-only file in a read-only dir") - void readOnlyFileInReadOnlyDirDoesNotCauseFailure() { - executeTestsForClass(ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisabledOnOs(OS.WINDOWS) - @DisplayName("is capable of removing a read-only file in a dir in a read-only dir") - void readOnlyFileInNestedReadOnlyDirDoesNotCauseFailure() { - executeTestsForClass(ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("can be used via instance field inside nested test classes") - void canBeUsedViaInstanceFieldInsideNestedTestClasses() { - executeTestsForClass(TempDirUsageInsideNestedClassesTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(3).succeeded(3)); - } - - @Test - @DisplayName("can be used via static field inside nested test classes") - void canBeUsedViaStaticFieldInsideNestedTestClasses() { - executeTestsForClass(StaticTempDirUsageInsideNestedClassTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(2).succeeded(2)); - } - - @TestFactory - @DisplayName("only attempts to delete undeletable paths once") - Stream onlyAttemptsToDeleteUndeletablePathsOnce() { - return Stream.of( // - dynamicTest("directory", () -> onlyAttemptsToDeleteUndeletablePathOnce(UndeletableDirectoryTestCase.class)), // - dynamicTest("file", () -> onlyAttemptsToDeleteUndeletablePathOnce(UndeletableFileTestCase.class)) // - ); - } - - private void onlyAttemptsToDeleteUndeletablePathOnce(Class testClass) { - var results = executeTestsForClass(testClass); - - var tempDir = results.testEvents().reportingEntryPublished().stream().map( - it -> it.getPayload(ReportEntry.class).orElseThrow()).map( - it -> Path.of(it.getKeyValuePairs().get(UndeletableTestCase.TEMP_DIR))).findAny().orElseThrow(); - - assertSingleFailedTest(results, // - instanceOf(IOException.class), // - message("Failed to delete temp directory " + tempDir.toAbsolutePath() + ". " + // - "The following paths could not be deleted (see suppressed exceptions for details): , undeletable"), // - suppressed(0, instanceOf(DirectoryNotEmptyException.class)), // - suppressed(1, instanceOf(IOException.class), message("Simulated failure"))); - } - - @Nested - @DisplayName("reports failure") - @TestMethodOrder(OrderAnnotation.class) - class Failures { - - @Test - @DisplayName("when @TempDir is used on static field of an unsupported type") - @Order(20) - void onlySupportsStaticFieldsOfTypePathAndFile() { - var results = executeTestsForClass(AnnotationOnStaticFieldWithUnsupportedTypeTestCase.class); - - assertSingleFailedContainer(results, ExtensionConfigurationException.class, - "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); - } - - @Test - @DisplayName("when @TempDir is used on instance field of an unsupported type") - @Order(21) - void onlySupportsInstanceFieldsOfTypePathAndFile() { - var results = executeTestsForClass(AnnotationOnInstanceFieldWithUnsupportedTypeTestCase.class); - - assertSingleFailedTest(results, ExtensionConfigurationException.class, - "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); - } - - @Test - @DisplayName("when @TempDir is used on parameter of an unsupported type") - @Order(22) - void onlySupportsParametersOfTypePathAndFile() { - var results = executeTestsForClass(InvalidTestCase.class); - - // @formatter:off - TempDirectoryPerDeclarationTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), - message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), - cause( - instanceOf(ExtensionConfigurationException.class), - message("Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String"))); - // @formatter:on - } - - @Test - @DisplayName("when @TempDir is used on constructor parameter") - @Order(30) - void doesNotSupportTempDirAnnotationOnConstructorParameter() { - var results = executeTestsForClass(AnnotationOnConstructorParameterTestCase.class); - - assertSingleFailedTest(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); - } - - @Test - @DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)") - @Order(31) - void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerClass() { - var results = executeTestsForClass(AnnotationOnConstructorParameterWithTestInstancePerClassTestCase.class); - - assertSingleFailedContainer(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); - } - - } - - @Nested - @DisplayName("supports @TempDir") - @TestMethodOrder(OrderAnnotation.class) - class PrivateFields { - - @Test - @DisplayName("on private static field") - @Order(10) - void supportsPrivateInstanceFields() { - executeTestsForClass(AnnotationOnPrivateStaticFieldTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - @Test - @DisplayName("on private instance field") - @Order(11) - void supportsPrivateStaticFields() { - executeTestsForClass(AnnotationOnPrivateInstanceFieldTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - } - - } - - private static void assertSingleFailedContainer(EngineExecutionResults results, Class clazz, - String message) { - - assertSingleFailedContainer(results, instanceOf(clazz), message(actual -> actual.contains(message))); - } - - @SafeVarargs - @SuppressWarnings("varargs") - private static void assertSingleFailedContainer(EngineExecutionResults results, - Condition... conditions) { - - results.containerEvents()// - .assertStatistics(stats -> stats.started(2).failed(1).succeeded(1))// - .assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); - } - - private static void assertSingleFailedTest(EngineExecutionResults results, Class clazz, - String message) { - - assertSingleFailedTest(results, instanceOf(clazz), message(actual -> actual.contains(message))); - } - - @SafeVarargs - @SuppressWarnings("varargs") - private static void assertSingleFailedTest(EngineExecutionResults results, Condition... conditions) { - results.testEvents().assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); - results.testEvents().assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); - } - - // ------------------------------------------------------------------------- - - static class AnnotationOnPrivateInstanceFieldTestCase { - - @SuppressWarnings("unused") - @TempDir - private Path tempDir; - - @Test - void test() { - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnPrivateStaticFieldTestCase { - - @SuppressWarnings("unused") - @TempDir - private static Path tempDir; - - @Test - void test() { - assertTrue(Files.exists(tempDir)); - } - - } - - static class AnnotationOnStaticFieldWithUnsupportedTypeTestCase { - - @SuppressWarnings("unused") - @TempDir - static String tempDir; - - @Test - void test1() { - } - - } - - static class AnnotationOnInstanceFieldWithUnsupportedTypeTestCase { - - @SuppressWarnings("unused") - @TempDir - String tempDir; - - @Test - void test1() { - } - - } - - static class AnnotationOnConstructorParameterTestCase { - - AnnotationOnConstructorParameterTestCase(@SuppressWarnings("unused") @TempDir Path tempDir) { - // never called - } - - @Test - void test() { - // never called - } - - } - - @TestInstance(PER_CLASS) - static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase - extends AnnotationOnConstructorParameterTestCase { - - AnnotationOnConstructorParameterWithTestInstancePerClassTestCase(@TempDir Path tempDir) { - super(tempDir); - } - } - - static class InvalidTestCase { - - @Test - void wrongParameterType(@SuppressWarnings("unused") @TempDir String ignored) { - fail("this should never be called"); - } - } - - @Nested - @DisplayName("resolves java.io.File injection type") - class FileAndPathInjection { - - @TempDir - File fileTempDir; - - @TempDir - Path pathTempDir; - - @Test - @DisplayName("and injected File and Path do not reference the same temp directory") - void checkFile(@TempDir File tempDir, @TempDir Path ref) { - assertFileAndPathAreNotEqual(tempDir, ref); - assertFileAndPathAreNotEqual(this.fileTempDir, this.pathTempDir); - } - - private static void assertFileAndPathAreNotEqual(File tempDir, Path ref) { - Path path = tempDir.toPath(); - assertNotEquals(ref.toAbsolutePath(), path.toAbsolutePath()); - assertTrue(Files.exists(path)); - } - - } - - // https://github.com/junit-team/junit5/issues/1748 - static class TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase { - - @TempDir - Path tempDir; - - TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase(TestInfo testInfo) { - assertNotNull(testInfo); - } - - @Test - void test() { - assertNotNull(tempDir); - } - - } - - // https://github.com/junit-team/junit5/issues/1801 - static class UserTempDirectoryDeletionDoesNotCauseFailureTestCase { - - @Test - void deleteTempDir(@TempDir Path tempDir) throws IOException { - Files.delete(tempDir); - assertThat(tempDir).doesNotExist(); - } - - } - - // https://github.com/junit-team/junit5/issues/2046 - static class NonWritableFileDoesNotCauseFailureTestCase { - - @Test - void createReadonlyFile(@TempDir Path tempDir) throws IOException { - // Removal of setWritable(false) files might fail (e.g. for Windows) - // The test verifies that @TempDir is capable of removing of such files - var path = Files.write(tempDir.resolve("test.txt"), new byte[0]); - assumeTrue(path.toFile().setWritable(false), - () -> "Unable to set file " + path + " readonly via .toFile().setWritable(false)"); - } - - } - - // https://github.com/junit-team/junit5/issues/2171 - static class ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase { - - @Test - void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { - File file = tempDir.toPath().resolve("file").toFile(); - assumeTrue(file.createNewFile()); - assumeTrue(tempDir.setReadOnly()); - assumeTrue(file.setReadOnly()); - } - - } - - // https://github.com/junit-team/junit5/issues/2171 - static class ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase { - - @Test - void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { - File file = tempDir.toPath().resolve("dir").resolve("file").toFile(); - assumeTrue(file.getParentFile().mkdirs()); - assumeTrue(file.createNewFile()); - assumeTrue(tempDir.setReadOnly()); - assumeTrue(file.getParentFile().setReadOnly()); - assumeTrue(file.setReadOnly()); - } - - } - - // https://github.com/junit-team/junit5/issues/2609 - @SuppressWarnings("ResultOfMethodCallIgnored") - static class NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase { - - @Test - void createFile(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile(); - } - - @Test - void createFolder(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile(); - } - - @Test - void createNonWritableFile(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - } - - @Test - void createNonReadableFile(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - } - - @Test - void createNonWritableDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - } - - @Test - void createNonReadableDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - } - - @Test - void createNonExecutableDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - } - - @Test - void createNonEmptyNonWritableDirectory(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - } - - @Test - void createNonEmptyNonReadableDirectory(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - } - - @Test - void createNonEmptyNonExecutableDirectory(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - } - - @Test - void createNonEmptyDirectory(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - } - - @Test - void createNonEmptyDirectoryWithNonWritableFile(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - } - - @Test - void createNonEmptyDirectoryWithNonReadableFile(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - } - } - - // https://github.com/junit-team/junit5/issues/2609 - @SuppressWarnings("ResultOfMethodCallIgnored") - static class NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase { - - @Nested - class NonWritable { - - @Test - void makeEmptyTempDirectoryNonWritable(@TempDir Path tempDir) { - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithFileNonWritable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFileNonWritable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFileNonWritable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonWritable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setWritable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonWritable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setWritable(false); - } - } - - @Nested - class NonReadable { - - @Test - void makeEmptyTempDirectoryNonReadable(@TempDir Path tempDir) { - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithFileNonReadable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFileNonReadable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFileNonReadable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonReadable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setReadable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonReadable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setReadable(false); - } - } - - @Nested - class NonExecutable { - - @Test - void makeEmptyTempDirectoryNonExecutable(@TempDir Path tempDir) { - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithFileNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFileNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFileNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonExecutableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyNonExecutableFolderNonExecutable(@TempDir Path tempDir) - throws IOException { - Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - subDir.toFile().setExecutable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonExecutable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); - tempDir.toFile().setExecutable(false); - } - - @Test - void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonExecutable(@TempDir Path tempDir) - throws IOException { - Files.createDirectory(tempDir.resolve("test-sub-dir")); - Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); - tempDir.toFile().setExecutable(false); - } - } - } - - // https://github.com/junit-team/junit5/issues/2079 - static class TempDirUsageInsideNestedClassesTestCase { - - @TempDir - File tempDir; - - @Test - void topLevel() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - } - - @Nested - class NestedTestClass { - - @Test - void nested() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - } - - @Nested - class EvenDeeperNestedTestClass { - - @Test - void deeplyNested() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - } - } - } - } - - static class StaticTempDirUsageInsideNestedClassTestCase { - - @TempDir - static File tempDir; - - static File initialTempDir; - - @Test - void topLevel() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - initialTempDir = tempDir; - } - - @Nested - class NestedTestClass { - - @Test - void nested() { - assertNotNull(tempDir); - assertTrue(tempDir.exists()); - assertSame(initialTempDir, tempDir); - } - } - } - - @DisplayName("class") - static class AllPossibleDeclarationLocationsTestCase { - - static final Map> tempDirs = new HashMap<>(); - - @TempDir - static Path staticField1; - - @TempDir - static Path staticField2; - - @TempDir - Path instanceField1; - - @TempDir - Path instanceField2; - - @BeforeAll - static void beforeAll(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { - getTempDirs(testInfo).putAll(Map.of( // - "staticField1", staticField1, // - "staticField2", staticField2, // - "beforeAll1", param1, // - "beforeAll2", param2 // - )); - } - - @BeforeEach - void beforeEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { - getTempDirs(testInfo).putAll(Map.of( // - "staticField1", staticField1, // - "staticField2", staticField2, // - "instanceField1", instanceField1, // - "instanceField2", instanceField2, // - "beforeEach1", param1, // - "beforeEach2", param2 // - )); - } - - @Test - @DisplayName("testA") - void testA(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { - getTempDirs(testInfo).putAll(Map.of( // - "test1", param1, // - "test2", param2 // - )); - } - - @Test - @DisplayName("testB") - void testB(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { - getTempDirs(testInfo).putAll(Map.of( // - "test1", param1, // - "test2", param2 // - )); - } - - @AfterEach - void afterEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { - getTempDirs(testInfo).putAll(Map.of( // - "afterEach1", param1, // - "afterEach2", param2 // - )); - } - - @AfterAll - static void afterAll(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { - getTempDirs(testInfo).putAll(Map.of( // - "afterAll1", param1, // - "afterAll2", param2 // - )); - } - - private static Map getTempDirs(TestInfo testInfo) { - return tempDirs.computeIfAbsent(testInfo.getDisplayName(), __ -> new LinkedHashMap<>()); - } - } - - static class UndeletableTestCase { - - static final Path UNDELETABLE_PATH = Path.of("undeletable"); - static final String TEMP_DIR = "TEMP_DIR"; - - @RegisterExtension - BeforeEachCallback injector = context -> context // - .getStore(TempDirectory.NAMESPACE) // - .put(TempDirectory.FILE_OPERATIONS_KEY, (FileOperations) path -> { - if (path.endsWith(UNDELETABLE_PATH)) { - throw new IOException("Simulated failure"); - } - else { - Files.delete(path); - } - }); - - @TempDir - Path tempDir; - - @BeforeEach - void reportTempDir(TestReporter reporter) { - reporter.publishEntry(TEMP_DIR, tempDir.toString()); - } - } - - static class UndeletableDirectoryTestCase extends UndeletableTestCase { - @Test - void test() throws Exception { - Files.createDirectory(tempDir.resolve(UNDELETABLE_PATH)); - } - } - - static class UndeletableFileTestCase extends UndeletableTestCase { - @Test - void test() throws Exception { - Files.createFile(tempDir.resolve(UNDELETABLE_PATH)); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java deleted file mode 100644 index 4a5b020e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests for preconditions and assertions in the {@link TempDirectory} - * extension. - * - * @since 5.9 - */ -class TempDirectoryPreconditionTests extends AbstractJupiterTestEngineTests { - - @Test - @DisplayName("Valid and invalid @TempDir parameter types") - void parameterTypes() { - EngineExecutionResults executionResults = executeTestsForClass(ParameterTypeTestCase.class); - Events tests = executionResults.testEvents(); - tests.assertStatistics(stats -> stats.started(2).failed(1).succeeded(1)); - tests.succeeded().assertEventsMatchExactly(event(test("validTempDirType"), finishedSuccessfully())); - // @formatter:off - tests.failed().assertEventsMatchExactly(event(test("invalidTempDirType"), - finishedWithFailure(instanceOf(ParameterResolutionException.class), message(""" - Failed to resolve parameter [java.lang.String text] in method \ - [void org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$ParameterTypeTestCase.invalidTempDirType(java.lang.String)]: \ - Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String\ - """)))); - // @formatter:on - } - - @Test - @DisplayName("final static @TempDir fields are not supported") - void finalStaticFieldIsNotSupported() { - EngineExecutionResults executionResults = executeTestsForClass(FinalStaticFieldTestCase.class); - Events containers = executionResults.containerEvents(); - containers.assertStatistics(stats -> stats.started(2).failed(1).succeeded(1)); - containers.succeeded().assertEventsMatchExactly(event(container("junit-jupiter"), finishedSuccessfully())); - containers.failed().assertEventsMatchExactly(event(container("FinalStaticFieldTestCase"), - finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(""" - @TempDir field [static final java.nio.file.Path \ - org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$FinalStaticFieldTestCase.path] \ - must not be declared as final.\ - """)))); - } - - @Test - @DisplayName("final instance @TempDir fields are not supported") - void finalInstanceFieldIsNotSupported() { - EngineExecutionResults executionResults = executeTestsForClass(FinalInstanceFieldTestCase.class); - Events tests = executionResults.testEvents(); - tests.assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); - tests.failed().assertEventsMatchExactly( - event(test("test()"), finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(""" - @TempDir field [final java.nio.file.Path \ - org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$FinalInstanceFieldTestCase.path] \ - must not be declared as final.\ - """)))); - } - - // ------------------------------------------------------------------- - - static class ParameterTypeTestCase { - - @Test - void validTempDirType(@TempDir File file, @TempDir Path path) { - } - - @Test - void invalidTempDirType(@TempDir String text) { - } - } - - static class FinalStaticFieldTestCase { - - static final @TempDir Path path = Paths.get("."); - - @Test - void test() { - } - } - - static class FinalInstanceFieldTestCase { - - final @TempDir Path path = Paths.get("."); - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java deleted file mode 100644 index 1c4409c0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests that verify support for {@link TestExecutionExceptionHandler}. - * - * @since 5.0 - */ -class TestExecutionExceptionHandlerTests extends AbstractJupiterTestEngineTests { - - static List handlerCalls = new ArrayList<>(); - - @BeforeEach - void resetStatics() { - handlerCalls.clear(); - RethrowException.handleExceptionCalled = false; - ConvertException.handleExceptionCalled = false; - SwallowException.handleExceptionCalled = false; - ShouldNotBeCalled.handleExceptionCalled = false; - } - - @Test - void exceptionHandlerRethrowsException() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testRethrow")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - assertTrue(RethrowException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(ATestCase.class), started()), // - event(test("testRethrow"), started()), // - event(test("testRethrow"), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // - event(container(ATestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void exceptionHandlerSwallowsException() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSwallow")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - assertTrue(SwallowException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(ATestCase.class), started()), // - event(test("testSwallow"), started()), // - event(test("testSwallow"), finishedSuccessfully()), // - event(container(ATestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void exceptionHandlerConvertsException() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testConvert")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - assertTrue(ConvertException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(ATestCase.class), started()), // - event(test("testConvert"), started()), // - event(test("testConvert"), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // - event(container(ATestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void severalHandlersAreCalledInOrder() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSeveral")).build(); - - EngineExecutionResults executionResults = executeTests(request); - - assertTrue(ConvertException.handleExceptionCalled, "ConvertException should have been called"); - assertTrue(RethrowException.handleExceptionCalled, "RethrowException should have been called"); - assertTrue(SwallowException.handleExceptionCalled, "SwallowException should have been called"); - assertFalse(ShouldNotBeCalled.handleExceptionCalled, "ShouldNotBeCalled should not have been called"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(ATestCase.class), started()), // - event(test("testSeveral"), started()), // - event(test("testSeveral"), finishedSuccessfully()), // - event(container(ATestCase.class), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - - assertEquals(Arrays.asList("convert", "rethrow", "swallow"), handlerCalls); - } - - // ------------------------------------------------------------------- - - static class ATestCase { - - @Test - @ExtendWith(RethrowException.class) - void testRethrow() throws IOException { - throw new IOException("checked"); - } - - @Test - @ExtendWith(SwallowException.class) - void testSwallow() throws IOException { - throw new IOException("checked"); - } - - @Test - @ExtendWith(ConvertException.class) - void testConvert() { - throw new RuntimeException("unchecked"); - } - - @Test - @ExtendWith(ShouldNotBeCalled.class) - @ExtendWith(SwallowException.class) - @ExtendWith(RethrowException.class) - @ExtendWith(ConvertException.class) - void testSeveral() { - throw new RuntimeException("unchecked"); - } - } - - static class RethrowException implements TestExecutionExceptionHandler { - - static boolean handleExceptionCalled = false; - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { - assertTrue(throwable instanceof IOException); - handleExceptionCalled = true; - handlerCalls.add("rethrow"); - - throw throwable; - } - } - - static class SwallowException implements TestExecutionExceptionHandler { - - static boolean handleExceptionCalled = false; - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { - assertTrue(throwable instanceof IOException); - handleExceptionCalled = true; - handlerCalls.add("swallow"); - //swallow exception by not rethrowing it - } - } - - static class ConvertException implements TestExecutionExceptionHandler { - - static boolean handleExceptionCalled = false; - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { - assertTrue(throwable instanceof RuntimeException); - handleExceptionCalled = true; - handlerCalls.add("convert"); - throw new IOException("checked"); - } - - } - - static class ShouldNotBeCalled implements TestExecutionExceptionHandler { - - static boolean handleExceptionCalled = false; - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { - handleExceptionCalled = true; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java deleted file mode 100644 index ba461079..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; - -/** - * Integration tests for {@link TestInfoParameterResolver}. - * - * @since 5.0 - */ -@Tag("class-tag") -class TestInfoParameterResolverTests { - - private static List allDisplayNames = Arrays.asList("defaultDisplayName(TestInfo)", "custom display name", - "getTags(TestInfo)", "customDisplayNameThatIsEmpty(TestInfo)"); - - @Test - void defaultDisplayName(TestInfo testInfo) { - assertEquals("defaultDisplayName(TestInfo)", testInfo.getDisplayName()); - } - - @Test - @DisplayName("custom display name") - void providedDisplayName(TestInfo testInfo) { - assertEquals("custom display name", testInfo.getDisplayName()); - } - - // TODO Update test to expect an exception once #743 is fixed. - @Test - @DisplayName("") - void customDisplayNameThatIsEmpty(TestInfo testInfo) { - assertEquals("customDisplayNameThatIsEmpty(TestInfo)", testInfo.getDisplayName()); - } - - @Test - @Tag("method-tag") - void getTags(TestInfo testInfo) { - assertEquals(2, testInfo.getTags().size()); - assertTrue(testInfo.getTags().contains("method-tag")); - assertTrue(testInfo.getTags().contains("class-tag")); - } - - @BeforeEach - @AfterEach - void beforeAndAfter(TestInfo testInfo) { - assertThat(allDisplayNames).contains(testInfo.getDisplayName()); - } - - @BeforeAll - static void beforeAll(TestInfo testInfo) { - Set tags = testInfo.getTags(); - assertEquals(1, tags.size()); - assertTrue(tags.contains("class-tag")); - } - - @BeforeAll - @AfterAll - static void beforeAndAfterAll(TestInfo testInfo) { - assertEquals(TestInfoParameterResolverTests.class.getSimpleName(), testInfo.getDisplayName()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java deleted file mode 100644 index b773e9ee..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java +++ /dev/null @@ -1,727 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.nestedContainer; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.extension.TestInstanceFactory; -import org.junit.jupiter.api.extension.TestInstanceFactoryContext; -import org.junit.jupiter.api.extension.TestInstantiationException; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests that verify support for {@link TestInstanceFactory}. - * - * @since 5.3 - */ -class TestInstanceFactoryTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @BeforeEach - void resetCallSequence() { - callSequence.clear(); - } - - @Test - void multipleFactoriesRegisteredOnSingleTestClass() { - Class testClass = MultipleFactoriesRegisteredOnSingleTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), - finishedWithFailure(instanceOf(ExtensionConfigurationException.class), - message("The following TestInstanceFactory extensions were registered for test class [" - + testClass.getName() + "], but only one is permitted: " - + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void multipleFactoriesRegisteredWithinTestClassHierarchy() { - Class testClass = MultipleFactoriesRegisteredWithinClassHierarchyTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), - finishedWithFailure(instanceOf(ExtensionConfigurationException.class), - message("The following TestInstanceFactory extensions were registered for test class [" - + testClass.getName() + "], but only one is permitted: " - + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void multipleFactoriesRegisteredWithinNestedClassStructure() { - Class outerClass = MultipleFactoriesRegisteredWithinNestedClassStructureTestCase.class; - Class nestedClass = MultipleFactoriesRegisteredWithinNestedClassStructureTestCase.InnerTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(outerClass); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(outerClass), started()), // - event(test("outerTest()"), started()), // - event(test("outerTest()"), finishedSuccessfully()), // - event(nestedContainer(nestedClass), started()), // - event(nestedContainer(nestedClass), - finishedWithFailure(instanceOf(ExtensionConfigurationException.class), - message("The following TestInstanceFactory extensions were registered for test class [" - + nestedClass.getName() + "], but only one is permitted: " - + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // - event(container(outerClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void nullTestInstanceFactoryWithPerMethodLifecycle() { - Class testClass = NullTestInstanceFactoryTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("testShouldNotBeCalled"), started()), // - event(test("testShouldNotBeCalled"), - finishedWithFailure(instanceOf(TestInstantiationException.class), - message(m -> m.equals("TestInstanceFactory [" + NullTestInstanceFactory.class.getName() - + "] failed to return an instance of [" + testClass.getName() - + "] and instead returned an instance of [null].")))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void nullTestInstanceFactoryWithPerClassLifecycle() { - Class testClass = PerClassLifecycleNullTestInstanceFactoryTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), - finishedWithFailure(instanceOf(TestInstantiationException.class), - message(m -> m.equals("TestInstanceFactory [" + NullTestInstanceFactory.class.getName() - + "] failed to return an instance of [" + testClass.getName() - + "] and instead returned an instance of [null].")))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void bogusTestInstanceFactoryWithPerMethodLifecycle() { - Class testClass = BogusTestInstanceFactoryTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("testShouldNotBeCalled"), started()), // - event(test("testShouldNotBeCalled"), - finishedWithFailure(instanceOf(TestInstantiationException.class), - message(m -> m.equals("TestInstanceFactory [" + BogusTestInstanceFactory.class.getName() - + "] failed to return an instance of [" + testClass.getName() - + "] and instead returned an instance of [java.lang.String].")))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void bogusTestInstanceFactoryWithPerClassLifecycle() { - Class testClass = PerClassLifecycleBogusTestInstanceFactoryTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), - finishedWithFailure(instanceOf(TestInstantiationException.class), - message(m -> m.equals("TestInstanceFactory [" + BogusTestInstanceFactory.class.getName() - + "] failed to return an instance of [" + testClass.getName() - + "] and instead returned an instance of [java.lang.String].")))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void explosiveTestInstanceFactoryWithPerMethodLifecycle() { - Class testClass = ExplosiveTestInstanceFactoryTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("testShouldNotBeCalled"), started()), // - event(test("testShouldNotBeCalled"), - finishedWithFailure(instanceOf(TestInstantiationException.class), - message("TestInstanceFactory [" + ExplosiveTestInstanceFactory.class.getName() - + "] failed to instantiate test class [" + testClass.getName() + "]: boom!"))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void explosiveTestInstanceFactoryWithPerClassLifecycle() { - Class testClass = PerClassLifecycleExplosiveTestInstanceFactoryTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), // - finishedWithFailure(instanceOf(TestInstantiationException.class), - message("TestInstanceFactory [" + ExplosiveTestInstanceFactory.class.getName() - + "] failed to instantiate test class [" + testClass.getName() + "]: boom!"))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void proxyTestInstanceFactoryFailsDueToUseOfDifferentClassLoader() { - Class testClass = ProxiedTestCase.class; - EngineExecutionResults executionResults = executeTestsForClass(testClass); - - assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); - - executionResults.allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), // - // NOTE: the test class names are the same even though the objects are - // instantiated using different ClassLoaders. Thus, we check for the - // appended "@" but ignore the actual hash code for the test class - // loaded by the different ClassLoader. - finishedWithFailure(instanceOf(TestInstantiationException.class), - message(m -> m.startsWith("TestInstanceFactory [" + ProxyTestInstanceFactory.class.getName() + "]") - && m.contains("failed to return an instance of [" + testClass.getName() + "@" - + Integer.toHexString(System.identityHashCode(testClass))) - && m.contains("and instead returned an instance of [" + testClass.getName() + "@")// - ))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void instanceFactoryOnTopLevelTestClass() { - EngineExecutionResults executionResults = executeTestsForClass(ParentTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - - // @formatter:off - assertThat(callSequence).containsExactly( - "FooInstanceFactory instantiated: ParentTestCase", - "parentTest" - ); - // @formatter:on - } - - @Test - void instanceFactorySupportedWhenTestClassDeclaresMultipleConstructors() { - executeTestsForClass(MultipleConstructorsTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "MultipleConstructorsTestInstanceFactory instantiated: MultipleConstructorsTestCase", - "test: 42" - ); - // @formatter:on - } - - @Test - void inheritedFactoryInTestClassHierarchy() { - EngineExecutionResults executionResults = executeTestsForClass(InheritedFactoryTestCase.class); - - assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - - // @formatter:off - assertThat(callSequence).containsExactly( - "FooInstanceFactory instantiated: InheritedFactoryTestCase", - "parentTest", - "FooInstanceFactory instantiated: InheritedFactoryTestCase", - "childTest" - ); - // @formatter:on - } - - @Test - void instanceFactoriesInNestedClassStructureAreInherited() { - EngineExecutionResults executionResults = executeTestsForClass(OuterTestCase.class); - - assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - - // @formatter:off - assertThat(callSequence).containsExactly( - - // OuterTestCase - "FooInstanceFactory instantiated: OuterTestCase", - "outerTest", - - // InnerTestCase - "FooInstanceFactory instantiated: OuterTestCase", - "FooInstanceFactory instantiated: InnerTestCase", - "innerTest1", - - // InnerInnerTestCase - "FooInstanceFactory instantiated: OuterTestCase", - "FooInstanceFactory instantiated: InnerTestCase", - "FooInstanceFactory instantiated: InnerInnerTestCase", - "innerTest2" - ); - // @formatter:on - } - - @Test - void instanceFactoryRegisteredViaTestInterface() { - EngineExecutionResults executionResults = executeTestsForClass(FactoryFromInterfaceTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - - // @formatter:off - assertThat(callSequence).containsExactly( - "FooInstanceFactory instantiated: FactoryFromInterfaceTestCase", - "test" - ); - // @formatter:on - } - - @Test - void instanceFactoryRegisteredAsLambdaExpression() { - EngineExecutionResults executionResults = executeTestsForClass(LambdaFactoryTestCase.class); - - assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - - // @formatter:off - assertThat(callSequence).containsExactly( - "beforeEach: lambda", - "test: lambda" - ); - // @formatter:on - } - - @Test - void instanceFactoryWithPerClassLifecycle() { - EngineExecutionResults executionResults = executeTestsForClass(PerClassLifecycleTestCase.class); - - assertEquals(1, PerClassLifecycleTestCase.counter.get()); - - assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); - assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); - - // @formatter:off - assertThat(callSequence).containsExactly( - "FooInstanceFactory instantiated: PerClassLifecycleTestCase", - "@BeforeAll", - "@BeforeEach", - "test1", - "@BeforeEach", - "test2", - "@AfterAll" - ); - // @formatter:on - } - - // ------------------------------------------------------------------------- - - @ExtendWith({ FooInstanceFactory.class, BarInstanceFactory.class }) - static class MultipleFactoriesRegisteredOnSingleTestCase { - - @Test - void testShouldNotBeCalled() { - callSequence.add("testShouldNotBeCalled"); - } - } - - @ExtendWith(NullTestInstanceFactory.class) - static class NullTestInstanceFactoryTestCase { - - @Test - void testShouldNotBeCalled() { - callSequence.add("testShouldNotBeCalled"); - } - } - - @TestInstance(PER_CLASS) - static class PerClassLifecycleNullTestInstanceFactoryTestCase extends NullTestInstanceFactoryTestCase { - } - - @ExtendWith(BogusTestInstanceFactory.class) - static class BogusTestInstanceFactoryTestCase { - - @Test - void testShouldNotBeCalled() { - callSequence.add("testShouldNotBeCalled"); - } - } - - @TestInstance(PER_CLASS) - static class PerClassLifecycleBogusTestInstanceFactoryTestCase extends BogusTestInstanceFactoryTestCase { - } - - @ExtendWith(ExplosiveTestInstanceFactory.class) - static class ExplosiveTestInstanceFactoryTestCase { - - @Test - void testShouldNotBeCalled() { - callSequence.add("testShouldNotBeCalled"); - } - } - - @TestInstance(PER_CLASS) - static class PerClassLifecycleExplosiveTestInstanceFactoryTestCase extends ExplosiveTestInstanceFactoryTestCase { - } - - private static class MultipleConstructorsTestInstanceFactory implements TestInstanceFactory { - - @Override - public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { - instantiated(getClass(), factoryContext.getTestClass()); - return new MultipleConstructorsTestCase(42); - } - } - - @ExtendWith(MultipleConstructorsTestInstanceFactory.class) - static class MultipleConstructorsTestCase { - - private final int number; - - public MultipleConstructorsTestCase(String text) { - this.number = -1; - } - - public MultipleConstructorsTestCase(int number) { - this.number = number; - } - - @Test - void test() { - callSequence.add("test: " + this.number); - } - } - - @ExtendWith(FooInstanceFactory.class) - static class ParentTestCase { - - @Test - void parentTest() { - callSequence.add("parentTest"); - } - } - - static class InheritedFactoryTestCase extends ParentTestCase { - - @Test - void childTest() { - callSequence.add("childTest"); - } - } - - @ExtendWith(BarInstanceFactory.class) - static class MultipleFactoriesRegisteredWithinClassHierarchyTestCase extends ParentTestCase { - - @Test - void childTest() { - callSequence.add("childTest"); - } - } - - @ExtendWith(FooInstanceFactory.class) - static class OuterTestCase { - - @Test - void outerTest() { - callSequence.add("outerTest"); - } - - @Nested - class InnerTestCase { - - @Test - void innerTest1() { - callSequence.add("innerTest1"); - } - - @Nested - class InnerInnerTestCase { - - @Test - void innerTest2() { - callSequence.add("innerTest2"); - } - } - } - } - - @ExtendWith(FooInstanceFactory.class) - static class MultipleFactoriesRegisteredWithinNestedClassStructureTestCase { - - @Test - void outerTest() { - } - - @Nested - @ExtendWith(BarInstanceFactory.class) - class InnerTestCase { - - @Test - void innerTest() { - } - } - } - - @ExtendWith(FooInstanceFactory.class) - interface TestInterface { - } - - static class FactoryFromInterfaceTestCase implements TestInterface { - - @Test - void test() { - callSequence.add("test"); - } - } - - static class LambdaFactoryTestCase { - - private final String text; - - @RegisterExtension - static final TestInstanceFactory factory = (__, ___) -> new LambdaFactoryTestCase("lambda"); - - LambdaFactoryTestCase(String text) { - this.text = text; - } - - @BeforeEach - void beforeEach() { - callSequence.add("beforeEach: " + this.text); - } - - @Test - void test() { - callSequence.add("test: " + this.text); - } - } - - @ExtendWith(FooInstanceFactory.class) - @TestInstance(PER_CLASS) - static class PerClassLifecycleTestCase { - - static final AtomicInteger counter = new AtomicInteger(); - - PerClassLifecycleTestCase() { - counter.incrementAndGet(); - } - - @BeforeAll - void beforeAll() { - callSequence.add("@BeforeAll"); - } - - @BeforeEach - void beforeEach() { - callSequence.add("@BeforeEach"); - } - - @Test - void test1() { - callSequence.add("test1"); - } - - @Test - void test2() { - callSequence.add("test2"); - } - - @AfterAll - void afterAll() { - callSequence.add("@AfterAll"); - } - } - - @ExtendWith(ProxyTestInstanceFactory.class) - @TestInstance(PER_CLASS) - static class ProxiedTestCase { - - @Test - void test1() { - callSequence.add("test1"); - } - - @Test - void test2() { - callSequence.add("test2"); - } - } - - // ------------------------------------------------------------------------- - - private static abstract class AbstractTestInstanceFactory implements TestInstanceFactory { - - @Override - public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { - Class testClass = factoryContext.getTestClass(); - instantiated(getClass(), testClass); - - if (factoryContext.getOuterInstance().isPresent()) { - return ReflectionUtils.newInstance(testClass, factoryContext.getOuterInstance().get()); - } - // else - return ReflectionUtils.newInstance(testClass); - } - } - - private static class FooInstanceFactory extends AbstractTestInstanceFactory { - } - - private static class BarInstanceFactory extends AbstractTestInstanceFactory { - } - - /** - * {@link TestInstanceFactory} that returns null. - */ - private static class NullTestInstanceFactory implements TestInstanceFactory { - - @Override - public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { - return null; - } - } - - /** - * {@link TestInstanceFactory} that returns an object of a type that does - * not match the supplied test class. - */ - private static class BogusTestInstanceFactory implements TestInstanceFactory { - - @Override - public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { - return "bogus"; - } - } - - /** - * {@link TestInstanceFactory} that always throws an exception. - */ - private static class ExplosiveTestInstanceFactory implements TestInstanceFactory { - - @Override - public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { - throw new RuntimeException("boom!"); - } - } - - /** - * This does not actually create a proxy. Rather, it simulates what - * a proxy-based implementation might do, by loading the class from a - * different {@link ClassLoader}. - */ - private static class ProxyTestInstanceFactory implements TestInstanceFactory { - - @Override - public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { - Class testClass = factoryContext.getTestClass(); - String className = testClass.getName(); - instantiated(getClass(), testClass); - - try (ProxyClassLoader proxyClassLoader = new ProxyClassLoader()) { - // Load test class from different class loader - Class clazz = proxyClassLoader.loadClass(className); - return ReflectionUtils.newInstance(clazz); - } - catch (Exception ex) { - throw new RuntimeException("Failed to load class [" + className + "]", ex); - } - } - } - - private static class ProxyClassLoader extends URLClassLoader { - - ProxyClassLoader() { - super(new URL[] { ProxyClassLoader.class.getProtectionDomain().getCodeSource().getLocation() }, - getSystemClassLoader()); - } - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - return (name.startsWith(TestInstanceFactoryTests.class.getName()) ? findClass(name) - : super.loadClass(name)); - } - } - - private static boolean instantiated(Class factoryClass, Class testClass) { - return callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java deleted file mode 100644 index 2b684fa9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.JupiterTestEngine; - -/** - * Integration tests that verify support for {@link TestInstancePostProcessor} - * and {@link TestInstancePreDestroyCallback} in the {@link JupiterTestEngine}. - * - * @since 5.6 - */ -class TestInstancePostProcessorAndPreDestroyCallbackTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @Test - void postProcessorAndPreDestroyCallbacks() { - // @formatter:off - assertPostProcessorAndPreDestroyCallbacks(TopLevelTestCase.class, - "fooPostProcessTestInstance", - "barPostProcessTestInstance", - "test-1", - "barPreDestroyTestInstance", - "fooPreDestroyTestInstance" - ); - // @formatter:on - } - - @Test - void postProcessorAndPreDestroyCallbacksInSubclass() { - // @formatter:off - assertPostProcessorAndPreDestroyCallbacks(SecondLevelTestCase.class, - "fooPostProcessTestInstance", - "barPostProcessTestInstance", - "bazPostProcessTestInstance", - "test-2", - "bazPreDestroyTestInstance", - "barPreDestroyTestInstance", - "fooPreDestroyTestInstance" - ); - // @formatter:on - } - - @Test - void postProcessorAndPreDestroyCallbacksInSubSubclass() { - // @formatter:off - assertPostProcessorAndPreDestroyCallbacks(ThirdLevelTestCase.class, - "fooPostProcessTestInstance", - "barPostProcessTestInstance", - "bazPostProcessTestInstance", - "quuxPostProcessTestInstance", - "test-3", - "quuxPreDestroyTestInstance", - "bazPreDestroyTestInstance", - "barPreDestroyTestInstance", - "fooPreDestroyTestInstance" - ); - // @formatter:on - } - - @Test - void preDestroyTestInstanceMethodThrowsAnException() { - // @formatter:off - assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestInstancePreDestroyCallbackTestCase.class, 0, - "fooPostProcessTestInstance", - "test", - "exceptionThrowingTestInstancePreDestroyCallback" - ); - // @formatter:on - } - - @Test - void postProcessTestInstanceMethodThrowsAnException() { - assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestInstancePostProcessorTestCase.class, 0, - "exceptionThrowingTestInstancePostProcessor", "exceptionPreDestroyTestInstance"); - } - - @Test - void testClassConstructorThrowsAnException() { - assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestClassConstructorTestCase.class, 0, - "exceptionThrowingConstructor"); - } - - private void assertPostProcessorAndPreDestroyCallbacks(Class testClass, String... expectedCalls) { - assertPostProcessorAndPreDestroyCallbacks(testClass, 1, expectedCalls); - } - - private void assertPostProcessorAndPreDestroyCallbacks(Class testClass, int testsSuccessful, - String... expectedCalls) { - - callSequence.clear(); - - executeTestsForClass(testClass).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(testsSuccessful)); - - assertEquals(asList(expectedCalls), callSequence, () -> "wrong call sequence for " + testClass.getName()); - } - - // ------------------------------------------------------------------------- - - // Must NOT be private; otherwise, the @Test method gets discovered but never executed. - @ExtendWith({ FooTestInstanceCallbacks.class, BarTestInstanceCallbacks.class }) - static class TopLevelTestCase { - - @Test - void test() { - callSequence.add("test-1"); - } - } - - // Must NOT be private; otherwise, the @Test method gets discovered but never executed. - @ExtendWith(BazTestInstanceCallbacks.class) - static class SecondLevelTestCase extends TopLevelTestCase { - - @Test - @Override - void test() { - callSequence.add("test-2"); - } - } - - @ExtendWith(QuuxTestInstanceCallbacks.class) - static class ThirdLevelTestCase extends SecondLevelTestCase { - - @Test - @Override - void test() { - callSequence.add("test-3"); - } - } - - @ExtendWith(ExceptionThrowingTestInstancePreDestroyCallback.class) - static class ExceptionInTestInstancePreDestroyCallbackTestCase { - - @Test - void test() { - callSequence.add("test"); - } - } - - @ExtendWith(ExceptionThrowingTestInstancePostProcessor.class) - static class ExceptionInTestInstancePostProcessorTestCase { - - @Test - void test() { - callSequence.add("test"); - } - } - - @ExtendWith(FooTestInstanceCallbacks.class) - static class ExceptionInTestClassConstructorTestCase { - - ExceptionInTestClassConstructorTestCase() { - callSequence.add("exceptionThrowingConstructor"); - throw new RuntimeException("in constructor"); - } - - @Test - void test() { - callSequence.add("test"); - } - } - - // ------------------------------------------------------------------------- - - static class FooTestInstanceCallbacks extends AbstractTestInstanceCallbacks { - - protected FooTestInstanceCallbacks() { - super("foo"); - } - } - - static class BarTestInstanceCallbacks extends AbstractTestInstanceCallbacks { - - protected BarTestInstanceCallbacks() { - super("bar"); - } - } - - static class BazTestInstanceCallbacks extends AbstractTestInstanceCallbacks { - - protected BazTestInstanceCallbacks() { - super("baz"); - } - } - - static class QuuxTestInstanceCallbacks extends AbstractTestInstanceCallbacks { - - protected QuuxTestInstanceCallbacks() { - super("quux"); - } - } - - static class ExceptionThrowingTestInstancePreDestroyCallback extends AbstractTestInstanceCallbacks { - - protected ExceptionThrowingTestInstancePreDestroyCallback() { - super("foo"); - } - - @Override - public void preDestroyTestInstance(ExtensionContext context) { - callSequence.add("exceptionThrowingTestInstancePreDestroyCallback"); - throw new EnigmaException("preDestroyTestInstance"); - } - } - - static class ExceptionThrowingTestInstancePostProcessor extends AbstractTestInstanceCallbacks { - - protected ExceptionThrowingTestInstancePostProcessor() { - super("exception"); - } - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - callSequence.add("exceptionThrowingTestInstancePostProcessor"); - throw new EnigmaException("postProcessTestInstance"); - } - } - - private static abstract class AbstractTestInstanceCallbacks - implements TestInstancePostProcessor, TestInstancePreDestroyCallback { - - private final String name; - - AbstractTestInstanceCallbacks(String name) { - this.name = name; - } - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - callSequence.add(name + "PostProcessTestInstance"); - } - - @Override - public void preDestroyTestInstance(ExtensionContext context) { - callSequence.add(name + "PreDestroyTestInstance"); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java deleted file mode 100644 index 225e89de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; - -/** - * Integration tests that verify support for {@link TestInstancePostProcessor}. - * - * @since 5.0 - */ -class TestInstancePostProcessorTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @BeforeEach - void resetCallSequence() { - callSequence.clear(); - } - - @Test - void instancePostProcessorsInNestedClasses() { - executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - - // OuterTestCase - "fooPostProcessTestInstance:OuterTestCase", - "beforeOuterMethod", - "testOuter", - - // InnerTestCase - - "fooPostProcessTestInstance:OuterTestCase", - "fooPostProcessTestInstance:InnerTestCase", - "barPostProcessTestInstance:InnerTestCase", - "beforeOuterMethod", - "beforeInnerMethod", - "testInner" - ); - // @formatter:on - } - - @Test - void testSpecificTestInstancePostProcessorIsCalled() { - executeTestsForClass(TestCaseWithTestSpecificTestInstancePostProcessor.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - - assertThat(callSequence).containsExactly( - "fooPostProcessTestInstance:TestCaseWithTestSpecificTestInstancePostProcessor", "beforeEachMethod", "test"); - } - - // ------------------------------------------------------------------- - - @ExtendWith(FooInstancePostProcessor.class) - static class OuterTestCase implements Named { - - private String outerName; - - @Override - public void setName(String name) { - this.outerName = name; - } - - @BeforeEach - void beforeOuterMethod() { - callSequence.add("beforeOuterMethod"); - } - - @Test - void testOuter() { - assertEquals("foo:" + OuterTestCase.class.getSimpleName(), outerName); - callSequence.add("testOuter"); - } - - @Nested - @ExtendWith(BarInstancePostProcessor.class) - class InnerTestCase implements Named { - - private String innerName; - - @Override - public void setName(String name) { - this.innerName = name; - } - - @BeforeEach - void beforeInnerMethod() { - callSequence.add("beforeInnerMethod"); - } - - @Test - void testInner() { - assertEquals("foo:" + OuterTestCase.class.getSimpleName(), outerName); - assertEquals("bar:" + InnerTestCase.class.getSimpleName(), innerName); - callSequence.add("testInner"); - } - } - - } - - static class TestCaseWithTestSpecificTestInstancePostProcessor implements Named { - - private String name; - - @Override - public void setName(String name) { - this.name = name; - } - - @BeforeEach - void beforeEachMethod() { - callSequence.add("beforeEachMethod"); - } - - @ExtendWith(FooInstancePostProcessor.class) - @Test - void test() { - callSequence.add("test"); - assertEquals("foo:" + getClass().getSimpleName(), name); - } - } - - static class FooInstancePostProcessor implements TestInstancePostProcessor { - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - if (testInstance instanceof Named) { - ((Named) testInstance).setName("foo:" + context.getRequiredTestClass().getSimpleName()); - } - callSequence.add("fooPostProcessTestInstance:" + testInstance.getClass().getSimpleName()); - } - } - - static class BarInstancePostProcessor implements TestInstancePostProcessor { - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - if (testInstance instanceof Named) { - ((Named) testInstance).setName("bar:" + context.getRequiredTestClass().getSimpleName()); - } - callSequence.add("barPostProcessTestInstance:" + testInstance.getClass().getSimpleName()); - } - } - - private interface Named { - - void setName(String name); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java deleted file mode 100644 index 100cb30f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.extension.TestInstanceFactory; -import org.junit.jupiter.api.extension.TestInstanceFactoryContext; -import org.junit.jupiter.api.extension.TestInstancePreConstructCallback; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; - -/** - * Integration tests that verify support for {@link TestInstancePreConstructCallback}. - * - * @since 5.9 - */ -class TestInstancePreConstructCallbackTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @BeforeEach - void resetCallSequence() { - callSequence.clear(); - } - - @Test - void instancePreConstruct() { - executeTestsForClass(InstancePreConstructTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "beforeAll", - - "PreConstructCallback: name=foo, testClass=InstancePreConstructTestCase, outerInstance: null", - "constructor", - "beforeEach", - "test1", - "afterEach", - - "PreConstructCallback: name=foo, testClass=InstancePreConstructTestCase, outerInstance: null", - "constructor", - "beforeEach", - "test2", - "afterEach", - - "afterAll" - ); - // @formatter:on - } - - @Test - void factoryPreConstruct() { - executeTestsForClass(FactoryPreConstructTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "beforeAll", - - "PreConstructCallback: name=foo, testClass=FactoryPreConstructTestCase, outerInstance: null", - "testInstanceFactory", - "constructor", - "beforeEach", - "test1", - "afterEach", - - "PreConstructCallback: name=foo, testClass=FactoryPreConstructTestCase, outerInstance: null", - "testInstanceFactory", - "constructor", - "beforeEach", - "test2", - "afterEach", - - "afterAll" - ); - // @formatter:on - } - - @Test - void preConstructInNested() { - executeTestsForClass(PreConstructInNestedTestCase.class).testEvents()// - .assertStatistics(stats -> stats.started(3).succeeded(3)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "beforeAll", - - "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", - "constructor", - "beforeEach", - "outerTest1", - "afterEach", - - "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", - "constructor", - "beforeEach", - "outerTest2", - "afterEach", - - "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", - "constructor", - - "PreConstructCallback: name=foo, testClass=InnerTestCase, outerInstance: #3", - "PreConstructCallback: name=bar, testClass=InnerTestCase, outerInstance: #3", - "PreConstructCallback: name=baz, testClass=InnerTestCase, outerInstance: #3", - "constructorInner", - "beforeEach", - "beforeEachInner", - "innerTest1", - "afterEachInner", - "afterEach", - - "afterAll" - ); - // @formatter:on - } - - @Test - void preConstructOnMethod() { - executeTestsForClass(PreConstructOnMethod.class).testEvents()// - .assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "PreConstructCallback: name=foo, testClass=PreConstructOnMethod, outerInstance: null", - "constructor", - "beforeEach", - "test1", - "afterEach", - - "constructor", - "beforeEach", - "test2", - "afterEach" - ); - // @formatter:on - } - - @Test - void preConstructWithClassLifecycle() { - executeTestsForClass(PreConstructWithClassLifecycle.class).testEvents()// - .assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "PreConstructCallback: name=foo, testClass=PreConstructWithClassLifecycle, outerInstance: null", - "PreConstructCallback: name=bar, testClass=PreConstructWithClassLifecycle, outerInstance: null", - "constructor", - "beforeEach", - "test1", - "beforeEach", - "test2" - ); - // @formatter:on - } - - private abstract static class CallSequenceRecordingTestCase { - protected static void record(String event) { - callSequence.add(event); - } - } - - @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) - static class InstancePreConstructTestCase extends CallSequenceRecordingTestCase { - - InstancePreConstructTestCase() { - record("constructor"); - } - - @BeforeAll - static void beforeAll() { - record("beforeAll"); - } - - @BeforeEach - void beforeEach() { - record("beforeEach"); - } - - @Test - void test1() { - record("test1"); - } - - @Test - void test2() { - record("test2"); - } - - @AfterEach - void afterEach() { - record("afterEach"); - } - - @AfterAll - static void afterAll() { - record("afterAll"); - } - } - - @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) - static class FactoryPreConstructTestCase extends CallSequenceRecordingTestCase { - - @RegisterExtension - static final TestInstanceFactory factory = (factoryContext, extensionContext) -> { - record("testInstanceFactory"); - return new FactoryPreConstructTestCase(); - }; - - FactoryPreConstructTestCase() { - record("constructor"); - } - - @BeforeAll - static void beforeAll() { - record("beforeAll"); - } - - @BeforeEach - void beforeEach() { - record("beforeEach"); - } - - @Test - void test1() { - record("test1"); - } - - @Test - void test2() { - record("test2"); - } - - @AfterEach - void afterEach() { - record("afterEach"); - } - - @AfterAll - static void afterAll() { - record("afterAll"); - } - } - - @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) - static class PreConstructInNestedTestCase extends CallSequenceRecordingTestCase { - - static AtomicInteger instanceCounter = new AtomicInteger(); - - private final String instanceId; - - PreConstructInNestedTestCase() { - record("constructor"); - instanceId = "#" + instanceCounter.incrementAndGet(); - } - - @BeforeAll - static void beforeAll() { - instanceCounter.set(0); - record("beforeAll"); - } - - @BeforeEach - void beforeEach() { - record("beforeEach"); - } - - @Test - void outerTest1() { - record("outerTest1"); - } - - @Test - void outerTest2() { - record("outerTest2"); - } - - @AfterEach - void afterEach() { - record("afterEach"); - } - - @AfterAll - static void afterAll() { - record("afterAll"); - } - - @Override - public String toString() { - return instanceId; - } - - @ExtendWith(InstancePreConstructCallbackRecordingBar.class) - abstract class InnerParent extends CallSequenceRecordingTestCase { - } - - @Nested - @ExtendWith(InstancePreConstructCallbackRecordingBaz.class) - class InnerTestCase extends InnerParent { - - InnerTestCase() { - record("constructorInner"); - } - - @BeforeEach - void beforeEachInner() { - record("beforeEachInner"); - } - - @Test - void innerTest1() { - record("innerTest1"); - } - - @AfterEach - void afterEachInner() { - record("afterEachInner"); - } - } - } - - static class PreConstructOnMethod extends CallSequenceRecordingTestCase { - PreConstructOnMethod() { - record("constructor"); - } - - @BeforeEach - void beforeEach() { - record("beforeEach"); - } - - @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) - @Test - void test1() { - record("test1"); - } - - @Test - void test2() { - record("test2"); - } - - @AfterEach - void afterEach() { - record("afterEach"); - } - } - - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) - @ExtendWith(InstancePreConstructCallbackRecordingBar.class) - static class PreConstructWithClassLifecycle extends CallSequenceRecordingTestCase { - PreConstructWithClassLifecycle() { - record("constructor"); - } - - @BeforeEach - void beforeEach() { - record("beforeEach"); - } - - @Test - void test1() { - callSequence.add("test1"); - } - - @Test - void test2() { - callSequence.add("test2"); - } - } - - static abstract class AbstractTestInstancePreConstructCallback implements TestInstancePreConstructCallback { - private final String name; - - AbstractTestInstancePreConstructCallback(String name) { - this.name = name; - } - - @Override - public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) { - assertThat(context.getTestInstance()).isNotPresent(); - assertThat(context.getTestClass()).isPresent(); - assertThat(factoryContext.getTestClass()).isSameAs(context.getTestClass().get()); - callSequence.add( - "PreConstructCallback: name=" + name + ", testClass=" + factoryContext.getTestClass().getSimpleName() - + ", outerInstance: " + factoryContext.getOuterInstance().orElse(null)); - } - } - - static class InstancePreConstructCallbackRecordingFoo extends AbstractTestInstancePreConstructCallback { - InstancePreConstructCallbackRecordingFoo() { - super("foo"); - } - } - - static class InstancePreConstructCallbackRecordingBar extends AbstractTestInstancePreConstructCallback { - InstancePreConstructCallbackRecordingBar() { - super("bar"); - } - } - - static class InstancePreConstructCallbackRecordingBaz extends AbstractTestInstancePreConstructCallback { - InstancePreConstructCallbackRecordingBaz() { - super("baz"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java deleted file mode 100644 index a20d3460..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; - -/** - * Integration tests that verify support for {@link TestInstancePreDestroyCallback}. - * - * @since 5.6 - */ -class TestInstancePreDestroyCallbackTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @BeforeEach - void resetCallSequence() { - callSequence.clear(); - } - - @Test - void instancePreDestroyCallbacksInNestedClasses() { - executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - // OuterTestCase - "beforeOuterMethod", - "testOuter", - "fooPreDestroyCallbackTestInstance:OuterTestCase", - - // InnerTestCase - "beforeOuterMethod", - "beforeInnerMethod", - "testInner", - "bazPreDestroyCallbackTestInstance:InnerTestCase", - "barPreDestroyCallbackTestInstance:InnerTestCase", - "fooPreDestroyCallbackTestInstance:InnerTestCase" - ); - // @formatter:on - } - - @Test - void testSpecificTestInstancePreDestroyCallbackIsCalled() { - executeTestsForClass(TestCaseWithTestSpecificTestInstancePreDestroyCallback.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "beforeEachMethod", - "test", - "fooPreDestroyCallbackTestInstance:TestCaseWithTestSpecificTestInstancePreDestroyCallback" - ); - // @formatter:on - } - - @Test - void classLifecyclePreDestroyCallbacks() { - executeTestsForClass(PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods.class).testEvents()// - .assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - "beforeEachMethod", - "test1", - "beforeEachMethod", - "test2", - "barPreDestroyCallbackTestInstance:PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods", - "fooPreDestroyCallbackTestInstance:PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods" - ); - // @formatter:on - } - - // ------------------------------------------------------------------- - - private abstract static class Destroyable { - - boolean destroyed; - - void setDestroyed() { - this.destroyed = true; - } - } - - @ExtendWith(FooInstancePreDestroyCallback.class) - static class OuterTestCase extends Destroyable { - - @BeforeEach - void beforeOuterMethod() { - callSequence.add("beforeOuterMethod"); - } - - @Test - void testOuter() { - assertFalse(destroyed); - callSequence.add("testOuter"); - } - - @Nested - @ExtendWith(BarInstancePreDestroyCallback.class) - @ExtendWith(BazInstancePreDestroyCallback.class) - class InnerTestCase extends Destroyable { - - @BeforeEach - void beforeInnerMethod() { - assertFalse(destroyed); - callSequence.add("beforeInnerMethod"); - } - - @Test - void testInner() { - callSequence.add("testInner"); - } - } - } - - static class TestCaseWithTestSpecificTestInstancePreDestroyCallback extends Destroyable { - - @BeforeEach - void beforeEachMethod() { - assertFalse(destroyed); - callSequence.add("beforeEachMethod"); - } - - @ExtendWith(FooInstancePreDestroyCallback.class) - @Test - void test() { - callSequence.add("test"); - } - } - - @TestInstance(PER_CLASS) - @ExtendWith(FooInstancePreDestroyCallback.class) - @ExtendWith(BarInstancePreDestroyCallback.class) - static class PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods extends Destroyable { - - @BeforeEach - void beforeEachMethod() { - callSequence.add("beforeEachMethod"); - } - - @Test - void test1() { - callSequence.add("test1"); - } - - @Test - void test2() { - callSequence.add("test2"); - } - } - - static abstract class AbstractTestInstancePreDestroyCallback implements TestInstancePreDestroyCallback { - - private final String name; - - AbstractTestInstancePreDestroyCallback(String name) { - this.name = name; - } - - @Override - public void preDestroyTestInstance(ExtensionContext context) { - assertThat(context.getTestInstance()).isPresent(); - Object testInstance = context.getTestInstance().get(); - if (testInstance instanceof Destroyable) { - ((Destroyable) testInstance).setDestroyed(); - } - callSequence.add(name + "PreDestroyCallbackTestInstance:" + testInstance.getClass().getSimpleName()); - } - } - - static class FooInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { - - FooInstancePreDestroyCallback() { - super("foo"); - } - } - - static class BarInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { - - BarInstancePreDestroyCallback() { - super("bar"); - } - } - - static class BazInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { - - BazInstancePreDestroyCallback() { - super("baz"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java deleted file mode 100644 index 4ffff626..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.reportEntry; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; - -import java.util.Map; -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; - -public class TestInstancePreDestroyCallbackUtilityMethodTests extends AbstractJupiterTestEngineTests { - - @TestFactory - Stream destroysWhatWasPostProcessed() { - var testClasses = Stream.of(PerMethodLifecycleOnAllLevels.class, PerMethodWithinPerClassLifecycle.class, - PerClassWithinPerMethodLifecycle.class, PerClassLifecycleOnAllLevels.class); - return testClasses.map(testClass -> dynamicTest( // - testClass.getSimpleName(), // - () -> executeTestsForClass(testClass).allEvents().debug() // - .assertStatistics(stats -> stats.reportingEntryPublished(4)) // - .assertEventsMatchLooselyInOrder( // - reportEntry(Map.of("post-process", testClass.getSimpleName())), - reportEntry(Map.of("post-process", "Inner")), // - event(test(), started()), // - reportEntry(Map.of("pre-destroy", "Inner")), // - reportEntry(Map.of("pre-destroy", testClass.getSimpleName())) // - ))); - } - - @ExtendWith(TestInstanceLifecycleExtension.class) - static class PerMethodLifecycleOnAllLevels { - @Nested - class Inner { - @Test - void test() { - } - } - } - - @ExtendWith(TestInstanceLifecycleExtension.class) - @TestInstance(PER_CLASS) - static class PerMethodWithinPerClassLifecycle { - @Nested - class Inner { - @Test - void test() { - } - } - } - - @ExtendWith(TestInstanceLifecycleExtension.class) - static class PerClassWithinPerMethodLifecycle { - @Nested - @TestInstance(PER_CLASS) - class Inner { - @Test - void test() { - } - } - } - - @ExtendWith(TestInstanceLifecycleExtension.class) - @TestInstance(PER_CLASS) - static class PerClassLifecycleOnAllLevels { - @Nested - @TestInstance(PER_CLASS) - class Inner { - @Test - void test() { - } - } - } - - private static class TestInstanceLifecycleExtension - implements TestInstancePostProcessor, TestInstancePreDestroyCallback { - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - context.publishReportEntry("post-process", testInstance.getClass().getSimpleName()); - } - - @Override - public void preDestroyTestInstance(ExtensionContext context) { - TestInstancePreDestroyCallback.preDestroyTestInstances(context, - testInstance -> context.publishReportEntry("pre-destroy", testInstance.getClass().getSimpleName())); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java deleted file mode 100644 index 6acbcafa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.0 - */ -class TestReporterParameterResolverTests { - - TestReporterParameterResolver resolver = new TestReporterParameterResolver(); - - @Test - void supports() { - Parameter parameter1 = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); - assertTrue(this.resolver.supportsParameter(parameterContext(parameter1), null)); - - Parameter parameter2 = findParameterOfMethod("methodWithoutTestReporterParameter", String.class); - assertFalse(this.resolver.supportsParameter(parameterContext(parameter2), null)); - } - - @Test - void resolve() { - Parameter parameter = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); - - TestReporter testReporter = this.resolver.resolveParameter(parameterContext(parameter), mock()); - assertNotNull(testReporter); - } - - private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionUtils.findMethod(Sample.class, methodName, parameterTypes).get(); - return method.getParameters()[0]; - } - - private static ParameterContext parameterContext(Parameter parameter) { - ParameterContext parameterContext = mock(); - when(parameterContext.getParameter()).thenReturn(parameter); - return parameterContext; - } - - static class Sample { - - void methodWithTestReporterParameter(TestReporter reporter) { - } - - void methodWithoutTestReporterParameter(String nothing) { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java deleted file mode 100644 index 4c446f6f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.function.Predicate.not; -import static java.util.stream.Collectors.toUnmodifiableList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestWatcher; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.testkit.engine.EngineExecutionResults; - -/** - * Integration tests for the {@link TestWatcher} extension API. - * - * @since 5.4 - */ -class TestWatcherTests extends AbstractJupiterTestEngineTests { - - private static final List testWatcherMethodNames = Arrays.stream(TestWatcher.class.getDeclaredMethods())// - .filter(not(Method::isSynthetic))// - .map(Method::getName)// - .collect(toUnmodifiableList()); - - @BeforeEach - void clearResults() { - TrackingTestWatcher.results.clear(); - } - - @Test - void testWatcherIsInvokedForTestMethodsInTopLevelAndNestedTestClasses() { - assertCommonStatistics(executeTestsForClass(TrackingTestWatcherTestMethodsTestCase.class)); - assertThat(TrackingTestWatcher.results.keySet()).containsAll(testWatcherMethodNames); - TrackingTestWatcher.results.values().forEach(uidList -> assertEquals(2, uidList.size())); - } - - @Test - void testWatcherIsInvokedForRepeatedTestMethods() { - EngineExecutionResults results = executeTestsForClass(TrackingTestWatcherRepeatedTestMethodsTestCase.class); - - results.containerEvents().assertStatistics( - stats -> stats.skipped(1).started(5).succeeded(5).aborted(0).failed(0)); - results.testEvents().assertStatistics( - stats -> stats.dynamicallyRegistered(6).skipped(0).started(6).succeeded(2).aborted(2).failed(2)); - - ArrayList expectedMethods = new ArrayList<>(testWatcherMethodNames); - // Since the @RepeatedTest container is disabled, the individual invocations never occur. - assertThat(TrackingTestWatcher.results.keySet()).containsAll(expectedMethods); - // 2 => number of iterations declared in @RepeatedTest(2). - TrackingTestWatcher.results.forEach( - (methodName, uidList) -> assertEquals("testDisabled".endsWith(methodName) ? 1 : 2, uidList.size())); - } - - @Test - void testWatcherIsNotInvokedForTestFactoryMethods() { - EngineExecutionResults results = executeTestsForClass(TrackingTestWatcherTestFactoryMethodsTestCase.class); - - results.containerEvents().assertStatistics( - stats -> stats.skipped(1).started(5).succeeded(5).aborted(0).failed(0)); - results.testEvents().assertStatistics( - stats -> stats.dynamicallyRegistered(6).skipped(0).started(6).succeeded(2).aborted(2).failed(2)); - - // There should be zero results, since the TestWatcher API is not supported for @TestFactory containers. - assertThat(TrackingTestWatcher.results).isEmpty(); - } - - @Test - @TrackLogRecords - void testWatcherExceptionsAreLoggedAndSwallowed(LogRecordListener logRecordListener) { - assertCommonStatistics(executeTestsForClass(ExceptionThrowingTestWatcherTestCase.class)); - - // @formatter:off - long exceptionCount = logRecordListener.stream(MethodBasedTestDescriptor.class, Level.WARNING) - .map(LogRecord::getThrown) - .filter(throwable -> throwable instanceof JUnitException) - .filter(throwable -> testWatcherMethodNames.contains(throwable.getStackTrace()[0].getMethodName())) - .count(); - // @formatter:on - - assertEquals(8, exceptionCount, "Thrown exceptions were not logged properly."); - } - - @Test - void testWatcherInvokedForTestMethodsInTestCaseWithProblematicConstructor() { - EngineExecutionResults results = executeTestsForClass(ProblematicConstructorTestCase.class); - results.testEvents().assertStatistics(stats -> stats.skipped(0).started(8).succeeded(0).aborted(0).failed(8)); - assertThat(TrackingTestWatcher.results.keySet()).containsExactly("testFailed"); - assertThat(TrackingTestWatcher.results.get("testFailed")).hasSize(8); - } - - private void assertCommonStatistics(EngineExecutionResults results) { - results.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3).failed(0)); - results.testEvents().assertStatistics(stats -> stats.skipped(2).started(6).succeeded(2).aborted(2).failed(2)); - } - - // ------------------------------------------------------------------------- - - private static abstract class AbstractTestCase { - - @Test - public void successfulTest() { - //no-op - } - - @Test - public void failedTest() { - fail("Must fail"); - } - - @Test - public void abortedTest() { - assumeTrue(false); - } - - @Test - @Disabled - public void skippedTest() { - //no-op - } - - @Nested - class SecondLevel { - - @Test - public void successfulTest() { - //no-op - } - - @Test - public void failedTest() { - fail("Must fail"); - } - - @Test - public void abortedTest() { - assumeTrue(false); - } - - @Test - @Disabled - public void skippedTest() { - //no-op - } - - } - - } - - @ExtendWith(TrackingTestWatcher.class) - static class TrackingTestWatcherTestMethodsTestCase extends AbstractTestCase { - } - - @ExtendWith(TrackingTestWatcher.class) - static class TrackingTestWatcherRepeatedTestMethodsTestCase { - - @RepeatedTest(2) - void successfulTest() { - //no-op - } - - @RepeatedTest(2) - void failedTest() { - fail("Must fail"); - } - - @RepeatedTest(2) - void abortedTest() { - assumeTrue(false); - } - - @RepeatedTest(2) - @Disabled - void skippedTest() { - //no-op - } - - } - - @ExtendWith(TrackingTestWatcher.class) - static class TrackingTestWatcherTestFactoryMethodsTestCase { - - @TestFactory - Stream successfulTest() { - return Stream.of("A", "B").map(text -> dynamicTest(text, () -> assertTrue(true))); - } - - @TestFactory - Stream failedTest() { - return Stream.of("A", "B").map(text -> dynamicTest(text, () -> fail("Must fail"))); - - } - - @TestFactory - Stream abortedTest() { - return Stream.of("A", "B").map(text -> dynamicTest(text, () -> assumeTrue(false))); - - } - - @TestFactory - @Disabled - Stream skippedTest() { - return Stream.of("A", "B").map(text -> dynamicTest(text, () -> assertTrue(false))); - } - - } - - @ExtendWith(ExceptionThrowingTestWatcher.class) - static class ExceptionThrowingTestWatcherTestCase extends AbstractTestCase { - } - - @ExtendWith(TrackingTestWatcher.class) - static class ProblematicConstructorTestCase extends AbstractTestCase { - ProblematicConstructorTestCase(Object ignore) { - } - } - - private static class TrackingTestWatcher implements TestWatcher { - - private static final Map> results = new HashMap<>(); - - @Override - public void testSuccessful(ExtensionContext context) { - trackResult("testSuccessful", context.getUniqueId()); - } - - @Override - public void testAborted(ExtensionContext context, Throwable cause) { - trackResult("testAborted", context.getUniqueId()); - } - - @Override - public void testFailed(ExtensionContext context, Throwable cause) { - trackResult("testFailed", context.getUniqueId()); - } - - @Override - public void testDisabled(ExtensionContext context, Optional reason) { - trackResult("testDisabled", context.getUniqueId()); - } - - protected void trackResult(String status, String uid) { - results.computeIfAbsent(status, k -> new ArrayList<>()).add(uid); - } - - } - - private static class ExceptionThrowingTestWatcher implements TestWatcher { - - @Override - public void testSuccessful(ExtensionContext context) { - throw new JUnitException("Exception in testSuccessful()"); - } - - @Override - public void testDisabled(ExtensionContext context, Optional reason) { - throw new JUnitException("Exception in testDisabled()"); - } - - @Override - public void testAborted(ExtensionContext context, Throwable cause) { - throw new JUnitException("Exception in testAborted()"); - } - - @Override - public void testFailed(ExtensionContext context, Throwable cause) { - throw new JUnitException("Exception in testFailed()"); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java deleted file mode 100644 index d37d922f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.HOURS; -import static java.util.concurrent.TimeUnit.MICROSECONDS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; -import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; - -/** - * @since 5.5 - */ -class TimeoutConfigurationTests { - - ExtensionContext extensionContext = mock(); - TimeoutConfiguration config = new TimeoutConfiguration(extensionContext); - - @Test - void noTimeoutIfNoPropertiesAreSet() { - assertThat(config.getDefaultBeforeAllMethodTimeout()).isEmpty(); - assertThat(config.getDefaultBeforeEachMethodTimeout()).isEmpty(); - assertThat(config.getDefaultTestMethodTimeout()).isEmpty(); - assertThat(config.getDefaultTestTemplateMethodTimeout()).isEmpty(); - assertThat(config.getDefaultTestFactoryMethodTimeout()).isEmpty(); - assertThat(config.getDefaultAfterEachMethodTimeout()).isEmpty(); - assertThat(config.getDefaultAfterAllMethodTimeout()).isEmpty(); - assertThat(config.getDefaultTimeoutThreadMode()).isEmpty(); - } - - @Test - void defaultTimeoutIsUsedUnlessAMoreSpecificOneIsSet() { - when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("42")); - - assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); - assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); - assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); - assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); - assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); - assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); - assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); - } - - @Test - void defaultCategoryTimeoutIsUsedUnlessAMoreSpecificOneIsSet() { - when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("2")); - when(extensionContext.getConfigurationParameter(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("3")); - when(extensionContext.getConfigurationParameter(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("5")); - - assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); - assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); - assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); - assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); - assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); - assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); - assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); - } - - @Test - void specificTimeoutsAreUsedIfSet() { - when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("2")); - when(extensionContext.getConfigurationParameter(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("3")); - when(extensionContext.getConfigurationParameter(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("5")); - when(extensionContext.getConfigurationParameter(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("7ns")); - when(extensionContext.getConfigurationParameter(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("11μs")); - when(extensionContext.getConfigurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("13ms")); - when(extensionContext.getConfigurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("17s")); - when(extensionContext.getConfigurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("19m")); - when(extensionContext.getConfigurationParameter(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("23h")); - when(extensionContext.getConfigurationParameter(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("29d")); - - assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(7, NANOSECONDS)); - assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(11, MICROSECONDS)); - assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(13, MILLISECONDS)); - assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(17, SECONDS)); - assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(19, MINUTES)); - assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(23, HOURS)); - assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(29, DAYS)); - } - - @Test - @TrackLogRecords - void logsInvalidValues(LogRecordListener logRecordListener) { - when(extensionContext.getConfigurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( - Optional.of("invalid")); - - assertThat(config.getDefaultTestMethodTimeout()).isEmpty(); - assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // - .containsExactly( - "Ignored invalid timeout 'invalid' set via the 'junit.jupiter.execution.timeout.test.method.default' configuration parameter."); - } - - @Test - void specificThreadModeIsUsed() { - when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)).thenReturn( - Optional.of("SEPARATE_THREAD")); - assertThat(config.getDefaultTimeoutThreadMode()).isEqualTo(Optional.of(SEPARATE_THREAD)); - } - - @Test - @TrackLogRecords - void logsInvalidThreadModeValueAndReturnEmpty(LogRecordListener logRecordListener) { - when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)).thenReturn( - Optional.of("invalid")); - - assertThat(config.getDefaultTimeoutThreadMode()).isEqualTo(Optional.empty()); - assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // - .containsExactly( - "Invalid timeout thread mode 'invalid' set via the 'junit.jupiter.execution.timeout.thread.mode.default' configuration parameter."); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java deleted file mode 100644 index 7eee8ae4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.HOURS; -import static java.util.concurrent.TimeUnit.MICROSECONDS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.time.format.DateTimeParseException; -import java.util.Map; -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; - -/** - * @since 5.5 - */ -class TimeoutDurationParserTests { - - private final TimeoutDurationParser parser = new TimeoutDurationParser(); - - @Test - void parsesNumberWithoutUnitIntoSecondsDurations() { - assertEquals(new TimeoutDuration(42, SECONDS), parser.parse("42")); - } - - @TestFactory - Stream parsesNumbersWithUnits() { - var unitsWithRepresentations = Map.of( // - NANOSECONDS, "ns", // - MICROSECONDS, "μs", // - MILLISECONDS, "ms", // - SECONDS, "s", // - MINUTES, "m", // - HOURS, "h", // - DAYS, "d"); - return unitsWithRepresentations.entrySet().stream() // - .map(entry -> { - var unit = entry.getKey(); - var plainRepresentation = entry.getValue(); - var representations = Stream.of( // - plainRepresentation, // - " " + plainRepresentation, // - plainRepresentation.toUpperCase(), // - " " + plainRepresentation.toUpperCase()); - return dynamicContainer(unit.name().toLowerCase(), - representations.map(representation -> dynamicTest("\"" + representation + "\"", () -> { - var expected = new TimeoutDuration(42, unit); - var actual = parser.parse("42" + representation); - assertEquals(expected, actual); - }))); - }); - } - - @Test - void rejectsNumbersStartingWithZero() { - assertThrows(DateTimeParseException.class, () -> parser.parse("01")); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java deleted file mode 100644 index 494ea9df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.5 - */ -class TimeoutDurationTests { - - @Test - void formatsDurationNicely() { - assertThat(new TimeoutDuration(1, SECONDS)).hasToString("1 second"); - assertThat(new TimeoutDuration(2, SECONDS)).hasToString("2 seconds"); - } - - @Test - void fulfillsEqualsAndHashCodeContract() { - var oneSecond = new TimeoutDuration(1, SECONDS); - - assertThat(oneSecond) // - .isEqualTo(oneSecond) // - .isEqualTo(new TimeoutDuration(1, SECONDS)) // - .hasSameHashCodeAs(new TimeoutDuration(1, SECONDS)) // - .isNotEqualTo("foo") // - .isNotEqualTo(new TimeoutDuration(2, SECONDS)) // - .isNotEqualTo(new TimeoutDuration(1, MINUTES)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java deleted file mode 100644 index 95aa77c4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.engine.extension.TimeoutExceptionFactory.create; - -import java.util.concurrent.TimeoutException; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * @since 5.9 - */ -@DisplayName("TimeoutExceptionFactory") -class TimeoutExceptionFactoryTest { - - private static final TimeoutDuration tenMillisDuration = new TimeoutDuration(10, MILLISECONDS); - private static final Exception suppressedException = new Exception("Winke!"); - private static final String methodSignature = "test()"; - - @Test - @DisplayName("creates exception with method signature and timeout") - void createExceptionWithMethodSignatureTimeout() { - TimeoutException exception = create(methodSignature, tenMillisDuration); - - assertThat(exception) // - .hasMessage("test() timed out after 10 milliseconds") // - .hasNoSuppressedExceptions(); - } - - @Test - @DisplayName("creates exception with method signature, timeout and throwable") - void createExceptionWithMethodSignatureTimeoutAndThrowable() { - TimeoutException exception = create(methodSignature, tenMillisDuration, suppressedException); - - assertThat(exception) // - .hasMessage("test() timed out after 10 milliseconds") // - .hasSuppressedException(suppressedException); - } - - @Nested - @DisplayName("throws exception when") - class ThrowException { - - @Test - @DisplayName("method signature is null") - void methodSignatureIsnull() { - assertThatThrownBy(() -> create(null, tenMillisDuration, suppressedException)) // - .hasMessage("method signature must not be null"); - } - - @Test - @DisplayName("method timeout duration is null") - void timeoutDurationIsnull() { - assertThatThrownBy(() -> create(methodSignature, null, suppressedException)) // - .hasMessage("timeout duration must not be null"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java deleted file mode 100644 index 6fc77244..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java +++ /dev/null @@ -1,851 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; -import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; -import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.TIMEOUT_MODE_PROPERTY_NAME; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.time.Duration; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeoutException; -import java.util.stream.Stream; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.RuntimeUtils; -import org.junit.platform.engine.TestExecutionResult.Status; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; -import org.junit.platform.testkit.engine.Execution; -import org.opentest4j.AssertionFailedError; - -/** - * @since 5.5 - */ -@DisplayName("@Timeout") -class TimeoutExtensionTests extends AbstractJupiterTestEngineTests { - - @Test - @DisplayName("is applied on annotated @Test methods") - void appliesTimeoutOnAnnotatedTestMethods() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // - .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testMethod() timed out after 10 milliseconds"); - } - - @Test - @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") - void doesNotApplyTimeoutOnAnnotatedTestMethodsUsingDisabledTimeoutMode() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // - .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled").build()); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // - .isEmpty(); - } - - @Test - @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") - void applyTimeoutOnAnnotatedTestMethodsUsingDisabledOnDebugTimeoutMode() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // - .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled_on_debug").build()); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - // The check to see if debugging is pushing the timer just above 1 second - .isLessThan(Duration.ofSeconds(2)); - - // Should we test if we're debugging? This test will fail if we are debugging. - if (RuntimeUtils.isDebugMode()) { - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // - .isEmpty(); - } - else { - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testMethod() timed out after 10 milliseconds"); - } - } - - @Test - @DisplayName("is applied on annotated @TestTemplate methods") - void appliesTimeoutOnAnnotatedTestTemplateMethods() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testTemplateMethod")) // - .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Stream.of("repetition 1", "repetition 2").forEach(displayName -> { - Execution execution = findExecution(results.testEvents(), displayName); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testTemplateMethod() timed out after 10 milliseconds"); - }); - } - - @Test - @DisplayName("is applied on annotated @TestFactory methods") - void appliesTimeoutOnAnnotatedTestFactoryMethods() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testFactoryMethod")) // - .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Execution execution = findExecution(results.containerEvents(), "testFactoryMethod()"); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testFactoryMethod() timed out after 10 milliseconds"); - } - - @TestFactory - @DisplayName("is applied on testable methods in annotated classes") - Stream appliesTimeoutOnTestableMethodsInAnnotatedClasses() { - return Stream.of(TimeoutAnnotatedClassTestCase.class, InheritedTimeoutAnnotatedClassTestCase.class).map( - testClass -> dynamicTest(testClass.getSimpleName(), () -> { - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(testClass)) // - .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Stream.of("testMethod()", "repetition 1", "repetition 2", "testFactoryMethod()").forEach( - displayName -> { - Execution execution = findExecution(results.allEvents(), displayName); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessageEndingWith("timed out after 10000000 nanoseconds"); - }); - })); - } - - @Test - @DisplayName("fails methods that do not throw InterruptedException") - void failsMethodsWithoutInterruptedException() { - EngineExecutionResults results = executeTestsForClass(MethodWithoutInterruptedExceptionTestCase.class); - - Execution execution = findExecution(results.testEvents(), "methodThatDoesNotThrowInterruptedException()"); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(1)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getStatus()).isEqualTo(FAILED); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("methodThatDoesNotThrowInterruptedException() timed out after 1 millisecond"); - } - - @Test - @DisplayName("is applied on annotated @BeforeAll methods") - void appliesTimeoutOnAnnotatedBeforeAllMethods() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(TimeoutAnnotatedBeforeAllMethodTestCase.class)) // - .configurationParameter(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Execution execution = findExecution(results.containerEvents(), - TimeoutAnnotatedBeforeAllMethodTestCase.class.getSimpleName()); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("setUp() timed out after 10 milliseconds"); - } - - @Test - @DisplayName("is applied on annotated @BeforeEach methods") - void appliesTimeoutOnAnnotatedBeforeEachMethods() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(TimeoutAnnotatedBeforeEachMethodTestCase.class)) // - .configurationParameter(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("setUp() timed out after 10 milliseconds"); - } - - @Test - @DisplayName("is applied on annotated @AfterEach methods") - void appliesTimeoutOnAnnotatedAfterEachMethods() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(TimeoutAnnotatedAfterEachMethodTestCase.class)) // - .configurationParameter(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("tearDown() timed out after 10 milliseconds"); - } - - @Test - @DisplayName("is applied on annotated @AfterAll methods") - void appliesTimeoutOnAnnotatedAfterAllMethods() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(TimeoutAnnotatedAfterAllMethodTestCase.class)) // - .configurationParameter(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Execution execution = findExecution(results.containerEvents(), - TimeoutAnnotatedAfterAllMethodTestCase.class.getSimpleName()); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("tearDown() timed out after 10 milliseconds"); - } - - @TestFactory - @DisplayName("is applied from configuration parameters by default") - Stream appliesDefaultTimeoutsFromConfigurationParameters() { - return Map.of(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "beforeAll()", // - DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "beforeEach()", // - DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "test()", // - DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "testTemplate()", // - DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "testFactory()", // - DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "afterEach()", // - DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "afterAll()" // - ).entrySet().stream().map(entry -> dynamicTest("uses " + entry.getKey() + " config param", () -> { - PlainTestCase.slowMethod = entry.getValue(); - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(PlainTestCase.class)) // - .configurationParameter(entry.getKey(), "1ns") // - .build()); - var failure = results.allEvents().executions().failed() // - .map(execution -> execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .findFirst(); - assertThat(failure).containsInstanceOf(TimeoutException.class); - assertThat(failure.get()).hasMessage(entry.getValue() + " timed out after 1 nanosecond"); - })); - } - - @Test - @DisplayName("does not swallow unrecoverable exceptions") - void doesNotSwallowUnrecoverableExceptions() { - assertThrows(OutOfMemoryError.class, () -> executeTestsForClass(UnrecoverableExceptionTestCase.class)); - } - - @Test - @DisplayName("does not affect tests that don't exceed the timeout") - void doesNotAffectTestsThatDoNotExceedTimeoutDuration() { - executeTestsForClass(NonTimeoutExceedingTestCase.class).allEvents().assertStatistics(stats -> stats.failed(0)); - } - - @Test - @DisplayName("includes fully qualified class name if method is not in the test class") - void includesClassNameIfMethodIsNotInTestClass() { - EngineExecutionResults results = executeTestsForClass(NestedClassWithOuterSetupMethodTestCase.class); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessageEndingWith( - "$NestedClassWithOuterSetupMethodTestCase#setUp() timed out after 10 milliseconds"); - } - - @Test - @DisplayName("reports illegal timeout durations") - void reportsIllegalTimeoutDurations() { - EngineExecutionResults results = executeTestsForClass(IllegalTimeoutDurationTestCase.class); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("timeout duration must be a positive number: 0"); - } - - private Execution findExecution(Events events, String displayName) { - return getOnlyElement(events // - .executions() // - .filter(execution -> execution.getTestDescriptor().getDisplayName().contains(displayName)) // - .collect(toList())); - } - - @Nested - @DisplayName("separate thread") - class SeparateThread { - @Test - @DisplayName("timeout exceeded") - void timeoutExceededInSeparateThread() { - EngineExecutionResults results = executeTestsForClass(TimeoutExceedingSeparateThreadTestCase.class); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - Throwable failure = execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow(); - assertThat(failure) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testMethod() timed out after 100 milliseconds"); - assertThat(failure.getCause()) // - .hasMessageStartingWith("Execution timed out in ") // - .hasStackTraceContaining(TimeoutExceedingSeparateThreadTestCase.class.getName() + ".testMethod"); - } - - @Test - @DisplayName("non timeout exceeded") - void nonTimeoutExceededInSeparateThread() { - executeTestsForClass(NonTimeoutExceedingSeparateThreadTestCase.class).allEvents() // - .assertStatistics(stats -> stats.failed(0)); - } - - @Test - @DisplayName("does not swallow unrecoverable exceptions") - void separateThreadDoesNotSwallowUnrecoverableExceptions() { - assertThrows(OutOfMemoryError.class, - () -> executeTestsForClass(UnrecoverableExceptionInSeparateThreadTestCase.class)); - } - - @Test - @DisplayName("handles invocation exceptions") - void separateThreadHandlesInvocationExceptions() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectMethod(ExceptionInSeparateThreadTestCase.class, "test")) // - .build()); - - Execution execution = findExecution(results.testEvents(), "test()"); - assertThat(execution.getDuration()) // - .isLessThan(Duration.ofSeconds(5)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(RuntimeException.class) // - .hasMessage("Oppps!"); - } - - @Test - @DisplayName("propagates assertion exceptions") - void separateThreadHandlesOpenTestFailedAssertion() { - EngineExecutionResults results = executeTestsForClass(FailedAssertionInSeparateThreadTestCase.class); - - Execution openTestFailure = findExecution(results.testEvents(), "testOpenTestAssertion()"); - assertThat(openTestFailure.getDuration()) // - .isLessThan(Duration.ofSeconds(5)); - assertThat(openTestFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(AssertionFailedError.class); - - Execution javaLangFailure = findExecution(results.testEvents(), "testJavaLangAssertion()"); - assertThat(javaLangFailure.getDuration()) // - .isLessThan(Duration.ofSeconds(5)); - assertThat(javaLangFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(AssertionError.class); - } - - @Test - @DisplayName("when one test is stuck \"forever\" the next tests should not get stuck") - void oneThreadStuckForever() { - EngineExecutionResults results = executeTestsForClass(OneTestStuckForeverAndTheOthersNotTestCase.class); - - Execution stuckExecution = findExecution(results.testEvents(), "stuck()"); - assertThat(stuckExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("stuck() timed out after 10 milliseconds"); - - Execution testZeroExecution = findExecution(results.testEvents(), "testZero()"); - assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getStatus()) // - .isEqualTo(Status.SUCCESSFUL); - - Execution testOneExecution = findExecution(results.testEvents(), "testOne()"); - assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getStatus()) // - .isEqualTo(Status.SUCCESSFUL); - } - - @Test - @DisplayName("mixed same thread and separate thread tests") - void mixedSameThreadAndSeparateThreadTests() { - EngineExecutionResults results = executeTestsForClass(MixedSameThreadAndSeparateThreadTestCase.class); - - Execution stuck = findExecution(results.testEvents(), "testZero()"); - assertThat(stuck.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testZero() timed out after 10 milliseconds"); - - Execution testZeroExecution = findExecution(results.testEvents(), "testOne()"); - assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testOne() timed out after 10 milliseconds"); - - Execution testOneExecution = findExecution(results.testEvents(), "testTwo()"); - assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("testTwo() timed out after 10 milliseconds"); - } - - @Test - @DisplayName("one test is stuck \"forever\" in separate thread and other tests in same thread not") - void oneThreadStuckForeverAndOtherTestsInSameThread() { - EngineExecutionResults results = executeTestsForClass( - OneTestStuckForeverAndTheOthersInSameThreadNotTestCase.class); - - Execution stuckExecution = findExecution(results.testEvents(), "stuck()"); - assertThat(stuckExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("stuck() timed out after 10 milliseconds"); - - Execution testZeroExecution = findExecution(results.testEvents(), "testZero()"); - assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getStatus()) // - .isEqualTo(Status.SUCCESSFUL); - - Execution testOneExecution = findExecution(results.testEvents(), "testOne()"); - assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getStatus()) // - .isEqualTo(Status.SUCCESSFUL); - } - - @Test - @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") - void doesNotApplyTimeoutOnAnnotatedTestMethodsUsingDisabledTimeoutMode() { - EngineExecutionResults results = executeTests(request() // - .selectors(selectMethod(TimeoutExceedingSeparateThreadTestCase.class, "testMethod")) // - .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled").build()); - - Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // - .isEmpty(); - } - - @Nested - @DisplayName("on class level") - class OnClassLevel { - @Test - @DisplayName("timeout exceeded") - void timeoutExceededInSeparateThreadOnClassLevel() { - EngineExecutionResults results = executeTestsForClass(TimeoutExceededOnClassLevelTestCase.class); - - Execution execution = findExecution(results.testEvents(), "exceptionThrown()"); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessage("exceptionThrown() timed out after 100 milliseconds"); - } - - @Test - @DisplayName("non timeout exceeded") - void nonTimeoutExceededInSeparateThreadOnClassLevel() { - executeTestsForClass(NonTimeoutExceededOnClassLevelTestCase.class).allEvents() // - .assertStatistics(stats -> stats.failed(0)); - } - } - } - - static class TimeoutAnnotatedTestMethodTestCase { - @Test - @Timeout(value = 10, unit = MILLISECONDS) - void testMethod() throws Exception { - Thread.sleep(1000); - } - - @RepeatedTest(2) - @Timeout(value = 10, unit = MILLISECONDS) - void testTemplateMethod() throws Exception { - Thread.sleep(1000); - } - - @TestFactory - @Timeout(value = 10, unit = MILLISECONDS) - Stream testFactoryMethod() throws Exception { - Thread.sleep(1000); - return Stream.empty(); - } - } - - static class TimeoutAnnotatedBeforeAllMethodTestCase { - @BeforeAll - @Timeout(value = 10, unit = MILLISECONDS) - static void setUp() throws Exception { - Thread.sleep(1000); - } - - @Test - void testMethod() { - // never called - } - } - - static class TimeoutAnnotatedBeforeEachMethodTestCase { - @BeforeEach - @Timeout(value = 10, unit = MILLISECONDS) - void setUp() throws Exception { - Thread.sleep(1000); - } - - @Test - void testMethod() { - // never called - } - } - - static class TimeoutAnnotatedAfterEachMethodTestCase { - @Test - void testMethod() { - // do nothing - } - - @AfterEach - @Timeout(value = 10, unit = MILLISECONDS) - void tearDown() throws Exception { - Thread.sleep(1000); - } - } - - static class TimeoutAnnotatedAfterAllMethodTestCase { - @Test - void testMethod() { - // do nothing - } - - @AfterAll - @Timeout(value = 10, unit = MILLISECONDS) - static void tearDown() throws Exception { - Thread.sleep(1000); - } - } - - @Timeout(value = 10_000_000, unit = NANOSECONDS) - static class TimeoutAnnotatedClassTestCase { - @Nested - class NestedClass { - @Test - void testMethod() throws Exception { - Thread.sleep(1000); - } - - @RepeatedTest(2) - void testTemplateMethod() throws Exception { - Thread.sleep(1000); - } - - @TestFactory - Stream testFactoryMethod() throws Exception { - Thread.sleep(1000); - return Stream.empty(); - } - } - } - - static class InheritedTimeoutAnnotatedClassTestCase extends TimeoutAnnotatedClassTestCase { - } - - static class MethodWithoutInterruptedExceptionTestCase { - @Test - @Timeout(value = 1, unit = MILLISECONDS) - void methodThatDoesNotThrowInterruptedException() { - new EventuallyInterruptibleInvocation().proceed(); - } - } - - static class PlainTestCase { - - public static String slowMethod; - - @BeforeAll - static void beforeAll() throws Exception { - waitForInterrupt("beforeAll()"); - } - - @BeforeEach - void beforeEach() throws Exception { - waitForInterrupt("beforeEach()"); - } - - @Test - void test() throws Exception { - waitForInterrupt("test()"); - } - - @RepeatedTest(2) - void testTemplate() throws Exception { - waitForInterrupt("testTemplate()"); - } - - @TestFactory - Stream testFactory() throws Exception { - waitForInterrupt("testFactory()"); - return Stream.empty(); - } - - @AfterEach - void afterEach() throws Exception { - waitForInterrupt("afterEach()"); - } - - @AfterAll - static void afterAll() throws Exception { - waitForInterrupt("afterAll()"); - } - - private static void waitForInterrupt(String methodName) throws InterruptedException { - if (methodName.equals(slowMethod)) { - blockUntilInterrupted(); - } - } - } - - static class UnrecoverableExceptionTestCase { - @Test - @Timeout(value = 1, unit = NANOSECONDS) - void test() { - new EventuallyInterruptibleInvocation().proceed(); - throw new OutOfMemoryError(); - } - } - - @Timeout(10) - static class NonTimeoutExceedingTestCase { - @Test - void testMethod() { - } - - @RepeatedTest(1) - void testTemplateMethod() { - } - - @TestFactory - Stream testFactoryMethod() { - return Stream.of(dynamicTest("dynamicTest", () -> { - })); - } - } - - static class NestedClassWithOuterSetupMethodTestCase { - - @Timeout(value = 10, unit = MILLISECONDS) - @BeforeEach - void setUp() throws Exception { - Thread.sleep(1000); - } - - @Nested - class NestedClass { - - @BeforeEach - void setUp() { - } - - @Test - void testMethod() { - } - - } - - } - - static class IllegalTimeoutDurationTestCase { - - @Test - @Timeout(0) - void testMethod() { - } - - } - - static class TimeoutExceedingWithInferredThreadModeTestCase { - @Test - @Timeout(value = 10, unit = MILLISECONDS) - void testMethod() throws InterruptedException { - Thread.sleep(1000); - } - } - - static class TimeoutExceedingSeparateThreadTestCase { - @Test - @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void testMethod() throws InterruptedException { - Thread.sleep(1000); - } - } - - static class NonTimeoutExceedingSeparateThreadTestCase { - @Test - @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void testMethod() { - } - } - - static class UnrecoverableExceptionInSeparateThreadTestCase { - @Test - @Timeout(value = 100, unit = SECONDS, threadMode = SEPARATE_THREAD) - void test() { - throw new OutOfMemoryError(); - } - } - - static class ExceptionInSeparateThreadTestCase { - @Test - @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) - void test() { - throw new RuntimeException("Oppps!"); - } - } - - static class FailedAssertionInSeparateThreadTestCase { - @Test - @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) - void testOpenTestAssertion() { - throw new AssertionFailedError(); - } - - @Test - @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) - void testJavaLangAssertion() { - throw new AssertionError(); - } - } - - @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - static class TimeoutExceededOnClassLevelTestCase { - @Test - void exceptionThrown() throws InterruptedException { - Thread.sleep(1000); - } - } - - @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - static class NonTimeoutExceededOnClassLevelTestCase { - @Test - void test() { - } - } - - @TestMethodOrder(OrderAnnotation.class) - static class OneTestStuckForeverAndTheOthersNotTestCase { - - @Test - @Order(0) - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void stuck() throws InterruptedException { - blockUntilInterrupted(); - } - - @Test - @Order(1) - @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void testZero() { - } - - @Test - @Order(2) - @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void testOne() { - } - } - - static class MixedSameThreadAndSeparateThreadTestCase { - @Test - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void testZero() throws InterruptedException { - Thread.sleep(1000); - } - - @Test - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) - void testOne() throws InterruptedException { - Thread.sleep(1000); - } - - @Test - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void testTwo() throws InterruptedException { - Thread.sleep(1000); - } - } - - @TestMethodOrder(OrderAnnotation.class) - static class OneTestStuckForeverAndTheOthersInSameThreadNotTestCase { - - @Test - @Order(0) - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) - void stuck() throws InterruptedException { - blockUntilInterrupted(); - } - - @Test - @Order(1) - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) - void testZero() { - } - - @Test - @Order(2) - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) - void testOne() { - } - } - - private static void blockUntilInterrupted() throws InterruptedException { - new CountDownLatch(1).await(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java deleted file mode 100644 index 31dda773..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.verify; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout.ThreadMode; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.junit.jupiter.engine.execution.ExtensionValuesStore; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; -import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.SingleThreadExecutorResource; -import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; - -@DisplayName("TimeoutInvocationFactory") -@ExtendWith(MockitoExtension.class) -class TimeoutInvocationFactoryTest { - - @Spy - private final Store store = new NamespaceAwareStore(new ExtensionValuesStore(null), - ExtensionContext.Namespace.create(TimeoutInvocationFactoryTest.class)); - @Mock - private Invocation invocation; - @Mock - private TimeoutDuration timeoutDuration; - private TimeoutInvocationFactory timeoutInvocationFactory; - private TimeoutInvocationParameters parameters; - - @BeforeEach - void setUp() { - parameters = new TimeoutInvocationParameters<>(invocation, timeoutDuration, () -> "description"); - timeoutInvocationFactory = new TimeoutInvocationFactory(store); - } - - @Test - @DisplayName("throws exception when null store is provided on create") - void shouldThrowExceptionWhenInstantiatingWithNullStore() { - assertThatThrownBy(() -> new TimeoutInvocationFactory(null)) // - .hasMessage("store must not be null"); - } - - @Test - @DisplayName("throws exception when null timeout thread mode is provided on create") - void shouldThrowExceptionWhenNullTimeoutThreadModeIsProvidedWhenCreate() { - assertThatThrownBy(() -> timeoutInvocationFactory.create(null, parameters)) // - .hasMessage("thread mode must not be null"); - } - - @Test - @DisplayName("throws exception when null timeout invocation parameters is provided on create") - void shouldThrowExceptionWhenNullTimeoutInvocationParametersIsProvidedWhenCreate() { - assertThatThrownBy(() -> timeoutInvocationFactory.create(ThreadMode.SAME_THREAD, null)) // - .hasMessage("timeout invocation parameters must not be null"); - } - - @Test - @DisplayName("creates timeout invocation for SAME_THREAD thread mode") - void shouldCreateTimeoutInvocationForSameThreadTimeoutThreadMode() { - var invocation = timeoutInvocationFactory.create(ThreadMode.SAME_THREAD, parameters); - assertThat(invocation).isInstanceOf(SameThreadTimeoutInvocation.class); - verify(store).getOrComputeIfAbsent(SingleThreadExecutorResource.class); - } - - @Test - @DisplayName("creates timeout invocation for SEPARATE_THREAD thread mode") - void shouldCreateTimeoutInvocationForSeparateThreadTimeoutThreadMode() { - var invocation = timeoutInvocationFactory.create(ThreadMode.SEPARATE_THREAD, parameters); - assertThat(invocation).isInstanceOf(SeparateThreadTimeoutInvocation.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java deleted file mode 100644 index 9e9533e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension.sub; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * Intentionally in a subpackage in order to properly test deactivation - * of conditions based on patterns. In other words, we do not want this - * condition declared in the same package as the - * {@link org.junit.jupiter.engine.extension.DisabledCondition} - * - * ExecutionCondition always returns disabled, since we want to test the - * deactivation of the condition itself. - * - * @since 5.7 - */ -public class AlwaysDisabledCondition implements ExecutionCondition { - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return ConditionEvaluationResult.disabled("Always Disabled"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java deleted file mode 100644 index d8846b32..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension.sub; - -/** - * Intentionally in a subpackage in order to properly test deactivation - * of conditions based on patterns. In other words, we do not want this - * condition declared in the same package as the - * {@link org.junit.jupiter.engine.extension.DisabledCondition} - * - * ExecutionCondition always returns disabled, since we want to test the - * deactivation of the condition itself. - * - * @since 5.7 - */ -public class AnotherAlwaysDisabledCondition extends AlwaysDisabledCondition { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java deleted file mode 100644 index f2f2cab4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension.sub; - -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Objects; -import java.util.Optional; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * Intentionally in a subpackage in order to properly test deactivation - * of conditions based on patterns. In other words, we do not want this - * condition declared in the same package as the - * {@link org.junit.jupiter.engine.extension.DisabledCondition} - * - * @since 5.0 - */ -public class SystemPropertyCondition implements ExecutionCondition { - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @ExtendWith(SystemPropertyCondition.class) - public @interface SystemProperty { - - String key(); - - String value(); - } - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - Optional optional = findAnnotation(context.getElement(), SystemProperty.class); - - if (optional.isPresent()) { - SystemProperty systemProperty = optional.get(); - String key = systemProperty.key(); - String expected = systemProperty.value(); - String actual = System.getProperty(key); - - if (!Objects.equals(expected, actual)) { - return ConditionEvaluationResult.disabled( - String.format("System property [%s] has a value of [%s] instead of [%s]", key, actual, expected)); - } - } - - return ConditionEvaluationResult.enabled("@SystemProperty is not present"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java deleted file mode 100644 index c2d18426..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.subpackage; - -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @since 5.9 - */ -public class SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { - - @BeforeEach - void beforeEach() { - fail(); - } - - @Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java deleted file mode 100644 index a3b492b8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.support; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.net.URL; -import java.net.URLClassLoader; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.internal.AssumptionViolatedException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link OpenTest4JAndJUnit4AwareThrowableCollector}. - * - * @since 5.5.2 - */ -@TrackLogRecords -class OpenTest4JAndJUnit4AwareThrowableCollectorTests { - - @Test - void simulateJUnit4NotInTheClasspath(LogRecordListener listener) throws Throwable { - TestClassLoader classLoader = new TestClassLoader(true, false); - - doWithCustomClassLoader(classLoader, () -> { - // Ensure that our custom ClassLoader actually throws a ClassNotFoundException - // when attempting to load the AssumptionViolatedException class. - assertThrows(ClassNotFoundException.class, - () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); - - Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); - assertNotNull(ReflectionUtils.newInstance(clazz)); - - // @formatter:off - assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) - .isEqualTo( - "Failed to load class org.junit.internal.AssumptionViolatedException: " + - "only supporting org.opentest4j.TestAbortedException for aborted execution."); - // @formatter:on - }); - } - - @Test - void simulateHamcrestNotInTheClasspath(LogRecordListener listener) throws Throwable { - TestClassLoader classLoader = new TestClassLoader(false, true); - - doWithCustomClassLoader(classLoader, () -> { - // Ensure that our custom ClassLoader actually throws a NoClassDefFoundError - // when attempting to load the AssumptionViolatedException class. - assertThrows(NoClassDefFoundError.class, - () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); - - Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); - assertNotNull(ReflectionUtils.newInstance(clazz)); - - // @formatter:off - assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) - .isEqualTo( - "Failed to load class org.junit.internal.AssumptionViolatedException: " + - "only supporting org.opentest4j.TestAbortedException for aborted execution. " + - "Note that org.junit.internal.AssumptionViolatedException requires that Hamcrest is on the classpath."); - // @formatter:on - }); - } - - private void doWithCustomClassLoader(ClassLoader classLoader, Executable executable) throws Throwable { - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try { - // We have to set our custom ClassLoader as the TCCL so that - // ReflectionUtils uses it (indirectly via ClassLoaderUtils). - Thread.currentThread().setContextClassLoader(classLoader); - - executable.execute(); - } - finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - } - - private static class TestClassLoader extends URLClassLoader { - - private static URL[] CLASSPATH_URLS = new URL[] { - OpenTest4JAndJUnit4AwareThrowableCollector.class.getProtectionDomain().getCodeSource().getLocation() }; - - private final boolean simulateJUnit4Missing; - private final boolean simulateHamcrestMissing; - - public TestClassLoader(boolean simulateJUnit4Missing, boolean simulateHamcrestMissing) { - super(CLASSPATH_URLS, getSystemClassLoader()); - this.simulateJUnit4Missing = simulateJUnit4Missing; - this.simulateHamcrestMissing = simulateHamcrestMissing; - } - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - - // Load a new instance of the OpenTest4JAndJUnit4AwareThrowableCollector class - if (name.equals(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName())) { - return findClass(name); - } - - // Simulate that JUnit 4 is not in the classpath when loading AssumptionViolatedException - if (this.simulateJUnit4Missing && name.equals(AssumptionViolatedException.class.getName())) { - throw new ClassNotFoundException(AssumptionViolatedException.class.getName()); - } - - // Simulate that Hamcrest is not in the classpath when loading AssumptionViolatedException - if (this.simulateHamcrestMissing && name.equals(AssumptionViolatedException.class.getName())) { - throw new NoClassDefFoundError("org/hamcrest/SelfDescribing"); - } - - // Else - return super.loadClass(name); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt deleted file mode 100644 index 9170c22c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.api - -import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals -import org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.platform.commons.util.ExceptionUtils -import org.opentest4j.AssertionFailedError -import java.time.Duration.ofMillis -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicBoolean - -/** - * Unit tests for JUnit Jupiter [Assertions]. - * - * @since 5.5 - */ -internal class KotlinAssertTimeoutAssertionsTests { - - // --- executable ---------------------------------------------------------- - - @Test - fun assertTimeoutForExecutableThatCompletesBeforeTheTimeout() { - changed.get().set(false) - assertTimeout(ofMillis(500)) { changed.get().set(true) } - assertTrue(changed.get().get(), "should have executed in the same thread") - assertTimeout(ofMillis(500), "message") { } - assertTimeout(ofMillis(500), "message") { } - } - - @Test - fun assertTimeoutForExecutableThatThrowsAnException() { - val exception = assertThrows { - assertTimeout(ofMillis(500)) { - throw RuntimeException("not this time") - } - } - assertMessageEquals(exception, "not this time") - } - - @Test - fun assertTimeoutForExecutableThatThrowsAnAssertionFailedError() { - val exception = assertThrows { - assertTimeout(ofMillis(500)) { fail("enigma") } - } - assertMessageEquals(exception, "enigma") - } - - @Test - fun assertTimeoutForExecutableThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeout(ofMillis(10)) { this.nap() } - } - assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by") - } - - @Test - fun assertTimeoutWithMessageForExecutableThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeout(ofMillis(10), "Tempus Fugit") { this.nap() } - } - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") - } - - @Test - fun assertTimeoutWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeout(ofMillis(10), { "Tempus" + " " + "Fugit" }) { this.nap() } - } - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") - } - - // --- supplier ------------------------------------------------------------ - - @Test - fun assertTimeoutForSupplierThatCompletesBeforeTheTimeout() { - changed.get().set(false) - val result = assertTimeout(ofMillis(500)) { - changed.get().set(true) - "Tempus Fugit" - } - assertTrue(changed.get().get(), "should have executed in the same thread") - assertEquals("Tempus Fugit", result) - assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), "message") { "Tempus Fugit" }) - assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), { "message" }, { "Tempus Fugit" })) - } - - @Test - fun assertTimeoutForSupplierThatThrowsAnException() { - val exception = assertThrows { - assertTimeout(ofMillis(500)) { - ExceptionUtils.throwAsUncheckedException(RuntimeException("not this time")) - } - } - assertMessageEquals(exception, "not this time") - } - - @Test - fun assertTimeoutForSupplierThatThrowsAnAssertionFailedError() { - val exception = assertThrows { - assertTimeout(ofMillis(500)) { - fail("enigma") - } - } - assertMessageEquals(exception, "enigma") - } - - @Test - fun assertTimeoutForSupplierThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeout(ofMillis(10)) { - nap() - } - } - assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by") - } - - @Test - fun assertTimeoutWithMessageForSupplierThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeout(ofMillis(10), "Tempus Fugit") { - nap() - } - } - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") - } - - @Test - fun assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeout(ofMillis(10), { "Tempus" + " " + "Fugit" }) { - nap() - } - } - assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") - } - - // -- executable - preemptively --- - - @Test - fun assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { - changed.get().set(false) - assertTimeoutPreemptively(ofMillis(500)) { changed.get().set(true) } - assertFalse(changed.get().get(), "should have executed in a different thread") - assertTimeoutPreemptively(ofMillis(500), "message") {} - assertTimeoutPreemptively(ofMillis(500), { "message" }) {} - } - - @Test - fun assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { - val exception = assertThrows { - assertTimeoutPreemptively(ofMillis(500)) { throw RuntimeException("not this time") } - } - assertMessageEquals(exception, "not this time") - } - - @Test - fun assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { - val exception = assertThrows { - assertTimeoutPreemptively(ofMillis(500)) { fail("enigma") } - } - assertMessageEquals(exception, "enigma") - } - - @Test - fun assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeoutPreemptively(ofMillis(10)) { waitForInterrupt() } - } - assertMessageEquals(error, "execution timed out after 10 ms") - } - - @Test - fun assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeoutPreemptively(ofMillis(10), "Tempus Fugit") { waitForInterrupt() } - } - assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") - } - - @Test - fun assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeoutPreemptively(ofMillis(10), { "Tempus" + " " + "Fugit" }) { waitForInterrupt() } - } - assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") - } - - @Test - fun assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { - assertTimeoutPreemptively(ofMillis(500), { "Tempus" + " " + "Fugit" }) {} - } - - // -- supplier - preemptively --- - - @Test - fun assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { - changed.get().set(false) - val result = assertTimeoutPreemptively(ofMillis(500)) { - changed.get().set(true) - "Tempus Fugit" - } - assertFalse(changed.get().get(), "should have executed in a different thread") - assertEquals("Tempus Fugit", result) - assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), "message") { "Tempus Fugit" }) - assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), { "message" }) { "Tempus Fugit" }) - } - - @Test - fun assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { - val exception = assertThrows { - assertTimeoutPreemptively(ofMillis(500)) { - ExceptionUtils.throwAsUncheckedException(RuntimeException("not this time")) - } - } - assertMessageEquals(exception, "not this time") - } - - @Test - fun assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { - val exception = assertThrows { - assertTimeoutPreemptively(ofMillis(500)) { - fail("enigma") - } - } - assertMessageEquals(exception, "enigma") - } - - @Test - fun assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeoutPreemptively(ofMillis(10)) { - waitForInterrupt() - } - } - assertMessageEquals(error, "execution timed out after 10 ms") - } - - @Test - fun assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeoutPreemptively(ofMillis(10), "Tempus Fugit") { - waitForInterrupt() - } - } - assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") - } - - @Test - fun assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { - val error = assertThrows { - assertTimeoutPreemptively(ofMillis(10), { "Tempus" + " " + "Fugit" }) { - waitForInterrupt() - } - } - assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") - } - - /** - * Take a nap for 100 milliseconds. - */ - private fun nap() { - val start = System.currentTimeMillis() - // workaround for imprecise clocks (yes, Windows, I'm talking about you) - do { - Thread.sleep(100) - } while (System.currentTimeMillis() - start < 100) - } - - private fun waitForInterrupt() { - try { - CountDownLatch(1).await() - } catch (ignore: InterruptedException) { - // ignore - } - } - - companion object { - private val changed = ThreadLocal.withInitial { AtomicBoolean(false) } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt deleted file mode 100644 index d283aad8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.api - -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals -import org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith -import org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DynamicContainer.dynamicContainer -import org.junit.jupiter.api.DynamicTest.dynamicTest -import org.opentest4j.AssertionFailedError -import org.opentest4j.MultipleFailuresError -import java.util.stream.Stream -import kotlin.reflect.KClass - -/** - * Unit tests for JUnit Jupiter [org.junit.jupiter.api] top-level assertion functions. - */ -class KotlinAssertionsTests { - - // Bonus: no null check tests as these get handled by the compiler! - - @Test - fun `assertAll with functions that do not throw exceptions`() { - assertAll(Stream.of({ assertTrue(true) }, { assertFalse(false) })) - assertAll("heading", Stream.of({ assertTrue(true) }, { assertFalse(false) })) - assertAll(setOf({ assertTrue(true) }, { assertFalse(false) })) - assertAll("heading", setOf({ assertTrue(true) }, { assertFalse(false) })) - assertAll({ assertTrue(true) }, { assertFalse(false) }) - assertAll("heading", { assertTrue(true) }, { assertFalse(false) }) - } - - @Test - fun `assertAll with functions that throw AssertionErrors`() { - val multipleFailuresError = assertThrows { - assertAll( - { assertFalse(true) }, - { assertFalse(true) } - ) - } - assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) - } - - @Test - fun `assertThrows and fail`() { - assertThrows { fail("message") } - assertThrows { fail("message", AssertionError()) } - assertThrows { fail("message", null) } - assertThrows("should fail") { fail({ "message" }) } - assertThrows({ "should fail" }) { fail(AssertionError()) } - assertThrows({ "should fail" }) { fail(null as Throwable?) } - } - - @Test - fun `expected context exception testing`() = runBlocking { - assertThrows("Should fail async") { - suspend { fail("Should fail async") }() - } - } - - @TestFactory - fun assertDoesNotThrow(): Stream = Stream.of( - dynamicContainer( - "succeeds when no exception thrown", - Stream.of( - dynamicTest("for no arguments variant") { - val actual = assertDoesNotThrow { 1 } - assertEquals(1, actual) - }, - dynamicTest("for no arguments variant (suspended)") { - runBlocking { - val actual = assertDoesNotThrow { suspend { 1 }() } - assertEquals(1, actual) - } - }, - dynamicTest("for message variant") { - val actual = assertDoesNotThrow("message") { 2 } - assertEquals(2, actual) - }, - dynamicTest("for message variant (suspended)") { - runBlocking { - val actual = assertDoesNotThrow("message") { suspend { 2 }() } - assertEquals(2, actual) - } - }, - dynamicTest("for message supplier variant") { - val actual = assertDoesNotThrow({ "message" }) { 3 } - assertEquals(3, actual) - }, - dynamicTest("for message supplier variant (suspended)") { - runBlocking { - val actual = assertDoesNotThrow({ "message" }) { suspend { 3 }() } - assertEquals(3, actual) - } - } - ) - ), - dynamicContainer( - "fails when an exception is thrown", - Stream.of( - dynamicTest("for no arguments variant") { - val exception = assertThrows { - assertDoesNotThrow { - fail("fail") - } - } - assertMessageEquals( - exception, - "Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" - ) - }, - dynamicTest("for no arguments variant (suspended)") { - runBlocking { - val exception = assertThrows { - assertDoesNotThrow { - suspend { fail("fail") }() - } - } - assertMessageEquals( - exception, - "Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" - ) - } - }, - dynamicTest("for message variant") { - val exception = assertThrows { - assertDoesNotThrow("Does not throw") { - fail("fail") - } - } - assertMessageEquals( - exception, - "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" - ) - }, - dynamicTest("for message variant (suspended)") { - runBlocking { - val exception = assertThrows { - assertDoesNotThrow("Does not throw") { - suspend { fail("fail") }() - } - } - assertMessageEquals( - exception, - "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" - ) - } - }, - dynamicTest("for message supplier variant") { - val exception = assertThrows { - assertDoesNotThrow({ "Does not throw" }) { - fail("fail") - } - } - assertMessageEquals( - exception, - "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" - ) - }, - dynamicTest("for message supplier variant (suspended)") { - runBlocking { - val exception = assertThrows { - assertDoesNotThrow({ "Does not throw" }) { - suspend { fail("fail") }() - } - } - assertMessageEquals( - exception, - "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" - ) - } - } - ) - ) - ) - - @Test - fun `assertAll with stream of functions that throw AssertionErrors`() { - val multipleFailuresError = assertThrows("Should have thrown multiple errors") { - assertAll(Stream.of({ assertFalse(true) }, { assertFalse(true) })) - } - assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) - } - - @Test - fun `assertAll with collection of functions that throw AssertionErrors`() { - val multipleFailuresError = assertThrows("Should have thrown multiple errors") { - assertAll(setOf({ assertFalse(true) }, { assertFalse(true) })) - } - assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) - } - - @Test - fun `assertThrows with function that does not throw an exception`() { - val assertionMessage = "This will not throw an exception" - val error = assertThrows("assertThrows did not throw the correct exception") { - assertThrows(assertionMessage) { } - // This should never execute: - expectAssertionFailedError() - } - assertMessageStartsWith(error, assertionMessage) - } - - companion object { - fun assertExpectedExceptionTypes( - multipleFailuresError: MultipleFailuresError, - vararg exceptionTypes: KClass - ) = - AssertAllAssertionsTests.assertExpectedExceptionTypes( - multipleFailuresError, *exceptionTypes.map { it.java }.toTypedArray() - ) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt deleted file mode 100644 index 00023d0a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.api - -import org.junit.jupiter.api.AssertEquals.assertEquals -import org.junit.jupiter.api.AssertionTestUtils.assertEmptyMessage -import org.junit.jupiter.api.AssertionTestUtils.assertMessageContains -import org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals -import org.opentest4j.AssertionFailedError -import java.util.stream.Stream - -class KotlinFailAssertionsTests { - - @Test - fun `fail with string`() { - val message = "test" - val ex = assertThrows { - fail(message) - } - assertMessageEquals(ex, message) - } - - @Test - fun `fail with message supplier`() { - val message = "test" - val ex = assertThrows { - fail { message } - } - assertMessageEquals(ex, message) - } - - @Test - fun `fail with null string`() { - val ex = assertThrows { - fail(null as String?) - } - assertEmptyMessage(ex) - } - - @Test - fun `fail with null message supplier`() { - val ex = assertThrows { - fail(null as (() -> String)?) - } - assertEmptyMessage(ex) - } - - @Test - fun `fail with string and throwable`() { - val message = "message" - val throwableCause = "cause" - val ex = assertThrows { - fail(message, Throwable(throwableCause)) - } - assertMessageEquals(ex, message) - val cause = ex.cause - assertMessageContains(cause, throwableCause) - } - - @Test - fun `fail with throwable`() { - val throwableCause = "cause" - val ex = assertThrows { - fail(Throwable(throwableCause)) - } - assertEmptyMessage(ex) - val cause = ex.cause - assertMessageContains(cause, throwableCause) - } - - @Test - fun `fail with string and null throwable`() { - val message = "message" - val ex = assertThrows { - fail(message, null) - } - assertMessageEquals(ex, message) - if (ex.cause != null) { - throw AssertionError("Cause should have been null") - } - } - - @Test - fun `fail with null string and throwable`() { - val throwableCause = "cause" - val ex = assertThrows { - fail(null, Throwable(throwableCause)) - } - assertEmptyMessage(ex) - val cause = ex.cause - assertMessageContains(cause, throwableCause) - } - - @Test - fun `fail usable as a stream expression`() { - val count = Stream.empty() - .peek { _ -> fail("peek should never be called") } - .filter { _ -> fail("filter should never be called", Throwable("cause")) } - .map { _ -> fail(Throwable("map should never be called")) } - .sorted { _, _ -> fail { "sorted should never be called" } } - .count() - assertEquals(0L, count) - } - - @Test - fun `fail usable as a sequence expression`() { - val count = emptyList() - .asSequence() - .onEach { _ -> fail("peek should never be called") } - .filter { _ -> fail("filter should never be called", Throwable("cause")) } - .map { _ -> fail(Throwable("map should never be called")) } - .count() - assertEquals(0, count) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt deleted file mode 100644 index 1b919a9f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.engine.kotlin - -import org.junit.jupiter.api.Test - -class ArbitraryNamingKotlinTestCase { - companion object { - @JvmField - val METHOD_NAME = "\uD83E\uDD86 ~|~test with a really, (really) terrible name & that needs to be changed!~|~" - } - - @Suppress("DANGEROUS_CHARACTERS") - @Test - fun `🦆 ~|~test with a really, (really) terrible name & that needs to be changed!~|~`() { } - - @Test - fun `test name ends with parentheses()`() { } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt deleted file mode 100644 index f5c4e5b8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.engine.kotlin - -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS - -@TestInstance(PER_CLASS) -class InstancePerClassKotlinTestCase { - - companion object { - @JvmField - val TEST_INSTANCES: MutableMap> = HashMap() - } - - @BeforeAll - fun beforeAll() { - increment("beforeAll") - } - - @BeforeEach - fun beforeEach() { - increment("beforeEach") - } - - @AfterEach - fun afterEach() { - increment("afterEach") - } - - @AfterAll - fun afterAll() { - increment("afterAll") - } - - @Test - fun firstTest() { - increment("test") - } - - @Test - fun secondTest() { - increment("test") - } - - private fun increment(name: String) { - TEST_INSTANCES.computeIfAbsent(this, { _ -> HashMap() }) - .compute(name, { _, oldValue -> (oldValue ?: 0) + 1 }) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt deleted file mode 100644 index b5845f9a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.engine.kotlin - -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -class InstancePerMethodKotlinTestCase { - - companion object { - @JvmField - val TEST_INSTANCES: MutableMap> = LinkedHashMap() - - @JvmStatic - @BeforeAll - fun beforeAll() { - increment(this, "beforeAll") - } - - @JvmStatic - @AfterAll - fun afterAll() { - increment(this, "afterAll") - } - - private fun increment(instance: Any, name: String) { - TEST_INSTANCES.computeIfAbsent(instance, { _ -> LinkedHashMap() }) - .compute(name, { _, oldValue -> (oldValue ?: 0) + 1 }) - } - } - - @BeforeEach - fun beforeEach() { - increment(this, "beforeEach") - } - - @AfterEach - fun afterEach() { - increment(this, "afterEach") - } - - @Test - fun firstTest() { - increment(this, "test") - } - - @Test - fun secondTest() { - increment(this, "test") - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension deleted file mode 100644 index 758c3e55..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension +++ /dev/null @@ -1 +0,0 @@ -org.junit.jupiter.engine.extension.ServiceLoaderExtension diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml deleted file mode 100644 index d734a7fa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java deleted file mode 100644 index e80608fb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.discovery; - -import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; - -import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; -import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; -import org.junit.platform.engine.UniqueId; - -/** - * Test data builder for unique IDs for JupiterTestEngine. - * - * Used to decouple tests from concrete unique ID strings. - * - * @since 5.0 - */ -public class JupiterUniqueIdBuilder { - - public static UniqueId uniqueIdForClass(Class clazz) { - UniqueId containerId = engineId(); - if (isInnerClass(clazz)) { - containerId = uniqueIdForClass(clazz.getEnclosingClass()); - return containerId.append(NestedClassTestDescriptor.SEGMENT_TYPE, clazz.getSimpleName()); - } - return containerId.append(ClassTestDescriptor.SEGMENT_TYPE, clazz.getName()); - } - - public static UniqueId uniqueIdForTopLevelClass(String className) { - return engineId().append(ClassTestDescriptor.SEGMENT_TYPE, className); - } - - public static UniqueId uniqueIdForMethod(Class clazz, String methodPart) { - return uniqueIdForClass(clazz).append(TestMethodTestDescriptor.SEGMENT_TYPE, methodPart); - } - - public static UniqueId uniqueIdForTestFactoryMethod(Class clazz, String methodPart) { - return uniqueIdForClass(clazz).append(TestFactoryTestDescriptor.SEGMENT_TYPE, methodPart); - } - - public static UniqueId uniqueIdForTestTemplateMethod(Class clazz, String methodPart) { - return uniqueIdForClass(clazz).append(TestTemplateTestDescriptor.SEGMENT_TYPE, methodPart); - } - - public static UniqueId appendTestTemplateInvocationSegment(UniqueId parentId, int index) { - return parentId.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); - } - - public static UniqueId engineId() { - return UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md deleted file mode 100644 index 730e2c8e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Module junit-jupiter-migrationsupport - -This module provides support for JUnit 4 rules within JUnit Jupiter. -Currently, this support is limited to subclasses of the ```org.junit.rules.Verifier``` -and ```org.junit.rules.ExternalResource``` rules of JUnit 4, respectively. - -Please note that a general support for arbitrary ```org.junit.rules.TestRule``` -implementations is not possible within the JUnit Jupiter extension model. - -The main purpose of this module is to facilitate the migration of large -JUnit 4 codebases containing such JUnit 4 rules by minimizing the effort -needed to run such legacy tests under JUnit 5. -By using one of the two provided class-level extensions on a test class -such rules in legacy code bases can be left unchanged -including the JUnit 4 rule import statements. - -However, if you intend to develop a *new* extension for -JUnit 5 please use the new extension model of JUnit Jupiter instead -of the rule-based model of JUnit 4. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts deleted file mode 100644 index ff570cc7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - `java-library-conventions` - `junit4-compatibility` - `testing-conventions` -} - -description = "JUnit Jupiter Migration Support" - -dependencies { - api(platform(projects.junitBom)) - api(libs.junit4) - api(projects.junitJupiterApi) - - compileOnlyApi(libs.apiguardian) - - testImplementation(projects.junitJupiterEngine) - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteEngine) - testImplementation(projects.junitPlatformTestkit) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -tasks.jar { - bundle { - bnd(""" - # Import JUnit4 packages with a version - Import-Package: \ - ${extra["importAPIGuardian"]},\ - org.junit;version="[${libs.versions.junit4Min.get()},5)",\ - org.junit.platform.commons.logging;status=INTERNAL,\ - org.junit.rules;version="[${libs.versions.junit4Min.get()},5)",\ - * - """) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java deleted file mode 100644 index a5ee8bc8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.migrationsupport.conditions.IgnoreCondition; -import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; -import org.junit.jupiter.migrationsupport.rules.ExpectedExceptionSupport; -import org.junit.jupiter.migrationsupport.rules.ExternalResourceSupport; -import org.junit.jupiter.migrationsupport.rules.VerifierSupport; - -/** - * {@code EnableJUnit4MigrationSupport} is a class-level annotation that - * enables all JUnit 4 migration support within JUnit Jupiter. - * - *

Specifically, this annotation registers all extensions supported by - * {@link EnableRuleMigrationSupport @EnableRuleMigrationSupport} and provides - * support for JUnit 4's {@link org.junit.Ignore @Ignore} annotation for - * disabling test classes and test methods. - * - *

Technically speaking, {@code @EnableJUnit4MigrationSupport} is a composed - * annotation which registers all of the following migration extensions: - * {@link VerifierSupport}, {@link ExternalResourceSupport}, - * {@link ExpectedExceptionSupport}, and {@link IgnoreCondition}. Note, however, - * that you can optionally register one or more of these extensions explicitly - * without the use of this composed annotation. - * - * @since 5.4 - * @see ExternalResourceSupport - * @see VerifierSupport - * @see ExpectedExceptionSupport - * @see IgnoreCondition - * @see EnableRuleMigrationSupport - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@API(status = STABLE, since = "5.7") -@EnableRuleMigrationSupport -@ExtendWith(IgnoreCondition.class) -public @interface EnableJUnit4MigrationSupport { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java deleted file mode 100644 index 477cedcb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.conditions; - -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; - -import java.lang.reflect.AnnotatedElement; - -import org.apiguardian.api.API; -import org.junit.Ignore; -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.util.StringUtils; - -/** - * {@link ExecutionCondition} that supports JUnit 4's {@link Ignore @Ignore} - * annotation. - * - * @since 5.4 - * @see org.junit.Ignore @Ignore - * @see org.junit.jupiter.api.Disabled @Disabled - * @see #evaluateExecutionCondition(ExtensionContext) - * @see org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport - */ -@API(status = STABLE, since = "5.7") -public class IgnoreCondition implements ExecutionCondition { - - private static final ConditionEvaluationResult ENABLED = // - ConditionEvaluationResult.enabled("@org.junit.Ignore is not present"); - - /** - * Containers/tests are disabled if {@link Ignore @Ignore} is present on - * the test class or method. - */ - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - AnnotatedElement element = context.getElement().orElse(null); - return findAnnotation(element, Ignore.class) // - .map(annotation -> toResult(element, annotation)) // - .orElse(ENABLED); - } - - private ConditionEvaluationResult toResult(AnnotatedElement element, Ignore annotation) { - String value = annotation.value(); - String reason = StringUtils.isNotBlank(value) ? value : element + " is disabled via @org.junit.Ignore"; - return ConditionEvaluationResult.disabled(reason); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java deleted file mode 100644 index 530ff605..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Extensions which provide support for conditional test execution features of - * JUnit 4 (e.g., the {@link org.junit.Ignore @Ignore} annotation) within JUnit - * Jupiter. - */ - -package org.junit.jupiter.migrationsupport.conditions; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java deleted file mode 100644 index 236a13fd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Support for migrating from JUnit 4 to JUnit Jupiter. - */ - -package org.junit.jupiter.migrationsupport; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java deleted file mode 100644 index c215ecc8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * This class-level annotation enables native JUnit 4 rule support - * within JUnit Jupiter. - * - *

Currently, rules of type {@code Verifier}, {@code ExternalResource}, - * and {@code ExpectedException} rules are supported. - * - *

{@code @EnableRuleMigrationSupport} is a composed annotation which - * enables all supported extensions: {@link VerifierSupport}, - * {@link ExternalResourceSupport}, and {@link ExpectedExceptionSupport}. - * - * @since 5.0 - * @see ExternalResourceSupport - * @see VerifierSupport - * @see ExpectedExceptionSupport - * @see org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@API(status = STABLE, since = "5.7") -@ExtendWith(ExternalResourceSupport.class) -@ExtendWith(VerifierSupport.class) -@ExtendWith(ExpectedExceptionSupport.class) -public @interface EnableRuleMigrationSupport { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java deleted file mode 100644 index 538a053b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; -import org.junit.jupiter.migrationsupport.rules.adapter.ExpectedExceptionAdapter; -import org.junit.rules.ExpectedException; - -/** - * This {@code Extension} provides native support for the - * {@link ExpectedException} rule from JUnit 4. - * - *

By using this class-level extension on a test class, - * {@code ExpectedException} can continue to be used. - * - *

However, you should rather switch to - * {@link org.junit.jupiter.api.Assertions#assertThrows} for new code. - * - * @since 5.0 - * @see org.junit.jupiter.api.Assertions#assertThrows - * @see org.junit.rules.ExpectedException - * @see org.junit.rules.TestRule - * @see org.junit.Rule - */ -@API(status = STABLE, since = "5.7") -public class ExpectedExceptionSupport implements AfterEachCallback, TestExecutionExceptionHandler { - - private static final String EXCEPTION_WAS_HANDLED = "exceptionWasHandled"; - - private final TestRuleSupport support = new TestRuleSupport(ExpectedExceptionAdapter::new, ExpectedException.class); - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { - getStore(context).put(EXCEPTION_WAS_HANDLED, TRUE); - this.support.handleTestExecutionException(context, throwable); - } - - @Override - public void afterEach(ExtensionContext context) throws Exception { - boolean handled = getStore(context).getOrComputeIfAbsent(EXCEPTION_WAS_HANDLED, key -> FALSE, Boolean.class); - if (!handled) { - this.support.afterEach(context); - } - } - - private Store getStore(ExtensionContext context) { - return context.getStore(Namespace.create(getClass(), context.getUniqueId())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java deleted file mode 100644 index f31d6bc1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.migrationsupport.rules.adapter.ExternalResourceAdapter; -import org.junit.rules.ExternalResource; - -/** - * This {@code Extension} provides native support for subclasses of - * the {@link ExternalResource} rule from JUnit 4. - * - *

{@code @Rule}-annotated fields as well as methods are supported. - * - *

By using this class-level extension on a test class such - * {@code ExternalResource} implementations in legacy code bases - * can be left unchanged including the JUnit 4 rule import statements. - * - *

However, if you intend to develop a new extension for - * JUnit 5 please use the new extension model of JUnit Jupiter instead - * of the rule-based model of JUnit 4. - * - * @since 5.0 - * @see org.junit.rules.ExternalResource - * @see org.junit.rules.TestRule - * @see org.junit.Rule - */ -@API(status = STABLE, since = "5.7") -public class ExternalResourceSupport implements BeforeEachCallback, AfterEachCallback { - - private final TestRuleSupport support = new TestRuleSupport(ExternalResourceAdapter::new, ExternalResource.class); - - @Override - public void beforeEach(ExtensionContext context) throws Exception { - this.support.beforeEach(context); - } - - @Override - public void afterEach(ExtensionContext context) throws Exception { - this.support.afterEach(context); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java deleted file mode 100644 index 99a24d9b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static java.util.Collections.unmodifiableList; -import static org.junit.platform.commons.util.AnnotationUtils.findPublicAnnotatedFields; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.junit.Rule; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; -import org.junit.jupiter.migrationsupport.rules.adapter.AbstractTestRuleAdapter; -import org.junit.jupiter.migrationsupport.rules.adapter.GenericBeforeAndAfterAdvice; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedField; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMethod; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.rules.TestRule; - -/** - * @since 5.0 - */ -class TestRuleSupport implements BeforeEachCallback, TestExecutionExceptionHandler, AfterEachCallback { - - private final Class ruleType; - private final Function adapterGenerator; - - TestRuleSupport(Function adapterGenerator, - Class ruleType) { - - this.adapterGenerator = adapterGenerator; - this.ruleType = ruleType; - } - - /** - * @see org.junit.runners.BlockJUnit4ClassRunner#withRules - * @see org.junit.rules.RunRules - */ - @SuppressWarnings("JavadocReference") - private List findRuleAnnotatedMembers(Object testInstance) { - List result = new ArrayList<>(); - // @formatter:off - // Instantiate rules from methods by calling them - findAnnotatedMethods(testInstance).stream() - .map(method -> new TestRuleAnnotatedMethod(testInstance, method)) - .forEach(result::add); - // Fields are already instantiated because we have a test instance - findAnnotatedFields(testInstance).stream() - .map(field -> new TestRuleAnnotatedField(testInstance, field)) - .forEach(result::add); - // @formatter:on - // Due to how rules are applied (see RunRules), the last rule gets called first. - // Rules from fields get called before those from methods. - // Thus, we first add methods and then fields and reverse the list in the end. - Collections.reverse(result); - return unmodifiableList(result); - } - - private List findAnnotatedMethods(Object testInstance) { - Predicate isRuleMethod = method -> isAnnotated(method, Rule.class); - Predicate hasCorrectReturnType = method -> TestRule.class.isAssignableFrom(method.getReturnType()); - - return findMethods(testInstance.getClass(), isRuleMethod.and(hasCorrectReturnType)); - } - - private List findAnnotatedFields(Object testInstance) { - return findPublicAnnotatedFields(testInstance.getClass(), TestRule.class, Rule.class); - } - - @Override - public void beforeEach(ExtensionContext context) { - invokeAppropriateMethodOnRuleAnnotatedMembers(context, false, GenericBeforeAndAfterAdvice::before); - } - - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { - int numRuleAnnotatedMembers = invokeAppropriateMethodOnRuleAnnotatedMembers(context, true, - advice -> advice.handleTestExecutionException(throwable)); - - // If no appropriate @Rule annotated members were discovered, we then - // have to rethrow the exception in order not to silently swallow it. - // Fixes bug: https://github.com/junit-team/junit5/issues/1069 - if (numRuleAnnotatedMembers == 0) { - throw throwable; - } - } - - @Override - public void afterEach(ExtensionContext context) { - invokeAppropriateMethodOnRuleAnnotatedMembers(context, true, GenericBeforeAndAfterAdvice::after); - } - - /** - * @return the number of appropriate rule-annotated members that were discovered - */ - private int invokeAppropriateMethodOnRuleAnnotatedMembers(ExtensionContext context, boolean reverseOrder, - AdviceInvoker adviceInvoker) { - - List ruleAnnotatedMembers = getRuleAnnotatedMembers(context); - if (reverseOrder) { - Collections.reverse(ruleAnnotatedMembers); - } - - AtomicInteger counter = new AtomicInteger(); - - // @formatter:off - ruleAnnotatedMembers.stream() - .filter(annotatedMember -> this.ruleType.isInstance(annotatedMember.getTestRule())) - .map(this.adapterGenerator) - .forEach(advice -> { - adviceInvoker.invokeAndMaskCheckedExceptions(advice); - counter.incrementAndGet(); - }); - // @formatter:on - - return counter.get(); - } - - /** - * @return a modifiable copy of the list of rule-annotated members - */ - @SuppressWarnings("unchecked") - private List getRuleAnnotatedMembers(ExtensionContext context) { - Object testInstance = context.getRequiredTestInstance(); - Namespace namespace = Namespace.create(TestRuleSupport.class, context.getRequiredTestClass()); - // @formatter:off - return new ArrayList<>(context.getStore(namespace) - .getOrComputeIfAbsent("rule-annotated-members", key -> findRuleAnnotatedMembers(testInstance), List.class)); - // @formatter:on - } - - @FunctionalInterface - private interface AdviceInvoker { - - default void invokeAndMaskCheckedExceptions(GenericBeforeAndAfterAdvice advice) { - try { - invoke(advice); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(t); - } - } - - void invoke(GenericBeforeAndAfterAdvice advice) throws Throwable; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java deleted file mode 100644 index dca4784f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.migrationsupport.rules.adapter.VerifierAdapter; -import org.junit.rules.Verifier; - -/** - * This {@code Extension} provides native support for subclasses of - * the {@link Verifier} rule from JUnit 4. - * - *

{@code @Rule}-annotated fields as well as methods are supported. - * - *

By using this class-level extension on a test class such - * {@code Verifier} implementations in legacy code bases - * can be left unchanged including the JUnit 4 rule import statements. - * - *

However, if you intend to develop a new extension for - * JUnit 5 please use the new extension model of JUnit Jupiter instead - * of the rule-based model of JUnit 4. - * - * @since 5.0 - * @see org.junit.rules.Verifier - * @see org.junit.rules.TestRule - * @see org.junit.Rule - */ -@API(status = STABLE, since = "5.7") -public class VerifierSupport implements AfterEachCallback { - - private final TestRuleSupport support = new TestRuleSupport(VerifierAdapter::new, Verifier.class); - - @Override - public void afterEach(ExtensionContext context) throws Exception { - this.support.afterEach(context); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java deleted file mode 100644 index 223f609e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.adapter; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; -import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; - -import java.lang.reflect.Method; - -import org.apiguardian.api.API; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.rules.TestRule; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public abstract class AbstractTestRuleAdapter implements GenericBeforeAndAfterAdvice { - - private final TestRule target; - - public AbstractTestRuleAdapter(TestRuleAnnotatedMember annotatedMember, Class adapteeClass) { - this.target = annotatedMember.getTestRule(); - Preconditions.condition(adapteeClass.isAssignableFrom(this.target.getClass()), - () -> adapteeClass + " is not assignable from " + this.target.getClass()); - } - - protected Object executeMethod(String name) { - return executeMethod(name, new Class[0]); - } - - protected Object executeMethod(String methodName, Class[] parameterTypes, Object... arguments) { - Method method = findMethod(this.target.getClass(), methodName, parameterTypes).orElseThrow( - () -> new JUnitException(String.format("Failed to find method %s(%s) in class %s", methodName, - ClassUtils.nullSafeToString(parameterTypes), this.target.getClass().getName()))); - - return invokeMethod(method, this.target, arguments); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java deleted file mode 100644 index 1b1971b2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.adapter; - -import static java.lang.Boolean.TRUE; -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; -import org.junit.rules.ExpectedException; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class ExpectedExceptionAdapter extends AbstractTestRuleAdapter { - - public ExpectedExceptionAdapter(TestRuleAnnotatedMember annotatedMember) { - super(annotatedMember, ExpectedException.class); - } - - @Override - public void handleTestExecutionException(Throwable cause) throws Throwable { - executeMethod("handleException", new Class[] { Throwable.class }, cause); - } - - @Override - public void after() { - if (TRUE.equals(executeMethod("isAnyExceptionExpected"))) { - executeMethod("failDueToMissingException"); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java deleted file mode 100644 index b6905f42..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.adapter; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; -import org.junit.rules.ExternalResource; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class ExternalResourceAdapter extends AbstractTestRuleAdapter { - - public ExternalResourceAdapter(TestRuleAnnotatedMember annotatedMember) { - super(annotatedMember, ExternalResource.class); - } - - @Override - public void before() { - executeMethod("before"); - } - - @Override - public void after() { - executeMethod("after"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java deleted file mode 100644 index ad384ce9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.adapter; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public interface GenericBeforeAndAfterAdvice { - - default void before() { - } - - default void handleTestExecutionException(Throwable cause) throws Throwable { - } - - default void after() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java deleted file mode 100644 index ec00fc82..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.adapter; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; -import org.junit.rules.Verifier; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public class VerifierAdapter extends AbstractTestRuleAdapter { - - public VerifierAdapter(TestRuleAnnotatedMember annotatedMember) { - super(annotatedMember, Verifier.class); - } - - @Override - public void after() { - executeMethod("verify"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java deleted file mode 100644 index d69bee97..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Simple wrappers for JUnit 4 rules to overcome visibility limitations of the JUnit 4 implementations. - */ - -package org.junit.jupiter.migrationsupport.rules.adapter; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java deleted file mode 100644 index c7dcdd68..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.member; - -import org.junit.rules.TestRule; - -/** - * @since 5.0 - */ -abstract class AbstractTestRuleAnnotatedMember implements TestRuleAnnotatedMember { - - private final TestRule testRule; - - AbstractTestRuleAnnotatedMember(TestRule testRule) { - this.testRule = testRule; - } - - @Override - public TestRule getTestRule() { - return this.testRule; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java deleted file mode 100644 index 7a8dd0c5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.member; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; - -import java.lang.reflect.Field; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.rules.TestRule; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.1") -public final class TestRuleAnnotatedField extends AbstractTestRuleAnnotatedMember { - - public TestRuleAnnotatedField(Object testInstance, Field field) { - super(retrieveTestRule(testInstance, field)); - } - - private static TestRule retrieveTestRule(Object testInstance, Field field) { - try { - return (TestRule) makeAccessible(field).get(testInstance); - } - catch (IllegalAccessException exception) { - throw ExceptionUtils.throwAsUncheckedException(exception); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java deleted file mode 100644 index 0004b905..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.member; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.rules.TestRule; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public interface TestRuleAnnotatedMember { - - TestRule getTestRule(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java deleted file mode 100644 index 75fc5ad6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules.member; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.lang.reflect.Method; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.rules.TestRule; - -/** - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.1") -public final class TestRuleAnnotatedMethod extends AbstractTestRuleAnnotatedMember { - - public TestRuleAnnotatedMethod(Object testInstance, Method method) { - super((TestRule) ReflectionUtils.invokeMethod(method, testInstance)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java deleted file mode 100644 index 85ce964a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Abstractions for members which can be targets of JUnit 4 rule annotations. - */ - -package org.junit.jupiter.migrationsupport.rules.member; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java deleted file mode 100644 index 752ebb7e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Extensions which provide (limited) support for JUnit 4 rules within JUnit Jupiter. - */ - -package org.junit.jupiter.migrationsupport.rules; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java deleted file mode 100644 index 3ace009e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Support for migrating from JUnit 4 to JUnit Jupiter. - * - * @since 5.0 - */ -module org.junit.jupiter.migrationsupport { - requires transitive junit; // 4 - requires static transitive org.apiguardian.api; - requires transitive org.junit.jupiter.api; - requires org.junit.platform.commons; - - exports org.junit.jupiter.migrationsupport; - exports org.junit.jupiter.migrationsupport.conditions; - exports org.junit.jupiter.migrationsupport.rules; - exports org.junit.jupiter.migrationsupport.rules.adapter; - exports org.junit.jupiter.migrationsupport.rules.member; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java deleted file mode 100644 index f37c15b8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; - -/** - * Test suite for JUnit Jupiter migration support. - * - *

Logging Configuration

- * - *

In order for our log4j2 configuration to be used in an IDE, you must - * set the following system property before running any tests — for - * example, in Run Configurations in Eclipse. - * - *

- * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
- * 
- * - * @since 5.0 - */ -@Suite -@SelectPackages("org.junit.jupiter.migrationsupport") -@IncludeClassNamePatterns(".*Tests?") -@IncludeEngines("junit-jupiter") -class JupiterMigrationSupportTestSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java deleted file mode 100644 index 3befccdf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.conditions; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Ignore; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport; - -/** - * Empirical integration tests for JUnit 4's {@link Ignore @Ignore} support in - * JUnit Jupiter, covering the {@link IgnoreCondition} and - * {@link EnableJUnit4MigrationSupport @EnableJUnit4MigrationSupport}. - * - * @since 5.4 - * @see IgnoreConditionTests - */ -class IgnoreAnnotationIntegrationTests { - - @Nested - @ExtendWith(IgnoreCondition.class) - class ExplicitIgnoreConditionRegistration extends BaseNestedTestCase { - } - - @Nested - @EnableJUnit4MigrationSupport - class ImplicitIgnoreConditionRegistration extends BaseNestedTestCase { - } - - @TestInstance(PER_CLASS) - private static abstract class BaseNestedTestCase { - - private static List tests = new ArrayList<>(); - - @BeforeAll - void clearTracking() { - tests.clear(); - } - - @AfterAll - void verifyTracking() { - assertThat(tests).containsExactly("notIgnored"); - } - - @BeforeEach - void track(TestInfo testInfo) { - tests.add(testInfo.getTestMethod().get().getName()); - } - - @Test - @Ignore - void ignored() { - fail("This method should have been disabled via @Ignore"); - } - - @Test - // @Ignore - void notIgnored() { - /* no-op */ - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java deleted file mode 100644 index 95be5fb3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.conditions; - -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; - -import org.junit.Ignore; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Events; - -/** - * Integration tests for JUnit 4's {@link Ignore @Ignore} support in JUnit - * Jupiter provided by the {@link IgnoreCondition}. - * - * @since 5.4 - * @see IgnoreAnnotationIntegrationTests - */ -class IgnoreConditionTests { - - @Test - void ignoredTestClassWithDefaultMessage() { - Class testClass = IgnoredClassWithDefaultMessageTestCase.class; - - // @formatter:off - executeTestsForClass(testClass).allEvents().assertEventsMatchExactly( - event(engine(), started()), - event(container(testClass), skippedWithReason(testClass + " is disabled via @org.junit.Ignore")), - event(engine(), finishedSuccessfully()) - ); - // @formatter:on - } - - @Test - void ignoredTestClassWithCustomMessage() { - Class testClass = IgnoredClassWithCustomMessageTestCase.class; - - // @formatter:off - executeTestsForClass(testClass).allEvents().assertEventsMatchExactly( - event(engine(), started()), - event(container(testClass), skippedWithReason("Ignored Class")), - event(engine(), finishedSuccessfully()) - ); - // @formatter:on - } - - @Test - void ignoredAndNotIgnoredTestMethods() { - EngineExecutionResults executionResults = executeTestsForClass(IgnoredMethodsTestCase.class); - Events containers = executionResults.containerEvents(); - Events tests = executionResults.testEvents(); - - // executionResults.allEvents().debug(); - // executionResults.allEvents().debug(System.err); - - // containers.debug(); - - // tests.debug(System.err); - // tests.debug(); - // tests.skipped().debug(); - // tests.started().debug(); - // tests.succeeded().debug(); - - // executionResults.allEvents().executions().debug(); - // containers.executions().debug(); - // tests.executions().debug(); - - executionResults.allEvents().executions().assertThatExecutions().hasSize(5); - containers.executions().assertThatExecutions().hasSize(2); - tests.executions().assertThatExecutions().hasSize(3); - - // @formatter:off - // tests.debug().assertEventsMatchExactly( - tests.assertEventsMatchExactly( - event(test("ignoredWithCustomMessage"), skippedWithReason("Ignored Method")), - event(test("notIgnored"), started()), - event(test("notIgnored"), finishedSuccessfully()), - event(test("ignoredWithDefaultMessage"), skippedWithReason( - reason -> reason.endsWith("ignoredWithDefaultMessage() is disabled via @org.junit.Ignore"))) - ); - // @formatter:on - } - - private EngineExecutionResults executeTestsForClass(Class testClass) { - return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()); - } - - // ------------------------------------------------------------------------- - - @ExtendWith(IgnoreCondition.class) - @Ignore - static class IgnoredClassWithDefaultMessageTestCase { - - @Test - void ignoredBecauseClassIsIgnored() { - /* no-op */ - } - } - - @ExtendWith(IgnoreCondition.class) - @Ignore("Ignored Class") - static class IgnoredClassWithCustomMessageTestCase { - - @Test - void ignoredBecauseClassIsIgnored() { - /* no-op */ - } - } - - @ExtendWith(IgnoreCondition.class) - static class IgnoredMethodsTestCase { - - @Test - void notIgnored() { - /* no-op */ - } - - @Test - @Ignore - void ignoredWithDefaultMessage() { - fail("This method should have been disabled via @Ignore"); - } - - @Test - @Ignore("Ignored Method") - void ignoredWithCustomMessage() { - fail("This method should have been disabled via @Ignore"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java deleted file mode 100644 index 4829db6d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.migrationsupport.rules.adapter.AbstractTestRuleAdapter; -import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.rules.ErrorCollector; -import org.junit.rules.TemporaryFolder; -import org.junit.rules.TestRule; -import org.junit.rules.Verifier; - -/** - * @since 5.0 - */ -public class AbstractTestRuleAdapterTests { - - @Test - void constructionWithAssignableArgumentsIsSuccessful() { - new TestableTestRuleAdapter(new SimpleRuleAnnotatedMember(new ErrorCollector()), Verifier.class); - } - - @Test - void constructionWithUnassignableArgumentsFails() { - PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> new TestableTestRuleAdapter(new SimpleRuleAnnotatedMember(new TemporaryFolder()), Verifier.class)); - - assertEquals(exception.getMessage(), - "class org.junit.rules.Verifier is not assignable from class org.junit.rules.TemporaryFolder"); - } - - @Test - void exceptionsDuringMethodLookupAreWrappedAndThrown() { - AbstractTestRuleAdapter adapter = new AbstractTestRuleAdapter( - new SimpleRuleAnnotatedMember(new ErrorCollector()), Verifier.class) { - - @Override - public void before() { - super.executeMethod("foo"); - } - }; - - JUnitException exception = assertThrows(JUnitException.class, adapter::before); - - assertEquals(exception.getMessage(), "Failed to find method foo() in class org.junit.rules.ErrorCollector"); - } - - private static class TestableTestRuleAdapter extends AbstractTestRuleAdapter { - - TestableTestRuleAdapter(TestRuleAnnotatedMember annotatedMember, Class adapteeClass) { - super(annotatedMember, adapteeClass); - } - } - - private static class SimpleRuleAnnotatedMember implements TestRuleAnnotatedMember { - - private final TestRule testRule; - - SimpleRuleAnnotatedMember(TestRule testRule) { - this.testRule = testRule; - } - - @Override - public TestRule getTestRule() { - return this.testRule; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java deleted file mode 100644 index 00185dfd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.rules.ExternalResource; -import org.junit.rules.Verifier; - -@EnableRuleMigrationSupport -public class EnableRuleMigrationSupportWithBothRuleTypesTests { - - private static boolean afterOfRule1WasExecuted = false; - - private static boolean beforeOfRule2WasExecuted = false; - private static boolean afterOfRule2WasExecuted = false; - - private static int numberOfRule1InstancesCreated = 0; - private static int numberOfRule2InstancesCreated = 0; - - @Rule - public Verifier verifier1 = new Verifier() { - { - numberOfRule1InstancesCreated++; - } - - @Override - protected void verify() { - afterOfRule1WasExecuted = true; - } - }; - - @Rule - public ExternalResource getResource2() { - return new ExternalResource() { - { - numberOfRule2InstancesCreated++; - } - - private Object instance; - - @Override - protected void before() { - instance = this; - beforeOfRule2WasExecuted = true; - } - - @Override - protected void after() { - assertNotNull(instance); - assertSame(instance, this); - afterOfRule2WasExecuted = true; - } - }; - } - - @Test - void beforeMethodOfBothRule2WasExecuted() { - assertTrue(beforeOfRule2WasExecuted); - } - - @AfterAll - static void afterMethodsOfBothRulesWereExecuted() { - assertEquals(1, numberOfRule1InstancesCreated); - assertEquals(1, numberOfRule2InstancesCreated); - if (!afterOfRule1WasExecuted) - fail(); - if (!afterOfRule2WasExecuted) - fail(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java deleted file mode 100644 index 54845902..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.io.IOException; - -import org.junit.Rule; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Events; -import org.junit.rules.ExpectedException; - -/** - * Integration tests for {@link ExpectedExceptionSupport}. - * - * @since 5.0 - */ -class ExpectedExceptionSupportTests { - - @Test - void expectedExceptionIsProcessedCorrectly() { - Events tests = executeTestsForClass(ExpectedExceptionTestCase.class); - - tests.assertStatistics(stats -> stats.started(4).succeeded(1).aborted(0).failed(3)); - - tests.succeeded().assertThatEvents().have( - event(test("correctExceptionExpectedThrown"), finishedSuccessfully())); - - tests.failed().assertThatEvents()// - .haveExactly(1, // - event(test("noExceptionExpectedButThrown"), // - finishedWithFailure(message("no exception expected")))) // - .haveExactly(1, // - event(test("exceptionExpectedButNotThrown"), // - finishedWithFailure(instanceOf(AssertionError.class), // - message("Expected test to throw an instance of java.lang.RuntimeException")))) // - .haveExactly(1, // - event(test("wrongExceptionExpected"), // - finishedWithFailure(instanceOf(AssertionError.class), // - message(value -> value.contains("Expected: an instance of java.io.IOException"))))); - } - - @Test - void expectedExceptionSupportWithoutExpectedExceptionRule() { - Class testClass = ExpectedExceptionSupportWithoutExpectedExceptionRuleTestCase.class; - Events tests = executeTestsForClass(testClass); - - tests.assertStatistics(stats -> stats.started(2).succeeded(1).aborted(0).failed(1)); - - tests.succeeded().assertThatEvents().have(event(test("success"), finishedSuccessfully())); - - tests.failed().assertThatEvents()// - .haveExactly(1, event(test("failure"), finishedWithFailure(message("must fail")))); - } - - private Events executeTestsForClass(Class testClass) { - return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); - } - - @ExtendWith(ExpectedExceptionSupport.class) - static class ExpectedExceptionTestCase { - - @SuppressWarnings("deprecation") - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - void noExceptionExpectedButThrown() { - throw new RuntimeException("no exception expected"); - } - - @Test - void exceptionExpectedButNotThrown() { - thrown.expect(RuntimeException.class); - } - - @Test - void wrongExceptionExpected() { - thrown.expect(IOException.class); - throw new RuntimeException("wrong exception"); - } - - @Test - void correctExceptionExpectedThrown() { - thrown.expect(RuntimeException.class); - throw new RuntimeException("right exception"); - } - - } - - @ExtendWith(ExpectedExceptionSupport.class) - static class ExpectedExceptionSupportWithoutExpectedExceptionRuleTestCase { - - @Test - void success() { - /* no-op */ - } - - @Test - void failure() { - fail("must fail"); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java deleted file mode 100644 index 96629e2e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.ExternalResource; -import org.junit.rules.TestRule; - -@ExtendWith(ExternalResourceSupport.class) -class ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests { - - private static boolean beforeOfRule1WasExecuted = false; - private static boolean beforeOfRule2WasExecuted = false; - - private static boolean afterOfRule1WasExecuted = false; - private static boolean afterOfRule2WasExecuted = false; - - @Rule - public MyExternalResource1 getResource1() { - return new MyExternalResource1(); - } - - @Rule - public TestRule getResource2() { - return new ExternalResource() { - @Override - protected void before() { - beforeOfRule2WasExecuted = true; - } - - @Override - protected void after() { - afterOfRule2WasExecuted = true; - } - }; - } - - @Test - void beforeMethodsOfBothRulesWereExecuted() { - assertTrue(beforeOfRule1WasExecuted); - assertTrue(beforeOfRule2WasExecuted); - } - - @AfterAll - static void afterMethodsOfBothRulesWereExecuted() { - if (!afterOfRule1WasExecuted) - fail(); - if (!afterOfRule2WasExecuted) - fail(); - } - - private static class MyExternalResource1 extends ExternalResource { - @Override - protected void before() { - beforeOfRule1WasExecuted = true; - } - - @Override - protected void after() { - afterOfRule1WasExecuted = true; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java deleted file mode 100644 index 45a5e81e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.ExternalResource; - -@ExtendWith(ExternalResourceSupport.class) -public class ExternalResourceSupportForMixedMethodAndFieldRulesTests { - - private static List initEvents = new ArrayList<>(); - private static List beforeEvents = new ArrayList<>(); - private static List afterEvents = new ArrayList<>(); - - @BeforeAll - static void clear() { - initEvents.clear(); - beforeEvents.clear(); - afterEvents.clear(); - } - - @Rule - public ExternalResource fieldRule1 = new MyExternalResource("fieldRule1"); - - @Rule - public ExternalResource fieldRule2 = new MyExternalResource("fieldRule2"); - - @Rule - ExternalResource methodRule1() { - return new MyExternalResource("methodRule1"); - } - - @Rule - ExternalResource methodRule2() { - return new MyExternalResource("methodRule2"); - } - - @Test - void constructorsAndBeforeEachMethodsOfAllRulesWereExecuted() { - assertThat(initEvents).hasSize(4); - // the order of fields and methods is not stable, but fields are initialized before methods are called - assertThat(initEvents.subList(0, 2)).allMatch(item -> item.startsWith("fieldRule")); - assertThat(initEvents.subList(2, 4)).allMatch(item -> item.startsWith("methodRule")); - // beforeEach methods of rules from fields are run before those from methods but in reverse order of instantiation - assertEquals(asList(initEvents.get(1), initEvents.get(0), initEvents.get(3), initEvents.get(2)), beforeEvents); - } - - @AfterAll - static void afterMethodsOfAllRulesWereExecuted() { - // beforeEach methods of rules from methods are run before those from fields but in reverse order - if (!asList(initEvents.get(2), initEvents.get(3), initEvents.get(0), initEvents.get(1)).equals(afterEvents)) - fail(); - } - - static class MyExternalResource extends ExternalResource { - - private final String name; - - MyExternalResource(String name) { - this.name = name; - initEvents.add(name); - } - - @Override - protected void before() { - beforeEvents.add(name); - } - - @Override - protected void after() { - afterEvents.add(name); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java deleted file mode 100644 index 28bedf13..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.ExternalResource; - -@ExtendWith(ExternalResourceSupport.class) -public class ExternalResourceSupportForMultipleFieldRulesTests { - - private static boolean beforeOfRule1WasExecuted = false; - private static boolean beforeOfRule2WasExecuted = false; - - private static boolean afterOfRule1WasExecuted = false; - private static boolean afterOfRule2WasExecuted = false; - - @Rule - public ExternalResource resource1 = new ExternalResource() { - @Override - protected void before() { - beforeOfRule1WasExecuted = true; - } - - @Override - protected void after() { - afterOfRule1WasExecuted = true; - } - }; - - @Rule - public ExternalResource resource2 = new ExternalResource() { - @Override - protected void before() { - beforeOfRule2WasExecuted = true; - } - - @Override - protected void after() { - afterOfRule2WasExecuted = true; - } - }; - - @Test - void beforeMethodsOfBothRulesWereExecuted() { - assertTrue(beforeOfRule1WasExecuted); - assertTrue(beforeOfRule2WasExecuted); - } - - @AfterAll - static void afterMethodsOfBothRulesWereExecuted() { - if (!afterOfRule1WasExecuted) - fail(); - if (!afterOfRule2WasExecuted) - fail(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java deleted file mode 100644 index 7e15b4ab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.ExternalResource; - -@ExtendWith(ExternalResourceSupport.class) -public class ExternalResourceSupportForMultipleMethodRulesTests { - - private static boolean beforeOfRule1WasExecuted = false; - private static boolean beforeOfRule2WasExecuted = false; - - private static boolean afterOfRule1WasExecuted = false; - private static boolean afterOfRule2WasExecuted = false; - - @Rule - public ExternalResource getResource1() { - return new ExternalResource() { - @Override - protected void before() { - beforeOfRule1WasExecuted = true; - } - - @Override - protected void after() { - afterOfRule1WasExecuted = true; - } - }; - } - - @Rule - public ExternalResource getResource2() { - return new ExternalResource() { - @Override - protected void before() { - beforeOfRule2WasExecuted = true; - } - - @Override - protected void after() { - afterOfRule2WasExecuted = true; - } - }; - } - - @Test - void beforeMethodsOfBothRulesWereExecuted() { - assertTrue(beforeOfRule1WasExecuted); - assertTrue(beforeOfRule2WasExecuted); - } - - @AfterAll - static void afterMethodsOfBothRulesWereExecuted() { - if (!afterOfRule1WasExecuted) - fail(); - if (!afterOfRule2WasExecuted) - fail(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java deleted file mode 100644 index 4119170d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.File; -import java.io.IOException; - -import org.junit.Rule; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.TemporaryFolder; - -@ExtendWith(ExternalResourceSupport.class) -public class ExternalResourceSupportForTemporaryFolderFieldTests { - - private File file; - - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - @BeforeEach - void setup() throws IOException { - this.file = folder.newFile("temp.txt"); - } - - @Test - void checkTemporaryFolder() { - assertTrue(file.canRead()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java deleted file mode 100644 index dc6f4fca..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -public class ExternalResourceSupportWithInheritanceTests - extends ExternalResourceSupportForMixedMethodAndFieldRulesTests { - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java deleted file mode 100644 index 67c395b4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.Rule; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.rules.TemporaryFolder; - -public class ExternalResourceWithoutAdapterTests { - - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - @BeforeEach - void setup() { - try { - folder.newFile("temp.txt"); - } - catch (Exception exception) { - assertTrue(exception.getMessage().equals("the temporary folder has not yet been created")); - } - } - - @Test - void checkTemporaryFolder() { - // only needed to invoke testing at all - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java deleted file mode 100644 index 08d51f17..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -/** - * @since 5.0 - */ -class FailAfterAllHelper { - - static void fail() { - // hack: use this unrecoverable exception to fail the build, since all others would be swallowed... - throw new OutOfMemoryError("a postcondition was violated"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java deleted file mode 100644 index 37774078..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import org.junit.Rule; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Events; -import org.junit.rules.ErrorCollector; -import org.junit.rules.ExternalResource; -import org.junit.rules.Verifier; - -class LauncherBasedEnableRuleMigrationSupportTests { - - @Test - void enableRuleMigrationSupportAnnotationWorksForBothRuleTypes() { - Events tests = executeTestsForClass(EnableRuleMigrationSupportWithBothRuleTypesTestCase.class); - - tests.assertStatistics(stats -> stats.started(1).succeeded(1).aborted(0).failed(0)); - - assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.afterOfRule1WasExecuted, - "after of rule 1 executed?"); - assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.beforeOfRule2WasExecuted, - "before of rule 2 executed?"); - assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.afterOfRule2WasExecuted, - "before of rule 2 executed?"); - } - - @Test - void verifierSupportForErrorCollectorFieldFailsTheTest() { - Events tests = executeTestsForClass(VerifierSupportForErrorCollectorTestCase.class); - - tests.assertStatistics(stats -> stats.started(1).succeeded(0).aborted(0).failed(1)); - - assertTrue(VerifierSupportForErrorCollectorTestCase.survivedBothErrors, "after of rule 1 executed?"); - } - - private Events executeTestsForClass(Class testClass) { - return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); - } - - @EnableRuleMigrationSupport - static class EnableRuleMigrationSupportWithBothRuleTypesTestCase { - - static boolean afterOfRule1WasExecuted = false; - - static boolean beforeOfRule2WasExecuted = false; - static boolean afterOfRule2WasExecuted = false; - - @Rule - public Verifier verifier1 = new Verifier() { - - @Override - protected void verify() { - afterOfRule1WasExecuted = true; - } - }; - - private ExternalResource resource2 = new ExternalResource() { - @Override - protected void before() { - beforeOfRule2WasExecuted = true; - } - - @Override - protected void after() { - afterOfRule2WasExecuted = true; - } - }; - - @Rule - public ExternalResource getResource2() { - return resource2; - } - - @Test - void beforeMethodOfBothRule2WasExecuted() { - assertTrue(beforeOfRule2WasExecuted); - } - - } - - @ExtendWith(VerifierSupport.class) - static class VerifierSupportForErrorCollectorTestCase { - - static boolean survivedBothErrors = false; - - @Rule - public ErrorCollector collector = new ErrorCollector(); - - @Test - void addingTwoThrowablesToErrorCollectorFailsLate() { - collector.addError(new Throwable("first thing went wrong")); - collector.addError(new Throwable("second thing went wrong")); - - survivedBothErrors = true; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java deleted file mode 100644 index a42db4c5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.Verifier; - -@ExtendWith(VerifierSupport.class) -public class VerifierSupportForMixedMethodAndFieldRulesTests { - - private static boolean afterOfRule1WasExecuted = false; - private static boolean afterOfRule2WasExecuted = false; - - @Rule - public Verifier verifier1 = new Verifier() { - - @Override - protected void verify() { - afterOfRule1WasExecuted = true; - } - }; - - private Verifier verifier2 = new Verifier() { - - @Override - protected void verify() { - afterOfRule2WasExecuted = true; - } - }; - - @Rule - public Verifier getVerifier2() { - return verifier2; - } - - @Test - void testNothing() { - //needed to start the test process at all - } - - @AfterAll - static void afterMethodsOfBothRulesWereExecuted() { - if (!afterOfRule1WasExecuted) - fail(); - if (!afterOfRule2WasExecuted) - fail(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java deleted file mode 100644 index 9b559cc0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.Verifier; - -@ExtendWith(ExternalResourceSupport.class) -public class WrongExtendWithForVerifierFieldTests { - - private static boolean afterOfRule1WasExecuted = false; - - @Rule - public Verifier verifier1 = new Verifier() { - - @Override - protected void verify() { - afterOfRule1WasExecuted = true; - } - }; - - @Test - void testNothing() { - //needed to start the test process at all - } - - @AfterAll - static void afterMethodOfRuleWasNotExecuted() { - if (afterOfRule1WasExecuted) - fail(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java deleted file mode 100644 index 9d566a58..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.migrationsupport.rules; - -import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; - -import org.junit.Rule; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.rules.Verifier; - -@ExtendWith(ExternalResourceSupport.class) -public class WrongExtendWithForVerifierMethodTests { - - private static boolean afterOfRule1WasExecuted = false; - - private Verifier verifier1 = new Verifier() { - - @Override - protected void verify() { - afterOfRule1WasExecuted = true; - } - }; - - @Rule - public Verifier getVerifier1() { - return verifier1; - } - - @Test - void testNothing() { - //needed to start the test process at all - } - - @AfterAll - static void afterMethodsOfBothRulesWereExecuted() { - if (afterOfRule1WasExecuted) - fail(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml deleted file mode 100644 index 1c1cbb8d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md deleted file mode 100644 index f58ac2e9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/LICENSE-univocity-parsers.md +++ /dev/null @@ -1,168 +0,0 @@ -Apache License -============== - -_Version 2.0, January 2004_ -_<>_ - -### Terms and Conditions for use, reproduction, and distribution - -#### 1. Definitions - -“License” shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -“Licensor” shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -“Legal Entity” shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, “control” means **(i)** the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the -outstanding shares, or **(iii)** beneficial ownership of such entity. - -“You” (or “Your”) shall mean an individual or Legal Entity exercising -permissions granted by this License. - -“Source” form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -“Object” form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -“Work” shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -“Derivative Works” shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -“Contribution” shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -“submitted” means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as “Not a Contribution.” - -“Contributor” shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -#### 2. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -#### 3. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -#### 4. Redistribution - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -* **(a)** You must give any other recipients of the Work or Derivative Works a copy of -this License; and -* **(b)** You must cause any modified files to carry prominent notices stating that You -changed the files; and -* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. - -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -#### 5. Submission of Contributions - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -#### 6. Trademarks - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -#### 7. Disclaimer of Warranty - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -#### 8. Limitation of Liability - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -#### 9. Accepting Warranty or Additional Liability - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts deleted file mode 100644 index 528ef09c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ /dev/null @@ -1,51 +0,0 @@ -plugins { - `kotlin-library-conventions` - `shadow-conventions` - `testing-conventions` -} - -description = "JUnit Jupiter Params" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitJupiterApi) - - compileOnlyApi(libs.apiguardian) - - shadowed(libs.univocity.parsers) - - testImplementation(projects.junitPlatformTestkit) - testImplementation(projects.junitJupiterEngine) - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteEngine) - testImplementation(testFixtures(projects.junitJupiterEngine)) - - compileOnly(kotlin("stdlib")) - testImplementation(kotlin("stdlib")) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -tasks { - jar { - bundle { - bnd(""" - Require-Capability:\ - org.junit.platform.engine;\ - filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;${rootProject.property("version")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("version")!!}}})))';\ - effective:=active - """) - } - } -} - -tasks { - shadowJar { - relocate("com.univocity", "org.junit.jupiter.params.shadow.com.univocity") - from(projectDir) { - include("LICENSE-univocity-parsers.md") - into("META-INF") - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java deleted file mode 100644 index 1fcdd80b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * {@code @ParameterizedTest} is used to signal that the annotated method is a - * parameterized test method. - * - *

Such methods must not be {@code private} or {@code static}. - * - *

Argument Providers and Sources

- * - *

{@code @ParameterizedTest} methods must specify at least one - * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} - * via {@link org.junit.jupiter.params.provider.ArgumentsSource @ArgumentsSource} - * or a corresponding composed annotation (e.g., {@code @ValueSource}, - * {@code @CsvSource}, etc.). The provider is responsible for providing a - * {@link java.util.stream.Stream Stream} of - * {@link org.junit.jupiter.params.provider.Arguments Arguments} that will be - * used to invoke the parameterized test method. - * - *

Formal Parameter List

- * - *

A {@code @ParameterizedTest} method may declare additional parameters at - * the end of the method's parameter list to be resolved by other - * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers} - * (e.g., {@code TestInfo}, {@code TestReporter}, etc). Specifically, a - * parameterized test method must declare formal parameters according to the - * following rules. - * - *

    - *
  1. Zero or more indexed arguments must be declared first.
  2. - *
  3. Zero or more aggregators must be declared next.
  4. - *
  5. Zero or more arguments supplied by other {@code ParameterResolver} - * implementations must be declared last.
  6. - *
- * - *

In this context, an indexed argument is an argument for a given - * index in the {@code Arguments} provided by an {@code ArgumentsProvider} that - * is passed as an argument to the parameterized method at the same index in the - * method's formal parameter list. An aggregator is any parameter of type - * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor ArgumentsAccessor} - * or any parameter annotated with - * {@link org.junit.jupiter.params.aggregator.AggregateWith @AggregateWith}. - * - *

Argument Conversion

- * - *

Method parameters may be annotated with - * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} - * or a corresponding composed annotation to specify an explicit - * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter}. - * Otherwise, JUnit Jupiter will attempt to perform an implicit - * conversion to the target type automatically (see the User Guide for further - * details). - * - *

Composed Annotations

- * - *

{@code @ParameterizedTest} may also be used as a meta-annotation in order - * to create a custom composed annotation that inherits the semantics - * of {@code @ParameterizedTest}. - * - *

Test Execution Order

- * - *

By default, test methods will be ordered using an algorithm that is - * deterministic but intentionally nonobvious. This ensures that subsequent runs - * of a test suite execute test methods in the same order, thereby allowing for - * repeatable builds. In this context, a test method is any instance - * method that is directly annotated or meta-annotated with {@code @Test}, - * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or - * {@code @TestTemplate}. - * - *

Although true unit tests typically should not rely on the order - * in which they are executed, there are times when it is necessary to enforce - * a specific test method execution order — for example, when writing - * integration tests or functional tests where the sequence of - * the tests is important, especially in conjunction with - * {@link org.junit.jupiter.api.TestInstance @TestInstance(Lifecycle.PER_CLASS)}. - * - *

To control the order in which test methods are executed, annotate your - * test class or test interface with - * {@link org.junit.jupiter.api.TestMethodOrder @TestMethodOrder} and specify - * the desired {@link org.junit.jupiter.api.MethodOrderer MethodOrderer} - * implementation. - * - * @since 5.0 - * @see org.junit.jupiter.params.provider.Arguments - * @see org.junit.jupiter.params.provider.ArgumentsProvider - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.provider.CsvFileSource - * @see org.junit.jupiter.params.provider.CsvSource - * @see org.junit.jupiter.params.provider.EnumSource - * @see org.junit.jupiter.params.provider.MethodSource - * @see org.junit.jupiter.params.provider.ValueSource - * @see org.junit.jupiter.params.aggregator.ArgumentsAccessor - * @see org.junit.jupiter.params.aggregator.AggregateWith - * @see org.junit.jupiter.params.converter.ArgumentConverter - * @see org.junit.jupiter.params.converter.ConvertWith - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@TestTemplate -@ExtendWith(ParameterizedTestExtension.class) -public @interface ParameterizedTest { - - /** - * Placeholder for the {@linkplain org.junit.jupiter.api.TestInfo#getDisplayName - * display name} of a {@code @ParameterizedTest} method: {displayName} - * - * @since 5.3 - * @see #name - */ - String DISPLAY_NAME_PLACEHOLDER = "{displayName}"; - - /** - * Placeholder for the current invocation index of a {@code @ParameterizedTest} - * method (1-based): {index} - * - * @since 5.3 - * @see #name - */ - String INDEX_PLACEHOLDER = "{index}"; - - /** - * Placeholder for the complete, comma-separated arguments list of the - * current invocation of a {@code @ParameterizedTest} method: - * {arguments} - * - * @since 5.3 - * @see #name - */ - String ARGUMENTS_PLACEHOLDER = "{arguments}"; - - /** - * Placeholder for the complete, comma-separated named arguments list - * of the current invocation of a {@code @ParameterizedTest} method: - * {argumentsWithNames} - * - *

Argument names will be retrieved via the {@link java.lang.reflect.Parameter#getName()} - * API if the byte code contains parameter names — for example, if - * the code was compiled with the {@code -parameters} command line argument - * for {@code javac}. - * - * @since 5.6 - * @see #name - */ - String ARGUMENTS_WITH_NAMES_PLACEHOLDER = "{argumentsWithNames}"; - - /** - * Default display name pattern for the current invocation of a - * {@code @ParameterizedTest} method: {@value} - * - *

Note that the default pattern does not include the - * {@linkplain #DISPLAY_NAME_PLACEHOLDER display name} of the - * {@code @ParameterizedTest} method. - * - * @since 5.3 - * @see #name - * @see #DISPLAY_NAME_PLACEHOLDER - * @see #INDEX_PLACEHOLDER - * @see #ARGUMENTS_WITH_NAMES_PLACEHOLDER - */ - String DEFAULT_DISPLAY_NAME = "[" + INDEX_PLACEHOLDER + "] " + ARGUMENTS_WITH_NAMES_PLACEHOLDER; - - /** - * The display name to be used for individual invocations of the - * parameterized test; never blank or consisting solely of whitespace. - * - *

Defaults to {default_display_name}. - * - *

If the default display name flag ({default_display_name}) - * is not overridden, JUnit will: - *

    - *
  • Look up the {@value ParameterizedTestExtension#DISPLAY_NAME_PATTERN_KEY} - * configuration parameter and use it if available. The configuration - * parameter can be supplied via the {@code Launcher} API, build tools (e.g., - * Gradle and Maven), a JVM system property, or the JUnit Platform configuration - * file (i.e., a file named {@code junit-platform.properties} in the root of - * the class path). Consult the User Guide for further information.
  • - *
  • Otherwise, the value of the {@link #DEFAULT_DISPLAY_NAME} constant will - * be used.
  • - *
- * - *

Supported placeholders

- *
    - *
  • {@link #DISPLAY_NAME_PLACEHOLDER}
  • - *
  • {@link #INDEX_PLACEHOLDER}
  • - *
  • {@link #ARGUMENTS_PLACEHOLDER}
  • - *
  • {@link #ARGUMENTS_WITH_NAMES_PLACEHOLDER}
  • - *
  • {0}, {1}, etc.: an individual argument (0-based)
  • - *
- * - *

For the latter, you may use {@link java.text.MessageFormat} patterns - * to customize formatting. Please note that the original arguments are - * passed when formatting, regardless of any implicit or explicit argument - * conversions. - * - *

Note that {default_display_name} is a flag rather than a - * placeholder. - * - * @see java.text.MessageFormat - */ - String name() default "{default_display_name}"; - - /** - * Configure whether all arguments of the parameterized test that implement {@link AutoCloseable} - * will be closed after {@link org.junit.jupiter.api.AfterEach @AfterEach} methods - * and {@link org.junit.jupiter.api.extension.AfterEachCallback AfterEachCallback} - * extensions have been called for the current parameterized test invocation. - * - *

Defaults to {@code true}. - * - *

WARNING: if an argument that implements {@code AutoCloseable} - * is reused for multiple invocations of the same parameterized test method, - * you must set {@code autoCloseArguments} to {@code false} to ensure that - * the argument is not closed between invocations. - * - * @since 5.8 - * @see java.lang.AutoCloseable - */ - @API(status = EXPERIMENTAL, since = "5.8") - boolean autoCloseArguments() default true; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java deleted file mode 100644 index 3b9f8c3c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Stream; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.jupiter.params.support.AnnotationConsumerInitializer; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.0 - */ -class ParameterizedTestExtension implements TestTemplateInvocationContextProvider { - - private static final String METHOD_CONTEXT_KEY = "context"; - static final String ARGUMENT_MAX_LENGTH_KEY = "junit.jupiter.params.displayname.argument.maxlength"; - private static final String DEFAULT_DISPLAY_NAME = "{default_display_name}"; - static final String DISPLAY_NAME_PATTERN_KEY = "junit.jupiter.params.displayname.default"; - - @Override - public boolean supportsTestTemplate(ExtensionContext context) { - if (!context.getTestMethod().isPresent()) { - return false; - } - - Method testMethod = context.getTestMethod().get(); - if (!isAnnotated(testMethod, ParameterizedTest.class)) { - return false; - } - - ParameterizedTestMethodContext methodContext = new ParameterizedTestMethodContext(testMethod); - - Preconditions.condition(methodContext.hasPotentiallyValidSignature(), - () -> String.format( - "@ParameterizedTest method [%s] declares formal parameters in an invalid order: " - + "argument aggregators must be declared after any indexed arguments " - + "and before any arguments resolved by another ParameterResolver.", - testMethod.toGenericString())); - - getStore(context).put(METHOD_CONTEXT_KEY, methodContext); - - return true; - } - - @Override - public Stream provideTestTemplateInvocationContexts( - ExtensionContext extensionContext) { - - Method templateMethod = extensionContext.getRequiredTestMethod(); - String displayName = extensionContext.getDisplayName(); - ParameterizedTestMethodContext methodContext = getStore(extensionContext)// - .get(METHOD_CONTEXT_KEY, ParameterizedTestMethodContext.class); - int argumentMaxLength = extensionContext.getConfigurationParameter(ARGUMENT_MAX_LENGTH_KEY, - Integer::parseInt).orElse(512); - ParameterizedTestNameFormatter formatter = createNameFormatter(extensionContext, templateMethod, methodContext, - displayName, argumentMaxLength); - AtomicLong invocationCount = new AtomicLong(0); - - // @formatter:off - return findRepeatableAnnotations(templateMethod, ArgumentsSource.class) - .stream() - .map(ArgumentsSource::value) - .map(this::instantiateArgumentsProvider) - .map(provider -> AnnotationConsumerInitializer.initialize(templateMethod, provider)) - .flatMap(provider -> arguments(provider, extensionContext)) - .map(Arguments::get) - .map(arguments -> consumedArguments(arguments, methodContext)) - .map(arguments -> { - invocationCount.incrementAndGet(); - return createInvocationContext(formatter, methodContext, arguments); - }) - .onClose(() -> - Preconditions.condition(invocationCount.get() > 0, - "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest")); - // @formatter:on - } - - @SuppressWarnings("ConstantConditions") - private ArgumentsProvider instantiateArgumentsProvider(Class clazz) { - try { - return ReflectionUtils.newInstance(clazz); - } - catch (Exception ex) { - if (ex instanceof NoSuchMethodException) { - String message = String.format("Failed to find a no-argument constructor for ArgumentsProvider [%s]. " - + "Please ensure that a no-argument constructor exists and " - + "that the class is either a top-level class or a static nested class", - clazz.getName()); - throw new JUnitException(message, ex); - } - throw ex; - } - } - - private ExtensionContext.Store getStore(ExtensionContext context) { - return context.getStore(Namespace.create(ParameterizedTestExtension.class, context.getRequiredTestMethod())); - } - - private TestTemplateInvocationContext createInvocationContext(ParameterizedTestNameFormatter formatter, - ParameterizedTestMethodContext methodContext, Object[] arguments) { - return new ParameterizedTestInvocationContext(formatter, methodContext, arguments); - } - - private ParameterizedTestNameFormatter createNameFormatter(ExtensionContext extensionContext, Method templateMethod, - ParameterizedTestMethodContext methodContext, String displayName, int argumentMaxLength) { - ParameterizedTest parameterizedTest = findAnnotation(templateMethod, ParameterizedTest.class).get(); - String pattern = parameterizedTest.name().equals(DEFAULT_DISPLAY_NAME) - ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY).orElse( - ParameterizedTest.DEFAULT_DISPLAY_NAME) - : parameterizedTest.name(); - pattern = Preconditions.notBlank(pattern.trim(), - () -> String.format( - "Configuration error: @ParameterizedTest on method [%s] must be declared with a non-empty name.", - templateMethod)); - return new ParameterizedTestNameFormatter(pattern, displayName, methodContext, argumentMaxLength); - } - - protected static Stream arguments(ArgumentsProvider provider, ExtensionContext context) { - try { - return provider.provideArguments(context); - } - catch (Exception e) { - throw ExceptionUtils.throwAsUncheckedException(e); - } - } - - private Object[] consumedArguments(Object[] arguments, ParameterizedTestMethodContext methodContext) { - int parameterCount = methodContext.getParameterCount(); - if (methodContext.hasAggregator()) { - return arguments; - } - return arguments.length > parameterCount ? Arrays.copyOf(arguments, parameterCount) : arguments; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java deleted file mode 100644 index 778fe936..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static java.util.Collections.singletonList; - -import java.util.List; - -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; - -/** - * @since 5.0 - */ -class ParameterizedTestInvocationContext implements TestTemplateInvocationContext { - - private final ParameterizedTestNameFormatter formatter; - private final ParameterizedTestMethodContext methodContext; - private final Object[] arguments; - - ParameterizedTestInvocationContext(ParameterizedTestNameFormatter formatter, - ParameterizedTestMethodContext methodContext, Object[] arguments) { - this.formatter = formatter; - this.methodContext = methodContext; - this.arguments = arguments; - } - - @Override - public String getDisplayName(int invocationIndex) { - return this.formatter.format(invocationIndex, this.arguments); - } - - @Override - public List getAdditionalExtensions() { - return singletonList(new ParameterizedTestParameterResolver(this.methodContext, this.arguments)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java deleted file mode 100644 index fc0c3c0e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static org.junit.jupiter.params.ParameterizedTestMethodContext.ResolverType.AGGREGATOR; -import static org.junit.jupiter.params.ParameterizedTestMethodContext.ResolverType.CONVERTER; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; - -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.params.aggregator.AggregateWith; -import org.junit.jupiter.params.aggregator.ArgumentsAccessor; -import org.junit.jupiter.params.aggregator.ArgumentsAggregator; -import org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor; -import org.junit.jupiter.params.converter.ArgumentConverter; -import org.junit.jupiter.params.converter.ConvertWith; -import org.junit.jupiter.params.converter.DefaultArgumentConverter; -import org.junit.jupiter.params.support.AnnotationConsumerInitializer; -import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; - -/** - * Encapsulates access to the parameters of a parameterized test method and - * caches the converters and aggregators used to resolve them. - * - * @since 5.3 - */ -class ParameterizedTestMethodContext { - - private final Parameter[] parameters; - private final Resolver[] resolvers; - private final List resolverTypes; - - ParameterizedTestMethodContext(Method testMethod) { - this.parameters = testMethod.getParameters(); - this.resolvers = new Resolver[this.parameters.length]; - this.resolverTypes = new ArrayList<>(this.parameters.length); - for (Parameter parameter : this.parameters) { - this.resolverTypes.add(isAggregator(parameter) ? AGGREGATOR : CONVERTER); - } - } - - /** - * Determine if the supplied {@link Parameter} is an aggregator (i.e., of - * type {@link ArgumentsAccessor} or annotated with {@link AggregateWith}). - * - * @return {@code true} if the parameter is an aggregator - */ - private static boolean isAggregator(Parameter parameter) { - return ArgumentsAccessor.class.isAssignableFrom(parameter.getType()) - || isAnnotated(parameter, AggregateWith.class); - } - - /** - * Determine if the {@link Method} represented by this context has a - * potentially valid signature (i.e., formal parameter - * declarations) with regard to aggregators. - * - *

This method takes a best-effort approach at enforcing the following - * policy for parameterized test methods that accept aggregators as arguments. - * - *

    - *
  1. zero or more indexed arguments come first.
  2. - *
  3. zero or more aggregators come next.
  4. - *
  5. zero or more arguments supplied by other {@code ParameterResolver} - * implementations come last.
  6. - *
- * - * @return {@code true} if the method has a potentially valid signature - */ - boolean hasPotentiallyValidSignature() { - int indexOfPreviousAggregator = -1; - for (int i = 0; i < getParameterCount(); i++) { - if (isAggregator(i)) { - if ((indexOfPreviousAggregator != -1) && (i != indexOfPreviousAggregator + 1)) { - return false; - } - indexOfPreviousAggregator = i; - } - } - return true; - } - - /** - * Get the number of parameters of the {@link Method} represented by this - * context. - */ - int getParameterCount() { - return parameters.length; - } - - /** - * Get the name of the {@link Parameter} with the supplied index, if - * it is present and declared before the aggregators. - * - * @return an {@code Optional} containing the name of the parameter - */ - Optional getParameterName(int parameterIndex) { - if (parameterIndex >= getParameterCount()) { - return Optional.empty(); - } - Parameter parameter = this.parameters[parameterIndex]; - if (!parameter.isNamePresent()) { - return Optional.empty(); - } - if (hasAggregator() && parameterIndex >= indexOfFirstAggregator()) { - return Optional.empty(); - } - return Optional.of(parameter.getName()); - } - - /** - * Determine if the {@link Method} represented by this context declares at - * least one {@link Parameter} that is an - * {@linkplain #isAggregator aggregator}. - * - * @return {@code true} if the method has an aggregator - */ - boolean hasAggregator() { - return resolverTypes.contains(AGGREGATOR); - } - - /** - * Determine if the {@link Parameter} with the supplied index is an - * aggregator (i.e., of type {@link ArgumentsAccessor} or annotated with - * {@link AggregateWith}). - * - * @return {@code true} if the parameter is an aggregator - */ - boolean isAggregator(int parameterIndex) { - return resolverTypes.get(parameterIndex) == AGGREGATOR; - } - - /** - * Find the index of the first {@linkplain #isAggregator aggregator} - * {@link Parameter} in the {@link Method} represented by this context. - * - * @return the index of the first aggregator, or {@code -1} if not found - */ - int indexOfFirstAggregator() { - return resolverTypes.indexOf(AGGREGATOR); - } - - /** - * Resolve the parameter for the supplied context using the supplied - * arguments. - */ - Object resolve(ParameterContext parameterContext, Object[] arguments) { - return getResolver(parameterContext).resolve(parameterContext, arguments); - } - - private Resolver getResolver(ParameterContext parameterContext) { - int index = parameterContext.getIndex(); - if (resolvers[index] == null) { - resolvers[index] = resolverTypes.get(index).createResolver(parameterContext); - } - return resolvers[index]; - } - - enum ResolverType { - - CONVERTER { - @Override - Resolver createResolver(ParameterContext parameterContext) { - try { // @formatter:off - return AnnotationUtils.findAnnotation(parameterContext.getParameter(), ConvertWith.class) - .map(ConvertWith::value) - .map(clazz -> (ArgumentConverter) ReflectionUtils.newInstance(clazz)) - .map(converter -> AnnotationConsumerInitializer.initialize(parameterContext.getParameter(), converter)) - .map(Converter::new) - .orElse(Converter.DEFAULT); - } // @formatter:on - catch (Exception ex) { - throw parameterResolutionException("Error creating ArgumentConverter", ex, parameterContext); - } - } - }, - - AGGREGATOR { - @Override - Resolver createResolver(ParameterContext parameterContext) { - try { // @formatter:off - return AnnotationUtils.findAnnotation(parameterContext.getParameter(), AggregateWith.class) - .map(AggregateWith::value) - .map(clazz -> (ArgumentsAggregator) ReflectionSupport.newInstance(clazz)) - .map(Aggregator::new) - .orElse(Aggregator.DEFAULT); - } // @formatter:on - catch (Exception ex) { - throw parameterResolutionException("Error creating ArgumentsAggregator", ex, parameterContext); - } - } - }; - - abstract Resolver createResolver(ParameterContext parameterContext); - - } - - interface Resolver { - - Object resolve(ParameterContext parameterContext, Object[] arguments); - - } - - static class Converter implements Resolver { - - private static final Converter DEFAULT = new Converter(DefaultArgumentConverter.INSTANCE); - - private final ArgumentConverter argumentConverter; - - Converter(ArgumentConverter argumentConverter) { - this.argumentConverter = argumentConverter; - } - - @Override - public Object resolve(ParameterContext parameterContext, Object[] arguments) { - Object argument = arguments[parameterContext.getIndex()]; - try { - return this.argumentConverter.convert(argument, parameterContext); - } - catch (Exception ex) { - throw parameterResolutionException("Error converting parameter", ex, parameterContext); - } - } - - } - - static class Aggregator implements Resolver { - - private static final Aggregator DEFAULT = new Aggregator((accessor, context) -> accessor); - - private final ArgumentsAggregator argumentsAggregator; - - Aggregator(ArgumentsAggregator argumentsAggregator) { - this.argumentsAggregator = argumentsAggregator; - } - - @Override - public Object resolve(ParameterContext parameterContext, Object[] arguments) { - ArgumentsAccessor accessor = new DefaultArgumentsAccessor(arguments); - try { - return this.argumentsAggregator.aggregateArguments(accessor, parameterContext); - } - catch (Exception ex) { - throw parameterResolutionException("Error aggregating arguments for parameter", ex, parameterContext); - } - } - - } - - private static ParameterResolutionException parameterResolutionException(String message, Exception cause, - ParameterContext parameterContext) { - String fullMessage = message + " at index " + parameterContext.getIndex(); - if (StringUtils.isNotBlank(cause.getMessage())) { - fullMessage += ": " + cause.getMessage(); - } - return new ParameterResolutionException(fullMessage, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java deleted file mode 100644 index a505bd81..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_PLACEHOLDER; -import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER; -import static org.junit.jupiter.params.ParameterizedTest.DISPLAY_NAME_PLACEHOLDER; -import static org.junit.jupiter.params.ParameterizedTest.INDEX_PLACEHOLDER; - -import java.text.Format; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Named; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.StringUtils; - -/** - * @since 5.0 - */ -class ParameterizedTestNameFormatter { - - private static final char ELLIPSIS = '\u2026'; - private static final String TEMPORARY_DISPLAY_NAME_PLACEHOLDER = "~~~JUNIT_DISPLAY_NAME~~~"; - - private final String pattern; - private final String displayName; - private final ParameterizedTestMethodContext methodContext; - private final int argumentMaxLength; - - ParameterizedTestNameFormatter(String pattern, String displayName, ParameterizedTestMethodContext methodContext, - int argumentMaxLength) { - this.pattern = pattern; - this.displayName = displayName; - this.methodContext = methodContext; - this.argumentMaxLength = argumentMaxLength; - } - - String format(int invocationIndex, Object... arguments) { - try { - return formatSafely(invocationIndex, arguments); - } - catch (Exception ex) { - String message = "The display name pattern defined for the parameterized test is invalid. " - + "See nested exception for further details."; - throw new JUnitException(message, ex); - } - } - - private String formatSafely(int invocationIndex, Object[] arguments) { - Object[] namedArguments = extractNamedArguments(arguments); - String pattern = prepareMessageFormatPattern(invocationIndex, namedArguments); - MessageFormat format = new MessageFormat(pattern); - Object[] humanReadableArguments = makeReadable(format, namedArguments); - String formatted = format.format(humanReadableArguments); - return formatted.replace(TEMPORARY_DISPLAY_NAME_PLACEHOLDER, this.displayName); - } - - private Object[] extractNamedArguments(Object[] arguments) { - return Arrays.stream(arguments) // - .map(argument -> argument instanceof Named ? ((Named) argument).getName() : argument) // - .toArray(); - } - - private String prepareMessageFormatPattern(int invocationIndex, Object[] arguments) { - String result = pattern// - .replace(DISPLAY_NAME_PLACEHOLDER, TEMPORARY_DISPLAY_NAME_PLACEHOLDER)// - .replace(INDEX_PLACEHOLDER, String.valueOf(invocationIndex)); - - if (result.contains(ARGUMENTS_WITH_NAMES_PLACEHOLDER)) { - result = result.replace(ARGUMENTS_WITH_NAMES_PLACEHOLDER, argumentsWithNamesPattern(arguments)); - } - - if (result.contains(ARGUMENTS_PLACEHOLDER)) { - result = result.replace(ARGUMENTS_PLACEHOLDER, argumentsPattern(arguments)); - } - - return result; - } - - private String argumentsWithNamesPattern(Object[] arguments) { - return IntStream.range(0, arguments.length) // - .mapToObj(index -> methodContext.getParameterName(index).map(name -> name + "=").orElse("") + "{" - + index + "}") // - .collect(joining(", ")); - } - - private String argumentsPattern(Object[] arguments) { - return IntStream.range(0, arguments.length) // - .mapToObj(index -> "{" + index + "}") // - .collect(joining(", ")); - } - - private Object[] makeReadable(MessageFormat format, Object[] arguments) { - Format[] formats = format.getFormatsByArgumentIndex(); - Object[] result = Arrays.copyOf(arguments, Math.min(arguments.length, formats.length), Object[].class); - for (int i = 0; i < result.length; i++) { - if (formats[i] == null) { - result[i] = truncateIfExceedsMaxLength(StringUtils.nullSafeToString(arguments[i])); - } - } - return result; - } - - private String truncateIfExceedsMaxLength(String argument) { - if (argument != null && argument.length() > argumentMaxLength) { - return argument.substring(0, argumentMaxLength - 1) + ELLIPSIS; - } - return argument; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java deleted file mode 100644 index b9d6a7b4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.util.AnnotationUtils; - -/** - * @since 5.0 - */ -class ParameterizedTestParameterResolver implements ParameterResolver, AfterTestExecutionCallback { - - private static final Namespace NAMESPACE = Namespace.create(ParameterizedTestParameterResolver.class); - - private final ParameterizedTestMethodContext methodContext; - private final Object[] arguments; - - ParameterizedTestParameterResolver(ParameterizedTestMethodContext methodContext, Object[] arguments) { - this.methodContext = methodContext; - this.arguments = arguments; - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - Executable declaringExecutable = parameterContext.getDeclaringExecutable(); - Method testMethod = extensionContext.getTestMethod().orElse(null); - int parameterIndex = parameterContext.getIndex(); - - // Not a @ParameterizedTest method? - if (!declaringExecutable.equals(testMethod)) { - return false; - } - - // Current parameter is an aggregator? - if (this.methodContext.isAggregator(parameterIndex)) { - return true; - } - - // Ensure that the current parameter is declared before aggregators. - // Otherwise, a different ParameterResolver should handle it. - if (this.methodContext.hasAggregator()) { - return parameterIndex < this.methodContext.indexOfFirstAggregator(); - } - - // Else fallback to behavior for parameterized test methods without aggregators. - return parameterIndex < this.arguments.length; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - return this.methodContext.resolve(parameterContext, extractPayloads(this.arguments)); - } - - /** - * @since 5.8 - */ - @Override - public void afterTestExecution(ExtensionContext context) { - ParameterizedTest parameterizedTest = AnnotationUtils.findAnnotation(context.getRequiredTestMethod(), - ParameterizedTest.class).get(); - if (!parameterizedTest.autoCloseArguments()) { - return; - } - - Store store = context.getStore(NAMESPACE); - AtomicInteger argumentIndex = new AtomicInteger(); - - Arrays.stream(this.arguments) // - .filter(AutoCloseable.class::isInstance) // - .map(AutoCloseable.class::cast) // - .map(CloseableArgument::new) // - .forEach(closeable -> store.put("closeableArgument#" + argumentIndex.incrementAndGet(), closeable)); - } - - private static class CloseableArgument implements Store.CloseableResource { - - private final AutoCloseable autoCloseable; - - CloseableArgument(AutoCloseable autoCloseable) { - this.autoCloseable = autoCloseable; - } - - @Override - public void close() throws Throwable { - this.autoCloseable.close(); - } - - } - - private Object[] extractPayloads(Object[] arguments) { - return Arrays.stream(arguments) // - .map(argument -> { - if (argument instanceof Named) { - return ((Named) argument).getPayload(); - } - return argument; - }) // - .toArray(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java deleted file mode 100644 index 57a0b88f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @AggregateWith} is an annotation that allows one to specify an - * {@link ArgumentsAggregator}. - * - *

This annotation may be applied to a parameter of a - * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method - * in order for an aggregated value to be resolved for the annotated parameter - * when the test method is invoked. - * - *

{@code @AggregateWith} may also be used as a meta-annotation in order to - * create a custom composed annotation that inherits the semantics - * of {@code @AggregateWith}. - * - * @since 5.2 - * @see ArgumentsAggregator - * @see org.junit.jupiter.params.ParameterizedTest - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) -@Documented -@API(status = STABLE, since = "5.7") -public @interface AggregateWith { - - Class value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java deleted file mode 100644 index ce439621..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * {@code ArgumentAccessException} is an exception thrown by an - * {@link ArgumentsAccessor} if an error occurs while accessing - * or converting an argument. - * - * @since 5.2 - * @see ArgumentsAccessor - */ -@API(status = STABLE, since = "5.7") -public class ArgumentAccessException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public ArgumentAccessException(String message) { - super(message); - } - - public ArgumentAccessException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java deleted file mode 100644 index e62d7864..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; - -import org.apiguardian.api.API; - -/** - * {@code ArgumentsAccessor} defines the public API for accessing arguments provided - * by an {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} - * for a single invocation of a - * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method. - * - *

Specifically, an {@code ArgumentsAccessor} aggregates a set of - * arguments for a given invocation of a parameterized test and provides convenience - * methods for accessing those arguments in a type-safe manner with support for - * automatic type conversion. - * - *

An instance of {@code ArgumentsAccessor} will be automatically supplied - * for any parameter of type {@code ArgumentsAccessor} in a parameterized test. - * In addition, {@link ArgumentsAggregator} implementations are given access to - * an {@code ArgumentsAccessor}. - * - *

This interface is not intended to be implemented by clients. - * - *

Additional Kotlin arguments accessors can be - * found as extension functions in the {@link org.junit.jupiter.params.aggregator} - * package. - * - * @since 5.2 - * @see ArgumentsAggregator - * @see org.junit.jupiter.params.ParameterizedTest - */ -@API(status = STABLE, since = "5.7") -public interface ArgumentsAccessor { - - /** - * Get the value of the argument at the given index as an {@link Object}. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - */ - Object get(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as an instance of the - * required type. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @param requiredType the required type of the value; never {@code null} - * @return the value at the given index, potentially {@code null} - */ - T get(int index, Class requiredType) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Character}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Character getCharacter(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Boolean}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Boolean getBoolean(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Byte}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Byte getByte(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Short}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Short getShort(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Integer}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Integer getInteger(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Long}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Long getLong(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Float}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Float getFloat(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link Double}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - Double getDouble(int index) throws ArgumentAccessException; - - /** - * Get the value of the argument at the given index as a {@link String}, - * performing automatic type conversion as necessary. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @throws ArgumentAccessException if the value cannot be accessed - * or converted to the desired type - */ - String getString(int index) throws ArgumentAccessException; - - /** - * Get the number of arguments in this accessor. - */ - int size(); - - /** - * Get all arguments in this accessor as an array. - */ - Object[] toArray(); - - /** - * Get all arguments in this accessor as an immutable list. - */ - List toList(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java deleted file mode 100644 index 10b96762..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * {@code ArgumentsAggregationException} is an exception thrown by an - * {@link ArgumentsAggregator} when an error occurs while aggregating - * arguments. - * - * @since 5.2 - * @see ArgumentsAggregator - */ -@API(status = STABLE, since = "5.7") -public class ArgumentsAggregationException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public ArgumentsAggregationException(String message) { - super(message); - } - - public ArgumentsAggregationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java deleted file mode 100644 index 52887ed5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ParameterContext; - -/** - * {@code ArgumentsAggregator} is an abstraction for the aggregation of arguments - * provided by an {@link org.junit.jupiter.params.provider.ArgumentsProvider - * ArgumentsProvider} for a single invocation of a - * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method - * into a single object. - * - *

An {@code ArgumentsAggregator} is applied to a method parameter of a - * {@code @ParameterizedTest} method via the {@link AggregateWith @AggregateWith} - * annotation. - * - *

The result of the aggregation will be passed as an argument to the - * {@code @ParameterizedTest} method for the annotated parameter. - * - *

A common use case is the aggregation of multiple columns from a single line - * in a CSV file into a domain object such as a {@code Person}, {@code Address}, - * {@code Order}, etc. - * - *

Implementations must provide a no-args constructor and should not make any - * assumptions regarding when they are instantiated or how often they are called. - * Since instances may potentially be cached and called from different threads, - * they should be thread-safe and designed to be used as singletons. - * - * @since 5.2 - * @see AggregateWith - * @see ArgumentsAccessor - * @see org.junit.jupiter.params.ParameterizedTest - */ -@API(status = STABLE, since = "5.7") -public interface ArgumentsAggregator { - - /** - * Aggregate the arguments contained in the supplied {@code accessor} into a - * single object. - * - * @param accessor an {@link ArgumentsAccessor} containing the arguments to be - * aggregated; never {@code null} - * @param context the parameter context where the aggregated result is to be - * supplied; never {@code null} - * @return the aggregated result; may be {@code null} but only if the target - * type is a reference type - * @throws ArgumentsAggregationException if an error occurs during the - * aggregation - */ - Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) - throws ArgumentsAggregationException; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java deleted file mode 100644 index d0e4d363..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static java.lang.String.format; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.jupiter.params.converter.DefaultArgumentConverter; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.Preconditions; - -/** - * Default implementation of the {@link ArgumentsAccessor} API. - * - *

Delegates conversion to {@link DefaultArgumentConverter}. - * - * @since 5.2 - * @see ArgumentsAccessor - * @see DefaultArgumentConverter - * @see org.junit.jupiter.params.ParameterizedTest - */ -@API(status = INTERNAL, since = "5.2") -public class DefaultArgumentsAccessor implements ArgumentsAccessor { - - private final Object[] arguments; - - public DefaultArgumentsAccessor(Object... arguments) { - Preconditions.notNull(arguments, "Arguments array must not be null"); - this.arguments = arguments; - } - - @Override - public Object get(int index) { - Preconditions.condition(index >= 0 && index < this.arguments.length, - () -> format("index must be >= 0 and < %d", this.arguments.length)); - return this.arguments[index]; - } - - @Override - public T get(int index, Class requiredType) { - Preconditions.notNull(requiredType, "requiredType must not be null"); - Object value = get(index); - try { - Object convertedValue = DefaultArgumentConverter.INSTANCE.convert(value, requiredType); - return requiredType.cast(convertedValue); - } - catch (Exception ex) { - String message = format( - "Argument at index [%d] with value [%s] and type [%s] could not be converted or cast to type [%s].", - index, value, ClassUtils.nullSafeToString(value == null ? null : value.getClass()), - requiredType.getName()); - throw new ArgumentAccessException(message, ex); - } - } - - @Override - public Character getCharacter(int index) { - return get(index, Character.class); - } - - @Override - public Boolean getBoolean(int index) { - return get(index, Boolean.class); - } - - @Override - public Byte getByte(int index) { - return get(index, Byte.class); - } - - @Override - public Short getShort(int index) { - return get(index, Short.class); - } - - @Override - public Integer getInteger(int index) { - return get(index, Integer.class); - } - - @Override - public Long getLong(int index) { - return get(index, Long.class); - } - - @Override - public Float getFloat(int index) { - return get(index, Float.class); - } - - @Override - public Double getDouble(int index) { - return get(index, Double.class); - } - - @Override - public String getString(int index) { - return get(index, String.class); - } - - @Override - public int size() { - return this.arguments.length; - } - - @Override - public Object[] toArray() { - return Arrays.copyOf(this.arguments, this.arguments.length); - } - - @Override - public List toList() { - return Collections.unmodifiableList(Arrays.asList(this.arguments)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java deleted file mode 100644 index e7129488..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * The {@link org.junit.jupiter.params.aggregator.ArgumentsAggregator} and - * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor} interfaces and the - * {@link org.junit.jupiter.params.aggregator.AggregateWith} annotation. - */ - -package org.junit.jupiter.params.aggregator; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java deleted file mode 100644 index 0bd9513c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * {@code ArgumentConversionException} is an exception that can occur when an - * object is converted to another object by an implementation of an - * {@link ArgumentConverter}. - * - * @since 5.0 - * @see ArgumentConverter - */ -@API(status = STABLE, since = "5.7") -public class ArgumentConversionException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public ArgumentConversionException(String message) { - super(message); - } - - public ArgumentConversionException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java deleted file mode 100644 index d1758965..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ParameterContext; - -/** - * {@code ArgumentConverter} is an abstraction that allows an input object to - * be converted to an instance of a different class. - * - *

Such an {@code ArgumentConverter} is applied to the method parameter - * of a {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} - * method with the help of a - * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} annotation. - * - *

Implementations must provide a no-args constructor and should not make any - * assumptions regarding when they are instantiated or how often they are called. - * Since instances may potentially be cached and called from different threads, - * they should be thread-safe and designed to be used as singletons. - * - *

Extend {@link SimpleArgumentConverter} if your implementation only needs - * to know the target type and does not need access to the {@link ParameterContext} - * to perform the conversion. - * - *

Extend {@link TypedArgumentConverter} if your implementation always converts - * from a given source type into a given target type and does not need access to - * the {@link ParameterContext} to perform the conversion. - * - * @since 5.0 - * @see SimpleArgumentConverter - * @see org.junit.jupiter.params.ParameterizedTest - * @see org.junit.jupiter.params.converter.ConvertWith - * @see org.junit.jupiter.params.support.AnnotationConsumer - * @see SimpleArgumentConverter - * @see TypedArgumentConverter - */ -@API(status = STABLE, since = "5.7") -public interface ArgumentConverter { - - /** - * Convert the supplied {@code source} object according to the supplied - * {@code context}. - * - * @param source the source object to convert; may be {@code null} - * @param context the parameter context where the converted object will be - * used; never {@code null} - * @return the converted object; may be {@code null} but only if the target - * type is a reference type - * @throws ArgumentConversionException if an error occurs during the - * conversion - */ - Object convert(Object source, ParameterContext context) throws ArgumentConversionException; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java deleted file mode 100644 index 42be3c88..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ConvertWith} is an annotation that allows one to specify an explicit - * {@link ArgumentConverter}. - - *

This annotation may be applied to parameters of - * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} methods - * which need to have their {@code Arguments} converted before consuming them. - * - * @since 5.0 - * @see org.junit.jupiter.params.ParameterizedTest - * @see org.junit.jupiter.params.converter.ArgumentConverter - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -public @interface ConvertWith { - - /** - * The type of {@link ArgumentConverter} to use. - */ - Class value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java deleted file mode 100644 index 0db0027c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static java.util.Arrays.asList; -import static java.util.Collections.unmodifiableList; -import static java.util.Collections.unmodifiableMap; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; - -import java.io.File; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.MonthDay; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Period; -import java.time.Year; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.Currency; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * {@code DefaultArgumentConverter} is the default implementation of the - * {@link ArgumentConverter} API. - * - *

The {@code DefaultArgumentConverter} is able to convert from strings to a - * number of primitive types and their corresponding wrapper types (Byte, Short, - * Integer, Long, Float, and Double), date and time types from the - * {@code java.time} package, and some additional common Java types such as - * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency}, - * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc. - * - *

If the source and target types are identical the source object will not - * be modified. - * - * @since 5.0 - * @see org.junit.jupiter.params.converter.ArgumentConverter - */ -@API(status = INTERNAL, since = "5.0") -public class DefaultArgumentConverter extends SimpleArgumentConverter { - - public static final DefaultArgumentConverter INSTANCE = new DefaultArgumentConverter(); - - private static final List stringToObjectConverters = unmodifiableList(asList( // - new StringToBooleanAndCharPrimitiveConverter(), // - new StringToNumericPrimitiveConverter(), // - new StringToEnumConverter(), // - new StringToJavaTimeConverter(), // - new StringToCommonJavaTypesConverter(), // - new FallbackStringToObjectConverter() // - )); - - private DefaultArgumentConverter() { - // nothing to initialize - } - - @Override - public Object convert(Object source, Class targetType) { - if (source == null) { - if (targetType.isPrimitive()) { - throw new ArgumentConversionException( - "Cannot convert null to primitive value of type " + targetType.getName()); - } - return null; - } - - if (ReflectionUtils.isAssignableTo(source, targetType)) { - return source; - } - - return convertToTargetType(source, toWrapperType(targetType)); - } - - private Object convertToTargetType(Object source, Class targetType) { - if (source instanceof String) { - Optional converter = stringToObjectConverters.stream().filter( - candidate -> candidate.canConvert(targetType)).findFirst(); - if (converter.isPresent()) { - try { - return converter.get().convert((String) source, targetType); - } - catch (Exception ex) { - if (ex instanceof ArgumentConversionException) { - // simply rethrow it - throw (ArgumentConversionException) ex; - } - // else - throw new ArgumentConversionException( - "Failed to convert String \"" + source + "\" to type " + targetType.getName(), ex); - } - } - } - throw new ArgumentConversionException("No implicit conversion to convert object of type " - + source.getClass().getName() + " to type " + targetType.getName()); - } - - private static Class toWrapperType(Class targetType) { - Class wrapperType = getWrapperType(targetType); - return wrapperType != null ? wrapperType : targetType; - } - - interface StringToObjectConverter { - - boolean canConvert(Class targetType); - - Object convert(String source, Class targetType) throws Exception; - - } - - private static class StringToBooleanAndCharPrimitiveConverter implements StringToObjectConverter { - - private static final Map, Function> CONVERTERS; - static { - Map, Function> converters = new HashMap<>(); - converters.put(Boolean.class, Boolean::valueOf); - converters.put(Character.class, source -> { - Preconditions.condition(source.length() == 1, () -> "String must have length of 1: " + source); - return source.charAt(0); - }); - CONVERTERS = unmodifiableMap(converters); - } - - @Override - public boolean canConvert(Class targetType) { - return CONVERTERS.containsKey(targetType); - } - - @Override - public Object convert(String source, Class targetType) { - return CONVERTERS.get(targetType).apply(source); - } - } - - private static class StringToNumericPrimitiveConverter implements StringToObjectConverter { - - private static final Map, Function> CONVERTERS; - static { - Map, Function> converters = new HashMap<>(); - converters.put(Byte.class, Byte::decode); - converters.put(Short.class, Short::decode); - converters.put(Integer.class, Integer::decode); - converters.put(Long.class, Long::decode); - converters.put(Float.class, Float::valueOf); - converters.put(Double.class, Double::valueOf); - CONVERTERS = unmodifiableMap(converters); - } - - @Override - public boolean canConvert(Class targetType) { - return CONVERTERS.containsKey(targetType); - } - - @Override - public Object convert(String source, Class targetType) { - return CONVERTERS.get(targetType).apply(source.replace("_", "")); - } - } - - private static class StringToEnumConverter implements StringToObjectConverter { - - @Override - public boolean canConvert(Class targetType) { - return targetType.isEnum(); - } - - @Override - public Object convert(String source, Class targetType) throws Exception { - return valueOf(targetType, source); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Object valueOf(Class targetType, String source) { - return Enum.valueOf(targetType, source); - } - } - - private static class StringToJavaTimeConverter implements StringToObjectConverter { - - private static final Map, Function> CONVERTERS; - static { - Map, Function> converters = new HashMap<>(); - converters.put(Duration.class, Duration::parse); - converters.put(Instant.class, Instant::parse); - converters.put(LocalDate.class, LocalDate::parse); - converters.put(LocalDateTime.class, LocalDateTime::parse); - converters.put(LocalTime.class, LocalTime::parse); - converters.put(MonthDay.class, MonthDay::parse); - converters.put(OffsetDateTime.class, OffsetDateTime::parse); - converters.put(OffsetTime.class, OffsetTime::parse); - converters.put(Period.class, Period::parse); - converters.put(Year.class, Year::parse); - converters.put(YearMonth.class, YearMonth::parse); - converters.put(ZonedDateTime.class, ZonedDateTime::parse); - converters.put(ZoneId.class, ZoneId::of); - converters.put(ZoneOffset.class, ZoneOffset::of); - CONVERTERS = Collections.unmodifiableMap(converters); - } - - @Override - public boolean canConvert(Class targetType) { - return CONVERTERS.containsKey(targetType); - } - - @Override - public Object convert(String source, Class targetType) throws Exception { - return CONVERTERS.get(targetType).apply(source); - } - } - - private static class StringToCommonJavaTypesConverter implements StringToObjectConverter { - - private static final Map, Function> CONVERTERS; - - static { - Map, Function> converters = new HashMap<>(); - - // java.lang - converters.put(Class.class, StringToCommonJavaTypesConverter::toClass); - // java.io and java.nio - converters.put(File.class, File::new); - converters.put(Charset.class, Charset::forName); - converters.put(Path.class, Paths::get); - // java.net - converters.put(URI.class, URI::create); - converters.put(URL.class, StringToCommonJavaTypesConverter::toURL); - // java.math - converters.put(BigDecimal.class, BigDecimal::new); - converters.put(BigInteger.class, BigInteger::new); - // java.util - converters.put(Currency.class, Currency::getInstance); - converters.put(Locale.class, Locale::new); - converters.put(UUID.class, UUID::fromString); - - CONVERTERS = Collections.unmodifiableMap(converters); - } - - @Override - public boolean canConvert(Class targetType) { - return CONVERTERS.containsKey(targetType); - } - - @Override - public Object convert(String source, Class targetType) throws Exception { - return CONVERTERS.get(targetType).apply(source); - } - - private static Class toClass(String type) { - //@formatter:off - return ReflectionUtils - .tryToLoadClass(type) - .getOrThrow(cause -> new ArgumentConversionException( - "Failed to convert String \"" + type + "\" to type " + Class.class.getName(), cause)); - //@formatter:on - } - - private static URL toURL(String url) { - try { - return URI.create(url).toURL(); - } - catch (MalformedURLException ex) { - throw new ArgumentConversionException( - "Failed to convert String \"" + url + "\" to type " + URL.class.getName(), ex); - } - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java deleted file mode 100644 index be2ea7d1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; -import static org.junit.platform.commons.util.ReflectionUtils.findConstructors; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; -import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; -import static org.junit.platform.commons.util.ReflectionUtils.isNotPrivate; -import static org.junit.platform.commons.util.ReflectionUtils.isNotStatic; -import static org.junit.platform.commons.util.ReflectionUtils.newInstance; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.junit.jupiter.params.converter.DefaultArgumentConverter.StringToObjectConverter; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@code FallbackStringToObjectConverter} is a {@link StringToObjectConverter} - * that provides a fallback conversion strategy for converting from a - * {@link String} to a given target type by invoking a static factory method - * or factory constructor defined in the target type. - * - *

Search Algorithm

- * - *
    - *
  1. Search for a single, non-private static factory method in the target - * type that converts from a String to the target type. Use the factory method - * if present.
  2. - *
  3. Search for a single, non-private constructor in the target type that - * accepts a String. Use the constructor if present.
  4. - *
- * - *

If multiple suitable factory methods are discovered they will be ignored. - * If neither a single factory method nor a single constructor is found, this - * converter acts as a no-op. - * - * @since 5.1 - * @see DefaultArgumentConverter - */ -class FallbackStringToObjectConverter implements StringToObjectConverter { - - /** - * Implementation of the NULL Object Pattern. - */ - private static final Function NULL_EXECUTABLE = source -> source; - - /** - * Cache for factory methods and factory constructors. - * - *

Searches that do not find a factory method or constructor are tracked - * by the presence of a {@link #NULL_EXECUTABLE} object stored in the map. - * This prevents the framework from repeatedly searching for things which - * are already known not to exist. - */ - private static final ConcurrentHashMap, Function> factoryExecutableCache // - = new ConcurrentHashMap<>(64); - - @Override - public boolean canConvert(Class targetType) { - return findFactoryExecutable(targetType) != NULL_EXECUTABLE; - } - - @Override - public Object convert(String source, Class targetType) throws Exception { - Function executable = findFactoryExecutable(targetType); - Preconditions.condition(executable != NULL_EXECUTABLE, - "Illegal state: convert() must not be called if canConvert() returned false"); - - return executable.apply(source); - } - - private static Function findFactoryExecutable(Class targetType) { - return factoryExecutableCache.computeIfAbsent(targetType, type -> { - Method factoryMethod = findFactoryMethod(type); - if (factoryMethod != null) { - return source -> invokeMethod(factoryMethod, null, source); - } - Constructor constructor = findFactoryConstructor(type); - if (constructor != null) { - return source -> newInstance(constructor, source); - } - return NULL_EXECUTABLE; - }); - } - - private static Method findFactoryMethod(Class targetType) { - List factoryMethods = findMethods(targetType, new IsFactoryMethod(targetType), BOTTOM_UP); - if (factoryMethods.size() == 1) { - return factoryMethods.get(0); - } - return null; - } - - private static Constructor findFactoryConstructor(Class targetType) { - List> constructors = findConstructors(targetType, new IsFactoryConstructor(targetType)); - if (constructors.size() == 1) { - return constructors.get(0); - } - return null; - } - - /** - * {@link Predicate} that determines if the {@link Method} supplied to - * {@link #test(Method)} is a non-private static factory method for the - * supplied {@link #targetType}. - */ - static class IsFactoryMethod implements Predicate { - - private final Class targetType; - - IsFactoryMethod(Class targetType) { - this.targetType = targetType; - } - - @Override - public boolean test(Method method) { - // Please do not collapse the following into a single statement. - if (!method.getReturnType().equals(this.targetType)) { - return false; - } - if (isNotStatic(method)) { - return false; - } - return isNotPrivateAndAcceptsSingleStringArgument(method); - } - - } - - /** - * {@link Predicate} that determines if the {@link Constructor} supplied to - * {@link #test(Constructor)} is a non-private factory constructor for the - * supplied {@link #targetType}. - */ - static class IsFactoryConstructor implements Predicate> { - - private final Class targetType; - - IsFactoryConstructor(Class targetType) { - this.targetType = targetType; - } - - @Override - public boolean test(Constructor constructor) { - // Please do not collapse the following into a single statement. - if (!constructor.getDeclaringClass().equals(this.targetType)) { - return false; - } - return isNotPrivateAndAcceptsSingleStringArgument(constructor); - } - - } - - private static boolean isNotPrivateAndAcceptsSingleStringArgument(Executable executable) { - return isNotPrivate(executable) // - && (executable.getParameterCount() == 1) // - && (executable.getParameterTypes()[0] == String.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java deleted file mode 100644 index 7c1defaa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Year; -import java.time.YearMonth; -import java.time.ZonedDateTime; -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.ChronoLocalDateTime; -import java.time.chrono.ChronoZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalQuery; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.jupiter.params.support.AnnotationConsumer; - -/** - * @since 5.0 - */ -class JavaTimeArgumentConverter extends SimpleArgumentConverter - implements AnnotationConsumer { - - private static final Map, TemporalQuery> TEMPORAL_QUERIES; - static { - Map, TemporalQuery> queries = new LinkedHashMap<>(); - queries.put(ChronoLocalDate.class, ChronoLocalDate::from); - queries.put(ChronoLocalDateTime.class, ChronoLocalDateTime::from); - queries.put(ChronoZonedDateTime.class, ChronoZonedDateTime::from); - queries.put(LocalDate.class, LocalDate::from); - queries.put(LocalDateTime.class, LocalDateTime::from); - queries.put(LocalTime.class, LocalTime::from); - queries.put(OffsetDateTime.class, OffsetDateTime::from); - queries.put(OffsetTime.class, OffsetTime::from); - queries.put(Year.class, Year::from); - queries.put(YearMonth.class, YearMonth::from); - queries.put(ZonedDateTime.class, ZonedDateTime::from); - TEMPORAL_QUERIES = Collections.unmodifiableMap(queries); - } - - private String pattern; - - @Override - public void accept(JavaTimeConversionPattern annotation) { - pattern = annotation.value(); - } - - @Override - public Object convert(Object input, Class targetClass) throws ArgumentConversionException { - if (!TEMPORAL_QUERIES.containsKey(targetClass)) { - throw new ArgumentConversionException("Cannot convert to " + targetClass.getName() + ": " + input); - } - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); - TemporalQuery temporalQuery = TEMPORAL_QUERIES.get(targetClass); - return formatter.parse(input.toString(), temporalQuery); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java deleted file mode 100644 index bbfab9e6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.params.ParameterizedTest; - -/** - * {@code @JavaTimeConversionPattern} is an annotation that allows a date/time - * conversion pattern to be specified on a parameter of a - * {@link ParameterizedTest @ParameterizedTest} method. - * - * @since 5.0 - * @see org.junit.jupiter.params.ParameterizedTest - * @see java.time.format.DateTimeFormatterBuilder#appendPattern(String) - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ConvertWith(JavaTimeArgumentConverter.class) -public @interface JavaTimeConversionPattern { - - /** - * The date/time conversion pattern. - * - * @see java.time.format.DateTimeFormatterBuilder#appendPattern(String) - */ - String value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java deleted file mode 100644 index dc1a32ea..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ParameterContext; - -/** - * {@code SimpleArgumentConverter} is an abstract base class for - * {@link ArgumentConverter} implementations that only need to know the target - * type and do not need access to the {@link ParameterContext} to perform the - * conversion. - * - * @since 5.0 - * @see ArgumentConverter - * @see TypedArgumentConverter - */ -@API(status = STABLE, since = "5.7") -public abstract class SimpleArgumentConverter implements ArgumentConverter { - - @Override - public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { - return convert(source, context.getParameter().getType()); - } - - /** - * Convert the supplied {@code source} object into the supplied - * {@code targetType}. - * - * @param source the source object to convert; may be {@code null} - * @param targetType the target type the source object should be converted - * into; never {@code null} - * @return the converted object; may be {@code null} but only if the target - * type is a reference type - * @throws ArgumentConversionException in case an error occurs during the - * conversion - */ - protected abstract Object convert(Object source, Class targetType) throws ArgumentConversionException; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java deleted file mode 100644 index 505ba540..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * {@code TypedArgumentConverter} is an abstract base class for - * {@link ArgumentConverter} implementations that always convert objects of a - * given source type into a given target type. - * - * @param the type of the source argument to convert - * @param the type of the target object to create from the source - * @since 5.7 - * @see ArgumentConverter - * @see SimpleArgumentConverter - */ -@API(status = EXPERIMENTAL, since = "5.7") -public abstract class TypedArgumentConverter implements ArgumentConverter { - - private final Class sourceType; - private final Class targetType; - - /** - * Create a new {@code TypedArgumentConverter}. - * - * @param sourceType the type of the argument to convert; never {@code null} - * @param targetType the type of the target object to create from the source; - * never {@code null} - */ - protected TypedArgumentConverter(Class sourceType, Class targetType) { - this.sourceType = Preconditions.notNull(sourceType, "sourceType must not be null"); - this.targetType = Preconditions.notNull(targetType, "targetType must not be null"); - } - - @Override - public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { - if (source == null) { - return convert(null); - } - if (!this.sourceType.isInstance(source)) { - String message = String.format( - "%s cannot convert objects of type [%s]. Only source objects of type [%s] are supported.", - getClass().getSimpleName(), source.getClass().getName(), this.sourceType.getName()); - throw new ArgumentConversionException(message); - } - if (!ReflectionUtils.isAssignableTo(this.targetType, context.getParameter().getType())) { - String message = String.format("%s cannot convert to type [%s]. Only target type [%s] is supported.", - getClass().getSimpleName(), context.getParameter().getType().getName(), this.targetType.getName()); - throw new ArgumentConversionException(message); - } - return convert(this.sourceType.cast(source)); - } - - /** - * Convert the supplied {@code source} object of type {@code S} into an object - * of type {@code T}. - * - * @param source the source object to convert; may be {@code null} - * @return the converted object; may be {@code null} but only if the target - * type is a reference type - * @throws ArgumentConversionException if an error occurs during the - * conversion - */ - protected abstract T convert(S source) throws ArgumentConversionException; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java deleted file mode 100644 index ff813c18..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} - * implementations and the corresponding - * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} annotation. - */ - -package org.junit.jupiter.params.converter; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java deleted file mode 100644 index aa0820df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * JUnit Jupiter extension for parameterized tests. - */ - -package org.junit.jupiter.params; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java deleted file mode 100644 index da3371f3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@code Arguments} is an abstraction that provides access to an array of - * objects to be used for invoking a {@code @ParameterizedTest} method. - * - *

A {@link java.util.stream.Stream} of such {@code Arguments} will - * typically be provided by an {@link ArgumentsProvider}. - * - * @apiNote

This interface is specifically designed as a simple holder of - * arguments of a parameterized test. Therefore, if you end up - * {@linkplain java.util.stream.Stream#map(java.util.function.Function) transforming} - * or - * {@linkplain java.util.stream.Stream#filter(java.util.function.Predicate) filtering} - * the arguments, you should consider using one of the following in intermediate - * steps: - * - *

    - *
  • The standard collections
  • - *
  • Tuples from third-party libraries, e.g., - * Commons Lang, - * or javatuples
  • - *
  • Your own data class
  • - *
- * - *

Alternatively, you can use an - * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} - * to convert some of the arguments from one type to another. - * - * @since 5.0 - * @see org.junit.jupiter.params.ParameterizedTest - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.provider.ArgumentsProvider - * @see org.junit.jupiter.params.converter.ArgumentConverter - */ -@API(status = STABLE, since = "5.7") -public interface Arguments { - - /** - * Get the arguments used for an invocation of the - * {@code @ParameterizedTest} method. - * - * @apiNote If you need a type-safe way to access some or all of the arguments, - * please read the {@linkplain Arguments class-level API note}. - * - * @return the arguments; must not be {@code null} - */ - Object[] get(); - - /** - * Factory method for creating an instance of {@code Arguments} based on - * the supplied {@code arguments}. - * - * @param arguments the arguments to be used for an invocation of the test - * method; must not be {@code null} - * @return an instance of {@code Arguments}; never {@code null} - * @see #arguments(Object...) - */ - static Arguments of(Object... arguments) { - Preconditions.notNull(arguments, "argument array must not be null"); - return () -> arguments; - } - - /** - * Factory method for creating an instance of {@code Arguments} based on - * the supplied {@code arguments}. - * - *

This method is an alias for {@link Arguments#of} and is - * intended to be used when statically imported — for example, via: - * {@code import static org.junit.jupiter.params.provider.Arguments.arguments;} - * - * @param arguments the arguments to be used for an invocation of the test - * method; must not be {@code null} - * @return an instance of {@code Arguments}; never {@code null} - * @since 5.3 - */ - static Arguments arguments(Object... arguments) { - return of(arguments); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java deleted file mode 100644 index e57f5423..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * An {@code ArgumentsProvider} is responsible for {@linkplain #provideArguments - * providing} a stream of arguments to be passed to a {@code @ParameterizedTest} - * method. - * - *

An {@code ArgumentsProvider} can be registered via the - * {@link ArgumentsSource @ArgumentsSource} annotation. - * - *

Implementations must provide a no-args constructor. - * - * @since 5.0 - * @see org.junit.jupiter.params.ParameterizedTest - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.provider.Arguments - * @see org.junit.jupiter.params.support.AnnotationConsumer - */ -@API(status = STABLE, since = "5.7") -public interface ArgumentsProvider { - - /** - * Provide a {@link Stream} of {@link Arguments} to be passed to a - * {@code @ParameterizedTest} method. - * - * @param context the current extension context; never {@code null} - * @return a stream of arguments; never {@code null} - */ - Stream provideArguments(ExtensionContext context) throws Exception; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java deleted file mode 100644 index 04f7849d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ArgumentsSource} is a {@linkplain Repeatable repeatable} annotation - * that is used to register {@linkplain ArgumentsProvider argument providers} - * for the annotated test method. - * - *

{@code @ArgumentsSource} may also be used as a meta-annotation in order to - * create a custom composed annotation that inherits the semantics - * of {@code @ArgumentsSource}. - * - * @since 5.0 - * @see org.junit.jupiter.params.provider.ArgumentsProvider - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Repeatable(ArgumentsSources.class) -@API(status = STABLE, since = "5.7") -public @interface ArgumentsSource { - - /** - * The type of {@link ArgumentsProvider} to be used. - */ - Class value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java deleted file mode 100644 index 320c8ad8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ArgumentsSources} is a simple container for one or more - * {@link ArgumentsSource} annotations. - * - *

Note, however, that use of the {@code @ArgumentsSources} container is completely - * optional since {@code @ArgumentsSource} is a {@linkplain java.lang.annotation.Repeatable - * repeatable} annotation. - * - * @since 5.0 - * @see org.junit.jupiter.params.provider.ArgumentsSource - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -public @interface ArgumentsSources { - - /** - * An array of one or more {@link ArgumentsSource @ArgumentsSource} - * annotations. - */ - ArgumentsSource[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java deleted file mode 100644 index ceecaccb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.junit.jupiter.params.provider.CsvParserFactory.createParserFor; -import static org.junit.platform.commons.util.CollectionUtils.toSet; - -import java.io.StringReader; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; - -import com.univocity.parsers.csv.CsvParser; - -import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.support.AnnotationConsumer; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.UnrecoverableExceptions; - -/** - * @since 5.0 - */ -class CsvArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { - - private static final String LINE_SEPARATOR = "\n"; - - private CsvSource annotation; - private Set nullValues; - private CsvParser csvParser; - - @Override - public void accept(CsvSource annotation) { - this.annotation = annotation; - this.nullValues = toSet(annotation.nullValues()); - this.csvParser = createParserFor(annotation); - } - - @Override - public Stream provideArguments(ExtensionContext context) { - final boolean textBlockDeclared = !this.annotation.textBlock().isEmpty(); - Preconditions.condition(this.annotation.value().length > 0 ^ textBlockDeclared, - () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); - - return textBlockDeclared ? parseTextBlock() : parseValueArray(); - } - - private Stream parseTextBlock() { - String textBlock = this.annotation.textBlock(); - boolean useHeadersInDisplayName = this.annotation.useHeadersInDisplayName(); - List argumentsList = new ArrayList<>(); - - try { - List csvRecords = this.csvParser.parseAll(new StringReader(textBlock)); - String[] headers = useHeadersInDisplayName ? getHeaders(this.csvParser) : null; - - AtomicInteger index = new AtomicInteger(0); - for (String[] csvRecord : csvRecords) { - index.incrementAndGet(); - Preconditions.notNull(csvRecord, - () -> "Record at index " + index + " contains invalid CSV: \"\"\"\n" + textBlock + "\n\"\"\""); - argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); - } - } - catch (Throwable throwable) { - throw handleCsvException(throwable, this.annotation); - } - - return argumentsList.stream(); - } - - private Stream parseValueArray() { - boolean useHeadersInDisplayName = this.annotation.useHeadersInDisplayName(); - List argumentsList = new ArrayList<>(); - - try { - String[] headers = null; - AtomicInteger index = new AtomicInteger(0); - for (String input : this.annotation.value()) { - index.incrementAndGet(); - String[] csvRecord = this.csvParser.parseLine(input + LINE_SEPARATOR); - // Lazily retrieve headers if necessary. - if (useHeadersInDisplayName && headers == null) { - headers = getHeaders(this.csvParser); - continue; - } - Preconditions.notNull(csvRecord, - () -> "Record at index " + index + " contains invalid CSV: \"" + input + "\""); - argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); - } - } - catch (Throwable throwable) { - throw handleCsvException(throwable, this.annotation); - } - - return argumentsList.stream(); - } - - // Cannot get parsed headers until after parsing has started. - static String[] getHeaders(CsvParser csvParser) { - return Arrays.stream(csvParser.getContext().parsedHeaders())// - .map(String::trim)// - .toArray(String[]::new); - } - - /** - * Processes custom null values, supports wrapping of column values in - * {@link Named} if necessary (for CSV header support), and returns the - * CSV record wrapped in an {@link Arguments} instance. - */ - static Arguments processCsvRecord(Object[] csvRecord, Set nullValues, boolean useHeadersInDisplayName, - String[] headers) { - - // Nothing to process? - if (nullValues.isEmpty() && !useHeadersInDisplayName) { - return Arguments.of(csvRecord); - } - - Preconditions.condition(!useHeadersInDisplayName || (csvRecord.length <= headers.length), - () -> String.format( - "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", - csvRecord.length, headers.length, Arrays.toString(csvRecord))); - - Object[] arguments = new Object[csvRecord.length]; - for (int i = 0; i < csvRecord.length; i++) { - Object column = csvRecord[i]; - if (nullValues.contains(column)) { - column = null; - } - if (useHeadersInDisplayName) { - column = Named.of(headers[i] + " = " + column, column); - } - arguments[i] = column; - } - return Arguments.of(arguments); - } - - /** - * @return this method always throws an exception and therefore never - * returns anything; the return type is merely present to allow this - * method to be supplied as the operand in a {@code throw} statement - */ - static RuntimeException handleCsvException(Throwable throwable, Annotation annotation) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - if (throwable instanceof PreconditionViolationException) { - throw (PreconditionViolationException) throwable; - } - throw new CsvParsingException("Failed to parse CSV input configured via " + annotation, throwable); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java deleted file mode 100644 index 14650f66..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static java.util.Spliterators.spliteratorUnknownSize; -import static java.util.stream.Collectors.toList; -import static java.util.stream.StreamSupport.stream; -import static org.junit.jupiter.params.provider.CsvArgumentsProvider.getHeaders; -import static org.junit.jupiter.params.provider.CsvArgumentsProvider.handleCsvException; -import static org.junit.jupiter.params.provider.CsvArgumentsProvider.processCsvRecord; -import static org.junit.jupiter.params.provider.CsvParserFactory.createParserFor; -import static org.junit.platform.commons.util.CollectionUtils.toSet; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.Spliterator; -import java.util.stream.Stream; - -import com.univocity.parsers.csv.CsvParser; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.support.AnnotationConsumer; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.0 - */ -class CsvFileArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { - - private final InputStreamProvider inputStreamProvider; - - private CsvFileSource annotation; - private List sources; - private Charset charset; - private int numLinesToSkip; - private CsvParser csvParser; - - CsvFileArgumentsProvider() { - this(DefaultInputStreamProvider.INSTANCE); - } - - CsvFileArgumentsProvider(InputStreamProvider inputStreamProvider) { - this.inputStreamProvider = inputStreamProvider; - } - - @Override - public void accept(CsvFileSource annotation) { - this.annotation = annotation; - Stream resources = Arrays.stream(annotation.resources()).map(inputStreamProvider::classpathResource); - Stream files = Arrays.stream(annotation.files()).map(inputStreamProvider::file); - this.sources = Stream.concat(resources, files).collect(toList()); - this.charset = getCharsetFrom(annotation); - this.numLinesToSkip = annotation.numLinesToSkip(); - this.csvParser = createParserFor(annotation); - } - - private Charset getCharsetFrom(CsvFileSource annotation) { - try { - return Charset.forName(annotation.encoding()); - } - catch (Exception ex) { - throw new PreconditionViolationException("The charset supplied in " + annotation + " is invalid", ex); - } - } - - @Override - public Stream provideArguments(ExtensionContext context) { - // @formatter:off - return Preconditions.notEmpty(this.sources, "Resources or files must not be empty") - .stream() - .map(source -> source.open(context)) - .map(this::beginParsing) - .flatMap(this::toStream); - // @formatter:on - } - - private CsvParser beginParsing(InputStream inputStream) { - try { - this.csvParser.beginParsing(inputStream, this.charset); - } - catch (Throwable throwable) { - handleCsvException(throwable, this.annotation); - } - return this.csvParser; - } - - private Stream toStream(CsvParser csvParser) { - CsvParserIterator iterator = new CsvParserIterator(csvParser, this.annotation); - return stream(spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) // - .skip(this.numLinesToSkip) // - .onClose(() -> { - try { - csvParser.stopParsing(); - } - catch (Throwable throwable) { - handleCsvException(throwable, this.annotation); - } - }); - } - - private static class CsvParserIterator implements Iterator { - - private final CsvParser csvParser; - private final CsvFileSource annotation; - private final boolean useHeadersInDisplayName; - private final Set nullValues; - private Arguments nextArguments; - private String[] headers; - - CsvParserIterator(CsvParser csvParser, CsvFileSource annotation) { - this.csvParser = csvParser; - this.annotation = annotation; - this.useHeadersInDisplayName = annotation.useHeadersInDisplayName(); - this.nullValues = toSet(annotation.nullValues()); - advance(); - } - - @Override - public boolean hasNext() { - return this.nextArguments != null; - } - - @Override - public Arguments next() { - Arguments result = this.nextArguments; - advance(); - return result; - } - - private void advance() { - try { - String[] csvRecord = this.csvParser.parseNext(); - if (csvRecord != null) { - // Lazily retrieve headers if necessary. - if (this.useHeadersInDisplayName && this.headers == null) { - this.headers = getHeaders(this.csvParser); - } - this.nextArguments = processCsvRecord(csvRecord, this.nullValues, this.useHeadersInDisplayName, - this.headers); - } - else { - this.nextArguments = null; - } - } - catch (Throwable throwable) { - handleCsvException(throwable, this.annotation); - } - } - - } - - @FunctionalInterface - private interface Source { - - InputStream open(ExtensionContext context); - - } - - interface InputStreamProvider { - - InputStream openClasspathResource(Class baseClass, String path); - - InputStream openFile(String path); - - default Source classpathResource(String path) { - return context -> openClasspathResource(context.getRequiredTestClass(), path); - } - - default Source file(String path) { - return context -> openFile(path); - } - - } - - private static class DefaultInputStreamProvider implements InputStreamProvider { - - private static final DefaultInputStreamProvider INSTANCE = new DefaultInputStreamProvider(); - - @Override - public InputStream openClasspathResource(Class baseClass, String path) { - Preconditions.notBlank(path, () -> "Classpath resource [" + path + "] must not be null or blank"); - InputStream inputStream = baseClass.getResourceAsStream(path); - return Preconditions.notNull(inputStream, () -> "Classpath resource [" + path + "] does not exist"); - } - - @Override - public InputStream openFile(String path) { - Preconditions.notBlank(path, () -> "File [" + path + "] must not be null or blank"); - try { - return Files.newInputStream(Paths.get(path)); - } - catch (IOException e) { - throw new JUnitException("File [" + path + "] could not be read", e); - } - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java deleted file mode 100644 index 6c0a765c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @CsvFileSource} is an {@link ArgumentsSource} which is used to load - * comma-separated value (CSV) files from one or more classpath {@link #resources} - * or {@link #files}. - * - *

The CSV records parsed from these resources and files will be provided as - * arguments to the annotated {@code @ParameterizedTest} method. Note that the - * first record may optionally be used to supply CSV headers (see - * {@link #useHeadersInDisplayName}). - * - *

Any line beginning with a {@code #} symbol will be interpreted as a comment - * and will be ignored. - * - *

The column delimiter (which defaults to a comma ({@code ,})) can be customized - * via either {@link #delimiter} or {@link #delimiterString}. - * - *

In contrast to the default syntax used in {@code @CsvSource}, {@code @CsvFileSource} - * uses a double quote ({@code "}) as its quote character by default, but this can - * be changed via {@link #quoteCharacter}. An empty, quoted value ({@code ""}) - * results in an empty {@link String} unless the {@link #emptyValue} attribute is - * set; whereas, an entirely empty value is interpreted as a {@code null} - * reference. By specifying one or more {@link #nullValues} a custom value can be - * interpreted as a {@code null} reference (see the User Guide for an example). An - * {@link org.junit.jupiter.params.converter.ArgumentConversionException - * ArgumentConversionException} is thrown if the target type of a {@code null} - * reference is a primitive type. - * - *

NOTE: An unquoted empty value will always be converted to a - * {@code null} reference regardless of any custom values configured via the - * {@link #nullValues} attribute. - * - *

Except within a quoted string, leading and trailing whitespace in a CSV - * column is trimmed by default. This behavior can be changed by setting the - * {@link #ignoreLeadingAndTrailingWhitespace} attribute to {@code true}. - * - * @since 5.0 - * @see CsvSource - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.ParameterizedTest - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ArgumentsSource(CsvFileArgumentsProvider.class) -public @interface CsvFileSource { - - /** - * The CSV classpath resources to use as the sources of arguments; must not - * be empty unless {@link #files} is non-empty. - */ - String[] resources() default {}; - - /** - * The CSV files to use as the sources of arguments; must not be empty - * unless {@link #resources} is non-empty. - */ - String[] files() default {}; - - /** - * The encoding to use when reading the CSV files; must be a valid charset. - * - *

Defaults to {@code "UTF-8"}. - * - * @see java.nio.charset.StandardCharsets - */ - String encoding() default "UTF-8"; - - /** - * The line separator to use when reading the CSV files; must consist of 1 - * or 2 characters, typically {@code "\r"}, {@code "\n"}, or {@code "\r\n"}. - * - *

Defaults to {@code "\n"}. - */ - String lineSeparator() default "\n"; - - /** - * Configures whether the first CSV record should be treated as header names - * for columns. - * - *

When set to {@code true}, the header names will be used in the - * generated display name for each {@code @ParameterizedTest} method - * invocation. When using this feature, you must ensure that the display name - * pattern for {@code @ParameterizedTest} includes - * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_PLACEHOLDER} instead of - * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_WITH_NAMES_PLACEHOLDER} - * as demonstrated in the example below. - * - *

Defaults to {@code false}. - * - * - *

Example

- *
-	 * {@literal @}ParameterizedTest(name = "[{index}] {arguments}")
-	 * {@literal @}CsvFileSource(resources = "fruits.csv", useHeadersInDisplayName = true)
-	 * void test(String fruit, int rank) {
-	 *     // ...
-	 * }
- * - * @since 5.8.2 - */ - @API(status = EXPERIMENTAL, since = "5.8.2") - boolean useHeadersInDisplayName() default false; - - /** - * The quote character to use for quoted strings. - * - *

Defaults to a double quote ({@code "}). - * - *

You may change the quote character to anything that makes sense for - * your use case. - * - * @since 5.8.2 - */ - @API(status = EXPERIMENTAL, since = "5.8.2") - char quoteCharacter() default '"'; - - /** - * The column delimiter character to use when reading the CSV files. - * - *

This is an alternative to {@link #delimiterString} and cannot be - * used in conjunction with {@link #delimiterString}. - * - *

Defaults implicitly to {@code ','}, if neither delimiter attribute is - * explicitly set. - */ - char delimiter() default '\0'; - - /** - * The column delimiter string to use when reading the CSV files. - * - *

This is an alternative to {@link #delimiter} and cannot be used in - * conjunction with {@link #delimiter}. - * - *

Defaults implicitly to {@code ","}, if neither delimiter attribute is - * explicitly set. - * - * @since 5.6 - */ - String delimiterString() default ""; - - /** - * The number of lines to skip when reading the CSV files. - * - *

Typically used to skip header lines. - * - *

Defaults to {@code 0}. - * - * @since 5.1 - */ - int numLinesToSkip() default 0; - - /** - * The empty value to use when reading the CSV files. - * - *

This value replaces quoted empty strings read from the input. - * - *

Defaults to {@code ""}. - * - * @since 5.5 - */ - String emptyValue() default ""; - - /** - * A list of strings that should be interpreted as {@code null} references. - * - *

For example, you may wish for certain values such as {@code "N/A"} or - * {@code "NIL"} to be converted to {@code null} references. - * - *

Please note that unquoted empty values will always be converted - * to {@code null} references regardless of the value of this {@code nullValues} - * attribute; whereas, a quoted empty string will be treated as an - * {@link #emptyValue}. - * - *

Defaults to {@code {}}. - * - * @since 5.6 - */ - String[] nullValues() default {}; - - /** - * The maximum number of characters allowed per CSV column. - * - *

Must be a positive number. - * - *

Defaults to {@code 4096}. - * - * @since 5.7 - */ - @API(status = EXPERIMENTAL, since = "5.7") - int maxCharsPerColumn() default 4096; - - /** - * Controls whether leading and trailing whitespace characters of unquoted - * CSV columns should be ignored. - * - *

Defaults to {@code true}. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - boolean ignoreLeadingAndTrailingWhitespace() default true; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java deleted file mode 100644 index b31f5223..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import java.lang.annotation.Annotation; - -import com.univocity.parsers.csv.CsvParser; -import com.univocity.parsers.csv.CsvParserSettings; - -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.6 - */ -class CsvParserFactory { - - private static final String DEFAULT_DELIMITER = ","; - private static final String LINE_SEPARATOR = "\n"; - private static final char EMPTY_CHAR = '\0'; - private static final boolean COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE = true; - - static CsvParser createParserFor(CsvSource annotation) { - String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString()); - boolean commentProcessingEnabled = !annotation.textBlock().isEmpty(); - return createParser(delimiter, LINE_SEPARATOR, annotation.quoteCharacter(), annotation.emptyValue(), - annotation.maxCharsPerColumn(), commentProcessingEnabled, annotation.useHeadersInDisplayName(), - annotation.ignoreLeadingAndTrailingWhitespace()); - } - - static CsvParser createParserFor(CsvFileSource annotation) { - String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString()); - return createParser(delimiter, annotation.lineSeparator(), annotation.quoteCharacter(), annotation.emptyValue(), - annotation.maxCharsPerColumn(), COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE, - annotation.useHeadersInDisplayName(), annotation.ignoreLeadingAndTrailingWhitespace()); - } - - private static String selectDelimiter(Annotation annotation, char delimiter, String delimiterString) { - Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), - () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); - - if (delimiter != EMPTY_CHAR) { - return String.valueOf(delimiter); - } - if (!delimiterString.isEmpty()) { - return delimiterString; - } - return DEFAULT_DELIMITER; - } - - private static CsvParser createParser(String delimiter, String lineSeparator, char quote, String emptyValue, - int maxCharsPerColumn, boolean commentProcessingEnabled, boolean headerExtractionEnabled, - boolean ignoreLeadingAndTrailingWhitespace) { - return new CsvParser(createParserSettings(delimiter, lineSeparator, quote, emptyValue, maxCharsPerColumn, - commentProcessingEnabled, headerExtractionEnabled, ignoreLeadingAndTrailingWhitespace)); - } - - private static CsvParserSettings createParserSettings(String delimiter, String lineSeparator, char quote, - String emptyValue, int maxCharsPerColumn, boolean commentProcessingEnabled, boolean headerExtractionEnabled, - boolean ignoreLeadingAndTrailingWhitespace) { - - CsvParserSettings settings = new CsvParserSettings(); - settings.setHeaderExtractionEnabled(headerExtractionEnabled); - settings.getFormat().setDelimiter(delimiter); - settings.getFormat().setLineSeparator(lineSeparator); - settings.getFormat().setQuote(quote); - settings.getFormat().setQuoteEscape(quote); - settings.setEmptyValue(emptyValue); - settings.setCommentProcessingEnabled(commentProcessingEnabled); - settings.setAutoConfigurationEnabled(false); - settings.setIgnoreLeadingWhitespaces(ignoreLeadingAndTrailingWhitespace); - settings.setIgnoreTrailingWhitespaces(ignoreLeadingAndTrailingWhitespace); - Preconditions.condition(maxCharsPerColumn > 0, - () -> "maxCharsPerColumn must be a positive number: " + maxCharsPerColumn); - settings.setMaxCharsPerColumn(maxCharsPerColumn); - // Do not use the built-in support for skipping rows/lines since it will - // throw an IllegalArgumentException if the file does not contain at least - // the number of specified lines to skip. - // settings.setNumberOfRowsToSkip(annotation.numLinesToSkip()); - return settings; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java deleted file mode 100644 index 25267061..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * Thrown if an error is encountered while parsing CSV input. - * - * @since 5.3 - * @see CsvSource - * @see CsvFileSource - */ -@API(status = STABLE, since = "5.7") -public class CsvParsingException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public CsvParsingException(String message) { - super(message); - } - - public CsvParsingException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java deleted file mode 100644 index be44973c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @CsvSource} is an {@link ArgumentsSource} which reads comma-separated - * values (CSV) from one or more CSV records supplied via the {@link #value} - * attribute or {@link #textBlock} attribute. - * - *

The supplied values will be provided as arguments to the annotated - * {@code @ParameterizedTest} method. - * - *

The column delimiter (which defaults to a comma ({@code ,})) can be customized - * via either {@link #delimiter} or {@link #delimiterString}. - * - *

By default, {@code @CsvSource} uses a single quote ({@code '}) as its quote - * character, but this can be changed via {@link #quoteCharacter}. See the - * {@code 'lemon, lime'} examples in the documentation for the {@link #value} - * and {@link #textBlock} attributes. An empty, quoted value ({@code ''}) results - * in an empty {@link String} unless the {@link #emptyValue} attribute is set; - * whereas, an entirely empty value is interpreted as a {@code null} reference. - * By specifying one or more {@link #nullValues} a custom value can be interpreted - * as a {@code null} reference (see the User Guide for an example). An - * {@link org.junit.jupiter.params.converter.ArgumentConversionException - * ArgumentConversionException} is thrown if the target type of a {@code null} - * reference is a primitive type. - * - *

NOTE: An unquoted empty value will always be converted to a - * {@code null} reference regardless of any custom values configured via the - * {@link #nullValues} attribute. - * - *

Except within a quoted string, leading and trailing whitespace in a CSV - * column is trimmed by default. This behavior can be changed by setting the - * {@link #ignoreLeadingAndTrailingWhitespace} attribute to {@code true}. - * - *

In general, CSV records should not contain explicit newlines ({@code \n}) - * unless they are placed within quoted strings. Note that CSV records supplied - * via {@link #textBlock} will implicitly contain newlines at the end of each - * physical line within the text block. Thus, if a CSV column wraps across a - * new line in a text block, the column must be a quoted string. - * - * @since 5.0 - * @see CsvFileSource - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.ParameterizedTest - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ArgumentsSource(CsvArgumentsProvider.class) -public @interface CsvSource { - - /** - * The CSV records to use as the source of arguments; must not be empty. - * - *

Defaults to an empty array. You therefore must supply CSV content - * via this attribute or the {@link #textBlock} attribute. - * - *

Each value corresponds to a record in a CSV file and will be split using - * the specified {@link #delimiter} or {@link #delimiterString}. Note that - * the first value may optionally be used to supply CSV headers (see - * {@link #useHeadersInDisplayName}). - * - *

If text block syntax is supported by your programming language, - * you may find it more convenient to declare your CSV content via the - * {@link #textBlock} attribute. - * - *

Example

- *
-	 * {@literal @}ParameterizedTest
-	 * {@literal @}CsvSource({
-	 *     "apple,         1",
-	 *     "banana,        2",
-	 *     "'lemon, lime', 0xF1",
-	 *     "strawberry,    700_000"
-	 * })
-	 * void test(String fruit, int rank) {
-	 *     // ...
-	 * }
- * - * @see #textBlock - */ - String[] value() default {}; - - /** - * The CSV records to use as the source of arguments, supplied as a single - * text block; must not be empty. - * - *

Defaults to an empty string. You therefore must supply CSV content - * via this attribute or the {@link #value} attribute. - * - *

Text block syntax is supported by various languages on the JVM - * including Java SE 15 or higher. If text blocks are not supported, you - * should declare your CSV content via the {@link #value} attribute. - * - *

Each record in the text block corresponds to a record in a CSV file and will - * be split using the specified {@link #delimiter} or {@link #delimiterString}. - * Note that the first record may optionally be used to supply CSV headers (see - * {@link #useHeadersInDisplayName}). - * - *

In contrast to CSV records supplied via {@link #value}, a text block - * can contain comments. Any line beginning with a hash tag ({@code #}) will - * be treated as a comment and ignored. Note, however, that the {@code #} - * symbol must be the first character on the line without any leading - * whitespace. It is therefore recommended that the closing text block - * delimiter {@code """} be placed either at the end of the last line of - * input or on the following line, vertically aligned with the rest of the - * input (as can be seen in the example below). - * - *

Java's text block - * feature automatically removes incidental whitespace when the code - * is compiled. However other JVM languages such as Groovy and Kotlin do not. - * Thus, if you are using a programming language other than Java and your text - * block contains comments or new lines within quoted strings, you will need - * to ensure that there is no leading whitespace within your text block. - * - *

Example

- *
-	 * {@literal @}ParameterizedTest
-	 * {@literal @}CsvSource(quoteCharacter = '"', textBlock = """
-	 *     # FRUIT,       RANK
-	 *     apple,         1
-	 *     banana,        2
-	 *     "lemon, lime", 0xF1
-	 *     strawberry,    700_000
-	 *     """)
-	 * void test(String fruit, int rank) {
-	 *     // ...
-	 * }
- * - * @since 5.8.1 - * @see #value - * @see #quoteCharacter - */ - @API(status = EXPERIMENTAL, since = "5.8.1") - String textBlock() default ""; - - /** - * Configures whether the first CSV record should be treated as header names - * for columns. - * - *

When set to {@code true}, the header names will be used in the - * generated display name for each {@code @ParameterizedTest} method - * invocation. When using this feature, you must ensure that the display name - * pattern for {@code @ParameterizedTest} includes - * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_PLACEHOLDER} instead of - * {@value org.junit.jupiter.params.ParameterizedTest#ARGUMENTS_WITH_NAMES_PLACEHOLDER} - * as demonstrated in the example below. - * - *

Defaults to {@code false}. - * - *

Example

- *
-	 * {@literal @}ParameterizedTest(name = "[{index}] {arguments}")
-	 * {@literal @}CsvSource(useHeadersInDisplayName = true, textBlock = """
-	 *     FRUIT,         RANK
-	 *     apple,         1
-	 *     banana,        2
-	 *     'lemon, lime', 0xF1
-	 *     strawberry,    700_000
-	 *     """)
-	 * void test(String fruit, int rank) {
-	 *     // ...
-	 * }
- * - * @since 5.8.2 - */ - @API(status = EXPERIMENTAL, since = "5.8.2") - boolean useHeadersInDisplayName() default false; - - /** - * The quote character to use for quoted strings. - * - *

Defaults to a single quote ({@code '}). - * - *

You may change the quote character to anything that makes sense for - * your use case; however, the primary use case is to allow you to use double - * quotes in {@link #textBlock}. - * - * @since 5.8.2 - * @see #textBlock - */ - @API(status = EXPERIMENTAL, since = "5.8.2") - char quoteCharacter() default '\''; - - /** - * The column delimiter character to use when reading the {@linkplain #value records}. - * - *

This is an alternative to {@link #delimiterString} and cannot be - * used in conjunction with {@link #delimiterString}. - * - *

Defaults implicitly to {@code ','}, if neither delimiter attribute is - * explicitly set. - */ - char delimiter() default '\0'; - - /** - * The column delimiter string to use when reading the {@linkplain #value records}. - * - *

This is an alternative to {@link #delimiter} and cannot be used in - * conjunction with {@link #delimiter}. - * - *

Defaults implicitly to {@code ","}, if neither delimiter attribute is - * explicitly set. - * - * @since 5.6 - */ - String delimiterString() default ""; - - /** - * The empty value to use when reading the {@linkplain #value records}. - * - *

This value replaces quoted empty strings read from the input. - * - *

Defaults to {@code ""}. - * - * @since 5.5 - */ - String emptyValue() default ""; - - /** - * A list of strings that should be interpreted as {@code null} references. - * - *

For example, you may wish for certain values such as {@code "N/A"} or - * {@code "NIL"} to be converted to {@code null} references. - * - *

Please note that unquoted empty values will always be converted - * to {@code null} references regardless of the value of this {@code nullValues} - * attribute; whereas, a quoted empty string will be treated as an - * {@link #emptyValue}. - * - *

Defaults to {@code {}}. - * - * @since 5.6 - */ - String[] nullValues() default {}; - - /** - * The maximum number of characters allowed per CSV column. - * - *

Must be a positive number. - * - *

Defaults to {@code 4096}. - * - * @since 5.7 - */ - @API(status = EXPERIMENTAL, since = "5.7") - int maxCharsPerColumn() default 4096; - - /** - * Controls whether leading and trailing whitespace characters of unquoted - * CSV columns should be ignored. - * - *

Defaults to {@code true}. - * - * @since 5.8 - */ - @API(status = EXPERIMENTAL, since = "5.8") - boolean ignoreLeadingAndTrailingWhitespace() default true; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java deleted file mode 100644 index 8debbb3a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.4 - * @see EmptySource - */ -class EmptyArgumentsProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - Method testMethod = context.getRequiredTestMethod(); - Class[] parameterTypes = testMethod.getParameterTypes(); - - Preconditions.condition(parameterTypes.length > 0, () -> String.format( - "@EmptySource cannot provide an empty argument to method [%s]: the method does not declare any formal parameters.", - testMethod.toGenericString())); - - Class parameterType = parameterTypes[0]; - - if (String.class.equals(parameterType)) { - return Stream.of(arguments("")); - } - if (List.class.equals(parameterType)) { - return Stream.of(arguments(Collections.emptyList())); - } - if (Set.class.equals(parameterType)) { - return Stream.of(arguments(Collections.emptySet())); - } - if (Map.class.equals(parameterType)) { - return Stream.of(arguments(Collections.emptyMap())); - } - if (parameterType.isArray()) { - Object array = Array.newInstance(parameterType.getComponentType(), 0); - return Stream.of(arguments(array)); - } - // else - throw new PreconditionViolationException( - String.format("@EmptySource cannot provide an empty argument to method [%s]: [%s] is not a supported type.", - testMethod.toGenericString(), parameterType.getName())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java deleted file mode 100644 index 99023a73..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @EmptySource} is an {@link ArgumentsSource} which provides a single - * empty argument to the annotated {@code @ParameterizedTest} method. - * - *

Supported Parameter Types

- * - *

This argument source will only provide an empty argument for the following - * method parameter types. Subtypes of the supported types are not supported. - * - *

    - *
  • {@link java.lang.String}
  • - *
  • {@link java.util.List}
  • - *
  • {@link java.util.Set}
  • - *
  • {@link java.util.Map}
  • - *
  • primitive arrays — for example {@code int[]}, {@code char[][]}, etc.
  • - *
  • object arrays — for example {@code String[]}, {@code Integer[][]}, etc.
  • - *
- * - * @since 5.4 - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.ParameterizedTest - * @see NullSource - * @see NullAndEmptySource - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ArgumentsSource(EmptyArgumentsProvider.class) -public @interface EmptySource { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java deleted file mode 100644 index a4853eeb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toSet; - -import java.lang.reflect.Method; -import java.util.EnumSet; -import java.util.Set; -import java.util.stream.Stream; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.support.AnnotationConsumer; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.0 - */ -class EnumArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { - - private EnumSource enumSource; - - @Override - public void accept(EnumSource enumSource) { - this.enumSource = enumSource; - } - - @Override - public Stream provideArguments(ExtensionContext context) { - Set> constants = getEnumConstants(context); - EnumSource.Mode mode = enumSource.mode(); - String[] declaredConstantNames = enumSource.names(); - if (declaredConstantNames.length > 0) { - Set uniqueNames = stream(declaredConstantNames).collect(toSet()); - Preconditions.condition(uniqueNames.size() == declaredConstantNames.length, - () -> "Duplicate enum constant name(s) found in " + enumSource); - mode.validate(enumSource, constants, uniqueNames); - constants.removeIf(constant -> !mode.select(constant, uniqueNames)); - } - return constants.stream().map(Arguments::of); - } - - private > Set getEnumConstants(ExtensionContext context) { - Class enumClass = determineEnumClass(context); - return EnumSet.allOf(enumClass); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private > Class determineEnumClass(ExtensionContext context) { - Class enumClass = enumSource.value(); - if (enumClass.equals(NullEnum.class)) { - Method method = context.getRequiredTestMethod(); - Class[] parameterTypes = method.getParameterTypes(); - Preconditions.condition(parameterTypes.length > 0, - () -> "Test method must declare at least one parameter: " + method.toGenericString()); - Preconditions.condition(Enum.class.isAssignableFrom(parameterTypes[0]), - () -> "First parameter must reference an Enum type (alternatively, use the annotation's 'value' attribute to specify the type explicitly): " - + method.toGenericString()); - enumClass = parameterTypes[0]; - } - return enumClass; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java deleted file mode 100644 index 2a215ae1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static java.util.stream.Collectors.toSet; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Set; -import java.util.function.BiPredicate; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; - -/** - * {@code @EnumSource} is an {@link ArgumentsSource} for constants of - * an {@link Enum}. - * - *

The enum constants will be provided as arguments to the annotated - * {@code @ParameterizedTest} method. - * - *

The enum type can be specified explicitly using the {@link #value} - * attribute. Otherwise, the declared type of the first parameter of the - * {@code @ParameterizedTest} method is used. - * - *

The set of enum constants can be restricted via the {@link #names} and - * {@link #mode} attributes. - * - * @since 5.0 - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.ParameterizedTest - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ArgumentsSource(EnumArgumentsProvider.class) -public @interface EnumSource { - - /** - * The enum type that serves as the source of the enum constants. - * - *

If this attribute is not set explicitly, the declared type of the - * first parameter of the {@code @ParameterizedTest} method is used. - * - * @see #names - * @see #mode - */ - Class> value() default NullEnum.class; - - /** - * The names of enum constants to provide, or regular expressions to select - * the names of enum constants to provide. - * - *

If no names or regular expressions are specified, all enum constants - * declared in the specified {@linkplain #value enum type} will be provided. - * - *

The {@link #mode} determines how the names are interpreted. - * - * @see #value - * @see #mode - */ - String[] names() default {}; - - /** - * The enum constant selection mode. - * - *

Defaults to {@link Mode#INCLUDE INCLUDE}. - * - * @see Mode#INCLUDE - * @see Mode#EXCLUDE - * @see Mode#MATCH_ALL - * @see Mode#MATCH_ANY - * @see Mode#MATCH_NONE - * @see #names - */ - Mode mode() default Mode.INCLUDE; - - /** - * Enumeration of modes for selecting enum constants by name. - */ - enum Mode { - - /** - * Select only those enum constants whose names are supplied via the - * {@link EnumSource#names} attribute. - */ - INCLUDE(Mode::validateNames, (name, names) -> names.contains(name)), - - /** - * Select all declared enum constants except those supplied via the - * {@link EnumSource#names} attribute. - */ - EXCLUDE(Mode::validateNames, (name, names) -> !names.contains(name)), - - /** - * Select only those enum constants whose names match all patterns supplied - * via the {@link EnumSource#names} attribute. - * - * @see java.util.stream.Stream#allMatch(java.util.function.Predicate) - */ - MATCH_ALL(Mode::validatePatterns, (name, patterns) -> patterns.stream().allMatch(name::matches)), - - /** - * Select only those enum constants whose names match any pattern supplied - * via the {@link EnumSource#names} attribute. - * - * @see java.util.stream.Stream#anyMatch(java.util.function.Predicate) - */ - MATCH_ANY(Mode::validatePatterns, (name, patterns) -> patterns.stream().anyMatch(name::matches)), - - /** - * Select only those enum constants whose names match none of the patterns supplied - * via the {@link EnumSource#names} attribute. - * - * @since 5.9 - * @see java.util.stream.Stream#noneMatch(java.util.function.Predicate) - */ - @API(status = EXPERIMENTAL, since = "5.9") - MATCH_NONE(Mode::validatePatterns, (name, patterns) -> patterns.stream().noneMatch(name::matches)); - - private final Validator validator; - private final BiPredicate> selector; - - Mode(Validator validator, BiPredicate> selector) { - this.validator = validator; - this.selector = selector; - } - - void validate(EnumSource enumSource, Set> constants, Set names) { - Preconditions.notNull(enumSource, "EnumSource must not be null"); - Preconditions.notNull(names, "names must not be null"); - - validator.validate(enumSource, constants, names); - } - - boolean select(Enum constant, Set names) { - Preconditions.notNull(constant, "Enum constant must not be null"); - Preconditions.notNull(names, "names must not be null"); - - return selector.test(constant.name(), names); - } - - private static void validateNames(EnumSource enumSource, Set> constants, Set names) { - Set allNames = constants.stream().map(Enum::name).collect(toSet()); - Preconditions.condition(allNames.containsAll(names), - () -> "Invalid enum constant name(s) in " + enumSource + ". Valid names include: " + allNames); - } - - private static void validatePatterns(EnumSource enumSource, Set> constants, - Set names) { - try { - names.forEach(Pattern::compile); - } - catch (PatternSyntaxException e) { - throw new PreconditionViolationException( - "Pattern compilation failed for a regular expression supplied in " + enumSource, e); - } - } - - private interface Validator { - void validate(EnumSource enumSource, Set> constants, Set names); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java deleted file mode 100644 index d9fdffab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static java.lang.String.format; -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toList; -import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; -import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestTemplate; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.support.AnnotationConsumer; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.CollectionUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; - -/** - * @since 5.0 - */ -class MethodArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { - - private String[] methodNames; - - private static final Predicate isFactoryMethod = // - method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method); - - @Override - public void accept(MethodSource annotation) { - this.methodNames = annotation.value(); - } - - @Override - public Stream provideArguments(ExtensionContext context) { - Class testClass = context.getRequiredTestClass(); - Method testMethod = context.getRequiredTestMethod(); - Object testInstance = context.getTestInstance().orElse(null); - // @formatter:off - return stream(this.methodNames) - .map(factoryMethodName -> findFactoryMethod(testClass, testMethod, factoryMethodName)) - .map(factoryMethod -> context.getExecutableInvoker().invoke(factoryMethod, testInstance)) - .flatMap(CollectionUtils::toStream) - .map(MethodArgumentsProvider::toArguments); - // @formatter:on - } - - private static Method findFactoryMethod(Class testClass, Method testMethod, String factoryMethodName) { - String originalFactoryMethodName = factoryMethodName; - - // If the user did not provide a factory method name, find a "default" local - // factory method with the same name as the parameterized test method. - if (StringUtils.isBlank(factoryMethodName)) { - factoryMethodName = testMethod.getName(); - return findFactoryMethodBySimpleName(testClass, testMethod, factoryMethodName); - } - - // Convert local factory method name to fully-qualified method name. - if (!looksLikeAFullyQualifiedMethodName(factoryMethodName)) { - factoryMethodName = testClass.getName() + "#" + factoryMethodName; - } - - // Find factory method using fully-qualified name. - Method factoryMethod = findFactoryMethodByFullyQualifiedName(testMethod, factoryMethodName); - - // Ensure factory method has a valid return type and is not a test method. - Preconditions.condition(isFactoryMethod.test(factoryMethod), () -> format( - "Could not find valid factory method [%s] for test class [%s] but found the following invalid candidate: %s", - originalFactoryMethodName, testClass.getName(), factoryMethod)); - - return factoryMethod; - } - - private static boolean looksLikeAFullyQualifiedMethodName(String factoryMethodName) { - if (factoryMethodName.contains("#")) { - return true; - } - int indexOfDot = factoryMethodName.indexOf("."); - if (indexOfDot == -1) { - return false; - } - int indexOfOpeningParenthesis = factoryMethodName.indexOf("("); - if (indexOfOpeningParenthesis > 0) { - // Exclude simple method names with parameters - return indexOfDot < indexOfOpeningParenthesis; - } - // If we get this far, we conclude the supplied factory method name "looks" - // like it was intended to be a fully-qualified method name, even if the - // syntax is invalid. We do this in order to provide better diagnostics for - // the user when a fully-qualified method name is in fact invalid. - return true; - } - - private static Method findFactoryMethodByFullyQualifiedName(Method testMethod, String fullyQualifiedMethodName) { - String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); - String className = methodParts[0]; - String methodName = methodParts[1]; - String methodParameters = methodParts[2]; - Class clazz = loadRequiredClass(className); - - // Attempt to find an exact match first. - Method factoryMethod = ReflectionUtils.findMethod(clazz, methodName, methodParameters).orElse(null); - if (factoryMethod != null) { - return factoryMethod; - } - - boolean explicitParameterListSpecified = // - StringUtils.isNotBlank(methodParameters) || fullyQualifiedMethodName.endsWith("()"); - - // If we didn't find an exact match but an explicit parameter list was specified, - // that's a user configuration error. - Preconditions.condition(!explicitParameterListSpecified, - () -> format("Could not find factory method [%s(%s)] in class [%s]", methodName, methodParameters, - className)); - - // Otherwise, fall back to the same lenient search semantics that are used - // to locate a "default" local factory method. - return findFactoryMethodBySimpleName(clazz, testMethod, methodName); - } - - /** - * Find the factory method by searching for all methods in the given {@code clazz} - * with the desired {@code factoryMethodName} which have return types that can be - * converted to a {@link Stream}, ignoring the {@code testMethod} itself as well - * as any {@code @Test}, {@code @TestTemplate}, or {@code @TestFactory} methods - * with the same name. - * @return the single factory method matching the search criteria - * @throws PreconditionViolationException if the factory method was not found or - * multiple competing factory methods with the same name were found - */ - private static Method findFactoryMethodBySimpleName(Class clazz, Method testMethod, String factoryMethodName) { - Predicate isCandidate = candidate -> factoryMethodName.equals(candidate.getName()) - && !testMethod.equals(candidate); - List candidates = ReflectionUtils.findMethods(clazz, isCandidate); - - List factoryMethods = candidates.stream().filter(isFactoryMethod).collect(toList()); - - Preconditions.condition(factoryMethods.size() > 0, () -> { - // If we didn't find the factory method using the isFactoryMethod Predicate, perhaps - // the specified factory method has an invalid return type or is a test method. - // In that case, we report the invalid candidates that were found. - if (candidates.size() > 0) { - return format( - "Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s", - factoryMethodName, clazz.getName(), candidates); - } - // Otherwise, report that we didn't find anything. - return format("Could not find factory method [%s] in class [%s]", factoryMethodName, clazz.getName()); - }); - Preconditions.condition(factoryMethods.size() == 1, - () -> format("%d factory methods named [%s] were found in class [%s]: %s", factoryMethods.size(), - factoryMethodName, clazz.getName(), factoryMethods)); - return factoryMethods.get(0); - } - - private static boolean isTestMethod(Method candidate) { - return isAnnotated(candidate, Test.class) || isAnnotated(candidate, TestTemplate.class) - || isAnnotated(candidate, TestFactory.class); - } - - private static Class loadRequiredClass(String className) { - return ReflectionUtils.tryToLoadClass(className).getOrThrow( - cause -> new JUnitException(format("Could not load class [%s]", className), cause)); - } - - private static Arguments toArguments(Object item) { - - // Nothing to do except cast. - if (item instanceof Arguments) { - return (Arguments) item; - } - - // Pass all multidimensional arrays "as is", in contrast to Object[]. - // See https://github.com/junit-team/junit5/issues/1665 - if (ReflectionUtils.isMultidimensionalArray(item)) { - return arguments(item); - } - - // Special treatment for one-dimensional reference arrays. - // See https://github.com/junit-team/junit5/issues/1665 - if (item instanceof Object[]) { - return arguments((Object[]) item); - } - - // Pass everything else "as is". - return arguments(item); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java deleted file mode 100644 index 94f6225f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.junit.jupiter.params.ParameterizedTest; - -/** - * {@code @MethodSource} is an {@link ArgumentsSource} which provides access - * to values returned from {@linkplain #value() factory methods} of the class in - * which this annotation is declared or from static factory methods in external - * classes referenced by fully qualified method name. - * - *

Each factory method must generate a stream of arguments, - * and each set of "arguments" within the "stream" will be provided as the physical - * arguments for individual invocations of the annotated - * {@link ParameterizedTest @ParameterizedTest} method. Generally speaking this - * translates to a {@link java.util.stream.Stream Stream} of {@link Arguments} - * (i.e., {@code Stream}); however, the actual concrete return type - * can take on many forms. In this context, a "stream" is anything that JUnit - * can reliably convert into a {@code Stream}, such as - * {@link java.util.stream.Stream Stream}, - * {@link java.util.stream.DoubleStream DoubleStream}, - * {@link java.util.stream.LongStream LongStream}, - * {@link java.util.stream.IntStream IntStream}, - * {@link java.util.Collection Collection}, - * {@link java.util.Iterator Iterator}, - * {@link Iterable}, an array of objects, or an array of primitives. Each set of - * "arguments" within the "stream" can be supplied as an instance of - * {@link Arguments}, an array of objects (e.g., {@code Object[]}, - * {@code String[]}, etc.), or a single value if the parameterized test - * method accepts a single argument. - * - *

Please note that a one-dimensional array of objects supplied as a set of - * "arguments" will be handled differently than other types of arguments. - * Specifically, all of the elements of a one-dimensional array of objects will - * be passed as individual physical arguments to the {@code @ParameterizedTest} - * method. This behavior can be seen in the table below for the - * {@code static Stream factory()} method: the {@code @ParameterizedTest} - * method accepts individual {@code String} and {@code int} arguments rather than - * a single {@code Object[]} array. In contrast, any multidimensional array - * supplied as a set of "arguments" will be passed as a single physical argument - * to the {@code @ParameterizedTest} method without modification. This behavior - * can be seen in the table below for the {@code static Stream factory()} - * and {@code static Stream factory()} methods: the - * {@code @ParameterizedTest} methods for those factories accept individual - * {@code int[][]} and {@code Object[][]} arguments, respectively. - * - *

Examples

- * - *

The following table displays compatible method signatures for parameterized - * test methods and their corresponding factory methods. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Compatible method signatures and factory methods
{@code @ParameterizedTest} methodFactory method
{@code void test(int)}{@code static int[] factory()}
{@code void test(int)}{@code static IntStream factory()}
{@code void test(String)}{@code static String[] factory()}
{@code void test(String)}{@code static List factory()}
{@code void test(String)}{@code static Stream factory()}
{@code void test(String, String)}{@code static String[][] factory()}
{@code void test(String, int)}{@code static Object[][] factory()}
{@code void test(String, int)}{@code static Stream factory()}
{@code void test(String, int)}{@code static Stream factory()}
{@code void test(int[])}{@code static int[][] factory()}
{@code void test(int[])}{@code static Stream factory()}
{@code void test(int[][])}{@code static Stream factory()}
{@code void test(Object[][])}{@code static Stream factory()}
- * - *

Factory methods within the test class must be {@code static} unless the - * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} - * test instance lifecycle mode is used; whereas, factory methods in external - * classes must always be {@code static}. - * - *

Factory methods can declare parameters, which will be provided by registered - * implementations of {@link org.junit.jupiter.api.extension.ParameterResolver}. - * - * @since 5.0 - * @see Arguments - * @see ArgumentsSource - * @see ParameterizedTest - * @see org.junit.jupiter.api.TestInstance - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ArgumentsSource(MethodArgumentsProvider.class) -public @interface MethodSource { - - /** - * The names of factory methods within the test class or in external classes - * to use as sources for arguments. - * - *

Factory methods in external classes must be referenced by - * fully-qualified method name — for example, - * {@code "com.example.StringsProviders#blankStrings"} or - * {@code "com.example.TopLevelClass$NestedClass#classMethod"} for a factory - * method in a static nested class. - * - *

If a factory method accepts arguments that are provided by a - * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolver}, - * you can supply the formal parameter list in the qualified method name to - * disambiguate between overloaded variants of the factory method. For example, - * {@code "blankStrings(int)"} for a local qualified method name or - * {@code "com.example.StringsProviders#blankStrings(int)"} for a fully-qualified - * method name. - * - *

If no factory method names are declared, a method within the test class - * that has the same name as the test method will be used as the factory - * method by default. - * - *

For further information, see the {@linkplain MethodSource class-level Javadoc}. - */ - String[] value() default ""; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java deleted file mode 100644 index ebbd4162..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @NullAndEmptySource} is a composed annotation that combines - * the functionality of {@link NullSource @NullSource} and - * {@link EmptySource @EmptySource}. - * - *

Annotating a {@code @ParameterizedTest} method with - * {@code @NullAndEmptySource} is equivalent to annotating the method with - * {@code @NullSource} and {@code @EmptySource}. - * - * @since 5.4 - * @see org.junit.jupiter.params.ParameterizedTest - * @see NullSource - * @see EmptySource - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@NullSource -@EmptySource -public @interface NullAndEmptySource { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java deleted file mode 100644 index 569dffde..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.lang.reflect.Method; -import java.util.stream.Stream; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.4 - * @see NullSource - */ -class NullArgumentsProvider implements ArgumentsProvider { - - private static final Arguments nullArguments = arguments(new Object[] { null }); - - @Override - public Stream provideArguments(ExtensionContext context) { - Method testMethod = context.getRequiredTestMethod(); - Preconditions.condition(testMethod.getParameterCount() > 0, () -> String.format( - "@NullSource cannot provide a null argument to method [%s]: the method does not declare any formal parameters.", - testMethod.toGenericString())); - - return Stream.of(nullArguments); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java deleted file mode 100644 index f040c26c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; - -/** - * Dummy enum class used as default value for optional attributes of - * annotations. - * - * @since 5.6 - * @see EnumSource#value() - */ -@API(status = INTERNAL, since = "5.7") -public enum NullEnum { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java deleted file mode 100644 index 283e78bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @NullSource} is an {@link ArgumentsSource} which provides a single - * {@code null} argument to the annotated {@code @ParameterizedTest} method. - * - *

Note that {@code @NullSource} cannot be used for an argument that has - * a primitive type, unless the argument is converted to a corresponding wrapper - * type with an {@link org.junit.jupiter.params.converter.ArgumentConverter}. - * - * @since 5.4 - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.ParameterizedTest - * @see EmptySource - * @see NullAndEmptySource - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ArgumentsSource(NullArgumentsProvider.class) -public @interface NullSource { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java deleted file mode 100644 index 8bda32cf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static java.util.stream.Collectors.toList; - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.List; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.support.AnnotationConsumer; -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.0 - */ -class ValueArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { - - private Object[] arguments; - - @Override - public void accept(ValueSource source) { - // @formatter:off - List arrays = - Stream.of( - source.shorts(), - source.bytes(), - source.ints(), - source.longs(), - source.floats(), - source.doubles(), - source.chars(), - source.booleans(), - source.strings(), - source.classes() - ) - .filter(array -> Array.getLength(array) > 0) - .collect(toList()); - // @formatter:on - - Preconditions.condition(arrays.size() == 1, () -> "Exactly one type of input must be provided in the @" - + ValueSource.class.getSimpleName() + " annotation, but there were " + arrays.size()); - - Object originalArray = arrays.get(0); - arguments = IntStream.range(0, Array.getLength(originalArray)) // - .mapToObj(index -> Array.get(originalArray, index)) // - .toArray(); - } - - @Override - public Stream provideArguments(ExtensionContext context) { - return Arrays.stream(arguments).map(Arguments::of); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java deleted file mode 100644 index bc63eb03..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ValueSource} is an {@link ArgumentsSource} which provides access to - * an array of literal values. - * - *

Supported types include {@link #shorts}, {@link #bytes}, {@link #ints}, - * {@link #longs}, {@link #floats}, {@link #doubles}, {@link #chars}, - * {@link #booleans}, {@link #strings}, and {@link #classes}. Note, however, - * that only one of the supported types may be specified per - * {@code @ValueSource} declaration. - * - *

The supplied literal values will be provided as arguments to the - * annotated {@code @ParameterizedTest} method. - * - * @since 5.0 - * @see org.junit.jupiter.params.provider.ArgumentsSource - * @see org.junit.jupiter.params.ParameterizedTest - */ -@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@API(status = STABLE, since = "5.7") -@ArgumentsSource(ValueArgumentsProvider.class) -public @interface ValueSource { - - /** - * The {@code short} values to use as sources of arguments; must not be empty. - * - * @since 5.1 - */ - short[] shorts() default {}; - - /** - * The {@code byte} values to use as sources of arguments; must not be empty. - * - * @since 5.1 - */ - byte[] bytes() default {}; - - /** - * The {@code int} values to use as sources of arguments; must not be empty. - */ - int[] ints() default {}; - - /** - * The {@code long} values to use as sources of arguments; must not be empty. - */ - long[] longs() default {}; - - /** - * The {@code float} values to use as sources of arguments; must not be empty. - * - * @since 5.1 - */ - float[] floats() default {}; - - /** - * The {@code double} values to use as sources of arguments; must not be empty. - */ - double[] doubles() default {}; - - /** - * The {@code char} values to use as sources of arguments; must not be empty. - * - * @since 5.1 - */ - char[] chars() default {}; - - /** - * The {@code boolean} values to use as sources of arguments; must not be empty. - * - * @since 5.5 - */ - boolean[] booleans() default {}; - - /** - * The {@link String} values to use as sources of arguments; must not be empty. - */ - String[] strings() default {}; - - /** - * The {@link Class} values to use as sources of arguments; must not be empty. - * - * @since 5.1 - */ - Class[] classes() default {}; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java deleted file mode 100644 index a41697ab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} - * implementations and their corresponding - * {@link org.junit.jupiter.params.provider.ArgumentsSource ArgumentsSource} - * annotations. - */ - -package org.junit.jupiter.params.provider; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java deleted file mode 100644 index 4930441c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.support; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Annotation; -import java.util.function.Consumer; - -import org.apiguardian.api.API; - -/** - * {@code AnnotationConsumer} is a {@linkplain FunctionalInterface functional - * interface} for consuming annotations. - * - *

It is typically implemented by implementations of - * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} - * and {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} - * in order to signal that they can {@link #accept} a certain annotation. - * - * @since 5.0 - */ -@FunctionalInterface -@API(status = STABLE, since = "5.7") -public interface AnnotationConsumer extends Consumer { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java deleted file mode 100644 index a9e2f01a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.support; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.AnnotationUtils; - -/** - * {@code AnnotationConsumerInitializer} is an internal helper class for - * initializing {@link AnnotationConsumer AnnotationConsumers}. - * - * @since 5.0 - */ -@API(status = INTERNAL, since = "5.0") -public final class AnnotationConsumerInitializer { - - private AnnotationConsumerInitializer() { - /* no-op */ - } - - // @formatter:off - private static final Predicate isAnnotationConsumerAcceptMethod = method -> - method.getName().equals("accept") - && method.getParameterCount() == 1 - && method.getParameterTypes()[0].isAnnotation(); - // @formatter:on - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static T initialize(AnnotatedElement annotatedElement, T instance) { - if (instance instanceof AnnotationConsumer) { - Method method = findMethods(instance.getClass(), isAnnotationConsumerAcceptMethod, BOTTOM_UP).get(0); - Class annotationType = (Class) method.getParameterTypes()[0]; - Annotation annotation = AnnotationUtils.findAnnotation(annotatedElement, annotationType) // - .orElseThrow(() -> new JUnitException(instance.getClass().getName() - + " must be used with an annotation of type " + annotationType.getName())); - initializeAnnotationConsumer((AnnotationConsumer) instance, annotation); - } - return instance; - } - - private static void initializeAnnotationConsumer(AnnotationConsumer instance, - A annotation) { - try { - instance.accept(annotation); - } - catch (Exception ex) { - throw new JUnitException("Failed to initialize AnnotationConsumer: " + instance, ex); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java deleted file mode 100644 index 56772a43..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Support classes for building - * {@linkplain org.junit.jupiter.params.provider.ArgumentsProvider providers} - * and - * {@linkplain org.junit.jupiter.params.converter.ArgumentConverter converters} - * for arguments. - */ - -package org.junit.jupiter.params.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt deleted file mode 100644 index bd1ef56d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -@file:API(status = API.Status.STABLE, since = "5.7") - -package org.junit.jupiter.params.aggregator - -import org.apiguardian.api.API - -/** - * Get the value of the argument at the given index as an instance of the - * reified type. - * - * @param index the index of the argument to get; must be greater than or - * equal to zero and less than {@link #size} - * @return the value at the given index, potentially {@code null} - * @since 5.3 - * @receiver[ArgumentsAccessor] - * @see ArgumentsAccessor.get(Int, Class!) - */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // method is in fact not shadowed due to reified type -inline fun ArgumentsAccessor.get(index: Int): T = - this.get(index, T::class.java) diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java deleted file mode 100644 index 7f3ea055..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * JUnit Jupiter extension for parameterized tests. - * - * @since 5.0 - */ -module org.junit.jupiter.params { - requires static transitive org.apiguardian.api; - requires transitive org.junit.jupiter.api; - requires transitive org.junit.platform.commons; - - exports org.junit.jupiter.params; - exports org.junit.jupiter.params.aggregator; - exports org.junit.jupiter.params.converter; - exports org.junit.jupiter.params.provider; - exports org.junit.jupiter.params.support; - - opens org.junit.jupiter.params to org.junit.platform.commons; - opens org.junit.jupiter.params.converter to org.junit.platform.commons; - opens org.junit.jupiter.params.provider to org.junit.platform.commons; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java deleted file mode 100644 index 12f044db..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.ParameterizedTestExtension.arguments; - -import java.io.FileNotFoundException; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.execution.ExtensionValuesStore; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link ParameterizedTestExtension}. - * - * @since 5.0 - */ -class ParameterizedTestExtensionTests { - - private final ParameterizedTestExtension parameterizedTestExtension = new ParameterizedTestExtension(); - - static boolean streamWasClosed = false; - - @Test - void supportsReturnsFalseForMissingTestMethod() { - var extensionContextWithoutTestMethod = getExtensionContextReturningSingleMethod(new TestCaseWithoutMethod()); - assertFalse(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithoutTestMethod)); - } - - @Test - void supportsReturnsFalseForTestMethodWithoutParameterizedTestAnnotation() { - var extensionContextWithUnAnnotatedTestMethod = getExtensionContextReturningSingleMethod( - new TestCaseWithMethod()); - assertFalse(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithUnAnnotatedTestMethod)); - } - - @Test - void supportsReturnsTrueForTestMethodWithParameterizedTestAnnotation() { - var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( - new TestCaseWithAnnotatedMethod()); - assertTrue(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithAnnotatedTestMethod)); - } - - @Test - void streamsReturnedByProvidersAreClosedWhenCallingProvide() { - var extensionContext = getExtensionContextReturningSingleMethod( - new ArgumentsProviderWithCloseHandlerTestCase()); - // we need to call supportsTestTemplate() first, because it creates and - // puts the ParameterizedTestMethodContext into the Store - this.parameterizedTestExtension.supportsTestTemplate(extensionContext); - - var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext); - - assertFalse(streamWasClosed); - // cause the stream to be evaluated - stream.count(); - assertTrue(streamWasClosed); - } - - @Test - void emptyDisplayNameIsIllegal() { - var extensionContext = getExtensionContextReturningSingleMethod(new EmptyDisplayNameProviderTestCase()); - assertThrows(PreconditionViolationException.class, - () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext)); - } - - @Test - void defaultDisplayNameWithEmptyStringInConfigurationIsIllegal() { - AtomicInteger invocations = new AtomicInteger(); - Function> configurationSupplier = key -> { - if (key.equals(ParameterizedTestExtension.DISPLAY_NAME_PATTERN_KEY)) { - invocations.incrementAndGet(); - return Optional.of(""); - } - else { - return Optional.empty(); - } - }; - var extensionContext = getExtensionContextReturningSingleMethod(new DefaultDisplayNameProviderTestCase(), - configurationSupplier); - assertThrows(PreconditionViolationException.class, - () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext)); - assertEquals(1, invocations.get()); - } - - @Test - void argumentsRethrowsOriginalExceptionFromProviderAsUncheckedException() { - ArgumentsProvider failingProvider = (context) -> { - throw new FileNotFoundException("a message"); - }; - - var exception = assertThrows(FileNotFoundException.class, () -> arguments(failingProvider, null)); - assertEquals("a message", exception.getMessage()); - } - - @Test - void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { - var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( - new TestCaseWithAnnotatedMethod()); - - var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( - extensionContextWithAnnotatedTestMethod); - // cause the stream to be evaluated - stream.toArray(); - var exception = assertThrows(JUnitException.class, stream::close); - - assertThat(exception).hasMessage( - "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest"); - } - - @Test - void throwsExceptionWhenArgumentsProviderIsNotStatic() { - var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( - new NonStaticArgumentsProviderTestCase()); - - var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( - extensionContextWithAnnotatedTestMethod); - - var exception = assertThrows(JUnitException.class, stream::toArray); - - assertArgumentsProviderInstantiationException(exception, NonStaticArgumentsProvider.class); - } - - @Test - void throwsExceptionWhenArgumentsProviderDoesNotContainNoArgumentConstructor() { - var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( - new MissingNoArgumentsConstructorArgumentsProviderTestCase()); - - var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( - extensionContextWithAnnotatedTestMethod); - - var exception = assertThrows(JUnitException.class, stream::toArray); - - assertArgumentsProviderInstantiationException(exception, MissingNoArgumentsConstructorArgumentsProvider.class); - } - - private void assertArgumentsProviderInstantiationException(JUnitException exception, Class clazz) { - assertThat(exception).hasMessage( - String.format("Failed to find a no-argument constructor for ArgumentsProvider [%s]. " - + "Please ensure that a no-argument constructor exists and " - + "that the class is either a top-level class or a static nested class", - clazz.getName())); - } - - private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { - return getExtensionContextReturningSingleMethod(testCase, ignored -> Optional.empty()); - } - - private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, - Function> configurationSupplier) { - - // @formatter:off - var optional = Arrays.stream(testCase.getClass().getDeclaredMethods()) - .filter(method -> method.getName().equals("method")) - .findFirst(); - // @formatter:on - - return new ExtensionContext() { - - private final ExtensionValuesStore store = new ExtensionValuesStore(null); - - @Override - public Optional getTestMethod() { - return optional; - } - - @Override - public Optional getParent() { - return Optional.empty(); - } - - @Override - public ExtensionContext getRoot() { - return this; - } - - @Override - public String getUniqueId() { - return null; - } - - @Override - public String getDisplayName() { - return null; - } - - @Override - public Set getTags() { - return null; - } - - @Override - public Optional getElement() { - return Optional.empty(); - } - - @Override - public Optional> getTestClass() { - return Optional.empty(); - } - - @Override - public Optional getTestInstanceLifecycle() { - return Optional.empty(); - } - - @Override - public java.util.Optional getTestInstance() { - return Optional.empty(); - } - - @Override - public Optional getTestInstances() { - return Optional.empty(); - } - - @Override - public Optional getExecutionException() { - return Optional.empty(); - } - - @Override - public Optional getConfigurationParameter(String key) { - return configurationSupplier.apply(key); - } - - @Override - public Optional getConfigurationParameter(String key, Function transformer) { - return configurationSupplier.apply(key).map(transformer); - } - - @Override - public void publishReportEntry(Map map) { - } - - @Override - public Store getStore(Namespace namespace) { - return new NamespaceAwareStore(store, namespace); - } - - @Override - public ExecutionMode getExecutionMode() { - return ExecutionMode.SAME_THREAD; - } - - @Override - public ExecutableInvoker getExecutableInvoker() { - return null; - } - }; - } - - static class TestCaseWithoutMethod { - } - - static class TestCaseWithMethod { - - void method() { - } - } - - static class TestCaseWithAnnotatedMethod { - - @ParameterizedTest - void method() { - } - } - - static class ArgumentsProviderWithCloseHandlerTestCase { - - @ParameterizedTest - @ArgumentsSource(ArgumentsProviderWithCloseHandler.class) - void method(String parameter) { - } - } - - static class ArgumentsProviderWithCloseHandler implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - var argumentsStream = Stream.of("foo", "bar").map(Arguments::of); - return argumentsStream.onClose(() -> streamWasClosed = true); - } - } - - static class NonStaticArgumentsProviderTestCase { - - @ParameterizedTest - @ArgumentsSource(NonStaticArgumentsProvider.class) - void method() { - } - } - - class NonStaticArgumentsProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return null; - } - } - - static class MissingNoArgumentsConstructorArgumentsProviderTestCase { - - @ParameterizedTest - @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) - void method() { - } - } - - static class EmptyDisplayNameProviderTestCase { - - @ParameterizedTest(name = "") - @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) - void method() { - } - } - - static class DefaultDisplayNameProviderTestCase { - - @ParameterizedTest - @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) - void method() { - } - } - - static class MissingNoArgumentsConstructorArgumentsProvider implements ArgumentsProvider { - - MissingNoArgumentsConstructorArgumentsProvider(String parameter) { - } - - @Override - public Stream provideArguments(ExtensionContext context) { - return null; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java deleted file mode 100644 index 24d2a711..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ /dev/null @@ -1,1483 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.within; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.Named.named; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.appendTestTemplateInvocationSegment; -import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; -import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.displayName; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.jupiter.params.aggregator.AggregateWith; -import org.junit.jupiter.params.aggregator.ArgumentsAccessor; -import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; -import org.junit.jupiter.params.aggregator.ArgumentsAggregator; -import org.junit.jupiter.params.converter.ArgumentConversionException; -import org.junit.jupiter.params.converter.ArgumentConverter; -import org.junit.jupiter.params.converter.ConvertWith; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.jupiter.params.provider.CsvFileSource; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.EmptySource; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Event; -import org.opentest4j.TestAbortedException; - -/** - * @since 5.0 - */ -class ParameterizedTestIntegrationTests { - - @ParameterizedTest - @CsvSource(quoteCharacter = '"', textBlock = """ - - - # This is a comment preceded by multiple opening blank lines. - apple, 1 - banana, 2 - # This is a comment pointing out that the next line contains multiple explicit newlines in quoted text. - "lemon \s - - - \s lime", 0xF1 - # The next line is a blank line in the middle of the CSV rows. - - strawberry, 700_000 - # This is a comment followed by 2 closing blank line. - - """) - void executesLinesFromTextBlock(String fruit, int rank) { - switch (fruit) { - case "apple" -> assertThat(rank).isEqualTo(1); - case "banana" -> assertThat(rank).isEqualTo(2); - case "lemon \n\n\n lime" -> assertThat(rank).isEqualTo(241); - case "strawberry" -> assertThat(rank).isEqualTo(700_000); - default -> fail("Unexpected fruit : " + fruit); - } - } - - @ParameterizedTest(name = "[{index}] {arguments}") - @CsvSource(delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL", textBlock = """ - #--------------------------------- - FRUIT | RANK - #--------------------------------- - apple | 1 - #--------------------------------- - banana | 2 - #--------------------------------- - cherry | 3.14159265358979323846 - #--------------------------------- - | 0 - #--------------------------------- - NIL | 0 - #--------------------------------- - """) - void executesLinesFromTextBlockUsingTableFormatAndHeadersAndNullValues(String fruit, double rank, - TestInfo testInfo) { - assertFruitTable(fruit, rank, testInfo); - } - - @ParameterizedTest(name = "[{index}] {arguments}") - @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") - void executesLinesFromClasspathResourceUsingTableFormatAndHeadersAndNullValues(String fruit, double rank, - TestInfo testInfo) { - assertFruitTable(fruit, rank, testInfo); - } - - private void assertFruitTable(String fruit, double rank, TestInfo testInfo) { - String displayName = testInfo.getDisplayName(); - - if (fruit == null) { - assertThat(rank).isEqualTo(0); - assertThat(displayName).matches("\\[(4|5)\\] FRUIT = null, RANK = 0"); - return; - } - - switch (fruit) { - case "apple" -> { - assertThat(rank).isEqualTo(1); - assertThat(displayName).isEqualTo("[1] FRUIT = apple, RANK = 1"); - } - case "banana" -> { - assertThat(rank).isEqualTo(2); - assertThat(displayName).isEqualTo("[2] FRUIT = banana, RANK = 2"); - } - case "cherry" -> { - assertThat(rank).isCloseTo(Math.PI, within(0.0)); - assertThat(displayName).isEqualTo("[3] FRUIT = cherry, RANK = 3.14159265358979323846"); - } - default -> fail("Unexpected fruit : " + fruit); - } - } - - @Test - void executesWithSingleArgumentsProviderWithMultipleInvocations() { - var results = execute("testWithTwoSingleStringArgumentsProvider", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); - } - - @Test - void executesWithCsvSource() { - var results = execute("testWithCsvSource", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); - } - - @Test - void executesWithCustomName() { - var results = execute("testWithCustomName", String.class, int.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("foo and 23"), finishedWithFailure(message("foo, 23")))) // - .haveExactly(1, event(test(), displayName("bar and 42"), finishedWithFailure(message("bar, 42")))); - } - - /** - * @since 5.2 - */ - @Test - void executesWithPrimitiveWideningConversion() { - var results = execute("testWithPrimitiveWideningConversion", double.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] num=1"), finishedWithFailure(message("num: 1.0")))) // - .haveExactly(1, event(test(), displayName("[2] num=2"), finishedWithFailure(message("num: 2.0")))); - } - - /** - * @since 5.1 - */ - @Test - void executesWithImplicitGenericConverter() { - var results = execute("testWithImplicitGenericConverter", Book.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] book=book 1"), finishedWithFailure(message("book 1")))) // - .haveExactly(1, event(test(), displayName("[2] book=book 2"), finishedWithFailure(message("book 2")))); - } - - @Test - void legacyReportingNames() { - var results = execute("testWithCustomName", String.class, int.class); - - // @formatter:off - var legacyReportingNames = results.testEvents().dynamicallyRegistered() - .map(Event::getTestDescriptor) - .map(TestDescriptor::getLegacyReportingName); - // @formatter:on - assertThat(legacyReportingNames).containsExactly("testWithCustomName(String, int)[1]", - "testWithCustomName(String, int)[2]"); - } - - @Test - void executesWithExplicitConverter() { - var results = execute("testWithExplicitConverter", int.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] length=O"), finishedWithFailure(message("length: 1")))) // - .haveExactly(1, - event(test(), displayName("[2] length=XXX"), finishedWithFailure(message("length: 3")))); - } - - @Test - void executesWithAggregator() { - var results = execute("testWithAggregator", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, - event(test(), displayName("[1] ab, cd"), finishedWithFailure(message("concatenation: abcd")))) // - .haveExactly(1, - event(test(), displayName("[2] ef, gh"), finishedWithFailure(message("concatenation: efgh")))); - } - - @Test - void executesWithIgnoreLeadingAndTrailingSetToFalseForCsvSource() { - var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvSource", String.class, - String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: ' ab ', ' cd'")))) // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef ', 'gh'")))); - } - - @Test - void executesWithIgnoreLeadingAndTrailingSetToTrueForCsvSource() { - var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource", String.class, - String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ab', 'cd'")))) // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef', 'gh'")))); - } - - @Test - void executesWithIgnoreLeadingAndTrailingSetToFalseForCsvFileSource() { - var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource", String.class, - String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: ' ab ', ' cd'")))) // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef ', 'gh'")))); - } - - @Test - void executesWithIgnoreLeadingAndTrailingSetToTrueForCsvFileSource() { - var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource", String.class, - String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ab', 'cd'")))) // - .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef', 'gh'")))); - } - - @Test - void failsContainerOnEmptyName() { - var results = execute("testWithEmptyName", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(container(), displayName("testWithEmptyName(String)"), // - finishedWithFailure(message(msg -> msg.contains("must be declared with a non-empty name"))))); - } - - @Test - void reportsExceptionForErroneousConverter() { - var results = execute("testWithErroneousConverter", Object.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), finishedWithFailure(instanceOf(ParameterResolutionException.class), // - message("Error converting parameter at index 0: something went horribly wrong")))); - } - - @Test - void executesLifecycleMethods() { - // reset static collections - LifecycleTestCase.lifecycleEvents.clear(); - LifecycleTestCase.testMethods.clear(); - - var results = execute(selectClass(LifecycleTestCase.class)); - results.allEvents().assertThatEvents() // - .haveExactly(1, - event(test("test1"), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, - event(test("test1"), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); - - List testMethods = new ArrayList<>(LifecycleTestCase.testMethods); - - // @formatter:off - assertThat(LifecycleTestCase.lifecycleEvents).containsExactly( - "beforeAll:ParameterizedTestIntegrationTests$LifecycleTestCase", - "providerMethod", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", - "beforeEach:[1] argument=foo", - testMethods.get(0) + ":[1] argument=foo", - "afterEach:[1] argument=foo", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", - "beforeEach:[2] argument=bar", - testMethods.get(0) + ":[2] argument=bar", - "afterEach:[2] argument=bar", - "providerMethod", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", - "beforeEach:[1] argument=foo", - testMethods.get(1) + ":[1] argument=foo", - "afterEach:[1] argument=foo", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", - "beforeEach:[2] argument=bar", - testMethods.get(1) + ":[2] argument=bar", - "afterEach:[2] argument=bar", - "afterAll:ParameterizedTestIntegrationTests$LifecycleTestCase"); - // @formatter:on - } - - @Test - void truncatesArgumentsThatExceedMaxLength() { - var results = EngineTestKit.engine(new JupiterTestEngine()) // - .configurationParameter(ParameterizedTestExtension.ARGUMENT_MAX_LENGTH_KEY, "2") // - .selectors(selectMethod(TestCase.class, "testWithCsvSource", String.class.getName())) // - .execute(); - results.testEvents().assertThatEvents() // - .haveExactly(1, event(displayName("[1] argument=f…"), started())) // - .haveExactly(1, event(displayName("[2] argument=b…"), started())); - } - - @Test - void displayNamePatternFromConfiguration() { - var results = EngineTestKit.engine(new JupiterTestEngine()) // - .configurationParameter(ParameterizedTestExtension.DISPLAY_NAME_PATTERN_KEY, "{index}") // - .selectors(selectMethod(TestCase.class, "testWithCsvSource", String.class.getName())) // - .execute(); - results.testEvents().assertThatEvents() // - .haveExactly(1, event(displayName("1"), started())) // - .haveExactly(1, event(displayName("2"), started())); - } - - private EngineExecutionResults execute(DiscoverySelector... selectors) { - return EngineTestKit.engine(new JupiterTestEngine()).selectors(selectors).execute(); - } - - private EngineExecutionResults execute(Class testClass, String methodName, Class... methodParameterTypes) { - return execute(selectMethod(testClass, methodName, ClassUtils.nullSafeToString(methodParameterTypes))); - } - - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { - return execute(TestCase.class, methodName, methodParameterTypes); - } - - /** - * @since 5.4 - */ - @Nested - class NullSourceIntegrationTests { - - @Test - void executesWithNullSourceForString() { - var results = execute("testWithNullSourceForString", String.class); - results.testEvents().failed().assertEventsMatchExactly( - event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); - } - - @Test - void executesWithNullSourceForStringAndTestInfo() { - var results = execute("testWithNullSourceForStringAndTestInfo", String.class, TestInfo.class); - results.testEvents().failed().assertEventsMatchExactly( - event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); - } - - @Test - void executesWithNullSourceForNumber() { - var results = execute("testWithNullSourceForNumber", Number.class); - results.testEvents().failed().assertEventsMatchExactly( - event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); - } - - @Test - void failsWithNullSourceWithZeroFormalParameters() { - var methodName = "testWithNullSourceWithZeroFormalParameters"; - execute(methodName).containerEvents().failed().assertEventsMatchExactly(// - event(container(methodName), // - finishedWithFailure(// - instanceOf(PreconditionViolationException.class), // - message(msg -> msg.matches( - "@NullSource cannot provide a null argument to method .+: the method does not declare any formal parameters."))))); - } - - @Test - void failsWithNullSourceForPrimitive() { - var results = execute("testWithNullSourceForPrimitive", int.class); - results.testEvents().failed().assertEventsMatchExactly(event(test(), displayName("[1] argument=null"), - finishedWithFailure(instanceOf(ParameterResolutionException.class), message( - "Error converting parameter at index 0: Cannot convert null to primitive value of type int")))); - } - - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { - return ParameterizedTestIntegrationTests.this.execute(NullSourceTestCase.class, methodName, - methodParameterTypes); - } - - } - - /** - * @since 5.4 - */ - @Nested - class EmptySourceIntegrationTests { - - @Test - void executesWithEmptySourceForString() { - var results = execute("testWithEmptySourceForString", String.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument="))); - } - - @Test - void executesWithEmptySourceForStringAndTestInfo() { - var results = execute("testWithEmptySourceForStringAndTestInfo", String.class, TestInfo.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument="))); - } - - @Test - void executesWithEmptySourceForList() { - var results = execute("testWithEmptySourceForList", List.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); - } - - @Test - void executesWithEmptySourceForSet() { - var results = execute("testWithEmptySourceForSet", Set.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); - } - - @Test - void executesWithEmptySourceForMap() { - var results = execute("testWithEmptySourceForMap", Map.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument={}"))); - } - - @Test - void executesWithEmptySourceForOneDimensionalPrimitiveArray() { - var results = execute("testWithEmptySourceForOneDimensionalPrimitiveArray", int[].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); - } - - @Test - void executesWithEmptySourceForOneDimensionalStringArray() { - var results = execute("testWithEmptySourceForOneDimensionalStringArray", String[].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); - } - - @Test - void executesWithEmptySourceForTwoDimensionalPrimitiveArray() { - var results = execute("testWithEmptySourceForTwoDimensionalPrimitiveArray", int[][].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); - } - - @Test - void executesWithEmptySourceForTwoDimensionalStringArray() { - var results = execute("testWithEmptySourceForTwoDimensionalStringArray", String[][].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); - } - - @Test - void failsWithEmptySourceWithZeroFormalParameters() { - var methodName = "testWithEmptySourceWithZeroFormalParameters"; - execute(methodName).containerEvents().failed().assertEventsMatchExactly(// - event(container(methodName), // - finishedWithFailure(// - instanceOf(PreconditionViolationException.class), // - message(msg -> msg.matches( - "@EmptySource cannot provide an empty argument to method .+: the method does not declare any formal parameters."))))); - } - - @ParameterizedTest(name = "{1}") - @CsvSource({ // - "testWithEmptySourceForPrimitive, int", // - "testWithEmptySourceForUnsupportedReferenceType, java.lang.Integer", // - "testWithEmptySourceForUnsupportedListSubtype, java.util.ArrayList", // - "testWithEmptySourceForUnsupportedSetSubtype, java.util.HashSet", // - "testWithEmptySourceForUnsupportedMapSubtype, java.util.HashMap"// - }) - void failsWithEmptySourceForUnsupportedType(String methodName, Class parameterType) { - execute(methodName, parameterType).containerEvents().failed().assertEventsMatchExactly(// - event(container(methodName), // - finishedWithFailure(// - instanceOf(PreconditionViolationException.class), // - message(msg -> msg.matches("@EmptySource cannot provide an empty argument to method .+: \\[" - + parameterType.getName() + "] is not a supported type."))// - ))); - } - - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { - return ParameterizedTestIntegrationTests.this.execute(EmptySourceTestCase.class, methodName, - methodParameterTypes); - } - - } - - /** - * @since 5.4 - */ - @Nested - class NullAndEmptySourceIntegrationTests { - - @Test - void executesWithNullAndEmptySourceForString() { - var results = execute("testWithNullAndEmptySourceForString", String.class); - assertNullAndEmptyString(results); - } - - @Test - void executesWithNullAndEmptySourceForStringAndTestInfo() { - var results = execute("testWithNullAndEmptySourceForStringAndTestInfo", String.class, TestInfo.class); - assertNullAndEmptyString(results); - } - - @Test - void executesWithNullAndEmptySourceForList() { - var results = execute("testWithNullAndEmptySourceForList", List.class); - assertNullAndEmpty(results); - } - - @Test - void executesWithNullAndEmptySourceForOneDimensionalPrimitiveArray() { - var results = execute("testWithNullAndEmptySourceForOneDimensionalPrimitiveArray", int[].class); - assertNullAndEmpty(results); - } - - @Test - void executesWithNullAndEmptySourceForTwoDimensionalStringArray() { - var results = execute("testWithNullAndEmptySourceForTwoDimensionalStringArray", String[][].class); - assertNullAndEmpty(results); - } - - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { - return ParameterizedTestIntegrationTests.this.execute(NullAndEmptySourceTestCase.class, methodName, - methodParameterTypes); - } - - private void assertNullAndEmptyString(EngineExecutionResults results) { - results.testEvents().succeeded().assertEventsMatchExactly(// - event(test(), displayName("[1] argument=null")), // - event(test(), displayName("[2] argument="))// - ); - } - - private void assertNullAndEmpty(EngineExecutionResults results) { - results.testEvents().succeeded().assertEventsMatchExactly(// - event(test(), displayName("[1] argument=null")), // - event(test(), displayName("[2] argument=[]"))// - ); - } - - } - - @Nested - class MethodSourceIntegrationTests { - - @Test - void emptyMethodSource() { - execute("emptyMethodSource", String.class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("empty method source")))); - } - - /** - * @since 5.3.2 - */ - @Test - void oneDimensionalPrimitiveArray() { - execute("oneDimensionalPrimitiveArray", int.class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("1"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("2")))); - } - - /** - * @since 5.3.2 - */ - @Test - void twoDimensionalPrimitiveArray() { - execute("twoDimensionalPrimitiveArray", int[].class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); - } - - /** - * @since 5.3.2 - */ - @Test - void oneDimensionalObjectArray() { - execute("oneDimensionalObjectArray", Object.class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("2"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("three")))); - } - - /** - * @since 5.3.2 - */ - @Test - void oneDimensionalStringArray() { - execute("oneDimensionalStringArray", String.class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("two")))); - } - - @Test - void twoDimensionalObjectArray() { - execute("twoDimensionalObjectArray", String.class, int.class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); - } - - /** - * @since 5.3.2 - */ - @Test - void twoDimensionalStringArray() { - execute("twoDimensionalStringArray", String.class, String.class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("one:two"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("three:four")))); - } - - /** - * @since 5.3.2 - */ - @Test - void streamOfOneDimensionalPrimitiveArrays() { - execute("streamOfOneDimensionalPrimitiveArrays", int[].class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); - } - - /** - * @since 5.3.2 - */ - @Test - void streamOfTwoDimensionalPrimitiveArrays() { - assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArrays"); - } - - /** - * @since 5.3.2 - */ - @Test - void streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays() { - assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays"); - } - - /** - * @since 5.3.2 - */ - @Test - void streamOfTwoDimensionalPrimitiveArraysWrappedInArguments() { - assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArraysWrappedInArguments"); - } - - private void assertStreamOfTwoDimensionalPrimitiveArrays(String methodName) { - execute(methodName, int[][].class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("[[1, 2], [3, 4]]"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("[[5, 6], [7, 8]]")))); - } - - /** - * @since 5.3.2 - */ - @Test - void streamOfOneDimensionalObjectArrays() { - execute("streamOfOneDimensionalObjectArrays", String.class, int.class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); - } - - /** - * @since 5.3.2 - */ - @Test - void streamOfTwoDimensionalObjectArrays() { - execute("streamOfTwoDimensionalObjectArrays", Object[][].class).testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(message("[[one, 2], [three, 4]]"))))// - .haveExactly(1, event(test(), finishedWithFailure(message("[[five, 6], [seven, 8]]")))); - } - - @Test - void reportsContainerWithAssumptionFailureInMethodSourceAsAborted() { - execute("assumptionFailureInMethodSourceFactoryMethod", String.class).allEvents().assertThatEvents() // - .haveExactly(1, event(container("test-template:assumptionFailureInMethodSourceFactoryMethod"), // - abortedWithReason(instanceOf(TestAbortedException.class), - message("Assumption failed: nothing to test")))); - } - - @Test - void namedParameters() { - execute("namedParameters", String.class).allEvents().assertThatEvents() // - .haveAtLeast(1, - event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) // - .haveAtLeast(1, - event(test(), displayName("default name"), finishedWithFailure(message("default name")))); - } - - @Test - void nameParametersAlias() { - execute("namedParametersAlias", String.class).allEvents().assertThatEvents() // - .haveAtLeast(1, - event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) // - .haveAtLeast(1, - event(test(), displayName("default name"), finishedWithFailure(message("default name")))); - } - - /** - * @since 5.9.1 - * @see https://github.com/junit-team/junit5/issues/3001 - */ - @Test - void duplicateMethodNames() { - // It is sufficient to assert that 8 tests started and finished, because - // without the fix for #3001 the 4 parameterized tests would fail. In - // other words, we're not really testing the support for @RepeatedTest - // and @TestFactory, but their presence also contributes to the bug - // reported in #3001. - ParameterizedTestIntegrationTests.this.execute(selectClass(DuplicateMethodNamesMethodSourceTestCase.class))// - .testEvents()// - .assertStatistics(stats -> stats.started(8).failed(0).finished(8)); - } - - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { - return ParameterizedTestIntegrationTests.this.execute(MethodSourceTestCase.class, methodName, - methodParameterTypes); - } - - } - - @Nested - class UnusedArgumentsIntegrationTests { - - @Test - void executesWithArgumentsSourceProvidingUnusedArguments() { - var results = execute("testWithTwoUnusedStringArgumentsProvider", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); - } - - @Test - void executesWithCsvSourceContainingUnusedArguments() { - var results = execute("testWithCsvSourceContainingUnusedArguments", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); - } - - @Test - void executesWithCsvFileSourceContainingUnusedArguments() { - var results = execute("testWithCsvFileSourceContainingUnusedArguments", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); - } - - @Test - void executesWithMethodSourceProvidingUnusedArguments() { - var results = execute("testWithMethodSourceProvidingUnusedArguments", String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); - } - - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { - return ParameterizedTestIntegrationTests.this.execute(UnusedArgumentsTestCase.class, methodName, - methodParameterTypes); - } - - } - - @Test - void closeAutoCloseableArgumentsAfterTest() { - var results = execute("testWithAutoCloseableArgument", AutoCloseableArgument.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), finishedSuccessfully())); - - assertTrue(AutoCloseableArgument.isClosed); - } - - @Test - void executesTwoIterationsBasedOnIterationAndUniqueIdSelector() { - var methodId = uniqueIdForTestTemplateMethod(TestCase.class, "testWithThreeIterations(int)"); - var results = execute(selectUniqueId(appendTestTemplateInvocationSegment(methodId, 3)), - selectIteration(selectMethod(TestCase.class, "testWithThreeIterations", "int"), 1)); - - results.allEvents().assertThatEvents() // - .haveExactly(2, event(test(), finishedWithFailure())) // - .haveExactly(1, event(test(), displayName("[2] argument=3"), finishedWithFailure())) // - .haveExactly(1, event(test(), displayName("[3] argument=5"), finishedWithFailure())); - } - - // ------------------------------------------------------------------------- - - static class TestCase { - - @ParameterizedTest - @ArgumentsSource(TwoSingleStringArgumentsProvider.class) - void testWithTwoSingleStringArgumentsProvider(String argument) { - fail(argument); - } - - @ParameterizedTest - @CsvSource({ "foo", "bar" }) - void testWithCsvSource(String argument) { - fail(argument); - } - - @ParameterizedTest(name = "{0} and {1}") - @CsvSource({ "foo, 23", "bar, 42" }) - void testWithCustomName(String argument, int i) { - fail(argument + ", " + i); - } - - @ParameterizedTest - @ValueSource(shorts = { 1, 2 }) - void testWithPrimitiveWideningConversion(double num) { - fail("num: " + num); - } - - @ParameterizedTest - @ValueSource(strings = { "book 1", "book 2" }) - void testWithImplicitGenericConverter(Book book) { - fail(book.title); - } - - @ParameterizedTest - @ValueSource(strings = { "O", "XXX" }) - void testWithExplicitConverter(@ConvertWith(StringLengthConverter.class) int length) { - fail("length: " + length); - } - - @ParameterizedTest(name = " \t ") - @ValueSource(strings = "not important") - void testWithEmptyName(String argument) { - fail(argument); - } - - @ParameterizedTest - @ValueSource(ints = 42) - void testWithErroneousConverter(@ConvertWith(ErroneousConverter.class) Object ignored) { - fail("this should never be called"); - } - - @ParameterizedTest - @CsvSource({ "ab, cd", "ef, gh" }) - void testWithAggregator(@AggregateWith(StringAggregator.class) String concatenation) { - fail("concatenation: " + concatenation); - } - - @ParameterizedTest - @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = false) - void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvSource(String argument1, String argument2) { - fail("arguments: '" + argument1 + "', '" + argument2 + "'"); - } - - @ParameterizedTest - @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = true) - void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource(String argument1, String argument2) { - fail("arguments: '" + argument1 + "', '" + argument2 + "'"); - } - - @ParameterizedTest - @CsvFileSource(resources = "/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = false) - void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource(String argument1, String argument2) { - fail("arguments: '" + argument1 + "', '" + argument2 + "'"); - } - - @ParameterizedTest - @CsvFileSource(resources = "/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = true) - void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource(String argument1, String argument2) { - fail("arguments: '" + argument1 + "', '" + argument2 + "'"); - } - - @ParameterizedTest - @ArgumentsSource(AutoCloseableArgumentProvider.class) - void testWithAutoCloseableArgument(AutoCloseableArgument autoCloseable) { - assertFalse(AutoCloseableArgument.isClosed); - } - - @ParameterizedTest - @ValueSource(ints = { 2, 3, 5 }) - void testWithThreeIterations(int argument) { - fail("argument: " + argument); - } - } - - static class NullSourceTestCase { - - @ParameterizedTest - @NullSource - void testWithNullSourceForString(String argument) { - fail(String.valueOf(argument)); - } - - @ParameterizedTest - @NullSource - void testWithNullSourceForStringAndTestInfo(String argument, TestInfo testInfo) { - assertThat(testInfo).isNotNull(); - fail(String.valueOf(argument)); - } - - @ParameterizedTest - @NullSource - void testWithNullSourceForNumber(Number argument) { - fail(String.valueOf(argument)); - } - - @ParameterizedTest - @NullSource - void testWithNullSourceWithZeroFormalParameters() { - fail("should not have been executed"); - } - - @ParameterizedTest - @NullSource - void testWithNullSourceForPrimitive(int argument) { - fail("should not have been executed"); - } - - } - - static class EmptySourceTestCase { - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForString(String argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForStringAndTestInfo(String argument, TestInfo testInfo) { - assertThat(argument).isEmpty(); - assertThat(testInfo).isNotNull(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForList(List argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForSet(Set argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForMap(Map argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForOneDimensionalPrimitiveArray(int[] argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForOneDimensionalStringArray(String[] argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForTwoDimensionalPrimitiveArray(int[][] argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForTwoDimensionalStringArray(String[][] argument) { - assertThat(argument).isEmpty(); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceWithZeroFormalParameters() { - fail("should not have been executed"); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForPrimitive(int argument) { - fail("should not have been executed"); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForUnsupportedReferenceType(Integer argument) { - fail("should not have been executed"); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForUnsupportedListSubtype(ArrayList argument) { - fail("should not have been executed"); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForUnsupportedSetSubtype(HashSet argument) { - fail("should not have been executed"); - } - - @ParameterizedTest - @EmptySource - void testWithEmptySourceForUnsupportedMapSubtype(HashMap argument) { - fail("should not have been executed"); - } - - } - - static class NullAndEmptySourceTestCase { - - @ParameterizedTest - @NullAndEmptySource - void testWithNullAndEmptySourceForString(String argument) { - assertTrue(argument == null || argument.isEmpty()); - } - - @ParameterizedTest - @NullAndEmptySource - void testWithNullAndEmptySourceForStringAndTestInfo(String argument, TestInfo testInfo) { - assertTrue(argument == null || argument.isEmpty()); - assertThat(testInfo).isNotNull(); - } - - @ParameterizedTest - @NullAndEmptySource - void testWithNullAndEmptySourceForList(List argument) { - assertTrue(argument == null || argument.isEmpty()); - } - - @ParameterizedTest - @NullAndEmptySource - void testWithNullAndEmptySourceForOneDimensionalPrimitiveArray(int[] argument) { - assertTrue(argument == null || argument.length == 0); - } - - @ParameterizedTest - @NullAndEmptySource - void testWithNullAndEmptySourceForTwoDimensionalStringArray(String[][] argument) { - assertTrue(argument == null || argument.length == 0); - } - - } - - @TestMethodOrder(OrderAnnotation.class) - static class MethodSourceTestCase { - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @ParameterizedTest(name = "{arguments}") - @MethodSource - @interface MethodSourceTest { - } - - @MethodSourceTest - void emptyMethodSource(String argument) { - fail(argument); - } - - @MethodSourceTest - @Order(1) - void oneDimensionalPrimitiveArray(int x) { - fail("" + x); - } - - @MethodSourceTest - @Order(2) - void twoDimensionalPrimitiveArray(int[] array) { - fail(Arrays.toString(array)); - } - - @MethodSourceTest - @Order(3) - void oneDimensionalObjectArray(Object o) { - fail("" + o); - } - - @MethodSourceTest - @Order(4) - void oneDimensionalStringArray(String s) { - fail(s); - } - - @MethodSourceTest - @Order(5) - void twoDimensionalObjectArray(String s, int x) { - fail(s + ":" + x); - } - - @MethodSourceTest - @Order(6) - void twoDimensionalStringArray(String s1, String s2) { - fail(s1 + ":" + s2); - } - - @MethodSourceTest - @Order(7) - void streamOfOneDimensionalPrimitiveArrays(int[] array) { - fail(Arrays.toString(array)); - } - - @MethodSourceTest - @Order(8) - void streamOfTwoDimensionalPrimitiveArrays(int[][] array) { - fail(Arrays.deepToString(array)); - } - - @MethodSourceTest - @Order(9) - void streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays(int[][] array) { - fail(Arrays.deepToString(array)); - } - - @MethodSourceTest - @Order(10) - void streamOfTwoDimensionalPrimitiveArraysWrappedInArguments(int[][] array) { - fail(Arrays.deepToString(array)); - } - - @MethodSourceTest - @Order(11) - void streamOfOneDimensionalObjectArrays(String s, int x) { - fail(s + ":" + x); - } - - @MethodSourceTest - @Order(12) - void streamOfTwoDimensionalObjectArrays(Object[][] array) { - fail(Arrays.deepToString(array)); - } - - @MethodSourceTest - @Order(13) - void namedParameters(String string) { - fail(string); - } - - @MethodSourceTest - @Order(14) - void namedParametersAlias(String string) { - fail(string); - } - - // --------------------------------------------------------------------- - - static Stream emptyMethodSource() { - return Stream.of(arguments("empty method source")); - } - - static int[] oneDimensionalPrimitiveArray() { - return new int[] { 1, 2 }; - } - - static int[][] twoDimensionalPrimitiveArray() { - return new int[][] { { 1, 2 }, { 3, 4 } }; - } - - static Object[] oneDimensionalObjectArray() { - return new Object[] { "one", 2, "three" }; - } - - static Object[] oneDimensionalStringArray() { - return new Object[] { "one", "two" }; - } - - static Object[][] twoDimensionalObjectArray() { - return new Object[][] { { "one", 2 }, { "three", 4 } }; - } - - static String[][] twoDimensionalStringArray() { - return new String[][] { { "one", "two" }, { "three", "four" } }; - } - - static Stream streamOfOneDimensionalPrimitiveArrays() { - return Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); - } - - static Stream streamOfTwoDimensionalPrimitiveArrays() { - return Stream.of(new int[][] { { 1, 2 }, { 3, 4 } }, new int[][] { { 5, 6 }, { 7, 8 } }); - } - - static Stream streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays() { - return Stream.of(new Object[] { new int[][] { { 1, 2 }, { 3, 4 } } }, - new Object[] { new int[][] { { 5, 6 }, { 7, 8 } } }); - } - - static Stream streamOfTwoDimensionalPrimitiveArraysWrappedInArguments() { - return Stream.of(arguments((Object) new int[][] { { 1, 2 }, { 3, 4 } }), - arguments((Object) new int[][] { { 5, 6 }, { 7, 8 } })); - } - - static Stream streamOfOneDimensionalObjectArrays() { - return Stream.of(new Object[] { "one", 2 }, new Object[] { "three", 4 }); - } - - static Stream streamOfTwoDimensionalObjectArrays() { - return Stream.of(new Object[][] { { "one", 2 }, { "three", 4 } }, - new Object[][] { { "five", 6 }, { "seven", 8 } }); - } - - static Stream namedParameters() { - return Stream.of(arguments(Named.of("cool name", "parameter value")), arguments("default name")); - } - - static Stream namedParametersAlias() { - return Stream.of(arguments(named("cool name", "parameter value")), arguments("default name")); - } - - // --------------------------------------------------------------------- - - @MethodSourceTest - void assumptionFailureInMethodSourceFactoryMethod(String test) { - } - - static List assumptionFailureInMethodSourceFactoryMethod() { - Assumptions.assumeFalse(true, "nothing to test"); - return null; - } - - } - - /** - * @since 5.9.1 - * @see https://github.com/junit-team/junit5/issues/3001 - */ - static class DuplicateMethodNamesMethodSourceTestCase { - - @ParameterizedTest - @MethodSource - void test(String value) { - test(1, value); - } - - @ParameterizedTest - @MethodSource("test") - void anotherTest(String value) { - assertTrue(test(value, 1)); - } - - @RepeatedTest(2) - void test(TestReporter testReporter) { - assertNotNull(testReporter); - } - - @TestFactory - Stream test(TestInfo testInfo) { - return test().map(value -> dynamicTest(value, () -> test(1, value))); - } - - // neither a test method nor a factory method. - // intentionally void. - private void test(int expectedLength, String value) { - assertEquals(expectedLength, value.length()); - } - - // neither a test method nor a factory method. - // intentionally non-void and also not convertible to a Stream. - private boolean test(String value, int expectedLength) { - return (value.length() == expectedLength); - } - - // legitimate factory method. - private static Stream test() { - return Stream.of("a", "b"); - } - - } - - static class UnusedArgumentsTestCase { - - @ParameterizedTest - @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) - void testWithTwoUnusedStringArgumentsProvider(String argument) { - fail(argument); - } - - @ParameterizedTest - @CsvSource({ "foo, unused1", "bar, unused2" }) - void testWithCsvSourceContainingUnusedArguments(String argument) { - fail(argument); - } - - @ParameterizedTest - @CsvFileSource(resources = "two-column.csv") - void testWithCsvFileSourceContainingUnusedArguments(String argument) { - fail(argument); - } - - @ParameterizedTest - @MethodSource("unusedArgumentsProviderMethod") - void testWithMethodSourceProvidingUnusedArguments(String argument) { - fail(argument); - } - - static Stream unusedArgumentsProviderMethod() { - return Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); - } - - } - - static class LifecycleTestCase { - - private static final List lifecycleEvents = new ArrayList<>(); - private static final Set testMethods = new LinkedHashSet<>(); - - public LifecycleTestCase(TestInfo testInfo) { - lifecycleEvents.add("constructor:" + testInfo.getDisplayName()); - } - - @BeforeAll - static void beforeAll(TestInfo testInfo) { - lifecycleEvents.add("beforeAll:" + testInfo.getDisplayName()); - } - - @AfterAll - static void afterAll(TestInfo testInfo) { - lifecycleEvents.add("afterAll:" + testInfo.getDisplayName()); - } - - @BeforeEach - void beforeEach(TestInfo testInfo) { - lifecycleEvents.add("beforeEach:" + testInfo.getDisplayName()); - } - - @AfterEach - void afterEach(TestInfo testInfo) { - lifecycleEvents.add("afterEach:" + testInfo.getDisplayName()); - } - - @ParameterizedTest - @MethodSource("providerMethod") - void test1(String argument, TestInfo testInfo) { - performTest(argument, testInfo); - } - - @ParameterizedTest - @MethodSource("providerMethod") - void test2(String argument, TestInfo testInfo) { - performTest(argument, testInfo); - } - - private void performTest(String argument, TestInfo testInfo) { - var testMethod = testInfo.getTestMethod().orElseThrow().getName(); - testMethods.add(testMethod); - lifecycleEvents.add(testMethod + ":" + testInfo.getDisplayName()); - fail(argument); - } - - static Stream providerMethod() { - lifecycleEvents.add("providerMethod"); - return Stream.of("foo", "bar"); - } - - } - - private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of(arguments("foo"), arguments("bar")); - } - } - - private static class TwoUnusedStringArgumentsProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); - } - } - - private static class StringLengthConverter implements ArgumentConverter { - - @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { - return String.valueOf(source).length(); - } - } - - private static class StringAggregator implements ArgumentsAggregator { - - @Override - public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) - throws ArgumentsAggregationException { - return accessor.getString(0) + accessor.getString(1); - } - } - - private static class ErroneousConverter implements ArgumentConverter { - - @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { - throw new ArgumentConversionException("something went horribly wrong"); - } - } - - private static class AutoCloseableArgumentProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of(arguments(new AutoCloseableArgument())); - } - } - - static class AutoCloseableArgument implements AutoCloseable { - - static boolean isClosed = false; - - @Override - public void close() { - isClosed = true; - } - } - - static class Book { - - private final String title; - - private Book(String title) { - this.title = title; - } - - static Book factory(String title) { - return new Book(title); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java deleted file mode 100644 index 038c3502..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.reflect.Method; -import java.util.Arrays; - -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.CsvToPerson; -import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.Person; -import org.junit.jupiter.params.aggregator.ArgumentsAccessor; -import org.junit.jupiter.params.provider.ValueSource; - -/** - * Unit tests for {@link ParameterizedTestMethodContext}. - * - * @since 5.2 - */ -class ParameterizedTestMethodContextTests { - - @ParameterizedTest - @ValueSource(strings = { "onePrimitive", "twoPrimitives", "twoAggregators", "twoAggregatorsWithTestInfoAtTheEnd", - "mixedMode" }) - void validSignatures(String name) { - assertTrue(new ParameterizedTestMethodContext(method(name)).hasPotentiallyValidSignature()); - } - - @ParameterizedTest - @ValueSource(strings = { "twoAggregatorsWithPrimitiveInTheMiddle", "twoAggregatorsWithTestInfoInTheMiddle" }) - void invalidSignatures(String name) { - assertFalse(new ParameterizedTestMethodContext(method(name)).hasPotentiallyValidSignature()); - } - - private Method method(String name) { - return Arrays.stream(getClass().getDeclaredMethods()) // - .filter(m -> m.getName().equals(name)) // - .findFirst() // - .orElseThrow(); - } - - // --- VALID --------------------------------------------------------------- - - void onePrimitive(int num) { - } - - void twoPrimitives(int num1, int num2) { - } - - void twoAggregators(@CsvToPerson Person person, ArgumentsAccessor arguments) { - } - - void twoAggregatorsWithTestInfoAtTheEnd(@CsvToPerson Person person1, @CsvToPerson Person person2, - TestInfo testInfo) { - } - - void mixedMode(int num1, int num2, ArgumentsAccessor arguments1, ArgumentsAccessor arguments2, - @CsvToPerson Person person1, @CsvToPerson Person person2, TestInfo testInfo1, TestInfo testInfo2) { - } - - // --- INVALID ------------------------------------------------------------- - - void twoAggregatorsWithPrimitiveInTheMiddle(@CsvToPerson Person person1, int num, @CsvToPerson Person person2) { - } - - void twoAggregatorsWithTestInfoInTheMiddle(@CsvToPerson Person person1, TestInfo testInfo, - @CsvToPerson Person person2) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java deleted file mode 100644 index 1cbabc82..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_PLACEHOLDER; -import static org.junit.jupiter.params.ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER; -import static org.junit.jupiter.params.ParameterizedTest.DISPLAY_NAME_PLACEHOLDER; -import static org.junit.jupiter.params.ParameterizedTest.INDEX_PLACEHOLDER; -import static org.mockito.Mockito.mock; - -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.sql.Date; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.ZoneId; -import java.util.Arrays; -import java.util.Locale; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.params.aggregator.AggregateWith; -import org.junit.jupiter.params.aggregator.ArgumentsAccessor; -import org.junit.jupiter.params.aggregator.ArgumentsAggregator; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.0 - */ -class ParameterizedTestNameFormatterTests { - - private final Locale originalLocale = Locale.getDefault(); - - @AfterEach - void restoreLocale() { - Locale.setDefault(originalLocale); - } - - @Test - void formatsDisplayName() { - var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, "enigma"); - - assertEquals("enigma", formatter.format(1)); - assertEquals("enigma", formatter.format(2)); - } - - @Test - void formatsDisplayNameContainingApostrophe() { - String displayName = "display'Zero"; - var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, "display'Zero"); - - assertEquals(displayName, formatter.format(1)); - assertEquals(displayName, formatter.format(2)); - } - - @Test - void formatsDisplayNameContainingFormatElements() { - String displayName = "{enigma} {0} '{1}'"; - var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, displayName); - - assertEquals(displayName, formatter.format(1)); - assertEquals(displayName, formatter.format(2)); - } - - @Test - void formatsInvocationIndex() { - var formatter = formatter(INDEX_PLACEHOLDER, "enigma"); - - assertEquals("1", formatter.format(1)); - assertEquals("2", formatter.format(2)); - } - - @Test - void formatsIndividualArguments() { - var formatter = formatter("{0} -> {1}", "enigma"); - - assertEquals("foo -> 42", formatter.format(1, "foo", 42)); - } - - @Test - void formatsCompleteArgumentsList() { - var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); - - // @formatter:off - assertEquals("42, 99, enigma, null, [1, 2, 3], [foo, bar], [[2, 4], [3, 9]]", - formatter.format(1, - 42, - 99, - "enigma", - null, - new int[] { 1, 2, 3 }, - new String[] { "foo", "bar" }, - new Integer[][] { { 2, 4 }, { 3, 9 } } - )); - // @formatter:on - } - - @Test - void formatsCompleteArgumentsListWithNames() { - var testMethod = ParameterizedTestCases.getMethod("parameterizedTest", int.class, String.class, Object[].class); - var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); - - var formattedName = formatter.format(1, 42, "enigma", new Object[] { "foo", 1 }); - assertEquals("someNumber=42, someString=enigma, someArray=[foo, 1]", formattedName); - } - - @Test - void formatsCompleteArgumentsListWithoutNamesForAggregators() { - var testMethod = ParameterizedTestCases.getMethod("parameterizedTestWithAggregator", int.class, String.class); - var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); - - var formattedName = formatter.format(1, 42, "foo", "bar"); - assertEquals("someNumber=42, foo, bar", formattedName); - } - - @Test - void formatsCompleteArgumentsListWithArrays() { - var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); - - // Explicit test for https://github.com/junit-team/junit5/issues/814 - assertEquals("[foo, bar]", formatter.format(1, (Object) new String[] { "foo", "bar" })); - - assertEquals("[foo, bar], 42, true", formatter.format(1, new String[] { "foo", "bar" }, 42, true)); - } - - @Test - void formatsEverythingUsingCustomPattern() { - var pattern = DISPLAY_NAME_PLACEHOLDER + " " + INDEX_PLACEHOLDER + " :: " + ARGUMENTS_PLACEHOLDER + " :: {1}"; - var formatter = formatter(pattern, "enigma"); - - assertEquals("enigma 1 :: foo, bar :: bar", formatter.format(1, "foo", "bar")); - assertEquals("enigma 2 :: foo, 42 :: 42", formatter.format(2, "foo", 42)); - } - - @Test - void formatDoesNotAlterArgumentsArray() { - var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); - Object[] actual = { 1, "two", Byte.valueOf("-128"), new Integer[][] { { 2, 4 }, { 3, 9 } } }; - var expected = Arrays.copyOf(actual, actual.length); - assertEquals("1, two, -128, [[2, 4], [3, 9]]", formatter.format(1, actual)); - assertArrayEquals(expected, actual); - } - - @Test - void formatDoesNotRaiseAnArrayStoreException() { - var formatter = formatter("{0} -> {1}", "enigma"); - - Object[] arguments = new Number[] { 1, 2 }; - assertEquals("1 -> 2", formatter.format(1, arguments)); - } - - @Test - void throwsReadableExceptionForInvalidPattern() { - var formatter = formatter("{index", "enigma"); - - var exception = assertThrows(JUnitException.class, () -> formatter.format(1)); - assertNotNull(exception.getCause()); - assertEquals(IllegalArgumentException.class, exception.getCause().getClass()); - } - - @Test - void formattingDoesNotFailIfArgumentToStringImplementationReturnsNull() { - var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); - - var formattedName = formatter.format(1, new ToStringReturnsNull(), "foo"); - - assertThat(formattedName).isEqualTo("null, foo"); - } - - @Test - void formattingDoesNotFailIfArgumentToStringImplementationThrowsAnException() { - var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); - - var formattedName = formatter.format(1, new ToStringThrowsException(), "foo"); - - assertThat(formattedName).startsWith(ToStringThrowsException.class.getName() + "@"); - assertThat(formattedName).endsWith("foo"); - } - - @ParameterizedTest(name = "{0}") - @CsvSource(delimiter = '|', value = { "US | 42.23 is positive on 2019 Jan 13 at 12:34:56", - "DE | 42,23 is positive on 13.01.2019 at 12:34:56" }) - void customFormattingExpressionsAreSupported(Locale locale, String expectedValue) { - var pattern = "[{index}] {1,number,#.##} is {1,choice,0... parameterTypes) { - return ReflectionUtils.findMethod(ParameterizedTestCases.class, methodName, parameterTypes).orElseThrow(); - } - - @SuppressWarnings("unused") - void parameterizedTest(int someNumber, String someString, Object[] someArray) { - } - - @SuppressWarnings("unused") - void parameterizedTestWithAggregator(int someNumber, - @AggregateWith(CustomAggregator.class) String someAggregatedString) { - } - - private static class CustomAggregator implements ArgumentsAggregator { - @Override - public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) { - return accessor.get(0); - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java deleted file mode 100644 index dd3a68eb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; - -/** - * Test suite for JUnit Jupiter parameterized test support. - * - *

Logging Configuration

- * - *

In order for our log4j2 configuration to be used in an IDE, you must - * set the following system property before running any tests — for - * example, in Run Configurations in Eclipse. - * - *

- * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
- * 
- * - * @since 5.0 - */ -@Suite -@SelectPackages("org.junit.jupiter.params") -@IncludeClassNamePatterns(".*Tests?") -@IncludeEngines("junit-jupiter") -class ParameterizedTestSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java deleted file mode 100644 index 97d814f6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.converter.ArgumentConversionException; -import org.junit.jupiter.params.converter.ArgumentConverter; -import org.junit.jupiter.params.converter.ConvertWith; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.EngineTestKit; - -/** - * Integration tests for {@link ArgumentsAccessor}, {@link AggregateWith}, - * and {@link ArgumentsAggregator}. - * - * @since 5.2 - */ -public class AggregatorIntegrationTests { - - @ParameterizedTest - @CsvSource({ // - "Jane, Doe, 1980-04-16, F, red", // - "Jack, Smith, 2000-11-22, M, blue" // - }) - void personAggregator(@AggregateWith(PersonAggregator.class) Person person) { - testPersonAggregator(person); - } - - @ParameterizedTest - @CsvSource({ // - "Jane, Doe, 1980-04-16, F, red", // - "Jack, Smith, 2000-11-22, M, blue" // - }) - void personAggregatorRegisteredViaCustomAnnotation(@CsvToPerson Person person) { - testPersonAggregator(person); - } - - @ParameterizedTest - @CsvSource({ // - "42 Peachtree Street, Atlanta, 30318", // - "99 Peachtree Road, Atlanta, 30318"// - }) - void addressAggregator(@CsvToAddress Address address) { - testAddressAggregator(address); - } - - @ParameterizedTest - @CsvSource({ // - "Jane, Doe, 1980-04-16, F, 42 Peachtree Street, Atlanta, 30318, red", // - "Jack, Smith, 2000-11-22, M, 99 Peachtree Road, Atlanta, 30318, blue"// - }) - void personAggregatorAndAddressAggregator(@CsvToPerson Person person, - @CsvToAddress @StartIndex(4) Address address) { - - testPersonAggregator(person); - testAddressAggregator(address); - } - - @ParameterizedTest(name = "Mixed Mode #1: {arguments}") - @CsvSource({ // - "gh-11111111, Jane, Doe, 1980-04-16, F, 42 Peachtree Street, Atlanta, 30318, red", // - "gh-22222222, Jack, Smith, 2000-11-22, M, 99 Peachtree Road, Atlanta, 30318, blue"// - }) - void mixedMode(String issueNumber, @CsvToPerson @StartIndex(1) Person person, - @CsvToAddress @StartIndex(5) Address address, TestInfo testInfo) { - - assertThat(issueNumber).startsWith("gh-"); - testPersonAggregator(person); - testAddressAggregator(address); - assertThat(testInfo.getDisplayName()).startsWith("Mixed Mode #1"); - } - - @ParameterizedTest - @CsvSource({ "cat, bird, mouse", "mouse, cat, bird", "mouse, bird, cat" }) - void mapAggregator(@AggregateWith(MapAggregator.class) Map map) { - assertThat(map).containsOnly(entry("cat", 3), entry("bird", 4), entry("mouse", 5)); - } - - @ParameterizedTest - @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) - void argumentsAccessor(ArgumentsAccessor arguments) { - assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); - } - - @ParameterizedTest(name = "2 ArgumentsAccessors: {arguments}") - @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) - void argumentsAccessors(ArgumentsAccessor arguments1, ArgumentsAccessor arguments2) { - assertArrayEquals(arguments1.toArray(), arguments2.toArray()); - } - - @ParameterizedTest(name = "ArgumentsAccessor and TestInfo: {arguments}") - @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) - void argumentsAccessorAndTestInfo(ArgumentsAccessor arguments, TestInfo testInfo) { - assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); - assertThat(testInfo.getDisplayName()).startsWith("ArgumentsAccessor and TestInfo"); - } - - @ParameterizedTest(name = "Indexed Arguments and ArgumentsAccessor: {arguments}") - @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) - void indexedArgumentsAndArgumentsAccessor(int num1, int num2, ArgumentsAccessor arguments) { - assertEquals(1, num1); - assertEquals(2, num2); - assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); - } - - @ParameterizedTest(name = "Indexed Arguments, ArgumentsAccessor, and TestInfo: {arguments}") - @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) - void indexedArgumentsArgumentsAccessorAndTestInfo(int num1, int num2, ArgumentsAccessor arguments, - TestInfo testInfo) { - - assertEquals(1, num1); - assertEquals(2, num2); - assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); - assertThat(testInfo.getDisplayName()).startsWith("Indexed Arguments, ArgumentsAccessor, and TestInfo"); - } - - @ParameterizedTest(name = "Indexed Arguments, 2 ArgumentsAccessors, and TestInfo: {arguments}") - @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) - void indexedArgumentsArgumentsAccessorsAndTestInfo(int num1, int num2, ArgumentsAccessor arguments1, - ArgumentsAccessor arguments2, TestInfo testInfo) { - - assertEquals(1, num1); - assertEquals(2, num2); - assertArrayEquals(arguments1.toArray(), arguments2.toArray()); - assertEquals(55, IntStream.range(0, arguments1.size()).map(arguments1::getInteger).sum()); - assertThat(testInfo.getDisplayName()).startsWith("Indexed Arguments, 2 ArgumentsAccessors, and TestInfo"); - } - - @ParameterizedTest - @CsvSource({ "foo, bar" }) - void nullAggregator(@AggregateWith(NullAggregator.class) Person person) { - assertNull(person); - } - - @Test - void reportsExceptionForErroneousAggregator() { - var results = execute( - selectMethod(ErroneousTestCases.class, "testWithErroneousAggregator", Object.class.getName())); - - results.testEvents().assertThatEvents()// - .haveExactly(1, event(test(), finishedWithFailure(instanceOf(ParameterResolutionException.class), // - message("Error aggregating arguments for parameter at index 0: something went horribly wrong")))); - } - - private void testPersonAggregator(Person person) { - if (person.firstName.equals("Jane")) { - assertEquals("Jane Doe", person.getFullName()); - assertEquals(1980, person.dateOfBirth.getYear()); - assertEquals(Gender.F, person.gender); - } - - if (person.firstName.equals("Jack")) { - assertEquals("Jack Smith", person.getFullName()); - assertEquals(2000, person.dateOfBirth.getYear()); - assertEquals(Gender.M, person.gender); - } - } - - private void testAddressAggregator(Address address) { - assertThat(address.street).contains("Peachtree"); - assertEquals("Atlanta", address.city); - assertEquals(30318, address.zipCode); - } - - private EngineExecutionResults execute(DiscoverySelector... selectors) { - return EngineTestKit.execute("junit-jupiter", request().selectors(selectors).build()); - } - - // ------------------------------------------------------------------------- - - public static class Person { - - final String firstName; - final String lastName; - final Gender gender; - final LocalDate dateOfBirth; - - Person(String firstName, String lastName, LocalDate dateOfBirth, Gender gender) { - this.firstName = firstName; - this.lastName = lastName; - this.gender = gender; - this.dateOfBirth = dateOfBirth; - } - - String getFullName() { - return this.firstName + " " + this.lastName; - } - } - - enum Gender { - F, M - } - - static class Address { - - final String street; - final String city; - final int zipCode; - - Address(String street, String city, int zipCode) { - this.street = street; - this.city = city; - this.zipCode = zipCode; - } - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @interface StartIndex { - int value(); - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @AggregateWith(PersonAggregator.class) - public @interface CsvToPerson { - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @AggregateWith(AddressAggregator.class) - @interface CsvToAddress { - } - - static class PersonAggregator implements ArgumentsAggregator { - - @Override - public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { - int startIndex = context.findAnnotation(StartIndex.class).map(StartIndex::value).orElse(0); - - // @formatter:off - return new Person( - arguments.getString(startIndex + 0), - arguments.getString(startIndex + 1), - arguments.get(startIndex + 2, LocalDate.class), - arguments.get(startIndex + 3, Gender.class) - ); - // @formatter:on - } - } - - static class AddressAggregator implements ArgumentsAggregator { - - @Override - public Address aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { - int startIndex = context.findAnnotation(StartIndex.class).map(StartIndex::value).orElse(0); - - // @formatter:off - return new Address( - arguments.getString(startIndex + 0), - arguments.getString(startIndex + 1), - arguments.getInteger(startIndex + 2) - ); - // @formatter:on - } - } - - /** - * Maps from String to length of String. - */ - static class MapAggregator implements ArgumentsAggregator { - - @Override - public Map aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { - // @formatter:off - return IntStream.range(0, arguments.size()) - .mapToObj(arguments::getString) - .collect(toMap(s -> s, String::length)); - // @formatter:on - } - } - - static class NullAggregator implements ArgumentsAggregator { - @Override - public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) { - Preconditions.condition(!context.getParameter().getType().isPrimitive(), - () -> "only supports reference types"); - return null; - } - } - - static class ErroneousAggregator implements ArgumentsAggregator { - @Override - public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) - throws ArgumentsAggregationException { - throw new ArgumentsAggregationException("something went horribly wrong"); - } - } - - static class ErroneousTestCases { - @ParameterizedTest - @ValueSource(ints = 42) - void testWithErroneousAggregator(@AggregateWith(ErroneousAggregator.class) Object ignored) { - fail("this should never be called"); - } - } - - @Test - @ResourceLock("InstanceCountingConverter.instanceCount") - void aggregatorIsInstantiatedOnlyOnce() { - InstanceCountingAggregator.instanceCount = 0; - CountingTestCase.output.clear(); - - execute(selectMethod(CountingTestCase.class, "testWithCountingConverterAggregator", - int.class.getName() + "," + Object.class.getName())); - - assertThat(InstanceCountingAggregator.instanceCount).isEqualTo(1); - assertThat(CountingTestCase.output)// - .containsExactly("noisy test(1, enigma)", "noisy test(2, enigma)", "noisy test(3, enigma)"); - } - - @Test - @ResourceLock("InstanceCountingConverter.instanceCount") - void converterIsInstantiatedOnlyOnce() { - InstanceCountingConverter.instanceCount = 0; - CountingTestCase.output.clear(); - - execute(selectMethod(CountingTestCase.class, "testWithCountingConverterAggregator", - int.class.getName() + "," + Object.class.getName())); - - assertThat(InstanceCountingConverter.instanceCount).isEqualTo(1); - assertThat(CountingTestCase.output)// - .containsExactly("noisy test(1, enigma)", "noisy test(2, enigma)", "noisy test(3, enigma)"); - } - - static class InstanceCountingConverter implements ArgumentConverter { - static int instanceCount; - - InstanceCountingConverter() { - instanceCount++; - } - - @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { - return source; - } - } - - static class InstanceCountingAggregator implements ArgumentsAggregator { - static int instanceCount; - - InstanceCountingAggregator() { - instanceCount++; - } - - @Override - public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) - throws ArgumentsAggregationException { - return "enigma"; - } - } - - static class CountingTestCase { - - static final List output = new ArrayList<>(); - - @ParameterizedTest - @ValueSource(ints = { 1, 2, 3 }) - void testWithCountingConverterAggregator(@ConvertWith(InstanceCountingConverter.class) int i, - @AggregateWith(InstanceCountingAggregator.class) Object o) { - - output.add("noisy test(" + i + ", " + o + ")"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java deleted file mode 100644 index 198a05b4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.aggregator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Arrays; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DefaultArgumentsAccessor}. - * - * @since 5.2 - */ -class DefaultArgumentsAccessorTests { - - @Test - void argumentsMustNotBeNull() { - assertThrows(PreconditionViolationException.class, () -> new DefaultArgumentsAccessor((Object[]) null)); - } - - @Test - void indexMustNotBeNegative() { - ArgumentsAccessor arguments = new DefaultArgumentsAccessor(1, 2); - Exception exception = assertThrows(PreconditionViolationException.class, () -> arguments.get(-1)); - assertThat(exception.getMessage()).containsSubsequence("index must be", ">= 0"); - } - - @Test - void indexMustBeSmallerThanLength() { - ArgumentsAccessor arguments = new DefaultArgumentsAccessor(1, 2); - Exception exception = assertThrows(PreconditionViolationException.class, () -> arguments.get(2)); - assertThat(exception.getMessage()).containsSubsequence("index must be", "< 2"); - } - - @Test - void getNull() { - assertNull(new DefaultArgumentsAccessor(new Object[] { null }).get(0)); - } - - @Test - void getWithNullCastToWrapperType() { - assertNull(new DefaultArgumentsAccessor((Object[]) new Integer[] { null }).get(0, Integer.class)); - } - - @Test - void get() { - assertEquals(1, new DefaultArgumentsAccessor(1).get(0)); - } - - @Test - void getWithCast() { - assertEquals(Integer.valueOf(1), new DefaultArgumentsAccessor(1).get(0, Integer.class)); - assertEquals(Character.valueOf('A'), new DefaultArgumentsAccessor('A').get(0, Character.class)); - } - - @Test - void getWithCastToPrimitiveType() { - Exception exception = assertThrows(ArgumentAccessException.class, - () -> new DefaultArgumentsAccessor(1).get(0, int.class)); - assertThat(exception.getMessage()).isEqualTo( - "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [int]."); - - exception = assertThrows(ArgumentAccessException.class, - () -> new DefaultArgumentsAccessor(new Object[] { null }).get(0, int.class)); - assertThat(exception.getMessage()).isEqualTo( - "Argument at index [0] with value [null] and type [null] could not be converted or cast to type [int]."); - } - - @Test - void getWithCastToIncompatibleType() { - Exception exception = assertThrows(ArgumentAccessException.class, - () -> new DefaultArgumentsAccessor(1).get(0, Character.class)); - assertThat(exception.getMessage()).isEqualTo( - "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [java.lang.Character]."); - } - - @Test - void getCharacter() { - assertEquals(Character.valueOf('A'), new DefaultArgumentsAccessor('A', 'B').getCharacter(0)); - } - - @Test - void getBoolean() { - assertEquals(Boolean.TRUE, new DefaultArgumentsAccessor(true, false).getBoolean(0)); - } - - @Test - void getByte() { - assertEquals(Byte.valueOf((byte) 42), new DefaultArgumentsAccessor((byte) 42).getByte(0)); - } - - @Test - void getShort() { - assertEquals(Short.valueOf((short) 42), new DefaultArgumentsAccessor((short) 42).getShort(0)); - } - - @Test - void getInteger() { - assertEquals(Integer.valueOf(42), new DefaultArgumentsAccessor(42).getInteger(0)); - } - - @Test - void getLong() { - assertEquals(Long.valueOf(42L), new DefaultArgumentsAccessor(42L).getLong(0)); - } - - @Test - void getFloat() { - assertEquals(Float.valueOf(42.0f), new DefaultArgumentsAccessor(42.0f).getFloat(0)); - } - - @Test - void getDouble() { - assertEquals(Double.valueOf(42.0), new DefaultArgumentsAccessor(42.0).getDouble(0)); - } - - @Test - void getString() { - assertEquals("foo", new DefaultArgumentsAccessor("foo", "bar").getString(0)); - } - - @Test - void toArray() { - var arguments = new DefaultArgumentsAccessor("foo", "bar"); - var copy = arguments.toArray(); - assertArrayEquals(new String[] { "foo", "bar" }, copy); - - // Modify local copy: - copy[0] = "Boom!"; - assertEquals("foo", arguments.toArray()[0]); - } - - @Test - void toList() { - var arguments = new DefaultArgumentsAccessor("foo", "bar"); - var copy = arguments.toList(); - assertIterableEquals(Arrays.asList("foo", "bar"), copy); - - // Modify local copy: - assertThrows(UnsupportedOperationException.class, () -> copy.set(0, "Boom!")); - } - - @Test - void size() { - assertEquals(0, new DefaultArgumentsAccessor().size()); - assertEquals(1, new DefaultArgumentsAccessor(42).size()); - assertEquals(5, new DefaultArgumentsAccessor('a', 'b', 'c', 'd', 'e').size()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java deleted file mode 100644 index 12ad9cca..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.File; -import java.lang.Thread.State; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.URI; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.MonthDay; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Period; -import java.time.Year; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Currency; -import java.util.Locale; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link DefaultArgumentConverter}. - * - * @since 5.0 - */ -class DefaultArgumentConverterTests { - - @Test - void isAwareOfNull() { - assertConverts(null, Object.class, null); - assertConverts(null, String.class, null); - } - - @Test - void isAwareOfWrapperTypesForPrimitiveTypes() { - assertConverts(true, boolean.class, true); - assertConverts((byte) 1, byte.class, (byte) 1); - assertConverts('o', char.class, 'o'); - assertConverts((short) 1, short.class, (short) 1); - assertConverts(1, int.class, 1); - assertConverts(1L, long.class, 1L); - assertConverts(1.0f, float.class, 1.0f); - assertConverts(1.0d, double.class, 1.0d); - } - - @Test - void isAwareOfWideningConversions() { - assertConverts((byte) 1, short.class, (byte) 1); - assertConverts((short) 1, int.class, (short) 1); - assertConverts((char) 1, int.class, (char) 1); - assertConverts((byte) 1, long.class, (byte) 1); - assertConverts(1, long.class, 1); - assertConverts((char) 1, float.class, (char) 1); - assertConverts(1, float.class, 1); - assertConverts(1L, double.class, 1L); - assertConverts(1.0f, double.class, 1.0f); - } - - @Test - void convertsStringsToPrimitiveTypes() { - assertConverts("true", boolean.class, true); - assertConverts("o", char.class, 'o'); - assertConverts("1", byte.class, (byte) 1); - assertConverts("1_0", byte.class, (byte) 10); - assertConverts("1", short.class, (short) 1); - assertConverts("1_2", short.class, (short) 12); - assertConverts("42", int.class, 42); - assertConverts("700_050_000", int.class, 700_050_000); - assertConverts("42", long.class, 42L); - assertConverts("4_2", long.class, 42L); - assertConverts("42.23", float.class, 42.23f); - assertConverts("42.2_3", float.class, 42.23f); - assertConverts("42.23", double.class, 42.23); - assertConverts("42.2_3", double.class, 42.23); - } - - /** - * @since 5.4 - */ - @Test - @SuppressWarnings("OctalInteger") // We test parsing octal integers here as well as hex. - void convertsEncodedStringsToIntegralTypes() { - assertConverts("0x1f", byte.class, (byte) 0x1F); - assertConverts("-0x1F", byte.class, (byte) -0x1F); - assertConverts("010", byte.class, (byte) 010); - - assertConverts("0x1f00", short.class, (short) 0x1F00); - assertConverts("-0x1F00", short.class, (short) -0x1F00); - assertConverts("01000", short.class, (short) 01000); - - assertConverts("0x1f000000", int.class, 0x1F000000); - assertConverts("-0x1F000000", int.class, -0x1F000000); - assertConverts("010000000", int.class, 010000000); - - assertConverts("0x1f000000000", long.class, 0x1F000000000L); - assertConverts("-0x1F000000000", long.class, -0x1F000000000L); - assertConverts("0100000000000", long.class, 0100000000000L); - } - - @Test - void convertsStringsToEnumConstants() { - assertConverts("DAYS", TimeUnit.class, TimeUnit.DAYS); - } - - // --- java.io and java.nio ------------------------------------------------ - - @Test - void convertsStringToCharset() { - assertConverts("ISO-8859-1", Charset.class, StandardCharsets.ISO_8859_1); - assertConverts("UTF-8", Charset.class, StandardCharsets.UTF_8); - } - - @Test - void convertsStringToFile() { - assertConverts("file", File.class, new File("file")); - assertConverts("/file", File.class, new File("/file")); - assertConverts("/some/file", File.class, new File("/some/file")); - } - - @Test - void convertsStringToPath() { - assertConverts("path", Path.class, Paths.get("path")); - assertConverts("/path", Path.class, Paths.get("/path")); - assertConverts("/some/path", Path.class, Paths.get("/some/path")); - } - - // --- java.lang ----------------------------------------------------------- - - @Test - void convertsStringToClass() { - assertConverts("java.lang.Integer", Class.class, Integer.class); - assertConverts("java.lang.Thread$State", Class.class, State.class); - assertConverts("byte", Class.class, byte.class); - assertConverts("char[]", Class.class, char[].class); - assertConverts("java.lang.Long[][]", Class.class, Long[][].class); - assertConverts("[[[I", Class.class, int[][][].class); - assertConverts("[[Ljava.lang.String;", Class.class, String[][].class); - } - - // --- java.math ----------------------------------------------------------- - - @Test - void convertsStringToBigDecimal() { - assertConverts("123.456e789", BigDecimal.class, new BigDecimal("123.456e789")); - } - - @Test - void convertsStringToBigInteger() { - assertConverts("1234567890123456789", BigInteger.class, new BigInteger("1234567890123456789")); - } - - // --- java.net ------------------------------------------------------------ - - @Test - void convertsStringToURI() { - assertConverts("https://docs.oracle.com/en/java/javase/12/", URI.class, - URI.create("https://docs.oracle.com/en/java/javase/12/")); - } - - @Test - void convertsStringToURL() throws Exception { - assertConverts("https://junit.org/junit5", URL.class, URI.create("https://junit.org/junit5").toURL()); - } - - // --- java.time ----------------------------------------------------------- - - @Test - void convertsStringsToJavaTimeInstances() { - assertConverts("PT1234.5678S", Duration.class, Duration.ofSeconds(1234, 567800000)); - assertConverts("1970-01-01T00:00:00Z", Instant.class, Instant.ofEpochMilli(0)); - assertConverts("2017-03-14", LocalDate.class, LocalDate.of(2017, 3, 14)); - assertConverts("2017-03-14T12:34:56.789", LocalDateTime.class, - LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)); - assertConverts("12:34:56.789", LocalTime.class, LocalTime.of(12, 34, 56, 789_000_000)); - assertConverts("--03-14", MonthDay.class, MonthDay.of(3, 14)); - assertConverts("2017-03-14T12:34:56.789Z", OffsetDateTime.class, - OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); - assertConverts("12:34:56.789Z", OffsetTime.class, OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)); - assertConverts("P2M6D", Period.class, Period.of(0, 2, 6)); - assertConverts("2017", Year.class, Year.of(2017)); - assertConverts("2017-03", YearMonth.class, YearMonth.of(2017, 3)); - assertConverts("2017-03-14T12:34:56.789Z", ZonedDateTime.class, - ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); - assertConverts("Europe/Berlin", ZoneId.class, ZoneId.of("Europe/Berlin")); - assertConverts("+02:30", ZoneOffset.class, ZoneOffset.ofHoursMinutes(2, 30)); - } - - // --- java.util ----------------------------------------------------------- - - @Test - void convertsStringToCurrency() { - assertConverts("JPY", Currency.class, Currency.getInstance("JPY")); - } - - @Test - void convertsStringToLocale() { - assertConverts("en", Locale.class, Locale.ENGLISH); - assertConverts("en_us", Locale.class, new Locale(Locale.US.toString())); - } - - @Test - void convertsStringToUUID() { - var uuid = "d043e930-7b3b-48e3-bdbe-5a3ccfb833db"; - assertConverts(uuid, UUID.class, UUID.fromString(uuid)); - } - - // ------------------------------------------------------------------------- - - private void assertConverts(Object input, Class targetClass, Object expectedOutput) { - var result = DefaultArgumentConverter.INSTANCE.convert(input, targetClass); - - assertThat(result) // - .describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) // - .isEqualTo(expectedOutput); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java deleted file mode 100644 index 1d2cd21a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Objects; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.converter.FallbackStringToObjectConverter.IsFactoryConstructor; -import org.junit.jupiter.params.converter.FallbackStringToObjectConverter.IsFactoryMethod; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link FallbackStringToObjectConverter}, {@link IsFactoryMethod}, - * and {@link IsFactoryConstructor}. - * - * @since 5.1 - */ -class FallbackStringToObjectConverterTests { - - private static final IsFactoryMethod isBookFactoryMethod = new IsFactoryMethod(Book.class); - - private static final FallbackStringToObjectConverter converter = new FallbackStringToObjectConverter(); - - @Test - void isNotFactoryMethodForWrongParameterType() { - assertThat(isBookFactoryMethod).rejects(bookMethod("factory", Object.class)); - } - - @Test - void isNotFactoryMethodForPrivateMethod() { - assertThat(isBookFactoryMethod).rejects(bookMethod("privateFactory")); - } - - @Test - void isNotFactoryMethodForNonStaticMethod() { - assertThat(isBookFactoryMethod).rejects(bookMethod("nonStaticFactory")); - } - - @Test - void isFactoryMethodForValidMethods() { - assertThat(isBookFactoryMethod).accepts(bookMethod("factory")); - assertThat(new IsFactoryMethod(Newspaper.class)).accepts(newspaperMethod("from"), newspaperMethod("of")); - assertThat(new IsFactoryMethod(Magazine.class)).accepts(magazineMethod("from"), magazineMethod("of")); - } - - @Test - void isNotFactoryConstructorForPrivateConstructor() { - assertThat(new IsFactoryConstructor(Magazine.class)).rejects(constructor(Magazine.class)); - } - - @Test - void isFactoryConstructorForValidConstructors() { - assertThat(new IsFactoryConstructor(Book.class)).accepts(constructor(Book.class)); - assertThat(new IsFactoryConstructor(Journal.class)).accepts(constructor(Journal.class)); - assertThat(new IsFactoryConstructor(Newspaper.class)).accepts(constructor(Newspaper.class)); - } - - @Test - void convertsStringToBookViaStaticFactoryMethod() throws Exception { - assertConverts("enigma", Book.class, Book.factory("enigma")); - } - - @Test - void convertsStringToJournalViaFactoryConstructor() throws Exception { - assertConverts("enigma", Journal.class, new Journal("enigma")); - } - - @Test - void convertsStringToNewspaperViaConstructorIgnoringMultipleFactoryMethods() throws Exception { - assertConverts("enigma", Newspaper.class, new Newspaper("enigma")); - } - - @Test - @DisplayName("Cannot convert String to Diary because Diary has neither a static factory method nor a factory constructor") - void cannotConvertStringToDiary() { - assertThat(converter.canConvert(Diary.class)).isFalse(); - } - - @Test - @DisplayName("Cannot convert String to Magazine because Magazine has multiple static factory methods") - void cannotConvertStringToMagazine() { - assertThat(converter.canConvert(Magazine.class)).isFalse(); - } - - // ------------------------------------------------------------------------- - - private static Constructor constructor(Class clazz) { - return ReflectionUtils.findConstructors(clazz, - ctr -> ctr.getParameterCount() == 1 && ctr.getParameterTypes()[0] == String.class).get(0); - } - - private static Method bookMethod(String methodName) { - return bookMethod(methodName, String.class); - } - - private static Method bookMethod(String methodName, Class parameterType) { - return findMethod(Book.class, methodName, parameterType).orElseThrow(); - } - - private static Method newspaperMethod(String methodName) { - return findMethod(Newspaper.class, methodName, String.class).orElseThrow(); - } - - private static Method magazineMethod(String methodName) { - return findMethod(Magazine.class, methodName, String.class).orElseThrow(); - } - - private static void assertConverts(String input, Class targetType, Object expectedOutput) throws Exception { - assertThat(converter.canConvert(targetType)).isTrue(); - - var result = converter.convert(input, targetType); - - assertThat(result) // - .describedAs(input + " --(" + targetType.getName() + ")--> " + expectedOutput) // - .isEqualTo(expectedOutput); - } - - static class Book { - - private final String title; - - Book(String title) { - this.title = title; - } - - // static and non-private - static Book factory(String title) { - return new Book(title); - } - - // wrong parameter type - static Book factory(Object obj) { - return new Book(String.valueOf(obj)); - } - - @SuppressWarnings("unused") - private static Book privateFactory(String title) { - return new Book(title); - } - - Book nonStaticFactory(String title) { - return new Book(title); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Book)) { - return false; - } - var that = (Book) obj; - return Objects.equals(this.title, that.title); - } - - } - - static class Journal { - - private final String title; - - Journal(String title) { - this.title = title; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Journal)) { - return false; - } - var that = (Journal) obj; - return Objects.equals(this.title, that.title); - } - - } - - static class Newspaper { - - private final String title; - - Newspaper(String title) { - this.title = title; - } - - static Newspaper from(String title) { - return new Newspaper(title); - } - - static Newspaper of(String title) { - return new Newspaper(title); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Newspaper)) { - return false; - } - var that = (Newspaper) obj; - return Objects.equals(this.title, that.title); - } - - } - - static class Magazine { - - private Magazine(String title) { - } - - static Magazine from(String title) { - return new Magazine(title); - } - - static Magazine of(String title) { - return new Magazine(title); - } - - } - - static class Diary { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java deleted file mode 100644 index 3bb89074..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static java.time.ZoneOffset.UTC; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Year; -import java.time.YearMonth; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.ChronoLocalDateTime; -import java.time.chrono.ChronoZonedDateTime; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -class JavaTimeArgumentConverterTests { - - @Test - void convertsStringToChronoLocalDate() { - assertThat(convert("01.02.2017", "dd.MM.yyyy", ChronoLocalDate.class)) // - .isEqualTo(LocalDate.of(2017, 2, 1)); - } - - @Test - void convertsStringToChronoLocalDateTime() { - assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", ChronoLocalDateTime.class)) // - .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); - } - - @Test - void convertsStringToChronoZonedDateTime() { - assertThat(convert("01.02.2017 12:34:56.789 Z", "dd.MM.yyyy HH:mm:ss.SSS X", ChronoZonedDateTime.class)) // - .isEqualTo(ZonedDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, UTC)); - } - - @Test - void convertsStringToLocalDate() { - assertThat(convert("01.02.2017", "dd.MM.yyyy", LocalDate.class)) // - .isEqualTo(LocalDate.of(2017, 2, 1)); - } - - @Test - void convertsStringToLocalDateTime() { - assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", LocalDateTime.class)) // - .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); - } - - @Test - void convertsStringToLocalTime() { - assertThat(convert("12:34:56.789", "HH:mm:ss.SSS", LocalTime.class)) // - .isEqualTo(LocalTime.of(12, 34, 56, 789_000_000)); - } - - @Test - void convertsStringToOffsetDateTime() { - assertThat(convert("01.02.2017 12:34:56.789 +02", "dd.MM.yyyy HH:mm:ss.SSS X", OffsetDateTime.class)) // - .isEqualTo(OffsetDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, ZoneOffset.ofHours(2))); - } - - @Test - void convertsStringToOffsetTime() { - assertThat(convert("12:34:56.789 -02", "HH:mm:ss.SSS X", OffsetTime.class)) // - .isEqualTo(OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.ofHours(-2))); - } - - @Test - void convertsStringToYear() { - assertThat(convert("2017", "yyyy", Year.class)) // - .isEqualTo(Year.of(2017)); - } - - @Test - void convertsStringToYearMonth() { - assertThat(convert("03/2017", "MM/yyyy", YearMonth.class)) // - .isEqualTo(YearMonth.of(2017, 3)); - } - - @Test - void convertsStringToZonedDateTime() { - assertThat(convert("01.02.2017 12:34:56.789 Europe/Berlin", "dd.MM.yyyy HH:mm:ss.SSS VV", ZonedDateTime.class)) // - .isEqualTo(ZonedDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, ZoneId.of("Europe/Berlin"))); - } - - @Test - void throwsExceptionOnInvalidTargetType() { - var exception = assertThrows(ArgumentConversionException.class, () -> convert("2017", "yyyy", Integer.class)); - - assertThat(exception).hasMessage("Cannot convert to java.lang.Integer: 2017"); - } - - private Object convert(Object input, String pattern, Class targetClass) { - var converter = new JavaTimeArgumentConverter(); - var annotation = mock(JavaTimeConversionPattern.class); - when(annotation.value()).thenReturn(pattern); - converter.accept(annotation); - - return converter.convert(input, targetClass); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java deleted file mode 100644 index c32a0313..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Tests for {@link TypedArgumentConverter}. - * - * @since 5.7 - */ -class TypedArgumentConverterTests { - - @Nested - class UnitTests { - - private final StringLengthArgumentConverter converter = new StringLengthArgumentConverter(); - - /** - * @since 5.8 - */ - @Test - void preconditions() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> new StringLengthArgumentConverter(null, Integer.class))// - .withMessage("sourceType must not be null"); - - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> new StringLengthArgumentConverter(String.class, null))// - .withMessage("targetType must not be null"); - } - - @Test - void convertsSourceToTarget() { - assertAll(// - () -> assertConverts("abcd", 4), // - () -> assertConverts("", 0), // - () -> assertConverts(null, 0)// - ); - } - - private void assertConverts(String input, int expected) { - assertThat(this.converter.convert(input)).isEqualTo(expected); - } - - @Test - void sourceTypeMismatch() { - Parameter parameter = findParameterOfMethod("stringToBoolean", Boolean.class); - ParameterContext parameterContext = parameterContext(parameter); - assertThatExceptionOfType(ArgumentConversionException.class)// - .isThrownBy(() -> this.converter.convert(Boolean.TRUE, parameterContext))// - .withMessage("StringLengthArgumentConverter cannot convert objects of type [java.lang.Boolean]. " - + "Only source objects of type [java.lang.String] are supported."); - } - - @Test - void targetTypeMismatch() { - Parameter parameter = findParameterOfMethod("stringToBoolean", Boolean.class); - ParameterContext parameterContext = parameterContext(parameter); - assertThatExceptionOfType(ArgumentConversionException.class)// - .isThrownBy(() -> this.converter.convert("enigma", parameterContext))// - .withMessage("StringLengthArgumentConverter cannot convert to type [java.lang.Boolean]. " - + "Only target type [java.lang.Integer] is supported."); - } - - private ParameterContext parameterContext(Parameter parameter) { - ParameterContext parameterContext = mock(); - when(parameterContext.getParameter()).thenReturn(parameter); - return parameterContext; - } - - private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionUtils.findMethod(getClass(), methodName, parameterTypes).get(); - return method.getParameters()[0]; - } - - void stringToBoolean(Boolean b) { - } - - } - - /** - * @since 5.8 - */ - @Nested - class IntegrationTests { - - @ParameterizedTest - @NullSource - void nullStringToInteger(@StringLength Integer length) { - assertThat(length).isEqualTo(0); - } - - @ParameterizedTest - @NullSource - void nullStringToPrimitiveInt(@StringLength int length) { - assertThat(length).isEqualTo(0); - } - - @ParameterizedTest - @NullSource - void nullStringToPrimitiveLong(@StringLength long length) { - assertThat(length).isEqualTo(0); - } - - @ParameterizedTest - @ValueSource(strings = "enigma") - void stringToInteger(@StringLength Integer length) { - assertThat(length).isEqualTo(6); - } - - @ParameterizedTest - @ValueSource(strings = "enigma") - void stringToPrimitiveInt(@StringLength int length) { - assertThat(length).isEqualTo(6); - } - - @ParameterizedTest - @ValueSource(strings = "enigma") - void stringToPrimitiveLong(@StringLength long length) { - assertThat(length).isEqualTo(6); - } - - } - - @Target(ElementType.PARAMETER) - @Retention(RetentionPolicy.RUNTIME) - @ConvertWith(StringLengthArgumentConverter.class) - private @interface StringLength { - } - - private static class StringLengthArgumentConverter extends TypedArgumentConverter { - - StringLengthArgumentConverter() { - this(String.class, Integer.class); - } - - StringLengthArgumentConverter(Class sourceType, Class targetType) { - super(sourceType, targetType); - } - - @Override - protected Integer convert(String source) throws ArgumentConversionException { - return (source != null ? source.length() : 0); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java deleted file mode 100644 index 48958cf4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.junit.jupiter.params.provider.Arguments.of; - -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link Arguments}. - * - * @since 5.0 - */ -class ArgumentsTests { - - @Test - void ofSupportsVarargs() { - var arguments = of(1, "2", 3.0); - - assertArrayEquals(new Object[] { 1, "2", 3.0 }, arguments.get()); - } - - @Test - void argumentsSupportsVarargs() { - var arguments = arguments(1, "2", 3.0); - - assertArrayEquals(new Object[] { 1, "2", 3.0 }, arguments.get()); - } - - @Test - void ofReturnsSameArrayUsedForCreating() { - Object[] input = { 1, "2", 3.0 }; - - var arguments = of(input); - - assertThat(arguments.get()).isSameAs(input); - } - - @Test - void argumentsReturnsSameArrayUsedForCreating() { - Object[] input = { 1, "2", 3.0 }; - - var arguments = arguments(input); - - assertThat(arguments.get()).isSameAs(input); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java deleted file mode 100644 index 67acc181..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvSource; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 5.0 - */ -class CsvArgumentsProviderTests { - - @Test - void throwsExceptionForInvalidCsv() { - var annotation = csvSource("foo", "bar", ""); - - assertThatExceptionOfType(JUnitException.class)// - .isThrownBy(() -> provideArguments(annotation).toArray())// - .withMessage("Record at index 3 contains invalid CSV: \"\""); - } - - @Test - void throwsExceptionIfNeitherValueNorTextBlockIsDeclared() { - var annotation = csvSource().build(); - - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// - .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); - } - - @Test - void throwsExceptionIfValueAndTextBlockAreDeclared() { - var annotation = csvSource().lines("foo").textBlock(""" - bar - baz - """).build(); - - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// - .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); - } - - @Test - void providesSingleArgument() { - var annotation = csvSource("foo"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo")); - } - - @Test - void providesSingleArgumentFromTextBlock() { - var annotation = csvSource().textBlock("foo").build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo")); - } - - @Test - void providesMultipleArguments() { - var annotation = csvSource("foo", "bar"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesMultipleArgumentsFromTextBlock() { - var annotation = csvSource().textBlock(""" - foo - bar - """).build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void splitsAndTrimsArguments() { - var annotation = csvSource(" foo , bar "); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo", "bar")); - } - - @Test - void trimsLeadingSpaces() { - var annotation = csvSource("'', 1", " '', 2", "'' , 3", " '' , 4"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(new Object[][] { { "", "1" }, { "", "2" }, { "", "3" }, { "", "4" } }); - } - - @Test - void trimsTrailingSpaces() { - var annotation = csvSource("1,''", "2, ''", "3,'' ", "4, '' "); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(new Object[][] { { "1", "" }, { "2", "" }, { "3", "" }, { "4", "" } }); - } - - @Test - void ignoresLeadingAndTrailingSpaces() { - var annotation = csvSource().lines("1,a", "2, b", "3,c ", "4, d ") // - .ignoreLeadingAndTrailingWhitespace(false).build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly( - new Object[][] { { "1", "a" }, { "2", " b" }, { "3", "c " }, { "4", " d " } }); - } - - @Test - void understandsQuotes() { - var annotation = csvSource("'foo, bar'"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo, bar")); - } - - @Test - void understandsQuotesInTextBlock() { - var annotation = csvSource().textBlock(""" - 'foo, bar' - """).build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo, bar")); - } - - @Test - void understandsCustomQuotes() { - var annotation = csvSource().quoteCharacter('~').lines("~foo, bar~").build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo, bar")); - } - - @Test - void understandsCustomQuotesInTextBlock() { - var annotation = csvSource().quoteCharacter('"').textBlock(""" - "foo, bar" - """).build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo, bar")); - } - - @Test - void understandsEscapeCharacters() { - var annotation = csvSource("'foo or ''bar''', baz"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo or 'bar'", "baz")); - } - - @Test - void understandsEscapeCharactersWithCutomQuoteCharacter() { - var annotation = csvSource().quoteCharacter('~').lines("~foo or ~~bar~~~, baz").build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo or ~bar~", "baz")); - } - - @Test - void doesNotTrimSpacesInsideQuotes() { - var annotation = csvSource("''", "' '", "'blank '", "' not blank '"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array(""), array(" "), array("blank "), array(" not blank ")); - } - - @Test - void providesArgumentsWithCharacterDelimiter() { - var annotation = csvSource().delimiter('|').lines("foo|bar", "bar|foo").build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo", "bar"), array("bar", "foo")); - } - - @Test - void providesArgumentsWithStringDelimiter() { - var annotation = csvSource().delimiterString("~~~").lines("foo~~~ bar", "bar~~~ foo").build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo", "bar"), array("bar", "foo")); - } - - @Test - void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { - var annotation = csvSource().delimiter('|').delimiterString("~~~").build(); - - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// - .withMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// - .withMessageContaining("CsvSource"); - } - - @Test - void defaultEmptyValueAndDefaultNullValue() { - var annotation = csvSource("'', null, , apple"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("", "null", null, "apple")); - } - - @Test - void customEmptyValueAndDefaultNullValue() { - var annotation = csvSource().emptyValue("EMPTY").lines("'', null, , apple").build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("EMPTY", "null", null, "apple")); - } - - @Test - void customNullValues() { - var annotation = csvSource().nullValues("N/A", "NIL").lines("apple, , NIL, '', N/A, banana").build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana")); - } - - @Test - void convertsEmptyValuesToNullInLinesAfterFirstLine() { - var annotation = csvSource("'', ''", " , "); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(new Object[][] { { "", "" }, { null, null } }); - } - - @Test - void throwsExceptionIfSourceExceedsMaxCharsPerColumnConfig() { - var annotation = csvSource().lines("413").maxCharsPerColumn(2).build(); - - assertThatExceptionOfType(CsvParsingException.class)// - .isThrownBy(() -> provideArguments(annotation))// - .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// - .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); - } - - @Test - void providesArgumentWithDefaultMaxCharsPerColumnConfig() { - var annotation = csvSource().lines("0".repeat(4096)).delimiter(';').build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments.toArray()).hasSize(1); - } - - @Test - void throwsExceptionWhenSourceExceedsDefaultMaxCharsPerColumnConfig() { - var annotation = csvSource().lines("0".repeat(4097)).delimiter(';').build(); - - assertThatExceptionOfType(CsvParsingException.class)// - .isThrownBy(() -> provideArguments(annotation))// - .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// - .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); - } - - @Test - void providesArgumentsForExceedsSourceWithCustomMaxCharsPerColumnConfig() { - var annotation = csvSource().lines("0".repeat(4097)).delimiter(';').maxCharsPerColumn(4097).build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments.toArray()).hasSize(1); - } - - @Test - void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber() { - var annotation = csvSource().lines("41").delimiter(';').maxCharsPerColumn(-1).build(); - - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// - .withMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); - } - - @Test - void ignoresCommentCharacterWhenUsingValueAttribute() { - var annotation = csvSource("#foo", "#bar,baz", "baz,#quux"); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("#foo"), array("#bar", "baz"), array("baz", "#quux")); - } - - @Test - void honorsCommentCharacterWhenUsingTextBlockAttribute() { - var annotation = csvSource().textBlock(""" - #foo - bar, #baz - '#bar', baz - """).build(); - - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("bar", "#baz"), array("#bar", "baz")); - } - - @Test - void supportsCsvHeadersWhenUsingTextBlockAttribute() { - supportsCsvHeaders(csvSource().useHeadersInDisplayName(true).textBlock(""" - FRUIT, RANK - apple, 1 - banana, 2 - """).build()); - } - - @Test - void supportsCsvHeadersWhenUsingValueAttribute() { - supportsCsvHeaders(csvSource().useHeadersInDisplayName(true)// - .lines("FRUIT, RANK", "apple, 1", "banana, 2").build()); - } - - private void supportsCsvHeaders(CsvSource csvSource) { - var arguments = provideArguments(csvSource); - Stream argumentsAsStrings = arguments.map(array -> { - String[] strings = new String[array.length]; - for (int i = 0; i < array.length; i++) { - strings[i] = String.valueOf(array[i]); - } - return strings; - }); - - assertThat(argumentsAsStrings).containsExactly(array("FRUIT = apple", "RANK = 1"), - array("FRUIT = banana", "RANK = 2")); - } - - @Test - void throwsExceptionIfColumnCountExceedsHeaderCount() { - var annotation = csvSource().useHeadersInDisplayName(true).textBlock(""" - FRUIT, RANK - apple, 1 - banana, 2, BOOM! - """).build(); - - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// - .withMessage( - "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); - } - - private Stream provideArguments(CsvSource annotation) { - var provider = new CsvArgumentsProvider(); - provider.accept(annotation); - return provider.provideArguments(null).map(Arguments::get); - } - - @SuppressWarnings("unchecked") - private static T[] array(T... elements) { - return elements; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java deleted file mode 100644 index 1933e502..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvFileSource; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.provider.CsvFileArgumentsProvider.InputStreamProvider; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 5.0 - */ -class CsvFileArgumentsProviderTests { - - @Test - void providesArgumentsForNewlineAndComma() { - var annotation = csvFileSource()// - .resources("test.csv")// - .lineSeparator("\n")// - .delimiter(',')// - .build(); - - var arguments = provideArguments(annotation, "foo, bar \n baz, qux \n"); - - assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); - } - - @Test - void providesArgumentsForCarriageReturnAndSemicolon() { - var annotation = csvFileSource()// - .resources("test.csv")// - .lineSeparator("\r")// - .delimiter(';')// - .build(); - - var arguments = provideArguments(annotation, "foo; bar \r baz; qux"); - - assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); - } - - @Test - void providesArgumentsWithCustomQuoteCharacter() { - var annotation = csvFileSource()// - .resources("test.csv")// - .quoteCharacter('\'')// - .build(); - - var arguments = provideArguments(annotation, "foo, 'bar \"and\" baz', qux \n 'lemon lime', banana, apple"); - - assertThat(arguments).containsExactly(array("foo", "bar \"and\" baz", "qux"), - array("lemon lime", "banana", "apple")); - } - - @Test - void providesArgumentsWithStringDelimiter() { - var annotation = csvFileSource()// - .resources("test.csv")// - .delimiterString(",")// - .build(); - - var arguments = provideArguments(annotation, "foo, bar \n baz, qux \n"); - - assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); - } - - @Test - void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { - var annotation = csvFileSource()// - .resources("test.csv")// - .delimiter(',')// - .delimiterString(";")// - .build(); - - var exception = assertThrows(PreconditionViolationException.class, () -> provideArguments(annotation, "foo")); - - assertThat(exception)// - .hasMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// - .hasMessageContaining("CsvFileSource"); - } - - @Test - void ignoresCommentedOutEntries() { - var annotation = csvFileSource()// - .resources("test.csv")// - .delimiter(',')// - .build(); - - var arguments = provideArguments(annotation, "foo, bar \n#baz, qux"); - - assertThat(arguments).containsExactly(array("foo", "bar")); - } - - @Test - void closesInputStreamForClasspathResource() { - var closed = new AtomicBoolean(false); - InputStream inputStream = new ByteArrayInputStream("foo".getBytes()) { - - @Override - public void close() { - closed.set(true); - } - }; - var annotation = csvFileSource().resources("test.csv").build(); - - var arguments = provideArguments(inputStream, annotation); - - assertThat(arguments.count()).isEqualTo(1); - assertThat(closed.get()).describedAs("closed").isTrue(); - } - - @Test - void closesInputStreamForFile(@TempDir Path tempDir) { - var closed = new AtomicBoolean(false); - InputStream inputStream = new ByteArrayInputStream("foo".getBytes()) { - - @Override - public void close() { - closed.set(true); - } - }; - var annotation = csvFileSource().files(tempDir.resolve("test.csv").toAbsolutePath().toString()).build(); - - var arguments = provideArguments(inputStream, annotation); - - assertThat(arguments.count()).isEqualTo(1); - assertThat(closed.get()).describedAs("closed").isTrue(); - } - - @Test - void readsFromSingleClasspathResource() { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv")// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); - } - - @Test - void readsFromSingleFileWithAbsolutePath(@TempDir Path tempDir) throws Exception { - var csvFile = writeClasspathResourceToFile("/single-column.csv", tempDir.resolve("single-column.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .files(csvFile.toAbsolutePath().toString())// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); - } - - @Test - void readsFromClasspathResourcesAndFiles(@TempDir Path tempDir) throws Exception { - var csvFile = writeClasspathResourceToFile("/single-column.csv", tempDir.resolve("single-column.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv")// - .files(csvFile.toAbsolutePath().toString())// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).hasSize(2 * 5); - } - - @Test - void readsFromSingleFileWithRelativePath() throws Exception { - var csvFile = writeClasspathResourceToFile("/single-column.csv", Path.of("single-column.csv")); - try { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .files(csvFile.getFileName().toString())// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); - } - finally { - Files.delete(csvFile); - } - } - - @Test - void readsFromSingleClasspathResourceWithCustomEmptyValue() { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv")// - .emptyValue("EMPTY")// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("EMPTY")); - } - - @Test - void readsFromMultipleClasspathResources() { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv", "/single-column.csv")// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).hasSize(10); - } - - @Test - void readsFromSingleClasspathResourceWithHeaders() { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv")// - .numLinesToSkip(1)// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).containsExactly(array("bar"), array("baz"), array("qux"), array("")); - } - - @Test - void readsFromSingleClasspathResourceWithMoreHeadersThanLines() { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv")// - .numLinesToSkip(10)// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).isEmpty(); - } - - @Test - void readsFromMultipleClasspathResourcesWithHeaders() { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv", "/single-column.csv")// - .numLinesToSkip(1)// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).containsExactly(array("bar"), array("baz"), array("qux"), array(""), array("bar"), - array("baz"), array("qux"), array("")); - } - - @Test - void supportsCsvHeadersInDisplayNames() { - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/single-column.csv")// - .useHeadersInDisplayName(true)// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - Stream argumentsAsStrings = arguments.map(array -> new String[] { String.valueOf(array[0]) }); - - assertThat(argumentsAsStrings).containsExactly(array("foo = bar"), array("foo = baz"), array("foo = qux"), - array("foo = ")); - } - - @Test - void throwsExceptionForMissingClasspathResource() { - var annotation = csvFileSource()// - .resources("/does-not-exist.csv")// - .build(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception).hasMessageContaining("Classpath resource [/does-not-exist.csv] does not exist"); - } - - @Test - void throwsExceptionForBlankClasspathResource() { - var annotation = csvFileSource()// - .resources(" ")// - .build(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception).hasMessageContaining("Classpath resource [ ] must not be null or blank"); - } - - @Test - void throwsExceptionForMissingFile() { - var annotation = csvFileSource()// - .files("does-not-exist.csv")// - .build(); - - var exception = assertThrows(JUnitException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception).hasMessageContaining("File [does-not-exist.csv] could not be read"); - } - - @Test - void throwsExceptionForBlankFile() { - var annotation = csvFileSource()// - .files(" ")// - .build(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception).hasMessageContaining("File [ ] must not be null or blank"); - } - - @Test - void throwsExceptionIfResourcesAndFilesAreEmpty() { - var annotation = csvFileSource()// - .resources()// - .files()// - .build(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception).hasMessageContaining("Resources or files must not be empty"); - } - - @Test - void throwsExceptionForInvalidCharset() { - var annotation = csvFileSource()// - .encoding("Bogus-Charset")// - .resources("/bogus-charset.csv")// - .build(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception)// - .hasMessageContaining("The charset supplied in Mock for CsvFileSource")// - .hasMessageEndingWith("is invalid"); - } - - @Test - void throwsExceptionForInvalidCsvFormat() { - var annotation = csvFileSource()// - .resources("/broken.csv")// - .build(); - - var exception = assertThrows(CsvParsingException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception)// - .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// - .hasRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); - } - - @Test - void emptyValueIsAnEmptyWithCustomNullValueString() { - var annotation = csvFileSource()// - .resources("test.csv")// - .lineSeparator("\n")// - .delimiter(',')// - .nullValues("NIL")// - .build(); - - var arguments = provideArguments(annotation, "apple, , NIL, ''\nNIL, NIL, foo, bar"); - - assertThat(arguments).containsExactly(array("apple", null, null, "''"), array(null, null, "foo", "bar")); - } - - @Test - void readsLineFromDefaultMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws Exception { - var csvFile = writeClasspathResourceToFile("/default-max-chars.csv", tempDir.resolve("default-max-chars.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/default-max-chars.csv")// - .files(csvFile.toAbsolutePath().toString())// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).hasSize(2); - } - - @Test - void readsLineFromExceedsMaxCharsFileWithCustomConfig(@TempDir Path tempDir) throws java.io.IOException { - var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", - tempDir.resolve("exceeds-default-max-chars.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/exceeds-default-max-chars.csv")// - .maxCharsPerColumn(4097)// - .files(csvFile.toAbsolutePath().toString())// - .build(); - - var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - - assertThat(arguments).hasSize(2); - } - - @Test - void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber(@TempDir Path tempDir) throws java.io.IOException { - var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", - tempDir.resolve("exceeds-default-max-chars.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/exceeds-default-max-chars.csv")// - .maxCharsPerColumn(-1).files(csvFile.toAbsolutePath().toString())// - .build(); - - var exception = assertThrows(PreconditionViolationException.class, // - () -> provideArguments(new CsvFileArgumentsProvider(), annotation)); - - assertThat(exception)// - .hasMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); - } - - @Test - void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws java.io.IOException { - var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", - tempDir.resolve("exceeds-default-max-chars.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/exceeds-default-max-chars.csv")// - .files(csvFile.toAbsolutePath().toString())// - .build(); - - var exception = assertThrows(CsvParsingException.class, - () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); - - assertThat(exception)// - .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// - .hasRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); - } - - @Test - void ignoresLeadingAndTrailingSpaces(@TempDir Path tempDir) throws IOException { - var csvFile = writeClasspathResourceToFile("/leading-trailing-spaces.csv", - tempDir.resolve("leading-trailing-spaces.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/leading-trailing-spaces.csv")// - .files(csvFile.toAbsolutePath().toString())// - .ignoreLeadingAndTrailingWhitespace(true)// - .build(); - - var arguments = provideArguments(new ByteArrayInputStream(Files.readAllBytes(csvFile)), annotation); - - assertThat(arguments).containsExactly(array("ab", "cd"), array("ef", "gh")); - } - - @Test - void trimsLeadingAndTrailingSpaces(@TempDir Path tempDir) throws IOException { - var csvFile = writeClasspathResourceToFile("/leading-trailing-spaces.csv", - tempDir.resolve("leading-trailing-spaces.csv")); - var annotation = csvFileSource()// - .encoding("ISO-8859-1")// - .resources("/leading-trailing-spaces.csv")// - .files(csvFile.toAbsolutePath().toString())// - .delimiter(',')// - .ignoreLeadingAndTrailingWhitespace(false)// - .build(); - - var arguments = provideArguments(new ByteArrayInputStream(Files.readAllBytes(csvFile)), annotation); - - assertThat(arguments).containsExactly(array(" ab ", " cd"), array("ef ", "gh")); - } - - private Stream provideArguments(CsvFileSource annotation, String content) { - return provideArguments(new ByteArrayInputStream(content.getBytes(UTF_8)), annotation); - } - - private Stream provideArguments(InputStream inputStream, CsvFileSource annotation) { - var provider = new CsvFileArgumentsProvider(new InputStreamProvider() { - @Override - public InputStream openClasspathResource(Class baseClass, String path) { - assertThat(path).isEqualTo(annotation.resources()[0]); - return inputStream; - } - - @Override - public InputStream openFile(String path) { - assertThat(path).isEqualTo(annotation.files()[0]); - return inputStream; - } - }); - return provideArguments(provider, annotation); - } - - private Stream provideArguments(CsvFileArgumentsProvider provider, CsvFileSource annotation) { - provider.accept(annotation); - var context = mock(ExtensionContext.class); - when(context.getTestClass()).thenReturn(Optional.of(CsvFileArgumentsProviderTests.class)); - doCallRealMethod().when(context).getRequiredTestClass(); - return provider.provideArguments(context).map(Arguments::get); - } - - @SuppressWarnings("unchecked") - private static T[] array(T... elements) { - return elements; - } - - private static Path writeClasspathResourceToFile(String name, Path target) throws IOException { - try (var in = CsvFileArgumentsProviderTests.class.getResourceAsStream(name)) { - Files.copy(in, target); - } - return target; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java deleted file mode 100644 index eb265d3e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithTwoConstants.BAR; -import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithTwoConstants.FOO; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.provider.EnumSource.Mode; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 5.0 - */ -class EnumArgumentsProviderTests { - - private ExtensionContext extensionContext = mock(); - - @Test - void providesAllEnumConstants() { - var arguments = provideArguments(EnumWithTwoConstants.class); - - assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); - } - - @Test - void provideSingleEnumConstant() { - var arguments = provideArguments(EnumWithTwoConstants.class, "FOO"); - - assertThat(arguments).containsExactly(new Object[] { FOO }); - } - - @Test - void provideAllEnumConstantsWithNamingAll() { - var arguments = provideArguments(EnumWithTwoConstants.class, "FOO", "BAR"); - - assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); - } - - @Test - void duplicateConstantNameIsDetected() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, "FOO", "BAR", "FOO")); - assertThat(exception).hasMessageContaining("Duplicate enum constant name(s) found"); - } - - @Test - void invalidConstantNameIsDetected() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, "FO0", "B4R")); - assertThat(exception).hasMessageContaining("Invalid enum constant name(s) in"); - } - - @Test - void invalidPatternIsDetected() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, Mode.MATCH_ALL, "(", ")")); - assertThat(exception).hasMessageContaining("Pattern compilation failed"); - } - - @Test - void providesEnumConstantsBasedOnTestMethod() throws Exception { - when(extensionContext.getRequiredTestMethod()).thenReturn( - TestCase.class.getDeclaredMethod("methodWithCorrectParameter", EnumWithTwoConstants.class)); - - var arguments = provideArguments(NullEnum.class); - - assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); - } - - @Test - void incorrectParameterTypeIsDetected() throws Exception { - when(extensionContext.getRequiredTestMethod()).thenReturn( - TestCase.class.getDeclaredMethod("methodWithIncorrectParameter", Object.class)); - - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(NullEnum.class)); - assertThat(exception).hasMessageStartingWith("First parameter must reference an Enum type"); - } - - @Test - void methodsWithoutParametersAreDetected() throws Exception { - when(extensionContext.getRequiredTestMethod()).thenReturn( - TestCase.class.getDeclaredMethod("methodWithoutParameters")); - - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(NullEnum.class)); - assertThat(exception).hasMessageStartingWith("Test method must declare at least one parameter"); - } - - static class TestCase { - void methodWithCorrectParameter(EnumWithTwoConstants parameter) { - } - - void methodWithIncorrectParameter(Object parameter) { - } - - void methodWithoutParameters() { - } - } - - enum EnumWithTwoConstants { - FOO, BAR - } - - private > Stream provideArguments(Class enumClass, String... names) { - return provideArguments(enumClass, Mode.INCLUDE, names); - } - - private > Stream provideArguments(Class enumClass, Mode mode, String... names) { - var annotation = mock(EnumSource.class); - when(annotation.value()).thenAnswer(invocation -> enumClass); - when(annotation.mode()).thenAnswer(invocation -> mode); - when(annotation.names()).thenAnswer(invocation -> names); - when(annotation.toString()).thenReturn(String.format("@EnumSource(value=%s.class, mode=%s, names=%s)", - enumClass.getSimpleName(), mode, Arrays.toString(names))); - - var provider = new EnumArgumentsProvider(); - provider.accept(annotation); - return provider.provideArguments(extensionContext).map(Arguments::get); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java deleted file mode 100644 index f36a3140..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static java.util.stream.Collectors.toSet; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; -import static org.junit.jupiter.params.provider.EnumSource.Mode.INCLUDE; -import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; -import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ANY; -import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_NONE; -import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.BAR; -import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.BAZ; -import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.FOO; - -import java.util.EnumSet; -import java.util.Set; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -/** - * @since 5.0 - */ -class EnumSourceTests { - - @Test - void includeNamesWithAll() { - assertAll("include names with all", // - () -> assertTrue(INCLUDE.select(FOO, allOf(EnumWithThreeConstants::name))), - () -> assertTrue(INCLUDE.select(BAR, allOf(EnumWithThreeConstants::name))), - () -> assertTrue(INCLUDE.select(BAZ, allOf(EnumWithThreeConstants::name))) // - ); - } - - @Test - void includeNamesWithSingleton() { - assertAll("include names with singleton", // - () -> assertTrue(INCLUDE.select(FOO, Set.of(FOO.name()))), - () -> assertTrue(INCLUDE.select(BAR, Set.of(BAR.name()))), - () -> assertTrue(INCLUDE.select(BAZ, Set.of(BAZ.name()))) // - ); - assertAll("include names with singleton complement", // - () -> assertFalse(INCLUDE.select(BAR, Set.of(FOO.name()))), - () -> assertFalse(INCLUDE.select(BAZ, Set.of(FOO.name()))) // - ); - } - - @Test - void excludeNames() { - assertAll("exclude name with none excluded", // - () -> assertTrue(EXCLUDE.select(FOO, Set.of())), // - () -> assertTrue(EXCLUDE.select(BAR, Set.of())), // - () -> assertTrue(EXCLUDE.select(BAZ, Set.of())) // - ); - assertAll("exclude name with FOO excluded", // - () -> assertFalse(EXCLUDE.select(FOO, Set.of(FOO.name()))), - () -> assertTrue(EXCLUDE.select(BAR, Set.of(FOO.name()))), - () -> assertTrue(EXCLUDE.select(BAZ, Set.of(FOO.name()))) // - ); - } - - @Test - void matchesAll() { - assertAll("matches all", // - () -> assertTrue(MATCH_ALL.select(FOO, Set.of("F.."))), - () -> assertTrue(MATCH_ALL.select(BAR, Set.of("B.."))), - () -> assertTrue(MATCH_ALL.select(BAZ, Set.of("B.."))) // - ); - assertAll("matches all fails if not all match", // - () -> assertFalse(MATCH_ALL.select(FOO, Set.of("F..", "."))), - () -> assertFalse(MATCH_ALL.select(BAR, Set.of("B..", "."))), - () -> assertFalse(MATCH_ALL.select(BAZ, Set.of("B..", "."))) // - ); - } - - @Test - void matchesAny() { - assertAll("matches any", // - () -> assertTrue(MATCH_ANY.select(FOO, Set.of("B..", "^F.*"))), - () -> assertTrue(MATCH_ANY.select(BAR, Set.of("B", "B.", "B.."))), - () -> assertTrue(MATCH_ANY.select(BAZ, Set.of("^.+[zZ]$")))); - } - - @Test - void matchesNone() { - assertAll("matches none fails if any match", // - () -> assertFalse(MATCH_NONE.select(FOO, Set.of("F.."))), - () -> assertFalse(MATCH_NONE.select(FOO, Set.of("B..", "F.."))), - () -> assertFalse(MATCH_NONE.select(BAZ, Set.of("B.", "F.", "^.+[zZ]$")))); - - assertAll("matches none", // - () -> assertTrue(MATCH_NONE.select(FOO, Set.of())), // - () -> assertTrue(MATCH_NONE.select(FOO, Set.of("F."))), - () -> assertTrue(MATCH_NONE.select(FOO, Set.of("B.."))), - () -> assertTrue(MATCH_NONE.select(BAZ, Set.of(".", "B.", "F.")))); - } - - enum EnumWithThreeConstants { - FOO, BAR, BAZ; - - } - - static Set allOf(Function mapper) { - return EnumSet.allOf(EnumWithThreeConstants.class).stream().map(mapper).collect(toSet()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java deleted file mode 100644 index 1bd49479..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java +++ /dev/null @@ -1,998 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.DoubleStream; -import java.util.stream.IntStream; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 5.0 - */ -class MethodArgumentsProviderTests { - - private MutableExtensionRegistry extensionRegistry; - - @Test - void providesArgumentsUsingStream() { - var arguments = provideArguments("stringStreamProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingDoubleStream() { - var arguments = provideArguments("doubleStreamProvider"); - - assertThat(arguments).containsExactly(array(1.2), array(3.4)); - } - - @Test - void providesArgumentsUsingLongStream() { - var arguments = provideArguments("longStreamProvider"); - - assertThat(arguments).containsExactly(array(1L), array(2L)); - } - - @Test - void providesArgumentsUsingIntStream() { - var arguments = provideArguments("intStreamProvider"); - - assertThat(arguments).containsExactly(array(1), array(2)); - } - - /** - * @since 5.3.2 - */ - @Test - void providesArgumentsUsingStreamOfIntArrays() { - var arguments = provideArguments("intArrayStreamProvider"); - - assertThat(arguments).containsExactly( // - new Object[] { new int[] { 1, 2 } }, // - new Object[] { new int[] { 3, 4 } } // - ); - } - - /** - * @since 5.3.2 - */ - @Test - void providesArgumentsUsingStreamOfTwoDimensionalIntArrays() { - var arguments = provideArguments("twoDimensionalIntArrayStreamProvider"); - - assertThat(arguments).containsExactly( // - array((Object) new int[][] { { 1, 2 }, { 2, 3 } }), // - array((Object) new int[][] { { 4, 5 }, { 5, 6 } }) // - ); - } - - @Test - void providesArgumentsUsingStreamOfObjectArrays() { - var arguments = provideArguments("objectArrayStreamProvider"); - - assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); - } - - /** - * @since 5.3.2 - */ - @Test - void providesArgumentsUsingStreamOfTwoDimensionalObjectArrays() { - var arguments = provideArguments("twoDimensionalObjectArrayStreamProvider"); - - assertThat(arguments).containsExactly( // - array((Object) array(array("a", 1), array("b", 2))), // - array((Object) array(array("c", 3), array("d", 4))) // - ); - } - - @Test - void providesArgumentsUsingStreamOfArguments() { - var arguments = provideArguments("argumentsStreamProvider"); - - assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); - } - - @Test - void providesArgumentsUsingIterable() { - var arguments = provideArguments("stringIterableProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingIterator() { - var arguments = provideArguments("stringIteratorProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingMultipleFactoryMethods() { - var arguments = provideArguments("stringStreamProvider", "stringIterableProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar"), array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingIterableOfObjectArrays() { - var arguments = provideArguments("objectArrayIterableProvider"); - - assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); - } - - @Test - void providesArgumentsUsingListOfStrings() { - var arguments = provideArguments("stringArrayListProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingListOfObjectArrays() { - var arguments = provideArguments("objectArrayListProvider"); - - assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); - } - - @Test - void throwsExceptionWhenNonStaticFactoryMethodIsReferencedAndStaticIsRequired() { - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(NonStaticTestCase.class, null, false, "nonStaticStringStreamProvider").toArray()); - - assertThat(exception).hasMessageContaining("Cannot invoke non-static method"); - } - - @Test - void providesArgumentsFromNonStaticFactoryMethodWhenStaticIsNotRequired() { - var arguments = provideArguments(NonStaticTestCase.class, null, true, "nonStaticStringStreamProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingDefaultFactoryMethodName() { - var testClass = DefaultFactoryMethodNameTestCase.class; - var methodName = "testDefaultFactoryMethodName"; - var testMethod = findMethod(testClass, methodName, String.class).get(); - - var arguments = provideArguments(testClass, testMethod, false, ""); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingExternalFactoryMethod() { - var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "#stringsProvider"); - - assertThat(arguments).containsExactly(array("string1"), array("string2")); - } - - @Test - void providesArgumentsUsingExternalFactoryMethodWithParentheses() { - var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "#stringsProvider()"); - - assertThat(arguments).containsExactly(array("string1"), array("string2")); - } - - @Test - void providesArgumentsUsingExternalFactoryMethodFromStaticNestedClass() { - var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "$Nested#stringsProvider()"); - - assertThat(arguments).containsExactly(array("nested string1"), array("nested string2")); - } - - @Test - void providesArgumentsUsingExternalAndInternalFactoryMethodsCombined() { - var arguments = provideArguments("stringStreamProvider", - ExternalFactoryMethods.class.getName() + "#stringsProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar"), array("string1"), array("string2")); - } - - @Nested - class PrimitiveArrays { - - @Test - void providesArgumentsUsingBooleanArray() { - var arguments = provideArguments("booleanArrayProvider"); - - assertThat(arguments).containsExactly(array(Boolean.TRUE), array(Boolean.FALSE)); - } - - @Test - void providesArgumentsUsingByteArray() { - var arguments = provideArguments("byteArrayProvider"); - - assertThat(arguments).containsExactly(array((byte) 1), array(Byte.MIN_VALUE)); - } - - @Test - void providesArgumentsUsingCharArray() { - var arguments = provideArguments("charArrayProvider"); - - assertThat(arguments).containsExactly(array((char) 1), array(Character.MIN_VALUE)); - } - - @Test - void providesArgumentsUsingDoubleArray() { - var arguments = provideArguments("doubleArrayProvider"); - - assertThat(arguments).containsExactly(array(1d), array(Double.MIN_VALUE)); - } - - @Test - void providesArgumentsUsingFloatArray() { - var arguments = provideArguments("floatArrayProvider"); - - assertThat(arguments).containsExactly(array(1f), array(Float.MIN_VALUE)); - } - - @Test - void providesArgumentsUsingIntArray() { - var arguments = provideArguments("intArrayProvider"); - - assertThat(arguments).containsExactly(array(47), array(Integer.MIN_VALUE)); - } - - @Test - void providesArgumentsUsingLongArray() { - var arguments = provideArguments("longArrayProvider"); - - assertThat(arguments).containsExactly(array(47L), array(Long.MIN_VALUE)); - } - - @Test - void providesArgumentsUsingShortArray() { - var arguments = provideArguments("shortArrayProvider"); - - assertThat(arguments).containsExactly(array((short) 47), array(Short.MIN_VALUE)); - } - - } - - @Nested - class ObjectArrays { - - @Test - void providesArgumentsUsingObjectArray() { - var arguments = provideArguments("objectArrayProvider"); - - assertThat(arguments).containsExactly(array(42), array("bar")); - } - - @Test - void providesArgumentsUsingStringArray() { - var arguments = provideArguments("stringArrayProvider"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsing2dObjectArray() { - var arguments = provideArguments("twoDimensionalObjectArrayProvider"); - - assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); - } - - } - - @Nested - class ParameterResolution { - - private final Method testMethod = findMethod(TestCase.class, "test").get(); - - @BeforeEach - void registerParameterResolver() { - extensionRegistry = createRegistryWithDefaultExtensions(mock()); - extensionRegistry.registerExtension(StringResolver.class); - extensionRegistry.registerExtension(StringArrayResolver.class); - extensionRegistry.registerExtension(IntArrayResolver.class); - } - - @Test - void providesArgumentsInferringDefaultFactoryMethodThatAcceptsArgument() { - Method testMethod = findMethod(TestCase.class, "overloadedStringStreamProvider", Object.class).get(); - String factoryMethodName = ""; // signals to use default - var arguments = provideArguments(testMethod, factoryMethodName); - - assertThat(arguments).containsExactly(array("foo!"), array("bar!")); - } - - @Test - void providesArgumentsUsingSimpleNameForFactoryMethodThatAcceptsArgumentWithoutSpecifyingParameterList() { - var arguments = provideArguments("stringStreamProviderWithParameter"); - - assertThat(arguments).containsExactly(array("foo!"), array("bar!")); - } - - @Test - void providesArgumentsUsingFullyQualifiedNameForFactoryMethodThatAcceptsArgumentWithoutSpecifyingParameterList() { - var arguments = provideArguments(TestCase.class.getName() + "#stringStreamProviderWithParameter"); - - assertThat(arguments).containsExactly(array("foo!"), array("bar!")); - } - - @Test - void providesArgumentsUsingFullyQualifiedNameSpecifyingParameter() { - var arguments = provideArguments( - TestCase.class.getName() + "#stringStreamProviderWithParameter(java.lang.String)"); - - assertThat(arguments).containsExactly(array("foo!"), array("bar!")); - } - - @Test - void providesArgumentsUsingLocalQualifiedNameSpecifyingParameter() { - var arguments = provideArguments(testMethod, "stringStreamProviderWithParameter(java.lang.String)"); - - assertThat(arguments).containsExactly(array("foo!"), array("bar!")); - } - - @Test - void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodSpecifyingEmptyParameterList() { - var arguments = provideArguments( - TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter()"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodSpecifyingEmptyParameterList() { - var arguments = provideArguments(this.testMethod, "stringStreamProviderWithOrWithoutParameter()"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodSpecifyingParameter() { - var arguments = provideArguments( - TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter(java.lang.String)"); - - assertThat(arguments).containsExactly(array("foo!"), array("bar!")); - } - - @Test - void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodSpecifyingParameter() { - var arguments = provideArguments(testMethod, - "stringStreamProviderWithOrWithoutParameter(java.lang.String)"); - - assertThat(arguments).containsExactly(array("foo!"), array("bar!")); - } - - @Test - void failsToProvideArgumentsUsingFullyQualifiedNameSpecifyingInvalidParameterType() { - String method = TestCase.class.getName() + "#stringStreamProviderWithParameter(example.FooBar)"; - var exception = assertThrows(JUnitException.class, () -> provideArguments(method).toArray()); - - assertThat(exception).hasMessage(""" - Failed to load parameter type [example.FooBar] for method [stringStreamProviderWithParameter] \ - in class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]."""); - } - - @Test - void failsToProvideArgumentsUsingLocalQualifiedNameSpecifyingInvalidParameterType() { - var method = "stringStreamProviderWithParameter(example.FooBar)"; - var exception = assertThrows(JUnitException.class, - () -> provideArguments(this.testMethod, method).toArray()); - - assertThat(exception).hasMessage(""" - Failed to load parameter type [example.FooBar] for method [stringStreamProviderWithParameter] \ - in class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]."""); - } - - @Test - void failsToProvideArgumentsUsingFullyQualifiedNameSpecifyingIncorrectParameterType() { - String method = TestCase.class.getName() + "#stringStreamProviderWithParameter(java.lang.Integer)"; - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(method).toArray()); - - assertThat(exception).hasMessage(""" - Could not find factory method [stringStreamProviderWithParameter(java.lang.Integer)] in \ - class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]"""); - } - - @Test - void failsToProvideArgumentsUsingLocalQualifiedNameSpecifyingIncorrectParameterType() { - var method = "stringStreamProviderWithParameter(java.lang.Integer)"; - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(this.testMethod, method).toArray()); - - assertThat(exception).hasMessage(""" - Could not find factory method [stringStreamProviderWithParameter(java.lang.Integer)] in \ - class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]"""); - } - - @ParameterizedTest - @ValueSource(strings = { - "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter(java.lang.String[])", - "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter([Ljava.lang.String;)", }) - void providesArgumentsUsingFullyQualifiedNameSpecifyingObjectArrayParameter(String method) { - var arguments = provideArguments(method); - - assertThat(arguments).containsExactly(array("foo :)"), array("bar :)")); - } - - @ParameterizedTest - @ValueSource(strings = { // - "stringStreamProviderWithArrayParameter(java.lang.String[])", - "stringStreamProviderWithArrayParameter([Ljava.lang.String;)" }) - void providesArgumentsUsingLocalQualifiedNameSpecifyingObjectArrayParameter(String method) { - var arguments = provideArguments(this.testMethod, method); - - assertThat(arguments).containsExactly(array("foo :)"), array("bar :)")); - } - - @ParameterizedTest - @ValueSource(strings = { - "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter(int[])", - "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter([I)", }) - void providesArgumentsUsingFullyQualifiedNameSpecifyingPrimitiveArrayParameter(String method) { - var arguments = provideArguments(method); - - assertThat(arguments).containsExactly(array("foo 42"), array("bar 42")); - } - - @ParameterizedTest - @ValueSource(strings = { // - "stringStreamProviderWithArrayParameter(int[])", // - "stringStreamProviderWithArrayParameter([I)" }) - void providesArgumentsUsingLocalQualifiedNameSpecifyingPrimitiveArrayParameter(String method) { - var arguments = provideArguments(this.testMethod, method); - - assertThat(arguments).containsExactly(array("foo 42"), array("bar 42")); - } - - @ParameterizedTest - @ValueSource(strings = { "java.lang.String,java.lang.String", "java.lang.String, java.lang.String", - "java.lang.String, java.lang.String" }) - void providesArgumentsUsingFullyQualifiedNameSpecifyingMultipleParameters(String params) { - var method = TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter(" + params + ")"; - var arguments = provideArguments(method); - - assertThat(arguments).containsExactly(array("foo!!"), array("bar!!")); - } - - @ParameterizedTest - @ValueSource(strings = { "java.lang.String,java.lang.String", "java.lang.String, java.lang.String", - "java.lang.String, java.lang.String" }) - void providesArgumentsUsingLocalQualifiedNameSpecifyingMultipleParameters(String params) { - var arguments = provideArguments(this.testMethod, - "stringStreamProviderWithOrWithoutParameter(" + params + ")"); - - assertThat(arguments).containsExactly(array("foo!!"), array("bar!!")); - } - - @Test - void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodWhenParameterListIsNotSpecified() { - var arguments = provideArguments(TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter"); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - @Test - void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodWhenParameterListIsNotSpecified() { - var arguments = provideArguments("stringStreamProviderWithOrWithoutParameter").toArray(); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - } - - @Nested - class ErrorCases { - - @Test - void throwsExceptionWhenFullyQualifiedMethodNameSyntaxIsInvalid() { - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments("org.example.wrongSyntax").toArray()); - - assertThat(exception.getMessage()).isEqualTo( - "[org.example.wrongSyntax] is not a valid fully qualified method name: " - + "it must start with a fully qualified class name followed by a '#' and then the method name, " - + "optionally followed by a parameter list enclosed in parentheses."); - } - - @Test - void throwsExceptionWhenClassForExternalFactoryMethodCannotBeLoaded() { - var exception = assertThrows(JUnitException.class, - () -> provideArguments("com.example.NonExistentClass#stringsProvider").toArray()); - - assertThat(exception.getMessage()).isEqualTo("Could not load class [com.example.NonExistentClass]"); - } - - @Test - void throwsExceptionWhenExternalFactoryMethodDoesNotExist() { - String factoryClass = ExternalFactoryMethods.class.getName(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(factoryClass + "#nonExistentMethod").toArray()); - - assertThat(exception.getMessage()).isEqualTo( - "Could not find factory method [nonExistentMethod] in class [%s]", factoryClass); - } - - @Test - void throwsExceptionWhenLocalFactoryMethodDoesNotExist() { - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments("nonExistentMethod").toArray()); - - assertThat(exception.getMessage()).isEqualTo( - "Could not find factory method [nonExistentMethod] in class [%s]", TestCase.class.getName()); - } - - @Test - void throwsExceptionWhenExternalFactoryMethodAcceptingSingleArgumentDoesNotExist() { - String factoryClass = ExternalFactoryMethods.class.getName(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(factoryClass + "#nonExistentMethod(int)").toArray()); - - assertThat(exception.getMessage()).isEqualTo( - "Could not find factory method [nonExistentMethod(int)] in class [%s]", factoryClass); - } - - @Test - void throwsExceptionWhenLocalFactoryMethodAcceptingSingleArgumentDoesNotExist() { - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments("nonExistentMethod(int)").toArray()); - - assertThat(exception.getMessage()).isEqualTo( - "Could not find factory method [nonExistentMethod(int)] in class [%s]", TestCase.class.getName()); - } - - @Test - void throwsExceptionWhenExternalFactoryMethodAcceptingMultipleArgumentsDoesNotExist() { - String factoryClass = ExternalFactoryMethods.class.getName(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(factoryClass + "#nonExistentMethod(int, java.lang.String)").toArray()); - - assertThat(exception.getMessage()).isEqualTo( - "Could not find factory method [nonExistentMethod(int, java.lang.String)] in class [%s]", factoryClass); - } - - @Test - void throwsExceptionWhenLocalFactoryMethodAcceptingMultipleArgumentsDoesNotExist() { - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments("nonExistentMethod(java.lang.String,int)").toArray()); - - assertThat(exception.getMessage()).isEqualTo( - "Could not find factory method [nonExistentMethod(java.lang.String,int)] in class [%s]", - TestCase.class.getName()); - } - - @Test - void throwsExceptionWhenExternalFactoryMethodHasInvalidReturnType() { - String testClass = TestCase.class.getName(); - String factoryClass = ExternalFactoryMethods.class.getName(); - String factoryMethod = factoryClass + "#factoryWithInvalidReturnType"; - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(TestCase.class, null, false, factoryMethod).toArray()); - - assertThat(exception.getMessage())// - .containsSubsequence("Could not find valid factory method [" + factoryMethod + "] for test class [", - testClass + "]", // - "but found the following invalid candidate: ", - "static java.lang.Object " + factoryClass + ".factoryWithInvalidReturnType()"); - } - - @Test - void throwsExceptionWhenLocalFactoryMethodHasInvalidReturnType() { - String testClass = TestCase.class.getName(); - String factoryClass = testClass; - String factoryMethod = "factoryWithInvalidReturnType"; - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(factoryMethod).toArray()); - - assertThat(exception.getMessage())// - .containsSubsequence("Could not find valid factory method [" + factoryMethod + "] for test class [", - factoryClass + "]", // - "but found the following invalid candidate: ", // - "static java.lang.Object " + factoryClass + ".factoryWithInvalidReturnType()"); - } - - @Test - void throwsExceptionWhenMultipleDefaultFactoryMethodCandidatesExist() { - var testClass = MultipleDefaultFactoriesTestCase.class; - var methodName = "test"; - var testMethod = findMethod(testClass, methodName, String.class).get(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(testClass, testMethod, false, "").toArray()); - - assertThat(exception.getMessage()).contains(// - "2 factory methods named [test] were found in class [", testClass.getName() + "]: ", // - "$MultipleDefaultFactoriesTestCase.test()", // - "$MultipleDefaultFactoriesTestCase.test(int)"// - ); - } - - @Test - void throwsExceptionWhenMultipleInvalidDefaultFactoryMethodCandidatesExist() { - var testClass = MultipleInvalidDefaultFactoriesTestCase.class; - var methodName = "test"; - var testMethod = findMethod(testClass, methodName, String.class).get(); - - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(testClass, testMethod, false, "").toArray()); - - assertThat(exception.getMessage()).contains(// - "Could not find valid factory method [test] in class [", testClass.getName() + "]", // - "but found the following invalid candidates: ", // - "$MultipleInvalidDefaultFactoriesTestCase.test()", // - "$MultipleInvalidDefaultFactoriesTestCase.test(int)"// - ); - } - - } - - // ------------------------------------------------------------------------- - - private static Object[] array(Object... objects) { - return objects; - } - - private Stream provideArguments(String... factoryMethodNames) { - return provideArguments(TestCase.class, null, false, factoryMethodNames); - } - - private Stream provideArguments(Method testMethod, String factoryMethodName) { - return provideArguments(TestCase.class, testMethod, false, factoryMethodName); - } - - private Stream provideArguments(Class testClass, Method testMethod, boolean allowNonStaticMethod, - String... factoryMethodNames) { - - if (testMethod == null) { - try { - class DummyClass { - @SuppressWarnings("unused") - public void dummyMethod() { - }; - } - // ensure we have a non-null method, even if it's not a real test method. - testMethod = DummyClass.class.getMethod("dummyMethod"); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - var methodSource = mock(MethodSource.class); - - when(methodSource.value()).thenReturn(factoryMethodNames); - - var extensionContext = mock(ExtensionContext.class); - when(extensionContext.getTestClass()).thenReturn(Optional.of(testClass)); - when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); - when(extensionContext.getExecutableInvoker()).thenReturn( - new DefaultExecutableInvoker(extensionContext, extensionRegistry)); - - doCallRealMethod().when(extensionContext).getRequiredTestMethod(); - doCallRealMethod().when(extensionContext).getRequiredTestClass(); - - var testInstance = allowNonStaticMethod ? ReflectionUtils.newInstance(testClass) : null; - when(extensionContext.getTestInstance()).thenReturn(Optional.ofNullable(testInstance)); - - var provider = new MethodArgumentsProvider(); - provider.accept(methodSource); - return provider.provideArguments(extensionContext).map(Arguments::get); - } - - // ------------------------------------------------------------------------- - - static class DefaultFactoryMethodNameTestCase { - - // Test - void testDefaultFactoryMethodName(String param) { - } - - // Factory - static Stream testDefaultFactoryMethodName() { - return Stream.of("foo", "bar"); - } - } - - static class MultipleDefaultFactoriesTestCase { - - // Test - void test(String param) { - } - - // Factory - static Stream test() { - return Stream.of(); - } - - // Another Factory - static Stream test(int num) { - return Stream.of(); - } - } - - static class MultipleInvalidDefaultFactoriesTestCase { - - // Test - void test(String param) { - } - - // NOT a Factory - static String test() { - return null; - } - - // Also NOT a Factory - static Object test(int num) { - return null; - } - } - - static class TestCase { - - void test() { - } - - // --- Invalid --------------------------------------------------------- - - static Object factoryWithInvalidReturnType() { - return -1; - } - - // --- Stream ---------------------------------------------------------- - - static Stream stringStreamProvider() { - return Stream.of("foo", "bar"); - } - - static Stream stringStreamProviderWithParameter(String parameter) { - return Stream.of("foo" + parameter, "bar" + parameter); - } - - static Stream stringStreamProviderWithArrayParameter(String[] parameter) { - String suffix = Arrays.stream(parameter).collect(Collectors.joining()); - return Stream.of("foo " + suffix, "bar " + suffix); - } - - static Stream stringStreamProviderWithArrayParameter(int[] parameter) { - return stringStreamProviderWithArrayParameter( - Arrays.stream(parameter).mapToObj(String::valueOf).toArray(String[]::new)); - } - - static Stream stringStreamProviderWithOrWithoutParameter() { - return stringStreamProvider(); - } - - static Stream stringStreamProviderWithOrWithoutParameter(String parameter) { - return stringStreamProviderWithParameter(parameter); - } - - static Stream stringStreamProviderWithOrWithoutParameter(String parameter1, String parameter2) { - return stringStreamProviderWithParameter(parameter1 + parameter2); - } - - // Overloaded method, but not a valid return type for a factory method - static void stringStreamProviderWithOrWithoutParameter(String parameter1, int parameter2) { - } - - // @ParameterizedTest - // @MethodSource // use default, inferred factory method - void overloadedStringStreamProvider(Object parameter) { - // test implementation - } - - // Default factory method for overloadedStringStreamProvider(Object) - static Stream overloadedStringStreamProvider(String parameter) { - return stringStreamProviderWithParameter(parameter); - } - - static DoubleStream doubleStreamProvider() { - return DoubleStream.of(1.2, 3.4); - } - - static LongStream longStreamProvider() { - return LongStream.of(1L, 2L); - } - - static IntStream intStreamProvider() { - return IntStream.of(1, 2); - } - - static Stream intArrayStreamProvider() { - return Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); - } - - static Stream twoDimensionalIntArrayStreamProvider() { - return Stream.of(new int[][] { { 1, 2 }, { 2, 3 } }, new int[][] { { 4, 5 }, { 5, 6 } }); - } - - static Stream objectArrayStreamProvider() { - return Stream.of(new Object[] { "foo", 42 }, new Object[] { "bar", 23 }); - } - - static Stream twoDimensionalObjectArrayStreamProvider() { - return Stream.of(new Object[][] { { "a", 1 }, { "b", 2 } }, new Object[][] { { "c", 3 }, { "d", 4 } }); - } - - static Stream argumentsStreamProvider() { - return objectArrayStreamProvider().map(Arguments::of); - } - - // --- Iterable / Collection ------------------------------------------- - - static Iterable stringIterableProvider() { - return TestCase::stringIteratorProvider; - } - - static Iterable objectArrayIterableProvider() { - return objectArrayListProvider(); - } - - static List stringArrayListProvider() { - return Arrays.asList("foo", "bar"); - } - - static List objectArrayListProvider() { - return Arrays.asList(array("foo", 42), array("bar", 23)); - } - - // --- Iterator -------------------------------------------------------- - - static Iterator stringIteratorProvider() { - return Arrays.asList("foo", "bar").iterator(); - } - - // --- Array of primitives --------------------------------------------- - - static boolean[] booleanArrayProvider() { - return new boolean[] { true, false }; - } - - static byte[] byteArrayProvider() { - return new byte[] { (byte) 1, Byte.MIN_VALUE }; - } - - static char[] charArrayProvider() { - return new char[] { (char) 1, Character.MIN_VALUE }; - } - - static double[] doubleArrayProvider() { - return new double[] { 1d, Double.MIN_VALUE }; - } - - static float[] floatArrayProvider() { - return new float[] { 1f, Float.MIN_VALUE }; - } - - static int[] intArrayProvider() { - return new int[] { 47, Integer.MIN_VALUE }; - } - - static long[] longArrayProvider() { - return new long[] { 47L, Long.MIN_VALUE }; - } - - static short[] shortArrayProvider() { - return new short[] { (short) 47, Short.MIN_VALUE }; - } - - // --- Array of objects ------------------------------------------------ - - static Object[] objectArrayProvider() { - return new Object[] { 42, "bar" }; - } - - static String[] stringArrayProvider() { - return new String[] { "foo", "bar" }; - } - - static Object[][] twoDimensionalObjectArrayProvider() { - return new Object[][] { { "foo", 42 }, { "bar", 23 } }; - } - - } - - // This test case mimics @TestInstance(Lifecycle.PER_CLASS) - static class NonStaticTestCase { - - Stream nonStaticStringStreamProvider() { - return Stream.of("foo", "bar"); - } - } - - static class ExternalFactoryMethods { - - static Object factoryWithInvalidReturnType() { - return -1; - } - - static Stream stringsProvider() { - return Stream.of("string1", "string2"); - } - - static class Nested { - - static Stream stringsProvider() { - return Stream.of("nested string1", "nested string2"); - } - } - } - - static class StringResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == String.class; - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return "!"; - } - } - - static class StringArrayResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == String[].class; - } - - @Override - public String[] resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return new String[] { ":", ")" }; - } - } - - static class IntArrayResolver implements ParameterResolver { - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return parameterContext.getParameter().getType() == int[].class; - } - - @Override - public int[] resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return new int[] { 4, 2 }; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java deleted file mode 100644 index 629709cb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.annotation.Annotation; - -/** - * @since 5.6 - */ -abstract class MockCsvAnnotationBuilder> { - - static CsvSource csvSource(String... lines) { - return csvSource().lines(lines).build(); - } - - static MockCsvSourceBuilder csvSource() { - return new MockCsvSourceBuilder(); - } - - static MockCsvFileSourceBuilder csvFileSource() { - return new MockCsvFileSourceBuilder(); - } - - // ------------------------------------------------------------------------- - - private boolean useHeadersInDisplayName = false; - private char quoteCharacter = '\0'; - protected char delimiter = '\0'; - protected String delimiterString = ""; - protected String emptyValue = ""; - protected String[] nullValues = new String[0]; - protected int maxCharsPerColumn = 4096; - protected boolean ignoreLeadingAndTrailingWhitespace = true; - - private MockCsvAnnotationBuilder() { - } - - protected abstract B getSelf(); - - B useHeadersInDisplayName(boolean useHeadersInDisplayName) { - this.useHeadersInDisplayName = useHeadersInDisplayName; - return getSelf(); - } - - B quoteCharacter(char quoteCharacter) { - this.quoteCharacter = quoteCharacter; - return getSelf(); - } - - B delimiter(char delimiter) { - this.delimiter = delimiter; - return getSelf(); - } - - B delimiterString(String delimiterString) { - this.delimiterString = delimiterString; - return getSelf(); - } - - B emptyValue(String emptyValue) { - this.emptyValue = emptyValue; - return getSelf(); - } - - B nullValues(String... nullValues) { - this.nullValues = nullValues; - return getSelf(); - } - - B maxCharsPerColumn(int maxCharsPerColumn) { - this.maxCharsPerColumn = maxCharsPerColumn; - return getSelf(); - } - - B ignoreLeadingAndTrailingWhitespace(boolean ignoreLeadingAndTrailingWhitespace) { - this.ignoreLeadingAndTrailingWhitespace = ignoreLeadingAndTrailingWhitespace; - return getSelf(); - } - - abstract A build(); - - // ------------------------------------------------------------------------- - - static class MockCsvSourceBuilder extends MockCsvAnnotationBuilder { - - private String[] lines = new String[0]; - private String textBlock = ""; - - private MockCsvSourceBuilder() { - super.quoteCharacter = '\''; - } - - @Override - protected MockCsvSourceBuilder getSelf() { - return this; - } - - MockCsvSourceBuilder lines(String... lines) { - this.lines = lines; - return this; - } - - MockCsvSourceBuilder textBlock(String textBlock) { - this.textBlock = textBlock; - return this; - } - - @Override - CsvSource build() { - var annotation = mock(CsvSource.class); - - // Common - when(annotation.useHeadersInDisplayName()).thenReturn(super.useHeadersInDisplayName); - when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter); - when(annotation.delimiter()).thenReturn(super.delimiter); - when(annotation.delimiterString()).thenReturn(super.delimiterString); - when(annotation.emptyValue()).thenReturn(super.emptyValue); - when(annotation.nullValues()).thenReturn(super.nullValues); - when(annotation.maxCharsPerColumn()).thenReturn(super.maxCharsPerColumn); - when(annotation.ignoreLeadingAndTrailingWhitespace()).thenReturn(super.ignoreLeadingAndTrailingWhitespace); - - // @CsvSource - when(annotation.value()).thenReturn(this.lines); - when(annotation.textBlock()).thenReturn(this.textBlock); - - return annotation; - } - - } - - static class MockCsvFileSourceBuilder extends MockCsvAnnotationBuilder { - - private String[] resources = {}; - private String[] files = {}; - private String encoding = "UTF-8"; - private String lineSeparator = "\n"; - private int numLinesToSkip = 0; - - private MockCsvFileSourceBuilder() { - super.quoteCharacter = '"'; - } - - @Override - protected MockCsvFileSourceBuilder getSelf() { - return this; - } - - MockCsvFileSourceBuilder resources(String... resources) { - this.resources = resources; - return this; - } - - MockCsvFileSourceBuilder files(String... files) { - this.files = files; - return this; - } - - MockCsvFileSourceBuilder encoding(String encoding) { - this.encoding = encoding; - return this; - } - - MockCsvFileSourceBuilder lineSeparator(String lineSeparator) { - this.lineSeparator = lineSeparator; - return this; - } - - MockCsvFileSourceBuilder numLinesToSkip(int numLinesToSkip) { - this.numLinesToSkip = numLinesToSkip; - return this; - } - - @Override - CsvFileSource build() { - var annotation = mock(CsvFileSource.class); - - // Common - when(annotation.useHeadersInDisplayName()).thenReturn(super.useHeadersInDisplayName); - when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter); - when(annotation.delimiter()).thenReturn(super.delimiter); - when(annotation.delimiterString()).thenReturn(super.delimiterString); - when(annotation.emptyValue()).thenReturn(super.emptyValue); - when(annotation.nullValues()).thenReturn(super.nullValues); - when(annotation.maxCharsPerColumn()).thenReturn(super.maxCharsPerColumn); - when(annotation.ignoreLeadingAndTrailingWhitespace()).thenReturn(super.ignoreLeadingAndTrailingWhitespace); - - // @CsvFileSource - when(annotation.resources()).thenReturn(this.resources); - when(annotation.files()).thenReturn(this.files); - when(annotation.encoding()).thenReturn(this.encoding); - when(annotation.lineSeparator()).thenReturn(this.lineSeparator); - when(annotation.numLinesToSkip()).thenReturn(this.numLinesToSkip); - - return annotation; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java deleted file mode 100644 index b9416ba4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 5.0 - */ -class ValueArgumentsProviderTests { - - @Test - void multipleInputsAreNotAllowed() { - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(new short[1], new byte[0], new int[1], new long[0], new float[0], new double[0], - new char[0], new boolean[0], new String[0], new Class[0])); - - assertThat(exception).hasMessageContaining( - "Exactly one type of input must be provided in the @ValueSource annotation, but there were 2"); - } - - @Test - void onlyEmptyInputsAreNotAllowed() { - var exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], - new char[0], new boolean[0], new String[0], new Class[0])); - - assertThat(exception).hasMessageContaining( - "Exactly one type of input must be provided in the @ValueSource annotation, but there were 0"); - } - - /** - * @since 5.1 - */ - @Test - void providesShorts() { - var arguments = provideArguments(new short[] { 23, 42 }, new byte[0], new int[0], new long[0], new float[0], - new double[0], new char[0], new boolean[0], new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array((short) 23), array((short) 42)); - } - - /** - * @since 5.1 - */ - @Test - void providesBytes() { - var arguments = provideArguments(new short[0], new byte[] { 23, 42 }, new int[0], new long[0], new float[0], - new double[0], new char[0], new boolean[0], new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array((byte) 23), array((byte) 42)); - } - - @Test - void providesInts() { - var arguments = provideArguments(new short[0], new byte[0], new int[] { 23, 42 }, new long[0], new float[0], - new double[0], new char[0], new boolean[0], new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array(23), array(42)); - } - - @Test - void providesLongs() { - var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[] { 23, 42 }, new float[0], - new double[0], new char[0], new boolean[0], new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array(23L), array(42L)); - } - - /** - * @since 5.1 - */ - @Test - void providesFloats() { - var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], - new float[] { 23.32F, 42.24F }, new double[0], new char[0], new boolean[0], new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array(23.32F), array(42.24F)); - } - - @Test - void providesDoubles() { - var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], - new double[] { 23.32, 42.24 }, new char[0], new boolean[0], new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array(23.32), array(42.24)); - } - - /** - * @since 5.1 - */ - @Test - void providesChars() { - var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], - new double[0], new char[] { 'a', 'b', 'c' }, new boolean[0], new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array('a'), array('b'), array('c')); - } - - /** - * @since 5.5 - */ - @Test - void providesBooleans() { - var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], - new double[0], new char[0], new boolean[] { true, false }, new String[0], new Class[0]); - - assertThat(arguments).containsExactly(array(true), array(false)); - } - - @Test - void providesStrings() { - var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], - new double[0], new char[0], new boolean[0], new String[] { "foo", "bar" }, new Class[0]); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); - } - - /** - * @since 5.1 - */ - @Test - void providesClasses() { - var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], - new double[0], new char[0], new boolean[0], new String[0], new Class[] { Integer.class, getClass() }); - - assertThat(arguments).containsExactly(array(Integer.class), array(getClass())); - } - - private static Stream provideArguments(short[] shorts, byte[] bytes, int[] ints, long[] longs, - float[] floats, double[] doubles, char[] chars, boolean[] booleans, String[] strings, Class[] classes) { - - var annotation = mock(ValueSource.class); - when(annotation.shorts()).thenReturn(shorts); - when(annotation.bytes()).thenReturn(bytes); - when(annotation.ints()).thenReturn(ints); - when(annotation.longs()).thenReturn(longs); - when(annotation.floats()).thenReturn(floats); - when(annotation.doubles()).thenReturn(doubles); - when(annotation.chars()).thenReturn(chars); - when(annotation.booleans()).thenReturn(booleans); - when(annotation.strings()).thenReturn(strings); - when(annotation.classes()).thenReturn(classes); - - var provider = new ValueArgumentsProvider(); - provider.accept(annotation); - return provider.provideArguments(null).map(Arguments::get); - } - - private static Object[] array(Object... objects) { - return objects; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt deleted file mode 100644 index 829f98ae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.TestInfo -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource - -class ParameterizedTestNameFormatterIntegrationTests { - - @ValueSource(strings = ["foo", "bar"]) - @ParameterizedTest - fun defaultDisplayName(param: String, info: TestInfo) { - if (param.equals("foo")) { - assertEquals("[1] foo", info.displayName) - } else { - assertEquals("[2] bar", info.displayName) - } - } - - @ValueSource(strings = ["foo", "bar"]) - @ParameterizedTest(name = "{0}") - fun `1st argument`(param: String, info: TestInfo) { - if (param.equals("foo")) { - assertEquals("foo", info.displayName) - } else { - assertEquals("bar", info.displayName) - } - } - - @ValueSource(strings = ["foo", "bar"]) - @ParameterizedTest(name = "{displayName}") - fun `it's an {enigma} '{0}'`(@Suppress("UNUSED_PARAMETER") param: String, info: TestInfo) { - assertEquals("it's an {enigma} '{0}'(String, TestInfo)", info.displayName) - } - - @ValueSource(strings = ["foo", "bar"]) - @ParameterizedTest(name = "{displayName} - {0}") - fun `displayName and 1st 'argument'`(param: String, info: TestInfo) { - if (param.equals("foo")) { - assertEquals("displayName and 1st 'argument'(String, TestInfo) - foo", info.displayName) - } else { - assertEquals("displayName and 1st 'argument'(String, TestInfo) - bar", info.displayName) - } - } - - @ValueSource(strings = ["foo", "bar"]) - @ParameterizedTest(name = "{0} - {displayName}") - fun `1st 'argument' and displayName`(param: String, info: TestInfo) { - if (param.equals("foo")) { - assertEquals("foo - 1st 'argument' and displayName(String, TestInfo)", info.displayName) - } else { - assertEquals("bar - 1st 'argument' and displayName(String, TestInfo)", info.displayName) - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt deleted file mode 100644 index 17d9bb15..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package org.junit.jupiter.params.aggregator - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -/** - * Unit tests for using [ArgumentsAccessor] from Kotlin. - */ -class ArgumentsAccessorKotlinTests { - - @Test - fun `get() with reified type and index`() { - assertEquals(1, DefaultArgumentsAccessor(1).get(0)) - assertEquals('A', DefaultArgumentsAccessor('A').get(0)) - } - - @Test - fun `get() with reified type and index for incompatible type`() { - val exception = assertThrows { - DefaultArgumentsAccessor(Integer.valueOf(1)).get(0) - } - - assertThat(exception).hasMessage( - "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [java.lang.Character]." - ) - } - - @Test - fun `get() with index`() { - assertEquals(1, DefaultArgumentsAccessor(1).get(0)) - assertEquals('A', DefaultArgumentsAccessor('A').get(0)) - } - - @Test - fun `get() with index and class reference`() { - assertEquals(1, DefaultArgumentsAccessor(1).get(0, Integer::class.java)) - assertEquals('A', DefaultArgumentsAccessor('A').get(0, Character::class.java)) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt deleted file mode 100644 index 96b717ad..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -@file:Suppress("unused") - -package org.junit.jupiter.params.aggregator - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.TestInfo -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource - -// https://github.com/junit-team/junit5/issues/1836 -object DisplayNameTests { - @JvmStatic - fun data() = arrayOf( - arrayOf("A", 1), - arrayOf("B", 2), - arrayOf("C", 3), - arrayOf("", 4), // empty is okay - arrayOf(null, 5) // null was the problem - ) - - @ParameterizedTest - @MethodSource("data") - fun test(char: String?, number: Int, info: TestInfo) { - assertEquals("[$number] $char, $number", info.displayName) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv deleted file mode 100644 index c62f19df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/broken.csv +++ /dev/null @@ -1,2 +0,0 @@ -# more than 512 columns -x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x, \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv deleted file mode 100644 index 3931019b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/default-max-chars.csv +++ /dev/null @@ -1 +0,0 @@ -"41,6,8469,0,22,6336,9177,null,3,15,31,5734,6509,8770,9379,2,4,10,20,28,39,1490,5775,6410,6986,8493,8823,9298,9975,1,null,null,5,8,12,16,21,26,29,34,40,506,4491,5737,6034,6384,6434,6853,8178,8492,8503,8779,9106,9283,9339,9774,9982,null,null,null,null,7,9,11,13,null,17,null,null,23,27,null,30,32,35,null,null,176,837,3317,5715,5735,5743,5944,6310,6338,6398,6425,6436,6802,6917,7473,8397,8472,null,8494,8711,8776,8782,9009,9153,9210,9289,9336,9371,9400,9953,9980,9993,null,null,null,null,null,null,null,14,null,18,null,25,null,null,null,null,null,33,null,36,53,316,739,1409,3011,3924,4845,5730,null,5736,5741,5754,5807,5968,6056,6329,6337,6345,6387,6400,6420,6428,6435,6467,6575,6814,6911,6970,7111,7761,8283,8461,8470,8473,null,8496,8674,8722,8773,8778,8780,8803,8924,9078,9111,9172,9185,9270,9285,9296,9307,9337,9369,9376,9391,9633,9933,9967,9979,9981,9989,9994,null,null,null,19,24,null,null,null,null,37,46,140,199,505,669,814,1281,1430,1976,3096,3627,3934,4646,5455,5719,5733,null,null,5740,5742,5752,5758,5790,5828,5948,5974,6051,6278,6326,6331,null,null,6339,6365,6385,6388,6399,6405,6417,6424,6426,6433,null,null,6441,6492,6572,6739,6808,6845,6867,6915,6931,6985,7086,7463,7758,7797,8179,8349,8421,8467,null,8471,null,8476,8495,8497,8576,8708,8719,8761,8771,8774,8777,null,null,8781,8793,8806,8882,8937,9054,9084,9107,9134,9166,9175,9181,9187,9255,9282,9284,9287,9292,9297,9305,9319,null,9338,9342,9370,9375,9378,9380,9396,9546,9713,9798,9946,9956,9973,9978,null,null,null,9986,9991,null,9999,null,null,null,null,null,38,45,49,111,175,196,203,352,null,631,673,799,832,1056,1366,1415,1480,1819,2471,3016,3223,3532,3770,3931,4018,4592,4835,5233,5477,5718,5728,5732,null,5739,null,null,null,5745,5753,5756,5766,5785,5803,5823,5875,5946,5950,5971,5994,6043,6052,6124,6298,6317,6328,6330,6332,null,6341,6348,6369,null,6386,null,6396,null,null,6404,6408,6415,6419,6422,null,null,6427,6430,null,6440,6458,6471,6503,6520,6574,6721,6750,6807,6810,6843,6849,6863,6893,6912,6916,6919,6947,6983,null,6993,7088,7124,7466,7703,7759,7787,7962,null,8195,8331,8362,8411,8453,8462,8468,null,null,8474,8482,null,null,null,8500,8573,8619,8682,8710,8712,8721,8755,8767,null,8772,null,8775,null,null,null,null,8787,8795,8805,8819,8862,8886,8929,8978,9014,9076,9080,9087,null,9110,9112,9139,9163,9170,9173,9176,9178,9183,9186,9188,9240,9267,9278,null,null,null,9286,9288,9290,9293,null,null,9303,9306,9312,9326,null,null,9340,9366,null,null,9374,null,9377,null,null,9383,9393,9397,9493,9601,9652,9726,9789,9905,9942,9947,9954,9959,9969,9974,9976,null,9984,9988,9990,9992,9997,null,null,null,44,null,48,52,92,125,156,null,183,197,202,235,325,366,629,656,670,712,746,802,816,835,1026,1212,1293,1403,1414,1420,1471,1483,1604,1903,2432,2870,3014,3062,3219,3311,3336,3565,3730,3918,3927,3933,3975,4330,4546,4627,4709,4840,5158,5399,5456,5697,5716,null,5727,5729,5731,null,5738,null,5744,5746,null,null,5755,5757,5762,5774,5778,5786,5795,5805,5822,5824,5848,5895,5945,5947,5949,5961,5969,5973,5976,6014,6042,6049,null,6053,6117,6139,6290,6308,6314,6320,6327,null,null,null,null,6335,6340,6344,6346,6354,6366,6383,null,null,6390,6397,6401,null,6406,6409,6413,6416,6418,null,6421,6423,null,null,6429,6431,6438,null,6443,6462,6470,6472,6497,6506,6512,6528,6573,null,6648,6733,6748,6767,6805,null,6809,6813,6828,6844,6847,6852,6854,6864,6875,6910,null,6914,null,null,6918,6922,6944,6968,6976,6984,6991,7047,7087,7091,7112,7260,7464,7467,7656,7740,null,7760,7774,7795,7876,8110,8187,8202,8315,8341,8359,8391,8410,8414,8448,8455,null,8463,null,null,null,8475,8478,8483,8498,8502,8531,8575,8600,8624,8681,8688,8709,null,null,8716,8720,null,8735,8760,8766,8768,null,null,null,null,8784,8791,8794,8797,8804,null,8815,8820,8825,8867,8884,8887,8927,8936,8974,8986,9013,9041,9067,9077,9079,9081,9086,9093,9109,null,null,9119,9138,9144,9159,9164,9167,9171,null,9174,null,null,null,9179,9182,9184,null,null,null,9189,9223,9247,9258,9269,9275,9279,null,null,null,null,null,9291,null,9295,9301,9304,null,null,9310,9315,9324,9330,null,9341,9358,9367,9372,null,null,null,9382,9386,9392,9395,null,9398,9488,9509,9547,9616,9643,9702,94,122" diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv deleted file mode 100644 index 469411a2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv +++ /dev/null @@ -1 +0,0 @@ -"41,6,8469,0,22,6336,9177,null,3,15,31,5734,6509,8770,9379,2,4,10,20,28,39,1490,5775,6410,6986,8493,8823,9298,9975,1,null,null,5,8,12,16,21,26,29,34,40,506,4491,5737,6034,6384,6434,6853,8178,8492,8503,8779,9106,9283,9339,9774,9982,null,null,null,null,7,9,11,13,null,17,null,null,23,27,null,30,32,35,null,null,176,837,3317,5715,5735,5743,5944,6310,6338,6398,6425,6436,6802,6917,7473,8397,8472,null,8494,8711,8776,8782,9009,9153,9210,9289,9336,9371,9400,9953,9980,9993,null,null,null,null,null,null,null,14,null,18,null,25,null,null,null,null,null,33,null,36,53,316,739,1409,3011,3924,4845,5730,null,5736,5741,5754,5807,5968,6056,6329,6337,6345,6387,6400,6420,6428,6435,6467,6575,6814,6911,6970,7111,7761,8283,8461,8470,8473,null,8496,8674,8722,8773,8778,8780,8803,8924,9078,9111,9172,9185,9270,9285,9296,9307,9337,9369,9376,9391,9633,9933,9967,9979,9981,9989,9994,null,null,null,19,24,null,null,null,null,37,46,140,199,505,669,814,1281,1430,1976,3096,3627,3934,4646,5455,5719,5733,null,null,5740,5742,5752,5758,5790,5828,5948,5974,6051,6278,6326,6331,null,null,6339,6365,6385,6388,6399,6405,6417,6424,6426,6433,null,null,6441,6492,6572,6739,6808,6845,6867,6915,6931,6985,7086,7463,7758,7797,8179,8349,8421,8467,null,8471,null,8476,8495,8497,8576,8708,8719,8761,8771,8774,8777,null,null,8781,8793,8806,8882,8937,9054,9084,9107,9134,9166,9175,9181,9187,9255,9282,9284,9287,9292,9297,9305,9319,null,9338,9342,9370,9375,9378,9380,9396,9546,9713,9798,9946,9956,9973,9978,null,null,null,9986,9991,null,9999,null,null,null,null,null,38,45,49,111,175,196,203,352,null,631,673,799,832,1056,1366,1415,1480,1819,2471,3016,3223,3532,3770,3931,4018,4592,4835,5233,5477,5718,5728,5732,null,5739,null,null,null,5745,5753,5756,5766,5785,5803,5823,5875,5946,5950,5971,5994,6043,6052,6124,6298,6317,6328,6330,6332,null,6341,6348,6369,null,6386,null,6396,null,null,6404,6408,6415,6419,6422,null,null,6427,6430,null,6440,6458,6471,6503,6520,6574,6721,6750,6807,6810,6843,6849,6863,6893,6912,6916,6919,6947,6983,null,6993,7088,7124,7466,7703,7759,7787,7962,null,8195,8331,8362,8411,8453,8462,8468,null,null,8474,8482,null,null,null,8500,8573,8619,8682,8710,8712,8721,8755,8767,null,8772,null,8775,null,null,null,null,8787,8795,8805,8819,8862,8886,8929,8978,9014,9076,9080,9087,null,9110,9112,9139,9163,9170,9173,9176,9178,9183,9186,9188,9240,9267,9278,null,null,null,9286,9288,9290,9293,null,null,9303,9306,9312,9326,null,null,9340,9366,null,null,9374,null,9377,null,null,9383,9393,9397,9493,9601,9652,9726,9789,9905,9942,9947,9954,9959,9969,9974,9976,null,9984,9988,9990,9992,9997,null,null,null,44,null,48,52,92,125,156,null,183,197,202,235,325,366,629,656,670,712,746,802,816,835,1026,1212,1293,1403,1414,1420,1471,1483,1604,1903,2432,2870,3014,3062,3219,3311,3336,3565,3730,3918,3927,3933,3975,4330,4546,4627,4709,4840,5158,5399,5456,5697,5716,null,5727,5729,5731,null,5738,null,5744,5746,null,null,5755,5757,5762,5774,5778,5786,5795,5805,5822,5824,5848,5895,5945,5947,5949,5961,5969,5973,5976,6014,6042,6049,null,6053,6117,6139,6290,6308,6314,6320,6327,null,null,null,null,6335,6340,6344,6346,6354,6366,6383,null,null,6390,6397,6401,null,6406,6409,6413,6416,6418,null,6421,6423,null,null,6429,6431,6438,null,6443,6462,6470,6472,6497,6506,6512,6528,6573,null,6648,6733,6748,6767,6805,null,6809,6813,6828,6844,6847,6852,6854,6864,6875,6910,null,6914,null,null,6918,6922,6944,6968,6976,6984,6991,7047,7087,7091,7112,7260,7464,7467,7656,7740,null,7760,7774,7795,7876,8110,8187,8202,8315,8341,8359,8391,8410,8414,8448,8455,null,8463,null,null,null,8475,8478,8483,8498,8502,8531,8575,8600,8624,8681,8688,8709,null,null,8716,8720,null,8735,8760,8766,8768,null,null,null,null,8784,8791,8794,8797,8804,null,8815,8820,8825,8867,8884,8887,8927,8936,8974,8986,9013,9041,9067,9077,9079,9081,9086,9093,9109,null,null,9119,9138,9144,9159,9164,9167,9171,null,9174,null,null,null,9179,9182,9184,null,null,null,9189,9223,9247,9258,9269,9275,9279,null,null,null,null,null,9291,null,9295,9301,9304,null,null,9310,9315,9324,9330,null,9341,9358,9367,9372,null,null,null,9382,9386,9392,9395,null,9398,9488,9509,9547,9616,9643,9702,94,1223" diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv deleted file mode 100644 index 260888ce..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv +++ /dev/null @@ -1,2 +0,0 @@ - ab , cd -ef ,gh \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml deleted file mode 100644 index 1c1cbb8d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv deleted file mode 100644 index 9a9fd17d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv +++ /dev/null @@ -1,13 +0,0 @@ -#--------------------------------- - FRUIT | RANK -#--------------------------------- - apple | 1 -#--------------------------------- - banana | 2 -#--------------------------------- - cherry | 3.14159265358979323846 -#--------------------------------- - | 0 -#--------------------------------- - NIL | 0 -#--------------------------------- diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv deleted file mode 100644 index b58e4e1e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv +++ /dev/null @@ -1,4 +0,0 @@ -foo,1 -bar,2 -baz,3 -qux,4 diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv deleted file mode 100644 index f118269a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter-params/src/test/resources/single-column.csv +++ /dev/null @@ -1,5 +0,0 @@ -foo -bar -baz -qux -"" diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts deleted file mode 100644 index 181698f0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/junit-jupiter.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - `java-library-conventions` -} - -description = "JUnit Jupiter (Aggregator)" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitJupiterApi) - api(projects.junitJupiterParams) - - runtimeOnly(projects.junitJupiterEngine) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java deleted file mode 100644 index 986039ad..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-jupiter/src/module/org.junit.jupiter/module-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Aggregates all JUnit Jupiter modules. - * - * @since 5.4 - */ -module org.junit.jupiter { - requires transitive org.junit.jupiter.api; - requires transitive org.junit.jupiter.engine; - requires transitive org.junit.jupiter.params; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts deleted file mode 100644 index 393d655e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/junit-platform-commons.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -import org.junit.gradle.java.ExecJarAction - -plugins { - `java-library-conventions` - `java-multi-release-sources` - `java-repackage-jars` - `java-test-fixtures` -} - -description = "JUnit Platform Commons" - -dependencies { - api(platform(projects.junitBom)) - - compileOnlyApi(libs.apiguardian) -} - -tasks.jar { - val release9ClassesDir = sourceSets.mainRelease9.get().output.classesDirs.singleFile - inputs.dir(release9ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) - doLast(objects.newInstance(ExecJarAction::class).apply { - javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) - args.addAll( - "--update", - "--file", archiveFile.get().asFile.absolutePath, - "--release", "9", - "-C", release9ClassesDir.absolutePath, "." - ) - }) -} - -tasks.codeCoverageClassesJar { - exclude("org/junit/platform/commons/util/ModuleUtils.class") -} - -eclipse { - classpath { - sourceSets -= project.sourceSets.mainRelease9.get() - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java deleted file mode 100644 index 20c6d943..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * Base class for all {@link RuntimeException RuntimeExceptions} thrown - * by JUnit. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.5") -public class JUnitException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public JUnitException(String message) { - super(message); - } - - public JUnitException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java deleted file mode 100644 index e858ad26..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * Thrown if a precondition is violated. - * - * @since 1.5 - */ -@API(status = STABLE, since = "1.5") -@SuppressWarnings({ "deprecation", "exports" }) -public class PreconditionViolationException extends org.junit.platform.commons.util.PreconditionViolationException { - - private static final long serialVersionUID = 1L; - - public PreconditionViolationException(String message) { - super(message); - } - - public PreconditionViolationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java deleted file mode 100644 index 476289b2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.annotation; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import org.apiguardian.api.API; - -/** - * {@code @Testable} is used to signal to IDEs and tooling vendors that the - * annotated or meta-annotated element is testable. - * - *

In this context, the term "testable" means that the annotated element - * (typically a method, field, or class) can be executed by a {@code TestEngine} - * as a test or test container on the JUnit Platform. - * - *

Motivation for {@code @Testable}

- *

Some clients of the JUnit Platform, notably IDEs such as IntelliJ IDEA, - * operate only on sources for test discovery. Thus, they cannot use the full - * runtime discovery mechanism of the JUnit Platform since it relies on compiled - * classes. {@code @Testable} therefore serves as an alternative mechanism for - * IDEs to discover potential tests by analyzing the source code only. - * - *

Common Use Cases

- *

{@code @Testable} will typically be used as a meta-annotation in order to - * create a custom composed annotation that inherits the semantics - * of {@code @Testable}. For example, the {@code @Test} and {@code @TestFactory} - * annotations in JUnit Jupiter are meta-annotated with {@code @Testable}. - *

For test programming models that do not rely on annotations, test classes, - * test methods, or test fields may be directly annotated with {@code @Testable}. - * Alternatively, if concrete test classes extend from a base class, the base class - * can be annotated with {@code @Testable}. Note that {@code @Testable} is an - * {@link Inherited @Inherited} annotation. - * - *

Requirements for IDEs and Tooling Vendors

- *
    - *
  • If a top-level class, static nested class, or inner class is not - * annotated or meta-annotated with {@code @Testable} but contains a method or field - * that is annotated or meta-annotated with {@code @Testable}, the class must - * be considered to be a testable class.
  • - *
  • If annotation hierarchies containing {@code @Testable} are present on - * classes, methods, or fields in compiled byte code (e.g., in JARs in the user's - * classpath), IDEs and tooling vendors must also take such annotation - * hierarchies into consideration when performing annotation processing for - * source code.
  • - *
- * - *

Restrictions for TestEngine Implementations

- *

A {@code TestEngine} must not in any way perform - * discovery based on the presence of {@code @Testable}. In terms of - * discovery, the presence of {@code @Testable} should only be meaningful to - * clients such as IDEs and tooling vendors. A {@code TestEngine} implementation - * is therefore required to discover tests based on information specific to - * that test engine (e.g., annotations specific to that test engine). - * - *

Supported Target Elements

- *

Since JUnit Platform version 1.7, {@code @Testable} may target any - * declaration {@linkplain java.lang.annotation.ElementType element type}. This - * includes the aforementioned method, field, and class elements. - * - * @since 1.0 - */ -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -@API(status = STABLE, since = "1.0") -public @interface Testable { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java deleted file mode 100644 index f5f0eaf5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Common annotations for the JUnit Platform. - */ - -package org.junit.platform.commons.annotation; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java deleted file mode 100644 index 9d7c5997..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.function; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * A container object which may either contain a nullable value in case of - * success or an exception in case of failure. - * - *

Instances of this class should be returned by methods instead of - * {@link Optional} when callers might want to report the exception via logging - * or by wrapping it in another exception at a later point in time, e.g. via - * {@link #getOrThrow(Function)}. - * - *

Moreover, it makes it particularly convenient to attach follow-up actions - * should the {@code Try} have been successful (cf. {@link #andThen} and - * {@link #andThenTry}) or fallback actions should it not have been (cf. - * {@link #orElse} and {@link #orElseTry}). - * - * @since 1.4 - */ -@API(status = MAINTAINED, since = "1.4") -public abstract class Try { - - /** - * Call the supplied {@link Callable} and return a successful {@code Try} - * that contains the returned value or, in case an exception was thrown, a - * failed {@code Try} that contains the exception. - * - * @param action the action to try; must not be {@code null} - * @return a succeeded or failed {@code Try} depending on the outcome of the - * supplied action; never {@code null} - * @see #success(Object) - * @see #failure(Exception) - */ - public static Try call(Callable action) { - checkNotNull(action, "action"); - return Try.of(() -> success(action.call())); - } - - /** - * Convert the supplied value into a succeeded {@code Try}. - * - * @param value the value to wrap; potentially {@code null} - * @return a succeeded {@code Try} that contains the supplied value; never - * {@code null} - */ - public static Try success(V value) { - return new Success<>(value); - } - - /** - * Convert the supplied exception into a failed {@code Try}. - * - * @param cause the exception to wrap; must not be {@code null} - * @return a failed {@code Try} that contains the supplied value; never - * {@code null} - */ - public static Try failure(Exception cause) { - return new Failure<>(checkNotNull(cause, "cause")); - } - - // Cannot use Preconditions due to package cycle - private static T checkNotNull(T input, String title) { - if (input == null) { - // Cannot use PreconditionViolationException due to package cycle - throw new JUnitException(title + " must not be null"); - } - return input; - } - - private static Try of(Callable> action) { - try { - return action.call(); - } - catch (Exception e) { - return failure(e); - } - } - - private Try() { - /* no-op */ - } - - /** - * If this {@code Try} is a success, apply the supplied transformer to its - * value and return a new successful or failed {@code Try} depending on the - * transformer's outcome; if this {@code Try} is a failure, do nothing. - * - * @param transformer the transformer to try; must not be {@code null} - * @return a succeeded or failed {@code Try}; never {@code null} - */ - public abstract Try andThenTry(Transformer transformer); - - /** - * If this {@code Try} is a success, apply the supplied function to its - * value and return the resulting {@code Try}; if this {@code Try} is a - * failure, do nothing. - * - * @param function the function to apply; must not be {@code null} - * @return a succeeded or failed {@code Try}; never {@code null} - */ - public abstract Try andThen(Function> function); - - /** - * If this {@code Try} is a failure, call the supplied action and return a - * new successful or failed {@code Try} depending on the action's outcome; - * if this {@code Try} is a success, do nothing. - * - * @param action the action to try; must not be {@code null} - * @return a succeeded or failed {@code Try}; never {@code null} - */ - public abstract Try orElseTry(Callable action); - - /** - * If this {@code Try} is a failure, call the supplied supplier and return - * the resulting {@code Try}; if this {@code Try} is a success, do nothing. - * - * @param supplier the supplier to call; must not be {@code null} - * @return a succeeded or failed {@code Try}; never {@code null} - */ - public abstract Try orElse(Supplier> supplier); - - /** - * If this {@code Try} is a success, get the contained value; if this - * {@code Try} is a failure, throw the contained exception. - * - * @return the contained value, if available; potentially {@code null} - * @throws Exception if this {@code Try} is a failure - */ - public abstract V get() throws Exception; - - /** - * If this {@code Try} is a success, get the contained value; if this - * {@code Try} is a failure, call the supplied {@link Function} with the - * contained exception and throw the resulting {@link Exception}. - * - * @param exceptionTransformer the transformer to be called with the - * contained exception, if available; must not be {@code null} - * @return the contained value, if available - * @throws E if this {@code Try} is a failure - */ - public abstract V getOrThrow(Function exceptionTransformer) throws E; - - /** - * If this {@code Try} is a success, call the supplied {@link Consumer} with - * the contained value; otherwise, do nothing. - * - * @param valueConsumer the consumer to be called with the contained value, - * if available; must not be {@code null} - * @return the same {@code Try} for method chaining - */ - public abstract Try ifSuccess(Consumer valueConsumer); - - /** - * If this {@code Try} is a failure, call the supplied {@link Consumer} with - * the contained exception; otherwise, do nothing. - * - * @param causeConsumer the consumer to be called with the contained - * exception, if available; must not be {@code null} - * @return the same {@code Try} for method chaining - */ - public abstract Try ifFailure(Consumer causeConsumer); - - /** - * If this {@code Try} is a failure, return an empty {@link Optional}; if - * this {@code Try} is a success, wrap the contained value using - * {@link Optional#ofNullable(Object)}. - * - * @return an {@link Optional}; never {@code null} but potentially - * empty - */ - public abstract Optional toOptional(); - - /** - * A transformer for values of type {@code S} to type {@code T}. - * - *

The {@code Transformer} interface is similar to {@link Function}, - * except that a {@code Transformer} may throw an exception. - */ - @FunctionalInterface - public interface Transformer { - - /** - * Apply this transformer to the supplied value. - * - * @throws Exception if the transformation fails - */ - T apply(S value) throws Exception; - - } - - private static class Success extends Try { - - private final V value; - - Success(V value) { - this.value = value; - } - - @Override - public Try andThenTry(Transformer transformer) { - checkNotNull(transformer, "transformer"); - return Try.call(() -> transformer.apply(this.value)); - } - - @Override - public Try andThen(Function> function) { - checkNotNull(function, "function"); - return Try.of(() -> function.apply(this.value)); - } - - @Override - public Try orElseTry(Callable action) { - // don't call action because this Try is a success - return this; - } - - @Override - public Try orElse(Supplier> supplier) { - // don't call supplier because this Try is a success - return this; - } - - @Override - public V get() { - return this.value; - } - - @Override - public V getOrThrow(Function exceptionTransformer) { - // don't call exceptionTransformer because this Try is a success - return this.value; - } - - @Override - public Try ifSuccess(Consumer valueConsumer) { - checkNotNull(valueConsumer, "valueConsumer"); - valueConsumer.accept(this.value); - return this; - } - - @Override - public Try ifFailure(Consumer causeConsumer) { - // don't call causeConsumer because this Try was a success - return this; - } - - @Override - public Optional toOptional() { - return Optional.ofNullable(this.value); - } - - @Override - public boolean equals(Object that) { - if (this == that) { - return true; - } - if (that == null || this.getClass() != that.getClass()) { - return false; - } - return Objects.equals(this.value, ((Success) that).value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - } - - private static class Failure extends Try { - - private final Exception cause; - - Failure(Exception cause) { - this.cause = cause; - } - - @Override - public Try andThenTry(Transformer transformer) { - // don't call transformer because this Try is a failure - return uncheckedCast(); - } - - @Override - public Try andThen(Function> function) { - // don't call function because this Try is a failure - return uncheckedCast(); - } - - @SuppressWarnings("unchecked") - private Try uncheckedCast() { - return (Try) this; - } - - @Override - public Try orElseTry(Callable action) { - checkNotNull(action, "action"); - return Try.call(action); - } - - @Override - public Try orElse(Supplier> supplier) { - checkNotNull(supplier, "supplier"); - return Try.of(supplier::get); - } - - @Override - public V get() throws Exception { - throw this.cause; - } - - @Override - public V getOrThrow(Function exceptionTransformer) throws E { - checkNotNull(exceptionTransformer, "exceptionTransformer"); - throw exceptionTransformer.apply(this.cause); - } - - @Override - public Try ifSuccess(Consumer valueConsumer) { - // don't call valueConsumer because this Try is a failure - return this; - } - - @Override - public Try ifFailure(Consumer causeConsumer) { - checkNotNull(causeConsumer, "causeConsumer"); - causeConsumer.accept(this.cause); - return this; - } - - @Override - public Optional toOptional() { - return Optional.empty(); - } - - @Override - public boolean equals(Object that) { - if (this == that) { - return true; - } - if (that == null || this.getClass() != that.getClass()) { - return false; - } - return Objects.equals(this.cause, ((Failure) that).cause); - } - - @Override - public int hashCode() { - return Objects.hash(cause); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java deleted file mode 100644 index 5990d37b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Maintained functional interfaces and support classes. - */ - -package org.junit.platform.commons.function; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java deleted file mode 100644 index 3b0c357f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.logging; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * {@code LogRecordListener} is only intended for testing purposes within - * JUnit's own test suite. - * - * @since 1.1 - */ -@API(status = INTERNAL, since = "1.1") -public class LogRecordListener { - - // capture log records by thread to support parallel test execution - private final ThreadLocal> logRecords = ThreadLocal.withInitial(ArrayList::new); - - /** - * Inform the listener of a {@link LogRecord} that was submitted to JUL for - * processing. - */ - public void logRecordSubmitted(LogRecord logRecord) { - this.logRecords.get().add(logRecord); - } - - /** - * Get a stream of {@link LogRecord log records} that have been - * {@linkplain #logRecordSubmitted submitted} to this listener by the - * current thread. - * - *

As stated in the Javadoc for {@code LogRecord}, a submitted - * {@code LogRecord} should not be updated by the client application. Thus, - * the {@code LogRecords} in the returned stream should only be inspected for - * testing purposes and not modified in any way. - * - * @see #stream(Level) - * @see #stream(Class) - * @see #stream(Class, Level) - */ - public Stream stream() { - return this.logRecords.get().stream(); - } - - /** - * Get a stream of {@link LogRecord log records} that have been - * {@linkplain #logRecordSubmitted submitted} to this listener by the current - * thread at the given log level. - * - *

As stated in the Javadoc for {@code LogRecord}, a submitted - * {@code LogRecord} should not be updated by the client application. Thus, - * the {@code LogRecords} in the returned stream should only be inspected for - * testing purposes and not modified in any way. - * - * @param level the log level for which to get the log records; never {@code null} - * @since 1.4 - * @see #stream() - * @see #stream(Class) - * @see #stream(Class, Level) - */ - public Stream stream(Level level) { - // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here - // since that would introduce a package cycle. - if (level == null) { - throw new JUnitException("Level must not be null"); - } - - return stream().filter(logRecord -> logRecord.getLevel() == level); - } - - /** - * Get a stream of {@link LogRecord log records} that have been - * {@linkplain #logRecordSubmitted submitted} to this listener by the current - * thread for the logger name equal to the name of the given class. - * - *

As stated in the Javadoc for {@code LogRecord}, a submitted - * {@code LogRecord} should not be updated by the client application. Thus, - * the {@code LogRecords} in the returned stream should only be inspected for - * testing purposes and not modified in any way. - * - * @param clazz the class for which to get the log records; never {@code null} - * @see #stream() - * @see #stream(Level) - * @see #stream(Class, Level) - */ - public Stream stream(Class clazz) { - // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here - // since that would introduce a package cycle. - if (clazz == null) { - throw new JUnitException("Class must not be null"); - } - - return stream().filter(logRecord -> logRecord.getLoggerName().equals(clazz.getName())); - } - - /** - * Get a stream of {@link LogRecord log records} that have been - * {@linkplain #logRecordSubmitted submitted} to this listener by the current - * thread for the logger name equal to the name of the given class at the given - * log level. - * - *

As stated in the Javadoc for {@code LogRecord}, a submitted - * {@code LogRecord} should not be updated by the client application. Thus, - * the {@code LogRecords} in the returned stream should only be inspected for - * testing purposes and not modified in any way. - * - * @param clazz the class for which to get the log records; never {@code null} - * @param level the log level for which to get the log records; never {@code null} - * @see #stream() - * @see #stream(Level) - * @see #stream(Class) - */ - public Stream stream(Class clazz, Level level) { - // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here - // since that would introduce a package cycle. - if (level == null) { - throw new JUnitException("Level must not be null"); - } - - return stream(clazz).filter(logRecord -> logRecord.getLevel() == level); - } - - /** - * Clear all existing {@link LogRecord log records} that have been - * {@linkplain #logRecordSubmitted submitted} to this listener by the - * current thread. - */ - public void clear() { - this.logRecords.get().clear(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java deleted file mode 100644 index 5283e18d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.logging; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.function.Supplier; - -import org.apiguardian.api.API; - -/** - * The {@code Logger} API serves as a simple logging facade for - * {@code java.util.logging} (JUL). - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public interface Logger { - - /** - * Log the message from the provided {@code messageSupplier} at error level. - * - *

Maps to {@link java.util.logging.Level#SEVERE} in JUL. - */ - void error(Supplier messageSupplier); - - /** - * Log the provided {@code Throwable} and message from the provided - * {@code messageSupplier} at error level. - * - *

Maps to {@link java.util.logging.Level#SEVERE} in JUL. - */ - void error(Throwable throwable, Supplier messageSupplier); - - /** - * Log the message from the provided {@code messageSupplier} at warning level. - * - *

Maps to {@link java.util.logging.Level#WARNING} in JUL. - */ - void warn(Supplier messageSupplier); - - /** - * Log the provided {@code Throwable} and message from the provided - * {@code messageSupplier} at warning level. - * - *

Maps to {@link java.util.logging.Level#WARNING} in JUL. - */ - void warn(Throwable throwable, Supplier messageSupplier); - - /** - * Log the message from the provided {@code messageSupplier} at info level. - * - *

Maps to {@link java.util.logging.Level#INFO} in JUL. - */ - void info(Supplier messageSupplier); - - /** - * Log the provided {@code Throwable} and message from the provided - * {@code messageSupplier} at info level. - * - *

Maps to {@link java.util.logging.Level#INFO} in JUL. - */ - void info(Throwable throwable, Supplier messageSupplier); - - /** - * Log the message from the provided {@code messageSupplier} at config level. - * - *

Maps to {@link java.util.logging.Level#CONFIG} in JUL. - */ - void config(Supplier messageSupplier); - - /** - * Log the provided {@code Throwable} and message from the provided - * {@code messageSupplier} at config level. - * - *

Maps to {@link java.util.logging.Level#CONFIG} in JUL. - */ - void config(Throwable throwable, Supplier messageSupplier); - - /** - * Log the message from the provided {@code messageSupplier} at debug level. - * - *

Maps to {@link java.util.logging.Level#FINE} in JUL. - */ - void debug(Supplier messageSupplier); - - /** - * Log the provided {@code Throwable} and message from the provided - * {@code messageSupplier} at debug level. - * - *

Maps to {@link java.util.logging.Level#FINE} in JUL. - */ - void debug(Throwable throwable, Supplier messageSupplier); - - /** - * Log the message from the provided {@code messageSupplier} at trace level. - * - *

Maps to {@link java.util.logging.Level#FINER} in JUL. - */ - void trace(Supplier messageSupplier); - - /** - * Log the provided {@code Throwable} and message from the provided - * {@code messageSupplier} at trace level. - * - *

Maps to {@link java.util.logging.Level#FINER} in JUL. - */ - void trace(Throwable throwable, Supplier messageSupplier); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java deleted file mode 100644 index 5e06b26f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.logging; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * Factory for the {@link Logger} facade for JUL. - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class LoggerFactory { - - private LoggerFactory() { - /* no-op */ - } - - private static final Set listeners = ConcurrentHashMap.newKeySet(); - - /** - * Get a {@link Logger} for the specified class. - * - * @param clazz the class for which to get the logger; never {@code null} - * @return the logger - */ - public static Logger getLogger(Class clazz) { - // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here - // since that would introduce a package cycle. - if (clazz == null) { - throw new JUnitException("Class must not be null"); - } - - return new DelegatingLogger(clazz.getName()); - } - - /** - * Add the supplied {@link LogRecordListener} to the set of registered - * listeners. - */ - public static void addListener(LogRecordListener listener) { - listeners.add(listener); - } - - /** - * Remove the supplied {@link LogRecordListener} from the set of registered - * listeners. - */ - public static void removeListener(LogRecordListener listener) { - listeners.remove(listener); - } - - private static final class DelegatingLogger implements Logger { - - private static final String FQCN = DelegatingLogger.class.getName(); - - private final String name; - - private final java.util.logging.Logger julLogger; - - DelegatingLogger(String name) { - this.name = name; - this.julLogger = java.util.logging.Logger.getLogger(this.name); - } - - @Override - public void error(Supplier messageSupplier) { - log(Level.SEVERE, null, messageSupplier); - } - - @Override - public void error(Throwable throwable, Supplier messageSupplier) { - log(Level.SEVERE, throwable, messageSupplier); - } - - @Override - public void warn(Supplier messageSupplier) { - log(Level.WARNING, null, messageSupplier); - } - - @Override - public void warn(Throwable throwable, Supplier messageSupplier) { - log(Level.WARNING, throwable, messageSupplier); - } - - @Override - public void info(Supplier messageSupplier) { - log(Level.INFO, null, messageSupplier); - } - - @Override - public void info(Throwable throwable, Supplier messageSupplier) { - log(Level.INFO, throwable, messageSupplier); - } - - @Override - public void config(Supplier messageSupplier) { - log(Level.CONFIG, null, messageSupplier); - } - - @Override - public void config(Throwable throwable, Supplier messageSupplier) { - log(Level.CONFIG, throwable, messageSupplier); - } - - @Override - public void debug(Supplier messageSupplier) { - log(Level.FINE, null, messageSupplier); - } - - @Override - public void debug(Throwable throwable, Supplier messageSupplier) { - log(Level.FINE, throwable, messageSupplier); - } - - @Override - public void trace(Supplier messageSupplier) { - log(Level.FINER, null, messageSupplier); - } - - @Override - public void trace(Throwable throwable, Supplier messageSupplier) { - log(Level.FINER, throwable, messageSupplier); - } - - private void log(Level level, Throwable throwable, Supplier messageSupplier) { - boolean loggable = this.julLogger.isLoggable(level); - if (loggable || !listeners.isEmpty()) { - LogRecord logRecord = createLogRecord(level, throwable, nullSafeGet(messageSupplier)); - if (loggable) { - this.julLogger.log(logRecord); - } - listeners.forEach(listener -> listener.logRecordSubmitted(logRecord)); - } - } - - private LogRecord createLogRecord(Level level, Throwable throwable, String message) { - String sourceClassName = null; - String sourceMethodName = null; - boolean found = false; - for (StackTraceElement element : new Throwable().getStackTrace()) { - String className = element.getClassName(); - if (FQCN.equals(className)) { - found = true; - } - else if (found) { - sourceClassName = className; - sourceMethodName = element.getMethodName(); - break; - } - } - - LogRecord logRecord = new LogRecord(level, message); - logRecord.setLoggerName(this.name); - logRecord.setThrown(throwable); - logRecord.setSourceClassName(sourceClassName); - logRecord.setSourceMethodName(sourceMethodName); - logRecord.setResourceBundleName(this.julLogger.getResourceBundleName()); - logRecord.setResourceBundle(this.julLogger.getResourceBundle()); - - return logRecord; - } - - private static String nullSafeGet(Supplier messageSupplier) { - return (messageSupplier != null ? messageSupplier.get() : null); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java deleted file mode 100644 index 75260c34..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Internal logging package. - * - *

DISCLAIMER

- * - *

These classes are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - */ - -package org.junit.platform.commons.logging; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java deleted file mode 100644 index 66e1cf2a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Common APIs and support utilities for the JUnit Platform. - * - *

DISCLAIMER

- * - *

Any API annotated with {@code @API(status = INTERNAL)} is intended solely - * for usage within the JUnit framework itself. Any usage of internal - * APIs by external parties is not supported! - */ - -package org.junit.platform.commons; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java deleted file mode 100644 index 037c9ee5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * {@code AnnotationSupport} provides static utility methods for common tasks - * regarding annotations — for example, checking if a class, method, or - * field is annotated with a particular annotation; finding annotations on a - * given class, method, or field; finding fields or methods annotated with - * a particular annotation, etc. - * - *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension - * authors are encouraged to use these supported methods in order to align with - * the behavior of the JUnit Platform. - * - * @since 1.0 - * @see ClassSupport - * @see ModifierSupport - * @see ReflectionSupport - */ -@API(status = MAINTAINED, since = "1.0") -public final class AnnotationSupport { - - private AnnotationSupport() { - /* no-op */ - } - - /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the supplied optional - * {@code element}. - * - * @param element an {@link Optional} containing the element on which to - * search for the annotation; may be {@code null} or empty - * @param annotationType the annotation type to search for; never {@code null} - * @return {@code true} if the annotation is present or meta-present - * @since 1.3 - * @see #isAnnotated(AnnotatedElement, Class) - * @see #findAnnotation(Optional, Class) - */ - @API(status = MAINTAINED, since = "1.3") - public static boolean isAnnotated(Optional element, - Class annotationType) { - - return AnnotationUtils.isAnnotated(element, annotationType); - } - - /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the supplied - * {@code element}. - * - * @param element the element on which to search for the annotation; may be - * {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @return {@code true} if the annotation is present or meta-present - * @see #isAnnotated(Optional, Class) - * @see #findAnnotation(AnnotatedElement, Class) - */ - public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { - return AnnotationUtils.isAnnotated(element, annotationType); - } - - /** - * Find the first annotation of {@code annotationType} that is either - * present or meta-present on the supplied optional - * {@code element}. - * - * @param the annotation type - * @param element an {@link Optional} containing the element on which to - * search for the annotation; may be {@code null} or empty - * @param annotationType the annotation type to search for; never {@code null} - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty - * @since 1.1 - * @see #findAnnotation(AnnotatedElement, Class) - */ - @API(status = MAINTAINED, since = "1.1") - public static Optional findAnnotation(Optional element, - Class annotationType) { - - return AnnotationUtils.findAnnotation(element, annotationType); - } - - /** - * Find the first annotation of {@code annotationType} that is either - * directly present, meta-present, or indirectly - * present on the supplied {@code element}. - * - *

If the element is a class and the annotation is neither directly - * present nor meta-present on the class, this method will - * additionally search on interfaces implemented by the class before - * finding an annotation that is indirectly present on the class. - * - * @param the annotation type - * @param element the element on which to search for the annotation; may be - * {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty - */ - public static Optional findAnnotation(AnnotatedElement element, Class annotationType) { - return AnnotationUtils.findAnnotation(element, annotationType); - } - - /** - * Find the first annotation of the specified type that is either - * directly present, meta-present, or indirectly - * present on the supplied class. - * - *

If the annotation is neither directly present nor meta-present - * on the class, this method will additionally search on interfaces implemented - * by the class before searching for an annotation that is indirectly present - * on the class (i.e., within the class inheritance hierarchy). - * - *

If the annotation still has not been found, this method will optionally - * search recursively through the enclosing class hierarchy if - * {@link SearchOption#INCLUDE_ENCLOSING_CLASSES} is specified. - * - *

If {@link SearchOption#DEFAULT} is specified, this method has the same - * semantics as {@link #findAnnotation(AnnotatedElement, Class)}. - * - * @param the annotation type - * @param clazz the class on which to search for the annotation; may be {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @param searchOption the {@code SearchOption} to use; never {@code null} - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty - * @since 1.8 - * @see SearchOption - * @see #findAnnotation(AnnotatedElement, Class) - */ - @API(status = EXPERIMENTAL, since = "1.8") - public static Optional findAnnotation(Class clazz, Class annotationType, - SearchOption searchOption) { - - Preconditions.notNull(searchOption, "SearchOption must not be null"); - - return AnnotationUtils.findAnnotation(clazz, annotationType, - searchOption == SearchOption.INCLUDE_ENCLOSING_CLASSES); - } - - /** - * Find all repeatable {@linkplain Annotation annotations} of the - * supplied {@code annotationType} that are either present, - * indirectly present, or meta-present on the supplied - * optional {@code element}. - * - *

See {@link #findRepeatableAnnotations(AnnotatedElement, Class)} for - * details of the algorithm used. - * - * @param the annotation type - * @param element an {@link Optional} containing the element on which to - * search for the annotation; may be {@code null} or empty - * @param annotationType the repeatable annotation type to search for; never {@code null} - * @return an immutable list of all such annotations found; never {@code null} - * @since 1.5 - * @see java.lang.annotation.Repeatable - * @see java.lang.annotation.Inherited - * @see #findRepeatableAnnotations(AnnotatedElement, Class) - */ - @API(status = MAINTAINED, since = "1.5") - public static List findRepeatableAnnotations(Optional element, - Class annotationType) { - - return AnnotationUtils.findRepeatableAnnotations(element, annotationType); - } - - /** - * Find all repeatable {@linkplain Annotation annotations} of the - * supplied {@code annotationType} that are either present, - * indirectly present, or meta-present on the supplied - * {@link AnnotatedElement}. - * - *

This method extends the functionality of - * {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)} - * with additional support for meta-annotations. - * - *

In addition, if the element is a class and the repeatable annotation - * is {@link java.lang.annotation.Inherited @Inherited}, this method will - * search on superclasses first in order to support top-down semantics. - * The result is that this algorithm finds repeatable annotations that - * would be shadowed and therefore not visible according to Java's - * standard semantics for inherited, repeatable annotations, but most - * developers will naturally assume that all repeatable annotations in JUnit - * are discovered regardless of whether they are declared stand-alone, in a - * container, or as a meta-annotation (e.g., multiple declarations of - * {@code @ExtendWith} within a test class hierarchy). - * - *

If the element is a class and the repeatable annotation is not - * discovered within the class hierarchy, this method will additionally - * search on interfaces implemented by each class in the hierarchy. - * - *

If the supplied {@code element} is {@code null}, this method returns - * an empty list. - * - *

As of JUnit Platform 1.5, the search algorithm will also find - * repeatable annotations used as meta-annotations on other repeatable - * annotations. - * - * @param the annotation type - * @param element the element to search on; may be {@code null} - * @param annotationType the repeatable annotation type to search for; never {@code null} - * @return an immutable list of all such annotations found; never {@code null} - * @see java.lang.annotation.Repeatable - * @see java.lang.annotation.Inherited - */ - public static List findRepeatableAnnotations(AnnotatedElement element, - Class annotationType) { - - return AnnotationUtils.findRepeatableAnnotations(element, annotationType); - } - - /** - * Find all {@code public} {@linkplain Field fields} of the supplied class - * or interface that are declared to be of the specified {@code fieldType} - * and are annotated or meta-annotated with the specified - * {@code annotationType}. - * - *

Consult the Javadoc for {@link Class#getFields()} for details on - * inheritance and ordering. - * - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param fieldType the declared type of fields to find; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @return the list of all such fields found; neither {@code null} nor mutable - * @see Class#getFields() - * @see Field#getType() - * @see #findAnnotatedFields(Class, Class) - * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#tryToReadFieldValue(Field, Object) - */ - public static List findPublicAnnotatedFields(Class clazz, Class fieldType, - Class annotationType) { - - return AnnotationUtils.findPublicAnnotatedFields(clazz, fieldType, annotationType); - } - - /** - * Find all {@linkplain Field fields} of the supplied class or interface - * that are annotated or meta-annotated with the specified - * {@code annotationType}, using top-down search semantics within the type - * hierarchy. - * - *

Fields declared in the same class or interface will be ordered using - * an algorithm that is deterministic but intentionally nonobvious. - * - *

The results will not contain fields that are hidden or - * {@linkplain Field#isSynthetic() synthetic}. - * - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @return the list of all such fields found; neither {@code null} nor mutable - * @since 1.4 - * @see Class#getDeclaredFields() - * @see #findPublicAnnotatedFields(Class, Class, Class) - * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#tryToReadFieldValue(Field, Object) - */ - @API(status = MAINTAINED, since = "1.4") - public static List findAnnotatedFields(Class clazz, Class annotationType) { - return AnnotationUtils.findAnnotatedFields(clazz, annotationType, field -> true); - } - - /** - * Find all {@linkplain Field fields} of the supplied class or interface - * that are annotated or meta-annotated with the specified - * {@code annotationType} and match the specified {@code predicate}, using - * the supplied hierarchy traversal mode. - * - *

Fields declared in the same class or interface will be ordered using - * an algorithm that is deterministic but intentionally nonobvious. - * - *

The results will not contain fields that are hidden or - * {@linkplain Field#isSynthetic() synthetic}. - * - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @param predicate the field filter; never {@code null} - * @param traversalMode the hierarchy traversal mode; never {@code null} - * @return the list of all such fields found; neither {@code null} nor mutable - * @since 1.4 - * @see Class#getDeclaredFields() - * @see #findAnnotatedFields(Class, Class) - * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#tryToReadFieldValue(Field, Object) - */ - @API(status = MAINTAINED, since = "1.4") - public static List findAnnotatedFields(Class clazz, Class annotationType, - Predicate predicate, HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - return AnnotationUtils.findAnnotatedFields(clazz, annotationType, predicate, - ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); - } - - /** - * Find the values of all non-static {@linkplain Field fields} of the supplied - * {@code instance} that are annotated or meta-annotated with the - * specified {@code annotationType}, using top-down search semantics within - * the type hierarchy. - * - *

Values from fields declared in the same class or interface will be - * ordered using an algorithm that is deterministic but intentionally - * nonobvious. - * - *

The results will not contain values from fields that are hidden - * or {@linkplain Field#isSynthetic() synthetic}. - * - * @param instance the instance in which to find the fields; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @return the list of all such field values found; neither {@code null} nor mutable - * @since 1.4 - * @see #findAnnotatedFields(Class, Class) - * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#tryToReadFieldValue(Field, Object) - */ - @API(status = MAINTAINED, since = "1.4") - public static List findAnnotatedFieldValues(Object instance, Class annotationType) { - Preconditions.notNull(instance, "instance must not be null"); - - List fields = findAnnotatedFields(instance.getClass(), annotationType, ModifierSupport::isNotStatic, - HierarchyTraversalMode.TOP_DOWN); - - return ReflectionUtils.readFieldValues(fields, instance); - } - - /** - * Find the values of all static {@linkplain Field fields} of the supplied - * class or interface that are annotated or meta-annotated with the - * specified {@code annotationType}, using top-down search semantics within - * the type hierarchy. - * - *

Values from fields declared in the same class or interface will be - * ordered using an algorithm that is deterministic but intentionally - * nonobvious. - * - *

The results will not contain values from fields that are hidden - * or {@linkplain Field#isSynthetic() synthetic}. - * - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @return the list of all such field values found; neither {@code null} nor mutable - * @since 1.4 - * @see #findAnnotatedFields(Class, Class) - * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#tryToReadFieldValue(Field, Object) - */ - @API(status = MAINTAINED, since = "1.4") - public static List findAnnotatedFieldValues(Class clazz, Class annotationType) { - - List fields = findAnnotatedFields(clazz, annotationType, ModifierSupport::isStatic, - HierarchyTraversalMode.TOP_DOWN); - - return ReflectionUtils.readFieldValues(fields, null); - } - - /** - * Find the values of all non-static {@linkplain Field fields} of the supplied - * {@code instance} that are declared to be of the specified {@code fieldType} - * and are annotated or meta-annotated with the specified - * {@code annotationType}, using top-down search semantics within the type - * hierarchy. - * - *

Values from fields declared in the same class or interface will be - * ordered using an algorithm that is deterministic but intentionally - * nonobvious. - * - *

The results will not contain values from fields that are hidden - * or {@linkplain Field#isSynthetic() synthetic}. - * - * @param instance the instance in which to find the fields; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @param fieldType the declared type of fields to find; never {@code null} - * @return the list of all such field values found; neither {@code null} nor mutable - * @since 1.4 - * @see Field#getType() - * @see #findAnnotatedFields(Class, Class) - * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#tryToReadFieldValue(Field, Object) - */ - @SuppressWarnings("unchecked") - @API(status = MAINTAINED, since = "1.4") - public static List findAnnotatedFieldValues(Object instance, Class annotationType, - Class fieldType) { - - Preconditions.notNull(instance, "instance must not be null"); - Preconditions.notNull(fieldType, "fieldType must not be null"); - - Predicate predicate = // - field -> ModifierSupport.isNotStatic(field) && fieldType.isAssignableFrom(field.getType()); - - List fields = findAnnotatedFields(instance.getClass(), annotationType, predicate, - HierarchyTraversalMode.TOP_DOWN); - - return (List) ReflectionUtils.readFieldValues(fields, instance); - } - - /** - * Find the values of all static {@linkplain Field fields} of the supplied - * class or interface that are declared to be of the specified - * {@code fieldType} and are annotated or meta-annotated with the - * specified {@code annotationType}, using top-down search semantics within - * the type hierarchy. - * - *

Values from fields declared in the same class or interface will be - * ordered using an algorithm that is deterministic but intentionally - * nonobvious. - * - *

The results will not contain values from fields that are hidden - * or {@linkplain Field#isSynthetic() synthetic}. - * - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @param fieldType the declared type of fields to find; never {@code null} - * @return the list of all such field values found; neither {@code null} nor mutable - * @since 1.4 - * @see Field#getType() - * @see #findAnnotatedFields(Class, Class) - * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) - * @see ReflectionSupport#tryToReadFieldValue(Field, Object) - */ - @SuppressWarnings("unchecked") - @API(status = MAINTAINED, since = "1.4") - public static List findAnnotatedFieldValues(Class clazz, Class annotationType, - Class fieldType) { - - Preconditions.notNull(fieldType, "fieldType must not be null"); - - Predicate predicate = // - field -> ModifierSupport.isStatic(field) && fieldType.isAssignableFrom(field.getType()); - - List fields = findAnnotatedFields(clazz, annotationType, predicate, HierarchyTraversalMode.TOP_DOWN); - - return (List) ReflectionUtils.readFieldValues(fields, null); - } - - /** - * Find all {@linkplain Method methods} of the supplied class or interface - * that are annotated or meta-annotated with the specified - * {@code annotationType}. - * - * @param clazz the class or interface in which to find the methods; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @param traversalMode the hierarchy traversal mode; never {@code null} - * @return the list of all such methods found; neither {@code null} nor mutable - */ - public static List findAnnotatedMethods(Class clazz, Class annotationType, - HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - return AnnotationUtils.findAnnotatedMethods(clazz, annotationType, - ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java deleted file mode 100644 index 5039fd87..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassUtils; - -/** - * {@code ClassSupport} provides static utility methods for common tasks - * regarding {@linkplain Class classes} — for example, generating a - * comma-separated list of fully qualified class names for a set of supplied - * classes. - * - *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension - * authors are encouraged to use these supported methods in order to align with - * the behavior of the JUnit Platform. - * - * @since 1.1 - * @see AnnotationSupport - * @see ModifierSupport - * @see ReflectionSupport - */ -@API(status = MAINTAINED, since = "1.1") -public final class ClassSupport { - - private ClassSupport() { - /* no-op */ - } - - /** - * Generate a comma-separated list of fully qualified class names for the - * supplied classes. - * - * @param classes the classes whose names should be included in the - * generated string - * @return a comma-separated list of fully qualified class names, or an empty - * string if the supplied class array is {@code null} or empty - * @see #nullSafeToString(Function, Class...) - */ - public static String nullSafeToString(Class... classes) { - return ClassUtils.nullSafeToString(classes); - } - - /** - * Generate a comma-separated list of mapped values for the supplied classes. - * - *

The values are generated by the supplied {@code mapper} - * (e.g., {@code Class::getName}, {@code Class::getSimpleName}, etc.), unless - * a class reference is {@code null} in which case it will be mapped to - * {@code "null"}. - * - * @param mapper the mapper to use; never {@code null} - * @param classes the classes to map - * @return a comma-separated list of mapped values, or an empty string if - * the supplied class array is {@code null} or empty - * @see #nullSafeToString(Class...) - */ - public static String nullSafeToString(Function, ? extends String> mapper, Class... classes) { - return ClassUtils.nullSafeToString(mapper, classes); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java deleted file mode 100644 index aefcb1e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import org.apiguardian.api.API; - -/** - * Modes in which a hierarchy can be traversed — for example, when - * searching for methods or fields within a class hierarchy. - * - * @since 1.0 - * @see #TOP_DOWN - * @see #BOTTOM_UP - */ -@API(status = MAINTAINED, since = "1.0") -public enum HierarchyTraversalMode { - - /** - * Traverse the hierarchy using top-down semantics. - */ - TOP_DOWN, - - /** - * Traverse the hierarchy using bottom-up semantics. - */ - BOTTOM_UP; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java deleted file mode 100644 index 7dbb7489..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.reflect.Member; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * {@code ModifierSupport} provides static utility methods for working with - * class and member {@linkplain java.lang.reflect.Modifier modifiers} — - * for example, to determine if a class or member is declared as - * {@code public}, {@code private}, {@code abstract}, {@code static}, etc. - * - *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension - * authors are encouraged to use these supported methods in order to align with - * the behavior of the JUnit Platform. - * - * @since 1.4 - * @see java.lang.reflect.Modifier - * @see AnnotationSupport - * @see ClassSupport - * @see ReflectionSupport - */ -@API(status = MAINTAINED, since = "1.4") -public final class ModifierSupport { - - private ModifierSupport() { - /* no-op */ - } - - /** - * Determine if the supplied class is {@code public}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is {@code public} - * @see java.lang.reflect.Modifier#isPublic(int) - */ - public static boolean isPublic(Class clazz) { - return ReflectionUtils.isPublic(clazz); - } - - /** - * Determine if the supplied member is {@code public}. - * - * @param member the member to check; never {@code null} - * @return {@code true} if the member is {@code public} - * @see java.lang.reflect.Modifier#isPublic(int) - */ - public static boolean isPublic(Member member) { - return ReflectionUtils.isPublic(member); - } - - /** - * Determine if the supplied class is {@code private}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is {@code private} - * @see java.lang.reflect.Modifier#isPrivate(int) - */ - public static boolean isPrivate(Class clazz) { - return ReflectionUtils.isPrivate(clazz); - } - - /** - * Determine if the supplied member is {@code private}. - * - * @param member the member to check; never {@code null} - * @return {@code true} if the member is {@code private} - * @see java.lang.reflect.Modifier#isPrivate(int) - */ - public static boolean isPrivate(Member member) { - return ReflectionUtils.isPrivate(member); - } - - /** - * Determine if the supplied class is not {@code private}. - * - *

In other words this method will return {@code true} for classes - * declared as {@code public}, {@code protected}, or - * package private and {@code false} for classes declared as - * {@code private}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is not {@code private} - * @see java.lang.reflect.Modifier#isPublic(int) - * @see java.lang.reflect.Modifier#isProtected(int) - * @see java.lang.reflect.Modifier#isPrivate(int) - */ - public static boolean isNotPrivate(Class clazz) { - return ReflectionUtils.isNotPrivate(clazz); - } - - /** - * Determine if the supplied member is not {@code private}. - * - *

In other words this method will return {@code true} for members - * declared as {@code public}, {@code protected}, or - * package private and {@code false} for members declared as - * {@code private}. - * - * @param member the member to check; never {@code null} - * @return {@code true} if the member is not {@code private} - * @see java.lang.reflect.Modifier#isPublic(int) - * @see java.lang.reflect.Modifier#isProtected(int) - * @see java.lang.reflect.Modifier#isPrivate(int) - */ - public static boolean isNotPrivate(Member member) { - return ReflectionUtils.isNotPrivate(member); - } - - /** - * Determine if the supplied class is {@code abstract}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is {@code abstract} - * @see java.lang.reflect.Modifier#isAbstract(int) - */ - public static boolean isAbstract(Class clazz) { - return ReflectionUtils.isAbstract(clazz); - } - - /** - * Determine if the supplied member is {@code abstract}. - * - * @param member the class to check; never {@code null} - * @return {@code true} if the member is {@code abstract} - * @see java.lang.reflect.Modifier#isAbstract(int) - */ - public static boolean isAbstract(Member member) { - return ReflectionUtils.isAbstract(member); - } - - /** - * Determine if the supplied class is {@code static}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is {@code static} - * @see java.lang.reflect.Modifier#isStatic(int) - */ - public static boolean isStatic(Class clazz) { - return ReflectionUtils.isStatic(clazz); - } - - /** - * Determine if the supplied member is {@code static}. - * - * @param member the member to check; never {@code null} - * @return {@code true} if the member is {@code static} - * @see java.lang.reflect.Modifier#isStatic(int) - */ - public static boolean isStatic(Member member) { - return ReflectionUtils.isStatic(member); - } - - /** - * Determine if the supplied class is not {@code static}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is not {@code static} - * @see java.lang.reflect.Modifier#isStatic(int) - */ - public static boolean isNotStatic(Class clazz) { - return ReflectionUtils.isNotStatic(clazz); - } - - /** - * Determine if the supplied member is not {@code static}. - * - * @param member the member to check; never {@code null} - * @return {@code true} if the member is not {@code static} - * @see java.lang.reflect.Modifier#isStatic(int) - */ - public static boolean isNotStatic(Member member) { - return ReflectionUtils.isNotStatic(member); - } - - /** - * Determine if the supplied class is {@code final}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is {@code final} - * @since 1.5 - * @see java.lang.reflect.Modifier#isFinal(int) - */ - @API(status = MAINTAINED, since = "1.5") - public static boolean isFinal(Class clazz) { - return ReflectionUtils.isFinal(clazz); - } - - /** - * Determine if the supplied class is not {@code final}. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is not {@code final} - * @since 1.5 - * @see java.lang.reflect.Modifier#isFinal(int) - */ - @API(status = MAINTAINED, since = "1.5") - public static boolean isNotFinal(Class clazz) { - return ReflectionUtils.isNotFinal(clazz); - } - - /** - * Determine if the supplied member is {@code final}. - * - * @param member the member to check; never {@code null} - * @return {@code true} if the member is {@code final} - * @since 1.5 - * @see java.lang.reflect.Modifier#isFinal(int) - */ - @API(status = MAINTAINED, since = "1.5") - public static boolean isFinal(Member member) { - return ReflectionUtils.isFinal(member); - } - - /** - * Determine if the supplied member is not {@code final}. - * - * @param member the member to check; never {@code null} - * @return {@code true} if the member is not {@code final} - * @since 1.5 - * @see java.lang.reflect.Modifier#isFinal(int) - */ - @API(status = MAINTAINED, since = "1.5") - public static boolean isNotFinal(Member member) { - return ReflectionUtils.isNotFinal(member); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java deleted file mode 100644 index 819d5809..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.function.Try; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * {@code ReflectionSupport} provides static utility methods for common - * reflection tasks — for example, scanning for classes in the class-path - * or module-path, loading classes, finding methods, invoking methods, etc. - * - *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension - * authors are encouraged to use these supported methods in order to align with - * the behavior of the JUnit Platform. - * - * @since 1.0 - * @see AnnotationSupport - * @see ClassSupport - * @see ModifierSupport - */ -@API(status = MAINTAINED, since = "1.0") -public final class ReflectionSupport { - - private ReflectionSupport() { - /* no-op */ - } - - /** - * Load a class by its primitive name or fully qualified name, - * using the default {@link ClassLoader}. - * - *

Class names for arrays may be specified using either the JVM's internal - * String representation (e.g., {@code [[I} for {@code int[][]}, - * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or - * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, - * etc.). - * - * @param name the name of the class to load; never {@code null} or blank - * @return an {@code Optional} containing the loaded class; never {@code null} - * but potentially empty if no such class could be loaded - * @deprecated Please use {@link #tryToLoadClass(String)} instead. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - @SuppressWarnings("deprecation") - public static Optional> loadClass(String name) { - return ReflectionUtils.loadClass(name); - } - - /** - * Try to load a class by its primitive name or fully qualified name, - * using the default {@link ClassLoader}. - * - *

Class names for arrays may be specified using either the JVM's internal - * String representation (e.g., {@code [[I} for {@code int[][]}, - * {@code [Lava.lang.String;} for {@code java.lang.String[]}, etc.) or - * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, - * etc.). - * - * @param name the name of the class to load; never {@code null} or blank - * @return a successful {@code Try} containing the loaded class or a failed - * {@code Try} containing the exception if no such class could be loaded; - * never {@code null} - * @since 1.4 - */ - @API(status = MAINTAINED, since = "1.4") - public static Try> tryToLoadClass(String name) { - return ReflectionUtils.tryToLoadClass(name); - } - - /** - * Find all {@linkplain Class classes} in the supplied classpath {@code root} - * that match the specified {@code classFilter} and {@code classNameFilter} - * predicates. - * - *

The classpath scanning algorithm searches recursively in subpackages - * beginning with the root of the classpath. - * - * @param root the URI for the classpath root in which to scan; never - * {@code null} - * @param classFilter the class type filter; never {@code null} - * @param classNameFilter the class name filter; never {@code null} - * @return an immutable list of all such classes found; never {@code null} - * but potentially empty - * @see #findAllClassesInPackage(String, Predicate, Predicate) - * @see #findAllClassesInModule(String, Predicate, Predicate) - */ - public static List> findAllClassesInClasspathRoot(URI root, Predicate> classFilter, - Predicate classNameFilter) { - - return ReflectionUtils.findAllClassesInClasspathRoot(root, classFilter, classNameFilter); - } - - /** - * Find all {@linkplain Class classes} in the supplied {@code basePackageName} - * that match the specified {@code classFilter} and {@code classNameFilter} - * predicates. - * - *

The classpath scanning algorithm searches recursively in subpackages - * beginning within the supplied base package. - * - * @param basePackageName the name of the base package in which to start - * scanning; must not be {@code null} and must be valid in terms of Java - * syntax - * @param classFilter the class type filter; never {@code null} - * @param classNameFilter the class name filter; never {@code null} - * @return an immutable list of all such classes found; never {@code null} - * but potentially empty - * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) - * @see #findAllClassesInModule(String, Predicate, Predicate) - */ - public static List> findAllClassesInPackage(String basePackageName, Predicate> classFilter, - Predicate classNameFilter) { - - return ReflectionUtils.findAllClassesInPackage(basePackageName, classFilter, classNameFilter); - } - - /** - * Find all {@linkplain Class classes} in the supplied {@code moduleName} - * that match the specified {@code classFilter} and {@code classNameFilter} - * predicates. - * - *

The module-path scanning algorithm searches recursively in all - * packages contained in the module. - * - * @param moduleName the name of the module to scan; never {@code null} or - * empty - * @param classFilter the class type filter; never {@code null} - * @param classNameFilter the class name filter; never {@code null} - * @return an immutable list of all such classes found; never {@code null} - * but potentially empty - * @since 1.1.1 - * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) - * @see #findAllClassesInPackage(String, Predicate, Predicate) - */ - public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, - Predicate classNameFilter) { - - return ReflectionUtils.findAllClassesInModule(moduleName, classFilter, classNameFilter); - } - - /** - * Create a new instance of the specified {@link Class} by invoking - * the constructor whose argument list matches the types of the supplied - * arguments. - * - *

The constructor will be made accessible if necessary, and any checked - * exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked} - * as an unchecked exception. - * - * @param clazz the class to instantiate; never {@code null} - * @param args the arguments to pass to the constructor, none of which may - * be {@code null} - * @return the new instance; never {@code null} - * @see ExceptionUtils#throwAsUncheckedException(Throwable) - */ - public static T newInstance(Class clazz, Object... args) { - return ReflectionUtils.newInstance(clazz, args); - } - - /** - * Invoke the supplied method, making it accessible if necessary and - * {@linkplain ExceptionUtils#throwAsUncheckedException masking} any - * checked exception as an unchecked exception. - * - * @param method the method to invoke; never {@code null} - * @param target the object on which to invoke the method; may be - * {@code null} if the method is {@code static} - * @param args the arguments to pass to the method - * @return the value returned by the method invocation or {@code null} - * if the return type is {@code void} - * @see ExceptionUtils#throwAsUncheckedException(Throwable) - */ - public static Object invokeMethod(Method method, Object target, Object... args) { - return ReflectionUtils.invokeMethod(method, target, args); - } - - /** - * Find all {@linkplain Field fields} of the supplied class or interface - * that match the specified {@code predicate}. - * - *

Fields declared in the same class or interface will be ordered using - * an algorithm that is deterministic but intentionally nonobvious. - * - *

The results will not contain fields that are hidden or - * {@linkplain Field#isSynthetic() synthetic}. - * - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param predicate the field filter; never {@code null} - * @param traversalMode the hierarchy traversal mode; never {@code null} - * @return an immutable list of all such fields found; never {@code null} - * but potentially empty - * @since 1.4 - */ - @API(status = MAINTAINED, since = "1.4") - public static List findFields(Class clazz, Predicate predicate, - HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - return ReflectionUtils.findFields(clazz, predicate, - ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); - } - - /** - * Try to read the value of a potentially inaccessible field. - * - *

If an exception occurs while reading the field, a failed {@link Try} - * is returned that contains the corresponding exception. - * - * @param field the field to read; never {@code null} - * @param instance the instance from which the value is to be read; may - * be {@code null} for a static field - * @since 1.4 - */ - @API(status = MAINTAINED, since = "1.4") - public static Try tryToReadFieldValue(Field field, Object instance) { - return ReflectionUtils.tryToReadFieldValue(field, instance); - } - - /** - * Find the first {@link Method} of the supplied class or interface that - * meets the specified criteria, beginning with the specified class or - * interface and traversing up the type hierarchy until such a method is - * found or the type hierarchy is exhausted. - * - *

The algorithm does not search for methods in {@link java.lang.Object}. - * - * @param clazz the class or interface in which to find the method; never {@code null} - * @param methodName the name of the method to find; never {@code null} or empty - * @param parameterTypeNames the fully qualified names of the types of parameters - * accepted by the method, if any, provided as a comma-separated list - * @return an {@code Optional} containing the method found; never {@code null} - * but potentially empty if no such method could be found - * @see #findMethod(Class, String, Class...) - */ - public static Optional findMethod(Class clazz, String methodName, String parameterTypeNames) { - return ReflectionUtils.findMethod(clazz, methodName, parameterTypeNames); - } - - /** - * Find the first {@link Method} of the supplied class or interface that - * meets the specified criteria, beginning with the specified class or - * interface and traversing up the type hierarchy until such a method is - * found or the type hierarchy is exhausted. - * - *

The algorithm does not search for methods in {@link java.lang.Object}. - * - * @param clazz the class or interface in which to find the method; never {@code null} - * @param methodName the name of the method to find; never {@code null} or empty - * @param parameterTypes the types of parameters accepted by the method, if any; - * never {@code null} - * @return an {@code Optional} containing the method found; never {@code null} - * but potentially empty if no such method could be found - * @see #findMethod(Class, String, String) - */ - public static Optional findMethod(Class clazz, String methodName, Class... parameterTypes) { - return ReflectionUtils.findMethod(clazz, methodName, parameterTypes); - } - - /** - * Find all distinct {@linkplain Method methods} of the supplied class or - * interface that match the specified {@code predicate}. - * - *

The results will not contain instance methods that are overridden - * or {@code static} methods that are hidden. - * - *

If you're are looking for methods annotated with a certain annotation - * type, consider using - * {@link AnnotationSupport#findAnnotatedMethods(Class, Class, HierarchyTraversalMode)}. - * - * @param clazz the class or interface in which to find the methods; never {@code null} - * @param predicate the method filter; never {@code null} - * @param traversalMode the hierarchy traversal mode; never {@code null} - * @return an immutable list of all such methods found; never {@code null} - * but potentially empty - */ - public static List findMethods(Class clazz, Predicate predicate, - HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - return ReflectionUtils.findMethods(clazz, predicate, - ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); - } - - /** - * Find all nested classes within the supplied class, or inherited by the - * supplied class, that conform to the supplied predicate. - * - *

This method does not search for nested classes - * recursively. - * - *

As of JUnit Platform 1.6, this method detects cycles in inner - * class hierarchies — from the supplied class up to the outermost - * enclosing class — and throws a {@link JUnitException} if such a cycle - * is detected. Cycles within inner class hierarchies below the - * supplied class are not detected by this method. - * - * @param clazz the class to be searched; never {@code null} - * @param predicate the predicate against which the list of nested classes is - * checked; never {@code null} - * @return an immutable list of all such classes found; never {@code null} - * but potentially empty - * @throws JUnitException if a cycle is detected within an inner class hierarchy - */ - public static List> findNestedClasses(Class clazz, Predicate> predicate) - throws JUnitException { - - return ReflectionUtils.findNestedClasses(clazz, predicate); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java deleted file mode 100644 index 005167cc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * Search options for finding an annotation within a class hierarchy. - * - * @since 1.8 - * @see #DEFAULT - * @see #INCLUDE_ENCLOSING_CLASSES - */ -@API(status = EXPERIMENTAL, since = "1.8") -public enum SearchOption { - - /** - * Search the inheritance hierarchy (i.e., the current class, implemented - * interfaces, and superclasses), but do not search on enclosing classes. - * - * @see Class#getSuperclass() - * @see Class#getInterfaces() - */ - DEFAULT, - - /** - * Search the inheritance hierarchy as with the {@link #DEFAULT} search option - * but also search the {@linkplain Class#getEnclosingClass() enclosing class} - * hierarchy for inner classes (i.e., a non-static member classes). - */ - INCLUDE_ENCLOSING_CLASSES; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java deleted file mode 100644 index 6e9c4601..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Maintained common support APIs provided by the JUnit Platform. - * - *

The purpose of this package is to provide {@code TestEngine} and - * {@code Extension} authors convenient access to a subset of internal utility - * methods to assist with their implementation. This prevents re-inventing the - * wheel and ensures that common tasks are handled in third-party engines and - * extensions with the same semantics as within the JUnit Platform itself. - */ - -package org.junit.platform.commons.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java deleted file mode 100644 index 9da0cb56..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.Arrays.asList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; -import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; - -import java.lang.annotation.Annotation; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; - -/** - * Collection of utilities for working with {@linkplain Annotation annotations}. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - *

Some utilities are published via the maintained {@code AnnotationSupport} - * class. - * - * @since 1.0 - * @see Annotation - * @see AnnotatedElement - * @see org.junit.platform.commons.support.AnnotationSupport - */ -@API(status = INTERNAL, since = "1.0") -public final class AnnotationUtils { - - private AnnotationUtils() { - /* no-op */ - } - - private static final ConcurrentHashMap, Boolean> repeatableAnnotationContainerCache = // - new ConcurrentHashMap<>(16); - - /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the supplied optional - * {@code element}. - * - * @see #findAnnotation(Optional, Class) - * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(Optional, Class) - */ - public static boolean isAnnotated(Optional element, - Class annotationType) { - - return findAnnotation(element, annotationType).isPresent(); - } - - /** - * @since 1.8 - * @see #findAnnotation(Parameter, int, Class) - */ - public static boolean isAnnotated(Parameter parameter, int index, Class annotationType) { - return findAnnotation(parameter, index, annotationType).isPresent(); - } - - /** - * Determine if an annotation of {@code annotationType} is either - * present or meta-present on the supplied - * {@code element}. - * - * @param element the element on which to search for the annotation; may be - * {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @return {@code true} if the annotation is present or meta-present - * @see #findAnnotation(AnnotatedElement, Class) - * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(AnnotatedElement, Class) - */ - public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { - return findAnnotation(element, annotationType).isPresent(); - } - - /** - * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(Optional, Class) - */ - public static Optional findAnnotation(Optional element, - Class annotationType) { - - if (element == null || !element.isPresent()) { - return Optional.empty(); - } - - return findAnnotation(element.get(), annotationType); - } - - /** - * @since 1.8 - * @see #findAnnotation(AnnotatedElement, Class) - */ - public static Optional findAnnotation(Parameter parameter, int index, - Class annotationType) { - - return findAnnotation(getEffectiveAnnotatedParameter(parameter, index), annotationType); - } - - /** - * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(AnnotatedElement, Class) - */ - public static Optional findAnnotation(AnnotatedElement element, Class annotationType) { - Preconditions.notNull(annotationType, "annotationType must not be null"); - boolean inherited = annotationType.isAnnotationPresent(Inherited.class); - return findAnnotation(element, annotationType, inherited, new HashSet<>()); - } - - private static Optional findAnnotation(AnnotatedElement element, Class annotationType, - boolean inherited, Set visited) { - - Preconditions.notNull(annotationType, "annotationType must not be null"); - - if (element == null) { - return Optional.empty(); - } - - // Directly present? - A annotation = element.getDeclaredAnnotation(annotationType); - if (annotation != null) { - return Optional.of(annotation); - } - - // Meta-present on directly present annotations? - Optional directMetaAnnotation = findMetaAnnotation(annotationType, element.getDeclaredAnnotations(), - inherited, visited); - if (directMetaAnnotation.isPresent()) { - return directMetaAnnotation; - } - - if (element instanceof Class) { - Class clazz = (Class) element; - - // Search on interfaces - for (Class ifc : clazz.getInterfaces()) { - if (ifc != Annotation.class) { - Optional annotationOnInterface = findAnnotation(ifc, annotationType, inherited, visited); - if (annotationOnInterface.isPresent()) { - return annotationOnInterface; - } - } - } - - // Indirectly present? - // Search in class hierarchy - if (inherited) { - Class superclass = clazz.getSuperclass(); - if (superclass != null && superclass != Object.class) { - Optional annotationOnSuperclass = findAnnotation(superclass, annotationType, inherited, visited); - if (annotationOnSuperclass.isPresent()) { - return annotationOnSuperclass; - } - } - } - } - - // Meta-present on indirectly present annotations? - return findMetaAnnotation(annotationType, element.getAnnotations(), inherited, visited); - } - - private static Optional findMetaAnnotation(Class annotationType, - Annotation[] candidates, boolean inherited, Set visited) { - - for (Annotation candidateAnnotation : candidates) { - Class candidateAnnotationType = candidateAnnotation.annotationType(); - if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidateAnnotation)) { - Optional metaAnnotation = findAnnotation(candidateAnnotationType, annotationType, inherited, - visited); - if (metaAnnotation.isPresent()) { - return metaAnnotation; - } - } - } - return Optional.empty(); - } - - /** - * Find the first annotation of the specified type that is either - * directly present, meta-present, or indirectly - * present on the supplied class, optionally searching recursively - * through the enclosing class hierarchy if not found on the supplied class. - * - *

The enclosing class hierarchy will only be searched above an inner - * class (i.e., a non-static member class). - * - * @param the annotation type - * @param clazz the class on which to search for the annotation; may be {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @param searchEnclosingClasses whether the enclosing class hierarchy should - * be searched - * @return an {@code Optional} containing the annotation; never {@code null} but - * potentially empty - * @since 1.8 - * @see #findAnnotation(AnnotatedElement, Class) - */ - public static Optional findAnnotation(Class clazz, Class annotationType, - boolean searchEnclosingClasses) { - - Preconditions.notNull(annotationType, "annotationType must not be null"); - - if (!searchEnclosingClasses) { - return findAnnotation(clazz, annotationType); - } - - Class candidate = clazz; - while (candidate != null) { - Optional annotation = findAnnotation(candidate, annotationType); - if (annotation.isPresent()) { - return annotation; - } - candidate = (isInnerClass(candidate) ? candidate.getEnclosingClass() : null); - } - return Optional.empty(); - } - - /** - * @since 1.5 - * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(Optional, Class) - */ - public static List findRepeatableAnnotations(Optional element, - Class annotationType) { - - if (element == null || !element.isPresent()) { - return Collections.emptyList(); - } - - return findRepeatableAnnotations(element.get(), annotationType); - } - - /** - * @since 1.8 - * @see #findRepeatableAnnotations(AnnotatedElement, Class) - */ - public static List findRepeatableAnnotations(Parameter parameter, int index, - Class annotationType) { - - return findRepeatableAnnotations(getEffectiveAnnotatedParameter(parameter, index), annotationType); - } - - /** - * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(AnnotatedElement, Class) - */ - public static List findRepeatableAnnotations(AnnotatedElement element, - Class annotationType) { - - Preconditions.notNull(annotationType, "annotationType must not be null"); - Repeatable repeatable = annotationType.getAnnotation(Repeatable.class); - Preconditions.notNull(repeatable, () -> annotationType.getName() + " must be @Repeatable"); - Class containerType = repeatable.value(); - boolean inherited = containerType.isAnnotationPresent(Inherited.class); - - // Short circuit the search algorithm. - if (element == null) { - return Collections.emptyList(); - } - - // We use a LinkedHashSet because the search algorithm may discover - // duplicates, but we need to maintain the original order. - Set found = new LinkedHashSet<>(16); - findRepeatableAnnotations(element, annotationType, containerType, inherited, found, new HashSet<>(16)); - // unmodifiable since returned from public, non-internal method(s) - return Collections.unmodifiableList(new ArrayList<>(found)); - } - - private static void findRepeatableAnnotations(AnnotatedElement element, - Class annotationType, Class containerType, boolean inherited, Set found, - Set visited) { - - if (element instanceof Class) { - Class clazz = (Class) element; - - // Recurse first in order to support top-down semantics for inherited, repeatable annotations. - if (inherited) { - Class superclass = clazz.getSuperclass(); - if (superclass != null && superclass != Object.class) { - findRepeatableAnnotations(superclass, annotationType, containerType, inherited, found, visited); - } - } - - // Search on interfaces - for (Class ifc : clazz.getInterfaces()) { - if (ifc != Annotation.class) { - findRepeatableAnnotations(ifc, annotationType, containerType, inherited, found, visited); - } - } - } - - // Find annotations that are directly present or meta-present on directly present annotations. - findRepeatableAnnotations(element.getDeclaredAnnotations(), annotationType, containerType, inherited, found, - visited); - - // Find annotations that are indirectly present or meta-present on indirectly present annotations. - findRepeatableAnnotations(element.getAnnotations(), annotationType, containerType, inherited, found, visited); - } - - @SuppressWarnings("unchecked") - private static void findRepeatableAnnotations(Annotation[] candidates, - Class annotationType, Class containerType, boolean inherited, Set found, - Set visited) { - - for (Annotation candidate : candidates) { - Class candidateAnnotationType = candidate.annotationType(); - if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidate)) { - // Exact match? - if (candidateAnnotationType.equals(annotationType)) { - found.add(annotationType.cast(candidate)); - } - // Container? - else if (candidateAnnotationType.equals(containerType)) { - // Note: it's not a legitimate containing annotation type if it doesn't declare - // a 'value' attribute that returns an array of the contained annotation type. - // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.3 - Method method = ReflectionUtils.tryToGetMethod(containerType, "value").getOrThrow( - cause -> new JUnitException(String.format( - "Container annotation type '%s' must declare a 'value' attribute of type %s[].", - containerType, annotationType), cause)); - - Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); - found.addAll((Collection) asList(containedAnnotations)); - } - // Nested container annotation? - else if (isRepeatableAnnotationContainer(candidateAnnotationType)) { - Method method = ReflectionUtils.tryToGetMethod(candidateAnnotationType, "value").toOptional().get(); - Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); - - for (Annotation containedAnnotation : containedAnnotations) { - findRepeatableAnnotations(containedAnnotation.getClass(), annotationType, containerType, - inherited, found, visited); - } - } - // Otherwise search recursively through the meta-annotation hierarchy... - else { - findRepeatableAnnotations(candidateAnnotationType, annotationType, containerType, inherited, found, - visited); - } - } - } - } - - /** - * Determine if the supplied annotation type is a container for a repeatable - * annotation. - * - * @since 1.5 - */ - private static boolean isRepeatableAnnotationContainer(Class candidateContainerType) { - return repeatableAnnotationContainerCache.computeIfAbsent(candidateContainerType, candidate -> { - // @formatter:off - Repeatable repeatable = Arrays.stream(candidate.getMethods()) - .filter(attribute -> attribute.getName().equals("value") && attribute.getReturnType().isArray()) - .findFirst() - .map(attribute -> attribute.getReturnType().getComponentType().getAnnotation(Repeatable.class)) - .orElse(null); - // @formatter:on - - return repeatable != null && candidate.equals(repeatable.value()); - }); - } - - /** - * Due to a bug in {@code javac} on JDK versions prior to JDK 9, looking up - * annotations directly on a {@link Parameter} will fail for inner class - * constructors. - * - *

Bug in {@code javac} on JDK versions prior to JDK 9

- * - *

The parameter annotations array in the compiled byte code for the user's - * class excludes an entry for the implicit enclosing instance - * parameter for an inner class constructor. - * - *

Workaround

- * - *

This method provides a workaround for this off-by-one error by helping - * JUnit maintainers and extension authors to access annotations on the preceding - * {@link Parameter} object (i.e., {@code index - 1}). If the supplied - * {@code index} is zero in such situations this method will return {@code null} - * since the parameter for the implicit enclosing instance will never - * be annotated. - * - *

WARNING

- * - *

The {@code AnnotatedElement} returned by this method should never be cast and - * treated as a {@code Parameter} since the metadata (e.g., {@link Parameter#getName()}, - * {@link Parameter#getType()}, etc.) will not match those for the declared parameter - * at the given index in an inner class constructor for code compiled with JDK 8. - * - * @return the supplied {@code Parameter}, or the effective {@code Parameter} - * if the aforementioned bug is detected, or {@code null} if the bug is detected and - * the supplied {@code index} is {@code 0} - * @since 1.8 - */ - private static AnnotatedElement getEffectiveAnnotatedParameter(Parameter parameter, int index) { - Preconditions.notNull(parameter, "Parameter must not be null"); - Executable executable = parameter.getDeclaringExecutable(); - - if (executable instanceof Constructor && isInnerClass(executable.getDeclaringClass()) - && executable.getParameterAnnotations().length == executable.getParameterCount() - 1) { - - if (index == 0) { - return null; - } - - return executable.getParameters()[index - 1]; - } - - return parameter; - } - - /** - * @see org.junit.platform.commons.support.AnnotationSupport#findPublicAnnotatedFields(Class, Class, Class) - */ - public static List findPublicAnnotatedFields(Class clazz, Class fieldType, - Class annotationType) { - - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(fieldType, "fieldType must not be null"); - Preconditions.notNull(annotationType, "annotationType must not be null"); - - // @formatter:off - return Arrays.stream(clazz.getFields()) - .filter(field -> fieldType.isAssignableFrom(field.getType()) && isAnnotated(field, annotationType)) - .collect(toUnmodifiableList()); - // @formatter:on - } - - /** - * Find all {@linkplain Field fields} of the supplied class or interface - * that are annotated or meta-annotated with the specified - * {@code annotationType} and match the specified {@code predicate}, using - * top-down search semantics within the type hierarchy. - * - * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) - */ - public static List findAnnotatedFields(Class clazz, Class annotationType, - Predicate predicate) { - - return findAnnotatedFields(clazz, annotationType, predicate, HierarchyTraversalMode.TOP_DOWN); - } - - /** - * Find all {@linkplain Field fields} of the supplied class or interface - * that are annotated or meta-annotated with the specified - * {@code annotationType} and match the specified {@code predicate}. - * - * @param clazz the class or interface in which to find the fields; never {@code null} - * @param annotationType the annotation type to search for; never {@code null} - * @param predicate the field filter; never {@code null} - * @param traversalMode the hierarchy traversal mode; never {@code null} - * @return the list of all such fields found; neither {@code null} nor mutable - */ - public static List findAnnotatedFields(Class clazz, Class annotationType, - Predicate predicate, HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(annotationType, "annotationType must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - - Predicate annotated = field -> isAnnotated(field, annotationType); - - return ReflectionUtils.findFields(clazz, annotated.and(predicate), traversalMode); - } - - /** - * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotatedMethods(Class, Class, org.junit.platform.commons.support.HierarchyTraversalMode) - */ - public static List findAnnotatedMethods(Class clazz, Class annotationType, - HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(annotationType, "annotationType must not be null"); - - return ReflectionUtils.findMethods(clazz, method -> isAnnotated(method, annotationType), traversalMode); - } - - private static boolean isInJavaLangAnnotationPackage(Class annotationType) { - return (annotationType != null && annotationType.getName().startsWith("java.lang.annotation")); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java deleted file mode 100644 index 2c82dd50..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.DEPRECATED; - -import org.apiguardian.api.API; - -/** - * Internal utilities for working with unrecoverable exceptions. - * - *

Unrecoverable exceptions are those that should always terminate - * test plan execution immediately. - * - *

Currently Unrecoverable Exceptions

- *
    - *
  • {@link OutOfMemoryError}
  • - *
- * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - * @deprecated Use {@link UnrecoverableExceptions} instead. - */ -@Deprecated -@API(status = DEPRECATED, since = "1.7") -public final class BlacklistedExceptions { - - private BlacklistedExceptions() { - /* no-op */ - } - - /** - * Rethrow the supplied {@link Throwable exception} if it is - * unrecoverable. - * - *

If the supplied {@code exception} is not unrecoverable, this - * method does nothing. - * - * @deprecated Use {@link UnrecoverableExceptions#rethrowIfUnrecoverable} - * instead. - */ - @Deprecated - public static void rethrowIfBlacklisted(Throwable exception) { - UnrecoverableExceptions.rethrowIfUnrecoverable(exception); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java deleted file mode 100644 index a6844206..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.nio.file.FileVisitResult.CONTINUE; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.function.Consumer; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * @since 1.0 - */ -class ClassFileVisitor extends SimpleFileVisitor { - - private static final Logger logger = LoggerFactory.getLogger(ClassFileVisitor.class); - - static final String CLASS_FILE_SUFFIX = ".class"; - private static final String PACKAGE_INFO_FILE_NAME = "package-info" + CLASS_FILE_SUFFIX; - private static final String MODULE_INFO_FILE_NAME = "module-info" + CLASS_FILE_SUFFIX; - - private final Consumer classFileConsumer; - - ClassFileVisitor(Consumer classFileConsumer) { - this.classFileConsumer = classFileConsumer; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) { - if (isNotPackageInfo(file) && isNotModuleInfo(file) && isClassFile(file)) { - classFileConsumer.accept(file); - } - return CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException ex) { - logger.warn(ex, () -> "I/O error visiting file: " + file); - return CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException ex) { - if (ex != null) { - logger.warn(ex, () -> "I/O error visiting directory: " + dir); - } - return CONTINUE; - } - - private static boolean isNotPackageInfo(Path path) { - return !path.endsWith(PACKAGE_INFO_FILE_NAME); - } - - private static boolean isNotModuleInfo(Path path) { - return !path.endsWith(MODULE_INFO_FILE_NAME); - } - - private static boolean isClassFile(Path file) { - return file.getFileName().toString().endsWith(CLASS_FILE_SUFFIX); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java deleted file mode 100644 index 63c15fb7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.function.Predicate; - -import org.apiguardian.api.API; - -/** - * Class-related predicate used by reflection utilities. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.1 - */ -@API(status = INTERNAL, since = "1.1") -public class ClassFilter implements Predicate> { - - /** - * Create a {@link ClassFilter} instance that accepts all names but filters classes. - */ - public static ClassFilter of(Predicate> classPredicate) { - return of(name -> true, classPredicate); - } - - /** - * Create a {@link ClassFilter} instance that filters by names and classes. - */ - public static ClassFilter of(Predicate namePredicate, Predicate> classPredicate) { - return new ClassFilter(namePredicate, classPredicate); - } - - private final Predicate namePredicate; - private final Predicate> classPredicate; - - private ClassFilter(Predicate namePredicate, Predicate> classPredicate) { - this.namePredicate = Preconditions.notNull(namePredicate, "name predicate must not be null"); - this.classPredicate = Preconditions.notNull(classPredicate, "class predicate must not be null"); - } - - /** - * Test name using the stored name predicate. - */ - public boolean match(String name) { - return namePredicate.test(name); - } - - /** - * Test class using the stored class predicate. - */ - public boolean match(Class type) { - return classPredicate.test(type); - } - - /** - * @implNote This implementation combines all tests stored in the predicates - * of this instance. Any new predicate must be added to this test method as - * well. - */ - @Override - public boolean test(Class type) { - return match(type.getName()) && match(type); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java deleted file mode 100644 index 0bd035b9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.net.URL; -import java.security.CodeSource; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@linkplain ClassLoader} and associated tasks. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class ClassLoaderUtils { - - private ClassLoaderUtils() { - /* no-op */ - } - - public static ClassLoader getDefaultClassLoader() { - try { - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - if (contextClassLoader != null) { - return contextClassLoader; - } - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - /* otherwise ignore */ - } - return ClassLoader.getSystemClassLoader(); - } - - /** - * Get the location from which the supplied object's class was loaded. - * - * @param object the object for whose class the location should be retrieved - * @return an {@code Optional} containing the URL of the class' location; never - * {@code null} but potentially empty - */ - public static Optional getLocation(Object object) { - Preconditions.notNull(object, "object must not be null"); - // determine class loader - ClassLoader loader = object.getClass().getClassLoader(); - if (loader == null) { - loader = ClassLoader.getSystemClassLoader(); - while (loader != null && loader.getParent() != null) { - loader = loader.getParent(); - } - } - // try finding resource by name - if (loader != null) { - String name = object.getClass().getName(); - name = name.replace(".", "/") + ".class"; - try { - return Optional.ofNullable(loader.getResource(name)); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - /* otherwise ignore */ - } - } - // try protection domain - try { - CodeSource codeSource = object.getClass().getProtectionDomain().getCodeSource(); - if (codeSource != null) { - return Optional.ofNullable(codeSource.getLocation()); - } - } - catch (SecurityException ignore) { - /* ignore */ - } - return Optional.empty(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java deleted file mode 100644 index cea745d1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for creating filters based on class names. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.7 - */ -@API(status = INTERNAL, since = "1.7") -public class ClassNamePatternFilterUtils { - - private ClassNamePatternFilterUtils() { - /* no-op */ - } - - public static final String DEACTIVATE_ALL_PATTERN = "*"; - - /** - * Create a {@link Predicate} that can be used to exclude (i.e., filter out) - * objects of type {@code T} whose fully qualified class names match any of - * the supplied patterns. - * - * @param patterns a comma-separated list of patterns - */ - public static Predicate excludeMatchingClasses(String patterns) { - // @formatter:off - return Optional.ofNullable(patterns) - .filter(StringUtils::isNotBlank) - .map(String::trim) - .map(ClassNamePatternFilterUtils::createPredicateFromPatterns) - .orElse(object -> true); - // @formatter:on - } - - private static Predicate createPredicateFromPatterns(String patterns) { - if (DEACTIVATE_ALL_PATTERN.equals(patterns)) { - return object -> false; - } - List patternList = convertToRegularExpressions(patterns); - return object -> patternList.stream().noneMatch(it -> it.matcher(object.getClass().getName()).matches()); - } - - private static List convertToRegularExpressions(String patterns) { - // @formatter:off - return Arrays.stream(patterns.split(",")) - .filter(StringUtils::isNotBlank) - .map(String::trim) - .map(ClassNamePatternFilterUtils::replaceRegExElements) - .map(Pattern::compile) - .collect(toList()); - // @formatter:on - } - - private static String replaceRegExElements(String pattern) { - return Matcher.quoteReplacement(pattern) - // Match "." against "." and "$" since users may declare a "." instead of a - // "$" as the separator between classes and nested classes. - .replace(".", "[.$]") - // Convert our "*" wildcard into a proper RegEx pattern. - .replace("*", ".+"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java deleted file mode 100644 index 41c44ddb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.joining; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.function.Function; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@link Class classes}. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class ClassUtils { - - private ClassUtils() { - /* no-op */ - } - - /** - * Get the fully qualified name of the supplied class. - * - *

This is a null-safe variant of {@link Class#getName()}. - * - * @param clazz the class whose name should be retrieved, potentially - * {@code null} - * @return the fully qualified class name or {@code "null"} if the supplied - * class reference is {@code null} - * @since 1.3 - * @see #nullSafeToString(Class...) - * @see StringUtils#nullSafeToString(Object) - */ - public static String nullSafeToString(Class clazz) { - return clazz == null ? "null" : clazz.getName(); - } - - /** - * Generate a comma-separated list of fully qualified class names for the - * supplied classes. - * - * @param classes the classes whose names should be included in the - * generated string - * @return a comma-separated list of fully qualified class names, or an empty - * string if the supplied class array is {@code null} or empty - * @see #nullSafeToString(Function, Class...) - * @see StringUtils#nullSafeToString(Object) - */ - public static String nullSafeToString(Class... classes) { - return nullSafeToString(Class::getName, classes); - } - - /** - * Generate a comma-separated list of mapped values for the supplied classes. - * - *

The values are generated by the supplied {@code mapper} - * (e.g., {@code Class::getName}, {@code Class::getSimpleName}, etc.), unless - * a class reference is {@code null} in which case it will be mapped to - * {@code "null"}. - * - * @param mapper the mapper to use; never {@code null} - * @param classes the classes to map - * @return a comma-separated list of mapped values, or an empty string if - * the supplied class array is {@code null} or empty - * @see #nullSafeToString(Class...) - * @see StringUtils#nullSafeToString(Object) - */ - public static String nullSafeToString(Function, ? extends String> mapper, Class... classes) { - Preconditions.notNull(mapper, "Mapping function must not be null"); - - if (classes == null || classes.length == 0) { - return ""; - } - return stream(classes).map(clazz -> clazz == null ? "null" : mapper.apply(clazz)).collect(joining(", ")); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java deleted file mode 100644 index c4589503..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.ClassFileVisitor.CLASS_FILE_SUFFIX; -import static org.junit.platform.commons.util.StringUtils.isNotBlank; - -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.function.Try; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -class ClasspathScanner { - - private static final Logger logger = LoggerFactory.getLogger(ClasspathScanner.class); - - private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/'; - private static final String CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING = String.valueOf( - CLASSPATH_RESOURCE_PATH_SEPARATOR); - private static final char PACKAGE_SEPARATOR_CHAR = '.'; - private static final String PACKAGE_SEPARATOR_STRING = String.valueOf(PACKAGE_SEPARATOR_CHAR); - - /** - * Malformed class name InternalError like reported in #401. - */ - private static final String MALFORMED_CLASS_NAME_ERROR_MESSAGE = "Malformed class name"; - - private final Supplier classLoaderSupplier; - - private final BiFunction>> loadClass; - - ClasspathScanner(Supplier classLoaderSupplier, - BiFunction>> loadClass) { - - this.classLoaderSupplier = classLoaderSupplier; - this.loadClass = loadClass; - } - - List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) { - Preconditions.condition( - PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), - "basePackageName must not be null or blank"); - Preconditions.notNull(classFilter, "classFilter must not be null"); - basePackageName = basePackageName.trim(); - - return findClassesForUris(getRootUrisForPackageNameOnClassPathAndModulePath(basePackageName), basePackageName, - classFilter); - } - - List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter) { - Preconditions.notNull(root, "root must not be null"); - Preconditions.notNull(classFilter, "classFilter must not be null"); - - return findClassesForUri(root, PackageUtils.DEFAULT_PACKAGE_NAME, classFilter); - } - - /** - * Recursively scan for classes in all of the supplied source directories. - */ - private List> findClassesForUris(List baseUris, String basePackageName, ClassFilter classFilter) { - // @formatter:off - return baseUris.stream() - .map(baseUri -> findClassesForUri(baseUri, basePackageName, classFilter)) - .flatMap(Collection::stream) - .distinct() - .collect(toList()); - // @formatter:on - } - - private List> findClassesForUri(URI baseUri, String basePackageName, ClassFilter classFilter) { - try (CloseablePath closeablePath = CloseablePath.create(baseUri)) { - Path baseDir = closeablePath.getPath(); - return findClassesForPath(baseDir, basePackageName, classFilter); - } - catch (PreconditionViolationException ex) { - throw ex; - } - catch (Exception ex) { - logger.warn(ex, () -> "Error scanning files for URI " + baseUri); - return emptyList(); - } - } - - private List> findClassesForPath(Path baseDir, String basePackageName, ClassFilter classFilter) { - Preconditions.condition(Files.exists(baseDir), () -> "baseDir must exist: " + baseDir); - List> classes = new ArrayList<>(); - try { - Files.walkFileTree(baseDir, new ClassFileVisitor( - classFile -> processClassFileSafely(baseDir, basePackageName, classFilter, classFile, classes::add))); - } - catch (IOException ex) { - logger.warn(ex, () -> "I/O error scanning files in " + baseDir); - } - return classes; - } - - private void processClassFileSafely(Path baseDir, String basePackageName, ClassFilter classFilter, Path classFile, - Consumer> classConsumer) { - try { - String fullyQualifiedClassName = determineFullyQualifiedClassName(baseDir, basePackageName, classFile); - if (classFilter.match(fullyQualifiedClassName)) { - try { - // @formatter:off - loadClass.apply(fullyQualifiedClassName, getClassLoader()) - .toOptional() - .filter(classFilter) // Always use ".filter(classFilter)" to include future predicates. - .ifPresent(classConsumer); - // @formatter:on - } - catch (InternalError internalError) { - handleInternalError(classFile, fullyQualifiedClassName, internalError); - } - } - } - catch (Throwable throwable) { - handleThrowable(classFile, throwable); - } - } - - private String determineFullyQualifiedClassName(Path baseDir, String basePackageName, Path classFile) { - // @formatter:off - return Stream.of( - basePackageName, - determineSubpackageName(baseDir, classFile), - determineSimpleClassName(classFile) - ) - .filter(value -> !value.isEmpty()) // Handle default package appropriately. - .collect(joining(PACKAGE_SEPARATOR_STRING)); - // @formatter:on - } - - private String determineSimpleClassName(Path classFile) { - String fileName = classFile.getFileName().toString(); - return fileName.substring(0, fileName.length() - CLASS_FILE_SUFFIX.length()); - } - - private String determineSubpackageName(Path baseDir, Path classFile) { - Path relativePath = baseDir.relativize(classFile.getParent()); - String pathSeparator = baseDir.getFileSystem().getSeparator(); - String subpackageName = relativePath.toString().replace(pathSeparator, PACKAGE_SEPARATOR_STRING); - if (subpackageName.endsWith(pathSeparator)) { - // Workaround for JDK bug: https://bugs.openjdk.java.net/browse/JDK-8153248 - subpackageName = subpackageName.substring(0, subpackageName.length() - pathSeparator.length()); - } - return subpackageName; - } - - private void handleInternalError(Path classFile, String fullyQualifiedClassName, InternalError ex) { - if (MALFORMED_CLASS_NAME_ERROR_MESSAGE.equals(ex.getMessage())) { - logMalformedClassName(classFile, fullyQualifiedClassName, ex); - } - else { - logGenericFileProcessingException(classFile, ex); - } - } - - private void handleThrowable(Path classFile, Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - logGenericFileProcessingException(classFile, throwable); - } - - private void logMalformedClassName(Path classFile, String fullyQualifiedClassName, InternalError ex) { - try { - logger.debug(ex, () -> format("The java.lang.Class loaded from path [%s] has a malformed class name [%s].", - classFile.toAbsolutePath(), fullyQualifiedClassName)); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - ex.addSuppressed(t); - logGenericFileProcessingException(classFile, ex); - } - } - - private void logGenericFileProcessingException(Path classFile, Throwable throwable) { - logger.debug(throwable, () -> format("Failed to load java.lang.Class for path [%s] during classpath scanning.", - classFile.toAbsolutePath())); - } - - private ClassLoader getClassLoader() { - return this.classLoaderSupplier.get(); - } - - private List getRootUrisForPackageNameOnClassPathAndModulePath(String basePackageName) { - Set uriSet = new LinkedHashSet<>(getRootUrisForPackage(basePackageName)); - if (!basePackageName.isEmpty() && !basePackageName.endsWith(PACKAGE_SEPARATOR_STRING)) { - getRootUrisForPackage(basePackageName + PACKAGE_SEPARATOR_STRING).stream() // - .map(ClasspathScanner::removeTrailingClasspathResourcePathSeparator) // - .forEach(uriSet::add); - } - return new ArrayList<>(uriSet); - } - - private static URI removeTrailingClasspathResourcePathSeparator(URI uri) { - String string = uri.toString(); - if (string.endsWith(CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING)) { - return URI.create(string.substring(0, string.length() - 1)); - } - return uri; - } - - private static String packagePath(String packageName) { - if (packageName.isEmpty()) { - return ""; - } - return packageName.replace(PACKAGE_SEPARATOR_CHAR, CLASSPATH_RESOURCE_PATH_SEPARATOR); - } - - private List getRootUrisForPackage(String basePackageName) { - try { - Enumeration resources = getClassLoader().getResources(packagePath(basePackageName)); - List uris = new ArrayList<>(); - while (resources.hasMoreElements()) { - URL resource = resources.nextElement(); - uris.add(resource.toURI()); - } - return uris; - } - catch (Exception ex) { - logger.warn(ex, () -> "Error reading URIs from class loader for base package " + basePackageName); - return emptyList(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java deleted file mode 100644 index e6bb3ddd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.Collections.emptyMap; - -import java.io.Closeable; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; - -/** - * @since 1.0 - */ -final class CloseablePath implements Closeable { - - private static final String FILE_URI_SCHEME = "file"; - private static final String JAR_URI_SCHEME = "jar"; - private static final String JAR_FILE_EXTENSION = ".jar"; - private static final String JAR_URI_SEPARATOR = "!"; - - private static final Closeable NULL_CLOSEABLE = () -> { - }; - - private static final ConcurrentMap MANAGED_FILE_SYSTEMS = new ConcurrentHashMap<>(); - - private final Path path; - private final Closeable delegate; - - static CloseablePath create(URI uri) throws URISyntaxException { - if (JAR_URI_SCHEME.equals(uri.getScheme())) { - String[] parts = uri.toString().split(JAR_URI_SEPARATOR); - String jarUri = parts[0]; - String jarEntry = parts[1]; - return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry)); - } - if (uri.getScheme().equals(FILE_URI_SCHEME) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) { - return createForJarFileSystem(new URI(JAR_URI_SCHEME + ':' + uri), - fileSystem -> fileSystem.getRootDirectories().iterator().next()); - } - return new CloseablePath(Paths.get(uri), NULL_CLOSEABLE); - } - - private static CloseablePath createForJarFileSystem(URI jarUri, Function pathProvider) { - ManagedFileSystem managedFileSystem = MANAGED_FILE_SYSTEMS.compute(jarUri, - (__, oldValue) -> oldValue == null ? new ManagedFileSystem(jarUri) : oldValue.retain()); - Path path = pathProvider.apply(managedFileSystem.fileSystem); - return new CloseablePath(path, - () -> MANAGED_FILE_SYSTEMS.compute(jarUri, (__, ___) -> managedFileSystem.release())); - } - - private CloseablePath(Path path, Closeable delegate) { - this.path = path; - this.delegate = delegate; - } - - public Path getPath() { - return path; - } - - @Override - public void close() throws IOException { - delegate.close(); - } - - private static class ManagedFileSystem { - - private final AtomicInteger referenceCount = new AtomicInteger(1); - private final FileSystem fileSystem; - private final URI jarUri; - - ManagedFileSystem(URI jarUri) { - this.jarUri = jarUri; - try { - fileSystem = FileSystems.newFileSystem(jarUri, emptyMap()); - } - catch (IOException e) { - throw new UncheckedIOException("Failed to create file system for " + jarUri, e); - } - } - - private ManagedFileSystem retain() { - referenceCount.incrementAndGet(); - return this; - } - - private ManagedFileSystem release() { - if (referenceCount.decrementAndGet() == 0) { - close(); - return null; - } - return this; - } - - private void close() { - try { - fileSystem.close(); - } - catch (IOException e) { - throw new UncheckedIOException("Failed to close file system for " + jarUri, e); - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java deleted file mode 100644 index dc6bf37a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.Spliterator.ORDERED; -import static java.util.Spliterators.spliteratorUnknownSize; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; -import static java.util.stream.StreamSupport.stream; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.stream.Collector; -import java.util.stream.DoubleStream; -import java.util.stream.IntStream; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Collection of utilities for working with {@link Collection Collections}. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class CollectionUtils { - - private CollectionUtils() { - /* no-op */ - } - - /** - * Read the only element of a collection of size 1. - * - * @param collection the collection to get the element from - * @return the only element of the collection - * @throws PreconditionViolationException if the collection is {@code null} - * or does not contain exactly one element - */ - public static T getOnlyElement(Collection collection) { - Preconditions.notNull(collection, "collection must not be null"); - Preconditions.condition(collection.size() == 1, - () -> "collection must contain exactly one element: " + collection); - return collection.iterator().next(); - } - - /** - * Convert the supplied array of values to a {@link Set}. - * - * @param values the array of values; never {@code null} - * @return a set of the values - * @throws PreconditionViolationException if the array is {@code null} - * @since 1.6 - */ - @API(status = INTERNAL, since = "1.6") - public static Set toSet(T[] values) { - Preconditions.notNull(values, "values array must not be null"); - if (values.length == 0) { - return Collections.emptySet(); - } - if (values.length == 1) { - return Collections.singleton(values[0]); - } - Set set = new HashSet<>(); - Collections.addAll(set, values); - return set; - } - - /** - * Return a {@code Collector} that accumulates the input elements into a - * new unmodifiable list, in encounter order. - * - *

There are no guarantees on the type or serializability of the list - * returned, so if more control over the returned list is required, - * consider creating a new {@code Collector} implementation like the - * following: - * - *

-	 * public static <T> Collector<T, ?, List<T>> toUnmodifiableList(Supplier<List<T>> listSupplier) {
-	 *     return Collectors.collectingAndThen(Collectors.toCollection(listSupplier), Collections::unmodifiableList);
-	 * }
-	 * 
- * - * @param the type of the input elements - * @return a {@code Collector} which collects all the input elements into - * an unmodifiable list, in encounter order - */ - public static Collector> toUnmodifiableList() { - return collectingAndThen(toList(), Collections::unmodifiableList); - } - - /** - * Determine if an instance of the supplied type can be converted into a - * {@code Stream}. - * - *

If this method returns {@code true}, {@link #toStream(Object)} can - * successfully convert an object of the specified type into a stream. See - * {@link #toStream(Object)} for supported types. - * - * @param type the type to check; may be {@code null} - * @return {@code true} if an instance of the type can be converted into a stream - * @since 1.9.1 - * @see #toStream(Object) - */ - @API(status = INTERNAL, since = "1.9.1") - public static boolean isConvertibleToStream(Class type) { - if (type == null || type == void.class) { - return false; - } - return (Stream.class.isAssignableFrom(type)// - || DoubleStream.class.isAssignableFrom(type)// - || IntStream.class.isAssignableFrom(type)// - || LongStream.class.isAssignableFrom(type)// - || Iterable.class.isAssignableFrom(type)// - || Iterator.class.isAssignableFrom(type)// - || Object[].class.isAssignableFrom(type)// - || (type.isArray() && type.getComponentType().isPrimitive())); - } - - /** - * Convert an object of one of the following supported types into a {@code Stream}. - * - *

    - *
  • {@link Stream}
  • - *
  • {@link DoubleStream}
  • - *
  • {@link IntStream}
  • - *
  • {@link LongStream}
  • - *
  • {@link Collection}
  • - *
  • {@link Iterable}
  • - *
  • {@link Iterator}
  • - *
  • {@link Object} array
  • - *
  • primitive array
  • - *
- * - * @param object the object to convert into a stream; never {@code null} - * @return the resulting stream - * @throws PreconditionViolationException if the supplied object is {@code null} - * or not one of the supported types - * @see #isConvertibleToStream(Class) - */ - public static Stream toStream(Object object) { - Preconditions.notNull(object, "Object must not be null"); - if (object instanceof Stream) { - return (Stream) object; - } - if (object instanceof DoubleStream) { - return ((DoubleStream) object).boxed(); - } - if (object instanceof IntStream) { - return ((IntStream) object).boxed(); - } - if (object instanceof LongStream) { - return ((LongStream) object).boxed(); - } - if (object instanceof Collection) { - return ((Collection) object).stream(); - } - if (object instanceof Iterable) { - return stream(((Iterable) object).spliterator(), false); - } - if (object instanceof Iterator) { - return stream(spliteratorUnknownSize((Iterator) object, ORDERED), false); - } - if (object instanceof Object[]) { - return Arrays.stream((Object[]) object); - } - if (object instanceof double[]) { - return DoubleStream.of((double[]) object).boxed(); - } - if (object instanceof int[]) { - return IntStream.of((int[]) object).boxed(); - } - if (object instanceof long[]) { - return LongStream.of((long[]) object).boxed(); - } - if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) { - return IntStream.range(0, Array.getLength(object)).mapToObj(i -> Array.get(object, i)); - } - throw new PreconditionViolationException( - "Cannot convert instance of " + object.getClass().getName() + " into a Stream: " + object); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java deleted file mode 100644 index d8177037..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with exceptions. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class ExceptionUtils { - - private ExceptionUtils() { - /* no-op */ - } - - /** - * Throw the supplied {@link Throwable}, masked as an - * unchecked exception. - * - *

The supplied {@code Throwable} will not be wrapped. Rather, it - * will be thrown as is using an exploit of the Java language - * that relies on a combination of generics and type erasure to trick - * the Java compiler into believing that the thrown exception is an - * unchecked exception even if it is a checked exception. - * - *

Warning

- * - *

This method should be used sparingly. - * - * @param t the {@code Throwable} to throw as an unchecked exception; - * never {@code null} - * @return this method always throws an exception and therefore never - * returns anything; the return type is merely present to allow this - * method to be supplied as the operand in a {@code throw} statement - */ - public static RuntimeException throwAsUncheckedException(Throwable t) { - Preconditions.notNull(t, "Throwable must not be null"); - ExceptionUtils.throwAs(t); - - // Appeasing the compiler: the following line will never be executed. - return null; - } - - @SuppressWarnings("unchecked") - private static void throwAs(Throwable t) throws T { - throw (T) t; - } - - /** - * Read the stacktrace of the supplied {@link Throwable} into a String. - */ - public static String readStackTrace(Throwable throwable) { - Preconditions.notNull(throwable, "Throwable must not be null"); - StringWriter stringWriter = new StringWriter(); - try (PrintWriter printWriter = new PrintWriter(stringWriter)) { - throwable.printStackTrace(printWriter); - } - return stringWriter.toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java deleted file mode 100644 index 492df5c6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.function.Function; -import java.util.function.Predicate; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@link Function Functions}, - * {@link Predicate Predicates}, etc. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class FunctionUtils { - - private FunctionUtils() { - /* no-op */ - } - - /** - * Return a predicate that first applies the specified function and then - * tests the specified predicate against the result of the function. - * - * @param function the function to apply - * @param predicate the predicate to test against the result of the function - */ - public static Predicate where(Function function, Predicate predicate) { - Preconditions.notNull(function, "function must not be null"); - Preconditions.notNull(predicate, "predicate must not be null"); - return input -> predicate.test(function.apply(input)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java deleted file mode 100644 index dc955396..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apiguardian.api.API; - -/** - * A simple LRU cache with a maximum size. - * - *

This class is not thread-safe. - * - * @param the type of keys maintained by this cache - * @param the type of values maintained by this cache - * @since 1.6 - */ -@API(status = INTERNAL, since = "1.6") -public class LruCache extends LinkedHashMap { - - private static final long serialVersionUID = 1L; - - private final int maxSize; - - /** - * Create a new LRU cache that maintains at most the supplied number of - * entries. - * - *

For optimal use of the internal data structures, you should pick a - * number that's one below a power of two since this is based on a - * {@link java.util.HashMap} and the eldest entry will be evicted after - * adding the entry that increases the {@linkplain #size() size} to be above - * {@code maxSize}. - */ - public LruCache(int maxSize) { - super(maxSize + 1, 1, true); - this.maxSize = maxSize; - } - - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > maxSize; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java deleted file mode 100644 index b230c921..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * Collection of utilities for working with {@code java.lang.Module} - * and friends. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.1 - */ -@API(status = INTERNAL, since = "1.1") -public class ModuleUtils { - - private static final Logger logger = LoggerFactory.getLogger(ModuleUtils.class); - - /** - * Find all non-system boot modules names. - * - * @return a set of all such module names; never {@code null} but - * potentially empty - */ - public static Set findAllNonSystemBootModuleNames() { - logger.config(() -> "Basic version of findAllNonSystemBootModuleNames() always returns an empty set!"); - return emptySet(); - } - - /** - * Determine if the current Java runtime supports the Java Platform Module System. - * - * @return {@code true} if the Java Platform Module System is available, - * otherwise {@code false} - */ - public static boolean isJavaPlatformModuleSystemAvailable() { - return false; - } - - /** - * Return the name of the module that the class or interface is a member of. - * - * @param type class or interface to analyze - * @return the module name; never {@code null} but potentially empty - */ - public static Optional getModuleName(Class type) { - return Optional.empty(); - } - - /** - * Return the raw version of the module that the class or interface is a member of. - * - * @param type class or interface to analyze - * @return the raw module version; never {@code null} but potentially empty - */ - public static Optional getModuleVersion(Class type) { - return Optional.empty(); - } - - /** - * Find all classes for the given module name. - * - * @param moduleName the name of the module to scan; never {@code null} or - * empty - * @param filter the class filter to apply; never {@code null} - * @return an immutable list of all such classes found; never {@code null} - * but potentially empty - */ - public static List> findAllClassesInModule(String moduleName, ClassFilter filter) { - Preconditions.notBlank(moduleName, "Module name must not be null or empty"); - Preconditions.notNull(filter, "Class filter must not be null"); - - logger.config(() -> "Basic version of findAllClassesInModule() always returns an empty list!"); - return emptyList(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java deleted file mode 100644 index 2156b2df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.File; -import java.net.URL; -import java.util.Optional; -import java.util.function.Function; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@linkplain Package packages}. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class PackageUtils { - - private PackageUtils() { - /* no-op */ - } - - static final String DEFAULT_PACKAGE_NAME = ""; - - /** - * Get the package attribute for the supplied {@code type} using the - * supplied {@code function}. - * - *

This method only returns a non-empty {@link Optional} value holder - * if the class loader for the supplied type created a {@link Package} - * object and the supplied function does not return {@code null} when - * applied. - * - * @param type the type to get the package attribute for - * @param function a function that computes the package attribute value - * (e.g., {@code Package::getImplementationTitle}); never {@code null} - * @return an {@code Optional} containing the attribute value; never - * {@code null} but potentially empty - * @throws org.junit.platform.commons.PreconditionViolationException if the - * supplied type or function is {@code null} - * @see Class#getPackage() - * @see Package#getImplementationTitle() - * @see Package#getImplementationVersion() - */ - public static Optional getAttribute(Class type, Function function) { - Preconditions.notNull(type, "type must not be null"); - Preconditions.notNull(function, "function must not be null"); - return Optional.ofNullable(type.getPackage()).map(function); - } - - /** - * Get the value of the specified attribute name, specified as a string, - * or an empty {@link Optional} if the attribute was not found. The attribute - * name is case-insensitive. - * - *

This method also returns an empty {@link Optional} value holder - * if any exception is caught while loading the manifest file via the - * JAR file of the specified type. - * - * @param type the type to get the attribute for - * @param name the attribute name as a string - * @return an {@code Optional} containing the attribute value; never - * {@code null} but potentially empty - * @throws org.junit.platform.commons.PreconditionViolationException if the - * supplied type is {@code null} or the specified name is blank - * @see Manifest#getMainAttributes() - */ - public static Optional getAttribute(Class type, String name) { - Preconditions.notNull(type, "type must not be null"); - Preconditions.notBlank(name, "name must not be blank"); - try { - URL jarUrl = type.getProtectionDomain().getCodeSource().getLocation(); - try (JarFile jarFile = new JarFile(new File(jarUrl.toURI()))) { - Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); - return Optional.ofNullable(mainAttributes.getValue(name)); - } - } - catch (Exception e) { - return Optional.empty(); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java deleted file mode 100644 index 67dcdde4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.DEPRECATED; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -/** - * Thrown if a precondition is violated. - * - * @since 1.0 - * @see Preconditions - * @deprecated Use {@linkplain org.junit.platform.commons.PreconditionViolationException} instead. - */ -@API(status = DEPRECATED, since = "1.5") -@Deprecated -public class PreconditionViolationException extends JUnitException { - - private static final long serialVersionUID = 1L; - - public PreconditionViolationException(String message) { - super(message); - } - - public PreconditionViolationException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java deleted file mode 100644 index 6e64150c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Arrays; -import java.util.Collection; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Collection of utilities for asserting preconditions for method and - * constructor arguments. - * - *

Each method in this class throws a {@link PreconditionViolationException} - * if the precondition is violated. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class Preconditions { - - private Preconditions() { - /* no-op */ - } - - /** - * Assert that the supplied {@link Object} is not {@code null}. - * - * @param object the object to check - * @param message precondition violation message - * @return the supplied object as a convenience - * @throws PreconditionViolationException if the supplied object is {@code null} - * @see #notNull(Object, Supplier) - */ - public static T notNull(T object, String message) throws PreconditionViolationException { - condition(object != null, message); - return object; - } - - /** - * Assert that the supplied {@link Object} is not {@code null}. - * - * @param object the object to check - * @param messageSupplier precondition violation message supplier - * @return the supplied object as a convenience - * @throws PreconditionViolationException if the supplied object is {@code null} - * @see #condition(boolean, Supplier) - */ - public static T notNull(T object, Supplier messageSupplier) throws PreconditionViolationException { - condition(object != null, messageSupplier); - return object; - } - - /** - * Assert that the supplied array is neither {@code null} nor empty. - * - * @param array the array to check - * @param message precondition violation message - * @return the supplied array as a convenience - * @throws PreconditionViolationException if the supplied array is - * {@code null} or empty - * @since 1.9 - * @see #condition(boolean, String) - */ - @API(status = EXPERIMENTAL, since = "1.9") - public static int[] notEmpty(int[] array, String message) throws PreconditionViolationException { - condition(array != null && array.length > 0, message); - return array; - } - - /** - * Assert that the supplied array is neither {@code null} nor empty. - * - *

WARNING: this method does NOT check if the supplied - * array contains any {@code null} elements. - * - * @param array the array to check - * @param message precondition violation message - * @return the supplied array as a convenience - * @throws PreconditionViolationException if the supplied array is - * {@code null} or empty - * @see #containsNoNullElements(Object[], String) - * @see #condition(boolean, String) - */ - public static T[] notEmpty(T[] array, String message) throws PreconditionViolationException { - condition(array != null && array.length > 0, message); - return array; - } - - /** - * Assert that the supplied array is neither {@code null} nor empty. - * - *

WARNING: this method does NOT check if the supplied - * array contains any {@code null} elements. - * - * @param array the array to check - * @param messageSupplier precondition violation message supplier - * @return the supplied array as a convenience - * @throws PreconditionViolationException if the supplied array is - * {@code null} or empty - * @see #containsNoNullElements(Object[], String) - * @see #condition(boolean, String) - */ - public static T[] notEmpty(T[] array, Supplier messageSupplier) throws PreconditionViolationException { - condition(array != null && array.length > 0, messageSupplier); - return array; - } - - /** - * Assert that the supplied {@link Collection} is neither {@code null} nor empty. - * - *

WARNING: this method does NOT check if the supplied - * collection contains any {@code null} elements. - * - * @param collection the collection to check - * @param message precondition violation message - * @return the supplied collection as a convenience - * @throws PreconditionViolationException if the supplied collection is {@code null} or empty - * @see #containsNoNullElements(Collection, String) - * @see #condition(boolean, String) - */ - public static > T notEmpty(T collection, String message) - throws PreconditionViolationException { - - condition(collection != null && !collection.isEmpty(), message); - return collection; - } - - /** - * Assert that the supplied {@link Collection} is neither {@code null} nor empty. - * - *

WARNING: this method does NOT check if the supplied - * collection contains any {@code null} elements. - * - * @param collection the collection to check - * @param messageSupplier precondition violation message supplier - * @return the supplied collection as a convenience - * @throws PreconditionViolationException if the supplied collection is {@code null} or empty - * @see #containsNoNullElements(Collection, String) - * @see #condition(boolean, String) - */ - public static > T notEmpty(T collection, Supplier messageSupplier) - throws PreconditionViolationException { - - condition(collection != null && !collection.isEmpty(), messageSupplier); - return collection; - } - - /** - * Assert that the supplied array contains no {@code null} elements. - * - *

WARNING: this method does NOT check if the supplied - * array is {@code null} or empty. - * - * @param array the array to check - * @param message precondition violation message - * @return the supplied array as a convenience - * @throws PreconditionViolationException if the supplied array contains - * any {@code null} elements - * @see #notNull(Object, String) - */ - public static T[] containsNoNullElements(T[] array, String message) throws PreconditionViolationException { - if (array != null) { - Arrays.stream(array).forEach(object -> notNull(object, message)); - } - return array; - } - - /** - * Assert that the supplied array contains no {@code null} elements. - * - *

WARNING: this method does NOT check if the supplied - * array is {@code null} or empty. - * - * @param array the array to check - * @param messageSupplier precondition violation message supplier - * @return the supplied array as a convenience - * @throws PreconditionViolationException if the supplied array contains - * any {@code null} elements - * @see #notNull(Object, String) - */ - public static T[] containsNoNullElements(T[] array, Supplier messageSupplier) - throws PreconditionViolationException { - - if (array != null) { - Arrays.stream(array).forEach(object -> notNull(object, messageSupplier)); - } - return array; - } - - /** - * Assert that the supplied collection contains no {@code null} elements. - * - *

WARNING: this method does NOT check if the supplied - * collection is {@code null} or empty. - * - * @param collection the collection to check - * @param message precondition violation message - * @return the supplied collection as a convenience - * @throws PreconditionViolationException if the supplied collection contains - * any {@code null} elements - * @see #notNull(Object, String) - */ - public static > T containsNoNullElements(T collection, String message) - throws PreconditionViolationException { - - if (collection != null) { - collection.forEach(object -> notNull(object, message)); - } - return collection; - } - - /** - * Assert that the supplied collection contains no {@code null} elements. - * - *

WARNING: this method does NOT check if the supplied - * collection is {@code null} or empty. - * - * @param collection the collection to check - * @param messageSupplier precondition violation message supplier - * @return the supplied collection as a convenience - * @throws PreconditionViolationException if the supplied collection contains - * any {@code null} elements - * @see #notNull(Object, String) - */ - public static > T containsNoNullElements(T collection, Supplier messageSupplier) - throws PreconditionViolationException { - - if (collection != null) { - collection.forEach(object -> notNull(object, messageSupplier)); - } - return collection; - } - - /** - * Assert that the supplied {@link String} is not blank. - * - *

A {@code String} is blank if it is {@code null} or consists - * only of whitespace characters. - * - * @param str the string to check - * @param message precondition violation message - * @return the supplied string as a convenience - * @throws PreconditionViolationException if the supplied string is blank - * @see #notBlank(String, Supplier) - */ - public static String notBlank(String str, String message) throws PreconditionViolationException { - condition(StringUtils.isNotBlank(str), message); - return str; - } - - /** - * Assert that the supplied {@link String} is not blank. - * - *

A {@code String} is blank if it is {@code null} or consists - * only of whitespace characters. - * - * @param str the string to check - * @param messageSupplier precondition violation message supplier - * @return the supplied string as a convenience - * @throws PreconditionViolationException if the supplied string is blank - * @see StringUtils#isNotBlank(String) - * @see #condition(boolean, Supplier) - */ - public static String notBlank(String str, Supplier messageSupplier) throws PreconditionViolationException { - condition(StringUtils.isNotBlank(str), messageSupplier); - return str; - } - - /** - * Assert that the supplied {@code predicate} is {@code true}. - * - * @param predicate the predicate to check - * @param message precondition violation message - * @throws PreconditionViolationException if the predicate is {@code false} - * @see #condition(boolean, Supplier) - */ - public static void condition(boolean predicate, String message) throws PreconditionViolationException { - if (!predicate) { - throw new PreconditionViolationException(message); - } - } - - /** - * Assert that the supplied {@code predicate} is {@code true}. - * - * @param predicate the predicate to check - * @param messageSupplier precondition violation message supplier - * @throws PreconditionViolationException if the predicate is {@code false} - */ - public static void condition(boolean predicate, Supplier messageSupplier) - throws PreconditionViolationException { - - if (!predicate) { - throw new PreconditionViolationException(messageSupplier.get()); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java deleted file mode 100644 index c20c93b9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ /dev/null @@ -1,1757 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; - -import java.io.File; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.function.Try; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * Collection of utilities for working with the Java reflection APIs. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - *

Some utilities are published via the maintained {@code ReflectionSupport} - * class. - * - * @since 1.0 - * @see org.junit.platform.commons.support.ReflectionSupport - */ -@API(status = INTERNAL, since = "1.0") -public final class ReflectionUtils { - - private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class); - - private ReflectionUtils() { - /* no-op */ - } - - /** - * Modes in which a hierarchy can be traversed — for example, when - * searching for methods or fields within a class hierarchy. - */ - public enum HierarchyTraversalMode { - - /** - * Traverse the hierarchy using top-down semantics. - */ - TOP_DOWN, - - /** - * Traverse the hierarchy using bottom-up semantics. - */ - BOTTOM_UP; - } - - // Pattern: "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. - private static final Pattern VM_INTERNAL_OBJECT_ARRAY_PATTERN = Pattern.compile("^(\\[+)L(.+);$"); - - /** - * Pattern: "[x", "[[[[x", etc., where x is Z, B, C, D, F, I, J, S, etc. - * - *

The pattern intentionally captures the last bracket with the - * capital letter so that the combination can be looked up via - * {@link #classNameToTypeMap}. For example, the last matched group - * will contain {@code "[I"} instead of {@code "I"}. - * - * @see Class#getName() - */ - private static final Pattern VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN = Pattern.compile("^(\\[+)(\\[[ZBCDFIJS])$"); - - // Pattern: "java.lang.String[]", "int[]", "int[][][][]", etc. - // ?> => non-capturing atomic group - // ++ => possessive quantifier - private static final Pattern SOURCE_CODE_SYNTAX_ARRAY_PATTERN = Pattern.compile("^([^\\[\\]]+)((?>\\[\\])++)$"); - - private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - - private static final ClasspathScanner classpathScanner = new ClasspathScanner( - ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); - - /** - * Set of fully qualified class names for which no cycles have been detected - * in inner class hierarchies. - *

This serves as a cache to avoid repeated cycle detection for classes - * that have already been checked. - * @since 1.6 - * @see #detectInnerClassCycle(Class) - */ - private static final Set noCyclesDetectedCache = ConcurrentHashMap.newKeySet(); - - /** - * Internal cache of common class names mapped to their types. - */ - private static final Map> classNameToTypeMap; - - /** - * Internal cache of primitive types mapped to their wrapper types. - */ - private static final Map, Class> primitiveToWrapperMap; - - static { - // @formatter:off - List> commonTypes = Arrays.asList( - boolean.class, - byte.class, - char.class, - short.class, - int.class, - long.class, - float.class, - double.class, - - boolean[].class, - byte[].class, - char[].class, - short[].class, - int[].class, - long[].class, - float[].class, - double[].class, - - boolean[][].class, - byte[][].class, - char[][].class, - short[][].class, - int[][].class, - long[][].class, - float[][].class, - double[][].class, - - Boolean.class, - Byte.class, - Character.class, - Short.class, - Integer.class, - Long.class, - Float.class, - Double.class, - String.class, - - Boolean[].class, - Byte[].class, - Character[].class, - Short[].class, - Integer[].class, - Long[].class, - Float[].class, - Double[].class, - String[].class, - - Boolean[][].class, - Byte[][].class, - Character[][].class, - Short[][].class, - Integer[][].class, - Long[][].class, - Float[][].class, - Double[][].class, - String[][].class - ); - // @formatter:on - - Map> classNamesToTypes = new HashMap<>(64); - - commonTypes.forEach(type -> { - classNamesToTypes.put(type.getName(), type); - classNamesToTypes.put(type.getCanonicalName(), type); - }); - - classNameToTypeMap = Collections.unmodifiableMap(classNamesToTypes); - - Map, Class> primitivesToWrappers = new IdentityHashMap<>(8); - - primitivesToWrappers.put(boolean.class, Boolean.class); - primitivesToWrappers.put(byte.class, Byte.class); - primitivesToWrappers.put(char.class, Character.class); - primitivesToWrappers.put(short.class, Short.class); - primitivesToWrappers.put(int.class, Integer.class); - primitivesToWrappers.put(long.class, Long.class); - primitivesToWrappers.put(float.class, Float.class); - primitivesToWrappers.put(double.class, Double.class); - - primitiveToWrapperMap = Collections.unmodifiableMap(primitivesToWrappers); - } - - public static boolean isPublic(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return Modifier.isPublic(clazz.getModifiers()); - } - - public static boolean isPublic(Member member) { - Preconditions.notNull(member, "Member must not be null"); - return Modifier.isPublic(member.getModifiers()); - } - - public static boolean isPrivate(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return Modifier.isPrivate(clazz.getModifiers()); - } - - public static boolean isPrivate(Member member) { - Preconditions.notNull(member, "Member must not be null"); - return Modifier.isPrivate(member.getModifiers()); - } - - @API(status = INTERNAL, since = "1.4") - public static boolean isNotPrivate(Class clazz) { - return !isPrivate(clazz); - } - - @API(status = INTERNAL, since = "1.1") - public static boolean isNotPrivate(Member member) { - return !isPrivate(member); - } - - public static boolean isAbstract(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return Modifier.isAbstract(clazz.getModifiers()); - } - - public static boolean isAbstract(Member member) { - Preconditions.notNull(member, "Member must not be null"); - return Modifier.isAbstract(member.getModifiers()); - } - - public static boolean isStatic(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return Modifier.isStatic(clazz.getModifiers()); - } - - @API(status = INTERNAL, since = "1.4") - public static boolean isNotStatic(Class clazz) { - return !isStatic(clazz); - } - - public static boolean isStatic(Member member) { - Preconditions.notNull(member, "Member must not be null"); - return Modifier.isStatic(member.getModifiers()); - } - - @API(status = INTERNAL, since = "1.1") - public static boolean isNotStatic(Member member) { - return !isStatic(member); - } - - /** - * @since 1.5 - */ - @API(status = INTERNAL, since = "1.5") - public static boolean isFinal(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return Modifier.isFinal(clazz.getModifiers()); - } - - /** - * @since 1.5 - */ - @API(status = INTERNAL, since = "1.5") - public static boolean isNotFinal(Class clazz) { - return !isFinal(clazz); - } - - /** - * @since 1.5 - */ - @API(status = INTERNAL, since = "1.5") - public static boolean isFinal(Member member) { - Preconditions.notNull(member, "Member must not be null"); - return Modifier.isFinal(member.getModifiers()); - } - - /** - * @since 1.5 - */ - @API(status = INTERNAL, since = "1.5") - public static boolean isNotFinal(Member member) { - return !isFinal(member); - } - - /** - * Determine if the supplied class is an inner class (i.e., a - * non-static member class). - * - *

Technically speaking (i.e., according to the Java Language - * Specification), "an inner class may be a non-static member class, a - * local class, or an anonymous class." However, this method does not - * return {@code true} for a local or anonymous class. - * - * @param clazz the class to check; never {@code null} - * @return {@code true} if the class is an inner class - */ - public static boolean isInnerClass(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return !isStatic(clazz) && clazz.isMemberClass(); - } - - public static boolean returnsVoid(Method method) { - return method.getReturnType().equals(Void.TYPE); - } - - /** - * Determine if the supplied object is an array. - * - * @param obj the object to test; potentially {@code null} - * @return {@code true} if the object is an array - */ - public static boolean isArray(Object obj) { - return (obj != null && obj.getClass().isArray()); - } - - /** - * Determine if the supplied object is a multidimensional array. - * - * @param obj the object to test; potentially {@code null} - * @return {@code true} if the object is a multidimensional array - * @since 1.3.2 - */ - @API(status = INTERNAL, since = "1.3.2") - public static boolean isMultidimensionalArray(Object obj) { - return (obj != null && obj.getClass().isArray() && obj.getClass().getComponentType().isArray()); - } - - /** - * Determine if an object of the supplied source type can be assigned to the - * supplied target type for the purpose of reflective method invocations. - * - *

In contrast to {@link Class#isAssignableFrom(Class)}, this method - * returns {@code true} if the target type represents a primitive type whose - * wrapper matches the supplied source type. In addition, this method - * also supports - * - * widening conversions for primitive target types. - * - * @param sourceType the non-primitive target type; never {@code null} - * @param targetType the target type; never {@code null} - * @return {@code true} if an object of the source type is assignment compatible - * with the target type - * @since 1.8 - * @see Class#isInstance(Object) - * @see Class#isAssignableFrom(Class) - * @see #isAssignableTo(Object, Class) - */ - public static boolean isAssignableTo(Class sourceType, Class targetType) { - Preconditions.notNull(sourceType, "source type must not be null"); - Preconditions.condition(!sourceType.isPrimitive(), "source type must not be a primitive type"); - Preconditions.notNull(targetType, "target type must not be null"); - - if (targetType.isAssignableFrom(sourceType)) { - return true; - } - - if (targetType.isPrimitive()) { - return sourceType == primitiveToWrapperMap.get(targetType) || isWideningConversion(sourceType, targetType); - } - - return false; - } - - /** - * Determine if the supplied object can be assigned to the supplied target - * type for the purpose of reflective method invocations. - * - *

In contrast to {@link Class#isInstance(Object)}, this method returns - * {@code true} if the target type represents a primitive type whose - * wrapper matches the supplied object's type. In addition, this method - * also supports - * - * widening conversions for primitive types and their corresponding - * wrapper types. - * - *

If the supplied object is {@code null} and the supplied type does not - * represent a primitive type, this method returns {@code true}. - * - * @param obj the object to test for assignment compatibility; potentially {@code null} - * @param targetType the type to check against; never {@code null} - * @return {@code true} if the object is assignment compatible - * @see Class#isInstance(Object) - * @see Class#isAssignableFrom(Class) - * @see #isAssignableTo(Class, Class) - */ - public static boolean isAssignableTo(Object obj, Class targetType) { - Preconditions.notNull(targetType, "target type must not be null"); - - if (obj == null) { - return !targetType.isPrimitive(); - } - - if (targetType.isInstance(obj)) { - return true; - } - - if (targetType.isPrimitive()) { - Class sourceType = obj.getClass(); - return sourceType == primitiveToWrapperMap.get(targetType) || isWideningConversion(sourceType, targetType); - } - - return false; - } - - /** - * Determine if Java supports a widening primitive conversion from the - * supplied source type to the supplied primitive target type. - */ - static boolean isWideningConversion(Class sourceType, Class targetType) { - Preconditions.condition(targetType.isPrimitive(), "targetType must be primitive"); - - boolean isPrimitive = sourceType.isPrimitive(); - boolean isWrapper = primitiveToWrapperMap.containsValue(sourceType); - - // Neither a primitive nor a wrapper? - if (!isPrimitive && !isWrapper) { - return false; - } - - if (isPrimitive) { - sourceType = primitiveToWrapperMap.get(sourceType); - } - - // @formatter:off - if (sourceType == Byte.class) { - return - targetType == short.class || - targetType == int.class || - targetType == long.class || - targetType == float.class || - targetType == double.class; - } - - if (sourceType == Short.class || sourceType == Character.class) { - return - targetType == int.class || - targetType == long.class || - targetType == float.class || - targetType == double.class; - } - - if (sourceType == Integer.class) { - return - targetType == long.class || - targetType == float.class || - targetType == double.class; - } - - if (sourceType == Long.class) { - return - targetType == float.class || - targetType == double.class; - } - - if (sourceType == Float.class) { - return - targetType == double.class; - } - // @formatter:on - - return false; - } - - /** - * Get the wrapper type for the supplied primitive type. - * - * @param type the primitive type for which to retrieve the wrapper type - * @return the corresponding wrapper type or {@code null} if the - * supplied type is {@code null} or not a primitive type - */ - public static Class getWrapperType(Class type) { - return primitiveToWrapperMap.get(type); - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#newInstance(Class, Object...) - * @see #newInstance(Constructor, Object...) - */ - public static T newInstance(Class clazz, Object... args) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(args, "Argument array must not be null"); - Preconditions.containsNoNullElements(args, "Individual arguments must not be null"); - - try { - Class[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); - return newInstance(clazz.getDeclaredConstructor(parameterTypes), args); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); - } - } - - /** - * Create a new instance of type {@code T} by invoking the supplied constructor - * with the supplied arguments. - * - *

The constructor will be made accessible if necessary, and any checked - * exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked} - * as an unchecked exception. - * - * @param constructor the constructor to invoke; never {@code null} - * @param args the arguments to pass to the constructor - * @return the new instance; never {@code null} - * @see #newInstance(Class, Object...) - * @see ExceptionUtils#throwAsUncheckedException(Throwable) - */ - public static T newInstance(Constructor constructor, Object... args) { - Preconditions.notNull(constructor, "Constructor must not be null"); - - try { - return makeAccessible(constructor).newInstance(args); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); - } - } - - /** - * Read the value of a potentially inaccessible or nonexistent field. - * - *

If the field does not exist or the value of the field is {@code null}, - * an empty {@link Optional} will be returned. - * - * @param clazz the class where the field is declared; never {@code null} - * @param fieldName the name of the field; never {@code null} or empty - * @param instance the instance from where the value is to be read; may - * be {@code null} for a static field - * @see #readFieldValue(Field) - * @see #readFieldValue(Field, Object) - * @deprecated Please use {@link #tryToReadFieldValue(Class, String, Object)} - * instead. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - public static Optional readFieldValue(Class clazz, String fieldName, T instance) { - return tryToReadFieldValue(clazz, fieldName, instance).toOptional(); - } - - /** - * Try to read the value of a potentially inaccessible or nonexistent field. - * - *

If the field does not exist or an exception occurs while reading it, a - * failed {@link Try} is returned that contains the corresponding exception. - * - * @param clazz the class where the field is declared; never {@code null} - * @param fieldName the name of the field; never {@code null} or empty - * @param instance the instance from where the value is to be read; may - * be {@code null} for a static field - * @since 1.4 - * @see #tryToReadFieldValue(Field) - * @see #tryToReadFieldValue(Field, Object) - */ - @API(status = INTERNAL, since = "1.4") - public static Try tryToReadFieldValue(Class clazz, String fieldName, T instance) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notBlank(fieldName, "Field name must not be null or blank"); - - // @formatter:off - return Try.call(() -> clazz.getDeclaredField(fieldName)) - .andThen(field -> tryToReadFieldValue(field, instance)); - // @formatter:on - } - - /** - * Read the value of the supplied static field, making it accessible if - * necessary and {@linkplain ExceptionUtils#throwAsUncheckedException masking} - * any checked exception as an unchecked exception. - * - *

If the value of the field is {@code null}, an empty {@link Optional} - * will be returned. - * - * @param field the field to read; never {@code null} - * @see #readFieldValue(Field, Object) - * @see #readFieldValue(Class, String, Object) - * @deprecated Please use {@link #tryToReadFieldValue(Field)} instead. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - public static Optional readFieldValue(Field field) { - return tryToReadFieldValue(field).toOptional(); - } - - /** - * Try to read the value of a potentially inaccessible static field. - * - *

If an exception occurs while reading the field, a failed {@link Try} - * is returned that contains the corresponding exception. - * - * @param field the field to read; never {@code null} - * @since 1.4 - * @see #tryToReadFieldValue(Field, Object) - * @see #tryToReadFieldValue(Class, String, Object) - */ - @API(status = INTERNAL, since = "1.4") - public static Try tryToReadFieldValue(Field field) { - return tryToReadFieldValue(field, null); - } - - /** - * Read the value of the supplied field, making it accessible if necessary - * and {@linkplain ExceptionUtils#throwAsUncheckedException masking} any - * checked exception as an unchecked exception. - * - *

If the value of the field is {@code null}, an empty {@link Optional} - * will be returned. - * - * @param field the field to read; never {@code null} - * @param instance the instance from which the value is to be read; may - * be {@code null} for a static field - * @see #readFieldValue(Field) - * @see #readFieldValue(Class, String, Object) - * @deprecated Please use {@link #tryToReadFieldValue(Field, Object)} - * instead. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - public static Optional readFieldValue(Field field, Object instance) { - return tryToReadFieldValue(field, instance).toOptional(); - } - - /** - * @since 1.4 - * @see org.junit.platform.commons.support.ReflectionSupport#tryToReadFieldValue(Field, Object) - * @see #tryToReadFieldValue(Class, String, Object) - */ - @API(status = INTERNAL, since = "1.4") - public static Try tryToReadFieldValue(Field field, Object instance) { - Preconditions.notNull(field, "Field must not be null"); - Preconditions.condition((instance != null || isStatic(field)), - () -> String.format("Cannot read non-static field [%s] on a null instance.", field)); - - return Try.call(() -> makeAccessible(field).get(instance)); - } - - /** - * Read the values of the supplied fields, making each field accessible if - * necessary and {@linkplain ExceptionUtils#throwAsUncheckedException masking} - * any checked exception as an unchecked exception. - * - * @param fields the list of fields to read; never {@code null} - * @param instance the instance from which the values are to be read; may - * be {@code null} for static fields - * @return an immutable list of the values of the specified fields; never - * {@code null} but may be empty or contain {@code null} entries - */ - public static List readFieldValues(List fields, Object instance) { - return readFieldValues(fields, instance, field -> true); - } - - /** - * Read the values of the supplied fields, making each field accessible if - * necessary, {@linkplain ExceptionUtils#throwAsUncheckedException masking} - * any checked exception as an unchecked exception, and filtering out fields - * that do not pass the supplied {@code predicate}. - * - * @param fields the list of fields to read; never {@code null} - * @param instance the instance from which the values are to be read; may - * be {@code null} for static fields - * @param predicate the field filter; never {@code null} - * @return an immutable list of the values of the specified fields; never - * {@code null} but may be empty or contain {@code null} entries - */ - public static List readFieldValues(List fields, Object instance, Predicate predicate) { - Preconditions.notNull(fields, "fields list must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - - // @formatter:off - return fields.stream() - .filter(predicate) - .map(field -> - tryToReadFieldValue(field, instance) - .getOrThrow(ExceptionUtils::throwAsUncheckedException)) - .collect(toUnmodifiableList()); - // @formatter:on - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#invokeMethod(Method, Object, Object...) - */ - public static Object invokeMethod(Method method, Object target, Object... args) { - Preconditions.notNull(method, "Method must not be null"); - Preconditions.condition((target != null || isStatic(method)), - () -> String.format("Cannot invoke non-static method [%s] on a null target.", method.toGenericString())); - - try { - return makeAccessible(method).invoke(target, args); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); - } - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#loadClass(String) - * @deprecated Please use {@link #tryToLoadClass(String)} instead. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - public static Optional> loadClass(String name) { - return tryToLoadClass(name).toOptional(); - } - - /** - * @since 1.4 - * @see org.junit.platform.commons.support.ReflectionSupport#tryToLoadClass(String) - */ - @API(status = INTERNAL, since = "1.4") - public static Try> tryToLoadClass(String name) { - return tryToLoadClass(name, ClassLoaderUtils.getDefaultClassLoader()); - } - - /** - * Load a class by its primitive name or fully qualified name, - * using the supplied {@link ClassLoader}. - * - *

See {@link org.junit.platform.commons.support.ReflectionSupport#loadClass(String)} - * for details on support for class names for arrays. - * - * @param name the name of the class to load; never {@code null} or blank - * @param classLoader the {@code ClassLoader} to use; never {@code null} - * @see #loadClass(String) - * @deprecated Please use {@link #tryToLoadClass(String, ClassLoader)} - * instead. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - public static Optional> loadClass(String name, ClassLoader classLoader) { - return tryToLoadClass(name, classLoader).toOptional(); - } - - /** - * Try to load a class by its primitive name or fully qualified - * name, using the supplied {@link ClassLoader}. - * - *

See {@link org.junit.platform.commons.support.ReflectionSupport#tryToLoadClass(String)} - * for details on support for class names for arrays. - * - * @param name the name of the class to load; never {@code null} or blank - * @param classLoader the {@code ClassLoader} to use; never {@code null} - * @since 1.4 - * @see #tryToLoadClass(String) - */ - @API(status = INTERNAL, since = "1.4") - public static Try> tryToLoadClass(String name, ClassLoader classLoader) { - Preconditions.notBlank(name, "Class name must not be null or blank"); - Preconditions.notNull(classLoader, "ClassLoader must not be null"); - String trimmedName = name.trim(); - - if (classNameToTypeMap.containsKey(trimmedName)) { - return Try.success(classNameToTypeMap.get(trimmedName)); - } - - return Try.call(() -> { - Matcher matcher; - - // Primitive arrays such as "[I", "[[[[D", etc. - matcher = VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - - // Object arrays such as "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. - matcher = VM_INTERNAL_OBJECT_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - - // Arrays such as "java.lang.String[]", "int[]", "int[][][][]", etc. - matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String componentTypeName = matcher.group(1); - String bracketPairs = matcher.group(2); - // Calculate dimensions by counting bracket pairs. - int dimensions = bracketPairs.length() / 2; - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - - // Fallback to standard VM class loading - return classLoader.loadClass(trimmedName); - }); - } - - private static Class loadArrayType(ClassLoader classLoader, String componentTypeName, int dimensions) - throws ClassNotFoundException { - - Class componentType = classNameToTypeMap.containsKey(componentTypeName) - ? classNameToTypeMap.get(componentTypeName) - : classLoader.loadClass(componentTypeName); - - return Array.newInstance(componentType, new int[dimensions]).getClass(); - } - - /** - * Build the fully qualified method name for the method described by the - * supplied class and method. - * - *

Note that the class is not necessarily the class in which the method is - * declared. - * - * @param clazz the class from which the method should be referenced; never {@code null} - * @param method the method; never {@code null} - * @return fully qualified method name; never {@code null} - * @since 1.4 - * @see #getFullyQualifiedMethodName(Class, String, Class...) - */ - public static String getFullyQualifiedMethodName(Class clazz, Method method) { - Preconditions.notNull(method, "Method must not be null"); - - return getFullyQualifiedMethodName(clazz, method.getName(), method.getParameterTypes()); - } - - /** - * Build the fully qualified method name for the method described by the - * supplied class, method name, and parameter types. - * - *

Note that the class is not necessarily the class in which the method is - * declared. - * - * @param clazz the class from which the method should be referenced; never {@code null} - * @param methodName the name of the method; never {@code null} or blank - * @param parameterTypes the parameter types of the method; may be {@code null} or empty - * @return fully qualified method name; never {@code null} - * @see #getFullyQualifiedMethodName(Class, Method) - */ - public static String getFullyQualifiedMethodName(Class clazz, String methodName, Class... parameterTypes) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - - return String.format("%s#%s(%s)", clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes)); - } - - /** - * Parse the supplied fully qualified method name into a 3-element - * {@code String[]} with the following content. - * - *

    - *
  • index {@code 0}: the fully qualified class name
  • - *
  • index {@code 1}: the name of the method
  • - *
  • index {@code 2}: a comma-separated list of parameter types, or a - * blank string if the method does not declare any formal parameters
  • - *
- * - * @param fullyQualifiedMethodName a fully qualified method name, - * never {@code null} or blank - * @return a 3-element array of strings containing the parsed values - */ - public static String[] parseFullyQualifiedMethodName(String fullyQualifiedMethodName) { - Preconditions.notBlank(fullyQualifiedMethodName, "fullyQualifiedMethodName must not be null or blank"); - - int indexOfFirstHashtag = fullyQualifiedMethodName.indexOf('#'); - boolean validSyntax = (indexOfFirstHashtag > 0) - && (indexOfFirstHashtag < fullyQualifiedMethodName.length() - 1); - - Preconditions.condition(validSyntax, - () -> "[" + fullyQualifiedMethodName + "] is not a valid fully qualified method name: " - + "it must start with a fully qualified class name followed by a '#' " - + "and then the method name, optionally followed by a parameter list enclosed in parentheses."); - - String className = fullyQualifiedMethodName.substring(0, indexOfFirstHashtag); - String methodPart = fullyQualifiedMethodName.substring(indexOfFirstHashtag + 1); - String methodName = methodPart; - String methodParameters = ""; - - if (methodPart.endsWith("()")) { - methodName = methodPart.substring(0, methodPart.length() - 2); - } - else if (methodPart.endsWith(")")) { - int indexOfLastOpeningParenthesis = methodPart.lastIndexOf('('); - if ((indexOfLastOpeningParenthesis > 0) && (indexOfLastOpeningParenthesis < methodPart.length() - 1)) { - methodName = methodPart.substring(0, indexOfLastOpeningParenthesis); - methodParameters = methodPart.substring(indexOfLastOpeningParenthesis + 1, methodPart.length() - 1); - } - } - return new String[] { className, methodName, methodParameters }; - } - - /** - * Get the outermost instance of the required type, searching recursively - * through enclosing instances. - * - *

If the supplied inner object is of the required type, it will be - * returned. - * - * @param inner the inner object from which to begin the search; never {@code null} - * @param requiredType the required type of the outermost instance; never {@code null} - * @return an {@code Optional} containing the outermost instance; never {@code null} - * but potentially empty - * @deprecated Please discontinue use of this method since it relies on internal - * implementation details of the JDK that may not work in the future. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - public static Optional getOutermostInstance(Object inner, Class requiredType) { - Preconditions.notNull(inner, "inner object must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - - if (requiredType.isInstance(inner)) { - return Optional.of(inner); - } - - Optional candidate = getOuterInstance(inner); - if (candidate.isPresent()) { - return getOutermostInstance(candidate.get(), requiredType); - } - - return Optional.empty(); - } - - private static Optional getOuterInstance(Object inner) { - // This is risky since it depends on the name of the field which is nowhere guaranteed - // but has been stable so far in all JDKs - - // @formatter:off - return Arrays.stream(inner.getClass().getDeclaredFields()) - .filter(field -> field.getName().startsWith("this$")) - .findFirst() - .map(field -> { - try { - return makeAccessible(field).get(inner); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(t); - } - }); - // @formatter:on - } - - public static Set getAllClasspathRootDirectories() { - // This is quite a hack, since sometimes the classpath is quite different - String fullClassPath = System.getProperty("java.class.path"); - // @formatter:off - return Arrays.stream(fullClassPath.split(File.pathSeparator)) - .map(Paths::get) - .filter(Files::isDirectory) - .collect(toSet()); - // @formatter:on - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInClasspathRoot(URI, Predicate, Predicate) - */ - public static List> findAllClassesInClasspathRoot(URI root, Predicate> classFilter, - Predicate classNameFilter) { - // unmodifiable since returned by public, non-internal method(s) - return findAllClassesInClasspathRoot(root, ClassFilter.of(classNameFilter, classFilter)); - } - - /** - * @since 1.1 - */ - public static List> findAllClassesInClasspathRoot(URI root, ClassFilter classFilter) { - return Collections.unmodifiableList(classpathScanner.scanForClassesInClasspathRoot(root, classFilter)); - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInPackage(String, Predicate, Predicate) - */ - public static List> findAllClassesInPackage(String basePackageName, Predicate> classFilter, - Predicate classNameFilter) { - // unmodifiable since returned by public, non-internal method(s) - return findAllClassesInPackage(basePackageName, ClassFilter.of(classNameFilter, classFilter)); - } - - /** - * @since 1.1 - */ - public static List> findAllClassesInPackage(String basePackageName, ClassFilter classFilter) { - return Collections.unmodifiableList(classpathScanner.scanForClassesInPackage(basePackageName, classFilter)); - } - - /** - * @since 1.1.1 - * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate) - */ - public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, - Predicate classNameFilter) { - // unmodifiable since returned by public, non-internal method(s) - return findAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter)); - } - - /** - * @since 1.1.1 - */ - public static List> findAllClassesInModule(String moduleName, ClassFilter classFilter) { - return Collections.unmodifiableList(ModuleUtils.findAllClassesInModule(moduleName, classFilter)); - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#findNestedClasses(Class, Predicate) - */ - public static List> findNestedClasses(Class clazz, Predicate> predicate) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - - Set> candidates = new LinkedHashSet<>(); - findNestedClasses(clazz, predicate, candidates); - return Collections.unmodifiableList(new ArrayList<>(candidates)); - } - - private static void findNestedClasses(Class clazz, Predicate> predicate, Set> candidates) { - if (!isSearchable(clazz)) { - return; - } - - if (isInnerClass(clazz) && predicate.test(clazz)) { - detectInnerClassCycle(clazz); - } - - try { - // Candidates in current class - for (Class nestedClass : clazz.getDeclaredClasses()) { - if (predicate.test(nestedClass)) { - detectInnerClassCycle(nestedClass); - candidates.add(nestedClass); - } - } - } - catch (NoClassDefFoundError error) { - logger.debug(error, () -> "Failed to retrieve declared classes for " + clazz.getName()); - } - - // Search class hierarchy - findNestedClasses(clazz.getSuperclass(), predicate, candidates); - - // Search interface hierarchy - for (Class ifc : clazz.getInterfaces()) { - findNestedClasses(ifc, predicate, candidates); - } - } - - /** - * Detect a cycle in the inner class hierarchy in which the supplied class - * resides — from the supplied class up to the outermost enclosing class - * — and throw a {@link JUnitException} if a cycle is detected. - * - *

This method does not detect cycles within inner class - * hierarchies below the supplied class. - * - *

If the supplied class is not an inner class and does not have a - * searchable superclass, this method is effectively a no-op. - * - * @since 1.6 - * @see #isInnerClass(Class) - * @see #isSearchable(Class) - */ - private static void detectInnerClassCycle(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - String className = clazz.getName(); - - if (noCyclesDetectedCache.contains(className)) { - return; - } - - Class superclass = clazz.getSuperclass(); - if (isInnerClass(clazz) && isSearchable(superclass)) { - for (Class enclosing = clazz.getEnclosingClass(); enclosing != null; enclosing = enclosing.getEnclosingClass()) { - if (superclass.equals(enclosing)) { - throw new JUnitException(String.format("Detected cycle in inner class hierarchy between %s and %s", - className, enclosing.getName())); - } - } - } - - noCyclesDetectedCache.add(className); - } - - /** - * Get the sole declared, non-synthetic {@link Constructor} for the supplied class. - * - *

Throws a {@link org.junit.platform.commons.PreconditionViolationException} - * if the supplied class declares more than one non-synthetic constructor. - * - * @param clazz the class to get the constructor for - * @return the sole declared constructor; never {@code null} - * @see Class#getDeclaredConstructors() - * @see Class#isSynthetic() - */ - @SuppressWarnings("unchecked") - public static Constructor getDeclaredConstructor(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - try { - List> constructors = Arrays.stream(clazz.getDeclaredConstructors())// - .filter(ctor -> !ctor.isSynthetic())// - .collect(toList()); - - Preconditions.condition(constructors.size() == 1, - () -> String.format("Class [%s] must declare a single constructor", clazz.getName())); - - return (Constructor) constructors.get(0); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); - } - } - - /** - * Find all constructors in the supplied class that match the supplied predicate. - * - *

Note that this method may return {@linkplain Class#isSynthetic() synthetic} - * constructors. If you wish to ignore synthetic constructors, you may filter - * them out with the supplied {@code predicate} or filter them out of the list - * returned by this method. - * - * @param clazz the class in which to search for constructors; never {@code null} - * @param predicate the predicate to use to test for a match; never {@code null} - * @return an immutable list of all such constructors found; never {@code null} - * but potentially empty - * @see Class#getDeclaredConstructors() - * @see Class#isSynthetic() - */ - public static List> findConstructors(Class clazz, Predicate> predicate) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - - try { - // @formatter:off - return Arrays.stream(clazz.getDeclaredConstructors()) - .filter(predicate) - .collect(toUnmodifiableList()); - // @formatter:on - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); - } - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#findFields(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) - */ - public static List findFields(Class clazz, Predicate predicate, - HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - // @formatter:off - return findAllFieldsInHierarchy(clazz, traversalMode).stream() - .filter(predicate) - // unmodifiable since returned by public, non-internal method(s) - .collect(toUnmodifiableList()); - // @formatter:on - } - - private static List findAllFieldsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - // @formatter:off - List localFields = getDeclaredFields(clazz).stream() - .filter(field -> !field.isSynthetic()) - .collect(toList()); - List superclassFields = getSuperclassFields(clazz, traversalMode).stream() - .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) - .collect(toList()); - List interfaceFields = getInterfaceFields(clazz, traversalMode).stream() - .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) - .collect(toList()); - // @formatter:on - - List fields = new ArrayList<>(); - if (traversalMode == TOP_DOWN) { - fields.addAll(superclassFields); - fields.addAll(interfaceFields); - } - fields.addAll(localFields); - if (traversalMode == BOTTOM_UP) { - fields.addAll(interfaceFields); - fields.addAll(superclassFields); - } - return fields; - } - - /** - * Determine if a {@link Method} matching the supplied {@link Predicate} - * is present within the type hierarchy of the specified class, beginning - * with the specified class or interface and traversing up the type - * hierarchy until such a method is found or the type hierarchy is exhausted. - * - * @param clazz the class or interface in which to find the method; never - * {@code null} - * @param predicate the predicate to use to test for a match; never - * {@code null} - * @return {@code true} if such a method is present - * @see #findMethod(Class, String, String) - * @see #findMethod(Class, String, Class...) - */ - public static boolean isMethodPresent(Class clazz, Predicate predicate) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - - return findMethod(clazz, predicate).isPresent(); - } - - /** - * Get the {@link Method} in the specified class with the specified name - * and parameter types. - * - *

This method delegates to {@link Class#getMethod(String, Class...)} but - * swallows any exception thrown. - * - * @param clazz the class in which to search for the method; never {@code null} - * @param methodName the name of the method to get; never {@code null} or blank - * @param parameterTypes the parameter types of the method; may be {@code null} - * or empty - * @return an {@code Optional} containing the method; never {@code null} but - * empty if the invocation of {@code Class#getMethod()} throws a - * {@link NoSuchMethodException} - * @deprecated Please use {@link #tryToGetMethod(Class, String, Class[])} - * instead. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - static Optional getMethod(Class clazz, String methodName, Class... parameterTypes) { - return tryToGetMethod(clazz, methodName, parameterTypes).toOptional(); - } - - /** - * Try to get the {@link Method} in the specified class with the specified - * name and parameter types. - * - *

This method delegates to {@link Class#getMethod(String, Class...)} but - * catches any exception thrown. - * - * @param clazz the class in which to search for the method; never {@code null} - * @param methodName the name of the method to get; never {@code null} or blank - * @param parameterTypes the parameter types of the method; may be {@code null} - * or empty - * @return a successful {@link Try} containing the method or a failed - * {@link Try} containing the {@link NoSuchMethodException} thrown by - * {@code Class#getMethod()}; never {@code null} - * @since 1.4 - */ - @API(status = INTERNAL, since = "1.4") - public static Try tryToGetMethod(Class clazz, String methodName, Class... parameterTypes) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - - return Try.call(() -> clazz.getMethod(methodName, parameterTypes)); - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, String) - */ - public static Optional findMethod(Class clazz, String methodName, String parameterTypeNames) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - return findMethod(clazz, methodName, resolveParameterTypes(clazz, methodName, parameterTypeNames)); - } - - private static Class[] resolveParameterTypes(Class clazz, String methodName, String parameterTypeNames) { - if (StringUtils.isBlank(parameterTypeNames)) { - return EMPTY_CLASS_ARRAY; - } - - // @formatter:off - return Arrays.stream(parameterTypeNames.split(",")) - .map(String::trim) - .map(typeName -> loadRequiredParameterType(clazz, methodName, typeName)) - .toArray(Class[]::new); - // @formatter:on - } - - private static Class loadRequiredParameterType(Class clazz, String methodName, String typeName) { - // @formatter:off - return tryToLoadClass(typeName) - .getOrThrow(cause -> new JUnitException( - String.format("Failed to load parameter type [%s] for method [%s] in class [%s].", - typeName, methodName, clazz.getName()), cause)); - // @formatter:on - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, Class...) - */ - public static Optional findMethod(Class clazz, String methodName, Class... parameterTypes) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - Preconditions.notNull(parameterTypes, "Parameter types array must not be null"); - Preconditions.containsNoNullElements(parameterTypes, "Individual parameter types must not be null"); - - return findMethod(clazz, method -> hasCompatibleSignature(method, methodName, parameterTypes)); - } - - private static Optional findMethod(Class clazz, Predicate predicate) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - - for (Class current = clazz; isSearchable(current); current = current.getSuperclass()) { - // Search for match in current type - List methods = current.isInterface() ? getMethods(current) : getDeclaredMethods(current, BOTTOM_UP); - for (Method method : methods) { - if (predicate.test(method)) { - return Optional.of(method); - } - } - - // Search for match in interfaces implemented by current type - for (Class ifc : current.getInterfaces()) { - Optional optional = findMethod(ifc, predicate); - if (optional.isPresent()) { - return optional; - } - } - } - - return Optional.empty(); - } - - /** - * Find the first {@link Method} of the supplied class or interface that - * meets the specified criteria, beginning with the specified class or - * interface and traversing up the type hierarchy until such a method is - * found or the type hierarchy is exhausted. - * - *

Use this method as an alternative to - * {@link #findMethod(Class, String, Class...)} for use cases in which the - * method is required to be present. - * - * @param clazz the class or interface in which to find the method; - * never {@code null} - * @param methodName the name of the method to find; never {@code null} - * or empty - * @param parameterTypes the types of parameters accepted by the method, - * if any; never {@code null} - * @return the {@code Method} found; never {@code null} - * @throws JUnitException if no method is found - * - * @since 1.7 - * @see #findMethod(Class, String, Class...) - */ - @API(status = STABLE, since = "1.7") - public static Method getRequiredMethod(Class clazz, String methodName, Class... parameterTypes) { - return ReflectionUtils.findMethod(clazz, methodName, parameterTypes).orElseThrow( - () -> new JUnitException(format("Could not find method [%s] in class [%s]", methodName, clazz.getName()))); - } - - /** - * Find all {@linkplain Method methods} of the supplied class or interface - * that match the specified {@code predicate}, using top-down search semantics - * within the type hierarchy. - * - *

The results will not contain instance methods that are overridden - * or {@code static} methods that are hidden. - * - * @param clazz the class or interface in which to find the methods; never {@code null} - * @param predicate the method filter; never {@code null} - * @return an immutable list of all such methods found; never {@code null} - * @see HierarchyTraversalMode#TOP_DOWN - * @see #findMethods(Class, Predicate, HierarchyTraversalMode) - */ - public static List findMethods(Class clazz, Predicate predicate) { - return findMethods(clazz, predicate, TOP_DOWN); - } - - /** - * @see org.junit.platform.commons.support.ReflectionSupport#findMethods(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) - */ - public static List findMethods(Class clazz, Predicate predicate, - HierarchyTraversalMode traversalMode) { - - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(predicate, "Predicate must not be null"); - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - // @formatter:off - return findAllMethodsInHierarchy(clazz, traversalMode).stream() - .filter(predicate) - .distinct() - // unmodifiable since returned by public, non-internal method(s) - .collect(toUnmodifiableList()); - // @formatter:on - } - - /** - * Find all non-synthetic methods in the superclass and interface hierarchy, - * excluding Object. - */ - private static List findAllMethodsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - - // @formatter:off - List localMethods = getDeclaredMethods(clazz, traversalMode).stream() - .filter(method -> !method.isSynthetic()) - .collect(toList()); - List superclassMethods = getSuperclassMethods(clazz, traversalMode).stream() - .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) - .collect(toList()); - List interfaceMethods = getInterfaceMethods(clazz, traversalMode).stream() - .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) - .collect(toList()); - // @formatter:on - - List methods = new ArrayList<>(); - if (traversalMode == TOP_DOWN) { - methods.addAll(superclassMethods); - methods.addAll(interfaceMethods); - } - methods.addAll(localMethods); - if (traversalMode == BOTTOM_UP) { - methods.addAll(interfaceMethods); - methods.addAll(superclassMethods); - } - return methods; - } - - /** - * Custom alternative to {@link Class#getFields()} that sorts the fields - * and converts them to a mutable list. - */ - private static List getFields(Class clazz) { - return toSortedMutableList(clazz.getFields()); - } - - /** - * Custom alternative to {@link Class#getDeclaredFields()} that sorts the - * fields and converts them to a mutable list. - */ - private static List getDeclaredFields(Class clazz) { - return toSortedMutableList(clazz.getDeclaredFields()); - } - - /** - * Custom alternative to {@link Class#getMethods()} that sorts the methods - * and converts them to a mutable list. - */ - private static List getMethods(Class clazz) { - return toSortedMutableList(clazz.getMethods()); - } - - /** - * Custom alternative to {@link Class#getDeclaredMethods()} that sorts the - * methods and converts them to a mutable list. - * - *

In addition, the list returned by this method includes interface - * default methods which are either prepended or appended to the list of - * declared methods depending on the supplied traversal mode. - */ - private static List getDeclaredMethods(Class clazz, HierarchyTraversalMode traversalMode) { - // Note: getDefaultMethods() already sorts the methods, - List defaultMethods = getDefaultMethods(clazz); - List declaredMethods = toSortedMutableList(clazz.getDeclaredMethods()); - - // Take the traversal mode into account in order to retain the inherited - // nature of interface default methods. - if (traversalMode == BOTTOM_UP) { - declaredMethods.addAll(defaultMethods); - return declaredMethods; - } - else { - defaultMethods.addAll(declaredMethods); - return defaultMethods; - } - } - - /** - * Get a sorted, mutable list of all default methods present in interfaces - * implemented by the supplied class which are also visible within - * the supplied class. - * - * @see Method Visibility - * in the Java Language Specification - */ - private static List getDefaultMethods(Class clazz) { - // @formatter:off - // Visible default methods are interface default methods that have not - // been overridden. - List visibleDefaultMethods = Arrays.stream(clazz.getMethods()) - .filter(Method::isDefault) - .collect(toCollection(ArrayList::new)); - if (visibleDefaultMethods.isEmpty()) { - return visibleDefaultMethods; - } - return Arrays.stream(clazz.getInterfaces()) - .map(ReflectionUtils::getMethods) - .flatMap(List::stream) - .filter(visibleDefaultMethods::contains) - .collect(toCollection(ArrayList::new)); - // @formatter:on - } - - private static List toSortedMutableList(Field[] fields) { - // @formatter:off - return Arrays.stream(fields) - .sorted(ReflectionUtils::defaultFieldSorter) - // Use toCollection() instead of toList() to ensure list is mutable. - .collect(toCollection(ArrayList::new)); - // @formatter:on - } - - private static List toSortedMutableList(Method[] methods) { - // @formatter:off - return Arrays.stream(methods) - .sorted(ReflectionUtils::defaultMethodSorter) - // Use toCollection() instead of toList() to ensure list is mutable. - .collect(toCollection(ArrayList::new)); - // @formatter:on - } - - /** - * Field comparator inspired by JUnit 4's {@code org.junit.internal.MethodSorter} - * implementation. - */ - private static int defaultFieldSorter(Field field1, Field field2) { - return Integer.compare(field1.getName().hashCode(), field2.getName().hashCode()); - } - - /** - * Method comparator based upon JUnit 4's {@code org.junit.internal.MethodSorter} - * implementation. - */ - private static int defaultMethodSorter(Method method1, Method method2) { - String name1 = method1.getName(); - String name2 = method2.getName(); - int comparison = Integer.compare(name1.hashCode(), name2.hashCode()); - if (comparison == 0) { - comparison = name1.compareTo(name2); - if (comparison == 0) { - comparison = method1.toString().compareTo(method2.toString()); - } - } - return comparison; - } - - private static List getInterfaceMethods(Class clazz, HierarchyTraversalMode traversalMode) { - List allInterfaceMethods = new ArrayList<>(); - for (Class ifc : clazz.getInterfaces()) { - - // @formatter:off - List localInterfaceMethods = getMethods(ifc).stream() - .filter(m -> !isAbstract(m)) - .collect(toList()); - - List superinterfaceMethods = getInterfaceMethods(ifc, traversalMode).stream() - .filter(method -> !isMethodShadowedByLocalMethods(method, localInterfaceMethods)) - .collect(toList()); - // @formatter:on - - if (traversalMode == TOP_DOWN) { - allInterfaceMethods.addAll(superinterfaceMethods); - } - allInterfaceMethods.addAll(localInterfaceMethods); - if (traversalMode == BOTTOM_UP) { - allInterfaceMethods.addAll(superinterfaceMethods); - } - } - return allInterfaceMethods; - } - - private static List getInterfaceFields(Class clazz, HierarchyTraversalMode traversalMode) { - List allInterfaceFields = new ArrayList<>(); - for (Class ifc : clazz.getInterfaces()) { - List localInterfaceFields = getFields(ifc); - - // @formatter:off - List superinterfaceFields = getInterfaceFields(ifc, traversalMode).stream() - .filter(field -> !isFieldShadowedByLocalFields(field, localInterfaceFields)) - .collect(toList()); - // @formatter:on - - if (traversalMode == TOP_DOWN) { - allInterfaceFields.addAll(superinterfaceFields); - } - allInterfaceFields.addAll(localInterfaceFields); - if (traversalMode == BOTTOM_UP) { - allInterfaceFields.addAll(superinterfaceFields); - } - } - return allInterfaceFields; - } - - private static List getSuperclassFields(Class clazz, HierarchyTraversalMode traversalMode) { - Class superclass = clazz.getSuperclass(); - if (!isSearchable(superclass)) { - return Collections.emptyList(); - } - return findAllFieldsInHierarchy(superclass, traversalMode); - } - - private static boolean isFieldShadowedByLocalFields(Field field, List localFields) { - return localFields.stream().anyMatch(local -> local.getName().equals(field.getName())); - } - - private static List getSuperclassMethods(Class clazz, HierarchyTraversalMode traversalMode) { - Class superclass = clazz.getSuperclass(); - if (!isSearchable(superclass)) { - return Collections.emptyList(); - } - return findAllMethodsInHierarchy(superclass, traversalMode); - } - - private static boolean isMethodShadowedByLocalMethods(Method method, List localMethods) { - return localMethods.stream().anyMatch(local -> isMethodShadowedBy(method, local)); - } - - private static boolean isMethodShadowedBy(Method upper, Method lower) { - return hasCompatibleSignature(upper, lower.getName(), lower.getParameterTypes()); - } - - /** - * Determine if the supplied candidate method (typically a method higher in - * the type hierarchy) has a signature that is compatible with a method that - * has the supplied name and parameter types, taking method sub-signatures - * and generics into account. - */ - private static boolean hasCompatibleSignature(Method candidate, String methodName, Class[] parameterTypes) { - if (!methodName.equals(candidate.getName())) { - return false; - } - if (parameterTypes.length != candidate.getParameterCount()) { - return false; - } - // trivial case: parameter types exactly match - if (Arrays.equals(parameterTypes, candidate.getParameterTypes())) { - return true; - } - // param count is equal, but types do not match exactly: check for method sub-signatures - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2 - for (int i = 0; i < parameterTypes.length; i++) { - Class lowerType = parameterTypes[i]; - Class upperType = candidate.getParameterTypes()[i]; - if (!upperType.isAssignableFrom(lowerType)) { - return false; - } - } - // lower is sub-signature of upper: check for generics in upper method - if (isGeneric(candidate)) { - return true; - } - return false; - } - - static boolean isGeneric(Method method) { - return isGeneric(method.getGenericReturnType()) - || Arrays.stream(method.getGenericParameterTypes()).anyMatch(ReflectionUtils::isGeneric); - } - - private static boolean isGeneric(Type type) { - return type instanceof TypeVariable || type instanceof GenericArrayType; - } - - @SuppressWarnings("deprecation") // "AccessibleObject.isAccessible()" is deprecated in Java 9 - public static T makeAccessible(T object) { - if (!object.isAccessible()) { - object.setAccessible(true); - } - return object; - } - - /** - * Return all classes and interfaces that can be used as assignment types - * for instances of the specified {@link Class}, including itself. - * - * @param clazz the {@code Class} to look up - * @see Class#isAssignableFrom - */ - public static Set> getAllAssignmentCompatibleClasses(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - - Set> result = new LinkedHashSet<>(); - getAllAssignmentCompatibleClasses(clazz, result); - return result; - } - - private static void getAllAssignmentCompatibleClasses(Class clazz, Set> result) { - for (Class current = clazz; current != null; current = current.getSuperclass()) { - result.add(current); - for (Class interfaceClass : current.getInterfaces()) { - if (!result.contains(interfaceClass)) { - getAllAssignmentCompatibleClasses(interfaceClass, result); - } - } - } - } - - /** - * Determine if the supplied class is searchable: is non-null and is - * not equal to the class reference for {@code java.lang.Object}. - * - *

This method is often used to determine if a superclass should be - * searched but may be applicable for other use cases as well. - * @since 1.6 - */ - private static boolean isSearchable(Class clazz) { - return (clazz != null && clazz != Object.class); - } - - /** - * Get the underlying cause of the supplied {@link Throwable}. - * - *

If the supplied {@code Throwable} is an instance of - * {@link InvocationTargetException}, this method will be invoked - * recursively with the underlying - * {@linkplain InvocationTargetException#getTargetException() target - * exception}; otherwise, this method returns the supplied {@code Throwable}. - */ - private static Throwable getUnderlyingCause(Throwable t) { - if (t instanceof InvocationTargetException) { - return getUnderlyingCause(((InvocationTargetException) t).getTargetException()); - } - return t; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java deleted file mode 100644 index cdbdc1fb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@link Runtime}, - * {@link java.lang.management.RuntimeMXBean}, etc. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.6 - */ -@API(status = INTERNAL, since = "1.6") -public final class RuntimeUtils { - - private RuntimeUtils() { - /* no-op */ - } - - /** - * Try to determine whether the VM was started in debug mode or not. - */ - public static boolean isDebugMode() { - return getInputArguments() // - .map(args -> args.stream().anyMatch( - arg -> arg.startsWith("-agentlib:jdwp") || arg.startsWith("-Xrunjdwp"))) // - .orElse(false); - } - - /** - * Try to get the input arguments the VM was started with. - */ - static Optional> getInputArguments() { - Optional> managementFactoryClass = ReflectionUtils.tryToLoadClass( - "java.lang.management.ManagementFactory").toOptional(); - if (!managementFactoryClass.isPresent()) { - return Optional.empty(); - } - // Can't use "java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments()" - // directly as module "java.management" might not be available and/or the current platform - // doesn't support the Java Management Extensions (JMX) API (like Android?). - // See https://github.com/junit-team/junit4/pull/1187 - try { - Object bean = managementFactoryClass.get().getMethod("getRuntimeMXBean").invoke(null); - Class mx = ReflectionUtils.tryToLoadClass("java.lang.management.RuntimeMXBean").get(); - @SuppressWarnings("unchecked") - List args = (List) mx.getMethod("getInputArguments").invoke(bean); - return Optional.of(args); - } - catch (Exception e) { - return Optional.empty(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java deleted file mode 100644 index 11155c0c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Arrays; -import java.util.regex.Pattern; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@link String Strings}, - * {@link CharSequence CharSequences}, etc. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public final class StringUtils { - - private static final Pattern ISO_CONTROL_PATTERN = compileIsoControlPattern(); - private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s"); - - /** - * Guard against "IllegalArgumentException: Unsupported flags: 256" errors. - * @see #1800 - */ - static Pattern compileIsoControlPattern() { - // https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#posix - try { - // All of the characters that Unicode refers to as 'control characters' - return Pattern.compile("\\p{Cntrl}", UNICODE_CHARACTER_CLASS); - } - catch (IllegalArgumentException e) { - // Fall-back to ASCII control characters only: [\x00-\x1F\x7F] - return Pattern.compile("\\p{Cntrl}"); - } - } - - private StringUtils() { - /* no-op */ - } - - /** - * Determine if the supplied {@link String} is blank (i.e., - * {@code null} or consisting only of whitespace characters). - * - * @param str the string to check; may be {@code null} - * @return {@code true} if the string is blank - * @see #isNotBlank(String) - */ - public static boolean isBlank(String str) { - return (str == null || str.trim().isEmpty()); - } - - /** - * Determine if the supplied {@link String} is not {@linkplain #isBlank - * blank}. - * - * @param str the string to check; may be {@code null} - * @return {@code true} if the string is not blank - * @see #isBlank(String) - */ - public static boolean isNotBlank(String str) { - return !isBlank(str); - } - - /** - * Determine if the supplied {@link String} contains any whitespace characters. - * - * @param str the string to check; may be {@code null} - * @return {@code true} if the string contains whitespace - * @see #containsIsoControlCharacter(String) - * @see Character#isWhitespace(int) - */ - public static boolean containsWhitespace(String str) { - return str != null && str.codePoints().anyMatch(Character::isWhitespace); - } - - /** - * Determine if the supplied {@link String} does not contain any whitespace - * characters. - * - * @param str the string to check; may be {@code null} - * @return {@code true} if the string does not contain whitespace - * @see #containsWhitespace(String) - * @see #containsIsoControlCharacter(String) - * @see Character#isWhitespace(int) - */ - public static boolean doesNotContainWhitespace(String str) { - return !containsWhitespace(str); - } - - /** - * Determine if the supplied {@link String} contains any ISO control characters. - * - * @param str the string to check; may be {@code null} - * @return {@code true} if the string contains an ISO control character - * @see #containsWhitespace(String) - * @see Character#isISOControl(int) - */ - public static boolean containsIsoControlCharacter(String str) { - return str != null && str.codePoints().anyMatch(Character::isISOControl); - } - - /** - * Determine if the supplied {@link String} does not contain any ISO control - * characters. - * - * @param str the string to check; may be {@code null} - * @return {@code true} if the string does not contain an ISO control character - * @see #containsIsoControlCharacter(String) - * @see #containsWhitespace(String) - * @see Character#isISOControl(int) - */ - public static boolean doesNotContainIsoControlCharacter(String str) { - return !containsIsoControlCharacter(str); - } - - /** - * Convert the supplied {@code Object} to a {@code String} using the - * following algorithm. - * - *

    - *
  • If the supplied object is {@code null}, this method returns {@code "null"}.
  • - *
  • If the supplied object is a primitive array, the appropriate - * {@code Arrays#toString(...)} variant will be used to convert it to a String.
  • - *
  • If the supplied object is an object array, {@code Arrays#deepToString(Object[])} - * will be used to convert it to a String.
  • - *
  • Otherwise, {@code toString()} will be invoked on the object. If the - * result is non-null, that result will be returned. If the result is - * {@code null}, {@code "null"} will be returned.
  • - *
  • If any of the above results in an exception, this method delegates to - * {@link #defaultToString(Object)}
  • - *
- * - * @param obj the object to convert to a String; may be {@code null} - * @return a String representation of the supplied object; never {@code null} - * @see Arrays#deepToString(Object[]) - * @see ClassUtils#nullSafeToString(Class...) - */ - public static String nullSafeToString(Object obj) { - if (obj == null) { - return "null"; - } - - try { - if (obj.getClass().isArray()) { - if (obj.getClass().getComponentType().isPrimitive()) { - if (obj instanceof boolean[]) { - return Arrays.toString((boolean[]) obj); - } - if (obj instanceof char[]) { - return Arrays.toString((char[]) obj); - } - if (obj instanceof short[]) { - return Arrays.toString((short[]) obj); - } - if (obj instanceof byte[]) { - return Arrays.toString((byte[]) obj); - } - if (obj instanceof int[]) { - return Arrays.toString((int[]) obj); - } - if (obj instanceof long[]) { - return Arrays.toString((long[]) obj); - } - if (obj instanceof float[]) { - return Arrays.toString((float[]) obj); - } - if (obj instanceof double[]) { - return Arrays.toString((double[]) obj); - } - } - return Arrays.deepToString((Object[]) obj); - } - - // else - String result = obj.toString(); - return result != null ? result : "null"; - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - - return defaultToString(obj); - } - } - - /** - * Convert the supplied {@code Object} to a default {@code String} - * representation using the following algorithm. - * - *
    - *
  • If the supplied object is {@code null}, this method returns {@code "null"}.
  • - *
  • Otherwise, the String returned by this method will be generated analogous - * to the default implementation of {@link Object#toString()} by using the supplied - * object's class name and hash code as follows: - * {@code obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj))}
  • - *
- * - * @param obj the object to convert to a String; may be {@code null} - * @return the default String representation of the supplied object; never {@code null} - * @see #nullSafeToString(Object) - * @see ClassUtils#nullSafeToString(Class...) - */ - public static String defaultToString(Object obj) { - if (obj == null) { - return "null"; - } - - return obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj)); - } - - /** - * Replace all ISO control characters in the supplied {@link String}. - * - * @param str the string in which to perform the replacement; may be {@code null} - * @param replacement the replacement string; never {@code null} - * @return the supplied string with all control characters replaced, or - * {@code null} if the supplied string was {@code null} - * @since 1.4 - */ - @API(status = INTERNAL, since = "1.4") - public static String replaceIsoControlCharacters(String str, String replacement) { - Preconditions.notNull(replacement, "replacement must not be null"); - return str == null ? null : ISO_CONTROL_PATTERN.matcher(str).replaceAll(replacement); - } - - /** - * Replace all whitespace characters in the supplied {@link String}. - * - * @param str the string in which to perform the replacement; may be {@code null} - * @param replacement the replacement string; never {@code null} - * @return the supplied string with all whitespace characters replaced, or - * {@code null} if the supplied string was {@code null} - * @since 1.4 - */ - @API(status = INTERNAL, since = "1.4") - public static String replaceWhitespaceCharacters(String str, String replacement) { - Preconditions.notNull(replacement, "replacement must not be null"); - return str == null ? null : WHITESPACE_PATTERN.matcher(str).replaceAll(replacement); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java deleted file mode 100644 index 72695f50..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.lang.String.join; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.ArrayList; -import java.util.List; - -import org.apiguardian.api.API; - -/** - * Simple builder for generating strings in custom implementations of - * {@link Object#toString toString()}. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public class ToStringBuilder { - - private final String typeName; - - private final List values = new ArrayList<>(); - - public ToStringBuilder(Object obj) { - this(Preconditions.notNull(obj, "Object must not be null").getClass().getSimpleName()); - } - - public ToStringBuilder(Class type) { - this(Preconditions.notNull(type, "Class must not be null").getSimpleName()); - } - - @API(status = INTERNAL, since = "1.7") - public ToStringBuilder(String typeName) { - this.typeName = Preconditions.notNull(typeName, "Type name must not be null"); - } - - public ToStringBuilder append(String name, Object value) { - Preconditions.notBlank(name, "Name must not be null or blank"); - this.values.add(name + " = " + toString(value)); - return this; - } - - private String toString(Object obj) { - return (obj instanceof CharSequence) ? ("'" + obj + "'") : StringUtils.nullSafeToString(obj); - } - - @Override - public String toString() { - return this.typeName + " [" + join(", ", this.values) + "]"; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java deleted file mode 100644 index dd730e30..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; - -/** - * Internal utilities for working with unrecoverable exceptions. - * - *

Unrecoverable exceptions are those that should always terminate - * test plan execution immediately. - * - *

Currently Unrecoverable Exceptions

- *
    - *
  • {@link OutOfMemoryError}
  • - *
- * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.7 - */ -@API(status = INTERNAL, since = "1.7") -public final class UnrecoverableExceptions { - - private UnrecoverableExceptions() { - /* no-op */ - } - - /** - * Rethrow the supplied {@link Throwable exception} if it is - * unrecoverable. - * - *

If the supplied {@code exception} is not unrecoverable, this - * method does nothing. - */ - public static void rethrowIfUnrecoverable(Throwable exception) { - if (exception instanceof OutOfMemoryError) { - ExceptionUtils.throwAsUncheckedException(exception); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java deleted file mode 100644 index 11994068..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Internal common utilities for JUnit. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - */ - -package org.junit.platform.commons.util; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java deleted file mode 100644 index ff2b0566..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.function.Predicate.isEqual; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toSet; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.IOException; -import java.lang.module.Configuration; -import java.lang.module.ModuleFinder; -import java.lang.module.ModuleReader; -import java.lang.module.ModuleReference; -import java.lang.module.ResolvedModule; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -/** - * Collection of utilities for working with {@code java.lang.Module} - * and friends. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.1 - */ -@API(status = INTERNAL, since = "1.1") -public class ModuleUtils { - - private static final Logger logger = LoggerFactory.getLogger(ModuleUtils.class); - - /** - * Find all non-system boot modules names. - * - * @return a set of all such module names; never {@code null} but - * potentially empty - */ - public static Set findAllNonSystemBootModuleNames() { - // @formatter:off - Set systemModules = ModuleFinder.ofSystem().findAll().stream() - .map(reference -> reference.descriptor().name()) - .collect(toSet()); - return streamResolvedModules(name -> !systemModules.contains(name)) - .map(ResolvedModule::name) - .collect(toCollection(LinkedHashSet::new)); - // @formatter:on - } - - /** - * Java 9+ runtime supports the Java Platform Module System. - * - * @return {@code true} - */ - public static boolean isJavaPlatformModuleSystemAvailable() { - return true; - } - - public static Optional getModuleName(Class type) { - Preconditions.notNull(type, "Class type must not be null"); - - return Optional.ofNullable(type.getModule().getName()); - } - - public static Optional getModuleVersion(Class type) { - Preconditions.notNull(type, "Class type must not be null"); - - Module module = type.getModule(); - return module.isNamed() ? module.getDescriptor().rawVersion() : Optional.empty(); - } - - /** - * Find all classes for the given module name. - * - * @param moduleName the name of the module to scan; never {@code null} or - * empty - * @param filter the class filter to apply; never {@code null} - * @return an immutable list of all such classes found; never {@code null} - * but potentially empty - */ - public static List> findAllClassesInModule(String moduleName, ClassFilter filter) { - Preconditions.notBlank(moduleName, "Module name must not be null or empty"); - Preconditions.notNull(filter, "Class filter must not be null"); - - logger.debug(() -> "Looking for classes in module: " + moduleName); - // @formatter:off - Set moduleReferences = streamResolvedModules(isEqual(moduleName)) - .map(ResolvedModule::reference) - .collect(toSet()); - // @formatter:on - return scan(moduleReferences, filter, ModuleUtils.class.getClassLoader()); - } - - /** - * Stream resolved modules from current (or boot) module layer. - */ - private static Stream streamResolvedModules(Predicate moduleNamePredicate) { - Module module = ModuleUtils.class.getModule(); - ModuleLayer layer = module.getLayer(); - if (layer == null) { - logger.config(() -> ModuleUtils.class + " is a member of " + module - + " - using boot layer returned by ModuleLayer.boot() as fall-back."); - layer = ModuleLayer.boot(); - } - return streamResolvedModules(moduleNamePredicate, layer); - } - - /** - * Stream resolved modules from the supplied layer. - */ - private static Stream streamResolvedModules(Predicate moduleNamePredicate, - ModuleLayer layer) { - logger.debug(() -> "Streaming modules for layer @" + System.identityHashCode(layer) + ": " + layer); - Configuration configuration = layer.configuration(); - logger.debug(() -> "Module layer configuration: " + configuration); - Stream stream = configuration.modules().stream(); - return stream.filter(module -> moduleNamePredicate.test(module.name())); - } - - /** - * Scan for classes using the supplied set of module references, class - * filter, and loader. - */ - private static List> scan(Set references, ClassFilter filter, ClassLoader loader) { - logger.debug(() -> "Scanning " + references.size() + " module references: " + references); - ModuleReferenceScanner scanner = new ModuleReferenceScanner(filter, loader); - List> classes = new ArrayList<>(); - for (ModuleReference reference : references) { - classes.addAll(scanner.scan(reference)); - } - logger.debug(() -> "Found " + classes.size() + " classes: " + classes); - return Collections.unmodifiableList(classes); - } - - /** - * {@link ModuleReference} scanner. - */ - static class ModuleReferenceScanner { - - private final ClassFilter classFilter; - private final ClassLoader classLoader; - - ModuleReferenceScanner(ClassFilter classFilter, ClassLoader classLoader) { - this.classFilter = classFilter; - this.classLoader = classLoader; - } - - /** - * Scan module reference for classes that potentially contain testable methods. - */ - List> scan(ModuleReference reference) { - try (ModuleReader reader = reference.open()) { - try (Stream names = reader.list()) { - // @formatter:off - return names.filter(name -> name.endsWith(".class")) - .map(this::className) - .filter(name -> !name.equals("module-info")) - .filter(classFilter::match) - .map(this::loadClassUnchecked) - .filter(classFilter::match) - .collect(Collectors.toList()); - // @formatter:on - } - } - catch (IOException e) { - throw new JUnitException("Failed to read contents of " + reference + ".", e); - } - } - - /** - * Convert resource name to binary class name. - */ - private String className(String resourceName) { - resourceName = resourceName.substring(0, resourceName.length() - 6); // 6 = ".class".length() - resourceName = resourceName.replace('/', '.'); - return resourceName; - } - - /** - * Load class by its binary name. - * - * @see ClassLoader#loadClass(String) - */ - private Class loadClassUnchecked(String binaryName) { - try { - return classLoader.loadClass(binaryName); - } - catch (ClassNotFoundException e) { - throw new JUnitException("Failed to load class with name '" + binaryName + "'.", e); - } - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java deleted file mode 100644 index f33ffd31..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Common APIs and support utilities for the JUnit Platform. - * - * @since 1.0 - */ -module org.junit.platform.commons { - requires java.logging; - requires java.management; // needed by RuntimeUtils to determine input arguments - requires static transitive org.apiguardian.api; - - exports org.junit.platform.commons; - exports org.junit.platform.commons.annotation; - exports org.junit.platform.commons.function; - exports org.junit.platform.commons.logging to - org.junit.jupiter.api, - org.junit.jupiter.engine, - org.junit.jupiter.migrationsupport, - org.junit.jupiter.params, - org.junit.platform.console, - org.junit.platform.engine, - org.junit.platform.launcher, - org.junit.platform.reporting, - org.junit.platform.runner, - org.junit.platform.suite.api, - org.junit.platform.suite.engine, - org.junit.platform.testkit, - org.junit.vintage.engine; - exports org.junit.platform.commons.support; - exports org.junit.platform.commons.util to - org.junit.jupiter.api, - org.junit.jupiter.engine, - org.junit.jupiter.migrationsupport, - org.junit.jupiter.params, - org.junit.platform.console, - org.junit.platform.engine, - org.junit.platform.launcher, - org.junit.platform.reporting, - org.junit.platform.runner, - org.junit.platform.suite.api, - org.junit.platform.suite.commons, - org.junit.platform.suite.engine, - org.junit.platform.testkit, - org.junit.vintage.engine; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java deleted file mode 100644 index 70dabdf0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.test; - -import static java.util.concurrent.TimeUnit.SECONDS; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class ConcurrencyTestingUtils { - - public static void executeConcurrently(int threads, Runnable action) throws Exception { - executeConcurrently(threads, () -> { - action.run(); - return null; - }); - } - - public static List executeConcurrently(int threads, Callable action) throws Exception { - ExecutorService executorService = Executors.newFixedThreadPool(threads); - try { - CountDownLatch latch = new CountDownLatch(threads); - List> futures = new ArrayList<>(); - for (int i = 0; i < threads; i++) { - futures.add(CompletableFuture.supplyAsync(() -> { - try { - latch.countDown(); - latch.await(); - return action.call(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new CompletionException(e); - } - catch (Exception e) { - throw new CompletionException("Action failed", e); - } - }, executorService)); - } - List list = new ArrayList<>(); - for (CompletableFuture future : futures) { - list.add(future.get(5, SECONDS)); - } - return list; - } - finally { - executorService.shutdownNow(); - var terminated = executorService.awaitTermination(5, SECONDS); - if (!terminated) { - //noinspection ThrowFromFinallyBlock - throw new AssertionError("ExecutorService did not cleanly shut down"); - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts deleted file mode 100644 index 6a42539e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ /dev/null @@ -1,93 +0,0 @@ -import org.junit.gradle.java.WriteArtifactsFile - -plugins { - `java-library-conventions` - `shadow-conventions` -} - -description = "JUnit Platform Console Standalone" - -dependencies { - shadowed(projects.junitPlatformReporting) - shadowed(projects.junitPlatformConsole) - shadowed(projects.junitPlatformSuite) - shadowed(projects.junitJupiterEngine) - shadowed(projects.junitJupiterParams) - shadowed(projects.junitVintageEngine) - shadowed(libs.apiguardian) { - because("downstream projects need it to avoid compiler warnings") - } -} - -val jupiterVersion = rootProject.version -val vintageVersion: String by project - -tasks { - jar { - manifest { - attributes("Main-Class" to "org.junit.platform.console.ConsoleLauncher") - } - } - val shadowedArtifactsFile by registering(WriteArtifactsFile::class) { - from(configurations.shadowed) - outputFile.set(layout.buildDirectory.file("shadowed-artifacts")) - } - shadowJar { - // https://github.com/junit-team/junit5/issues/2557 - // exclude compiled module declarations from any source (e.g. /*, /META-INF/versions/N/*) - exclude("**/module-info.class") - // https://github.com/junit-team/junit5/issues/761 - // prevent duplicates, add 3rd-party licenses explicitly - exclude("META-INF/LICENSE*.md") - from(project.projects.junitPlatformConsole.dependencyProject.projectDir) { - include("LICENSE-picocli.md") - into("META-INF") - } - from(project.projects.junitJupiterParams.dependencyProject.projectDir) { - include("LICENSE-univocity-parsers.md") - into("META-INF") - } - from(shadowedArtifactsFile) { - into("META-INF") - } - - bundle { - bnd(""" - # Customize the imports because this is an aggregate jar - Import-Package: \ - ${extra["importAPIGuardian"]},\ - kotlin.*;resolution:="optional",\ - * - # Disable the APIGuardian plugin since everything was already - # processed, again because this is an aggregate jar - -export-apiguardian: - """) - } - - mergeServiceFiles() - manifest.apply { - inheritFrom(jar.get().manifest) - attributes(mapOf( - "Specification-Title" to project.name, - "Implementation-Title" to project.name, - // Generate test engine version information in single shared manifest file. - // Pattern of key and value: `"Engine-Version-{YourTestEngine#getId()}": "47.11"` - "Engine-Version-junit-jupiter" to jupiterVersion, - "Engine-Version-junit-vintage" to vintageVersion, - // Version-aware binaries are already included - set Multi-Release flag here. - // See https://openjdk.java.net/jeps/238 for details - // Note: the "jar --update ... --release X" command does not work with the - // shadowed JAR as it contains nested classes that do not comply with multi-release jars. - "Multi-Release" to true - )) - } - } - - // This jar contains some Java 9 code - // (org.junit.platform.console.ConsoleLauncherToolProvider which implements - // java.util.spi.ToolProvider which is @since 9). - // So in order to resolve this, it can only run on Java 9 - osgiProperties { - property("-runee", "JavaSE-9") - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md deleted file mode 100644 index 6aec502b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/LICENSE-picocli.md +++ /dev/null @@ -1,194 +0,0 @@ -Apache License -============== - -_Version 2.0, January 2004_ -_<>_ - -### Terms and Conditions for use, reproduction, and distribution - -#### 1. Definitions - -“License” shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -“Licensor” shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -“Legal Entity” shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, “control” means **(i)** the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the -outstanding shares, or **(iii)** beneficial ownership of such entity. - -“You” (or “Your”) shall mean an individual or Legal Entity exercising -permissions granted by this License. - -“Source” form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -“Object” form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -“Work” shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -“Derivative Works” shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -“Contribution” shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -“submitted” means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as “Not a Contribution.” - -“Contributor” shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -#### 2. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -#### 3. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -#### 4. Redistribution - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -* **(a)** You must give any other recipients of the Work or Derivative Works a copy of -this License; and -* **(b)** You must cause any modified files to carry prominent notices stating that You -changed the files; and -* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. - -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -#### 5. Submission of Contributions - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -#### 6. Trademarks - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -#### 7. Disclaimer of Warranty - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -#### 8. Limitation of Liability - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -#### 9. Accepting Warranty or Additional Liability - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -_END OF TERMS AND CONDITIONS_ - -### APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets `[]` replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same “printed page” as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts deleted file mode 100644 index 6006a8c8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/junit-platform-console.gradle.kts +++ /dev/null @@ -1,75 +0,0 @@ -plugins { - `java-library-conventions` - `shadow-conventions` - `java-multi-release-sources` - `java-repackage-jars` -} - -description = "JUnit Platform Console" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformReporting) - - compileOnlyApi(libs.apiguardian) - - compileOnly(libs.openTestReporting.events) - - shadowed(libs.picocli) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -tasks { - compileModule { - options.compilerArgs.addAll(listOf( - "--add-modules", "org.opentest4j.reporting.events", - "--add-reads", "org.junit.platform.reporting=org.opentest4j.reporting.events" - )) - } - shadowJar { - val release17ClassesDir = sourceSets.mainRelease17.get().output.classesDirs.singleFile - inputs.dir(release17ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) - exclude("META-INF/versions/9/module-info.class") - relocate("picocli", "org.junit.platform.console.shadow.picocli") - from(projectDir) { - include("LICENSE-picocli.md") - into("META-INF") - } - from(sourceSets.mainRelease9.get().output.classesDirs) - doLast(objects.newInstance(org.junit.gradle.java.ExecJarAction::class).apply { - javaLauncher.set(project.javaToolchains.launcherFor(java.toolchain)) - args.addAll( - "--update", - "--file", archiveFile.get().asFile.absolutePath, - "--main-class", "org.junit.platform.console.ConsoleLauncher", - "--release", "17", - "-C", release17ClassesDir.absolutePath, "." - ) - }) - } - codeCoverageClassesJar { - exclude("org/junit/platform/console/options/ConsoleUtils.class") - } - jar { - manifest { - attributes("Main-Class" to "org.junit.platform.console.ConsoleLauncher") - } - } - - // This jar contains some Java 9 code - // (org.junit.platform.console.ConsoleLauncherToolProvider which implements - // java.util.spi.ToolProvider which is @since 9). - // So in order to resolve this, it can only run on Java 9 - osgiProperties { - property("-runee", "JavaSE-9") - } -} - -eclipse { - classpath { - sourceSets -= project.sourceSets.mainRelease9.get() - sourceSets -= project.sourceSets.mainRelease17.get() - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java deleted file mode 100644 index d697987c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.io.PrintStream; -import java.io.PrintWriter; -import java.util.Comparator; -import java.util.StringJoiner; -import java.util.stream.StreamSupport; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.console.options.CommandLineOptionsParser; -import org.junit.platform.console.options.PicocliCommandLineOptionsParser; -import org.junit.platform.console.tasks.ConsoleTestExecutor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; -import org.junit.platform.launcher.listeners.TestExecutionSummary; - -/** - * The {@code ConsoleLauncher} is a stand-alone application for launching the - * JUnit Platform from the console. - * - * @since 1.0 - */ -@API(status = MAINTAINED, since = "1.0") -public class ConsoleLauncher { - - public static void main(String... args) { - int exitCode = execute(System.out, System.err, args).getExitCode(); - System.exit(exitCode); - } - - @API(status = INTERNAL, since = "1.0") - public static ConsoleLauncherExecutionResult execute(PrintStream out, PrintStream err, String... args) { - return execute(new PrintWriter(out), new PrintWriter(err), args); - } - - @API(status = INTERNAL, since = "1.0") - public static ConsoleLauncherExecutionResult execute(PrintWriter out, PrintWriter err, String... args) { - CommandLineOptionsParser parser = new PicocliCommandLineOptionsParser(); - ConsoleLauncher consoleLauncher = new ConsoleLauncher(parser, out, err); - return consoleLauncher.execute(args); - } - - private final CommandLineOptionsParser commandLineOptionsParser; - private final PrintWriter out; - private final PrintWriter err; - - ConsoleLauncher(CommandLineOptionsParser commandLineOptionsParser, PrintWriter out, PrintWriter err) { - this.commandLineOptionsParser = commandLineOptionsParser; - this.out = out; - this.err = err; - } - - ConsoleLauncherExecutionResult execute(String... args) { - try { - CommandLineOptions options = commandLineOptionsParser.parse(args); - if (options.isListEngines()) { - displayEngines(out); - return ConsoleLauncherExecutionResult.success(); - } - if (!options.isBannerDisabled()) { - displayBanner(out); - } - if (options.isDisplayHelp()) { - commandLineOptionsParser.printHelp(out, options.isAnsiColorOutputDisabled()); - return ConsoleLauncherExecutionResult.success(); - } - return executeTests(options, out); - } - catch (JUnitException ex) { - err.println(ex.getMessage()); - err.println(); - commandLineOptionsParser.printHelp(err, false); - return ConsoleLauncherExecutionResult.failed(); - } - finally { - out.flush(); - err.flush(); - } - } - - void displayBanner(PrintWriter out) { - out.println(); - out.println("Thanks for using JUnit! Support its development at https://junit.org/sponsoring"); - out.println(); - } - - void displayEngines(PrintWriter out) { - ServiceLoaderTestEngineRegistry registry = new ServiceLoaderTestEngineRegistry(); - Iterable engines = registry.loadTestEngines(); - StreamSupport.stream(engines.spliterator(), false) // - .sorted(Comparator.comparing(TestEngine::getId)) // - .forEach(engine -> displayEngine(out, engine)); - } - - private void displayEngine(PrintWriter out, TestEngine engine) { - StringJoiner details = new StringJoiner(":", " (", ")"); - engine.getGroupId().ifPresent(details::add); - engine.getArtifactId().ifPresent(details::add); - engine.getVersion().ifPresent(details::add); - out.println(engine.getId() + details); - } - - private ConsoleLauncherExecutionResult executeTests(CommandLineOptions options, PrintWriter out) { - try { - TestExecutionSummary testExecutionSummary = new ConsoleTestExecutor(options).execute(out); - return ConsoleLauncherExecutionResult.forSummary(testExecutionSummary, options); - } - catch (Exception exception) { - exception.printStackTrace(err); - err.println(); - commandLineOptionsParser.printHelp(out, options.isAnsiColorOutputDisabled()); - return ConsoleLauncherExecutionResult.failed(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java deleted file mode 100644 index 23dd8225..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherExecutionResult.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.launcher.listeners.TestExecutionSummary; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public class ConsoleLauncherExecutionResult { - - /** - * Exit code indicating successful execution - */ - private static final int SUCCESS = 0; - - /** - * Exit code indicating test failure(s) - */ - private static final int TEST_FAILED = 1; - - /** - * Exit code indicating no tests found - */ - private static final int NO_TESTS_FOUND = 2; - - /** - * Exit code indicating any failure(s) - */ - private static final int FAILED = -1; - - public static int computeExitCode(TestExecutionSummary summary, CommandLineOptions options) { - if (options.isFailIfNoTests() && summary.getTestsFoundCount() == 0) { - return NO_TESTS_FOUND; - } - return summary.getTotalFailureCount() == 0 ? SUCCESS : TEST_FAILED; - } - - static ConsoleLauncherExecutionResult success() { - return new ConsoleLauncherExecutionResult(SUCCESS, null); - } - - static ConsoleLauncherExecutionResult failed() { - return new ConsoleLauncherExecutionResult(FAILED, null); - } - - static ConsoleLauncherExecutionResult forSummary(TestExecutionSummary summary, CommandLineOptions options) { - int exitCode = computeExitCode(summary, options); - return new ConsoleLauncherExecutionResult(exitCode, summary); - } - - private final int exitCode; - private final TestExecutionSummary testExecutionSummary; - - private ConsoleLauncherExecutionResult(int exitCode, TestExecutionSummary testExecutionSummary) { - this.testExecutionSummary = testExecutionSummary; - this.exitCode = exitCode; - } - - public int getExitCode() { - return exitCode; - } - - public Optional getTestExecutionSummary() { - return Optional.ofNullable(testExecutionSummary); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java deleted file mode 100644 index fce8ad50..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/AvailableOptions.java +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.junit.platform.console.options.CommandLineOptions.DEFAULT_DETAILS; -import static org.junit.platform.console.options.CommandLineOptions.DEFAULT_DETAILS_NAME; -import static org.junit.platform.console.options.CommandLineOptions.DEFAULT_THEME; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UriSelector; - -import picocli.CommandLine; -import picocli.CommandLine.ArgGroup; -import picocli.CommandLine.Command; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Option; -import picocli.CommandLine.ParameterException; -import picocli.CommandLine.ParseResult; -import picocli.CommandLine.Spec; - -/** - * @since 1.0 - */ -@Command(// - name = "ConsoleLauncher", // - abbreviateSynopsis = true, // - sortOptions = false, // - usageHelpWidth = 95, // - showAtFileInUsageHelp = true, // - usageHelpAutoWidth = true, // - description = "Launches the JUnit Platform for test discovery and execution.", // - footerHeading = "%n", // - footer = "For more information, please refer to the JUnit User Guide at%n" // - + "@|underline https://junit.org/junit5/docs/current/user-guide/|@"// -) -class AvailableOptions { - - private static final String CP_OPTION = "cp"; - - @ArgGroup(validate = false, order = 1, heading = "%n@|bold COMMANDS|@%n%n") - CommandOptions commandOptions; - - @ArgGroup(validate = false, order = 2, heading = "%n@|bold SELECTORS|@%n%n") - SelectorOptions selectorOptions; - - @ArgGroup(validate = false, order = 3, heading = "%n@|bold FILTERS|@%n%n") - FilterOptions filterOptions; - - @ArgGroup(validate = false, order = 4, heading = "%n@|bold RUNTIME CONFIGURATION|@%n%n") - RuntimeConfigurationOptions runtimeConfigurationOptions; - - @ArgGroup(validate = false, order = 5, heading = "%n@|bold REPORTING|@%n%n") - ReportingOptions reportingOptions; - - @ArgGroup(validate = false, order = 6, heading = "%n@|bold CONSOLE OUTPUT|@%n%n") - ConsoleOutputOptions consoleOutputOptions; - - static class CommandOptions { - - @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display help information.") - private boolean helpRequested; - - @Option(names = { "--h", "-help" }, help = true, hidden = true) - private boolean helpRequested2; - - @Option(names = { "--list-engines" }, description = "List all observable test engines.") - private boolean listEnginesRequested; - - private void applyTo(CommandLineOptions result) { - result.setDisplayHelp(this.helpRequested || this.helpRequested2); - result.setListEngines(this.listEnginesRequested); - } - } - - static class SelectorOptions { - - @Option(names = { "--scan-classpath", - "--scan-class-path" }, converter = ClasspathEntriesConverter.class, paramLabel = "PATH", arity = "0..1", description = "Scan all directories on the classpath or explicit classpath roots. " // - + "Without arguments, only directories on the system classpath as well as additional classpath " // - + "entries supplied via -" + CP_OPTION + " (directories and JAR files) are scanned. " // - + "Explicit classpath roots that are not on the classpath will be silently ignored. " // - + "This option can be repeated.") - private List selectedClasspathEntries = new ArrayList<>(); - - @Option(names = { "-scan-class-path", - "-scan-classpath" }, converter = ClasspathEntriesConverter.class, arity = "0..1", hidden = true) - private List selectedClasspathEntries2 = new ArrayList<>(); - - @Option(names = "--scan-modules", description = "EXPERIMENTAL: Scan all resolved modules for test discovery.") - private boolean scanModulepath; - - @Option(names = "-scan-modules", hidden = true) - private boolean scanModulepath2; - - @Option(names = { "-u", - "--select-uri" }, paramLabel = "URI", arity = "1", converter = SelectorConverter.Uri.class, description = "Select a URI for test discovery. This option can be repeated.") - private List selectedUris = new ArrayList<>(); - - @Option(names = { "--u", "-select-uri" }, arity = "1", hidden = true, converter = SelectorConverter.Uri.class) - private List selectedUris2 = new ArrayList<>(); - - @Option(names = { "-f", - "--select-file" }, paramLabel = "FILE", arity = "1", converter = SelectorConverter.File.class, description = "Select a file for test discovery. This option can be repeated.") - private List selectedFiles = new ArrayList<>(); - - @Option(names = { "--f", "-select-file" }, arity = "1", hidden = true, converter = SelectorConverter.File.class) - private List selectedFiles2 = new ArrayList<>(); - - @Option(names = { "-d", - "--select-directory" }, paramLabel = "DIR", arity = "1", converter = SelectorConverter.Directory.class, description = "Select a directory for test discovery. This option can be repeated.") - private List selectedDirectories = new ArrayList<>(); - - @Option(names = { "--d", - "-select-directory" }, arity = "1", hidden = true, converter = SelectorConverter.Directory.class) - private List selectedDirectories2 = new ArrayList<>(); - - @Option(names = { "-o", - "--select-module" }, paramLabel = "NAME", arity = "1", converter = SelectorConverter.Module.class, description = "EXPERIMENTAL: Select single module for test discovery. This option can be repeated.") - private List selectedModules = new ArrayList<>(); - - @Option(names = { "--o", - "-select-module" }, arity = "1", converter = SelectorConverter.Module.class, hidden = true) - private List selectedModules2 = new ArrayList<>(); - - @Option(names = { "-p", - "--select-package" }, paramLabel = "PKG", arity = "1", converter = SelectorConverter.Package.class, description = "Select a package for test discovery. This option can be repeated.") - private List selectedPackages = new ArrayList<>(); - - @Option(names = { "--p", - "-select-package" }, arity = "1", hidden = true, converter = SelectorConverter.Package.class) - private List selectedPackages2 = new ArrayList<>(); - - @Option(names = { "-c", - "--select-class" }, paramLabel = "CLASS", arity = "1", converter = SelectorConverter.Class.class, description = "Select a class for test discovery. This option can be repeated.") - private List selectedClasses = new ArrayList<>(); - - @Option(names = { "--c", - "-select-class" }, arity = "1", hidden = true, converter = SelectorConverter.Class.class) - private List selectedClasses2 = new ArrayList<>(); - - @Option(names = { "-m", - "--select-method" }, paramLabel = "NAME", arity = "1", converter = SelectorConverter.Method.class, description = "Select a method for test discovery. This option can be repeated.") - private List selectedMethods = new ArrayList<>(); - - @Option(names = { "--m", - "-select-method" }, arity = "1", hidden = true, converter = SelectorConverter.Method.class) - private List selectedMethods2 = new ArrayList<>(); - - @Option(names = { "-r", - "--select-resource" }, paramLabel = "RESOURCE", arity = "1", converter = SelectorConverter.ClasspathResource.class, description = "Select a classpath resource for test discovery. This option can be repeated.") - private List selectedClasspathResources = new ArrayList<>(); - - @Option(names = { "--r", - "-select-resource" }, arity = "1", hidden = true, converter = SelectorConverter.ClasspathResource.class) - private List selectedClasspathResources2 = new ArrayList<>(); - - @Option(names = { "-i", - "--select-iteration" }, paramLabel = "TYPE:VALUE[INDEX(..INDEX)?(,INDEX(..INDEX)?)*]", arity = "1", converter = SelectorConverter.Iteration.class, description = "Select iterations for test discovery (e.g. method:com.acme.Foo#m()[1..2]). This option can be repeated.") - private List selectedIterations = new ArrayList<>(); - - @Option(names = { "--i", - "-select-iteration" }, arity = "1", hidden = true, converter = SelectorConverter.Iteration.class) - private List selectedIterations2 = new ArrayList<>(); - - private void applyTo(ParseResult parseResult, CommandLineOptions result) { - result.setScanClasspath(parseResult.hasMatchedOption("scan-class-path")); // flag was specified - result.setScanModulepath(this.scanModulepath || this.scanModulepath2); - result.setSelectedModules(merge(this.selectedModules, this.selectedModules2)); - result.setSelectedClasspathEntries(merge(this.selectedClasspathEntries, this.selectedClasspathEntries2)); - result.setSelectedUris(merge(this.selectedUris, this.selectedUris2)); - result.setSelectedFiles(merge(this.selectedFiles, this.selectedFiles2)); - result.setSelectedDirectories(merge(this.selectedDirectories, this.selectedDirectories2)); - result.setSelectedPackages(merge(this.selectedPackages, this.selectedPackages2)); - result.setSelectedClasses(merge(this.selectedClasses, this.selectedClasses2)); - result.setSelectedMethods(merge(this.selectedMethods, this.selectedMethods2)); - result.setSelectedClasspathResources( - merge(this.selectedClasspathResources, this.selectedClasspathResources2)); - result.setSelectedIterations(merge(this.selectedIterations, this.selectedIterations2)); - } - } - - static class FilterOptions { - - @Option(names = { "-n", - "--include-classname" }, paramLabel = "PATTERN", defaultValue = ClassNameFilter.STANDARD_INCLUDE_PATTERN, arity = "1", description = "Provide a regular expression to include only classes whose fully qualified names match. " // - + "To avoid loading classes unnecessarily, the default pattern only includes class " // - + "names that begin with \"Test\" or end with \"Test\" or \"Tests\". " // - + "When this option is repeated, all patterns will be combined using OR semantics. " // - + "Default: ${DEFAULT-VALUE}") - private List includeClassNamePatterns = new ArrayList<>(); - - @Option(names = { "--n", "-include-classname" }, arity = "1", hidden = true) - private List includeClassNamePatterns2 = new ArrayList<>(); - - @Option(names = { "-N", - "--exclude-classname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to exclude those classes whose fully qualified names match. " // - + "When this option is repeated, all patterns will be combined using OR semantics.") - private List excludeClassNamePatterns = new ArrayList<>(); - - @Option(names = { "--N", "-exclude-classname" }, arity = "1", hidden = true) - private List excludeClassNamePatterns2 = new ArrayList<>(); - - @Option(names = { - "--include-package" }, paramLabel = "PKG", arity = "1", description = "Provide a package to be included in the test run. This option can be repeated.") - private List includePackages = new ArrayList<>(); - - @Option(names = { "-include-package" }, arity = "1", hidden = true) - private List includePackages2 = new ArrayList<>(); - - @Option(names = { - "--exclude-package" }, paramLabel = "PKG", arity = "1", description = "Provide a package to be excluded from the test run. This option can be repeated.") - private List excludePackages = new ArrayList<>(); - - @Option(names = { "-exclude-package" }, arity = "1", hidden = true) - private List excludePackages2 = new ArrayList<>(); - - @Option(names = { "-t", - "--include-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to include only tests whose tags match. " - + // - "When this option is repeated, all patterns will be combined using OR semantics.") - private List includedTags = new ArrayList<>(); - - @Option(names = { "--t", "-include-tag" }, arity = "1", hidden = true) - private List includedTags2 = new ArrayList<>(); - - @Option(names = { "-T", - "--exclude-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to exclude those tests whose tags match. " - + // - "When this option is repeated, all patterns will be combined using OR semantics.") - private List excludedTags = new ArrayList<>(); - - @Option(names = { "--T", "-exclude-tag" }, arity = "1", hidden = true) - private List excludedTags2 = new ArrayList<>(); - - @Option(names = { "-e", - "--include-engine" }, paramLabel = "ID", arity = "1", description = "Provide the ID of an engine to be included in the test run. This option can be repeated.") - private List includedEngines = new ArrayList<>(); - - @Option(names = { "--e", "-include-engine" }, arity = "1", hidden = true) - private List includedEngines2 = new ArrayList<>(); - - @Option(names = { "-E", - "--exclude-engine" }, paramLabel = "ID", arity = "1", description = "Provide the ID of an engine to be excluded from the test run. This option can be repeated.") - private List excludedEngines = new ArrayList<>(); - - @Option(names = { "--E", "-exclude-engine" }, arity = "1", hidden = true) - private List excludedEngines2 = new ArrayList<>(); - - private void applyTo(CommandLineOptions result) { - result.setIncludedClassNamePatterns(merge(this.includeClassNamePatterns, this.includeClassNamePatterns2)); - result.setExcludedClassNamePatterns(merge(this.excludeClassNamePatterns, this.excludeClassNamePatterns2)); - result.setIncludedPackages(merge(this.includePackages, this.includePackages2)); - result.setExcludedPackages(merge(this.excludePackages, this.excludePackages2)); - result.setIncludedTagExpressions(merge(this.includedTags, this.includedTags2)); - result.setExcludedTagExpressions(merge(this.excludedTags, this.excludedTags2)); - result.setIncludedEngines(merge(this.includedEngines, this.includedEngines2)); - result.setExcludedEngines(merge(this.excludedEngines, this.excludedEngines2)); - } - } - - static class RuntimeConfigurationOptions { - - @Option(names = { "-" + CP_OPTION, "--classpath", - "--class-path" }, converter = ClasspathEntriesConverter.class, paramLabel = "PATH", arity = "1", description = "Provide additional classpath entries " - + "-- for example, for adding engines and their dependencies. This option can be repeated.") - private List additionalClasspathEntries = new ArrayList<>(); - - @Option(names = { "--cp", "-classpath", - "-class-path" }, converter = ClasspathEntriesConverter.class, hidden = true) - private List additionalClasspathEntries2 = new ArrayList<>(); - - // Implementation note: the @Option annotation is on a setter method to allow validation. - private Map configurationParameters = new LinkedHashMap<>(); - - @Spec - private CommandSpec spec; - - @Option(names = { "-config" }, arity = "1", hidden = true) - public void setConfigurationParameters2(Map keyValuePairs) { - setConfigurationParameters(keyValuePairs); - } - - /** - * Adds the specified key-value pair (or pairs) to the configuration parameters. - * A {@code ParameterException} is thrown if the same key is specified multiple times - * on the command line. - * - * @param map the key-value pairs to add - * @throws picocli.CommandLine.ParameterException if the map already contains this key - * @see #1308 - */ - @Option(names = "--config", paramLabel = "KEY=VALUE", arity = "1", description = "Set a configuration parameter for test discovery and execution. This option can be repeated.") - public void setConfigurationParameters(Map map) { - for (String key : map.keySet()) { - String newValue = map.get(key); - validateUnique(key, newValue); - configurationParameters.put(key, newValue); - } - } - - private void validateUnique(String key, String newValue) { - String existing = configurationParameters.get(key); - if (existing != null && !existing.equals(newValue)) { - throw new ParameterException(spec.commandLine(), - String.format("Duplicate key '%s' for values '%s' and '%s'.", key, existing, newValue)); - } - } - - private void applyTo(CommandLineOptions result) { - result.setAdditionalClasspathEntries(merge(additionalClasspathEntries, additionalClasspathEntries2)); - result.setConfigurationParameters(configurationParameters); - } - } - - static class ReportingOptions { - - @Option(names = "--fail-if-no-tests", description = "Fail and return exit status code 2 if no tests are found.") - private boolean failIfNoTests; // no single-dash equivalent: was introduced in 5.3-M1 - - @Option(names = "--reports-dir", paramLabel = "DIR", description = "Enable report output into a specified local directory (will be created if it does not exist).") - private Path reportsDir; - - @Option(names = "-reports-dir", hidden = true) - private Path reportsDir2; - - private void applyTo(CommandLineOptions result) { - result.setFailIfNoTests(this.failIfNoTests); - result.setReportsDir(choose(this.reportsDir, this.reportsDir2, null)); - } - } - - static class ConsoleOutputOptions { - @Option(names = "--disable-ansi-colors", description = "Disable ANSI colors in output (not supported by all terminals).") - private boolean disableAnsiColors; - - @Option(names = "-disable-ansi-colors", hidden = true) - private boolean disableAnsiColors2; - - @Option(names = "--color-palette", paramLabel = "FILE", description = "Specify a path to a properties file to customize ANSI style of output (not supported by all terminals).") - private Path colorPalette; - @Option(names = "-color-palette", hidden = true) - private Path colorPalette2; - - @Option(names = "--single-color", description = "Style test output using only text attributes, no color (not supported by all terminals).") - private boolean singleColorPalette; - @Option(names = "-single-color", hidden = true) - private boolean singleColorPalette2; - - @Option(names = "--disable-banner", description = "Disable print out of the welcome message.") - private boolean disableBanner; - - @Option(names = "-disable-banner", hidden = true) - private boolean disableBanner2; - - @Option(names = "--details", paramLabel = "MODE", defaultValue = DEFAULT_DETAILS_NAME, description = "Select an output details mode for when tests are executed. " // - + "Use one of: ${COMPLETION-CANDIDATES}. If 'none' is selected, " // - + "then only the summary and test failures are shown. Default: ${DEFAULT-VALUE}.") - private Details details = DEFAULT_DETAILS; - - @Option(names = "-details", hidden = true, defaultValue = DEFAULT_DETAILS_NAME) - private Details details2 = DEFAULT_DETAILS; - - @Option(names = "--details-theme", paramLabel = "THEME", description = "Select an output details tree theme for when tests are executed. " - + "Use one of: ${COMPLETION-CANDIDATES}. Default is detected based on default character encoding.") - private Theme theme = DEFAULT_THEME; - - @Option(names = "-details-theme", hidden = true) - private Theme theme2 = DEFAULT_THEME; - - private void applyTo(CommandLineOptions result) { - result.setAnsiColorOutputDisabled(disableAnsiColors || disableAnsiColors2); - result.setColorPalettePath(choose(colorPalette, colorPalette2, null)); - result.setSingleColorPalette(singleColorPalette || singleColorPalette2); - result.setBannerDisabled(disableBanner || disableBanner2); - result.setDetails(choose(details, details2, DEFAULT_DETAILS)); - result.setTheme(choose(theme, theme2, DEFAULT_THEME)); - } - } - - AvailableOptions() { - } - - CommandLine getParser() { - CommandLine result = new CommandLine(this); - result.setCaseInsensitiveEnumValuesAllowed(true); - result.setAtFileCommentChar(null); // for --select-method com.acme.Foo#m() - return result; - } - - CommandLineOptions toCommandLineOptions(ParseResult parseResult) { - CommandLineOptions result = new CommandLineOptions(); - if (commandOptions != null) { - this.commandOptions.applyTo(result); - } - if (this.selectorOptions != null) { - this.selectorOptions.applyTo(parseResult, result); - } - if (this.filterOptions != null) { - this.filterOptions.applyTo(result); - } - if (this.runtimeConfigurationOptions != null) { - this.runtimeConfigurationOptions.applyTo(result); - } - if (this.reportingOptions != null) { - this.reportingOptions.applyTo(result); - } - if (this.consoleOutputOptions != null) { - this.consoleOutputOptions.applyTo(result); - } - return result; - } - - private static List merge(List list1, List list2) { - List result = new ArrayList<>(list1); - result.addAll(list2); - return result; - } - - private static T choose(T left, T right, T defaultValue) { - return left == right ? left : (left == defaultValue ? right : left); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java deleted file mode 100644 index 8ea5670c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import picocli.CommandLine; - -class ClasspathEntriesConverter implements CommandLine.ITypeConverter> { - @Override - public List convert(String value) { - return Stream.of(value.split(File.pathSeparator)).map(Paths::get).collect(Collectors.toList()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java deleted file mode 100644 index 4321acbc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptions.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UriSelector; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public class CommandLineOptions { - - static final String DEFAULT_DETAILS_NAME = "tree"; - static final Details DEFAULT_DETAILS = Details.valueOf(DEFAULT_DETAILS_NAME.toUpperCase(Locale.ROOT)); - static final Theme DEFAULT_THEME = Theme.valueOf(ConsoleUtils.charset()); - - private boolean displayHelp; - private boolean listEngines; - private boolean ansiColorOutputDisabled; - private Path colorPalettePath; - private boolean isSingleColorPalette; - private boolean bannerDisabled; - private Details details = DEFAULT_DETAILS; - private Theme theme = DEFAULT_THEME; - private List additionalClasspathEntries = emptyList(); - private boolean failIfNoTests; - - private boolean scanClasspath; - private List selectedClasspathEntries = emptyList(); - - private boolean scanModulepath; - - private List selectedModules = emptyList(); - private List selectedUris = emptyList(); - private List selectedFiles = emptyList(); - private List selectedDirectories = emptyList(); - private List selectedPackages = emptyList(); - private List selectedClasses = emptyList(); - private List selectedMethods = emptyList(); - private List selectedClasspathResources = emptyList(); - private List selectedIterations = emptyList(); - - private List includedClassNamePatterns = singletonList(STANDARD_INCLUDE_PATTERN); - private List excludedClassNamePatterns = emptyList(); - private List includedPackages = emptyList(); - private List excludedPackages = emptyList(); - private List includedEngines = emptyList(); - private List excludedEngines = emptyList(); - private List includedTagExpressions = emptyList(); - private List excludedTagExpressions = emptyList(); - - private Path reportsDir; - - private Map configurationParameters = emptyMap(); - - public boolean isDisplayHelp() { - return this.displayHelp; - } - - public void setDisplayHelp(boolean displayHelp) { - this.displayHelp = displayHelp; - } - - public boolean isListEngines() { - return this.listEngines; - } - - public void setListEngines(boolean listEngines) { - this.listEngines = listEngines; - } - - public boolean isAnsiColorOutputDisabled() { - return this.ansiColorOutputDisabled; - } - - public void setAnsiColorOutputDisabled(boolean ansiColorOutputDisabled) { - this.ansiColorOutputDisabled = ansiColorOutputDisabled; - } - - public Path getColorPalettePath() { - return colorPalettePath; - } - - public void setColorPalettePath(Path colorPalettePath) { - this.colorPalettePath = colorPalettePath; - } - - public boolean isSingleColorPalette() { - return isSingleColorPalette; - } - - public void setSingleColorPalette(boolean singleColorPalette) { - this.isSingleColorPalette = singleColorPalette; - } - - public boolean isBannerDisabled() { - return this.bannerDisabled; - } - - public void setBannerDisabled(boolean bannerDisabled) { - this.bannerDisabled = bannerDisabled; - } - - public boolean isScanModulepath() { - return this.scanModulepath; - } - - public void setScanModulepath(boolean scanModulepath) { - this.scanModulepath = scanModulepath; - } - - public boolean isScanClasspath() { - return this.scanClasspath; - } - - public void setScanClasspath(boolean scanClasspath) { - this.scanClasspath = scanClasspath; - } - - public Details getDetails() { - return this.details; - } - - public void setDetails(Details details) { - this.details = details; - } - - public Theme getTheme() { - return this.theme; - } - - public void setTheme(Theme theme) { - this.theme = theme; - } - - public List getExistingAdditionalClasspathEntries() { - return this.additionalClasspathEntries.stream().filter(Files::exists).collect(toList()); - } - - public List getAdditionalClasspathEntries() { - return this.additionalClasspathEntries; - } - - public void setAdditionalClasspathEntries(List additionalClasspathEntries) { - this.additionalClasspathEntries = additionalClasspathEntries; - } - - public boolean isFailIfNoTests() { - return this.failIfNoTests; - } - - public void setFailIfNoTests(boolean failIfNoTests) { - this.failIfNoTests = failIfNoTests; - } - - public List getSelectedClasspathEntries() { - return this.selectedClasspathEntries; - } - - public void setSelectedClasspathEntries(List selectedClasspathEntries) { - this.selectedClasspathEntries = selectedClasspathEntries; - } - - public List getSelectedUris() { - return selectedUris; - } - - public void setSelectedUris(List selectedUris) { - this.selectedUris = selectedUris; - } - - public List getSelectedFiles() { - return selectedFiles; - } - - public void setSelectedFiles(List selectedFiles) { - this.selectedFiles = selectedFiles; - } - - public List getSelectedDirectories() { - return selectedDirectories; - } - - public void setSelectedDirectories(List selectedDirectories) { - this.selectedDirectories = selectedDirectories; - } - - public List getSelectedModules() { - return selectedModules; - } - - public void setSelectedModules(List selectedModules) { - this.selectedModules = selectedModules; - } - - public List getSelectedPackages() { - return selectedPackages; - } - - public void setSelectedPackages(List selectedPackages) { - this.selectedPackages = selectedPackages; - } - - public List getSelectedClasses() { - return selectedClasses; - } - - public void setSelectedClasses(List selectedClasses) { - this.selectedClasses = selectedClasses; - } - - public List getSelectedMethods() { - return selectedMethods; - } - - public void setSelectedMethods(List selectedMethods) { - this.selectedMethods = selectedMethods; - } - - public List getSelectedClasspathResources() { - return selectedClasspathResources; - } - - public void setSelectedClasspathResources(List selectedClasspathResources) { - this.selectedClasspathResources = selectedClasspathResources; - } - - public List getSelectedIterations() { - return selectedIterations; - } - - public void setSelectedIterations(List selectedIterations) { - this.selectedIterations = selectedIterations; - } - - public List getExplicitSelectors() { - List selectors = new ArrayList<>(); - selectors.addAll(getSelectedUris()); - selectors.addAll(getSelectedFiles()); - selectors.addAll(getSelectedDirectories()); - selectors.addAll(getSelectedModules()); - selectors.addAll(getSelectedPackages()); - selectors.addAll(getSelectedClasses()); - selectors.addAll(getSelectedMethods()); - selectors.addAll(getSelectedClasspathResources()); - selectors.addAll(getSelectedIterations()); - return selectors; - } - - public List getIncludedClassNamePatterns() { - return this.includedClassNamePatterns; - } - - public void setIncludedClassNamePatterns(List includedClassNamePatterns) { - this.includedClassNamePatterns = includedClassNamePatterns; - } - - public List getExcludedClassNamePatterns() { - return this.excludedClassNamePatterns; - } - - public void setExcludedClassNamePatterns(List excludedClassNamePatterns) { - this.excludedClassNamePatterns = excludedClassNamePatterns; - } - - public List getIncludedPackages() { - return this.includedPackages; - } - - public void setIncludedPackages(List includedPackages) { - this.includedPackages = includedPackages; - } - - public List getExcludedPackages() { - return this.excludedPackages; - } - - public void setExcludedPackages(List excludedPackages) { - this.excludedPackages = excludedPackages; - } - - public List getIncludedEngines() { - return this.includedEngines; - } - - public void setIncludedEngines(List includedEngines) { - this.includedEngines = includedEngines; - } - - public List getExcludedEngines() { - return this.excludedEngines; - } - - public void setExcludedEngines(List excludedEngines) { - this.excludedEngines = excludedEngines; - } - - public List getIncludedTagExpressions() { - return this.includedTagExpressions; - } - - public void setIncludedTagExpressions(List includedTags) { - this.includedTagExpressions = includedTags; - } - - public List getExcludedTagExpressions() { - return this.excludedTagExpressions; - } - - public void setExcludedTagExpressions(List excludedTags) { - this.excludedTagExpressions = excludedTags; - } - - public Optional getReportsDir() { - return Optional.ofNullable(this.reportsDir); - } - - public void setReportsDir(Path reportsDir) { - this.reportsDir = reportsDir; - } - - public Map getConfigurationParameters() { - return this.configurationParameters; - } - - public void setConfigurationParameters(Map configurationParameters) { - this.configurationParameters = configurationParameters; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java deleted file mode 100644 index b460540e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandLineOptionsParser.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.Writer; - -import org.apiguardian.api.API; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public interface CommandLineOptionsParser { - - CommandLineOptions parse(String... arguments); - - void printHelp(Writer writer, boolean ansiColorOutputDisabled); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java deleted file mode 100644 index da43806a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.nio.charset.Charset; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@code java.io.Console} - * and friends. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.9 - */ -@API(status = INTERNAL, since = "1.9") -public class ConsoleUtils { - - /** - * {@return the charset of the console} - */ - public static Charset charset() { - return Charset.defaultCharset(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java deleted file mode 100644 index 7e6e4bdd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public enum Details { - - /** - * No test plan execution details are printed. - */ - NONE, - - /** - * Print summary table of counts only. - */ - SUMMARY, - - /** - * Test plan execution details are rendered in a flat, line-by-line mode. - */ - FLAT, - - /** - * Test plan execution details are rendered as a simple tree. - */ - TREE, - - /** - * Combines {@link #TREE} and {@link #FLAT} modes. - */ - VERBOSE; - - /** - * Return lower case {@link #name} for easier usage in help text for - * available options. - */ - @Override - public String toString() { - return name().toLowerCase(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java deleted file mode 100644 index a064c128..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/PicocliCommandLineOptionsParser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.IOException; -import java.io.Writer; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; - -import picocli.CommandLine; -import picocli.CommandLine.ParseResult; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public class PicocliCommandLineOptionsParser implements CommandLineOptionsParser { - - @Override - public CommandLineOptions parse(String... arguments) { - AvailableOptions availableOptions = getAvailableOptions(); - CommandLine parser = availableOptions.getParser(); - try { - ParseResult detectedOptions = parser.parseArgs(arguments); - return availableOptions.toCommandLineOptions(detectedOptions); - } - catch (Exception ex) { - throw new JUnitException("Error parsing command-line arguments: " + ex.getMessage(), ex); - } - } - - @Override - public void printHelp(Writer writer, boolean ansiColorOutputDisabled) { - try { - CommandLine parser = getAvailableOptions().getParser(); - if (ansiColorOutputDisabled) { - parser.setColorScheme(CommandLine.Help.defaultColorScheme(CommandLine.Help.Ansi.OFF)); - } - writer.append(parser.getUsageMessage()); - } - catch (IOException ex) { - throw new JUnitException("Error printing help", ex); - } - } - - private AvailableOptions getAvailableOptions() { - return new AvailableOptions(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java deleted file mode 100644 index 65f338da..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; - -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.IntStream; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UriSelector; -import picocli.CommandLine.ITypeConverter; - -class SelectorConverter { - - static class Module implements ITypeConverter { - @Override - public ModuleSelector convert(String value) { - return selectModule(value); - } - } - - static class Uri implements ITypeConverter { - @Override - public UriSelector convert(String value) { - return selectUri(value); - } - } - - static class File implements ITypeConverter { - @Override - public FileSelector convert(String value) { - return selectFile(value); - } - } - - static class Directory implements ITypeConverter { - @Override - public DirectorySelector convert(String value) { - return selectDirectory(value); - } - } - - static class Package implements ITypeConverter { - @Override - public PackageSelector convert(String value) { - return selectPackage(value); - } - } - - static class Class implements ITypeConverter { - @Override - public ClassSelector convert(String value) { - return selectClass(value); - } - } - - static class Method implements ITypeConverter { - @Override - public MethodSelector convert(String value) { - return selectMethod(value); - } - } - - static class ClasspathResource implements ITypeConverter { - @Override - public ClasspathResourceSelector convert(String value) { - return selectClasspathResource(value); - } - } - - static class Iteration implements ITypeConverter { - - public static final Pattern PATTERN = Pattern.compile( - "(?[a-z]+):(?.*)\\[(?(\\d+)(\\.\\.\\d+)?(\\s*,\\s*(\\d+)(\\.\\.\\d+)?)*)]"); - - @Override - public IterationSelector convert(String value) { - Matcher matcher = PATTERN.matcher(value); - Preconditions.condition(matcher.matches(), "Invalid format: must be TYPE:VALUE[INDEX(,INDEX)*]"); - DiscoverySelector parentSelector = createParentSelector(matcher.group("type"), matcher.group("value")); - int[] iterationIndices = Arrays.stream(matcher.group("indices").split(",")) // - .flatMapToInt(this::parseIndexDefinition) // - .toArray(); - return selectIteration(parentSelector, iterationIndices); - } - - private IntStream parseIndexDefinition(String value) { - String[] parts = value.split("\\.\\.", 2); - int firstIndex = Integer.parseInt(parts[0]); - if (parts.length == 2) { - int lastIndex = Integer.parseInt(parts[1]); - return IntStream.rangeClosed(firstIndex, lastIndex); - } - return IntStream.of(firstIndex); - } - - private DiscoverySelector createParentSelector(String type, String value) { - switch (type) { - case "module": - return selectModule(value); - case "uri": - return selectUri(value); - case "file": - return selectFile(value); - case "directory": - return selectDirectory(value); - case "package": - return selectPackage(value); - case "class": - return selectClass(value); - case "method": - return selectMethod(value); - case "resource": - return selectClasspathResource(value); - default: - throw new IllegalArgumentException("Unknown type: " + type); - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java deleted file mode 100644 index 8981def9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestExecutionResult; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public enum Theme { - - /** - * ASCII 7-bit characters form the tree branch. - * - *

Example test plan execution tree: - *

-	 * +-- engine alpha
-	 * | '-- container BEGIN
-	 * |   +-- test 00 [OK]
-	 * |   '-- test 01 [OK]
-	 * '-- engine omega
-	 *   +-- container END
-	 *   | +-- test 10 [OK]
-	 *   | '-- test 11 [A] aborted
-	 *   '-- container FINAL
-	 *     +-- skipped [S] because
-	 *     '-- failing [X] BäMM
-	 * 
- */ - ASCII(".", "| ", "+--", "'--", "[OK]", "[A]", "[X]", "[S]"), - - /** - * Unicode (extended ASCII) characters are used to display the test execution tree. - * - *

Example test plan execution tree: - *

-	 * ├─ engine alpha ✔
-	 * │  └─ container BEGIN ✔
-	 * │     ├─ test 00 ✔
-	 * │     └─ test 01 ✔
-	 * └─ engine omega ✔
-	 *    ├─ container END ✔
-	 *    │  ├─ test 10 ✔
-	 *    │  └─ test 11 ■ aborted
-	 *    └─ container FINAL ✔
-	 *       ├─ skipped ↷ because
-	 *       └─ failing ✘ BäMM
-	 * 
- */ - UNICODE("╷", "│ ", "├─", "└─", "✔", "■", "✘", "↷"); - - public static Theme valueOf(Charset charset) { - if (StandardCharsets.UTF_8.equals(charset)) { - return UNICODE; - } - return ASCII; - } - - private final String[] tiles; - private final String blank; - - Theme(String... tiles) { - this.tiles = tiles; - this.blank = new String(new char[vertical().length()]).replace('\0', ' '); - } - - public final String root() { - return tiles[0]; - } - - public final String vertical() { - return tiles[1]; - } - - public final String blank() { - return blank; - } - - public final String entry() { - return tiles[2]; - } - - public final String end() { - return tiles[3]; - } - - public final String successful() { - return tiles[4]; - } - - public final String aborted() { - return tiles[5]; - } - - public final String failed() { - return tiles[6]; - } - - public final String skipped() { - return tiles[7]; - } - - public final String status(TestExecutionResult result) { - switch (result.getStatus()) { - case SUCCESSFUL: - return successful(); - case ABORTED: - return aborted(); - case FAILED: - return failed(); - default: - return result.getStatus().name(); - } - } - - /** - * Return lower case {@link #name()} for easier usage in help text for - * available options. - */ - @Override - public final String toString() { - return name().toLowerCase(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java deleted file mode 100644 index babb46e2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Configuration options for JUnit's console launcher. - */ - -package org.junit.platform.console.options; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java deleted file mode 100644 index 73bc1b41..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Support for launching the JUnit Platform from the console. - */ - -package org.junit.platform.console; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java deleted file mode 100644 index d330be01..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Color.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.TestIdentifier; - -/** - * @since 1.0 - */ -enum Color { - - NONE(0), - - BLACK(30), - - RED(31), - - GREEN(32), - - YELLOW(33), - - BLUE(34), - - PURPLE(35), - - CYAN(36), - - WHITE(37); - - static Color valueOf(TestExecutionResult result) { - switch (result.getStatus()) { - case SUCCESSFUL: - return Color.SUCCESSFUL; - case ABORTED: - return Color.ABORTED; - case FAILED: - return Color.FAILED; - default: - return Color.NONE; - } - } - - static Color valueOf(TestIdentifier testIdentifier) { - return testIdentifier.isContainer() ? CONTAINER : TEST; - } - - static final Color SUCCESSFUL = GREEN; - - static final Color ABORTED = YELLOW; - - static final Color FAILED = RED; - - static final Color SKIPPED = PURPLE; - - static final Color CONTAINER = CYAN; - - static final Color TEST = BLUE; - - static final Color DYNAMIC = PURPLE; - - static final Color REPORTED = WHITE; - - private final String ansiString; - - Color(int ansiCode) { - this.ansiString = "\u001B[" + ansiCode + "m"; - } - - @Override - public String toString() { - return this.ansiString; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java deleted file mode 100644 index a587980f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import java.io.FileReader; -import java.io.IOException; -import java.io.Reader; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.Map; -import java.util.Properties; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * @since 1.9 - */ -class ColorPalette { - - public static final ColorPalette SINGLE_COLOR = new ColorPalette(singleColorPalette(), false); - public static final ColorPalette DEFAULT = new ColorPalette(defaultPalette(), false); - public static final ColorPalette NONE = new ColorPalette(new EnumMap<>(Style.class), true); - - private final Map colorsToAnsiSequences; - private final boolean disableAnsiColors; - - // https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters - private static Map defaultPalette() { - Map colorsToAnsiSequences = new EnumMap<>(Style.class); - colorsToAnsiSequences.put(Style.NONE, "0"); - colorsToAnsiSequences.put(Style.SUCCESSFUL, "32"); - colorsToAnsiSequences.put(Style.ABORTED, "33"); - colorsToAnsiSequences.put(Style.FAILED, "31"); - colorsToAnsiSequences.put(Style.SKIPPED, "35"); - colorsToAnsiSequences.put(Style.CONTAINER, "36"); - colorsToAnsiSequences.put(Style.TEST, "34"); - colorsToAnsiSequences.put(Style.DYNAMIC, "35"); - colorsToAnsiSequences.put(Style.REPORTED, "37"); - return colorsToAnsiSequences; - } - - private static Map singleColorPalette() { - Map colorsToAnsiSequences = new EnumMap<>(Style.class); - colorsToAnsiSequences.put(Style.NONE, "0"); - colorsToAnsiSequences.put(Style.SUCCESSFUL, "1"); - colorsToAnsiSequences.put(Style.ABORTED, "4"); - colorsToAnsiSequences.put(Style.FAILED, "7"); - colorsToAnsiSequences.put(Style.SKIPPED, "9"); - colorsToAnsiSequences.put(Style.CONTAINER, "1"); - colorsToAnsiSequences.put(Style.TEST, "0"); - colorsToAnsiSequences.put(Style.DYNAMIC, "0"); - colorsToAnsiSequences.put(Style.REPORTED, "2"); - return colorsToAnsiSequences; - } - - public ColorPalette(Map overrides) { - this(defaultPalette(), false); - - if (overrides.containsKey(Style.NONE)) { - throw new IllegalArgumentException("Cannot override the standard style 'NONE'"); - } - this.colorsToAnsiSequences.putAll(overrides); - } - - public ColorPalette(Properties properties) { - this(toOverrideMap(properties)); - } - - public ColorPalette(Reader reader) { - this(getProperties(reader)); - } - - public ColorPalette(Path path) { - this(getProperties(path)); - } - - private ColorPalette(Map colorsToAnsiSequences, boolean disableAnsiColors) { - this.colorsToAnsiSequences = colorsToAnsiSequences; - this.disableAnsiColors = disableAnsiColors; - } - - private static Map toOverrideMap(Properties properties) { - Map upperCaseProperties = properties.entrySet().stream().collect( - Collectors.toMap(entry -> ((String) entry.getKey()).toUpperCase(), entry -> (String) entry.getValue())); - - return Arrays.stream(Style.values()).filter(style -> upperCaseProperties.containsKey(style.name())).collect( - Collectors.toMap(Function.identity(), style -> upperCaseProperties.get(style.name()))); - } - - private static Properties getProperties(Reader reader) { - Properties properties = new Properties(); - try { - properties.load(reader); - } - catch (IOException e) { - throw new IllegalArgumentException("Could not read color palette properties", e); - } - return properties; - } - - private static Properties getProperties(Path path) { - try (FileReader fileReader = new FileReader(path.toFile())) { - return getProperties(fileReader); - } - catch (IOException e) { - throw new IllegalArgumentException("Could not open color palette properties file", e); - } - } - - public String paint(Style style, String text) { - return this.disableAnsiColors || style == Style.NONE ? text - : getAnsiFormatter(style) + text + getAnsiFormatter(Style.NONE); - } - - private String getAnsiFormatter(Style style) { - return String.format("\u001B[%sm", this.colorsToAnsiSequences.get(style)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java deleted file mode 100644 index a9e540a3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.PrintWriter; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ClassLoaderUtils; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.console.options.Details; -import org.junit.platform.console.options.Theme; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.core.LauncherFactory; -import org.junit.platform.launcher.listeners.SummaryGeneratingListener; -import org.junit.platform.launcher.listeners.TestExecutionSummary; -import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0") -public class ConsoleTestExecutor { - - private final CommandLineOptions options; - private final Supplier launcherSupplier; - - public ConsoleTestExecutor(CommandLineOptions options) { - this(options, LauncherFactory::create); - } - - // for tests only - ConsoleTestExecutor(CommandLineOptions options, Supplier launcherSupplier) { - this.options = options; - this.launcherSupplier = launcherSupplier; - } - - public TestExecutionSummary execute(PrintWriter out) throws Exception { - return new CustomContextClassLoaderExecutor(createCustomClassLoader()).invoke(() -> executeTests(out)); - } - - private TestExecutionSummary executeTests(PrintWriter out) { - Launcher launcher = launcherSupplier.get(); - SummaryGeneratingListener summaryListener = registerListeners(out, launcher); - - LauncherDiscoveryRequest discoveryRequest = new DiscoveryRequestCreator().toDiscoveryRequest(options); - launcher.execute(discoveryRequest); - - TestExecutionSummary summary = summaryListener.getSummary(); - if (summary.getTotalFailureCount() > 0 || options.getDetails() != Details.NONE) { - printSummary(summary, out); - } - - return summary; - } - - private Optional createCustomClassLoader() { - List additionalClasspathEntries = options.getExistingAdditionalClasspathEntries(); - if (!additionalClasspathEntries.isEmpty()) { - URL[] urls = additionalClasspathEntries.stream().map(this::toURL).toArray(URL[]::new); - ClassLoader parentClassLoader = ClassLoaderUtils.getDefaultClassLoader(); - ClassLoader customClassLoader = URLClassLoader.newInstance(urls, parentClassLoader); - return Optional.of(customClassLoader); - } - return Optional.empty(); - } - - private URL toURL(Path path) { - try { - return path.toUri().toURL(); - } - catch (Exception ex) { - throw new JUnitException("Invalid classpath entry: " + path, ex); - } - } - - private SummaryGeneratingListener registerListeners(PrintWriter out, Launcher launcher) { - // always register summary generating listener - SummaryGeneratingListener summaryListener = new SummaryGeneratingListener(); - launcher.registerTestExecutionListeners(summaryListener); - // optionally, register test plan execution details printing listener - createDetailsPrintingListener(out).ifPresent(launcher::registerTestExecutionListeners); - // optionally, register XML reports writing listener - createXmlWritingListener(out).ifPresent(launcher::registerTestExecutionListeners); - return summaryListener; - } - - private Optional createDetailsPrintingListener(PrintWriter out) { - ColorPalette colorPalette = getColorPalette(); - Theme theme = options.getTheme(); - switch (options.getDetails()) { - case SUMMARY: - // summary listener is always created and registered - return Optional.empty(); - case FLAT: - return Optional.of(new FlatPrintingListener(out, colorPalette)); - case TREE: - return Optional.of(new TreePrintingListener(out, colorPalette, theme)); - case VERBOSE: - return Optional.of(new VerboseTreePrintingListener(out, colorPalette, 16, theme)); - default: - return Optional.empty(); - } - } - - private ColorPalette getColorPalette() { - if (options.isAnsiColorOutputDisabled()) { - return ColorPalette.NONE; - } - if (options.getColorPalettePath() != null) { - return new ColorPalette(options.getColorPalettePath()); - } - if (options.isSingleColorPalette()) { - return ColorPalette.SINGLE_COLOR; - } - return ColorPalette.DEFAULT; - } - - private Optional createXmlWritingListener(PrintWriter out) { - return options.getReportsDir().map(reportsDir -> new LegacyXmlReportGeneratingListener(reportsDir, out)); - } - - private void printSummary(TestExecutionSummary summary, PrintWriter out) { - // Otherwise the failures have already been printed in detail - if (EnumSet.of(Details.NONE, Details.SUMMARY, Details.TREE).contains(options.getDetails())) { - summary.printFailuresTo(out); - } - summary.printTo(out); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java deleted file mode 100644 index 19794e9b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import java.util.Optional; -import java.util.concurrent.Callable; - -/** - * @since 1.0 - */ -class CustomContextClassLoaderExecutor { - - private final Optional customClassLoader; - - CustomContextClassLoaderExecutor(Optional customClassLoader) { - this.customClassLoader = customClassLoader; - } - - T invoke(Callable callable) throws Exception { - if (customClassLoader.isPresent()) { - // Only get/set context class loader when necessary to prevent problems with - // security managers - return replaceThreadContextClassLoaderAndInvoke(customClassLoader.get(), callable); - } - return callable.call(); - } - - private T replaceThreadContextClassLoaderAndInvoke(ClassLoader customClassLoader, Callable callable) - throws Exception { - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(customClassLoader); - return callable.call(); - } - finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - if (customClassLoader instanceof AutoCloseable) { - ((AutoCloseable) customClassLoader).close(); - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java deleted file mode 100644 index 2fb1ea03..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.platform.engine.discovery.ClassNameFilter.excludeClassNamePatterns; -import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModules; -import static org.junit.platform.engine.discovery.PackageNameFilter.excludePackageNames; -import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; -import static org.junit.platform.launcher.EngineFilter.excludeEngines; -import static org.junit.platform.launcher.EngineFilter.includeEngines; -import static org.junit.platform.launcher.TagFilter.excludeTags; -import static org.junit.platform.launcher.TagFilter.includeTags; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.nio.file.Path; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import org.junit.platform.commons.util.ModuleUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; - -/** - * @since 1.0 - */ -class DiscoveryRequestCreator { - - LauncherDiscoveryRequest toDiscoveryRequest(CommandLineOptions options) { - LauncherDiscoveryRequestBuilder requestBuilder = request(); - List selectors = createDiscoverySelectors(options); - requestBuilder.selectors(selectors); - addFilters(requestBuilder, options, selectors); - requestBuilder.configurationParameters(options.getConfigurationParameters()); - return requestBuilder.build(); - } - - private List createDiscoverySelectors(CommandLineOptions options) { - List explicitSelectors = options.getExplicitSelectors(); - if (options.isScanClasspath()) { - Preconditions.condition(explicitSelectors.isEmpty(), - "Scanning the classpath and using explicit selectors at the same time is not supported"); - return createClasspathRootSelectors(options); - } - if (options.isScanModulepath()) { - Preconditions.condition(explicitSelectors.isEmpty(), - "Scanning the module-path and using explicit selectors at the same time is not supported"); - return selectModules(ModuleUtils.findAllNonSystemBootModuleNames()); - } - return Preconditions.notEmpty(explicitSelectors, "No arguments were supplied to the ConsoleLauncher"); - } - - private List createClasspathRootSelectors(CommandLineOptions options) { - Set classpathRoots = determineClasspathRoots(options); - return selectClasspathRoots(classpathRoots); - } - - private Set determineClasspathRoots(CommandLineOptions options) { - if (options.getSelectedClasspathEntries().isEmpty()) { - Set rootDirs = new LinkedHashSet<>(ReflectionUtils.getAllClasspathRootDirectories()); - rootDirs.addAll(options.getExistingAdditionalClasspathEntries()); - return rootDirs; - } - return new LinkedHashSet<>(options.getSelectedClasspathEntries()); - } - - private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, CommandLineOptions options, - List selectors) { - requestBuilder.filters(includedClassNamePatterns(options, selectors)); - - if (!options.getExcludedClassNamePatterns().isEmpty()) { - requestBuilder.filters( - excludeClassNamePatterns(options.getExcludedClassNamePatterns().toArray(new String[0]))); - } - - if (!options.getIncludedPackages().isEmpty()) { - requestBuilder.filters(includePackageNames(options.getIncludedPackages())); - } - - if (!options.getExcludedPackages().isEmpty()) { - requestBuilder.filters(excludePackageNames(options.getExcludedPackages())); - } - - if (!options.getIncludedTagExpressions().isEmpty()) { - requestBuilder.filters(includeTags(options.getIncludedTagExpressions())); - } - - if (!options.getExcludedTagExpressions().isEmpty()) { - requestBuilder.filters(excludeTags(options.getExcludedTagExpressions())); - } - - if (!options.getIncludedEngines().isEmpty()) { - requestBuilder.filters(includeEngines(options.getIncludedEngines())); - } - - if (!options.getExcludedEngines().isEmpty()) { - requestBuilder.filters(excludeEngines(options.getExcludedEngines())); - } - } - - private ClassNameFilter includedClassNamePatterns(CommandLineOptions options, - List selectors) { - Stream patternStreams = Stream.concat( // - options.getIncludedClassNamePatterns().stream(), // - selectors.stream() // - .map(selector -> selector instanceof IterationSelector - ? ((IterationSelector) selector).getParentSelector() - : selector) // - .map(selector -> { - if (selector instanceof ClassSelector) { - return ((ClassSelector) selector).getClassName(); - } - if (selector instanceof MethodSelector) { - return ((MethodSelector) selector).getClassName(); - } - return null; - }) // - .filter(Objects::nonNull) // - .map(Pattern::quote)); - return includeClassNamePatterns(patternStreams.toArray(String[]::new)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java deleted file mode 100644 index bb68bbff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import java.io.PrintWriter; -import java.util.regex.Pattern; - -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.0 - */ -class FlatPrintingListener implements TestExecutionListener { - - private static final Pattern LINE_START_PATTERN = Pattern.compile("(?m)^"); - - static final String INDENTATION = " "; - - private final PrintWriter out; - private final ColorPalette colorPalette; - - FlatPrintingListener(PrintWriter out, ColorPalette colorPalette) { - this.out = out; - this.colorPalette = colorPalette; - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - this.out.printf("Test execution started. Number of static tests: %d%n", - testPlan.countTestIdentifiers(TestIdentifier::isTest)); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - this.out.println("Test execution finished."); - } - - @Override - public void dynamicTestRegistered(TestIdentifier testIdentifier) { - printlnTestDescriptor(Style.DYNAMIC, "Registered:", testIdentifier); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - printlnTestDescriptor(Style.SKIPPED, "Skipped:", testIdentifier); - printlnMessage(Style.SKIPPED, "Reason", reason); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - printlnTestDescriptor(Style.valueOf(testIdentifier), "Started:", testIdentifier); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - Style style = Style.valueOf(testExecutionResult); - printlnTestDescriptor(style, "Finished:", testIdentifier); - testExecutionResult.getThrowable().ifPresent(t -> printlnException(style, t)); - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - printlnTestDescriptor(Style.REPORTED, "Reported:", testIdentifier); - printlnMessage(Style.REPORTED, "Reported values", entry.toString()); - } - - private void printlnTestDescriptor(Style style, String message, TestIdentifier testIdentifier) { - println(style, "%-10s %s (%s)", message, testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); - } - - private void printlnException(Style style, Throwable throwable) { - printlnMessage(style, "Exception", ExceptionUtils.readStackTrace(throwable)); - } - - private void printlnMessage(Style style, String message, String detail) { - println(style, INDENTATION + "=> " + message + ": %s", indented(detail)); - } - - private void println(Style style, String format, Object... args) { - this.out.println(colorPalette.paint(style, String.format(format, args))); - } - - /** - * Indent the given message if it is a multi-line string. - * - *

{@link #INDENTATION} is used to prefix the start of each new line - * except the first one. - * - * @param message the message to indent - * @return indented message - */ - private static String indented(String message) { - return LINE_START_PATTERN.matcher(message).replaceAll(INDENTATION).trim(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java deleted file mode 100644 index 31f530b8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.TestIdentifier; - -/** - * @since 1.9 - */ -enum Style { - - NONE, SUCCESSFUL, ABORTED, FAILED, SKIPPED, CONTAINER, TEST, DYNAMIC, REPORTED; - - static Style valueOf(TestExecutionResult result) { - switch (result.getStatus()) { - case SUCCESSFUL: - return Style.SUCCESSFUL; - case ABORTED: - return Style.ABORTED; - case FAILED: - return Style.FAILED; - default: - return Style.NONE; - } - } - - static Style valueOf(TestIdentifier testIdentifier) { - return testIdentifier.isContainer() ? CONTAINER : TEST; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java deleted file mode 100644 index 40fdbf93..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import java.util.Optional; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestIdentifier; - -/** - * @since 1.0 - */ -class TreeNode { - - private final String caption; - private final long creation; - long duration; - private String reason; - private TestIdentifier identifier; - private TestExecutionResult result; - final Queue reports = new ConcurrentLinkedQueue<>(); - final Queue children = new ConcurrentLinkedQueue<>(); - boolean visible; - - TreeNode(String caption) { - this.caption = caption; - this.creation = System.currentTimeMillis(); - this.visible = false; - } - - TreeNode(TestIdentifier identifier) { - this(createCaption(identifier.getDisplayName())); - this.identifier = identifier; - this.visible = true; - } - - TreeNode(TestIdentifier identifier, String reason) { - this(identifier); - this.reason = reason; - } - - TreeNode addChild(TreeNode node) { - children.add(node); - return this; - } - - TreeNode addReportEntry(ReportEntry reportEntry) { - reports.add(reportEntry); - return this; - } - - TreeNode setResult(TestExecutionResult result) { - this.result = result; - this.duration = System.currentTimeMillis() - creation; - return this; - } - - public String caption() { - return caption; - } - - Optional reason() { - return Optional.ofNullable(reason); - } - - Optional result() { - return Optional.ofNullable(result); - } - - Optional identifier() { - return Optional.ofNullable(identifier); - } - - static String createCaption(String displayName) { - boolean normal = displayName.length() <= 80; - String caption = normal ? displayName : displayName.substring(0, 80) + "..."; - String whites = StringUtils.replaceWhitespaceCharacters(caption, " "); - return StringUtils.replaceIsoControlCharacters(whites, "."); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java deleted file mode 100644 index 4f72afff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; - -import java.io.PrintWriter; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.console.options.Theme; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestExecutionResult.Status; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * @since 1.0 - */ -class TreePrinter { - - private final PrintWriter out; - private final Theme theme; - private final ColorPalette colorPalette; - - TreePrinter(PrintWriter out, Theme theme, ColorPalette colorPalette) { - this.out = out; - this.theme = theme; - this.colorPalette = colorPalette; - } - - void print(TreeNode node) { - out.println(color(Style.CONTAINER, theme.root())); - print(node, "", true); - out.flush(); - } - - private void print(TreeNode node, String indent, boolean continuous) { - if (node.visible) { - printVisible(node, indent, continuous); - } - if (node.children.isEmpty()) { - return; - } - if (node.visible) { - indent += continuous ? theme.vertical() : theme.blank(); - } - Iterator iterator = node.children.iterator(); - while (iterator.hasNext()) { - print(iterator.next(), indent, iterator.hasNext()); - } - } - - private void printVisible(TreeNode node, String indent, boolean continuous) { - String bullet = continuous ? theme.entry() : theme.end(); - String prefix = color(Style.CONTAINER, indent + bullet); - String tabbed = color(Style.CONTAINER, indent + tab(node, continuous)); - String caption = colorCaption(node); - String duration = color(Style.CONTAINER, node.duration + " ms"); - String icon = color(Style.SKIPPED, theme.skipped()); - if (node.result().isPresent()) { - TestExecutionResult result = node.result().get(); - Style resultStyle = Style.valueOf(result); - icon = color(resultStyle, theme.status(result)); - } - out.print(prefix); - out.print(" "); - out.print(caption); - if (node.duration > 10000 && node.children.isEmpty()) { - out.print(" "); - out.print(duration); - } - out.print(" "); - out.print(icon); - node.result().ifPresent(result -> printThrowable(tabbed, result)); - node.reason().ifPresent(reason -> printMessage(Style.SKIPPED, tabbed, reason)); - node.reports.forEach(e -> printReportEntry(tabbed, e)); - out.println(); - } - - private String tab(TreeNode node, boolean continuous) { - // We might be the "last" node in this level, that means - // `continuous == false`, but still need to include a vertical - // bar for printing stack traces, messages and reports. - // See https://github.com/junit-team/junit5/issues/1531 - if (node.children.size() > 0) { - return theme.blank() + theme.vertical(); - } - return (continuous ? theme.vertical() : theme.blank()) + theme.blank(); - } - - private String colorCaption(TreeNode node) { - String caption = node.caption(); - if (node.result().isPresent()) { - TestExecutionResult result = node.result().get(); - Style resultStyle = Style.valueOf(result); - if (result.getStatus() != Status.SUCCESSFUL) { - return color(resultStyle, caption); - } - } - if (node.reason().isPresent()) { - return color(Style.SKIPPED, caption); - } - Style style = node.identifier().map(Style::valueOf).orElse(Style.NONE); - return color(style, caption); - } - - private void printThrowable(String indent, TestExecutionResult result) { - if (!result.getThrowable().isPresent()) { - return; - } - Throwable throwable = result.getThrowable().get(); - String message = throwable.getMessage(); - if (StringUtils.isBlank(message)) { - message = throwable.toString(); - } - printMessage(Style.FAILED, indent, message); - } - - private void printReportEntry(String indent, ReportEntry reportEntry) { - out.println(); - out.print(indent); - out.print(reportEntry.getTimestamp()); - Set> entries = reportEntry.getKeyValuePairs().entrySet(); - if (entries.size() == 1) { - printReportEntry(" ", getOnlyElement(entries)); - return; - } - for (Map.Entry entry : entries) { - out.println(); - printReportEntry(indent + theme.blank(), entry); - } - } - - private void printReportEntry(String indent, Map.Entry mapEntry) { - out.print(indent); - out.print(color(Style.ABORTED, mapEntry.getKey())); - out.print(" = `"); - out.print(color(Style.SUCCESSFUL, mapEntry.getValue())); - out.print("`"); - } - - private void printMessage(Style style, String indent, String message) { - String[] lines = message.split("\\R"); - out.print(" "); - out.print(color(style, lines[0])); - if (lines.length > 1) { - for (int i = 1; i < lines.length; i++) { - out.println(); - out.print(indent); - if (StringUtils.isNotBlank(lines[i])) { - String extra = theme.blank(); - out.print(color(style, extra + lines[i])); - } - } - } - } - - private String color(Style style, String text) { - return colorPalette.paint(style, text); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java deleted file mode 100644 index 685ddff5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import java.io.PrintWriter; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; - -import org.junit.platform.console.options.Theme; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.0 - */ -class TreePrintingListener implements TestExecutionListener { - - private final Map nodesByUniqueId = new ConcurrentHashMap<>(); - private TreeNode root; - private final TreePrinter treePrinter; - - TreePrintingListener(PrintWriter out, ColorPalette colorPalette, Theme theme) { - this.treePrinter = new TreePrinter(out, theme, colorPalette); - } - - private void addNode(TestIdentifier testIdentifier, Supplier nodeSupplier) { - TreeNode node = nodeSupplier.get(); - nodesByUniqueId.put(testIdentifier.getUniqueIdObject(), node); - testIdentifier.getParentIdObject().map(nodesByUniqueId::get).orElse(root).addChild(node); - } - - private TreeNode getNode(TestIdentifier testIdentifier) { - return nodesByUniqueId.get(testIdentifier.getUniqueIdObject()); - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - root = new TreeNode(testPlan.toString()); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - treePrinter.print(root); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - addNode(testIdentifier, () -> new TreeNode(testIdentifier)); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - getNode(testIdentifier).setResult(testExecutionResult); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - addNode(testIdentifier, () -> new TreeNode(testIdentifier, reason)); - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - getNode(testIdentifier).addReportEntry(entry); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java deleted file mode 100644 index 9031a383..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; -import static org.junit.platform.console.tasks.Style.NONE; - -import java.io.PrintWriter; -import java.util.ArrayDeque; -import java.util.Deque; - -import org.junit.platform.console.options.Theme; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.0 - */ -class VerboseTreePrintingListener implements TestExecutionListener { - - private final PrintWriter out; - private final Theme theme; - private final ColorPalette colorPalette; - private final Deque frames; - private final String[] verticals; - private long executionStartedMillis; - - VerboseTreePrintingListener(PrintWriter out, ColorPalette colorPalette, int maxContainerNestingLevel, Theme theme) { - this.out = out; - this.colorPalette = colorPalette; - this.theme = theme; - - // create frame stack and push initial root frame - this.frames = new ArrayDeque<>(); - this.frames.push(0L); - - // create and populate vertical indentation lookup table - this.verticals = new String[Math.max(10, maxContainerNestingLevel) + 1]; - this.verticals[0] = ""; // no frame - this.verticals[1] = ""; // synthetic root "/" level - this.verticals[2] = ""; // "engine" level - - for (int i = 3; i < verticals.length; i++) { - verticals[i] = verticals[i - 1] + theme.vertical(); - } - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - frames.push(System.currentTimeMillis()); - - long tests = testPlan.countTestIdentifiers(TestIdentifier::isTest); - printf(NONE, "%s", "Test plan execution started. Number of static tests: "); - printf(Style.TEST, "%d%n", tests); - printf(Style.CONTAINER, "%s%n", theme.root()); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - frames.pop(); - - long tests = testPlan.countTestIdentifiers(TestIdentifier::isTest); - printf(NONE, "%s", "Test plan execution finished. Number of all tests: "); - printf(Style.TEST, "%d%n", tests); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - this.executionStartedMillis = System.currentTimeMillis(); - if (testIdentifier.isContainer()) { - printVerticals(theme.entry()); - printf(Style.CONTAINER, " %s", testIdentifier.getDisplayName()); - printf(NONE, "%n"); - frames.push(System.currentTimeMillis()); - } - if (testIdentifier.isContainer()) { - return; - } - printVerticals(theme.entry()); - printf(Style.valueOf(testIdentifier), " %s%n", testIdentifier.getDisplayName()); - printDetails(testIdentifier); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - testExecutionResult.getThrowable().ifPresent(t -> printDetail(Style.FAILED, "caught", readStackTrace(t))); - if (testIdentifier.isContainer()) { - Long creationMillis = frames.pop(); - printVerticals(theme.end()); - printf(Style.CONTAINER, " %s", testIdentifier.getDisplayName()); - printf(NONE, " finished after %d ms.%n", System.currentTimeMillis() - creationMillis); - return; - } - printDetail(NONE, "duration", "%d ms%n", System.currentTimeMillis() - executionStartedMillis); - String status = theme.status(testExecutionResult) + " " + testExecutionResult.getStatus(); - printDetail(Style.valueOf(testExecutionResult), "status", "%s%n", status); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - printVerticals(theme.entry()); - printf(Style.valueOf(testIdentifier), " %s%n", testIdentifier.getDisplayName()); - printDetails(testIdentifier); - printDetail(Style.SKIPPED, "reason", reason); - printDetail(Style.SKIPPED, "status", theme.skipped() + " SKIPPED"); - } - - @Override - public void dynamicTestRegistered(TestIdentifier testIdentifier) { - printVerticals(theme.entry()); - printf(Style.DYNAMIC, " %s", testIdentifier.getDisplayName()); - printf(NONE, "%s%n", " dynamically registered"); - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - printDetail(Style.REPORTED, "reports", entry.toString()); - } - - /** - * Print static information about the test identifier. - */ - private void printDetails(TestIdentifier testIdentifier) { - printDetail(NONE, "tags", "%s%n", testIdentifier.getTags()); - printDetail(NONE, "uniqueId", "%s%n", testIdentifier.getUniqueId()); - printDetail(NONE, "parent", "%s%n", testIdentifier.getParentId().orElse("[]")); - testIdentifier.getSource().ifPresent(source -> printDetail(NONE, "source", "%s%n", source)); - } - - private String verticals() { - return verticals(frames.size()); - } - - private String verticals(int index) { - return verticals[Math.min(index, verticals.length - 1)]; - } - - private void printVerticals(String tile) { - printf(NONE, verticals()); - printf(NONE, tile); - } - - private void printf(Style style, String message, Object... args) { - out.printf(colorPalette.paint(style, message), args); - out.flush(); - } - - /** - * Print single detail with a potential multi-line message. - */ - private void printDetail(Style style, String detail, String format, Object... args) { - // print initial verticals - expecting to be at start of the line - String verticals = verticals(frames.size() + 1); - printf(NONE, verticals); - String detailFormat = "%9s"; - // omit detail string if it's empty - if (!detail.isEmpty()) { - printf(NONE, "%s", String.format(detailFormat + ": ", detail)); - } - // trivial case: at least one arg is given? Let printf do the entire work - if (args.length > 0) { - printf(style, format, args); - return; - } - // still here? Split format into separate lines and indent them from the second line on - String[] lines = format.split("\\R"); - printf(style, "%s", lines[0]); - if (lines.length > 1) { - String delimiter = System.lineSeparator() + verticals + String.format(detailFormat + " ", ""); - for (int i = 1; i < lines.length; i++) { - printf(NONE, "%s", delimiter); - printf(style, "%s", lines[i]); - } - } - printf(NONE, "%n"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java deleted file mode 100644 index bcfe3d83..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Internal execution tasks for JUnit's console launcher. - */ - -package org.junit.platform.console.tasks; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java deleted file mode 100644 index 89698f10..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.Console; -import java.nio.charset.Charset; - -import org.apiguardian.api.API; - -/** - * Collection of utilities for working with {@code java.io.Console} - * and friends. - * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * - * @since 1.9 - */ -@API(status = INTERNAL, since = "1.9") -public class ConsoleUtils { - - /** - * {@return the charset of the console} - */ - public static Charset charset() { - Console console = System.console(); - return console != null ? console.charset() : Charset.defaultCharset(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java deleted file mode 100644 index d0a830a4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.io.PrintWriter; -import java.util.spi.ToolProvider; - -import org.apiguardian.api.API; - -/** - * Run the JUnit Platform Console Launcher as a service. - * - * @since 1.6 - */ -@API(status = EXPERIMENTAL, since = "1.6") -public class ConsoleLauncherToolProvider implements ToolProvider { - - @Override - public String name() { - return "junit"; - } - - @Override - public int run(PrintWriter out, PrintWriter err, String... args) { - return ConsoleLauncher.execute(out, err, args).getExitCode(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider deleted file mode 100644 index ac107bc0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.console.ConsoleLauncherToolProvider diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java deleted file mode 100644 index 08d28b43..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-console/src/module/org.junit.platform.console/module-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Support for launching the JUnit Platform from the console. - * - * @since 1.0 - * @provides java.util.spi.ToolProvider - */ -module org.junit.platform.console { - requires static org.apiguardian.api; - requires org.junit.platform.commons; - requires org.junit.platform.engine; - requires org.junit.platform.launcher; - requires org.junit.platform.reporting; - - provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts deleted file mode 100644 index 6c780cea..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/junit-platform-engine.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - `java-library-conventions` - `java-test-fixtures` -} - -description = "JUnit Platform Engine API" - -dependencies { - api(platform(projects.junitBom)) - api(libs.opentest4j) - api(projects.junitPlatformCommons) - - compileOnlyApi(libs.apiguardian) - - testImplementation(libs.assertj) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java deleted file mode 100644 index 41522e85..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static java.lang.String.format; -import static java.util.stream.Collectors.joining; -import static org.junit.platform.engine.FilterResult.included; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Predicate; - -import org.junit.platform.commons.util.Preconditions; - -/** - * Combines a collection of {@link Filter Filters} into a new filter that will - * include elements if and only if all of the filters in the specified collection - * include it. - * - * @since 1.0 - */ -class CompositeFilter implements Filter { - - @SuppressWarnings("rawtypes") - private static final Filter ALWAYS_INCLUDED_FILTER = new Filter() { - @Override - public FilterResult apply(Object obj) { - return ALWAYS_INCLUDED_RESULT; - } - - @Override - public Predicate toPredicate() { - return obj -> true; - } - }; - - private static final FilterResult ALWAYS_INCLUDED_RESULT = included("Always included"); - private static final FilterResult INCLUDED_BY_ALL_FILTERS = included("Element was included by all filters."); - - @SuppressWarnings("unchecked") - static Filter alwaysIncluded() { - return ALWAYS_INCLUDED_FILTER; - } - - private final Collection> filters; - - CompositeFilter(Collection> filters) { - this.filters = new ArrayList<>(Preconditions.notEmpty(filters, "filters must not be empty")); - } - - @Override - public FilterResult apply(T element) { - // @formatter:off - return filters.stream() - .map(filter -> filter.apply(element)) - .filter(FilterResult::excluded) - .findFirst() - .orElse(INCLUDED_BY_ALL_FILTERS); - // @formatter:on - } - - @Override - public Predicate toPredicate() { - // @formatter:off - return filters.stream() - .map(Filter::toPredicate) - .reduce(Predicate::and) - .get(); // it's safe to call get() here because the constructor ensures filters is not empty - // @formatter:on - } - - @Override - public String toString() { - // @formatter:off - return filters.stream() - .map(Object::toString) - .map(value -> format("(%s)", value)) - .collect(joining(" and ")); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java deleted file mode 100644 index 7c859ab3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; - -/** - * Configuration parameters that {@link TestEngine TestEngines} may use to - * influence test discovery and execution. - * - *

For example, the JUnit Jupiter engine uses a configuration parameter to - * enable IDEs and build tools to deactivate conditional test execution. - * - *

As of JUnit Platform 1.8, configuration parameters are also made available to - * implementations of the {@link org.junit.platform.launcher.TestExecutionListener} - * API via the {@link org.junit.platform.launcher.TestPlan}. - * - * @since 1.0 - * @see TestEngine - * @see EngineDiscoveryRequest - * @see ExecutionRequest - */ -@API(status = STABLE, since = "1.0") -public interface ConfigurationParameters { - - /** - * Name of the JUnit Platform configuration file: {@value}. - * - *

If a properties file with this name is present in the root of the - * classpath, it will be used as a source for configuration - * parameters. If multiple files are present, only the first one - * detected in the classpath will be used. - * - * @see java.util.Properties - */ - String CONFIG_FILE_NAME = "junit-platform.properties"; - - /** - * Get the configuration parameter stored under the specified {@code key}. - * - *

If no such key is present in this {@code ConfigurationParameters}, - * an attempt will be made to look up the value as a JVM system property. - * If no such system property exists, an attempt will be made to look up - * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties - * file}. - * - * @param key the key to look up; never {@code null} or blank - * @return an {@code Optional} containing the value; never {@code null} - * but potentially empty - * - * @see #getBoolean(String) - * @see System#getProperty(String) - * @see #CONFIG_FILE_NAME - */ - Optional get(String key); - - /** - * Get the boolean configuration parameter stored under the specified - * {@code key}. - * - *

If no such key is present in this {@code ConfigurationParameters}, - * an attempt will be made to look up the value as a JVM system property. - * If no such system property exists, an attempt will be made to look up - * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties - * file}. - * - * @param key the key to look up; never {@code null} or blank - * @return an {@code Optional} containing the value; never {@code null} - * but potentially empty - * - * @see #get(String) - * @see Boolean#parseBoolean(String) - * @see System#getProperty(String) - * @see #CONFIG_FILE_NAME - */ - Optional getBoolean(String key); - - /** - * Get and transform the configuration parameter stored under the specified - * {@code key} using the specified {@code transformer}. - * - *

If no such key is present in this {@code ConfigurationParameters}, - * an attempt will be made to look up the value as a JVM system property. - * If no such system property exists, an attempt will be made to look up - * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties - * file}. - * - *

In case the transformer throws an exception, it will be wrapped in a - * {@link JUnitException} with a helpful message. - * - * @param key the key to look up; never {@code null} or blank - * @param transformer the transformer to apply in case a value is found; - * never {@code null} - * @return an {@code Optional} containing the value; never {@code null} - * but potentially empty - * - * @since 1.3 - * @see #getBoolean(String) - * @see System#getProperty(String) - * @see #CONFIG_FILE_NAME - */ - @API(status = STABLE, since = "1.3") - default Optional get(String key, Function transformer) { - Preconditions.notNull(transformer, "transformer must not be null"); - return get(key).map(input -> { - try { - return transformer.apply(input); - } - catch (Exception ex) { - String message = String.format( - "Failed to transform configuration parameter with key '%s' and initial value '%s'", key, input); - throw new JUnitException(message, ex); - } - }); - } - - /** - * Get the number of configuration parameters stored directly in this - * {@code ConfigurationParameters}. - * @deprecated as of JUnit Platform 1.9 in favor of {@link #keySet()} - */ - @Deprecated - @API(status = DEPRECATED, since = "1.9") - int size(); - - /** - * Get the keys of all configuration parameters stored in this - * {@code ConfigurationParameters}. - * - * @return the set of keys contained in this {@code ConfigurationParameters} - */ - @API(status = STABLE, since = "1.9") - Set keySet(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java deleted file mode 100644 index 68b5b1d5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * A {@link DiscoveryFilter} is applied during test discovery to determine if - * a given container or test should be included in the test plan. - * - *

{@link TestEngine TestEngines} should apply {@code DiscoveryFilters} - * during the test discovery phase. - * - * @since 1.0 - * @see EngineDiscoveryRequest - * @see TestEngine - */ -@API(status = STABLE, since = "1.0") -public interface DiscoveryFilter extends Filter { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java deleted file mode 100644 index 06c222ac..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * A selector defines what a {@link TestEngine} can use to discover tests - * — for example, the name of a Java class, the path to a file or - * directory, etc. - * - * @since 1.0 - * @see EngineDiscoveryRequest - * @see org.junit.platform.engine.discovery.DiscoverySelectors - */ -@API(status = STABLE, since = "1.0") -public interface DiscoverySelector { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java deleted file mode 100644 index 615ce857..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * {@code EngineDiscoveryListener} contains {@link TestEngine} access to the - * information necessary to discover tests and containers. - * - *

All methods in this interface have empty default implementations. - * Concrete implementations may therefore override one or more of these methods - * to be notified of the selected events. - * - *

The methods declared in this interface should be called by - * each {@link TestEngine} during test discovery. However, since this interface - * was only added in 1.6, older engines might not yet do so. - * - * @since 1.6 - * @see EngineDiscoveryRequest#getDiscoveryListener() - */ -@API(status = EXPERIMENTAL, since = "1.6") -public interface EngineDiscoveryListener { - - /** - * No-op implementation of {@code EngineDiscoveryListener} - */ - EngineDiscoveryListener NOOP = new EngineDiscoveryListener() { - }; - - /** - * Must be called after a discovery selector has been processed by a test - * engine. - * - *

Exceptions thrown by implementations of this method will cause test - * discovery of the current engine to be aborted. - * - * @param engineId the unique ID of the engine descriptor - * @param selector the processed selector - * @param result the resolution result of the supplied engine and selector - * @see SelectorResolutionResult - */ - default void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java deleted file mode 100644 index e5062594..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; - -import org.apiguardian.api.API; - -/** - * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the - * information necessary to discover tests and containers. - * - *

A request is comprised of {@linkplain DiscoverySelector selectors} and - * {@linkplain DiscoveryFilter filters}. While the former select - * resources that engines can use to discover tests, the latter specify how - * such resources are to be filtered. All of the filters - * have to include a resource for it to end up in the test plan. - * - *

In addition, the supplied {@linkplain ConfigurationParameters - * configuration parameters} can be used to influence the discovery process. - * - * @since 1.0 - * @see TestEngine - * @see TestDescriptor - * @see DiscoverySelector - * @see DiscoveryFilter - * @see ConfigurationParameters - */ -@API(status = STABLE, since = "1.0") -public interface EngineDiscoveryRequest { - - /** - * Get the {@link DiscoverySelector DiscoverySelectors} for this request, - * filtered by a particular type. - * - * @param selectorType the type of {@link DiscoverySelector} to filter by - * @return all selectors of this request that are instances of - * {@code selectorType}; never {@code null} but potentially empty - */ - List getSelectorsByType(Class selectorType); - - /** - * Get the {@link DiscoveryFilter DiscoveryFilters} for this request, - * filtered by a particular type. - * - *

The returned filters are to be combined using AND semantics, i.e. all - * of them have to include a resource for it to end up in the test plan. - * - * @param filterType the type of {@link DiscoveryFilter} to filter by - * @return all filters of this request that are instances of - * {@code filterType}; never {@code null} but potentially empty - */ - > List getFiltersByType(Class filterType); - - /** - * Get the {@link ConfigurationParameters} for this request. - * - * @return the configuration parameters; never {@code null} - */ - ConfigurationParameters getConfigurationParameters(); - - /** - * Get the {@link EngineDiscoveryListener} for this request. - * - *

The default implementation returns a no-op listener that ignores all - * calls so that engines that call this methods can be used with an earlier - * version of the JUnit Platform that did not yet include this API. - * - * @return the discovery listener; never {@code null} - * @since 1.6 - */ - @API(status = EXPERIMENTAL, since = "1.6") - default EngineDiscoveryListener getDiscoveryListener() { - return EngineDiscoveryListener.NOOP; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java deleted file mode 100644 index 315548ff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestExecutionResult.Status; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * Listener to be notified of test execution events by - * {@linkplain TestEngine test engines}. - * - *

Contrary to JUnit 4, {@linkplain TestEngine test engines} - * must report events not only for {@linkplain TestDescriptor test descriptors} - * that represent executable leaves but also for all intermediate containers. - * - * @since 1.0 - * @see TestEngine - * @see ExecutionRequest - */ -@API(status = STABLE, since = "1.0") -public interface EngineExecutionListener { - - /** - * No-op implementation of {@code EngineExecutionListener} - */ - EngineExecutionListener NOOP = new EngineExecutionListener() { - }; - - /** - * Must be called when a new, dynamic {@link TestDescriptor} has been - * registered. - * - *

A dynamic test is a test that is not known a-priori and - * therefore was not present in the test tree when discovering tests. - * - * @param testDescriptor the descriptor of the newly registered test - * or container - */ - default void dynamicTestRegistered(TestDescriptor testDescriptor) { - } - - /** - * Must be called when the execution of a leaf or subtree of the test tree - * has been skipped. - * - *

The {@link TestDescriptor} may represent a test or a container. In the - * case of a container, engines must not fire any additional events for its - * descendants. - * - *

A skipped test or subtree of tests must not be reported as - * {@linkplain #executionStarted started} or - * {@linkplain #executionFinished finished}. - * - * @param testDescriptor the descriptor of the skipped test or container - * @param reason a human-readable message describing why the execution - * has been skipped - */ - default void executionSkipped(TestDescriptor testDescriptor, String reason) { - } - - /** - * Must be called when the execution of a leaf or subtree of the test tree - * is about to be started. - * - *

The {@link TestDescriptor} may represent a test or a container. In the - * case of a container, engines have to fire additional events for its - * children. - * - *

This method may only be called if the test or container has not - * been {@linkplain #executionSkipped skipped}. - * - *

This method must be called for a container {@code TestDescriptor} - * before {@linkplain #executionStarted starting} or - * {@linkplain #executionSkipped skipping} any of its children. - * - * @param testDescriptor the descriptor of the started test or container - */ - default void executionStarted(TestDescriptor testDescriptor) { - } - - /** - * Must be called when the execution of a leaf or subtree of the test tree - * has finished, regardless of the outcome. - * - *

The {@link TestDescriptor} may represent a test or a container. - * - *

This method may only be called if the test or container has not - * been {@linkplain #executionSkipped skipped}. - * - *

This method must be called for a container {@code TestIdentifier} - * after all of its children have been - * {@linkplain #executionSkipped skipped} or have - * {@linkplain #executionFinished finished}. - * - *

The {@link TestExecutionResult} describes the result of the execution - * for the supplied {@code testDescriptor}. The result does not include or - * aggregate the results of its children. For example, a container with a - * failing test must be reported as {@link Status#SUCCESSFUL SUCCESSFUL} even - * if one or more of its children are reported as {@link Status#FAILED FAILED}. - * - * @param testDescriptor the descriptor of the finished test or container - * @param testExecutionResult the (unaggregated) result of the execution for - * the supplied {@code TestDescriptor} - * - * @see TestExecutionResult - */ - default void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { - } - - /** - * Can be called for any {@link TestDescriptor} in order to publish additional - * information to the reporting infrastructure — for example: - * - *

    - *
  • Output that would otherwise go to {@code System.out}
  • - *
  • Information about test context or test data
  • - *
- * - *

The current lifecycle state of the supplied {@code TestDescriptor} is - * not relevant: reporting events can occur at any time. - * - * @param testDescriptor the descriptor of the test or container to which - * the reporting entry belongs - * @param entry a {@code ReportEntry} instance to be published - */ - default void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java deleted file mode 100644 index 2c77f1fd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * Provides a single {@link TestEngine} access to the information necessary to - * execute its tests. - * - *

A request contains an engine's root {@link TestDescriptor}, the - * {@link EngineExecutionListener} to be notified of test execution events, and - * {@link ConfigurationParameters} that the engine may use to influence test - * execution. - * - * @since 1.0 - * @see TestEngine - */ -@API(status = STABLE, since = "1.0") -public class ExecutionRequest { - - private final TestDescriptor rootTestDescriptor; - - private final EngineExecutionListener engineExecutionListener; - - private final ConfigurationParameters configurationParameters; - - @API(status = INTERNAL, since = "1.0") - public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, - ConfigurationParameters configurationParameters) { - this.rootTestDescriptor = rootTestDescriptor; - this.engineExecutionListener = engineExecutionListener; - this.configurationParameters = configurationParameters; - } - - /** - * Factory for creating an execution request. - * - * @param rootTestDescriptor the engine's root {@link TestDescriptor} - * @param engineExecutionListener the {@link EngineExecutionListener} to be - * notified of test execution events - * @param configurationParameters {@link ConfigurationParameters} that the - * engine may use to influence test execution - * @return a new {@code ExecutionRequest}; never {@code null} - * @since 1.9 - */ - @API(status = STABLE, since = "1.9") - public static ExecutionRequest create(TestDescriptor rootTestDescriptor, - EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { - return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters); - } - - /** - * Get the root {@link TestDescriptor} of the engine that processes this - * request. - * - *

Note: the root descriptor is the - * {@code TestDescriptor} returned by - * {@link TestEngine#discover(EngineDiscoveryRequest, UniqueId)}. - */ - public TestDescriptor getRootTestDescriptor() { - return this.rootTestDescriptor; - } - - /** - * Get the {@link EngineExecutionListener} to be notified of test execution - * events. - */ - public EngineExecutionListener getEngineExecutionListener() { - return this.engineExecutionListener; - } - - /** - * Get the {@link ConfigurationParameters} that the engine may use to - * influence test execution. - */ - public ConfigurationParameters getConfigurationParameters() { - return this.configurationParameters; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java deleted file mode 100644 index 2d77ff1e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static java.util.Arrays.asList; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.CompositeFilter.alwaysIncluded; - -import java.util.Collection; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; - -/** - * A {@link Filter} can be applied to determine if an object should be - * included or excluded in a result set. - * - *

For example, tests may be filtered during or after test discovery - * based on certain criteria. - * - *

Clients should not implement this interface directly but rather one of - * its subinterfaces. - * - * @since 1.0 - * @see DiscoveryFilter - */ -@FunctionalInterface -@API(status = STABLE, since = "1.0") -public interface Filter { - - /** - * Return a filter that will include elements if and only if all of the - * filters in the supplied array of {@link Filter filters} include it. - * - *

If the array is empty, the returned filter will include all elements - * it is asked to filter. - * - * @param filters the array of filters to compose; never {@code null} - * @see #composeFilters(Collection) - */ - @SafeVarargs - @SuppressWarnings("varargs") - static Filter composeFilters(Filter... filters) { - Preconditions.notNull(filters, "filters array must not be null"); - Preconditions.containsNoNullElements(filters, "individual filters must not be null"); - - if (filters.length == 0) { - return alwaysIncluded(); - } - if (filters.length == 1) { - return filters[0]; - } - return new CompositeFilter<>(asList(filters)); - } - - /** - * Return a filter that will include elements if and only if all of the - * filters in the supplied collection of {@link Filter filters} include it. - * - *

If the collection is empty, the returned filter will include all - * elements it is asked to filter. - * - * @param filters the collection of filters to compose; never {@code null} - * @see #composeFilters(Filter...) - */ - static Filter composeFilters(Collection> filters) { - Preconditions.notNull(filters, "Filters must not be null"); - - if (filters.isEmpty()) { - return alwaysIncluded(); - } - if (filters.size() == 1) { - return getOnlyElement(filters); - } - return new CompositeFilter<>(filters); - } - - /** - * Return a filter that will include elements if and only if the adapted - * {@code Filter} includes the value converted using the supplied - * {@link Function}. - * - * @param adaptee the filter to be adapted - * @param converter the converter function to apply - */ - static Filter adaptFilter(Filter adaptee, Function converter) { - return input -> adaptee.apply(converter.apply(input)); - } - - /** - * Apply this filter to the supplied object. - */ - FilterResult apply(T object); - - /** - * Return a {@link Predicate} that returns {@code true} if this filter - * includes the object supplied to the predicate's - * {@link Predicate#test test} method. - */ - default Predicate toPredicate() { - return object -> apply(object).included(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java deleted file mode 100644 index 622205aa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Optional; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * The result of applying a {@link Filter}. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public class FilterResult { - - /** - * Factory for creating included results. - * - * @param reason the reason why the filtered object was included - * @return an included {@code FilterResult} with the given reason - */ - public static FilterResult included(String reason) { - return new FilterResult(true, reason); - } - - /** - * Factory for creating excluded results. - * - * @param reason the reason why the filtered object was excluded - * @return an excluded {@code FilterResult} with the given reason - */ - public static FilterResult excluded(String reason) { - return new FilterResult(false, reason); - } - - /** - * Factory for creating filter results based on the condition given. - * - * @param included whether or not the filtered object should be included - * @return a valid {@code FilterResult} for the given condition - */ - public static FilterResult includedIf(boolean included) { - return includedIf(included, () -> null, () -> null); - } - - /** - * Factory for creating filter results based on the condition given. - * - * @param included whether or not the filtered object should be included - * @param inclusionReasonSupplier supplier for the reason in case of inclusion - * @param exclusionReasonSupplier supplier for the reason in case of exclusion - * @return a valid {@code FilterResult} for the given condition - */ - public static FilterResult includedIf(boolean included, Supplier inclusionReasonSupplier, - Supplier exclusionReasonSupplier) { - return included ? included(inclusionReasonSupplier.get()) : excluded(exclusionReasonSupplier.get()); - } - - private final boolean included; - - private final Optional reason; - - private FilterResult(boolean included, String reason) { - this.included = included; - this.reason = Optional.ofNullable(reason); - } - - /** - * @return {@code true} if the filtered object should be included - */ - public boolean included() { - return this.included; - } - - /** - * @return {@code true} if the filtered object should be excluded - */ - public boolean excluded() { - return !included(); - } - - /** - * Get the reason why the filtered object should be included or excluded, - * if available. - */ - public Optional getReason() { - return this.reason; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("included", this.included) - .append("reason", this.reason.orElse("")) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java deleted file mode 100644 index 22291017..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@code SelectorResolutionResult} encapsulates the result of resolving a - * {@link DiscoverySelector} by a {@link TestEngine}. - * - *

A {@code SelectorResolutionResult} consists of a mandatory - * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. - * - * @since 1.6 - */ -@API(status = EXPERIMENTAL, since = "1.6") -public class SelectorResolutionResult { - - /** - * Status of resolving a {@link DiscoverySelector}. - */ - public enum Status { - - /** - * Indicates that the {@link TestEngine} has successfully resolved the - * selector. - */ - RESOLVED, - - /** - * Indicates that the {@link TestEngine} was unable to resolve the - * selector. - */ - UNRESOLVED, - - /** - * Indicates that the {@link TestEngine} has encountered an error while - * resolving the selector. - */ - FAILED - - } - - private static final SelectorResolutionResult RESOLVED_RESULT = new SelectorResolutionResult(Status.RESOLVED, null); - private static final SelectorResolutionResult UNRESOLVED_RESULT = new SelectorResolutionResult(Status.UNRESOLVED, - null); - - /** - * Create a {@code SelectorResolutionResult} for a resolved - * selector. - * @return the {@code SelectorResolutionResult}; never {@code null} - */ - public static SelectorResolutionResult resolved() { - return RESOLVED_RESULT; - } - - /** - * Create a {@code SelectorResolutionResult} for an unresolved - * selector. - * @return the {@code SelectorResolutionResult}; never {@code null} - */ - public static SelectorResolutionResult unresolved() { - return UNRESOLVED_RESULT; - } - - /** - * Create a {@code SelectorResolutionResult} for a failed - * selector resolution. - * @return the {@code SelectorResolutionResult}; never {@code null} - */ - public static SelectorResolutionResult failed(Throwable throwable) { - return new SelectorResolutionResult(Status.FAILED, throwable); - } - - private final Status status; - private final Throwable throwable; - - private SelectorResolutionResult(Status status, Throwable throwable) { - this.status = status; - this.throwable = throwable; - } - - /** - * Get the {@linkplain Status status} of this result. - * - * @return the status; never {@code null} - */ - public Status getStatus() { - return status; - } - - /** - * Get the throwable that caused this result, if available. - * - * @return an {@code Optional} containing the throwable; never {@code null} - * but potentially empty - */ - public Optional getThrowable() { - return Optional.ofNullable(throwable); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("status", status) - .append("throwable", throwable) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java deleted file mode 100644 index b16a8d43..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; - -/** - * Mutable descriptor for a test or container that has been discovered by a - * {@link TestEngine}. - * - * @since 1.0 - * @see TestEngine - */ -@API(status = STABLE, since = "1.0") -public interface TestDescriptor { - - /** - * Get the unique identifier (UID) for this descriptor. - * - *

Uniqueness must be guaranteed across an entire test plan, - * regardless of how many engines are used behind the scenes. - * - * @return the {@code UniqueId} for this descriptor; never {@code null} - */ - UniqueId getUniqueId(); - - /** - * Get the display name for this descriptor. - * - *

A display name is a human-readable name for a test or - * container that is typically used for test reporting in IDEs and build - * tools. Display names may contain spaces, special characters, and emoji, - * and the format may be customized by {@link TestEngine TestEngines} or - * potentially by end users as well. Consequently, display names should - * never be parsed; rather, they should be used for display purposes only. - * - * @return the display name for this descriptor; never {@code null} or blank - * @see #getSource() - */ - String getDisplayName(); - - /** - * Get the name of this descriptor in a format that is suitable for legacy - * reporting infrastructure — for example, for reporting systems built - * on the Ant-based XML reporting format for JUnit 4. - * - *

The default implementation delegates to {@link #getDisplayName()}. - * - * @return the legacy reporting name; never {@code null} or blank - */ - default String getLegacyReportingName() { - return getDisplayName(); - } - - /** - * Get the set of {@linkplain TestTag tags} associated with this descriptor. - * - * @return the set of tags associated with this descriptor; never {@code null} - * but potentially empty - * @see TestTag - */ - Set getTags(); - - /** - * Get the {@linkplain TestSource source} of the test or container described - * by this descriptor, if available. - * - * @see TestSource - */ - Optional getSource(); - - /** - * Get the parent of this descriptor, if available. - */ - Optional getParent(); - - /** - * Set the parent of this descriptor. - * - * @param parent the new parent of this descriptor; may be {@code null}. - */ - void setParent(TestDescriptor parent); - - /** - * Get the immutable set of children of this descriptor. - * - * @return the set of children of this descriptor; neither {@code null} - * nor mutable, but potentially empty - * @see #getDescendants() - */ - Set getChildren(); - - /** - * Get the immutable set of all descendants of this descriptor. - * - *

A descendant is a child of this descriptor or a child of one of - * its children, recursively. - * - * @see #getChildren() - */ - default Set getDescendants() { - Set descendants = new LinkedHashSet<>(); - descendants.addAll(getChildren()); - for (TestDescriptor child : getChildren()) { - descendants.addAll(child.getDescendants()); - } - return Collections.unmodifiableSet(descendants); - } - - /** - * Add a child to this descriptor. - * - * @param descriptor the child to add to this descriptor; never {@code null} - */ - void addChild(TestDescriptor descriptor); - - /** - * Remove a child from this descriptor. - * - * @param descriptor the child to remove from this descriptor; never - * {@code null} - */ - void removeChild(TestDescriptor descriptor); - - /** - * Remove this non-root descriptor from its parent and remove all the - * children from this descriptor. - * - *

If this method is invoked on a {@linkplain #isRoot root} descriptor, - * this method must throw a {@link org.junit.platform.commons.JUnitException - * JUnitException} explaining that a root cannot be removed from the - * hierarchy. - */ - void removeFromHierarchy(); - - /** - * Determine if this descriptor is a root descriptor. - * - *

A root descriptor is a descriptor without a parent. - */ - default boolean isRoot() { - return !getParent().isPresent(); - } - - /** - * Determine the {@link Type} of this descriptor. - * - * @return the descriptor type; never {@code null}. - * @see #isContainer() - * @see #isTest() - */ - Type getType(); - - /** - * Determine if this descriptor describes a container. - * - *

The default implementation delegates to {@link Type#isContainer()}. - */ - default boolean isContainer() { - return getType().isContainer(); - } - - /** - * Determine if this descriptor describes a test. - * - *

The default implementation delegates to {@link Type#isTest()}. - */ - default boolean isTest() { - return getType().isTest(); - } - - /** - * Determine if this descriptor may register dynamic tests during execution. - * - *

The default implementation assumes tests are usually known during - * discovery and thus returns {@code false}. - */ - default boolean mayRegisterTests() { - return false; - } - - /** - * Determine if the supplied descriptor (or any of its descendants) - * {@linkplain TestDescriptor#isTest() is a test} or - * {@linkplain TestDescriptor#mayRegisterTests() may potentially register - * tests dynamically}. - * - * @param testDescriptor the {@code TestDescriptor} to check for tests; never - * {@code null} - * @return {@code true} if the descriptor is a test, contains tests, or may - * later register tests dynamically - */ - static boolean containsTests(TestDescriptor testDescriptor) { - Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); - return testDescriptor.isTest() || testDescriptor.mayRegisterTests() - || testDescriptor.getChildren().stream().anyMatch(TestDescriptor::containsTests); - } - - /** - * Remove this descriptor from the hierarchy unless it is a root or contains - * tests. - * - *

A concrete {@link TestEngine} may override this method in order to - * implement a different algorithm or to skip pruning altogether. - * - * @see #isRoot() - * @see #containsTests(TestDescriptor) - * @see #removeFromHierarchy() - */ - default void prune() { - if (!isRoot() && !containsTests(this)) { - removeFromHierarchy(); - } - } - - /** - * Find the descriptor with the supplied unique ID. - * - *

The search algorithm begins with this descriptor and then searches - * through its descendants. - * - * @param uniqueId the {@code UniqueId} to search for; never {@code null} - */ - Optional findByUniqueId(UniqueId uniqueId); - - /** - * Accept a {@link Visitor} to the subtree starting with this descriptor. - * - * @param visitor the {@code Visitor} to accept; never {@code null} - */ - default void accept(Visitor visitor) { - Preconditions.notNull(visitor, "Visitor must not be null"); - visitor.visit(this); - // Create a copy of the set in order to avoid a ConcurrentModificationException - new LinkedHashSet<>(this.getChildren()).forEach(child -> child.accept(visitor)); - } - - /** - * Visitor for the tree-like {@link TestDescriptor} structure. - * - * @see TestDescriptor#accept(Visitor) - */ - @FunctionalInterface - interface Visitor { - - /** - * Visit a {@link TestDescriptor}. - * - * @param descriptor the {@code TestDescriptor} to visit; never {@code null} - */ - void visit(TestDescriptor descriptor); - - } - - /** - * Supported types for {@link TestDescriptor TestDescriptors}. - */ - enum Type { - - /** - * Denotes that the {@link TestDescriptor} is for a container. - */ - CONTAINER, - - /** - * Denotes that the {@link TestDescriptor} is for a test. - */ - TEST, - - /** - * Denotes that the {@link TestDescriptor} is for a test - * that may potentially also be a container. - */ - CONTAINER_AND_TEST; - - /** - * @return {@code true} if this type represents a descriptor that can - * contain other descriptors - */ - public boolean isContainer() { - return this == CONTAINER || this == CONTAINER_AND_TEST; - } - - /** - * @return {@code true} if this type represents a descriptor for a test - */ - public boolean isTest() { - return this == TEST || this == CONTAINER_AND_TEST; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java deleted file mode 100644 index 401fa193..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ModuleUtils; -import org.junit.platform.commons.util.PackageUtils; - -/** - * A {@code TestEngine} facilitates discovery and execution of - * tests for a particular programming model. - * - *

For example, JUnit provides a {@code TestEngine} that discovers and - * executes tests written using the JUnit Jupiter programming model. - * - *

Every {@code TestEngine} must {@linkplain #getId provide its own unique ID}, - * {@linkplain #discover discover tests} from - * {@link EngineDiscoveryRequest EngineDiscoveryRequests}, - * and {@linkplain #execute execute those tests} according to - * {@link ExecutionRequest ExecutionRequests}. - * - *

In order to facilitate test discovery within IDEs and tools prior - * to launching the JUnit Platform, {@code TestEngine} implementations are - * encouraged to make use of the - * {@link org.junit.platform.commons.annotation.Testable @Testable} annotation. - * For example, the {@code @Test} and {@code @TestFactory} annotations in JUnit - * Jupiter are meta-annotated with {@code @Testable}. Consult the Javadoc for - * {@code @Testable} for further details. - * - * @since 1.0 - * @see org.junit.platform.engine.EngineDiscoveryRequest - * @see org.junit.platform.engine.ExecutionRequest - * @see org.junit.platform.commons.annotation.Testable - */ -@API(status = STABLE, since = "1.0") -public interface TestEngine { - - /** - * Get the ID that uniquely identifies this test engine. - * - *

Each test engine must provide a unique ID. For example, JUnit Vintage - * and JUnit Jupiter use {@code "junit-vintage"} and {@code "junit-jupiter"}, - * respectively. When in doubt, you may use the fully qualified name of your - * custom {@code TestEngine} implementation class. - */ - String getId(); - - /** - * Discover tests according to the supplied {@link EngineDiscoveryRequest}. - * - *

The supplied {@link UniqueId} must be used as the unique ID of the - * returned root {@link TestDescriptor}. In addition, the {@code UniqueId} - * must be used to create unique IDs for children of the root's descriptor - * by calling {@link UniqueId#append}. - * - * @param discoveryRequest the discovery request; never {@code null} - * @param uniqueId the unique ID to be used for this test engine's - * {@code TestDescriptor}; never {@code null} - * @return the root {@code TestDescriptor} of this engine, typically an - * instance of {@code EngineDescriptor} - * @see org.junit.platform.engine.support.descriptor.EngineDescriptor - */ - TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId); - - /** - * Execute tests according to the supplied {@link ExecutionRequest}. - * - *

The {@code request} passed to this method contains the root - * {@link TestDescriptor} that was previously returned by {@link #discover}, - * the {@link EngineExecutionListener} to be notified of test execution - * events, and {@link ConfigurationParameters} that may influence test execution. - * - * @param request the request to execute tests for; never {@code null} - */ - void execute(ExecutionRequest request); - - /** - * Get the Group ID of the JAR in which this test engine is packaged. - * - *

This information is used solely for debugging and reporting purposes. - * - *

The default implementation returns an empty {@link Optional}, - * signaling that the group ID is unknown. - * - *

Concrete test engine implementations may override this method in - * order to provide a known group ID. - * - * @return an {@code Optional} containing the group ID; never {@code null} - * but potentially empty if the group ID is unknown - * @see #getArtifactId() - * @see #getVersion() - */ - default Optional getGroupId() { - return Optional.empty(); - } - - /** - * Get the Artifact ID of the JAR in which this test engine is packaged. - * - *

This information is used solely for debugging and reporting purposes. - * - *

The default implementation assumes the implementation title is equivalent - * to the artifact ID and therefore attempts to query the - * {@linkplain Package#getImplementationTitle() implementation title} - * from the package attributes for the {@link Package} in which the engine - * resides. Note that a package only has attributes if the information is - * defined in the {@link java.util.jar.Manifest Manifest} of the JAR - * containing that package, and if the class loader created the - * {@link Package} instance with the attributes from the manifest. - * - *

If the implementation title cannot be queried from the package - * attributes, the default implementation returns an empty - * {@link Optional}. - * - *

Concrete test engine implementations may override this method in - * order to determine the artifact ID by some other means. - * - * @implNote Since JUnit Platform version 1.1 this default implementation - * returns the "module name" stored in the module (modular jar on the - * module-path) of this test engine. - * - * @return an {@code Optional} containing the artifact ID; never - * {@code null} but potentially empty if the artifact ID is unknown - * @see Class#getPackage() - * @see Package#getImplementationTitle() - * @see #getGroupId() - * @see #getVersion() - */ - default Optional getArtifactId() { - Optional moduleName = ModuleUtils.getModuleName(getClass()); - if (moduleName.isPresent()) { - return moduleName; - } - return PackageUtils.getAttribute(getClass(), Package::getImplementationTitle); - } - - /** - * Get the version of this test engine. - * - *

This information is used solely for debugging and reporting purposes. - * - *

Initially, the default implementation tries to retrieve the engine - * version from the manifest attribute named: {@code "Engine-Version-" + getId()} - * - *

Then the default implementation attempts to query the - * {@linkplain Package#getImplementationVersion() implementation version} - * from the package attributes for the {@link Package} in which the engine - * resides. Note that a package only has attributes if the information is - * defined in the {@link java.util.jar.Manifest Manifest} of the JAR - * containing that package, and if the class loader created the - * {@link Package} instance with the attributes from the manifest. - * - *

If the implementation version cannot be queried from the package - * attributes, the default implementation returns {@code "DEVELOPMENT"}. - * - *

Concrete test engine implementations may override this method to - * determine the version by some other means. - * - *

implNote: Since JUnit Platform version 1.1 this default implementation - * honors the "raw version" information stored in the module (modular jar - * on the module-path) of this test engine. - * - * @return an {@code Optional} containing the version; never {@code null} - * but potentially empty if the version is unknown - * @see Class#getPackage() - * @see Package#getImplementationVersion() - * @see #getGroupId() - * @see #getArtifactId() - */ - default Optional getVersion() { - Optional standalone = PackageUtils.getAttribute(getClass(), "Engine-Version-" + getId()); - if (standalone.isPresent()) { - return standalone; - } - String fallback = "DEVELOPMENT"; - Optional moduleVersion = ModuleUtils.getModuleVersion(getClass()); - if (moduleVersion.isPresent()) { - return moduleVersion; - } - return Optional.of(PackageUtils.getAttribute(getClass(), Package::getImplementationVersion).orElse(fallback)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java deleted file mode 100644 index 6509ecd4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@code TestExecutionResult} encapsulates the result of executing a single test - * or container. - * - *

A {@code TestExecutionResult} consists of a mandatory - * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public class TestExecutionResult { - - /** - * Status of executing a single test or container. - */ - public enum Status { - - /** - * Indicates that the execution of a test or container was - * successful. - */ - SUCCESSFUL, - - /** - * Indicates that the execution of a test or container was - * aborted (started but not finished). - */ - ABORTED, - - /** - * Indicates that the execution of a test or container failed. - */ - FAILED; - - } - - private static final TestExecutionResult SUCCESSFUL_RESULT = new TestExecutionResult(SUCCESSFUL, null); - - /** - * Create a {@code TestExecutionResult} for a successful execution - * of a test or container. - * @return the {@code TestExecutionResult}; never {@code null} - */ - public static TestExecutionResult successful() { - return SUCCESSFUL_RESULT; - } - - /** - * Create a {@code TestExecutionResult} for an aborted execution - * of a test or container with the supplied {@link Throwable throwable}. - * - * @param throwable the throwable that caused the aborted execution; may be - * {@code null} - * @return the {@code TestExecutionResult}; never {@code null} - */ - public static TestExecutionResult aborted(Throwable throwable) { - return new TestExecutionResult(ABORTED, throwable); - } - - /** - * Create a {@code TestExecutionResult} for a failed execution - * of a test or container with the supplied {@link Throwable throwable}. - * - * @param throwable the throwable that caused the failed execution; may be - * {@code null} - * @return the {@code TestExecutionResult}; never {@code null} - */ - public static TestExecutionResult failed(Throwable throwable) { - return new TestExecutionResult(FAILED, throwable); - } - - private final Status status; - private final Throwable throwable; - - private TestExecutionResult(Status status, Throwable throwable) { - this.status = Preconditions.notNull(status, "Status must not be null"); - this.throwable = throwable; - } - - /** - * Get the {@linkplain Status status} of this result. - * - * @return the status; never {@code null} - */ - public Status getStatus() { - return status; - } - - /** - * Get the throwable that caused this result, if available. - * - * @return an {@code Optional} containing the throwable; never {@code null} - * but potentially empty - */ - public Optional getThrowable() { - return Optional.ofNullable(throwable); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("status", status) - .append("throwable", throwable) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java deleted file mode 100644 index 0f3d8738..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.Serializable; - -import org.apiguardian.api.API; - -/** - * Representation of the source of a test or container used to navigate to - * its location by IDEs and build tools. - * - *

This is a marker interface. Clients need to check instances for concrete - * subclasses or subinterfaces. - * - *

Implementations of this interface need to ensure that they are - * serializable and immutable since they may be used as data - * transfer objects. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public interface TestSource extends Serializable { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java deleted file mode 100644 index 9a32e07f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; - -/** - * Immutable value object for a tag that is assigned to a test or - * container. - * - * @since 1.0 - * @see #isValid(String) - * @see #create(String) - */ -@API(status = STABLE, since = "1.0") -public final class TestTag implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String name; - - /** - * Reserved characters that are not permissible as part of a tag name. - * - *

    - *
  • {@code ,}: comma
  • - *
  • {@code (}: left parenthesis
  • - *
  • {@code )}: right parenthesis
  • - *
  • {@code &}: ampersand
  • - *
  • {@code |}: vertical bar
  • - *
  • {@code !}: exclamation point
  • - *
- */ - public static final Set RESERVED_CHARACTERS = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList(",", "(", ")", "&", "|", "!"))); - - /** - * Determine if the supplied tag name is valid with regard to the supported - * syntax for tags. - * - *

Syntax Rules for Tags

- *
    - *
  • A tag must not be {@code null}.
  • - *
  • A tag must not be blank.
  • - *
  • A trimmed tag must not contain whitespace.
  • - *
  • A trimmed tag must not contain ISO control characters.
  • - *
  • A trimmed tag must not contain {@linkplain #RESERVED_CHARACTERS - * reserved characters}.
  • - *
- * - *

If this method returns {@code true} for a given name, it is then a - * valid candidate for the {@link TestTag#create(String) create()} factory - * method. - * - * @param name the name of the tag to validate; may be {@code null} or blank - * @return {@code true} if the supplied tag name conforms to the supported - * syntax for tags - * @see StringUtils#isNotBlank(String) - * @see String#trim() - * @see StringUtils#doesNotContainWhitespace(String) - * @see StringUtils#doesNotContainIsoControlCharacter(String) - * @see #RESERVED_CHARACTERS - * @see TestTag#create(String) - */ - public static boolean isValid(String name) { - if (name == null) { - return false; - } - name = name.trim(); - - return !name.isEmpty() && // - StringUtils.doesNotContainWhitespace(name) && // - StringUtils.doesNotContainIsoControlCharacter(name) && // - doesNotContainReservedCharacter(name); - } - - private static boolean doesNotContainReservedCharacter(String str) { - return RESERVED_CHARACTERS.stream().noneMatch(str::contains); - } - - /** - * Create a {@code TestTag} from the supplied {@code name}. - * - *

Consider checking whether the syntax of the supplied {@code name} - * is {@linkplain #isValid(String) valid} before attempting to create a - * {@code TestTag} using this factory method. - * - *

Note: the supplied {@code name} will be {@linkplain String#trim() trimmed}. - * - * @param name the name of the tag; must be syntactically valid - * @throws PreconditionViolationException if the supplied tag name is not - * syntactically valid - * @see TestTag#isValid(String) - */ - public static TestTag create(String name) throws PreconditionViolationException { - return new TestTag(name); - } - - private TestTag(String name) { - Preconditions.condition(TestTag.isValid(name), - () -> String.format("Tag name [%s] must be syntactically valid", name)); - this.name = name.trim(); - } - - /** - * Get the name of this tag. - * - * @return the name of this tag; never {@code null} or blank - */ - public String getName() { - return this.name; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof TestTag) { - TestTag that = (TestTag) obj; - return Objects.equals(this.name, that.name); - } - return false; - } - - @Override - public int hashCode() { - return this.name.hashCode(); - } - - @Override - public String toString() { - return this.name; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java deleted file mode 100644 index f4d516cf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static java.util.Collections.singletonList; -import static java.util.Collections.unmodifiableList; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.Serializable; -import java.lang.ref.SoftReference; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@code UniqueId} encapsulates the creation, parsing, and display of unique IDs - * for {@link TestDescriptor TestDescriptors}. - * - *

Instances of this class have value semantics and are immutable. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public class UniqueId implements Cloneable, Serializable { - - private static final long serialVersionUID = 1L; - - private static final String ENGINE_SEGMENT_TYPE = "engine"; - - /** - * Parse a {@code UniqueId} from the supplied string representation using the - * default format. - * - * @param uniqueId the string representation to parse; never {@code null} or blank - * @return a properly constructed {@code UniqueId} - * @throws JUnitException if the string cannot be parsed - */ - public static UniqueId parse(String uniqueId) throws JUnitException { - Preconditions.notBlank(uniqueId, "Unique ID string must not be null or blank"); - return UniqueIdFormat.getDefault().parse(uniqueId); - } - - /** - * Create an engine's unique ID from its {@code engineId} using the default - * format. - * - *

The engine ID will be stored in a {@link Segment} with - * {@link Segment#getType type} {@code "engine"}. - * - * @param engineId the engine ID; never {@code null} or blank - * @see #root(String, String) - */ - public static UniqueId forEngine(String engineId) { - Preconditions.notBlank(engineId, "engineId must not be null or blank"); - return root(ENGINE_SEGMENT_TYPE, engineId); - } - - /** - * Create a root unique ID from the supplied {@code segmentType} and - * {@code value} using the default format. - * - * @param segmentType the segment type; never {@code null} or blank - * @param value the value; never {@code null} or blank - * @see #forEngine(String) - */ - public static UniqueId root(String segmentType, String value) { - return new UniqueId(UniqueIdFormat.getDefault(), new Segment(segmentType, value)); - } - - private final UniqueIdFormat uniqueIdFormat; - - @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (singletonList() or ArrayList) - private final List segments; - - // lazily computed - private transient int hashCode; - - // lazily computed - private transient SoftReference toString; - - private UniqueId(UniqueIdFormat uniqueIdFormat, Segment segment) { - this(uniqueIdFormat, singletonList(segment)); - } - - /** - * Initialize a {@code UniqueId} instance. - * - * @implNote A defensive copy of the segment list is not created by - * this implementation. All callers should immediately drop the reference - * to the list instance that they pass into this constructor. - */ - UniqueId(UniqueIdFormat uniqueIdFormat, List segments) { - this.uniqueIdFormat = uniqueIdFormat; - this.segments = segments; - } - - final Optional getRoot() { - return this.segments.isEmpty() ? Optional.empty() : Optional.of(this.segments.get(0)); - } - - /** - * Get the engine ID stored in this {@code UniqueId}, if available. - * - * @see #forEngine(String) - */ - public final Optional getEngineId() { - return getRoot().filter(segment -> segment.getType().equals(ENGINE_SEGMENT_TYPE)).map(Segment::getValue); - } - - /** - * Get the immutable list of {@linkplain Segment segments} that make up this - * {@code UniqueId}. - */ - public final List getSegments() { - return unmodifiableList(this.segments); - } - - /** - * Construct a new {@code UniqueId} by appending a new {@link Segment}, based - * on the supplied {@code segmentType} and {@code value}, to the end of this - * {@code UniqueId}. - * - *

This {@code UniqueId} will not be modified. - * - *

Neither the {@code segmentType} nor the {@code value} may contain any - * of the special characters used for constructing the string representation - * of this {@code UniqueId}. - * - * @param segmentType the type of the segment; never {@code null} or blank - * @param value the value of the segment; never {@code null} or blank - */ - public final UniqueId append(String segmentType, String value) { - return append(new Segment(segmentType, value)); - } - - /** - * Construct a new {@code UniqueId} by appending a new {@link Segment} to - * the end of this {@code UniqueId}. - * - *

This {@code UniqueId} will not be modified. - * - * @param segment the segment to be appended; never {@code null} - * - * @since 1.1 - */ - @API(status = STABLE, since = "1.1") - public final UniqueId append(Segment segment) { - Preconditions.notNull(segment, "segment must not be null"); - List baseSegments = new ArrayList<>(this.segments.size() + 1); - baseSegments.addAll(this.segments); - baseSegments.add(segment); - return new UniqueId(this.uniqueIdFormat, baseSegments); - } - - /** - * Construct a new {@code UniqueId} by appending a new {@link Segment}, based - * on the supplied {@code engineId}, to the end of this {@code UniqueId}. - * - *

This {@code UniqueId} will not be modified. - * - *

The engine ID will be stored in a {@link Segment} with - * {@link Segment#getType type} {@value ENGINE_SEGMENT_TYPE}. - * - * @param engineId the engine ID; never {@code null} or blank - * - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - public UniqueId appendEngine(String engineId) { - return append(new Segment(ENGINE_SEGMENT_TYPE, engineId)); - } - - /** - * Determine if the supplied {@code UniqueId} is a prefix for this - * {@code UniqueId}. - * - * @param potentialPrefix the {@code UniqueId} to be checked; never {@code null} - * - * @since 1.1 - */ - @API(status = STABLE, since = "1.1") - public boolean hasPrefix(UniqueId potentialPrefix) { - Preconditions.notNull(potentialPrefix, "potentialPrefix must not be null"); - int size = this.segments.size(); - int prefixSize = potentialPrefix.segments.size(); - return size >= prefixSize && this.segments.subList(0, prefixSize).equals(potentialPrefix.segments); - } - - /** - * Construct a new {@code UniqueId} and removing the last {@link Segment} of - * this {@code UniqueId}. - * - *

This {@code UniqueId} will not be modified. - * - * @return a new {@code UniqueId}; never {@code null} - * @throws org.junit.platform.commons.PreconditionViolationException - * if this {@code UniqueId} contains a single segment - * @since 1.5 - */ - @API(status = STABLE, since = "1.5") - public UniqueId removeLastSegment() { - Preconditions.condition(this.segments.size() > 1, "Cannot remove last remaining segment"); - return new UniqueId(uniqueIdFormat, new ArrayList<>(this.segments.subList(0, this.segments.size() - 1))); - } - - /** - * Get the last {@link Segment} of this {@code UniqueId}. - * - * @return the last {@code Segment}; never {@code null} - * @since 1.5 - */ - @API(status = STABLE, since = "1.5") - public Segment getLastSegment() { - return this.segments.get(this.segments.size() - 1); - } - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - UniqueId that = (UniqueId) o; - return this.segments.equals(that.segments); - } - - @Override - public int hashCode() { - int value = this.hashCode; - if (value == 0) { - value = this.segments.hashCode(); - if (value == 0) { - // handle the edge case of the computed hashCode being 0 - value = 1; - } - // this is a benign race like String#hash - // we potentially read and write values from multiple threads - // without a happens-before relationship - // however the JMM guarantees us that we only ever see values - // that were valid at one point, either 0 or the hash code - // so we might end up not seeing a value that a different thread - // has computed or multiple threads writing the same value - this.hashCode = value; - } - return value; - } - - /** - * Generate the unique, formatted string representation of this {@code UniqueId} - * using the configured {@link UniqueIdFormat}. - */ - @Override - public String toString() { - SoftReference s = this.toString; - String value = s == null ? null : s.get(); - if (value == null) { - value = this.uniqueIdFormat.format(this); - // this is a benign race like String#hash - // we potentially read and write values from multiple threads - // without a happens-before relationship - // however the JMM guarantees us that we only ever see values - // that were valid at one point, either null or the toString value - // so we might end up not seeing a value that a different thread - // has computed or multiple threads writing the same value - this.toString = new SoftReference<>(value); - } - return value; - } - - /** - * A segment of a {@link UniqueId} comprises a type and a - * value. - */ - @API(status = STABLE, since = "1.0") - public static class Segment implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String type; - private final String value; - - /** - * Create a new {@code Segment} using the supplied {@code type} and - * {@code value}. - * - * @param type the type of this segment - * @param value the value of this segment - */ - Segment(String type, String value) { - Preconditions.notBlank(type, "type must not be null or blank"); - Preconditions.notBlank(value, "value must not be null or blank"); - this.type = type; - this.value = value; - } - - /** - * Get the type of this segment. - */ - public String getType() { - return this.type; - } - - /** - * Get the value of this segment. - */ - public String getValue() { - return this.value; - } - - @Override - public int hashCode() { - return Objects.hash(this.type, this.value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Segment that = (Segment) o; - return Objects.equals(this.type, that.type) && Objects.equals(this.value, that.value); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("type", this.type) - .append("value", this.value) - .toString(); - // @formatter:on - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java deleted file mode 100644 index f89dddd9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; - -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.UniqueId.Segment; - -/** - * Used to {@link #parse} a {@link UniqueId} from a string representation - * or to {@link #format} a {@link UniqueId} into a string representation. - * - * @since 1.0 - */ -class UniqueIdFormat implements Serializable { - - private static final long serialVersionUID = 1L; - - private static final UniqueIdFormat defaultFormat = new UniqueIdFormat('[', ':', ']', '/'); - - static UniqueIdFormat getDefault() { - return defaultFormat; - } - - private static String quote(char c) { - return Pattern.quote(String.valueOf(c)); - } - - private static String encode(char c) { - try { - return URLEncoder.encode(String.valueOf(c), StandardCharsets.UTF_8.name()); - } - catch (UnsupportedEncodingException e) { - throw new AssertionError("UTF-8 should be supported", e); - } - } - - private final char openSegment; - private final char closeSegment; - private final char segmentDelimiter; - private final char typeValueSeparator; - private final Pattern segmentPattern; - private final HashMap encodedCharacterMap = new HashMap<>(); - - UniqueIdFormat(char openSegment, char typeValueSeparator, char closeSegment, char segmentDelimiter) { - this.openSegment = openSegment; - this.typeValueSeparator = typeValueSeparator; - this.closeSegment = closeSegment; - this.segmentDelimiter = segmentDelimiter; - this.segmentPattern = Pattern.compile( - String.format("%s(.+)%s(.+)%s", quote(openSegment), quote(typeValueSeparator), quote(closeSegment)), - Pattern.DOTALL); - - // Compute "forbidden" character encoding map. - // Note that the map is always empty at this point. Thus the use of - // computeIfAbsent() is purely syntactic sugar. - encodedCharacterMap.computeIfAbsent('%', UniqueIdFormat::encode); - encodedCharacterMap.computeIfAbsent('+', UniqueIdFormat::encode); - encodedCharacterMap.computeIfAbsent(openSegment, UniqueIdFormat::encode); - encodedCharacterMap.computeIfAbsent(typeValueSeparator, UniqueIdFormat::encode); - encodedCharacterMap.computeIfAbsent(closeSegment, UniqueIdFormat::encode); - encodedCharacterMap.computeIfAbsent(segmentDelimiter, UniqueIdFormat::encode); - } - - /** - * Parse a {@code UniqueId} from the supplied string representation. - * - * @return a properly constructed {@code UniqueId} - * @throws JUnitException if the string cannot be parsed - */ - UniqueId parse(String source) throws JUnitException { - String[] parts = source.split(String.valueOf(this.segmentDelimiter)); - List segments = Arrays.stream(parts).map(this::createSegment).collect(toList()); - return new UniqueId(this, segments); - } - - private Segment createSegment(String segmentString) throws JUnitException { - Matcher segmentMatcher = this.segmentPattern.matcher(segmentString); - if (!segmentMatcher.matches()) { - throw new JUnitException(String.format("'%s' is not a well-formed UniqueId segment", segmentString)); - } - String type = decode(checkAllowed(segmentMatcher.group(1))); - String value = decode(checkAllowed(segmentMatcher.group(2))); - return new Segment(type, value); - } - - private String checkAllowed(String typeOrValue) { - checkDoesNotContain(typeOrValue, this.segmentDelimiter); - checkDoesNotContain(typeOrValue, this.typeValueSeparator); - checkDoesNotContain(typeOrValue, this.openSegment); - checkDoesNotContain(typeOrValue, this.closeSegment); - return typeOrValue; - } - - private void checkDoesNotContain(String typeOrValue, char forbiddenCharacter) { - Preconditions.condition(typeOrValue.indexOf(forbiddenCharacter) < 0, - () -> String.format("type or value '%s' must not contain '%s'", typeOrValue, forbiddenCharacter)); - } - - /** - * Format and return the string representation of the supplied {@code UniqueId}. - */ - String format(UniqueId uniqueId) { - // @formatter:off - return uniqueId.getSegments().stream() - .map(this::describe) - .collect(joining(String.valueOf(this.segmentDelimiter))); - // @formatter:on - } - - private String describe(Segment segment) { - String body = encode(segment.getType()) + typeValueSeparator + encode(segment.getValue()); - return openSegment + body + closeSegment; - } - - private String encode(String s) { - StringBuilder builder = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - String value = encodedCharacterMap.get(c); - if (value == null) { - builder.append(c); - continue; - } - builder.append(value); - } - return builder.toString(); - } - - private static String decode(String s) { - try { - return URLDecoder.decode(s, StandardCharsets.UTF_8.name()); - } - catch (UnsupportedEncodingException e) { - throw new JUnitException("UTF-8 should be supported", e); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java deleted file mode 100644 index d8512db3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import org.junit.platform.commons.util.Preconditions; - -/** - * Abstract {@link ClassNameFilter} that servers as a superclass - * for filters including or excluding fully qualified class names - * based on pattern-matching. - * - * @since 1.0 - */ -abstract class AbstractClassNameFilter implements ClassNameFilter { - - protected final List patterns; - protected final String patternDescription; - - AbstractClassNameFilter(String... patterns) { - Preconditions.notEmpty(patterns, "patterns array must not be null or empty"); - Preconditions.containsNoNullElements(patterns, "patterns array must not contain null elements"); - this.patterns = Arrays.stream(patterns).map(Pattern::compile).collect(toList()); - this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'")); - } - - @Override - public abstract Predicate toPredicate(); - - protected Optional findMatchingPattern(String className) { - return this.patterns.stream().filter(pattern -> pattern.matcher(className).matches()).findAny(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java deleted file mode 100644 index ae404989..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.engine.DiscoveryFilter; - -/** - * {@link DiscoveryFilter} that is applied to the name of a {@link Class}. - * - * @since 1.0 - * @see #includeClassNamePatterns(String...) - * @see #excludeClassNamePatterns(String...) - * @see PackageNameFilter - */ -@API(status = STABLE, since = "1.0") -public interface ClassNameFilter extends DiscoveryFilter { - - /** - * Standard include pattern in the form of a regular expression that is - * used to match against fully qualified class names: - * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} - * - *

This pattern matches against class names beginning with {@code Test} - * or ending with {@code Test} or {@code Tests} (in any package). - */ - // Implementation notes: - // - Test.* :: "Test" prefix for classes in default package - // - .+[.$]Test.* :: "Test" prefix for top-level and nested classes in a named package - // - .*Tests? :: "Test" and "Tests" suffixes in any package - String STANDARD_INCLUDE_PATTERN = "^(Test.*|.+[.$]Test.*|.*Tests?)$"; - - /** - * Create a new include {@link ClassNameFilter} based on the - * supplied patterns. - * - *

The patterns are combined using OR semantics, i.e. if the fully - * qualified name of a class matches against at least one of the patterns, - * the class will be included in the result set. - * - * @param patterns regular expressions to match against fully qualified - * class names; never {@code null}, empty, or containing {@code null} - * @see Class#getName() - * @see #excludeClassNamePatterns(String...) - */ - static ClassNameFilter includeClassNamePatterns(String... patterns) { - return new IncludeClassNameFilter(patterns); - } - - /** - * Create a new exclude {@link ClassNameFilter} based on the - * supplied patterns. - * - *

The patterns are combined using OR semantics, i.e. if the fully - * qualified name of a class matches against at least one of the patterns, - * the class will be excluded from the result set. - * - * @param patterns regular expressions to match against fully qualified - * class names; never {@code null}, empty, or containing {@code null} - * @see Class#getName() - * @see #includeClassNamePatterns(String...) - */ - static ClassNameFilter excludeClassNamePatterns(String... patterns) { - return new ExcludeClassNameFilter(patterns); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java deleted file mode 100644 index 30f69069..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a {@link Class} or class name so - * that {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on classes. - * - *

If a Java {@link Class} reference is provided, the selector will return - * that {@code Class} and its class name accordingly. If a class name is - * provided, the selector will only attempt to lazily load the {@link Class} - * if {@link #getJavaClass()} is invoked. - * - *

In this context, Java {@link Class} means anything that can be referenced - * as a {@link Class} on the JVM — for example, classes from other JVM - * languages such Groovy, Scala, etc. - * - * @since 1.0 - * @see DiscoverySelectors#selectClass(String) - * @see DiscoverySelectors#selectClass(Class) - * @see org.junit.platform.engine.support.descriptor.ClassSource - */ -@API(status = STABLE, since = "1.0") -public class ClassSelector implements DiscoverySelector { - - private final String className; - - private Class javaClass; - - ClassSelector(String className) { - this.className = className; - } - - ClassSelector(Class javaClass) { - this.className = javaClass.getName(); - this.javaClass = javaClass; - } - - /** - * Get the selected class name. - */ - public String getClassName() { - return this.className; - } - - /** - * Get the selected {@link Class}. - * - *

If the {@link Class} was not provided, but only the name, this method - * attempts to lazily load the {@link Class} based on its name and throws a - * {@link PreconditionViolationException} if the class cannot be loaded. - */ - public Class getJavaClass() { - if (this.javaClass == null) { - this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( - cause -> new PreconditionViolationException("Could not load class with name: " + this.className, - cause)); - } - return this.javaClass; - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ClassSelector that = (ClassSelector) o; - return Objects.equals(this.className, that.className); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return this.className.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("className", this.className).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java deleted file mode 100644 index b257e6c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects the name of a classpath resource - * so that {@link org.junit.platform.engine.TestEngine TestEngines} can load resources - * from the classpath — for example, to load XML or JSON files from the classpath, - * potentially within JARs. - * - *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not - * expected to modify the classpath, the classpath resource represented by this - * selector must be on the classpath of the - * {@linkplain Thread#getContextClassLoader() context class loader} of the - * {@linkplain Thread thread} that uses it. - * - * @since 1.0 - * @see DiscoverySelectors#selectClasspathResource(String) - * @see ClasspathRootSelector - * @see #getClasspathResourceName() - */ -@API(status = STABLE, since = "1.0") -public class ClasspathResourceSelector implements DiscoverySelector { - - private final String classpathResourceName; - private final FilePosition position; - - ClasspathResourceSelector(String classpathResourceName, FilePosition position) { - boolean startsWithSlash = classpathResourceName.startsWith("/"); - this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); - this.position = position; - } - - /** - * Get the name of the selected classpath resource. - * - *

The name of a classpath resource must follow the semantics - * for resource paths as defined in {@link ClassLoader#getResource(String)}. - * - * @see ClassLoader#getResource(String) - * @see ClassLoader#getResourceAsStream(String) - * @see ClassLoader#getResources(String) - */ - public String getClasspathResourceName() { - return this.classpathResourceName; - } - - /** - * Get the selected {@code FilePosition} within the classpath resource. - */ - public Optional getPosition() { - return Optional.ofNullable(this.position); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ClasspathResourceSelector that = (ClasspathResourceSelector) o; - return Objects.equals(this.classpathResourceName, that.classpathResourceName) - && Objects.equals(this.position, that.position); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return Objects.hash(this.classpathResourceName, this.position); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("classpathResourceName", this.classpathResourceName).append("position", - this.position).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java deleted file mode 100644 index b9d03f48..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.net.URI; -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a classpath root so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can search for class - * files or resources within the physical classpath — for example, to - * scan for test classes. - * - *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not - * expected to modify the classpath, the classpath root represented by this - * selector must be on the classpath of the - * {@linkplain Thread#getContextClassLoader() context class loader} of the - * {@linkplain Thread thread} that uses this selector. - * - * @since 1.0 - * @see DiscoverySelectors#selectClasspathRoots(java.util.Set) - * @see ClasspathResourceSelector - * @see Thread#getContextClassLoader() - */ -@API(status = STABLE, since = "1.0") -public class ClasspathRootSelector implements DiscoverySelector { - - private final URI classpathRoot; - - ClasspathRootSelector(URI classpathRoot) { - this.classpathRoot = classpathRoot; - } - - /** - * Get the selected classpath root directory as an {@link URI}. - */ - public URI getClasspathRoot() { - return this.classpathRoot; - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ClasspathRootSelector that = (ClasspathRootSelector) o; - return Objects.equals(this.classpathRoot, that.classpathRoot); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return this.classpathRoot.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("classpathRoot", this.classpathRoot).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java deleted file mode 100644 index 167da07b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.File; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a directory so that - * {@link org.junit.platform.engine.TestEngine TestEngines} - * can discover tests or containers based on directories in the - * file system. - * - * @since 1.0 - * @see DiscoverySelectors#selectDirectory(String) - * @see DiscoverySelectors#selectDirectory(File) - * @see FileSelector - * @see #getDirectory() - * @see #getPath() - * @see #getRawPath() - */ -@API(status = STABLE, since = "1.0") -public class DirectorySelector implements DiscoverySelector { - - private final String path; - - DirectorySelector(String path) { - this.path = path; - } - - /** - * Get the selected directory as a {@link java.io.File}. - * - * @see #getPath() - * @see #getRawPath() - */ - public File getDirectory() { - return new File(this.path); - } - - /** - * Get the selected directory as a {@link java.nio.file.Path} using the - * {@linkplain FileSystems#getDefault default} {@link FileSystem}. - * - * @see #getDirectory() - * @see #getRawPath() - */ - public Path getPath() { - return Paths.get(this.path); - } - - /** - * Get the selected directory as a raw path. - * - * @see #getDirectory() - * @see #getPath() - */ - public String getRawPath() { - return this.path; - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DirectorySelector that = (DirectorySelector) o; - return Objects.equals(this.path, that.path); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return this.path.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("path", this.path).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java deleted file mode 100644 index 77336370..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java +++ /dev/null @@ -1,716 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.UniqueId; - -/** - * Collection of {@code static} factory methods for creating - * {@link DiscoverySelector DiscoverySelectors}. - * - * @since 1.0 - * @see UriSelector - * @see FileSelector - * @see DirectorySelector - * @see ClasspathRootSelector - * @see ClasspathResourceSelector - * @see ModuleSelector - * @see PackageSelector - * @see ClassSelector - * @see MethodSelector - * @see NestedClassSelector - * @see NestedMethodSelector - * @see UniqueIdSelector - */ -@API(status = STABLE, since = "1.0") -public final class DiscoverySelectors { - - private DiscoverySelectors() { - /* no-op */ - } - - /** - * Create a {@code UriSelector} for the supplied URI. - * - * @param uri the URI to select; never {@code null} or blank - * @see UriSelector - * @see #selectUri(URI) - * @see #selectFile(String) - * @see #selectFile(File) - * @see #selectDirectory(String) - * @see #selectDirectory(File) - */ - public static UriSelector selectUri(String uri) { - Preconditions.notBlank(uri, "URI must not be null or blank"); - try { - return new UriSelector(new URI(uri)); - } - catch (URISyntaxException ex) { - throw new PreconditionViolationException("Failed to create a java.net.URI from: " + uri, ex); - } - } - - /** - * Create a {@code UriSelector} for the supplied {@link URI}. - * - * @param uri the URI to select; never {@code null} - * @see UriSelector - * @see #selectUri(String) - * @see #selectFile(String) - * @see #selectFile(File) - * @see #selectDirectory(String) - * @see #selectDirectory(File) - */ - public static UriSelector selectUri(URI uri) { - Preconditions.notNull(uri, "URI must not be null"); - return new UriSelector(uri); - } - - /** - * Create a {@code FileSelector} for the supplied file path. - * - *

This method selects the file using the supplied path as is, - * without verifying if the file exists. - * - * @param path the path to the file to select; never {@code null} or blank - * @see FileSelector - * @see #selectFile(File) - * @see #selectFile(String, FilePosition) - * @see #selectFile(File, FilePosition) - * @see #selectDirectory(String) - * @see #selectDirectory(File) - */ - public static FileSelector selectFile(String path) { - return selectFile(path, null); - } - - /** - * Create a {@code FileSelector} for the supplied {@linkplain File file}. - * - *

This method selects the file in its {@linkplain File#getCanonicalPath() - * canonical} form and throws a {@link PreconditionViolationException} if the - * file does not exist. - * - * @param file the file to select; never {@code null} - * @see FileSelector - * @see #selectFile(String) - * @see #selectFile(File, FilePosition) - * @see #selectFile(String, FilePosition) - * @see #selectDirectory(String) - * @see #selectDirectory(File) - */ - public static FileSelector selectFile(File file) { - return selectFile(file, null); - } - - /** - * Create a {@code FileSelector} for the supplied file path. - * - *

This method selects the file using the supplied path as is, - * without verifying if the file exists. - * - * @param path the path to the file to select; never {@code null} or blank - * @param position the position inside the file; may be {@code null} - * @see FileSelector - * @see #selectFile(String) - * @see #selectFile(File) - * @see #selectFile(File, FilePosition) - * @see #selectDirectory(String) - * @see #selectDirectory(File) - */ - public static FileSelector selectFile(String path, FilePosition position) { - Preconditions.notBlank(path, "File path must not be null or blank"); - return new FileSelector(path, position); - } - - /** - * Create a {@code FileSelector} for the supplied {@linkplain File file}. - * - *

This method selects the file in its {@linkplain File#getCanonicalPath() - * canonical} form and throws a {@link PreconditionViolationException} if the - * file does not exist. - * - * @param file the file to select; never {@code null} - * @param position the position inside the file; may be {@code null} - * @see FileSelector - * @see #selectFile(File) - * @see #selectFile(String) - * @see #selectFile(String, FilePosition) - * @see #selectDirectory(String) - * @see #selectDirectory(File) - */ - public static FileSelector selectFile(File file, FilePosition position) { - Preconditions.notNull(file, "File must not be null"); - Preconditions.condition(file.isFile(), - () -> String.format("The supplied java.io.File [%s] must represent an existing file", file)); - try { - return new FileSelector(file.getCanonicalPath(), position); - } - catch (IOException ex) { - throw new PreconditionViolationException("Failed to retrieve canonical path for file: " + file, ex); - } - } - - /** - * Create a {@code DirectorySelector} for the supplied directory path. - * - *

This method selects the directory using the supplied path as is, - * without verifying if the directory exists. - * - * @param path the path to the directory to select; never {@code null} or blank - * @see DirectorySelector - * @see #selectDirectory(File) - * @see #selectFile(String) - * @see #selectFile(File) - */ - public static DirectorySelector selectDirectory(String path) { - Preconditions.notBlank(path, "Directory path must not be null or blank"); - return new DirectorySelector(path); - } - - /** - * Create a {@code DirectorySelector} for the supplied {@linkplain File directory}. - * - *

This method selects the directory in its {@linkplain File#getCanonicalPath() - * canonical} form and throws a {@link PreconditionViolationException} if the - * directory does not exist. - * - * @param directory the directory to select; never {@code null} - * @see DirectorySelector - * @see #selectDirectory(String) - * @see #selectFile(String) - * @see #selectFile(File) - */ - public static DirectorySelector selectDirectory(File directory) { - Preconditions.notNull(directory, "Directory must not be null"); - Preconditions.condition(directory.isDirectory(), - () -> String.format("The supplied java.io.File [%s] must represent an existing directory", directory)); - try { - return new DirectorySelector(directory.getCanonicalPath()); - } - catch (IOException ex) { - throw new PreconditionViolationException("Failed to retrieve canonical path for directory: " + directory, - ex); - } - } - - /** - * Create a list of {@code ClasspathRootSelectors} for the supplied - * classpath roots (directories or JAR files). - * - *

Since the supplied paths are converted to {@link URI URIs}, the - * {@link java.nio.file.FileSystem} that created them must be the - * {@linkplain java.nio.file.FileSystems#getDefault() default} or one that - * has been created by an installed - * {@link java.nio.file.spi.FileSystemProvider}. - * - *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not - * expected to modify the classpath, the classpath roots represented by the - * resulting selectors must be on the classpath of the - * {@linkplain Thread#getContextClassLoader() context class loader} of the - * {@linkplain Thread thread} that uses these selectors. - * - * @param classpathRoots set of directories and JAR files in the filesystem - * that represent classpath roots; never {@code null} - * @return a list of selectors for the supplied classpath roots; elements - * which do not physically exist in the filesystem will be filtered out - * @see ClasspathRootSelector - * @see Thread#getContextClassLoader() - */ - public static List selectClasspathRoots(Set classpathRoots) { - Preconditions.notNull(classpathRoots, "classpathRoots must not be null"); - - // @formatter:off - return classpathRoots.stream() - .filter(Files::exists) - .map(Path::toUri) - .map(ClasspathRootSelector::new) - // unmodifiable since selectClasspathRoots is a public, non-internal method - .collect(toUnmodifiableList()); - // @formatter:on - } - - /** - * Create a {@code ClasspathResourceSelector} for the supplied classpath - * resource name. - * - *

The name of a classpath resource must follow the semantics - * for resource paths as defined in {@link ClassLoader#getResource(String)}. - * - *

If the supplied classpath resource name is prefixed with a slash - * ({@code /}), the slash will be removed. - * - *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not - * expected to modify the classpath, the supplied classpath resource must be - * on the classpath of the - * {@linkplain Thread#getContextClassLoader() context class loader} of the - * {@linkplain Thread thread} that uses the resulting selector. - * - * @param classpathResourceName the name of the classpath resource; never - * {@code null} or blank - * @see #selectClasspathResource(String, FilePosition) - * @see ClasspathResourceSelector - * @see ClassLoader#getResource(String) - * @see ClassLoader#getResourceAsStream(String) - * @see ClassLoader#getResources(String) - */ - public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName) { - return selectClasspathResource(classpathResourceName, null); - } - - /** - * Create a {@code ClasspathResourceSelector} for the supplied classpath - * resource name. - * - *

The name of a classpath resource must follow the semantics - * for resource paths as defined in {@link ClassLoader#getResource(String)}. - * - *

If the supplied classpath resource name is prefixed with a slash - * ({@code /}), the slash will be removed. - * - *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not - * expected to modify the classpath, the supplied classpath resource must be - * on the classpath of the - * {@linkplain Thread#getContextClassLoader() context class loader} of the - * {@linkplain Thread thread} that uses the resulting selector. - * - * @param classpathResourceName the name of the classpath resource; never - * {@code null} or blank - * @param position the position inside the classpath resource; may be {@code null} - * @see #selectClasspathResource(String) - * @see ClasspathResourceSelector - * @see ClassLoader#getResource(String) - * @see ClassLoader#getResourceAsStream(String) - * @see ClassLoader#getResources(String) - */ - public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, - FilePosition position) { - Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); - return new ClasspathResourceSelector(classpathResourceName, position); - } - - /** - * Create a {@code ModuleSelector} for the supplied module name. - * - *

The unnamed module is not supported. - * - * @param moduleName the module name to select; never {@code null} or blank - * @since 1.1 - * @see ModuleSelector - */ - @API(status = EXPERIMENTAL, since = "1.1") - public static ModuleSelector selectModule(String moduleName) { - Preconditions.notBlank(moduleName, "Module name must not be null or blank"); - return new ModuleSelector(moduleName.trim()); - } - - /** - * Create a list of {@code ModuleSelectors} for the supplied module names. - * - *

The unnamed module is not supported. - * - * @param moduleNames the module names to select; never {@code null}, never - * containing {@code null} or blank - * @since 1.1 - * @see ModuleSelector - */ - @API(status = EXPERIMENTAL, since = "1.1") - public static List selectModules(Set moduleNames) { - Preconditions.notNull(moduleNames, "Module names must not be null"); - Preconditions.containsNoNullElements(moduleNames, "Individual module name must not be null"); - - // @formatter:off - return moduleNames.stream() - .map(DiscoverySelectors::selectModule) - // unmodifiable since this is a public, non-internal method - .collect(toUnmodifiableList()); - // @formatter:on - } - - /** - * Create a {@code PackageSelector} for the supplied package name. - * - *

The default package is represented by an empty string ({@code ""}). - * - * @param packageName the package name to select; never {@code null} and - * never containing whitespace only - * @see PackageSelector - */ - public static PackageSelector selectPackage(String packageName) { - Preconditions.notNull(packageName, "Package name must not be null"); - Preconditions.condition(packageName.isEmpty() || !packageName.trim().isEmpty(), - "Package name must not contain only whitespace"); - return new PackageSelector(packageName.trim()); - } - - /** - * Create a {@code ClassSelector} for the supplied {@link Class}. - * - * @param clazz the class to select; never {@code null} - * @see ClassSelector - */ - public static ClassSelector selectClass(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return new ClassSelector(clazz); - } - - /** - * Create a {@code ClassSelector} for the supplied class name. - * - * @param className the fully qualified name of the class to select; - * never {@code null} or blank - * @see ClassSelector - */ - public static ClassSelector selectClass(String className) { - Preconditions.notBlank(className, "Class name must not be null or blank"); - return new ClassSelector(className); - } - - /** - * Create a {@code MethodSelector} for the supplied fully qualified - * method name. - * - *

The following formats are supported. - * - *

    - *
  • {@code [fully qualified class name]#[methodName]}
  • - *
  • {@code [fully qualified class name]#[methodName](parameter type list)} - *
- * - *

The parameter type list is a comma-separated list of primitive - * names or fully qualified class names for the types of parameters accepted - * by the method. - * - *

Array parameter types may be specified using either the JVM's internal - * String representation (e.g., {@code [[I} for {@code int[][]}, - * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or - * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, - * etc.). - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Examples
MethodFully Qualified Method Name
{@code java.lang.String.chars()}{@code java.lang.String#chars}
{@code java.lang.String.chars()}{@code java.lang.String#chars()}
{@code java.lang.String.equalsIgnoreCase(String)}{@code java.lang.String#equalsIgnoreCase(java.lang.String)}
{@code java.lang.String.substring(int, int)}{@code java.lang.String#substring(int, int)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg([I)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg(int[])}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply([[D)}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply(double[][])}
{@code example.Service.process(String[])}{@code example.Service#process([Ljava.lang.String;)}
{@code example.Service.process(String[])}{@code example.Service#process(java.lang.String[])}
{@code example.Service.process(String[][])}{@code example.Service#process([[Ljava.lang.String;)}
{@code example.Service.process(String[][])}{@code example.Service#process(java.lang.String[][])}
- * - * @param fullyQualifiedMethodName the fully qualified name of the method to select; never - * {@code null} or blank - * @see MethodSelector - */ - public static MethodSelector selectMethod(String fullyQualifiedMethodName) throws PreconditionViolationException { - String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); - return selectMethod(methodParts[0], methodParts[1], methodParts[2]); - } - - /** - * Create a {@code MethodSelector} for the supplied class name and method name. - * - * @param className the fully qualified name of the class in which the method - * is declared, or a subclass thereof; never {@code null} or blank - * @param methodName the name of the method to select; never {@code null} or blank - * @see MethodSelector - */ - public static MethodSelector selectMethod(String className, String methodName) { - Preconditions.notBlank(className, "Class name must not be null or blank"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - return new MethodSelector(className, methodName); - } - - /** - * Create a {@code MethodSelector} for the supplied class name, method name, - * and method parameter types. - * - *

The parameter types {@code String} is typically a comma-separated list - * of atomic types, fully qualified class names, or array types; however, - * the exact syntax depends on the underlying test engine. - * - * @param className the fully qualified name of the class in which the method - * is declared, or a subclass thereof; never {@code null} or blank - * @param methodName the name of the method to select; never {@code null} or blank - * @param methodParameterTypes the method parameter types as a single string; never - * {@code null} though potentially an empty string if the method does not accept - * arguments - * @see MethodSelector - */ - public static MethodSelector selectMethod(String className, String methodName, String methodParameterTypes) { - Preconditions.notBlank(className, "Class name must not be null or blank"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); - return new MethodSelector(className, methodName, methodParameterTypes.trim()); - } - - /** - * Create a {@code MethodSelector} for the supplied {@link Class} and method name. - * - * @param javaClass the class in which the method is declared, or a subclass thereof; - * never {@code null} - * @param methodName the name of the method to select; never {@code null} or blank - * @see MethodSelector - */ - public static MethodSelector selectMethod(Class javaClass, String methodName) { - Preconditions.notNull(javaClass, "Class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - return new MethodSelector(javaClass, methodName); - } - - /** - * Create a {@code MethodSelector} for the supplied {@link Class}, method name, - * and method parameter types. - * - *

The parameter types {@code String} is typically a comma-separated list - * of atomic types, fully qualified class names, or array types; however, - * the exact syntax depends on the underlying test engine. - * - * @param javaClass the class in which the method is declared, or a subclass thereof; - * never {@code null} - * @param methodName the name of the method to select; never {@code null} or blank - * @param methodParameterTypes the method parameter types as a single string; never - * {@code null} though potentially an empty string if the method does not accept - * arguments - * @see MethodSelector - */ - public static MethodSelector selectMethod(Class javaClass, String methodName, String methodParameterTypes) { - Preconditions.notNull(javaClass, "Class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); - return new MethodSelector(javaClass, methodName, methodParameterTypes.trim()); - } - - /** - * Create a {@code MethodSelector} for the supplied {@link Class} and {@link Method}. - * - * @param javaClass the class in which the method is declared, or a subclass thereof; - * never {@code null} - * @param method the method to select; never {@code null} - * @see MethodSelector - */ - public static MethodSelector selectMethod(Class javaClass, Method method) { - Preconditions.notNull(javaClass, "Class must not be null"); - Preconditions.notNull(method, "Method must not be null"); - return new MethodSelector(javaClass, method); - } - - /** - * Create a {@code NestedClassSelector} for the supplied nested {@link Class} and its - * enclosing classes. - * - * @param enclosingClasses the path to the nested class to select; never {@code null} or empty - * @param nestedClass the nested class to select; never {@code null} - * @since 1.6 - * @see NestedClassSelector - */ - @API(status = STABLE, since = "1.6") - public static NestedClassSelector selectNestedClass(List> enclosingClasses, Class nestedClass) { - Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); - Preconditions.notNull(nestedClass, "Nested class must not be null"); - return new NestedClassSelector(enclosingClasses, nestedClass); - } - - /** - * Create a {@code NestedClassSelector} for the supplied class name and its enclosing - * classes' names. - * - * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty - * @param nestedClassName the name of the nested class to select; never {@code null} or blank - * @since 1.6 - * @see NestedClassSelector - */ - @API(status = STABLE, since = "1.6") - public static NestedClassSelector selectNestedClass(List enclosingClassNames, String nestedClassName) { - Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); - Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); - return new NestedClassSelector(enclosingClassNames, nestedClassName); - } - - /** - * Create a {@code NestedMethodSelector} for the supplied nested class name and method name. - * - * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty - * @param nestedClassName the name of the nested class to select; never {@code null} or blank - * @param methodName the name of the method to select; never {@code null} or blank - * @since 1.6 - * @see NestedMethodSelector - */ - @API(status = STABLE, since = "1.6") - public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName, - String methodName) { - - Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); - Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName); - } - - /** - * Create a {@code NestedMethodSelector} for the supplied nested class name, method name, - * and method parameter types. - * - *

The parameter types {@code String} is typically a comma-separated list - * of atomic types, fully qualified class names, or array types; however, - * the exact syntax depends on the underlying test engine. - * - * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty - * @param nestedClassName the name of the nested class to select; never {@code null} or blank - * @param methodName the name of the method to select; never {@code null} or blank - * @param methodParameterTypes the method parameter types as a single string; never - * {@code null} though potentially an empty string if the method does not accept - * arguments - * @since 1.6 - * @see NestedMethodSelector - */ - @API(status = STABLE, since = "1.6") - public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName, - String methodName, String methodParameterTypes) { - - Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); - Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); - return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName, methodParameterTypes); - } - - /** - * Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and method name. - * - * @param enclosingClasses the path to the nested class to select; never {@code null} or empty - * @param nestedClass the nested class to select; never {@code null} - * @param methodName the name of the method to select; never {@code null} or blank - * @since 1.6 - * @see NestedMethodSelector - */ - @API(status = STABLE, since = "1.6") - public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, - String methodName) { - - Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); - Preconditions.notNull(nestedClass, "Nested class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - return new NestedMethodSelector(enclosingClasses, nestedClass, methodName); - } - - /** - * Create a {@code NestedMethodSelector} for the supplied {@link Class}, method name, - * and method parameter types. - * - *

The parameter types {@code String} is typically a comma-separated list - * of atomic types, fully qualified class names, or array types; however, - * the exact syntax depends on the underlying test engine. - * - * @param enclosingClasses the path to the nested class to select; never {@code null} or empty - * @param nestedClass the nested class to select; never {@code null} - * @param methodName the name of the method to select; never {@code null} or blank - * @param methodParameterTypes the method parameter types as a single string; never - * {@code null} though potentially an empty string if the method does not accept - * arguments - * @since 1.6 - * @see NestedMethodSelector - */ - @API(status = STABLE, since = "1.6") - public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, - String methodName, String methodParameterTypes) { - - Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); - Preconditions.notNull(nestedClass, "Nested class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - Preconditions.notNull(methodParameterTypes, "Parameter types must not be null"); - return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, methodParameterTypes); - } - - /** - * Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and {@link Method}. - * - * @param enclosingClasses the path to the nested class to select; never {@code null} or empty - * @param nestedClass the nested class to select; never {@code null} - * @param method the method to select; never {@code null} - * @since 1.6 - * @see NestedMethodSelector - */ - @API(status = STABLE, since = "1.6") - public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, - Method method) { - - Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); - Preconditions.notNull(nestedClass, "Nested class must not be null"); - Preconditions.notNull(method, "Method must not be null"); - return new NestedMethodSelector(enclosingClasses, nestedClass, method); - } - - /** - * Create a {@code UniqueIdSelector} for the supplied {@link UniqueId}. - * - * @param uniqueId the {@code UniqueId} to select; never {@code null} - * @see UniqueIdSelector - */ - public static UniqueIdSelector selectUniqueId(UniqueId uniqueId) { - Preconditions.notNull(uniqueId, "UniqueId must not be null"); - return new UniqueIdSelector(uniqueId); - } - - /** - * Create a {@code UniqueIdSelector} for the supplied unique ID. - * - * @param uniqueId the unique ID to select; never {@code null} or blank - * @see UniqueIdSelector - */ - public static UniqueIdSelector selectUniqueId(String uniqueId) { - Preconditions.notBlank(uniqueId, "Unique ID must not be null or blank"); - return new UniqueIdSelector(UniqueId.parse(uniqueId)); - } - - /** - * Create an {@code IterationSelector} for the supplied parent selector and - * iteration indices. - * - * @param parentSelector the parent selector to select iterations for; never - * {@code null} - * @param iterationIndices the iteration indices to select; never - * {@code null} or empty - * @since 1.9 - * @see IterationSelector - */ - @API(status = EXPERIMENTAL, since = "1.9") - public static IterationSelector selectIteration(DiscoverySelector parentSelector, int... iterationIndices) { - Preconditions.notNull(parentSelector, "Parent selector must not be null"); - Preconditions.notEmpty(iterationIndices, "iteration indices must not be empty"); - return new IterationSelector(parentSelector, iterationIndices); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java deleted file mode 100644 index 8a3aa725..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.junit.platform.engine.FilterResult.excluded; -import static org.junit.platform.engine.FilterResult.included; - -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import org.junit.platform.engine.FilterResult; - -/** - * {@link ClassNameFilter} that matches fully qualified class names against - * patterns in the form of regular expressions. - * - *

If the fully qualified name of a class matches against at least one - * pattern, the class will be excluded. - * - * @since 1.0 - */ -class ExcludeClassNameFilter extends AbstractClassNameFilter { - - ExcludeClassNameFilter(String... patterns) { - super(patterns); - } - - @Override - public FilterResult apply(String className) { - return findMatchingPattern(className) // - .map(pattern -> excluded(formatExclusionReason(className, pattern))) // - .orElseGet(() -> included(formatInclusionReason(className))); - } - - private String formatInclusionReason(String className) { - return String.format("Class name [%s] does not match any excluded pattern: %s", className, patternDescription); - } - - private String formatExclusionReason(String className, Pattern pattern) { - return String.format("Class name [%s] matches excluded pattern: '%s'", className, pattern); - } - - @Override - public Predicate toPredicate() { - return className -> !findMatchingPattern(className).isPresent(); - } - - @Override - public String toString() { - return String.format("%s that excludes class names that match one of the following regular expressions: %s", - getClass().getSimpleName(), this.patternDescription); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java deleted file mode 100644 index 37fb11ca..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static java.util.stream.Collectors.joining; -import static org.junit.platform.engine.FilterResult.excluded; -import static org.junit.platform.engine.FilterResult.included; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.FilterResult; - -/** - * {@link PackageNameFilter} that matches fully qualified package names that - * are not prefixed by one of the package names provided to the filter. - * - *

If the fully qualified name of a package starts with at least one of the - * packages names of the filter, the package will be excluded. - * - * @since 1.0 - */ -class ExcludePackageNameFilter implements PackageNameFilter { - - private final List packageNames; - private final String patternDescription; - - ExcludePackageNameFilter(String... packageNames) { - Preconditions.notEmpty(packageNames, "packageNames must not be null or empty"); - Preconditions.containsNoNullElements(packageNames, "packageNames must not contain null elements"); - this.packageNames = Arrays.asList(packageNames); - this.patternDescription = Arrays.stream(packageNames).collect(joining("' OR '", "'", "'")); - } - - @Override - public FilterResult apply(String packageName) { - return findMatchingName(packageName) // - .map(matchedName -> excluded(formatExclusionReason(packageName, matchedName))) // - .orElseGet(() -> included(formatInclusionReason(packageName))); - } - - private String formatInclusionReason(String packageName) { - return String.format("Package name [%s] does not match any excluded names: %s", packageName, - this.patternDescription); - } - - private String formatExclusionReason(String packageName, String matchedName) { - return String.format("Package name [%s] matches excluded name: '%s'", packageName, matchedName); - } - - @Override - public Predicate toPredicate() { - return packageName -> !findMatchingName(packageName).isPresent(); - } - - private Optional findMatchingName(String packageName) { - return this.packageNames.stream().filter( - name -> name.equals(packageName) || packageName.startsWith(name + ".")).findAny(); - } - - @Override - public String toString() { - return String.format( - "%s that excludes packages whose names are either equal to or start with one of the following: %s", - getClass().getSimpleName(), this.patternDescription); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java deleted file mode 100644 index 4245187a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.Serializable; -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Position inside a file represented by {@linkplain #getLine line} and - * {@linkplain #getColumn column} numbers. - * - * @implNote This class is a copy of - * {@link org.junit.platform.engine.support.descriptor.FilePosition FilePosition}, - * which is not accessible from this package. The decision to duplicate it is - * motivated by an eventual divergence between the two classes in the future. - * - * @since 1.7 - */ -@API(status = STABLE, since = "1.7") -public class FilePosition implements Serializable { - - private static final long serialVersionUID = 1L; - - private static final Logger logger = LoggerFactory.getLogger(FilePosition.class); - - /** - * Create a new {@code FilePosition} using the supplied {@code line} number - * and an undefined column number. - * - * @param line the line number; must be greater than zero - * @return a {@link FilePosition} with the given line number - */ - public static FilePosition from(int line) { - return new FilePosition(line); - } - - /** - * Create a new {@code FilePosition} using the supplied {@code line} and - * {@code column} numbers. - * - * @param line the line number; must be greater than zero - * @param column the column number; must be greater than zero - * @return a {@link FilePosition} with the given line and column numbers - */ - public static FilePosition from(int line, int column) { - return new FilePosition(line, column); - } - - /** - * Create an optional {@code FilePosition} by parsing the supplied - * {@code query} string. - * - *

Examples of valid {@code query} strings: - *

    - *
  • {@code "line=23"}
  • - *
  • {@code "line=23&column=42"}
  • - *
- * - * @param query the query string; may be {@code null} - * @return an {@link Optional} containing a {@link FilePosition} with - * the parsed line and column numbers; never {@code null} but potentially - * empty - * @since 1.3 - * @see #from(int) - * @see #from(int, int) - */ - public static Optional fromQuery(String query) { - FilePosition result = null; - Integer line = null; - Integer column = null; - if (StringUtils.isNotBlank(query)) { - try { - for (String pair : query.split("&")) { - String[] data = pair.split("="); - if (data.length == 2) { - String key = data[0]; - if (line == null && "line".equals(key)) { - line = Integer.valueOf(data[1]); - } - else if (column == null && "column".equals(key)) { - column = Integer.valueOf(data[1]); - } - } - - // Already found what we're looking for? - if (line != null && column != null) { - break; - } - } - } - catch (IllegalArgumentException ex) { - logger.debug(ex, () -> "Failed to parse 'line' and/or 'column' from query string: " + query); - // fall-through and continue - } - - if (line != null) { - result = column == null ? new FilePosition(line) : new FilePosition(line, column); - } - } - return Optional.ofNullable(result); - } - - private final int line; - private final Integer column; - - private FilePosition(int line) { - Preconditions.condition(line > 0, "line number must be greater than zero"); - this.line = line; - this.column = null; - } - - private FilePosition(int line, int column) { - Preconditions.condition(line > 0, "line number must be greater than zero"); - Preconditions.condition(column > 0, "column number must be greater than zero"); - this.line = line; - this.column = column; - } - - /** - * Get the line number of this {@code FilePosition}. - * - * @return the line number - */ - public int getLine() { - return this.line; - } - - /** - * Get the column number of this {@code FilePosition}, if available. - * - * @return an {@code Optional} containing the column number; never - * {@code null} but potentially empty - */ - public Optional getColumn() { - return Optional.ofNullable(this.column); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FilePosition that = (FilePosition) o; - return (this.line == that.line) && Objects.equals(this.column, that.column); - } - - @Override - public int hashCode() { - return Objects.hash(this.line, this.column); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("line", this.line) - .append("column", getColumn().orElse(-1)) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java deleted file mode 100644 index 78bf501c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.File; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a file so that - * {@link org.junit.platform.engine.TestEngine TestEngines} - * can discover tests or containers based on files in the - * file system. - * - * @since 1.0 - * @see DiscoverySelectors#selectFile(String) - * @see DiscoverySelectors#selectFile(File) - * @see DirectorySelector - * @see #getFile() - * @see #getPath() - * @see #getRawPath() - */ -@API(status = STABLE, since = "1.0") -public class FileSelector implements DiscoverySelector { - - private final String path; - private final FilePosition position; - - FileSelector(String path, FilePosition position) { - this.path = path; - this.position = position; - } - - /** - * Get the selected file as a {@link java.io.File}. - * - * @see #getPath() - * @see #getRawPath() - */ - public File getFile() { - return new File(this.path); - } - - /** - * Get the selected file as a {@link java.nio.file.Path} using the - * {@linkplain FileSystems#getDefault default} {@link FileSystem}. - * - * @see #getFile() - * @see #getRawPath() - */ - public Path getPath() { - return Paths.get(this.path); - } - - /** - * Get the selected file as a raw path. - * - * @see #getFile() - * @see #getPath() - */ - public String getRawPath() { - return this.path; - } - - /** - * Get the selected position within the file as a {@link FilePosition}. - */ - public Optional getPosition() { - return Optional.ofNullable(this.position); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FileSelector that = (FileSelector) o; - return Objects.equals(this.path, that.path) && Objects.equals(this.position, that.position); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return Objects.hash(path, position); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("path", this.path).append("position", this.position).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java deleted file mode 100644 index 5cd49e14..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.junit.platform.engine.FilterResult.excluded; -import static org.junit.platform.engine.FilterResult.included; - -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import org.junit.platform.engine.FilterResult; - -/** - * {@link ClassNameFilter} that matches fully qualified class names against - * patterns in the form of regular expressions. - * - *

If the fully qualified name of a class matches against at least one - * pattern, the class will be included. - * - * @since 1.0 - */ -class IncludeClassNameFilter extends AbstractClassNameFilter { - - IncludeClassNameFilter(String... patterns) { - super(patterns); - } - - @Override - public FilterResult apply(String className) { - return findMatchingPattern(className) // - .map(pattern -> included(formatInclusionReason(className, pattern))) // - .orElseGet(() -> excluded(formatExclusionReason(className))); - } - - private String formatInclusionReason(String className, Pattern pattern) { - return String.format("Class name [%s] matches included pattern: '%s'", className, pattern); - } - - private String formatExclusionReason(String className) { - return String.format("Class name [%s] does not match any included pattern: %s", className, - this.patternDescription); - } - - @Override - public Predicate toPredicate() { - return className -> findMatchingPattern(className).isPresent(); - } - - @Override - public String toString() { - return String.format("%s that includes class names that match one of the following regular expressions: %s", - getClass().getSimpleName(), this.patternDescription); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java deleted file mode 100644 index 2ceb30f4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static java.util.stream.Collectors.joining; -import static org.junit.platform.engine.FilterResult.excluded; -import static org.junit.platform.engine.FilterResult.included; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.FilterResult; - -/** - * {@link PackageNameFilter} that matches fully qualified package names that - * are prefixed by one of the package names provided to the filter. - * - *

If the fully qualified name of a package starts with at least one of the - * packages names of the filter, the package will be included. - * - * @since 1.0 - */ -class IncludePackageNameFilter implements PackageNameFilter { - - private final List packageNames; - private final String patternDescription; - - IncludePackageNameFilter(String... packageNames) { - Preconditions.notEmpty(packageNames, "packageNames array must not be null or empty"); - Preconditions.containsNoNullElements(packageNames, "packageNames array must not contain null elements"); - this.packageNames = Arrays.asList(packageNames); - this.patternDescription = Arrays.stream(packageNames).collect(joining("' OR '", "'", "'")); - } - - @Override - public FilterResult apply(String packageName) { - return findMatchingName(packageName) // - .map(matchedName -> included(formatInclusionReason(packageName, matchedName))) // - .orElseGet(() -> excluded(formatExclusionReason(packageName))); - } - - private String formatInclusionReason(String packageName, String matchedName) { - return String.format("Package name [%s] matches included name: '%s'", packageName, matchedName); - } - - private String formatExclusionReason(String packageName) { - return String.format("Package name [%s] does not match any included names: %s", packageName, - this.patternDescription); - } - - @Override - public Predicate toPredicate() { - return packageName -> findMatchingName(packageName).isPresent(); - } - - private Optional findMatchingName(String packageName) { - return this.packageNames.stream().filter( - name -> name.equals(packageName) || packageName.startsWith(name + ".")).findAny(); - } - - @Override - public String toString() { - return String.format( - "%s that includes packages whose names are either equal to or start with one of the following: %s", - getClass().getSimpleName(), this.patternDescription); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java deleted file mode 100644 index fe9ff20a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toCollection; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Objects; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects the iterations of a parent - * {@code DiscoverySelector} via their indices so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * a subset of the iterations of tests or containers. - * - * @since 1.9 - * @see DiscoverySelectors#selectIteration(DiscoverySelector, int...) - */ -@API(status = EXPERIMENTAL, since = "1.9") -public class IterationSelector implements DiscoverySelector { - - private final DiscoverySelector parentSelector; - private final SortedSet iterationIndices; - - IterationSelector(DiscoverySelector parentSelector, int... iterationIndices) { - this.parentSelector = parentSelector; - this.iterationIndices = toSortedSet(iterationIndices); - } - - private SortedSet toSortedSet(int[] iterationIndices) { - return Arrays.stream(iterationIndices) // - .boxed() // - .collect(collectingAndThen(toCollection(TreeSet::new), Collections::unmodifiableSortedSet)); - } - - /** - * Get the selected parent {@link DiscoverySelector}. - */ - public DiscoverySelector getParentSelector() { - return parentSelector; - } - - /** - * Get the selected iteration indices. - */ - public SortedSet getIterationIndices() { - return iterationIndices; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - IterationSelector that = (IterationSelector) o; - return parentSelector.equals(that.parentSelector) && iterationIndices.equals(that.iterationIndices); - } - - @Override - public int hashCode() { - return Objects.hash(parentSelector, iterationIndices); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("parentSelector", this.parentSelector) - .append("iterationIndices", this.iterationIndices) - .toString(); - // @formatter:on - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java deleted file mode 100644 index ff7e627a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.reflect.Method; -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a {@link Method} or a combination of - * class name, method name, and parameter types so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover tests - * or containers based on methods. - * - *

If a Java {@link Method} is provided, the selector will return that - * {@linkplain #getJavaMethod() method} and its method name, class name, and - * parameter types accordingly. If a {@link Class} and method name, a class name - * and method name, or a fully qualified method name is provided, - * this selector will only attempt to lazily load the {@link Class} and - * {@link Method} if {@link #getJavaClass()} or {@link #getJavaMethod()} is - * invoked. - * - *

In this context, a Java {@code Method} means anything that can be referenced - * as a {@link Method} on the JVM — for example, methods from Java classes - * or methods from other JVM languages such Groovy, Scala, etc. - * - * @since 1.0 - * @see DiscoverySelectors#selectMethod(String) - * @see DiscoverySelectors#selectMethod(String, String) - * @see DiscoverySelectors#selectMethod(String, String, String) - * @see DiscoverySelectors#selectMethod(Class, String) - * @see DiscoverySelectors#selectMethod(Class, String, String) - * @see DiscoverySelectors#selectMethod(Class, Method) - * @see org.junit.platform.engine.support.descriptor.MethodSource - */ -@API(status = STABLE, since = "1.0") -public class MethodSelector implements DiscoverySelector { - - private final String className; - private final String methodName; - private final String methodParameterTypes; - - private Class javaClass; - private Method javaMethod; - - MethodSelector(String className, String methodName) { - this(className, methodName, ""); - } - - MethodSelector(String className, String methodName, String methodParameterTypes) { - this.className = className; - this.methodName = methodName; - this.methodParameterTypes = methodParameterTypes; - } - - MethodSelector(Class javaClass, String methodName) { - this(javaClass, methodName, ""); - } - - MethodSelector(Class javaClass, String methodName, String methodParameterTypes) { - this.javaClass = javaClass; - this.className = javaClass.getName(); - this.methodName = methodName; - this.methodParameterTypes = methodParameterTypes; - } - - MethodSelector(Class javaClass, Method method) { - this.javaClass = javaClass; - this.className = javaClass.getName(); - this.javaMethod = method; - this.methodName = method.getName(); - this.methodParameterTypes = ClassUtils.nullSafeToString(method.getParameterTypes()); - } - - /** - * Get the selected class name. - */ - public String getClassName() { - return this.className; - } - - /** - * Get the selected method name. - */ - public String getMethodName() { - return this.methodName; - } - - /** - * Get the parameter types for the selected method as a {@link String}, - * typically a comma-separated list of primitive types, fully qualified - * class names, or array types. - * - *

Note: the parameter types are provided as a single string instead of - * a collection in order to allow this selector to be used in a generic - * fashion by various test engines. It is therefore the responsibility of - * the caller of this method to determine how to parse the returned string. - * - * @return the parameter types supplied to this {@code MethodSelector} via - * a constructor or deduced from a {@code Method} supplied via a constructor; - * never {@code null} - */ - public String getMethodParameterTypes() { - return this.methodParameterTypes; - } - - /** - * Get the {@link Class} in which the selected {@linkplain #getJavaMethod - * method} is declared, or a subclass thereof. - * - *

If the {@link Class} was not provided, but only the name, this method - * attempts to lazily load the {@code Class} based on its name and throws a - * {@link PreconditionViolationException} if the class cannot be loaded. - * - * @see #getJavaMethod() - */ - public Class getJavaClass() { - lazyLoadJavaClass(); - return this.javaClass; - } - - /** - * Get the selected {@link Method}. - * - *

If the {@link Method} was not provided, but only the name, this method - * attempts to lazily load the {@code Method} based on its name and throws a - * {@link PreconditionViolationException} if the method cannot be loaded. - * - * @see #getJavaClass() - */ - public Method getJavaMethod() { - lazyLoadJavaMethod(); - return this.javaMethod; - } - - private void lazyLoadJavaClass() { - if (this.javaClass == null) { - // @formatter:off - this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( - cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); - // @formatter:on - } - } - - private void lazyLoadJavaMethod() { - lazyLoadJavaClass(); - - if (this.javaMethod == null) { - if (StringUtils.isNotBlank(this.methodParameterTypes)) { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName, - this.methodParameterTypes).orElseThrow( - () -> new PreconditionViolationException(String.format( - "Could not find method with name [%s] and parameter types [%s] in class [%s].", - this.methodName, this.methodParameterTypes, this.javaClass.getName()))); - } - else { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName).orElseThrow( - () -> new PreconditionViolationException( - String.format("Could not find method with name [%s] in class [%s].", this.methodName, - this.javaClass.getName()))); - } - } - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MethodSelector that = (MethodSelector) o; - return Objects.equals(this.className, that.className)// - && Objects.equals(this.methodName, that.methodName)// - && Objects.equals(this.methodParameterTypes, that.methodParameterTypes); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return Objects.hash(this.className, this.methodName, this.methodParameterTypes); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("className", this.className) - .append("methodName", this.methodName) - .append("methodParameterTypes", this.methodParameterTypes) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java deleted file mode 100644 index 68825eb0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a module name so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on modules. - * - * @since 1.1 - * @see DiscoverySelectors#selectModule(String) - * @see DiscoverySelectors#selectModules(java.util.Set) - */ -@API(status = STABLE, since = "1.1") -public class ModuleSelector implements DiscoverySelector { - - private final String moduleName; - - ModuleSelector(String moduleName) { - this.moduleName = moduleName; - } - - /** - * Get the selected module name. - */ - public String getModuleName() { - return this.moduleName; - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ModuleSelector that = (ModuleSelector) o; - return Objects.equals(this.moduleName, that.moduleName); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return this.moduleName.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("moduleName", this.moduleName).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java deleted file mode 100644 index 5d463eab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a nested {@link Class} - * or class name enclosed in other classes so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on classes. - * - *

If Java {@link Class} references are provided for the nested class or - * the enclosing classes, the selector will return these {@code Class} and - * their class names accordingly. If class names are provided, the selector - * will only attempt to lazily load the {@link Class} if - * {@link #getEnclosingClasses()} or {@link #getNestedClass()} are invoked. - * - *

In this context, Java {@link Class} means anything that can be referenced - * as a {@link Class} on the JVM — for example, classes from other JVM - * languages such Groovy, Scala, etc. - * - * @since 1.6 - * @see DiscoverySelectors#selectNestedClass(List, Class) - * @see DiscoverySelectors#selectNestedClass(List, String) - * @see org.junit.platform.engine.support.descriptor.ClassSource - * @see ClassSelector - */ -@API(status = STABLE, since = "1.6") -public class NestedClassSelector implements DiscoverySelector { - - private List enclosingClassSelectors; - private ClassSelector nestedClassSelector; - - NestedClassSelector(List enclosingClassNames, String nestedClassName) { - this.enclosingClassSelectors = enclosingClassNames.stream().map(ClassSelector::new).collect(toList()); - this.nestedClassSelector = new ClassSelector(nestedClassName); - } - - NestedClassSelector(List> enclosingClasses, Class nestedClass) { - this.enclosingClassSelectors = enclosingClasses.stream().map(ClassSelector::new).collect(toList()); - this.nestedClassSelector = new ClassSelector(nestedClass); - } - - /** - * Get the names of the classes enclosing the selected nested class. - */ - public List getEnclosingClassNames() { - return enclosingClassSelectors.stream().map(ClassSelector::getClassName).collect(toList()); - } - - /** - * Get the list of {@link Class} enclosing the selected nested - * {@link Class}. - * - *

If the {@link Class} were not provided, but only the name of the - * nested class and its enclosing classes, this method attempts to lazily - * load the list of enclosing {@link Class} and throws a - * {@link PreconditionViolationException} if the classes cannot be loaded. - */ - public List> getEnclosingClasses() { - return enclosingClassSelectors.stream().map(ClassSelector::getJavaClass).collect(toList()); - } - - /** - * Get the name of the selected nested class. - */ - public String getNestedClassName() { - return nestedClassSelector.getClassName(); - } - - /** - * Get the selected nested {@link Class}. - * - *

If the {@link Class} were not provided, but only the name of the - * nested class and its enclosing classes, this method attempts to lazily - * load the nested {@link Class} and throws a - * {@link PreconditionViolationException} if the class cannot be loaded. - */ - public Class getNestedClass() { - return nestedClassSelector.getJavaClass(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - NestedClassSelector that = (NestedClassSelector) o; - return enclosingClassSelectors.equals(that.enclosingClassSelectors) - && nestedClassSelector.equals(that.nestedClassSelector); - } - - @Override - public int hashCode() { - return Objects.hash(enclosingClassSelectors, nestedClassSelector); - } - - @Override - public String toString() { - return new ToStringBuilder(this) // - .append("enclosingClassNames", getEnclosingClassNames()) // - .append("nestedClassName", getNestedClassName()) // - .toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java deleted file mode 100644 index 28f00afa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a nested {@link Method} - * or a combination of enclosing classes names, class name, method - * name, and parameter types so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on methods. - * - *

If a Java {@link Method} is provided, the selector will return that - * {@linkplain #getMethod() method} and its method name, class name, enclosing - * classes names and parameter types accordingly. If class or methods names are - * provided, this selector will only attempt to lazily load the {@link Class} - * and {@link Method} if {@link #getEnclosingClasses()}, - * {@link #getNestedClass()} or {@link #getMethod()} is invoked. - * - *

In this context, a Java {@code Method} means anything that can be referenced - * as a {@link Method} on the JVM — for example, methods from Java classes - * or methods from other JVM languages such Groovy, Scala, etc. - * - * @since 1.6 - * @see DiscoverySelectors#selectNestedMethod(List, String, String) - * @see DiscoverySelectors#selectNestedMethod(List, String, String, String) - * @see DiscoverySelectors#selectNestedMethod(List, Class, String) - * @see DiscoverySelectors#selectNestedMethod(List, Class, String, String) - * @see DiscoverySelectors#selectNestedMethod(List, Class, Method) - * @see org.junit.platform.engine.support.descriptor.MethodSource - * @see NestedClassSelector - * @see MethodSelector - */ -@API(status = STABLE, since = "1.6") -public class NestedMethodSelector implements DiscoverySelector { - - private final NestedClassSelector nestedClassSelector; - private final MethodSelector methodSelector; - - NestedMethodSelector(List enclosingClassNames, String nestedClassName, String methodName) { - this.nestedClassSelector = new NestedClassSelector(enclosingClassNames, nestedClassName); - this.methodSelector = new MethodSelector(nestedClassName, methodName); - } - - NestedMethodSelector(List enclosingClassNames, String nestedClassName, String methodName, - String methodParameterTypes) { - this.nestedClassSelector = new NestedClassSelector(enclosingClassNames, nestedClassName); - this.methodSelector = new MethodSelector(nestedClassName, methodName, methodParameterTypes); - } - - NestedMethodSelector(List> enclosingClasses, Class nestedClass, String methodName) { - this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); - this.methodSelector = new MethodSelector(nestedClass, methodName); - } - - NestedMethodSelector(List> enclosingClasses, Class nestedClass, String methodName, - String methodParameterTypes) { - this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); - this.methodSelector = new MethodSelector(nestedClass, methodName, methodParameterTypes); - } - - NestedMethodSelector(List> enclosingClasses, Class nestedClass, Method method) { - this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); - this.methodSelector = new MethodSelector(nestedClass, method); - } - - /** - * Get the names of the classes enclosing the nested class - * containing the selected method. - */ - public List getEnclosingClassNames() { - return nestedClassSelector.getEnclosingClassNames(); - } - - /** - * Get the list of {@link Class} enclosing the nested {@link Class} - * containing the selected {@link Method}. - * - *

If the {@link Class} were not provided, but only the name of the - * nested class and its enclosing classes, this method attempts to lazily - * load the list of enclosing {@link Class} and throws a - * {@link PreconditionViolationException} if the classes cannot be loaded. - */ - public List> getEnclosingClasses() { - return nestedClassSelector.getEnclosingClasses(); - } - - /** - * Get the name of the nested class containing the selected method. - */ - public String getNestedClassName() { - return nestedClassSelector.getNestedClassName(); - } - - /** - * Get the nested {@link Class} containing the selected {@link Method}. - * - *

If the {@link Class} were not provided, but only the name of the - * nested class and its enclosing classes, this method attempts to lazily - * load the nested {@link Class} and throws a - * {@link PreconditionViolationException} if the class cannot be loaded. - */ - public Class getNestedClass() { - return nestedClassSelector.getNestedClass(); - } - - /** - * Get the name of the selected method. - */ - public String getMethodName() { - return methodSelector.getMethodName(); - } - - /** - * Get the selected {@link Method}. - * - *

If the {@link Method} was not provided, but only the name, this method - * attempts to lazily load the {@code Method} based on its name and throws a - * {@link PreconditionViolationException} if the method cannot be loaded. - */ - public Method getMethod() { - return methodSelector.getJavaMethod(); - } - - /** - * Get the parameter types for the selected method as a {@link String}, - * typically a comma-separated list of primitive types, fully qualified - * class names, or array types. - * - *

Note: the parameter types are provided as a single string instead of - * a collection in order to allow this selector to be used in a generic - * fashion by various test engines. It is therefore the responsibility of - * the caller of this method to determine how to parse the returned string. - * - * @return the parameter types supplied to this {@code NestedMethodSelector} - * via a constructor or deduced from a {@code Method} supplied via a - * constructor; never {@code null} - */ - public String getMethodParameterTypes() { - return methodSelector.getMethodParameterTypes(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - NestedMethodSelector that = (NestedMethodSelector) o; - return nestedClassSelector.equals(that.nestedClassSelector) && methodSelector.equals(that.methodSelector); - } - - @Override - public int hashCode() { - return Objects.hash(nestedClassSelector, methodSelector); - } - - @Override - public String toString() { - return new ToStringBuilder(this) // - .append("enclosingClassNames", getEnclosingClassNames()) // - .append("nestedClassName", getNestedClassName()) // - .append("methodName", getMethodName()) // - .append("methodParameterTypes", getMethodParameterTypes()) // - .toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java deleted file mode 100644 index 55a808ef..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.engine.DiscoveryFilter; - -/** - * {@link DiscoveryFilter} that is applied to the name of a {@link Package}. - * - * @since 1.0 - * @see #includePackageNames(String...) - * @see #excludePackageNames(String...) - * @see ClassNameFilter - */ -@API(status = STABLE, since = "1.0") -public interface PackageNameFilter extends DiscoveryFilter { - - /** - * Create a new include {@link PackageNameFilter} based on the - * supplied package names. - * - *

The names are combined using OR semantics, i.e. if the fully - * qualified name of a package starts with at least one of the names, - * the package will be included in the result set. - * - * @param names package names that we be compared against fully qualified - * package names; never {@code null}, empty, or containing {@code null} - * @see Package#getName() - * @see #includePackageNames(List) - * @see #excludePackageNames(String...) - */ - static PackageNameFilter includePackageNames(String... names) { - return new IncludePackageNameFilter(names); - } - - /** - * Create a new include {@link PackageNameFilter} based on the - * supplied package names. - * - *

The names are combined using OR semantics, i.e. if the fully - * qualified name of a package starts with at least one of the names, - * the package will be included in the result set. - * - * @param names package names that we be compared against fully qualified - * package names; never {@code null}, empty, or containing {@code null} - * @see Package#getName() - * @see #includePackageNames(String...) - * @see #excludePackageNames(String...) - */ - static PackageNameFilter includePackageNames(List names) { - return includePackageNames(names.toArray(new String[0])); - } - - /** - * Create a new exclude {@link PackageNameFilter} based on the - * supplied package names. - * - *

The names are combined using OR semantics, i.e. if the fully - * qualified name of a package starts with at least one of the names, - * the package will be excluded in the result set. - * - * @param names package names that we be compared against fully qualified - * package names; never {@code null}, empty, or containing {@code null} - * @see Package#getName() - * @see #excludePackageNames(List) - * @see #includePackageNames(String...) - */ - static PackageNameFilter excludePackageNames(String... names) { - return new ExcludePackageNameFilter(names); - } - - /** - * Create a new exclude {@link PackageNameFilter} based on the - * supplied package names. - * - *

The names are combined using OR semantics, i.e. if the fully - * qualified name of a package starts with at least one of the names, - * the package will be excluded in the result set. - * - * @param names package names that we be compared against fully qualified - * package names; never {@code null}, empty, or containing {@code null} - * @see Package#getName() - * @see #excludePackageNames(String...) - * @see #includePackageNames(String...) - */ - static PackageNameFilter excludePackageNames(List names) { - return excludePackageNames(names.toArray(new String[0])); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java deleted file mode 100644 index 781fc2af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a package name so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on packages. - * - * @since 1.0 - * @see DiscoverySelectors#selectPackage(String) - * @see org.junit.platform.engine.support.descriptor.PackageSource - */ -@API(status = STABLE, since = "1.0") -public class PackageSelector implements DiscoverySelector { - - private final String packageName; - - PackageSelector(String packageName) { - this.packageName = packageName; - } - - /** - * Get the selected package name. - */ - public String getPackageName() { - return this.packageName; - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PackageSelector that = (PackageSelector) o; - return Objects.equals(this.packageName, that.packageName); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return this.packageName.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("packageName", this.packageName).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java deleted file mode 100644 index 4fee909e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.UniqueId; - -/** - * A {@link DiscoverySelector} that selects a {@link UniqueId} so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on unique IDs. - * - * @since 1.0 - * @see DiscoverySelectors#selectUniqueId(String) - * @see DiscoverySelectors#selectUniqueId(UniqueId) - */ -@API(status = STABLE, since = "1.0") -public class UniqueIdSelector implements DiscoverySelector { - - private final UniqueId uniqueId; - - UniqueIdSelector(UniqueId uniqueId) { - this.uniqueId = uniqueId; - } - - /** - * Get the selected {@link UniqueId}. - */ - public UniqueId getUniqueId() { - return this.uniqueId; - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - UniqueIdSelector that = (UniqueIdSelector) o; - return Objects.equals(this.uniqueId, that.uniqueId); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return this.uniqueId.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("uniqueId", this.uniqueId).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java deleted file mode 100644 index c673851d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.net.URI; -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.DiscoverySelector; - -/** - * A {@link DiscoverySelector} that selects a {@link URI} so that - * {@link org.junit.platform.engine.TestEngine TestEngines} - * can discover tests or containers based on URIs. - * - * @since 1.0 - * @see DiscoverySelectors#selectUri(String) - * @see DiscoverySelectors#selectUri(URI) - * @see FileSelector - * @see DirectorySelector - * @see org.junit.platform.engine.support.descriptor.UriSource - */ -@API(status = STABLE, since = "1.0") -public class UriSelector implements DiscoverySelector { - - private final URI uri; - - UriSelector(URI uri) { - this.uri = uri; - } - - /** - * Get the selected {@link URI}. - */ - public URI getUri() { - return this.uri; - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - UriSelector that = (UriSelector) o; - return Objects.equals(this.uri, that.uri); - } - - /** - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - @Override - public int hashCode() { - return this.uri.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("uri", this.uri).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java deleted file mode 100644 index 55321771..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Concrete {@linkplain org.junit.platform.engine.DiscoverySelector selectors} and - * {@linkplain org.junit.platform.engine.DiscoveryFilter filters} to be used in - * {@linkplain org.junit.platform.engine.EngineDiscoveryRequest discovery requests}. - */ - -package org.junit.platform.engine.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java deleted file mode 100644 index 63bb4172..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Public API for test engines. - */ - -package org.junit.platform.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java deleted file mode 100644 index 6e114191..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.reporting; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.STABLE; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@code ReportEntry} encapsulates a time-stamped map of {@code String}-based - * key-value pairs to be published to the reporting infrastructure. - * - * @since 1.0 - * @see #from(Map) - * @see #from(String, String) - */ -@API(status = STABLE, since = "1.0") -public final class ReportEntry { - - private final LocalDateTime timestamp = LocalDateTime.now(); - private final Map keyValuePairs = new LinkedHashMap<>(); - - /** - * @deprecated Use {@link #from(String, String)} or {@link #from(Map)} - */ - @API(status = DEPRECATED, since = "5.8") - @Deprecated - public ReportEntry() { - } - - /** - * Factory for creating a new {@code ReportEntry} from a map of key-value pairs. - * - * @param keyValuePairs the map of key-value pairs to be published; never - * {@code null}; keys and values within entries in the map also must not be - * {@code null} or blank - */ - public static ReportEntry from(Map keyValuePairs) { - Preconditions.notNull(keyValuePairs, "keyValuePairs must not be null"); - - ReportEntry reportEntry = new ReportEntry(); - keyValuePairs.forEach(reportEntry::add); - return reportEntry; - } - - /** - * Factory for creating a new {@code ReportEntry} from a key-value pair. - * - * @param key the key under which the value should published; never - * {@code null} or blank - * @param value the value to publish; never {@code null} or blank - */ - public static ReportEntry from(String key, String value) { - ReportEntry reportEntry = new ReportEntry(); - reportEntry.add(key, value); - return reportEntry; - } - - private void add(String key, String value) { - Preconditions.notBlank(key, "key must not be null or blank"); - Preconditions.notBlank(value, "value must not be null or blank"); - this.keyValuePairs.put(key, value); - } - - /** - * Get an unmodifiable copy of the map of key-value pairs to be published. - * - * @return a copy of the map of key-value pairs; never {@code null} - */ - public final Map getKeyValuePairs() { - return Collections.unmodifiableMap(this.keyValuePairs); - } - - /** - * Get the timestamp for when this {@code ReportEntry} was created. - * - *

Can be used, for example, to order entries. - * - * @return when this entry was created; never {@code null} - */ - public final LocalDateTime getTimestamp() { - return this.timestamp; - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder(this); - builder.append("timestamp", this.timestamp); - this.keyValuePairs.forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java deleted file mode 100644 index 11965058..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Classes used by test engines to report additional data to execution - * listeners. - */ - -package org.junit.platform.engine.reporting; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java deleted file mode 100644 index f50e9a32..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.config; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * View of {@link ConfigurationParameters} that applies a supplied prefix to all - * queries. - * - * @since 1.3 - */ -@API(status = EXPERIMENTAL, since = "1.3") -public class PrefixedConfigurationParameters implements ConfigurationParameters { - - private final ConfigurationParameters delegate; - private final String prefix; - - /** - * Create a new view of the supplied {@link ConfigurationParameters} that - * applies the supplied prefix to all queries. - * - * @param delegate the {@link ConfigurationParameters} to delegate to; never - * {@code null} - * @param prefix the prefix to apply to all queries; never {@code null} or - * blank - */ - public PrefixedConfigurationParameters(ConfigurationParameters delegate, String prefix) { - this.delegate = Preconditions.notNull(delegate, "delegate must not be null"); - this.prefix = Preconditions.notBlank(prefix, "prefix must not be null or blank"); - } - - @Override - public Optional get(String key) { - return delegate.get(prefixed(key)); - } - - @Override - public Optional getBoolean(String key) { - return delegate.getBoolean(prefixed(key)); - } - - @Override - public Optional get(String key, Function transformer) { - return delegate.get(prefixed(key), transformer); - } - - private String prefixed(String key) { - return prefix + key; - } - - @Override - @SuppressWarnings("deprecation") - public int size() { - return delegate.size(); - } - - @Override - public Set keySet() { - return delegate.keySet(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java deleted file mode 100644 index 05f555a2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * {@link org.junit.platform.engine.ConfigurationParameters}-related support - * classes intended to be used by test engine implementations. - */ - -package org.junit.platform.engine.support.config; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java deleted file mode 100644 index dd0a53e8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static java.util.Collections.emptySet; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; - -/** - * Abstract base implementation of {@link TestDescriptor} that may be used by - * custom {@link org.junit.platform.engine.TestEngine TestEngines}. - * - *

Subclasses should provide a {@link TestSource} in their constructor, if - * possible, and override {@link #getTags()}, if appropriate. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public abstract class AbstractTestDescriptor implements TestDescriptor { - - private final UniqueId uniqueId; - - private final String displayName; - - private final TestSource source; - - private TestDescriptor parent; - - /** - * The synchronized set of children associated with this {@code TestDescriptor}. - * - *

This set is used in methods such as {@link #addChild(TestDescriptor)}, - * {@link #removeChild(TestDescriptor)}, {@link #removeFromHierarchy()}, and - * {@link #findByUniqueId(UniqueId)}, and an immutable copy of this set is - * returned by {@link #getChildren()}. - * - *

If a subclass overrides any of the methods related to children, this - * set should be used instead of a set local to the subclass. - */ - protected final Set children = Collections.synchronizedSet(new LinkedHashSet<>(16)); - - /** - * Create a new {@code AbstractTestDescriptor} with the supplied - * {@link UniqueId} and display name. - * - * @param uniqueId the unique ID of this {@code TestDescriptor}; never - * {@code null} - * @param displayName the display name for this {@code TestDescriptor}; - * never {@code null} or blank - * @see #AbstractTestDescriptor(UniqueId, String, TestSource) - */ - protected AbstractTestDescriptor(UniqueId uniqueId, String displayName) { - this(uniqueId, displayName, null); - } - - /** - * Create a new {@code AbstractTestDescriptor} with the supplied - * {@link UniqueId}, display name, and source. - * - * @param uniqueId the unique ID of this {@code TestDescriptor}; never - * {@code null} - * @param displayName the display name for this {@code TestDescriptor}; - * never {@code null} or blank - * @param source the source of the test or container described by this - * {@code TestDescriptor}; can be {@code null} - * @see #AbstractTestDescriptor(UniqueId, String) - */ - protected AbstractTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) { - this.uniqueId = Preconditions.notNull(uniqueId, "UniqueId must not be null"); - this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank"); - this.source = source; - } - - @Override - public final UniqueId getUniqueId() { - return this.uniqueId; - } - - @Override - public final String getDisplayName() { - return this.displayName; - } - - @Override - public Set getTags() { - return emptySet(); - } - - @Override - public Optional getSource() { - return Optional.ofNullable(this.source); - } - - @Override - public final Optional getParent() { - return Optional.ofNullable(this.parent); - } - - @Override - public final void setParent(TestDescriptor parent) { - this.parent = parent; - } - - @Override - public final Set getChildren() { - return Collections.unmodifiableSet(this.children); - } - - @Override - public void addChild(TestDescriptor child) { - Preconditions.notNull(child, "child must not be null"); - child.setParent(this); - this.children.add(child); - } - - @Override - public void removeChild(TestDescriptor child) { - Preconditions.notNull(child, "child must not be null"); - this.children.remove(child); - child.setParent(null); - } - - @Override - public void removeFromHierarchy() { - Preconditions.condition(!isRoot(), "cannot remove the root of a hierarchy"); - this.parent.removeChild(this); - this.children.forEach(child -> child.setParent(null)); - this.children.clear(); - } - - @Override - public Optional findByUniqueId(UniqueId uniqueId) { - Preconditions.notNull(uniqueId, "UniqueId must not be null"); - if (getUniqueId().equals(uniqueId)) { - return Optional.of(this); - } - // @formatter:off - return this.children.stream() - .map(child -> child.findByUniqueId(uniqueId)) - .filter(Optional::isPresent) - .findAny() - .orElse(Optional.empty()); - // @formatter:on - } - - @Override - public final int hashCode() { - return this.uniqueId.hashCode(); - } - - @Override - public final boolean equals(Object other) { - if (other == null) { - return false; - } - if (this.getClass() != other.getClass()) { - return false; - } - TestDescriptor that = (TestDescriptor) other; - return this.getUniqueId().equals(that.getUniqueId()); - } - - @Override - public String toString() { - return getClass().getSimpleName() + ": " + getUniqueId(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java deleted file mode 100644 index 89f33add..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.net.URI; -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestSource; - -/** - * Class based {@link org.junit.platform.engine.TestSource TestSource} with - * an optional {@linkplain FilePosition file position}. - * - *

If a Java {@link Class} reference is provided, the {@code ClassSource} - * will contain that {@code Class} and its class name accordingly. If a class - * name is provided, the {@code ClassSource} will contain the class name and - * will only attempt to lazily load the {@link Class} if {@link #getJavaClass()} - * is invoked. - * - *

In this context, Java {@link Class} means anything that can be referenced - * as a {@link Class} on the JVM — for example, classes from other JVM - * languages such Groovy, Scala, etc. - * - * @since 1.0 - * @see org.junit.platform.engine.discovery.ClassSelector - */ -@API(status = STABLE, since = "1.0") -public class ClassSource implements TestSource { - - private static final long serialVersionUID = 1L; - - /** - * {@link URI} {@linkplain URI#getScheme() scheme} for class sources: {@value} - * - * @since 1.8 - */ - @API(status = STABLE, since = "1.8") - public static final String CLASS_SCHEME = "class"; - - /** - * Create a new {@code ClassSource} using the supplied class name. - * - * @param className the class name; must not be {@code null} or blank - */ - public static ClassSource from(String className) { - return new ClassSource(className); - } - - /** - * Create a new {@code ClassSource} using the supplied class name and - * {@linkplain FilePosition file position}. - * - * @param className the class name; must not be {@code null} or blank - * @param filePosition the position in the source file; may be {@code null} - */ - public static ClassSource from(String className, FilePosition filePosition) { - return new ClassSource(className, filePosition); - } - - /** - * Create a new {@code ClassSource} using the supplied {@linkplain Class class}. - * - * @param javaClass the Java class; must not be {@code null} - */ - public static ClassSource from(Class javaClass) { - return new ClassSource(javaClass); - } - - /** - * Create a new {@code ClassSource} using the supplied {@linkplain Class class} - * and {@linkplain FilePosition file position}. - * - * @param javaClass the Java class; must not be {@code null} - * @param filePosition the position in the Java source file; may be {@code null} - */ - public static ClassSource from(Class javaClass, FilePosition filePosition) { - return new ClassSource(javaClass, filePosition); - } - - /** - * Create a new {@code ClassSource} from the supplied {@link URI}. - * - *

URIs should be formatted as {@code class:fully.qualified.class.Name}. - * The {@linkplain URI#getQuery() query} component of the {@code URI}, if - * present, will be used to retrieve the {@link FilePosition} via - * {@link FilePosition#fromQuery(String)}. For example, line 42 and column - * 13 can be referenced in class {@code org.example.MyType} via the following - * URI: {@code class:com.example.MyType?line=42&column=13}. The URI fragment, - * if present, will be ignored. - * - * @param uri the {@code URI} for the class source; never {@code null} - * @return a new {@code ClassSource}; never {@code null} - * @throws PreconditionViolationException if the supplied {@code URI} is - * {@code null}, if the scheme of the supplied {@code URI} is not equal - * to the {@link #CLASS_SCHEME}, or if the specified class name is empty - * @since 1.8 - * @see #CLASS_SCHEME - */ - @API(status = STABLE, since = "1.8") - public static ClassSource from(URI uri) { - Preconditions.notNull(uri, "URI must not be null"); - Preconditions.condition(CLASS_SCHEME.equals(uri.getScheme()), - () -> "URI [" + uri + "] must have [" + CLASS_SCHEME + "] scheme"); - - String className = uri.getSchemeSpecificPart(); - FilePosition filePosition = null; - int indexOfQuery = className.indexOf('?'); - if (indexOfQuery >= 0) { - filePosition = FilePosition.fromQuery(className.substring(indexOfQuery + 1)).orElse(null); - className = className.substring(0, indexOfQuery); - } - - return ClassSource.from(className, filePosition); - } - - private final String className; - private final FilePosition filePosition; - private Class javaClass; - - private ClassSource(String className) { - this(className, null); - } - - private ClassSource(String className, FilePosition filePosition) { - this.className = Preconditions.notBlank(className, "Class name must not be null or blank"); - this.filePosition = filePosition; - } - - private ClassSource(Class javaClass) { - this(javaClass, null); - } - - private ClassSource(Class javaClass, FilePosition filePosition) { - this.javaClass = Preconditions.notNull(javaClass, "Class must not be null"); - this.className = this.javaClass.getName(); - this.filePosition = filePosition; - } - - /** - * Get the class name of this source. - * - * @see #getJavaClass() - * @see #getPosition() - */ - public final String getClassName() { - return this.className; - } - - /** - * Get the {@linkplain Class Java class} of this source. - * - *

If the {@link Class} was not provided, but only the name, this method - * attempts to lazily load the {@link Class} based on its name and throws a - * {@link PreconditionViolationException} if the class cannot be loaded. - * - * @see #getClassName() - * @see #getPosition() - */ - public final Class getJavaClass() { - if (this.javaClass == null) { - // @formatter:off - this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( - cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); - // @formatter:on - } - return this.javaClass; - } - - /** - * Get the {@linkplain FilePosition position} in the source file for - * the associated {@linkplain #getClassName class}, if available. - * - * @see #getClassName() - * @see #getJavaClass() - */ - public final Optional getPosition() { - return Optional.ofNullable(this.filePosition); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ClassSource that = (ClassSource) o; - return Objects.equals(this.className, that.className) && Objects.equals(this.filePosition, that.filePosition); - } - - @Override - public int hashCode() { - return Objects.hash(this.className, this.filePosition); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("className", this.className) - .append("filePosition", this.filePosition) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java deleted file mode 100644 index 0a288d32..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.net.URI; -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestSource; - -/** - * Classpath resource based {@link org.junit.platform.engine.TestSource} - * with an optional {@linkplain FilePosition position}. - * - * @since 1.0 - * @see org.junit.platform.engine.discovery.ClasspathResourceSelector - */ -@API(status = STABLE, since = "1.0") -public class ClasspathResourceSource implements TestSource { - - private static final long serialVersionUID = 1L; - - /** - * {@link URI} {@linkplain URI#getScheme() scheme} for classpath - * resources: {@value} - * - * @since 1.3 - */ - public static final String CLASSPATH_SCHEME = "classpath"; - - /** - * Create a new {@code ClasspathResourceSource} using the supplied classpath - * resource name. - * - *

The name of a classpath resource must follow the semantics - * for resource paths as defined in {@link ClassLoader#getResource(String)}. - * - *

If the supplied classpath resource name is prefixed with a slash - * ({@code /}), the slash will be removed. - * - * @param classpathResourceName the name of the classpath resource; never - * {@code null} or blank - * @see ClassLoader#getResource(String) - * @see ClassLoader#getResourceAsStream(String) - * @see ClassLoader#getResources(String) - */ - public static ClasspathResourceSource from(String classpathResourceName) { - return new ClasspathResourceSource(classpathResourceName); - } - - /** - * Create a new {@code ClasspathResourceSource} using the supplied classpath - * resource name and {@link FilePosition}. - * - *

The name of a classpath resource must follow the semantics - * for resource paths as defined in {@link ClassLoader#getResource(String)}. - * - *

If the supplied classpath resource name is prefixed with a slash - * ({@code /}), the slash will be removed. - * - * @param classpathResourceName the name of the classpath resource; never - * {@code null} or blank - * @param filePosition the position in the classpath resource; may be {@code null} - */ - public static ClasspathResourceSource from(String classpathResourceName, FilePosition filePosition) { - return new ClasspathResourceSource(classpathResourceName, filePosition); - } - - /** - * Create a new {@code ClasspathResourceSource} from the supplied {@link URI}. - * - *

The {@link URI#getPath() path} component of the {@code URI} (excluding - * the query) will be used as the classpath resource name. The - * {@linkplain URI#getQuery() query} component of the {@code URI}, if present, - * will be used to retrieve the {@link FilePosition} via - * {@link FilePosition#fromQuery(String)}. - * - * @param uri the {@code URI} for the classpath resource; never {@code null} - * @return a new {@code ClasspathResourceSource}; never {@code null} - * @throws PreconditionViolationException if the supplied {@code URI} is - * {@code null} or if the scheme of the supplied {@code URI} is not equal - * to the {@link #CLASSPATH_SCHEME} - * @since 1.3 - * @see #CLASSPATH_SCHEME - */ - @API(status = STABLE, since = "1.3") - public static ClasspathResourceSource from(URI uri) { - Preconditions.notNull(uri, "URI must not be null"); - Preconditions.condition(CLASSPATH_SCHEME.equals(uri.getScheme()), - () -> "URI [" + uri + "] must have [" + CLASSPATH_SCHEME + "] scheme"); - - String classpathResource = ResourceUtils.stripQueryComponent(uri).getPath(); - FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null); - return ClasspathResourceSource.from(classpathResource, filePosition); - } - - private final String classpathResourceName; - private final FilePosition filePosition; - - private ClasspathResourceSource(String classpathResourceName) { - this(classpathResourceName, null); - } - - private ClasspathResourceSource(String classpathResourceName, FilePosition filePosition) { - Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); - boolean startsWithSlash = classpathResourceName.startsWith("/"); - this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); - this.filePosition = filePosition; - } - - /** - * Get the name of the source classpath resource. - * - *

The name of a classpath resource follows the semantics for - * resource paths as defined in {@link ClassLoader#getResource(String)}. - * - * @see ClassLoader#getResource(String) - * @see ClassLoader#getResourceAsStream(String) - * @see ClassLoader#getResources(String) - */ - public String getClasspathResourceName() { - return this.classpathResourceName; - } - - /** - * Get the {@link FilePosition}, if available. - */ - public final Optional getPosition() { - return Optional.ofNullable(this.filePosition); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ClasspathResourceSource that = (ClasspathResourceSource) o; - return Objects.equals(this.classpathResourceName, that.classpathResourceName) - && Objects.equals(this.filePosition, that.filePosition); - } - - @Override - public int hashCode() { - return Objects.hash(this.classpathResourceName, this.filePosition); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("classpathResourceName", this.classpathResourceName) - .append("filePosition", this.filePosition) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java deleted file mode 100644 index 3fa3ccc9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static java.util.Collections.unmodifiableList; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestSource; - -/** - * A {@code CompositeTestSource} contains one or more {@link TestSource TestSources}. - * - *

{@code CompositeTestSource} and its {@link #getSources sources} are immutable. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public class CompositeTestSource implements TestSource { - - private static final long serialVersionUID = 1L; - - /** - * Create a new {@code CompositeTestSource} based on the supplied - * collection of {@link TestSource sources}. - * - *

This constructor makes a defensive copy of the supplied collection - * and stores the sources as a list in the order in which they are - * returned by the collection's iterator. - * - * @param sources the collection of sources to store in this - * {@code CompositeTestSource}; never {@code null} or empty - */ - public static CompositeTestSource from(Collection sources) { - return new CompositeTestSource(sources); - } - - @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (unmodifiableList()) - private final List sources; - - private CompositeTestSource(Collection sources) { - Preconditions.notEmpty(sources, "TestSource collection must not be null or empty"); - Preconditions.containsNoNullElements(sources, "individual TestSources must not be null"); - this.sources = unmodifiableList(new ArrayList<>(sources)); - } - - /** - * Get an immutable list of the {@linkplain TestSource sources} stored in this - * {@code CompositeTestSource}. - * - * @return the sources stored in this {@code CompositeTestSource}; never {@code null} - */ - public final List getSources() { - return this.sources; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - CompositeTestSource that = (CompositeTestSource) obj; - return this.sources.equals(that.sources); - } - - @Override - public int hashCode() { - return this.sources.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("sources", this.sources).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java deleted file mode 100644 index dd7a602a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import java.net.URI; -import java.util.Objects; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Default implementation of {@link UriSource}. - * - * @since 1.3 - */ -class DefaultUriSource implements UriSource { - - private static final long serialVersionUID = 1L; - - private final URI uri; - - DefaultUriSource(URI uri) { - this.uri = Preconditions.notNull(uri, "URI must not be null"); - } - - @Override - public URI getUri() { - return uri; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DefaultUriSource that = (DefaultUriSource) o; - return Objects.equals(this.uri, that.uri); - } - - @Override - public int hashCode() { - return this.uri.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("uri", this.uri).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java deleted file mode 100644 index c64c2dba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.File; -import java.io.IOException; -import java.net.URI; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Directory based {@link org.junit.platform.engine.TestSource}. - * - * @since 1.0 - * @see org.junit.platform.engine.discovery.DirectorySelector - */ -@API(status = STABLE, since = "1.0") -public class DirectorySource implements FileSystemSource { - - private static final long serialVersionUID = 1L; - - /** - * Create a new {@code DirectorySource} using the supplied - * {@linkplain File directory}. - * - * @param directory the source directory; must not be {@code null} - */ - public static DirectorySource from(File directory) { - return new DirectorySource(directory); - } - - private final File directory; - - private DirectorySource(File directory) { - Preconditions.notNull(directory, "directory must not be null"); - try { - this.directory = directory.getCanonicalFile(); - } - catch (IOException ex) { - throw new JUnitException("Failed to retrieve canonical path for directory: " + directory, ex); - } - } - - /** - * Get the {@link URI} for the source {@linkplain #getFile directory}. - * - * @return the source {@code URI}; never {@code null} - */ - @Override - public final URI getUri() { - return getFile().toURI(); - } - - /** - * Get the source {@linkplain File directory}. - * - * @return the source directory; never {@code null} - */ - @Override - public final File getFile() { - return this.directory; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DirectorySource that = (DirectorySource) o; - return this.directory.equals(that.directory); - } - - @Override - public int hashCode() { - return this.directory.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("directory", this.directory).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java deleted file mode 100644 index fbf57519..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.engine.UniqueId; - -/** - * An {@code EngineDescriptor} is a {@link org.junit.platform.engine.TestDescriptor - * TestDescriptor} for a specific {@link org.junit.platform.engine.TestEngine TestEngine}. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public class EngineDescriptor extends AbstractTestDescriptor { - - /** - * Create a new {@code EngineDescriptor} with the supplied {@link UniqueId} - * and display name. - * - * @param uniqueId the {@code UniqueId} for the described {@code TestEngine}; - * never {@code null} - * @param displayName the display name for the described {@code TestEngine}; - * never {@code null} or blank - * @see org.junit.platform.engine.TestEngine#getId() - * @see org.junit.platform.engine.TestDescriptor#getDisplayName() - */ - public EngineDescriptor(UniqueId uniqueId, String displayName) { - super(uniqueId, displayName); - } - - /** - * Returns {@link org.junit.platform.engine.TestDescriptor.Type#CONTAINER}. - * - * @see org.junit.platform.engine.TestDescriptor#isContainer() - * @see org.junit.platform.engine.TestDescriptor#isTest() - */ - @Override - public Type getType() { - return Type.CONTAINER; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java deleted file mode 100644 index a0fb2c94..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.Serializable; -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * Position inside a file represented by {@linkplain #getLine line} and - * {@linkplain #getColumn column} numbers. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public class FilePosition implements Serializable { - - private static final long serialVersionUID = 1L; - - private static final Logger logger = LoggerFactory.getLogger(FilePosition.class); - - /** - * Create a new {@code FilePosition} using the supplied {@code line} number - * and an undefined column number. - * - * @param line the line number; must be greater than zero - * @return a {@link FilePosition} with the given line number - */ - public static FilePosition from(int line) { - return new FilePosition(line); - } - - /** - * Create a new {@code FilePosition} using the supplied {@code line} and - * {@code column} numbers. - * - * @param line the line number; must be greater than zero - * @param column the column number; must be greater than zero - * @return a {@link FilePosition} with the given line and column numbers - */ - public static FilePosition from(int line, int column) { - return new FilePosition(line, column); - } - - /** - * Create an optional {@code FilePosition} by parsing the supplied - * {@code query} string. - * - *

Examples of valid {@code query} strings: - *

    - *
  • {@code "line=23"}
  • - *
  • {@code "line=23&column=42"}
  • - *
- * - * @param query the query string; may be {@code null} - * @return an {@link Optional} containing a {@link FilePosition} with - * the parsed line and column numbers; never {@code null} but potentially - * empty - * @since 1.3 - * @see #from(int) - * @see #from(int, int) - */ - public static Optional fromQuery(String query) { - FilePosition result = null; - Integer line = null; - Integer column = null; - if (StringUtils.isNotBlank(query)) { - try { - for (String pair : query.split("&")) { - String[] data = pair.split("="); - if (data.length == 2) { - String key = data[0]; - if (line == null && "line".equals(key)) { - line = Integer.valueOf(data[1]); - } - else if (column == null && "column".equals(key)) { - column = Integer.valueOf(data[1]); - } - } - - // Already found what we're looking for? - if (line != null && column != null) { - break; - } - } - } - catch (IllegalArgumentException ex) { - logger.debug(ex, () -> "Failed to parse 'line' and/or 'column' from query string: " + query); - // fall-through and continue - } - - if (line != null) { - result = column == null ? new FilePosition(line) : new FilePosition(line, column); - } - } - return Optional.ofNullable(result); - } - - private final int line; - private final Integer column; - - private FilePosition(int line) { - Preconditions.condition(line > 0, "line number must be greater than zero"); - this.line = line; - this.column = null; - } - - private FilePosition(int line, int column) { - Preconditions.condition(line > 0, "line number must be greater than zero"); - Preconditions.condition(column > 0, "column number must be greater than zero"); - this.line = line; - this.column = column; - } - - /** - * Get the line number of this {@code FilePosition}. - * - * @return the line number - */ - public int getLine() { - return this.line; - } - - /** - * Get the column number of this {@code FilePosition}, if available. - * - * @return an {@code Optional} containing the column number; never - * {@code null} but potentially empty - */ - public Optional getColumn() { - return Optional.ofNullable(this.column); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FilePosition that = (FilePosition) o; - return (this.line == that.line) && Objects.equals(this.column, that.column); - } - - @Override - public int hashCode() { - return Objects.hash(this.line, this.column); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("line", this.line) - .append("column", getColumn().orElse(-1)) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java deleted file mode 100644 index 7e4ab6c9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.util.Objects; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * File based {@link org.junit.platform.engine.TestSource} with an optional - * {@linkplain FilePosition position}. - * - * @since 1.0 - * @see org.junit.platform.engine.discovery.FileSelector - */ -@API(status = STABLE, since = "1.0") -public class FileSource implements FileSystemSource { - - private static final long serialVersionUID = 1L; - - /** - * Create a new {@code FileSource} using the supplied {@link File file}. - * - * @param file the source file; must not be {@code null} - */ - public static FileSource from(File file) { - return new FileSource(file); - } - - /** - * Create a new {@code FileSource} using the supplied {@link File file} and - * {@link FilePosition filePosition}. - * - * @param file the source file; must not be {@code null} - * @param filePosition the position in the source file; may be {@code null} - */ - public static FileSource from(File file, FilePosition filePosition) { - return new FileSource(file, filePosition); - } - - private final File file; - private final FilePosition filePosition; - - private FileSource(File file) { - this(file, null); - } - - private FileSource(File file, FilePosition filePosition) { - Preconditions.notNull(file, "file must not be null"); - try { - this.file = file.getCanonicalFile(); - } - catch (IOException ex) { - throw new JUnitException("Failed to retrieve canonical path for file: " + file, ex); - } - this.filePosition = filePosition; - } - - /** - * Get the {@link URI} for the source {@linkplain #getFile file}. - * - * @return the source {@code URI}; never {@code null} - */ - @Override - public final URI getUri() { - return getFile().toURI(); - } - - /** - * Get the source {@linkplain File file}. - * - * @return the source file; never {@code null} - */ - @Override - public final File getFile() { - return this.file; - } - - /** - * Get the {@link FilePosition}, if available. - */ - public final Optional getPosition() { - return Optional.ofNullable(this.filePosition); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FileSource that = (FileSource) o; - return Objects.equals(this.file, that.file) && Objects.equals(this.filePosition, that.filePosition); - } - - @Override - public int hashCode() { - return Objects.hash(this.file, this.filePosition); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("file", this.file) - .append("filePosition", this.filePosition) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java deleted file mode 100644 index 2a4c976d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.File; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestSource; - -/** - * File system based {@link TestSource}. - * - *

This interface uses {@link File} instead of {@link java.nio.file.Path} - * because the latter does not implement {@link java.io.Serializable}. - * - * @since 1.0 - */ -@API(status = STABLE, since = "1.0") -public interface FileSystemSource extends UriSource { - - /** - * Get the source file or directory. - * - * @return the source file or directory; never {@code null} - */ - File getFile(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java deleted file mode 100644 index 977bd4f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; - -import java.lang.reflect.Method; -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestSource; - -/** - * Method based {@link org.junit.platform.engine.TestSource TestSource}. - * - *

This class stores the method name along with the names of its parameter - * types because {@link Method} does not implement {@link java.io.Serializable}. - * - * @since 1.0 - * @see org.junit.platform.engine.discovery.MethodSelector - */ -@API(status = STABLE, since = "1.0") -public class MethodSource implements TestSource { - - private static final long serialVersionUID = 1L; - - /** - * Create a new {@code MethodSource} using the supplied class name and - * method name. - * - * @param className the class name; must not be {@code null} or blank - * @param methodName the method name; must not be {@code null} or blank - */ - public static MethodSource from(String className, String methodName) { - return new MethodSource(className, methodName); - } - - /** - * Create a new {@code MethodSource} using the supplied class name, method - * name, and method parameter types. - * - * @param className the class name; must not be {@code null} or blank - * @param methodName the method name; must not be {@code null} or blank - * @param methodParameterTypes a comma-separated list of fully qualified - * class names representing the method parameter types - */ - public static MethodSource from(String className, String methodName, String methodParameterTypes) { - return new MethodSource(className, methodName, methodParameterTypes); - } - - /** - * Create a new {@code MethodSource} using the supplied class name, method - * name, and method parameter types. - * - * @param className the class name; must not be {@code null} or blank - * @param methodName the method name; must not be {@code null} or blank - * @param methodParameterTypes a varargs array of classes representing the - * method parameter types - * @since 1.5 - */ - @API(status = STABLE, since = "1.5") - public static MethodSource from(String className, String methodName, Class... methodParameterTypes) { - return new MethodSource(className, methodName, nullSafeToString(methodParameterTypes)); - } - - /** - * Create a new {@code MethodSource} using the supplied {@link Method method}. - * - * @param testMethod the Java method; must not be {@code null} - * @see #from(Class, Method) - */ - public static MethodSource from(Method testMethod) { - return new MethodSource(testMethod); - } - - /** - * Create a new {@code MethodSource} using the supplied - * {@link Class class} and {@link Method method}. - * - *

This method should be used in favor of {@link #from(Method)} if the - * test method is inherited from a superclass or present as an interface - * {@code default} method. - * - * @param testClass the Java class; must not be {@code null} - * @param testMethod the Java method; must not be {@code null} - * @since 1.3 - */ - @API(status = STABLE, since = "1.3") - public static MethodSource from(Class testClass, Method testMethod) { - return new MethodSource(testClass, testMethod); - } - - private final String className; - private final String methodName; - private final String methodParameterTypes; - private Class javaClass; - private transient Method javaMethod; - - private MethodSource(String className, String methodName) { - this(className, methodName, null); - } - - private MethodSource(String className, String methodName, String methodParameterTypes) { - Preconditions.notBlank(className, "Class name must not be null or blank"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); - this.className = className; - this.methodName = methodName; - this.methodParameterTypes = methodParameterTypes; - } - - private MethodSource(Method testMethod) { - this(Preconditions.notNull(testMethod, "Method must not be null").getDeclaringClass(), testMethod); - } - - /** - * @since 1.3 - */ - private MethodSource(Class testClass, Method testMethod) { - Preconditions.notNull(testClass, "Class must not be null"); - Preconditions.notNull(testMethod, "Method must not be null"); - this.className = testClass.getName(); - this.methodName = testMethod.getName(); - this.methodParameterTypes = nullSafeToString(testMethod.getParameterTypes()); - this.javaClass = testClass; - this.javaMethod = testMethod; - } - - /** - * Get the class name of this source. - */ - public String getClassName() { - return this.className; - } - - /** - * Get the method name of this source. - */ - public final String getMethodName() { - return this.methodName; - } - - /** - * Get the method parameter types of this source. - */ - public final String getMethodParameterTypes() { - return this.methodParameterTypes; - } - - /** - * Get the {@linkplain Class Java class} of this source. - * - *

If the {@link Class} was not provided, but only the name, this method - * attempts to lazily load the {@link Class} based on its name and throws a - * {@link PreconditionViolationException} if the class cannot be loaded. - * - * @since 1.7 - * @see #getClassName() - */ - @API(status = STABLE, since = "1.7") - public final Class getJavaClass() { - lazyLoadJavaClass(); - return this.javaClass; - } - - /** - * Get the {@linkplain Method Java method} of this source. - * - *

If the {@link Method} was not provided, but only the name, this method - * attempts to lazily load the {@code Method} based on its name and throws a - * {@link PreconditionViolationException} if the method cannot be loaded. - * - * @since 1.7 - * @see #getMethodName() - */ - @API(status = STABLE, since = "1.7") - public final Method getJavaMethod() { - lazyLoadJavaMethod(); - return this.javaMethod; - } - - private void lazyLoadJavaClass() { - if (this.javaClass == null) { - // @formatter:off - this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( - cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); - // @formatter:on - } - } - - private void lazyLoadJavaMethod() { - lazyLoadJavaClass(); - - if (this.javaMethod == null) { - if (StringUtils.isNotBlank(this.methodParameterTypes)) { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName, - this.methodParameterTypes).orElseThrow( - () -> new PreconditionViolationException(String.format( - "Could not find method with name [%s] and parameter types [%s] in class [%s].", - this.methodName, this.methodParameterTypes, this.javaClass.getName()))); - } - else { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName).orElseThrow( - () -> new PreconditionViolationException( - String.format("Could not find method with name [%s] in class [%s].", this.methodName, - this.javaClass.getName()))); - } - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MethodSource that = (MethodSource) o; - return Objects.equals(this.className, that.className)// - && Objects.equals(this.methodName, that.methodName)// - && Objects.equals(this.methodParameterTypes, that.methodParameterTypes); - } - - @Override - public int hashCode() { - return Objects.hash(this.className, this.methodName, this.methodParameterTypes); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("className", this.className) - .append("methodName", this.methodName) - .append("methodParameterTypes", this.methodParameterTypes) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java deleted file mode 100644 index b19dc3b3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Objects; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestSource; - -/** - * Package based {@link org.junit.platform.engine.TestSource}. - * - *

This class stores the package name because {@link Package} does not - * implement {@link java.io.Serializable}. - * - * @since 1.0 - * @see org.junit.platform.engine.discovery.PackageSelector - */ -@API(status = STABLE, since = "1.0") -public class PackageSource implements TestSource { - - private static final long serialVersionUID = 1L; - - /** - * Create a new {@code PackageSource} using the supplied Java {@link Package}. - * - * @param javaPackage the Java package; must not be {@code null} - */ - public static PackageSource from(Package javaPackage) { - return new PackageSource(javaPackage); - } - - /** - * Create a new {@code PackageSource} using the supplied {@code packageName}. - * - * @param packageName the package name; must not be {@code null} or blank - */ - public static PackageSource from(String packageName) { - return new PackageSource(packageName); - } - - private final String packageName; - - private PackageSource(Package javaPackage) { - this(Preconditions.notNull(javaPackage, "package must not be null").getName()); - } - - private PackageSource(String packageName) { - this.packageName = Preconditions.notBlank(packageName, "package name must not be null or blank"); - } - - /** - * Get the package name of this test source. - */ - public final String getPackageName() { - return this.packageName; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PackageSource that = (PackageSource) o; - return Objects.equals(this.packageName, that.packageName); - } - - @Override - public int hashCode() { - return this.packageName.hashCode(); - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("packageName", this.packageName).toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java deleted file mode 100644 index ae9cfdd2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import java.net.URI; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; - -/** - * Collection of static utility methods for working with resources. - * - * @since 1.3 - */ -final class ResourceUtils { - - private ResourceUtils() { - /* no-op */ - } - - /** - * Strip the {@link URI#getQuery() query} component from the supplied - * {@link URI}. - * - * @param uri the {@code URI} from which to strip the query component - * @return a new {@code URI} with the query component removed, or the - * original {@code URI} unmodified if it does not have a query component - */ - static URI stripQueryComponent(URI uri) { - Preconditions.notNull(uri, "URI must not be null"); - - if (StringUtils.isBlank(uri.getQuery())) { - return uri; - } - - String uriAsString = uri.toString(); - return URI.create(uriAsString.substring(0, uriAsString.indexOf('?'))); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java deleted file mode 100644 index fb6829e5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestSource; - -/** - * A {@link TestSource} that can be represented as a {@link URI}. - * - * @since 1.0 - * @see org.junit.platform.engine.discovery.UriSelector - */ -@API(status = STABLE, since = "1.0") -public interface UriSource extends TestSource { - - /** - * Get the {@link URI} that represents this source. - * - * @return the source {@code URI}; never {@code null} - */ - URI getUri(); - - /** - * Create a new {@code UriSource} using the supplied {@code URI}. - * - *

This implementation first attempts to resolve the supplied {@code URI} - * to a path-based {@code UriSource} in the local filesystem. If that fails - * for any reason, an instance of the default {@code UriSource} - * implementation storing the supplied {@code URI} as-is will be - * returned. - * - * @param uri the URI to use as the source; never {@code null} - * @return an appropriate {@code UriSource} for the supplied {@code URI} - * @since 1.3 - * @see org.junit.platform.engine.support.descriptor.FileSource - * @see org.junit.platform.engine.support.descriptor.DirectorySource - */ - static UriSource from(URI uri) { - Preconditions.notNull(uri, "URI must not be null"); - - try { - URI uriWithoutQuery = ResourceUtils.stripQueryComponent(uri); - Path path = Paths.get(uriWithoutQuery); - if (Files.isRegularFile(path)) { - return FileSource.from(path.toFile(), FilePosition.fromQuery(uri.getQuery()).orElse(null)); - } - if (Files.isDirectory(path)) { - return DirectorySource.from(path.toFile()); - } - } - catch (RuntimeException ex) { - LoggerFactory.getLogger(UriSource.class).debug(ex, () -> String.format( - "The supplied URI [%s] is not path-based. Falling back to default UriSource implementation.", uri)); - } - - // Store supplied URI as-is - return new DefaultUriSource(uri); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java deleted file mode 100644 index 9f063532..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * {@link org.junit.platform.engine.TestDescriptor}-related support classes - * intended to be used by test engine implementations and clients of - * the launcher. - */ - -package org.junit.platform.engine.support.descriptor; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java deleted file mode 100644 index 886743da..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.discovery; - -import static java.util.stream.Collectors.toSet; -import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInClasspathRoot; -import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInModule; -import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInPackage; -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.selectors; -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; - -import java.util.List; -import java.util.function.Predicate; - -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageSelector; - -/** - * @since 1.5 - */ -class ClassContainerSelectorResolver implements SelectorResolver { - - private final Predicate> classFilter; - private final Predicate classNameFilter; - - ClassContainerSelectorResolver(Predicate> classFilter, Predicate classNameFilter) { - this.classFilter = classFilter; - this.classNameFilter = classNameFilter; - } - - @Override - public Resolution resolve(ClasspathRootSelector selector, Context context) { - return classSelectors(findAllClassesInClasspathRoot(selector.getClasspathRoot(), classFilter, classNameFilter)); - } - - @Override - public Resolution resolve(ModuleSelector selector, Context context) { - return classSelectors(findAllClassesInModule(selector.getModuleName(), classFilter, classNameFilter)); - } - - @Override - public Resolution resolve(PackageSelector selector, Context context) { - return classSelectors(findAllClassesInPackage(selector.getPackageName(), classFilter, classNameFilter)); - } - - private Resolution classSelectors(List> classes) { - if (classes.isEmpty()) { - return unresolved(); - } - return selectors(classes.stream().map(DiscoverySelectors::selectClass).collect(toSet())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java deleted file mode 100644 index 81c618f9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.discovery; - -import static java.util.stream.Collectors.joining; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.SelectorResolutionResult.failed; -import static org.junit.platform.engine.SelectorResolutionResult.resolved; -import static org.junit.platform.engine.SelectorResolutionResult.unresolved; - -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Queue; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.EngineDiscoveryListener; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.NestedClassSelector; -import org.junit.platform.engine.discovery.NestedMethodSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.discovery.UriSelector; -import org.junit.platform.engine.support.discovery.SelectorResolver.Context; -import org.junit.platform.engine.support.discovery.SelectorResolver.Match; -import org.junit.platform.engine.support.discovery.SelectorResolver.Resolution; - -/** - * @since 1.5 - */ -class EngineDiscoveryRequestResolution { - - private final EngineDiscoveryRequest request; - private final Context defaultContext; - private final List resolvers; - private final List visitors; - private final TestDescriptor engineDescriptor; - private final Map resolvedSelectors = new LinkedHashMap<>(); - private final Map resolvedUniqueIds = new LinkedHashMap<>(); - private final Queue remainingSelectors = new ArrayDeque<>(); - private final Map contextBySelector = new HashMap<>(); - - EngineDiscoveryRequestResolution(EngineDiscoveryRequest request, TestDescriptor engineDescriptor, - List resolvers, List visitors) { - this.request = request; - this.engineDescriptor = engineDescriptor; - this.resolvers = resolvers; - this.visitors = visitors; - this.defaultContext = new DefaultContext(null); - this.resolvedUniqueIds.put(engineDescriptor.getUniqueId(), Match.exact(engineDescriptor)); - } - - void run() { - remainingSelectors.addAll(request.getSelectorsByType(DiscoverySelector.class)); - while (!remainingSelectors.isEmpty()) { - resolveCompletely(remainingSelectors.poll()); - } - visitors.forEach(engineDescriptor::accept); - } - - private void resolveCompletely(DiscoverySelector selector) { - EngineDiscoveryListener discoveryListener = request.getDiscoveryListener(); - UniqueId engineId = engineDescriptor.getUniqueId(); - try { - Optional result = resolve(selector); - if (result.isPresent()) { - discoveryListener.selectorProcessed(engineId, selector, resolved()); - enqueueAdditionalSelectors(result.get()); - } - else { - discoveryListener.selectorProcessed(engineId, selector, unresolved()); - } - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - discoveryListener.selectorProcessed(engineId, selector, failed(t)); - } - } - - private void enqueueAdditionalSelectors(Resolution resolution) { - remainingSelectors.addAll(resolution.getSelectors()); - resolution.getMatches().stream().filter(Match::isExact).forEach(match -> { - Set childSelectors = match.expand(); - if (!childSelectors.isEmpty()) { - remainingSelectors.addAll(childSelectors); - DefaultContext context = new DefaultContext(match.getTestDescriptor()); - childSelectors.forEach(selector -> contextBySelector.put(selector, context)); - } - }); - } - - private Optional resolve(DiscoverySelector selector) { - if (resolvedSelectors.containsKey(selector)) { - return Optional.of(resolvedSelectors.get(selector)); - } - if (selector instanceof UniqueIdSelector) { - return resolveUniqueId((UniqueIdSelector) selector); - } - return resolve(selector, resolver -> { - Context context = getContext(selector); - if (selector instanceof ClasspathResourceSelector) { - return resolver.resolve((ClasspathResourceSelector) selector, context); - } - if (selector instanceof ClasspathRootSelector) { - return resolver.resolve((ClasspathRootSelector) selector, context); - } - if (selector instanceof ClassSelector) { - return resolver.resolve((ClassSelector) selector, context); - } - if (selector instanceof IterationSelector) { - return resolver.resolve((IterationSelector) selector, context); - } - if (selector instanceof NestedClassSelector) { - return resolver.resolve((NestedClassSelector) selector, context); - } - if (selector instanceof DirectorySelector) { - return resolver.resolve((DirectorySelector) selector, context); - } - if (selector instanceof FileSelector) { - return resolver.resolve((FileSelector) selector, context); - } - if (selector instanceof MethodSelector) { - return resolver.resolve((MethodSelector) selector, context); - } - if (selector instanceof NestedMethodSelector) { - return resolver.resolve((NestedMethodSelector) selector, context); - } - if (selector instanceof ModuleSelector) { - return resolver.resolve((ModuleSelector) selector, context); - } - if (selector instanceof PackageSelector) { - return resolver.resolve((PackageSelector) selector, context); - } - if (selector instanceof UriSelector) { - return resolver.resolve((UriSelector) selector, context); - } - return resolver.resolve(selector, context); - }); - } - - private Optional resolveUniqueId(UniqueIdSelector selector) { - UniqueId uniqueId = selector.getUniqueId(); - if (resolvedUniqueIds.containsKey(uniqueId)) { - return Optional.of(Resolution.match(resolvedUniqueIds.get(uniqueId))); - } - if (!uniqueId.hasPrefix(engineDescriptor.getUniqueId())) { - return Optional.empty(); - } - return resolve(selector, resolver -> resolver.resolve(selector, getContext(selector))); - } - - private Context getContext(DiscoverySelector selector) { - return contextBySelector.getOrDefault(selector, defaultContext); - } - - private Optional resolve(DiscoverySelector selector, - Function resolutionFunction) { - // @formatter:off - return resolvers.stream() - .map(resolutionFunction) - .filter(Resolution::isResolved) - .findFirst() - .map(resolution -> { - contextBySelector.remove(selector); - resolvedSelectors.put(selector, resolution); - resolution.getMatches() - .forEach(match -> resolvedUniqueIds.put(match.getTestDescriptor().getUniqueId(), match)); - return resolution; - }); - // @formatter:on - } - - private class DefaultContext implements Context { - - private final TestDescriptor parent; - - DefaultContext(TestDescriptor parent) { - this.parent = parent; - } - - @Override - public Optional addToParent(Function> creator) { - if (parent != null) { - return createAndAdd(parent, creator); - } - return createAndAdd(engineDescriptor, creator); - } - - @Override - public Optional addToParent(Supplier parentSelectorSupplier, - Function> creator) { - if (parent != null) { - return createAndAdd(parent, creator); - } - return resolve(parentSelectorSupplier.get()).flatMap(parent -> createAndAdd(parent, creator)); - } - - @Override - public Optional resolve(DiscoverySelector selector) { - // @formatter:off - return EngineDiscoveryRequestResolution.this.resolve(selector) - .map(Resolution::getMatches) - .flatMap(matches -> { - if (matches.size() > 1) { - String stringRepresentation = matches.stream() - .map(Match::getTestDescriptor) - .map(Objects::toString) - .collect(joining(", ")); - throw new JUnitException( - "Selector " + selector + " did not yield unique test descriptor: " + stringRepresentation); - } - if (matches.size() == 1) { - return Optional.of(getOnlyElement(matches).getTestDescriptor()); - } - return Optional.empty(); - }); - // @formatter:on - } - - @SuppressWarnings("unchecked") - private Optional createAndAdd(TestDescriptor parent, - Function> creator) { - Optional child = creator.apply(parent); - if (child.isPresent()) { - UniqueId uniqueId = child.get().getUniqueId(); - if (resolvedUniqueIds.containsKey(uniqueId)) { - return Optional.of((T) resolvedUniqueIds.get(uniqueId).getTestDescriptor()); - } - parent.addChild(child.get()); - } - return child; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java deleted file mode 100644 index 344f917c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.discovery; - -import static java.util.stream.Collectors.toCollection; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageNameFilter; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.support.discovery.SelectorResolver.Match; -import org.junit.platform.engine.support.discovery.SelectorResolver.Resolution; - -/** - * Configurable test discovery implementation based on {@link SelectorResolver} - * and {@link TestDescriptor.Visitor} that can be reused by different - * {@link org.junit.platform.engine.TestEngine TestEngines}. - * - *

This resolver takes care of notifying registered - * {@link org.junit.platform.engine.EngineDiscoveryListener - * EngineDiscoveryListeners} about the results of processed - * {@link org.junit.platform.engine.DiscoverySelector DiscoverySelectors}. - * - * @param the type of the engine's descriptor - * @since 1.5 - * @see #builder() - * @see #resolve(EngineDiscoveryRequest, TestDescriptor) - */ -@API(status = EXPERIMENTAL, since = "1.5") -public class EngineDiscoveryRequestResolver { - - private final List, SelectorResolver>> resolverCreators; - private final List, TestDescriptor.Visitor>> visitorCreators; - - private EngineDiscoveryRequestResolver(List, SelectorResolver>> resolverCreators, - List, TestDescriptor.Visitor>> visitorCreators) { - this.resolverCreators = new ArrayList<>(resolverCreators); - this.visitorCreators = new ArrayList<>(visitorCreators); - } - - /** - * Resolve the supplied {@link EngineDiscoveryRequest} and collect the - * results into the supplied {@link TestDescriptor}. - * - *

The algorithm works as follows: - * - *

    - *
  1. Enqueue all selectors in the supplied - * {@linkplain EngineDiscoveryRequest request} to be resolved. - *
  2. - *
  3. - * While there are selectors to be resolved, get the next one. - * Otherwise, the resolution is finished. - *
      - *
    1. - * Iterate over all registered {@linkplain SelectorResolver - * resolvers} in the order they were registered in and find the - * first one that returns a {@linkplain Resolution resolution} - * other than {@link Resolution#unresolved() unresolved()}. - *
    2. - *
    3. - * If such a {@linkplain Resolution resolution} exists, enqueue - * its {@linkplain Resolution#getSelectors() selectors}. - *
    4. - *
    5. - * For each exact {@linkplain Match match} in the {@linkplain - * Resolution resolution}, {@linkplain Match#expand() expand} - * its children and enqueue them as well. - *
    6. - *
    - *
  4. - *
  5. - * Iterate over all registered {@linkplain TestDescriptor.Visitor - * visitors} and let the engine test descriptor {@linkplain - * TestDescriptor#accept(TestDescriptor.Visitor) accept} them. - *
  6. - *
- * - * @param request the request to be resolved; never {@code null} - * @param engineDescriptor the engine's {@code TestDescriptor} to be used - * for adding direct children - * @see SelectorResolver - * @see TestDescriptor.Visitor - */ - public void resolve(EngineDiscoveryRequest request, T engineDescriptor) { - Preconditions.notNull(request, "request must not be null"); - Preconditions.notNull(engineDescriptor, "engineDescriptor must not be null"); - InitializationContext initializationContext = new DefaultInitializationContext<>(request, engineDescriptor); - List resolvers = instantiate(resolverCreators, initializationContext); - List visitors = instantiate(visitorCreators, initializationContext); - new EngineDiscoveryRequestResolution(request, engineDescriptor, resolvers, visitors).run(); - } - - private List instantiate(List, R>> creators, - InitializationContext context) { - return creators.stream().map(creator -> creator.apply(context)).collect(toCollection(ArrayList::new)); - } - - /** - * Create a new {@link Builder} for creating a {@link EngineDiscoveryRequestResolver}. - * - * @param the type of the engine's descriptor - * @return a new builder; never {@code null} - */ - public static Builder builder() { - return new Builder<>(); - } - - /** - * Builder for {@link EngineDiscoveryRequestResolver}. - * - * @param the type of the engine's descriptor - * @since 1.5 - */ - @API(status = EXPERIMENTAL, since = "1.5") - public static class Builder { - - private final List, SelectorResolver>> resolverCreators = new ArrayList<>(); - private final List, TestDescriptor.Visitor>> visitorCreators = new ArrayList<>(); - - private Builder() { - } - - /** - * Add a predefined resolver that resolves {@link ClasspathRootSelector - * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and - * {@link PackageSelector PackageSelectors} into {@link ClassSelector - * ClassSelectors} by scanning for classes that satisfy the supplied - * predicate in the respective class containers to this builder. - * - * @param classFilter predicate the resolved classes must satisfy; never - * {@code null} - * @return this builder for method chaining - */ - public Builder addClassContainerSelectorResolver(Predicate> classFilter) { - Preconditions.notNull(classFilter, "classFilter must not be null"); - return addSelectorResolver( - context -> new ClassContainerSelectorResolver(classFilter, context.getClassNameFilter())); - } - - /** - * Add a context insensitive {@link SelectorResolver} to this builder. - * - * @param resolver the resolver to add; never {@code null} - * @return this builder for method chaining - */ - public Builder addSelectorResolver(SelectorResolver resolver) { - Preconditions.notNull(resolver, "resolver must not be null"); - return addSelectorResolver(context -> resolver); - } - - /** - * Add a context sensitive {@link SelectorResolver} to this builder. - * - * @param resolverCreator the function that will be called to create the - * {@link SelectorResolver} to be added. - * @return this builder for method chaining - * @see InitializationContext - */ - public Builder addSelectorResolver(Function, SelectorResolver> resolverCreator) { - resolverCreators.add(resolverCreator); - return this; - } - - /** - * Add a context sensitive {@link TestDescriptor.Visitor} to this - * builder. - * - * @param visitorCreator the function that will be called to create the - * {@link TestDescriptor.Visitor} to be added. - * @return this builder for method chaining - * @see InitializationContext - */ - public Builder addTestDescriptorVisitor( - Function, TestDescriptor.Visitor> visitorCreator) { - visitorCreators.add(visitorCreator); - return this; - } - - /** - * Build the {@link EngineDiscoveryRequestResolver} that has been - * configured via this builder. - */ - public EngineDiscoveryRequestResolver build() { - return new EngineDiscoveryRequestResolver<>(resolverCreators, visitorCreators); - } - - } - - /** - * The initialization context for creating {@linkplain SelectorResolver - * resolvers} and {@linkplain TestDescriptor.Visitor visitors} that depend - * on the {@link EngineDiscoveryRequest} to be resolved or the engine - * descriptor that will be used to collect the results. - * - * @since 1.5 - * @see Builder#addSelectorResolver(Function) - * @see Builder#addTestDescriptorVisitor(Function) - */ - @API(status = EXPERIMENTAL, since = "1.5") - public interface InitializationContext { - - /** - * Get the {@link EngineDiscoveryRequest} that is about to be resolved. - * - * @return the {@link EngineDiscoveryRequest}; never {@code null} - */ - EngineDiscoveryRequest getDiscoveryRequest(); - - /** - * Get the engine's {@link TestDescriptor} that will be used to collect - * the results. - * - * @return engine's {@link TestDescriptor}; never {@code null} - */ - T getEngineDescriptor(); - - /** - * Get the class name filter built from the {@link ClassNameFilter - * ClassNameFilters} and {@link PackageNameFilter PackageNameFilters} - * in the {@link EngineDiscoveryRequest} that is about to be resolved. - * - * @return the predicate for filtering the resolved class names; never - * {@code null} - */ - Predicate getClassNameFilter(); - - } - - private static class DefaultInitializationContext implements InitializationContext { - - private final EngineDiscoveryRequest request; - private final T engineDescriptor; - private final Predicate classNameFilter; - - DefaultInitializationContext(EngineDiscoveryRequest request, T engineDescriptor) { - this.request = request; - this.engineDescriptor = engineDescriptor; - this.classNameFilter = buildClassNamePredicate(request); - } - - /** - * Build a {@link Predicate} for fully qualified class names to be used for - * classpath scanning from an {@link EngineDiscoveryRequest}. - * - * @param request the request to build a predicate from - */ - private Predicate buildClassNamePredicate(EngineDiscoveryRequest request) { - List> filters = new ArrayList<>(); - filters.addAll(request.getFiltersByType(ClassNameFilter.class)); - filters.addAll(request.getFiltersByType(PackageNameFilter.class)); - return Filter.composeFilters(filters).toPredicate(); - } - - @Override - public EngineDiscoveryRequest getDiscoveryRequest() { - return request; - } - - @Override - public T getEngineDescriptor() { - return engineDescriptor; - } - - @Override - public Predicate getClassNameFilter() { - return classNameFilter; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java deleted file mode 100644 index 93bc2b1d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java +++ /dev/null @@ -1,676 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.discovery; - -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Collections; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.NestedClassSelector; -import org.junit.platform.engine.discovery.NestedMethodSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.discovery.UriSelector; - -/** - * A resolver that supports resolving one or multiple types of - * {@link DiscoverySelector DiscoverySelectors}. - * - *

An implementation of a {@code resolve()} method is typically comprised - * of the following steps: - * - *

    - *
  1. - * Check whether the selector is applicable for the current - * {@link org.junit.platform.engine.TestEngine} and the current - * {@link org.junit.platform.engine.EngineDiscoveryRequest} (e.g. - * for a test class: is it relevant for the current engine and does - * it pass all filters in the request?). - *
  2. - *
  3. - * If so, use the supplied {@link Context Context}, to add one or - * multiple {@link TestDescriptor TestDescriptors} to the designated - * parent (see {@link Context Context} for details) and return a - * {@linkplain Resolution#match(Match) match} or multiple {@linkplain - * Resolution#matches(Set) matches}. Alternatively, convert the supplied - * selector into one or multiple other - * {@linkplain Resolution#selectors(Set) selectors} (e.g. a {@link - * PackageSelector} into a set of {@link ClassSelector ClassSelectors}). - * Otherwise, return {@link Resolution#unresolved() unresolved()}. - *
  4. - *
- * - * @since 1.5 - */ -@API(status = EXPERIMENTAL, since = "1.5") -public interface SelectorResolver { - - /** - * Resolve the supplied {@link ClasspathResourceSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(ClasspathResourceSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link ClasspathRootSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(ClasspathRootSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link ClassSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(ClassSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link NestedClassSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(NestedClassSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link DirectorySelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(DirectorySelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link FileSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(FileSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link MethodSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(MethodSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link NestedMethodSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(NestedMethodSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link ModuleSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(ModuleSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link PackageSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(PackageSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link UniqueIdSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(UniqueIdSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link UriSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - default Resolution resolve(UriSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link IterationSelector} using the supplied - * {@link Context Context}. - * - *

The default implementation delegates to {@link - * #resolve(DiscoverySelector, Context)}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see #resolve(DiscoverySelector, Context) - */ - @API(status = EXPERIMENTAL, since = "1.9") - default Resolution resolve(IterationSelector selector, Context context) { - return resolve((DiscoverySelector) selector, context); - } - - /** - * Resolve the supplied {@link DiscoverySelector} using the supplied - * {@link Context Context}. - * - *

This method is only called if none of the overloaded variants match. - * - *

The default implementation returns {@link Resolution#unresolved() - * unresolved()}. - * - * @param selector the selector to be resolved; never {@code null} - * @param context the context to be used for resolving the selector; never - * {@code null} - * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() - * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link - * Resolution#matches(Set) matches()}; never {@code null} - * @see Context - */ - default Resolution resolve(DiscoverySelector selector, Context context) { - return Resolution.unresolved(); - } - - /** - * The context for resolving a {@link DiscoverySelector} and adding it to - * the test tree. - * - *

The context is used to add resolved {@link TestDescriptor - * TestDescriptors} to the test tree if and only if the parent - * {@code TestDescriptor} could be found. Alternatively, a resolver may - * use the context to {@linkplain #resolve(DiscoverySelector) resolve} a - * certain {@code DiscoverySelector} into a {@code TestDescriptor} (e.g. for - * adding a filter and returning a {@linkplain Match#partial(TestDescriptor) - * partial match}). - * - * @since 1.5 - * @see SelectorResolver - */ - @API(status = EXPERIMENTAL, since = "1.5") - interface Context { - - /** - * Resolve the supplied {@link TestDescriptor}, if possible. - * - *

Calling this method has the same effect as returning a {@linkplain - * Match#partial(TestDescriptor) partial match} from a {@link - * SelectorResolver}: the children of the resulting {@link - * TestDescriptor} will only be resolved if a subsequent resolution - * finds an exact match that contains a {@code TestDescriptor} with the - * same {@linkplain TestDescriptor#getUniqueId() unique ID}. - * - * @param selector the selector to resolve - * @return the resolved {@code TestDescriptor}; never {@code null} but - * potentially empty - */ - Optional resolve(DiscoverySelector selector); - - /** - * Add a {@link TestDescriptor} to an unspecified parent, usually the - * engine descriptor, by applying the supplied {@code Function} to the - * new parent. - * - *

The parent will be the engine descriptor unless another parent has - * already been determined, i.e. if the selector that is being resolved - * is the result of {@linkplain Match#expand() expanding} a {@link - * Match}. - * - *

If the result of applying the {@code Function} is {@linkplain - * Optional#isPresent() present}, it will be added as a child of the - * parent {@code TestDescriptor} unless a descriptor with the same - * {@linkplain TestDescriptor#getUniqueId() unique ID} was added - * earlier. - * - * @param creator {@code Function} that will be called with the new - * parent to determine the new {@code TestDescriptor} to be added; must - * not return {@code null} - * @param the type of the new {@code TestDescriptor} - * @return the new {@code TestDescriptor} or the previously existing one - * with the same unique ID; never {@code null} but potentially empty - * @throws ClassCastException if the previously existing {@code - * TestDescriptor} is not an instance of {@code T} - */ - Optional addToParent(Function> creator); - - /** - * Add a {@link TestDescriptor} to a parent, specified by the {@link - * DiscoverySelector} returned by the supplied {@code Supplier}, by - * applying the supplied {@code Function} to the new parent. - * - *

Unless another parent has already been determined, i.e. if the - * selector that is being resolved is the result of {@linkplain - * Match#expand() expanding} a {@link Match}, the {@link - * DiscoverySelector} returned by the supplied {@code Supplier} will - * be used to determine the parent. If no parent is found, the supplied - * {@code Function} will not be called. If there are multiple potential - * parents, an exception will be thrown. Otherwise, the resolved - * {@code TestDescriptor} will be used as the parent and passed to the - * supplied {@code Function}. - * - *

If the result of applying the {@code Function} is {@linkplain - * Optional#isPresent() present}, it will be added as a child of the - * parent {@code TestDescriptor} unless a descriptor with the same - * {@linkplain TestDescriptor#getUniqueId() unique ID} was added - * earlier. - * - * @param creator {@code Function} that will be called with the new - * parent to determine the new {@code TestDescriptor} to be added; must - * not return {@code null} - * @param the type of the new {@code TestDescriptor} - * @return the new {@code TestDescriptor} or the previously existing one - * with the same unique ID; never {@code null} but potentially empty - * @throws ClassCastException if the previously existing {@code - * TestDescriptor} is not an instance of {@code T} - */ - Optional addToParent(Supplier parentSelectorSupplier, - Function> creator); - - } - - /** - * The result of an attempt to resolve a {@link DiscoverySelector}. - * - *

A resolution is either {@linkplain #unresolved unresolved}, contains a - * {@linkplain #match match} or multiple {@linkplain #matches}, or a set of - * {@linkplain #selectors selectors}. - * - * @since 1.5 - * @see SelectorResolver - */ - @API(status = EXPERIMENTAL, since = "1.5") - class Resolution { - - private static final Resolution UNRESOLVED = new Resolution(emptySet(), emptySet()); - - private final Set matches; - private final Set selectors; - - /** - * Factory for creating unresolved resolutions. - * - * @return an unresolved resolution; never {@code null} - */ - public static Resolution unresolved() { - return UNRESOLVED; - } - - /** - * Factory for creating a resolution that contains the supplied - * {@link Match Match}. - * - * @param match the resolved {@code Match}; never {@code null} - * @return an resolution that contains the supplied {@code Match}; never - * {@code null} - */ - public static Resolution match(Match match) { - return new Resolution(singleton(match), emptySet()); - } - - /** - * Factory for creating a resolution that contains the supplied - * {@link Match Matches}. - * - * @param matches the resolved {@code Matches}; never {@code null} or - * empty - * @return an resolution that contains the supplied {@code Matches}; - * never {@code null} - */ - public static Resolution matches(Set matches) { - Preconditions.containsNoNullElements(matches, "matches must not contain null elements"); - Preconditions.notEmpty(matches, "matches must not be empty"); - return new Resolution(matches, emptySet()); - } - - /** - * Factory for creating a resolution that contains the supplied - * {@link DiscoverySelector DiscoverySelectors}. - * - * @param selectors the resolved {@code DiscoverySelectors}; never - * {@code null} or empty - * @return an resolution that contains the supplied - * {@code DiscoverySelectors}; never {@code null} - */ - public static Resolution selectors(Set selectors) { - Preconditions.containsNoNullElements(selectors, "selectors must not contain null elements"); - Preconditions.notEmpty(selectors, "selectors must not be empty"); - return new Resolution(emptySet(), selectors); - } - - private Resolution(Set matches, Set selectors) { - this.matches = matches; - this.selectors = selectors; - } - - /** - * Whether this resolution contains matches or selectors. - * - * @return {@code true} if this resolution contains matches or selectors - */ - public boolean isResolved() { - return this != UNRESOLVED; - } - - /** - * Returns the matches contained by this resolution. - * - * @return the set of matches; never {@code null} but potentially empty - */ - public Set getMatches() { - return matches; - } - - /** - * Returns the selectors contained by this resolution. - * - * @return the set of selectors; never {@code null} but potentially empty - */ - public Set getSelectors() { - return selectors; - } - - } - - /** - * An exact or partial match for resolving a {@link DiscoverySelector} into - * a {@link TestDescriptor}. - * - *

A match is exact if the {@link DiscoverySelector} directly - * represents the resulting {@link TestDescriptor}, e.g. if a - * {@link ClassSelector} was resolved into the {@link TestDescriptor} that - * represents the test class. It is partial if the matching - * {@link TestDescriptor} does not directly correspond to the resolved - * {@link DiscoverySelector}, e.g. when resolving a {@link UniqueIdSelector} - * that represents a dynamic child of the resolved {@link TestDescriptor}. - * - *

In addition to the {@link TestDescriptor}, a match may contain a - * {@code Supplier} of {@link DiscoverySelector DiscoverySelectors} that may - * be used to discover the children of the {@link TestDescriptor}. The - * algorithm implemented by {@link EngineDiscoveryRequestResolver} - * {@linkplain #expand() expands} all exact matches immediately, i.e. it - * resolves all of their children. Partial matches will only be expanded in - * case a subsequent resolution finds an exact match that contains a {@link - * TestDescriptor} with the same {@linkplain TestDescriptor#getUniqueId() - * unique ID}. - * - * @since 1.5 - * @see SelectorResolver - * @see Resolution#match(Match) - * @see Resolution#matches(Set) - */ - @API(status = EXPERIMENTAL, since = "1.5") - class Match { - - private final TestDescriptor testDescriptor; - private final Supplier> childSelectorsSupplier; - private final Type type; - - /** - * Factory for creating an exact match without any children. - * - * @param testDescriptor the resolved {@code TestDescriptor}; never - * {@code null} - * @return a match that contains the supplied {@code TestDescriptor}; - * never {@code null} - */ - public static Match exact(TestDescriptor testDescriptor) { - return exact(testDescriptor, Collections::emptySet); - } - - /** - * Factory for creating an exact match with potential children. - * - * @param testDescriptor the resolved {@code TestDescriptor}; never - * {@code null} - * @param childSelectorsSupplier a {@code Supplier} of children - * selectors that will be resolved when this match is expanded; never - * {@code null} - * @return a match that contains the supplied {@code TestDescriptor}; - * never {@code null} - */ - public static Match exact(TestDescriptor testDescriptor, - Supplier> childSelectorsSupplier) { - return new Match(testDescriptor, childSelectorsSupplier, Type.EXACT); - } - - /** - * Factory for creating a partial match without any children. - * - * @param testDescriptor the resolved {@code TestDescriptor}; never - * {@code null} - * @return a match that contains the supplied {@code TestDescriptor}; - * never {@code null} - */ - public static Match partial(TestDescriptor testDescriptor) { - return partial(testDescriptor, Collections::emptySet); - } - - /** - * Factory for creating a partial match with potential children. - * - * @param testDescriptor the resolved {@code TestDescriptor}; never - * {@code null} - * @param childSelectorsSupplier a {@code Supplier} of children - * selectors that will be resolved when this match is expanded; never - * {@code null} - * @return a match that contains the supplied {@code TestDescriptor}; - * never {@code null} - */ - public static Match partial(TestDescriptor testDescriptor, - Supplier> childSelectorsSupplier) { - return new Match(testDescriptor, childSelectorsSupplier, Type.PARTIAL); - } - - private Match(TestDescriptor testDescriptor, Supplier> childSelectorsSupplier, - Type type) { - this.testDescriptor = Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); - this.childSelectorsSupplier = Preconditions.notNull(childSelectorsSupplier, - "childSelectorsSupplier must not be null"); - this.type = type; - } - - /** - * Whether this match is exact. - * - * @return {@code true} if this match is exact; {@code false} if it's - * partial - */ - public boolean isExact() { - return type == Type.EXACT; - } - - /** - * Get the contained {@link TestDescriptor}. - * - * @return the contained {@code TestDescriptor}; never {@code null} - */ - public TestDescriptor getTestDescriptor() { - return testDescriptor; - } - - /** - * Expand this match in order to resolve the children of the contained - * {@link TestDescriptor}. - * - * @return the set of {@code DiscoverySelectors} that represent the - * children of the contained {@code TestDescriptor}; never {@code null} - */ - public Set expand() { - return childSelectorsSupplier.get(); - } - - private enum Type { - EXACT, PARTIAL - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java deleted file mode 100644 index 1f0ea1ff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Configurable test discovery implementation that can be reused by different test engines. - * - * @see org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver#builder() - * @see org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver#resolve - * @see org.junit.platform.engine.support.discovery.SelectorResolver - */ - -package org.junit.platform.engine.support.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java deleted file mode 100644 index 9169ea77..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.filter; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.junit.platform.engine.Filter.composeFilters; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassFilter; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.PackageNameFilter; - -/** - * Support utility methods for classpath scanning. - * - * @since 1.0 - * @deprecated Please use {@link org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver} instead. - */ -@Deprecated -@API(status = DEPRECATED, since = "1.5") -public final class ClasspathScanningSupport { - - private ClasspathScanningSupport() { - /* no-op */ - } - - /** - * Build a {@link Predicate} for fully qualified class names to be used for - * classpath scanning from an {@link EngineDiscoveryRequest}. - * - * @param request the request to build a predicate from - * @deprecated Please use {@link org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver} instead. - */ - @Deprecated - public static Predicate buildClassNamePredicate(EngineDiscoveryRequest request) { - List> filters = new ArrayList<>(); - filters.addAll(request.getFiltersByType(ClassNameFilter.class)); - filters.addAll(request.getFiltersByType(PackageNameFilter.class)); - return composeFilters(filters).toPredicate(); - } - - /** - * Build a {@link ClassFilter} by combining the name predicate built by - * {@link #buildClassNamePredicate(EngineDiscoveryRequest)} and the passed-in - * class predicate. - * - * @param request the request to build a name predicate from - * @param classPredicate the class predicate - * @deprecated Please use {@link org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver} instead. - */ - @Deprecated - public static ClassFilter buildClassFilter(EngineDiscoveryRequest request, Predicate> classPredicate) { - return ClassFilter.of(buildClassNamePredicate(request), classPredicate); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java deleted file mode 100644 index c074ecf0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * {@link org.junit.platform.engine.Filter}-related support classes intended to be - * used by test engine implementations. - */ - -package org.junit.platform.engine.support.filter; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java deleted file mode 100644 index 41b08d5f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.locks.Lock; - -/** - * @since 1.3 - */ -class CompositeLock implements ResourceLock { - - private final List locks; - - CompositeLock(List locks) { - this.locks = locks; - } - - // for tests only - List getLocks() { - return locks; - } - - @Override - public ResourceLock acquire() throws InterruptedException { - ForkJoinPool.managedBlock(new CompositeLockManagedBlocker()); - return this; - } - - private void acquireAllLocks() throws InterruptedException { - List acquiredLocks = new ArrayList<>(locks.size()); - try { - for (Lock lock : locks) { - lock.lockInterruptibly(); - acquiredLocks.add(lock); - } - } - catch (InterruptedException e) { - release(acquiredLocks); - throw e; - } - } - - @Override - public void release() { - release(locks); - } - - private void release(List acquiredLocks) { - for (int i = acquiredLocks.size() - 1; i >= 0; i--) { - acquiredLocks.get(i).unlock(); - } - } - - private class CompositeLockManagedBlocker implements ForkJoinPool.ManagedBlocker { - - private boolean acquired; - - @Override - public boolean block() throws InterruptedException { - acquireAllLocks(); - acquired = true; - return true; - } - - @Override - public boolean isReleasable() { - return acquired; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java deleted file mode 100644 index f00735a7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import java.util.concurrent.ForkJoinPool; -import java.util.function.Predicate; - -/** - * @since 1.3 - */ -class DefaultParallelExecutionConfiguration implements ParallelExecutionConfiguration { - - private final int parallelism; - private final int minimumRunnable; - private final int maxPoolSize; - private final int corePoolSize; - private final int keepAliveSeconds; - private final Predicate saturate; - - DefaultParallelExecutionConfiguration(int parallelism, int minimumRunnable, int maxPoolSize, int corePoolSize, - int keepAliveSeconds, Predicate saturate) { - this.parallelism = parallelism; - this.minimumRunnable = minimumRunnable; - this.maxPoolSize = maxPoolSize; - this.corePoolSize = corePoolSize; - this.keepAliveSeconds = keepAliveSeconds; - this.saturate = saturate; - } - - @Override - public int getParallelism() { - return parallelism; - } - - @Override - public int getMinimumRunnable() { - return minimumRunnable; - } - - @Override - public int getMaxPoolSize() { - return maxPoolSize; - } - - @Override - public int getCorePoolSize() { - return corePoolSize; - } - - @Override - public int getKeepAliveSeconds() { - return keepAliveSeconds; - } - - @Override - public Predicate getSaturatePredicate() { - return saturate; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java deleted file mode 100644 index 52f03cb6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.math.BigDecimal; -import java.util.Locale; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * Default implementations of configuration strategies for parallel test - * execution. - * - * @since 1.3 - */ -@API(status = EXPERIMENTAL, since = "1.3") -public enum DefaultParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy { - - /** - * Uses the mandatory {@value CONFIG_FIXED_PARALLELISM_PROPERTY_NAME} configuration - * parameter as the desired parallelism. - */ - FIXED { - @Override - public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { - int parallelism = configurationParameters.get(CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, - Integer::valueOf).orElseThrow( - () -> new JUnitException(String.format("Configuration parameter '%s' must be set", - CONFIG_FIXED_PARALLELISM_PROPERTY_NAME))); - - int maxPoolSize = configurationParameters.get(CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, - Integer::valueOf).orElse(parallelism + 256); - - boolean saturate = configurationParameters.get(CONFIG_FIXED_SATURATE_PROPERTY_NAME, - Boolean::valueOf).orElse(true); - - return new DefaultParallelExecutionConfiguration(parallelism, parallelism, maxPoolSize, parallelism, - KEEP_ALIVE_SECONDS, __ -> saturate); - } - }, - - /** - * Computes the desired parallelism based on the number of available - * processors/cores multiplied by the {@value CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} - * configuration parameter. - */ - DYNAMIC { - @Override - public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { - BigDecimal factor = configurationParameters.get(CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME, - BigDecimal::new).orElse(BigDecimal.ONE); - - Preconditions.condition(factor.compareTo(BigDecimal.ZERO) > 0, - () -> String.format("Factor '%s' specified via configuration parameter '%s' must be greater than 0", - factor, CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME)); - - int parallelism = Math.max(1, - factor.multiply(BigDecimal.valueOf(Runtime.getRuntime().availableProcessors())).intValue()); - - return new DefaultParallelExecutionConfiguration(parallelism, parallelism, 256 + parallelism, parallelism, - KEEP_ALIVE_SECONDS, null); - } - }, - - /** - * Allows the specification of a custom {@link ParallelExecutionConfigurationStrategy} - * implementation via the mandatory {@value CONFIG_CUSTOM_CLASS_PROPERTY_NAME} - * configuration parameter to determine the desired configuration. - */ - CUSTOM { - @Override - public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { - String className = configurationParameters.get(CONFIG_CUSTOM_CLASS_PROPERTY_NAME).orElseThrow( - () -> new JUnitException(CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " must be set")); - return ReflectionUtils.tryToLoadClass(className) // - .andThenTry(strategyClass -> { - Preconditions.condition( - ParallelExecutionConfigurationStrategy.class.isAssignableFrom(strategyClass), - CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " does not implement " - + ParallelExecutionConfigurationStrategy.class); - return (ParallelExecutionConfigurationStrategy) ReflectionUtils.newInstance(strategyClass); - }) // - .andThenTry(strategy -> strategy.createConfiguration(configurationParameters)) // - .getOrThrow(cause -> new JUnitException( - "Could not create configuration for strategy class: " + className, cause)); - } - }; - - private static final int KEEP_ALIVE_SECONDS = 30; - - /** - * Property name used to determine the desired configuration strategy. - * - *

Value must be one of {@code dynamic}, {@code fixed}, or - * {@code custom}. - */ - public static final String CONFIG_STRATEGY_PROPERTY_NAME = "strategy"; - - /** - * Property name used to determine the desired parallelism for the - * {@link #FIXED} configuration strategy. - * - *

No default value; must be an integer. - * - * @see #FIXED - */ - public static final String CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = "fixed.parallelism"; - - /** - * Property name used to configure the maximum pool size of the underlying - * fork-join pool for the {@link #FIXED} configuration strategy. - * - *

Value must be an integer and greater than or equal to - * {@value #CONFIG_FIXED_PARALLELISM_PROPERTY_NAME}; defaults to - * {@code 256 + fixed.parallelism}. - * - * @since 1.10 - * @see #FIXED - */ - @API(status = EXPERIMENTAL, since = "1.10") - public static final String CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = "fixed.max-pool-size"; - - /** - * Property name used to disable saturation of the underlying fork-join pool - * for the {@link #FIXED} configuration strategy. - * - *

When set to {@code false} the underlying fork-join pool will reject - * additional tasks if all available workers are busy and the maximum - * pool-size would be exceeded. - *

Value must either {@code true} or {@code false}; defaults to {@code true}. - * - * @since 1.10 - * @see #FIXED - * @see #CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "1.10") - public static final String CONFIG_FIXED_SATURATE_PROPERTY_NAME = "fixed.saturate"; - - /** - * Property name of the factor used to determine the desired parallelism for the - * {@link #DYNAMIC} configuration strategy. - * - *

Value must be a decimal number; defaults to {@code 1}. - * - * @see #DYNAMIC - */ - public static final String CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = "dynamic.factor"; - - /** - * Property name used to specify the fully qualified class name of the - * {@link ParallelExecutionConfigurationStrategy} to be used by the - * {@link #CUSTOM} configuration strategy. - * - * @see #CUSTOM - */ - public static final String CONFIG_CUSTOM_CLASS_PROPERTY_NAME = "custom.class"; - - static ParallelExecutionConfigurationStrategy getStrategy(ConfigurationParameters configurationParameters) { - return valueOf( - configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME).orElse("dynamic").toUpperCase(Locale.ROOT)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java deleted file mode 100644 index 4a3748d9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import org.apiguardian.api.API; - -/** - * Marker interface for an execution context used by a concrete implementation - * of {@link HierarchicalTestEngine} and its collaborators. - * - * @since 1.0 - * @see HierarchicalTestEngine - */ -@API(status = MAINTAINED, since = "1.0") -public interface EngineExecutionContext { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java deleted file mode 100644 index 1674101c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Objects; -import java.util.concurrent.locks.ReadWriteLock; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; - -/** - * An exclusive resource identified by a key with a lock mode that is used to - * synchronize access to shared resources when executing nodes in parallel. - * - * @since 1.3 - * @see Node#getExecutionMode() - */ -@API(status = EXPERIMENTAL, since = "1.3") -public class ExclusiveResource { - - /** - * Key of the global resource lock that all direct children of the engine - * descriptor acquire in {@linkplain LockMode#READ read mode} by default: - * {@value} - * - *

If any node {@linkplain Node#getExclusiveResources() requires} an - * exclusive resource with the same key in - * {@linkplain LockMode#READ_WRITE read-write mode}, the lock will be - * coarsened to be acquired by the node's ancestor that is a direct child of - * the engine descriptor and all of the ancestor's descendants will be - * forced to run in the {@linkplain ExecutionMode#SAME_THREAD same thread}. - * - * @since 1.7 - */ - @API(status = EXPERIMENTAL, since = "1.7") - public static final String GLOBAL_KEY = "org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY"; - - static final ExclusiveResource GLOBAL_READ = new ExclusiveResource(GLOBAL_KEY, LockMode.READ); - static final ExclusiveResource GLOBAL_READ_WRITE = new ExclusiveResource(GLOBAL_KEY, LockMode.READ_WRITE); - - private final String key; - private final LockMode lockMode; - private int hash; - - /** - * Create a new {@code ExclusiveResource}. - * - * @param key the identifier of the resource; never {@code null} or blank - * @param lockMode the lock mode to use to synchronize access to the - * resource; never {@code null} - */ - public ExclusiveResource(String key, LockMode lockMode) { - this.key = Preconditions.notBlank(key, "key must not be blank"); - this.lockMode = Preconditions.notNull(lockMode, "lockMode must not be null"); - } - - /** - * Get the key of this resource. - */ - public String getKey() { - return key; - } - - /** - * Get the lock mode of this resource. - */ - public LockMode getLockMode() { - return lockMode; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ExclusiveResource that = (ExclusiveResource) o; - return Objects.equals(key, that.key) && lockMode == that.lockMode; - } - - @Override - public int hashCode() { - int h = hash; - if (h == 0) { - h = hash = Objects.hash(key, lockMode); - } - return h; - } - - @Override - public String toString() { - return new ToStringBuilder(this).append("key", key).append("lockMode", lockMode).toString(); - } - - /** - * {@code LockMode} translates to the respective {@link ReadWriteLock} - * locks. - * - * @implNote Enum order is important, since it can be used to sort locks, so - * the stronger mode has to be first. - */ - public enum LockMode { - - /** - * Require read and write access to the resource. - * - * @see ReadWriteLock#writeLock() - */ - READ_WRITE, - - /** - * Require only read access to the resource. - * - * @see ReadWriteLock#readLock() - */ - READ - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java deleted file mode 100644 index 84ef2561..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; - -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.Constructor; -import java.util.Deque; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; -import java.util.concurrent.ForkJoinTask; -import java.util.concurrent.ForkJoinWorkerThread; -import java.util.concurrent.Future; -import java.util.concurrent.RecursiveAction; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.function.Try; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * A {@link ForkJoinPool}-based - * {@linkplain HierarchicalTestExecutorService executor service} that executes - * {@linkplain TestTask test tasks} with the configured parallelism. - * - * @since 1.3 - * @see ForkJoinPool - * @see DefaultParallelExecutionConfigurationStrategy - */ -@API(status = EXPERIMENTAL, since = "1.3") -public class ForkJoinPoolHierarchicalTestExecutorService implements HierarchicalTestExecutorService { - - private final ForkJoinPool forkJoinPool; - private final int parallelism; - - /** - * Create a new {@code ForkJoinPoolHierarchicalTestExecutorService} based on - * the supplied {@link ConfigurationParameters}. - * - * @see DefaultParallelExecutionConfigurationStrategy - */ - public ForkJoinPoolHierarchicalTestExecutorService(ConfigurationParameters configurationParameters) { - this(createConfiguration(configurationParameters)); - } - - /** - * Create a new {@code ForkJoinPoolHierarchicalTestExecutorService} based on - * the supplied {@link ParallelExecutionConfiguration}. - * - * @since 1.7 - */ - @API(status = EXPERIMENTAL, since = "1.7") - public ForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration) { - forkJoinPool = createForkJoinPool(configuration); - parallelism = forkJoinPool.getParallelism(); - LoggerFactory.getLogger(getClass()).config(() -> "Using ForkJoinPool with parallelism of " + parallelism); - } - - private static ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.getStrategy( - configurationParameters); - return strategy.createConfiguration(configurationParameters); - } - - private ForkJoinPool createForkJoinPool(ParallelExecutionConfiguration configuration) { - ForkJoinWorkerThreadFactory threadFactory = new WorkerThreadFactory(); - // Try to use constructor available in Java >= 9 - Callable constructorInvocation = sinceJava9Constructor() // - .map(sinceJava9ConstructorInvocation(configuration, threadFactory)) - // Fallback for Java 8 - .orElse(sinceJava7ConstructorInvocation(configuration, threadFactory)); - return Try.call(constructorInvocation) // - .getOrThrow(cause -> new JUnitException("Failed to create ForkJoinPool", cause)); - } - - private static Optional> sinceJava9Constructor() { - return Try.call(() -> ForkJoinPool.class.getDeclaredConstructor(Integer.TYPE, ForkJoinWorkerThreadFactory.class, - UncaughtExceptionHandler.class, Boolean.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, Predicate.class, - Long.TYPE, TimeUnit.class)) // - .toOptional(); - } - - private static Function, Callable> sinceJava9ConstructorInvocation( - ParallelExecutionConfiguration configuration, ForkJoinWorkerThreadFactory threadFactory) { - return constructor -> () -> constructor.newInstance(configuration.getParallelism(), threadFactory, null, false, - configuration.getCorePoolSize(), configuration.getMaxPoolSize(), configuration.getMinimumRunnable(), - configuration.getSaturatePredicate(), configuration.getKeepAliveSeconds(), TimeUnit.SECONDS); - } - - private static Callable sinceJava7ConstructorInvocation(ParallelExecutionConfiguration configuration, - ForkJoinWorkerThreadFactory threadFactory) { - return () -> new ForkJoinPool(configuration.getParallelism(), threadFactory, null, false); - } - - @Override - public Future submit(TestTask testTask) { - ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); - if (!isAlreadyRunningInForkJoinPool()) { - // ensure we're running inside the ForkJoinPool so we - // can use ForkJoinTask API in invokeAll etc. - return forkJoinPool.submit(exclusiveTask); - } - // Limit the amount of queued work so we don't consume dynamic tests too eagerly - // by forking only if the current worker thread's queue length is below the - // desired parallelism. This optimistically assumes that the already queued tasks - // can be stolen by other workers and the new task requires about the same - // execution time as the already queued tasks. If the other workers are busy, - // the parallelism is already at its desired level. If all already queued tasks - // can be stolen by otherwise idle workers and the new task takes significantly - // longer, parallelism will drop. However, that only happens if the enclosing test - // task is the only one remaining which should rarely be the case. - if (testTask.getExecutionMode() == CONCURRENT && ForkJoinTask.getSurplusQueuedTaskCount() < parallelism) { - return exclusiveTask.fork(); - } - exclusiveTask.compute(); - return completedFuture(null); - } - - private boolean isAlreadyRunningInForkJoinPool() { - return ForkJoinTask.getPool() == forkJoinPool; - } - - @Override - public void invokeAll(List tasks) { - if (tasks.size() == 1) { - new ExclusiveTask(tasks.get(0)).compute(); - return; - } - Deque nonConcurrentTasks = new LinkedList<>(); - Deque concurrentTasksInReverseOrder = new LinkedList<>(); - forkConcurrentTasks(tasks, nonConcurrentTasks, concurrentTasksInReverseOrder); - executeNonConcurrentTasks(nonConcurrentTasks); - joinConcurrentTasksInReverseOrderToEnableWorkStealing(concurrentTasksInReverseOrder); - } - - private void forkConcurrentTasks(List tasks, Deque nonConcurrentTasks, - Deque concurrentTasksInReverseOrder) { - for (TestTask testTask : tasks) { - ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); - if (testTask.getExecutionMode() == CONCURRENT) { - exclusiveTask.fork(); - concurrentTasksInReverseOrder.addFirst(exclusiveTask); - } - else { - nonConcurrentTasks.add(exclusiveTask); - } - } - } - - private void executeNonConcurrentTasks(Deque nonConcurrentTasks) { - for (ExclusiveTask task : nonConcurrentTasks) { - task.compute(); - } - } - - private void joinConcurrentTasksInReverseOrderToEnableWorkStealing( - Deque concurrentTasksInReverseOrder) { - for (ExclusiveTask forkedTask : concurrentTasksInReverseOrder) { - forkedTask.join(); - } - } - - @Override - public void close() { - forkJoinPool.shutdownNow(); - } - - // this class cannot not be serialized because TestTask is not Serializable - @SuppressWarnings("serial") - static class ExclusiveTask extends RecursiveAction { - - private final TestTask testTask; - - ExclusiveTask(TestTask testTask) { - this.testTask = testTask; - } - - @SuppressWarnings("try") - @Override - public void compute() { - try (ResourceLock lock = testTask.getResourceLock().acquire()) { - testTask.execute(); - } - catch (InterruptedException e) { - ExceptionUtils.throwAsUncheckedException(e); - } - } - - } - - static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { - - private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - - @Override - public ForkJoinWorkerThread newThread(ForkJoinPool pool) { - return new WorkerThread(pool, contextClassLoader); - } - - } - - static class WorkerThread extends ForkJoinWorkerThread { - - WorkerThread(ForkJoinPool pool, ClassLoader contextClassLoader) { - super(pool); - setContextClassLoader(contextClassLoader); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java deleted file mode 100644 index 44ff65f3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestEngine; - -/** - * Abstract base class for all {@link TestEngine} implementations that wish - * to organize test suites hierarchically based on the {@link Node} abstraction. - * - * @param the type of {@code EngineExecutionContext} used by this engine - * @since 1.0 - * @see Node - */ -@API(status = MAINTAINED, since = "1.0") -public abstract class HierarchicalTestEngine implements TestEngine { - - public HierarchicalTestEngine() { - } - - /** - * Create an {@linkplain #createExecutorService(ExecutionRequest) executor - * service}; create an initial {@linkplain #createExecutionContext execution - * context}; execute the behavior of all {@linkplain Node nodes} in the - * hierarchy starting with the supplied {@code request}'s - * {@linkplain ExecutionRequest#getRootTestDescriptor() root} and notify - * its {@linkplain ExecutionRequest#getEngineExecutionListener() execution - * listener} of test execution events. - * - * @see Node - * @see #createExecutorService - * @see #createExecutionContext - */ - @Override - public final void execute(ExecutionRequest request) { - try (HierarchicalTestExecutorService executorService = createExecutorService(request)) { - C executionContext = createExecutionContext(request); - ThrowableCollector.Factory throwableCollectorFactory = createThrowableCollectorFactory(request); - new HierarchicalTestExecutor<>(request, executionContext, executorService, - throwableCollectorFactory).execute().get(); - } - catch (Exception exception) { - throw new JUnitException("Error executing tests for engine " + getId(), exception); - } - } - - /** - * Create the {@linkplain HierarchicalTestExecutorService executor service} - * to use for executing the supplied {@linkplain ExecutionRequest request}. - * - *

An engine may use the information in the supplied request - * such as the contained - * {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters} - * to decide what kind of service to return or how to configure it. - * - *

By default, this method returns an instance of - * {@link SameThreadHierarchicalTestExecutorService}. - * - * @param request the request about to be executed - * @since 1.3 - * @see ForkJoinPoolHierarchicalTestExecutorService - * @see SameThreadHierarchicalTestExecutorService - */ - @API(status = EXPERIMENTAL, since = "1.3") - protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) { - return new SameThreadHierarchicalTestExecutorService(); - } - - /** - * Create the {@linkplain ThrowableCollector.Factory factory} for creating - * {@link ThrowableCollector} instances used to handle exceptions that occur - * during execution of this engine's tests. - * - *

An engine may use the information in the supplied request - * such as the contained - * {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters} - * to decide what kind of factory to return or how to configure it. - * - *

By default, this method returns a factory that always creates instances of - * {@link OpenTest4JAwareThrowableCollector}. - * - * @param request the request about to be executed - * @since 1.3 - * @see OpenTest4JAwareThrowableCollector - * @see ThrowableCollector - */ - @API(status = EXPERIMENTAL, since = "1.3") - protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) { - return OpenTest4JAwareThrowableCollector::new; - } - - /** - * Create the initial execution context for executing the supplied - * {@linkplain ExecutionRequest request}. - * - * @param request the request about to be executed - * @return the initial context that will be passed to nodes in the hierarchy - */ - protected abstract C createExecutionContext(ExecutionRequest request); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java deleted file mode 100644 index e1535dc1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import java.util.concurrent.Future; - -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; - -/** - * Implementation core of all {@link TestEngine TestEngines} that wish to - * use the {@link Node} abstraction as the driving principle for structuring - * and executing test suites. - * - *

A {@code HierarchicalTestExecutor} is instantiated by a concrete - * implementation of {@link HierarchicalTestEngine} and takes care of - * executing nodes in the hierarchy in the appropriate order as well as - * firing the necessary events in the {@link EngineExecutionListener}. - * - * @param the type of {@code EngineExecutionContext} used by the - * {@code HierarchicalTestEngine} - * @since 1.0 - */ -class HierarchicalTestExecutor { - - private final ExecutionRequest request; - private final C rootContext; - private final HierarchicalTestExecutorService executorService; - private final ThrowableCollector.Factory throwableCollectorFactory; - - HierarchicalTestExecutor(ExecutionRequest request, C rootContext, HierarchicalTestExecutorService executorService, - ThrowableCollector.Factory throwableCollectorFactory) { - this.request = request; - this.rootContext = rootContext; - this.executorService = executorService; - this.throwableCollectorFactory = throwableCollectorFactory; - } - - Future execute() { - TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor(); - EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); - NodeExecutionAdvisor executionAdvisor = new NodeTreeWalker().walk(rootTestDescriptor); - NodeTestTaskContext taskContext = new NodeTestTaskContext(executionListener, this.executorService, - this.throwableCollectorFactory, executionAdvisor); - NodeTestTask rootTestTask = new NodeTestTask<>(taskContext, rootTestDescriptor); - rootTestTask.setParentContext(this.rootContext); - return this.executorService.submit(rootTestTask); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java deleted file mode 100644 index b4290a77..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.List; -import java.util.concurrent.Future; - -import org.apiguardian.api.API; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; - -/** - * A closeable service that executes {@linkplain TestTask test tasks}. - * - * @since 1.3 - * @see HierarchicalTestEngine#createExecutorService(ExecutionRequest) - * @see SameThreadHierarchicalTestExecutorService - * @see ForkJoinPoolHierarchicalTestExecutorService - */ -@API(status = EXPERIMENTAL, since = "1.3") -public interface HierarchicalTestExecutorService extends AutoCloseable { - - /** - * Submit the supplied {@linkplain TestTask test task} to be executed by - * this service. - * - *

Implementations may {@linkplain TestTask#execute() execute} the task - * asynchronously as long as its - * {@linkplain TestTask#getExecutionMode() execution mode} is - * {@linkplain ExecutionMode#CONCURRENT concurrent}. - * - *

Implementations must generally acquire and release the task's - * {@linkplain TestTask#getResourceLock() resource lock} before and after its - * execution unless they execute all tests in the same thread which - * upholds the same guarantees. - * - * @param testTask the test task to be executed - * @return a future that the caller can use to wait for the task's execution - * to be finished - * @see #invokeAll(List) - */ - Future submit(TestTask testTask); - - /** - * Invoke all supplied {@linkplain TestTask test tasks} and block until - * their execution has finished. - * - *

Implementations may {@linkplain TestTask#execute() execute} one or - * multiple of the supplied tasks in parallel as long as their - * {@linkplain TestTask#getExecutionMode() execution mode} is - * {@linkplain ExecutionMode#CONCURRENT concurrent}. - * - *

Implementations must generally acquire and release each task's - * {@linkplain TestTask#getResourceLock() resource lock} before and after its - * execution unless they execute all tests in the same thread which - * upholds the same guarantees. - * - * @param testTasks the test tasks to be executed - * @see #submit(TestTask) - */ - void invokeAll(List testTasks); - - /** - * Close this service and let it perform any required cleanup work. - * - *

For example, thread-based implementations should usually close their - * thread pools in this method. - */ - @Override - void close(); - - /** - * An executable task that represents a single test or container. - */ - interface TestTask { - - /** - * Get the {@linkplain ExecutionMode execution mode} of this task. - */ - ExecutionMode getExecutionMode(); - - /** - * Get the {@linkplain ResourceLock resource lock} of this task. - */ - ResourceLock getResourceLock(); - - /** - * Execute this task. - */ - void execute(); - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java deleted file mode 100644 index 1b5b37b0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static java.util.Comparator.comparing; -import static java.util.Comparator.naturalOrder; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; - -import java.util.Collection; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * @since 1.3 - */ -class LockManager { - - private static final Comparator COMPARATOR // - = comparing(ExclusiveResource::getKey, globalKeyFirst().thenComparing(naturalOrder())) // - .thenComparing(ExclusiveResource::getLockMode); - - private static Comparator globalKeyFirst() { - return comparing(key -> !GLOBAL_KEY.equals(key)); - } - - private final Map locksByKey = new ConcurrentHashMap<>(); - - ResourceLock getLockForResources(Collection resources) { - if (resources.size() == 1) { - return getLockForResource(getOnlyElement(resources)); - } - List locks = getDistinctSortedLocks(resources); - return toResourceLock(locks); - } - - ResourceLock getLockForResource(ExclusiveResource resource) { - return new SingleLock(toLock(resource)); - } - - private List getDistinctSortedLocks(Collection resources) { - // @formatter:off - Map> resourcesByKey = resources.stream() - .sorted(COMPARATOR) - .distinct() - .collect(groupingBy(ExclusiveResource::getKey, LinkedHashMap::new, toList())); - - return resourcesByKey.values().stream() - .map(resourcesWithSameKey -> resourcesWithSameKey.get(0)) - .map(this::toLock) - .collect(toList()); - // @formatter:on - } - - private Lock toLock(ExclusiveResource resource) { - ReadWriteLock lock = this.locksByKey.computeIfAbsent(resource.getKey(), key -> new ReentrantReadWriteLock()); - return resource.getLockMode() == READ ? lock.readLock() : lock.writeLock(); - } - - private ResourceLock toResourceLock(List locks) { - switch (locks.size()) { - case 0: - return NopLock.INSTANCE; - case 1: - return new SingleLock(locks.get(0)); - default: - return new CompositeLock(locks); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java deleted file mode 100644 index 691d5140..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static java.util.Collections.emptySet; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Future; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; - -/** - * A node within the execution hierarchy. - * - * @param the type of {@code EngineExecutionContext} used by the - * {@code HierarchicalTestEngine} - * @since 1.0 - * @see HierarchicalTestEngine - */ -@API(status = MAINTAINED, since = "1.0", consumers = "org.junit.platform.engine.support.hierarchical") -public interface Node { - - /** - * Prepare the supplied {@code context} prior to execution. - * - *

The default implementation returns the supplied {@code context} unmodified. - * - * @see #cleanUp(EngineExecutionContext) - */ - default C prepare(C context) throws Exception { - return context; - } - - /** - * Clean up the supplied {@code context} after execution. - * - *

The default implementation does nothing. - * - * @param context the context to execute in - * @since 1.1 - * @see #prepare(EngineExecutionContext) - */ - default void cleanUp(C context) throws Exception { - } - - /** - * Determine if the execution of the supplied {@code context} should be - * skipped. - * - *

The default implementation returns {@link SkipResult#doNotSkip()}. - */ - default SkipResult shouldBeSkipped(C context) throws Exception { - return SkipResult.doNotSkip(); - } - - /** - * Execute the before behavior of this node. - * - *

This method will be called once before {@linkplain #execute - * execution} of this node. - * - *

The default implementation returns the supplied {@code context} unmodified. - * - * @param context the context to execute in - * @return the new context to be used for children of this node; never - * {@code null} - * @see #execute(EngineExecutionContext, DynamicTestExecutor) - * @see #after(EngineExecutionContext) - */ - default C before(C context) throws Exception { - return context; - } - - /** - * Execute the behavior of this node. - * - *

Containers typically do not implement this method since the - * {@link HierarchicalTestEngine} handles execution of their children. - * - *

The supplied {@code dynamicTestExecutor} may be used to submit - * additional dynamic tests for immediate execution. - * - *

The default implementation returns the supplied {@code context} unmodified. - * - * @param context the context to execute in - * @param dynamicTestExecutor the executor to submit dynamic tests to - * @return the new context to be used for children of this node and for the - * after behavior of the parent of this node, if any - * @see #before - * @see #after - */ - default C execute(C context, DynamicTestExecutor dynamicTestExecutor) throws Exception { - return context; - } - - /** - * Execute the after behavior of this node. - * - *

This method will be called once after {@linkplain #execute - * execution} of this node. - * - *

The default implementation does nothing. - * - * @param context the context to execute in - * @see #before - * @see #execute - */ - default void after(C context) throws Exception { - } - - /** - * Wraps around the invocation of {@link #before(EngineExecutionContext)}, - * {@link #execute(EngineExecutionContext, DynamicTestExecutor)}, and - * {@link #after(EngineExecutionContext)}. - * - * @param context context the context to execute in - * @param invocation the wrapped invocation (must be invoked exactly once) - * @since 1.4 - */ - @API(status = EXPERIMENTAL, since = "1.4") - default void around(C context, Invocation invocation) throws Exception { - invocation.invoke(context); - } - - /** - * Callback invoked when the execution of this node has been skipped. - * - *

The default implementation does nothing. - * - * @param context the execution context - * @param testDescriptor the test descriptor that was skipped - * @param result the result of skipped execution - * @since 1.4 - */ - @API(status = EXPERIMENTAL, since = "1.4", consumers = "org.junit.platform.engine.support.hierarchical") - default void nodeSkipped(C context, TestDescriptor testDescriptor, SkipResult result) { - } - - /** - * Callback invoked when the execution of this node has finished. - * - *

The default implementation does nothing. - * - * @param context the execution context - * @param testDescriptor the test descriptor that was executed - * @param result the result of the execution - * @since 1.4 - */ - @API(status = EXPERIMENTAL, since = "1.4", consumers = "org.junit.platform.engine.support.hierarchical") - default void nodeFinished(C context, TestDescriptor testDescriptor, TestExecutionResult result) { - } - - /** - * Get the set of {@linkplain ExclusiveResource exclusive resources} - * required to execute this node. - * - *

The default implementation returns an empty set. - * - * @return the set of exclusive resources required by this node; never - * {@code null} but potentially empty - * @since 1.3 - * @see ExclusiveResource - */ - @API(status = EXPERIMENTAL, since = "1.3", consumers = "org.junit.platform.engine.support.hierarchical") - default Set getExclusiveResources() { - return emptySet(); - } - - /** - * Get the preferred of {@linkplain ExecutionMode execution mode} for - * parallel execution of this node. - * - *

The default implementation returns {@link ExecutionMode#CONCURRENT}. - * - * @return the preferred execution mode of this node; never {@code null} - * @since 1.3 - * @see ExecutionMode - */ - @API(status = EXPERIMENTAL, since = "1.3", consumers = "org.junit.platform.engine.support.hierarchical") - default ExecutionMode getExecutionMode() { - return ExecutionMode.CONCURRENT; - } - - /** - * The result of determining whether the execution of a given {@code context} - * should be skipped. - * - * @see Node#shouldBeSkipped(EngineExecutionContext) - */ - class SkipResult { - - private static final SkipResult alwaysExecuteSkipResult = new SkipResult(false, null); - - private final boolean skipped; - private final Optional reason; - - /** - * Factory for creating skipped results. - * - *

A context that is skipped will be not be executed. - * - * @param reason the reason that the context should be skipped, - * may be {@code null} - * @return a skipped {@code SkipResult} with the given reason - */ - public static SkipResult skip(String reason) { - return new SkipResult(true, reason); - } - - /** - * Factory for creating do not skip results. - * - *

A context that is not skipped will be executed as normal. - * - * @return a do not skip {@code SkipResult} - */ - public static SkipResult doNotSkip() { - return alwaysExecuteSkipResult; - } - - private SkipResult(boolean skipped, String reason) { - this.skipped = skipped; - this.reason = Optional.ofNullable(reason); - } - - /** - * Whether execution of the context should be skipped. - * - * @return {@code true} if the execution should be skipped - */ - public boolean isSkipped() { - return this.skipped; - } - - /** - * Get the reason that execution of the context should be skipped, - * if available. - */ - public Optional getReason() { - return this.reason; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("skipped", this.skipped) - .append("reason", this.reason.orElse("")) - .toString(); - // @formatter:on - } - - } - - /** - * Executor for additional, dynamic test descriptors discovered during - * execution of a {@link Node}. - * - *

The test descriptors will be executed by the same - * {@link HierarchicalTestExecutor} that executes the submitting node. - * - *

This interface is not intended to be implemented by clients. - * - * @see Node#execute(EngineExecutionContext, DynamicTestExecutor) - * @see HierarchicalTestExecutor - */ - interface DynamicTestExecutor { - - /** - * Submit a dynamic test descriptor for immediate execution. - * - * @param testDescriptor the test descriptor to be executed; never - * {@code null} - */ - void execute(TestDescriptor testDescriptor); - - /** - * Submit a dynamic test descriptor for immediate execution with a - * custom, potentially no-op, execution listener. - * - * @param testDescriptor the test descriptor to be executed; never - * {@code null} - * @param executionListener the executionListener to be notified; never - * {@code null} - * @return a future to cancel or wait for the execution - * @since 5.7 - * @see EngineExecutionListener#NOOP - */ - @API(status = EXPERIMENTAL, since = "5.7") - Future execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener); - - /** - * Block until all dynamic test descriptors submitted to this executor - * are finished. - * - *

This method is useful if the node needs to perform actions in its - * {@link #execute(EngineExecutionContext, DynamicTestExecutor)} method - * after all its dynamic children have finished. - * - * @throws InterruptedException if interrupted while waiting - */ - void awaitFinished() throws InterruptedException; - } - - /** - * Supported execution modes for parallel execution. - * - * @since 1.3 - * @see #SAME_THREAD - * @see #CONCURRENT - * @see Node#getExecutionMode() - */ - @API(status = EXPERIMENTAL, since = "1.3", consumers = "org.junit.platform.engine.support.hierarchical") - enum ExecutionMode { - - /** - * Force execution in same thread as the parent node. - * - * @see #CONCURRENT - */ - SAME_THREAD, - - /** - * Allow concurrent execution with any other node. - * - * @see #SAME_THREAD - */ - CONCURRENT - } - - /** - * Represents an invocation that runs with the supplied context. - * - * @param the type of {@code EngineExecutionContext} used by the {@code HierarchicalTestEngine} - * @since 1.4 - */ - @API(status = EXPERIMENTAL, since = "1.4") - interface Invocation { - - /** - * Invoke this invocation with the supplied context. - * - * @param context the context to invoke in - */ - void invoke(C context) throws Exception; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java deleted file mode 100644 index 0a860361..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; - -/** - * @since 1.3.1 - */ -class NodeExecutionAdvisor { - - private final Map forcedDescendantExecutionModeByTestDescriptor = new HashMap<>(); - private final Map resourceLocksByTestDescriptor = new HashMap<>(); - - void forceDescendantExecutionMode(TestDescriptor testDescriptor, ExecutionMode executionMode) { - forcedDescendantExecutionModeByTestDescriptor.put(testDescriptor, executionMode); - } - - void useResourceLock(TestDescriptor testDescriptor, ResourceLock resourceLock) { - resourceLocksByTestDescriptor.put(testDescriptor, resourceLock); - } - - Optional getForcedExecutionMode(TestDescriptor testDescriptor) { - return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor); - } - - private Optional lookupExecutionModeForcedByAncestor(TestDescriptor testDescriptor) { - ExecutionMode value = forcedDescendantExecutionModeByTestDescriptor.get(testDescriptor); - if (value != null) { - return Optional.of(value); - } - return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor); - } - - ResourceLock getResourceLock(TestDescriptor testDescriptor) { - return resourceLocksByTestDescriptor.getOrDefault(testDescriptor, NopLock.INSTANCE); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java deleted file mode 100644 index 34df1e0c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toCollection; -import static org.junit.platform.engine.TestExecutionResult.failed; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService.TestTask; -import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; -import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; -import org.junit.platform.engine.support.hierarchical.Node.SkipResult; - -/** - * @since 1.3 - */ -class NodeTestTask implements TestTask { - - private static final Logger logger = LoggerFactory.getLogger(NodeTestTask.class); - private static final Runnable NOOP = () -> { - }; - - private final NodeTestTaskContext taskContext; - private final TestDescriptor testDescriptor; - private final Node node; - private final Runnable finalizer; - - private C parentContext; - private C context; - - private SkipResult skipResult; - private boolean started; - private ThrowableCollector throwableCollector; - - NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor) { - this(taskContext, testDescriptor, NOOP); - } - - NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor, Runnable finalizer) { - this.taskContext = taskContext; - this.testDescriptor = testDescriptor; - this.node = NodeUtils.asNode(testDescriptor); - this.finalizer = finalizer; - } - - @Override - public ResourceLock getResourceLock() { - return taskContext.getExecutionAdvisor().getResourceLock(testDescriptor); - } - - @Override - public ExecutionMode getExecutionMode() { - return taskContext.getExecutionAdvisor().getForcedExecutionMode(testDescriptor).orElse(node.getExecutionMode()); - } - - void setParentContext(C parentContext) { - this.parentContext = parentContext; - } - - @Override - public void execute() { - try { - throwableCollector = taskContext.getThrowableCollectorFactory().create(); - prepare(); - if (throwableCollector.isEmpty()) { - checkWhetherSkipped(); - } - if (throwableCollector.isEmpty() && !skipResult.isSkipped()) { - executeRecursively(); - } - if (context != null) { - cleanUp(); - } - reportCompletion(); - } - finally { - // Ensure that the 'interrupted status' flag for the current thread - // is cleared for reuse of the thread in subsequent task executions. - // See https://github.com/junit-team/junit5/issues/1688 - if (Thread.interrupted()) { - logger.debug(() -> String.format( - "Execution of TestDescriptor with display name [%s] " - + "and unique ID [%s] failed to clear the 'interrupted status' flag for the " - + "current thread. JUnit has cleared the flag, but you may wish to investigate " - + "why the flag was not cleared by user code.", - this.testDescriptor.getDisplayName(), this.testDescriptor.getUniqueId())); - } - finalizer.run(); - } - - // Clear reference to context to allow it to be garbage collected. - // See https://github.com/junit-team/junit5/issues/1578 - context = null; - } - - private void prepare() { - throwableCollector.execute(() -> context = node.prepare(parentContext)); - - // Clear reference to parent context to allow it to be garbage collected. - // See https://github.com/junit-team/junit5/issues/1578 - parentContext = null; - } - - private void checkWhetherSkipped() { - throwableCollector.execute(() -> skipResult = node.shouldBeSkipped(context)); - } - - private void executeRecursively() { - taskContext.getListener().executionStarted(testDescriptor); - started = true; - - throwableCollector.execute(() -> { - node.around(context, ctx -> { - context = ctx; - throwableCollector.execute(() -> { - // @formatter:off - List> children = testDescriptor.getChildren().stream() - .map(descriptor -> new NodeTestTask(taskContext, descriptor)) - .collect(toCollection(ArrayList::new)); - // @formatter:on - - context = node.before(context); - - final DynamicTestExecutor dynamicTestExecutor = new DefaultDynamicTestExecutor(); - context = node.execute(context, dynamicTestExecutor); - - if (!children.isEmpty()) { - children.forEach(child -> child.setParentContext(context)); - taskContext.getExecutorService().invokeAll(children); - } - - throwableCollector.execute(dynamicTestExecutor::awaitFinished); - }); - - throwableCollector.execute(() -> node.after(context)); - }); - }); - } - - private void cleanUp() { - throwableCollector.execute(() -> node.cleanUp(context)); - } - - private void reportCompletion() { - if (throwableCollector.isEmpty() && skipResult.isSkipped()) { - try { - node.nodeSkipped(context, testDescriptor, skipResult); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - logger.debug(throwable, - () -> String.format("Failed to invoke nodeSkipped() on Node %s", testDescriptor.getUniqueId())); - } - taskContext.getListener().executionSkipped(testDescriptor, skipResult.getReason().orElse("")); - return; - } - if (!started) { - // Call executionStarted first to comply with the contract of EngineExecutionListener. - taskContext.getListener().executionStarted(testDescriptor); - } - try { - node.nodeFinished(context, testDescriptor, throwableCollector.toTestExecutionResult()); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - logger.debug(throwable, - () -> String.format("Failed to invoke nodeFinished() on Node %s", testDescriptor.getUniqueId())); - } - taskContext.getListener().executionFinished(testDescriptor, throwableCollector.toTestExecutionResult()); - throwableCollector = null; - } - - private class DefaultDynamicTestExecutor implements DynamicTestExecutor { - private final Map unfinishedTasks = new ConcurrentHashMap<>(); - - @Override - public void execute(TestDescriptor testDescriptor) { - execute(testDescriptor, taskContext.getListener()); - } - - @Override - public Future execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener) { - Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); - Preconditions.notNull(executionListener, "executionListener must not be null"); - - executionListener.dynamicTestRegistered(testDescriptor); - Set exclusiveResources = NodeUtils.asNode(testDescriptor).getExclusiveResources(); - if (!exclusiveResources.isEmpty()) { - executionListener.executionStarted(testDescriptor); - String message = "Dynamic test descriptors must not declare exclusive resources: " + exclusiveResources; - executionListener.executionFinished(testDescriptor, failed(new JUnitException(message))); - return completedFuture(null); - } - else { - UniqueId uniqueId = testDescriptor.getUniqueId(); - NodeTestTask nodeTestTask = new NodeTestTask<>(taskContext.withListener(executionListener), - testDescriptor, () -> unfinishedTasks.remove(uniqueId)); - nodeTestTask.setParentContext(context); - unfinishedTasks.put(uniqueId, DynamicTaskState.unscheduled()); - Future future = taskContext.getExecutorService().submit(nodeTestTask); - unfinishedTasks.computeIfPresent(uniqueId, (__, state) -> DynamicTaskState.scheduled(future)); - return future; - } - } - - @Override - public void awaitFinished() throws InterruptedException { - for (DynamicTaskState state : unfinishedTasks.values()) { - try { - state.awaitFinished(); - } - catch (CancellationException ignore) { - // Futures returned by execute() may have been cancelled - } - catch (ExecutionException e) { - ExceptionUtils.throwAsUncheckedException(e.getCause()); - } - } - } - } - - @FunctionalInterface - private interface DynamicTaskState { - - DynamicTaskState UNSCHEDULED = () -> { - }; - - static DynamicTaskState unscheduled() { - return UNSCHEDULED; - } - - static DynamicTaskState scheduled(Future future) { - return future::get; - } - - void awaitFinished() throws CancellationException, ExecutionException, InterruptedException; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java deleted file mode 100644 index 35a34af8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import org.junit.platform.engine.EngineExecutionListener; - -/** - * @since 1.3.1 - */ -class NodeTestTaskContext { - - private final EngineExecutionListener listener; - private final HierarchicalTestExecutorService executorService; - private final ThrowableCollector.Factory throwableCollectorFactory; - private final NodeExecutionAdvisor executionAdvisor; - - public NodeTestTaskContext(EngineExecutionListener listener, HierarchicalTestExecutorService executorService, - ThrowableCollector.Factory throwableCollectorFactory, NodeExecutionAdvisor executionAdvisor) { - this.listener = listener; - this.executorService = executorService; - this.throwableCollectorFactory = throwableCollectorFactory; - this.executionAdvisor = executionAdvisor; - } - - NodeTestTaskContext withListener(EngineExecutionListener listener) { - if (this.listener == listener) { - return this; - } - return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor); - } - - EngineExecutionListener getListener() { - return listener; - } - - HierarchicalTestExecutorService getExecutorService() { - return executorService; - } - - ThrowableCollector.Factory getThrowableCollectorFactory() { - return throwableCollectorFactory; - } - - NodeExecutionAdvisor getExecutionAdvisor() { - return executionAdvisor; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java deleted file mode 100644 index 2bb59d50..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; -import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; - -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; - -/** - * @since 1.3 - */ -class NodeTreeWalker { - - private final LockManager lockManager; - private final ResourceLock globalReadLock; - private final ResourceLock globalReadWriteLock; - - NodeTreeWalker() { - this(new LockManager()); - } - - NodeTreeWalker(LockManager lockManager) { - this.lockManager = lockManager; - this.globalReadLock = lockManager.getLockForResource(GLOBAL_READ); - this.globalReadWriteLock = lockManager.getLockForResource(GLOBAL_READ_WRITE); - } - - NodeExecutionAdvisor walk(TestDescriptor rootDescriptor) { - Preconditions.condition(getExclusiveResources(rootDescriptor).isEmpty(), - "Engine descriptor must not declare exclusive resources"); - NodeExecutionAdvisor advisor = new NodeExecutionAdvisor(); - rootDescriptor.getChildren().forEach(child -> walk(child, child, advisor)); - return advisor; - } - - private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescriptor, - NodeExecutionAdvisor advisor) { - Set exclusiveResources = getExclusiveResources(testDescriptor); - if (exclusiveResources.isEmpty()) { - advisor.useResourceLock(testDescriptor, globalReadLock); - testDescriptor.getChildren().forEach(child -> walk(globalLockDescriptor, child, advisor)); - } - else { - Set allResources = new HashSet<>(exclusiveResources); - if (isReadOnly(allResources)) { - doForChildrenRecursively(testDescriptor, child -> allResources.addAll(getExclusiveResources(child))); - if (!isReadOnly(allResources)) { - forceDescendantExecutionModeRecursively(advisor, testDescriptor); - } - } - else { - advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD); - doForChildrenRecursively(testDescriptor, child -> { - allResources.addAll(getExclusiveResources(child)); - advisor.forceDescendantExecutionMode(child, SAME_THREAD); - }); - } - if (!globalLockDescriptor.equals(testDescriptor) && allResources.contains(GLOBAL_READ_WRITE)) { - forceDescendantExecutionModeRecursively(advisor, globalLockDescriptor); - advisor.useResourceLock(globalLockDescriptor, globalReadWriteLock); - } - if (globalLockDescriptor.equals(testDescriptor) && !allResources.contains(GLOBAL_READ_WRITE)) { - allResources.add(GLOBAL_READ); - } - advisor.useResourceLock(testDescriptor, lockManager.getLockForResources(allResources)); - } - } - - private void forceDescendantExecutionModeRecursively(NodeExecutionAdvisor advisor, TestDescriptor testDescriptor) { - advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD); - doForChildrenRecursively(testDescriptor, child -> advisor.forceDescendantExecutionMode(child, SAME_THREAD)); - } - - private boolean isReadOnly(Set exclusiveResources) { - return exclusiveResources.stream().allMatch( - exclusiveResource -> exclusiveResource.getLockMode() == ExclusiveResource.LockMode.READ); - } - - private Set getExclusiveResources(TestDescriptor testDescriptor) { - return NodeUtils.asNode(testDescriptor).getExclusiveResources(); - } - - private void doForChildrenRecursively(TestDescriptor parent, Consumer consumer) { - parent.getChildren().forEach(child -> { - consumer.accept(child); - doForChildrenRecursively(child, consumer); - }); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java deleted file mode 100644 index b76ba240..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import org.junit.platform.engine.TestDescriptor; - -/** - * @since 1.3.1 - */ -final class NodeUtils { - - private NodeUtils() { - /* no-op */ - } - - @SuppressWarnings("unchecked") - static Node asNode(TestDescriptor testDescriptor) { - return (testDescriptor instanceof Node ? (Node) testDescriptor : noOpNode); - } - - @SuppressWarnings("rawtypes") - private static final Node noOpNode = new Node() { - }; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java deleted file mode 100644 index df6f64fc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -/** - * No-op {@link ResourceLock} implementation. - * - * @since 1.3 - */ -class NopLock implements ResourceLock { - - static final ResourceLock INSTANCE = new NopLock(); - - private NopLock() { - } - - @Override - public ResourceLock acquire() { - return this; - } - - @Override - public void release() { - // nothing to do - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java deleted file mode 100644 index 53b9e216..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import org.apiguardian.api.API; -import org.opentest4j.TestAbortedException; - -/** - * Specialization of {@link ThrowableCollector} that treats instances of - * {@link TestAbortedException} as aborting. - * - * @since 1.3 - * @see ThrowableCollector - */ -@API(status = MAINTAINED, since = "1.3") -public class OpenTest4JAwareThrowableCollector extends ThrowableCollector { - - public OpenTest4JAwareThrowableCollector() { - super(TestAbortedException.class::isInstance); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java deleted file mode 100644 index 47078896..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.concurrent.ForkJoinPool; -import java.util.function.Predicate; - -import org.apiguardian.api.API; - -/** - * Configuration to use for parallel test execution. - * - *

Instances of this class are intended to be used to configure - * implementations of {@link HierarchicalTestExecutorService}. Such - * implementations may use all of the properties in this class or - * only a subset. - * - * @since 1.3 - * @see ForkJoinPoolHierarchicalTestExecutorService - * @see ParallelExecutionConfigurationStrategy - * @see DefaultParallelExecutionConfigurationStrategy - */ -@API(status = EXPERIMENTAL, since = "1.3") -public interface ParallelExecutionConfiguration { - - /** - * Get the parallelism to be used. - * - * @see ForkJoinPool#getParallelism() - */ - int getParallelism(); - - /** - * Get the minimum number of runnable threads to be used. - */ - int getMinimumRunnable(); - - /** - * Get the maximum thread pool size to be used. - */ - int getMaxPoolSize(); - - /** - * Get the core thread pool size to be used. - */ - int getCorePoolSize(); - - /** - * Get the number of seconds for which inactive threads should be kept alive - * before terminating them and shrinking the thread pool. - */ - int getKeepAliveSeconds(); - - /** - * Get the saturate predicate to be used for the execution's {@link ForkJoinPool}. - * @return the saturate predicate to be passed to the {@code ForkJoinPool} constructor; may be {@code null} - * @since 1.9 - * @see ForkJoinPool#ForkJoinPool(int, ForkJoinPool.ForkJoinWorkerThreadFactory, Thread.UncaughtExceptionHandler, - * boolean, int, int, int, Predicate, long, TimeUnit) - */ - @API(status = EXPERIMENTAL, since = "1.9") - default Predicate getSaturatePredicate() { - return null; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java deleted file mode 100644 index 181ae463..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * A strategy to use for configuring parallel test execution. - * - * @since 1.3 - * @see DefaultParallelExecutionConfigurationStrategy - */ -@API(status = EXPERIMENTAL, since = "1.3") -public interface ParallelExecutionConfigurationStrategy { - - /** - * Create a configuration for parallel test execution based on the supplied - * {@link ConfigurationParameters}. - */ - ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java deleted file mode 100644 index 9ec55b8f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; - -/** - * A lock for a one or more resources. - * - * @since 1.3 - * @see HierarchicalTestExecutorService.TestTask#getResourceLock() - */ -@API(status = EXPERIMENTAL, since = "1.3") -public interface ResourceLock extends AutoCloseable { - - /** - * Acquire this resource lock, potentially blocking. - * - * @return this lock so it can easily be used in a try-with-resources - * statement. - * @throws InterruptedException if the calling thread is interrupted - * while waiting to acquire this lock - */ - ResourceLock acquire() throws InterruptedException; - - /** - * Release this resource lock. - */ - void release(); - - @Override - default void close() { - release(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java deleted file mode 100644 index c1844da9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.List; -import java.util.concurrent.Future; - -import org.apiguardian.api.API; - -/** - * A simple {@linkplain HierarchicalTestExecutorService executor service} that - * executes all {@linkplain TestTask test tasks} in the caller's thread. - * - * @since 1.3 - */ -@API(status = EXPERIMENTAL, since = "1.3") -public class SameThreadHierarchicalTestExecutorService implements HierarchicalTestExecutorService { - - public SameThreadHierarchicalTestExecutorService() { - } - - @Override - public Future submit(TestTask testTask) { - testTask.execute(); - return completedFuture(null); - } - - @Override - public void invokeAll(List tasks) { - tasks.forEach(TestTask::execute); - } - - @Override - public void close() { - // nothing to do - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java deleted file mode 100644 index 5d104e8d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.locks.Lock; - -/** - * @since 1.3 - */ -class SingleLock implements ResourceLock { - - private final Lock lock; - - SingleLock(Lock lock) { - this.lock = lock; - } - - // for tests only - Lock getLock() { - return lock; - } - - @Override - public ResourceLock acquire() throws InterruptedException { - ForkJoinPool.managedBlock(new SingleLockManagedBlocker()); - return this; - } - - @Override - public void release() { - lock.unlock(); - } - - private class SingleLockManagedBlocker implements ForkJoinPool.ManagedBlocker { - - private boolean acquired; - - @Override - public boolean block() throws InterruptedException { - lock.lockInterruptibly(); - acquired = true; - return true; - } - - @Override - public boolean isReleasable() { - return acquired || lock.tryLock(); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java deleted file mode 100644 index 6c87fb11..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.junit.platform.engine.TestExecutionResult.aborted; -import static org.junit.platform.engine.TestExecutionResult.failed; -import static org.junit.platform.engine.TestExecutionResult.successful; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestExecutionResult; -import org.opentest4j.TestAbortedException; - -/** - * {@code SingleTestExecutor} encapsulates the execution of a single test - * wrapped in an {@link Executable}. - * - * @since 1.0 - * @see #executeSafely(Executable) - * @deprecated Please use {@link ThrowableCollector#execute} and - * {@link ThrowableCollector#toTestExecutionResult} instead. - */ -@Deprecated -@API(status = DEPRECATED, since = "1.2") -public class SingleTestExecutor { - - /** - * Functional interface for a single test to be executed by - * {@link SingleTestExecutor}. - */ - @FunctionalInterface - public interface Executable { - - /** - * Execute the test. - * - * @throws TestAbortedException to signal aborted execution - * @throws Throwable to signal failure - */ - void execute() throws TestAbortedException, Throwable; - - } - - /** - * Execute the supplied {@link Executable} and return a - * {@link TestExecutionResult} based on the outcome. - * - *

If the {@code Executable} throws an unrecoverable exception - * — for example, an {@link OutOfMemoryError} — this method will - * rethrow it. - * - * @param executable the test to be executed - * @return {@linkplain TestExecutionResult#aborted aborted} if the - * {@code Executable} throws a {@link TestAbortedException}; - * {@linkplain TestExecutionResult#failed failed} if any other - * {@link Throwable} is thrown; and {@linkplain TestExecutionResult#successful - * successful} otherwise - */ - public TestExecutionResult executeSafely(Executable executable) { - try { - executable.execute(); - return successful(); - } - catch (TestAbortedException e) { - return aborted(e); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - return failed(t); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java deleted file mode 100644 index 5a1e9d56..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.junit.platform.engine.TestExecutionResult.aborted; -import static org.junit.platform.engine.TestExecutionResult.failed; -import static org.junit.platform.engine.TestExecutionResult.successful; - -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestExecutionResult; - -/** - * Simple component that can be used to collect one or more instances of - * {@link Throwable}. - * - *

This class distinguishes between {@code Throwables} that abort - * and those that fail test execution. The latter take precedence over - * the former, i.e. if both types of {@code Throwables} were collected, the ones - * that abort execution are reported as - * {@linkplain Throwable#addSuppressed(Throwable) suppressed} {@code Throwables} - * of the first {@code Throwable} that failed execution. - * - * @since 1.3 - * @see OpenTest4JAwareThrowableCollector - */ -@API(status = MAINTAINED, since = "1.3") -public class ThrowableCollector { - - private final Predicate abortedExecutionPredicate; - - private Throwable throwable; - - /** - * Create a new {@code ThrowableCollector} that uses the supplied - * {@link Predicate} to determine whether a {@link Throwable} - * aborted or failed execution. - * - * @param abortedExecutionPredicate the predicate used to decide whether a - * {@code Throwable} aborted execution; never {@code null}. - */ - public ThrowableCollector(Predicate abortedExecutionPredicate) { - this.abortedExecutionPredicate = Preconditions.notNull(abortedExecutionPredicate, - "abortedExecutionPredicate must not be null"); - } - - /** - * Execute the supplied {@link Executable} and collect any {@link Throwable} - * thrown during the execution. - * - *

If the {@code Executable} throws an unrecoverable exception - * — for example, an {@link OutOfMemoryError} — this method will - * rethrow it. - * - * @param executable the {@code Executable} to execute - * @see #assertEmpty() - */ - public void execute(Executable executable) { - try { - executable.execute(); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - add(t); - } - } - - /** - * Add the supplied {@link Throwable} to this {@code ThrowableCollector}. - * - * @param t the {@code Throwable} to add - * @see #execute(Executable) - * @see #assertEmpty() - */ - private void add(Throwable t) { - Preconditions.notNull(t, "Throwable must not be null"); - - if (this.throwable == null) { - this.throwable = t; - } - else if (hasAbortedExecution(this.throwable) && !hasAbortedExecution(t)) { - t.addSuppressed(this.throwable); - this.throwable = t; - } - else if (throwable != t) { - // Jupiter does not throw the same Throwable from Node.after() anymore but other engines might - this.throwable.addSuppressed(t); - } - } - - /** - * Get the first {@link Throwable} collected by this - * {@code ThrowableCollector}. - * - *

If this collector is not empty, the first collected {@code Throwable} - * will be returned with any additional {@code Throwables} - * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the - * first {@code Throwable}. - * - *

If the first collected {@code Throwable} aborted execution - * and at least one later collected {@code Throwable} failed - * execution, the first failing {@code Throwable} will be returned - * with the previous aborting and any additional {@code Throwables} - * {@linkplain Throwable#addSuppressed(Throwable) suppressed} inside. - * - * @return the first collected {@code Throwable} or {@code null} if this - * {@code ThrowableCollector} is empty - * @see #isEmpty() - * @see #assertEmpty() - */ - public Throwable getThrowable() { - return this.throwable; - } - - /** - * Determine if this {@code ThrowableCollector} is empty (i.e., - * has not collected any {@code Throwables}). - */ - public boolean isEmpty() { - return (this.throwable == null); - } - - /** - * Determine if this {@code ThrowableCollector} is not empty (i.e., - * has collected at least one {@code Throwable}). - */ - public boolean isNotEmpty() { - return (this.throwable != null); - } - - /** - * Assert that this {@code ThrowableCollector} is empty (i.e., - * has not collected any {@code Throwables}). - * - *

If this collector is not empty, the first collected {@code Throwable} - * will be thrown with any additional {@code Throwables} - * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the - * first {@code Throwable}. Note, however, that the {@code Throwable} - * will not be wrapped. Rather, it will be - * {@linkplain ExceptionUtils#throwAsUncheckedException masked} - * as an unchecked exception. - * - * @see #getThrowable() - * @see ExceptionUtils#throwAsUncheckedException(Throwable) - */ - public void assertEmpty() { - if (!isEmpty()) { - ExceptionUtils.throwAsUncheckedException(this.throwable); - } - } - - /** - * Convert the collected {@link Throwable Throwables} into a {@link TestExecutionResult}. - * - * @return {@linkplain TestExecutionResult#aborted aborted} if the collected - * {@code Throwable} aborted execution; - * {@linkplain TestExecutionResult#failed failed} if it failed - * execution; and {@linkplain TestExecutionResult#successful successful} - * otherwise - * @since 1.6 - */ - @API(status = MAINTAINED, since = "1.6") - public TestExecutionResult toTestExecutionResult() { - if (isEmpty()) { - return successful(); - } - if (hasAbortedExecution(throwable)) { - return aborted(throwable); - } - return failed(throwable); - } - - private boolean hasAbortedExecution(Throwable t) { - return this.abortedExecutionPredicate.test(t); - } - - /** - * Functional interface for an executable block of code that may throw a - * {@link Throwable}. - */ - @FunctionalInterface - public interface Executable { - - /** - * Execute this executable, potentially throwing a {@link Throwable} - * that signals abortion or failure. - */ - void execute() throws Throwable; - - } - - /** - * Factory for {@code ThrowableCollector} instances. - */ - public interface Factory { - - /** - * Create a new instance of a {@code ThrowableCollector}. - */ - ThrowableCollector create(); - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java deleted file mode 100644 index 033745ba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Support classes and base implementation for any - * {@link org.junit.platform.engine.TestEngine} that wishes to organize test suites - * hierarchically based on the - * {@link org.junit.platform.engine.support.hierarchical.Node} abstraction. - */ - -package org.junit.platform.engine.support.hierarchical; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java deleted file mode 100644 index 0b6336c6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Public API for test engines. - * - *

Provides the {@linkplain org.junit.platform.engine.TestEngine} interface, test discovery - * and execution reporting support. - * - * @since 1.0 - */ -module org.junit.platform.engine { - requires static transitive org.apiguardian.api; - requires transitive org.junit.platform.commons; - requires transitive org.opentest4j; - - exports org.junit.platform.engine; - exports org.junit.platform.engine.discovery; - exports org.junit.platform.engine.reporting; - // exports org.junit.platform.engine.support; empty package - exports org.junit.platform.engine.support.config; - exports org.junit.platform.engine.support.descriptor; - exports org.junit.platform.engine.support.discovery; - exports org.junit.platform.engine.support.filter; - exports org.junit.platform.engine.support.hierarchical; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java deleted file mode 100644 index 8d3c74a7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import org.junit.platform.engine.ExecutionRequest; - -/** - * @since 1.0 - */ -public class DemoEngineExecutionContext implements EngineExecutionContext { - - public final ExecutionRequest request; - - public DemoEngineExecutionContext(ExecutionRequest request) { - this.request = request; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java deleted file mode 100644 index 09c0b622..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; - -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; - -/** - * @since 1.0 - */ -public class DemoHierarchicalContainerDescriptor extends AbstractTestDescriptor - implements Node { - - private final Runnable beforeBlock; - - public DemoHierarchicalContainerDescriptor(UniqueId uniqueId, String displayName, TestSource source, - Runnable beforeBlock) { - super(uniqueId, displayName, source); - this.beforeBlock = beforeBlock; - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public boolean mayRegisterTests() { - return true; - } - - @Override - public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { - return doNotSkip(); - } - - @Override - public DemoEngineExecutionContext before(DemoEngineExecutionContext context) { - if (this.beforeBlock != null) { - this.beforeBlock.run(); - } - return context; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java deleted file mode 100644 index 1d14b771..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; -import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip; - -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; - -/** - * @since 1.0 - */ -public class DemoHierarchicalEngineDescriptor extends EngineDescriptor implements Node { - - private String skippedReason; - private boolean skipped; - private Runnable beforeAllBehavior = () -> { - }; - - public DemoHierarchicalEngineDescriptor(UniqueId uniqueId) { - super(uniqueId, uniqueId.getEngineId().orElseThrow()); - } - - public void markSkipped(String reason) { - this.skipped = true; - this.skippedReason = reason; - } - - public void setBeforeAllBehavior(Runnable beforeAllBehavior) { - this.beforeAllBehavior = beforeAllBehavior; - } - - @Override - public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { - return skipped ? skip(skippedReason) : doNotSkip(); - } - - @Override - public DemoEngineExecutionContext before(DemoEngineExecutionContext context) { - beforeAllBehavior.run(); - return context; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java deleted file mode 100644 index 130aba4a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; -import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip; - -import java.util.function.BiConsumer; - -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; - -/** - * @since 1.0 - */ -public class DemoHierarchicalTestDescriptor extends AbstractTestDescriptor implements Node { - - private final BiConsumer executeBlock; - private String skippedReason; - private boolean skipped; - - public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, - BiConsumer executeBlock) { - this(uniqueId, displayName, null, executeBlock); - } - - public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, - BiConsumer executeBlock) { - super(uniqueId, displayName, source); - this.executeBlock = executeBlock; - } - - @Override - public Type getType() { - return this.executeBlock != null ? Type.TEST : Type.CONTAINER; - } - - public void markSkipped(String reason) { - this.skipped = true; - this.skippedReason = reason; - } - - @Override - public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { - return skipped ? skip(skippedReason) : doNotSkip(); - } - - @Override - public DemoEngineExecutionContext execute(DemoEngineExecutionContext context, - DynamicTestExecutor dynamicTestExecutor) { - if (this.executeBlock != null) { - this.executeBlock.accept(context, this); - } - return context; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java deleted file mode 100644 index 97f23ce1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import java.util.function.BiConsumer; -import java.util.function.Function; - -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; - -/** - * @since 1.0 - */ -public final class DemoHierarchicalTestEngine extends HierarchicalTestEngine { - - private final String engineId; - private final DemoHierarchicalEngineDescriptor engineDescriptor; - - public DemoHierarchicalTestEngine() { - this("dummy"); - } - - public DemoHierarchicalTestEngine(String engineId) { - this.engineId = engineId; - this.engineDescriptor = new DemoHierarchicalEngineDescriptor(UniqueId.forEngine(getId())); - } - - @Override - public String getId() { - return engineId; - } - - public DemoHierarchicalEngineDescriptor getEngineDescriptor() { - return engineDescriptor; - } - - public DemoHierarchicalTestDescriptor addTest(String uniqueName, Runnable executeBlock) { - return addTest(uniqueName, uniqueName, executeBlock); - } - - public DemoHierarchicalTestDescriptor addTest(String uniqueName, String displayName, Runnable executeBlock) { - return addChild(uniqueName, - uniqueId -> new DemoHierarchicalTestDescriptor(uniqueId, displayName, (c, t) -> executeBlock.run()), - "test"); - } - - public DemoHierarchicalTestDescriptor addTest(String uniqueName, String displayName, - BiConsumer executeBlock) { - return addChild(uniqueName, uniqueId -> new DemoHierarchicalTestDescriptor(uniqueId, displayName, executeBlock), - "test"); - } - - public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, TestSource source) { - return addContainer(uniqueName, displayName, source, null); - } - - public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, Runnable beforeBlock) { - return addContainer(uniqueName, uniqueName, null, beforeBlock); - } - - public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, TestSource source, - Runnable beforeBlock) { - - return addChild(uniqueName, - uniqueId -> new DemoHierarchicalContainerDescriptor(uniqueId, displayName, source, beforeBlock), - "container"); - } - - public > T addChild(String uniqueName, - Function creator, String segmentType) { - var uniqueId = engineDescriptor.getUniqueId().append(segmentType, uniqueName); - var child = creator.apply(uniqueId); - engineDescriptor.addChild(child); - return child; - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - return engineDescriptor; - } - - @Override - protected DemoEngineExecutionContext createExecutionContext(ExecutionRequest request) { - return new DemoEngineExecutionContext(request); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java deleted file mode 100644 index 49907eec..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.fakes; - -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; - -/** - * @since 1.4 - */ -public class TestDescriptorStub extends AbstractTestDescriptor { - - public TestDescriptorStub(UniqueId uniqueId, String displayName) { - super(uniqueId, displayName); - } - - @Override - public Type getType() { - return getChildren().isEmpty() ? Type.TEST : Type.CONTAINER; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java deleted file mode 100644 index 2cb30350..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.fakes; - -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; - -/** - * @since 1.4 - */ -public class TestEngineSpy implements TestEngine { - - public static final String ID = TestEngineSpy.class.getSimpleName(); - - public ExecutionRequest requestForExecution; - - @Override - public String getId() { - return ID; - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - var engineUniqueId = UniqueId.forEngine(ID); - var engineDescriptor = new TestDescriptorStub(engineUniqueId, ID); - var testDescriptor = new TestDescriptorStub(engineUniqueId.append("test", "test"), "test"); - engineDescriptor.addChild(testDescriptor); - return engineDescriptor; - } - - @Override - public void execute(ExecutionRequest request) { - this.requestForExecution = request; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java deleted file mode 100644 index 3ef71865..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.fakes; - -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; - -/** - * @since 1.4 - */ -public class TestEngineStub implements TestEngine { - - private final String id; - - public TestEngineStub() { - this(TestEngineStub.class.getSimpleName()); - } - - public TestEngineStub(String id) { - this.id = id; - } - - @Override - public String getId() { - return this.id; - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - return new TestDescriptorStub(UniqueId.forEngine(getId()), getId()); - } - - @Override - public void execute(ExecutionRequest request) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts deleted file mode 100644 index 00198d86..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/junit-platform-jfr.gradle.kts +++ /dev/null @@ -1,31 +0,0 @@ -plugins { - `java-library-conventions` -} - -description = "JUnit Platform Flight Recorder Support" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformLauncher) - - compileOnlyApi(libs.apiguardian) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -javaLibrary { - // --release 8 does not support jdk.jfr even though it was backported - configureRelease = false -} - -tasks { - compileJava { - javaCompiler.set(project.javaToolchains.compilerFor { - languageVersion.set(JavaLanguageVersion.of(8)) - }) - } - compileModule { - options.release.set(11) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java deleted file mode 100644 index d506a562..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.jfr; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; - -import jdk.jfr.Category; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.StackTrace; - -import org.apiguardian.api.API; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.launcher.EngineDiscoveryResult; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; - -/** - * A {@link LauncherDiscoveryListener} that generates Java Flight Recorder - * events. - * - * @since 1.8 - * @see JEP 328: Flight Recorder - */ -@API(status = EXPERIMENTAL, since = "1.8") -public class FlightRecordingDiscoveryListener implements LauncherDiscoveryListener { - - private final AtomicReference launcherDiscoveryEvent = new AtomicReference<>(); - private final Map engineDiscoveryEvents = new ConcurrentHashMap<>(); - - @Override - public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { - LauncherDiscoveryEvent event = new LauncherDiscoveryEvent(); - event.selectors = request.getSelectorsByType(DiscoverySelector.class).size(); - event.filters = request.getFiltersByType(DiscoveryFilter.class).size(); - event.begin(); - launcherDiscoveryEvent.set(event); - } - - @Override - public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { - launcherDiscoveryEvent.getAndSet(null).commit(); - } - - @Override - public void engineDiscoveryStarted(org.junit.platform.engine.UniqueId engineId) { - EngineDiscoveryEvent event = new EngineDiscoveryEvent(); - event.uniqueId = engineId.toString(); - event.begin(); - engineDiscoveryEvents.put(engineId, event); - } - - @Override - public void engineDiscoveryFinished(org.junit.platform.engine.UniqueId engineId, EngineDiscoveryResult result) { - EngineDiscoveryEvent event = engineDiscoveryEvents.remove(engineId); - event.result = result.getStatus().toString(); - event.commit(); - } - - @Category({ "JUnit", "Discovery" }) - @StackTrace(false) - abstract static class DiscoveryEvent extends Event { - } - - @Label("Test Discovery") - @Category({ "JUnit", "Discovery" }) - @Name("org.junit.LauncherDiscovery") - static class LauncherDiscoveryEvent extends DiscoveryEvent { - - @Label("Number of selectors") - int selectors; - - @Label("Number of filters") - int filters; - } - - @Label("Engine Discovery") - @Category({ "JUnit", "Discovery" }) - @Name("org.junit.EngineDiscovery") - static class EngineDiscoveryEvent extends DiscoveryEvent { - - @UniqueId - @Label("Unique Id") - String uniqueId; - - @Label("Result") - String result; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java deleted file mode 100644 index be666602..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.jfr; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; - -import jdk.jfr.Category; -import jdk.jfr.Event; -import jdk.jfr.Label; -import jdk.jfr.Name; -import jdk.jfr.StackTrace; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * A {@link TestExecutionListener} that generates Java Flight Recorder - * events. - * - * @since 1.8 - * @see JEP 328: Flight Recorder - */ -@API(status = EXPERIMENTAL, since = "1.8") -public class FlightRecordingExecutionListener implements TestExecutionListener { - - private final AtomicReference testPlanExecutionEvent = new AtomicReference<>(); - private final Map testExecutionEvents = new ConcurrentHashMap<>(); - - @Override - public void testPlanExecutionStarted(TestPlan plan) { - TestPlanExecutionEvent event = new TestPlanExecutionEvent(); - event.containsTests = plan.containsTests(); - event.engineNames = plan.getRoots().stream().map(TestIdentifier::getDisplayName).collect( - Collectors.joining(", ")); - testPlanExecutionEvent.set(event); - event.begin(); - } - - @Override - public void testPlanExecutionFinished(TestPlan plan) { - testPlanExecutionEvent.getAndSet(null).commit(); - } - - @Override - public void executionSkipped(TestIdentifier test, String reason) { - SkippedTestEvent event = new SkippedTestEvent(); - event.initialize(test); - event.reason = reason; - event.commit(); - } - - @Override - public void executionStarted(TestIdentifier test) { - TestExecutionEvent event = new TestExecutionEvent(); - testExecutionEvents.put(test.getUniqueIdObject(), event); - event.initialize(test); - event.begin(); - } - - @Override - public void executionFinished(TestIdentifier test, TestExecutionResult result) { - Optional throwable = result.getThrowable(); - TestExecutionEvent event = testExecutionEvents.remove(test.getUniqueIdObject()); - event.end(); - event.result = result.getStatus().toString(); - event.exceptionClass = throwable.map(Throwable::getClass).orElse(null); - event.exceptionMessage = throwable.map(Throwable::getMessage).orElse(null); - event.commit(); - } - - @Override - public void reportingEntryPublished(TestIdentifier test, ReportEntry reportEntry) { - for (Map.Entry entry : reportEntry.getKeyValuePairs().entrySet()) { - ReportEntryEvent event = new ReportEntryEvent(); - event.uniqueId = test.getUniqueId(); - event.key = entry.getKey(); - event.value = entry.getValue(); - event.commit(); - } - } - - @Category({ "JUnit", "Execution" }) - @StackTrace(false) - abstract static class ExecutionEvent extends Event { - } - - @Label("Test Execution") - @Name("org.junit.TestPlanExecution") - static class TestPlanExecutionEvent extends ExecutionEvent { - @Label("Contains Tests") - boolean containsTests; - @Label("Engine Names") - String engineNames; - } - - abstract static class TestEvent extends ExecutionEvent { - @UniqueId - @Label("Unique Id") - String uniqueId; - @Label("Display Name") - String displayName; - @Label("Tags") - String tags; - @Label("Type") - String type; - - void initialize(TestIdentifier test) { - this.uniqueId = test.getUniqueId(); - this.displayName = test.getDisplayName(); - this.tags = test.getTags().isEmpty() ? null : test.getTags().toString(); - this.type = test.getType().name(); - } - } - - @Label("Skipped Test") - @Name("org.junit.SkippedTest") - static class SkippedTestEvent extends TestEvent { - @Label("Reason") - String reason; - } - - @Label("Test") - @Name("org.junit.TestExecution") - static class TestExecutionEvent extends TestEvent { - @Label("Result") - String result; - @Label("Exception Class") - Class exceptionClass; - @Label("Exception Message") - String exceptionMessage; - } - - @Label("Report Entry") - @Name("org.junit.ReportEntry") - static class ReportEntryEvent extends ExecutionEvent { - @UniqueId - @Label("Unique Id") - String uniqueId; - @Label("Key") - String key; - @Label("Value") - String value; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java deleted file mode 100644 index 653226e6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.jfr; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import jdk.jfr.MetadataDefinition; -import jdk.jfr.Name; -import jdk.jfr.Relational; - -@MetadataDefinition -@Relational -@Name("org.junit.UniqueId") -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -@interface UniqueId { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java deleted file mode 100644 index 0a8c9813..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Java Flight Recorder support package. - */ - -package org.junit.platform.jfr; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener deleted file mode 100644 index de4e472c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.jfr.FlightRecordingDiscoveryListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener deleted file mode 100644 index 35571863..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.jfr.FlightRecordingExecutionListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java deleted file mode 100644 index 61aad3ec..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Provides Java Flight Recorder events for the JUnit Platform. - * - *

The Flight Recording Listener module implements a - * {@link org.junit.platform.launcher.LauncherDiscoveryListener} and a - * {@link org.junit.platform.launcher.TestExecutionListener} that generate Java - * Flight Recorder (JFR) events. - * - * @see JEP 328: Flight Recorder - * @since 1.7 - */ -module org.junit.platform.jfr { - requires jdk.jfr; - requires static org.apiguardian.api; - requires org.junit.platform.engine; - requires org.junit.platform.launcher; - - provides org.junit.platform.launcher.LauncherDiscoveryListener - with org.junit.platform.jfr.FlightRecordingDiscoveryListener; - provides org.junit.platform.launcher.TestExecutionListener - with org.junit.platform.jfr.FlightRecordingExecutionListener; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts deleted file mode 100644 index d51d0df3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/junit-platform-launcher.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - `java-library-conventions` - `java-test-fixtures` -} - -description = "JUnit Platform Launcher" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformEngine) - - compileOnlyApi(libs.apiguardian) - - osgiVerification(projects.junitJupiterEngine) -} - -tasks { - jar { - bundle { - bnd(""" - Provide-Capability:\ - org.junit.platform.launcher;\ - org.junit.platform.launcher='junit-platform-launcher';\ - version:Version="${'$'}{version_cleanup;${project.version}}" - """) - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java deleted file mode 100644 index 39c70372..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ToStringBuilder; - -/** - * {@code EngineDiscoveryResult} encapsulates the result of test discovery by a - * {@link org.junit.platform.engine.TestEngine}. - * - *

A {@code EngineDiscoveryResult} consists of a mandatory - * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. - * - * @since 1.6 - */ -@API(status = EXPERIMENTAL, since = "1.6") -public class EngineDiscoveryResult { - - /** - * Status of test discovery by a - * {@link org.junit.platform.engine.TestEngine}. - */ - public enum Status { - - /** - * Indicates that test discovery was successful. - */ - SUCCESSFUL, - - /** - * Indicates that test discovery has failed. - */ - FAILED - - } - - private static final EngineDiscoveryResult SUCCESSFUL_RESULT = new EngineDiscoveryResult(Status.SUCCESSFUL, null); - - /** - * Create a {@code EngineDiscoveryResult} for a successful test - * discovery. - * @return the {@code EngineDiscoveryResult}; never {@code null} - */ - public static EngineDiscoveryResult successful() { - return SUCCESSFUL_RESULT; - } - - /** - * Create a {@code EngineDiscoveryResult} for a failed test - * discovery. - * - * @param throwable the throwable that caused the failed discovery; may be - * {@code null} - * @return the {@code EngineDiscoveryResult}; never {@code null} - */ - public static EngineDiscoveryResult failed(Throwable throwable) { - return new EngineDiscoveryResult(Status.FAILED, throwable); - } - - private final Status status; - private final Throwable throwable; - - private EngineDiscoveryResult(Status status, Throwable throwable) { - this.status = status; - this.throwable = throwable; - } - - /** - * Get the {@linkplain Status status} of this result. - * - * @return the status; never {@code null} - */ - public Status getStatus() { - return status; - } - - /** - * Get the throwable that caused this result, if available. - * - * @return an {@code Optional} containing the throwable; never {@code null} - * but potentially empty - */ - public Optional getThrowable() { - return Optional.ofNullable(throwable); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("status", status) - .append("throwable", throwable) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java deleted file mode 100644 index 6013abb2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.engine.FilterResult.includedIf; - -import java.util.Arrays; -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestEngine; - -/** - * An {@code EngineFilter} is applied to all {@link TestEngine TestEngines} - * before they are used. - * - *

Warning: be cautious when registering multiple competing - * {@link #includeEngines include} {@code EngineFilters} or multiple competing - * {@link #excludeEngines exclude} {@code EngineFilters} for the same discovery - * request since doing so will likely lead to undesirable results (i.e., zero - * engines being active). - * - * @since 1.0 - * @see #includeEngines(String...) - * @see #excludeEngines(String...) - * @see LauncherDiscoveryRequest - */ -@API(status = STABLE, since = "1.0") -public class EngineFilter implements Filter { - - /** - * Create a new include {@code EngineFilter} based on the - * supplied engine IDs. - * - *

Only {@code TestEngines} with matching engine IDs will be - * included within the test discovery and execution. - * - * @param engineIds the list of engine IDs to match against; never {@code null} - * or empty; individual IDs must also not be null or blank - * @see #includeEngines(String...) - */ - public static EngineFilter includeEngines(String... engineIds) { - return includeEngines(Arrays.asList(engineIds)); - } - - /** - * Create a new include {@code EngineFilter} based on the - * supplied engine IDs. - * - *

Only {@code TestEngines} with matching engine IDs will be - * included within the test discovery and execution. - * - * @param engineIds the list of engine IDs to match against; never {@code null} - * or empty; individual IDs must also not be null or blank - * @see #includeEngines(String...) - */ - public static EngineFilter includeEngines(List engineIds) { - return new EngineFilter(engineIds, Type.INCLUDE); - } - - /** - * Create a new exclude {@code EngineFilter} based on the - * supplied engine IDs. - * - *

{@code TestEngines} with matching engine IDs will be - * excluded from test discovery and execution. - * - * @param engineIds the list of engine IDs to match against; never {@code null} - * or empty; individual IDs must also not be null or blank - * @see #excludeEngines(List) - */ - public static EngineFilter excludeEngines(String... engineIds) { - return excludeEngines(Arrays.asList(engineIds)); - } - - /** - * Create a new exclude {@code EngineFilter} based on the - * supplied engine IDs. - * - *

{@code TestEngines} with matching engine IDs will be - * excluded from test discovery and execution. - * - * @param engineIds the list of engine IDs to match against; never {@code null} - * or empty; individual IDs must also not be null or blank - * @see #includeEngines(String...) - */ - public static EngineFilter excludeEngines(List engineIds) { - return new EngineFilter(engineIds, Type.EXCLUDE); - } - - private final List engineIds; - private final Type type; - - private EngineFilter(List engineIds, Type type) { - this.engineIds = validateAndTrim(engineIds); - this.type = type; - } - - @API(status = INTERNAL, since = "1.9") - public List getEngineIds() { - return engineIds; - } - - @API(status = INTERNAL, since = "1.9") - public boolean isIncludeFilter() { - return type == Type.INCLUDE; - } - - @Override - public FilterResult apply(TestEngine testEngine) { - Preconditions.notNull(testEngine, "TestEngine must not be null"); - String engineId = testEngine.getId(); - Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); - - if (this.type == Type.INCLUDE) { - return includedIf(this.engineIds.stream().anyMatch(engineId::equals), // - () -> String.format("Engine ID [%s] is in included list [%s]", engineId, this.engineIds), // - () -> String.format("Engine ID [%s] is not in included list [%s]", engineId, this.engineIds)); - } - else { - return includedIf(this.engineIds.stream().noneMatch(engineId::equals), // - () -> String.format("Engine ID [%s] is not in excluded list [%s]", engineId, this.engineIds), // - () -> String.format("Engine ID [%s] is in excluded list [%s]", engineId, this.engineIds)); - } - } - - @Override - public String toString() { - return String.format("%s that %s engines with IDs %s", getClass().getSimpleName(), this.type.verb, - this.engineIds); - } - - private static List validateAndTrim(List engineIds) { - Preconditions.notEmpty(engineIds, "engine ID list must not be null or empty"); - - // @formatter:off - return engineIds.stream() - .map(id -> Preconditions.notBlank(id, "engine ID must not be null or blank").trim()) - .distinct() - .collect(toList()); - // @formatter:on - } - - private enum Type { - - INCLUDE("includes"), - - EXCLUDE("excludes"); - - private final String verb; - - Type(String verb) { - this.verb = verb; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java deleted file mode 100644 index 2c3a96df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; - -/** - * The {@code Launcher} API is the main entry point for client code that - * wishes to discover and execute tests using one or more - * {@linkplain org.junit.platform.engine.TestEngine test engines}. - * - *

Implementations of this interface are responsible for determining - * the set of test engines to delegate to at runtime and for ensuring that - * each test engine has an - * {@linkplain org.junit.platform.engine.TestEngine#getId ID} that is unique - * among the registered test engines. For example, the default implementation - * returned by {@link org.junit.platform.launcher.core.LauncherFactory#create} - * dynamically discovers test engines via Java's {@link java.util.ServiceLoader - * ServiceLoader} mechanism. - * - *

Test discovery and execution require a {@link LauncherDiscoveryRequest} - * that is passed to all registered engines. Each engine decides which tests it - * can discover and execute according to the supplied request. - * - *

Prior to executing tests, clients of this interface should - * {@linkplain #registerTestExecutionListeners register} one or more - * {@link TestExecutionListener} instances in order to get feedback about the - * progress and results of test execution. Listeners will be notified of events - * in the order in which they were registered. The default implementation - * returned by {@link org.junit.platform.launcher.core.LauncherFactory#create} - * dynamically discovers test execution listeners via Java's - * {@link java.util.ServiceLoader ServiceLoader} mechanism. - * - * @since 1.0 - * @see LauncherDiscoveryRequest - * @see TestPlan - * @see TestExecutionListener - * @see org.junit.platform.launcher.core.LauncherFactory - * @see org.junit.platform.engine.TestEngine - */ -@API(status = STABLE, since = "1.0") -public interface Launcher { - - /** - * Register one or more listeners for test discovery. - * - * @param listeners the listeners to be notified of test discovery events; - * never {@code null} or empty - */ - @API(status = EXPERIMENTAL, since = "1.8") - void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners); - - /** - * Register one or more listeners for test execution. - * - * @param listeners the listeners to be notified of test execution events; - * never {@code null} or empty - */ - void registerTestExecutionListeners(TestExecutionListener... listeners); - - /** - * Discover tests and build a {@link TestPlan} according to the supplied - * {@link LauncherDiscoveryRequest} by querying all registered engines and - * collecting their results. - * - * @apiNote This method may be called to generate a preview of the test - * tree. The resulting {@link TestPlan} is unmodifiable and may be passed to - * {@link #execute(TestPlan, TestExecutionListener...)} for execution at - * most once. - * - * @param launcherDiscoveryRequest the launcher discovery request; never - * {@code null} - * @return an unmodifiable {@code TestPlan} that contains all resolved - * {@linkplain TestIdentifier identifiers} from all registered engines - */ - TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest); - - /** - * Execute a {@link TestPlan} which is built according to the supplied - * {@link LauncherDiscoveryRequest} by querying all registered engines and - * collecting their results, and notify - * {@linkplain #registerTestExecutionListeners registered listeners} about - * the progress and results of the execution. - * - *

Supplied test execution listeners are registered in addition to already - * registered listeners but only for the supplied launcher discovery request. - * - * @apiNote Calling this method will cause test discovery to be executed for - * all registered engines. If the same {@link LauncherDiscoveryRequest} was - * previously passed to {@link #discover(LauncherDiscoveryRequest)}, you - * should instead call {@link #execute(TestPlan, TestExecutionListener...)} - * and pass the already acquired {@link TestPlan} to avoid the potential - * performance degradation (e.g., classpath scanning) of running test - * discovery twice. - * - * @param launcherDiscoveryRequest the launcher discovery request; never {@code null} - * @param listeners additional test execution listeners; never {@code null} - */ - void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners); - - /** - * Execute the supplied {@link TestPlan} and notify - * {@linkplain #registerTestExecutionListeners registered listeners} about - * the progress and results of the execution. - * - *

Supplied test execution listeners are registered in addition to - * already registered listeners but only for the execution of the supplied - * test plan. - * - * @apiNote The supplied {@link TestPlan} must not have been executed - * previously. - * - * @param testPlan the test plan to execute; never {@code null} - * @param listeners additional test execution listeners; never {@code null} - * @since 1.4 - */ - @API(status = STABLE, since = "1.4") - void execute(TestPlan testPlan, TestExecutionListener... listeners); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java deleted file mode 100644 index b4f5916f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassNamePatternFilterUtils; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * Collection of constants related to {@link Launcher}. - * - * @since 1.3 - * @see org.junit.platform.engine.ConfigurationParameters - */ -@API(status = STABLE, since = "1.7") -public class LauncherConstants { - - /** - * Property name used to enable capturing output to {@link System#out}: - * {@value} - * - *

By default, output to {@link System#out} is not captured. - * - *

If enabled, the JUnit Platform captures the corresponding output and - * publishes it as a {@link ReportEntry} using the - * {@value #STDOUT_REPORT_ENTRY_KEY} key immediately before reporting the - * test identifier as finished. - * - * @see #STDOUT_REPORT_ENTRY_KEY - * @see ReportEntry - * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) - */ - public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; - - /** - * Property name used to enable capturing output to {@link System#err}: - * {@value} - * - *

By default, output to {@link System#err} is not captured. - * - *

If enabled, the JUnit Platform captures the corresponding output and - * publishes it as a {@link ReportEntry} using the - * {@value #STDERR_REPORT_ENTRY_KEY} key immediately before reporting the - * test identifier as finished. - * - * @see #STDERR_REPORT_ENTRY_KEY - * @see ReportEntry - * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) - */ - public static final String CAPTURE_STDERR_PROPERTY_NAME = "junit.platform.output.capture.stderr"; - - /** - * Property name used to configure the maximum number of bytes for buffering - * to use per thread and output type if output capturing is enabled: - * {@value} - * - *

Value must be an integer; defaults to {@value CAPTURE_MAX_BUFFER_DEFAULT}. - * - * @see #CAPTURE_MAX_BUFFER_DEFAULT - */ - public static final String CAPTURE_MAX_BUFFER_PROPERTY_NAME = "junit.platform.output.capture.maxBuffer"; - - /** - * Default maximum number of bytes for buffering to use per thread and - * output type if output capturing is enabled. - * - * @see #CAPTURE_MAX_BUFFER_PROPERTY_NAME - */ - public static final int CAPTURE_MAX_BUFFER_DEFAULT = 4 * 1024 * 1024; - - /** - * Key used to publish captured output to {@link System#out} as part of a - * {@link ReportEntry}: {@value} - */ - public static final String STDOUT_REPORT_ENTRY_KEY = "stdout"; - - /** - * Key used to publish captured output to {@link System#err} as part of a - * {@link ReportEntry}: {@value} - */ - public static final String STDERR_REPORT_ENTRY_KEY = "stderr"; - - /** - * Property name used to provide patterns for deactivating listeners registered - * via the {@link java.util.ServiceLoader ServiceLoader} mechanism: {@value} - * - *

Pattern Matching Syntax

- * - *

If the property value consists solely of an asterisk ({@code *}), all - * listeners will be deactivated. Otherwise, the property value will be treated - * as a comma-separated list of patterns where each individual pattern will be - * matched against the fully qualified class name (FQCN) of each registered - * listener. Any dot ({@code .}) in a pattern will match against a dot ({@code .}) - * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match - * against one or more characters in a FQCN. All other characters in a pattern - * will be matched one-to-one against a FQCN. - * - *

Examples

- * - *
    - *
  • {@code *}: deactivates all listeners. - *
  • {@code org.junit.*}: deactivates every listener under the {@code org.junit} - * base package and any of its subpackages. - *
  • {@code *.MyListener}: deactivates every listener whose simple class name is - * exactly {@code MyListener}. - *
  • {@code *System*, *Dev*}: deactivates every listener whose FQCN contains - * {@code System} or {@code Dev}. - *
  • {@code org.example.MyListener, org.example.TheirListener}: deactivates - * listeners whose FQCN is exactly {@code org.example.MyListener} or - * {@code org.example.TheirListener}. - *
- * - * @see #DEACTIVATE_ALL_LISTENERS_PATTERN - * @see org.junit.platform.launcher.TestExecutionListener - */ - public static final String DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME = "junit.platform.execution.listeners.deactivate"; - - /** - * Wildcard pattern which signals that all listeners registered via the - * {@link java.util.ServiceLoader ServiceLoader} mechanism should be deactivated: - * {@value} - * - * @see #DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME - * @see org.junit.platform.launcher.TestExecutionListener - */ - public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; - - private LauncherConstants() { - /* no-op */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java deleted file mode 100644 index 90062041..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.platform.engine.EngineDiscoveryListener; -import org.junit.platform.engine.UniqueId; - -/** - * Register a concrete implementation of this interface with a - * {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} or - * {@link Launcher} to be notified of events that occur during test discovery. - * - *

All methods in this interface have empty default implementations. - * Concrete implementations may therefore override one or more of these methods - * to be notified of the selected events. - * - *

JUnit provides default implementations that are created via the factory - * methods in - * {@link org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners}. - * - *

The methods declared in this interface are called by the {@link Launcher} - * created via the {@link org.junit.platform.launcher.core.LauncherFactory} - * during test discovery. - * - * @since 1.6 - * @see org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners - * @see LauncherDiscoveryRequest#getDiscoveryListener() - * @see org.junit.platform.launcher.core.LauncherConfig.Builder#addLauncherDiscoveryListeners - */ -@API(status = EXPERIMENTAL, since = "1.6") -public interface LauncherDiscoveryListener extends EngineDiscoveryListener { - - /** - * No-op implementation of {@code LauncherDiscoveryListener} - */ - LauncherDiscoveryListener NOOP = new LauncherDiscoveryListener() { - }; - - /** - * Called when test discovery is about to be started. - * - * @param request the request for which discovery is being started - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - default void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { - } - - /** - * Called when test discovery has finished. - * - * @param request the request for which discovery has finished - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - default void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { - } - - /** - * Called when test discovery is about to be started for an engine. - * - * @param engineId the unique ID of the engine descriptor - */ - default void engineDiscoveryStarted(UniqueId engineId) { - } - - /** - * Called when test discovery has finished for an engine. - * - *

Exceptions thrown by implementations of this method will cause the - * complete test discovery to be aborted. - * - * @param engineId the unique ID of the engine descriptor - * @param result the discovery result of the supplied engine - * @see EngineDiscoveryResult - */ - default void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java deleted file mode 100644 index 1a91c9cc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.EngineDiscoveryRequest; - -/** - * {@code LauncherDiscoveryRequest} extends the {@link EngineDiscoveryRequest} API - * with additional filters that are applied by the {@link Launcher} itself. - * - *

Specifically, a {@code LauncherDiscoveryRequest} contains the following. - * - *

    - *
  • {@linkplain EngineFilter Engine Filters}: filters that are applied before - * each {@code TestEngine} is executed. All of them have to include an engine for it - * to contribute to the test plan.
  • - *
  • {@linkplain ConfigurationParameters Configuration Parameters}: configuration - * parameters that can be used to influence the discovery process
  • - *
  • {@linkplain DiscoverySelector Discovery Selectors}: components that select - * resources that a {@code TestEngine} can use to discover tests
  • - *
  • {@linkplain DiscoveryFilter Discovery Filters}: filters that should be applied - * by {@code TestEngines} during test discovery. All of them have to include a - * resource for it to end up in the test plan.
  • - *
  • {@linkplain PostDiscoveryFilter Post-Discovery Filters}: filters that will be - * applied by the {@code Launcher} after {@code TestEngines} have performed test - * discovery. All of them have to include a {@code TestDescriptor} for it to end up - * in the test plan.
  • - *
- * - * @since 1.0 - * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder - * @see EngineDiscoveryRequest - * @see EngineFilter - * @see ConfigurationParameters - * @see DiscoverySelector - * @see DiscoveryFilter - * @see PostDiscoveryFilter - * @see #getEngineFilters() - * @see #getPostDiscoveryFilters() - */ -@API(status = STABLE, since = "1.0") -public interface LauncherDiscoveryRequest extends EngineDiscoveryRequest { - - /** - * Get the {@code EngineFilters} for this request. - * - *

The returned filters are to be combined using AND semantics, i.e. all - * of them have to include an engine for it to contribute to the test plan. - * - * @return the list of {@code EngineFilters} for this request; never - * {@code null} but potentially empty - */ - List getEngineFilters(); - - /** - * Get the {@code PostDiscoveryFilters} for this request. - * - *

The returned filters are to be combined using AND semantics, i.e. all - * of them have to include a {@code TestDescriptor} for it to end up in the - * test plan. - * - * @return the list of {@code PostDiscoveryFilters} for this request; never - * {@code null} but potentially empty - */ - List getPostDiscoveryFilters(); - - /** - * Get the {@link LauncherDiscoveryListener} for this request. - * - *

The default implementation returns a no-op listener that ignores all - * calls so that engines that call this methods can be used with an earlier - * version of the JUnit Platform that did not yet include it. - * - * @return the discovery listener; never {@code null} - * @since 1.6 - */ - @API(status = EXPERIMENTAL, since = "1.6") - @Override - default LauncherDiscoveryListener getDiscoveryListener() { - return LauncherDiscoveryListener.NOOP; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java deleted file mode 100644 index b8e71c8d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.platform.launcher.core.LauncherFactory; - -/** - * The {@code LauncherSession} API is the main entry point for client code that - * wishes to repeatedly discover and execute tests using one - * or more {@linkplain org.junit.platform.engine.TestEngine test engines}. - * - * @since 1.8 - * @see Launcher - * @see LauncherSessionListener - * @see LauncherFactory - */ -@API(status = EXPERIMENTAL, since = "1.8") -public interface LauncherSession extends AutoCloseable { - - /** - * Get the {@link Launcher} associated with this session. - * - *

Any call to the launcher returned by this method after the session has - * been closed will throw an exception. - */ - Launcher getLauncher(); - - /** - * Close this session and notify all registered - * {@link LauncherSessionListener LauncherSessionListeners}. - * - * @apiNote The behavior of calling this method concurrently with any call - * to the {@link Launcher} returned by {@link #getLauncher()} is currently - * undefined. - */ - @Override - void close(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java deleted file mode 100644 index 96af1643..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import org.apiguardian.api.API; -import org.junit.platform.launcher.core.LauncherConfig; -import org.junit.platform.launcher.core.LauncherFactory; - -/** - * Register an implementation of this interface to be notified when a - * {@link LauncherSession} is opened and closed. - * - *

A {@code LauncherSessionListener} can be registered programmatically with - * the {@link LauncherConfig.Builder#addLauncherSessionListeners LauncherConfig} - * passed to the - * {@link LauncherFactory#openSession(LauncherConfig) LauncherFactory} or - * automatically via Java's {@link java.util.ServiceLoader ServiceLoader} - * mechanism. - * - *

All methods in this class have empty default implementations. - * Subclasses may therefore override one or more of these methods to be notified - * of the selected events. - * - *

The methods declared in this interface are called by the {@link Launcher} - * or {@link LauncherSession} created via the {@link LauncherFactory}. - * - * @since 1.8 - * @see LauncherSession - * @see LauncherConfig.Builder#addLauncherSessionListeners - * @see LauncherFactory - */ -@API(status = EXPERIMENTAL, since = "1.8") -public interface LauncherSessionListener { - - /** - * No-op implementation of {@code LauncherSessionListener} - */ - LauncherSessionListener NOOP = new LauncherSessionListener() { - }; - - /** - * Called when a launcher session was opened. - * - * @param session the opened session - */ - default void launcherSessionOpened(LauncherSession session) { - } - - /** - * Called when a launcher session was closed. - * - * @param session the closed session - */ - default void launcherSessionClosed(LauncherSession session) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java deleted file mode 100644 index 7c31d55c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; - -/** - * A {@code PostDiscoveryFilter} is applied to {@link TestDescriptor TestDescriptors} - * after test discovery. - * - *

A {@code PostDiscoveryFilter} must not modify the - * {@link TestDescriptor TestDescriptors} it is applied to in any way. - * - *

{@link TestEngine TestEngines} must not apply - * {@code PostDiscoveryFilters} during the test discovery phase. - * - * @since 1.0 - * @see LauncherDiscoveryRequest - * @see TestEngine - */ -@API(status = STABLE, since = "1.0") -public interface PostDiscoveryFilter extends Filter { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java deleted file mode 100644 index 7389aa53..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static java.util.Arrays.asList; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; - -import java.util.List; -import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestTag; -import org.junit.platform.launcher.tagexpression.TagExpression; - -/** - * Factory methods for creating {@link PostDiscoveryFilter PostDiscoveryFilters} - * based on included and excluded tags or tag expressions. - * - *

Tag expressions are boolean expressions with the following allowed - * operators: {@code !} (not), {@code &} (and), and {@code |} (or). Parentheses - * can be used to adjust for operator precedence. Please refer to the - * JUnit 5 User Guide - * for usage examples. - * - *

Please note that a tag name is a valid tag expression. Thus, wherever a tag - * expression can be used, a single tag name can also be used. - * - * @since 1.0 - * @see #includeTags(String...) - * @see #excludeTags(String...) - * @see TestTag - */ -@API(status = STABLE, since = "1.0") -public final class TagFilter { - - private TagFilter() { - /* no-op */ - } - - /** - * Create an include filter based on the supplied tag expressions. - * - *

Containers and tests will only be executed if their tags match at - * least one of the supplied included tag expressions. - * - * @param tagExpressions the included tag expressions; never {@code null} or - * empty - * @throws PreconditionViolationException if the supplied tag expressions - * array is {@code null} or empty, or if any individual tag expression is - * not syntactically valid - * @see #includeTags(List) - * @see TestTag#isValid(String) - */ - public static PostDiscoveryFilter includeTags(String... tagExpressions) throws PreconditionViolationException { - Preconditions.notNull(tagExpressions, "array of tag expressions must not be null"); - return includeTags(asList(tagExpressions)); - } - - /** - * Create an include filter based on the supplied tag expressions. - * - *

Containers and tests will only be executed if their tags match at - * least one of the supplied included tag expressions. - * - * @param tagExpressions the included tag expressions; never {@code null} or - * empty - * @throws PreconditionViolationException if the supplied tag expressions - * array is {@code null} or empty, or if any individual tag expression is - * not syntactically valid - * @see #includeTags(String...) - * @see TestTag#isValid(String) - */ - public static PostDiscoveryFilter includeTags(List tagExpressions) throws PreconditionViolationException { - Preconditions.notEmpty(tagExpressions, "list of tag expressions must not be null or empty"); - return includeMatching(tagExpressions); - } - - /** - * Create an exclude filter based on the supplied tag expressions. - * - *

Containers and tests will only be executed if their tags do - * not match any of the supplied excluded tag expressions. - * - * @param tagExpressions the excluded tag expressions; never {@code null} or - * empty - * @throws PreconditionViolationException if the supplied tag expressions - * array is {@code null} or empty, or if any individual tag expression is - * not syntactically valid - * @see #excludeTags(List) - * @see TestTag#isValid(String) - */ - public static PostDiscoveryFilter excludeTags(String... tagExpressions) throws PreconditionViolationException { - Preconditions.notNull(tagExpressions, "array of tag expressions must not be null"); - return excludeTags(asList(tagExpressions)); - } - - /** - * Create an exclude filter based on the supplied tag expressions. - * - *

Containers and tests will only be executed if their tags do - * not match any of the supplied excluded tag expressions. - * - * @param tagExpressions the excluded tag expressions; never {@code null} or - * empty - * @throws PreconditionViolationException if the supplied tag expressions - * array is {@code null} or empty, or if any individual tag expression is - * not syntactically valid - * @see #excludeTags(String...) - * @see TestTag#isValid(String) - */ - public static PostDiscoveryFilter excludeTags(List tagExpressions) throws PreconditionViolationException { - Preconditions.notEmpty(tagExpressions, "list of tag expressions must not be null or empty"); - return excludeMatching(tagExpressions); - } - - private static PostDiscoveryFilter includeMatching(List tagExpressions) { - Supplier inclusionReason = () -> inclusionReasonExpressionSatisfy(tagExpressions); - Supplier exclusionReason = () -> exclusionReasonExpressionNotSatisfy(tagExpressions); - List parsedTagExpressions = parseAll(tagExpressions); - return descriptor -> { - Set tags = descriptor.getTags(); - boolean included = parsedTagExpressions.stream().anyMatch(expression -> expression.evaluate(tags)); - - return FilterResult.includedIf(included, inclusionReason, exclusionReason); - }; - } - - private static String inclusionReasonExpressionSatisfy(List tagExpressions) { - return String.format("included because tags match expression(s): [%s]", formatToString(tagExpressions)); - } - - private static String exclusionReasonExpressionNotSatisfy(List tagExpressions) { - return String.format("excluded because tags do not match tag expression(s): [%s]", - formatToString(tagExpressions)); - } - - private static PostDiscoveryFilter excludeMatching(List tagExpressions) { - Supplier inclusionReason = () -> inclusionReasonExpressionNotSatisfy(tagExpressions); - Supplier exclusionReason = () -> exclusionReasonExpressionSatisfy(tagExpressions); - List parsedTagExpressions = parseAll(tagExpressions); - return descriptor -> { - Set tags = descriptor.getTags(); - boolean included = parsedTagExpressions.stream().noneMatch(expression -> expression.evaluate(tags)); - - return FilterResult.includedIf(included, inclusionReason, exclusionReason); - }; - } - - private static String inclusionReasonExpressionNotSatisfy(List tagExpressions) { - return String.format("included because tags do not match expression(s): [%s]", formatToString(tagExpressions)); - } - - private static String exclusionReasonExpressionSatisfy(List tagExpressions) { - return String.format("excluded because tags match tag expression(s): [%s]", formatToString(tagExpressions)); - } - - private static String formatToString(List tagExpressions) { - return tagExpressions.stream().map(String::trim).sorted().collect(Collectors.joining(",")); - } - - private static List parseAll(List tagExpressions) { - return tagExpressions.stream().map(TagFilter::parse).collect(toUnmodifiableList()); - } - - private static TagExpression parse(String tagExpression) { - return TagExpression.parseFrom(tagExpression).tagExpressionOrThrow( - message -> new PreconditionViolationException( - "Unable to parse tag expression \"" + tagExpression + "\": " + message)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java deleted file mode 100644 index fed40da4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.apiguardian.api.API.Status.STABLE; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestExecutionResult.Status; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * Register a concrete implementation of this interface with a {@link Launcher} - * to be notified of events that occur during test execution. - * - *

All methods in this interface have empty default implementations. - * Concrete implementations may therefore override one or more of these methods - * to be notified of the selected events. - * - *

JUnit provides two example implementations. - * - *

    - *
  • {@link org.junit.platform.launcher.listeners.LoggingListener}
  • - *
  • {@link org.junit.platform.launcher.listeners.SummaryGeneratingListener}
  • - *
- * - *

Contrary to JUnit 4, {@linkplain org.junit.platform.engine.TestEngine test engines} - * are supposed to report events not only for {@linkplain TestIdentifier identifiers} - * that represent executable leaves in the {@linkplain TestPlan test plan} but also - * for all intermediate containers. However, while both the JUnit Vintage and JUnit - * Jupiter engines comply with this contract, there is no way to guarantee this for - * third-party engines. - * - *

As of JUnit Platform 1.8, a {@code TestExecutionListener} can access - * {@linkplain org.junit.platform.engine.ConfigurationParameters configuration - * parameters} via the {@link TestPlan#getConfigurationParameters() - * getConfigurationParameters()} method in the {@code TestPlan} supplied to - * {@link #testPlanExecutionStarted(TestPlan)} and - * {@link #testPlanExecutionFinished(TestPlan)}. - * - *

Note on concurrency: {@link #testPlanExecutionStarted(TestPlan)} and - * {@link #testPlanExecutionFinished(TestPlan)} are always called from the same - * thread. It is safe to assume that there is at most one {@code TestPlan} - * instance at a time. All other methods could be called from different threads - * concurrently in case one or multiple test engines execute tests in parallel. - * - * @since 1.0 - * @see Launcher - * @see TestPlan - * @see TestIdentifier - */ -@API(status = STABLE, since = "1.0") -public interface TestExecutionListener { - - /** - * Called when the execution of the {@link TestPlan} has started, - * before any test has been executed. - * - *

Called from the same thread as {@link #testPlanExecutionFinished(TestPlan)}. - * - * @param testPlan describes the tree of tests about to be executed - */ - default void testPlanExecutionStarted(TestPlan testPlan) { - } - - /** - * Called when the execution of the {@link TestPlan} has finished, - * after all tests have been executed. - * - *

Called from the same thread as {@link #testPlanExecutionStarted(TestPlan)}. - * - * @param testPlan describes the tree of tests that have been executed - */ - default void testPlanExecutionFinished(TestPlan testPlan) { - } - - /** - * Called when a new, dynamic {@link TestIdentifier} has been registered. - * - *

A dynamic test is a test that is not known a-priori and - * therefore not contained in the original {@link TestPlan}. - * - * @param testIdentifier the identifier of the newly registered test - * or container - */ - default void dynamicTestRegistered(TestIdentifier testIdentifier) { - } - - /** - * Called when the execution of a leaf or subtree of the {@link TestPlan} - * has been skipped. - * - *

The {@link TestIdentifier} may represent a test or a container. In - * the case of a container, no listener methods will be called for any of - * its descendants. - * - *

A skipped test or subtree of tests will never be reported as - * {@linkplain #executionStarted started} or - * {@linkplain #executionFinished finished}. - * - * @param testIdentifier the identifier of the skipped test or container - * @param reason a human-readable message describing why the execution - * has been skipped - */ - default void executionSkipped(TestIdentifier testIdentifier, String reason) { - } - - /** - * Called when the execution of a leaf or subtree of the {@link TestPlan} - * is about to be started. - * - *

The {@link TestIdentifier} may represent a test or a container. - * - *

This method will only be called if the test or container has not - * been {@linkplain #executionSkipped skipped}. - * - *

This method will be called for a container {@code TestIdentifier} - * before {@linkplain #executionStarted starting} or - * {@linkplain #executionSkipped skipping} any of its children. - * - * @param testIdentifier the identifier of the started test or container - */ - default void executionStarted(TestIdentifier testIdentifier) { - } - - /** - * Called when the execution of a leaf or subtree of the {@link TestPlan} - * has finished, regardless of the outcome. - * - *

The {@link TestIdentifier} may represent a test or a container. - * - *

This method will only be called if the test or container has not - * been {@linkplain #executionSkipped skipped}. - * - *

This method will be called for a container {@code TestIdentifier} - * after all of its children have been - * {@linkplain #executionSkipped skipped} or have - * {@linkplain #executionFinished finished}. - * - *

The {@link TestExecutionResult} describes the result of the execution - * for the supplied {@code TestIdentifier}. The result does not include or - * aggregate the results of its children. For example, a container with a - * failing test will be reported as {@link Status#SUCCESSFUL SUCCESSFUL} even - * if one or more of its children are reported as {@link Status#FAILED FAILED}. - * - * @param testIdentifier the identifier of the finished test or container - * @param testExecutionResult the (unaggregated) result of the execution for - * the supplied {@code TestIdentifier} - * - * @see TestExecutionResult - */ - default void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - } - - /** - * Called when additional test reporting data has been published for - * the supplied {@link TestIdentifier}. - * - *

Can be called at any time during the execution of a test plan. - * - * @param testIdentifier describes the test or container to which the entry pertains - * @param entry the published {@code ReportEntry} - */ - default void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java deleted file mode 100644 index 20eaf356..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.unmodifiableSet; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.ObjectStreamClass; -import java.io.ObjectStreamField; -import java.io.Serializable; -import java.util.LinkedHashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestDescriptor.Type; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; - -/** - * Immutable data transfer object that represents a test or container which is - * usually part of a {@link TestPlan}. - * - * @since 1.0 - * @see TestPlan - */ -@API(status = STABLE, since = "1.0") -public final class TestIdentifier implements Serializable { - - private static final long serialVersionUID = 1L; - private static final ObjectStreamField[] serialPersistentFields = ObjectStreamClass.lookup( - SerializedForm.class).getFields(); - - // These are effectively final but not technically due to late initialization when deserializing - private /* final */ UniqueId uniqueId; - private /* final */ UniqueId parentId; - private /* final */ String displayName; - private /* final */ String legacyReportingName; - private /* final */ TestSource source; - private /* final */ Set tags; - private /* final */ Type type; - - /** - * Factory for creating a new {@link TestIdentifier} from a {@link TestDescriptor}. - */ - @API(status = INTERNAL, since = "1.0") - public static TestIdentifier from(TestDescriptor testDescriptor) { - Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); - UniqueId uniqueId = testDescriptor.getUniqueId(); - String displayName = testDescriptor.getDisplayName(); - TestSource source = testDescriptor.getSource().orElse(null); - Set tags = testDescriptor.getTags(); - Type type = testDescriptor.getType(); - UniqueId parentId = testDescriptor.getParent().map(TestDescriptor::getUniqueId).orElse(null); - String legacyReportingName = testDescriptor.getLegacyReportingName(); - return new TestIdentifier(uniqueId, displayName, source, tags, type, parentId, legacyReportingName); - } - - private TestIdentifier(UniqueId uniqueId, String displayName, TestSource source, Set tags, Type type, - UniqueId parentId, String legacyReportingName) { - Preconditions.notNull(type, "TestDescriptor.Type must not be null"); - this.uniqueId = uniqueId; - this.parentId = parentId; - this.displayName = displayName; - this.source = source; - this.tags = copyOf(tags); - this.type = type; - this.legacyReportingName = legacyReportingName; - } - - private Set copyOf(Set tags) { - switch (tags.size()) { - case 0: - return emptySet(); - case 1: - return singleton(getOnlyElement(tags)); - default: - return new LinkedHashSet<>(tags); - } - } - - /** - * Get the unique ID of the represented test or container as a - * {@code String}. - * - *

Uniqueness must be guaranteed across an entire - * {@linkplain TestPlan test plan}, regardless of how many engines are used - * behind the scenes. - * - * @return the unique ID for this identifier; never {@code null} - */ - public String getUniqueId() { - return this.uniqueId.toString(); - } - - /** - * Get the unique ID of the represented test or container as a - * {@code UniqueId}. - * - *

Uniqueness must be guaranteed across an entire - * {@linkplain TestPlan test plan}, regardless of how many engines are used - * behind the scenes. - * - * @return the unique ID for this identifier; never {@code null} - * @since 5.8 - */ - @API(status = STABLE, since = "5.8") - public UniqueId getUniqueIdObject() { - return this.uniqueId; - } - - /** - * Get the unique ID of this identifier's parent as a {@code String}, if - * available. - * - *

An identifier without a parent is called a root. - * - * @return a container for the unique ID for this identifier's parent; - * never {@code null} though potentially empty - */ - public Optional getParentId() { - return getParentIdObject().map(UniqueId::toString); - } - - /** - * Get the unique ID of this identifier's parent as a {@code UniqueId}, if - * available. - * - *

An identifier without a parent is called a root. - * - * @return a container for the unique ID for this identifier's parent; - * never {@code null} though potentially empty - * @since 5.8 - */ - @API(status = STABLE, since = "5.8") - public Optional getParentIdObject() { - return Optional.ofNullable(this.parentId); - } - - /** - * Get the display name of the represented test or container. - * - *

A display name is a human-readable name for a test or - * container that is typically used for test reporting in IDEs and build - * tools. Display names may contain spaces, special characters, and emoji, - * and the format may be customized by {@link org.junit.platform.engine.TestEngine - * TestEngines} or potentially by end users as well. Consequently, display - * names should never be parsed; rather, they should be used for display - * purposes only. - * - * @return the display name for this identifier; never {@code null} or blank - * @see #getSource() - * @see org.junit.platform.engine.TestDescriptor#getDisplayName() - */ - public String getDisplayName() { - return this.displayName; - } - - /** - * Get the name of this identifier in a format that is suitable for legacy - * reporting infrastructure — for example, for reporting systems built - * on the Ant-based XML reporting format for JUnit 4. - * - *

The default implementation delegates to {@link #getDisplayName()}. - * - * @return the legacy reporting name; never {@code null} or blank - * @see org.junit.platform.engine.TestDescriptor#getLegacyReportingName() - * @see org.junit.platform.reporting.legacy.LegacyReportingUtils - */ - @SuppressWarnings("JavadocReference") - public String getLegacyReportingName() { - return this.legacyReportingName; - } - - /** - * Get the underlying descriptor type. - * - * @return the underlying descriptor type; never {@code null} - */ - public Type getType() { - return type; - } - - /** - * Determine if this identifier represents a test. - * - * @return {@code true} if the underlying descriptor type represents a test, - * {@code false} otherwise - * @see Type#isTest() - */ - public boolean isTest() { - return getType().isTest(); - } - - /** - * Determine if this identifier represents a container. - * - * @return {@code true} if the underlying descriptor type represents a container, - * {@code false} otherwise - * @see Type#isContainer() - */ - public boolean isContainer() { - return getType().isContainer(); - } - - /** - * Get the {@linkplain TestSource source} of the represented test - * or container, if available. - * - * @see TestSource - */ - public Optional getSource() { - return Optional.ofNullable(this.source); - } - - /** - * Get the set of {@linkplain TestTag tags} associated with the represented - * test or container. - * - * @see TestTag - */ - public Set getTags() { - return unmodifiableSet(this.tags); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof TestIdentifier) { - TestIdentifier that = (TestIdentifier) obj; - return Objects.equals(this.uniqueId, that.uniqueId); - } - return false; - } - - @Override - public int hashCode() { - return this.uniqueId.hashCode(); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("uniqueId", this.uniqueId) - .append("parentId", this.parentId) - .append("displayName", this.displayName) - .append("legacyReportingName", this.legacyReportingName) - .append("source", this.source) - .append("tags", this.tags) - .append("type", this.type) - .toString(); - // @formatter:on - } - - private void writeObject(ObjectOutputStream s) throws IOException { - SerializedForm serializedForm = new SerializedForm(this); - serializedForm.serialize(s); - } - - private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { - SerializedForm serializedForm = SerializedForm.deserialize(s); - uniqueId = UniqueId.parse(serializedForm.uniqueId); - displayName = serializedForm.displayName; - source = serializedForm.source; - tags = serializedForm.tags; - type = serializedForm.type; - parentId = UniqueId.parse(serializedForm.parentId); - legacyReportingName = serializedForm.legacyReportingName; - } - - /** - * Represents the serialized output of {@code TestIdentifier}. The fields on this - * class match the fields that {@code TestIdentifier} had prior to 5.8. - */ - private static class SerializedForm implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String uniqueId; - private final String parentId; - private final String displayName; - private final String legacyReportingName; - private final TestSource source; - @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (see TestIdentifier#copyOf()) - private final Set tags; - private final Type type; - - SerializedForm(TestIdentifier testIdentifier) { - this.uniqueId = testIdentifier.uniqueId.toString(); - this.parentId = testIdentifier.parentId.toString(); - this.displayName = testIdentifier.displayName; - this.legacyReportingName = testIdentifier.legacyReportingName; - this.source = testIdentifier.source; - this.tags = testIdentifier.tags; - this.type = testIdentifier.type; - } - - @SuppressWarnings("unchecked") - private SerializedForm(ObjectInputStream.GetField fields) throws IOException { - this.uniqueId = (String) fields.get("uniqueId", null); - this.parentId = (String) fields.get("parentId", null); - this.displayName = (String) fields.get("displayName", null); - this.legacyReportingName = (String) fields.get("legacyReportingName", null); - this.source = (TestSource) fields.get("source", null); - this.tags = (Set) fields.get("tags", null); - this.type = (Type) fields.get("type", null); - } - - void serialize(ObjectOutputStream s) throws IOException { - ObjectOutputStream.PutField fields = s.putFields(); - fields.put("uniqueId", uniqueId); - fields.put("parentId", parentId); - fields.put("displayName", displayName); - fields.put("legacyReportingName", legacyReportingName); - fields.put("source", source); - fields.put("tags", tags); - fields.put("type", type); - s.writeFields(); - } - - static SerializedForm deserialize(ObjectInputStream s) throws ClassNotFoundException, IOException { - ObjectInputStream.GetField fields = s.readFields(); - return new SerializedForm(fields); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java deleted file mode 100644 index 0557b865..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static java.util.Collections.emptySet; -import static java.util.Collections.synchronizedSet; -import static java.util.Collections.unmodifiableSet; -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestDescriptor.Visitor; -import org.junit.platform.engine.UniqueId; - -/** - * {@code TestPlan} describes the tree of tests and containers as discovered - * by a {@link Launcher}. - * - *

Tests and containers are represented by {@link TestIdentifier} instances. - * The complete set of identifiers comprises a tree-like structure. However, - * each identifier only stores the unique ID of its parent. This class provides - * a number of helpful methods to retrieve the - * {@linkplain #getParent(TestIdentifier) parent}, - * {@linkplain #getChildren(TestIdentifier) children}, and - * {@linkplain #getDescendants(TestIdentifier) descendants} of an identifier. - * - *

While the contained instances of {@link TestIdentifier} are immutable, - * instances of this class contain mutable state. For example, when a dynamic - * test is registered at runtime, it is added to the original test plan and - * reported to {@link TestExecutionListener} implementations. - * - *

This class is not intended to be extended by clients. - * - * @since 1.0 - * @see Launcher - * @see TestExecutionListener - */ -@API(status = STABLE, since = "1.0") -public class TestPlan { - - private final Set roots = synchronizedSet(new LinkedHashSet<>(4)); - - private final Map> children = new ConcurrentHashMap<>(32); - - private final Map allIdentifiers = new ConcurrentHashMap<>(32); - - private final boolean containsTests; - - private final ConfigurationParameters configurationParameters; - - /** - * Construct a new {@code TestPlan} from the supplied collection of - * {@link TestDescriptor TestDescriptors}. - * - *

Each supplied {@code TestDescriptor} is expected to be a descriptor - * for a {@link org.junit.platform.engine.TestEngine TestEngine}. - * - * @param engineDescriptors the engine test descriptors from which the test - * plan should be created; never {@code null} - * @param configurationParameters the {@code ConfigurationParameters} for - * this test plan; never {@code null} - * @return a new test plan - */ - @API(status = INTERNAL, since = "1.0") - public static TestPlan from(Collection engineDescriptors, - ConfigurationParameters configurationParameters) { - Preconditions.notNull(engineDescriptors, "Cannot create TestPlan from a null collection of TestDescriptors"); - Preconditions.notNull(configurationParameters, "Cannot create TestPlan from null ConfigurationParameters"); - TestPlan testPlan = new TestPlan(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests), - configurationParameters); - Visitor visitor = descriptor -> testPlan.addInternal(TestIdentifier.from(descriptor)); - engineDescriptors.forEach(engineDescriptor -> engineDescriptor.accept(visitor)); - return testPlan; - } - - @API(status = INTERNAL, since = "1.4") - protected TestPlan(boolean containsTests, ConfigurationParameters configurationParameters) { - this.containsTests = containsTests; - this.configurationParameters = configurationParameters; - } - - /** - * Add the supplied {@link TestIdentifier} to this test plan. - * - * @param testIdentifier the identifier to add; never {@code null} - * @deprecated Calling this method is no longer supported and will throw an - * exception. - * @throws JUnitException always - */ - @Deprecated - @API(status = DEPRECATED, since = "1.4") - public void add(@SuppressWarnings("unused") TestIdentifier testIdentifier) { - throw new JUnitException("Unsupported attempt to modify the TestPlan was detected. " - + "Please contact your IDE/tool vendor and request a fix or downgrade to JUnit 5.7.x (see https://github.com/junit-team/junit5/issues/1732 for details)."); - } - - @API(status = INTERNAL, since = "1.8") - public void addInternal(TestIdentifier testIdentifier) { - Preconditions.notNull(testIdentifier, "testIdentifier must not be null"); - allIdentifiers.put(testIdentifier.getUniqueIdObject(), testIdentifier); - - // Root identifiers. Typically, a test engine. - if (!testIdentifier.getParentIdObject().isPresent()) { - roots.add(testIdentifier); - return; - } - - // Identifiers without a parent in this test plan. Could be a test - // engine that is used in a suite. - UniqueId parentId = testIdentifier.getParentIdObject().get(); - if (!allIdentifiers.containsKey(parentId)) { - roots.add(testIdentifier); - return; - } - - Set directChildren = children.computeIfAbsent(parentId, - key -> synchronizedSet(new LinkedHashSet<>(16))); - directChildren.add(testIdentifier); - } - - /** - * Get the root {@link TestIdentifier TestIdentifiers} for this test plan. - * - * @return an unmodifiable set of the root identifiers - */ - public Set getRoots() { - return unmodifiableSet(roots); - } - - /** - * Get the parent of the supplied {@link TestIdentifier}. - * - * @param child the identifier to look up the parent for; never {@code null} - * @return an {@code Optional} containing the parent, if present - */ - public Optional getParent(TestIdentifier child) { - Preconditions.notNull(child, "child must not be null"); - return child.getParentIdObject().map(this::getTestIdentifier); - } - - /** - * Get the children of the supplied {@link TestIdentifier}. - * - * @param parent the identifier to look up the children for; never {@code null} - * @return an unmodifiable set of the parent's children, potentially empty - * @see #getChildren(UniqueId) - */ - public Set getChildren(TestIdentifier parent) { - Preconditions.notNull(parent, "parent must not be null"); - return getChildren(parent.getUniqueIdObject()); - } - - /** - * Get the children of the supplied unique ID. - * - * @param parentId the unique ID to look up the children for; never - * {@code null} or blank - * @return an unmodifiable set of the parent's children, potentially empty - * @see #getChildren(TestIdentifier) - * @deprecated Use {@link #getChildren(UniqueId)} - */ - @API(status = DEPRECATED, since = "1.10") - @Deprecated - public Set getChildren(String parentId) { - Preconditions.notBlank(parentId, "parent ID must not be null or blank"); - return getChildren(UniqueId.parse(parentId)); - } - - /** - * Get the children of the supplied unique ID. - * - * @param parentId the unique ID to look up the children for; never - * {@code null} - * @return an unmodifiable set of the parent's children, potentially empty - * @see #getChildren(TestIdentifier) - */ - @API(status = MAINTAINED, since = "1.10") - public Set getChildren(UniqueId parentId) { - return children.containsKey(parentId) ? unmodifiableSet(children.get(parentId)) : emptySet(); - } - - /** - * Get the {@link TestIdentifier} with the supplied unique ID. - * - * @param uniqueId the unique ID to look up the identifier for; never - * {@code null} or blank - * @return the identifier with the supplied unique ID; never {@code null} - * @throws PreconditionViolationException if no {@code TestIdentifier} - * with the supplied unique ID is present in this test plan - * @deprecated Use {@link #getTestIdentifier(UniqueId)} - */ - @API(status = DEPRECATED, since = "1.10") - @Deprecated - public TestIdentifier getTestIdentifier(String uniqueId) throws PreconditionViolationException { - Preconditions.notBlank(uniqueId, "unique ID must not be null or blank"); - return getTestIdentifier(UniqueId.parse(uniqueId)); - } - - /** - * Get the {@link TestIdentifier} with the supplied unique ID. - * - * @param uniqueId the unique ID to look up the identifier for; never - * {@code null} - * @return the identifier with the supplied unique ID; never {@code null} - * @throws PreconditionViolationException if no {@code TestIdentifier} - * with the supplied unique ID is present in this test plan - */ - @API(status = MAINTAINED, since = "1.10") - public TestIdentifier getTestIdentifier(UniqueId uniqueId) { - Preconditions.notNull(uniqueId, () -> "uniqueId must not be null"); - return Preconditions.notNull(allIdentifiers.get(uniqueId), - () -> "No TestIdentifier with unique ID [" + uniqueId + "] has been added to this TestPlan."); - } - - /** - * Count all {@link TestIdentifier TestIdentifiers} that satisfy the - * given {@linkplain Predicate predicate}. - * - * @param predicate a predicate which returns {@code true} for identifiers - * to be counted; never {@code null} - * @return the number of identifiers that satisfy the supplied predicate - */ - public long countTestIdentifiers(Predicate predicate) { - Preconditions.notNull(predicate, "Predicate must not be null"); - return allIdentifiers.values().stream().filter(predicate).count(); - } - - /** - * Get all descendants of the supplied {@link TestIdentifier} (i.e., - * all of its children and their children, recursively). - * - * @param parent the identifier to look up the descendants for; never {@code null} - * @return an unmodifiable set of the parent's descendants, potentially empty - */ - public Set getDescendants(TestIdentifier parent) { - Preconditions.notNull(parent, "parent must not be null"); - Set result = new LinkedHashSet<>(16); - Set children = getChildren(parent); - result.addAll(children); - for (TestIdentifier child : children) { - result.addAll(getDescendants(child)); - } - return unmodifiableSet(result); - } - - /** - * Return whether this test plan contains any tests. - * - *

A test plan contains tests, if at least one of the contained engine - * descriptors {@linkplain TestDescriptor#containsTests(TestDescriptor) - * contains tests}. - * - * @return {@code true} if this test plan contains tests - * @see TestDescriptor#containsTests(TestDescriptor) - */ - public boolean containsTests() { - return containsTests; - } - - /** - * Get the {@link ConfigurationParameters} for this test plan. - * - * @return the configuration parameters; never {@code null} - * @since 1.8 - */ - @API(status = MAINTAINED, since = "1.8") - public ConfigurationParameters getConfigurationParameters() { - return this.configurationParameters; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java deleted file mode 100644 index fc3cf561..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; - -class CompositeEngineExecutionListener implements EngineExecutionListener { - - private static final Logger logger = LoggerFactory.getLogger(CompositeEngineExecutionListener.class); - - private final List engineExecutionListeners; - - CompositeEngineExecutionListener(List engineExecutionListeners) { - this.engineExecutionListeners = new ArrayList<>(engineExecutionListeners); - } - - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - notifyEach(engineExecutionListeners, listener -> listener.dynamicTestRegistered(testDescriptor), - () -> "dynamicTestRegistered(" + testDescriptor + ")"); - } - - @Override - public void executionSkipped(TestDescriptor testDescriptor, String reason) { - notifyEach(engineExecutionListeners, listener -> listener.executionSkipped(testDescriptor, reason), - () -> "executionSkipped(" + testDescriptor + ", " + reason + ")"); - } - - @Override - public void executionStarted(TestDescriptor testDescriptor) { - notifyEach(engineExecutionListeners, listener -> listener.executionStarted(testDescriptor), - () -> "executionStarted(" + testDescriptor + ")"); - } - - @Override - public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { - notifyEach(engineExecutionListeners, - listener -> listener.executionFinished(testDescriptor, testExecutionResult), - () -> "executionFinished(" + testDescriptor + ", " + testExecutionResult + ")"); - } - - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - notifyEach(engineExecutionListeners, listener -> listener.reportingEntryPublished(testDescriptor, entry), - () -> "reportingEntryPublished(" + testDescriptor + ", " + entry + ")"); - } - - private static void notifyEach(List listeners, Consumer consumer, - Supplier description) { - listeners.forEach(listener -> { - try { - consumer.accept(listener); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - logger.warn(throwable, - () -> String.format("EngineExecutionListener [%s] threw exception for method: %s", - listener.getClass().getName(), description.get())); - } - }); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java deleted file mode 100644 index a8c5d7ad..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.stream.Collectors.toList; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -class CompositeTestExecutionListener implements TestExecutionListener { - - private static final Logger logger = LoggerFactory.getLogger(CompositeTestExecutionListener.class); - - private final List testExecutionListeners; - private final List eagerTestExecutionListeners; - - CompositeTestExecutionListener(List testExecutionListeners) { - this.testExecutionListeners = new ArrayList<>(testExecutionListeners); - this.eagerTestExecutionListeners = this.testExecutionListeners.stream() // - .filter(EagerTestExecutionListener.class::isInstance) // - .map(EagerTestExecutionListener.class::cast) // - .collect(toList()); - } - - @Override - public void dynamicTestRegistered(TestIdentifier testIdentifier) { - notifyEach(testExecutionListeners, listener -> listener.dynamicTestRegistered(testIdentifier), - () -> "dynamicTestRegistered(" + testIdentifier + ")"); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - notifyEach(testExecutionListeners, listener -> listener.executionSkipped(testIdentifier, reason), - () -> "executionSkipped(" + testIdentifier + ", " + reason + ")"); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - notifyEach(eagerTestExecutionListeners, listener -> listener.executionJustStarted(testIdentifier), - () -> "executionJustStarted(" + testIdentifier + ")"); - notifyEach(testExecutionListeners, listener -> listener.executionStarted(testIdentifier), - () -> "executionStarted(" + testIdentifier + ")"); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - notifyEach(eagerTestExecutionListeners, - listener -> listener.executionJustFinished(testIdentifier, testExecutionResult), - () -> "executionJustFinished(" + testIdentifier + ", " + testExecutionResult + ")"); - notifyEach(testExecutionListeners, listener -> listener.executionFinished(testIdentifier, testExecutionResult), - () -> "executionFinished(" + testIdentifier + ", " + testExecutionResult + ")"); - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - notifyEach(testExecutionListeners, listener -> listener.testPlanExecutionStarted(testPlan), - () -> "testPlanExecutionStarted(" + testPlan + ")"); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - notifyEach(testExecutionListeners, listener -> listener.testPlanExecutionFinished(testPlan), - () -> "testPlanExecutionFinished(" + testPlan + ")"); - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - notifyEach(testExecutionListeners, listener -> listener.reportingEntryPublished(testIdentifier, entry), - () -> "reportingEntryPublished(" + testIdentifier + ", " + entry + ")"); - } - - private static void notifyEach(List listeners, Consumer consumer, - Supplier description) { - listeners.forEach(listener -> { - try { - consumer.accept(listener); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - logger.warn(throwable, () -> String.format("TestExecutionListener [%s] threw exception for method: %s", - listener.getClass().getName(), description.get())); - } - }); - } - - interface EagerTestExecutionListener extends TestExecutionListener { - default void executionJustStarted(TestIdentifier testIdentifier) { - } - - default void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java deleted file mode 100644 index 422f9592..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.Collections.unmodifiableList; -import static java.util.stream.Collectors.toList; - -import java.util.List; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.launcher.EngineFilter; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.PostDiscoveryFilter; - -/** - * {@code DefaultDiscoveryRequest} is the default implementation of the - * {@link EngineDiscoveryRequest} and {@link LauncherDiscoveryRequest} APIs. - * - * @since 1.0 - */ -final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { - - // Selectors provided to the engines to be used for discovering tests - private final List selectors; - - // Filters based on engines - private final List engineFilters; - - // Discovery filters are handed through to all engines to be applied during discovery. - private final List> discoveryFilters; - - // Descriptor filters are applied by the launcher itself after engines have performed discovery. - private final List postDiscoveryFilters; - - // Configuration parameters can be used to provide custom configuration to engines, e.g. for extensions - private final LauncherConfigurationParameters configurationParameters; - - // Listener for test discovery that may abort on errors. - private final LauncherDiscoveryListener discoveryListener; - - DefaultDiscoveryRequest(List selectors, List engineFilters, - List> discoveryFilters, List postDiscoveryFilters, - LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener) { - this.selectors = selectors; - this.engineFilters = engineFilters; - this.discoveryFilters = discoveryFilters; - this.postDiscoveryFilters = postDiscoveryFilters; - this.configurationParameters = configurationParameters; - this.discoveryListener = discoveryListener; - } - - @Override - public List getSelectorsByType(Class selectorType) { - Preconditions.notNull(selectorType, "selectorType must not be null"); - return this.selectors.stream().filter(selectorType::isInstance).map(selectorType::cast).collect(toList()); - } - - @Override - public List getEngineFilters() { - return unmodifiableList(this.engineFilters); - } - - @Override - public > List getFiltersByType(Class filterType) { - Preconditions.notNull(filterType, "filterType must not be null"); - return this.discoveryFilters.stream().filter(filterType::isInstance).map(filterType::cast).collect(toList()); - } - - @Override - public List getPostDiscoveryFilters() { - return unmodifiableList(this.postDiscoveryFilters); - } - - @Override - public ConfigurationParameters getConfigurationParameters() { - return this.configurationParameters; - } - - @Override - public LauncherDiscoveryListener getDiscoveryListener() { - return discoveryListener; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java deleted file mode 100644 index 3b0dc3e0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.Collections.unmodifiableCollection; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; - -import java.util.Collection; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestPlan; - -/** - * Default implementation of the {@link Launcher} API. - * - *

External clients can obtain an instance by invoking - * {@link LauncherFactory#create()}. - * - * @since 1.0 - * @see Launcher - * @see LauncherFactory - */ -class DefaultLauncher implements InternalLauncher { - - private final ListenerRegistry launcherDiscoveryListenerRegistry = ListenerRegistry.forLauncherDiscoveryListeners(); - private final ListenerRegistry testExecutionListenerRegistry = ListenerRegistry.forTestExecutionListeners(); - private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator( - testExecutionListenerRegistry); - private final EngineDiscoveryOrchestrator discoveryOrchestrator; - - /** - * Construct a new {@code DefaultLauncher} with the supplied test engines. - * - * @param testEngines the test engines to delegate to; never {@code null} or - * empty - * @param postDiscoveryFilters the additional post discovery filters for - * discovery requests; never {@code null} - */ - DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters) { - Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(), - () -> "Cannot create Launcher without at least one TestEngine; " - + "consider adding an engine implementation JAR to the classpath"); - Preconditions.notNull(postDiscoveryFilters, "PostDiscoveryFilter array must not be null"); - Preconditions.containsNoNullElements(postDiscoveryFilters, - "PostDiscoveryFilter array must not contain null elements"); - this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, - unmodifiableCollection(postDiscoveryFilters), launcherDiscoveryListenerRegistry); - } - - @Override - public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { - this.launcherDiscoveryListenerRegistry.addAll(listeners); - } - - @Override - public void registerTestExecutionListeners(TestExecutionListener... listeners) { - this.testExecutionListenerRegistry.addAll(listeners); - } - - @Override - public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { - Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); - return InternalTestPlan.from(discover(discoveryRequest, DISCOVERY)); - } - - @Override - public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { - Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); - Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); - Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); - execute(InternalTestPlan.from(discover(discoveryRequest, EXECUTION)), listeners); - } - - @Override - public void execute(TestPlan testPlan, TestExecutionListener... listeners) { - Preconditions.notNull(testPlan, "TestPlan must not be null"); - Preconditions.condition(testPlan instanceof InternalTestPlan, "TestPlan was not returned by this Launcher"); - Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); - Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); - execute((InternalTestPlan) testPlan, listeners); - } - - @Override - public ListenerRegistry getTestExecutionListenerRegistry() { - return testExecutionListenerRegistry; - } - - @Override - public ListenerRegistry getLauncherDiscoveryListenerRegistry() { - return launcherDiscoveryListenerRegistry; - } - - private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, - EngineDiscoveryOrchestrator.Phase phase) { - return discoveryOrchestrator.discover(discoveryRequest, phase); - } - - private void execute(InternalTestPlan internalTestPlan, TestExecutionListener[] listeners) { - executionOrchestrator.execute(internalTestPlan, listeners); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java deleted file mode 100644 index 500cdde2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.Collections.unmodifiableCollection; - -import java.util.Collection; - -import org.junit.platform.engine.TestEngine; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.TestExecutionListener; - -/** - * Default implementation of the {@link LauncherConfig} API. - * - * @since 1.3 - */ -class DefaultLauncherConfig implements LauncherConfig { - - private final boolean testEngineAutoRegistrationEnabled; - private final boolean launcherSessionListenerAutoRegistrationEnabled; - private final boolean launcherDiscoveryListenerAutoRegistrationEnabled; - private final boolean testExecutionListenerAutoRegistrationEnabled; - private final boolean postDiscoveryFilterAutoRegistrationEnabled; - private final Collection additionalTestEngines; - private final Collection additionalLauncherSessionListeners; - private final Collection additionalLauncherDiscoveryListeners; - private final Collection additionalTestExecutionListeners; - private final Collection additionalPostDiscoveryFilters; - - DefaultLauncherConfig(boolean testEngineAutoRegistrationEnabled, - boolean launcherSessionListenerAutoRegistrationEnabled, - boolean launcherDiscoveryListenerAutoRegistrationEnabled, - boolean testExecutionListenerAutoRegistrationEnabled, boolean postDiscoveryFilterAutoRegistrationEnabled, - Collection additionalTestEngines, - Collection additionalLauncherSessionListeners, - Collection additionalLauncherDiscoveryListeners, - Collection additionalTestExecutionListeners, - Collection additionalPostDiscoveryFilters) { - this.launcherSessionListenerAutoRegistrationEnabled = launcherSessionListenerAutoRegistrationEnabled; - this.launcherDiscoveryListenerAutoRegistrationEnabled = launcherDiscoveryListenerAutoRegistrationEnabled; - this.testExecutionListenerAutoRegistrationEnabled = testExecutionListenerAutoRegistrationEnabled; - this.testEngineAutoRegistrationEnabled = testEngineAutoRegistrationEnabled; - this.postDiscoveryFilterAutoRegistrationEnabled = postDiscoveryFilterAutoRegistrationEnabled; - this.additionalTestEngines = unmodifiableCollection(additionalTestEngines); - this.additionalLauncherSessionListeners = unmodifiableCollection(additionalLauncherSessionListeners); - this.additionalLauncherDiscoveryListeners = unmodifiableCollection(additionalLauncherDiscoveryListeners); - this.additionalTestExecutionListeners = unmodifiableCollection(additionalTestExecutionListeners); - this.additionalPostDiscoveryFilters = unmodifiableCollection(additionalPostDiscoveryFilters); - } - - @Override - public boolean isTestEngineAutoRegistrationEnabled() { - return this.testEngineAutoRegistrationEnabled; - } - - @Override - public boolean isLauncherSessionListenerAutoRegistrationEnabled() { - return launcherSessionListenerAutoRegistrationEnabled; - } - - @Override - public boolean isLauncherDiscoveryListenerAutoRegistrationEnabled() { - return launcherDiscoveryListenerAutoRegistrationEnabled; - } - - @Override - public boolean isTestExecutionListenerAutoRegistrationEnabled() { - return this.testExecutionListenerAutoRegistrationEnabled; - } - - @Override - public boolean isPostDiscoveryFilterAutoRegistrationEnabled() { - return this.postDiscoveryFilterAutoRegistrationEnabled; - } - - @Override - public Collection getAdditionalTestEngines() { - return this.additionalTestEngines; - } - - @Override - public Collection getAdditionalLauncherSessionListeners() { - return additionalLauncherSessionListeners; - } - - @Override - public Collection getAdditionalLauncherDiscoveryListeners() { - return additionalLauncherDiscoveryListeners; - } - - @Override - public Collection getAdditionalTestExecutionListeners() { - return this.additionalTestExecutionListeners; - } - - @Override - public Collection getAdditionalPostDiscoveryFilters() { - return this.additionalPostDiscoveryFilters; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java deleted file mode 100644 index 79f6d815..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.LauncherSession; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.8 - */ -class DefaultLauncherSession implements LauncherSession { - - private final DelegatingLauncher launcher; - private final LauncherSessionListener listener; - - DefaultLauncherSession(Launcher launcher, LauncherSessionListener listener) { - this.launcher = new DelegatingLauncher(launcher); - this.listener = listener; - listener.launcherSessionOpened(this); - } - - @Override - public Launcher getLauncher() { - return launcher; - } - - LauncherSessionListener getListener() { - return listener; - } - - @Override - public void close() { - if (launcher.getDelegate() != ClosedLauncher.INSTANCE) { - launcher.setDelegate(ClosedLauncher.INSTANCE); - listener.launcherSessionClosed(this); - } - } - - private static class DelegatingLauncher implements Launcher { - - private Launcher delegate; - - DelegatingLauncher(Launcher delegate) { - this.delegate = delegate; - } - - public Launcher getDelegate() { - return delegate; - } - - public void setDelegate(Launcher delegate) { - this.delegate = delegate; - } - - @Override - public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { - delegate.registerLauncherDiscoveryListeners(listeners); - } - - @Override - public void registerTestExecutionListeners(TestExecutionListener... listeners) { - delegate.registerTestExecutionListeners(listeners); - } - - @Override - public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { - return delegate.discover(launcherDiscoveryRequest); - } - - @Override - public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { - delegate.execute(launcherDiscoveryRequest, listeners); - } - - @Override - public void execute(TestPlan testPlan, TestExecutionListener... listeners) { - delegate.execute(testPlan, listeners); - } - } - - private static class ClosedLauncher implements Launcher { - - static final ClosedLauncher INSTANCE = new ClosedLauncher(); - - private ClosedLauncher() { - } - - @Override - public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { - throw new PreconditionViolationException("Launcher session has already been closed"); - } - - @Override - public void registerTestExecutionListeners(TestExecutionListener... listeners) { - throw new PreconditionViolationException("Launcher session has already been closed"); - } - - @Override - public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { - throw new PreconditionViolationException("Launcher session has already been closed"); - } - - @Override - public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { - throw new PreconditionViolationException("Launcher session has already been closed"); - } - - @Override - public void execute(TestPlan testPlan, TestExecutionListener... listeners) { - throw new PreconditionViolationException("Launcher session has already been closed"); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java deleted file mode 100644 index e2f41053..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * @since 1.6 - */ -class DelegatingEngineExecutionListener implements EngineExecutionListener { - - private final EngineExecutionListener delegate; - - DelegatingEngineExecutionListener(EngineExecutionListener delegate) { - this.delegate = delegate; - } - - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - delegate.dynamicTestRegistered(testDescriptor); - } - - @Override - public void executionSkipped(TestDescriptor testDescriptor, String reason) { - delegate.executionSkipped(testDescriptor, reason); - } - - @Override - public void executionStarted(TestDescriptor testDescriptor) { - delegate.executionStarted(testDescriptor); - } - - @Override - public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { - delegate.executionFinished(testDescriptor, testExecutionResult); - } - - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - delegate.reportingEntryPublished(testDescriptor, entry); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java deleted file mode 100644 index 50a4df10..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.ClassSource; - -/** - * Represents an error thrown by a {@link org.junit.platform.engine.TestEngine} - * during discovery. - * - *

The contained {@link Throwable} will be reported as the cause of a test - * failure by the {@link DefaultLauncher} when execution is started for this - * engine. - * - * @since 1.6 - */ -class EngineDiscoveryErrorDescriptor extends AbstractTestDescriptor { - - private final Throwable cause; - - EngineDiscoveryErrorDescriptor(UniqueId uniqueId, TestEngine testEngine, Throwable cause) { - super(uniqueId, testEngine.getId(), ClassSource.from(testEngine.getClass())); - this.cause = cause; - } - - Throwable getCause() { - return cause; - } - - @Override - public Type getType() { - return Type.TEST; - } - - @Override - public void prune() { - // prevent pruning - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java deleted file mode 100644 index 4bb7f319..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.stream.Collectors.joining; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.engine.Filter.composeFilters; - -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.EngineDiscoveryResult; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.PostDiscoveryFilter; - -/** - * Orchestrates test discovery using the configured test engines. - * - * @since 1.7 - */ -@API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) -public class EngineDiscoveryOrchestrator { - - private static final Logger logger = LoggerFactory.getLogger(EngineDiscoveryOrchestrator.class); - - private final EngineDiscoveryResultValidator discoveryResultValidator = new EngineDiscoveryResultValidator(); - private final Iterable testEngines; - private final Collection postDiscoveryFilters; - private final ListenerRegistry launcherDiscoveryListenerRegistry; - - public EngineDiscoveryOrchestrator(Iterable testEngines, - Collection postDiscoveryFilters) { - this(testEngines, postDiscoveryFilters, ListenerRegistry.forLauncherDiscoveryListeners()); - } - - EngineDiscoveryOrchestrator(Iterable testEngines, Collection postDiscoveryFilters, - ListenerRegistry launcherDiscoveryListenerRegistry) { - this.testEngines = EngineIdValidator.validate(testEngines); - this.postDiscoveryFilters = postDiscoveryFilters; - this.launcherDiscoveryListenerRegistry = launcherDiscoveryListenerRegistry; - } - - /** - * Discovers tests for the supplied request in the supplied phase using the - * configured test engines. - * - *

Applies {@linkplain org.junit.platform.launcher.EngineFilter engine - * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and - * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. - */ - public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase) { - Map result = discover(request, phase, UniqueId::forEngine); - return new LauncherDiscoveryResult(result, request.getConfigurationParameters()); - } - - /** - * Discovers tests for the supplied request in the supplied phase using the - * configured test engines to be used by the suite engine. - * - *

Applies {@linkplain org.junit.platform.launcher.EngineFilter engine - * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and - * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. - * - * Note: The test descriptors in the discovery result can safely be used as - * non-root descriptors. Engine-test descriptor entries are pruned from - * the returned result. As such execution by - * {@link EngineExecutionOrchestrator#execute(LauncherDiscoveryResult, EngineExecutionListener)} - * will not emit start or emit events for engines without tests. - */ - public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, UniqueId parentId) { - Map testEngines = discover(request, phase, parentId::appendEngine); - LauncherDiscoveryResult result = new LauncherDiscoveryResult(testEngines, request.getConfigurationParameters()); - return result.withRetainedEngines(TestDescriptor::containsTests); - } - - private Map discover(LauncherDiscoveryRequest request, Phase phase, - Function uniqueIdCreator) { - LauncherDiscoveryListener listener = getLauncherDiscoveryListener(request); - listener.launcherDiscoveryStarted(request); - try { - return discoverSafely(request, phase, listener, uniqueIdCreator); - } - finally { - listener.launcherDiscoveryFinished(request); - } - } - - private Map discoverSafely(LauncherDiscoveryRequest request, Phase phase, - LauncherDiscoveryListener listener, Function uniqueIdCreator) { - Map testEngineDescriptors = new LinkedHashMap<>(); - EngineFilterer engineFilterer = new EngineFilterer(request.getEngineFilters()); - - for (TestEngine testEngine : this.testEngines) { - boolean engineIsExcluded = engineFilterer.isExcluded(testEngine); - - if (engineIsExcluded) { - logger.debug(() -> String.format( - "Test discovery for engine '%s' was skipped due to an EngineFilter in %s phase.", - testEngine.getId(), phase)); - continue; - } - - logger.debug(() -> String.format("Discovering tests during Launcher %s phase in engine '%s'.", phase, - testEngine.getId())); - - TestDescriptor rootDescriptor = discoverEngineRoot(testEngine, request, listener, uniqueIdCreator); - testEngineDescriptors.put(testEngine, rootDescriptor); - } - - engineFilterer.performSanityChecks(); - - List filters = new LinkedList<>(postDiscoveryFilters); - filters.addAll(request.getPostDiscoveryFilters()); - - applyPostDiscoveryFilters(testEngineDescriptors, filters); - prune(testEngineDescriptors); - - return testEngineDescriptors; - } - - private TestDescriptor discoverEngineRoot(TestEngine testEngine, LauncherDiscoveryRequest request, - LauncherDiscoveryListener listener, Function uniqueIdCreator) { - UniqueId uniqueEngineId = uniqueIdCreator.apply(testEngine.getId()); - try { - listener.engineDiscoveryStarted(uniqueEngineId); - TestDescriptor engineRoot = testEngine.discover(request, uniqueEngineId); - discoveryResultValidator.validate(testEngine, engineRoot); - listener.engineDiscoveryFinished(uniqueEngineId, EngineDiscoveryResult.successful()); - return engineRoot; - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - String message = String.format("TestEngine with ID '%s' failed to discover tests", testEngine.getId()); - JUnitException cause = new JUnitException(message, throwable); - listener.engineDiscoveryFinished(uniqueEngineId, EngineDiscoveryResult.failed(cause)); - return new EngineDiscoveryErrorDescriptor(uniqueEngineId, testEngine, cause); - } - } - - LauncherDiscoveryListener getLauncherDiscoveryListener(LauncherDiscoveryRequest discoveryRequest) { - return ListenerRegistry.copyOf(launcherDiscoveryListenerRegistry) // - .add(discoveryRequest.getDiscoveryListener()) // - .getCompositeListener(); - } - - private void applyPostDiscoveryFilters(Map testEngineDescriptors, - List filters) { - Filter postDiscoveryFilter = composeFilters(filters); - Map> excludedTestDescriptorsByReason = new LinkedHashMap<>(); - TestDescriptor.Visitor removeExcludedTestDescriptors = descriptor -> { - FilterResult filterResult = postDiscoveryFilter.apply(descriptor); - if (!descriptor.isRoot() && isExcluded(descriptor, filterResult)) { - populateExclusionReasonInMap(filterResult.getReason(), descriptor, excludedTestDescriptorsByReason); - descriptor.removeFromHierarchy(); - } - }; - acceptInAllTestEngines(testEngineDescriptors, removeExcludedTestDescriptors); - logTestDescriptorExclusionReasons(excludedTestDescriptorsByReason); - } - - private void populateExclusionReasonInMap(Optional reason, TestDescriptor testDescriptor, - Map> excludedTestDescriptorsByReason) { - excludedTestDescriptorsByReason.computeIfAbsent(reason.orElse("Unknown"), list -> new LinkedList<>()).add( - testDescriptor); - } - - private void logTestDescriptorExclusionReasons(Map> excludedTestDescriptorsByReason) { - excludedTestDescriptorsByReason.forEach((exclusionReason, testDescriptors) -> { - String displayNames = testDescriptors.stream().map(TestDescriptor::getDisplayName).collect(joining(", ")); - long containerCount = testDescriptors.stream().filter(TestDescriptor::isContainer).count(); - long methodCount = testDescriptors.stream().filter(TestDescriptor::isTest).count(); - logger.config(() -> String.format("%d containers and %d tests were %s", containerCount, methodCount, - exclusionReason)); - logger.debug( - () -> String.format("The following containers and tests were %s: %s", exclusionReason, displayNames)); - }); - } - - /** - * Prune all branches in the tree of {@link TestDescriptor TestDescriptors} - * that do not have executable tests. - * - *

If a {@link TestEngine} ends up with no {@code TestDescriptors} after - * pruning, it will not be removed. - */ - private void prune(Map testEngineDescriptors) { - acceptInAllTestEngines(testEngineDescriptors, TestDescriptor::prune); - } - - private boolean isExcluded(TestDescriptor descriptor, FilterResult filterResult) { - return descriptor.getChildren().isEmpty() && filterResult.excluded(); - } - - private void acceptInAllTestEngines(Map testEngineDescriptors, - TestDescriptor.Visitor visitor) { - testEngineDescriptors.values().forEach(descriptor -> descriptor.accept(visitor)); - } - - public enum Phase { - DISCOVERY, EXECUTION; - - @Override - public String toString() { - return name().toLowerCase(Locale.ENGLISH); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java deleted file mode 100644 index a5441ae6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.util.ArrayDeque; -import java.util.HashSet; -import java.util.Queue; -import java.util.Set; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; - -/** - * Perform common validation checks on the result from the `discover()` method. - * - * @since 1.3 - */ -class EngineDiscoveryResultValidator { - - /** - * Perform common validation checks. - * - * @throws org.junit.platform.commons.PreconditionViolationException if any check fails - */ - void validate(TestEngine testEngine, TestDescriptor root) { - Preconditions.notNull(root, - () -> String.format( - "The discover() method for TestEngine with ID '%s' must return a non-null root TestDescriptor.", - testEngine.getId())); - Preconditions.condition(isAcyclic(root), - () -> String.format("The discover() method for TestEngine with ID '%s' returned a cyclic graph.", - testEngine.getId())); - } - - /** - * @return {@code true} if the tree does not contain a cycle; else {@code false}. - */ - boolean isAcyclic(TestDescriptor root) { - Set visited = new HashSet<>(); - visited.add(root.getUniqueId()); - Queue queue = new ArrayDeque<>(); - queue.add(root); - while (!queue.isEmpty()) { - for (TestDescriptor child : queue.remove().getChildren()) { - if (!visited.add(child.getUniqueId())) { - return false; // id already known: cycle detected! - } - if (child.isContainer()) { - queue.add(child); - } - } - } - return true; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java deleted file mode 100644 index 35e174e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.launcher.core.ListenerRegistry.forEngineExecutionListeners; - -import java.util.Optional; -import java.util.function.Consumer; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestPlan; - -/** - * Orchestrates test execution using the configured test engines. - * - * @since 1.7 - */ -@API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) -public class EngineExecutionOrchestrator { - - private final ListenerRegistry listenerRegistry; - - public EngineExecutionOrchestrator() { - this(ListenerRegistry.forTestExecutionListeners()); - } - - EngineExecutionOrchestrator(ListenerRegistry listenerRegistry) { - this.listenerRegistry = listenerRegistry; - } - - void execute(InternalTestPlan internalTestPlan, TestExecutionListener... listeners) { - ConfigurationParameters configurationParameters = internalTestPlan.getConfigurationParameters(); - ListenerRegistry testExecutionListenerListeners = buildListenerRegistryForExecution( - listeners); - withInterceptedStreams(configurationParameters, testExecutionListenerListeners, - testExecutionListener -> execute(internalTestPlan, EngineExecutionListener.NOOP, testExecutionListener)); - } - - /** - * Executes tests for the supplied {@linkplain LauncherDiscoveryResult - * discoveryResult} and notifies the supplied {@linkplain - * EngineExecutionListener engineExecutionListener} and - * {@linkplain TestExecutionListener testExecutionListener} of execution - * events. - */ - @API(status = INTERNAL, since = "1.9", consumers = { "org.junit.platform.suite.engine" }) - public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener, - TestExecutionListener testExecutionListener) { - Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); - Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); - Preconditions.notNull(testExecutionListener, "testExecutionListener must not be null"); - - InternalTestPlan internalTestPlan = InternalTestPlan.from(discoveryResult); - execute(internalTestPlan, engineExecutionListener, testExecutionListener); - } - - private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener parentEngineExecutionListener, - TestExecutionListener testExecutionListener) { - internalTestPlan.markStarted(); - - // Do not directly pass the internal test plan to test execution listeners. - // Hyrum's Law indicates that someone will eventually come to depend on it. - TestPlan testPlan = internalTestPlan.getDelegate(); - LauncherDiscoveryResult discoveryResult = internalTestPlan.getDiscoveryResult(); - - ListenerRegistry engineExecutionListenerRegistry = forEngineExecutionListeners(); - engineExecutionListenerRegistry.add(new ExecutionListenerAdapter(testPlan, testExecutionListener)); - engineExecutionListenerRegistry.add(parentEngineExecutionListener); - - testExecutionListener.testPlanExecutionStarted(testPlan); - execute(discoveryResult, engineExecutionListenerRegistry.getCompositeListener()); - testExecutionListener.testPlanExecutionFinished(testPlan); - } - - private void withInterceptedStreams(ConfigurationParameters configurationParameters, - ListenerRegistry listenerRegistry, Consumer action) { - - TestExecutionListener testExecutionListener = listenerRegistry.getCompositeListener(); - Optional streamInterceptingTestExecutionListener = StreamInterceptingTestExecutionListener.create( - configurationParameters, testExecutionListener::reportingEntryPublished); - streamInterceptingTestExecutionListener.ifPresent(listenerRegistry::add); - try { - action.accept(listenerRegistry.getCompositeListener()); - } - finally { - streamInterceptingTestExecutionListener.ifPresent(StreamInterceptingTestExecutionListener::unregister); - } - } - - /** - * Executes tests for the supplied {@linkplain LauncherDiscoveryResult - * discovery results} and notifies the supplied {@linkplain - * EngineExecutionListener listener} of execution events. - */ - @API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit" }) - public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener) { - Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); - Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); - - for (TestEngine testEngine : discoveryResult.getTestEngines()) { - TestDescriptor engineDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); - if (engineDescriptor instanceof EngineDiscoveryErrorDescriptor) { - engineExecutionListener.executionStarted(engineDescriptor); - engineExecutionListener.executionFinished(engineDescriptor, - TestExecutionResult.failed(((EngineDiscoveryErrorDescriptor) engineDescriptor).getCause())); - } - else { - execute(engineDescriptor, engineExecutionListener, discoveryResult.getConfigurationParameters(), - testEngine); - } - } - } - - private ListenerRegistry buildListenerRegistryForExecution( - TestExecutionListener... listeners) { - if (listeners.length == 0) { - return this.listenerRegistry; - } - return ListenerRegistry.copyOf(this.listenerRegistry).addAll(listeners); - } - - private void execute(TestDescriptor engineDescriptor, EngineExecutionListener listener, - ConfigurationParameters configurationParameters, TestEngine testEngine) { - - OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, - engineDescriptor); - try { - testEngine.execute(new ExecutionRequest(engineDescriptor, delayingListener, configurationParameters)); - delayingListener.reportEngineOutcome(); - } - catch (Throwable throwable) { - UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - delayingListener.reportEngineFailure(new JUnitException( - String.format("TestEngine with ID '%s' failed to execute tests", testEngine.getId()), throwable)); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java deleted file mode 100644 index 04f0d24f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toCollection; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.launcher.EngineFilter; - -class EngineFilterer { - - private static final Logger LOGGER = LoggerFactory.getLogger(EngineFilterer.class); - - private final List engineFilters; - - private final Map checkedTestEngines = new HashMap<>(); - - EngineFilterer(List engineFilters) { - this.engineFilters = engineFilters; - } - - boolean isExcluded(TestEngine testEngine) { - boolean excluded = engineFilters.stream() // - .map(engineFilter -> engineFilter.apply(testEngine)) // - .anyMatch(FilterResult::excluded); - checkedTestEngines.put(testEngine, excluded); - return excluded; - } - - void performSanityChecks() { - checkNoUnmatchedIncludeFilter(); - warnIfAllEnginesExcluded(); - } - - private void warnIfAllEnginesExcluded() { - if (!checkedTestEngines.isEmpty() && checkedTestEngines.values().stream().allMatch(excluded -> excluded)) { - LOGGER.warn(() -> "All TestEngines were excluded by EngineFilters.\n" + getStateDescription()); - } - } - - private void checkNoUnmatchedIncludeFilter() { - SortedSet unmatchedEngineIdsOfIncludeFilters = getUnmatchedEngineIdsOfIncludeFilters(); - if (!unmatchedEngineIdsOfIncludeFilters.isEmpty()) { - throw new JUnitException("No TestEngine ID matched the following include EngineFilters: " - + unmatchedEngineIdsOfIncludeFilters + ".\n" // - + "Please fix/remove the filter or add the engine.\n" // - + getStateDescription()); - } - } - - private SortedSet getUnmatchedEngineIdsOfIncludeFilters() { - return engineFilters.stream() // - .filter(EngineFilter::isIncludeFilter) // - .filter(engineFilter -> checkedTestEngines.keySet().stream() // - .map(engineFilter::apply) // - .noneMatch(FilterResult::included)) // - .flatMap(engineFilter -> engineFilter.getEngineIds().stream()) // - .collect(toCollection(TreeSet::new)); - } - - private String getStateDescription() { - return getRegisteredEnginesDescription() + "\n" + getRegisteredFiltersDescription(); - } - - private String getRegisteredEnginesDescription() { - return TestEngineFormatter.format("Registered TestEngines", checkedTestEngines.keySet()); - } - - private String getRegisteredFiltersDescription() { - return "Registered EngineFilters:" + engineFilters.stream() // - .map(Objects::toString) // - .collect(joining("\n- ", "\n- ", "")); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java deleted file mode 100644 index adf3739b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.util.HashSet; -import java.util.Set; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.engine.TestEngine; - -/** - * @since 1.7 - */ -class EngineIdValidator { - - private static final Logger logger = LoggerFactory.getLogger(EngineIdValidator.class); - - private EngineIdValidator() { - } - - static Iterable validate(Iterable testEngines) { - Set ids = new HashSet<>(); - for (TestEngine testEngine : testEngines) { - // check usage of reserved id prefix - if (!validateReservedIds(testEngine)) { - logger.warn(() -> String.format( - "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID: '%s'", - testEngine.getId())); - } - - // check uniqueness - if (!ids.add(testEngine.getId())) { - throw new JUnitException(String.format( - "Cannot create Launcher for multiple engines with the same ID '%s'.", testEngine.getId())); - } - } - return testEngines; - } - - // https://github.com/junit-team/junit5/issues/1557 - private static boolean validateReservedIds(TestEngine testEngine) { - String engineId = testEngine.getId(); - if (!engineId.startsWith("junit-")) { - return true; - } - if (engineId.equals("junit-jupiter")) { - validateWellKnownClassName(testEngine, "org.junit.jupiter.engine.JupiterTestEngine"); - return true; - } - if (engineId.equals("junit-vintage")) { - validateWellKnownClassName(testEngine, "org.junit.vintage.engine.VintageTestEngine"); - return true; - } - if (engineId.equals("junit-platform-suite")) { - validateWellKnownClassName(testEngine, "org.junit.platform.suite.engine.SuiteTestEngine"); - return true; - } - return false; - } - - private static void validateWellKnownClassName(TestEngine testEngine, String expectedClassName) { - String actualClassName = testEngine.getClass().getName(); - if (actualClassName.equals(expectedClassName)) { - return; - } - throw new JUnitException( - String.format("Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.", - actualClassName, testEngine.getId())); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java deleted file mode 100644 index f2d87e4b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * An {@code ExecutionListenerAdapter} adapts a {@link TestPlan} and a corresponding - * {@link TestExecutionListener} to the {@link EngineExecutionListener} API. - * - * @since 1.0 - */ -class ExecutionListenerAdapter implements EngineExecutionListener { - - private final TestPlan testPlan; - private final TestExecutionListener testExecutionListener; - - ExecutionListenerAdapter(TestPlan testPlan, TestExecutionListener testExecutionListener) { - this.testPlan = testPlan; - this.testExecutionListener = testExecutionListener; - } - - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - TestIdentifier testIdentifier = TestIdentifier.from(testDescriptor); - this.testPlan.addInternal(testIdentifier); - this.testExecutionListener.dynamicTestRegistered(testIdentifier); - } - - @Override - public void executionStarted(TestDescriptor testDescriptor) { - this.testExecutionListener.executionStarted(getTestIdentifier(testDescriptor)); - } - - @Override - public void executionSkipped(TestDescriptor testDescriptor, String reason) { - this.testExecutionListener.executionSkipped(getTestIdentifier(testDescriptor), reason); - } - - @Override - public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { - this.testExecutionListener.executionFinished(getTestIdentifier(testDescriptor), testExecutionResult); - } - - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - this.testExecutionListener.reportingEntryPublished(getTestIdentifier(testDescriptor), entry); - } - - private TestIdentifier getTestIdentifier(TestDescriptor testDescriptor) { - return this.testPlan.getTestIdentifier(testDescriptor.getUniqueId()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java deleted file mode 100644 index 453be693..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalLauncher.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.TestExecutionListener; - -/** - * @since 1.8 - */ -interface InternalLauncher extends Launcher { - - ListenerRegistry getTestExecutionListenerRegistry(); - - ListenerRegistry getLauncherDiscoveryListenerRegistry(); -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java deleted file mode 100644 index 20a15571..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Predicate; - -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.4 - */ -class InternalTestPlan extends TestPlan { - - private final AtomicBoolean executionStarted = new AtomicBoolean(false); - private final LauncherDiscoveryResult discoveryResult; - private final TestPlan delegate; - - static InternalTestPlan from(LauncherDiscoveryResult discoveryResult) { - TestPlan delegate = TestPlan.from(discoveryResult.getEngineTestDescriptors(), - discoveryResult.getConfigurationParameters()); - return new InternalTestPlan(discoveryResult, delegate); - } - - private InternalTestPlan(LauncherDiscoveryResult discoveryResult, TestPlan delegate) { - super(delegate.containsTests(), delegate.getConfigurationParameters()); - this.discoveryResult = discoveryResult; - this.delegate = delegate; - } - - void markStarted() { - if (!executionStarted.compareAndSet(false, true)) { - throw new PreconditionViolationException("TestPlan must only be executed once"); - } - } - - LauncherDiscoveryResult getDiscoveryResult() { - return discoveryResult; - } - - public TestPlan getDelegate() { - return delegate; - } - - @Override - @SuppressWarnings("deprecation") - public void add(TestIdentifier testIdentifier) { - delegate.add(testIdentifier); - } - - @Override - public void addInternal(TestIdentifier testIdentifier) { - delegate.addInternal(testIdentifier); - } - - @Override - public Set getRoots() { - return delegate.getRoots(); - } - - @Override - public Optional getParent(TestIdentifier child) { - return delegate.getParent(child); - } - - @Override - public Set getChildren(TestIdentifier parent) { - return delegate.getChildren(parent); - } - - @SuppressWarnings("deprecation") - @Override - public Set getChildren(String parentId) { - return delegate.getChildren(parentId); - } - - @Override - public Set getChildren(UniqueId parentId) { - return delegate.getChildren(parentId); - } - - @SuppressWarnings("deprecation") - @Override - public TestIdentifier getTestIdentifier(String uniqueId) throws PreconditionViolationException { - return delegate.getTestIdentifier(uniqueId); - } - - @Override - public TestIdentifier getTestIdentifier(UniqueId uniqueId) { - return delegate.getTestIdentifier(uniqueId); - } - - @Override - public long countTestIdentifiers(Predicate predicate) { - return delegate.countTestIdentifiers(predicate); - } - - @Override - public Set getDescendants(TestIdentifier parent) { - return delegate.getDescendants(parent); - } - - @Override - public boolean containsTests() { - return delegate.containsTests(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java deleted file mode 100644 index 0a622b16..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.TestExecutionListener; - -/** - * {@code LauncherConfig} defines the configuration API for creating - * {@link Launcher} instances via the {@link LauncherFactory}. - * - *

Example

- * - *
- * LauncherConfig launcherConfig = LauncherConfig.builder()
- *   .enableTestEngineAutoRegistration(false)
- *   .enableTestExecutionListenerAutoRegistration(false)
- *   .addTestEngines(new CustomTestEngine())
- *   .addTestExecutionListeners(new CustomTestExecutionListener())
- *   .build();
- *
- * Launcher launcher = LauncherFactory.create(launcherConfig);
- * LauncherDiscoveryRequest discoveryRequest = ...
- * launcher.execute(discoveryRequest);
- * 
- * - * @since 1.3 - * @see #builder() - * @see Launcher - * @see LauncherFactory - */ -@API(status = STABLE, since = "1.7") -public interface LauncherConfig { - - /** - * The default {@code LauncherConfig} which uses automatic registration for - * test engines, supported listeners, and post-discovery filters. - */ - LauncherConfig DEFAULT = builder().build(); - - /** - * Determine if test engines should be discovered at runtime using the - * {@link java.util.ServiceLoader ServiceLoader} mechanism and - * automatically registered. - * - * @return {@code true} if test engines should be automatically registered - */ - boolean isTestEngineAutoRegistrationEnabled(); - - /** - * Determine if launcher session listeners should be discovered at runtime - * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and - * automatically registered. - * - * @return {@code true} if launcher session listeners should be - * automatically registered - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - boolean isLauncherSessionListenerAutoRegistrationEnabled(); - - /** - * Determine if launcher discovery listeners should be discovered at runtime - * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and - * automatically registered. - * - * @return {@code true} if launcher discovery listeners should be - * automatically registered - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - boolean isLauncherDiscoveryListenerAutoRegistrationEnabled(); - - /** - * Determine if test execution listeners should be discovered at runtime - * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and - * automatically registered. - * - * @return {@code true} if test execution listeners should be automatically - * registered - */ - boolean isTestExecutionListenerAutoRegistrationEnabled(); - - /** - * Determine if post discovery filters should be discovered at runtime - * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and - * automatically registered. - * - * @return {@code true} if post discovery filters should be automatically - * registered - * @since 1.7 - */ - @API(status = EXPERIMENTAL, since = "1.7") - boolean isPostDiscoveryFilterAutoRegistrationEnabled(); - - /** - * Get the collection of additional test engines that should be added to - * the {@link Launcher}. - * - * @return the collection of additional test engines; never {@code null} but - * potentially empty - */ - Collection getAdditionalTestEngines(); - - /** - * Get the collection of additional launcher session listeners that should - * be added to the {@link Launcher}. - * - * @return the collection of additional launcher session listeners; never - * {@code null} but potentially empty - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - Collection getAdditionalLauncherSessionListeners(); - - /** - * Get the collection of additional launcher discovery listeners that should - * be added to the {@link Launcher}. - * - * @return the collection of additional launcher discovery listeners; never - * {@code null} but potentially empty - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - Collection getAdditionalLauncherDiscoveryListeners(); - - /** - * Get the collection of additional test execution listeners that should be - * added to the {@link Launcher}. - * - * @return the collection of additional test execution listeners; never - * {@code null} but potentially empty - */ - Collection getAdditionalTestExecutionListeners(); - - /** - * Get the collection of additional post discovery filters that should be - * added to the {@link Launcher}. - * - * @return the collection of additional post discovery filters; never - * {@code null} but potentially empty - * @since 1.7 - */ - @API(status = EXPERIMENTAL, since = "1.7") - Collection getAdditionalPostDiscoveryFilters(); - - /** - * Create a new {@link LauncherConfig.Builder}. - * - * @return a new builder; never {@code null} - */ - static Builder builder() { - return new Builder(); - } - - /** - * Builder API for {@link LauncherConfig}. - */ - class Builder { - - private boolean engineAutoRegistrationEnabled = true; - private boolean launcherSessionListenerAutoRegistrationEnabled = true; - private boolean launcherDiscoveryListenerAutoRegistrationEnabled = true; - private boolean testExecutionListenerAutoRegistrationEnabled = true; - private boolean postDiscoveryFilterAutoRegistrationEnabled = true; - private final Collection engines = new LinkedHashSet<>(); - private final Collection sessionListeners = new LinkedHashSet<>(); - private final Collection discoveryListeners = new LinkedHashSet<>(); - private final Collection executionListeners = new LinkedHashSet<>(); - private final Collection postDiscoveryFilters = new LinkedHashSet<>(); - - private Builder() { - /* no-op */ - } - - /** - * Configure the auto-registration flag for launcher session - * listeners. - * - *

Defaults to {@code true}. - * - * @param enabled {@code true} if launcher session listeners should be - * automatically registered - * @return this builder for method chaining - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - public Builder enableLauncherSessionListenerAutoRegistration(boolean enabled) { - this.launcherSessionListenerAutoRegistrationEnabled = enabled; - return this; - } - - /** - * Configure the auto-registration flag for launcher discovery - * listeners. - * - *

Defaults to {@code true}. - * - * @param enabled {@code true} if launcher discovery listeners should be - * automatically registered - * @return this builder for method chaining - * @since 1.8 - */ - @API(status = EXPERIMENTAL, since = "1.8") - public Builder enableLauncherDiscoveryListenerAutoRegistration(boolean enabled) { - this.launcherDiscoveryListenerAutoRegistrationEnabled = enabled; - return this; - } - - /** - * Configure the auto-registration flag for test execution listeners. - * - *

Defaults to {@code true}. - * - * @param enabled {@code true} if test execution listeners should be - * automatically registered - * @return this builder for method chaining - */ - public Builder enableTestExecutionListenerAutoRegistration(boolean enabled) { - this.testExecutionListenerAutoRegistrationEnabled = enabled; - return this; - } - - /** - * Configure the auto-registration flag for test engines. - * - *

Defaults to {@code true}. - * - * @param enabled {@code true} if test engines should be automatically - * registered - * @return this builder for method chaining - */ - public Builder enableTestEngineAutoRegistration(boolean enabled) { - this.engineAutoRegistrationEnabled = enabled; - return this; - } - - /** - * Configure the auto-registration flag for post discovery filters. - * - *

Defaults to {@code true}. - * - * @param enabled {@code true} if post discovery filters should be automatically - * registered - * @return this builder for method chaining - * @since 1.7 - */ - @API(status = EXPERIMENTAL, since = "1.7") - public Builder enablePostDiscoveryFilterAutoRegistration(boolean enabled) { - this.postDiscoveryFilterAutoRegistrationEnabled = enabled; - return this; - } - - /** - * Add all of the supplied test engines to the configuration. - * - * @param engines additional test engines to register; never {@code null} - * or containing {@code null} - * @return this builder for method chaining - */ - public Builder addTestEngines(TestEngine... engines) { - Preconditions.notNull(engines, "TestEngine array must not be null"); - Preconditions.containsNoNullElements(engines, "TestEngine array must not contain null elements"); - Collections.addAll(this.engines, engines); - return this; - } - - /** - * Add all of the supplied launcher session listeners to the configuration. - * - * @param listeners additional launcher session listeners to register; - * never {@code null} or containing {@code null} - * @return this builder for method chaining - */ - public Builder addLauncherSessionListeners(LauncherSessionListener... listeners) { - Preconditions.notNull(listeners, "LauncherSessionListener array must not be null"); - Preconditions.containsNoNullElements(listeners, - "LauncherSessionListener array must not contain null elements"); - Collections.addAll(this.sessionListeners, listeners); - return this; - } - - /** - * Add all of the supplied launcher discovery listeners to the configuration. - * - * @param listeners additional launcher discovery listeners to register; - * never {@code null} or containing {@code null} - * @return this builder for method chaining - */ - public Builder addLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { - Preconditions.notNull(listeners, "LauncherDiscoveryListener array must not be null"); - Preconditions.containsNoNullElements(listeners, - "LauncherDiscoveryListener array must not contain null elements"); - Collections.addAll(this.discoveryListeners, listeners); - return this; - } - - /** - * Add all of the supplied test execution listeners to the configuration. - * - * @param listeners additional test execution listeners to register; - * never {@code null} or containing {@code null} - * @return this builder for method chaining - */ - public Builder addTestExecutionListeners(TestExecutionListener... listeners) { - Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); - Preconditions.containsNoNullElements(listeners, - "TestExecutionListener array must not contain null elements"); - Collections.addAll(this.executionListeners, listeners); - return this; - } - - /** - * Add all of the supplied {@code filters} to the configuration. - * - * @param filters additional post discovery filters to register; - * never {@code null} or containing {@code null} - * @return this builder for method chaining - * @since 1.7 - */ - @API(status = EXPERIMENTAL, since = "1.7") - public Builder addPostDiscoveryFilters(PostDiscoveryFilter... filters) { - Preconditions.notNull(filters, "PostDiscoveryFilter array must not be null"); - Preconditions.containsNoNullElements(filters, "PostDiscoveryFilter array must not contain null elements"); - Collections.addAll(this.postDiscoveryFilters, filters); - return this; - } - - /** - * Build the {@link LauncherConfig} that has been configured via this - * builder. - */ - public LauncherConfig build() { - return new DefaultLauncherConfig(this.engineAutoRegistrationEnabled, - this.launcherSessionListenerAutoRegistrationEnabled, - this.launcherDiscoveryListenerAutoRegistrationEnabled, - this.testExecutionListenerAutoRegistrationEnabled, this.postDiscoveryFilterAutoRegistrationEnabled, - this.engines, this.sessionListeners, this.discoveryListeners, this.executionListeners, - this.postDiscoveryFilters); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java deleted file mode 100644 index 5d6b95ed..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassLoaderUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * @since 1.0 - */ -class LauncherConfigurationParameters implements ConfigurationParameters { - - private static final Logger logger = LoggerFactory.getLogger(LauncherConfigurationParameters.class); - - static Builder builder() { - return new Builder(); - } - - private final List providers; - - private LauncherConfigurationParameters(List providers) { - this.providers = providers; - } - - @Override - public Optional get(String key) { - return Optional.ofNullable(getProperty(key)); - } - - @Override - public Optional getBoolean(String key) { - return get(key).map(Boolean::parseBoolean); - } - - @Override - @SuppressWarnings("deprecation") - public int size() { - return providers.stream() // - .mapToInt(ParameterProvider::size) // - .sum(); - } - - @Override - public Set keySet() { - return providers.stream().map(ParameterProvider::keySet).flatMap(Collection::stream).collect( - Collectors.toSet()); - } - - private String getProperty(String key) { - Preconditions.notBlank(key, "key must not be null or blank"); - return providers.stream() // - .map(parameterProvider -> parameterProvider.getValue(key)) // - .filter(Objects::nonNull) // - .findFirst() // - .orElse(null); - } - - @Override - public String toString() { - return new ToStringBuilder(this) // - .append("lookups", providers) // - .toString(); - } - - static final class Builder { - - private final Map explicitParameters = new HashMap<>(); - private boolean implicitProvidersEnabled = true; - private String configFileName = ConfigurationParameters.CONFIG_FILE_NAME; - private ConfigurationParameters parentConfigurationParameters; - - private Builder() { - } - - Builder explicitParameters(Map parameters) { - Preconditions.notNull(parameters, "configuration parameters must not be null"); - explicitParameters.putAll(parameters); - return this; - } - - Builder enableImplicitProviders(boolean enabled) { - this.implicitProvidersEnabled = enabled; - return this; - } - - Builder configFileName(String configFileName) { - Preconditions.notBlank(configFileName, "configFileName must not be null or blank"); - this.configFileName = configFileName; - return this; - } - - Builder parentConfigurationParameters(ConfigurationParameters parameters) { - Preconditions.notNull(parameters, "parent configuration parameters must not be null"); - this.parentConfigurationParameters = parameters; - return this; - } - - LauncherConfigurationParameters build() { - List parameterProviders = new ArrayList<>(); - if (!explicitParameters.isEmpty()) { - parameterProviders.add(ParameterProvider.explicit(explicitParameters)); - } - - if (parentConfigurationParameters != null) { - parameterProviders.add(ParameterProvider.inherited(parentConfigurationParameters)); - } - - if (implicitProvidersEnabled) { - parameterProviders.add(ParameterProvider.systemProperties()); - parameterProviders.add(ParameterProvider.propertiesFile(configFileName)); - } - return new LauncherConfigurationParameters(parameterProviders); - } - } - - private interface ParameterProvider { - - String getValue(String key); - - default int size() { - return 0; - } - - Set keySet(); - - static ParameterProvider explicit(Map configParams) { - return new ParameterProvider() { - @Override - public String getValue(String key) { - return configParams.get(key); - } - - @Override - public int size() { - return configParams.size(); - } - - @Override - public Set keySet() { - return configParams.keySet(); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder("explicit"); - configParams.forEach(builder::append); - return builder.toString(); - } - }; - } - - static ParameterProvider systemProperties() { - return new ParameterProvider() { - @Override - public String getValue(String key) { - try { - return System.getProperty(key); - } - catch (Exception ignore) { - return null; - } - } - - @Override - public Set keySet() { - return System.getProperties().stringPropertyNames(); - } - - @Override - public String toString() { - return "systemProperties [...]"; - } - }; - } - - static ParameterProvider propertiesFile(String configFileName) { - Preconditions.notBlank(configFileName, "configFileName must not be null or blank"); - Properties properties = loadClasspathResource(configFileName.trim()); - return new ParameterProvider() { - @Override - public String getValue(String key) { - return properties.getProperty(key); - } - - @Override - public Set keySet() { - return properties.stringPropertyNames(); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder("propertiesFile"); - properties.stringPropertyNames().forEach(key -> builder.append(key, getValue(key))); - return builder.toString(); - } - }; - } - - static ParameterProvider inherited(ConfigurationParameters configParams) { - return new ParameterProvider() { - @Override - public String getValue(String key) { - return configParams.get(key).orElse(null); - } - - @Override - @SuppressWarnings("deprecation") - public int size() { - return configParams.size(); - } - - @Override - public Set keySet() { - return configParams.keySet(); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder("inherited"); - builder.append("parent", configParams); - return builder.toString(); - } - }; - } - - } - - private static Properties loadClasspathResource(String configFileName) { - Properties props = new Properties(); - - try { - ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader(); - Set resources = new LinkedHashSet<>(Collections.list(classLoader.getResources(configFileName))); - - if (!resources.isEmpty()) { - if (resources.size() > 1) { - logger.warn(() -> String.format( - "Discovered %d '%s' configuration files in the classpath; only the first will be used.", - resources.size(), configFileName)); - } - - URL configFileUrl = resources.iterator().next(); // same as List#get(0) - logger.config(() -> String.format( - "Loading JUnit Platform configuration parameters from classpath resource [%s].", configFileUrl)); - URLConnection urlConnection = configFileUrl.openConnection(); - urlConnection.setUseCaches(false); - try (InputStream inputStream = urlConnection.getInputStream()) { - props.load(inputStream); - } - } - } - catch (Exception ex) { - logger.warn(ex, - () -> String.format( - "Failed to load JUnit Platform configuration parameters from classpath resource [%s].", - configFileName)); - } - - return props; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java deleted file mode 100644 index 24be659b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.Filter; -import org.junit.platform.launcher.EngineFilter; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.core.LauncherConfigurationParameters.Builder; -import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; - -/** - * The {@code LauncherDiscoveryRequestBuilder} provides a light-weight DSL for - * generating a {@link LauncherDiscoveryRequest}. - * - *

Example

- * - *
- * import static org.junit.platform.engine.discovery.DiscoverySelectors.*;
- * import static org.junit.platform.engine.discovery.ClassNameFilter.*;
- * import static org.junit.platform.launcher.EngineFilter.*;
- * import static org.junit.platform.launcher.TagFilter.*;
- *
- * // ...
- *
- *   LauncherDiscoveryRequestBuilder.request()
- *     .selectors(
- *        selectPackage("org.example.user"),
- *        selectClass("org.example.payment.PaymentTests"),
- *        selectClass(ShippingTests.class),
- *        selectMethod("org.example.order.OrderTests#test1"),
- *        selectMethod("org.example.order.OrderTests#test2()"),
- *        selectMethod("org.example.order.OrderTests#test3(java.lang.String)"),
- *        selectMethod("org.example.order.OrderTests", "test4"),
- *        selectMethod(OrderTests.class, "test5"),
- *        selectMethod(OrderTests.class, testMethod),
- *        selectClasspathRoots(Collections.singleton(new File("/my/local/path1"))),
- *        selectUniqueId("unique-id-1"),
- *        selectUniqueId("unique-id-2")
- *     )
- *     .filters(
- *        includeEngines("junit-jupiter", "spek"),
- *        // excludeEngines("junit-vintage"),
- *        includeTags("fast"),
- *        // excludeTags("slow"),
- *        includeClassNamePatterns(".*Test[s]?")
- *        // includeClassNamePatterns("org\.example\.tests.*")
- *     )
- *     .configurationParameter("key1", "value1")
- *     .configurationParameters(configParameterMap)
- *     .build();
- * 
- * - * @since 1.0 - * @see org.junit.platform.engine.discovery.DiscoverySelectors - * @see org.junit.platform.engine.discovery.ClassNameFilter - * @see org.junit.platform.launcher.EngineFilter - * @see org.junit.platform.launcher.TagFilter - */ -@API(status = STABLE, since = "1.0") -public final class LauncherDiscoveryRequestBuilder { - - /** - * Property name used to set the default discovery listener that is added to all : {@value} - * - *

Supported Values

- * - *

Supported values are {@code "logging"} and {@code "abortOnFailure"}. - * - *

If not specified, the default is {@value #DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE}. - */ - public static final String DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME = "junit.platform.discovery.listener.default"; - - private static final String DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE = "abortOnFailure"; - - private final List selectors = new ArrayList<>(); - private final List engineFilters = new ArrayList<>(); - private final List> discoveryFilters = new ArrayList<>(); - private final List postDiscoveryFilters = new ArrayList<>(); - private final Map configurationParameters = new HashMap<>(); - private final List discoveryListeners = new ArrayList<>(); - private boolean implicitConfigurationParametersEnabled = true; - private ConfigurationParameters parentConfigurationParameters; - - /** - * Create a new {@code LauncherDiscoveryRequestBuilder}. - * - * @return a new builder - */ - public static LauncherDiscoveryRequestBuilder request() { - return new LauncherDiscoveryRequestBuilder(); - } - - /** - * @deprecated Use {@link #request()} - */ - @API(status = DEPRECATED, since = "1.8") - @Deprecated - public LauncherDiscoveryRequestBuilder() { - } - - /** - * Add all of the supplied {@code selectors} to the request. - * - * @param selectors the {@code DiscoverySelectors} to add; never {@code null} - * @return this builder for method chaining - */ - public LauncherDiscoveryRequestBuilder selectors(DiscoverySelector... selectors) { - Preconditions.notNull(selectors, "selectors array must not be null"); - selectors(Arrays.asList(selectors)); - return this; - } - - /** - * Add all of the supplied {@code selectors} to the request. - * - * @param selectors the {@code DiscoverySelectors} to add; never {@code null} - * @return this builder for method chaining - */ - public LauncherDiscoveryRequestBuilder selectors(List selectors) { - Preconditions.notNull(selectors, "selectors list must not be null"); - Preconditions.containsNoNullElements(selectors, "individual selectors must not be null"); - this.selectors.addAll(selectors); - return this; - } - - /** - * Add all of the supplied {@code filters} to the request. - * - *

The {@code filters} are combined using AND semantics, i.e. all of them - * have to include a resource for it to end up in the test plan. - * - *

Warning: be cautious when registering multiple competing - * {@link EngineFilter#includeEngines include} {@code EngineFilters} or multiple - * competing {@link EngineFilter#excludeEngines exclude} {@code EngineFilters} - * for the same discovery request since doing so will likely lead to - * undesirable results (i.e., zero engines being active). - * - * @param filters the {@code Filter}s to add; never {@code null} - * @return this builder for method chaining - */ - public LauncherDiscoveryRequestBuilder filters(Filter... filters) { - Preconditions.notNull(filters, "filters array must not be null"); - Preconditions.containsNoNullElements(filters, "individual filters must not be null"); - Arrays.stream(filters).forEach(this::storeFilter); - return this; - } - - /** - * Add the supplied configuration parameter to the request. - * - * @param key the configuration parameter key under which to store the - * value; never {@code null} or blank - * @param value the value to store - * @return this builder for method chaining - */ - public LauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { - Preconditions.notBlank(key, "configuration parameter key must not be null or blank"); - this.configurationParameters.put(key, value); - return this; - } - - /** - * Add all of the supplied configuration parameters to the request. - * - * @param configurationParameters the map of configuration parameters to add; - * never {@code null} - * @return this builder for method chaining - * @see #configurationParameter(String, String) - */ - public LauncherDiscoveryRequestBuilder configurationParameters(Map configurationParameters) { - Preconditions.notNull(configurationParameters, "configuration parameters map must not be null"); - configurationParameters.forEach(this::configurationParameter); - return this; - } - - /** - * Set the parent configuration parameters to use for the request. - * - *

Any explicit configuration parameters configured via - * {@link #configurationParameter(String, String)} or - * {@link #configurationParameters(Map)} takes precedence over the supplied - * configuration parameters. - * - * @param configurationParameters the parent instance to be used for looking - * up configuration parameters that have not been explicitly configured; - * never {@code null} - * @since 1.8 - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - */ - @API(status = EXPERIMENTAL, since = "1.8") - public LauncherDiscoveryRequestBuilder parentConfigurationParameters( - ConfigurationParameters configurationParameters) { - Preconditions.notNull(configurationParameters, "parent configuration parameters must not be null"); - this.parentConfigurationParameters = configurationParameters; - return this; - } - - /** - * Add all of the supplied discovery listeners to the request. - * - *

In addition to the {@linkplain LauncherDiscoveryListener listeners} - * registered using this method, this builder will add a default listener - * to this request that can be specified using the - * {@value LauncherDiscoveryRequestBuilder#DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME} - * configuration parameter. - * - * @param listeners the {@code LauncherDiscoveryListeners} to add; never - * {@code null} - * @return this builder for method chaining - * @since 1.6 - * @see LauncherDiscoveryListener - * @see LauncherDiscoveryListeners - * @see LauncherDiscoveryRequestBuilder#DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME - */ - @API(status = EXPERIMENTAL, since = "1.6") - public LauncherDiscoveryRequestBuilder listeners(LauncherDiscoveryListener... listeners) { - Preconditions.notNull(listeners, "discovery listener array must not be null"); - Preconditions.containsNoNullElements(listeners, "individual discovery listeners must not be null"); - this.discoveryListeners.addAll(Arrays.asList(listeners)); - return this; - } - - /** - * Configure whether implicit configuration parameters should be considered. - * - *

By default, in addition to those parameters that are passed explicitly - * to this builder, configuration parameters are read from system properties - * and from the {@code junit-platform.properties} classpath resource. - * Passing {@code false} to this method, disables the latter two sources so - * that only explicit configuration parameters are taken into account. - * - * @since 1.7 - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - */ - @API(status = EXPERIMENTAL, since = "1.7") - public LauncherDiscoveryRequestBuilder enableImplicitConfigurationParameters(boolean enabled) { - this.implicitConfigurationParametersEnabled = enabled; - return this; - } - - private void storeFilter(Filter filter) { - if (filter instanceof EngineFilter) { - this.engineFilters.add((EngineFilter) filter); - } - else if (filter instanceof PostDiscoveryFilter) { - this.postDiscoveryFilters.add((PostDiscoveryFilter) filter); - } - else if (filter instanceof DiscoveryFilter) { - this.discoveryFilters.add((DiscoveryFilter) filter); - } - else { - throw new PreconditionViolationException( - String.format("Filter [%s] must implement %s, %s, or %s.", filter, EngineFilter.class.getSimpleName(), - PostDiscoveryFilter.class.getSimpleName(), DiscoveryFilter.class.getSimpleName())); - } - } - - /** - * Build the {@link LauncherDiscoveryRequest} that has been configured via - * this builder. - */ - public LauncherDiscoveryRequest build() { - LauncherConfigurationParameters launcherConfigurationParameters = buildLauncherConfigurationParameters(); - LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); - return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, - this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener); - } - - private LauncherConfigurationParameters buildLauncherConfigurationParameters() { - Builder builder = LauncherConfigurationParameters.builder() // - .explicitParameters(this.configurationParameters) // - .enableImplicitProviders(this.implicitConfigurationParametersEnabled); - - if (parentConfigurationParameters != null) { - builder.parentConfigurationParameters(this.parentConfigurationParameters); - } - - return builder.build(); - } - - private LauncherDiscoveryListener getLauncherDiscoveryListener(ConfigurationParameters configurationParameters) { - LauncherDiscoveryListener defaultDiscoveryListener = getDefaultLauncherDiscoveryListener( - configurationParameters); - if (discoveryListeners.isEmpty()) { - return defaultDiscoveryListener; - } - if (discoveryListeners.contains(defaultDiscoveryListener)) { - return LauncherDiscoveryListeners.composite(discoveryListeners); - } - List allDiscoveryListeners = new ArrayList<>(discoveryListeners.size() + 1); - allDiscoveryListeners.addAll(discoveryListeners); - allDiscoveryListeners.add(defaultDiscoveryListener); - return LauncherDiscoveryListeners.composite(allDiscoveryListeners); - } - - private LauncherDiscoveryListener getDefaultLauncherDiscoveryListener( - ConfigurationParameters configurationParameters) { - String value = configurationParameters.get(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME) // - .orElse(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE); - return LauncherDiscoveryListeners.fromConfigurationParameter( - DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, value); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java deleted file mode 100644 index 9e6d3e7a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.Collections.unmodifiableMap; -import static java.util.stream.Collectors.toMap; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; - -/** - * Represents the result of test discovery of the configured - * {@linkplain TestEngine test engines}. - * - * @since 1.7 - */ -@API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) -public class LauncherDiscoveryResult { - - private final Map testEngineDescriptors; - private final ConfigurationParameters configurationParameters; - - LauncherDiscoveryResult(Map testEngineDescriptors, - ConfigurationParameters configurationParameters) { - this.testEngineDescriptors = unmodifiableMap(new LinkedHashMap<>(testEngineDescriptors)); - this.configurationParameters = configurationParameters; - } - - public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { - return this.testEngineDescriptors.get(testEngine); - } - - ConfigurationParameters getConfigurationParameters() { - return configurationParameters; - } - - public Collection getTestEngines() { - return this.testEngineDescriptors.keySet(); - } - - Collection getEngineTestDescriptors() { - return this.testEngineDescriptors.values(); - } - - public LauncherDiscoveryResult withRetainedEngines(Predicate predicate) { - Map prunedTestEngineDescriptors = retainEngines(predicate); - if (prunedTestEngineDescriptors.size() < testEngineDescriptors.size()) { - return new LauncherDiscoveryResult(prunedTestEngineDescriptors, configurationParameters); - } - return this; - } - - private Map retainEngines(Predicate predicate) { - // @formatter:off - return testEngineDescriptors.entrySet() - .stream() - .filter(entry -> predicate.test(entry.getValue())) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java deleted file mode 100644 index 3ac0b4c8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.launcher.LauncherConstants.DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ClassNamePatternFilterUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherSession; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.TestExecutionListener; - -/** - * Factory for creating {@link Launcher} instances by invoking {@link #create()} - * or {@link #create(LauncherConfig)}. - * - *

By default, test engines are discovered at runtime using the - * {@link java.util.ServiceLoader ServiceLoader} mechanism. For that purpose, a - * text file named {@code META-INF/services/org.junit.platform.engine.TestEngine} - * has to be added to the engine's JAR file in which the fully qualified name - * of the implementation class of the {@link org.junit.platform.engine.TestEngine} - * interface is declared. - * - *

By default, test execution listeners are discovered at runtime via the - * {@link java.util.ServiceLoader ServiceLoader} mechanism and are - * automatically registered with the {@link Launcher} created by this factory. - * Users may register additional listeners using the - * {@link Launcher#registerTestExecutionListeners(TestExecutionListener...)} - * method on the created launcher instance. - * - *

For full control over automatic registration and programmatic registration - * of test engines and listeners, supply an instance of {@link LauncherConfig} - * to {@link #create(LauncherConfig)}. - * - * @since 1.0 - * @see Launcher - * @see LauncherConfig - */ -@API(status = STABLE, since = "1.0") -public class LauncherFactory { - - private static final ServiceLoaderRegistry SERVICE_LOADER_REGISTRY = new ServiceLoaderRegistry(); - - private LauncherFactory() { - /* no-op */ - } - - /** - * Factory method for opening a new {@link LauncherSession} using the - * {@linkplain LauncherConfig#DEFAULT default} {@link LauncherConfig}. - * - * @throws PreconditionViolationException if no test engines are detected - * @since 1.8 - * @see #openSession(LauncherConfig) - */ - @API(status = EXPERIMENTAL, since = "1.8") - public static LauncherSession openSession() throws PreconditionViolationException { - return openSession(LauncherConfig.DEFAULT); - } - - /** - * Factory method for opening a new {@link LauncherSession} using the - * supplied {@link LauncherConfig}. - * - * @param config the configuration for the session and the launcher; never - * {@code null} - * @throws PreconditionViolationException if the supplied configuration is - * {@code null}, or if no test engines are detected - * @since 1.8 - * @see #openSession() - */ - @API(status = EXPERIMENTAL, since = "1.8") - public static LauncherSession openSession(LauncherConfig config) throws PreconditionViolationException { - return new DefaultLauncherSession(createDefaultLauncher(config), createLauncherSessionListener(config)); - } - - /** - * Factory method for creating a new {@link Launcher} using the - * {@linkplain LauncherConfig#DEFAULT default} {@link LauncherConfig}. - * - * @throws PreconditionViolationException if no test engines are detected - * @see #create(LauncherConfig) - */ - public static Launcher create() throws PreconditionViolationException { - return create(LauncherConfig.DEFAULT); - } - - /** - * Factory method for creating a new {@link Launcher} using the supplied - * {@link LauncherConfig}. - * - * @param config the configuration for the launcher; never {@code null} - * @throws PreconditionViolationException if the supplied configuration is - * {@code null}, or if no test engines are detected - * registered - * @since 1.3 - * @see #create() - */ - @API(status = EXPERIMENTAL, since = "1.3") - public static Launcher create(LauncherConfig config) throws PreconditionViolationException { - return new SessionPerRequestLauncher(createDefaultLauncher(config), createLauncherSessionListener(config)); - } - - private static DefaultLauncher createDefaultLauncher(LauncherConfig config) { - Preconditions.notNull(config, "LauncherConfig must not be null"); - - Set engines = collectTestEngines(config); - List filters = collectPostDiscoveryFilters(config); - - DefaultLauncher launcher = new DefaultLauncher(engines, filters); - - registerLauncherDiscoveryListeners(config, launcher); - registerTestExecutionListeners(config, launcher); - - return launcher; - } - - private static Set collectTestEngines(LauncherConfig config) { - Set engines = new LinkedHashSet<>(); - if (config.isTestEngineAutoRegistrationEnabled()) { - new ServiceLoaderTestEngineRegistry().loadTestEngines().forEach(engines::add); - } - engines.addAll(config.getAdditionalTestEngines()); - return engines; - } - - private static LauncherSessionListener createLauncherSessionListener(LauncherConfig config) { - ListenerRegistry listenerRegistry = ListenerRegistry.forLauncherSessionListeners(); - if (config.isLauncherSessionListenerAutoRegistrationEnabled()) { - SERVICE_LOADER_REGISTRY.load(LauncherSessionListener.class).forEach(listenerRegistry::add); - } - config.getAdditionalLauncherSessionListeners().forEach(listenerRegistry::add); - return listenerRegistry.getCompositeListener(); - } - - private static List collectPostDiscoveryFilters(LauncherConfig config) { - List filters = new ArrayList<>(); - if (config.isPostDiscoveryFilterAutoRegistrationEnabled()) { - SERVICE_LOADER_REGISTRY.load(PostDiscoveryFilter.class).forEach(filters::add); - } - filters.addAll(config.getAdditionalPostDiscoveryFilters()); - return filters; - } - - private static void registerLauncherDiscoveryListeners(LauncherConfig config, Launcher launcher) { - if (config.isLauncherDiscoveryListenerAutoRegistrationEnabled()) { - SERVICE_LOADER_REGISTRY.load(LauncherDiscoveryListener.class).forEach( - launcher::registerLauncherDiscoveryListeners); - } - config.getAdditionalLauncherDiscoveryListeners().forEach(launcher::registerLauncherDiscoveryListeners); - } - - private static void registerTestExecutionListeners(LauncherConfig config, Launcher launcher) { - if (config.isTestExecutionListenerAutoRegistrationEnabled()) { - loadAndFilterTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); - } - config.getAdditionalTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); - } - - private static Stream loadAndFilterTestExecutionListeners() { - Iterable listeners = SERVICE_LOADER_REGISTRY.load(TestExecutionListener.class); - ConfigurationParameters configurationParameters = LauncherConfigurationParameters.builder().build(); - String deactivatedListenersPattern = configurationParameters.get( - DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME).orElse(null); - // @formatter:off - return StreamSupport.stream(listeners.spliterator(), false) - .filter(ClassNamePatternFilterUtils.excludeMatchingClasses(deactivatedListenersPattern)); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java deleted file mode 100644 index 1fab26cd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; -import org.junit.platform.launcher.listeners.session.LauncherSessionListeners; - -class ListenerRegistry { - - private final Function, T> compositeListenerFactory; - - static ListenerRegistry forLauncherSessionListeners() { - return create(LauncherSessionListeners::composite); - } - - static ListenerRegistry forLauncherDiscoveryListeners() { - return create(LauncherDiscoveryListeners::composite); - } - - static ListenerRegistry forTestExecutionListeners() { - return create(CompositeTestExecutionListener::new); - } - - static ListenerRegistry forEngineExecutionListeners() { - return create(CompositeEngineExecutionListener::new); - } - - static ListenerRegistry create(Function, T> compositeListenerFactory) { - return new ListenerRegistry<>(compositeListenerFactory); - } - - static ListenerRegistry copyOf(ListenerRegistry source) { - ListenerRegistry registry = new ListenerRegistry<>(source.compositeListenerFactory); - if (!source.listeners.isEmpty()) { - registry.addAll(source.listeners); - } - return registry; - } - - private final ArrayList listeners = new ArrayList<>(); - - private ListenerRegistry(Function, T> compositeListenerFactory) { - this.compositeListenerFactory = compositeListenerFactory; - } - - ListenerRegistry add(T listener) { - Preconditions.notNull(listener, "listener must not be null"); - this.listeners.add(listener); - return this; - } - - @SafeVarargs - @SuppressWarnings("varargs") - final ListenerRegistry addAll(T... listeners) { - Preconditions.notEmpty(listeners, "listeners array must not be null or empty"); - return addAll(Arrays.asList(listeners)); - } - - ListenerRegistry addAll(Collection listeners) { - Preconditions.notEmpty(listeners, "listeners collection must not be null or empty"); - Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); - this.listeners.addAll(listeners); - return this; - } - - T getCompositeListener() { - this.listeners.trimToSize(); - return compositeListenerFactory.apply(this.listeners); - } - - List getListeners() { - return Collections.unmodifiableList(this.listeners); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java deleted file mode 100644 index 9b55df04..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; - -/** - * Delays reporting of engine skipped/finished events so that exceptions thrown - * by engines can be reported to listeners. - * - * @since 1.6 - */ -class OutcomeDelayingEngineExecutionListener extends DelegatingEngineExecutionListener { - - private final TestDescriptor engineDescriptor; - - private volatile boolean engineStarted; - private volatile Outcome outcome; - private volatile String skipReason; - private volatile TestExecutionResult executionResult; - - OutcomeDelayingEngineExecutionListener(EngineExecutionListener delegate, TestDescriptor engineDescriptor) { - super(delegate); - this.engineDescriptor = engineDescriptor; - } - - @Override - public void executionSkipped(TestDescriptor testDescriptor, String reason) { - if (testDescriptor == engineDescriptor) { - outcome = Outcome.SKIPPED; - skipReason = reason; - } - else { - super.executionSkipped(testDescriptor, reason); - } - } - - @Override - public void executionStarted(TestDescriptor testDescriptor) { - if (testDescriptor == engineDescriptor) { - engineStarted = true; - } - super.executionStarted(testDescriptor); - } - - @Override - public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult executionResult) { - if (testDescriptor == engineDescriptor) { - outcome = Outcome.FINISHED; - this.executionResult = executionResult; - } - else { - super.executionFinished(testDescriptor, executionResult); - } - } - - void reportEngineOutcome() { - if (outcome == Outcome.FINISHED) { - super.executionFinished(engineDescriptor, executionResult); - } - else if (outcome == Outcome.SKIPPED) { - super.executionSkipped(engineDescriptor, skipReason); - } - } - - void reportEngineFailure(Throwable throwable) { - if (!engineStarted) { - super.executionStarted(engineDescriptor); - } - if (executionResult != null && executionResult.getThrowable().isPresent()) { - throwable.addSuppressed(executionResult.getThrowable().get()); - } - super.executionFinished(engineDescriptor, TestExecutionResult.failed(throwable)); - } - - private enum Outcome { - SKIPPED, FINISHED - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java deleted file mode 100644 index 47ccd2f2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.stream.Collectors.toList; -import static java.util.stream.StreamSupport.stream; - -import java.util.ServiceLoader; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassLoaderUtils; - -/** - * @since 1.8 - */ -class ServiceLoaderRegistry { - - private static final Logger logger = LoggerFactory.getLogger(ServiceLoaderRegistry.class); - - Iterable load(Class serviceProviderClass) { - Iterable listeners = ServiceLoader.load(serviceProviderClass, ClassLoaderUtils.getDefaultClassLoader()); - logger.config(() -> "Loaded " + serviceProviderClass.getSimpleName() + " instances: " - + stream(listeners.spliterator(), false).map(Object::toString).collect(toList())); - return listeners; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java deleted file mode 100644 index 00233a6b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.ServiceLoader; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassLoaderUtils; -import org.junit.platform.engine.TestEngine; - -/** - * @since 1.0 - */ -@API(status = INTERNAL, since = "1.0", consumers = "org.junit.platform.suite.engine") -public final class ServiceLoaderTestEngineRegistry { - - public ServiceLoaderTestEngineRegistry() { - } - - private static final Logger logger = LoggerFactory.getLogger(ServiceLoaderTestEngineRegistry.class); - - public Iterable loadTestEngines() { - Iterable testEngines = ServiceLoader.load(TestEngine.class, - ClassLoaderUtils.getDefaultClassLoader()); - logger.config(() -> TestEngineFormatter.format("Discovered TestEngines", testEngines)); - return testEngines; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java deleted file mode 100644 index 9c0b7dd4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.LauncherSession; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.8 - */ -class SessionPerRequestLauncher implements InternalLauncher { - - private final InternalLauncher delegate; - private final LauncherSessionListener sessionListener; - - SessionPerRequestLauncher(InternalLauncher delegate, LauncherSessionListener sessionListener) { - this.delegate = delegate; - this.sessionListener = sessionListener; - } - - @Override - public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { - delegate.registerLauncherDiscoveryListeners(listeners); - } - - @Override - public void registerTestExecutionListeners(TestExecutionListener... listeners) { - delegate.registerTestExecutionListeners(listeners); - } - - @Override - public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { - try (LauncherSession session = createSession()) { - return session.getLauncher().discover(launcherDiscoveryRequest); - } - } - - @Override - public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { - try (LauncherSession session = createSession()) { - session.getLauncher().execute(launcherDiscoveryRequest, listeners); - } - } - - @Override - public void execute(TestPlan testPlan, TestExecutionListener... listeners) { - try (LauncherSession session = createSession()) { - session.getLauncher().execute(testPlan, listeners); - } - } - - @Override - public ListenerRegistry getTestExecutionListenerRegistry() { - return delegate.getTestExecutionListenerRegistry(); - } - - @Override - public ListenerRegistry getLauncherDiscoveryListenerRegistry() { - return delegate.getLauncherDiscoveryListenerRegistry(); - } - - private LauncherSession createSession() { - return new DefaultLauncherSession(delegate, sessionListener); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java deleted file mode 100644 index be0e6a41..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_DEFAULT; -import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME; -import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; -import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; -import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; -import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiConsumer; - -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.core.CompositeTestExecutionListener.EagerTestExecutionListener; - -/** - * @since 1.3 - */ -class StreamInterceptingTestExecutionListener implements EagerTestExecutionListener { - - private final Optional stdoutInterceptor; - private final Optional stderrInterceptor; - private final BiConsumer reporter; - - static Optional create(ConfigurationParameters configurationParameters, - BiConsumer reporter) { - - boolean captureStdout = configurationParameters.getBoolean(CAPTURE_STDOUT_PROPERTY_NAME).orElse(false); - boolean captureStderr = configurationParameters.getBoolean(CAPTURE_STDERR_PROPERTY_NAME).orElse(false); - if (!captureStdout && !captureStderr) { - return Optional.empty(); - } - - int maxSize = configurationParameters.get(CAPTURE_MAX_BUFFER_PROPERTY_NAME, Integer::valueOf) // - .orElse(CAPTURE_MAX_BUFFER_DEFAULT); - - Optional stdoutInterceptor = captureStdout ? StreamInterceptor.registerStdout(maxSize) - : Optional.empty(); - Optional stderrInterceptor = captureStderr ? StreamInterceptor.registerStderr(maxSize) - : Optional.empty(); - - if ((!stdoutInterceptor.isPresent() && captureStdout) || (!stderrInterceptor.isPresent() && captureStderr)) { - stdoutInterceptor.ifPresent(StreamInterceptor::unregister); - stderrInterceptor.ifPresent(StreamInterceptor::unregister); - return Optional.empty(); - } - return Optional.of(new StreamInterceptingTestExecutionListener(stdoutInterceptor, stderrInterceptor, reporter)); - } - - private StreamInterceptingTestExecutionListener(Optional stdoutInterceptor, - Optional stderrInterceptor, BiConsumer reporter) { - this.stdoutInterceptor = stdoutInterceptor; - this.stderrInterceptor = stderrInterceptor; - this.reporter = reporter; - } - - void unregister() { - stdoutInterceptor.ifPresent(StreamInterceptor::unregister); - stderrInterceptor.ifPresent(StreamInterceptor::unregister); - } - - @Override - public void executionJustStarted(TestIdentifier testIdentifier) { - stdoutInterceptor.ifPresent(StreamInterceptor::capture); - stderrInterceptor.ifPresent(StreamInterceptor::capture); - } - - @Override - public void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - Map map = new HashMap<>(); - String out = stdoutInterceptor.map(StreamInterceptor::consume).orElse(""); - if (StringUtils.isNotBlank(out)) { - map.put(STDOUT_REPORT_ENTRY_KEY, out); - } - String err = stderrInterceptor.map(StreamInterceptor::consume).orElse(""); - if (StringUtils.isNotBlank(err)) { - map.put(STDERR_REPORT_ENTRY_KEY, err); - } - if (!map.isEmpty()) { - reporter.accept(testIdentifier, ReportEntry.from(map)); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java deleted file mode 100644 index aa72890f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Optional; -import java.util.function.Consumer; - -/** - * @since 1.3 - */ -class StreamInterceptor extends PrintStream { - - private final PrintStream originalStream; - private final Consumer unregisterAction; - private final int maxNumberOfBytesPerThread; - - private final ThreadLocal output = ThreadLocal.withInitial( - RewindableByteArrayOutputStream::new); - - static Optional registerStdout(int maxNumberOfBytesPerThread) { - return register(System.out, System::setOut, maxNumberOfBytesPerThread); - } - - static Optional registerStderr(int maxNumberOfBytesPerThread) { - return register(System.err, System::setErr, maxNumberOfBytesPerThread); - } - - static Optional register(PrintStream originalStream, Consumer streamSetter, - int maxNumberOfBytesPerThread) { - if (originalStream instanceof StreamInterceptor) { - return Optional.empty(); - } - StreamInterceptor interceptor = new StreamInterceptor(originalStream, streamSetter, maxNumberOfBytesPerThread); - streamSetter.accept(interceptor); - return Optional.of(interceptor); - } - - private StreamInterceptor(PrintStream originalStream, Consumer unregisterAction, - int maxNumberOfBytesPerThread) { - super(originalStream); - this.originalStream = originalStream; - this.unregisterAction = unregisterAction; - this.maxNumberOfBytesPerThread = maxNumberOfBytesPerThread; - } - - void capture() { - output.get().mark(); - } - - String consume() { - return output.get().rewind(); - } - - void unregister() { - unregisterAction.accept(originalStream); - } - - @Override - public void write(int b) { - RewindableByteArrayOutputStream out = output.get(); - if (out.isMarked() && out.size() < maxNumberOfBytesPerThread) { - out.write(b); - } - super.write(b); - } - - @Override - public void write(byte[] b) { - write(b, 0, b.length); - } - - @Override - public void write(byte[] buf, int off, int len) { - RewindableByteArrayOutputStream out = output.get(); - if (out.isMarked()) { - int actualLength = Math.max(0, Math.min(len, maxNumberOfBytesPerThread - out.size())); - if (actualLength > 0) { - out.write(buf, off, actualLength); - } - } - super.write(buf, off, len); - } - - static class RewindableByteArrayOutputStream extends ByteArrayOutputStream { - - private final Deque markedPositions = new ArrayDeque<>(); - - boolean isMarked() { - return !markedPositions.isEmpty(); - } - - void mark() { - markedPositions.addFirst(count); - } - - String rewind() { - Integer position = markedPositions.pollFirst(); - if (position == null || position == count) { - return ""; - } - int length = count - position; - count -= length; - return new String(buf, position, length); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java deleted file mode 100644 index 0c5beba2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.lang.String.join; -import static java.util.stream.Collectors.joining; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import org.junit.platform.commons.util.ClassLoaderUtils; -import org.junit.platform.commons.util.CollectionUtils; -import org.junit.platform.engine.TestEngine; - -class TestEngineFormatter { - - private TestEngineFormatter() { - } - - @SuppressWarnings("unchecked") - static String format(String title, Iterable testEngines) { - return format(title, (Stream) CollectionUtils.toStream(testEngines)); - } - - private static String format(String title, Stream testEngines) { - String details = testEngines // - .map(engine -> String.format("- %s (%s)", engine.getId(), join(", ", computeAttributes(engine)))) // - .collect(joining("\n")); - return title + ":" + (details.isEmpty() ? " " : "\n" + details); - } - - private static List computeAttributes(TestEngine engine) { - List attributes = new ArrayList<>(4); - engine.getGroupId().ifPresent(groupId -> attributes.add("group ID: " + groupId)); - engine.getArtifactId().ifPresent(artifactId -> attributes.add("artifact ID: " + artifactId)); - engine.getVersion().ifPresent(version -> attributes.add("version: " + version)); - ClassLoaderUtils.getLocation(engine).ifPresent(location -> attributes.add("location: " + location)); - return attributes; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java deleted file mode 100644 index 779b81d6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Core support classes for the {@link org.junit.platform.launcher.Launcher Launcher} - * including the {@link org.junit.platform.launcher.core.LauncherFactory LauncherFactory} - * and the {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder - * LauncherDiscoveryRequestBuilder}. - */ - -package org.junit.platform.launcher.core; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java deleted file mode 100644 index 4ab5097a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.apiguardian.api.API.Status.DEPRECATED; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * Utility methods for dealing with legacy reporting infrastructure, such as - * reporting systems built on the Ant-based XML reporting format for JUnit 4. - * - * @since 1.0.3 - * @deprecated Use {@link org.junit.platform.reporting.legacy.LegacyReportingUtils} - * instead. - */ -@Deprecated -@SuppressWarnings("JavadocReference") -@API(status = DEPRECATED, since = "1.6") -public class LegacyReportingUtils { - - private LegacyReportingUtils() { - /* no-op */ - } - - /** - * Get the class name for the supplied {@link TestIdentifier} using the - * supplied {@link TestPlan}. - * - *

This implementation attempts to find the closest test identifier with - * a {@link ClassSource} by traversing the hierarchy upwards towards the - * root starting with the supplied test identifier. In case no such source - * is found, it falls back to using the parent's - * {@linkplain TestIdentifier#getLegacyReportingName legacy reporting name}. - * - * @param testPlan the test plan that contains the {@code TestIdentifier}; - * never {@code null} - * @param testIdentifier the identifier to determine the class name for; - * never {@code null} - * @see TestIdentifier#getLegacyReportingName - */ - public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifier) { - Preconditions.notNull(testPlan, "testPlan must not be null"); - Preconditions.notNull(testIdentifier, "testIdentifier must not be null"); - for (TestIdentifier current = testIdentifier; current != null; current = getParent(testPlan, current)) { - ClassSource source = getClassSource(current); - if (source != null) { - return source.getClassName(); - } - } - return getParentLegacyReportingName(testPlan, testIdentifier); - } - - private static TestIdentifier getParent(TestPlan testPlan, TestIdentifier testIdentifier) { - return testPlan.getParent(testIdentifier).orElse(null); - } - - private static ClassSource getClassSource(TestIdentifier current) { - // @formatter:off - return current.getSource() - .filter(ClassSource.class::isInstance) - .map(ClassSource.class::cast) - .orElse(null); - // @formatter:on - } - - private static String getParentLegacyReportingName(TestPlan testPlan, TestIdentifier testIdentifier) { - // @formatter:off - return testPlan.getParent(testIdentifier) - .map(TestIdentifier::getLegacyReportingName) - .orElse(""); - // @formatter:on - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java deleted file mode 100644 index 949d3657..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.util.function.BiConsumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * Simple {@link TestExecutionListener} for logging informational messages - * for all events via a {@link BiConsumer} that consumes {@code Throwable} - * and {@code Supplier}. - * - * @since 1.0 - * @see #forJavaUtilLogging() - * @see #forJavaUtilLogging(Level) - * @see LoggingListener#LoggingListener(BiConsumer) - */ -@API(status = MAINTAINED, since = "1.0") -public class LoggingListener implements TestExecutionListener { - - /** - * Create a {@code LoggingListener} which delegates to a - * {@link java.util.logging.Logger} using a log level of - * {@link Level#FINE FINE}. - * - * @see #forJavaUtilLogging(Level) - * @see #forBiConsumer(BiConsumer) - */ - public static LoggingListener forJavaUtilLogging() { - return forJavaUtilLogging(Level.FINE); - } - - /** - * Create a {@code LoggingListener} which delegates to a - * {@link java.util.logging.Logger} using the supplied - * {@linkplain Level log level}. - * - * @param logLevel the log level to use; never {@code null} - * @see #forJavaUtilLogging() - * @see #forBiConsumer(BiConsumer) - */ - public static LoggingListener forJavaUtilLogging(Level logLevel) { - Preconditions.notNull(logLevel, "logLevel must not be null"); - Logger logger = Logger.getLogger(LoggingListener.class.getName()); - return new LoggingListener((t, messageSupplier) -> logger.log(logLevel, t, messageSupplier)); - } - - /** - * Create a {@code LoggingListener} which delegates to the supplied - * {@link BiConsumer} for consumption of logging messages. - * - *

The {@code BiConsumer's} arguments are a {@link Throwable} (potentially - * {@code null}) and a {@link Supplier} (never {@code null}) for the log - * message. - * - * @param logger a logger implemented as a {@code BiConsumer}; - * never {@code null} - * - * @see #forJavaUtilLogging() - * @see #forJavaUtilLogging(Level) - */ - public static LoggingListener forBiConsumer(BiConsumer> logger) { - return new LoggingListener(logger); - } - - private final BiConsumer> logger; - - private LoggingListener(BiConsumer> logger) { - this.logger = Preconditions.notNull(logger, "logger must not be null"); - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - log("TestPlan Execution Started: %s", testPlan); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - log("TestPlan Execution Finished: %s", testPlan); - } - - @Override - public void dynamicTestRegistered(TestIdentifier testIdentifier) { - log("Dynamic Test Registered: %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - log("Execution Started: %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - log("Execution Skipped: %s - %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), reason); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - logWithThrowable("Execution Finished: %s - %s - %s", testExecutionResult.getThrowable().orElse(null), - testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), testExecutionResult); - } - - private void log(String message, Object... args) { - logWithThrowable(message, null, args); - } - - private void logWithThrowable(String message, Throwable t, Object... args) { - this.logger.accept(t, () -> String.format(message, args)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java deleted file mode 100644 index 96f34144..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static java.lang.String.join; -import static java.util.Collections.synchronizedList; - -import java.io.PrintWriter; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * Mutable, internal implementation of the {@link TestExecutionSummary} API. - * - * @since 1.0 - */ -class MutableTestExecutionSummary implements TestExecutionSummary { - - private static final String TAB = " "; - private static final String DOUBLE_TAB = TAB + TAB; - private static final int DEFAULT_MAX_STACKTRACE_LINES = 10; - - private static final String CAUSED_BY = "Caused by: "; - private static final String SUPPRESSED = "Suppressed: "; - private static final String CIRCULAR = "Circular reference: "; - - final AtomicLong containersFound = new AtomicLong(); - final AtomicLong containersStarted = new AtomicLong(); - final AtomicLong containersSkipped = new AtomicLong(); - final AtomicLong containersAborted = new AtomicLong(); - final AtomicLong containersSucceeded = new AtomicLong(); - final AtomicLong containersFailed = new AtomicLong(); - - final AtomicLong testsFound = new AtomicLong(); - final AtomicLong testsStarted = new AtomicLong(); - final AtomicLong testsSkipped = new AtomicLong(); - final AtomicLong testsAborted = new AtomicLong(); - final AtomicLong testsSucceeded = new AtomicLong(); - final AtomicLong testsFailed = new AtomicLong(); - - private final TestPlan testPlan; - private final List failures = synchronizedList(new ArrayList<>()); - private final long timeStarted; - private final long timeStartedNanos; - long timeFinished; - long timeFinishedNanos; - - MutableTestExecutionSummary(TestPlan testPlan) { - this.testPlan = testPlan; - this.containersFound.set(testPlan.countTestIdentifiers(TestIdentifier::isContainer)); - this.testsFound.set(testPlan.countTestIdentifiers(TestIdentifier::isTest)); - this.timeStarted = System.currentTimeMillis(); - this.timeStartedNanos = System.nanoTime(); - } - - void addFailure(TestIdentifier testIdentifier, Throwable throwable) { - this.failures.add(new DefaultFailure(testIdentifier, throwable)); - } - - @Override - public long getTimeStarted() { - return this.timeStarted; - } - - @Override - public long getTimeFinished() { - return this.timeFinished; - } - - @Override - public long getTotalFailureCount() { - return getTestsFailedCount() + getContainersFailedCount(); - } - - @Override - public long getContainersFoundCount() { - return this.containersFound.get(); - } - - @Override - public long getContainersStartedCount() { - return this.containersStarted.get(); - } - - @Override - public long getContainersSkippedCount() { - return this.containersSkipped.get(); - } - - @Override - public long getContainersAbortedCount() { - return this.containersAborted.get(); - } - - @Override - public long getContainersSucceededCount() { - return this.containersSucceeded.get(); - } - - @Override - public long getContainersFailedCount() { - return this.containersFailed.get(); - } - - @Override - public long getTestsFoundCount() { - return this.testsFound.get(); - } - - @Override - public long getTestsStartedCount() { - return this.testsStarted.get(); - } - - @Override - public long getTestsSkippedCount() { - return this.testsSkipped.get(); - } - - @Override - public long getTestsAbortedCount() { - return this.testsAborted.get(); - } - - @Override - public long getTestsSucceededCount() { - return this.testsSucceeded.get(); - } - - @Override - public long getTestsFailedCount() { - return this.testsFailed.get(); - } - - @Override - public void printTo(PrintWriter writer) { - // @formatter:off - writer.printf("%nTest run finished after %d ms%n" - - + "[%10d containers found ]%n" - + "[%10d containers skipped ]%n" - + "[%10d containers started ]%n" - + "[%10d containers aborted ]%n" - + "[%10d containers successful ]%n" - + "[%10d containers failed ]%n" - - + "[%10d tests found ]%n" - + "[%10d tests skipped ]%n" - + "[%10d tests started ]%n" - + "[%10d tests aborted ]%n" - + "[%10d tests successful ]%n" - + "[%10d tests failed ]%n" - + "%n", - - Duration.ofNanos(this.timeFinishedNanos - this.timeStartedNanos).toMillis(), - - getContainersFoundCount(), - getContainersSkippedCount(), - getContainersStartedCount(), - getContainersAbortedCount(), - getContainersSucceededCount(), - getContainersFailedCount(), - - getTestsFoundCount(), - getTestsSkippedCount(), - getTestsStartedCount(), - getTestsAbortedCount(), - getTestsSucceededCount(), - getTestsFailedCount() - ); - // @formatter:on - - writer.flush(); - } - - @Override - public void printFailuresTo(PrintWriter writer) { - printFailuresTo(writer, DEFAULT_MAX_STACKTRACE_LINES); - } - - @Override - public void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { - Preconditions.notNull(writer, "PrintWriter must not be null"); - Preconditions.condition(maxStackTraceLines >= 0, "maxStackTraceLines must be a positive number"); - - if (getTotalFailureCount() > 0) { - writer.printf("%nFailures (%d):%n", getTotalFailureCount()); - this.failures.forEach(failure -> { - writer.printf("%s%s%n", TAB, describeTest(failure.getTestIdentifier())); - printSource(writer, failure.getTestIdentifier()); - writer.printf("%s=> %s%n", DOUBLE_TAB, failure.getException()); - printStackTrace(writer, failure.getException(), maxStackTraceLines); - }); - writer.flush(); - } - } - - @Override - public List getFailures() { - return Collections.unmodifiableList(failures); - } - - private String describeTest(TestIdentifier testIdentifier) { - List descriptionParts = new ArrayList<>(); - collectTestDescription(testIdentifier, descriptionParts); - return join(":", descriptionParts); - } - - private void collectTestDescription(TestIdentifier identifier, List descriptionParts) { - descriptionParts.add(0, identifier.getDisplayName()); - this.testPlan.getParent(identifier).ifPresent(parent -> collectTestDescription(parent, descriptionParts)); - } - - private void printSource(PrintWriter writer, TestIdentifier testIdentifier) { - testIdentifier.getSource().ifPresent(source -> writer.printf("%s%s%n", DOUBLE_TAB, source)); - } - - private void printStackTrace(PrintWriter writer, Throwable throwable, int max) { - if (throwable.getCause() != null - || (throwable.getSuppressed() != null && throwable.getSuppressed().length > 0)) { - max = max / 2; - } - printStackTrace(writer, new StackTraceElement[] {}, throwable, "", DOUBLE_TAB + " ", new HashSet<>(), max); - writer.flush(); - } - - private void printStackTrace(PrintWriter writer, StackTraceElement[] parentTrace, Throwable throwable, - String caption, String indentation, Set seenThrowables, int max) { - if (seenThrowables.contains(throwable)) { - writer.printf("%s%s[%s%s]%n", indentation, TAB, CIRCULAR, throwable); - return; - } - seenThrowables.add(throwable); - - StackTraceElement[] trace = throwable.getStackTrace(); - if (parentTrace != null && parentTrace.length > 0) { - writer.printf("%s%s%s%n", indentation, caption, throwable); - } - int duplicates = numberOfCommonFrames(trace, parentTrace); - int numDistinctFrames = trace.length - duplicates; - int numDisplayLines = Math.min(numDistinctFrames, max); - for (int i = 0; i < numDisplayLines; i++) { - writer.printf("%s%s%s%n", indentation, TAB, trace[i]); - } - if (trace.length > max || duplicates != 0) { - writer.printf("%s%s%s%n", indentation, TAB, "[...]"); - } - - for (Throwable suppressed : throwable.getSuppressed()) { - printStackTrace(writer, trace, suppressed, SUPPRESSED, indentation + TAB, seenThrowables, max); - } - if (throwable.getCause() != null) { - printStackTrace(writer, trace, throwable.getCause(), CAUSED_BY, indentation, seenThrowables, max); - } - } - - private int numberOfCommonFrames(StackTraceElement[] currentTrace, StackTraceElement[] parentTrace) { - int currentIndex = currentTrace.length - 1; - for (int parentIndex = parentTrace.length - 1; currentIndex >= 0 - && parentIndex >= 0; currentIndex--, parentIndex--) { - if (!currentTrace[currentIndex].equals(parentTrace[parentIndex])) { - break; - } - } - return currentTrace.length - 1 - currentIndex; - } - - private static class DefaultFailure implements Failure { - - private static final long serialVersionUID = 1L; - - private final TestIdentifier testIdentifier; - private final Throwable exception; - - DefaultFailure(TestIdentifier testIdentifier, Throwable exception) { - this.testIdentifier = testIdentifier; - this.exception = exception; - } - - @Override - public TestIdentifier getTestIdentifier() { - return testIdentifier; - } - - @Override - public Throwable getException() { - return exception; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java deleted file mode 100644 index 14080b1f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.SecureRandom; -import java.util.Optional; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.StringUtils; - -@API(status = INTERNAL, since = "1.9") -public class OutputDir { - - public static OutputDir create(Optional customDir) { - try { - return createSafely(customDir, () -> Paths.get(".").toAbsolutePath()); - } - catch (IOException e) { - throw new UncheckedIOException("Failed to create output dir", e); - } - } - - /** - * Package private for testing purposes. - */ - static OutputDir createSafely(Optional customDir, Supplier currentWorkingDir) throws IOException { - Path cwd = currentWorkingDir.get(); - Path outputDir; - - if (customDir.isPresent() && StringUtils.isNotBlank(customDir.get())) { - outputDir = cwd.resolve(customDir.get()); - } - else if (Files.exists(cwd.resolve("pom.xml"))) { - outputDir = cwd.resolve("target"); - } - else if (containsFilesWithExtensions(cwd, ".gradle", ".gradle.kts")) { - outputDir = cwd.resolve("build"); - } - else { - outputDir = cwd; - } - - if (!Files.exists(outputDir)) { - Files.createDirectories(outputDir); - } - - return new OutputDir(outputDir); - } - - private final Path path; - - private OutputDir(Path path) { - this.path = path; - } - - public Path toPath() { - return path; - } - - public Path createFile(String prefix, String extension) throws UncheckedIOException { - String filename = String.format("%s-%d.%s", prefix, Math.abs(new SecureRandom().nextLong()), extension); - Path outputFile = path.resolve(filename); - - try { - if (Files.exists(outputFile)) { - Files.delete(outputFile); - } - return Files.createFile(outputFile); - } - catch (IOException e) { - throw new UncheckedIOException("Failed to create output file: " + outputFile, e); - } - } - - /** - * Determine if the supplied directory contains files with any of the - * supplied extensions. - */ - private static boolean containsFilesWithExtensions(Path dir, String... extensions) throws IOException { - return Files.find(dir, 1, // - (path, basicFileAttributes) -> { - if (basicFileAttributes.isRegularFile()) { - for (String extension : extensions) { - if (path.getFileName().toString().endsWith(extension)) { - return true; - } - } - } - return false; - }) // - .findFirst() // - .isPresent(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java deleted file mode 100644 index a161fc50..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static java.util.stream.Stream.concat; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * Simple {@link TestExecutionListener} that generates a - * {@linkplain TestExecutionSummary summary} of the test execution. - * - * @since 1.0 - * @see #getSummary() - */ -@API(status = MAINTAINED, since = "1.0") -public class SummaryGeneratingListener implements TestExecutionListener { - - private TestPlan testPlan; - private MutableTestExecutionSummary summary; - - public SummaryGeneratingListener() { - - } - - /** - * Get the summary generated by this listener. - */ - public TestExecutionSummary getSummary() { - return this.summary; - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - this.testPlan = testPlan; - this.summary = new MutableTestExecutionSummary(testPlan); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - this.summary.timeFinished = System.currentTimeMillis(); - this.summary.timeFinishedNanos = System.nanoTime(); - } - - @Override - public void dynamicTestRegistered(TestIdentifier testIdentifier) { - if (testIdentifier.isContainer()) { - this.summary.containersFound.incrementAndGet(); - } - if (testIdentifier.isTest()) { - this.summary.testsFound.incrementAndGet(); - } - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - // @formatter:off - long skippedContainers = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) - .filter(TestIdentifier::isContainer) - .count(); - long skippedTests = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) - .filter(TestIdentifier::isTest) - .count(); - // @formatter:on - this.summary.containersSkipped.addAndGet(skippedContainers); - this.summary.testsSkipped.addAndGet(skippedTests); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - if (testIdentifier.isContainer()) { - this.summary.containersStarted.incrementAndGet(); - } - if (testIdentifier.isTest()) { - this.summary.testsStarted.incrementAndGet(); - } - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - - switch (testExecutionResult.getStatus()) { - - case SUCCESSFUL: { - if (testIdentifier.isContainer()) { - this.summary.containersSucceeded.incrementAndGet(); - } - if (testIdentifier.isTest()) { - this.summary.testsSucceeded.incrementAndGet(); - } - break; - } - - case ABORTED: { - if (testIdentifier.isContainer()) { - this.summary.containersAborted.incrementAndGet(); - } - if (testIdentifier.isTest()) { - this.summary.testsAborted.incrementAndGet(); - } - break; - } - - case FAILED: { - if (testIdentifier.isContainer()) { - this.summary.containersFailed.incrementAndGet(); - } - if (testIdentifier.isTest()) { - this.summary.testsFailed.incrementAndGet(); - } - testExecutionResult.getThrowable().ifPresent( - throwable -> this.summary.addFailure(testIdentifier, throwable)); - break; - } - - default: - throw new PreconditionViolationException( - "Unsupported execution status:" + testExecutionResult.getStatus()); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java deleted file mode 100644 index 3a996d01..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.io.PrintWriter; -import java.io.Serializable; -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.launcher.TestIdentifier; - -/** - * Summary of test plan execution. - * - * @since 1.0 - * @see SummaryGeneratingListener - */ -@API(status = MAINTAINED, since = "1.0") -public interface TestExecutionSummary { - - /** - * Get the timestamp (in milliseconds) when the test plan started. - */ - long getTimeStarted(); - - /** - * Get the timestamp (in milliseconds) when the test plan finished. - */ - long getTimeFinished(); - - /** - * Get the total number of {@linkplain #getContainersFailedCount failed - * containers} and {@linkplain #getTestsFailedCount failed tests}. - * - * @see #getTestsFailedCount() - * @see #getContainersFailedCount() - */ - long getTotalFailureCount(); - - /** - * Get the number of containers found. - */ - long getContainersFoundCount(); - - /** - * Get the number of containers started. - */ - long getContainersStartedCount(); - - /** - * Get the number of containers skipped. - */ - long getContainersSkippedCount(); - - /** - * Get the number of containers aborted. - */ - long getContainersAbortedCount(); - - /** - * Get the number of containers that succeeded. - */ - long getContainersSucceededCount(); - - /** - * Get the number of containers that failed. - * - * @see #getTestsFailedCount() - * @see #getTotalFailureCount() - */ - long getContainersFailedCount(); - - /** - * Get the number of tests found. - */ - long getTestsFoundCount(); - - /** - * Get the number of tests started. - */ - long getTestsStartedCount(); - - /** - * Get the number of tests skipped. - */ - long getTestsSkippedCount(); - - /** - * Get the number of tests aborted. - */ - long getTestsAbortedCount(); - - /** - * Get the number of tests that succeeded. - */ - long getTestsSucceededCount(); - - /** - * Get the number of tests that failed. - * - * @see #getContainersFailedCount() - * @see #getTotalFailureCount() - */ - long getTestsFailedCount(); - - /** - * Print this summary to the supplied {@link PrintWriter}. - * - *

This method does not print failure messages. - * - * @see #printFailuresTo(PrintWriter) - */ - void printTo(PrintWriter writer); - - /** - * Print failed containers and tests, including sources and exception - * messages, to the supplied {@link PrintWriter}. - * - * @param writer the {@code PrintWriter} to which to print; never {@code null} - * @see #printTo(PrintWriter) - * @see #printFailuresTo(PrintWriter, int) - */ - void printFailuresTo(PrintWriter writer); - - /** - * Print failed containers and tests, including sources and exception - * messages, to the supplied {@link PrintWriter}. - * - *

The maximum number of lines to print for exception stack traces (if any) - * can be specified via the {@code maxStackTraceLines} argument. - * - *

By default, this method delegates to {@link #printFailuresTo(PrintWriter)}, - * effectively ignoring the {@code maxStackTraceLines} argument. Concrete - * implementations of this interface should therefore override this default - * method in order to honor the {@code maxStackTraceLines} argument. - * - * @param writer the {@code PrintWriter} to which to print; never {@code null} - * @param maxStackTraceLines the maximum number of lines to print for exception - * stack traces; must be a positive value - * @since 1.6 - * @see #printTo(PrintWriter) - * @see #printFailuresTo(PrintWriter) - */ - @API(status = MAINTAINED, since = "1.6") - default void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { - printFailuresTo(writer); - } - - /** - * Get an immutable list of the failures of the test plan execution. - */ - List getFailures(); - - /** - * Failure of a test or container. - */ - interface Failure extends Serializable { - - /** - * Get the identifier of the failed test or container. - * - * @return the {@link TestIdentifier} for this failure; never {@code null} - */ - TestIdentifier getTestIdentifier(); - - /** - * Get the {@link Throwable} causing the failure. - * - * @return the {@link Throwable} for this failure; never {@code null} - */ - Throwable getException(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java deleted file mode 100644 index 379dccdb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * {@code UniqueIdTrackingListener} is a {@link TestExecutionListener} that tracks - * the {@linkplain TestIdentifier#getUniqueId() unique IDs} of all - * {@linkplain TestIdentifier#isTest() tests} that were - * {@linkplain #executionFinished executed} during the execution of the - * {@link TestPlan} and generates a file containing the unique IDs once execution - * of the {@code TestPlan} has {@linkplain #testPlanExecutionFinished(TestPlan) - * finished}. - * - *

Tests are tracked regardless of their {@link TestExecutionResult} or whether - * they were skipped, and the unique IDs are written to an output file, one ID - * per line, encoding using UTF-8. - * - *

The output file can be used to execute the same set of tests again without - * having to query the user configuration for the test plan and without having to - * perform test discovery again. This can be useful for test environments such as - * within a native image — for example, a GraalVM native image — in - * order to rerun the exact same tests from a standard JVM test run within a - * native image. - * - *

Configuration and Defaults

- * - *

The {@code OUTPUT_DIR} is the directory in which this listener generates - * the output file. The exact path of the generated file is - * {@code OUTPUT_DIR/OUTPUT_FILE_PREFIX-.txt}, where - * {@code } is a pseudo-random number. The inclusion of a random - * number in the file name ensures that multiple concurrently executing test - * plans do not overwrite each other's results. - * - *

The value of the {@code OUTPUT_FILE_PREFIX} defaults to - * {@link #DEFAULT_OUTPUT_FILE_PREFIX}, but a custom prefix can be set via the - * {@link #OUTPUT_FILE_PREFIX_PROPERTY_NAME} configuration property. - * - *

The {@code OUTPUT_DIR} can be set to a custom directory via the - * {@link #OUTPUT_DIR_PROPERTY_NAME} configuration property. Otherwise the - * following algorithm is used to select a default output directory. - * - *

    - *
  • If the current working directory of the Java process contains a file named - * {@code pom.xml}, the output directory will be {@code ./target}, following the - * conventions of Maven.
  • - *
  • If the current working directory of the Java process contains a file with - * the extension {@code .gradle} or {@code .gradle.kts}, the output directory - * will be {@code ./build}, following the conventions of Gradle.
  • - *
  • Otherwise, the current working directory of the Java process will be used - * as the output directory.
  • - *
- * - *

For example, in a project using Gradle as the build tool, the file generated - * by this listener would be {@code ./build/junit-platform-unique-ids-.txt} - * by default. - * - *

Configuration properties can be set via JVM system properties, via a - * {@code junit-platform.properties} file in the root of the classpath, or as - * JUnit Platform {@linkplain ConfigurationParameters configuration parameters}. - * - * @since 1.8 - */ -@API(status = EXPERIMENTAL, since = "1.8") -public class UniqueIdTrackingListener implements TestExecutionListener { - - /** - * Property name used to enable the {@code UniqueIdTrackingListener}: {@value} - * - *

The {@code UniqueIdTrackingListener} is registered automatically via - * Java's {@link java.util.ServiceLoader} mechanism but disabled by default. - * - *

Set the value of this property to {@code true} to enable this listener. - */ - public static final String LISTENER_ENABLED_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.enabled"; - - /** - * Property name used to set the path to the output directory for the file - * generated by the {@code UniqueIdTrackingListener}: {@value} - * - *

For details on the default output directory, see the - * {@linkplain UniqueIdTrackingListener class-level Javadoc}. - */ - public static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.output.dir"; - - /** - * Property name used to set the prefix for the name of the file generated - * by the {@code UniqueIdTrackingListener}: {@value} - * - *

Defaults to {@link #DEFAULT_OUTPUT_FILE_PREFIX}. - */ - public static final String OUTPUT_FILE_PREFIX_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.output.file.prefix"; - - /** - * The default prefix for the name of the file generated by the - * {@code UniqueIdTrackingListener}: {@value} - * - * @see #OUTPUT_FILE_PREFIX_PROPERTY_NAME - */ - public static final String DEFAULT_OUTPUT_FILE_PREFIX = "junit-platform-unique-ids"; - - private final Logger logger = LoggerFactory.getLogger(UniqueIdTrackingListener.class); - - private final List uniqueIds = new ArrayList<>(); - - private boolean enabled; - - public UniqueIdTrackingListener() { - // to avoid missing-explicit-ctor warning - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - this.enabled = testPlan.getConfigurationParameters().getBoolean(LISTENER_ENABLED_PROPERTY_NAME).orElse(false); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - trackTestUid(testIdentifier); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - trackTestUid(testIdentifier); - } - - private void trackTestUid(TestIdentifier testIdentifier) { - if (this.enabled && testIdentifier.isTest()) { - this.uniqueIds.add(testIdentifier.getUniqueId()); - } - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - if (this.enabled) { - Path outputFile; - try { - outputFile = createOutputFile(testPlan.getConfigurationParameters()); - } - catch (Exception ex) { - logger.error(ex, () -> "Failed to create output file"); - // Abort since we cannot generate the file. - return; - } - - logger.debug(() -> "Writing unique IDs to output file " + outputFile.toAbsolutePath()); - try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputFile, StandardCharsets.UTF_8))) { - this.uniqueIds.forEach(writer::println); - writer.flush(); - } - catch (IOException ex) { - logger.error(ex, () -> "Failed to write unique IDs to output file " + outputFile.toAbsolutePath()); - } - } - } - - private Path createOutputFile(ConfigurationParameters configurationParameters) { - String prefix = configurationParameters.get(OUTPUT_FILE_PREFIX_PROPERTY_NAME) // - .orElse(DEFAULT_OUTPUT_FILE_PREFIX); - return OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)) // - .createFile(prefix, "txt"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java deleted file mode 100644 index b41816c4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.discovery; - -import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; -import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.SelectorResolutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.launcher.EngineDiscoveryResult; -import org.junit.platform.launcher.LauncherDiscoveryListener; - -/** - * @since 1.6 - * @see LauncherDiscoveryListeners#abortOnFailure() - */ -class AbortOnFailureLauncherDiscoveryListener implements LauncherDiscoveryListener { - - @Override - public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { - result.getThrowable().ifPresent(ExceptionUtils::throwAsUncheckedException); - } - - @Override - public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { - if (result.getStatus() == FAILED) { - throw new JUnitException(selector + " resolution failed", result.getThrowable().orElse(null)); - } - if (result.getStatus() == UNRESOLVED && selector instanceof UniqueIdSelector) { - UniqueId uniqueId = ((UniqueIdSelector) selector).getUniqueId(); - if (uniqueId.hasPrefix(engineId)) { - throw new JUnitException(selector + " could not be resolved"); - } - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - return getClass() == obj.getClass(); - } - - @Override - public int hashCode() { - return 0; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java deleted file mode 100644 index 14846966..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.discovery; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.SelectorResolutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.EngineDiscoveryResult; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; - -/** - * @since 1.6 - * @see LauncherDiscoveryListeners#composite(List) - */ -class CompositeLauncherDiscoveryListener implements LauncherDiscoveryListener { - - private final List listeners; - - CompositeLauncherDiscoveryListener(List listeners) { - this.listeners = Collections.unmodifiableList(new ArrayList<>(listeners)); - } - - @Override - public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { - listeners.forEach(delegate -> delegate.launcherDiscoveryStarted(request)); - } - - @Override - public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { - listeners.forEach(delegate -> delegate.launcherDiscoveryFinished(request)); - } - - @Override - public void engineDiscoveryStarted(UniqueId engineId) { - listeners.forEach(delegate -> delegate.engineDiscoveryStarted(engineId)); - } - - @Override - public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { - listeners.forEach(delegate -> delegate.engineDiscoveryFinished(engineId, result)); - } - - @Override - public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { - listeners.forEach(delegate -> delegate.selectorProcessed(engineId, selector, result)); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java deleted file mode 100644 index c64f28fc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.discovery; - -import static java.util.stream.Collectors.joining; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Supplier; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.SelectorResolutionResult.Status; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.launcher.LauncherDiscoveryListener; - -/** - * Collection of {@code static} factory methods for creating - * {@link LauncherDiscoveryListener LauncherDiscoveryListeners}. - * - * @since 1.6 - */ -@API(status = EXPERIMENTAL, since = "1.6") -public class LauncherDiscoveryListeners { - - private LauncherDiscoveryListeners() { - } - - /** - * Create a {@link LauncherDiscoveryListener} that aborts test discovery on - * failures. - * - *

The following events are considered failures: - * - *

    - *
  • - * a {@linkplain Status#FAILED failed} resolution result. - *
  • - *
  • - * an {@linkplain Status#FAILED unresolved} resolution result for a - * {@link UniqueIdSelector} that starts with the engine's unique ID. - *
  • - *
  • - * any recoverable {@link Throwable} thrown by - * {@link TestEngine#discover}. - *
  • - *
- */ - public static LauncherDiscoveryListener abortOnFailure() { - return new AbortOnFailureLauncherDiscoveryListener(); - } - - /** - * Create a {@link LauncherDiscoveryListener} that logs test discovery - * events based on their severity. - * - *

For example, failures during test discovery are logged as errors. - */ - public static LauncherDiscoveryListener logging() { - return new LoggingLauncherDiscoveryListener(); - } - - @API(status = INTERNAL, since = "1.6") - public static LauncherDiscoveryListener composite(List listeners) { - Preconditions.notNull(listeners, "listeners must not be null"); - Preconditions.containsNoNullElements(listeners, "listeners must not contain any null elements"); - if (listeners.isEmpty()) { - return LauncherDiscoveryListener.NOOP; - } - if (listeners.size() == 1) { - return listeners.get(0); - } - return new CompositeLauncherDiscoveryListener(listeners); - } - - @API(status = INTERNAL, since = "1.6") - public static LauncherDiscoveryListener fromConfigurationParameter(String key, String value) { - return Arrays.stream(LauncherDiscoveryListenerType.values()) // - .filter(type -> type.parameterValue.equalsIgnoreCase(value)) // - .findFirst() // - .map(type -> type.creator.get()) // - .orElseThrow(() -> newInvalidConfigurationParameterException(key, value)); - } - - private static JUnitException newInvalidConfigurationParameterException(String key, String value) { - String allowedValues = Arrays.stream(LauncherDiscoveryListenerType.values()) // - .map(type -> type.parameterValue) // - .collect(joining("', '", "'", "'")); - return new JUnitException("Invalid value of configuration parameter '" + key + "': " // - + value + " (allowed are " + allowedValues + ")"); - } - - private enum LauncherDiscoveryListenerType { - - LOGGING("logging", LauncherDiscoveryListeners::logging), - - ABORT_ON_FAILURE("abortOnFailure", LauncherDiscoveryListeners::abortOnFailure); - - private final String parameterValue; - private final Supplier creator; - - LauncherDiscoveryListenerType(String parameterValue, Supplier creator) { - this.parameterValue = parameterValue; - this.creator = creator; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java deleted file mode 100644 index b3a674d5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.discovery; - -import static org.junit.platform.launcher.EngineDiscoveryResult.Status.FAILED; - -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.SelectorResolutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.launcher.EngineDiscoveryResult; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; - -/** - * @since 1.6 - * @see LauncherDiscoveryListeners#logging() - */ -class LoggingLauncherDiscoveryListener implements LauncherDiscoveryListener { - - private static final Logger logger = LoggerFactory.getLogger(LoggingLauncherDiscoveryListener.class); - - @Override - public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { - logger.trace(() -> "Test discovery started"); - } - - @Override - public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { - logger.trace(() -> "Test discovery finished"); - } - - @Override - public void engineDiscoveryStarted(UniqueId engineId) { - logger.trace(() -> "Engine " + engineId + " has started discovering tests"); - } - - @Override - public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { - if (result.getStatus() == FAILED) { - Optional failure = result.getThrowable(); - if (failure.isPresent()) { - logger.error(failure.get().getCause(), () -> failure.get().getMessage()); - } - else { - logger.error(() -> "Engine " + engineId + " failed to discover tests"); - } - } - else { - logger.trace(() -> "Engine " + engineId + " has finished discovering tests"); - } - } - - @Override - public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { - switch (result.getStatus()) { - case RESOLVED: - logger.debug(() -> selector + " was resolved by " + engineId); - break; - case FAILED: - logger.error(result.getThrowable().orElse(null), - () -> "Resolution of " + selector + " by " + engineId + " failed"); - break; - case UNRESOLVED: - Consumer> loggingConsumer = logger::debug; - if (selector instanceof UniqueIdSelector) { - UniqueId uniqueId = ((UniqueIdSelector) selector).getUniqueId(); - if (uniqueId.hasPrefix(engineId)) { - loggingConsumer = logger::warn; - } - } - loggingConsumer.accept(() -> selector + " could not be resolved by " + engineId); - break; - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - return getClass() == obj.getClass(); - } - - @Override - public int hashCode() { - return 1; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java deleted file mode 100644 index 371df34d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Common {@link org.junit.platform.launcher.LauncherDiscoveryListener} - * implementations and factory methods. - * - * @see org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners - * @since 1.6 - */ - -package org.junit.platform.launcher.listeners.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java deleted file mode 100644 index d8a0a06e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Common {@link org.junit.platform.launcher.TestExecutionListener - * TestExecutionListener} implementations and related support classes for - * the {@link org.junit.platform.launcher.Launcher Launcher}. - */ - -package org.junit.platform.launcher.listeners; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java deleted file mode 100644 index a79f93f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.session; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.platform.launcher.LauncherSession; -import org.junit.platform.launcher.LauncherSessionListener; - -/** - * @since 1.8 - * @see LauncherSessionListeners#composite(List) - */ -class CompositeLauncherSessionListener implements LauncherSessionListener { - - private final List listeners; - - CompositeLauncherSessionListener(List listeners) { - this.listeners = Collections.unmodifiableList(new ArrayList<>(listeners)); - } - - @Override - public void launcherSessionOpened(LauncherSession session) { - listeners.forEach(delegate -> delegate.launcherSessionOpened(session)); - } - - @Override - public void launcherSessionClosed(LauncherSession session) { - listeners.forEach(delegate -> delegate.launcherSessionClosed(session)); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java deleted file mode 100644 index f200e01a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.session; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.launcher.LauncherSessionListener; - -/** - * Collection of {@code static} factory methods for creating - * {@link LauncherSessionListener LauncherSessionListeners}. - * - * @since 1.8 - */ -@API(status = INTERNAL, since = "1.8") -public class LauncherSessionListeners { - - private LauncherSessionListeners() { - } - - public static LauncherSessionListener composite(List listeners) { - Preconditions.notNull(listeners, "listeners must not be null"); - Preconditions.containsNoNullElements(listeners, "listeners must not contain any null elements"); - if (listeners.isEmpty()) { - return LauncherSessionListener.NOOP; - } - if (listeners.size() == 1) { - return listeners.get(0); - } - return new CompositeLauncherSessionListener(listeners); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java deleted file mode 100644 index 97541a15..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Common {@link org.junit.platform.launcher.LauncherSessionListener} - * implementations and factory methods. - * - * @see org.junit.platform.launcher.listeners.session.LauncherSessionListeners - * @since 1.8 - */ - -package org.junit.platform.launcher.listeners.session; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java deleted file mode 100644 index 4005a842..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Public API for configuring and launching test plans. - * - *

This API is typically used by IDEs and build tools. - */ - -package org.junit.platform.launcher; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java deleted file mode 100644 index c49df5f6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import java.util.ArrayDeque; -import java.util.Deque; - -/** - * @since 1.1 - */ -class DequeStack implements Stack { - - private final Deque deque = new ArrayDeque<>(); - - @Override - public void push(T t) { - deque.addFirst(t); - } - - @Override - public T peek() { - return deque.peek(); - } - - @Override - public T pop() { - return deque.pollFirst(); - } - - @Override - public boolean isEmpty() { - return deque.isEmpty(); - } - - @Override - public int size() { - return deque.size(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java deleted file mode 100644 index 936b53c5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Left; -import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOperatorBetween; -import static org.junit.platform.launcher.tagexpression.ParseStatus.missingRhsOperand; -import static org.junit.platform.launcher.tagexpression.ParseStatus.problemParsing; -import static org.junit.platform.launcher.tagexpression.ParseStatus.success; - -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * @since 1.1 - */ -class Operator { - - enum Associativity { - Left, Right - } - - interface TagExpressionCreator { - ParseStatus createExpressionAndAddTo(Stack> expressions, Token operatorToken); - } - - static Operator nullaryOperator(String representation, int precedence) { - return new Operator(representation, precedence, 0, null, (expressions, operatorToken) -> success()); - } - - static Operator unaryOperator(String representation, int precedence, Associativity associativity, - Function unaryExpression) { - - return new Operator(representation, precedence, 1, associativity, (expressions, operatorToken) -> { - TokenWith rhs = expressions.pop(); - if (operatorToken.isLeftOf(rhs.token)) { - Token combinedToken = operatorToken.concatenate(rhs.token); - expressions.push(new TokenWith<>(combinedToken, unaryExpression.apply(rhs.element))); - return success(); - } - return missingRhsOperand(operatorToken, representation); - }); - } - - static Operator binaryOperator(String representation, int precedence, Associativity associativity, - BiFunction binaryExpression) { - - return new Operator(representation, precedence, 2, associativity, (expressions, operatorToken) -> { - TokenWith rhs = expressions.pop(); - TokenWith lhs = expressions.pop(); - Token lhsToken = lhs.token; - if (lhsToken.isLeftOf(operatorToken) && operatorToken.isLeftOf(rhs.token)) { - Token combinedToken = lhsToken.concatenate(operatorToken).concatenate(rhs.token); - expressions.push(new TokenWith<>(combinedToken, binaryExpression.apply(lhs.element, rhs.element))); - return success(); - } - if (rhs.token.isLeftOf(operatorToken)) { - return missingRhsOperand(operatorToken, representation); - } - if (operatorToken.isLeftOf(lhsToken)) { - return missingOperatorBetween(lhs, rhs); - } - return problemParsing(operatorToken, representation); - }); - } - - private final String representation; - private final int precedence; - private final int arity; - private final Associativity associativity; - private final TagExpressionCreator tagExpressionCreator; - - private Operator(String representation, int precedence, int arity, Associativity associativity, - TagExpressionCreator tagExpressionCreator) { - - this.representation = representation; - this.precedence = precedence; - this.arity = arity; - this.associativity = associativity; - this.tagExpressionCreator = tagExpressionCreator; - } - - boolean represents(String token) { - return representation.equals(token); - } - - String representation() { - return representation; - } - - boolean hasLowerPrecedenceThan(Operator operator) { - return this.precedence < operator.precedence; - } - - boolean hasSamePrecedenceAs(Operator operator) { - return this.precedence == operator.precedence; - } - - boolean isLeftAssociative() { - return Left == associativity; - } - - ParseStatus createAndAddExpressionTo(Stack> expressions, Token operatorToken) { - if (expressions.size() < arity) { - String message = createMissingOperandMessage(expressions, operatorToken); - return ParseStatus.errorAt(operatorToken, representation, message); - } - return tagExpressionCreator.createExpressionAndAddTo(expressions, operatorToken); - } - - private String createMissingOperandMessage(Stack> expressions, Token operatorToken) { - if (1 == arity) { - return missingOneOperand(associativity == Left ? "lhs" : "rhs"); - } - - if (2 == arity) { - int mismatch = arity - expressions.size(); - if (2 == mismatch) { - return "missing lhs and rhs operand"; - } - return missingOneOperand(operatorToken.isLeftOf(expressions.peek().token) ? "lhs" : "rhs"); - } - return "missing operand"; - } - - private String missingOneOperand(String side) { - return "missing " + side + " operand"; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java deleted file mode 100644 index a8685088..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; -import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Left; -import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Right; - -import java.util.Map; -import java.util.stream.Stream; - -/** - * @since 1.1 - */ -class Operators { - - private static final Operator Not = Operator.unaryOperator("!", 3, Right, TagExpressions::not); - private static final Operator And = Operator.binaryOperator("&", 2, Left, TagExpressions::and); - private static final Operator Or = Operator.binaryOperator("|", 1, Left, TagExpressions::or); - - private final Map representationToOperator = Stream.of(Not, And, Or).collect( - toMap(Operator::representation, identity())); - - boolean isOperator(String token) { - return representationToOperator.containsKey(token); - } - - Operator operatorFor(String token) { - return representationToOperator.get(token); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java deleted file mode 100644 index 1c0a1346..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Optional; -import java.util.function.Function; - -import org.apiguardian.api.API; - -/** - * The result of attempting to parse a {@link TagExpression}. - * - *

An instance of this type either contains a successfully parsed - * {@link TagExpression} or an error message describing the parse - * error. - * - * @since 1.1 - * @see TagExpression#parseFrom(String) - */ -@API(status = INTERNAL, since = "1.1") -public interface ParseResult { - - /** - * Return the parsed {@link TagExpression} or throw an exception with the - * contained parse error. - * - * @param exceptionCreator will be called with the error message in case - * this parse result contains a parse error; never {@code null}. - */ - default TagExpression tagExpressionOrThrow(Function exceptionCreator) { - if (errorMessage().isPresent()) { - throw exceptionCreator.apply(errorMessage().get()); - } - return tagExpression().get(); - } - - /** - * Return the contained parse error message, if any. - */ - default Optional errorMessage() { - return Optional.empty(); - } - - /** - * Return the contained {@link TagExpression}, if any. - */ - default Optional tagExpression() { - return Optional.empty(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java deleted file mode 100644 index ceb19b32..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import java.util.Optional; - -/** - * @since 1.1 - */ -class ParseResults { - - private ParseResults() { - /* no-op */ - } - - static ParseResult success(TagExpression tagExpression) { - return new ParseResult() { - @Override - public Optional tagExpression() { - return Optional.of(tagExpression); - } - }; - } - - static ParseResult error(String errorMessage) { - return new ParseResult() { - @Override - public Optional errorMessage() { - return Optional.of(errorMessage); - } - }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java deleted file mode 100644 index 52f8fd15..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import java.util.function.Supplier; - -/** - * @since 1.1 - */ -class ParseStatus { - - static ParseStatus success() { - return error(null); - } - - static ParseStatus problemParsing(Token token, String representation) { - return errorAt(token, representation, "problem parsing"); - } - - static ParseStatus missingOpeningParenthesis(Token token, String representation) { - return errorAt(token, representation, "missing opening parenthesis"); - } - - static ParseStatus missingClosingParenthesis(Token token, String representation) { - return errorAt(token, representation, "missing closing parenthesis"); - } - - static ParseStatus missingRhsOperand(Token token, String representation) { - return errorAt(token, representation, "missing rhs operand"); - } - - static ParseStatus errorAt(Token token, String operatorRepresentation, String message) { - return error( - message + " for '" + operatorRepresentation + "' at index " + format(token.trimmedTokenStartIndex())); - } - - static ParseStatus missingOperatorBetween(TokenWith lhs, TokenWith rhs) { - String lhsString = "'" + lhs.element.toString() + "' at index " + format(lhs.token.lastCharacterIndex()); - String rhsString = "'" + rhs.element.toString() + "' at index " + format(rhs.token.trimmedTokenStartIndex()); - return error("missing operator between " + lhsString + " and " + rhsString); - } - - static ParseStatus emptyTagExpression() { - return error("empty tag expression"); - } - - private static String format(int indexInTagExpression) { - return "<" + indexInTagExpression + ">"; - } - - private static ParseStatus error(String errorMessage) { - return new ParseStatus(errorMessage); - } - - final String errorMessage; - - private ParseStatus(String errorMessage) { - this.errorMessage = errorMessage; - } - - public ParseStatus process(Supplier step) { - if (isSuccess()) { - return step.get(); - } - return this; - } - - public boolean isError() { - return !isSuccess(); - } - - private boolean isSuccess() { - return errorMessage == null; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java deleted file mode 100644 index c3b3000a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import java.util.List; - -/** - * @since 1.1 - */ -class Parser { - - private final Tokenizer tokenizer = new Tokenizer(); - - ParseResult parse(String infixTagExpression) { - return constructExpressionFrom(tokensDerivedFrom(infixTagExpression)); - } - - private List tokensDerivedFrom(String infixTagExpression) { - return tokenizer.tokenize(infixTagExpression); - } - - private ParseResult constructExpressionFrom(List tokens) { - return new ShuntingYard(tokens).execute(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java deleted file mode 100644 index 063574ff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static java.lang.Integer.MIN_VALUE; -import static org.junit.platform.launcher.tagexpression.Operator.nullaryOperator; -import static org.junit.platform.launcher.tagexpression.ParseStatus.emptyTagExpression; -import static org.junit.platform.launcher.tagexpression.ParseStatus.missingClosingParenthesis; -import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOpeningParenthesis; -import static org.junit.platform.launcher.tagexpression.ParseStatus.success; -import static org.junit.platform.launcher.tagexpression.TagExpressions.any; -import static org.junit.platform.launcher.tagexpression.TagExpressions.none; -import static org.junit.platform.launcher.tagexpression.TagExpressions.tag; - -import java.util.List; - -/** - * This is based on a modified version of the - * - * Shunting-yard algorithm. - * - * @since 1.1 - */ -class ShuntingYard { - - private static final Operator RightParenthesis = nullaryOperator(")", -1); - private static final Operator LeftParenthesis = nullaryOperator("(", -2); - private static final Operator Sentinel = nullaryOperator("sentinel", MIN_VALUE); - private static final Token SentinelToken = new Token(-1, ""); - - private final Operators validOperators = new Operators(); - private final Stack> expressions = new DequeStack<>(); - private final Stack> operators = new DequeStack<>(); - private final List tokens; - - ShuntingYard(List tokens) { - this.tokens = tokens; - pushOperatorAt(SentinelToken, Sentinel); - } - - public ParseResult execute() { - // @formatter:off - ParseStatus parseStatus = processTokens() - .process(this::consumeRemainingOperators) - .process(this::ensureOnlySingleExpressionRemains); - // @formatter:on - if (parseStatus.isError()) { - return ParseResults.error(parseStatus.errorMessage); - } - return ParseResults.success(expressions.pop().element); - } - - private ParseStatus processTokens() { - ParseStatus parseStatus = success(); - for (Token token : tokens) { - parseStatus = parseStatus.process(() -> process(token)); - } - return parseStatus; - } - - private ParseStatus process(Token token) { - String trimmed = token.string(); - if (LeftParenthesis.represents(trimmed)) { - pushOperatorAt(token, LeftParenthesis); - return success(); - } - if (RightParenthesis.represents(trimmed)) { - return findMatchingLeftParenthesis(token); - } - if (validOperators.isOperator(trimmed)) { - Operator operator = validOperators.operatorFor(trimmed); - return findOperands(token, operator); - } - pushExpressionAt(token, convertLeafTokenToExpression(trimmed)); - return success(); - } - - private TagExpression convertLeafTokenToExpression(String trimmed) { - if ("any()".equalsIgnoreCase(trimmed)) { - return any(); - } - if ("none()".equalsIgnoreCase(trimmed)) { - return none(); - } - return tag(trimmed); - } - - private ParseStatus findMatchingLeftParenthesis(Token token) { - while (!operators.isEmpty()) { - TokenWith tokenWithWithOperator = operators.pop(); - Operator operator = tokenWithWithOperator.element; - if (LeftParenthesis.equals(operator)) { - return success(); - } - ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token); - if (parseStatus.isError()) { - return parseStatus; - } - } - return missingOpeningParenthesis(token, RightParenthesis.representation()); - } - - private ParseStatus findOperands(Token token, Operator currentOperator) { - while (currentOperator.hasLowerPrecedenceThan(previousOperator()) - || currentOperator.hasSamePrecedenceAs(previousOperator()) && currentOperator.isLeftAssociative()) { - TokenWith tokenWithWithOperator = operators.pop(); - ParseStatus parseStatus = tokenWithWithOperator.element.createAndAddExpressionTo(expressions, - tokenWithWithOperator.token); - if (parseStatus.isError()) { - return parseStatus; - } - } - pushOperatorAt(token, currentOperator); - return success(); - } - - private Operator previousOperator() { - return operators.peek().element; - } - - private void pushExpressionAt(Token token, TagExpression tagExpression) { - expressions.push(new TokenWith<>(token, tagExpression)); - } - - private void pushOperatorAt(Token token, Operator operator) { - operators.push(new TokenWith<>(token, operator)); - } - - private ParseStatus consumeRemainingOperators() { - while (!operators.isEmpty()) { - TokenWith tokenWithWithOperator = operators.pop(); - Operator operator = tokenWithWithOperator.element; - if (LeftParenthesis.equals(operator)) { - return missingClosingParenthesis(tokenWithWithOperator.token, operator.representation()); - } - ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token); - if (parseStatus.isError()) { - return parseStatus; - } - } - return success(); - } - - private ParseStatus ensureOnlySingleExpressionRemains() { - if (expressions.size() == 1) { - return success(); - } - if (expressions.isEmpty()) { - return emptyTagExpression(); - } - TokenWith rhs = expressions.pop(); - TokenWith lhs = expressions.pop(); - return ParseStatus.missingOperatorBetween(lhs, rhs); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java deleted file mode 100644 index ab7d8b73..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -/** - * @since 1.1 - */ -interface Stack { - - void push(T t); - - T peek(); - - T pop(); - - boolean isEmpty(); - - int size(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java deleted file mode 100644 index 823191bc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Collection; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestTag; - -/** - * A tag expression can be evaluated against a collection of - * {@linkplain TestTag tags} to determine if they match the expression. - * - * @since 1.1 - */ -@API(status = INTERNAL, since = "1.1") -public interface TagExpression { - - /** - * Attempt to parse a {@link TagExpression} from the supplied tag - * expression string. - * - * @param infixTagExpression the tag expression string to parse; never {@code null}. - * @see ParseResult - */ - @API(status = INTERNAL, since = "1.1") - static ParseResult parseFrom(String infixTagExpression) { - return new Parser().parse(infixTagExpression); - } - - /** - * Evaluate this tag expression against the supplied collection of - * {@linkplain TestTag tags}. - * - * @param tags the tags this tag expression is to be evaluated against - * @return {@code true}, if the tags match this tag expression; {@code false}, otherwise - */ - boolean evaluate(Collection tags); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java deleted file mode 100644 index a9f8e04a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import java.util.Collection; - -import org.junit.platform.engine.TestTag; - -/** - * @since 1.1 - */ -class TagExpressions { - - static TagExpression tag(String tag) { - TestTag testTag = TestTag.create(tag); - return new TagExpression() { - @Override - public boolean evaluate(Collection tags) { - return tags.contains(testTag); - } - - @Override - public String toString() { - return testTag.getName(); - } - }; - } - - static TagExpression none() { - return new TagExpression() { - @Override - public boolean evaluate(Collection tags) { - return tags.isEmpty(); - } - - @Override - public String toString() { - return "none()"; - } - }; - } - - static TagExpression any() { - return new TagExpression() { - @Override - public boolean evaluate(Collection tags) { - return !tags.isEmpty(); - } - - @Override - public String toString() { - return "any()"; - } - }; - } - - static TagExpression not(TagExpression toNegate) { - return new TagExpression() { - @Override - public boolean evaluate(Collection tags) { - return !toNegate.evaluate(tags); - } - - @Override - public String toString() { - return "!" + toNegate + ""; - } - }; - } - - static TagExpression and(TagExpression lhs, TagExpression rhs) { - return new TagExpression() { - @Override - public boolean evaluate(Collection tags) { - return lhs.evaluate(tags) && rhs.evaluate(tags); - } - - @Override - public String toString() { - return "(" + lhs + " & " + rhs + ")"; - } - }; - } - - static TagExpression or(TagExpression lhs, TagExpression rhs) { - return new TagExpression() { - @Override - public boolean evaluate(Collection tags) { - return lhs.evaluate(tags) || rhs.evaluate(tags); - } - - @Override - public String toString() { - return "(" + lhs + " | " + rhs + ")"; - } - }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java deleted file mode 100644 index f06a60ca..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -/** - * @since 1.1 - */ -class Token { - - final int startIndex; - final String rawString; - - Token(int startIndex, String rawString) { - this.startIndex = startIndex; - this.rawString = rawString; - } - - String string() { - return rawString.trim(); - } - - public int trimmedTokenStartIndex() { - return startIndex + rawString.indexOf(string()); - } - - public boolean isLeftOf(Token token) { - return lastCharacterIndex() < token.startIndex; - } - - public int lastCharacterIndex() { - return endIndexExclusive() - 1; - } - - public int endIndexExclusive() { - return startIndex + rawString.length(); - } - - public Token concatenate(Token rightOfThis) { - String concatenatedRawString = this.rawString + rightOfThis.rawString; - return new Token(startIndex, concatenatedRawString); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java deleted file mode 100644 index a580df87..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -/** - * @since 1.1 - */ -class TokenWith { - - final Token token; - final T element; - - TokenWith(Token token, T element) { - this.token = token; - this.element = element; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java deleted file mode 100644 index 65d13c11..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static java.util.Collections.emptyList; -import static java.util.regex.Pattern.CASE_INSENSITIVE; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * @since 1.1 - */ -class Tokenizer { - - private static final Pattern PATTERN = Pattern.compile("\\s*(?:(?:(?:any|none)\\(\\))|[()!|&]|(?:[^\\s()!|&]+))", - CASE_INSENSITIVE); - - List tokenize(String infixTagExpression) { - if (infixTagExpression == null) { - return emptyList(); - } - List parts = new ArrayList<>(); - Matcher matcher = PATTERN.matcher(infixTagExpression); - while (matcher.find()) { - parts.add(new Token(matcher.start(), matcher.group())); - } - return parts; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java deleted file mode 100644 index 53e2a7fa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * The tag expression language parser and related support classes. - */ - -package org.junit.platform.launcher.tagexpression; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener deleted file mode 100644 index 85f17ac2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.launcher.listeners.UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java deleted file mode 100644 index 9501caff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Public API for configuring and launching test plans. - * - *

This API is typically used by IDEs and build tools. - * - * @since 1.0 - * @uses org.junit.platform.engine.TestEngine - * @uses org.junit.platform.launcher.LauncherDiscoveryListener - * @uses org.junit.platform.launcher.PostDiscoveryFilter - * @uses org.junit.platform.launcher.TestExecutionListener - */ -module org.junit.platform.launcher { - requires transitive java.logging; - requires static transitive org.apiguardian.api; - requires transitive org.junit.platform.commons; - requires transitive org.junit.platform.engine; - - exports org.junit.platform.launcher; - exports org.junit.platform.launcher.core; - exports org.junit.platform.launcher.listeners; - exports org.junit.platform.launcher.listeners.discovery; - - uses org.junit.platform.engine.TestEngine; - uses org.junit.platform.launcher.LauncherDiscoveryListener; - uses org.junit.platform.launcher.LauncherSessionListener; - uses org.junit.platform.launcher.PostDiscoveryFilter; - uses org.junit.platform.launcher.TestExecutionListener; - - provides org.junit.platform.launcher.TestExecutionListener - with org.junit.platform.launcher.listeners.UniqueIdTrackingListener; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java deleted file mode 100644 index 4444022d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import java.util.Map; - -import org.junit.platform.engine.ConfigurationParameters; - -public class ConfigurationParametersFactoryForTests { - - private ConfigurationParametersFactoryForTests() { - } - - public static ConfigurationParameters create(Map configParams) { - return LauncherConfigurationParameters.builder() // - .explicitParameters(configParams) // - .enableImplicitProviders(false) // - .build(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java deleted file mode 100644 index 93002b81..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import org.junit.platform.engine.TestEngine; -import org.junit.platform.launcher.Launcher; - -/** - * @since 1.0 - */ -public class LauncherFactoryForTestingPurposesOnly { - - public static Launcher createLauncher(TestEngine... engines) { - return LauncherFactory.create(createLauncherConfigBuilderWithDisabledServiceLoading() // - .addTestEngines(engines) // - .build()); - } - - public static LauncherConfig.Builder createLauncherConfigBuilderWithDisabledServiceLoading() { - return LauncherConfig.builder() // - .enableTestEngineAutoRegistration(false) // - .enableLauncherDiscoveryListenerAutoRegistration(false) // - .enableTestExecutionListenerAutoRegistration(false) // - .enablePostDiscoveryFilterAutoRegistration(false) // - .enableLauncherSessionListenerAutoRegistration(false); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md deleted file mode 100644 index 6aec502b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/LICENSE-open-test-reporting.md +++ /dev/null @@ -1,194 +0,0 @@ -Apache License -============== - -_Version 2.0, January 2004_ -_<>_ - -### Terms and Conditions for use, reproduction, and distribution - -#### 1. Definitions - -“License” shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -“Licensor” shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -“Legal Entity” shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, “control” means **(i)** the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the -outstanding shares, or **(iii)** beneficial ownership of such entity. - -“You” (or “Your”) shall mean an individual or Legal Entity exercising -permissions granted by this License. - -“Source” form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -“Object” form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -“Work” shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -“Derivative Works” shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -“Contribution” shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -“submitted” means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as “Not a Contribution.” - -“Contributor” shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -#### 2. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -#### 3. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -#### 4. Redistribution - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -* **(a)** You must give any other recipients of the Work or Derivative Works a copy of -this License; and -* **(b)** You must cause any modified files to carry prominent notices stating that You -changed the files; and -* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. - -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -#### 5. Submission of Contributions - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -#### 6. Trademarks - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -#### 7. Disclaimer of Warranty - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -#### 8. Limitation of Liability - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -#### 9. Accepting Warranty or Additional Liability - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -_END OF TERMS AND CONDITIONS_ - -### APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets `[]` replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same “printed page” as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts deleted file mode 100644 index 144c3fbe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/junit-platform-reporting.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - `java-library-conventions` - `shadow-conventions` -} - -description = "JUnit Platform Reporting" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformLauncher) - - compileOnlyApi(libs.apiguardian) - - shadowed(libs.openTestReporting.events) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -tasks { - shadowJar { - relocate("org.opentest4j.reporting", "org.junit.platform.reporting.shadow.org.opentest4j.reporting") - from(projectDir) { - include("LICENSE-open-test-reporting.md") - into("META-INF") - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java deleted file mode 100644 index fd8968f4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import org.apiguardian.api.API; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * Utility methods for dealing with legacy reporting infrastructure, such as - * reporting systems built on the Ant-based XML reporting format for JUnit 4. - * - * This class was formerly from {@code junit-platform-launcher} - * in {@link org.junit.platform.launcher.listeners} package. - * - * @since 1.0.3 - */ -@API(status = MAINTAINED, since = "1.6") -public final class LegacyReportingUtils { - - private LegacyReportingUtils() { - /* no-op */ - } - - /** - * Get the class name for the supplied {@link TestIdentifier} using the - * supplied {@link TestPlan}. - * - *

This implementation attempts to find the closest test identifier with - * a {@link ClassSource} by traversing the hierarchy upwards towards the - * root starting with the supplied test identifier. In case no such source - * is found, it falls back to using the parent's - * {@linkplain TestIdentifier#getLegacyReportingName legacy reporting name}. - * - * @param testPlan the test plan that contains the {@code TestIdentifier}; - * never {@code null} - * @param testIdentifier the identifier to determine the class name for; - * never {@code null} - * @see TestIdentifier#getLegacyReportingName - */ - @SuppressWarnings("deprecation") - public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifier) { - return org.junit.platform.launcher.listeners.LegacyReportingUtils.getClassName(testPlan, testIdentifier); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java deleted file mode 100644 index f7a50ee1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Support for legacy reporting formats. - */ - -package org.junit.platform.reporting.legacy; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java deleted file mode 100644 index 10d90288..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Clock; - -import javax.xml.stream.XMLStreamException; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * {@code LegacyXmlReportGeneratingListener} is a {@link TestExecutionListener} that - * generates a separate XML report for each {@linkplain TestPlan#getRoots() root} - * in the {@link TestPlan}. - * - *

Note that the generated XML format is compatible with the legacy - * de facto standard for JUnit 4 based test reports that was made popular by the - * Ant build system. - * - * @since 1.4 - * @see org.junit.platform.launcher.listeners.LoggingListener - * @see org.junit.platform.launcher.listeners.SummaryGeneratingListener - */ -@API(status = STABLE, since = "1.7") -public class LegacyXmlReportGeneratingListener implements TestExecutionListener { - - private final Path reportsDir; - private final PrintWriter out; - private final Clock clock; - - private XmlReportData reportData; - - public LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out) { - this(reportsDir, out, Clock.systemDefaultZone()); - } - - // For tests only - LegacyXmlReportGeneratingListener(String reportsDir, PrintWriter out, Clock clock) { - this(Paths.get(reportsDir), out, clock); - } - - private LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out, Clock clock) { - this.reportsDir = reportsDir; - this.out = out; - this.clock = clock; - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - this.reportData = new XmlReportData(testPlan, clock); - try { - Files.createDirectories(this.reportsDir); - } - catch (IOException e) { - printException("Could not create reports directory: " + this.reportsDir, e); - } - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - this.reportData = null; - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - this.reportData.markSkipped(testIdentifier, reason); - writeXmlReportInCaseOfRoot(testIdentifier); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - this.reportData.markStarted(testIdentifier); - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - this.reportData.addReportEntry(testIdentifier, entry); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult result) { - this.reportData.markFinished(testIdentifier, result); - writeXmlReportInCaseOfRoot(testIdentifier); - } - - private void writeXmlReportInCaseOfRoot(TestIdentifier testIdentifier) { - if (isRoot(testIdentifier)) { - String rootName = testIdentifier.getUniqueIdObject().getSegments().get(0).getValue(); - writeXmlReportSafely(testIdentifier, rootName); - } - } - - private void writeXmlReportSafely(TestIdentifier testIdentifier, String rootName) { - Path xmlFile = this.reportsDir.resolve("TEST-" + rootName + ".xml"); - try (Writer fileWriter = Files.newBufferedWriter(xmlFile)) { - new XmlReportWriter(this.reportData).writeXmlReport(testIdentifier, fileWriter); - } - catch (XMLStreamException | IOException e) { - printException("Could not write XML report: " + xmlFile, e); - } - } - - private boolean isRoot(TestIdentifier testIdentifier) { - return !testIdentifier.getParentIdObject().isPresent(); - } - - private void printException(String message, Exception exception) { - out.println(message); - exception.printStackTrace(out); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java deleted file mode 100644 index 1f799353..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; -import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; - -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.4 - */ -class XmlReportData { - - private static final int MILLIS_PER_SECOND = 1000; - - private final Map finishedTests = new ConcurrentHashMap<>(); - private final Map skippedTests = new ConcurrentHashMap<>(); - private final Map startInstants = new ConcurrentHashMap<>(); - private final Map endInstants = new ConcurrentHashMap<>(); - private final Map> reportEntries = new ConcurrentHashMap<>(); - - private final TestPlan testPlan; - private final Clock clock; - - XmlReportData(TestPlan testPlan, Clock clock) { - this.testPlan = testPlan; - this.clock = clock; - } - - TestPlan getTestPlan() { - return this.testPlan; - } - - Clock getClock() { - return this.clock; - } - - void markSkipped(TestIdentifier testIdentifier, String reason) { - this.skippedTests.put(testIdentifier, reason == null ? "" : reason); - } - - void markStarted(TestIdentifier testIdentifier) { - this.startInstants.put(testIdentifier, this.clock.instant()); - } - - void markFinished(TestIdentifier testIdentifier, TestExecutionResult result) { - this.endInstants.put(testIdentifier, this.clock.instant()); - if (result.getStatus() == ABORTED) { - String reason = result.getThrowable().map(ExceptionUtils::readStackTrace).orElse(""); - this.skippedTests.put(testIdentifier, reason); - } - else { - this.finishedTests.put(testIdentifier, result); - } - } - - void addReportEntry(TestIdentifier testIdentifier, ReportEntry entry) { - List entries = this.reportEntries.computeIfAbsent(testIdentifier, key -> new ArrayList<>()); - entries.add(entry); - } - - boolean wasSkipped(TestIdentifier testIdentifier) { - return findSkippedAncestor(testIdentifier).isPresent(); - } - - double getDurationInSeconds(TestIdentifier testIdentifier) { - Instant startInstant = this.startInstants.getOrDefault(testIdentifier, Instant.EPOCH); - Instant endInstant = this.endInstants.getOrDefault(testIdentifier, startInstant); - return Duration.between(startInstant, endInstant).toMillis() / (double) MILLIS_PER_SECOND; - } - - String getSkipReason(TestIdentifier testIdentifier) { - return findSkippedAncestor(testIdentifier).map(skippedTestIdentifier -> { - String reason = this.skippedTests.get(skippedTestIdentifier); - if (!testIdentifier.equals(skippedTestIdentifier)) { - reason = "parent was skipped: " + reason; - } - return reason; - }).orElse(null); - } - - List getResults(TestIdentifier testIdentifier) { - return getAncestors(testIdentifier).stream() // - .map(this.finishedTests::get) // - .filter(Objects::nonNull) // - .collect(toList()); - } - - List getReportEntries(TestIdentifier testIdentifier) { - return this.reportEntries.getOrDefault(testIdentifier, emptyList()); - } - - private Optional findSkippedAncestor(TestIdentifier testIdentifier) { - return findAncestor(testIdentifier, this.skippedTests::containsKey); - } - - private Optional findAncestor(TestIdentifier testIdentifier, Predicate predicate) { - Optional current = Optional.of(testIdentifier); - while (current.isPresent()) { - if (predicate.test(current.get())) { - return current; - } - current = this.testPlan.getParent(current.get()); - } - return Optional.empty(); - } - - private List getAncestors(TestIdentifier testIdentifier) { - TestIdentifier current = testIdentifier; - List ancestors = new ArrayList<>(); - while (current != null) { - ancestors.add(current); - current = this.testPlan.getParent(current).orElse(null); - } - return ancestors; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java deleted file mode 100644 index fd9dd98d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import static java.text.MessageFormat.format; -import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; -import static java.util.Collections.emptyList; -import static java.util.Comparator.naturalOrder; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.counting; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.mapping; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; -import static org.junit.platform.commons.util.StringUtils.isNotBlank; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; -import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; -import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.ERROR; -import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.FAILURE; -import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SKIPPED; -import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SUCCESS; - -import java.io.Writer; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.text.NumberFormat; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Properties; -import java.util.TreeSet; -import java.util.regex.Pattern; - -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.reporting.legacy.LegacyReportingUtils; -import org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type; - -/** - * {@code XmlReportWriter} writes an XML report whose format is compatible - * with the de facto standard for JUnit 4 based test reports that was made - * popular by the Ant build system. - * - * @since 1.4 - */ -class XmlReportWriter { - - // Using zero-width assertions in the split pattern simplifies the splitting process: All split parts - // (including the first and last one) can be used directly, without having to re-add separator characters. - private static final Pattern CDATA_SPLIT_PATTERN = Pattern.compile("(?<=]])(?=>)"); - - private final XmlReportData reportData; - - XmlReportWriter(XmlReportData reportData) { - this.reportData = reportData; - } - - void writeXmlReport(TestIdentifier rootDescriptor, Writer out) throws XMLStreamException { - TestPlan testPlan = this.reportData.getTestPlan(); - Map tests = testPlan.getDescendants(rootDescriptor) // - .stream() // - .filter(testIdentifier -> shouldInclude(testPlan, testIdentifier)) // - .collect(toMap(identity(), this::toAggregatedResult)); // - writeXmlReport(rootDescriptor, tests, out); - } - - private AggregatedTestResult toAggregatedResult(TestIdentifier testIdentifier) { - if (this.reportData.wasSkipped(testIdentifier)) { - return AggregatedTestResult.skipped(); - } - return AggregatedTestResult.nonSkipped(this.reportData.getResults(testIdentifier)); - } - - private boolean shouldInclude(TestPlan testPlan, TestIdentifier testIdentifier) { - return testIdentifier.isTest() || testPlan.getChildren(testIdentifier).isEmpty(); - } - - private void writeXmlReport(TestIdentifier testIdentifier, Map tests, - Writer out) throws XMLStreamException { - - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - XMLStreamWriter xmlWriter = factory.createXMLStreamWriter(out); - xmlWriter.writeStartDocument("UTF-8", "1.0"); - newLine(xmlWriter); - writeTestsuite(testIdentifier, tests, xmlWriter); - xmlWriter.writeEndDocument(); - xmlWriter.flush(); - xmlWriter.close(); - } - - private void writeTestsuite(TestIdentifier testIdentifier, Map tests, - XMLStreamWriter writer) throws XMLStreamException { - - // NumberFormat is not thread-safe. Thus, we instantiate it here and pass it to - // writeTestcase instead of using a constant - NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); - - writer.writeStartElement("testsuite"); - - writeSuiteAttributes(testIdentifier, tests.values(), numberFormat, writer); - - newLine(writer); - writeSystemProperties(writer); - - for (Entry entry : tests.entrySet()) { - writeTestcase(entry.getKey(), entry.getValue(), numberFormat, writer); - } - - writeOutputElement("system-out", formatNonStandardAttributesAsString(testIdentifier), writer); - - writer.writeEndElement(); - newLine(writer); - } - - private void writeSuiteAttributes(TestIdentifier testIdentifier, Collection testResults, - NumberFormat numberFormat, XMLStreamWriter writer) throws XMLStreamException { - - writeAttributeSafely(writer, "name", testIdentifier.getDisplayName()); - writeTestCounts(testResults, writer); - writeAttributeSafely(writer, "time", getTime(testIdentifier, numberFormat)); - writeAttributeSafely(writer, "hostname", getHostname().orElse("")); - writeAttributeSafely(writer, "timestamp", ISO_LOCAL_DATE_TIME.format(getCurrentDateTime())); - } - - private void writeTestCounts(Collection testResults, XMLStreamWriter writer) - throws XMLStreamException { - Map counts = testResults.stream().map(it -> it.type).collect(groupingBy(identity(), counting())); - long total = counts.values().stream().mapToLong(Long::longValue).sum(); - writeAttributeSafely(writer, "tests", String.valueOf(total)); - writeAttributeSafely(writer, "skipped", counts.getOrDefault(SKIPPED, 0L).toString()); - writeAttributeSafely(writer, "failures", counts.getOrDefault(FAILURE, 0L).toString()); - writeAttributeSafely(writer, "errors", counts.getOrDefault(ERROR, 0L).toString()); - } - - private void writeSystemProperties(XMLStreamWriter writer) throws XMLStreamException { - writer.writeStartElement("properties"); - newLine(writer); - Properties systemProperties = System.getProperties(); - for (String propertyName : new TreeSet<>(systemProperties.stringPropertyNames())) { - writer.writeEmptyElement("property"); - writeAttributeSafely(writer, "name", propertyName); - writeAttributeSafely(writer, "value", systemProperties.getProperty(propertyName)); - newLine(writer); - } - writer.writeEndElement(); - newLine(writer); - } - - private void writeTestcase(TestIdentifier testIdentifier, AggregatedTestResult testResult, - NumberFormat numberFormat, XMLStreamWriter writer) throws XMLStreamException { - - writer.writeStartElement("testcase"); - - writeAttributeSafely(writer, "name", getName(testIdentifier)); - writeAttributeSafely(writer, "classname", getClassName(testIdentifier)); - writeAttributeSafely(writer, "time", getTime(testIdentifier, numberFormat)); - newLine(writer); - - writeSkippedOrErrorOrFailureElement(testIdentifier, testResult, writer); - - List systemOutElements = new ArrayList<>(); - List systemErrElements = new ArrayList<>(); - systemOutElements.add(formatNonStandardAttributesAsString(testIdentifier)); - collectReportEntries(testIdentifier, systemOutElements, systemErrElements); - writeOutputElements("system-out", systemOutElements, writer); - writeOutputElements("system-err", systemErrElements, writer); - - writer.writeEndElement(); - newLine(writer); - } - - private String getName(TestIdentifier testIdentifier) { - return testIdentifier.getLegacyReportingName(); - } - - private String getClassName(TestIdentifier testIdentifier) { - return LegacyReportingUtils.getClassName(this.reportData.getTestPlan(), testIdentifier); - } - - private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, AggregatedTestResult testResult, - XMLStreamWriter writer) throws XMLStreamException { - - if (testResult.type == SKIPPED) { - writeSkippedElement(this.reportData.getSkipReason(testIdentifier), writer); - } - else { - Map>> throwablesByType = testResult.getThrowablesByType(); - for (Type type : EnumSet.of(FAILURE, ERROR)) { - for (Optional throwable : throwablesByType.getOrDefault(type, emptyList())) { - writeErrorOrFailureElement(type, throwable.orElse(null), writer); - } - } - } - } - - private void writeSkippedElement(String reason, XMLStreamWriter writer) throws XMLStreamException { - if (isNotBlank(reason)) { - writer.writeStartElement("skipped"); - writeCDataSafely(writer, reason); - writer.writeEndElement(); - } - else { - writer.writeEmptyElement("skipped"); - } - newLine(writer); - } - - private void writeErrorOrFailureElement(Type type, Throwable throwable, XMLStreamWriter writer) - throws XMLStreamException { - - String elementName = type == FAILURE ? "failure" : "error"; - if (throwable != null) { - writer.writeStartElement(elementName); - writeFailureAttributesAndContent(throwable, writer); - writer.writeEndElement(); - } - else { - writer.writeEmptyElement(elementName); - } - newLine(writer); - } - - private void writeFailureAttributesAndContent(Throwable throwable, XMLStreamWriter writer) - throws XMLStreamException { - - if (throwable.getMessage() != null) { - writeAttributeSafely(writer, "message", throwable.getMessage()); - } - writeAttributeSafely(writer, "type", throwable.getClass().getName()); - writeCDataSafely(writer, readStackTrace(throwable)); - } - - private void collectReportEntries(TestIdentifier testIdentifier, List systemOutElements, - List systemErrElements) { - List entries = this.reportData.getReportEntries(testIdentifier); - if (!entries.isEmpty()) { - List systemOutElementsForCapturedOutput = new ArrayList<>(); - StringBuilder formattedReportEntries = new StringBuilder(); - for (int i = 0; i < entries.size(); i++) { - ReportEntry reportEntry = entries.get(i); - Map keyValuePairs = new LinkedHashMap<>(reportEntry.getKeyValuePairs()); - removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDOUT_REPORT_ENTRY_KEY, - systemOutElementsForCapturedOutput); - removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDERR_REPORT_ENTRY_KEY, systemErrElements); - if (!keyValuePairs.isEmpty()) { - buildReportEntryDescription(reportEntry.getTimestamp(), keyValuePairs, i + 1, - formattedReportEntries); - } - } - systemOutElements.add(formattedReportEntries.toString().trim()); - systemOutElements.addAll(systemOutElementsForCapturedOutput); - } - } - - private void removeIfPresentAndAddAsSeparateElement(Map keyValuePairs, String key, - List elements) { - String value = keyValuePairs.remove(key); - if (value != null) { - elements.add(value); - } - } - - private void buildReportEntryDescription(LocalDateTime timestamp, Map keyValuePairs, - int entryNumber, StringBuilder result) { - result.append( - format("Report Entry #{0} (timestamp: {1})\n", entryNumber, ISO_LOCAL_DATE_TIME.format(timestamp))); - keyValuePairs.forEach((key, value) -> result.append(format("\t- {0}: {1}\n", key, value))); - } - - private String getTime(TestIdentifier testIdentifier, NumberFormat numberFormat) { - return numberFormat.format(this.reportData.getDurationInSeconds(testIdentifier)); - } - - private Optional getHostname() { - try { - return Optional.ofNullable(InetAddress.getLocalHost().getHostName()); - } - catch (UnknownHostException e) { - return Optional.empty(); - } - } - - private LocalDateTime getCurrentDateTime() { - return LocalDateTime.now(this.reportData.getClock()).withNano(0); - } - - private String formatNonStandardAttributesAsString(TestIdentifier testIdentifier) { - return "unique-id: " + testIdentifier.getUniqueId() // - + "\ndisplay-name: " + testIdentifier.getDisplayName(); - } - - private void writeOutputElements(String elementName, List elements, XMLStreamWriter writer) - throws XMLStreamException { - for (String content : elements) { - writeOutputElement(elementName, content, writer); - } - } - - private void writeOutputElement(String elementName, String content, XMLStreamWriter writer) - throws XMLStreamException { - writer.writeStartElement(elementName); - writeCDataSafely(writer, "\n" + content + "\n"); - writer.writeEndElement(); - newLine(writer); - } - - private void writeAttributeSafely(XMLStreamWriter writer, String name, String value) throws XMLStreamException { - writer.writeAttribute(name, escapeIllegalChars(value)); - } - - private void writeCDataSafely(XMLStreamWriter writer, String data) throws XMLStreamException { - for (String safeDataPart : CDATA_SPLIT_PATTERN.split(escapeIllegalChars(data))) { - writer.writeCData(safeDataPart); - } - } - - static String escapeIllegalChars(String text) { - if (text.codePoints().allMatch(XmlReportWriter::isAllowedXmlCharacter)) { - return text; - } - StringBuilder result = new StringBuilder(text.length() * 2); - text.codePoints().forEach(codePoint -> { - if (isAllowedXmlCharacter(codePoint)) { - result.appendCodePoint(codePoint); - } - else { // use a Character Reference (cf. https://www.w3.org/TR/xml/#NT-CharRef) - result.append("&#").append(codePoint).append(';'); - } - }); - return result.toString(); - } - - private static boolean isAllowedXmlCharacter(int codePoint) { - // source: https://www.w3.org/TR/xml/#charsets - return codePoint == 0x9 // - || codePoint == 0xA // - || codePoint == 0xD // - || (codePoint >= 0x20 && codePoint <= 0xD7FF) // - || (codePoint >= 0xE000 && codePoint <= 0xFFFD) // - || (codePoint >= 0x10000 && codePoint <= 0x10FFFF); - } - - private void newLine(XMLStreamWriter xmlWriter) throws XMLStreamException { - xmlWriter.writeCharacters("\n"); - } - - private static boolean isFailure(TestExecutionResult result) { - Optional throwable = result.getThrowable(); - return throwable.isPresent() && throwable.get() instanceof AssertionError; - } - - static class AggregatedTestResult { - - private static final AggregatedTestResult SKIPPED_RESULT = new AggregatedTestResult(SKIPPED, emptyList()); - - public static AggregatedTestResult skipped() { - return SKIPPED_RESULT; - } - - public static AggregatedTestResult nonSkipped(List executionResults) { - Type type = executionResults.stream() // - .map(Type::from) // - .max(naturalOrder()) // - .orElse(SUCCESS); - return new AggregatedTestResult(type, executionResults); - } - - private final Type type; - private final List executionResults; - - private AggregatedTestResult(Type type, List executionResults) { - this.type = type; - this.executionResults = executionResults; - } - - public Map>> getThrowablesByType() { - return executionResults.stream() // - .collect(groupingBy(Type::from, mapping(TestExecutionResult::getThrowable, toList()))); - } - - enum Type { - - SUCCESS, SKIPPED, FAILURE, ERROR; - - private static Type from(TestExecutionResult executionResult) { - if (executionResult.getStatus() == FAILED) { - return isFailure(executionResult) ? FAILURE : ERROR; - } - return SUCCESS; - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java deleted file mode 100644 index d41e891b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Support for generating XML reports using a format which is compatible with - * the de facto standard for JUnit 4 based test reports that was made popular - * by the Ant build system. - */ - -package org.junit.platform.reporting.legacy.xml; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java deleted file mode 100644 index 382404bd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.open.xml; - -import org.junit.platform.engine.TestDescriptor; -import org.opentest4j.reporting.events.api.Factory; -import org.opentest4j.reporting.schema.Namespace; - -class JUnitFactory { - - static Namespace NAMESPACE = Namespace.of("https://schemas.junit.org/open-test-reporting"); - - private JUnitFactory() { - } - - static Factory uniqueId(String uniqueId) { - return context -> new UniqueId(context, uniqueId); - } - - static Factory legacyReportingName(String legacyReportingName) { - return context -> new LegacyReportingName(context, legacyReportingName); - } - - static Factory type(TestDescriptor.Type type) { - return context -> new Type(context, type); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java deleted file mode 100644 index a81214a7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.open.xml; - -import org.opentest4j.reporting.events.api.ChildElement; -import org.opentest4j.reporting.events.api.Context; -import org.opentest4j.reporting.events.core.Metadata; -import org.opentest4j.reporting.schema.QualifiedName; - -class LegacyReportingName extends ChildElement { - - static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "legacyReportingName"); - - LegacyReportingName(Context context, String value) { - super(context, ELEMENT); - withContent(value); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java deleted file mode 100644 index 05c45288..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.open.xml; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.junit.platform.reporting.open.xml.JUnitFactory.legacyReportingName; -import static org.junit.platform.reporting.open.xml.JUnitFactory.type; -import static org.junit.platform.reporting.open.xml.JUnitFactory.uniqueId; -import static org.opentest4j.reporting.events.core.CoreFactory.attachments; -import static org.opentest4j.reporting.events.core.CoreFactory.cpuCores; -import static org.opentest4j.reporting.events.core.CoreFactory.data; -import static org.opentest4j.reporting.events.core.CoreFactory.directorySource; -import static org.opentest4j.reporting.events.core.CoreFactory.fileSource; -import static org.opentest4j.reporting.events.core.CoreFactory.hostName; -import static org.opentest4j.reporting.events.core.CoreFactory.infrastructure; -import static org.opentest4j.reporting.events.core.CoreFactory.metadata; -import static org.opentest4j.reporting.events.core.CoreFactory.operatingSystem; -import static org.opentest4j.reporting.events.core.CoreFactory.reason; -import static org.opentest4j.reporting.events.core.CoreFactory.result; -import static org.opentest4j.reporting.events.core.CoreFactory.sources; -import static org.opentest4j.reporting.events.core.CoreFactory.tag; -import static org.opentest4j.reporting.events.core.CoreFactory.tags; -import static org.opentest4j.reporting.events.core.CoreFactory.uriSource; -import static org.opentest4j.reporting.events.core.CoreFactory.userName; -import static org.opentest4j.reporting.events.java.JavaFactory.classSource; -import static org.opentest4j.reporting.events.java.JavaFactory.classpathResourceSource; -import static org.opentest4j.reporting.events.java.JavaFactory.fileEncoding; -import static org.opentest4j.reporting.events.java.JavaFactory.heapSize; -import static org.opentest4j.reporting.events.java.JavaFactory.javaVersion; -import static org.opentest4j.reporting.events.java.JavaFactory.methodSource; -import static org.opentest4j.reporting.events.java.JavaFactory.packageSource; -import static org.opentest4j.reporting.events.java.JavaFactory.throwable; -import static org.opentest4j.reporting.events.root.RootFactory.finished; -import static org.opentest4j.reporting.events.root.RootFactory.reported; -import static org.opentest4j.reporting.events.root.RootFactory.started; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.file.Path; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; -import org.junit.platform.engine.support.descriptor.CompositeTestSource; -import org.junit.platform.engine.support.descriptor.DirectorySource; -import org.junit.platform.engine.support.descriptor.FileSource; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.engine.support.descriptor.PackageSource; -import org.junit.platform.engine.support.descriptor.UriSource; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.listeners.OutputDir; -import org.opentest4j.reporting.events.api.DocumentWriter; -import org.opentest4j.reporting.events.api.NamespaceRegistry; -import org.opentest4j.reporting.events.core.Result; -import org.opentest4j.reporting.events.core.Sources; -import org.opentest4j.reporting.events.root.Events; -import org.opentest4j.reporting.schema.Namespace; - -/** - * Open Test Reporting events XML generating test execution listener. - * - * @since 1.9 - */ -@API(status = EXPERIMENTAL, since = "1.9") -public class OpenTestReportGeneratingListener implements TestExecutionListener { - - static final String ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.enabled"; - static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; - - private final AtomicInteger idCounter = new AtomicInteger(); - private final Map inProgressIds = new ConcurrentHashMap<>(); - private DocumentWriter eventsFileWriter = DocumentWriter.noop(); - - public OpenTestReportGeneratingListener() { - } - - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - ConfigurationParameters config = testPlan.getConfigurationParameters(); - if (isEnabled(config)) { - NamespaceRegistry namespaceRegistry = NamespaceRegistry.builder(Namespace.REPORTING_CORE) // - .add("e", Namespace.REPORTING_EVENTS) // - .add("java", Namespace.REPORTING_JAVA) // - .add("junit", JUnitFactory.NAMESPACE, - "https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd") // - .build(); - Path eventsXml = OutputDir.create(config.get(OUTPUT_DIR_PROPERTY_NAME)) // - .createFile("junit-platform-events", "xml"); - try { - eventsFileWriter = Events.createDocumentWriter(namespaceRegistry, eventsXml); - reportInfrastructure(); - } - catch (Exception e) { - throw new JUnitException("Failed to initialize XML events file: " + eventsXml, e); - } - } - } - - private Boolean isEnabled(ConfigurationParameters config) { - return config.getBoolean(ENABLED_PROPERTY_NAME).orElse(false); - } - - private void reportInfrastructure() { - eventsFileWriter.append(infrastructure(), infrastructure -> { - try { - String hostName = InetAddress.getLocalHost().getHostName(); - infrastructure.append(hostName(hostName)); - } - catch (UnknownHostException ignored) { - } - infrastructure // - .append(userName(System.getProperty("user.name"))) // - .append(operatingSystem(System.getProperty("os.name"))) // - .append(cpuCores(Runtime.getRuntime().availableProcessors())) // - .append(javaVersion(System.getProperty("java.version"))) // - .append(fileEncoding(System.getProperty("file.encoding"))) // - .append(heapSize(), heapSize -> heapSize.withMax(Runtime.getRuntime().maxMemory())); - }); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - try { - eventsFileWriter.close(); - } - catch (IOException e) { - throw new UncheckedIOException("Failed to close XML events file", e); - } - finally { - eventsFileWriter = DocumentWriter.noop(); - } - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - String id = String.valueOf(idCounter.incrementAndGet()); - reportStarted(testIdentifier, id); - eventsFileWriter.append(finished(id, Instant.now()), // - finished -> finished.append(result(Result.Status.SKIPPED), result -> { - if (StringUtils.isNotBlank(reason)) { - result.append(reason(reason)); - } - })); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - String id = String.valueOf(idCounter.incrementAndGet()); - inProgressIds.put(testIdentifier.getUniqueIdObject(), id); - reportStarted(testIdentifier, id); - } - - private void reportStarted(TestIdentifier testIdentifier, String id) { - eventsFileWriter.append(started(id, Instant.now(), testIdentifier.getDisplayName()), started -> { - testIdentifier.getParentIdObject().ifPresent(parentId -> started.withParentId(inProgressIds.get(parentId))); - started.append(metadata(), metadata -> { - if (!testIdentifier.getTags().isEmpty()) { - metadata.append(tags(), tags -> // - testIdentifier.getTags().forEach(tag -> tags.append(tag(tag.getName())))); - } - metadata.append(uniqueId(testIdentifier.getUniqueId())) // - .append(legacyReportingName(testIdentifier.getLegacyReportingName())) // - .append(type(testIdentifier.getType())); - }); - testIdentifier.getSource().ifPresent( - source -> started.append(sources(), sources -> addTestSource(source, sources))); - }); - } - - private void addTestSource(TestSource source, Sources sources) { - if (source instanceof CompositeTestSource) { - ((CompositeTestSource) source).getSources().forEach(it -> addTestSource(it, sources)); - } - else if (source instanceof ClassSource) { - ClassSource classSource = (ClassSource) source; - sources.append(classSource(classSource.getClassName()), // - element -> classSource.getPosition().ifPresent( - filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); - } - else if (source instanceof MethodSource) { - MethodSource methodSource = (MethodSource) source; - sources.append(methodSource(methodSource.getClassName(), methodSource.getMethodName()), element -> { - String methodParameterTypes = methodSource.getMethodParameterTypes(); - if (methodParameterTypes != null) { - element.withMethodParameterTypes(methodParameterTypes); - } - }); - } - else if (source instanceof ClasspathResourceSource) { - ClasspathResourceSource classpathResourceSource = (ClasspathResourceSource) source; - sources.append(classpathResourceSource(classpathResourceSource.getClasspathResourceName()), // - element -> classpathResourceSource.getPosition().ifPresent( - filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); - } - else if (source instanceof PackageSource) { - sources.append(packageSource(((PackageSource) source).getPackageName())); - } - else if (source instanceof FileSource) { - FileSource fileSource = (FileSource) source; - sources.append(fileSource(fileSource.getFile()), // - element -> fileSource.getPosition().ifPresent( - filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); - } - else if (source instanceof DirectorySource) { - sources.append(directorySource(((DirectorySource) source).getFile())); - } - else if (source instanceof UriSource) { - sources.append(uriSource(((UriSource) source).getUri())); - } - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - String id = inProgressIds.get(testIdentifier.getUniqueIdObject()); - eventsFileWriter.append(reported(id, Instant.now()), // - reported -> reported.append(attachments(), attachments -> attachments.append(data(), data -> { - data.withTime(entry.getTimestamp()); - entry.getKeyValuePairs().forEach(data::addEntry); - }))); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - String id = inProgressIds.remove(testIdentifier.getUniqueIdObject()); - eventsFileWriter.append(finished(id, Instant.now()), // - finished -> finished.append(result(convertStatus(testExecutionResult.getStatus())), // - result -> testExecutionResult.getThrowable() // - .ifPresent(throwable -> result.append(throwable(throwable))))); - } - - private Result.Status convertStatus(TestExecutionResult.Status status) { - switch (status) { - case FAILED: - return Result.Status.FAILED; - case SUCCESSFUL: - return Result.Status.SUCCESSFUL; - case ABORTED: - return Result.Status.ABORTED; - } - throw new JUnitException("Unhandled status: " + status); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java deleted file mode 100644 index 49f8d32c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.open.xml; - -import org.junit.platform.engine.TestDescriptor; -import org.opentest4j.reporting.events.api.ChildElement; -import org.opentest4j.reporting.events.api.Context; -import org.opentest4j.reporting.events.core.Metadata; -import org.opentest4j.reporting.schema.QualifiedName; - -class Type extends ChildElement { - - static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "type"); - - Type(Context context, TestDescriptor.Type type) { - super(context, ELEMENT); - withContent(type.toString()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java deleted file mode 100644 index 4d3ef7b6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.open.xml; - -import org.opentest4j.reporting.events.api.ChildElement; -import org.opentest4j.reporting.events.api.Context; -import org.opentest4j.reporting.events.core.Metadata; -import org.opentest4j.reporting.schema.QualifiedName; - -class UniqueId extends ChildElement { - - static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "uniqueId"); - - UniqueId(Context context, String value) { - super(context, ELEMENT); - withContent(value); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java deleted file mode 100644 index 5befd7af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Support for generating Open Test Reporting compatible XML event reports. - */ - -package org.junit.platform.reporting.open.xml; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java deleted file mode 100644 index d7c60324..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * JUnit Platform Reporting. - */ - -package org.junit.platform.reporting; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener deleted file mode 100644 index d48ed597..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml deleted file mode 100644 index 057d295a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd deleted file mode 100644 index b8456f0d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java deleted file mode 100644 index 66c74947..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Defines the JUnit Platform Reporting API. - * - * @since 1.4 - */ -module org.junit.platform.reporting { - requires java.xml; - requires static transitive org.apiguardian.api; - requires org.junit.platform.commons; - requires transitive org.junit.platform.engine; - requires transitive org.junit.platform.launcher; - - // exports org.junit.platform.reporting; empty package - exports org.junit.platform.reporting.legacy; - exports org.junit.platform.reporting.legacy.xml; - exports org.junit.platform.reporting.open.xml; - - provides org.junit.platform.launcher.TestExecutionListener - with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts deleted file mode 100644 index 78205ece..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/junit-platform-runner.gradle.kts +++ /dev/null @@ -1,36 +0,0 @@ -plugins { - `java-library-conventions` - `junit4-compatibility` -} - -description = "JUnit Platform Runner" - -dependencies { - api(platform(projects.junitBom)) - api(libs.junit4) - api(projects.junitPlatformLauncher) - api(projects.junitPlatformSuiteApi) - - compileOnlyApi(libs.apiguardian) - - implementation(projects.junitPlatformSuiteCommons) - - testImplementation(testFixtures(projects.junitPlatformEngine)) - testImplementation(testFixtures(projects.junitPlatformLauncher)) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -tasks.jar { - bundle { - bnd(""" - # Import JUnit4 packages with a version - Import-Package: \ - ${extra["importAPIGuardian"]},\ - org.junit.platform.commons.logging;status=INTERNAL,\ - org.junit.runner.*;version="[${libs.versions.junit4Min.get()},5)",\ - * - """) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java deleted file mode 100644 index bbc87847..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.runner; - -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request; - -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.core.LauncherFactory; -import org.junit.platform.suite.api.ConfigurationParameter; -import org.junit.platform.suite.api.ExcludeClassNamePatterns; -import org.junit.platform.suite.api.ExcludeEngines; -import org.junit.platform.suite.api.ExcludePackages; -import org.junit.platform.suite.api.ExcludeTags; -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.IncludePackages; -import org.junit.platform.suite.api.IncludeTags; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.SelectClasspathResource; -import org.junit.platform.suite.api.SelectDirectories; -import org.junit.platform.suite.api.SelectFile; -import org.junit.platform.suite.api.SelectModules; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.SelectUris; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.api.SuiteDisplayName; -import org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder; -import org.junit.runner.Description; -import org.junit.runner.Runner; -import org.junit.runner.manipulation.Filter; -import org.junit.runner.manipulation.Filterable; -import org.junit.runner.manipulation.NoTestsRemainException; -import org.junit.runner.notification.RunNotifier; - -/** - * JUnit 4 based {@link Runner} which runs tests on the JUnit Platform in a - * JUnit 4 environment. - * - *

Annotating a class with {@code @RunWith(JUnitPlatform.class)} allows it - * to be run with IDEs and build systems that support JUnit 4 but do not yet - * support the JUnit Platform directly. - * - *

Please note that test classes and suites annotated with - * {@code @RunWith(JUnitPlatform.class)} cannot be executed directly on - * the JUnit Platform (or as a "JUnit 5" test as documented in some IDEs). Such - * classes and suites can only be executed using JUnit 4 infrastructure. - * - *

Consult the various annotations in the {@code org.junit.platform.suite.api} - * package for configuration options. - * - *

If you do not use any configuration annotations from the - * {@code org.junit.platform.suite.api} package, you can use this runner on a - * test class whose programming model is supported on the JUnit Platform — - * for example, a JUnit Jupiter test class. Note, however, that any test class - * run with this runner must be {@code public} in order to be picked up by IDEs - * and build tools. - * - *

When used on a class that serves as a test suite and the - * {@link IncludeClassNamePatterns @IncludeClassNamePatterns} annotation is not - * present, the default include pattern - * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} - * will be used in order to avoid loading classes unnecessarily (see {@link - * org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN - * ClassNameFilter#STANDARD_INCLUDE_PATTERN}). - * - * @since 1.0 - * @see SelectClasses - * @see SelectClasspathResource - * @see SelectDirectories - * @see SelectFile - * @see SelectModules - * @see SelectPackages - * @see SelectUris - * @see IncludeClassNamePatterns - * @see ExcludeClassNamePatterns - * @see IncludeEngines - * @see ExcludeEngines - * @see IncludePackages - * @see ExcludePackages - * @see IncludeTags - * @see ExcludeTags - * @see SuiteDisplayName - * @see org.junit.platform.suite.api.UseTechnicalNames UseTechnicalNames - * @see ConfigurationParameter - * @deprecated since 1.8, in favor of the {@link Suite @Suite} support provided by - * the {@code junit-platform-suite-engine} module; to be removed in JUnit Platform 2.0 - */ -@API(status = DEPRECATED, since = "1.8") -@Deprecated -public class JUnitPlatform extends Runner implements Filterable { - - // @formatter:off - private static final List> IMPLICIT_SUITE_ANNOTATIONS = Arrays.asList( - SelectClasses.class, - SelectClasspathResource.class, - SelectDirectories.class, - SelectFile.class, - SelectFile.class, - SelectModules.class, - SelectPackages.class, - SelectUris.class - ); - // @formatter:on - - private final Class testClass; - private final Launcher launcher; - - private JUnitPlatformTestTree testTree; - - public JUnitPlatform(Class testClass) { - this(testClass, LauncherFactory.create()); - } - - // For testing only - JUnitPlatform(Class testClass, Launcher launcher) { - this.launcher = launcher; - this.testClass = testClass; - this.testTree = generateTestTree(createDiscoveryRequest()); - } - - @Override - public Description getDescription() { - return this.testTree.getSuiteDescription(); - } - - @Override - public void run(RunNotifier notifier) { - this.launcher.execute(this.testTree.getTestPlan(), new JUnitPlatformRunnerListener(this.testTree, notifier)); - } - - private JUnitPlatformTestTree generateTestTree(LauncherDiscoveryRequest discoveryRequest) { - TestPlan testPlan = this.launcher.discover(discoveryRequest); - return new JUnitPlatformTestTree(testPlan, this.testClass); - } - - private LauncherDiscoveryRequest createDiscoveryRequest() { - SuiteLauncherDiscoveryRequestBuilder requestBuilder = request(); - // Allows @RunWith(JUnitPlatform.class) to be added to any test case - boolean isSuite = isSuite(); - if (!isSuite) { - requestBuilder.selectors(selectClass(this.testClass)); - } - - // @formatter:off - return requestBuilder - .filterStandardClassNamePatterns(isSuite) - .suite(this.testClass) - .build(); - // @formatter:on - } - - private boolean isSuite() { - // @formatter:off - return IMPLICIT_SUITE_ANNOTATIONS.stream() - .anyMatch(annotation -> isAnnotated(this.testClass, annotation)); - // @formatter:on - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - Set filteredIdentifiers = this.testTree.getFilteredLeaves(filter); - if (filteredIdentifiers.isEmpty()) { - throw new NoTestsRemainException(); - } - this.testTree = generateTestTree(createDiscoveryRequestForUniqueIds(filteredIdentifiers)); - } - - private LauncherDiscoveryRequest createDiscoveryRequestForUniqueIds(Set testIdentifiers) { - // @formatter:off - List selectors = testIdentifiers.stream() - .map(TestIdentifier::getUniqueIdObject) - .map(DiscoverySelectors::selectUniqueId) - .collect(toList()); - // @formatter:on - return request().selectors(selectors).build(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java deleted file mode 100644 index 03911718..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.runner; - -import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; - -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestExecutionResult.Status; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.runner.Description; -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunNotifier; - -/** - * @since 1.0 - */ -class JUnitPlatformRunnerListener implements TestExecutionListener { - - private final JUnitPlatformTestTree testTree; - private final RunNotifier notifier; - - JUnitPlatformRunnerListener(JUnitPlatformTestTree testTree, RunNotifier notifier) { - this.testTree = testTree; - this.notifier = notifier; - } - - @Override - public void dynamicTestRegistered(TestIdentifier testIdentifier) { - UniqueId parentId = testIdentifier.getParentIdObject().get(); - testTree.addDynamicDescription(testIdentifier, parentId); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - if (testIdentifier.isTest()) { - fireTestIgnored(testIdentifier); - } - else { - testTree.getTestsInSubtree(testIdentifier).forEach(this::fireTestIgnored); - } - } - - private void fireTestIgnored(TestIdentifier testIdentifier) { - Description description = findJUnit4Description(testIdentifier); - this.notifier.fireTestIgnored(description); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - Description description = findJUnit4Description(testIdentifier); - if (description.isTest()) { - this.notifier.fireTestStarted(description); - } - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - Description description = findJUnit4Description(testIdentifier); - Status status = testExecutionResult.getStatus(); - if (status == ABORTED) { - this.notifier.fireTestAssumptionFailed(toFailure(testExecutionResult, description)); - } - else if (status == FAILED) { - this.notifier.fireTestFailure(toFailure(testExecutionResult, description)); - } - if (description.isTest()) { - this.notifier.fireTestFinished(description); - } - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - System.out.println(entry); - } - - private Failure toFailure(TestExecutionResult testExecutionResult, Description description) { - return new Failure(description, testExecutionResult.getThrowable().orElse(null)); - } - - private Description findJUnit4Description(TestIdentifier testIdentifier) { - return this.testTree.getDescription(testIdentifier); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java deleted file mode 100644 index cb32dfeb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.runner; - -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toSet; - -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.suite.api.SuiteDisplayName; -import org.junit.runner.Description; -import org.junit.runner.manipulation.Filter; - -/** - * @since 1.0 - */ -class JUnitPlatformTestTree { - - private final Map descriptions = new HashMap<>(); - private final TestPlan testPlan; - private final Function nameExtractor; - private final Description suiteDescription; - - JUnitPlatformTestTree(TestPlan testPlan, Class testClass) { - this.testPlan = testPlan; - this.nameExtractor = useTechnicalNames(testClass) ? this::getTechnicalName : TestIdentifier::getDisplayName; - this.suiteDescription = generateSuiteDescription(testPlan, testClass); - } - - public TestPlan getTestPlan() { - return testPlan; - } - - @SuppressWarnings("deprecation") - private static boolean useTechnicalNames(Class testClass) { - return testClass.isAnnotationPresent(org.junit.platform.suite.api.UseTechnicalNames.class); - } - - Description getSuiteDescription() { - return this.suiteDescription; - } - - Description getDescription(TestIdentifier identifier) { - return this.descriptions.get(identifier); - } - - private Description generateSuiteDescription(TestPlan testPlan, Class testClass) { - String displayName = useTechnicalNames(testClass) ? testClass.getName() : getSuiteDisplayName(testClass); - Description suiteDescription = Description.createSuiteDescription(displayName); - buildDescriptionTree(suiteDescription, testPlan); - return suiteDescription; - } - - private String getSuiteDisplayName(Class testClass) { - // @formatter:off - return AnnotationUtils.findAnnotation(testClass, SuiteDisplayName.class) - .map(SuiteDisplayName::value) - .filter(StringUtils::isNotBlank) - .orElse(testClass.getName()); - // @formatter:on - } - - private void buildDescriptionTree(Description suiteDescription, TestPlan testPlan) { - testPlan.getRoots().forEach(testIdentifier -> buildDescription(testIdentifier, suiteDescription, testPlan)); - } - - void addDynamicDescription(TestIdentifier newIdentifier, UniqueId parentId) { - Description parent = getDescription(this.testPlan.getTestIdentifier(parentId)); - buildDescription(newIdentifier, parent, this.testPlan); - } - - private void buildDescription(TestIdentifier identifier, Description parent, TestPlan testPlan) { - Description newDescription = createJUnit4Description(identifier, testPlan); - parent.addChild(newDescription); - this.descriptions.put(identifier, newDescription); - testPlan.getChildren(identifier).forEach( - testIdentifier -> buildDescription(testIdentifier, newDescription, testPlan)); - } - - private Description createJUnit4Description(TestIdentifier identifier, TestPlan testPlan) { - String name = nameExtractor.apply(identifier); - if (identifier.isTest()) { - String containerName = testPlan.getParent(identifier).map(nameExtractor).orElse(""); - return Description.createTestDescription(containerName, name, identifier.getUniqueIdObject()); - } - return Description.createSuiteDescription(name, identifier.getUniqueIdObject()); - } - - private String getTechnicalName(TestIdentifier testIdentifier) { - Optional optionalSource = testIdentifier.getSource(); - if (optionalSource.isPresent()) { - TestSource source = optionalSource.get(); - if (source instanceof ClassSource) { - return ((ClassSource) source).getJavaClass().getName(); - } - else if (source instanceof MethodSource) { - MethodSource methodSource = (MethodSource) source; - String methodParameterTypes = methodSource.getMethodParameterTypes(); - if (StringUtils.isBlank(methodParameterTypes)) { - return methodSource.getMethodName(); - } - return String.format("%s(%s)", methodSource.getMethodName(), methodParameterTypes); - } - } - - // Else fall back to display name - return testIdentifier.getDisplayName(); - } - - Set getTestsInSubtree(TestIdentifier ancestor) { - // @formatter:off - return testPlan.getDescendants(ancestor).stream() - .filter(TestIdentifier::isTest) - .collect(toCollection(LinkedHashSet::new)); - // @formatter:on - } - - Set getFilteredLeaves(Filter filter) { - Set identifiers = applyFilterToDescriptions(filter); - return removeNonLeafIdentifiers(identifiers); - } - - private Set removeNonLeafIdentifiers(Set identifiers) { - return identifiers.stream().filter(isALeaf(identifiers)).collect(toSet()); - } - - private Predicate isALeaf(Set identifiers) { - return testIdentifier -> { - Set descendants = testPlan.getDescendants(testIdentifier); - return identifiers.stream().noneMatch(descendants::contains); - }; - } - - private Set applyFilterToDescriptions(Filter filter) { - // @formatter:off - return descriptions.entrySet() - .stream() - .filter(entry -> filter.shouldRun(entry.getValue())) - .map(Entry::getKey) - .collect(toSet()); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java deleted file mode 100644 index ef778ce2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/main/java/org/junit/platform/runner/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * {@code Runner} and annotations for configuring and executing tests on the - * JUnit Platform in a JUnit 4 environment. - */ - -package org.junit.platform.runner; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java deleted file mode 100644 index 47edeb10..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * {@code Runner} and annotations for configuring and executing tests on the - * JUnit Platform in a JUnit 4 environment. - * - * @since 1.0 - */ -module org.junit.platform.runner { - requires transitive junit; // 4 - requires static transitive org.apiguardian.api; - requires transitive org.junit.platform.launcher; - requires transitive org.junit.platform.suite.api; - requires org.junit.platform.suite.commons; - - exports org.junit.platform.runner; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts deleted file mode 100644 index 7be1b0a2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/junit-platform-suite-api.gradle.kts +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - `java-library-conventions` -} - -description = "JUnit Platform Suite API" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformCommons) - - compileOnlyApi(libs.apiguardian) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java deleted file mode 100644 index 0ab3693b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @ConfigurationParameter} is a {@linkplain Repeatable repeatable} - * annotation that specifies a configuration {@link #key key} and - * {@link #value value} pair to be added to the discovery request when running - * a test suite on the JUnit Platform. - * - * @since 1.8 - * @see DisableParentConfigurationParameters - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder#configurationParameter(String, String) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -@Repeatable(ConfigurationParameters.class) -public @interface ConfigurationParameter { - - /** - * The configuration parameter key under which to add the {@link #value() value} - * to the discovery request; never {@code null} or blank. - */ - String key(); - - /** - * The value to add to the discovery request for the specified {@link #key() key}. - */ - String value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java deleted file mode 100644 index 0f12270a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @ConfigurationParameters} is a container for one or more - * {@link ConfigurationParameter @ConfigurationParameter} declarations. - * - *

Note, however, that use of the {@code @ConfigurationParameters} container - * is completely optional since {@code @ConfigurationParameter} is a - * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. - * - * @since 1.8 - * @see ConfigurationParameter - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -public @interface ConfigurationParameters { - - /** - * An array of one or more {@link ConfigurationParameter @ConfigurationParameter} - * declarations. - */ - ConfigurationParameter[] value(); -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java deleted file mode 100644 index d1eed0ee..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * Disable parent configuration parameters. - * - *

By default a suite discovers tests using the configuration parameters - * explicitly configured via {@link ConfigurationParameter @ConfigurationParameter} - * and the configuration parameters from the discovery request that was used to - * discover the suite. - * - *

Annotating a suite with this annotation disables the latter source so - * that only explicit configuration parameters are taken into account. - * - * @since 1.8 - * @see ConfigurationParameter - * @see Suite - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -public @interface DisableParentConfigurationParameters { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java deleted file mode 100644 index 3aa18b8b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ExcludeClassNamePatterns} specifies regular expressions that are used - * to match against fully qualified class names when running a test suite on the - * JUnit Platform. - * - *

The patterns are combined using OR semantics: if the fully qualified name - * of a class matches against at least one of the patterns, the class will be - * excluded from the test plan. - * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.ClassNameFilter#excludeClassNamePatterns - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface ExcludeClassNamePatterns { - - /** - * Regular expressions used to match against fully qualified class names. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java deleted file mode 100644 index e65acb5c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ExcludeEngines} specifies the {@linkplain #value IDs} of - * {@link org.junit.platform.engine.TestEngine TestEngines} to be excluded - * when running a test suite on the JUnit Platform. - * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.launcher.EngineFilter#excludeEngines - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface ExcludeEngines { - - /** - * One or more TestEngine IDs to be excluded from the test plan. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java deleted file mode 100644 index 22b7736c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ExcludePackages} specifies the {@linkplain #value packages} to be - * excluded when running a test suite on the JUnit Platform. - - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.PackageNameFilter#excludePackageNames(String...) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface ExcludePackages { - - /** - * One or more packages to exclude. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java deleted file mode 100644 index 9d55090e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @ExcludeTags} specifies the - * {@linkplain #value tags or tag expressions} to be excluded when running a - * test suite on the JUnit Platform. - * - *

Tag Expressions

- * - *

Tag expressions are boolean expressions with the following allowed - * operators: {@code !} (not), {@code &} (and) and {@code |} (or). Parentheses - * can be used to adjust for operator precedence. Please refer to the - * JUnit 5 User Guide - * for usage examples. - * - *

Syntax Rules for Tags

- *
    - *
  • A tag must not be blank.
  • - *
  • A trimmed tag must not contain whitespace.
  • - *
  • A trimmed tag must not contain ISO control characters.
  • - *
  • A trimmed tag must not contain reserved characters.
  • - *
- * - *

Reserved characters that are not permissible as part of a tag name. - * - *

    - *
  • {@code ","}
  • - *
  • {@code "("}
  • - *
  • {@code ")"}
  • - *
  • {@code "&"}
  • - *
  • {@code "|"}
  • - *
  • {@code "!"}
  • - *
- * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.launcher.TagFilter#excludeTags - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface ExcludeTags { - - /** - * One or more tags to exclude. - * - *

Note: each tag will be {@linkplain String#trim() trimmed} and - * validated according to the Syntax Rules for Tags (see - * {@linkplain ExcludeTags class-level Javadoc} for details). - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java deleted file mode 100644 index 5d75fedb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @IncludeClassNamePatterns} specifies regular expressions that are used - * to match against fully qualified class names when running a test suite on the - * JUnit Platform. - * - *

The patterns are combined using OR semantics: if the fully qualified name - * of a class matches against at least one of the patterns, the class will be - * included in the test plan. - * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN - * @see org.junit.platform.engine.discovery.ClassNameFilter#includeClassNamePatterns - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface IncludeClassNamePatterns { - - /** - * Regular expressions used to match against fully qualified class names. - * - *

The default pattern matches against classes whose names either begin - * with {@code Test} or end with {@code Test} or {@code Tests} (in any package). - */ - // Implementation notes: - // - Test.* :: "Test" prefix for classes in default package - // - .+[.$]Test.* :: "Test" prefix for top-level and nested classes in a named package - // - .*Tests? :: "Test" and "Tests" suffixes in any package - String[] value() default "^(Test.*|.+[.$]Test.*|.*Tests?)$"; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java deleted file mode 100644 index 4117947f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @IncludeEngines} specifies the {@linkplain #value IDs} of - * {@link org.junit.platform.engine.TestEngine TestEngines} to be included - * when running a test suite on the JUnit Platform. - * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.launcher.EngineFilter#includeEngines - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface IncludeEngines { - - /** - * One or more TestEngine IDs to be included in the test plan. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java deleted file mode 100644 index 7635d1dc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @IncludePackages} specifies the {@linkplain #value packages} to be - * included when running a test suite on the JUnit Platform. - * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.PackageNameFilter#includePackageNames(String...) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface IncludePackages { - - /** - * One or more packages to include. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java deleted file mode 100644 index 744dc75a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @IncludeTags} specifies the - * {@linkplain #value tags or tag expressions} to be included when running a - * test suite on the JUnit Platform. - * - *

Tag Expressions

- * - *

Tag expressions are boolean expressions with the following allowed - * operators: {@code !} (not), {@code &} (and) and {@code |} (or). Parentheses - * can be used to adjust for operator precedence. Please refer to the - * JUnit 5 User Guide - * for usage examples. - * - *

Syntax Rules for Tags

- *
    - *
  • A tag must not be blank.
  • - *
  • A trimmed tag must not contain whitespace.
  • - *
  • A trimmed tag must not contain ISO control characters.
  • - *
  • A trimmed tag must not contain reserved characters.
  • - *
- * - *

Reserved characters that are not permissible as part of a tag name. - * - *

    - *
  • {@code ","}
  • - *
  • {@code "("}
  • - *
  • {@code ")"}
  • - *
  • {@code "&"}
  • - *
  • {@code "|"}
  • - *
  • {@code "!"}
  • - *
- * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.launcher.TagFilter#includeTags - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface IncludeTags { - - /** - * One or more tags to include. - * - *

Note: each tag will be {@linkplain String#trim() trimmed} and - * validated according to the Syntax Rules for Tags (see - * {@linkplain IncludeTags class-level Javadoc} for details). - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java deleted file mode 100644 index 9d0a8d3c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @SelectClasses} specifies the classes to select when running - * a test suite on the JUnit Platform. - * - * @since 1.0 - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectClass(Class) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface SelectClasses { - - /** - * One or more classes to select. - */ - Class[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java deleted file mode 100644 index 64d6cc3d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @SelectClasspathResource} is a {@linkplain Repeatable repeatable} - * annotation that specifies a classpath resource to select when running - * a test suite on the JUnit Platform. - * - * @since 1.8 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectClasspathResource(String, org.junit.platform.engine.discovery.FilePosition) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -@Repeatable(SelectClasspathResources.class) -public @interface SelectClasspathResource { - - /** - * The name of the classpath resource to select. - */ - String value(); - - /** - * The line number within the classpath resource; ignored if not greater than - * zero. - */ - int line() default 0; - - /** - * The column number within the classpath resource; ignored if the line number - * is ignored or if not greater than zero. - */ - int column() default 0; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java deleted file mode 100644 index a8860b9c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @SelectClasspathResources} is a container for one or more - * {@link SelectClasspathResource @SelectClasspathResource} declarations. - * - *

Note, however, that use of the {@code @SelectClasspathResources} container is - * completely optional since {@code @SelectClasspathResource} is a - * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. - * - * @since 1.8 - * @see SelectClasspathResource - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -public @interface SelectClasspathResources { - - /** - * An array of one or more {@link SelectClasspathResource @SelectClasspathResource} - * declarations. - */ - SelectClasspathResource[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java deleted file mode 100644 index 30d32a53..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @SelectDirectories} specifies the directories to select when - * running a test suite on the JUnit Platform. - * - * @since 1.8 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectDirectory(String) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -public @interface SelectDirectories { - - /** - * One or more directories to select. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java deleted file mode 100644 index 88be9454..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @SelectFile} is a {@linkplain Repeatable repeatable} annotation that - * specifies a file to select when running a test suite on the JUnit - * Platform. - * - * @since 1.8 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectFile(String, org.junit.platform.engine.discovery.FilePosition) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -@Repeatable(SelectFiles.class) -public @interface SelectFile { - - /** - * The file to select. - */ - String value(); - - /** - * The line number within the file; ignored if not greater than zero. - */ - int line() default 0; - - /** - * The column number within the file; ignored if the line number is ignored - * or if not greater than zero. - */ - int column() default 0; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java deleted file mode 100644 index 4f4c8fd4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @SelectFiles} is a container for one or more - * {@link SelectFile @SelectFile} declarations. - * - *

Note, however, that use of the {@code @SelectFiles} container is - * completely optional since {@code @SelectFile} is a - * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. - * - * @since 1.8 - * @see SelectFile - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -public @interface SelectFiles { - - /** - * An array of one or more {@link SelectFile @SelectFile} declarations. - */ - SelectFile[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java deleted file mode 100644 index 12f90de7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Set; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @SelectModules} specifies the modules to select when running - * a test suite on the JUnit Platform. - * - * @since 1.8 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectModules(Set) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -public @interface SelectModules { - - /** - * One or more modules to select. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java deleted file mode 100644 index c2d5eaff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @SelectPackages} specifies the names of packages to select - * when running a test suite on the JUnit Platform. - * - * @since 1.0 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectPackage(String) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = MAINTAINED, since = "1.0") -public @interface SelectPackages { - - /** - * One or more fully qualified package names to select. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java deleted file mode 100644 index 82512f2f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; - -/** - * {@code @SelectUris} specifies the URIs to select when running a test - * suite on the JUnit Platform. - * - * @since 1.8 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectUri(String) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -public @interface SelectUris { - - /** - * One or more URIs to select. - */ - String[] value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java deleted file mode 100644 index 739d6582..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; -import org.junit.platform.commons.annotation.Testable; - -/** - * {@code @Suite} marks a class as a test suite on the JUnit Platform. - * - *

Selector and filter annotations are used to control the contents of the - * suite. Additionally configuration can be passed to the suite via the - * configuration annotations. - * - *

When the {@link IncludeClassNamePatterns @IncludeClassNamePatterns} - * annotation is not present, the default include pattern - * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} - * will be used in order to avoid loading classes unnecessarily (see {@link - * org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN - * ClassNameFilter#STANDARD_INCLUDE_PATTERN}). - * - *

By default a suite discovers tests using the configuration parameters - * explicitly configured by {@link ConfigurationParameter @ConfigurationParameter} - * and the configuration parameters from the discovery request that discovered - * the suite. Annotating a suite with - * {@link DisableParentConfigurationParameters @DisableParentConfigurationParameters} - * annotation disables the latter as a source of parameters so that only explicit - * configuration parameters are taken into account. - * - * @since 1.8 - * @see SelectClasses - * @see SelectClasspathResource - * @see SelectDirectories - * @see SelectFile - * @see SelectModules - * @see SelectPackages - * @see SelectUris - * @see IncludeClassNamePatterns - * @see ExcludeClassNamePatterns - * @see IncludeEngines - * @see ExcludeEngines - * @see IncludePackages - * @see ExcludePackages - * @see IncludeTags - * @see ExcludeTags - * @see SuiteDisplayName - * @see ConfigurationParameter - * @see DisableParentConfigurationParameters - * @see org.junit.platform.launcher.LauncherDiscoveryRequest - * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder - * @see org.junit.platform.launcher.Launcher - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = Status.EXPERIMENTAL, since = "1.8") -@Testable -public @interface Suite { - - /** - * Fail suite if no tests were discovered. - * - * @since 1.9 - */ - @API(status = Status.EXPERIMENTAL, since = "1.9") - boolean failIfNoTests() default true; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java deleted file mode 100644 index 3cd11cb9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @SuiteDisplayName} is used to declare a {@linkplain #value custom - * display name} for the annotated test class that is executed as a test suite - * on the JUnit Platform. - * - *

Display names are typically used for test reporting in IDEs and build - * tools and may contain spaces, special characters, and even emoji. - * - *

JUnit 4 Suite Support

- *

Test suites can be run on the JUnit Platform in a JUnit 4 environment via - * {@code @RunWith(JUnitPlatform.class)}. - * - *

JUnit 5 Suite Support

- *

Test suites can be run on the JUnit Platform in a JUnit 5 environment via - * the {@code junit-platform-suite-engine} module. - * - * @since 1.1 - * @see Suite - * @see org.junit.platform.runner.JUnitPlatform - */ -@Retention(RUNTIME) -@Target(TYPE) -@Documented -@API(status = MAINTAINED, since = "1.1") -public @interface SuiteDisplayName { - - /** - * Custom display name for the annotated class. - * - * @return a custom display name; never blank or consisting solely of - * whitespace - */ - String value(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java deleted file mode 100644 index 12b11808..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.api; - -import static org.apiguardian.api.API.Status.DEPRECATED; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apiguardian.api.API; - -/** - * {@code @UseTechnicalNames} specifies that technical names should be - * used instead of display names when running a test suite on the - * JUnit Platform. - * - *

By default, display names will be used for test artifacts in - * reports and graphical displays in IDEs; however, when a JUnit Platform test - * suite is executed with a build tool such as Gradle or Maven, the generated - * test report may need to include the technical names of test - * artifacts — for example, fully qualified class names — instead - * of shorter display names like the simple name of a test class or a - * custom display name containing special characters. - * - *

Note that the presence of {@code @UseTechnicalNames} overrides any custom - * display name configured for the suite via {@link SuiteDisplayName @SuiteDisplayName}. - * - *

JUnit 4 Suite Support

- *

Test suites can be run on the JUnit Platform in a JUnit 4 environment via - * {@code @RunWith(JUnitPlatform.class)}. - * - * @since 1.0 - * @see org.junit.platform.runner.JUnitPlatform - * @deprecated since 1.8, in favor of the {@link Suite @Suite} support provided by - * the {@code junit-platform-suite-engine} module; to be removed in JUnit Platform 2.0 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -@Documented -@API(status = DEPRECATED, since = "1.8") -@Deprecated -public @interface UseTechnicalNames { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java deleted file mode 100644 index f5cd9bc5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Annotations for configuring a test suite on the JUnit Platform. - * - *

JUnit 4 Suite Support

- *

Test suites can be run on the JUnit Platform in a JUnit 4 environment via - * {@code @RunWith(JUnitPlatform.class)} with the {@code junit-platform-runner}. - * - *

JUnit 5 Suite Support

- *

Test suites can be run on the JUnit Platform in a JUnit 5 environment via - * {@link Suite @Suite} with the {@code junit-platform-suite-engine}. - */ - -package org.junit.platform.suite.api; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java deleted file mode 100644 index 9af8daea..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Annotations for configuring a test suite on the JUnit Platform. - * - * @since 1.0 - */ -module org.junit.platform.suite.api { - requires static transitive org.apiguardian.api; - requires transitive org.junit.platform.commons; - - exports org.junit.platform.suite.api; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts deleted file mode 100644 index 8f578b0b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - `java-library-conventions` -} - -description = "JUnit Platform Suite Commons" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformLauncher) - - compileOnlyApi(libs.apiguardian) - - implementation(projects.junitPlatformEngine) - implementation(projects.junitPlatformSuiteApi) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java deleted file mode 100644 index 99dffdaf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.commons; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.discovery.FilePosition; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UriSelector; - -/** - * @since 1.8 - */ -class AdditionalDiscoverySelectors { - - static List selectUris(String... uris) { - Preconditions.notNull(uris, "URI list must not be null"); - Preconditions.containsNoNullElements(uris, "Individual URIs must not be null"); - - // @formatter:off - return uniqueStreamOf(uris) - .filter(StringUtils::isNotBlank) - .map(DiscoverySelectors::selectUri) - .collect(Collectors.toList()); - // @formatter:on - } - - static List selectDirectories(String... paths) { - Preconditions.notNull(paths, "Directory paths must not be null"); - Preconditions.containsNoNullElements(paths, "Individual directory paths must not be null"); - - // @formatter:off - return uniqueStreamOf(paths) - .filter(StringUtils::isNotBlank) - .map(DiscoverySelectors::selectDirectory) - .collect(Collectors.toList()); - // @formatter:on - } - - static List selectPackages(String... packageNames) { - Preconditions.notNull(packageNames, "Package names must not be null"); - Preconditions.containsNoNullElements(packageNames, "Individual package names must not be null"); - - // @formatter:off - return uniqueStreamOf(packageNames) - .map(DiscoverySelectors::selectPackage) - .collect(Collectors.toList()); - // @formatter:on - } - - static List selectClasses(Class... classes) { - Preconditions.notNull(classes, "classes must not be null"); - Preconditions.containsNoNullElements(classes, "Individual classes must not be null"); - - // @formatter:off - return uniqueStreamOf(classes) - .map(DiscoverySelectors::selectClass) - .collect(Collectors.toList()); - // @formatter:on - } - - static List selectModules(String... moduleNames) { - Preconditions.notNull(moduleNames, "Module names must not be null"); - Preconditions.containsNoNullElements(moduleNames, "Individual module names must not be null"); - - return DiscoverySelectors.selectModules(uniqueStreamOf(moduleNames).collect(Collectors.toSet())); - } - - static FileSelector selectFile(String path, int line, int column) { - Preconditions.notBlank(path, "File path must not be null or blank"); - - if (line <= 0) { - return DiscoverySelectors.selectFile(path); - } - if (column <= 0) { - return DiscoverySelectors.selectFile(path, FilePosition.from(line)); - } - return DiscoverySelectors.selectFile(path, FilePosition.from(line, column)); - } - - static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, int line, int column) { - Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); - - if (line <= 0) { - return DiscoverySelectors.selectClasspathResource(classpathResourceName); - } - if (column <= 0) { - return DiscoverySelectors.selectClasspathResource(classpathResourceName, FilePosition.from(line)); - } - return DiscoverySelectors.selectClasspathResource(classpathResourceName, FilePosition.from(line, column)); - } - - private static Stream uniqueStreamOf(T[] packageNames) { - return Arrays.stream(packageNames).distinct(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java deleted file mode 100644 index fabe8891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.commons; - -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; -import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; -import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectClasspathResource; -import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectFile; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.apiguardian.api.API.Status; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.PackageNameFilter; -import org.junit.platform.launcher.EngineFilter; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.TagFilter; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; -import org.junit.platform.suite.api.ConfigurationParameter; -import org.junit.platform.suite.api.DisableParentConfigurationParameters; -import org.junit.platform.suite.api.ExcludeClassNamePatterns; -import org.junit.platform.suite.api.ExcludeEngines; -import org.junit.platform.suite.api.ExcludePackages; -import org.junit.platform.suite.api.ExcludeTags; -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.IncludePackages; -import org.junit.platform.suite.api.IncludeTags; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.SelectClasspathResource; -import org.junit.platform.suite.api.SelectDirectories; -import org.junit.platform.suite.api.SelectFile; -import org.junit.platform.suite.api.SelectModules; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.SelectUris; - -/** - * @since 1.8 - */ -@API(status = Status.INTERNAL, since = "1.8", consumers = { "org.junit.platform.suite.engine", - "org.junit.platform.runner" }) -public final class SuiteLauncherDiscoveryRequestBuilder { - - private final LauncherDiscoveryRequestBuilder delegate = LauncherDiscoveryRequestBuilder.request(); - private final List selectedClassNames = new ArrayList<>(); - private boolean includeClassNamePatternsUsed; - private boolean filterStandardClassNamePatterns = false; - private ConfigurationParameters parentConfigurationParameters; - private boolean enableParentConfigurationParameters = true; - - private SuiteLauncherDiscoveryRequestBuilder() { - } - - public static SuiteLauncherDiscoveryRequestBuilder request() { - return new SuiteLauncherDiscoveryRequestBuilder(); - } - - public SuiteLauncherDiscoveryRequestBuilder filterStandardClassNamePatterns( - boolean filterStandardClassNamePatterns) { - this.filterStandardClassNamePatterns = filterStandardClassNamePatterns; - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder selectors(DiscoverySelector... selectors) { - delegate.selectors(selectors); - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder selectors(List selectors) { - delegate.selectors(selectors); - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder filters(Filter... filters) { - delegate.filters(filters); - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { - delegate.configurationParameter(key, value); - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder configurationParameters(Map configurationParameters) { - delegate.configurationParameters(configurationParameters); - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder parentConfigurationParameters( - ConfigurationParameters parentConfigurationParameters) { - this.parentConfigurationParameters = parentConfigurationParameters; - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder enableImplicitConfigurationParameters(boolean enabled) { - delegate.enableImplicitConfigurationParameters(enabled); - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder suite(Class suiteClass) { - Preconditions.notNull(suiteClass, "Suite class must not be null"); - - // Annotations in alphabetical order (except @SelectClasses) - // @formatter:off - findRepeatableAnnotations(suiteClass, ConfigurationParameter.class) - .forEach(configuration -> configurationParameter(configuration.key(), configuration.value())); - findAnnotation(suiteClass, DisableParentConfigurationParameters.class) - .ifPresent(__ -> enableParentConfigurationParameters = false); - findAnnotationValues(suiteClass, ExcludeClassNamePatterns.class, ExcludeClassNamePatterns::value) - .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) - .map(ClassNameFilter::excludeClassNamePatterns) - .ifPresent(this::filters); - findAnnotationValues(suiteClass, ExcludeEngines.class, ExcludeEngines::value) - .map(EngineFilter::excludeEngines) - .ifPresent(this::filters); - findAnnotationValues(suiteClass, ExcludePackages.class, ExcludePackages::value) - .map(PackageNameFilter::excludePackageNames) - .ifPresent(this::filters); - findAnnotationValues(suiteClass, ExcludeTags.class, ExcludeTags::value) - .map(TagFilter::excludeTags) - .ifPresent(this::filters); - // Process @SelectClasses before @IncludeClassNamePatterns, since the names - // of selected classes get automatically added to the include filter. - findAnnotationValues(suiteClass, SelectClasses.class, SelectClasses::value) - .map(this::selectClasses) - .ifPresent(this::selectors); - findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value) - .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) - .map(this::createIncludeClassNameFilter) - .ifPresent(filters -> { - includeClassNamePatternsUsed = true; - filters(filters); - }); - findAnnotationValues(suiteClass, IncludeEngines.class, IncludeEngines::value) - .map(EngineFilter::includeEngines) - .ifPresent(this::filters); - findAnnotationValues(suiteClass, IncludePackages.class, IncludePackages::value) - .map(PackageNameFilter::includePackageNames) - .ifPresent(this::filters); - findAnnotationValues(suiteClass, IncludeTags.class, IncludeTags::value) - .map(TagFilter::includeTags) - .ifPresent(this::filters); - findRepeatableAnnotations(suiteClass, SelectClasspathResource.class) - .stream() - .map(annotation -> selectClasspathResource(annotation.value(), annotation.line(), annotation.column())) - .forEach(this::selectors); - findAnnotationValues(suiteClass, SelectDirectories.class, SelectDirectories::value) - .map(AdditionalDiscoverySelectors::selectDirectories) - .ifPresent(this::selectors); - findRepeatableAnnotations(suiteClass, SelectFile.class) - .stream() - .map(annotation -> selectFile(annotation.value(), annotation.line(), annotation.column())) - .forEach(this::selectors); - findAnnotationValues(suiteClass, SelectModules.class, SelectModules::value) - .map(AdditionalDiscoverySelectors::selectModules) - .ifPresent(this::selectors); - findAnnotationValues(suiteClass, SelectUris.class, SelectUris::value) - .map(AdditionalDiscoverySelectors::selectUris) - .ifPresent(this::selectors); - findAnnotationValues(suiteClass, SelectPackages.class, SelectPackages::value) - .map(AdditionalDiscoverySelectors::selectPackages) - .ifPresent(this::selectors); - // @formatter:on - return this; - } - - public LauncherDiscoveryRequest build() { - if (filterStandardClassNamePatterns && !includeClassNamePatternsUsed) { - delegate.filters(createIncludeClassNameFilter(STANDARD_INCLUDE_PATTERN)); - } - - if (enableParentConfigurationParameters && parentConfigurationParameters != null) { - delegate.parentConfigurationParameters(parentConfigurationParameters); - } - - return delegate.build(); - } - - private List selectClasses(Class... classes) { - Arrays.stream(classes).map(Class::getName).distinct().forEach(this.selectedClassNames::add); - return AdditionalDiscoverySelectors.selectClasses(classes); - } - - private ClassNameFilter createIncludeClassNameFilter(String... patterns) { - String[] combinedPatterns = Stream.concat(// - this.selectedClassNames.stream().map(Pattern::quote), // - Arrays.stream(patterns)// - ).toArray(String[]::new); - return ClassNameFilter.includeClassNamePatterns(combinedPatterns); - } - - private static Optional findAnnotationValues(AnnotatedElement element, - Class annotationType, Function valueExtractor) { - return findAnnotation(element, annotationType).map(valueExtractor).filter(values -> values.length > 0); - } - - private static Optional trimmed(String[] patterns) { - if (patterns.length == 0) { - return Optional.empty(); - } - // @formatter:off - return Optional.of(Arrays.stream(patterns) - .filter(StringUtils::isNotBlank) - .map(String::trim) - .toArray(String[]::new)); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java deleted file mode 100644 index 3eeb6350..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Common support utilities for executing test suites on the JUnit Platform. - * - *

DISCLAIMER

- * - *

Any API annotated with {@code @API(status = INTERNAL)} is intended solely - * for usage within the JUnit framework itself. Any usage of internal - * APIs by external parties is not supported! - */ - -package org.junit.platform.suite.commons; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java deleted file mode 100644 index b810efc7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Common support utilities for declarative test suite executors. - * - * @since 1.8 - */ -module org.junit.platform.suite.commons { - requires static transitive org.apiguardian.api; - requires org.junit.platform.suite.api; - requires org.junit.platform.commons; - requires org.junit.platform.engine; - requires transitive org.junit.platform.launcher; - - exports org.junit.platform.suite.commons to - org.junit.platform.suite.engine, - org.junit.platform.runner; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts deleted file mode 100644 index 12e7f674..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - `java-library-conventions` -} - -description = "JUnit Platform Suite Engine" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformEngine) - api(projects.junitPlatformSuiteApi) - - compileOnlyApi(libs.apiguardian) - - implementation(projects.junitPlatformSuiteCommons) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java deleted file mode 100644 index 441dc3f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; - -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.UniqueId.Segment; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.support.discovery.SelectorResolver; - -/** - * @since 1.8 - */ -final class ClassSelectorResolver implements SelectorResolver { - - private static final Logger log = LoggerFactory.getLogger(ClassSelectorResolver.class); - - private static final IsSuiteClass isSuiteClass = new IsSuiteClass(); - - private final Predicate classNameFilter; - private final SuiteEngineDescriptor suiteEngineDescriptor; - private final ConfigurationParameters configurationParameters; - - ClassSelectorResolver(Predicate classNameFilter, SuiteEngineDescriptor suiteEngineDescriptor, - ConfigurationParameters configurationParameters) { - this.classNameFilter = classNameFilter; - this.suiteEngineDescriptor = suiteEngineDescriptor; - this.configurationParameters = configurationParameters; - } - - @Override - public Resolution resolve(ClassSelector selector, Context context) { - Class testClass = selector.getJavaClass(); - if (isSuiteClass.test(testClass)) { - if (classNameFilter.test(testClass.getName())) { - // @formatter:off - Optional suiteWithDiscoveryRequest = context - .addToParent(parent -> newSuiteDescriptor(testClass, parent)) - .map(suite -> suite.addDiscoveryRequestFrom(testClass)); - return toResolution(suiteWithDiscoveryRequest); - // @formatter:on - } - } - return unresolved(); - } - - @Override - public Resolution resolve(UniqueIdSelector selector, Context context) { - UniqueId uniqueId = selector.getUniqueId(); - UniqueId engineId = suiteEngineDescriptor.getUniqueId(); - List resolvedSegments = engineId.getSegments(); - // @formatter:off - return uniqueId.getSegments() - .stream() - .skip(resolvedSegments.size()) - .findFirst() - .filter(suiteSegment -> SuiteTestDescriptor.SEGMENT_TYPE.equals(suiteSegment.getType())) - .flatMap(ClassSelectorResolver::tryLoadSuiteClass) - .filter(isSuiteClass) - .map(suiteClass -> context - .addToParent(parent -> newSuiteDescriptor(suiteClass, parent)) - .map(suite -> uniqueId.equals(suite.getUniqueId()) - // The uniqueId selector either targeted a class annotated with @Suite; - ? suite.addDiscoveryRequestFrom(suiteClass) - // or a specific test in that suite - : suite.addDiscoveryRequestFrom(uniqueId))) - .map(ClassSelectorResolver::toResolution) - .orElseGet(Resolution::unresolved); - // @formatter:on - } - - private static Optional> tryLoadSuiteClass(UniqueId.Segment segment) { - return ReflectionUtils.tryToLoadClass(segment.getValue()).toOptional(); - } - - private static Resolution toResolution(Optional suite) { - return suite.map(Match::exact).map(Resolution::match).orElseGet(Resolution::unresolved); - } - - private Optional newSuiteDescriptor(Class suiteClass, TestDescriptor parent) { - UniqueId id = parent.getUniqueId().append(SuiteTestDescriptor.SEGMENT_TYPE, suiteClass.getName()); - if (containsCycle(id)) { - log.config(() -> createConfigContainsCycleMessage(suiteClass, id)); - return Optional.empty(); - } - - return Optional.of(new SuiteTestDescriptor(id, suiteClass, configurationParameters)); - } - - private static boolean containsCycle(UniqueId id) { - List segments = id.getSegments(); - List engineAndSuiteSegment = segments.subList(segments.size() - 2, segments.size()); - List ancestorSegments = segments.subList(0, segments.size() - 2); - for (int i = 0; i < ancestorSegments.size() - 1; i++) { - List candidate = ancestorSegments.subList(i, i + 2); - if (engineAndSuiteSegment.equals(candidate)) { - return true; - } - } - return false; - } - - private static String createConfigContainsCycleMessage(Class suiteClass, UniqueId suiteId) { - return String.format( - "The suite configuration of [%s] resulted in a cycle [%s] and will not be discovered a second time.", - suiteClass.getName(), suiteId); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java deleted file mode 100644 index f9c20e5a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; - -/** - * @since 1.8 - */ -final class DiscoverySelectorResolver { - - // @formatter:off - private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() - .addClassContainerSelectorResolver(new IsSuiteClass()) - .addSelectorResolver(context -> new ClassSelectorResolver( - context.getClassNameFilter(), - context.getEngineDescriptor(), - context.getDiscoveryRequest().getConfigurationParameters())) - .build(); - // @formatter:on - - private static void discoverSuites(SuiteEngineDescriptor engineDescriptor) { - // @formatter:off - engineDescriptor.getChildren().stream() - .map(SuiteTestDescriptor.class::cast) - .forEach(SuiteTestDescriptor::discover); - // @formatter:on - } - - void resolveSelectors(EngineDiscoveryRequest request, SuiteEngineDescriptor engineDescriptor) { - resolver.resolve(request, engineDescriptor); - discoverSuites(engineDescriptor); - engineDescriptor.accept(TestDescriptor::prune); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java deleted file mode 100644 index 9e6816af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; -import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; - -import java.util.function.Predicate; - -/** - * @since 1.8 - */ -final class IsPotentialTestContainer implements Predicate> { - - @Override - public boolean test(Class candidate) { - // Please do not collapse the following into a single statement. - if (isPrivate(candidate)) { - return false; - } - if (isAbstract(candidate)) { - return false; - } - if (candidate.isLocalClass()) { - return false; - } - if (candidate.isAnonymousClass()) { - return false; - } - return !isInnerClass(candidate); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java deleted file mode 100644 index f336c03f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import java.util.function.Predicate; - -import org.junit.platform.commons.support.AnnotationSupport; -import org.junit.platform.suite.api.Suite; - -/** - * @since 1.8 - */ -final class IsSuiteClass implements Predicate> { - - private static final IsPotentialTestContainer isPotentialTestContainer = new IsPotentialTestContainer(); - - @Override - public boolean test(Class testClass) { - return isPotentialTestContainer.test(testClass) && hasSuiteAnnotation(testClass); - } - - private boolean hasSuiteAnnotation(Class testClass) { - return AnnotationSupport.isAnnotated(testClass, Suite.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java deleted file mode 100644 index 966cfcbe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import org.junit.platform.commons.JUnitException; - -class NoTestsDiscoveredException extends JUnitException { - - private static final long serialVersionUID = 1L; - - NoTestsDiscoveredException(Class suiteClass) { - super(String.format("Suite [%s] did not discover any tests", suiteClass.getName())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java deleted file mode 100644 index 3c351b2e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; - -/** - * @since 1.8 - */ -final class SuiteEngineDescriptor extends EngineDescriptor { - - static final String ENGINE_ID = "junit-platform-suite"; - - SuiteEngineDescriptor(UniqueId uniqueId) { - super(uniqueId, "JUnit Platform Suite"); - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java deleted file mode 100644 index af776ef8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import static java.util.Collections.emptyList; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; -import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase; -import org.junit.platform.launcher.core.EngineExecutionOrchestrator; -import org.junit.platform.launcher.core.LauncherDiscoveryResult; -import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; -import org.junit.platform.launcher.listeners.SummaryGeneratingListener; -import org.junit.platform.launcher.listeners.TestExecutionSummary; - -/** - * @since 1.8 - */ -class SuiteLauncher { - - private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator(); - private final EngineDiscoveryOrchestrator discoveryOrchestrator; - - static SuiteLauncher create() { - Set engines = new LinkedHashSet<>(); - new ServiceLoaderTestEngineRegistry().loadTestEngines().forEach(engines::add); - return new SuiteLauncher(engines); - } - - private SuiteLauncher(Set testEngines) { - Preconditions.condition(hasTestEngineOtherThanSuiteEngine(testEngines), - () -> "Cannot create SuiteLauncher without at least one other TestEngine; " - + "consider adding an engine implementation JAR to the classpath"); - this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, emptyList()); - } - - private boolean hasTestEngineOtherThanSuiteEngine(Set testEngines) { - return testEngines.stream().anyMatch(testEngine -> !SuiteEngineDescriptor.ENGINE_ID.equals(testEngine.getId())); - } - - LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, UniqueId parentId) { - return discoveryOrchestrator.discover(discoveryRequest, Phase.DISCOVERY, parentId); - } - - TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, - EngineExecutionListener parentEngineExecutionListener) { - SummaryGeneratingListener listener = new SummaryGeneratingListener(); - executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener); - return listener.getSummary(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java deleted file mode 100644 index 52cb4e11..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.core.LauncherDiscoveryResult; -import org.junit.platform.launcher.listeners.TestExecutionSummary; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.api.SuiteDisplayName; -import org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder; - -/** - * {@link TestDescriptor} for tests based on the JUnit Platform Suite API. - * - *

Default Display Names

- * - *

The default display name is the simple name of the class. - * - * @since 1.8 - * @see SuiteDisplayName - */ -final class SuiteTestDescriptor extends AbstractTestDescriptor { - - static final String SEGMENT_TYPE = "suite"; - - private final SuiteLauncherDiscoveryRequestBuilder discoveryRequestBuilder = request(); - private final ConfigurationParameters configurationParameters; - private final Boolean failIfNoTests; - private final Class suiteClass; - - private LauncherDiscoveryResult launcherDiscoveryResult; - private SuiteLauncher launcher; - - SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters) { - super(id, getSuiteDisplayName(suiteClass), ClassSource.from(suiteClass)); - this.configurationParameters = configurationParameters; - this.failIfNoTests = getFailIfNoTests(suiteClass); - this.suiteClass = suiteClass; - } - - private static Boolean getFailIfNoTests(Class suiteClass) { - // @formatter:off - return findAnnotation(suiteClass, Suite.class) - .map(Suite::failIfNoTests) - .orElseThrow(() -> new JUnitException(String.format("Suite [%s] was not annotated with @Suite", suiteClass.getName()))); - // @formatter:on - } - - SuiteTestDescriptor addDiscoveryRequestFrom(Class suiteClass) { - Preconditions.condition(launcherDiscoveryResult == null, - "discovery request can not be modified after discovery"); - discoveryRequestBuilder.suite(suiteClass); - return this; - } - - SuiteTestDescriptor addDiscoveryRequestFrom(UniqueId uniqueId) { - Preconditions.condition(launcherDiscoveryResult == null, - "discovery request can not be modified after discovery"); - discoveryRequestBuilder.selectors(DiscoverySelectors.selectUniqueId(uniqueId)); - return this; - } - - void discover() { - if (launcherDiscoveryResult != null) { - return; - } - - // @formatter:off - LauncherDiscoveryRequest request = discoveryRequestBuilder - .filterStandardClassNamePatterns(true) - .enableImplicitConfigurationParameters(false) - .parentConfigurationParameters(configurationParameters) - .build(); - // @formatter:on - this.launcher = SuiteLauncher.create(); - this.launcherDiscoveryResult = launcher.discover(request, getUniqueId()); - // @formatter:off - launcherDiscoveryResult.getTestEngines() - .stream() - .map(testEngine -> launcherDiscoveryResult.getEngineTestDescriptor(testEngine)) - .forEach(this::addChild); - // @formatter:on - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - - private static String getSuiteDisplayName(Class testClass) { - // @formatter:off - return findAnnotation(testClass, SuiteDisplayName.class) - .map(SuiteDisplayName::value) - .filter(StringUtils::isNotBlank) - .orElse(testClass.getSimpleName()); - // @formatter:on - } - - void execute(EngineExecutionListener parentEngineExecutionListener) { - parentEngineExecutionListener.executionStarted(this); - // #2838: The discovery result from a suite may have been filtered by - // post discovery filters from the launcher. The discovery result should - // be pruned accordingly - LauncherDiscoveryResult discoveryResult = this.launcherDiscoveryResult.withRetainedEngines( - getChildren()::contains); - TestExecutionSummary summary = launcher.execute(discoveryResult, parentEngineExecutionListener); - parentEngineExecutionListener.executionFinished(this, computeTestExecutionResult(summary)); - } - - private TestExecutionResult computeTestExecutionResult(TestExecutionSummary summary) { - if (failIfNoTests && summary.getTestsFoundCount() == 0) { - return TestExecutionResult.failed(new NoTestsDiscoveredException(suiteClass)); - } - return TestExecutionResult.successful(); - } - - @Override - public boolean mayRegisterTests() { - // While a suite will not register new tests after discovery, we pretend - // it does. This allows the suite to fail if not tests were discovered. - // If not, the empty suite would be pruned. - return true; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java deleted file mode 100644 index 96b35339..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; - -/** - * The JUnit Platform Suite {@link org.junit.platform.engine.TestEngine TestEngine}. - * - * @since 1.8 - */ -@API(status = INTERNAL, since = "1.8") -public final class SuiteTestEngine implements TestEngine { - - @Override - public String getId() { - return SuiteEngineDescriptor.ENGINE_ID; - } - - /** - * Returns {@code org.junit.platform} as the group ID. - */ - @Override - public Optional getGroupId() { - return Optional.of("org.junit.platform"); - } - - /** - * Returns {@code junit-platform-suite-engine} as the artifact ID. - */ - @Override - public Optional getArtifactId() { - return Optional.of("junit-platform-suite-engine"); - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - SuiteEngineDescriptor engineDescriptor = new SuiteEngineDescriptor(uniqueId); - new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); - return engineDescriptor; - } - - @Override - public void execute(ExecutionRequest request) { - SuiteEngineDescriptor suiteEngineDescriptor = (SuiteEngineDescriptor) request.getRootTestDescriptor(); - EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); - - engineExecutionListener.executionStarted(suiteEngineDescriptor); - - // @formatter:off - suiteEngineDescriptor.getChildren() - .stream() - .map(SuiteTestDescriptor.class::cast) - .forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener)); - // @formatter:on - engineExecutionListener.executionFinished(suiteEngineDescriptor, TestExecutionResult.successful()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java deleted file mode 100644 index fa4237d3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Core package for the JUnit Platform Suite test engine. - */ - -package org.junit.platform.suite.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine deleted file mode 100644 index 5832d4fc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.suite.engine.SuiteTestEngine diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java deleted file mode 100644 index f98845cb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Provides a {@linkplain org.junit.platform.engine.TestEngine} for running - * declarative test suites. - * - * @since 1.8 - * @provides org.junit.platform.engine.TestEngine - */ -module org.junit.platform.suite.engine { - requires static org.apiguardian.api; - requires org.junit.platform.suite.api; - requires org.junit.platform.suite.commons; - requires org.junit.platform.commons; - requires org.junit.platform.engine; - requires org.junit.platform.launcher; - - provides org.junit.platform.engine.TestEngine - with org.junit.platform.suite.engine.SuiteTestEngine; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts deleted file mode 100644 index 8615483d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/junit-platform-suite.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - `java-library-conventions` -} - -description = "JUnit Platform Suite (Aggregator)" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformSuiteApi) - implementation(projects.junitPlatformSuiteEngine) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java deleted file mode 100644 index 74a575d7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Aggregates all JUnit Platform Suite modules. - * - * @since 1.8 - */ -module org.junit.platform.suite { - requires transitive org.junit.platform.suite.api; - requires transitive org.junit.platform.suite.engine; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md deleted file mode 100644 index a32decd8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/LICENSE.md +++ /dev/null @@ -1,98 +0,0 @@ -Eclipse Public License - v 2.0 -============================== - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -### 1. Definitions - -“Contribution” means: -* **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and -* **b)** in the case of each subsequent Contributor: - * **i)** changes to the Program, and - * **ii)** additions to the Program; -where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. - -“Contributor” means any person or entity that Distributes the Program. - -“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -“Program” means the Contributions Distributed in accordance with this Agreement. - -“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. - -“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. - -“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. - -“Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy. - -“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. - -“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. - -### 2. Grant of Rights - -**a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. - -**b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. - -**c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. - -**d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. - -**e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). - -### 3. Requirements - -**3.1** If a Contributor Distributes the Program in any form, then: - -* **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and - -* **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: - * **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; - * **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; - * **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and - * **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. - -**3.2** When the Program is Distributed as Source Code: - -* **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and -* **b)** a copy of this Agreement must be included with each copy of the Program. - -**3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. - -### 4. Commercial Distribution - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -### 5. No Warranty - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -### 6. Disclaimer of Liability - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -### 7. General - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. - -Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. - -#### Exhibit A - Form of Secondary Licenses Notice - -> “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” - -Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. - -If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts deleted file mode 100644 index 6a574bcf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/junit-platform-testkit.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - `java-library-conventions` -} - -description = "JUnit Platform Test Kit" - -dependencies { - api(platform(projects.junitBom)) - api(libs.assertj) - api(libs.opentest4j) - api(projects.junitPlatformLauncher) - - compileOnlyApi(libs.apiguardian) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java deleted file mode 100644 index 74015e88..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.opentest4j.AssertionFailedError; -import org.opentest4j.MultipleFailuresError; - -/** - * {@code Assertions} is a collection of selected assertion utility methods - * from JUnit Jupiter for use within the JUnit Platform Test Kit. - * - * @since 1.4 - */ -class Assertions { - - @FunctionalInterface - interface Executable { - void execute() throws Throwable; - } - - static void assertAll(String heading, Stream executables) { - Preconditions.notNull(executables, "executables stream must not be null"); - - List failures = executables // - .map(executable -> { - Preconditions.notNull(executable, "individual executables must not be null"); - try { - executable.execute(); - return null; - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - return t; - } - }) // - .filter(Objects::nonNull) // - .collect(Collectors.toList()); - - if (!failures.isEmpty()) { - MultipleFailuresError multipleFailuresError = new MultipleFailuresError(heading, failures); - failures.forEach(multipleFailuresError::addSuppressed); - throw multipleFailuresError; - } - } - - static void assertEquals(long expected, long actual, String message) { - if (expected != actual) { - failNotEqual(expected, actual, message); - } - } - - private static void failNotEqual(long expected, long actual, String message) { - fail(format(expected, actual, message), expected, actual); - } - - private static void fail(String message, Object expected, Object actual) { - throw new AssertionFailedError(message, expected, actual); - } - - private static String format(long expected, long actual, String message) { - return buildPrefix(message) + formatValues(expected, actual); - } - - private static String buildPrefix(String message) { - return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); - } - - private static String formatValues(long expected, long actual) { - return String.format("expected: <%d> but was: <%d>", expected, actual); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java deleted file mode 100644 index bce1db1c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.junit.platform.testkit.engine.Event.byTestDescriptor; - -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; - -/** - * {@code EngineExecutionResults} provides a fluent API for processing the - * results of executing a test plan on the JUnit Platform for a given - * {@link org.junit.platform.engine.TestEngine TestEngine}. - * - * @since 1.4 - * @see #allEvents() - * @see #containerEvents() - * @see #testEvents() - * @see ExecutionRecorder - * @see Events - * @see Executions - */ -@API(status = MAINTAINED, since = "1.7") -public class EngineExecutionResults { - - private final Events allEvents; - private final Events testEvents; - private final Events containerEvents; - - /** - * Construct {@link EngineExecutionResults} from the supplied list of recorded - * {@linkplain Event events}. - * - * @param events the list of events; never {@code null} or - * containing {@code null} elements - */ - EngineExecutionResults(List events) { - Preconditions.notNull(events, "Event list must not be null"); - Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); - - this.allEvents = new Events(events, "All"); - this.testEvents = new Events(filterEvents(events, TestDescriptor::isTest), "Test"); - this.containerEvents = new Events(filterEvents(events, TestDescriptor::isContainer), "Container"); - } - - /** - * Get all recorded events. - * - * @since 1.6 - * @see #containerEvents() - * @see #testEvents() - */ - public Events allEvents() { - return this.allEvents; - } - - /** - * Get recorded events for containers. - * - *

In this context, the word "container" applies to {@link TestDescriptor - * TestDescriptors} that return {@code true} from {@link TestDescriptor#isContainer()}. - * - * @since 1.6 - * @see #allEvents() - * @see #testEvents() - */ - public Events containerEvents() { - return this.containerEvents; - } - - /** - * Get recorded events for tests. - * - *

In this context, the word "test" applies to {@link TestDescriptor - * TestDescriptors} that return {@code true} from {@link TestDescriptor#isTest()}. - * - * @since 1.6 - * @see #allEvents() - * @see #containerEvents() - */ - public Events testEvents() { - return this.testEvents; - } - - /** - * Filter the supplied list of events using the supplied predicate. - */ - private static Stream filterEvents(List events, Predicate predicate) { - return events.stream().filter(byTestDescriptor(predicate)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java deleted file mode 100644 index 195c223f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; - -import java.util.Map; -import java.util.ServiceLoader; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.CollectionUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; -import org.junit.platform.launcher.core.EngineExecutionOrchestrator; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; -import org.junit.platform.launcher.core.LauncherDiscoveryResult; -import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; - -/** - * {@code EngineTestKit} provides support for executing a test plan for a given - * {@link TestEngine} and then accessing the results via - * {@linkplain EngineExecutionResults a fluent API} to verify the expected results. - * - * @since 1.4 - * @see #engine(String) - * @see #engine(TestEngine) - * @see #execute(String, LauncherDiscoveryRequest) - * @see #execute(TestEngine, LauncherDiscoveryRequest) - * @see EngineExecutionResults - */ -@API(status = MAINTAINED, since = "1.7") -public final class EngineTestKit { - - /** - * Create an execution {@link Builder} for the {@link TestEngine} with the - * supplied ID. - * - *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} - * mechanism, analogous to the manner in which test engines are loaded in - * the JUnit Platform Launcher API. - * - *

Example Usage

- * - *
-	 * EngineTestKit
-	 *     .engine("junit-jupiter")
-	 *     .selectors(selectClass(MyTests.class))
-	 *     .execute()
-	 *     .testEvents()
-	 *     .assertStatistics(stats -> stats.started(2).finished(2));
-	 * 
- * - * @param engineId the ID of the {@code TestEngine} to use; must not be - * {@code null} or blank - * @return the engine execution {@code Builder} - * @throws PreconditionViolationException if the supplied ID is {@code null} - * or blank, or if the {@code TestEngine} with the supplied ID - * cannot be loaded - * @see #engine(TestEngine) - * @see #execute(String, LauncherDiscoveryRequest) - * @see #execute(TestEngine, LauncherDiscoveryRequest) - */ - public static Builder engine(String engineId) { - Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); - return engine(loadTestEngine(engineId.trim())); - } - - /** - * Create an execution {@link Builder} for the supplied {@link TestEngine}. - * - *

Example Usage

- * - *
-	 * EngineTestKit
-	 *     .engine(new MyTestEngine())
-	 *     .selectors(selectClass(MyTests.class))
-	 *     .execute()
-	 *     .testEvents()
-	 *     .assertStatistics(stats -> stats.started(2).finished(2));
-	 * 
- * - * @param testEngine the {@code TestEngine} to use; must not be {@code null} - * @return the engine execution {@code Builder} - * @throws PreconditionViolationException if the {@code TestEngine} is - * {@code null} - * @see #engine(String) - * @see #execute(String, LauncherDiscoveryRequest) - * @see #execute(TestEngine, LauncherDiscoveryRequest) - */ - public static Builder engine(TestEngine testEngine) { - Preconditions.notNull(testEngine, "TestEngine must not be null"); - return new Builder(testEngine); - } - - /** - * Execute tests for the given {@link EngineDiscoveryRequest} using the - * {@link TestEngine} with the supplied ID. - * - *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} - * mechanism, analogous to the manner in which test engines are loaded in - * the JUnit Platform Launcher API. - * - *

Note that {@link org.junit.platform.launcher.LauncherDiscoveryRequest} - * from the {@code junit-platform-launcher} module is a subtype of - * {@code EngineDiscoveryRequest}. It is therefore quite convenient to make - * use of the DSL provided in - * {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} - * to build an appropriate discovery request to supply to this method. As - * an alternative, consider using {@link #engine(String)} for a more fluent - * API. - * - * @param engineId the ID of the {@code TestEngine} to use; must not be - * {@code null} or blank - * @param discoveryRequest the {@code EngineDiscoveryRequest} to use - * @return the results of the execution - * @throws PreconditionViolationException for invalid arguments or if the - * {@code TestEngine} with the supplied ID cannot be loaded - * @see #execute(String, LauncherDiscoveryRequest) - * @see #engine(String) - * @see #engine(TestEngine) - * @deprecated Please use {@link #execute(String, LauncherDiscoveryRequest)} - * instead. - */ - @Deprecated - @API(status = DEPRECATED, since = "1.7") - public static EngineExecutionResults execute(String engineId, EngineDiscoveryRequest discoveryRequest) { - Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); - return execute(loadTestEngine(engineId.trim()), discoveryRequest); - } - - /** - * Execute tests for the given {@link LauncherDiscoveryRequest} using the - * {@link TestEngine} with the supplied ID. - * - *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} - * mechanism, analogous to the manner in which test engines are loaded in - * the JUnit Platform Launcher API. - * - *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} - * provides a convenient way to build an appropriate discovery request to - * supply to this method. As an alternative, consider using - * {@link #engine(TestEngine)} for a more fluent API. - * - * @param engineId the ID of the {@code TestEngine} to use; must not be - * {@code null} or blank - * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use - * @return the results of the execution - * @throws PreconditionViolationException for invalid arguments or if the - * {@code TestEngine} with the supplied ID cannot be loaded - * @since 1.7 - * @see #execute(TestEngine, LauncherDiscoveryRequest) - * @see #engine(String) - * @see #engine(TestEngine) - */ - public static EngineExecutionResults execute(String engineId, LauncherDiscoveryRequest discoveryRequest) { - Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); - return execute(loadTestEngine(engineId.trim()), discoveryRequest); - } - - /** - * Execute tests for the given {@link EngineDiscoveryRequest} using the - * supplied {@link TestEngine}. - * - *

Note that {@link org.junit.platform.launcher.LauncherDiscoveryRequest} - * from the {@code junit-platform-launcher} module is a subtype of - * {@code EngineDiscoveryRequest}. It is therefore quite convenient to make - * use of the DSL provided in - * {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} - * to build an appropriate discovery request to supply to this method. As - * an alternative, consider using {@link #engine(TestEngine)} for a more fluent - * API. - * - * @param testEngine the {@code TestEngine} to use; must not be {@code null} - * @param discoveryRequest the {@code EngineDiscoveryRequest} to use; must - * not be {@code null} - * @return the recorded {@code EngineExecutionResults} - * @throws PreconditionViolationException for invalid arguments - * @see #execute(TestEngine, LauncherDiscoveryRequest) - * @see #engine(String) - * @see #engine(TestEngine) - * @deprecated Please use {@link #execute(TestEngine, LauncherDiscoveryRequest)} - * instead. - */ - @Deprecated - @API(status = DEPRECATED, since = "1.7") - public static EngineExecutionResults execute(TestEngine testEngine, EngineDiscoveryRequest discoveryRequest) { - Preconditions.notNull(testEngine, "TestEngine must not be null"); - Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); - - ExecutionRecorder executionRecorder = new ExecutionRecorder(); - executeDirectly(testEngine, discoveryRequest, executionRecorder); - return executionRecorder.getExecutionResults(); - } - - /** - * Execute tests for the given {@link LauncherDiscoveryRequest} using the - * supplied {@link TestEngine}. - * - *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} - * provides a convenient way to build an appropriate discovery request to - * supply to this method. As an alternative, consider using - * {@link #engine(TestEngine)} for a more fluent API. - * - * @param testEngine the {@code TestEngine} to use; must not be {@code null} - * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use; must - * not be {@code null} - * @return the recorded {@code EngineExecutionResults} - * @throws PreconditionViolationException for invalid arguments - * @since 1.7 - * @see #execute(String, LauncherDiscoveryRequest) - * @see #engine(String) - * @see #engine(TestEngine) - */ - public static EngineExecutionResults execute(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { - Preconditions.notNull(testEngine, "TestEngine must not be null"); - Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); - - ExecutionRecorder executionRecorder = new ExecutionRecorder(); - executeUsingLauncherOrchestration(testEngine, discoveryRequest, executionRecorder); - return executionRecorder.getExecutionResults(); - } - - private static void executeDirectly(TestEngine testEngine, EngineDiscoveryRequest discoveryRequest, - EngineExecutionListener listener) { - UniqueId engineUniqueId = UniqueId.forEngine(testEngine.getId()); - TestDescriptor engineTestDescriptor = testEngine.discover(discoveryRequest, engineUniqueId); - ExecutionRequest request = new ExecutionRequest(engineTestDescriptor, listener, - discoveryRequest.getConfigurationParameters()); - testEngine.execute(request); - } - - private static void executeUsingLauncherOrchestration(TestEngine testEngine, - LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { - LauncherDiscoveryResult discoveryResult = new EngineDiscoveryOrchestrator(singleton(testEngine), - emptySet()).discover(discoveryRequest, EXECUTION); - TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); - Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); - new EngineExecutionOrchestrator().execute(discoveryResult, listener); - } - - @SuppressWarnings("unchecked") - private static TestEngine loadTestEngine(String engineId) { - Iterable testEngines = new ServiceLoaderTestEngineRegistry().loadTestEngines(); - return ((Stream) CollectionUtils.toStream(testEngines)) // - .filter((TestEngine engine) -> engineId.equals(engine.getId()))// - .findFirst()// - .orElseThrow(() -> new PreconditionViolationException( - String.format("Failed to load TestEngine with ID [%s]", engineId))); - } - - private EngineTestKit() { - /* no-op */ - } - - // ------------------------------------------------------------------------- - - /** - * {@link TestEngine} execution builder. - * - *

See {@link EngineTestKit#engine(String)} and - * {@link EngineTestKit#engine(TestEngine)} for example usage. - * - * @since 1.4 - * @see #selectors(DiscoverySelector...) - * @see #filters(Filter...) - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - * @see #execute() - */ - public static final class Builder { - - private final LauncherDiscoveryRequestBuilder requestBuilder = LauncherDiscoveryRequestBuilder.request() // - .enableImplicitConfigurationParameters(false); - private final TestEngine testEngine; - - private Builder(TestEngine testEngine) { - this.testEngine = testEngine; - } - - /** - * Add all of the supplied {@linkplain DiscoverySelector discovery selectors}. - * - *

Built-in discovery selectors can be created via the static factory - * methods in {@link org.junit.platform.engine.discovery.DiscoverySelectors}. - * - * @param selectors the discovery selectors to add; never {@code null} - * @return this builder for method chaining - * @see #filters(Filter...) - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - * @see #execute() - */ - public Builder selectors(DiscoverySelector... selectors) { - this.requestBuilder.selectors(selectors); - return this; - } - - /** - * Add all of the supplied {@linkplain DiscoveryFilter discovery filters}. - * - *

Built-in discovery filters can be created via the static factory - * methods in {@link org.junit.platform.engine.discovery.ClassNameFilter} - * and {@link org.junit.platform.engine.discovery.PackageNameFilter}. - * - * @param filters the discovery filters to add; never {@code null} - * @return this builder for method chaining - * @see #filters(Filter...) - * @see #selectors(DiscoverySelector...) - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - * @see #execute() - * @deprecated Please use {@link #filters(Filter...)} instead. - */ - @Deprecated - @API(status = DEPRECATED, since = "1.7") - public Builder filters(DiscoveryFilter... filters) { - this.requestBuilder.filters(filters); - return this; - } - - /** - * Add all of the supplied {@linkplain Filter filters}. - * - *

Built-in discovery filters can be created via the static factory - * methods in {@link org.junit.platform.engine.discovery.ClassNameFilter} - * and {@link org.junit.platform.engine.discovery.PackageNameFilter}. - * - *

Built-in post-discovery filters can be created via the static - * factory methods in {@link org.junit.platform.launcher.TagFilter}. - * - * @param filters the filters to add; never {@code null} - * @return this builder for method chaining - * @since 1.7 - * @see #selectors(DiscoverySelector...) - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - * @see #execute() - */ - @API(status = EXPERIMENTAL, since = "1.7") - public Builder filters(Filter... filters) { - this.requestBuilder.filters(filters); - return this; - } - - /** - * Add the supplied configuration parameter. - * - * @param key the configuration parameter key under which to store the - * value; never {@code null} or blank - * @param value the value to store - * @return this builder for method chaining - * @see #selectors(DiscoverySelector...) - * @see #filters(Filter...) - * @see #configurationParameters(Map) - * @see #execute() - * @see org.junit.platform.engine.ConfigurationParameters - */ - public Builder configurationParameter(String key, String value) { - this.requestBuilder.configurationParameter(key, value); - return this; - } - - /** - * Add all of the supplied configuration parameters. - * - * @param configurationParameters the map of configuration parameters to add; - * never {@code null} - * @return this builder for method chaining - * @see #selectors(DiscoverySelector...) - * @see #filters(Filter...) - * @see #configurationParameter(String, String) - * @see #execute() - * @see org.junit.platform.engine.ConfigurationParameters - */ - public Builder configurationParameters(Map configurationParameters) { - this.requestBuilder.configurationParameters(configurationParameters); - return this; - } - - /** - * Configure whether implicit configuration parameters should be - * considered. - * - *

By default, only configuration parameters that are passed - * explicitly to this builder are taken into account. Passing - * {@code true} to this method, enables additionally reading - * configuration parameters from implicit sources, i.e. system - * properties and the {@code junit-platform.properties} classpath - * resource. - * - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - */ - @API(status = EXPERIMENTAL, since = "1.7") - public Builder enableImplicitConfigurationParameters(boolean enabled) { - this.requestBuilder.enableImplicitConfigurationParameters(enabled); - return this; - } - - /** - * Execute tests for the configured {@link TestEngine}, - * {@linkplain DiscoverySelector discovery selectors}, - * {@linkplain DiscoveryFilter discovery filters}, and - * configuration parameters. - * - * @return the recorded {@code EngineExecutionResults} - * @see #selectors(DiscoverySelector...) - * @see #filters(Filter...) - * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) - */ - public EngineExecutionResults execute() { - LauncherDiscoveryRequest request = this.requestBuilder.build(); - ExecutionRecorder executionRecorder = new ExecutionRecorder(); - EngineTestKit.executeUsingLauncherOrchestration(this.testEngine, request, executionRecorder); - return executionRecorder.getExecutionResults(); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java deleted file mode 100644 index 63c69409..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.junit.platform.commons.util.FunctionUtils.where; - -import java.time.Instant; -import java.util.Optional; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * {@code Event} represents a single event fired during execution of - * a test plan on the JUnit Platform. - * - * @since 1.4 - * @see EventType - */ -@API(status = MAINTAINED, since = "1.7") -public class Event { - - // --- Factories ----------------------------------------------------------- - - /** - * Create an {@code Event} for a reporting entry published for the - * supplied {@link TestDescriptor} and {@link ReportEntry}. - * - * @param testDescriptor the {@code TestDescriptor} associated with the event; - * never {@code null} - * @param entry the {@code ReportEntry} that was published; never {@code null} - * @return the newly created {@code Event} - * @see EventType#REPORTING_ENTRY_PUBLISHED - */ - public static Event reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - Preconditions.notNull(entry, "ReportEntry must not be null"); - return new Event(EventType.REPORTING_ENTRY_PUBLISHED, testDescriptor, entry); - } - - /** - * Create an {@code Event} for the dynamic registration of the - * supplied {@link TestDescriptor}. - * - * @param testDescriptor the {@code TestDescriptor} associated with the event; - * never {@code null} - * @return the newly created {@code Event} - * @see EventType#DYNAMIC_TEST_REGISTERED - */ - public static Event dynamicTestRegistered(TestDescriptor testDescriptor) { - return new Event(EventType.DYNAMIC_TEST_REGISTERED, testDescriptor, null); - } - - /** - * Create a skipped {@code Event} for the supplied - * {@link TestDescriptor} and {@code reason}. - * - * @param testDescriptor the {@code TestDescriptor} associated with the event; - * never {@code null} - * @param reason the reason the execution was skipped; may be {@code null} - * @return the newly created {@code Event} - * @see EventType#SKIPPED - */ - public static Event executionSkipped(TestDescriptor testDescriptor, String reason) { - return new Event(EventType.SKIPPED, testDescriptor, reason); - } - - /** - * Create a started {@code Event} for the supplied - * {@link TestDescriptor}. - * - * @param testDescriptor the {@code TestDescriptor} associated with the - * event; never {@code null} - * @return the newly created {@code Event} - * @see EventType#STARTED - */ - public static Event executionStarted(TestDescriptor testDescriptor) { - return new Event(EventType.STARTED, testDescriptor, null); - } - - /** - * Create a finished {@code Event} for the supplied - * {@link TestDescriptor} and {@link TestExecutionResult}. - * - * @param testDescriptor the {@code TestDescriptor} associated with the - * event; never {@code null} - * @param result the {@code TestExecutionResult} for the supplied - * {@code TestDescriptor}; never {@code null} - * @return the newly created {@code Event} - * @see EventType#FINISHED - */ - public static Event executionFinished(TestDescriptor testDescriptor, TestExecutionResult result) { - Preconditions.notNull(result, "Event of type FINISHED cannot have a null TestExecutionResult"); - return new Event(EventType.FINISHED, testDescriptor, result); - } - - // --- Predicates ---------------------------------------------------------- - - /** - * Create a {@link Predicate} for {@linkplain Event events} whose payload - * types match the supplied {@code payloadType} and whose payloads match the - * supplied {@code payloadPredicate}. - * - * @param payloadType the required payload type - * @param payloadPredicate a {@code Predicate} to match against payloads - * @return the resulting {@code Predicate} - */ - public static Predicate byPayload(Class payloadType, Predicate payloadPredicate) { - return event -> event.getPayload(payloadType).filter(payloadPredicate).isPresent(); - } - - /** - * Create a {@link Predicate} for {@linkplain Event events} whose - * {@linkplain EventType event types} match the supplied {@code type}. - * - * @param type the type to match against - * @return the resulting {@code Predicate} - */ - public static Predicate byType(EventType type) { - return event -> event.type.equals(type); - } - - /** - * Create a {@link Predicate} for {@linkplain Event events} whose - * {@link TestDescriptor TestDescriptors} match the supplied - * {@code testDescriptorPredicate}. - * - * @param testDescriptorPredicate a {@code Predicate} to match against test - * descriptors - * @return the resulting {@link Predicate} - */ - public static Predicate byTestDescriptor(Predicate testDescriptorPredicate) { - return where(Event::getTestDescriptor, testDescriptorPredicate); - } - - // ------------------------------------------------------------------------- - - private final Instant timestamp = Instant.now(); - private final EventType type; - private final TestDescriptor testDescriptor; - private final Object payload; - - /** - * Construct an {@code Event} with the supplied arguments. - * - * @param type the type of the event; never {@code null} - * @param testDescriptor the {@code TestDescriptor} associated with the event; - * never {@code null} - * @param payload the generic payload associated with the event; may be {@code null} - */ - private Event(EventType type, TestDescriptor testDescriptor, Object payload) { - this.type = Preconditions.notNull(type, "EventType must not be null"); - this.testDescriptor = Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); - this.payload = payload; - } - - /** - * Get the type of this {@code Event}. - * - * @return the event type; never {@code null} - * @see EventType - */ - public EventType getType() { - return this.type; - } - - /** - * Get the {@link TestDescriptor} associated with this {@code Event}. - * - * @return the {@code TestDescriptor}; never {@code null} - */ - public TestDescriptor getTestDescriptor() { - return this.testDescriptor; - } - - /** - * Get the {@link Instant} when this {@code Event} occurred. - * - * @return the {@code Instant} when this {@code Event} occurred; - * never {@code null} - */ - public Instant getTimestamp() { - return this.timestamp; - } - - /** - * Get the payload, if available. - * - * @return an {@code Optional} containing the payload; never {@code null} - * but potentially empty - * @see #getPayload(Class) - * @see #getRequiredPayload(Class) - */ - public Optional getPayload() { - return Optional.ofNullable(this.payload); - } - - /** - * Get the payload of the expected type, if available. - * - *

This is a convenience method that automatically casts the payload to - * the expected type. If the payload is not present or is not of the expected - * type, this method will return {@link Optional#empty()}. - * - * @param payloadType the expected payload type; never {@code null} - * @return an {@code Optional} containing the payload; never {@code null} - * but potentially empty - * @see #getPayload() - * @see #getRequiredPayload(Class) - */ - public Optional getPayload(Class payloadType) { - Preconditions.notNull(payloadType, "Payload type must not be null"); - return getPayload().filter(payloadType::isInstance).map(payloadType::cast); - } - - /** - * Get the payload of the required type. - * - *

This is a convenience method that automatically casts the payload to - * the required type. If the payload is not present or is not of the expected - * type, this method will throw an {@link IllegalArgumentException}. - * - * @param payloadType the required payload type; never {@code null} - * @return the payload - * @throws IllegalArgumentException if the payload is of a different type - * or is not present - * @see #getPayload() - * @see #getPayload(Class) - */ - public T getRequiredPayload(Class payloadType) throws IllegalArgumentException { - return getPayload(payloadType).orElseThrow(// - () -> new IllegalArgumentException("Event does not contain a payload of type " + payloadType.getName())); - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("type", this.type) - .append("testDescriptor", this.testDescriptor) - .append("timestamp", this.timestamp) - .append("payload", this.payload) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java deleted file mode 100644 index f1aa4734..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static java.util.function.Predicate.isEqual; -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.assertj.core.api.Assertions.allOf; -import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; -import static org.junit.platform.testkit.engine.Event.byPayload; -import static org.junit.platform.testkit.engine.Event.byTestDescriptor; -import static org.junit.platform.testkit.engine.Event.byType; -import static org.junit.platform.testkit.engine.EventType.DYNAMIC_TEST_REGISTERED; -import static org.junit.platform.testkit.engine.EventType.FINISHED; -import static org.junit.platform.testkit.engine.EventType.SKIPPED; -import static org.junit.platform.testkit.engine.EventType.STARTED; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.assertj.core.api.Condition; -import org.assertj.core.description.Description; -import org.assertj.core.description.JoinDescription; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestExecutionResult.Status; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; - -/** - * Collection of AssertJ {@linkplain Condition conditions} for {@link Event}. - * - * @since 1.4 - * @see TestExecutionResultConditions - */ -@API(status = MAINTAINED, since = "1.7") -public final class EventConditions { - - private EventConditions() { - /* no-op */ - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event} matches all of the supplied conditions. - */ - @SafeVarargs - @SuppressWarnings("varargs") - public static Condition event(Condition... conditions) { - return allOf(conditions); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * an instance of {@link EngineDescriptor}. - */ - public static Condition engine() { - return new Condition<>(byTestDescriptor(EngineDescriptor.class::isInstance), "is an engine"); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isTest() test} and its - * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied - * {@link String}. - * - * @see #test() - * @see #uniqueIdSubstring(String) - */ - public static Condition test(String uniqueIdSubstring) { - return test(uniqueIdSubstring(uniqueIdSubstring)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isTest() test}, its - * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied - * {@link String}, and its {@linkplain TestDescriptor#getDisplayName() - * display name} equals the supplied {@link String}. - * - * @see #test() - * @see #test(Condition) - * @see #uniqueIdSubstring(String) - * @see #displayName(String) - */ - public static Condition test(String uniqueIdSubstring, String displayName) { - return allOf(test(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event} matches the supplied {@code Condition} and its - * {@linkplain Event#getTestDescriptor() test descriptor} is a - * {@linkplain TestDescriptor#isTest() test}. - * - *

For example, {@code test(displayName("my display name"))} can be used - * to match against a test with the given display name. - * - * @since 1.8 - * @see #test(String) - * @see #test(String, String) - * @see #displayName(String) - */ - @API(status = MAINTAINED, since = "1.8") - public static Condition test(Condition condition) { - return allOf(test(), condition); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isTest() test}. - */ - public static Condition test() { - return new Condition<>(byTestDescriptor(TestDescriptor::isTest), "is a test"); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isContainer() container} and its - * {@linkplain TestDescriptor#getUniqueId() unique id} contains the - * fully-qualified name of the supplied {@link Class}. - */ - public static Condition container(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - return container(clazz.getName()); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isContainer() container} and its - * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied - * {@link String}. - */ - public static Condition container(String uniqueIdSubstring) { - return container(uniqueIdSubstring(uniqueIdSubstring)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event} matches the supplied {@code Condition} and its - * {@linkplain Event#getTestDescriptor() test descriptor} is a - * {@linkplain TestDescriptor#isContainer() container}. - */ - public static Condition container(Condition condition) { - return allOf(container(), condition); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isContainer() container}. - */ - public static Condition container() { - return new Condition<>(byTestDescriptor(TestDescriptor::isContainer), "is a container"); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event} matches the supplied {@code Condition}, its - * {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isContainer() container}, and its - * {@linkplain TestDescriptor#getUniqueId() unique id} contains the - * simple names of the supplied {@link Class} and all of its - * {@linkplain Class#getEnclosingClass() enclosing classes}. - * - *

For example, {@code nestedContainer(MyNestedTests.class, displayName("my display name"))} - * can be used to match against a nested container with the given display name. - * - *

Please note that this method does not differentiate between static - * nested classes and non-static member classes (e.g., inner classes). - * - * @since 1.8 - * @see #nestedContainer(Class) - */ - @API(status = MAINTAINED, since = "1.8") - public static Condition nestedContainer(Class clazz, Condition condition) { - return allOf(nestedContainer(clazz), condition); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is - * a {@linkplain TestDescriptor#isContainer() container} and its - * {@linkplain TestDescriptor#getUniqueId() unique id} contains the - * simple names of the supplied {@link Class} and all of its - * {@linkplain Class#getEnclosingClass() enclosing classes}. - * - *

Please note that this method does not differentiate between static - * nested classes and non-static member classes (e.g., inner classes). - * - * @see #nestedContainer(Class, Condition) - */ - public static Condition nestedContainer(Class clazz) { - Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notNull(clazz.getEnclosingClass(), () -> clazz.getName() + " must be a nested class"); - - List classNames = new ArrayList<>(); - for (Class current = clazz; current != null; current = current.getEnclosingClass()) { - classNames.add(0, current.getSimpleName()); - } - - return allOf(container(), uniqueIdSubstrings(classNames)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#DYNAMIC_TEST_REGISTERED} and its - * {@linkplain TestDescriptor#getUniqueId() unique id} contains the - * supplied {@link String}. - */ - public static Condition dynamicTestRegistered(String uniqueIdSubstring) { - return dynamicTestRegistered(uniqueIdSubstring(uniqueIdSubstring)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#DYNAMIC_TEST_REGISTERED} and it matches the supplied - * {@code Condition}. - */ - public static Condition dynamicTestRegistered(Condition condition) { - return allOf(type(DYNAMIC_TEST_REGISTERED), condition); - } - - /** - * Create a new {@link Condition} that matches if and only if the - * {@linkplain TestDescriptor#getUniqueId() unique id} of an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} - * contains the supplied {@link String}. - */ - public static Condition uniqueIdSubstring(String uniqueIdSubstring) { - Predicate predicate = segment -> { - String text = segment.getType() + ":" + segment.getValue(); - return text.contains(uniqueIdSubstring); - }; - - return new Condition<>( - byTestDescriptor( - where(TestDescriptor::getUniqueId, uniqueId -> uniqueId.getSegments().stream().anyMatch(predicate))), - "descriptor with uniqueId substring '%s'", uniqueIdSubstring); - } - - /** - * Create a new {@link Condition} that matches if and only if the - * {@linkplain TestDescriptor#getUniqueId() unique id} of an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} - * contains all of the supplied strings. - * - * @since 1.6 - */ - public static Condition uniqueIdSubstrings(String... uniqueIdSubstrings) { - return uniqueIdSubstrings(Arrays.asList(uniqueIdSubstrings)); - } - - /** - * Create a new {@link Condition} that matches if and only if the - * {@linkplain TestDescriptor#getUniqueId() unique id} of an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} - * contains all of the supplied strings. - * - * @since 1.6 - */ - public static Condition uniqueIdSubstrings(List uniqueIdSubstrings) { - // The following worked with AssertJ 3.13.2 - // return allOf(uniqueIdSubstrings.stream().map(EventConditions::uniqueIdSubstring).collect(toList())); - - // Workaround for a regression in AssertJ 3.14.0 that loses the individual descriptions - // when multiple conditions are supplied as an Iterable instead of as an array. - // The underlying cause is that org.assertj.core.condition.Join.Join(Condition...) - // tracks all descriptions; whereas, - // org.assertj.core.condition.Join.Join(Iterable>) - // does not track all descriptions. - List> conditions = uniqueIdSubstrings.stream()// - .map(EventConditions::uniqueIdSubstring)// - .collect(toList()); - List descriptions = conditions.stream().map(Condition::description).collect(toList()); - return allOf(conditions).describedAs(new JoinDescription("all of :[", "]", descriptions)); - } - - /** - * Create a new {@link Condition} that matches if and only if the - * {@linkplain TestDescriptor#getDisplayName() display name} of an - * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} - * is equal to the supplied {@link String}. - */ - public static Condition displayName(String displayName) { - return new Condition<>(byTestDescriptor(where(TestDescriptor::getDisplayName, isEqual(displayName))), - "descriptor with display name '%s'", displayName); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#SKIPPED} and the - * {@linkplain Event#getPayload() reason} is equal to the supplied - * {@link String}. - * - * @see #reason(String) - */ - public static Condition skippedWithReason(String expectedReason) { - return allOf(type(SKIPPED), reason(expectedReason)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#SKIPPED} and the - * {@linkplain Event#getPayload() reason} matches the supplied - * {@link Predicate}. - * - * @see #reason(Predicate) - */ - public static Condition skippedWithReason(Predicate predicate) { - return allOf(type(SKIPPED), reason(predicate)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#STARTED}. - */ - public static Condition started() { - return type(STARTED); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#FINISHED} and its - * {@linkplain Event#getPayload() result} has a - * {@linkplain TestExecutionResult#getStatus() status} of - * {@link TestExecutionResult.Status#ABORTED ABORTED} as well as a - * {@linkplain TestExecutionResult#getThrowable() cause} that matches all of - * the supplied conditions. - */ - @SafeVarargs - public static Condition abortedWithReason(Condition... conditions) { - return finishedWithCause(ABORTED, conditions); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#FINISHED} and its - * {@linkplain Event#getPayload() result} has a - * {@linkplain TestExecutionResult#getStatus() status} of - * {@link TestExecutionResult.Status#FAILED FAILED} as well as a - * {@linkplain TestExecutionResult#getThrowable() cause} that matches all of - * the supplied {@code Conditions}. - */ - @SafeVarargs - public static Condition finishedWithFailure(Condition... conditions) { - return finishedWithCause(FAILED, conditions); - } - - @SuppressWarnings("unchecked") - private static Condition finishedWithCause(Status expectedStatus, Condition... conditions) { - List> list = Arrays.asList(TestExecutionResultConditions.status(expectedStatus), - TestExecutionResultConditions.throwable(conditions)); - - return finished(allOf(list)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#FINISHED} and its - * {@linkplain Event#getPayload() result} has a - * {@linkplain TestExecutionResult#getStatus() status} of - * {@link TestExecutionResult.Status#FAILED FAILED}. - */ - public static Condition finishedWithFailure() { - return finished(TestExecutionResultConditions.status(FAILED)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#FINISHED} and its - * {@linkplain Event#getPayload() result} has a - * {@linkplain TestExecutionResult#getStatus() status} of - * {@link TestExecutionResult.Status#SUCCESSFUL SUCCESSFUL}. - */ - public static Condition finishedSuccessfully() { - return finished(TestExecutionResultConditions.status(SUCCESSFUL)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is - * {@link EventType#FINISHED} and its - * {@linkplain Event#getPayload() payload} is an instance of - * {@link TestExecutionResult} that matches the supplied {@code Condition}. - */ - public static Condition finished(Condition resultCondition) { - return allOf(type(FINISHED), result(resultCondition)); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getType() type} is equal to the - * supplied {@link EventType}. - */ - public static Condition type(EventType expectedType) { - return new Condition<>(byType(expectedType), "type is %s", expectedType); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of - * {@link TestExecutionResult} that matches the supplied {@code Condition}. - */ - public static Condition result(Condition condition) { - return new Condition<>(byPayload(TestExecutionResult.class, condition::matches), "event with result where %s", - condition); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of - * {@link String} that is equal to the supplied value. - */ - public static Condition reason(String expectedReason) { - return new Condition<>(byPayload(String.class, isEqual(expectedReason)), "event with reason '%s'", - expectedReason); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of - * {@link String} that matches the supplied {@link Predicate}. - */ - public static Condition reason(Predicate predicate) { - return new Condition<>(byPayload(String.class, predicate), "event with custom reason predicate"); - } - - /** - * Create a new {@link Condition} that matches if and only if an - * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of - * {@link ReportEntry} that contains the supplied key-value pairs. - */ - @API(status = EXPERIMENTAL, since = "1.7") - public static Condition reportEntry(Map keyValuePairs) { - return new Condition<>(byPayload(ReportEntry.class, it -> it.getKeyValuePairs().equals(keyValuePairs)), - "event for report entry with key-value pairs %s", keyValuePairs); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java deleted file mode 100644 index fda7d1d8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.junit.platform.testkit.engine.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import org.apiguardian.api.API; -import org.junit.platform.testkit.engine.Assertions.Executable; - -/** - * {@code EventStatistics} provides a fluent API for asserting statistics - * for {@linkplain Event events}. - * - *

{@code EventStatistics} is used in conjunction with - * {@link Events#assertStatistics(java.util.function.Consumer)} as in the - * following example. - * - *

{@code events.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));} - * - * @since 1.4 - * @see Event - * @see Events - */ -@API(status = MAINTAINED, since = "1.7") -public class EventStatistics { - - private final List executables = new ArrayList<>(); - private final Events events; - - EventStatistics(Events events, String category) { - this.events = events; - } - - void assertAll() { - Assertions.assertAll(this.events.getCategory() + " Event Statistics", this.executables.stream()); - } - - // ------------------------------------------------------------------------- - - /** - * Specify the number of expected skipped events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics skipped(long expected) { - this.executables.add(() -> assertEquals(expected, this.events.skipped().count(), "skipped")); - return this; - } - - /** - * Specify the number of expected started events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics started(long expected) { - this.executables.add(() -> assertEquals(expected, this.events.started().count(), "started")); - return this; - } - - /** - * Specify the number of expected finished events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics finished(long expected) { - this.executables.add(() -> assertEquals(expected, this.events.finished().count(), "finished")); - return this; - } - - /** - * Specify the number of expected aborted events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics aborted(long expected) { - this.executables.add(() -> assertEquals(expected, this.events.aborted().count(), "aborted")); - return this; - } - - /** - * Specify the number of expected succeeded events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics succeeded(long expected) { - this.executables.add(() -> assertEquals(expected, this.events.succeeded().count(), "succeeded")); - return this; - } - - /** - * Specify the number of expected failed events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics failed(long expected) { - this.executables.add(() -> assertEquals(expected, this.events.failed().count(), "failed")); - return this; - } - - /** - * Specify the number of expected reporting entry publication events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics reportingEntryPublished(long expected) { - this.executables.add( - () -> assertEquals(expected, this.events.reportingEntryPublished().count(), "reporting entry published")); - return this; - } - - /** - * Specify the number of expected dynamic registration events. - * - * @param expected the expected number of events - * @return this {@code EventStatistics} for method chaining - */ - public EventStatistics dynamicallyRegistered(long expected) { - this.executables.add( - () -> assertEquals(expected, this.events.dynamicallyRegistered().count(), "dynamically registered")); - return this; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java deleted file mode 100644 index 1fed5b9b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * Enumeration of the different possible {@link Event} types. - * - * @since 1.4 - * @see Event - */ -@API(status = MAINTAINED, since = "1.7") -public enum EventType { - - /** - * Signals that a {@link TestDescriptor} has been dynamically registered. - * - * @see org.junit.platform.engine.EngineExecutionListener#dynamicTestRegistered(TestDescriptor) - */ - DYNAMIC_TEST_REGISTERED, - - /** - * Signals that the execution of a {@link TestDescriptor} has been skipped. - * - * @see org.junit.platform.engine.EngineExecutionListener#executionSkipped(TestDescriptor, String) - */ - SKIPPED, - - /** - * Signals that the execution of a {@link TestDescriptor} has started. - * - * @see org.junit.platform.engine.EngineExecutionListener#executionStarted(TestDescriptor) - */ - STARTED, - - /** - * Signals that the execution of a {@link TestDescriptor} has finished, - * regardless of the outcome. - * - * @see org.junit.platform.engine.EngineExecutionListener#executionFinished(TestDescriptor, TestExecutionResult) - */ - FINISHED, - - /** - * Signals that a {@link TestDescriptor} published a reporting entry. - * - * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished(TestDescriptor, ReportEntry) - */ - REPORTING_ENTRY_PUBLISHED; - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java deleted file mode 100644 index 01481ac3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static java.util.Collections.sort; -import static java.util.function.Predicate.isEqual; -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.testkit.engine.Event.byPayload; -import static org.junit.platform.testkit.engine.Event.byType; - -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.assertj.core.api.Assertions; -import org.assertj.core.api.Condition; -import org.assertj.core.api.ListAssert; -import org.assertj.core.api.SoftAssertions; -import org.assertj.core.data.Index; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestExecutionResult.Status; -import org.opentest4j.AssertionFailedError; - -/** - * {@code Events} is a facade that provides a fluent API for working with - * {@linkplain Event events}. - * - * @since 1.4 - */ -@API(status = MAINTAINED, since = "1.7") -public final class Events { - - private final List events; - private final String category; - - Events(Stream events, String category) { - this(Preconditions.notNull(events, "Event stream must not be null").collect(toList()), category); - } - - Events(List events, String category) { - Preconditions.notNull(events, "Event list must not be null"); - Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); - - this.events = Collections.unmodifiableList(events); - this.category = category; - } - - String getCategory() { - return this.category; - } - - // --- Accessors ----------------------------------------------------------- - - /** - * Get the {@linkplain Event events} as a {@link List}. - * - * @return the list of events; never {@code null} - * @see #stream() - */ - public List list() { - return this.events; - } - - /** - * Get the {@linkplain Event events} as a {@link Stream}. - * - * @return the stream of events; never {@code null} - * @see #list() - */ - public Stream stream() { - return this.events.stream(); - } - - /** - * Shortcut for {@code events.stream().map(mapper)}. - * - * @param mapper a {@code Function} to apply to each event; never {@code null} - * @return the mapped stream of events; never {@code null} - * @see #stream() - * @see Stream#map(Function) - */ - public Stream map(Function mapper) { - Preconditions.notNull(mapper, "Mapping function must not be null"); - return stream().map(mapper); - } - - /** - * Shortcut for {@code events.stream().filter(predicate)}. - * - * @param predicate a {@code Predicate} to apply to each event to decide if - * it should be included in the filtered stream; never {@code null} - * @return the filtered stream of events; never {@code null} - * @see #stream() - * @see Stream#filter(Predicate) - */ - public Stream filter(Predicate predicate) { - Preconditions.notNull(predicate, "Filter predicate must not be null"); - return stream().filter(predicate); - } - - /** - * Get the {@link Executions} for the current set of {@linkplain Event events}. - * - * @return an instance of {@code Executions} for the current set of events; - * never {@code null} - */ - public Executions executions() { - return new Executions(this.events, this.category); - } - - // --- Statistics ---------------------------------------------------------- - - /** - * Get the number of {@linkplain Event events} contained in this {@code Events} - * object. - */ - public long count() { - return this.events.size(); - } - - // --- Built-in Filters ---------------------------------------------------- - - /** - * Get the skipped {@link Events} contained in this {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events skipped() { - return new Events(eventsByType(EventType.SKIPPED), this.category + " Skipped"); - } - - /** - * Get the started {@link Events} contained in this {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events started() { - return new Events(eventsByType(EventType.STARTED), this.category + " Started"); - } - - /** - * Get the finished {@link Events} contained in this {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events finished() { - return new Events(eventsByType(EventType.FINISHED), this.category + " Finished"); - } - - /** - * Get the aborted {@link Events} contained in this {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events aborted() { - return new Events(finishedEventsByStatus(Status.ABORTED), this.category + " Aborted"); - } - - /** - * Get the succeeded {@link Events} contained in this {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events succeeded() { - return new Events(finishedEventsByStatus(Status.SUCCESSFUL), this.category + " Successful"); - } - - /** - * Get the failed {@link Events} contained in this {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events failed() { - return new Events(finishedEventsByStatus(Status.FAILED), this.category + " Failed"); - } - - /** - * Get the reporting entry publication {@link Events} contained in this - * {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events reportingEntryPublished() { - return new Events(eventsByType(EventType.REPORTING_ENTRY_PUBLISHED), - this.category + " Reporting Entry Published"); - } - - /** - * Get the dynamic registration {@link Events} contained in this - * {@code Events} object. - * - * @return the filtered {@code Events}; never {@code null} - */ - public Events dynamicallyRegistered() { - return new Events(eventsByType(EventType.DYNAMIC_TEST_REGISTERED), this.category + " Dynamically Registered"); - } - - // --- Assertions ---------------------------------------------------------- - - /** - * Assert statistics for the {@linkplain Event events} contained in this - * {@code Events} object. - * - *

Example

- * - *

{@code events.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));} - * - * @param statisticsConsumer a {@link Consumer} of {@link EventStatistics}; - * never {@code null} - * @return this {@code Events} object for method chaining; never {@code null} - */ - public Events assertStatistics(Consumer statisticsConsumer) { - Preconditions.notNull(statisticsConsumer, "Consumer must not be null"); - EventStatistics eventStatistics = new EventStatistics(this, this.category); - statisticsConsumer.accept(eventStatistics); - eventStatistics.assertAll(); - return this; - } - - /** - * Assert that all {@linkplain Event events} contained in this {@code Events} - * object exactly match the provided conditions. - * - *

Conditions can be imported statically from {@link EventConditions} - * and {@link TestExecutionResultConditions}. - * - *

Example

- * - *
-	 * executionResults.testEvents().assertEventsMatchExactly(
-	 *     event(test("exampleTestMethod"), started()),
-	 *     event(test("exampleTestMethod"), finishedSuccessfully())
-	 * );
-	 * 
- * - * @param conditions the conditions to match against; never {@code null} - * @see #assertEventsMatchLoosely(Condition...) - * @see #assertEventsMatchLooselyInOrder(Condition...) - * @see EventConditions - * @see TestExecutionResultConditions - */ - @SafeVarargs - public final void assertEventsMatchExactly(Condition... conditions) { - Preconditions.notNull(conditions, "conditions must not be null"); - assertEventsMatchExactly(this.events, conditions); - } - - /** - * Assert that all provided conditions are matched by an {@linkplain Event event} - * contained in this {@code Events} object, regardless of order. - * - *

Note that this method performs a partial match. Thus, some events may - * not match any of the provided conditions. - * - *

Conditions can be imported statically from {@link EventConditions} - * and {@link TestExecutionResultConditions}. - * - *

Example

- * - *
-	 * executionResults.testEvents().assertEventsMatchLoosely(
-	 *     event(test("exampleTestMethod"), started()),
-	 *     event(test("exampleTestMethod"), finishedSuccessfully())
-	 * );
-	 * 
- * - * @param conditions the conditions to match against; never {@code null} - * @since 1.7 - * @see #assertEventsMatchExactly(Condition...) - * @see #assertEventsMatchLooselyInOrder(Condition...) - * @see EventConditions - * @see TestExecutionResultConditions - */ - @SafeVarargs - @SuppressWarnings("varargs") - public final void assertEventsMatchLoosely(Condition... conditions) { - Preconditions.notNull(conditions, "conditions must not be null"); - Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements"); - assertEventsMatchLoosely(this.events, conditions); - } - - /** - * Assert that all provided conditions are matched by an {@linkplain Event event} - * contained in this {@code Events} object. - * - *

Note that this method performs a partial match. Thus, some events may - * not match any of the provided conditions; however, the conditions provided - * must be in the correct order. - * - *

Conditions can be imported statically from {@link EventConditions} - * and {@link TestExecutionResultConditions}. - * - *

Example

- * - *
-	 * executionResults.testEvents().assertEventsMatchLooselyInOrder(
-	 *     event(test("exampleTestMethod"), started()),
-	 *     event(test("exampleTestMethod"), finishedSuccessfully())
-	 * );
-	 * 
- * - * @param conditions the conditions to match against; never {@code null} - * @since 1.7 - * @see #assertEventsMatchExactly(Condition...) - * @see #assertEventsMatchLoosely(Condition...) - * @see EventConditions - * @see TestExecutionResultConditions - */ - @SafeVarargs - @SuppressWarnings("varargs") - public final void assertEventsMatchLooselyInOrder(Condition... conditions) { - Preconditions.notNull(conditions, "conditions must not be null"); - Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements"); - assertEventsMatchLooselyInOrder(this.events, conditions); - } - - /** - * Shortcut for {@code org.assertj.core.api.Assertions.assertThat(events.list())}. - * - * @return an instance of {@link ListAssert} for events; never {@code null} - * @see org.assertj.core.api.Assertions#assertThat(List) - * @see org.assertj.core.api.ListAssert - */ - public ListAssert assertThatEvents() { - return org.assertj.core.api.Assertions.assertThat(list()); - } - - // --- Diagnostics --------------------------------------------------------- - - /** - * Print all events to {@link System#out}. - * - * @return this {@code Events} object for method chaining; never {@code null} - */ - public Events debug() { - debug(System.out); - return this; - } - - /** - * Print all events to the supplied {@link OutputStream}. - * - * @param out the {@code OutputStream} to print to; never {@code null} - * @return this {@code Events} object for method chaining; never {@code null} - */ - public Events debug(OutputStream out) { - Preconditions.notNull(out, "OutputStream must not be null"); - debug(new PrintWriter(out, true)); - return this; - } - - /** - * Print all events to the supplied {@link Writer}. - * - * @param writer the {@code Writer} to print to; never {@code null} - * @return this {@code Events} object for method chaining; never {@code null} - */ - public Events debug(Writer writer) { - Preconditions.notNull(writer, "Writer must not be null"); - debug(new PrintWriter(writer, true)); - return this; - } - - private Events debug(PrintWriter printWriter) { - printWriter.println(this.category + " Events:"); - this.events.forEach(event -> printWriter.printf("\t%s%n", event)); - return this; - } - - // --- Internals ----------------------------------------------------------- - - private Stream eventsByType(EventType type) { - Preconditions.notNull(type, "EventType must not be null"); - return stream().filter(byType(type)); - } - - private Stream finishedEventsByStatus(Status status) { - Preconditions.notNull(status, "Status must not be null"); - return eventsByType(EventType.FINISHED)// - .filter(byPayload(TestExecutionResult.class, where(TestExecutionResult::getStatus, isEqual(status)))); - } - - @SafeVarargs - private static void assertEventsMatchExactly(List events, Condition... conditions) { - Assertions.assertThat(events).hasSize(conditions.length); - - SoftAssertions softly = new SoftAssertions(); - for (int i = 0; i < conditions.length; i++) { - softly.assertThat(events).has(conditions[i], Index.atIndex(i)); - } - softly.assertAll(); - } - - @SafeVarargs - private static void assertEventsMatchLoosely(List events, Condition... conditions) { - SoftAssertions softly = new SoftAssertions(); - for (Condition condition : conditions) { - checkCondition(events, softly, condition); - } - softly.assertAll(); - } - - @SafeVarargs - @SuppressWarnings("varargs") - private static void assertEventsMatchLooselyInOrder(List events, Condition... conditions) { - Assertions.assertThat(conditions).hasSizeLessThanOrEqualTo(events.size()); - SoftAssertions softly = new SoftAssertions(); - - // @formatter:off - List indices = Arrays.stream(conditions) - .map(condition -> findEvent(events, softly, condition)) - .filter(Objects::nonNull) - .map(events::indexOf) - .collect(toList()); - // @formatter:on - - if (isNotInIncreasingOrder(indices)) { - throw new AssertionFailedError("Conditions are not in the correct order."); - } - - softly.assertAll(); - } - - private static boolean isNotInIncreasingOrder(List indices) { - List copy = new ArrayList<>(indices); - sort(copy); - - return !indices.equals(copy); - } - - private static void checkCondition(List events, SoftAssertions softly, Condition condition) { - if (events.stream().noneMatch(condition::matches)) { - softly.fail("Condition did not match any event: " + condition); - } - } - - private static Event findEvent(List events, SoftAssertions softly, Condition condition) { - // @formatter:off - Optional matchedEvent = events.stream() - .filter(condition::matches) - .findFirst(); - // @formatter:on - - if (!matchedEvent.isPresent()) { - softly.fail("Condition did not match any event: " + condition); - } - - return matchedEvent.orElse(null); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java deleted file mode 100644 index be14554e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.time.Duration; -import java.time.Instant; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; - -/** - * {@code Execution} encapsulates metadata for the execution of a single - * {@link TestDescriptor}. - * - * @since 1.4 - */ -@API(status = MAINTAINED, since = "1.7") -public class Execution { - - // --- Factories ----------------------------------------------------------- - - /** - * Create a new instance of an {@code Execution} that finished with the - * provided {@link TestExecutionResult}. - * - * @param testDescriptor the {@code TestDescriptor} that finished; - * never {@code null} - * @param startInstant the {@code Instant} that the {@code Execution} started; - * never {@code null} - * @param endInstant the {@code Instant} that the {@code Execution} completed; - * never {@code null} - * @param executionResult the {@code TestExecutionResult} of the finished - * {@code TestDescriptor}; never {@code null} - * @return the newly created {@code Execution} instance; never {@code null} - */ - public static Execution finished(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, - TestExecutionResult executionResult) { - - return new Execution(testDescriptor, startInstant, endInstant, TerminationInfo.executed(executionResult)); - } - - /** - * Create a new instance of an {@code Execution} that was skipped with the - * provided {@code skipReason}. - * - * @param testDescriptor the {@code TestDescriptor} that finished; - * never {@code null} - * @param startInstant the {@code Instant} that the {@code Execution} started; - * never {@code null} - * @param endInstant the {@code Instant} that the {@code Execution} completed; - * never {@code null} - * @param skipReason the reason the {@code TestDescriptor} was skipped; - * may be {@code null} - * @return the newly created {@code Execution} instance; never {@code null} - */ - public static Execution skipped(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, - String skipReason) { - - return new Execution(testDescriptor, startInstant, endInstant, TerminationInfo.skipped(skipReason)); - } - - // ------------------------------------------------------------------------- - - private final TestDescriptor testDescriptor; - private final Instant startInstant; - private final Instant endInstant; - private final Duration duration; - private final TerminationInfo terminationInfo; - - private Execution(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, - TerminationInfo terminationInfo) { - - Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); - Preconditions.notNull(startInstant, "Start Instant must not be null"); - Preconditions.notNull(endInstant, "End Instant must not be null"); - Preconditions.notNull(terminationInfo, "TerminationInfo must not be null"); - - this.testDescriptor = testDescriptor; - this.startInstant = startInstant; - this.endInstant = endInstant; - this.duration = Duration.between(startInstant, endInstant); - this.terminationInfo = terminationInfo; - } - - /** - * Get the {@link TestDescriptor} for this {@code Execution}. - * - * @return the {@code TestDescriptor} for this {@code Execution} - */ - public TestDescriptor getTestDescriptor() { - return this.testDescriptor; - } - - /** - * Get the start {@link Instant} of this {@code Execution}. - * - * @return the start {@code Instant} of this {@code Execution} - */ - public Instant getStartInstant() { - return this.startInstant; - } - - /** - * Get the end {@link Instant} of this {@code Execution}. - * - * @return the end {@code Instant} of this {@code Execution} - */ - public Instant getEndInstant() { - return this.endInstant; - } - - /** - * Get the {@link Duration} of this {@code Execution}. - * - * @return the {@code Duration} of this {@code Execution} - */ - public Duration getDuration() { - return this.duration; - } - - /** - * Get the {@link TerminationInfo} for this {@code Execution}. - * - * @return the {@code TerminationInfo} for this {@code Execution} - */ - public TerminationInfo getTerminationInfo() { - return this.terminationInfo; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("testDescriptor", this.testDescriptor) - .append("startInstant", this.startInstant) - .append("endInstant", this.endInstant) - .append("duration", this.duration) - .append("terminationInfo", this.terminationInfo) - .toString(); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java deleted file mode 100644 index b11a1e44..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.apiguardian.api.API; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.reporting.ReportEntry; - -/** - * {@code ExecutionRecorder} is an {@link EngineExecutionListener} that records - * data from every event that occurs during the engine execution lifecycle and - * provides functionality for retrieving execution state via - * {@link EngineExecutionResults}. - * - * @since 1.4 - * @see EngineExecutionResults - * @see Event - * @see Execution - */ -@API(status = MAINTAINED, since = "1.7") -public class ExecutionRecorder implements EngineExecutionListener { - - private final List events = new CopyOnWriteArrayList<>(); - - /** - * Record an {@link Event} for a dynamically registered container - * or test. - */ - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - this.events.add(Event.dynamicTestRegistered(testDescriptor)); - } - - /** - * Record an {@link Event} for a container or test that was skipped. - */ - @Override - public void executionSkipped(TestDescriptor testDescriptor, String reason) { - this.events.add(Event.executionSkipped(testDescriptor, reason)); - } - - /** - * Record an {@link Event} for a container or test that started. - */ - @Override - public void executionStarted(TestDescriptor testDescriptor) { - this.events.add(Event.executionStarted(testDescriptor)); - } - - /** - * Record an {@link Event} for a container or test that completed - * with the provided {@link TestExecutionResult}. - */ - @Override - public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { - this.events.add(Event.executionFinished(testDescriptor, testExecutionResult)); - } - - /** - * Record an {@link Event} for a published {@link ReportEntry}. - */ - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - this.events.add(Event.reportingEntryPublished(testDescriptor, entry)); - } - - /** - * Get the state of the engine's execution in the form of {@link EngineExecutionResults}. - * - * @return the {@code EngineExecutionResults} containing all current state information - */ - public EngineExecutionResults getExecutionResults() { - return new EngineExecutionResults(this.events); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java deleted file mode 100644 index 6ef7a89e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.MAINTAINED; - -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.Writer; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.assertj.core.api.ListAssert; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestExecutionResult.Status; - -/** - * {@code Executions} is a facade that provides a fluent API for working with - * {@linkplain Execution executions}. - * - * @since 1.4 - */ -@API(status = MAINTAINED, since = "1.7") -public final class Executions { - - private final List executions; - private final String category; - - private Executions(Stream executions, String category) { - Preconditions.notNull(executions, "Execution stream must not be null"); - - this.executions = Collections.unmodifiableList(executions.collect(toList())); - this.category = category; - } - - Executions(List events, String category) { - Preconditions.notNull(events, "Event list must not be null"); - Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); - - this.executions = Collections.unmodifiableList(createExecutions(events)); - this.category = category; - } - - // --- Accessors ----------------------------------------------------------- - - /** - * Get the {@linkplain Execution executions} as a {@link List}. - * - * @return the list of executions; never {@code null} - * @see #stream() - */ - public List list() { - return this.executions; - } - - /** - * Get the {@linkplain Execution executions} as a {@link Stream}. - * - * @return the stream of executions; never {@code null} - * @see #list() - */ - public Stream stream() { - return this.executions.stream(); - } - - /** - * Shortcut for {@code executions.stream().map(mapper)}. - * - * @param mapper a {@code Function} to apply to each execution; - * never {@code null} - * @return the mapped stream of executions; never {@code null} - * @see #stream() - * @see Stream#map(Function) - */ - public Stream map(Function mapper) { - Preconditions.notNull(mapper, "Mapping function must not be null"); - return stream().map(mapper); - } - - /** - * Shortcut for {@code executions.stream().filter(predicate)}. - * - * @param predicate a {@code Predicate} to apply to each execution to decide - * if it should be included in the filtered stream; never {@code null} - * @return the filtered stream of executions; never {@code null} - * @see #stream() - * @see Stream#filter(Predicate) - */ - public Stream filter(Predicate predicate) { - Preconditions.notNull(predicate, "Filter predicate must not be null"); - return stream().filter(predicate); - } - - // --- Statistics ---------------------------------------------------------- - - /** - * Get the number of {@linkplain Execution executions} contained in this - * {@code Executions} object. - */ - public long count() { - return this.executions.size(); - } - - // --- Built-in Filters ---------------------------------------------------- - - /** - * Get the skipped {@link Executions} contained in this {@code Executions} object. - * - * @return the filtered {@code Executions}; never {@code null} - */ - public Executions skipped() { - return new Executions(executionsByTerminationInfo(TerminationInfo::skipped), this.category + " Skipped"); - } - - /** - * Get the started {@link Executions} contained in this {@code Executions} object. - * - * @return the filtered {@code Executions}; never {@code null} - */ - public Executions started() { - return new Executions(executionsByTerminationInfo(TerminationInfo::notSkipped), this.category + " Started"); - } - - /** - * Get the finished {@link Executions} contained in this {@code Executions} object. - * - * @return the filtered {@code Executions}; never {@code null} - */ - public Executions finished() { - return new Executions(finishedExecutions(), this.category + " Finished"); - } - - /** - * Get the aborted {@link Executions} contained in this {@code Executions} object. - * - * @return the filtered {@code Executions}; never {@code null} - */ - public Executions aborted() { - return new Executions(finishedExecutionsByStatus(Status.ABORTED), this.category + " Aborted"); - } - - /** - * Get the succeeded {@link Executions} contained in this {@code Executions} object. - * - * @return the filtered {@code Executions}; never {@code null} - */ - public Executions succeeded() { - return new Executions(finishedExecutionsByStatus(Status.SUCCESSFUL), this.category + " Successful"); - } - - /** - * Get the failed {@link Executions} contained in this {@code Executions} object. - * - * @return the filtered {@code Executions}; never {@code null} - */ - public Executions failed() { - return new Executions(finishedExecutionsByStatus(Status.FAILED), this.category + " Failed"); - } - - // --- Assertions ---------------------------------------------------------- - - /** - * Shortcut for {@code org.assertj.core.api.Assertions.assertThat(executions.list())}. - * - * @return an instance of {@link ListAssert} for executions; never {@code null} - * @see org.assertj.core.api.Assertions#assertThat(List) - * @see org.assertj.core.api.ListAssert - */ - public ListAssert assertThatExecutions() { - return org.assertj.core.api.Assertions.assertThat(list()); - } - - // --- Diagnostics --------------------------------------------------------- - - /** - * Print all executions to {@link System#out}. - * - * @return this {@code Executions} object for method chaining; never {@code null} - */ - public Executions debug() { - debug(System.out); - return this; - } - - /** - * Print all executions to the supplied {@link OutputStream}. - * - * @param out the {@code OutputStream} to print to; never {@code null} - * @return this {@code Executions} object for method chaining; never {@code null} - */ - public Executions debug(OutputStream out) { - Preconditions.notNull(out, "OutputStream must not be null"); - debug(new PrintWriter(out, true)); - return this; - } - - /** - * Print all executions to the supplied {@link Writer}. - * - * @param writer the {@code Writer} to print to; never {@code null} - * @return this {@code Executions} object for method chaining; never {@code null} - */ - public Executions debug(Writer writer) { - Preconditions.notNull(writer, "Writer must not be null"); - debug(new PrintWriter(writer, true)); - return this; - } - - private Executions debug(PrintWriter printWriter) { - printWriter.println(this.category + " Executions:"); - this.executions.forEach(execution -> printWriter.printf("\t%s%n", execution)); - return this; - } - - // --- Internals ----------------------------------------------------------- - - private Stream finishedExecutions() { - return executionsByTerminationInfo(TerminationInfo::executed); - } - - private Stream finishedExecutionsByStatus(Status status) { - Preconditions.notNull(status, "Status must not be null"); - return finishedExecutions()// - .filter(execution -> execution.getTerminationInfo().getExecutionResult().getStatus() == status); - } - - private Stream executionsByTerminationInfo(Predicate predicate) { - return filter(execution -> predicate.test(execution.getTerminationInfo())); - } - - /** - * Create executions from the supplied list of events. - */ - private static List createExecutions(List events) { - List executions = new ArrayList<>(); - Map executionStarts = new HashMap<>(); - - for (Event event : events) { - switch (event.getType()) { - case STARTED: { - executionStarts.put(event.getTestDescriptor(), event.getTimestamp()); - break; - } - case SKIPPED: { - // Based on the Javadoc for EngineExecutionListener.executionSkipped(...), - // a skipped descriptor must never be reported as started or finished, - // but just in case a TestEngine does not adhere to that contract, we - // make an attempt to remove any tracked "started" event. - executionStarts.remove(event.getTestDescriptor()); - - // Use the same timestamp for both the start and end Instant values. - Instant timestamp = event.getTimestamp(); - - Execution skippedEvent = Execution.skipped(event.getTestDescriptor(), timestamp, timestamp, - event.getRequiredPayload(String.class)); - executions.add(skippedEvent); - break; - } - case FINISHED: { - Instant startInstant = executionStarts.remove(event.getTestDescriptor()); - Instant endInstant = event.getTimestamp(); - - // If "started" events have already been filtered out, it is no - // longer possible to create a legitimate Execution for the current - // descriptor. So we effectively ignore it in that case. - if (startInstant != null) { - Execution finishedEvent = Execution.finished(event.getTestDescriptor(), startInstant, - endInstant, event.getRequiredPayload(TestExecutionResult.class)); - executions.add(finishedEvent); - } - break; - } - default: { - // Ignore other events - break; - } - } - } - - return executions; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java deleted file mode 100644 index e3875c8c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.apiguardian.api.API.Status.MAINTAINED; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ToStringBuilder; -import org.junit.platform.engine.TestExecutionResult; - -/** - * {@code TerminationInfo} is a union type that allows propagation of terminated - * container/test state, supporting either the reason if the container/test - * was skipped or the {@link TestExecutionResult} if the container/test was executed. - * - * @since 1.4 - * @see Execution#getTerminationInfo() - */ -@API(status = MAINTAINED, since = "1.7") -public class TerminationInfo { - - // --- Factories ----------------------------------------------------------- - - /** - * Create a skipped {@code TerminationInfo} instance for the - * supplied reason. - * - * @param reason the reason the execution was skipped; may be {@code null} - * @return the created {@code TerminationInfo}; never {@code null} - * @see #executed(TestExecutionResult) - */ - public static TerminationInfo skipped(String reason) { - return new TerminationInfo(true, reason, null); - } - - /** - * Create an executed {@code TerminationInfo} instance for the - * supplied {@link TestExecutionResult}. - * - * @param testExecutionResult the result of the execution; never {@code null} - * @return the created {@code TerminationInfo}; never {@code null} - * @see #skipped(String) - */ - public static TerminationInfo executed(TestExecutionResult testExecutionResult) { - Preconditions.notNull(testExecutionResult, "TestExecutionResult must not be null"); - return new TerminationInfo(false, null, testExecutionResult); - } - - // ------------------------------------------------------------------------- - - private final boolean skipped; - private final String skipReason; - private final TestExecutionResult testExecutionResult; - - private TerminationInfo(boolean skipped, String skipReason, TestExecutionResult testExecutionResult) { - boolean executed = (testExecutionResult != null); - Preconditions.condition((skipped ^ executed), - "TerminationInfo must represent either a skipped execution or a TestExecutionResult but not both"); - - this.skipped = skipped; - this.skipReason = skipReason; - this.testExecutionResult = testExecutionResult; - } - - /** - * Determine if this {@code TerminationInfo} represents a skipped execution. - * - * @return {@code true} if this this {@code TerminationInfo} represents a - * skipped execution - */ - public boolean skipped() { - return this.skipped; - } - - /** - * Determine if this {@code TerminationInfo} does not represent a skipped - * execution. - * - * @return {@code true} if this this {@code TerminationInfo} does not - * represent a skipped execution - */ - public boolean notSkipped() { - return !skipped(); - } - - /** - * Determine if this {@code TerminationInfo} represents a completed execution. - * - * @return {@code true} if this this {@code TerminationInfo} represents a - * completed execution - */ - public boolean executed() { - return (this.testExecutionResult != null); - } - - /** - * Get the reason the execution was skipped. - * - * @return the reason the execution was skipped - * @throws UnsupportedOperationException if this {@code TerminationInfo} - * does not represent a skipped execution - */ - public String getSkipReason() throws UnsupportedOperationException { - if (skipped()) { - return this.skipReason; - } - // else - throw new UnsupportedOperationException("No skip reason contained in this TerminationInfo"); - } - - /** - * Get the {@link TestExecutionResult} for the completed execution. - * - * @return the result of the completed execution - * @throws UnsupportedOperationException if this {@code TerminationInfo} - * does not represent a completed execution - */ - public TestExecutionResult getExecutionResult() throws UnsupportedOperationException { - if (executed()) { - return this.testExecutionResult; - } - // else - throw new UnsupportedOperationException("No TestExecutionResult contained in this TerminationInfo"); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder(this); - if (skipped()) { - builder.append("skipped", true).append("reason", this.skipReason); - } - else { - builder.append("executed", true).append("result", this.testExecutionResult); - } - return builder.toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java deleted file mode 100644 index 21ff73e7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static java.util.function.Predicate.isEqual; -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.junit.platform.commons.util.FunctionUtils.where; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import org.apiguardian.api.API; -import org.assertj.core.api.Assertions; -import org.assertj.core.api.Condition; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestExecutionResult.Status; - -/** - * Collection of AssertJ {@linkplain Condition conditions} for - * {@link TestExecutionResult}. - * - * @since 1.4 - * @see EventConditions - */ -@API(status = MAINTAINED, since = "1.7") -public final class TestExecutionResultConditions { - - private TestExecutionResultConditions() { - /* no-op */ - } - - /** - * Create a new {@link Condition} that matches if and only if a - * {@link TestExecutionResult}'s {@linkplain TestExecutionResult#getStatus() - * status} is equal to the supplied {@link Status Status}. - */ - public static Condition status(Status expectedStatus) { - return new Condition<>(where(TestExecutionResult::getStatus, isEqual(expectedStatus)), "status is %s", - expectedStatus); - } - - /** - * Create a new {@link Condition} that matches if and only if a - * {@link TestExecutionResult}'s - * {@linkplain TestExecutionResult#getThrowable() throwable} matches all - * supplied conditions. - */ - @SafeVarargs - @SuppressWarnings("varargs") - public static Condition throwable(Condition... conditions) { - List> list = Arrays.stream(conditions)// - .map(TestExecutionResultConditions::throwable)// - .collect(toList()); - - return Assertions.allOf(list); - } - - /** - * Create a new {@link Condition} that matches if and only if a - * {@link Throwable}'s {@linkplain Throwable#getCause() cause} matches all - * supplied conditions. - */ - @SafeVarargs - @SuppressWarnings("varargs") - public static Condition cause(Condition... conditions) { - List> list = Arrays.stream(conditions)// - .map(TestExecutionResultConditions::cause)// - .collect(toList()); - - return Assertions.allOf(list); - } - - /** - * Create a new {@link Condition} that matches if and only if a - * {@link Throwable}'s {@linkplain Throwable#getSuppressed() suppressed - * throwable} at the supplied index matches all supplied conditions. - */ - @SafeVarargs - @SuppressWarnings("varargs") - public static Condition suppressed(int index, Condition... conditions) { - List> list = Arrays.stream(conditions)// - .map(condition -> suppressed(index, condition))// - .collect(toList()); - - return Assertions.allOf(list); - } - - /** - * Create a new {@link Condition} that matches if and only if a - * {@link Throwable} is an {@linkplain Class#isInstance(Object) instance of} - * the supplied {@link Class}. - */ - public static Condition instanceOf(Class expectedType) { - return new Condition<>(expectedType::isInstance, "instance of %s", expectedType.getName()); - } - - /** - * Create a new {@link Condition} that matches if and only if a - * {@link Throwable}'s {@linkplain Throwable#getMessage() message} is equal - * to the supplied {@link String}. - */ - public static Condition message(String expectedMessage) { - return new Condition<>(where(Throwable::getMessage, isEqual(expectedMessage)), "message is '%s'", - expectedMessage); - } - - /** - * Create a new {@link Condition} that matches if and only if a - * {@link Throwable}'s {@linkplain Throwable#getMessage() message} matches - * the supplied {@link Predicate}. - */ - public static Condition message(Predicate expectedMessagePredicate) { - return new Condition<>(where(Throwable::getMessage, expectedMessagePredicate), "message matches predicate"); - } - - private static Condition throwable(Condition condition) { - return new Condition<>( - where(TestExecutionResult::getThrowable, - throwable -> throwable.isPresent() && condition.matches(throwable.get())), - "throwable matches %s", condition); - } - - private static Condition cause(Condition condition) { - return new Condition<>(throwable -> condition.matches(throwable.getCause()), "throwable cause matches %s", - condition); - } - - private static Condition suppressed(int index, Condition condition) { - return new Condition<>( - throwable -> throwable.getSuppressed().length > index - && condition.matches(throwable.getSuppressed()[index]), - "suppressed throwable at index %d matches %s", index, condition); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java deleted file mode 100644 index c50fec3e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Test Kit for testing the execution of a {@link org.junit.platform.engine.TestEngine} - * running on the JUnit Platform. - */ - -package org.junit.platform.testkit.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java deleted file mode 100644 index 7a10b625..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Test Kit for the JUnit Platform. - */ - -package org.junit.platform.testkit; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java deleted file mode 100644 index c0ae3181..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Defines the Test Kit API for the JUnit Platform. - * - * @since 1.4 - * @uses org.junit.platform.engine.TestEngine - */ -module org.junit.platform.testkit { - requires static transitive org.apiguardian.api; - requires transitive org.assertj.core; - requires org.junit.platform.commons; - requires transitive org.junit.platform.engine; - requires org.junit.platform.launcher; - requires transitive org.opentest4j; - - // exports org.junit.platform.testkit; empty package - exports org.junit.platform.testkit.engine; - - uses org.junit.platform.engine.TestEngine; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts deleted file mode 100644 index b92fffa5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/junit-vintage-engine.gradle.kts +++ /dev/null @@ -1,85 +0,0 @@ -plugins { - `java-library-conventions` - `junit4-compatibility` - `testing-conventions` - `java-test-fixtures` - groovy -} - -description = "JUnit Vintage Engine" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformEngine) - api(libs.junit4) - - compileOnlyApi(libs.apiguardian) - - testFixturesApi(platform(libs.groovy2.bom)) - testFixturesApi(libs.spock1) - testFixturesImplementation(projects.junitPlatformRunner) - - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteEngine) - testImplementation(projects.junitPlatformTestkit) - - osgiVerification(projects.junitPlatformLauncher) -} - -tasks { - compileTestFixturesGroovy { - javaLauncher.set(project.javaToolchains.launcherFor { - // Groovy 2.x (used for Spock tests) does not support current JDKs - languageVersion.set(JavaLanguageVersion.of(8)) - }) - } - jar { - bundle { - val junit4Min = libs.versions.junit4Min.get() - bnd(""" - # Import JUnit4 packages with a version - Import-Package: \ - ${extra["importAPIGuardian"]},\ - junit.runner;version="[${junit4Min},5)",\ - org.junit;version="[${junit4Min},5)",\ - org.junit.experimental.categories;version="[${junit4Min},5)",\ - org.junit.internal.builders;version="[${junit4Min},5)",\ - org.junit.platform.commons.logging;status=INTERNAL,\ - org.junit.runner.*;version="[${junit4Min},5)",\ - org.junit.runners.model;version="[${junit4Min},5)",\ - * - - Provide-Capability:\ - org.junit.platform.engine;\ - org.junit.platform.engine='junit-vintage';\ - version:Version="${'$'}{version_cleanup;${project.version}}" - Require-Capability:\ - org.junit.platform.launcher;\ - filter:='(&(org.junit.platform.launcher=junit-platform-launcher)(version>=${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${rootProject.property("platformVersion")!!}}})))';\ - effective:=active - """) - } - } - val testWithoutJUnit4 by registering(Test::class) { - (options as JUnitPlatformOptions).apply { - includeTags("missing-junit4") - } - setIncludes(listOf("**/JUnit4VersionCheckTests.class")) - classpath = classpath.filter { - !it.name.startsWith("junit-4") - } - } - withType().matching { it.name != testWithoutJUnit4.name }.configureEach { - (options as JUnitPlatformOptions).apply { - excludeTags("missing-junit4") - } - } - withType().configureEach { - // Workaround for Groovy 2.5 - jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") - jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED") - } - check { - dependsOn(testWithoutJUnit4) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java deleted file mode 100644 index 8c5f1f27..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; - -import java.math.BigDecimal; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import junit.runner.Version; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.UnrecoverableExceptions; - -/** - * @since 5.4 - */ -class JUnit4VersionCheck { - - private static final Pattern versionPattern = Pattern.compile("^(\\d+\\.\\d+).*"); - private static final BigDecimal minVersion = new BigDecimal("4.12"); - - static void checkSupported() { - try { - checkSupported(Version::id); - } - catch (NoClassDefFoundError e) { - throw new JUnitException( - "Invalid class/module path: junit-vintage-engine is present but junit:junit is not. " - + "Please either remove junit-vintage-engine or add junit:junit, or " - + "alternatively use an excludeEngines(\"" + ENGINE_ID + "\") filter."); - } - } - - static void checkSupported(Supplier versionSupplier) { - String versionString = readVersion(versionSupplier); - BigDecimal version = parseVersion(versionString); - if (version.compareTo(minVersion) < 0) { - throw new JUnitException("Unsupported version of junit:junit: " + versionString - + ". Please upgrade to version " + minVersion + " or later."); - } - } - - static BigDecimal parseVersion(String versionString) { - try { - Matcher matcher = versionPattern.matcher(versionString); - if (matcher.matches()) { - return new BigDecimal(matcher.group(1)); - } - } - catch (Exception e) { - throw new JUnitException("Failed to parse version of junit:junit: " + versionString, e); - } - throw new JUnitException("Failed to parse version of junit:junit: " + versionString); - } - - private static String readVersion(Supplier versionSupplier) { - try { - return versionSupplier.get(); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - throw new JUnitException("Failed to read version of junit:junit", t); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java deleted file mode 100644 index fb52f79b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; - -import java.util.Iterator; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.VintageEngineDescriptor; -import org.junit.vintage.engine.discovery.VintageDiscoverer; -import org.junit.vintage.engine.execution.RunnerExecutor; - -/** - * The JUnit Vintage {@link TestEngine}. - * - * @since 4.12 - */ -@API(status = INTERNAL, since = "4.12") -public final class VintageTestEngine implements TestEngine { - - @Override - public String getId() { - return ENGINE_ID; - } - - /** - * Returns {@code org.junit.vintage} as the group ID. - */ - @Override - public Optional getGroupId() { - return Optional.of("org.junit.vintage"); - } - - /** - * Returns {@code junit-vintage-engine} as the artifact ID. - */ - @Override - public Optional getArtifactId() { - return Optional.of("junit-vintage-engine"); - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - JUnit4VersionCheck.checkSupported(); - return new VintageDiscoverer().discover(discoveryRequest, uniqueId); - } - - @Override - public void execute(ExecutionRequest request) { - EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); - VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); - engineExecutionListener.executionStarted(engineDescriptor); - executeAllChildren(engineDescriptor, engineExecutionListener); - engineExecutionListener.executionFinished(engineDescriptor, successful()); - } - - private void executeAllChildren(VintageEngineDescriptor engineDescriptor, - EngineExecutionListener engineExecutionListener) { - RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener); - for (Iterator iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) { - runnerExecutor.execute((RunnerTestDescriptor) iterator.next()); - iterator.remove(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java deleted file mode 100644 index 3cc89c7e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import org.apiguardian.api.API; -import org.junit.runner.Description; - -@API(status = API.Status.INTERNAL, since = "5.8") -public class DescriptionUtils { - - private DescriptionUtils() { - } - - public static String getMethodName(Description description) { - String displayName = description.getDisplayName(); - int i = displayName.indexOf('('); - if (i >= 0) { - int j = displayName.lastIndexOf('('); - if (i == j) { - char lastChar = displayName.charAt(displayName.length() - 1); - if (lastChar == ')') { - return displayName.substring(0, i); - } - } - } - return description.getMethodName(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java deleted file mode 100644 index 5bf9bf71..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static java.util.stream.Collectors.joining; - -import java.util.Collection; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.runner.Description; -import org.junit.runner.manipulation.Filter; - -/** - * @since 5.4 - */ -class OrFilter extends Filter { - - private final Collection filters; - - OrFilter(Collection filters) { - this.filters = Preconditions.notEmpty(filters, "filters must not be empty"); - } - - @Override - public boolean shouldRun(Description description) { - return filters.stream().anyMatch(filter -> filter.shouldRun(description)); - } - - @Override - public String describe() { - return filters.stream().map(Filter::describe).collect(joining(" OR ")); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java deleted file mode 100644 index d81b64f1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.runner.Runner; - -@API(status = INTERNAL, since = "5.4") -public interface RunnerDecorator { - - Runner getDecoratedRunner(); - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java deleted file mode 100644 index bb9037c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import org.junit.runner.Request; -import org.junit.runner.Runner; - -/** - * @since 4.12 - */ -class RunnerRequest extends Request { - - private final Runner runner; - - RunnerRequest(Runner runner) { - this.runner = runner; - } - - @Override - public Runner getRunner() { - return runner; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java deleted file mode 100644 index 0ad3c5f9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static java.util.Collections.singletonList; -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; - -import org.apiguardian.api.API; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.runner.Description; -import org.junit.runner.Request; -import org.junit.runner.Runner; -import org.junit.runner.manipulation.Filter; -import org.junit.runner.manipulation.Filterable; -import org.junit.runner.manipulation.NoTestsRemainException; - -/** - * @since 4.12 - */ -@API(status = INTERNAL, since = "4.12") -public class RunnerTestDescriptor extends VintageTestDescriptor { - - private static final Logger logger = LoggerFactory.getLogger(RunnerTestDescriptor.class); - - private final Set rejectedExclusions = new HashSet<>(); - private Runner runner; - private boolean wasFiltered; - private List filters = new ArrayList<>(); - - public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner) { - super(uniqueId, runner.getDescription(), testClass.getSimpleName(), ClassSource.from(testClass)); - this.runner = runner; - } - - @Override - public String getLegacyReportingName() { - return getSource().map(source -> ((ClassSource) source).getClassName()) // - .orElseThrow(() -> new JUnitException("source should have been present")); - } - - public Request toRequest() { - return new RunnerRequest(this.runner); - } - - @Override - protected boolean tryToExcludeFromRunner(Description description) { - boolean excluded = tryToFilterRunner(description); - if (excluded) { - wasFiltered = true; - } - else { - rejectedExclusions.add(description); - } - return excluded; - } - - private boolean tryToFilterRunner(Description description) { - if (runner instanceof Filterable) { - ExcludeDescriptionFilter filter = new ExcludeDescriptionFilter(description); - try { - ((Filterable) runner).filter(filter); - } - catch (NoTestsRemainException ignore) { - // it's safe to ignore this exception because childless TestDescriptors will get pruned - } - return filter.wasSuccessful(); - } - return false; - } - - @Override - protected boolean canBeRemovedFromHierarchy() { - return true; - } - - @Override - public void prune() { - if (wasFiltered) { - // filtering the runner may render intermediate Descriptions obsolete - // (e.g. test classes without any remaining children in a suite) - pruneDescriptorsForObsoleteDescriptions(singletonList(runner.getDescription())); - } - if (rejectedExclusions.isEmpty()) { - super.prune(); - } - else if (rejectedExclusions.containsAll(getDescription().getChildren())) { - // since the Runner was asked to remove all of its direct children, - // it's safe to remove it entirely - removeFromHierarchy(); - } - else { - logIncompleteFiltering(); - } - } - - private void logIncompleteFiltering() { - if (runner instanceof Filterable) { - logger.warn(() -> "Runner " + getRunnerToReport().getClass().getName() // - + " (used on class " + getLegacyReportingName() + ") was not able to satisfy all filter requests."); - } - else { - warnAboutUnfilterableRunner(); - } - } - - private void warnAboutUnfilterableRunner() { - logger.warn(() -> "Runner " + getRunnerToReport().getClass().getName() // - + " (used on class " + getLegacyReportingName() + ") does not support filtering" // - + " and will therefore be run completely."); - } - - public Optional> getFilters() { - return Optional.ofNullable(filters); - } - - public void clearFilters() { - this.filters = null; - } - - public void applyFilters(Consumer childrenCreator) { - if (filters != null && !filters.isEmpty()) { - if (runner instanceof Filterable) { - this.runner = toRequest().filterWith(new OrFilter(filters)).getRunner(); - this.description = runner.getDescription(); - this.children.clear(); - childrenCreator.accept(this); - } - else { - warnAboutUnfilterableRunner(); - } - } - clearFilters(); - } - - private Runner getRunnerToReport() { - return (runner instanceof RunnerDecorator) ? ((RunnerDecorator) runner).getDecoratedRunner() : runner; - } - - private static class ExcludeDescriptionFilter extends Filter { - - private final Description description; - private boolean successful; - - ExcludeDescriptionFilter(Description description) { - this.description = description; - } - - @Override - public boolean shouldRun(Description description) { - if (this.description.equals(description)) { - successful = true; - return false; - } - return true; - } - - @Override - public String describe() { - return "exclude " + description; - } - - boolean wasSuccessful() { - return successful; - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java deleted file mode 100644 index e5b327a2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static java.util.Collections.synchronizedMap; -import static java.util.function.Predicate.isEqual; -import static java.util.stream.Collectors.toList; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apiguardian.api.API; -import org.junit.platform.commons.support.ModifierSupport; -import org.junit.platform.commons.util.LruCache; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.runner.Description; - -/** - * @since 5.6 - */ -@API(status = INTERNAL, since = "5.6") -public class TestSourceProvider { - - @SuppressWarnings("serial") - private static final TestSource NULL_SOURCE = new TestSource() { - }; - - private final Map testSourceCache = new ConcurrentHashMap<>(); - private final Map, List> methodsCache = synchronizedMap(new LruCache<>(31)); - - public TestSource findTestSource(Description description) { - TestSource testSource = testSourceCache.computeIfAbsent(description, this::computeTestSource); - return testSource == NULL_SOURCE ? null : testSource; - } - - private TestSource computeTestSource(Description description) { - Class testClass = description.getTestClass(); - if (testClass != null) { - String methodName = DescriptionUtils.getMethodName(description); - if (methodName != null) { - Method method = findMethod(testClass, sanitizeMethodName(methodName)); - if (method != null) { - return MethodSource.from(testClass, method); - } - } - return ClassSource.from(testClass); - } - return NULL_SOURCE; - } - - private String sanitizeMethodName(String methodName) { - if (methodName.contains("[") && methodName.endsWith("]")) { - // special case for parameterized tests - return methodName.substring(0, methodName.indexOf("[")); - } - return methodName; - } - - private Method findMethod(Class testClass, String methodName) { - List methods = methodsCache.computeIfAbsent(testClass, clazz -> findMethods(clazz, m -> true)).stream() // - .filter(where(Method::getName, isEqual(methodName))) // - .collect(toList()); - if (methods.isEmpty()) { - return null; - } - if (methods.size() == 1) { - return methods.get(0); - } - methods = methods.stream().filter(ModifierSupport::isPublic).collect(toList()); - if (methods.size() == 1) { - return methods.get(0); - } - return null; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java deleted file mode 100644 index 9e0450fa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; - -/** - * @since 5.6 - */ -@API(status = INTERNAL, since = "5.6") -public class VintageEngineDescriptor extends EngineDescriptor { - - public VintageEngineDescriptor(UniqueId uniqueId) { - super(uniqueId, "JUnit Vintage"); - } - - public Set getModifiableChildren() { - return children; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java deleted file mode 100644 index d8a64ba7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static java.util.Arrays.stream; -import static java.util.function.Predicate.isEqual; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.StringUtils.isNotBlank; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.apiguardian.api.API; -import org.junit.experimental.categories.Category; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.runner.Description; - -/** - * @since 4.12 - */ -@API(status = INTERNAL, since = "4.12") -public class VintageTestDescriptor extends AbstractTestDescriptor { - - public static final String ENGINE_ID = "junit-vintage"; - public static final String SEGMENT_TYPE_RUNNER = "runner"; - public static final String SEGMENT_TYPE_TEST = "test"; - public static final String SEGMENT_TYPE_DYNAMIC = "dynamic"; - - protected Description description; - - public VintageTestDescriptor(UniqueId uniqueId, Description description, TestSource source) { - this(uniqueId, description, generateDisplayName(description), source); - } - - VintageTestDescriptor(UniqueId uniqueId, Description description, String displayName, TestSource source) { - super(uniqueId, displayName, source); - this.description = description; - } - - private static String generateDisplayName(Description description) { - String methodName = DescriptionUtils.getMethodName(description); - return isNotBlank(methodName) ? methodName : description.getDisplayName(); - } - - public Description getDescription() { - return description; - } - - @Override - public String getLegacyReportingName() { - String methodName = DescriptionUtils.getMethodName(description); - if (methodName == null) { - String className = description.getClassName(); - if (isNotBlank(className)) { - return className; - } - } - return super.getLegacyReportingName(); - } - - @Override - public Type getType() { - return description.isTest() ? Type.TEST : Type.CONTAINER; - } - - @Override - public Set getTags() { - Set tags = new LinkedHashSet<>(); - addTagsFromParent(tags); - addCategoriesAsTags(tags); - return tags; - } - - @Override - public void removeFromHierarchy() { - if (canBeRemovedFromHierarchy()) { - super.removeFromHierarchy(); - } - } - - protected boolean canBeRemovedFromHierarchy() { - return tryToExcludeFromRunner(this.description); - } - - protected boolean tryToExcludeFromRunner(Description description) { - // @formatter:off - return getParent().map(VintageTestDescriptor.class::cast) - .map(parent -> parent.tryToExcludeFromRunner(description)) - .orElse(false); - // @formatter:on - } - - void pruneDescriptorsForObsoleteDescriptions(List newSiblingDescriptions) { - Optional newDescription = newSiblingDescriptions.stream().filter(isEqual(description)).findAny(); - if (newDescription.isPresent()) { - List newChildren = newDescription.get().getChildren(); - new ArrayList<>(children).stream().map(VintageTestDescriptor.class::cast).forEach( - childDescriptor -> childDescriptor.pruneDescriptorsForObsoleteDescriptions(newChildren)); - } - else { - super.removeFromHierarchy(); - } - } - - private void addTagsFromParent(Set tags) { - getParent().map(TestDescriptor::getTags).ifPresent(tags::addAll); - } - - private void addCategoriesAsTags(Set tags) { - Category annotation = description.getAnnotation(Category.class); - if (annotation != null) { - // @formatter:off - stream(annotation.value()) - .map(ReflectionUtils::getAllAssignmentCompatibleClasses) - .flatMap(Collection::stream) - .distinct() - .map(Class::getName) - .map(TestTag::create) - .forEachOrdered(tags::add); - // @formatter:on - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java deleted file mode 100644 index bef751cb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Test descriptors used within the JUnit Vintage test engine. - */ - -package org.junit.vintage.engine.descriptor; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java deleted file mode 100644 index a2d1e7d4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static java.util.Collections.emptySet; -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; - -import java.util.Optional; - -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ClassFilter; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.UniqueId.Segment; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.support.discovery.SelectorResolver; -import org.junit.runner.Runner; -import org.junit.runners.model.RunnerBuilder; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; - -/** - * @since 4.12 - */ -class ClassSelectorResolver implements SelectorResolver { - - private static final RunnerBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder(); - - private final ClassFilter classFilter; - - ClassSelectorResolver(ClassFilter classFilter) { - this.classFilter = classFilter; - } - - @Override - public Resolution resolve(ClassSelector selector, Context context) { - return resolveTestClass(selector.getJavaClass(), context); - } - - @Override - public Resolution resolve(UniqueIdSelector selector, Context context) { - Segment lastSegment = selector.getUniqueId().getLastSegment(); - if (SEGMENT_TYPE_RUNNER.equals(lastSegment.getType())) { - String testClassName = lastSegment.getValue(); - Class testClass = ReflectionUtils.tryToLoadClass(testClassName)// - .getOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); - return resolveTestClass(testClass, context); - } - return unresolved(); - } - - private Resolution resolveTestClass(Class testClass, Context context) { - if (!classFilter.test(testClass)) { - return unresolved(); - } - Runner runner = RUNNER_BUILDER.safeRunnerForClass(testClass); - if (runner == null) { - return unresolved(); - } - return context.addToParent(parent -> Optional.of(createRunnerTestDescriptor(parent, testClass, runner))).map( - runnerTestDescriptor -> Match.exact(runnerTestDescriptor, () -> { - runnerTestDescriptor.clearFilters(); - return emptySet(); - })).map(Resolution::match).orElse(unresolved()); - } - - private RunnerTestDescriptor createRunnerTestDescriptor(TestDescriptor parent, Class testClass, Runner runner) { - UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - return new RunnerTestDescriptor(uniqueId, testClass, runner); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java deleted file mode 100644 index 0da94f68..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import java.lang.reflect.Method; -import java.util.function.Predicate; - -import org.junit.Ignore; -import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; -import org.junit.internal.builders.AnnotatedBuilder; -import org.junit.internal.builders.IgnoredBuilder; -import org.junit.internal.builders.IgnoredClassRunner; -import org.junit.internal.builders.JUnit4Builder; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.runner.Runner; -import org.junit.runner.manipulation.Filterable; -import org.junit.runners.model.RunnerBuilder; - -/** - * Customization of {@link AllDefaultPossibilitiesBuilder} from JUnit 4 to - * ignore certain classes that would otherwise be reported as errors or cause - * infinite recursion. - * - * @since 4.12 - * @see DefensiveAnnotatedBuilder - * @see DefensiveJUnit4Builder - * @see IgnoredClassRunner - */ -class DefensiveAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder { - - private static final Logger logger = LoggerFactory.getLogger(DefensiveAllDefaultPossibilitiesBuilder.class); - - private final AnnotatedBuilder annotatedBuilder; - private final JUnit4Builder junit4Builder; - private final IgnoredBuilder ignoredBuilder; - - @SuppressWarnings("deprecation") - DefensiveAllDefaultPossibilitiesBuilder() { - super(true); - annotatedBuilder = new DefensiveAnnotatedBuilder(this); - junit4Builder = new DefensiveJUnit4Builder(); - ignoredBuilder = new NullIgnoredBuilder(); - } - - @Override - public Runner runnerForClass(Class testClass) throws Throwable { - Runner runner = super.runnerForClass(testClass); - if (testClass.getAnnotation(Ignore.class) != null) { - if (runner == null) { - return new IgnoredClassRunner(testClass); - } - return decorateIgnoredTestClass(runner); - } - return runner; - } - - /** - * Instead of checking for the {@link Ignore} annotation and returning an - * {@link IgnoredClassRunner} from {@link IgnoredBuilder}, we've let the - * super class determine the regular runner that would have been used if - * {@link Ignore} hadn't been present. Now, we decorate the runner to - * override its runtime behavior (i.e. skip execution) but return its - * regular {@link org.junit.runner.Description}. - */ - private Runner decorateIgnoredTestClass(Runner runner) { - if (runner instanceof Filterable) { - return new FilterableIgnoringRunnerDecorator(runner); - } - return new IgnoringRunnerDecorator(runner); - } - - @Override - protected AnnotatedBuilder annotatedBuilder() { - return annotatedBuilder; - } - - @Override - protected JUnit4Builder junit4Builder() { - return junit4Builder; - } - - @Override - protected IgnoredBuilder ignoredBuilder() { - return ignoredBuilder; - } - - /** - * Customization of {@link AnnotatedBuilder} that ignores classes annotated - * with {@code @RunWith(JUnitPlatform.class)} to avoid infinite recursion. - */ - private static class DefensiveAnnotatedBuilder extends AnnotatedBuilder { - - DefensiveAnnotatedBuilder(RunnerBuilder suiteBuilder) { - super(suiteBuilder); - } - - @Override - public Runner buildRunner(Class runnerClass, Class testClass) throws Exception { - // Referenced by name because it might not be available at runtime. - if ("org.junit.platform.runner.JUnitPlatform".equals(runnerClass.getName())) { - logger.warn(() -> "Ignoring test class using JUnitPlatform runner: " + testClass.getName()); - return null; - } - return super.buildRunner(runnerClass, testClass); - } - } - - /** - * Customization of {@link JUnit4Builder} that ignores classes that do not - * contain any test methods in order not to report errors for them. - */ - private static class DefensiveJUnit4Builder extends JUnit4Builder { - - private static final Predicate isPotentialJUnit4TestMethod = new IsPotentialJUnit4TestMethod(); - - @Override - public Runner runnerForClass(Class testClass) throws Throwable { - if (containsTestMethods(testClass)) { - return super.runnerForClass(testClass); - } - return null; - } - - private boolean containsTestMethods(Class testClass) { - return ReflectionUtils.isMethodPresent(testClass, isPotentialJUnit4TestMethod); - } - } - - /** - * Customization of {@link IgnoredBuilder} that always returns {@code null}. - * - * @since 5.1 - */ - private static class NullIgnoredBuilder extends IgnoredBuilder { - @Override - public Runner runnerForClass(Class testClass) { - // don't ignore entire test classes just yet - return null; - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java deleted file mode 100644 index 636ef255..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.runner.Runner; -import org.junit.runner.manipulation.Filter; -import org.junit.runner.manipulation.Filterable; -import org.junit.runner.manipulation.NoTestsRemainException; - -/** - * {@link Filterable} {@link IgnoringRunnerDecorator}. - * - * @since 5.1 - */ -class FilterableIgnoringRunnerDecorator extends IgnoringRunnerDecorator implements Filterable { - - FilterableIgnoringRunnerDecorator(Runner runner) { - super(runner); - Preconditions.condition(runner instanceof Filterable, - () -> "Runner must be an instance of Filterable: " + runner.getClass().getName()); - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - ((Filterable) runner).filter(filter); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java deleted file mode 100644 index 3b5597e5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.runner.Description; -import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; -import org.junit.vintage.engine.descriptor.RunnerDecorator; - -/** - * Decorator for Runners that will be ignored completely. - * - *

Contrary to {@link org.junit.internal.builders.IgnoredClassRunner}, this - * runner returns a complete description including all children. - * - * @since 5.1 - */ -class IgnoringRunnerDecorator extends Runner implements RunnerDecorator { - - protected final Runner runner; - - IgnoringRunnerDecorator(Runner runner) { - this.runner = Preconditions.notNull(runner, "Runner must not be null"); - } - - @Override - public Description getDescription() { - return runner.getDescription(); - } - - @Override - public void run(RunNotifier notifier) { - notifier.fireTestIgnored(getDescription()); - } - - @Override - public Runner getDecoratedRunner() { - return runner; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java deleted file mode 100644 index 91b9b183..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; -import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPublic; - -import java.util.function.Predicate; - -import org.apiguardian.api.API; - -/** - * @since 4.12 - */ -@API(status = INTERNAL, since = "5.8", consumers = "org.junit.vintage.**") -public class IsPotentialJUnit4TestClass implements Predicate> { - - @Override - public boolean test(Class candidate) { - // Do not collapse into a single return statement. - if (!isPublic(candidate)) { - return false; - } - if (isAbstract(candidate)) { - return false; - } - if (isInnerClass(candidate)) { - return false; - } - - return true; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java deleted file mode 100644 index d382a606..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import java.lang.reflect.Method; -import java.util.function.Predicate; - -import org.junit.Test; - -/** - * @since 4.12 - */ -class IsPotentialJUnit4TestMethod implements Predicate { - - @Override - public boolean test(Method method) { - // Don't use AnnotationUtils.isAnnotated since JUnit 4 does not support - // meta-annotations - return method.isAnnotationPresent(Test.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java deleted file mode 100644 index 724e9a02..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; - -import java.util.Optional; -import java.util.function.Function; - -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.support.discovery.SelectorResolver; -import org.junit.runner.Description; -import org.junit.runner.manipulation.Filter; -import org.junit.vintage.engine.descriptor.DescriptionUtils; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; - -/** - * @since 4.12 - */ -class MethodSelectorResolver implements SelectorResolver { - - @Override - public Resolution resolve(MethodSelector selector, Context context) { - Class testClass = selector.getJavaClass(); - return resolveParentAndAddFilter(context, selectClass(testClass), parent -> toMethodFilter(selector)); - } - - @Override - public Resolution resolve(UniqueIdSelector selector, Context context) { - for (UniqueId current = selector.getUniqueId(); !current.getSegments().isEmpty(); current = current.removeLastSegment()) { - if (SEGMENT_TYPE_RUNNER.equals(current.getLastSegment().getType())) { - return resolveParentAndAddFilter(context, selectUniqueId(current), - parent -> toUniqueIdFilter(parent, selector.getUniqueId())); - } - } - return unresolved(); - } - - private Resolution resolveParentAndAddFilter(Context context, DiscoverySelector selector, - Function filterCreator) { - return context.resolve(selector).flatMap(parent -> addFilter(parent, filterCreator)).map( - this::toResolution).orElse(unresolved()); - } - - private Optional addFilter(TestDescriptor parent, - Function filterCreator) { - if (parent instanceof RunnerTestDescriptor) { - RunnerTestDescriptor runnerTestDescriptor = (RunnerTestDescriptor) parent; - runnerTestDescriptor.getFilters().ifPresent( - filters -> filters.add(filterCreator.apply(runnerTestDescriptor))); - return Optional.of(runnerTestDescriptor); - } - return Optional.empty(); - } - - private Resolution toResolution(RunnerTestDescriptor parent) { - return Resolution.match(Match.partial(parent)); - } - - private Filter toMethodFilter(MethodSelector methodSelector) { - Class testClass = methodSelector.getJavaClass(); - String methodName = methodSelector.getMethodName(); - return matchMethodDescription(Description.createTestDescription(testClass, methodName)); - } - - private Filter toUniqueIdFilter(RunnerTestDescriptor runnerTestDescriptor, UniqueId uniqueId) { - return new UniqueIdFilter(runnerTestDescriptor, uniqueId); - } - - /** - * The method {@link Filter#matchMethodDescription(Description)} returns a - * filter that does not account for the case when the description is for a - * {@link org.junit.runners.Parameterized} runner. - */ - private static Filter matchMethodDescription(final Description desiredDescription) { - String desiredMethodName = DescriptionUtils.getMethodName(desiredDescription); - return new Filter() { - - @Override - public boolean shouldRun(Description description) { - if (description.isTest()) { - return desiredDescription.equals(description) || isParameterizedMethod(description); - } - - // explicitly check if any children want to run - for (Description each : description.getChildren()) { - if (shouldRun(each)) { - return true; - } - } - return false; - } - - private boolean isParameterizedMethod(Description description) { - String methodName = DescriptionUtils.getMethodName(description); - return methodName.startsWith(desiredMethodName + "["); - } - - @Override - public String describe() { - return String.format("Method %s", desiredDescription.getDisplayName()); - } - }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java deleted file mode 100644 index b9948537..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toCollection; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.IntFunction; - -import org.junit.platform.engine.UniqueId; -import org.junit.runner.Description; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.TestSourceProvider; -import org.junit.vintage.engine.descriptor.VintageTestDescriptor; -import org.junit.vintage.engine.support.UniqueIdReader; -import org.junit.vintage.engine.support.UniqueIdStringifier; - -/** - * @since 5.5 - */ -class RunnerTestDescriptorPostProcessor { - - private final UniqueIdReader uniqueIdReader = new UniqueIdReader(); - private final UniqueIdStringifier uniqueIdStringifier = new UniqueIdStringifier(); - private final TestSourceProvider testSourceProvider = new TestSourceProvider(); - - void applyFiltersAndCreateDescendants(RunnerTestDescriptor runnerTestDescriptor) { - addChildrenRecursively(runnerTestDescriptor); - runnerTestDescriptor.applyFilters(this::addChildrenRecursively); - } - - private void addChildrenRecursively(VintageTestDescriptor parent) { - if (parent.getDescription().isTest()) { - return; - } - List children = parent.getDescription().getChildren(); - // Use LinkedHashMap to preserve order, ArrayList for fast access by index - Map> childrenByUniqueId = children.stream().collect( - groupingBy(uniqueIdReader.andThen(uniqueIdStringifier), LinkedHashMap::new, toCollection(ArrayList::new))); - for (Entry> entry : childrenByUniqueId.entrySet()) { - String uniqueId = entry.getKey(); - List childrenWithSameUniqueId = entry.getValue(); - IntFunction uniqueIdGenerator = determineUniqueIdGenerator(uniqueId, childrenWithSameUniqueId); - for (int index = 0; index < childrenWithSameUniqueId.size(); index++) { - String reallyUniqueId = uniqueIdGenerator.apply(index); - Description description = childrenWithSameUniqueId.get(index); - UniqueId id = parent.getUniqueId().append(VintageTestDescriptor.SEGMENT_TYPE_TEST, reallyUniqueId); - VintageTestDescriptor child = new VintageTestDescriptor(id, description, - testSourceProvider.findTestSource(description)); - parent.addChild(child); - addChildrenRecursively(child); - } - } - } - - private IntFunction determineUniqueIdGenerator(String uniqueId, - List childrenWithSameUniqueId) { - if (childrenWithSameUniqueId.size() == 1) { - return index -> uniqueId; - } - return index -> uniqueId + "[" + index + "]"; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java deleted file mode 100644 index 24685c55..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static java.util.stream.Collectors.toSet; - -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.Deque; -import java.util.Optional; -import java.util.Set; - -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.runner.Description; -import org.junit.runner.manipulation.Filter; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.VintageTestDescriptor; - -/** - * @since 4.12 - */ -class UniqueIdFilter extends Filter { - - private final RunnerTestDescriptor runnerTestDescriptor; - private final UniqueId uniqueId; - - private Deque path; - private Set descendants; - - UniqueIdFilter(RunnerTestDescriptor runnerTestDescriptor, UniqueId uniqueId) { - this.runnerTestDescriptor = runnerTestDescriptor; - this.uniqueId = uniqueId; - } - - private void ensureInitialized() { - if (descendants == null) { - Optional identifiedTestDescriptor = runnerTestDescriptor.findByUniqueId(uniqueId); - descendants = determineDescendants(identifiedTestDescriptor); - path = determinePath(runnerTestDescriptor, identifiedTestDescriptor); - } - } - - private Deque determinePath(RunnerTestDescriptor runnerTestDescriptor, - Optional identifiedTestDescriptor) { - Deque path = new ArrayDeque<>(); - Optional current = identifiedTestDescriptor; - while (current.isPresent() && !current.get().equals(runnerTestDescriptor)) { - path.addFirst(((VintageTestDescriptor) current.get()).getDescription()); - current = current.get().getParent(); - } - return path; - } - - private Set determineDescendants(Optional identifiedTestDescriptor) { - // @formatter:off - return identifiedTestDescriptor.map( - testDescriptor -> testDescriptor - .getDescendants() - .stream() - .map(VintageTestDescriptor.class::cast) - .map(VintageTestDescriptor::getDescription) - .collect(toSet())) - .orElseGet(Collections::emptySet); - // @formatter:on - } - - @Override - public boolean shouldRun(Description description) { - ensureInitialized(); - return path.contains(description) || descendants.contains(description); - } - - @Override - public String describe() { - return "Unique ID " + uniqueId; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java deleted file mode 100644 index 427526bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassFilter; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.VintageEngineDescriptor; - -/** - * @since 4.12 - */ -@API(status = INTERNAL, since = "4.12") -public class VintageDiscoverer { - - private static final IsPotentialJUnit4TestClass isPotentialJUnit4TestClass = new IsPotentialJUnit4TestClass(); - - // @formatter:off - private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() - .addClassContainerSelectorResolver(isPotentialJUnit4TestClass) - .addSelectorResolver(context -> new ClassSelectorResolver(ClassFilter.of(context.getClassNameFilter(), isPotentialJUnit4TestClass))) - .addSelectorResolver(new MethodSelectorResolver()) - .build(); - // @formatter:on - - public VintageEngineDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - VintageEngineDescriptor engineDescriptor = new VintageEngineDescriptor(uniqueId); - resolver.resolve(discoveryRequest, engineDescriptor); - RunnerTestDescriptorPostProcessor postProcessor = new RunnerTestDescriptorPostProcessor(); - for (TestDescriptor testDescriptor : engineDescriptor.getChildren()) { - RunnerTestDescriptor runnerTestDescriptor = (RunnerTestDescriptor) testDescriptor; - postProcessor.applyFiltersAndCreateDescendants(runnerTestDescriptor); - } - return engineDescriptor; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java deleted file mode 100644 index a22ae393..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Internal classes for test discovery within the JUnit Vintage test engine. - */ - -package org.junit.vintage.engine.discovery; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java deleted file mode 100644 index 949cc436..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.execution; - -/** - * @since 5.4.1 - */ -enum EventType { - REPORTED, SYNTHETIC -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java deleted file mode 100644 index 9cefb8fa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.execution; - -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_DYNAMIC; - -import java.util.Optional; -import java.util.function.Function; - -import org.junit.Ignore; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.runner.Description; -import org.junit.runner.Result; -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunListener; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.TestSourceProvider; -import org.junit.vintage.engine.descriptor.VintageTestDescriptor; -import org.junit.vintage.engine.support.UniqueIdReader; -import org.junit.vintage.engine.support.UniqueIdStringifier; - -/** - * @since 4.12 - */ -class RunListenerAdapter extends RunListener { - - private final TestRun testRun; - private final EngineExecutionListener listener; - private final TestSourceProvider testSourceProvider; - private final Function uniqueIdExtractor; - - RunListenerAdapter(TestRun testRun, EngineExecutionListener listener, TestSourceProvider testSourceProvider) { - this.testRun = testRun; - this.listener = listener; - this.testSourceProvider = testSourceProvider; - this.uniqueIdExtractor = new UniqueIdReader().andThen(new UniqueIdStringifier()); - } - - @Override - public void testRunStarted(Description description) { - if (description.isSuite() && description.getAnnotation(Ignore.class) == null) { - fireExecutionStarted(testRun.getRunnerTestDescriptor(), EventType.REPORTED); - } - } - - @Override - public void testSuiteStarted(Description description) { - RunnerTestDescriptor runnerTestDescriptor = testRun.getRunnerTestDescriptor(); - // runnerTestDescriptor is reported in testRunStarted - if (!runnerTestDescriptor.getDescription().equals(description)) { - testStarted(lookupOrRegisterNextTestDescriptor(description), EventType.REPORTED); - } - } - - @Override - public void testIgnored(Description description) { - testIgnored(lookupOrRegisterNextTestDescriptor(description), determineReasonForIgnoredTest(description)); - } - - @Override - public void testStarted(Description description) { - testStarted(lookupOrRegisterNextTestDescriptor(description), EventType.REPORTED); - } - - @Override - public void testAssumptionFailure(Failure failure) { - handleFailure(failure, TestExecutionResult::aborted); - } - - @Override - public void testFailure(Failure failure) { - handleFailure(failure, TestExecutionResult::failed); - } - - @Override - public void testFinished(Description description) { - testFinished(lookupOrRegisterCurrentTestDescriptor(description)); - } - - @Override - public void testSuiteFinished(Description description) { - RunnerTestDescriptor runnerTestDescriptor = testRun.getRunnerTestDescriptor(); - // runnerTestDescriptor is reported in testRunFinished - if (!runnerTestDescriptor.getDescription().equals(description)) { - reportContainerFinished(lookupOrRegisterCurrentTestDescriptor(description)); - } - } - - @Override - public void testRunFinished(Result result) { - reportContainerFinished(testRun.getRunnerTestDescriptor()); - } - - private void reportContainerFinished(TestDescriptor containerTestDescriptor) { - if (testRun.isNotSkipped(containerTestDescriptor)) { - if (testRun.isNotStarted(containerTestDescriptor)) { - fireExecutionStarted(containerTestDescriptor, EventType.SYNTHETIC); - } - testRun.getInProgressTestDescriptorsWithSyntheticStartEvents().stream() // - .filter(this::canFinish) // - .forEach(this::fireExecutionFinished); - if (testRun.isNotFinished(containerTestDescriptor)) { - fireExecutionFinished(containerTestDescriptor); - } - } - } - - private TestDescriptor lookupOrRegisterNextTestDescriptor(Description description) { - return lookupOrRegisterTestDescriptor(description, testRun::lookupNextTestDescriptor); - } - - private TestDescriptor lookupOrRegisterCurrentTestDescriptor(Description description) { - return lookupOrRegisterTestDescriptor(description, testRun::lookupCurrentTestDescriptor); - } - - private TestDescriptor lookupOrRegisterTestDescriptor(Description description, - Function> lookup) { - return lookup.apply(description).orElseGet(() -> registerDynamicTestDescriptor(description, lookup)); - } - - private VintageTestDescriptor registerDynamicTestDescriptor(Description description, - Function> lookup) { - // workaround for dynamic children as used by Spock's Runner - TestDescriptor parent = findParent(description, lookup); - UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_DYNAMIC, uniqueIdExtractor.apply(description)); - VintageTestDescriptor dynamicDescriptor = new VintageTestDescriptor(uniqueId, description, - testSourceProvider.findTestSource(description)); - parent.addChild(dynamicDescriptor); - testRun.registerDynamicTest(dynamicDescriptor); - dynamicTestRegistered(dynamicDescriptor); - return dynamicDescriptor; - } - - private TestDescriptor findParent(Description description, - Function> lookup) { - // @formatter:off - return Optional.ofNullable(description.getTestClass()) - .map(Description::createSuiteDescription) - .flatMap(lookup) - .orElseGet(testRun::getRunnerTestDescriptor); - // @formatter:on - } - - private void handleFailure(Failure failure, Function resultCreator) { - handleFailure(failure, resultCreator, lookupOrRegisterCurrentTestDescriptor(failure.getDescription())); - } - - private void handleFailure(Failure failure, Function resultCreator, - TestDescriptor testDescriptor) { - TestExecutionResult result = resultCreator.apply(failure.getException()); - testRun.storeResult(testDescriptor, result); - if (testRun.isNotStarted(testDescriptor)) { - testStarted(testDescriptor, EventType.SYNTHETIC); - } - if (testRun.isNotFinished(testDescriptor) && testDescriptor.isContainer() - && testRun.hasSyntheticStartEvent(testDescriptor) - && testRun.isDescendantOfRunnerTestDescriptor(testDescriptor)) { - testFinished(testDescriptor); - } - } - - private void testIgnored(TestDescriptor testDescriptor, String reason) { - fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents(testDescriptor); - fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); - fireExecutionSkipped(testDescriptor, reason); - } - - private String determineReasonForIgnoredTest(Description description) { - Ignore ignoreAnnotation = description.getAnnotation(Ignore.class); - return Optional.ofNullable(ignoreAnnotation).map(Ignore::value).orElse(""); - } - - private void dynamicTestRegistered(TestDescriptor testDescriptor) { - fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); - listener.dynamicTestRegistered(testDescriptor); - } - - private void testStarted(TestDescriptor testDescriptor, EventType eventType) { - fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents(testDescriptor); - fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); - fireExecutionStarted(testDescriptor, eventType); - } - - private void fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents( - TestDescriptor testDescriptor) { - testRun.getInProgressTestDescriptorsWithSyntheticStartEvents().stream() // - .filter(it -> !isAncestor(it, testDescriptor) && canFinish(it)) // - .forEach(this::fireExecutionFinished); - } - - private boolean isAncestor(TestDescriptor candidate, TestDescriptor testDescriptor) { - Optional parent = testDescriptor.getParent(); - if (!parent.isPresent()) { - return false; - } - if (parent.get().equals(candidate)) { - return true; - } - return isAncestor(candidate, parent.get()); - } - - private void testFinished(TestDescriptor descriptor) { - fireExecutionFinished(descriptor); - } - - private void fireExecutionStartedIncludingUnstartedAncestors(Optional parent) { - if (parent.isPresent() && canStart(parent.get())) { - fireExecutionStartedIncludingUnstartedAncestors(parent.get().getParent()); - fireExecutionStarted(parent.get(), EventType.SYNTHETIC); - } - } - - private boolean canStart(TestDescriptor testDescriptor) { - return testRun.isNotStarted(testDescriptor) // - && (testDescriptor.equals(testRun.getRunnerTestDescriptor()) - || testRun.isDescendantOfRunnerTestDescriptor(testDescriptor)); - } - - private boolean canFinish(TestDescriptor testDescriptor) { - return testRun.isNotFinished(testDescriptor) // - && testRun.isDescendantOfRunnerTestDescriptor(testDescriptor) - && testRun.areAllFinishedOrSkipped(testDescriptor.getChildren()); - } - - private void fireExecutionSkipped(TestDescriptor testDescriptor, String reason) { - testRun.markSkipped(testDescriptor); - listener.executionSkipped(testDescriptor, reason); - } - - private void fireExecutionStarted(TestDescriptor testDescriptor, EventType eventType) { - testRun.markStarted(testDescriptor, eventType); - listener.executionStarted(testDescriptor); - } - - private void fireExecutionFinished(TestDescriptor testDescriptor) { - testRun.markFinished(testDescriptor); - listener.executionFinished(testDescriptor, testRun.getStoredResultOrSuccessful(testDescriptor)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java deleted file mode 100644 index 23fad4de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.execution; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.engine.TestExecutionResult.failed; - -import org.apiguardian.api.API; -import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.runner.JUnitCore; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.TestSourceProvider; - -/** - * @since 4.12 - */ -@API(status = INTERNAL, since = "4.12") -public class RunnerExecutor { - - private final EngineExecutionListener engineExecutionListener; - private final TestSourceProvider testSourceProvider = new TestSourceProvider(); - - public RunnerExecutor(EngineExecutionListener engineExecutionListener) { - this.engineExecutionListener = engineExecutionListener; - } - - public void execute(RunnerTestDescriptor runnerTestDescriptor) { - TestRun testRun = new TestRun(runnerTestDescriptor); - JUnitCore core = new JUnitCore(); - core.addListener(new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider)); - try { - core.run(runnerTestDescriptor.toRequest()); - } - catch (Throwable t) { - UnrecoverableExceptions.rethrowIfUnrecoverable(t); - reportUnexpectedFailure(testRun, runnerTestDescriptor, failed(t)); - } - } - - private void reportUnexpectedFailure(TestRun testRun, RunnerTestDescriptor runnerTestDescriptor, - TestExecutionResult result) { - if (testRun.isNotStarted(runnerTestDescriptor)) { - engineExecutionListener.executionStarted(runnerTestDescriptor); - } - engineExecutionListener.executionFinished(runnerTestDescriptor, result); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java deleted file mode 100644 index 1e46e0e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.execution; - -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Stream.concat; -import static org.junit.platform.engine.TestExecutionResult.failed; -import static org.junit.platform.engine.TestExecutionResult.successful; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Stream; - -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.runner.Description; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.VintageTestDescriptor; -import org.opentest4j.MultipleFailuresError; - -/** - * @since 4.12 - */ -class TestRun { - - private final RunnerTestDescriptor runnerTestDescriptor; - private final Set runnerDescendants; - private final Map descriptionToDescriptors; - private final Map> executionResults = new LinkedHashMap<>(); - private final Set skippedDescriptors = new LinkedHashSet<>(); - private final Set startedDescriptors = new HashSet<>(); - private final Map inProgressDescriptors = new LinkedHashMap<>(); - private final Set finishedDescriptors = new LinkedHashSet<>(); - private final ThreadLocal> inProgressDescriptorsByStartingThread = ThreadLocal.withInitial( - ArrayDeque::new); - - TestRun(RunnerTestDescriptor runnerTestDescriptor) { - this.runnerTestDescriptor = runnerTestDescriptor; - runnerDescendants = new LinkedHashSet<>(runnerTestDescriptor.getDescendants()); - // @formatter:off - descriptionToDescriptors = concat(Stream.of(runnerTestDescriptor), runnerDescendants.stream()) - .map(VintageTestDescriptor.class::cast) - .collect(toMap(VintageTestDescriptor::getDescription, VintageDescriptors::new, VintageDescriptors::merge, HashMap::new)); - // @formatter:on - } - - void registerDynamicTest(VintageTestDescriptor testDescriptor) { - descriptionToDescriptors.computeIfAbsent(testDescriptor.getDescription(), __ -> new VintageDescriptors()).add( - testDescriptor); - runnerDescendants.add(testDescriptor); - } - - RunnerTestDescriptor getRunnerTestDescriptor() { - return runnerTestDescriptor; - } - - Collection getInProgressTestDescriptorsWithSyntheticStartEvents() { - List result = inProgressDescriptors.entrySet().stream() // - .filter(entry -> entry.getValue().equals(EventType.SYNTHETIC)) // - .map(Entry::getKey) // - .collect(toCollection(ArrayList::new)); - Collections.reverse(result); - return result; - } - - boolean isDescendantOfRunnerTestDescriptor(TestDescriptor testDescriptor) { - return runnerDescendants.contains(testDescriptor); - } - - boolean hasSyntheticStartEvent(TestDescriptor testDescriptor) { - return inProgressDescriptors.get(testDescriptor) == EventType.SYNTHETIC; - } - - Optional lookupNextTestDescriptor(Description description) { - return lookupUnambiguouslyOrApplyFallback(description, VintageDescriptors::getNextUnstarted); - } - - Optional lookupCurrentTestDescriptor(Description description) { - return lookupUnambiguouslyOrApplyFallback(description, __ -> { - VintageTestDescriptor lastStarted = inProgressDescriptorsByStartingThread.get().peekLast(); - if (lastStarted != null && description.equals(lastStarted.getDescription())) { - return Optional.of(lastStarted); - } - return Optional.empty(); - }); - } - - private Optional lookupUnambiguouslyOrApplyFallback(Description description, - Function> fallback) { - VintageDescriptors vintageDescriptors = descriptionToDescriptors.getOrDefault(description, - VintageDescriptors.NONE); - Optional result = vintageDescriptors.getUnambiguously(description); - if (!result.isPresent()) { - result = fallback.apply(vintageDescriptors); - } - return result; - } - - void markSkipped(TestDescriptor testDescriptor) { - skippedDescriptors.add(testDescriptor); - if (testDescriptor instanceof VintageTestDescriptor) { - VintageTestDescriptor vintageDescriptor = (VintageTestDescriptor) testDescriptor; - descriptionToDescriptors.get(vintageDescriptor.getDescription()).incrementSkippedOrStarted(); - } - } - - boolean isNotSkipped(TestDescriptor testDescriptor) { - return !isSkipped(testDescriptor); - } - - boolean isSkipped(TestDescriptor testDescriptor) { - return skippedDescriptors.contains(testDescriptor); - } - - void markStarted(TestDescriptor testDescriptor, EventType eventType) { - inProgressDescriptors.put(testDescriptor, eventType); - startedDescriptors.add(testDescriptor); - if (testDescriptor instanceof VintageTestDescriptor) { - VintageTestDescriptor vintageDescriptor = (VintageTestDescriptor) testDescriptor; - inProgressDescriptorsByStartingThread.get().addLast(vintageDescriptor); - descriptionToDescriptors.get(vintageDescriptor.getDescription()).incrementSkippedOrStarted(); - } - } - - boolean isNotStarted(TestDescriptor testDescriptor) { - return !startedDescriptors.contains(testDescriptor); - } - - void markFinished(TestDescriptor testDescriptor) { - inProgressDescriptors.remove(testDescriptor); - finishedDescriptors.add(testDescriptor); - if (testDescriptor instanceof VintageTestDescriptor) { - VintageTestDescriptor descriptor = (VintageTestDescriptor) testDescriptor; - inProgressDescriptorsByStartingThread.get().removeLastOccurrence(descriptor); - } - } - - boolean isNotFinished(TestDescriptor testDescriptor) { - return !isFinished(testDescriptor); - } - - boolean isFinished(TestDescriptor testDescriptor) { - return finishedDescriptors.contains(testDescriptor); - } - - boolean areAllFinishedOrSkipped(Set testDescriptors) { - return testDescriptors.stream().allMatch(this::isFinishedOrSkipped); - } - - boolean isFinishedOrSkipped(TestDescriptor testDescriptor) { - return isFinished(testDescriptor) || isSkipped(testDescriptor); - } - - void storeResult(TestDescriptor testDescriptor, TestExecutionResult result) { - List testExecutionResults = executionResults.computeIfAbsent(testDescriptor, - key -> new ArrayList<>()); - testExecutionResults.add(result); - } - - TestExecutionResult getStoredResultOrSuccessful(TestDescriptor testDescriptor) { - List testExecutionResults = executionResults.get(testDescriptor); - - if (testExecutionResults == null) { - return successful(); - } - if (testExecutionResults.size() == 1) { - return testExecutionResults.get(0); - } - // @formatter:off - List failures = testExecutionResults - .stream() - .map(TestExecutionResult::getThrowable) - .map(Optional::get) - .collect(toList()); - // @formatter:on - MultipleFailuresError multipleFailuresError = new MultipleFailuresError("", failures); - failures.forEach(multipleFailuresError::addSuppressed); - return failed(multipleFailuresError); - } - - private static class VintageDescriptors { - - private static final VintageDescriptors NONE = new VintageDescriptors(emptyList()); - - private final List descriptors; - private int skippedOrStartedCount; - - static VintageDescriptors merge(VintageDescriptors a, VintageDescriptors b) { - List mergedDescriptors = new ArrayList<>( - a.descriptors.size() + b.descriptors.size()); - mergedDescriptors.addAll(a.descriptors); - mergedDescriptors.addAll(b.descriptors); - return new VintageDescriptors(mergedDescriptors); - } - - VintageDescriptors(VintageTestDescriptor vintageTestDescriptor) { - this(); - add(vintageTestDescriptor); - } - - VintageDescriptors() { - this(new ArrayList<>(1)); - } - - VintageDescriptors(List descriptors) { - this.descriptors = descriptors; - } - - void add(VintageTestDescriptor descriptor) { - descriptors.add(descriptor); - } - - /** - * Returns the {@link TestDescriptor} that represents the specified - * {@link Description}. - * - *

There are edge cases where multiple {@link Description Descriptions} - * with the same {@code uniqueId} exist, e.g. when using overloaded methods - * to define {@linkplain org.junit.experimental.theories.Theory theories}. - * In this case, we try to find the correct {@link TestDescriptor} by - * checking for object identity on the {@link Description} it represents. - * - * @param description the {@code Description} to look up - */ - Optional getUnambiguously(Description description) { - if (descriptors.isEmpty()) { - return Optional.empty(); - } - if (descriptors.size() == 1) { - return Optional.of(descriptors.get(0)); - } - // @formatter:off - return descriptors.stream() - .filter(testDescriptor -> description == testDescriptor.getDescription()) - .findFirst(); - // @formatter:on - } - - public void incrementSkippedOrStarted() { - skippedOrStartedCount++; - } - - public Optional getNextUnstarted() { - if (skippedOrStartedCount < descriptors.size()) { - return Optional.of(descriptors.get(skippedOrStartedCount)); - } - return Optional.empty(); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java deleted file mode 100644 index 43e48e1d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Internal classes for test execution within the JUnit Vintage test engine. - */ - -package org.junit.vintage.engine.execution; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java deleted file mode 100644 index af7d60d3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Core package for the JUnit Vintage test engine. - */ - -package org.junit.vintage.engine; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java deleted file mode 100644 index 35a90736..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.support; - -import static java.lang.String.format; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; - -import java.io.Serializable; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.runner.Description; - -/** - * @since 4.12 - */ -@API(status = INTERNAL, since = "4.12") -public class UniqueIdReader implements Function { - - private static final Logger logger = LoggerFactory.getLogger(UniqueIdReader.class); - - private final String fieldName; - - public UniqueIdReader() { - this("fUniqueId"); - } - - // For tests only - UniqueIdReader(String fieldName) { - this.fieldName = fieldName; - } - - @Override - public Serializable apply(Description description) { - // @formatter:off - return tryToReadFieldValue(Description.class, fieldName, description) - .andThenTry(Serializable.class::cast) - .ifFailure(cause -> logger.warn(cause, () -> - format("Could not read unique ID for Description; using display name instead: %s", description))) - .toOptional() - .orElseGet(description::getDisplayName); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java deleted file mode 100644 index 6c2846e1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.support; - -import static org.apiguardian.api.API.Status.INTERNAL; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.text.NumberFormat; -import java.util.Base64; -import java.util.Locale; -import java.util.function.Function; - -import org.apiguardian.api.API; - -/** - * @since 4.12 - */ -@API(status = INTERNAL, since = "4.12") -public class UniqueIdStringifier implements Function { - - static final Charset CHARSET = StandardCharsets.UTF_8; - - @Override - public String apply(Serializable uniqueId) { - if (uniqueId instanceof CharSequence) { - return uniqueId.toString(); - } - if (uniqueId instanceof Number) { - return NumberFormat.getInstance(Locale.US).format(uniqueId); - } - return encodeBase64(serialize(uniqueId)); - } - - private byte[] serialize(Serializable uniqueId) { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - try (ObjectOutputStream out = new ObjectOutputStream(byteStream)) { - out.writeObject(uniqueId); - } - catch (IOException e) { - return uniqueId.toString().getBytes(CHARSET); - } - return byteStream.toByteArray(); - } - - private String encodeBase64(byte[] bytes) { - return new String(Base64.getEncoder().encode(bytes), CHARSET); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java deleted file mode 100644 index bb5a291a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Internal support classes for test discovery and execution within the JUnit - * Vintage test engine. - */ - -package org.junit.vintage.engine.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine deleted file mode 100644 index 97ee0b95..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine +++ /dev/null @@ -1 +0,0 @@ -org.junit.vintage.engine.VintageTestEngine \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java deleted file mode 100644 index 859744fc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -/** - * Provides a {@linkplain org.junit.platform.engine.TestEngine} for running - * JUnit 3 and 4 based tests on the platform. - * - * @since 4.12 - * @provides org.junit.platform.engine.TestEngine The {@code VintageTestEngine} - * runs JUnit 3 and 4 based tests on the platform. - */ -module org.junit.vintage.engine { - requires junit; // 4 - requires static org.apiguardian.api; - requires org.junit.platform.engine; - - provides org.junit.platform.engine.TestEngine - with org.junit.vintage.engine.VintageTestEngine; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java deleted file mode 100644 index bb533dbc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.launcher.EngineFilter.includeEngines; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.core.LauncherFactory; -import org.junit.vintage.engine.samples.junit4.JUnit4ParameterizedTestCase; - -/** - * @since 4.12 - */ -class JUnit4ParameterizedTests { - - private final Map callCounts = new HashMap<>(); - - @Test - void selectingWholeParameterizedClassRunsTestsWithAllValues() { - executeTests(selectClass(JUnit4ParameterizedTestCase.class)); - - Map expectedCallCounts = new HashMap<>(); - expectedCallCounts.put(SUCCESSFUL, 3); - expectedCallCounts.put(FAILED, 9); - - assertEquals(expectedCallCounts, callCounts); - } - - @Test - void selectingOneTestFromParameterizedClassRunsWithAllValues() { - executeTests(selectMethod(JUnit4ParameterizedTestCase.class, "test1")); - - assertEquals(Map.of(FAILED, 3), callCounts); - } - - private void executeTests(DiscoverySelector selector) { - var launcher = LauncherFactory.create(); - launcher.registerTestExecutionListeners(new StatusTrackingListener()); - - // @formatter:off - launcher.execute( - request() - .selectors(selector) - .filters(includeEngines("junit-vintage")) - .build() - ); - // @formatter:on - } - - private class StatusTrackingListener implements TestExecutionListener { - - @Override - public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { - if (identifier.isTest()) { - callCounts.merge(result.getStatus(), 1, Integer::sum); - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java deleted file mode 100644 index 4df0e86a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.JUnitException; - -/** - * @since 5.4 - */ -class JUnit4VersionCheckTests { - - /** - * @since 5.7 - */ - @Test - void handlesParsingSupportedVersionIdWithStandardVersionFormat() { - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12")); - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13")); - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13.1")); - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13.2")); - } - - /** - * @since 5.7 - */ - @Test - void handlesParsingSupportedVersionIdWithCustomizedVersionFormat() { - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12-patch_1")); - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0")); - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0.1")); - assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0.patch-042")); - } - - @Test - void throwsExceptionForUnsupportedVersion() { - var exception = assertThrows(JUnitException.class, () -> JUnit4VersionCheck.checkSupported(() -> "4.11")); - - assertEquals("Unsupported version of junit:junit: 4.11. Please upgrade to version 4.12 or later.", - exception.getMessage()); - } - - @Test - void handlesErrorsReadingVersion() { - Error error = new NoClassDefFoundError(); - - var exception = assertThrows(JUnitException.class, () -> JUnit4VersionCheck.checkSupported(() -> { - throw error; - })); - - assertEquals("Failed to read version of junit:junit", exception.getMessage()); - assertSame(error, exception.getCause()); - } - - @Test - void handlesErrorsParsingVersion() { - var exception = assertThrows(JUnitException.class, - () -> JUnit4VersionCheck.checkSupported(() -> "not a version")); - - assertEquals("Failed to parse version of junit:junit: not a version", exception.getMessage()); - } - - @Test - @Tag("missing-junit4") - void handlesMissingJUnit() { - var exception = assertThrows(JUnitException.class, JUnit4VersionCheck::checkSupported); - - assertEquals("Invalid class/module path: junit-vintage-engine is present but junit:junit is not. " - + "Please either remove junit-vintage-engine or add junit:junit, or alternatively use " - + "an excludeEngines(\"junit-vintage\") filter.", - exception.getMessage()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java deleted file mode 100644 index cb12669a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.FilterResult.excluded; -import static org.junit.platform.engine.FilterResult.includedIf; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.EngineFilter.includeEngines; -import static org.junit.platform.launcher.TagFilter.excludeTags; -import static org.junit.platform.launcher.TagFilter.includeTags; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.internal.runners.SuiteMethod; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; -import org.junit.platform.launcher.core.LauncherFactory; -import org.junit.runners.Suite; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; -import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; -import org.junit.vintage.engine.samples.junit4.Categories; -import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithFilterableChildRunner; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithTwoTestCases; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithNotFilterableRunner; -import org.junit.vintage.engine.samples.junit4.NotFilterableRunner; -import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; - -/** - * @since 5.1 - */ -class VintageLauncherIntegrationTests { - - @Test - void executesOnlyTaggedMethodOfRegularTestClass() { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters(includeTags(Categories.Failing.class.getName())); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(2); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), "failingTest"); - } - - @Test - void executesIncludedTaggedMethodOfNestedTestClass() { - Class testClass = EnclosedJUnit4TestCase.class; - Class nestedTestClass = EnclosedJUnit4TestCase.NestedClass.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters(includeTags(Categories.Failing.class.getName())); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), nestedTestClass.getName(), - "failingTest"); - } - - @Test - void executesOnlyNotExcludedTaggedMethodOfNestedTestClass() { - Class testClass = EnclosedJUnit4TestCase.class; - Class nestedTestClass = EnclosedJUnit4TestCase.NestedClass.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters(excludeTags(Categories.Failing.class.getName())); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), nestedTestClass.getName(), - "successfulTest"); - } - - @Test - void removesWholeSubtree() { - Class testClass = EnclosedJUnit4TestCase.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters(excludeTags(Categories.Plain.class.getName())); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage"); - } - - @Test - void removesCompleteClassIfNoMethodHasMatchingTags() { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters(includeTags("wrong-tag")); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactly("JUnit Vintage"); - } - - @Test - void removesCompleteClassIfItHasExcludedTag() { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters(excludeTags(Categories.Plain.class.getName())); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactly("JUnit Vintage"); - } - - @TrackLogRecords - @Test - void executesAllTestsForNotFilterableRunner(LogRecordListener logRecordListener) { - Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters((PostDiscoveryFilter) descriptor -> includedIf(descriptor.getDisplayName().contains("#1"))); - - var testPlan = discover(request); - logRecordListener.clear(); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), "Test #0", "Test #1"); - assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // - .containsExactly( - "Runner " + NotFilterableRunner.class.getName() + " (used on class " + testClass.getName() + ")" // - + " does not support filtering and will therefore be run completely."); - } - - @TrackLogRecords - @Test - void executesAllTestsForNotFilterableChildRunnerOfSuite(LogRecordListener logRecordListener) { - Class suiteClass = JUnit4SuiteOfSuiteWithFilterableChildRunner.class; - Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; - var request = request() // - .selectors(selectClass(suiteClass)) // - .filters((PostDiscoveryFilter) descriptor -> includedIf(descriptor.getDisplayName().contains("#1"))); - - var testPlan = discover(request); - logRecordListener.clear(); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(4); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), "Test #0", - "Test #1"); - assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // - .containsExactly("Runner " + Suite.class.getName() + " (used on class " + suiteClass.getName() + ")" // - + " was not able to satisfy all filter requests."); - } - - @TrackLogRecords - @Test - void executesAllTestsWhenFilterDidNotExcludeTestForJUnit3Suite(LogRecordListener logRecordListener) { - Class suiteClass = JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class; - Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; - var request = request() // - .selectors(selectClass(suiteClass)) // - .filters((PostDiscoveryFilter) descriptor -> excluded("not today")); - - var testPlan = discover(request); - logRecordListener.clear(); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), "test"); - assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // - .containsExactly( - "Runner " + SuiteMethod.class.getName() + " (used on class " + suiteClass.getName() + ")" // - + " was not able to satisfy all filter requests."); - } - - @Test - void executesOnlyTaggedMethodsForSuite() { - Class suiteClass = JUnit4SuiteWithTwoTestCases.class; - Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; - var request = request() // - .selectors(selectClass(suiteClass)) // - .filters(includeTags(Categories.Successful.class.getName())); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), - "successfulTest"); - } - - @Test - void removesCompleteClassWithNotFilterableRunnerIfItHasExcludedTag() { - Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters(excludeTags(Categories.Successful.class.getName())); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactly("JUnit Vintage"); - } - - @Test - void filtersOutAllDescendantsOfParameterizedTestCase() { - Class testClass = ParameterizedTestCase.class; - var request = request() // - .selectors(selectClass(testClass)) // - .filters((PostDiscoveryFilter) descriptor -> excluded("excluded")); - - var testPlan = discover(request); - assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); - - var results = execute(request); - assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // - .containsExactly("JUnit Vintage"); - } - - private TestPlan discover(LauncherDiscoveryRequestBuilder requestBuilder) { - var launcher = LauncherFactory.create(); - return launcher.discover(toRequest(requestBuilder)); - } - - private Map execute(LauncherDiscoveryRequestBuilder requestBuilder) { - Map results = new LinkedHashMap<>(); - var request = toRequest(requestBuilder); - var launcher = LauncherFactory.create(); - launcher.execute(request, new TestExecutionListener() { - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - results.put(testIdentifier, testExecutionResult); - } - }); - return results; - } - - private LauncherDiscoveryRequest toRequest(LauncherDiscoveryRequestBuilder requestBuilder) { - return requestBuilder.filters(includeEngines(ENGINE_ID)).build(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java deleted file mode 100644 index e78df198..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -/** - * Basic assertions regarding {@link org.junit.platform.engine.TestEngine} - * functionality in JUnit Vintage. - * - * @since 4.12 - */ -class VintageTestEngineBasicTests { - - private final VintageTestEngine vintage = new VintageTestEngine(); - - @Test - void id() { - assertEquals("junit-vintage", vintage.getId()); - } - - @Test - void groupId() { - assertEquals("org.junit.vintage", vintage.getGroupId().get()); - } - - @Test - void artifactId() { - assertEquals("junit-vintage-engine", vintage.getArtifactId().get()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java deleted file mode 100644 index b89470cf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java +++ /dev/null @@ -1,803 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static java.text.MessageFormat.format; -import static java.util.function.Predicate.isEqual; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.runner.manipulation.Filter; -import org.junit.vintage.engine.samples.PlainOldJavaClassWithoutAnyTestsTestCase; -import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; -import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; -import org.junit.vintage.engine.samples.junit4.Categories.Failing; -import org.junit.vintage.engine.samples.junit4.Categories.Plain; -import org.junit.vintage.engine.samples.junit4.Categories.Skipped; -import org.junit.vintage.engine.samples.junit4.Categories.SkippedWithReason; -import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; -import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithTwoTestCases; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithDistinguishableOverloadedMethod; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithIndistinguishableOverloadedMethod; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithNotFilterableRunner; -import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleInheritedTestWhichFails; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichIsIgnored; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; -import org.junit.vintage.engine.samples.junit4.SingleFailingTheoryTestCase; -import org.junit.vintage.engine.samples.junit4.TestCaseRunWithJUnitPlatformRunner; - -/** - * @since 4.12 - */ -class VintageTestEngineDiscoveryTests { - - VintageTestEngine engine = new VintageTestEngine(); - - @Test - void resolvesSimpleJUnit4TestClass() throws Exception { - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesIgnoredJUnit4TestClass() throws Exception { - Class testClass = IgnoredJUnit4TestCase.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - assertThat(runnerDescriptor.getChildren()).hasSize(2); - List children = new ArrayList<>(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(children.get(0), testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - assertTestMethodDescriptor(children.get(1), testClass, "succeedingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesEmptyIgnoredTestClass() { - Class testClass = EmptyIgnoredTestCase.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertFalse(runnerDescriptor.isContainer()); - assertTrue(runnerDescriptor.isTest()); - assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); - assertThat(runnerDescriptor.getChildren()).isEmpty(); - } - - @Test - void resolvesJUnit4TestClassWithCustomRunner() throws Exception { - Class testClass = SingleFailingTheoryTestCase.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(childDescriptor, testClass, "theory", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesJUnit3TestCase() throws Exception { - Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(childDescriptor, testClass, "test", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesJUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails() throws Exception { - Class suiteClass = JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class; - Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; - var discoveryRequest = discoveryRequestForClass(suiteClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(suiteDescriptor, suiteClass); - assertThat(suiteDescriptor.getDisplayName()).describedAs("display name") // - .startsWith(suiteClass.getSimpleName()); - assertThat(suiteDescriptor.getLegacyReportingName()).describedAs("legacy reporting name") // - .isEqualTo(suiteClass.getName()); - - var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); - assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertTestMethodDescriptor(testMethodDescriptor, testClass, "test", - VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); - } - - @Test - void resolvesJUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored() throws Exception { - Class suiteClass = JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; - var discoveryRequest = discoveryRequestForClass(suiteClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(suiteDescriptor, suiteClass); - - var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); - assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertTestMethodDescriptor(testMethodDescriptor, testClass, "ignoredTest", - VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); - } - - @Test - void resolvesJUnit4TestCaseWithIndistinguishableOverloadedMethod() { - Class testClass = JUnit4TestCaseWithIndistinguishableOverloadedMethod.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); - assertThat(testMethodDescriptors).hasSize(2); - - var testMethodDescriptor = testMethodDescriptors.get(0); - assertEquals("theory", testMethodDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "theory", "0"), - testMethodDescriptor.getUniqueId()); - assertClassSource(testClass, testMethodDescriptor); - - testMethodDescriptor = testMethodDescriptors.get(1); - assertEquals("theory", testMethodDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "theory", "1"), - testMethodDescriptor.getUniqueId()); - assertClassSource(testClass, testMethodDescriptor); - } - - @Test - void resolvesJUnit4TestCaseWithDistinguishableOverloadedMethod() throws Exception { - Class testClass = JUnit4TestCaseWithDistinguishableOverloadedMethod.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); - - var testMethodDescriptor = getOnlyElement(testMethodDescriptors); - assertEquals("test", testMethodDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "test"), testMethodDescriptor.getUniqueId()); - assertMethodSource(testClass.getMethod("test"), testMethodDescriptor); - } - - @Test - void doesNotResolvePlainOldJavaClassesWithoutAnyTest() { - assertYieldsNoDescriptors(PlainOldJavaClassWithoutAnyTestsTestCase.class); - } - - @Test - void doesNotResolveClassRunWithJUnitPlatform() { - assertYieldsNoDescriptors(TestCaseRunWithJUnitPlatformRunner.class); - } - - @Test - void resolvesClasspathSelector() throws Exception { - var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); - var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).build(); - var engineDescriptor = discoverTests(discoveryRequest); - - // @formatter:off - assertThat(engineDescriptor.getChildren()) - .extracting(TestDescriptor::getDisplayName) - .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()) - .contains(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()) - .doesNotContain(PlainOldJavaClassWithoutAnyTestsTestCase.class.getSimpleName()); - // @formatter:on - } - - @Test - void resolvesClasspathSelectorForJarFile() throws Exception { - var jarUrl = getClass().getResource("/vintage-testjar.jar"); - var jarFile = Paths.get(jarUrl.toURI()); - - var originalClassLoader = Thread.currentThread().getContextClassLoader(); - try (var classLoader = new URLClassLoader(new URL[] { jarUrl })) { - Thread.currentThread().setContextClassLoader(classLoader); - - var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(jarFile))).build(); - var engineDescriptor = discoverTests(discoveryRequest); - - // @formatter:off - assertThat(engineDescriptor.getChildren()) - .extracting(TestDescriptor::getDisplayName) - .containsExactly("JUnit4Test"); - // @formatter:on - } - finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - } - - @Test - void resolvesApplyingClassNameFilters() throws Exception { - var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); - - var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).filters( - includeClassNamePatterns(".*JUnit4.*"), includeClassNamePatterns(".*Plain.*")).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - // @formatter:off - assertThat(engineDescriptor.getChildren()) - .extracting(TestDescriptor::getDisplayName) - .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()) - .doesNotContain(JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getSimpleName()) - .doesNotContain(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()); - // @formatter:on - } - - @Test - void resolvesApplyingPackageNameFilters() throws Exception { - var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); - - var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).filters( - includePackageNames("org"), includePackageNames("org.junit")).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - // @formatter:off - assertThat(engineDescriptor.getChildren()) - .extracting(TestDescriptor::getDisplayName) - .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()); - // @formatter:on - } - - @Test - void resolvesPackageSelectorForJUnit4SamplesPackage() { - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - - var discoveryRequest = request().selectors(selectPackage(testClass.getPackage().getName())).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - // @formatter:off - assertThat(engineDescriptor.getChildren()) - .extracting(TestDescriptor::getDisplayName) - .contains(testClass.getSimpleName()) - .doesNotContain(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()); - // @formatter:on - } - - @Test - void resolvesPackageSelectorForJUnit3SamplesPackage() { - Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; - - var discoveryRequest = request().selectors(selectPackage(testClass.getPackage().getName())).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - // @formatter:off - assertThat(engineDescriptor.getChildren()) - .extracting(TestDescriptor::getDisplayName) - .contains(testClass.getSimpleName()) - .doesNotContain(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()); - // @formatter:on - } - - @Test - void resolvesClassesWithInheritedMethods() throws Exception { - Class superclass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - Class testClass = PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); - assertClassSource(testClass, runnerDescriptor); - - var testDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertEquals("failingTest", testDescriptor.getDisplayName()); - assertMethodSource(testClass, superclass.getMethod("failingTest"), testDescriptor); - } - - @Test - void resolvesCategoriesIntoTags() { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = discoveryRequestForClass(testClass); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(runnerDescriptor.getTags()).containsOnly(TestTag.create(Plain.class.getName())); - - var failingTest = findChildByDisplayName(runnerDescriptor, "failingTest"); - assertThat(failingTest.getTags()).containsOnly(// - TestTag.create(Plain.class.getName()), // - TestTag.create(Failing.class.getName())); - - var ignoredWithoutReason = findChildByDisplayName(runnerDescriptor, "ignoredTest1_withoutReason"); - assertThat(ignoredWithoutReason.getTags()).containsOnly(// - TestTag.create(Plain.class.getName()), // - TestTag.create(Skipped.class.getName())); - - var ignoredWithReason = findChildByDisplayName(runnerDescriptor, "ignoredTest2_withReason"); - assertThat(ignoredWithReason.getTags()).containsOnly(// - TestTag.create(Plain.class.getName()), // - TestTag.create(Skipped.class.getName()), // - TestTag.create(SkippedWithReason.class.getName())); - } - - @Test - void resolvesMethodSelectorForSingleMethod() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest"))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesMethodOfIgnoredJUnit4TestClass() throws Exception { - Class testClass = IgnoredJUnit4TestCase.class; - var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest"))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesMethodSelectorForTwoMethodsOfSameClass() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest")), - selectMethod(testClass, testClass.getMethod("successfulTest"))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); - assertThat(testMethodDescriptors).hasSize(2); - - var failingTest = testMethodDescriptors.get(0); - assertTestMethodDescriptor(failingTest, testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - - var successfulTest = testMethodDescriptors.get(1); - assertTestMethodDescriptor(successfulTest, testClass, "successfulTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesUniqueIdSelectorForSingleMethod() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors( - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest"))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void resolvesUniqueIdSelectorForSingleClass() { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors( - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - assertThat(runnerDescriptor.getChildren()).hasSize(5); - } - - @Test - void resolvesUniqueIdSelectorOfSingleClassWithinSuite() throws Exception { - Class suiteClass = JUnit4SuiteWithTwoTestCases.class; - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - var discoveryRequest = request().selectors( - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(suiteDescriptor, suiteClass); - - var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); - assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertTestMethodDescriptor(testMethodDescriptor, testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); - } - - @Test - void resolvesUniqueIdSelectorOfSingleMethodWithinSuite() throws Exception { - Class suiteClass = JUnit4SuiteWithTwoTestCases.class; - Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; - var discoveryRequest = request().selectors(selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod( - VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass), testClass, "successfulTest"))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(suiteDescriptor, suiteClass); - - var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); - assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertTestMethodDescriptor(testMethodDescriptor, testClass, "successfulTest", - VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); - } - - @Test - void resolvesMultipleUniqueIdSelectorsForMethodsOfSameClass() throws Exception { - Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; - var discoveryRequest = request().selectors( - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "successfulTest")), - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest"))).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); - assertThat(testMethodDescriptors).hasSize(2); - assertTestMethodDescriptor(testMethodDescriptors.get(0), testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - assertTestMethodDescriptor(testMethodDescriptors.get(1), testClass, "successfulTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void doesNotResolveMissingUniqueIdSelectorForSingleClass() { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors( - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass) + "/[test:doesNotExist]")).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var testDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertInitializationError(testDescriptor, Filter.class, testClass); - } - - @Test - void ignoresMoreFineGrainedSelectorsWhenClassIsSelectedAsWell() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors( // - selectMethod(testClass, testClass.getMethod("failingTest")), // - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "abortedTest")), selectClass(testClass) // - ).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - assertThat(runnerDescriptor.getChildren()).hasSize(5); - } - - @Test - void resolvesCombinationOfMethodAndUniqueIdSelector() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors( // - selectMethod(testClass, testClass.getMethod("failingTest")), // - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "abortedTest") // - )).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); - assertThat(testMethodDescriptors).hasSize(2); - assertTestMethodDescriptor(testMethodDescriptors.get(0), testClass, "abortedTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - assertTestMethodDescriptor(testMethodDescriptors.get(1), testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void ignoresRedundantSelector() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - var discoveryRequest = request().selectors( // - selectMethod(testClass, testClass.getMethod("failingTest")), // - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest") // - )).build(); - - var engineDescriptor = discoverTests(discoveryRequest); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var testMethodDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertTestMethodDescriptor(testMethodDescriptor, testClass, "failingTest", - VintageUniqueIdBuilder.uniqueIdForClass(testClass)); - } - - @Test - void doesNotResolveMethodOfClassNotAcceptedByClassNameFilter() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - // @formatter:off - var request = request() - .selectors(selectMethod(testClass, testClass.getMethod("failingTest"))) - .filters(includeClassNamePatterns("Foo")) - .build(); - // @formatter:on - - assertYieldsNoDescriptors(request); - } - - @Test - void doesNotResolveMethodOfClassNotAcceptedByPackageNameFilter() throws Exception { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - // @formatter:off - var request = request() - .selectors(selectMethod(testClass, testClass.getMethod("failingTest"))) - .filters(includePackageNames("com.acme")) - .build(); - // @formatter:on - - assertYieldsNoDescriptors(request); - } - - @Test - void resolvesClassForMethodSelectorForClassWithNonFilterableRunner() { - Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; - // @formatter:off - var request = request() - .selectors(selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "Test #0"))) - .build(); - // @formatter:on - - var engineDescriptor = discoverTests(request); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); - assertThat(runnerDescriptor.getChildren()).isNotEmpty(); - } - - @Test - void usesCustomUniqueIdsAndDisplayNamesWhenPresent() { - Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class; - var request = request().selectors(selectClass(suiteClass)).build(); - - var engineDescriptor = discoverTests(request); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, suiteClass); - - var testClassDescriptor = getOnlyElement(runnerDescriptor.getChildren()); - assertEquals("(TestClass)", testClassDescriptor.getDisplayName()); - - var childDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - - var prefix = VintageUniqueIdBuilder.uniqueIdForClass(suiteClass); - assertThat(childDescriptor.getUniqueId().toString()).startsWith(prefix.toString()); - assertEquals("(TestMethod)", childDescriptor.getDisplayName()); - - var customUniqueIdValue = childDescriptor.getUniqueId().getSegments().get(2).getType(); - assertNotNull(Base64.getDecoder().decode(customUniqueIdValue.getBytes(StandardCharsets.UTF_8)), - "is a valid Base64 encoding scheme"); - } - - @Test - void resolvesTestSourceForParameterizedTests() throws Exception { - Class testClass = ParameterizedTestCase.class; - var request = request().selectors(selectClass(testClass)).build(); - - var engineDescriptor = discoverTests(request); - - var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertRunnerTestDescriptor(runnerDescriptor, testClass); - - var fooParentDescriptor = findChildByDisplayName(runnerDescriptor, "[foo]"); - assertTrue(fooParentDescriptor.isContainer()); - assertFalse(fooParentDescriptor.isTest()); - assertThat(fooParentDescriptor.getSource()).isEmpty(); - - var testMethodDescriptor = getOnlyElement(fooParentDescriptor.getChildren()); - assertEquals("test[foo]", testMethodDescriptor.getDisplayName()); - assertTrue(testMethodDescriptor.isTest()); - assertFalse(testMethodDescriptor.isContainer()); - assertMethodSource(testClass.getMethod("test"), testMethodDescriptor); - } - - private TestDescriptor findChildByDisplayName(TestDescriptor runnerDescriptor, String displayName) { - // @formatter:off - var children = runnerDescriptor.getChildren(); - return children - .stream() - .filter(where(TestDescriptor::getDisplayName, isEqual(displayName))) - .findAny() - .orElseThrow(() -> - new AssertionError(format("No child with display name \"{0}\" in {1}", displayName, children))); - // @formatter:on - } - - private TestDescriptor discoverTests(LauncherDiscoveryRequest discoveryRequest) { - return engine.discover(discoveryRequest, UniqueId.forEngine(engine.getId())); - } - - private Path getClasspathRoot(Class testClass) throws Exception { - var location = testClass.getProtectionDomain().getCodeSource().getLocation(); - return Paths.get(location.toURI()); - } - - private void assertYieldsNoDescriptors(Class testClass) { - var request = discoveryRequestForClass(testClass); - - assertYieldsNoDescriptors(request); - } - - private void assertYieldsNoDescriptors(LauncherDiscoveryRequest request) { - var engineDescriptor = discoverTests(request); - - assertThat(engineDescriptor.getChildren()).isEmpty(); - } - - private static void assertRunnerTestDescriptor(TestDescriptor runnerDescriptor, Class testClass) { - assertTrue(runnerDescriptor.isContainer()); - assertFalse(runnerDescriptor.isTest()); - assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); - assertClassSource(testClass, runnerDescriptor); - } - - private static void assertTestMethodDescriptor(TestDescriptor testMethodDescriptor, Class testClass, - String methodName, UniqueId uniqueContainerId) throws Exception { - assertTrue(testMethodDescriptor.isTest()); - assertFalse(testMethodDescriptor.isContainer()); - assertEquals(methodName, testMethodDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(uniqueContainerId, testClass, methodName), - testMethodDescriptor.getUniqueId()); - assertThat(testMethodDescriptor.getChildren()).isEmpty(); - assertMethodSource(testClass.getMethod(methodName), testMethodDescriptor); - } - - private static void assertContainerTestDescriptor(TestDescriptor containerDescriptor, Class suiteClass, - Class testClass) { - assertTrue(containerDescriptor.isContainer()); - assertFalse(containerDescriptor.isTest()); - assertEquals(testClass.getName(), containerDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass), - containerDescriptor.getUniqueId()); - assertClassSource(testClass, containerDescriptor); - } - - private static void assertInitializationError(TestDescriptor testDescriptor, Class failingClass, - Class testClass) { - assertTrue(testDescriptor.isTest()); - assertFalse(testDescriptor.isContainer()); - assertEquals("initializationError", testDescriptor.getDisplayName()); - assertEquals(VintageUniqueIdBuilder.uniqueIdForErrorInClass(testClass, failingClass), - testDescriptor.getUniqueId()); - assertThat(testDescriptor.getChildren()).isEmpty(); - assertClassSource(failingClass, testDescriptor); - } - - private static void assertClassSource(Class expectedClass, TestDescriptor testDescriptor) { - assertThat(testDescriptor.getSource()).containsInstanceOf(ClassSource.class); - var classSource = (ClassSource) testDescriptor.getSource().get(); - assertThat(classSource.getJavaClass()).isEqualTo(expectedClass); - } - - private static void assertMethodSource(Method expectedMethod, TestDescriptor testDescriptor) { - assertMethodSource(expectedMethod.getDeclaringClass(), expectedMethod, testDescriptor); - } - - private static void assertMethodSource(Class expectedClass, Method expectedMethod, - TestDescriptor testDescriptor) { - assertThat(testDescriptor.getSource()).containsInstanceOf(MethodSource.class); - var methodSource = (MethodSource) testDescriptor.getSource().get(); - assertThat(methodSource.getClassName()).isEqualTo(expectedClass.getName()); - assertThat(methodSource.getMethodName()).isEqualTo(expectedMethod.getName()); - assertThat(methodSource.getMethodParameterTypes()).isEqualTo( - ClassUtils.nullSafeToString(expectedMethod.getParameterTypes())); - } - - private static LauncherDiscoveryRequest discoveryRequestForClass(Class testClass) { - return request().selectors(selectClass(testClass)).build(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java deleted file mode 100644 index 66af815a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ /dev/null @@ -1,923 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.displayName; -import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; -import static org.junit.runner.Description.createSuiteDescription; -import static org.junit.runner.Description.createTestDescription; - -import java.math.BigDecimal; - -import junit.runner.Version; - -import org.assertj.core.api.Condition; -import org.junit.AssumptionViolatedException; -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; -import org.junit.vintage.engine.samples.junit3.JUnit3ParallelSuiteWithSubsuites; -import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites; -import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; -import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase; -import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; -import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.EnclosedWithParameterizedChildrenJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.IgnoredParameterizedTestCase; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithExceptionThrowingRunner; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithIgnoredJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit3SuiteWithSingleTestCase; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished; -import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithAssumptionFailureInBeforeClass; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorCollectorStoringMultipleFailures; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorInAfterClass; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorInBeforeClass; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithExceptionThrowingRunner; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithIndistinguishableOverloadedMethod; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames; -import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions; -import org.junit.vintage.engine.samples.junit4.MalformedJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; -import org.junit.vintage.engine.samples.junit4.ParameterizedTimingTestCase; -import org.junit.vintage.engine.samples.junit4.ParameterizedWithAfterParamFailureTestCase; -import org.junit.vintage.engine.samples.junit4.ParameterizedWithBeforeParamFailureTestCase; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithLifecycleMethods; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichIsIgnored; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; -import org.junit.vintage.engine.samples.spock.SpockTestCaseWithUnrolledAndRegularFeatureMethods; -import org.opentest4j.MultipleFailuresError; - -/** - * @since 4.12 - */ -class VintageTestEngineExecutionTests { - - @Test - void executesPlainJUnit4TestCaseWithSingleTestWhichFails() { - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("failingTest"), started()), // - event(test("failingTest"), - finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesPlainJUnit4TestCaseWithTwoTests() { - Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("failingTest"), started()), // - event(test("failingTest"), - finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // - event(test("successfulTest"), started()), // - event(test("successfulTest"), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesPlainJUnit4TestCaseWithFiveTests() { - Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("abortedTest"), started()), // - event(test("abortedTest"), - abortedWithReason(instanceOf(AssumptionViolatedException.class), - message("this test should be aborted"))), // - event(test("failingTest"), started()), // - event(test("failingTest"), - finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // - event(test("ignoredTest1_withoutReason"), skippedWithReason("")), // - event(test("ignoredTest2_withReason"), skippedWithReason("a custom reason")), // - event(test("successfulTest"), started()), // - event(test("successfulTest"), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesEnclosedJUnit4TestCase() { - Class testClass = EnclosedJUnit4TestCase.class; - Class nestedClass = EnclosedJUnit4TestCase.NestedClass.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(nestedClass), started()), // - event(test("successfulTest"), started()), // - event(test("successfulTest"), finishedSuccessfully()), // - event(test("failingTest"), started()), // - event(test("failingTest"), - finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // - event(container(nestedClass), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesEnclosedWithParameterizedChildrenJUnit4TestCase() { - Class testClass = EnclosedWithParameterizedChildrenJUnit4TestCase.class; - String commonNestedClassPrefix = EnclosedWithParameterizedChildrenJUnit4TestCase.class.getName() - + "$NestedTestCase"; - - execute(testClass).allEvents().debug().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(commonNestedClassPrefix), started()), // - event(container("[0]"), started()), // - event(test("test[0]"), started()), // - event(test("test[0]"), finishedSuccessfully()), // - event(container("[0]"), finishedSuccessfully()), // - event(container("[1]"), started()), // - event(test("test[1]"), started()), // - event(test("test[1]"), finishedSuccessfully()), // - event(container("[1]"), finishedSuccessfully()), // - event(container(commonNestedClassPrefix), finishedSuccessfully()), // - event(container(commonNestedClassPrefix), started()), // - event(container("[0]"), started()), // - event(test("test[0]"), started()), // - event(test("test[0]"), finishedSuccessfully()), // - event(container("[0]"), finishedSuccessfully()), // - event(container("[1]"), started()), // - event(test("test[1]"), started()), // - event(test("test[1]"), finishedSuccessfully()), // - event(container("[1]"), finishedSuccessfully()), // - event(container(commonNestedClassPrefix), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4SuiteWithJUnit3SuiteWithSingleTestCase() { - Class junit4SuiteClass = JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.class; - Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; - - execute(junit4SuiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(junit4SuiteClass), started()), // - event(container("TestSuite with 1 tests"), started()), // - event(container(testClass), started()), // - event(test("test"), started()), // - event(test("test"), - finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // - event(container(testClass), finishedSuccessfully()), // - event(container("TestSuite with 1 tests"), finishedSuccessfully()), // - event(container(junit4SuiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesMalformedJUnit4TestCase() { - Class testClass = MalformedJUnit4TestCase.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("initializationError"), started()), // - event(test("initializationError"), - finishedWithFailure(message(it -> it.contains("Method nonPublicTest() should be public")))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithErrorInBeforeClass() { - Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), - finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass() { - Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; - Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; - - execute(suiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteClass), started()), // - event(container(testClass), started()), // - event(container(testClass), - finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // - event(container(suiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass() { - Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; - Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; - Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; - - execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteOfSuiteClass), started()), // - event(container(suiteClass), started()), // - event(container(testClass), started()), // - event(container(testClass), - finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // - event(container(suiteClass), finishedSuccessfully()), // - event(container(suiteOfSuiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithAssumptionFailureInBeforeClass() { - Class testClass = JUnit4TestCaseWithAssumptionFailureInBeforeClass.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), - abortedWithReason(instanceOf(AssumptionViolatedException.class), message("assumption violated"))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass() { - Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class; - Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class; - Class testClass = JUnit4TestCaseWithAssumptionFailureInBeforeClass.class; - - execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteOfSuiteClass), started()), // - event(container(suiteClass), started()), // - event(container(testClass), started()), // - event(container(testClass), - abortedWithReason(instanceOf(AssumptionViolatedException.class), message("assumption violated"))), // - event(container(suiteClass), finishedSuccessfully()), // - event(container(suiteOfSuiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithErrorInAfterClass() { - Class testClass = JUnit4TestCaseWithErrorInAfterClass.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("failingTest"), started()), // - event(test("failingTest"), - finishedWithFailure(instanceOf(AssertionError.class), message("expected to fail"))), // - event(test("succeedingTest"), started()), // - event(test("succeedingTest"), finishedSuccessfully()), // - event(container(testClass), - finishedWithFailure(instanceOf(AssertionError.class), message("error in @AfterClass"))), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithOverloadedMethod() { - Class testClass = JUnit4TestCaseWithIndistinguishableOverloadedMethod.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[0]"), - started()), // - event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[0]"), - finishedWithFailure()), // - event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[1]"), - started()), // - event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[1]"), - finishedWithFailure()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesIgnoredJUnit4TestCase() { - Class testClass = IgnoredJUnit4TestCase.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), skippedWithReason("complete class is ignored")), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesEmptyIgnoredTestClass() { - Class testClass = EmptyIgnoredTestCase.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(test(testClass.getName()), skippedWithReason("empty")), // - event(engine(), finishedSuccessfully())); - } - - @Test - void reportsExecutionEventsAroundLifecycleMethods() { - Class testClass = PlainJUnit4TestCaseWithLifecycleMethods.class; - PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.clear(); - - var listener = new EngineExecutionListener() { - - @Override - public void executionStarted(TestDescriptor testDescriptor) { - PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( - "executionStarted:" + testDescriptor.getDisplayName()); - } - - @Override - public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { - PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( - "executionFinished:" + testDescriptor.getDisplayName()); - } - - @Override - public void executionSkipped(TestDescriptor testDescriptor, String reason) { - PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( - "executionSkipped:" + testDescriptor.getDisplayName()); - } - - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - } - - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - } - }; - - execute(testClass, listener); - - // @formatter:off - assertThat(PlainJUnit4TestCaseWithLifecycleMethods.EVENTS).containsExactly( - "executionStarted:JUnit Vintage", - "executionStarted:" + testClass.getSimpleName(), - "beforeClass", - "executionStarted:failingTest", - "before", - "failingTest", - "after", - "executionFinished:failingTest", - "executionSkipped:skippedTest", - "executionStarted:succeedingTest", - "before", - "succeedingTest", - "after", - "executionFinished:succeedingTest", - "afterClass", - "executionFinished:" + testClass.getSimpleName(), - "executionFinished:JUnit Vintage" - ); - // @formatter:on - } - - @Test - void executesJUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored() { - Class suiteClass = JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; - - execute(suiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteClass), started()), // - event(container(testClass), started()), // - event(test("ignoredTest"), skippedWithReason("ignored test")), // - event(container(testClass), finishedSuccessfully()), // - event(container(suiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase() { - Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.class; - Class suiteClass = JUnit4SuiteWithIgnoredJUnit4TestCase.class; - Class testClass = IgnoredJUnit4TestCase.class; - - execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteOfSuiteClass), started()), // - event(container(suiteClass), started()), // - event(container(testClass), skippedWithReason("complete class is ignored")), // - event(container(suiteClass), finishedSuccessfully()), // - event(container(suiteOfSuiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesParameterizedTestCase() { - Class testClass = ParameterizedTestCase.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container("[foo]"), started()), // - event(test("test[foo]"), started()), // - event(test("test[foo]"), finishedSuccessfully()), // - event(container("[foo]"), finishedSuccessfully()), // - event(container("[bar]"), started()), // - event(test("test[bar]"), started()), // - event(test("test[bar]"), - finishedWithFailure(instanceOf(AssertionError.class), message("expected:<[foo]> but was:<[bar]>"))), // - event(container("[bar]"), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesIgnoredParameterizedTestCase() { - Class testClass = IgnoredParameterizedTestCase.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container("[foo]"), started()), // - event(test("test[foo]"), skippedWithReason("")), // - event(container("[foo]"), finishedSuccessfully()), // - event(container("[bar]"), started()), // - event(test("test[bar]"), skippedWithReason("")), // - event(container("[bar]"), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesParameterizedTimingTestCase() { - assumeTrue(atLeastJUnit4_13(), "@BeforeParam and @AfterParam were introduced in JUnit 4.13"); - - Class testClass = ParameterizedTimingTestCase.class; - - var events = execute(testClass).allEvents().debug(); - - var firstParamStartedEvent = events.filter(event(container("[foo]"), started())::matches).findFirst() // - .orElseThrow(() -> new AssertionError("No start event for [foo]")); - var firstParamFinishedEvent = events.filter( - event(container("[foo]"), finishedSuccessfully())::matches).findFirst() // - .orElseThrow(() -> new AssertionError("No finish event for [foo]")); - var secondParamStartedEvent = events.filter(event(container("[bar]"), started())::matches).findFirst() // - .orElseThrow(() -> new AssertionError("No start event for [bar]")); - var secondParamFinishedEvent = events.filter( - event(container("[bar]"), finishedSuccessfully())::matches).findFirst() // - .orElseThrow(() -> new AssertionError("No finish event for [bar]")); - - assertThat(ParameterizedTimingTestCase.EVENTS.get("beforeParam(foo)")).isAfterOrEqualTo( - firstParamStartedEvent.getTimestamp()); - assertThat(ParameterizedTimingTestCase.EVENTS.get("afterParam(foo)")).isBeforeOrEqualTo( - firstParamFinishedEvent.getTimestamp()); - assertThat(ParameterizedTimingTestCase.EVENTS.get("beforeParam(bar)")).isAfterOrEqualTo( - secondParamStartedEvent.getTimestamp()); - assertThat(ParameterizedTimingTestCase.EVENTS.get("afterParam(bar)")).isBeforeOrEqualTo( - secondParamFinishedEvent.getTimestamp()); - } - - @Test - void executesParameterizedWithAfterParamFailureTestCase() { - assumeTrue(atLeastJUnit4_13(), "@AfterParam was introduced in JUnit 4.13"); - - Class testClass = ParameterizedWithAfterParamFailureTestCase.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container("[foo]"), started()), // - event(test("test[foo]"), started()), // - event(test("test[foo]"), finishedSuccessfully()), // - event(container("[foo]"), finishedWithFailure(instanceOf(AssertionError.class))), // - event(container("[bar]"), started()), // - event(test("test[bar]"), started()), // - event(test("test[bar]"), - finishedWithFailure(instanceOf(AssertionError.class), message("expected:<[foo]> but was:<[bar]>"))), // - event(container("[bar]"), finishedWithFailure(instanceOf(AssertionError.class))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesParameterizedWithBeforeParamFailureTestCase() { - assumeTrue(atLeastJUnit4_13(), "@BeforeParam was introduced in JUnit 4.13"); - - Class testClass = ParameterizedWithBeforeParamFailureTestCase.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container("[foo]"), started()), // - event(container("[foo]"), finishedWithFailure(instanceOf(AssertionError.class))), // - event(container("[bar]"), started()), // - event(container("[bar]"), finishedWithFailure(instanceOf(AssertionError.class))), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithExceptionThrowingRunner() { - Class testClass = JUnit4TestCaseWithExceptionThrowingRunner.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(test(testClass.getName()), started()), // - event(test(testClass.getName()), finishedWithFailure()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4SuiteWithExceptionThrowingRunner() { - Class testClass = JUnit4SuiteWithExceptionThrowingRunner.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container(testClass), finishedWithFailure()), // - event(engine(), finishedSuccessfully())); - } - - public static class DynamicSuiteRunner extends Runner { - - private final Class testClass; - - public DynamicSuiteRunner(Class testClass) { - this.testClass = testClass; - } - - @Override - public Description getDescription() { - return createSuiteDescription(testClass); - } - - @Override - public void run(RunNotifier notifier) { - var dynamicDescription = createTestDescription(testClass, "dynamicTest"); - notifier.fireTestStarted(dynamicDescription); - notifier.fireTestFinished(dynamicDescription); - } - - } - - @RunWith(DynamicSuiteRunner.class) - public static class DynamicTestClass { - } - - @Test - void reportsDynamicTestsForUnknownDescriptions() { - Class testClass = DynamicTestClass.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(test(testClass.getName()), started()), // - event(dynamicTestRegistered("dynamicTest")), // - event(test("dynamicTest"), started()), // - event(test("dynamicTest"), finishedSuccessfully()), // - event(test(testClass.getName()), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - public static class DynamicAndStaticChildrenRunner extends Runner { - - private final Class testClass; - - public DynamicAndStaticChildrenRunner(Class testClass) { - this.testClass = testClass; - } - - @Override - public Description getDescription() { - var suiteDescription = createSuiteDescription(testClass); - suiteDescription.addChild(createTestDescription(testClass, "staticTest")); - return suiteDescription; - } - - @Override - public void run(RunNotifier notifier) { - var staticDescription = getDescription().getChildren().get(0); - notifier.fireTestStarted(staticDescription); - notifier.fireTestFinished(staticDescription); - var dynamicDescription = createTestDescription(testClass, "dynamicTest"); - notifier.fireTestStarted(dynamicDescription); - notifier.fireTestFinished(dynamicDescription); - } - - } - - @RunWith(DynamicAndStaticChildrenRunner.class) - public static class DynamicAndStaticTestClass { - } - - @RunWith(Suite.class) - @SuiteClasses(DynamicAndStaticTestClass.class) - public static class SuiteWithDynamicAndStaticTestClass { - } - - @Test - void reportsIntermediateContainersFinishedAfterTheirDynamicChildren() { - Class suiteClass = SuiteWithDynamicAndStaticTestClass.class; - Class testClass = DynamicAndStaticTestClass.class; - - execute(suiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteClass.getName()), started()), // - event(container(testClass.getName()), started()), // - event(test("staticTest"), started()), // - event(test("staticTest"), finishedSuccessfully()), // - event(dynamicTestRegistered("dynamicTest")), // - event(test("dynamicTest"), started()), // - event(test("dynamicTest"), finishedSuccessfully()), // - event(container(testClass.getName()), finishedSuccessfully()), // - event(container(suiteClass.getName()), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - public static class MisbehavingChildlessRunner extends Runner { - - private final Class testClass; - - public MisbehavingChildlessRunner(Class testClass) { - this.testClass = testClass; - } - - @Override - public Description getDescription() { - return createSuiteDescription(testClass); - } - - @Override - public void run(RunNotifier notifier) { - notifier.fireTestStarted(createTestDescription(testClass, "doesNotExist")); - } - - } - - @RunWith(MisbehavingChildlessRunner.class) - public static class MisbehavingChildTestClass { - - } - - @Test - void ignoreEventsForUnknownDescriptionsByMisbehavingChildlessRunner() { - Class testClass = MisbehavingChildTestClass.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(test(testClass.getName()), started()), // - event(dynamicTestRegistered("doesNotExist")), // - event(test("doesNotExist"), started()), // - event(test(testClass.getName()), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithRunnerWithCustomUniqueIds() { - Class testClass = JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(uniqueIdSubstring(testClass.getName()), started()), // - event(uniqueIdSubstring(testClass.getName()), finishedWithFailure()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithErrorCollectorStoringMultipleFailures() { - Class testClass = JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("example"), started()), // - event(test("example"), // - finishedWithFailure(// - instanceOf(MultipleFailuresError.class), // - new Condition<>(throwable -> ((MultipleFailuresError) throwable).getFailures().size() == 3, - "MultipleFailuresError must contain 3 failures"), // - new Condition<>(throwable -> ((MultipleFailuresError) throwable).getSuppressed().length == 3, - "MultipleFailuresError must contain 3 suppressed exceptions")// - )), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished() { - Class testClass = JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; - - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("testWithMissingEvents"), started()), // - event(test("testWithMissingEvents"), finishedWithFailure()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished() { - Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; - Class firstTestClass = JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; - Class secondTestClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - - execute(suiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteClass), started()), // - event(container(firstTestClass), started()), // - event(test("testWithMissingEvents"), started()), // - event(test("testWithMissingEvents"), finishedWithFailure()), // - event(container(firstTestClass), finishedSuccessfully()), // - event(container(secondTestClass), started()), // - event(test("failingTest"), started()), // - event(test("failingTest"), finishedWithFailure()), // - event(container(secondTestClass), finishedSuccessfully()), // - event(container(suiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesCompletelyDynamicTestCaseDiscoveredByUniqueId() { - Class testClass = CompletelyDynamicTestCase.class; - var request = LauncherDiscoveryRequestBuilder.request().selectors( - selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass))).build(); - - execute(request).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(displayName(testClass.getSimpleName()), started()), // - event(dynamicTestRegistered("Test #0")), // - event(test("Test #0"), started()), // - event(test("Test #0"), finishedSuccessfully()), // - event(displayName(testClass.getSimpleName()), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit3ParallelSuiteWithSubsuites() { - var suiteClass = JUnit3ParallelSuiteWithSubsuites.class; - var results = execute(suiteClass); - results.containerEvents() // - .assertStatistics(stats -> stats.started(4).dynamicallyRegistered(0).finished(4).succeeded(4)) // - .assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteClass), started()), // - event(container("Case"), started()), // - event(container("Case")), // - event(container("Case")), // - event(container("Case"), finishedSuccessfully()), // - event(container(suiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - results.testEvents() // - .assertStatistics(stats -> stats.started(2).dynamicallyRegistered(0).finished(2).succeeded(2)) // - .assertEventsMatchExactly( // - event(test("hello"), started()), // - event(test("hello")), // - event(test("hello")), // - event(test("hello"), finishedSuccessfully())); - } - - @Test - void executesJUnit3SuiteWithSubsuites() { - var suiteClass = JUnit3SuiteWithSubsuites.class; - execute(suiteClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(suiteClass), started()), // - event(container("Case1"), started()), // - event(test("hello"), started()), // - event(test("hello"), finishedSuccessfully()), // - event(container("Case1"), finishedSuccessfully()), // - event(container("Case2"), started()), // - event(test("hello"), started()), // - event(test("hello"), finishedSuccessfully()), // - event(container("Case2"), finishedSuccessfully()), // - event(container(suiteClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesJUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions() { - Class testClass = JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.class; - execute(testClass).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(container("1st"), started()), // - event(test("0"), skippedWithReason(__ -> true)), // - event(test("1"), started()), // - event(test("1"), finishedSuccessfully()), // - event(container("1st"), finishedSuccessfully()), // - event(container("2nd"), started()), // - event(test("0"), skippedWithReason(__ -> true)), // - event(test("1"), started()), // - event(test("1"), finishedSuccessfully()), // - event(container("2nd"), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesUnrolledSpockFeatureMethod() { - Class testClass = SpockTestCaseWithUnrolledAndRegularFeatureMethods.class; - var request = LauncherDiscoveryRequestBuilder.request().selectors( - selectMethod(testClass, "unrolled feature for #input")).build(); - execute(request).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(uniqueIdSubstring(testClass.getName()), started()), // - event(dynamicTestRegistered("unrolled feature for 23")), // - event(test("unrolled feature for 23"), started()), // - event(test("unrolled feature for 23"), finishedWithFailure()), // - event(dynamicTestRegistered("unrolled feature for 42")), // - event(test("unrolled feature for 42"), started()), // - event(test("unrolled feature for 42"), finishedSuccessfully()), // - event(uniqueIdSubstring(testClass.getName()), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - @Test - void executesRegularSpockFeatureMethod() { - Class testClass = SpockTestCaseWithUnrolledAndRegularFeatureMethods.class; - var request = LauncherDiscoveryRequestBuilder.request().selectors(selectMethod(testClass, "regular")).build(); - execute(request).allEvents().assertEventsMatchExactly( // - event(engine(), started()), // - event(container(testClass), started()), // - event(test("regular"), started()), // - event(test("regular"), finishedSuccessfully()), // - event(container(testClass), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - } - - private static EngineExecutionResults execute(Class testClass) { - return execute(request(testClass)); - } - - private static EngineExecutionResults execute(LauncherDiscoveryRequest request) { - return EngineTestKit.execute(new VintageTestEngine(), request); - } - - private static void execute(Class testClass, EngineExecutionListener listener) { - TestEngine testEngine = new VintageTestEngine(); - var discoveryRequest = request(testClass); - var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId())); - testEngine.execute( - new ExecutionRequest(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters())); - } - - private static LauncherDiscoveryRequest request(Class testClass) { - return LauncherDiscoveryRequestBuilder.request().selectors(selectClass(testClass)).build(); - } - - private static boolean atLeastJUnit4_13() { - return JUnit4VersionCheck.parseVersion(Version.id()).compareTo(new BigDecimal("4.13")) >= 0; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java deleted file mode 100644 index 62f9f6c8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; - -/** - * Test suite for the {@link VintageTestEngine}. - * - *

Logging Configuration

- * - *

In order for our log4j2 configuration to be used in an IDE, you must - * set the following system property before running any tests — for - * example, in Run Configurations in Eclipse. - * - *

- * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
- * 
- * - * @since 4.12 - */ -@Suite -@SelectPackages("org.junit.vintage.engine") -@IncludeClassNamePatterns(".*Tests?") -@IncludeEngines("junit-jupiter") -class VintageTestEngineTestSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java deleted file mode 100644 index 88fdd915..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine; - -import org.junit.platform.engine.UniqueId; -import org.junit.vintage.engine.descriptor.VintageTestDescriptor; - -/** - * Test data builder for building unique IDs for the {@link VintageTestEngine}. - * - * Used to decouple tests from concrete unique ID strings. - * - * @since 4.12 - */ -public class VintageUniqueIdBuilder { - - public static UniqueId uniqueIdForErrorInClass(Class clazz, Class failingClass) { - return uniqueIdForClasses(clazz).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, - "initializationError(" + failingClass.getName() + ")"); - } - - public static UniqueId uniqueIdForClass(Class clazz) { - return uniqueIdForClasses(clazz); - } - - public static UniqueId uniqueIdForClasses(Class clazz, Class... clazzes) { - var uniqueId = uniqueIdForClass(clazz.getName()); - for (var each : clazzes) { - uniqueId = uniqueId.append(VintageTestDescriptor.SEGMENT_TYPE_TEST, each.getName()); - } - return uniqueId; - } - - public static UniqueId uniqueIdForClass(String fullyQualifiedClassName) { - var containerId = engineId(); - return containerId.append(VintageTestDescriptor.SEGMENT_TYPE_RUNNER, fullyQualifiedClassName); - } - - public static UniqueId uniqueIdForMethod(Class testClass, String methodName) { - return uniqueIdForClass(testClass).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, - methodValue(testClass, methodName)); - } - - private static String methodValue(Class testClass, String methodName) { - return methodName + "(" + testClass.getName() + ")"; - } - - public static UniqueId uniqueIdForMethod(Class testClass, String methodName, String index) { - return uniqueIdForClass(testClass).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, - methodValue(testClass, methodName) + "[" + index + "]"); - } - - public static UniqueId uniqueIdForMethod(UniqueId containerId, Class testClass, String methodName) { - return containerId.append(VintageTestDescriptor.SEGMENT_TYPE_TEST, methodValue(testClass, methodName)); - } - - public static UniqueId engineId() { - return UniqueId.forEngine(VintageTestDescriptor.ENGINE_ID); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java deleted file mode 100644 index 10247cb7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.util.stream.Stream; - -import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; -import org.junit.platform.commons.util.ClassFilter; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.runner.Description; -import org.junit.vintage.engine.discovery.IsPotentialJUnit4TestClass; - -class DescriptionUtilsTests { - - @SuppressWarnings("deprecation") - AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true); - - @TestFactory - Stream computedMethodNameCorrectly() { - var classFilter = ClassFilter.of(new IsPotentialJUnit4TestClass()); - var testClasses = ReflectionUtils.findAllClassesInPackage("org.junit.vintage.engine.samples", classFilter); - return testClasses.stream().flatMap(this::toDynamicTests); - } - - private Stream toDynamicTests(Class testClass) { - try { - var runner = builder.runnerForClass(testClass); - return toDynamicTests(Stream.of(runner.getDescription())); - } - catch (Throwable throwable) { - throw new RuntimeException(throwable); - } - } - - Stream toDynamicTests(Stream children) { - return children.map(description -> description.isTest() // - ? toDynamicTest(description, "child: " + description.toString()) // - : dynamicContainer("class: " + description.toString(), Stream.concat( // - Stream.of(toDynamicTest(description, "self")), // - toDynamicTests(description.getChildren().stream())))); - } - - private DynamicTest toDynamicTest(Description description, String displayName) { - return dynamicTest(displayName, - () -> assertEquals(description.getMethodName(), DescriptionUtils.getMethodName(description))); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java deleted file mode 100644 index 572b4e7f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.runner.Description; -import org.junit.runner.manipulation.Filter; - -/** - * @since 5.5 - */ -class OrFilterTests { - - @Test - void exceptionWithoutAnyFilters() { - var actual = assertThrows(PreconditionViolationException.class, () -> new OrFilter(Set.of())); - assertEquals("filters must not be empty", actual.getMessage()); - } - - @Test - void evaluatesSingleFilter() { - var filter = mockFilter("foo", true); - - var orFilter = new OrFilter(Set.of(filter)); - - assertEquals("foo", orFilter.describe()); - - var description = Description.createTestDescription(getClass(), "evaluatesSingleFilter"); - assertTrue(orFilter.shouldRun(description)); - - verify(filter).shouldRun(same(description)); - } - - @Test - void evaluatesMultipleFilters() { - var filter1 = mockFilter("foo", false); - var filter2 = mockFilter("bar", true); - - var orFilter = new OrFilter(List.of(filter1, filter2)); - - assertEquals("foo OR bar", orFilter.describe()); - - var description = Description.createTestDescription(getClass(), "evaluatesMultipleFilters"); - assertTrue(orFilter.shouldRun(description)); - - verify(filter1).shouldRun(same(description)); - verify(filter2).shouldRun(same(description)); - } - - private Filter mockFilter(String description, boolean result) { - var filter = mock(Filter.class); - when(filter.describe()).thenReturn(description); - when(filter.shouldRun(any())).thenReturn(result); - return filter; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java deleted file mode 100644 index 8211f759..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.runner.Description; -import org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase; - -/** - * @since 5.6 - */ -class TestSourceProviderTests { - - @Test - void findsInheritedMethod() { - var description = Description.createTestDescription(ConcreteJUnit4TestCase.class, "theTest"); - - var source = new TestSourceProvider().findTestSource(description); - assertThat(source).isInstanceOf(MethodSource.class); - - var methodSource = (MethodSource) source; - assertEquals(ConcreteJUnit4TestCase.class.getName(), methodSource.getClassName()); - assertEquals("theTest", methodSource.getMethodName()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java deleted file mode 100644 index 58be5a83..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.descriptor; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.UniqueId; -import org.junit.runner.Description; -import org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase; - -class VintageTestDescriptorTests { - - private static final UniqueId uniqueId = UniqueId.forEngine("vintage"); - - @Test - void legacyReportingNameUsesClassName() { - var description = Description.createSuiteDescription(ConcreteJUnit4TestCase.class); - var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); - - assertEquals("org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase", - testDescriptor.getLegacyReportingName()); - } - - @Test - void legacyReportingNameUsesMethodName() { - var description = Description.createTestDescription(ConcreteJUnit4TestCase.class, "legacyTest"); - var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); - - assertEquals("legacyTest", testDescriptor.getLegacyReportingName()); - } - - @Test - void legacyReportingNameFallbackToDisplayName() { - var suiteName = "Legacy Suite"; - var description = Description.createSuiteDescription(suiteName); - var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); - - assertEquals(testDescriptor.getDisplayName(), testDescriptor.getLegacyReportingName()); - assertEquals(suiteName, testDescriptor.getLegacyReportingName()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java deleted file mode 100644 index 442daa53..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class IsPotentialJUnit4TestClassTests { - - private final IsPotentialJUnit4TestClass isPotentialJUnit4TestClass = new IsPotentialJUnit4TestClass(); - - @Test - void staticMemberClass() { - assertTrue(isPotentialJUnit4TestClass.test(Foo.class)); - } - - public static class Foo { - } - - @Test - void nonPublicClass() { - assertFalse(isPotentialJUnit4TestClass.test(Bar.class)); - } - - static class Bar { - } - - @Test - void abstractClass() { - assertFalse(isPotentialJUnit4TestClass.test(Baz.class)); - } - - public static abstract class Baz { - } - - @Test - void anonymousClass() { - var foo = new Foo() { - }; - - assertFalse(isPotentialJUnit4TestClass.test(foo.getClass())); - } - - public class FooBaz { - } - - @Test - void publicInnerClass() { - assertFalse(isPotentialJUnit4TestClass.test(FooBaz.class)); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java deleted file mode 100644 index 6b64d1af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.mockito.Mockito.mock; - -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; -import org.junit.vintage.engine.VintageUniqueIdBuilder; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; -import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCaseWithNotFilterableRunner; -import org.junit.vintage.engine.samples.junit4.NotFilterableRunner; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; - -/** - * Tests for {@link RunnerTestDescriptorPostProcessor}. - * - * @since 5.5 - */ -@TrackLogRecords -class RunnerTestDescriptorPostProcessorTests { - - @Test - void doesNotLogAnythingForFilterableRunner(LogRecordListener listener) { - resolve(selectMethod(PlainJUnit4TestCaseWithFiveTestMethods.class, "successfulTest")); - - assertThat(listener.stream(RunnerTestDescriptor.class)).isEmpty(); - } - - @Test - void doesNotLogAnythingForNonFilterableRunnerIfNoFiltersAreToBeApplied(LogRecordListener listener) { - resolve(selectClass(IgnoredJUnit4TestCase.class)); - - assertThat(listener.stream(RunnerTestDescriptor.class)).isEmpty(); - } - - @Test - void logsWarningOnNonFilterableRunner(LogRecordListener listener) { - Class testClass = IgnoredJUnit4TestCaseWithNotFilterableRunner.class; - - resolve(selectMethod(testClass, "someTest")); - - // @formatter:off - assertThat(listener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) - .containsOnlyOnce("Runner " + NotFilterableRunner.class.getName() - + " (used on class " + testClass.getName() + ") does not support filtering" - + " and will therefore be run completely."); - // @formatter:on - } - - private void resolve(DiscoverySelector selector) { - var request = LauncherDiscoveryRequestBuilder.request().selectors(selector).listeners( - mock(LauncherDiscoveryListener.class)).build(); - TestDescriptor engineDescriptor = new VintageDiscoverer().discover(request, VintageUniqueIdBuilder.engineId()); - var runnerTestDescriptor = (RunnerTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); - new RunnerTestDescriptorPostProcessor().applyFiltersAndCreateDescendants(runnerTestDescriptor); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java deleted file mode 100644 index 754c34b0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; -import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.vintage.engine.VintageUniqueIdBuilder.engineId; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.SelectorResolutionResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.PackageNameFilter; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.vintage.engine.VintageUniqueIdBuilder; -import org.junit.vintage.engine.samples.junit3.AbstractJUnit3TestCase; -import org.junit.vintage.engine.samples.junit4.AbstractJunit4TestCaseWithConstructorParameter; -import org.mockito.ArgumentCaptor; - -/** - * Tests for {@link VintageDiscoverer}. - * - * @since 4.12 - */ -class VintageDiscovererTests { - - @Test - void classNameFilterExcludesClass() { - // @formatter:off - EngineDiscoveryRequest request = request() - .selectors(selectClass(Foo.class), selectClass(Bar.class)) - .filters(ClassNameFilter.includeClassNamePatterns(".*Foo")) - .build(); - // @formatter:on - - var testDescriptor = discover(request); - - assertThat(testDescriptor.getChildren()).hasSize(1); - assertThat(getOnlyElement(testDescriptor.getChildren()).getUniqueId().toString()).contains(Foo.class.getName()); - } - - @Test - void packageNameFilterExcludesClasses() { - // @formatter:off - EngineDiscoveryRequest request = request() - .selectors(selectClass(Foo.class), selectClass(Bar.class)) - .filters(PackageNameFilter.excludePackageNames("org.junit.vintage.engine.discovery")) - .build(); - // @formatter:on - - var testDescriptor = discover(request); - - assertThat(testDescriptor.getChildren()).isEmpty(); - } - - @Test - void doesNotResolveAbstractJUnit3Classes() { - doesNotResolve(selectClass(AbstractJUnit3TestCase.class)); - } - - @Test - void doesNotResolveAbstractJUnit4Classes() { - doesNotResolve(selectClass(AbstractJunit4TestCaseWithConstructorParameter.class)); - } - - @Test - void failsToResolveUnloadableTestClass() { - var uniqueId = VintageUniqueIdBuilder.uniqueIdForClass("foo.bar.UnknownClass"); - - doesNotResolve(selectUniqueId(uniqueId), result -> { - assertThat(result.getStatus()).isEqualTo(FAILED); - assertThat(result.getThrowable().get()).hasMessageContaining("Unknown class"); - }); - } - - @Test - void ignoresUniqueIdsOfOtherEngines() { - doesNotResolve(selectUniqueId(UniqueId.forEngine("someEngine"))); - } - - private void doesNotResolve(DiscoverySelector selector) { - doesNotResolve(selector, result -> assertThat(result.getStatus()).isEqualTo(UNRESOLVED)); - } - - private void doesNotResolve(DiscoverySelector selector, Consumer resultCheck) { - var discoveryListener = mock(LauncherDiscoveryListener.class); - var request = request() // - .selectors(selector) // - .listeners(discoveryListener) // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .build(); - - var testDescriptor = discover(request); - - assertThat(testDescriptor.getChildren()).isEmpty(); - var resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); - verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-vintage")), eq(selector), - resultCaptor.capture()); - resultCheck.accept(resultCaptor.getValue()); - } - - private TestDescriptor discover(EngineDiscoveryRequest request) { - return new VintageDiscoverer().discover(request, engineId()); - } - - public static class Foo { - - @org.junit.Test - public void test() { - } - - } - - public static class Bar { - - @org.junit.Test - public void test() { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java deleted file mode 100644 index 69272fd5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.execution; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.runner.Description.createTestDescription; -import static org.junit.vintage.engine.VintageUniqueIdBuilder.engineId; -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_DYNAMIC; -import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; - -import org.junit.jupiter.api.Test; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; -import org.junit.vintage.engine.descriptor.VintageTestDescriptor; -import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; - -/** - * @since 4.12 - */ -class TestRunTests { - - @Test - void returnsEmptyOptionalForUnknownDescriptions() throws Exception { - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); - var unknownDescription = createTestDescription(testClass, "dynamicTest"); - - var testRun = new TestRun(runnerTestDescriptor); - var testDescriptor = testRun.lookupNextTestDescriptor(unknownDescription); - - assertThat(testDescriptor).isEmpty(); - } - - @Test - void registersDynamicTestDescriptors() throws Exception { - Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; - var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); - var dynamicTestId = runnerId.append(SEGMENT_TYPE_DYNAMIC, "dynamicTest"); - var dynamicDescription = createTestDescription(testClass, "dynamicTest"); - var dynamicTestDescriptor = new VintageTestDescriptor(dynamicTestId, dynamicDescription, null); - - var testRun = new TestRun(runnerTestDescriptor); - testRun.registerDynamicTest(dynamicTestDescriptor); - - assertThat(testRun.lookupNextTestDescriptor(dynamicDescription)).contains(dynamicTestDescriptor); - assertTrue(testRun.isDescendantOfRunnerTestDescriptor(dynamicTestDescriptor)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java deleted file mode 100644 index 3ea9cca2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.support; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.runner.Description.createTestDescription; - -import java.util.logging.Level; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; - -/** - * Tests for {@link UniqueIdReader}. - * - * @since 4.12 - */ -@TrackLogRecords -class UniqueIdReaderTests { - - @Test - void readsUniqueId(LogRecordListener listener) { - var description = createTestDescription("ClassName", "methodName", "uniqueId"); - - var uniqueId = new UniqueIdReader().apply(description); - - assertEquals("uniqueId", uniqueId); - assertThat(listener.stream(UniqueIdReader.class)).isEmpty(); - } - - @Test - void returnsDisplayNameWhenUniqueIdCannotBeRead(LogRecordListener listener) { - var description = createTestDescription("ClassName", "methodName", "uniqueId"); - assertEquals("methodName(ClassName)", description.getDisplayName()); - - var uniqueId = new UniqueIdReader("wrongFieldName").apply(description); - - assertEquals(description.getDisplayName(), uniqueId); - - var logRecord = listener.stream(UniqueIdReader.class, Level.WARNING).findFirst(); - assertThat(logRecord).isPresent(); - assertThat(logRecord.get().getMessage()).isEqualTo( - "Could not read unique ID for Description; using display name instead: " + description.getDisplayName()); - assertThat(logRecord.get().getThrown()).isInstanceOf(NoSuchFieldException.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java deleted file mode 100644 index 6ec8e41a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.support; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.io.ByteArrayInputStream; -import java.io.InvalidObjectException; -import java.io.ObjectInputStream; -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.util.Base64; - -import org.junit.jupiter.api.Test; - -/** - * @since 4.12 - */ -class UniqueIdStringifierTests { - - @Test - void returnsReadableStringForKnownTypes() { - var stringifier = new UniqueIdStringifier(); - - assertEquals("foo", stringifier.apply("foo")); - assertEquals("42", stringifier.apply(42)); - assertEquals("42", stringifier.apply(42L)); - assertEquals("42.23", stringifier.apply(42.23d)); - } - - @Test - void serializesUnknownTypes() throws Exception { - var stringifier = new UniqueIdStringifier(); - - var serialized = stringifier.apply(new MyCustomId(42)); - - var deserializedObject = deserialize(decodeBase64(serialized)); - assertThat(deserializedObject).isInstanceOf(MyCustomId.class); - assertEquals(42, ((MyCustomId) deserializedObject).getValue()); - } - - @Test - void usesToStringWhenSerializationFails() { - var stringifier = new UniqueIdStringifier(); - var serialized = stringifier.apply(new ClassWithErroneousSerialization()); - - var deserializedString = new String(decodeBase64(serialized), UniqueIdStringifier.CHARSET); - - assertEquals("value from toString()", deserializedString); - } - - private byte[] decodeBase64(String value) { - return Base64.getDecoder().decode(value.getBytes(UniqueIdStringifier.CHARSET)); - } - - private Object deserialize(byte[] bytes) throws Exception { - try (var inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes))) { - return inputStream.readObject(); - } - } - - private static class MyCustomId implements Serializable { - - private static final long serialVersionUID = 1L; - - private final int value; - - MyCustomId(int value) { - this.value = value; - } - - int getValue() { - return value; - } - - } - - private static class ClassWithErroneousSerialization implements Serializable { - - private static final long serialVersionUID = 1L; - - Object writeReplace() throws ObjectStreamException { - throw new InvalidObjectException("failed on purpose"); - } - - @Override - public String toString() { - return "value from toString()"; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml deleted file mode 100644 index 063e4159..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy deleted file mode 100644 index 8abfe8d4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy +++ /dev/null @@ -1,20 +0,0 @@ -package org.junit.vintage.engine.samples.spock - -import spock.lang.Specification -import spock.lang.Unroll - -class SpockTestCaseWithUnrolledAndRegularFeatureMethods extends Specification { - - @Unroll - def "unrolled feature for #input"() { - expect: - input == 42 - where: - input << [23, 42] - } - - def "regular"() { - expect: - true - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java deleted file mode 100644 index cad6044a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples; - -/** - * @since 4.12 - */ -public class PlainOldJavaClassWithoutAnyTestsTestCase { - - public void doSomething() { - // no-op - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java deleted file mode 100644 index ec7f01e9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit3; - -import junit.framework.TestCase; - -import org.junit.Assert; - -/** - * @since 4.12 - */ -public abstract class AbstractJUnit3TestCase extends TestCase { - - public void test() { - Assert.fail("this test should not be run"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java deleted file mode 100644 index 0171e86e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit3; - -import junit.extensions.ActiveTestSuite; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -public class JUnit3ParallelSuiteWithSubsuites extends TestCase { - private final String arg; - - public JUnit3ParallelSuiteWithSubsuites(String name, String arg) { - super(name); - this.arg = arg; - } - - public void hello() { - assertNotNull(arg); - } - - public static TestSuite suite() { - TestSuite root = new ActiveTestSuite("allTests"); - var case1 = new TestSuite("Case1"); - case1.addTest(new JUnit3ParallelSuiteWithSubsuites("hello", "world")); - root.addTest(case1); - var case2 = new TestSuite("Case2"); - case2.addTest(new JUnit3ParallelSuiteWithSubsuites("hello", "WORLD")); - root.addTest(case2); - return root; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java deleted file mode 100644 index a253e2f4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit3; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * @since 4.12 - */ -public class JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails extends TestCase { - - public static junit.framework.Test suite() { - var suite = new TestSuite(); - suite.addTestSuite(PlainJUnit3TestCaseWithSingleTestWhichFails.class); - return suite; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java deleted file mode 100644 index cb070d77..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit3; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -public class JUnit3SuiteWithSubsuites extends TestCase { - private final String arg; - - public JUnit3SuiteWithSubsuites(String name, String arg) { - super(name); - this.arg = arg; - } - - public void hello() { - assertNotNull(arg); - } - - public static TestSuite suite() { - var root = new TestSuite("allTests"); - var case1 = new TestSuite("Case1"); - case1.addTest(new JUnit3SuiteWithSubsuites("hello", "world")); - root.addTest(case1); - var case2 = new TestSuite("Case2"); - case2.addTest(new JUnit3SuiteWithSubsuites("hello", "WORLD")); - root.addTest(case2); - return root; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java deleted file mode 100644 index 17918c65..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit3; - -import junit.framework.TestCase; - -import org.junit.Assert; - -/** - * @since 4.12 - */ -public class PlainJUnit3TestCaseWithSingleTestWhichFails extends TestCase { - - public void test() { - Assert.fail("this test should fail"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java deleted file mode 100644 index 6c18d8fb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.Test; - -public abstract class AbstractJUnit4TestCase { - - @Test - public void theTest() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java deleted file mode 100644 index b25e1180..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.Test; - -public abstract class AbstractJunit4TestCaseWithConstructorParameter { - - public AbstractJunit4TestCaseWithConstructorParameter(int parameter) { - - } - - @Test - public void test() { - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java deleted file mode 100644 index 9cc57948..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -/** - * @since 4.12 - */ -public class Categories { - - public interface Plain { - } - - public interface Failing { - } - - public interface Skipped { - } - - public interface SkippedWithReason extends Skipped { - } - - public interface Successful { - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java deleted file mode 100644 index 2331a56b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; - -/** - * Simulates a Spock 1.x test with only {@code @Unroll} feature methods. - */ -@RunWith(DynamicRunner.class) -@ChildCount(1) -public class CompletelyDynamicTestCase { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java deleted file mode 100644 index e659b595..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -public class ConcreteJUnit4TestCase extends AbstractJUnit4TestCase { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java deleted file mode 100644 index 47400e94..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static java.util.stream.IntStream.range; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.junit.runner.Description; -import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; - -/** - * @since 5.1 - */ -abstract class ConfigurableRunner extends Runner { - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - public @interface ChildCount { - - int value(); - - } - - protected final Class testClass; - protected final List filteredChildren = new ArrayList<>(); - - ConfigurableRunner(Class testClass) { - this.testClass = testClass; - var childCountAnnotation = testClass.getAnnotation(ChildCount.class); - int childCount = Optional.ofNullable(childCountAnnotation).map(ChildCount::value).orElse(0); - // @formatter:off - range(0, childCount) - .mapToObj(index -> Description.createTestDescription(testClass, "Test #" + index)) - .forEach(filteredChildren::add); - // @formatter:on - } - - @Override - public Description getDescription() { - var suiteDescription = Description.createSuiteDescription(testClass); - filteredChildren.forEach(suiteDescription::addChild); - return suiteDescription; - } - - @Override - public void run(RunNotifier notifier) { - filteredChildren.forEach(child -> { - notifier.fireTestStarted(child); - notifier.fireTestFinished(child); - }); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java deleted file mode 100644 index 20f3238a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.Description; -import org.junit.runner.manipulation.Filter; -import org.junit.runner.manipulation.Filterable; -import org.junit.runner.manipulation.NoTestsRemainException; - -public class DynamicRunner extends ConfigurableRunner implements Filterable { - - public DynamicRunner(Class testClass) { - super(testClass); - } - - @Override - public Description getDescription() { - return Description.createSuiteDescription(testClass); - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - filteredChildren.removeIf(each -> !filter.shouldRun(each)); - if (filteredChildren.isEmpty()) { - throw new NoTestsRemainException(); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java deleted file mode 100644 index 69f53e2d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.Ignore; - -@Ignore("empty") -public class EmptyIgnoredTestCase { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java deleted file mode 100644 index dec70cc9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; - -/** - * @since 4.12 - */ -@RunWith(Enclosed.class) -public class EnclosedJUnit4TestCase { - - @Category(Categories.Plain.class) - public static class NestedClass { - - @Test - @Category(Categories.Failing.class) - public void failingTest() { - fail("this test should fail"); - } - - @Test - public void successfulTest() { - assertEquals(3, 1 + 2); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java deleted file mode 100644 index dce4f50c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import java.util.Arrays; -import java.util.Collection; - -import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -// Source: https://github.com/junit-team/junit5/issues/3083 -@RunWith(Enclosed.class) -public class EnclosedWithParameterizedChildrenJUnit4TestCase { - - @RunWith(Parameterized.class) - public static class NestedTestCase1 { - - @Parameters - public static Collection data() { - return Arrays.asList(new Object[] { 1, 2 }, new Object[] { 3, 4 }); - } - - @SuppressWarnings("unused") - public NestedTestCase1(final int a, final int b) { - } - - @Test - public void test() { - } - } - - @RunWith(Parameterized.class) - public static class NestedTestCase2 { - - @Parameters - public static Collection data() { - return Arrays.asList(new Object[] { 1, 2 }, new Object[] { 3, 4 }); - } - - @SuppressWarnings("unused") - public NestedTestCase2(final int a, final int b) { - } - - @Test - public void test() { - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java deleted file mode 100644 index 146d659c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.notification.RunNotifier; - -/** - * @since 4.12 - */ -public class ExceptionThrowingRunner extends ConfigurableRunner { - - public ExceptionThrowingRunner(Class testClass) { - super(testClass); - } - - @Override - public void run(RunNotifier notifier) { - throw new RuntimeException("Simulated exception in custom runner for " + testClass.getName()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java deleted file mode 100644 index 19d53de5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; -import static org.junit.runners.MethodSorters.NAME_ASCENDING; - -import org.junit.FixMethodOrder; -import org.junit.Ignore; -import org.junit.Test; - -/** - * @since 4.12 - */ -@Ignore("complete class is ignored") -@FixMethodOrder(NAME_ASCENDING) -public class IgnoredJUnit4TestCase { - - @Test - public void failingTest() { - fail("this test is discovered, but skipped"); - } - - @Test - public void succeedingTest() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java deleted file mode 100644 index dd19ad0a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.Ignore; - -/** - * @since 5.1 - */ -@Ignore -public class IgnoredJUnit4TestCaseWithNotFilterableRunner extends JUnit4TestCaseWithNotFilterableRunner { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java deleted file mode 100644 index 0c127b5b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import java.util.List; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -/** - * @since 5.4.1 - */ -@RunWith(Parameterized.class) -public class IgnoredParameterizedTestCase { - - @Parameters(name = "{0}") - public static Iterable parameters() { - return List.of("foo", "bar"); - } - - @Parameter - public String value; - - @Test - @Ignore - public void test() { - // never called - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java deleted file mode 100644 index 705f2b89..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -/** - * Test case used in {@link JUnit4ParameterizedTests}. - * - * @since 4.12 - */ -@RunWith(Parameterized.class) -public class JUnit4ParameterizedTestCase { - - @Parameters - public static Object[] data() { - return new Object[] { 1, 2, 3 }; - } - - public JUnit4ParameterizedTestCase(int i) { - } - - @Test - public void test1() { - fail("this test should fail"); - } - - @Test - public void endingIn_test1() { - fail("this test should fail"); - } - - @Test - public void test1_atTheBeginning() { - fail("this test should fail"); - } - - @Test - public void test2() { - /* always succeeds */ - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java deleted file mode 100644 index 5b8a1284..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 5.1 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit4TestCaseWithNotFilterableRunner.class) -public class JUnit4SuiteOfSuiteWithFilterableChildRunner { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java deleted file mode 100644 index 04c4aed2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit4SuiteWithIgnoredJUnit4TestCase.class) -public class JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java deleted file mode 100644 index 271cda18..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class) -public class JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java deleted file mode 100644 index 66b0273f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class) -public class JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java deleted file mode 100644 index 01dd3aeb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; - -/** - * @since 4.12 - */ -@RunWith(ExceptionThrowingRunner.class) -@ChildCount(1) -public class JUnit4SuiteWithExceptionThrowingRunner { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java deleted file mode 100644 index c1f1279a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(IgnoredJUnit4TestCase.class) -public class JUnit4SuiteWithIgnoredJUnit4TestCase { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java deleted file mode 100644 index aa4c4094..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; -import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class) -public class JUnit4SuiteWithJUnit3SuiteWithSingleTestCase { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java deleted file mode 100644 index 6f2e3673..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit4TestCaseWithAssumptionFailureInBeforeClass.class) -public class JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java deleted file mode 100644 index 56bc1bd2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit4TestCaseWithErrorInBeforeClass.class) -public class JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java deleted file mode 100644 index 7e4dc1b0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -@RunWith(Suite.class) -@SuiteClasses({ JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class, - PlainJUnit4TestCaseWithSingleTestWhichFails.class }) -public class JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java deleted file mode 100644 index b784ffde..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 5.6.2 - */ -@RunWith(Suite.class) -@SuiteClasses(JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class) -public class JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java deleted file mode 100644 index a6e65202..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses(PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class) -public class JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java deleted file mode 100644 index 4cf0eed1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -/** - * @since 4.12 - */ -@RunWith(Suite.class) -@SuiteClasses({ PlainJUnit4TestCaseWithTwoTestMethods.class, PlainJUnit4TestCaseWithSingleTestWhichFails.class }) -public class JUnit4SuiteWithTwoTestCases { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java deleted file mode 100644 index f06d3fe8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.AssumptionViolatedException; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * @since 4.12 - */ -public class JUnit4TestCaseWithAssumptionFailureInBeforeClass { - - @BeforeClass - public static void failingBeforeClass() { - throw new AssumptionViolatedException("assumption violated"); - } - - @Test - public void test() { - fail("this should never be called"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java deleted file mode 100644 index 10f77db2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.Test; -import org.junit.experimental.theories.Theories; -import org.junit.runner.RunWith; - -/** - * @since 5.5 - */ -@RunWith(Theories.class) -public class JUnit4TestCaseWithDistinguishableOverloadedMethod { - - @Test - public void test() { - test("foo"); - } - - @SuppressWarnings("SameParameterValue") - private void test(String message) { - fail(message); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java deleted file mode 100644 index 090e0401..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.hamcrest.core.IsNot.not; -import static org.hamcrest.core.StringContains.containsString; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ErrorCollector; - -public class JUnit4TestCaseWithErrorCollectorStoringMultipleFailures { - @Rule - public ErrorCollector collector = new ErrorCollector(); - - @Test - public void example() { - collector.addError(new Throwable("first thing went wrong")); - collector.addError(new Throwable("second thing went wrong")); - collector.checkThat(getResult(), not(containsString("ERROR!"))); - // all lines will run, and then a combined failure logged at the end. - } - - private String getResult() { - return "This is an ERROR!"; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java deleted file mode 100644 index 2cb44e60..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; -import static org.junit.runners.MethodSorters.NAME_ASCENDING; - -import org.junit.AfterClass; -import org.junit.FixMethodOrder; -import org.junit.Test; - -/** - * @since 4.12 - */ -@FixMethodOrder(NAME_ASCENDING) -public class JUnit4TestCaseWithErrorInAfterClass { - - @AfterClass - public static void failingAfterClass() { - fail("error in @AfterClass"); - } - - @Test - public void failingTest() { - fail("expected to fail"); - } - - @Test - public void succeedingTest() { - // no-op - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java deleted file mode 100644 index dd3afe5e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * @since 4.12 - */ -public class JUnit4TestCaseWithErrorInBeforeClass { - - @BeforeClass - public static void failingBeforeClass() { - fail("something went wrong"); - } - - @Test - public void test() { - fail("this should never be called"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java deleted file mode 100644 index 44329675..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.RunWith; -import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; - -/** - * @since 4.12 - */ -@RunWith(ExceptionThrowingRunner.class) -@ChildCount(0) -public class JUnit4TestCaseWithExceptionThrowingRunner { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java deleted file mode 100644 index b1816d2a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(RunnerThatOnlyReportsFailures.class) -public class JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished { - @Test - public void testWithMissingEvents() { - fail("boom"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java deleted file mode 100644 index 6d4099db..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.experimental.theories.DataPoint; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.runner.RunWith; - -/** - * @since 4.12 - */ -@RunWith(Theories.class) -public class JUnit4TestCaseWithIndistinguishableOverloadedMethod { - - @DataPoint - public static int MAGIC_NUMBER = 42; - - @Theory - public void theory(int i) { - fail("failing theory with single parameter"); - } - - @Theory - public void theory(int i, int j) { - fail("failing theory with two parameters"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java deleted file mode 100644 index 9ece2415..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; - -/** - * @since 5.1 - */ -@RunWith(NotFilterableRunner.class) -@ChildCount(2) -@Category(Categories.Successful.class) -public class JUnit4TestCaseWithNotFilterableRunner { - - @Test - public void someTest() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java deleted file mode 100644 index 8906ce68..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Retention; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * @since 4.12 - */ -@Label("(TestClass)") -@RunWith(RunnerWithCustomUniqueIdsAndDisplayNames.class) -public class JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames { - - @Test - @Label("(TestMethod)") - public void test() { - Assert.fail(); - } - -} - -@Retention(RUNTIME) -@interface Label { - String value(); -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java deleted file mode 100644 index aa039daf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.junit.runner.notification.RunNotifier; - -@RunWith(JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.Runner.class) -public class JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions { - public static class Runner extends org.junit.runner.Runner { - - private final Class testClass; - - public Runner(Class testClass) { - this.testClass = testClass; - } - - @Override - public Description getDescription() { - var suiteDescription = Description.createSuiteDescription(testClass); - suiteDescription.addChild(getContainerDescription("1st")); - suiteDescription.addChild(getContainerDescription("2nd")); - return suiteDescription; - } - - private Description getContainerDescription(String name) { - var parent = Description.createSuiteDescription(name); - parent.addChild(getLeafDescription()); - parent.addChild(getLeafDescription()); - return parent; - } - - private Description getLeafDescription() { - return Description.createTestDescription(testClass, "leaf"); - } - - @Override - public void run(RunNotifier notifier) { - for (var i = 0; i < 2; i++) { - notifier.fireTestIgnored(getLeafDescription()); - notifier.fireTestStarted(getLeafDescription()); - notifier.fireTestFinished(getLeafDescription()); - } - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java deleted file mode 100644 index 87480a21..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.Test; - -/** - * @since 4.12 - */ -public class MalformedJUnit4TestCase { - - @Test - @SuppressWarnings("TestMethodWithIncorrectSignature") // intentionally not public - void nonPublicTest() { - fail("this should never be called"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java deleted file mode 100644 index 4eb6bea4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -/** - * @since 5.1 - */ -public class NotFilterableRunner extends ConfigurableRunner { - - public NotFilterableRunner(Class testClass) { - super(testClass); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java deleted file mode 100644 index ec094427..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.assertEquals; - -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -/** - * @since 4.12 - */ -@RunWith(Parameterized.class) -public class ParameterizedTestCase { - - @Parameters(name = "{0}") - public static Iterable parameters() { - return List.of("foo", "bar"); - } - - @Parameter - public String value; - - @Test - public void test() { - assertEquals("foo", value); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java deleted file mode 100644 index a9905cbe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.assertEquals; - -import java.time.Instant; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.AfterParam; -import org.junit.runners.Parameterized.BeforeParam; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -/** - * @since 5.9 - */ -@RunWith(Parameterized.class) -public class ParameterizedTimingTestCase { - - public static Map EVENTS = new LinkedHashMap<>(); - - @BeforeClass - public static void beforeClass() throws Exception { - EVENTS.clear(); - } - - @BeforeParam - public static void beforeParam(String param) throws Exception { - EVENTS.put("beforeParam(" + param + ")", Instant.now()); - Thread.sleep(100); - } - - @AfterParam - public static void afterParam(String param) throws Exception { - Thread.sleep(100); - System.out.println("ParameterizedTimingTestCase.afterParam"); - EVENTS.put("afterParam(" + param + ")", Instant.now()); - } - - @Parameters(name = "{0}") - public static Iterable parameters() { - return List.of("foo", "bar"); - } - - @Parameter - public String value; - - @Test - public void test() { - assertEquals("foo", value); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java deleted file mode 100644 index a6852f99..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.AfterParam; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -/** - * @since 5.9 - */ -@RunWith(Parameterized.class) -public class ParameterizedWithAfterParamFailureTestCase { - - @AfterParam - public static void afterParam() { - fail(); - } - - @Parameters(name = "{0}") - public static Iterable parameters() { - return List.of("foo", "bar"); - } - - @Parameter - public String value; - - @Test - public void test() { - assertEquals("foo", value); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java deleted file mode 100644 index 515f8d4e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.BeforeParam; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -/** - * @since 5.9 - */ -@RunWith(Parameterized.class) -public class ParameterizedWithBeforeParamFailureTestCase { - - @BeforeParam - public static void beforeParam() { - fail(); - } - - @Parameters(name = "{0}") - public static Iterable parameters() { - return List.of("foo", "bar"); - } - - @Parameter - public String value; - - @Test - public void test() { - assertEquals("foo", value); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java deleted file mode 100644 index 08fe68db..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeFalse; -import static org.junit.runners.MethodSorters.NAME_ASCENDING; - -import org.junit.FixMethodOrder; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.vintage.engine.samples.junit4.Categories.Failing; -import org.junit.vintage.engine.samples.junit4.Categories.Plain; -import org.junit.vintage.engine.samples.junit4.Categories.Skipped; -import org.junit.vintage.engine.samples.junit4.Categories.SkippedWithReason; - -/** - * @since 4.12 - */ -@FixMethodOrder(NAME_ASCENDING) -@Category(Plain.class) -public class PlainJUnit4TestCaseWithFiveTestMethods { - - @Test - public void abortedTest() { - assumeFalse("this test should be aborted", true); - } - - @Test - @Category(Failing.class) - public void failingTest() { - fail("this test should fail"); - } - - @Test - @Ignore - @Category(Skipped.class) - public void ignoredTest1_withoutReason() { - fail("this should never be called"); - } - - @Test - @Ignore("a custom reason") - @Category(SkippedWithReason.class) - public void ignoredTest2_withReason() { - fail("this should never be called"); - } - - @Test - public void successfulTest() { - assertEquals(3, 1 + 2); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java deleted file mode 100644 index c629a217..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.FixMethodOrder; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runners.MethodSorters; - -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class PlainJUnit4TestCaseWithLifecycleMethods { - - public static final List EVENTS = new ArrayList<>(); - - @BeforeClass - public static void beforeClass() { - EVENTS.add("beforeClass"); - } - - @Before - public void before() { - EVENTS.add("before"); - } - - @Test - public void failingTest() { - EVENTS.add("failingTest"); - fail(); - } - - @Test - @Ignore("skipped") - public void skippedTest() { - EVENTS.add("this should never ever be executed because the test is skipped"); - } - - @Test - public void succeedingTest() { - EVENTS.add("succeedingTest"); - } - - @After - public void after() { - EVENTS.add("after"); - } - - @AfterClass - public static void afterClass() { - EVENTS.add("afterClass"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java deleted file mode 100644 index 6ac3601b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -/** - * @since 4.12 - */ -public class PlainJUnit4TestCaseWithSingleInheritedTestWhichFails extends PlainJUnit4TestCaseWithSingleTestWhichFails { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java deleted file mode 100644 index de6ac2b1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.fail; - -import org.junit.Test; - -/** - * @since 4.12 - */ -public class PlainJUnit4TestCaseWithSingleTestWhichFails { - - @Test - public void failingTest() { - fail("this test should fail"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java deleted file mode 100644 index fb80eb65..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -/** - * @since 4.12 - */ -public class PlainJUnit4TestCaseWithSingleTestWhichIsIgnored { - - @Test - @Ignore("ignored test") - public void ignoredTest() { - Assert.fail("this should not be called"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java deleted file mode 100644 index ffd2cdc0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.junit.runners.MethodSorters.NAME_ASCENDING; - -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.experimental.categories.Category; - -/** - * @since 4.12 - */ -@FixMethodOrder(NAME_ASCENDING) -public class PlainJUnit4TestCaseWithTwoTestMethods { - - @Test - public void failingTest() { - fail("this test should fail"); - } - - @Test - @Category(Categories.Successful.class) - public void successfulTest() { - assertEquals(3, 1 + 2); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java deleted file mode 100644 index dd94df32..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; - -public class RunnerThatOnlyReportsFailures extends BlockJUnit4ClassRunner { - public RunnerThatOnlyReportsFailures(Class klass) throws InitializationError { - super(klass); - } - - @Override - protected void runChild(FrameworkMethod method, RunNotifier notifier) { - var statement = methodBlock(method); - try { - statement.evaluate(); - } - catch (Throwable e) { - notifier.fireTestFailure(new Failure(describeChild(method), e)); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java deleted file mode 100644 index 1cf2140d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import static org.junit.runner.Description.createTestDescription; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Objects; -import java.util.function.Supplier; - -import org.junit.runner.Description; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.Annotatable; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; - -/** - * @since 4.12 - */ -public class RunnerWithCustomUniqueIdsAndDisplayNames extends BlockJUnit4ClassRunner { - - public RunnerWithCustomUniqueIdsAndDisplayNames(Class klass) throws InitializationError { - super(klass); - } - - @Override - protected String getName() { - return getLabel(getTestClass(), super::getName); - } - - @Override - protected Description describeChild(FrameworkMethod method) { - var testName = testName(method); - return createTestDescription(getTestClass().getJavaClass().getName(), testName, new CustomUniqueId(testName)); - } - - @Override - protected String testName(FrameworkMethod method) { - return getLabel(method, () -> super.testName(method)); - } - - private String getLabel(Annotatable element, Supplier fallback) { - var label = element.getAnnotation(Label.class); - return label == null ? fallback.get() : label.value(); - } - - private static class CustomUniqueId implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - private final String testName; - - public CustomUniqueId(String testName) { - this.testName = testName; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof CustomUniqueId) { - var that = (CustomUniqueId) obj; - return Objects.equals(this.testName, that.testName); - } - return false; - } - - @Override - public int hashCode() { - return testName.hashCode(); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java deleted file mode 100644 index 1718b50a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.Assert; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.runner.RunWith; - -/** - * @since 4.12 - */ -@RunWith(Theories.class) -public class SingleFailingTheoryTestCase { - - @Theory - public void theory() { - Assert.fail("this theory should fail"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java b/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java deleted file mode 100644 index e4074c2f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.vintage.engine.samples.junit4; - -import org.junit.platform.runner.JUnitPlatform; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.runner.RunWith; - -/** - * @since 4.12 - */ -@SuppressWarnings("deprecation") -@RunWith(JUnitPlatform.class) -@SelectClasses(PlainJUnit4TestCaseWithSingleTestWhichFails.class) -public class TestCaseRunWithJUnitPlatformRunner { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts deleted file mode 100644 index cca78db6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/platform-tests.gradle.kts +++ /dev/null @@ -1,96 +0,0 @@ -import org.gradle.api.tasks.PathSensitivity.NONE -import org.gradle.api.tasks.PathSensitivity.RELATIVE -import org.gradle.internal.os.OperatingSystem - -plugins { - `java-library-conventions` - `junit4-compatibility` - `testing-conventions` - id("me.champeau.jmh") -} - -dependencies { - // --- Things we are testing -------------------------------------------------- - testImplementation(projects.junitPlatformCommons) - testImplementation(projects.junitPlatformConsole) - testImplementation(projects.junitPlatformEngine) - testImplementation(projects.junitPlatformJfr) - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteCommons) - testImplementation(projects.junitPlatformSuiteEngine) - - // --- Things we are testing with --------------------------------------------- - testImplementation(projects.junitPlatformRunner) - testImplementation(projects.junitPlatformTestkit) - testImplementation(testFixtures(projects.junitPlatformCommons)) - testImplementation(testFixtures(projects.junitPlatformEngine)) - testImplementation(testFixtures(projects.junitPlatformLauncher)) - testImplementation(projects.junitJupiterEngine) - testImplementation(libs.apiguardian) - testImplementation(libs.jfrunit) { - exclude(group = "org.junit.vintage") - } - testImplementation(libs.joox) - testImplementation(libs.openTestReporting.tooling) - testImplementation(libs.bundles.xmlunit) - - // --- Test run-time dependencies --------------------------------------------- - testRuntimeOnly(projects.junitVintageEngine) - testRuntimeOnly(libs.groovy4) { - because("`ReflectionUtilsTests.findNestedClassesWithInvalidNestedClassFile` needs it") - } - - // --- https://openjdk.java.net/projects/code-tools/jmh/ ----------------------- - jmh(libs.jmh.core) - jmh(projects.junitJupiterApi) - jmh(libs.junit4) - jmhAnnotationProcessor(libs.jmh.generator.annprocess) -} - -jmh { - jmhVersion.set(libs.versions.jmh) - - duplicateClassesStrategy.set(DuplicatesStrategy.WARN) - fork.set(1) - warmupIterations.set(1) - iterations.set(5) -} - -tasks { - withType().configureEach { - useJUnitPlatform { - excludeTags("exclude") - } - jvmArgs("-Xmx1g") - distribution { - // Retry in a new JVM on Windows to improve chances of successful retries when - // cached resources are used (e.g. in ClasspathScannerTests) - retryInSameJvm.set(!OperatingSystem.current().isWindows) - } - } - test { - // Additional inputs for remote execution with Test Distribution - inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) - inputs.file(buildFile).withPathSensitivity(NONE) // for UniqueIdTrackingListenerIntegrationTests - } - test_4_12 { - useJUnitPlatform { - includeTags("junit4") - } - } - checkstyleJmh { // use same style rules as defined for tests - configFile = rootProject.file("src/checkstyle/checkstyleTest.xml") - } -} - -eclipse { - classpath { - plusConfigurations.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) - } -} - -idea { - module { - scopes["PROVIDED"]!!["plus"]!!.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowed"]) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java deleted file mode 100644 index 97136ecd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.jmh; - -import org.junit.Assert; -import org.junit.jupiter.api.Assertions; -import org.openjdk.jmh.annotations.Benchmark; - -/** - * JMH benchmarks for assertions. - * - * @since 5.1 - */ -public class AssertionBenchmarks { - - @Benchmark - public void junit4_assertTrue_boolean() { - Assert.assertTrue(true); - } - - @Benchmark - public void junitJupiter_assertTrue_boolean() { - Assertions.assertTrue(true); - } - - @Benchmark - public void junit4_assertTrue_String_boolean() { - Assert.assertTrue("message", true); - } - - @Benchmark - public void junitJupiter_assertTrue_boolean_String() { - Assertions.assertTrue(true, "message"); - } - - @Benchmark - public void junitJupiter_assertTrue_boolean_Supplier() { - Assertions.assertTrue(true, () -> "message"); - } - - @Benchmark - public void junitJupiter_assertTrue_BooleanSupplier_String() { - Assertions.assertTrue(() -> true, "message"); - } - - @Benchmark - public void junitJupiter_assertTrue_BooleanSupplier_Supplier() { - Assertions.assertTrue(() -> true, () -> "message"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java deleted file mode 100644 index 8018c935..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/DefaultPackageTestCase.java +++ /dev/null @@ -1,28 +0,0 @@ - -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Simple test case that is used to verify proper support for classpath scanning - * within the default package. - * - * @since 1.0 - */ -@Disabled("Only used reflectively by other tests") -class DefaultPackageTestCase { - - @Test - void test() { - // do nothing - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java deleted file mode 100644 index 714869b6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.ValueSource; - -class OSTests { - @ParameterizedTest - @NullAndEmptySource - @ValueSource(strings = { " ", "\t" }) - void blankOsNameYieldsNull(String name) { - assertNull(OS.parse(name)); - } - - @ParameterizedTest - @ValueSource(strings = { "!", "?", "✨", "MINIX" }) - void unknownOsNameYieldsOTHER(String name) { - assertEquals(OS.OTHER, OS.parse(name)); - } - - @Nested - class ValidNames { - @ParameterizedTest - @ValueSource(strings = { "AIX", "Aix", "LaIxOS" }) - void aix(String name) { - assertEquals(OS.AIX, OS.parse(name)); - } - - @ParameterizedTest - @ValueSource(strings = { "FREEBSD", "FreeBSD" }) - void freebsd(String name) { - assertEquals(OS.FREEBSD, OS.parse(name)); - } - - @ParameterizedTest - @ValueSource(strings = { "LINUX", "Linux" }) - void linux(String name) { - assertEquals(OS.LINUX, OS.parse(name)); - } - - @ParameterizedTest - @ValueSource(strings = { "MAC", "mac" }) - void mac(String name) { - assertEquals(OS.MAC, OS.parse(name)); - } - - @ParameterizedTest - @ValueSource(strings = { "OPENBSD", "OpenBSD" }) - void openbsd(String name) { - assertEquals(OS.OPENBSD, OS.parse(name)); - } - - @ParameterizedTest - @ValueSource(strings = { "SOLARIS", "SunOS" }) - void solaris(String name) { - assertEquals(OS.SOLARIS, OS.parse(name)); - } - - @ParameterizedTest - @ValueSource(strings = { "WINDOW", "Microsoft Windows [Version 10.?]" }) - void windows(String name) { - assertEquals(OS.WINDOWS, OS.parse(name)); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java deleted file mode 100644 index dd2df97c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.extensions; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolver; - -public class Heavyweight implements ParameterResolver, BeforeEachCallback { - - @Override - public void beforeEach(ExtensionContext context) { - context.getStore(ExtensionContext.Namespace.GLOBAL).put("once", new CloseableOnlyOnceResource()); - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) { - return Resource.class.equals(parameterContext.getParameter().getType()); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) { - var engineContext = context.getRoot(); - var store = engineContext.getStore(ExtensionContext.Namespace.GLOBAL); - var resource = store.getOrComputeIfAbsent(ResourceValue.class); - resource.usages.incrementAndGet(); - return resource; - } - - interface Resource { - String ID = "org.junit.jupiter.extensions.Heavyweight.Resource"; - - int usages(); - } - - /** - * Demo resource class. - * - *

The class implements interface {@link CloseableResource} - * and interface {@link AutoCloseable} to show and ensure that a single - * {@link ResourceValue#close()} method implementation is needed to comply - * with both interfaces. - */ - static class ResourceValue implements Resource, CloseableResource, AutoCloseable { - - static final AtomicInteger creations = new AtomicInteger(); - private final AtomicInteger usages = new AtomicInteger(); - - @SuppressWarnings("unused") // used via reflection - ResourceValue() { - // Open long-living resources here. - assertEquals(1, creations.incrementAndGet(), "Singleton pattern failure!"); - } - - @Override - public void close() { - // Close resources here. - assertEquals(9, usages.get(), "Usage counter mismatch!"); - } - - @Override - public int usages() { - return usages.get(); - } - } - - private static class CloseableOnlyOnceResource implements CloseableResource { - - private final AtomicBoolean closed = new AtomicBoolean(); - - @Override - public void close() { - assertTrue(closed.compareAndSet(false, true), "already closed!"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java deleted file mode 100644 index 2d316392..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.extensions; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.parallel.ResourceLock; - -/** - * Unit tests for {@link org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource} - * stored values. - * - * @since 1.1 - */ -class HeavyweightTests { - - @AfterAll - static void afterAll() { - Heavyweight.ResourceValue.creations.set(0); - } - - @Nested - @TestInstance(PER_CLASS) - @ExtendWith(Heavyweight.class) - @ResourceLock(Heavyweight.Resource.ID) - class Alpha { - - private int mark; - - @BeforeAll - void setMark(Heavyweight.Resource resource) { - assertTrue(resource.usages() > 0); - mark = resource.usages(); - } - - @TestFactory - Stream alpha1(Heavyweight.Resource resource) { - return Stream.of(dynamicTest("foo", () -> assertTrue(resource.usages() > 1))); - } - - @Test - void alpha2(Heavyweight.Resource resource) { - assertTrue(resource.usages() > 1); - } - - @Test - void alpha3(Heavyweight.Resource resource) { - assertTrue(resource.usages() > 1); - } - - @AfterAll - void checkMark(Heavyweight.Resource resource) { - assertEquals(mark, resource.usages() - 4); - } - - } - - @Nested - @TestInstance(PER_CLASS) - @ExtendWith(Heavyweight.class) - @ResourceLock(Heavyweight.Resource.ID) - class Beta { - - private int mark; - - @BeforeAll - void beforeAll(Heavyweight.Resource resource) { - assertTrue(resource.usages() > 0); - mark = resource.usages(); - } - - @BeforeEach - void beforeEach(Heavyweight.Resource resource) { - assertTrue(resource.usages() > 1); - } - - @Test - void beta(Heavyweight.Resource resource) { - assertTrue(resource.usages() > 2); - } - - @AfterAll - void afterAll(Heavyweight.Resource resource) { - assertEquals(mark, resource.usages() - 3); - } - - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java deleted file mode 100644 index 8fdd06ae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Abstract base class for unit tests that wish to test - * {@link Object#equals(Object)} and {@link Object#hashCode()}. - * - * @since 1.3 - */ -public abstract class AbstractEqualsAndHashCodeTests { - - protected final void assertEqualsAndHashCode(T equal1, T equal2, T different) { - assertThat(equal1).isNotNull(); - assertThat(equal2).isNotNull(); - assertThat(different).isNotNull(); - - assertThat(equal1).isNotSameAs(equal2); - assertThat(equal1).isNotEqualTo(null); - assertThat(equal1).isNotEqualTo(new Object()); - assertThat(equal1).isNotEqualTo(different); - assertThat(different).isNotEqualTo(equal1); - assertThat(different).isNotEqualTo(equal2); - assertThat(equal1.hashCode()).isNotEqualTo(different.hashCode()); - - assertThat(equal1).isEqualTo(equal1); - assertThat(equal1).isEqualTo(equal2); - assertThat(equal2).isEqualTo(equal1); - assertThat(equal1.hashCode()).isEqualTo(equal2.hashCode()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java deleted file mode 100644 index 48633071..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.Suite; - -/** - * Test suite for the JUnit Platform. - * - *

Logging Configuration

- * - *

In order for our log4j2 configuration to be used in an IDE, you must - * set the following system property before running any tests — for - * example, in Run Configurations in Eclipse. - * - *

- * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
- * 
- * - * @since 1.0 - */ -@Suite -@SelectPackages("org.junit.platform") -@IncludeClassNamePatterns(".*Tests?") -@IncludeEngines("junit-jupiter") -class JUnitPlatformTestSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java deleted file mode 100644 index fc3e8f13..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Optional; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; - -/** - * Test cases for default implementations of {@link org.junit.platform.engine.TestEngine}. - * - *

Note: the package {@code org.junit.platform} this class resides in is - * chosen on purpose. If it was in {@code org.junit.platform.engine} the default - * implementation will pick up values defined by the real Jupiter test engine. - * - * @since 1.1 - */ -class TestEngineTests { - - @Test - void defaults() { - TestEngine engine = new DefaultEngine(); - assertEquals(Optional.empty(), engine.getGroupId()); - assertEquals(Optional.empty(), engine.getArtifactId()); - assertEquals(Optional.of("DEVELOPMENT"), engine.getVersion()); - } - - private static class DefaultEngine implements TestEngine { - - @Override - public String getId() { - return getClass().getSimpleName(); - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - throw new UnsupportedOperationException("discover"); - } - - @Override - public void execute(ExecutionRequest request) { - throw new UnsupportedOperationException("execute"); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java deleted file mode 100644 index 611a30bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.annotation; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; - -/** - * Integration tests that indirectly verify that the {@link Testable @Testable} - * annotation may be declared on any element type. - * - * @since 1.7 - */ -class TestableAnnotationTests { - - @Test - void testMethod() { - assertNotNull(new TestableEverywhere().toString()); - } - - @Testable - static class TestableEverywhere { - - @Testable - final int field = 0; - - @Testable - TestableEverywhere() { - } - - @Testable - void test(@Testable int parameter) { - @Testable - @SuppressWarnings("unused") - var var = "var"; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java deleted file mode 100644 index 52d466ff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.function; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.JUnitException; - -public class TryTests { - - @Test - void successfulTriesCanBeTransformed() throws Exception { - var success = Try.success("foo"); - - assertThat(success.get()).isEqualTo("foo"); - assertThat(success.getOrThrow(RuntimeException::new)).isEqualTo("foo"); - assertThat(success.toOptional()).contains("foo"); - - assertThat(success.andThen(v -> { - assertThat(v).isEqualTo("foo"); - return Try.success("bar"); - }).get()).isEqualTo("bar"); - assertThat(success.andThenTry(v -> { - assertThat(v).isEqualTo("foo"); - return "bar"; - }).get()).isEqualTo("bar"); - - assertThat(success.orElse(() -> fail("should not be called"))).isSameAs(success); - assertThat(success.orElseTry(() -> fail("should not be called"))).isSameAs(success); - - var value = new AtomicReference(); - assertThat(success.ifSuccess(value::set)).isSameAs(success); - assertThat(value.get()).isEqualTo("foo"); - assertThat(success.ifFailure(cause -> fail("should not be called"))).isSameAs(success); - } - - @Test - void failedTriesCanBeTransformed() throws Exception { - var cause = new JUnitException("foo"); - var failure = Try.failure(cause); - - assertThat(assertThrows(JUnitException.class, failure::get)).isSameAs(cause); - assertThat(assertThrows(RuntimeException.class, () -> failure.getOrThrow(RuntimeException::new))).isInstanceOf( - RuntimeException.class).hasCause(cause); - assertThat(failure.toOptional()).isEmpty(); - - assertThat(failure.andThen(v -> fail("should not be called"))).isSameAs(failure); - assertThat(failure.andThenTry(v -> fail("should not be called"))).isSameAs(failure); - - assertThat(failure.orElse(() -> Try.success("bar")).get()).isEqualTo("bar"); - assertThat(failure.orElseTry(() -> "bar").get()).isEqualTo("bar"); - - assertThat(failure.ifSuccess(v -> fail("should not be called"))).isSameAs(failure); - var exception = new AtomicReference(); - assertThat(failure.ifFailure(exception::set)).isSameAs(failure); - assertThat(exception.get()).isSameAs(cause); - } - - @Test - void successfulTriesCanStoreNull() throws Exception { - var success = Try.success(null); - assertThat(success.get()).isNull(); - assertThat(success.getOrThrow(RuntimeException::new)).isNull(); - assertThat(success.toOptional()).isEmpty(); - } - - @Test - void triesWithSameContentAreEqual() { - var cause = new Exception(); - Callable failingCallable = () -> { - throw cause; - }; - - var success = Try.call(() -> "foo"); - assertThat(success).isEqualTo(success).hasSameHashCodeAs(success); - assertThat(success).isEqualTo(Try.success("foo")); - assertThat(success).isNotEqualTo(Try.failure(cause)); - - var failure = Try.call(failingCallable); - assertThat(failure).isEqualTo(failure).hasSameHashCodeAs(failure); - assertThat(failure).isNotEqualTo(Try.success("foo")); - assertThat(failure).isEqualTo(Try.failure(cause)); - } - - @Test - void methodPreconditionsAreChecked() { - assertThrows(JUnitException.class, () -> Try.call(null)); - - var success = Try.success("foo"); - assertThrows(JUnitException.class, () -> success.andThen(null)); - assertThrows(JUnitException.class, () -> success.andThenTry(null)); - assertThrows(JUnitException.class, () -> success.ifSuccess(null)); - - var failure = Try.failure(new Exception()); - assertThrows(JUnitException.class, () -> failure.orElse(null)); - assertThrows(JUnitException.class, () -> failure.orElseTry(null)); - assertThrows(JUnitException.class, () -> failure.ifFailure(null)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java deleted file mode 100644 index 8675d038..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Optional; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 1.0 - */ -class AnnotationSupportTests { - - @Test - void isAnnotatedPreconditions() { - var optional = Optional.of(Probe.class); - assertPreconditionViolationException("annotationType", () -> AnnotationSupport.isAnnotated(optional, null)); - assertPreconditionViolationException("annotationType", () -> AnnotationSupport.isAnnotated(Probe.class, null)); - } - - @Test - void isAnnotatedDelegates() { - var element = Probe.class; - var optional = Optional.of(element); - - assertEquals(AnnotationUtils.isAnnotated(optional, Tag.class), - AnnotationSupport.isAnnotated(optional, Tag.class)); - assertEquals(AnnotationUtils.isAnnotated(optional, Override.class), - AnnotationSupport.isAnnotated(optional, Override.class)); - - assertEquals(AnnotationUtils.isAnnotated(element, Tag.class), - AnnotationSupport.isAnnotated(element, Tag.class)); - assertEquals(AnnotationUtils.isAnnotated(element, Override.class), - AnnotationSupport.isAnnotated(element, Override.class)); - } - - @Test - void findAnnotationOnElementPreconditions() { - var optional = Optional.of(Probe.class); - assertPreconditionViolationException("annotationType", () -> AnnotationSupport.findAnnotation(optional, null)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotation(Probe.class, null)); - } - - @Test - void findAnnotationOnElementDelegates() { - var element = Probe.class; - var optional = Optional.of(element); - - assertEquals(AnnotationUtils.findAnnotation(optional, Tag.class), - AnnotationSupport.findAnnotation(optional, Tag.class)); - assertEquals(AnnotationUtils.findAnnotation(optional, Override.class), - AnnotationSupport.findAnnotation(optional, Override.class)); - - assertEquals(AnnotationUtils.findAnnotation(element, Tag.class), - AnnotationSupport.findAnnotation(element, Tag.class)); - assertEquals(AnnotationUtils.findAnnotation(element, Override.class), - AnnotationSupport.findAnnotation(element, Override.class)); - } - - @Test - void findAnnotationOnClassPreconditions() { - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotation(Probe.class, null, SearchOption.INCLUDE_ENCLOSING_CLASSES)); - assertPreconditionViolationException("SearchOption", - () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, null)); - } - - @Test - void findAnnotationOnClassDelegates() { - Class clazz = Probe.class; - assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false), - AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT)); - assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, true), - AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); - assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, false), - AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.DEFAULT)); - assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, true), - AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); - - clazz = Probe.InnerClass.class; - assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false), - AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT)); - assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, true), - AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); - assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, false), - AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.DEFAULT)); - assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, true), - AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); - } - - @Test - void findPublicAnnotatedFieldsPreconditions() { - assertPreconditionViolationException("Class", - () -> AnnotationSupport.findPublicAnnotatedFields(null, String.class, FieldMarker.class)); - assertPreconditionViolationException("fieldType", - () -> AnnotationSupport.findPublicAnnotatedFields(Probe.class, null, FieldMarker.class)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findPublicAnnotatedFields(Probe.class, String.class, null)); - } - - @Test - void findPublicAnnotatedFieldsDelegates() { - assertEquals(AnnotationUtils.findPublicAnnotatedFields(Probe.class, String.class, FieldMarker.class), - AnnotationSupport.findPublicAnnotatedFields(Probe.class, String.class, FieldMarker.class)); - assertEquals(AnnotationUtils.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class), - AnnotationSupport.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class)); - } - - @Test - void findAnnotatedMethodsPreconditions() { - assertPreconditionViolationException("Class", - () -> AnnotationSupport.findAnnotatedMethods(null, Tag.class, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotatedMethods(Probe.class, null, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("HierarchyTraversalMode", - () -> AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, null)); - } - - @Test - void findAnnotatedMethodsDelegates() { - assertEquals( - AnnotationUtils.findAnnotatedMethods(Probe.class, Tag.class, - ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), - AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, HierarchyTraversalMode.TOP_DOWN)); - assertEquals( - AnnotationUtils.findAnnotatedMethods(Probe.class, Tag.class, - ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), - AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, HierarchyTraversalMode.BOTTOM_UP)); - - assertEquals( - AnnotationUtils.findAnnotatedMethods(Probe.class, Override.class, - ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), - AnnotationSupport.findAnnotatedMethods(Probe.class, Override.class, HierarchyTraversalMode.TOP_DOWN)); - assertEquals( - AnnotationUtils.findAnnotatedMethods(Probe.class, Override.class, - ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), - AnnotationSupport.findAnnotatedMethods(Probe.class, Override.class, HierarchyTraversalMode.BOTTOM_UP)); - } - - @Test - void findRepeatableAnnotationsDelegates() throws Throwable { - var bMethod = Probe.class.getDeclaredMethod("bMethod"); - assertEquals(AnnotationUtils.findRepeatableAnnotations(bMethod, Tag.class), - AnnotationSupport.findRepeatableAnnotations(bMethod, Tag.class)); - Object expected = assertThrows(PreconditionViolationException.class, - () -> AnnotationUtils.findRepeatableAnnotations(bMethod, Override.class)); - Object actual = assertThrows(PreconditionViolationException.class, - () -> AnnotationSupport.findRepeatableAnnotations(bMethod, Override.class)); - assertSame(expected.getClass(), actual.getClass(), "expected same exception class"); - assertEquals(expected.toString(), actual.toString(), "expected equal exception toString representation"); - } - - @Test - void findRepeatableAnnotationsPreconditions() { - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findRepeatableAnnotations(Probe.class, null)); - } - - @Test - void findAnnotatedFieldsDelegates() { - assertEquals(AnnotationUtils.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true), - AnnotationSupport.findAnnotatedFields(Probe.class, FieldMarker.class)); - assertEquals(AnnotationUtils.findAnnotatedFields(Probe.class, Override.class, f -> true), - AnnotationSupport.findAnnotatedFields(Probe.class, Override.class)); - - assertEquals( - AnnotationUtils.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true, - ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), - AnnotationSupport.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true, - HierarchyTraversalMode.TOP_DOWN)); - assertEquals( - AnnotationUtils.findAnnotatedFields(Probe.class, Override.class, f -> true, - ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), - AnnotationSupport.findAnnotatedFields(Probe.class, Override.class, f -> true, - HierarchyTraversalMode.TOP_DOWN)); - } - - @Test - void findAnnotatedFieldsPreconditions() { - assertPreconditionViolationException("Class", - () -> AnnotationSupport.findAnnotatedFields(null, FieldMarker.class)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotatedFields(Probe.class, null)); - - assertPreconditionViolationException("Class", () -> AnnotationSupport.findAnnotatedFields(null, Override.class, - f -> true, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotatedFields(Probe.class, null, f -> true, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("HierarchyTraversalMode", - () -> AnnotationSupport.findAnnotatedFields(Probe.class, Override.class, f -> true, null)); - } - - @Test - void findAnnotatedFieldValuesForNonStaticFields() { - var instance = new Fields(); - - assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, Tag.class)).isEmpty(); - - assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class))// - .containsExactlyInAnyOrder("i1", "i2"); - } - - @Test - void findAnnotatedFieldValuesForStaticFields() { - assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, Tag.class)).isEmpty(); - - assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class))// - .containsExactlyInAnyOrder("s1", "s2"); - } - - @Test - void findAnnotatedFieldValuesForNonStaticFieldsByType() { - var instance = new Fields(); - - assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class, Number.class)).isEmpty(); - - assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class, String.class))// - .containsExactlyInAnyOrder("i1", "i2"); - } - - @Test - void findAnnotatedFieldValuesForStaticFieldsByType() { - assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class, Number.class)).isEmpty(); - - assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class, String.class))// - .containsExactlyInAnyOrder("s1", "s2"); - } - - @Test - void findAnnotatedFieldValuesPreconditions() { - assertPreconditionViolationException("instance", - () -> AnnotationSupport.findAnnotatedFieldValues((Object) null, FieldMarker.class)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotatedFieldValues(this, null)); - - assertPreconditionViolationException("Class", - () -> AnnotationSupport.findAnnotatedFieldValues(null, FieldMarker.class)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, null)); - - assertPreconditionViolationException("instance", - () -> AnnotationSupport.findAnnotatedFieldValues((Object) null, FieldMarker.class, Number.class)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotatedFieldValues(this, null, Number.class)); - assertPreconditionViolationException("fieldType", - () -> AnnotationSupport.findAnnotatedFieldValues(this, FieldMarker.class, null)); - - assertPreconditionViolationException("Class", - () -> AnnotationSupport.findAnnotatedFieldValues(null, FieldMarker.class, Number.class)); - assertPreconditionViolationException("annotationType", - () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, null, Number.class)); - assertPreconditionViolationException("fieldType", - () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, FieldMarker.class, null)); - } - - // ------------------------------------------------------------------------- - - @Target(ElementType.FIELD) - @Retention(RetentionPolicy.RUNTIME) - @interface FieldMarker { - } - - @Tag("class-tag") - static class Probe { - - @FieldMarker - public static String publicAnnotatedStaticField = "static"; - - @FieldMarker - public String publicAnnotatedInstanceField = "instance"; - - @Tag("method-tag") - void aMethod() { - } - - @Tag("method-tag-1") - @Tag("method-tag-2") - void bMethod() { - } - - class InnerClass { - } - - } - - static class Fields { - - @FieldMarker - static String staticField1 = "s1"; - - @FieldMarker - static String staticField2 = "s2"; - - @FieldMarker - String instanceField1 = "i1"; - - @FieldMarker - String instanceField2 = "i2"; - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java deleted file mode 100644 index 05352fa9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; - -import java.util.List; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.ClassUtils; - -/** - * @since 1.1 - */ -class ClassSupportTests { - - @Test - void nullSafeToStringPreconditions() { - Function, ? extends String> mapper = null; - assertPreconditionViolationException("Mapping function", - () -> ClassSupport.nullSafeToString(mapper, String.class, List.class)); - } - - @Test - void nullSafeToStringDelegates() { - assertEquals(ClassUtils.nullSafeToString(String.class, List.class), - ClassSupport.nullSafeToString(String.class, List.class)); - - Function, String> classToStringMapper = aClass -> aClass.getSimpleName() + "-Test"; - assertEquals(ClassUtils.nullSafeToString(classToStringMapper, String.class, List.class), - ClassSupport.nullSafeToString(classToStringMapper, String.class, List.class)); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java deleted file mode 100644 index 9bc39a1a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link ModifierSupport}. - * - * @since 1.4 - */ -class ModifierSupportTests { - - @Test - void isPublicPreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isPublic((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isPublic((Member) null)); - } - - @Classes - void isPublicDelegates(Class clazz) { - assertEquals(ReflectionUtils.isPublic(clazz), ModifierSupport.isPublic(clazz)); - } - - @Methods - void isPublicDelegates(Method method) { - assertEquals(ReflectionUtils.isPublic(method), ModifierSupport.isPublic(method)); - } - - @Test - void isPrivatePreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isPrivate((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isPrivate((Member) null)); - } - - @Classes - void isPrivateDelegates(Class clazz) { - assertEquals(ReflectionUtils.isPrivate(clazz), ModifierSupport.isPrivate(clazz)); - } - - @Methods - void isPrivateDelegates(Method method) { - assertEquals(ReflectionUtils.isPrivate(method), ModifierSupport.isPrivate(method)); - } - - @Test - void isNotPrivatePreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isNotPrivate((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isNotPrivate((Member) null)); - } - - @Classes - void isNotPrivateDelegates(Class clazz) { - assertEquals(ReflectionUtils.isNotPrivate(clazz), ModifierSupport.isNotPrivate(clazz)); - } - - @Methods - void isNotPrivateDelegates(Method method) { - assertEquals(ReflectionUtils.isNotPrivate(method), ModifierSupport.isNotPrivate(method)); - } - - @Test - void isAbstractPreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isAbstract((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isAbstract((Member) null)); - } - - @Classes - void isAbstractDelegates(Class clazz) { - assertEquals(ReflectionUtils.isAbstract(clazz), ModifierSupport.isAbstract(clazz)); - } - - @Methods - void isAbstractDelegates(Method method) { - assertEquals(ReflectionUtils.isAbstract(method), ModifierSupport.isAbstract(method)); - } - - @Test - void isStaticPreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isStatic((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isStatic((Member) null)); - } - - @Classes - void isStaticDelegates(Class clazz) { - assertEquals(ReflectionUtils.isStatic(clazz), ModifierSupport.isStatic(clazz)); - } - - @Methods - void isStaticDelegates(Method method) { - assertEquals(ReflectionUtils.isStatic(method), ModifierSupport.isStatic(method)); - } - - @Test - void isNotStaticPreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isNotStatic((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isNotStatic((Member) null)); - } - - @Classes - void isNotStaticDelegates(Class clazz) { - assertEquals(ReflectionUtils.isNotStatic(clazz), ModifierSupport.isNotStatic(clazz)); - } - - @Methods - void isNotStaticDelegates(Method method) { - assertEquals(ReflectionUtils.isNotStatic(method), ModifierSupport.isNotStatic(method)); - } - - @Test - void isFinalPreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isFinal((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isFinal((Member) null)); - } - - @Classes - void isFinalDelegates(Class clazz) { - assertEquals(ReflectionUtils.isFinal(clazz), ModifierSupport.isFinal(clazz)); - } - - @Methods - void isFinalDelegates(Method method) { - assertEquals(ReflectionUtils.isFinal(method), ModifierSupport.isFinal(method)); - } - - @Test - void isNotFinalPreconditions() { - assertPreconditionViolationException("Class", () -> ModifierSupport.isNotFinal((Class) null)); - assertPreconditionViolationException("Member", () -> ModifierSupport.isNotFinal((Member) null)); - } - - @Classes - void isNotFinalDelegates(Class clazz) { - assertEquals(ReflectionUtils.isNotFinal(clazz), ModifierSupport.isNotFinal(clazz)); - } - - @Methods - void isNotFinalDelegates(Method method) { - assertEquals(ReflectionUtils.isNotFinal(method), ModifierSupport.isNotFinal(method)); - } - - // ------------------------------------------------------------------------- - - // Intentionally non-static - public class PublicClass { - - public void publicMethod() { - } - } - - private class PrivateClass { - - @SuppressWarnings("unused") - private void privateMethod() { - } - } - - protected class ProtectedClass { - - @SuppressWarnings("unused") - protected void protectedMethod() { - } - } - - class PackageVisibleClass { - - @SuppressWarnings("unused") - void packageVisibleMethod() { - } - } - - abstract static class AbstractClass { - - abstract void abstractMethod(); - } - - static class StaticClass { - - static void staticMethod() { - } - } - - final class FinalClass { - - final void finalMethod() { - } - } - - // ------------------------------------------------------------------------- - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @ParameterizedTest - @ValueSource(classes = { PublicClass.class, PrivateClass.class, ProtectedClass.class, PackageVisibleClass.class, - AbstractClass.class, StaticClass.class, FinalClass.class }) - @interface Classes { - } - - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @ParameterizedTest - @MethodSource("methods") - @interface Methods { - } - - static Stream methods() throws Exception { - // @formatter:off - return Stream.of( - PublicClass.class.getMethod("publicMethod"), - PrivateClass.class.getDeclaredMethod("privateMethod"), - ProtectedClass.class.getDeclaredMethod("protectedMethod"), - PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"), - AbstractClass.class.getDeclaredMethod("abstractMethod"), - StaticClass.class.getDeclaredMethod("staticMethod"), - FinalClass.class.getDeclaredMethod("finalMethod") - ); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java deleted file mode 100644 index bd7a3fe1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 1.4 - */ -class PreconditionAssertions { - - static void assertPreconditionViolationException(String name, Executable executable) { - var exception = assertThrows(PreconditionViolationException.class, executable); - assertEquals(name + " must not be null", exception.getMessage()); - } - - static void assertPreconditionViolationExceptionForString(String name, Executable executable) { - var exception = assertThrows(PreconditionViolationException.class, executable); - assertEquals(name + " must not be null or blank", exception.getMessage()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java deleted file mode 100644 index f14ff26e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.support; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; -import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationExceptionForString; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * @since 1.0 - */ -class ReflectionSupportTests { - - private static final Predicate> allTypes = type -> true; - private static final Predicate allNames = name -> true; - private static final Predicate allMethods = name -> true; - private static final Predicate allFields = name -> true; - - static final String staticField = "static"; - final String instanceField = "instance"; - - @Test - @SuppressWarnings("deprecation") - void loadClassDelegates() { - assertEquals(ReflectionUtils.loadClass("-"), ReflectionSupport.loadClass("-")); - assertEquals(ReflectionUtils.loadClass("A"), ReflectionSupport.loadClass("A")); - assertEquals(ReflectionUtils.loadClass("java.nio.Bits"), ReflectionSupport.loadClass("java.nio.Bits")); - } - - @Test - @SuppressWarnings("deprecation") - void loadClassPreconditions() { - assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.loadClass(null)); - assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.loadClass("")); - } - - @Test - void tryToLoadClassDelegates() { - assertEquals(ReflectionUtils.tryToLoadClass("-").toOptional(), - ReflectionSupport.tryToLoadClass("-").toOptional()); - assertEquals(ReflectionUtils.tryToLoadClass("A").toOptional(), - ReflectionSupport.tryToLoadClass("A").toOptional()); - assertEquals(ReflectionUtils.tryToLoadClass("java.nio.Bits"), - ReflectionSupport.tryToLoadClass("java.nio.Bits")); - } - - @Test - void tryToLoadClassPreconditions() { - assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.tryToLoadClass(null)); - assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.tryToLoadClass("")); - } - - @TestFactory - List findAllClassesInClasspathRootDelegates() throws Throwable { - List tests = new ArrayList<>(); - List paths = new ArrayList<>(); - paths.add(Path.of(".").toRealPath()); - paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); - for (var path : paths) { - var root = path.toUri(); - var displayName = root.getPath(); - if (displayName.length() > 42) { - displayName = "..." + displayName.substring(displayName.length() - 42); - } - tests.add(DynamicTest.dynamicTest(displayName, - () -> assertEquals(ReflectionUtils.findAllClassesInClasspathRoot(root, allTypes, allNames), - ReflectionSupport.findAllClassesInClasspathRoot(root, allTypes, allNames)))); - } - return tests; - } - - @Test - void findAllClassesInClasspathRootPreconditions() { - var path = Path.of(".").toUri(); - assertPreconditionViolationException("root", - () -> ReflectionSupport.findAllClassesInClasspathRoot(null, allTypes, allNames)); - assertPreconditionViolationException("class predicate", - () -> ReflectionSupport.findAllClassesInClasspathRoot(path, null, allNames)); - assertPreconditionViolationException("name predicate", - () -> ReflectionSupport.findAllClassesInClasspathRoot(path, allTypes, null)); - } - - @Test - void findAllClassesInPackageDelegates() { - assertNotEquals(0, ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames).size()); - assertEquals(ReflectionUtils.findAllClassesInPackage("org.junit", allTypes, allNames), - ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames)); - } - - @Test - void findAllClassesInPackagePreconditions() { - assertPreconditionViolationExceptionForString("basePackageName", - () -> ReflectionSupport.findAllClassesInPackage(null, allTypes, allNames)); - assertPreconditionViolationException("class predicate", - () -> ReflectionSupport.findAllClassesInPackage("org.junit", null, allNames)); - assertPreconditionViolationException("name predicate", - () -> ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, null)); - } - - @Test - void findAllClassesInModuleDelegates() { - assertEquals(ReflectionUtils.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames), - ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames)); - } - - @Test - void findAllClassesInModulePreconditions() { - var exception = assertThrows(PreconditionViolationException.class, - () -> ReflectionSupport.findAllClassesInModule(null, allTypes, allNames)); - assertEquals("Module name must not be null or empty", exception.getMessage()); - assertPreconditionViolationException("class predicate", - () -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", null, allNames)); - assertPreconditionViolationException("name predicate", - () -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, null)); - } - - @Test - void newInstanceDelegates() { - assertEquals(ReflectionUtils.newInstance(String.class, "foo"), - ReflectionSupport.newInstance(String.class, "foo")); - } - - @Test - void newInstancePreconditions() { - assertPreconditionViolationException("Class", () -> ReflectionSupport.newInstance(null)); - assertPreconditionViolationException("Argument array", - () -> ReflectionSupport.newInstance(String.class, (Object[]) null)); - assertPreconditionViolationException("Individual arguments", - () -> ReflectionSupport.newInstance(String.class, new Object[] { null })); - } - - @Test - void invokeMethodDelegates() throws Exception { - var method = Boolean.class.getMethod("valueOf", String.class); - assertEquals(ReflectionUtils.invokeMethod(method, null, "true"), - ReflectionSupport.invokeMethod(method, null, "true")); - } - - @Test - void invokeMethodPreconditions() throws Exception { - assertPreconditionViolationException("Method", () -> ReflectionSupport.invokeMethod(null, null, "true")); - - var method = Boolean.class.getMethod("toString"); - var exception = assertThrows(PreconditionViolationException.class, - () -> ReflectionSupport.invokeMethod(method, null)); - assertEquals("Cannot invoke non-static method [" + method.toGenericString() + "] on a null target.", - exception.getMessage()); - } - - @Test - void findFieldsDelegates() { - assertEquals( - ReflectionUtils.findFields(ReflectionSupportTests.class, allFields, - ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), - ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.BOTTOM_UP)); - assertEquals( - ReflectionUtils.findFields(ReflectionSupportTests.class, allFields, - ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), - ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.TOP_DOWN)); - } - - @Test - void findFieldsPreconditions() { - assertPreconditionViolationException("Class", - () -> ReflectionSupport.findFields(null, allFields, HierarchyTraversalMode.BOTTOM_UP)); - assertPreconditionViolationException("Class", - () -> ReflectionSupport.findFields(null, allFields, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("Predicate", - () -> ReflectionSupport.findFields(ReflectionSupportTests.class, null, HierarchyTraversalMode.BOTTOM_UP)); - assertPreconditionViolationException("Predicate", - () -> ReflectionSupport.findFields(ReflectionSupportTests.class, null, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("HierarchyTraversalMode", - () -> ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, null)); - } - - @Test - void tryToReadFieldValueDelegates() throws Exception { - var staticField = getClass().getDeclaredField("staticField"); - assertEquals(ReflectionUtils.tryToReadFieldValue(staticField, null), - ReflectionSupport.tryToReadFieldValue(staticField, null)); - - var instanceField = getClass().getDeclaredField("instanceField"); - assertEquals(ReflectionUtils.tryToReadFieldValue(instanceField, this), - ReflectionSupport.tryToReadFieldValue(instanceField, this)); - } - - @Test - void tryToReadFieldValuePreconditions() throws Exception { - assertPreconditionViolationException("Field", () -> ReflectionSupport.tryToReadFieldValue(null, this)); - - var instanceField = getClass().getDeclaredField("instanceField"); - Exception exception = assertThrows(PreconditionViolationException.class, - () -> ReflectionSupport.tryToReadFieldValue(instanceField, null)); - assertThat(exception)// - .hasMessageStartingWith("Cannot read non-static field")// - .hasMessageEndingWith("on a null instance."); - } - - @Test - void findMethodDelegates() { - assertEquals(ReflectionUtils.findMethod(Boolean.class, "valueOf", String.class.getName()), - ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class.getName())); - - assertEquals(ReflectionUtils.findMethod(Boolean.class, "valueOf", String.class), - ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class)); - } - - @Test - void findMethodPreconditions() { - assertPreconditionViolationException("Class", - () -> ReflectionSupport.findMethod(null, "valueOf", String.class.getName())); - assertPreconditionViolationExceptionForString("Method name", - () -> ReflectionSupport.findMethod(Boolean.class, "", String.class.getName())); - assertPreconditionViolationExceptionForString("Method name", - () -> ReflectionSupport.findMethod(Boolean.class, " ", String.class.getName())); - - assertPreconditionViolationException("Class", - () -> ReflectionSupport.findMethod(null, "valueOf", String.class)); - assertPreconditionViolationExceptionForString("Method name", - () -> ReflectionSupport.findMethod(Boolean.class, "", String.class)); - assertPreconditionViolationExceptionForString("Method name", - () -> ReflectionSupport.findMethod(Boolean.class, " ", String.class)); - assertPreconditionViolationException("Parameter types array", - () -> ReflectionSupport.findMethod(Boolean.class, "valueOf", (Class[]) null)); - assertPreconditionViolationException("Individual parameter types", - () -> ReflectionSupport.findMethod(Boolean.class, "valueOf", new Class[] { null })); - } - - @Test - void findMethodsDelegates() { - assertEquals( - ReflectionUtils.findMethods(ReflectionSupportTests.class, allMethods, - ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), - ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.BOTTOM_UP)); - assertEquals( - ReflectionUtils.findMethods(ReflectionSupportTests.class, allMethods, - ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), - ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.TOP_DOWN)); - } - - @Test - void findMethodsPreconditions() { - assertPreconditionViolationException("Class", - () -> ReflectionSupport.findMethods(null, allMethods, HierarchyTraversalMode.BOTTOM_UP)); - assertPreconditionViolationException("Class", - () -> ReflectionSupport.findMethods(null, allMethods, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("Predicate", - () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, null, HierarchyTraversalMode.BOTTOM_UP)); - assertPreconditionViolationException("Predicate", - () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, null, HierarchyTraversalMode.TOP_DOWN)); - assertPreconditionViolationException("HierarchyTraversalMode", - () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, null)); - } - - @Test - void findNestedClassesDelegates() { - assertEquals(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic), - ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)); - } - - @Test - void findNestedClassesPreconditions() { - assertPreconditionViolationException("Class", - () -> ReflectionSupport.findNestedClasses(null, ReflectionUtils::isStatic)); - assertPreconditionViolationException("Predicate", - () -> ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, null)); - } - - static class ClassWithNestedClasses { - - class Nested1 { - } - - static class Nested2 { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java deleted file mode 100644 index 47849101..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java +++ /dev/null @@ -1,967 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedMethods; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findPublicAnnotatedFields; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; - -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.math.BigDecimal; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link AnnotationUtils}. - * - * @since 1.0 - */ -class AnnotationUtilsTests { - - private static final Predicate isStringField = field -> field.getType() == String.class; - - @Test - void findAnnotationForNullOptional() { - assertThat(findAnnotation((Optional) null, Annotation1.class)).isEmpty(); - } - - @Test - void findAnnotationForEmptyOptional() { - assertThat(findAnnotation(Optional.empty(), Annotation1.class)).isEmpty(); - } - - @Test - void findAnnotationForNullAnnotatedElement() { - assertThat(findAnnotation((AnnotatedElement) null, Annotation1.class)).isEmpty(); - } - - @Test - void findAnnotationOnClassWithoutAnnotation() { - assertThat(findAnnotation(Annotation1Class.class, Annotation2.class)).isNotPresent(); - } - - @Test - void findAnnotationIndirectlyPresentOnOptionalClass() { - Optional> optional = Optional.of(SubInheritedAnnotationClass.class); - assertThat(findAnnotation(optional, InheritedAnnotation.class)).isPresent(); - } - - @Test - void findAnnotationIndirectlyPresentOnClass() { - assertThat(findAnnotation(SubInheritedAnnotationClass.class, InheritedAnnotation.class)).isPresent(); - } - - /** - * Test for https://github.com/junit-team/junit5/issues/1133 - */ - @Test - void findInheritedAnnotationMetaPresentOnNonInheritedComposedAnnotationPresentOnSuperclass() { - assertThat(findAnnotation(SubNonInheritedCompositionOfInheritedAnnotationClass.class, - InheritedAnnotation.class)).isPresent(); - } - - @Test - void findAnnotationDirectlyPresentOnClass() { - assertThat(findAnnotation(Annotation1Class.class, Annotation1.class)).isPresent(); - } - - @Test - void findAnnotationMetaPresentOnClass() { - assertThat(findAnnotation(ComposedAnnotationClass.class, Annotation1.class)).isPresent(); - } - - /** - * Note: there is no findAnnotationIndirectlyMetaPresentOnMethod - * counterpart because the {@code @Inherited} annotation has no effect if - * the annotation type is used to annotate anything other than a class. - * - * @see Inherited - */ - @Test - void findAnnotationIndirectlyMetaPresentOnClass() { - assertThat(findAnnotation(SubInheritedComposedAnnotationClass.class, Annotation1.class)).isPresent(); - } - - @Test - void findAnnotationDirectlyPresentOnImplementedInterface() { - assertThat(findAnnotation(TestingTraitClass.class, Annotation1.class)).isPresent(); - } - - @Test - void findAnnotationMetaPresentOnImplementedInterface() { - assertThat(findAnnotation(ComposedTestingTraitClass.class, Annotation1.class)).isPresent(); - } - - @Test - void findAnnotationDirectlyPresentOnMethod() throws Exception { - var method = Annotation2Class.class.getDeclaredMethod("method"); - assertThat(findAnnotation(method, Annotation1.class)).isPresent(); - } - - @Test - void findAnnotationMetaPresentOnMethod() throws Exception { - var method = ComposedAnnotationClass.class.getDeclaredMethod("method"); - assertThat(findAnnotation(method, Annotation1.class)).isPresent(); - } - - @Test - void findAnnotationMetaPresentOnOptionalMethod() throws Exception { - var method = ComposedAnnotationClass.class.getDeclaredMethod("method"); - assertThat(findAnnotation(Optional.of(method), Annotation1.class)).isPresent(); - } - - @Test - void findAnnotationDirectlyPresentOnEnclosingClass() throws Exception { - Class clazz = Annotation1Class.InnerClass.class; - assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); - assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); - - clazz = Annotation1Class.InnerClass.InnerInnerClass.class; - assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); - assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); - - clazz = Annotation1Class.NestedClass.class; - assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); - assertThat(findAnnotation(clazz, Annotation1.class, true)).isNotPresent(); - } - - @Test - void findAnnotationMetaPresentOnEnclosingClass() throws Exception { - Class clazz = ComposedAnnotationClass.InnerClass.class; - assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); - assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); - - clazz = ComposedAnnotationClass.InnerClass.InnerInnerClass.class; - assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); - assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); - } - - @Test - void isAnnotatedForClassWithoutAnnotation() { - assertFalse(isAnnotated(Annotation1Class.class, Annotation2.class)); - } - - @Test - void isAnnotatedWhenIndirectlyPresentOnClass() { - assertTrue(isAnnotated(SubInheritedAnnotationClass.class, InheritedAnnotation.class)); - } - - @Test - void isAnnotatedWhenDirectlyPresentOnClass() { - assertTrue(isAnnotated(Annotation1Class.class, Annotation1.class)); - } - - @Test - void isAnnotatedWhenMetaPresentOnClass() { - assertTrue(isAnnotated(ComposedAnnotationClass.class, Annotation1.class)); - } - - @Test - void isAnnotatedWhenDirectlyPresentOnMethod() throws Exception { - assertTrue(isAnnotated(Annotation2Class.class.getDeclaredMethod("method"), Annotation1.class)); - } - - @Test - void isAnnotatedWhenMetaPresentOnMethod() throws Exception { - assertTrue(isAnnotated(ComposedAnnotationClass.class.getDeclaredMethod("method"), Annotation1.class)); - } - - @Test - void findRepeatableAnnotationsForNotRepeatableAnnotation() { - var exception = assertThrows(PreconditionViolationException.class, - () -> findRepeatableAnnotations(getClass(), Inherited.class)); - - assertThat(exception.getMessage()).isEqualTo(Inherited.class.getName() + " must be @Repeatable"); - } - - @Test - void findRepeatableAnnotationsForNullOptionalAnnotatedElement() { - assertThat(findRepeatableAnnotations((Optional) null, Tag.class)).isEmpty(); - } - - @Test - void findRepeatableAnnotationsForEmptyOptionalAnnotatedElement() { - assertThat(findRepeatableAnnotations(Optional.empty(), Tag.class)).isEmpty(); - } - - @Test - void findRepeatableAnnotationsForNullAnnotatedElement() { - assertThat(findRepeatableAnnotations((AnnotatedElement) null, Tag.class)).isEmpty(); - } - - @Test - void findRepeatableAnnotationsWithSingleTag() { - assertTagsFound(SingleTaggedClass.class, "a"); - } - - @Test - void findRepeatableAnnotationsWithSingleComposedTag() { - assertTagsFound(SingleComposedTaggedClass.class, "fast"); - } - - @Test - void findRepeatableAnnotationsWithSingleComposedTagOnImplementedInterface() { - assertTagsFound(TaggedInterfaceClass.class, "fast"); - } - - @Test - void findRepeatableAnnotationsWithLocalComposedTagAndComposedTagOnImplementedInterface() { - assertTagsFound(LocalTagOnTaggedInterfaceClass.class, "fast", "smoke"); - } - - @Test - void findRepeatableAnnotationsWithMultipleTags() { - assertTagsFound(MultiTaggedClass.class, "a", "b", "c"); - } - - @Test - void findRepeatableAnnotationsWithMultipleComposedTags() { - assertTagsFound(MultiComposedTaggedClass.class, "fast", "smoke"); - assertTagsFound(FastAndSmokyTaggedClass.class, "fast", "smoke"); - } - - @Test - void findRepeatableAnnotationsWithContainer() { - assertTagsFound(ContainerTaggedClass.class, "a", "b", "c", "d"); - } - - @Test - void findRepeatableAnnotationsWithComposedTagBeforeContainer() { - assertTagsFound(ContainerAfterComposedTaggedClass.class, "fast", "a", "b", "c"); - } - - private void assertTagsFound(Class clazz, String... tags) { - assertEquals(List.of(tags), - findRepeatableAnnotations(clazz, Tag.class).stream().map(Tag::value).collect(toList()), - () -> "Tags found for class " + clazz.getName()); - } - - @Test - void findInheritedRepeatableAnnotationsWithSingleAnnotationOnSuperclass() { - assertExtensionsFound(SingleExtensionClass.class, "a"); - assertExtensionsFound(SubSingleExtensionClass.class, "a"); - } - - @Test - void findInheritedRepeatableAnnotationsWithMultipleAnnotationsOnSuperclass() { - assertExtensionsFound(MultiExtensionClass.class, "a", "b", "c"); - assertExtensionsFound(SubMultiExtensionClass.class, "a", "b", "c", "x", "y", "z"); - } - - @Test - void findInheritedRepeatableAnnotationsWithContainerAnnotationOnSuperclass() { - assertExtensionsFound(ContainerExtensionClass.class, "a", "b", "c"); - assertExtensionsFound(SubContainerExtensionClass.class, "a", "b", "c", "x"); - } - - @Test - void findInheritedRepeatableAnnotationsWithSingleComposedAnnotation() { - assertExtensionsFound(SingleComposedExtensionClass.class, "foo"); - } - - /** - * @since 1.5 - */ - @Test - void findInheritedRepeatableAnnotationsWithComposedAnnotationsInNestedContainer() { - assertExtensionsFound(MultipleFoos1.class, "foo"); - assertExtensionsFound(MultipleFoos2.class, "foo"); - } - - @Test - void findInheritedRepeatableAnnotationsWithSingleComposedAnnotationOnSuperclass() { - assertExtensionsFound(SubSingleComposedExtensionClass.class, "foo"); - } - - @Test - void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotations() { - assertExtensionsFound(MultiComposedExtensionClass.class, "foo", "bar"); - } - - @Test - void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotationsOnSuperclass() { - assertExtensionsFound(SubMultiComposedExtensionClass.class, "foo", "bar"); - } - - @Test - void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotationsOnSuperclassAndLocalContainerAndComposed() { - assertExtensionsFound(ContainerPlusSubMultiComposedExtensionClass.class, "foo", "bar", "x", "y", "z"); - } - - private void assertExtensionsFound(Class clazz, String... tags) { - assertEquals(List.of(tags), - findRepeatableAnnotations(clazz, ExtendWith.class).stream().map(ExtendWith::value).collect(toList()), - () -> "Extensions found for class " + clazz.getName()); - } - - @Test - void findAnnotatedMethodsForNullClass() { - assertThrows(PreconditionViolationException.class, - () -> findAnnotatedMethods(null, Annotation1.class, TOP_DOWN)); - } - - @Test - void findAnnotatedMethodsForNullAnnotationType() { - assertThrows(PreconditionViolationException.class, - () -> findAnnotatedMethods(ClassWithAnnotatedMethods.class, null, TOP_DOWN)); - } - - @Test - void findAnnotatedMethodsForAnnotationThatIsNotPresent() { - assertThat(findAnnotatedMethods(ClassWithAnnotatedMethods.class, Fast.class, TOP_DOWN)).isEmpty(); - } - - @Test - void findAnnotatedMethodsForAnnotationOnMethodsInClassUsingHierarchyDownMode() throws Exception { - var method2 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method2"); - var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); - - var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation2.class, TOP_DOWN); - - assertThat(methods).containsOnly(method2, method3); - } - - @Test - void findAnnotatedMethodsForAnnotationOnMethodsInClassHierarchyUsingHierarchyUpMode() throws Exception { - var method1 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method1"); - var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); - var superMethod = SuperclassWithAnnotatedMethod.class.getDeclaredMethod("superMethod"); - - var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation1.class, BOTTOM_UP); - - assertEquals(3, methods.size()); - assertThat(methods.subList(0, 2)).containsOnly(method1, method3); - assertEquals(superMethod, methods.get(2)); - } - - @Test - void findAnnotatedMethodsForAnnotationUsedInClassAndSuperclassHierarchyDown() throws Exception { - var method1 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method1"); - var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); - var superMethod = SuperclassWithAnnotatedMethod.class.getDeclaredMethod("superMethod"); - - var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation1.class, TOP_DOWN); - - assertEquals(3, methods.size()); - assertEquals(superMethod, methods.get(0)); - assertThat(methods.subList(1, 3)).containsOnly(method1, method3); - } - - @Test - void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception { - var interfaceMethod = InterfaceWithAnnotatedDefaultMethod.class.getDeclaredMethod("interfaceMethod"); - - var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation3.class, BOTTOM_UP); - - assertThat(methods).containsExactly(interfaceMethod); - } - - // === findAnnotatedFields() =============================================== - - @Test - void findAnnotatedFieldsForNullClass() { - assertThrows(PreconditionViolationException.class, - () -> findAnnotatedFields(null, Annotation1.class, isStringField, TOP_DOWN)); - } - - @Test - void findAnnotatedFieldsForNullAnnotationType() { - assertThrows(PreconditionViolationException.class, - () -> findAnnotatedFields(ClassWithAnnotatedFields.class, null, isStringField, TOP_DOWN)); - } - - @Test - void findAnnotatedFieldsForNullPredicate() { - assertThrows(PreconditionViolationException.class, - () -> findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, null, TOP_DOWN)); - } - - @Test - void findAnnotatedFieldsForAnnotationThatIsNotPresent() { - assertThat(findAnnotatedFields(ClassWithAnnotatedFields.class, Fast.class, isStringField, TOP_DOWN)).isEmpty(); - } - - @Test - void findAnnotatedFieldsForAnnotationOnFieldsInClassUsingHierarchyDownMode() throws Exception { - var field2 = ClassWithAnnotatedFields.class.getDeclaredField("field2"); - var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); - - var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation2.class, isStringField, TOP_DOWN); - - assertThat(fields).containsOnly(field2, field3); - } - - @Test - void findAnnotatedFieldsForAnnotationOnFieldsInClassHierarchyUsingHierarchyUpMode() throws Exception { - var field1 = ClassWithAnnotatedFields.class.getDeclaredField("field1"); - var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); - var superField = SuperclassWithAnnotatedField.class.getDeclaredField("superField"); - - var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, isStringField, BOTTOM_UP); - - assertEquals(3, fields.size()); - assertThat(fields.subList(0, 2)).containsOnly(field1, field3); - assertEquals(superField, fields.get(2)); - } - - @Test - void findAnnotatedFieldsForAnnotationUsedInClassAndSuperclassHierarchyDown() throws Exception { - var field1 = ClassWithAnnotatedFields.class.getDeclaredField("field1"); - var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); - var superField = SuperclassWithAnnotatedField.class.getDeclaredField("superField"); - - var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, isStringField, TOP_DOWN); - - assertEquals(3, fields.size()); - assertEquals(superField, fields.get(0)); - assertThat(fields.subList(1, 3)).containsOnly(field1, field3); - } - - @Test - void findAnnotatedFieldsForAnnotationUsedInInterface() throws Exception { - var interfaceField = InterfaceWithAnnotatedField.class.getDeclaredField("interfaceField"); - - var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation3.class, isStringField, BOTTOM_UP); - - assertThat(fields).containsExactly(interfaceField); - } - - @Test - void findAnnotatedFieldsForShadowedFields() throws Exception { - Class clazz = ClassWithShadowedAnnotatedFields.class; - var interfaceField = clazz.getDeclaredField("interfaceField"); - var superField = clazz.getDeclaredField("superField"); - var field1 = clazz.getDeclaredField("field1"); - var field2 = clazz.getDeclaredField("field2"); - var field3 = clazz.getDeclaredField("field3"); - - assertThat(findShadowingAnnotatedFields(Annotation1.class)).containsExactly(superField, field1, field3); - assertThat(findShadowingAnnotatedFields(Annotation2.class)).containsExactly(field2, field3); - assertThat(findShadowingAnnotatedFields(Annotation3.class)).containsExactly(interfaceField); - } - - private List findShadowingAnnotatedFields(Class annotationType) { - return findAnnotatedFields(ClassWithShadowedAnnotatedFields.class, annotationType, isStringField); - } - - // === findPublicAnnotatedFields() ========================================= - - @Test - void findPublicAnnotatedFieldsForNullClass() { - assertThrows(PreconditionViolationException.class, - () -> findPublicAnnotatedFields(null, String.class, Annotation1.class)); - } - - @Test - void findPublicAnnotatedFieldsForNullFieldType() { - assertThrows(PreconditionViolationException.class, - () -> findPublicAnnotatedFields(getClass(), null, Annotation1.class)); - } - - @Test - void findPublicAnnotatedFieldsForNullAnnotationType() { - assertThrows(PreconditionViolationException.class, - () -> findPublicAnnotatedFields(getClass(), String.class, null)); - } - - @Test - void findPublicAnnotatedFieldsForPrivateField() { - var fields = findPublicAnnotatedFields(getClass(), Boolean.class, Annotation1.class); - assertNotNull(fields); - assertEquals(0, fields.size()); - } - - @Test - void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldOfWrongFieldType() { - var fields = findPublicAnnotatedFields(getClass(), BigDecimal.class, Annotation1.class); - assertNotNull(fields); - assertEquals(0, fields.size()); - } - - @Test - void findPublicAnnotatedFieldsForDirectlyAnnotatedField() { - var fields = findPublicAnnotatedFields(getClass(), String.class, Annotation1.class); - assertNotNull(fields); - assertIterableEquals(List.of("directlyAnnotatedField"), asNames(fields)); - } - - @Test - void findPublicAnnotatedFieldsForMetaAnnotatedField() { - var fields = findPublicAnnotatedFields(getClass(), Number.class, Annotation1.class); - assertNotNull(fields); - assertEquals(1, fields.size()); - assertIterableEquals(List.of("metaAnnotatedField"), asNames(fields)); - } - - @Test - void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldInInterface() { - var fields = findPublicAnnotatedFields(InterfaceWithAnnotatedFields.class, String.class, Annotation1.class); - assertNotNull(fields); - assertIterableEquals(List.of("foo"), asNames(fields)); - } - - @Test - void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldsInClassAndInterface() { - var fields = findPublicAnnotatedFields(ClassWithPublicAnnotatedFieldsFromInterface.class, String.class, - Annotation1.class); - assertNotNull(fields); - assertThat(asNames(fields)).containsExactlyInAnyOrder("foo", "bar"); - } - - private List asNames(List fields) { - return fields.stream().map(Field::getName).collect(toList()); - } - - // ------------------------------------------------------------------------- - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @interface AnnotationWithDefaultValue { - - String value() default "default"; - } - - @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) - @Retention(RetentionPolicy.RUNTIME) - @interface Annotation1 { - } - - @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) - @Retention(RetentionPolicy.RUNTIME) - @interface Annotation2 { - } - - @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) - @Retention(RetentionPolicy.RUNTIME) - @interface Annotation3 { - } - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @interface InheritedAnnotation { - } - - @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) - @Retention(RetentionPolicy.RUNTIME) - @Annotation1 - @interface ComposedAnnotation { - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Annotation1 - @Inherited - @interface InheritedComposedAnnotation { - } - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @InheritedAnnotation - // DO NOT make this @Inherited. - @interface NonInheritedCompositionOfInheritedAnnotation { - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - // DO NOT make this @Inherited. - @interface Tags { - - Tag[] value(); - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Repeatable(Tags.class) - // DO NOT make this @Inherited. - @interface Tag { - - String value(); - } - - @Retention(RetentionPolicy.RUNTIME) - @Tag("fast") - @interface Fast { - } - - @Retention(RetentionPolicy.RUNTIME) - @Tag("smoke") - @interface Smoke { - } - - @Retention(RetentionPolicy.RUNTIME) - @Fast - @Smoke - @interface FastAndSmoky { - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @interface Extensions { - - ExtendWith[] value(); - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @Repeatable(Extensions.class) - @interface ExtendWith { - - String value(); - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @ExtendWith("foo") - @Repeatable(FooExtensions.class) - @interface ExtendWithFoo { - - String info() default ""; - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @interface FooExtensions { - - ExtendWithFoo[] value(); - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - // Intentionally NOT @Inherited in order to ensure that the algorithm for - // findRepeatableAnnotations() in fact lives up to the claims in the - // Javadoc regarding searching for repeatable annotations with implicit - // "inheritance" if the repeatable annotation is @Inherited but the - // custom composed annotation is not @Inherited. - // @Inherited - @ExtendWith("bar") - @interface ExtendWithBar { - } - - @AnnotationWithDefaultValue - static class AnnotationWithDefaultValueClass { - } - - @Annotation1 - static class Annotation1Class { - class InnerClass { - class InnerInnerClass { - } - } - static class NestedClass { - } - } - - @Annotation2 - static class Annotation2Class { - - @Annotation1 - void method() { - } - } - - @InheritedAnnotation - static class InheritedAnnotationClass { - } - - static class SubInheritedAnnotationClass extends InheritedAnnotationClass { - } - - @ComposedAnnotation - static class ComposedAnnotationClass { - - @ComposedAnnotation - void method() { - } - - class InnerClass { - class InnerInnerClass { - } - } - } - - @InheritedComposedAnnotation - static class InheritedComposedAnnotationClass { - - @InheritedComposedAnnotation - void method() { - } - } - - static class SubInheritedComposedAnnotationClass extends InheritedComposedAnnotationClass { - } - - @NonInheritedCompositionOfInheritedAnnotation - static class NonInheritedCompositionOfInheritedAnnotationClass { - } - - static class SubNonInheritedCompositionOfInheritedAnnotationClass - extends NonInheritedCompositionOfInheritedAnnotationClass { - } - - @Annotation1 - interface TestingTrait { - } - - static class TestingTraitClass implements TestingTrait { - } - - @ComposedAnnotation - interface ComposedTestingTrait { - } - - static class ComposedTestingTraitClass implements ComposedTestingTrait { - } - - @Tag("a") - static class SingleTaggedClass { - } - - @Fast - static class SingleComposedTaggedClass { - } - - @Tag("a") - @Tag("b") - @Tag("c") - static class MultiTaggedClass { - } - - @Fast - @Smoke - static class MultiComposedTaggedClass { - } - - @FastAndSmoky - static class FastAndSmokyTaggedClass { - } - - @Fast - interface TaggedInterface { - } - - static class TaggedInterfaceClass implements TaggedInterface { - } - - @Smoke - static class LocalTagOnTaggedInterfaceClass implements TaggedInterface { - } - - @Tags({ @Tag("a"), @Tag("b"), @Tag("c") }) - @Tag("d") - static class ContainerTaggedClass { - } - - @Fast - @Tags({ @Tag("a"), @Tag("b"), @Tag("c") }) - static class ContainerAfterComposedTaggedClass { - } - - @ExtendWith("a") - static class SingleExtensionClass { - } - - static class SubSingleExtensionClass extends SingleExtensionClass { - } - - @ExtendWith("a") - @ExtendWith("b") - @ExtendWith("c") - static class MultiExtensionClass { - } - - @ExtendWith("x") - @ExtendWith("y") - @ExtendWith("b") // duplicates parent - @ExtendWith("z") - @ExtendWith("a") // duplicates parent - static class SubMultiExtensionClass extends MultiExtensionClass { - } - - @Extensions({ @ExtendWith("a"), @ExtendWith("b"), @ExtendWith("c"), @ExtendWith("a") }) - static class ContainerExtensionClass { - } - - @ExtendWith("x") - static class SubContainerExtensionClass extends ContainerExtensionClass { - } - - @ExtendWithFoo - static class SingleComposedExtensionClass { - } - - @ExtendWithFoo(info = "A") - @ExtendWithFoo(info = "B") - static class MultipleFoos1 { - } - - @FooExtensions({ @ExtendWithFoo(info = "A"), @ExtendWithFoo(info = "B") }) - static class MultipleFoos2 { - } - - static class SubSingleComposedExtensionClass extends SingleComposedExtensionClass { - } - - @ExtendWithFoo - @ExtendWithBar - static class MultiComposedExtensionClass { - } - - static class SubMultiComposedExtensionClass extends MultiComposedExtensionClass { - } - - @ExtendWith("x") - @Extensions({ @ExtendWith("y"), @ExtendWith("z") }) - @ExtendWithBar - static class ContainerPlusSubMultiComposedExtensionClass extends MultiComposedExtensionClass { - } - - interface InterfaceWithAnnotatedDefaultMethod { - - @Annotation3 - default void interfaceMethod() { - } - } - - static class SuperclassWithAnnotatedMethod { - - @Annotation1 - void superMethod() { - } - } - - static class ClassWithAnnotatedMethods extends SuperclassWithAnnotatedMethod - implements InterfaceWithAnnotatedDefaultMethod { - - @Annotation1 - void method1() { - } - - @Annotation2 - void method2() { - } - - @Annotation1 - @Annotation2 - void method3() { - } - } - - // ------------------------------------------------------------------------- - - interface InterfaceWithAnnotatedField { - - @Annotation3 - String interfaceField = "interface"; - } - - static class SuperclassWithAnnotatedField { - - @Annotation1 - String superField = "super"; - } - - static class ClassWithAnnotatedFields extends SuperclassWithAnnotatedField implements InterfaceWithAnnotatedField { - - @Annotation1 - protected Object field0 = "?"; - - @Annotation1 - protected String field1 = "foo"; - - @Annotation2 - protected String field2 = "bar"; - - @Annotation1 - @Annotation2 - protected String field3 = "baz"; - - } - - static class ClassWithShadowedAnnotatedFields extends ClassWithAnnotatedFields { - - @Annotation3 - String interfaceField = "interface-shadow"; - - @Annotation1 - String superField = "super-shadow"; - - @Annotation1 - protected String field1 = "foo-shadow"; - - @Annotation2 - protected String field2 = "bar-shadow"; - - @Annotation1 - @Annotation2 - protected String field3 = "baz-shadow"; - - } - - // ------------------------------------------------------------------------- - - @Annotation1 - private Boolean privateDirectlyAnnotatedField; - - @Annotation1 - public String directlyAnnotatedField; - - @ComposedAnnotation - public Integer metaAnnotatedField; - - interface InterfaceWithAnnotatedFields { - - @Annotation1 - String foo = "bar"; - - @Annotation1 - boolean wrongType = false; - } - - class ClassWithPublicAnnotatedFieldsFromInterface implements InterfaceWithAnnotatedFields { - - @Annotation1 - public String bar = "baz"; - - @Annotation1 - public boolean notAString = true; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java deleted file mode 100644 index 87d793a0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link ClassLoaderUtils}. - * - * @since 1.0 - */ -class ClassLoaderUtilsTests { - - @Test - void getDefaultClassLoaderWithExplicitContextClassLoader() { - var original = Thread.currentThread().getContextClassLoader(); - var mock = mock(ClassLoader.class); - Thread.currentThread().setContextClassLoader(mock); - try { - assertSame(mock, ClassLoaderUtils.getDefaultClassLoader()); - } - finally { - Thread.currentThread().setContextClassLoader(original); - } - } - - @Test - void getDefaultClassLoaderWithNullContextClassLoader() { - var original = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(null); - try { - assertSame(ClassLoader.getSystemClassLoader(), ClassLoaderUtils.getDefaultClassLoader()); - } - finally { - Thread.currentThread().setContextClassLoader(original); - } - } - - @Test - void getLocationFromNullFails() { - var exception = assertThrows(PreconditionViolationException.class, () -> ClassLoaderUtils.getLocation(null)); - assertEquals("object must not be null", exception.getMessage()); - } - - @Test - void getLocationFromVariousObjectsArePresent() { - assertTrue(ClassLoaderUtils.getLocation(void.class).isPresent()); - assertTrue(ClassLoaderUtils.getLocation(byte.class).isPresent()); - assertTrue(ClassLoaderUtils.getLocation(this).isPresent()); - assertTrue(ClassLoaderUtils.getLocation("").isPresent()); - assertTrue(ClassLoaderUtils.getLocation(0).isPresent()); - assertTrue(ClassLoaderUtils.getLocation(Thread.State.RUNNABLE).isPresent()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java deleted file mode 100644 index 7f0d6a7f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; - -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.util.classes.AExecutionConditionClass; -import org.junit.platform.commons.util.classes.ATestExecutionListenerClass; -import org.junit.platform.commons.util.classes.AVanillaEmpty; -import org.junit.platform.commons.util.classes.BExecutionConditionClass; -import org.junit.platform.commons.util.classes.BTestExecutionListenerClass; -import org.junit.platform.commons.util.classes.BVanillaEmpty; -import org.junit.platform.launcher.TestExecutionListener; - -/** - * Unit tests for {@link ClassNamePatternFilterUtils}. - * - * @since 1.7 - */ -@TestInstance(Lifecycle.PER_CLASS) -class ClassNamePatternFilterUtilsTests { - - //@formatter:off - @ValueSource(strings = { - "org.junit.jupiter.*", - "org.junit.platform.*.NonExistentClass", - "*.NonExistentClass*", - "*NonExistentClass*", - "AExecutionConditionClass, BExecutionConditionClass" - }) - //@formatter:on - @ParameterizedTest - void alwaysEnabledConditions(String pattern) { - List executionConditions = List.of(new AExecutionConditionClass(), - new BExecutionConditionClass()); - assertThat(executionConditions).filteredOn( - ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isNotEmpty(); - } - - //@formatter:off - @ValueSource(strings = { - "org.junit.platform.*", - "*.platform.*", - "*", - "*AExecutionConditionClass, *BExecutionConditionClass", - "*ExecutionConditionClass" - }) - //@formatter:on - @ParameterizedTest - void alwaysDisabledConditions(String pattern) { - List executionConditions = List.of(new AExecutionConditionClass(), - new BExecutionConditionClass()); - assertThat(executionConditions).filteredOn( - ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isEmpty(); - } - - //@formatter:off - @ValueSource(strings = { - "org.junit.jupiter.*", - "org.junit.platform.*.NonExistentClass", - "*.NonExistentClass*", - "*NonExistentClass*", - "ATestExecutionListenerClass, BTestExecutionListenerClass" - }) - //@formatter:on - @ParameterizedTest - void alwaysEnabledListeners(String pattern) { - List executionConditions = List.of(new ATestExecutionListenerClass(), - new BTestExecutionListenerClass()); - assertThat(executionConditions).filteredOn( - ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isNotEmpty(); - } - - //@formatter:off - @ValueSource(strings = { - "org.junit.platform.*", - "*.platform.*", - "*", - "*ATestExecutionListenerClass, *BTestExecutionListenerClass", - "*TestExecutionListenerClass" - }) - //@formatter:on - @ParameterizedTest - void alwaysDisabledListeners(String pattern) { - List executionConditions = List.of(new ATestExecutionListenerClass(), - new BTestExecutionListenerClass()); - assertThat(executionConditions).filteredOn( - ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isEmpty(); - } - - //@formatter:off - @ValueSource(strings = { - "org.junit.jupiter.*", - "org.junit.platform.*.NonExistentClass", - "*.NonExistentClass*", - "*NonExistentClass*", - "AVanillaEmpty, BVanillaEmpty" - }) - //@formatter:on - @ParameterizedTest - void alwaysEnabledClass(String pattern) { - var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); - assertThat(executionConditions).filteredOn( - ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isNotEmpty(); - } - - //@formatter:off - @ValueSource(strings = { - "org.junit.platform.*", - "*.platform.*", - "*", - "*AVanillaEmpty, *BVanillaEmpty", - "*VanillaEmpty" - }) - //@formatter:on - @ParameterizedTest - void alwaysDisabledClass(String pattern) { - var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); - assertThat(executionConditions).filteredOn( - ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)).isEmpty(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java deleted file mode 100644 index 015e60b4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; - -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link ClassUtils}. - * - * @since 1.0 - */ -class ClassUtilsTests { - - @Test - void nullSafeToStringWithDefaultMapper() { - assertEquals("", nullSafeToString((Class[]) null)); - assertEquals("", nullSafeToString()); - assertEquals("java.lang.String", nullSafeToString(String.class)); - assertEquals("java.lang.String, java.lang.Integer", nullSafeToString(String.class, Integer.class)); - assertEquals("java.lang.String, null, java.lang.Integer", nullSafeToString(String.class, null, Integer.class)); - } - - @Test - void nullSafeToStringWithCustomMapper() { - assertEquals("", nullSafeToString(Class::getSimpleName, (Class[]) null)); - assertEquals("", nullSafeToString(Class::getSimpleName)); - assertEquals("String", nullSafeToString(Class::getSimpleName, String.class)); - assertEquals("String, Integer", nullSafeToString(Class::getSimpleName, String.class, Integer.class)); - assertEquals("String, null, Integer", - nullSafeToString(Class::getSimpleName, String.class, null, Integer.class)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java deleted file mode 100644 index 69c2a8f7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; - -import java.io.IOException; -import java.lang.module.ModuleFinder; -import java.net.URI; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.spi.ToolProvider; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.api.io.TempDir; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.function.Try; -import org.junit.platform.commons.logging.LogRecordListener; - -/** - * Unit tests for {@link ClasspathScanner}. - * - * @since 1.0 - */ -@TrackLogRecords -class ClasspathScannerTests { - - private static final ClassFilter allClasses = ClassFilter.of(type -> true); - - private final List> loadedClasses = new ArrayList<>(); - - private final BiFunction>> trackingClassLoader = (name, - classLoader) -> ReflectionUtils.tryToLoadClass(name, classLoader).ifSuccess(loadedClasses::add); - - private final ClasspathScanner classpathScanner = new ClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, - trackingClassLoader); - - @Test - void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccursWithNullDetailedMessage( - LogRecordListener listener) throws Exception { - - Predicate> malformedClassNameSimulationFilter = clazz -> { - if (clazz.getSimpleName().equals(ClassForMalformedClassNameSimulation.class.getSimpleName())) { - throw new InternalError(); - } - return true; - }; - - assertClassesScannedWhenExceptionIsThrown(malformedClassNameSimulationFilter); - assertDebugMessageLogged(listener, "Failed to load java.lang.Class for path .+ during classpath scanning."); - } - - @Test - void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccurs(LogRecordListener listener) - throws Exception { - - Predicate> malformedClassNameSimulationFilter = clazz -> { - if (clazz.getSimpleName().equals(ClassForMalformedClassNameSimulation.class.getSimpleName())) { - throw new InternalError("Malformed class name"); - } - return true; - }; - - assertClassesScannedWhenExceptionIsThrown(malformedClassNameSimulationFilter); - assertDebugMessageLogged(listener, "The java.lang.Class loaded from path .+ has a malformed class name .+"); - } - - @Test - void scanForClassesInClasspathRootWhenOtherInternalErrorOccurs(LogRecordListener listener) throws Exception { - Predicate> otherInternalErrorSimulationFilter = clazz -> { - if (clazz.getSimpleName().equals(ClassForOtherInternalErrorSimulation.class.getSimpleName())) { - throw new InternalError("other internal error"); - } - return true; - }; - - assertClassesScannedWhenExceptionIsThrown(otherInternalErrorSimulationFilter); - assertDebugMessageLogged(listener, "Failed to load java.lang.Class for path .+ during classpath scanning."); - } - - @Test - void scanForClassesInClasspathRootWhenGenericRuntimeExceptionOccurs(LogRecordListener listener) throws Exception { - Predicate> runtimeExceptionSimulationFilter = clazz -> { - if (clazz.getSimpleName().equals(ClassForGenericRuntimeExceptionSimulation.class.getSimpleName())) { - throw new RuntimeException("a generic exception"); - } - return true; - }; - - assertClassesScannedWhenExceptionIsThrown(runtimeExceptionSimulationFilter); - assertDebugMessageLogged(listener, "Failed to load java.lang.Class for path .+ during classpath scanning."); - } - - private void assertClassesScannedWhenExceptionIsThrown(Predicate> filter) throws Exception { - var classFilter = ClassFilter.of(filter); - var classes = this.classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter); - assertThat(classes.size()).isGreaterThanOrEqualTo(150); - } - - private void assertDebugMessageLogged(LogRecordListener listener, String regex) { - // @formatter:off - assertThat(listener.stream(ClasspathScanner.class, Level.FINE) - .map(LogRecord::getMessage) - .filter(m -> m.matches(regex)) - ).hasSize(1); - // @formatter:on - } - - @Test - void scanForClassesInClasspathRootWhenOutOfMemoryErrorOccurs() { - Predicate> outOfMemoryErrorSimulationFilter = clazz -> { - if (clazz.getSimpleName().equals(ClassForOutOfMemoryErrorSimulation.class.getSimpleName())) { - throw new OutOfMemoryError(); - } - return true; - }; - var classFilter = ClassFilter.of(outOfMemoryErrorSimulationFilter); - - assertThrows(OutOfMemoryError.class, - () -> this.classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter)); - } - - @Test - void scanForClassesInClasspathRootWithinJarFile() throws Exception { - scanForClassesInClasspathRootWithinJarFile("/jartest.jar"); - } - - @Test - void scanForClassesInClasspathRootWithinJarWithSpacesInPath() throws Exception { - scanForClassesInClasspathRootWithinJarFile("/folder with spaces/jar test with spaces.jar"); - } - - private void scanForClassesInClasspathRootWithinJarFile(String resourceName) throws Exception { - var jarfile = getClass().getResource(resourceName); - - try (var classLoader = new URLClassLoader(new URL[] { jarfile })) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); - - var classes = classpathScanner.scanForClassesInClasspathRoot(jarfile.toURI(), allClasses); - var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); - assertThat(classNames).hasSize(3) // - .contains("org.junit.platform.jartest.notincluded.NotIncluded", - "org.junit.platform.jartest.included.recursive.RecursivelyIncluded", - "org.junit.platform.jartest.included.Included"); - } - } - - @Test - void scanForClassesInPackage() { - var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); - assertThat(classes.size()).isGreaterThanOrEqualTo(20); - assertTrue(classes.contains(NestedClassToBeFound.class)); - assertTrue(classes.contains(MemberClassToBeFound.class)); - } - - @Test - // #2500 - void scanForClassesInPackageWithinModulesSharingNamePrefix(@TempDir Path temp) throws Exception { - var moduleSourcePath = Path.of(getClass().getResource("/modules-2500/").toURI()).toString(); - run("javac", "--module", "foo,foo.bar", "--module-source-path", moduleSourcePath, "-d", temp.toString()); - - checkModules2500(ModuleFinder.of(temp)); // exploded modules - - var foo = temp.resolve("foo.jar"); - var bar = temp.resolve("foo.bar.jar"); - run("jar", "--create", "--file", foo.toString(), "-C", temp.resolve("foo").toString(), "."); - run("jar", "--create", "--file", bar.toString(), "-C", temp.resolve("foo.bar").toString(), "."); - - checkModules2500(ModuleFinder.of(foo, bar)); // jarred modules - - System.gc(); // required on Windows in order to release JAR file handles - } - - private static int run(String tool, String... args) { - return ToolProvider.findFirst(tool).orElseThrow().run(System.out, System.err, args); - } - - private void checkModules2500(ModuleFinder finder) { - var root = "foo.bar"; - var before = ModuleFinder.of(); - var boot = ModuleLayer.boot(); - var configuration = boot.configuration().resolve(before, finder, Set.of(root)); - var parent = ClassLoader.getPlatformClassLoader(); - var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer(); - - var classpathScanner = new ClasspathScanner(() -> layer.findLoader(root), ReflectionUtils::tryToLoadClass); - { - var classes = classpathScanner.scanForClassesInPackage("foo", allClasses); - var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); - assertThat(classNames).hasSize(2).contains("foo.Foo", "foo.bar.FooBar"); - } - { - var classes = classpathScanner.scanForClassesInPackage("foo.bar", allClasses); - var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); - assertThat(classNames).hasSize(1).contains("foo.bar.FooBar"); - } - } - - @Test - void findAllClassesInPackageWithinJarFileConcurrently() throws Exception { - var jarFile = getClass().getResource("/jartest.jar"); - var jarUri = URI.create("jar:" + jarFile); - - try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); - - var results = executeConcurrently(10, - () -> classpathScanner.scanForClassesInPackage("org.junit.platform.jartest.included", allClasses)); - - assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), - "FileSystem should be closed"); - - results.forEach(classes -> { - assertThat(classes).hasSize(2); - var classNames = classes.stream().map(Class::getSimpleName).toList(); - assertTrue(classNames.contains("Included")); - assertTrue(classNames.contains("RecursivelyIncluded")); - }); - } - } - - @Test - void scanForClassesInDefaultPackage() { - var classFilter = ClassFilter.of(this::inDefaultPackage); - var classes = classpathScanner.scanForClassesInPackage("", classFilter); - - assertThat(classes.size()).as("number of classes found in default package").isGreaterThanOrEqualTo(1); - assertTrue(classes.stream().allMatch(this::inDefaultPackage)); - assertTrue(classes.stream().anyMatch(clazz -> "DefaultPackageTestCase".equals(clazz.getName()))); - } - - @Test - void scanForClassesInPackageWithFilter() { - var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); - var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", thisClassOnly); - assertSame(ClasspathScannerTests.class, classes.get(0)); - } - - @Test - void scanForClassesInPackageForNullBasePackage() { - assertThrows(PreconditionViolationException.class, - () -> classpathScanner.scanForClassesInPackage(null, allClasses)); - } - - @Test - void scanForClassesInPackageForWhitespaceBasePackage() { - assertThrows(PreconditionViolationException.class, - () -> classpathScanner.scanForClassesInPackage(" ", allClasses)); - } - - @Test - void scanForClassesInPackageForNullClassFilter() { - assertThrows(PreconditionViolationException.class, - () -> classpathScanner.scanForClassesInPackage("org.junit.platform.commons", null)); - } - - @Test - void scanForClassesInPackageWhenIOExceptionOccurs() { - var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); - var classes = scanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); - assertThat(classes).isEmpty(); - } - - @Test - void scanForClassesInPackageOnlyLoadsClassesThatAreIncludedByTheClassNameFilter() { - Predicate classNameFilter = name -> ClasspathScannerTests.class.getName().equals(name); - var classFilter = ClassFilter.of(classNameFilter, type -> true); - - classpathScanner.scanForClassesInPackage("org.junit.platform.commons", classFilter); - - assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); - } - - @Test - void findAllClassesInClasspathRoot() throws Exception { - var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); - var root = getTestClasspathRoot(); - var classes = classpathScanner.scanForClassesInClasspathRoot(root, thisClassOnly); - assertSame(ClasspathScannerTests.class, classes.get(0)); - } - - @Test - void findAllClassesInDefaultPackageInClasspathRoot() throws Exception { - var classFilter = ClassFilter.of(this::inDefaultPackage); - var classes = classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter); - - assertEquals(1, classes.size(), "number of classes found in default package"); - var testClass = classes.get(0); - assertTrue(inDefaultPackage(testClass)); - assertEquals("DefaultPackageTestCase", testClass.getName()); - } - - @Test - void doesNotLoopInfinitelyWithCircularSymlinks(@TempDir Path tempDir) throws Exception { - - // Abort if running on Microsoft Windows since we are testing symbolic links - assumeFalse(System.getProperty("os.name").toLowerCase().contains("win")); - - var directory = Files.createDirectory(tempDir.resolve("directory")); - var symlink1 = Files.createSymbolicLink(tempDir.resolve("symlink1"), directory); - Files.createSymbolicLink(directory.resolve("symlink2"), symlink1); - - var classes = classpathScanner.scanForClassesInClasspathRoot(symlink1.toUri(), allClasses); - - assertThat(classes).isEmpty(); - } - - private boolean inDefaultPackage(Class clazz) { - // OpenJDK returns NULL for the default package. - var pkg = clazz.getPackage(); - return pkg == null || "".equals(clazz.getPackage().getName()); - } - - @Test - void findAllClassesInClasspathRootWithFilter() throws Exception { - var root = getTestClasspathRoot(); - var classes = classpathScanner.scanForClassesInClasspathRoot(root, allClasses); - - assertThat(classes.size()).isGreaterThanOrEqualTo(20); - assertTrue(classes.contains(ClasspathScannerTests.class)); - } - - @Test - void findAllClassesInClasspathRootForNullRoot() { - assertThrows(PreconditionViolationException.class, - () -> classpathScanner.scanForClassesInClasspathRoot(null, allClasses)); - } - - @Test - void findAllClassesInClasspathRootForNonExistingRoot() { - assertThrows(PreconditionViolationException.class, - () -> classpathScanner.scanForClassesInClasspathRoot(Paths.get("does_not_exist").toUri(), allClasses)); - } - - @Test - void findAllClassesInClasspathRootForNullClassFilter() { - assertThrows(PreconditionViolationException.class, - () -> classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), null)); - } - - @Test - void onlyLoadsClassesInClasspathRootThatAreIncludedByTheClassNameFilter() throws Exception { - var classFilter = ClassFilter.of(name -> ClasspathScannerTests.class.getName().equals(name), type -> true); - var root = getTestClasspathRoot(); - - classpathScanner.scanForClassesInClasspathRoot(root, classFilter); - - assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); - } - - private URI getTestClasspathRoot() throws Exception { - var location = getClass().getProtectionDomain().getCodeSource().getLocation(); - return location.toURI(); - } - - class MemberClassToBeFound { - } - - static class NestedClassToBeFound { - } - - static class ClassForMalformedClassNameSimulation { - } - - static class ClassForOtherInternalErrorSimulation { - } - - static class ClassForGenericRuntimeExceptionSimulation { - } - - static class ClassForOutOfMemoryErrorSimulation { - } - - private static class ThrowingClassLoader extends ClassLoader { - - @Override - public Enumeration getResources(String name) throws IOException { - throw new IOException("Demo I/O error"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java deleted file mode 100644 index f2c4745c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.DoubleStream; -import java.util.stream.IntStream; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link CollectionUtils}. - * - * @since 1.0 - */ -class CollectionUtilsTests { - - @Test - void getOnlyElementWithNullCollection() { - var exception = assertThrows(PreconditionViolationException.class, () -> CollectionUtils.getOnlyElement(null)); - assertEquals("collection must not be null", exception.getMessage()); - } - - @Test - void getOnlyElementWithEmptyCollection() { - var exception = assertThrows(PreconditionViolationException.class, - () -> CollectionUtils.getOnlyElement(Set.of())); - assertEquals("collection must contain exactly one element: []", exception.getMessage()); - } - - @Test - void getOnlyElementWithSingleElementCollection() { - var expected = new Object(); - var actual = CollectionUtils.getOnlyElement(Set.of(expected)); - assertSame(expected, actual); - } - - @Test - void getOnlyElementWithMultiElementCollection() { - var exception = assertThrows(PreconditionViolationException.class, - () -> CollectionUtils.getOnlyElement(List.of("foo", "bar"))); - assertEquals("collection must contain exactly one element: [foo, bar]", exception.getMessage()); - } - - @Test - void toUnmodifiableListThrowsOnMutation() { - var numbers = Stream.of(1).collect(toUnmodifiableList()); - assertThrows(UnsupportedOperationException.class, numbers::clear); - } - - @ParameterizedTest - @ValueSource(classes = { // - Stream.class, // - DoubleStream.class, // - IntStream.class, // - LongStream.class, // - Collection.class, // - Iterable.class, // - Iterator.class, // - Object[].class, // - String[].class, // - int[].class, // - double[].class, // - char[].class // - }) - void isConvertibleToStreamForSupportedTypes(Class type) { - assertThat(CollectionUtils.isConvertibleToStream(type)).isTrue(); - } - - @ParameterizedTest - @MethodSource("objectsConvertibleToStreams") - void isConvertibleToStreamForSupportedTypesFromObjects(Object object) { - assertThat(CollectionUtils.isConvertibleToStream(object.getClass())).isTrue(); - } - - static Stream objectsConvertibleToStreams() { - return Stream.of(// - Stream.of("cat", "dog"), // - DoubleStream.of(42.3), // - IntStream.of(99), // - LongStream.of(100000000), // - Set.of(1, 2, 3), // - Arguments.of((Object) new Object[] { 9, 8, 7 }), // - new int[] { 5, 10, 15 }// - ); - } - - @ParameterizedTest - @ValueSource(classes = { // - void.class, // - Void.class, // - Object.class, // - Integer.class, // - String.class, // - int.class, // - boolean.class // - }) - void isConvertibleToStreamForUnsupportedTypes(Class type) { - assertThat(CollectionUtils.isConvertibleToStream(type)).isFalse(); - } - - @Test - void isConvertibleToStreamForNull() { - assertThat(CollectionUtils.isConvertibleToStream(null)).isFalse(); - } - - @Test - void toStreamWithNull() { - Exception exception = assertThrows(PreconditionViolationException.class, () -> CollectionUtils.toStream(null)); - - assertThat(exception).hasMessage("Object must not be null"); - } - - @Test - void toStreamWithUnsupportedObjectType() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> CollectionUtils.toStream("unknown")); - - assertThat(exception).hasMessage("Cannot convert instance of java.lang.String into a Stream: unknown"); - } - - @Test - void toStreamWithExistingStream() { - var input = Stream.of("foo"); - - var result = CollectionUtils.toStream(input); - - assertThat(result).isSameAs(input); - } - - @Test - @SuppressWarnings("unchecked") - void toStreamWithDoubleStream() { - var input = DoubleStream.of(42.23); - - var result = (Stream) CollectionUtils.toStream(input); - - assertThat(result).containsExactly(42.23); - } - - @Test - @SuppressWarnings("unchecked") - void toStreamWithIntStream() { - var input = IntStream.of(23, 42); - - var result = (Stream) CollectionUtils.toStream(input); - - assertThat(result).containsExactly(23, 42); - } - - @Test - @SuppressWarnings("unchecked") - void toStreamWithLongStream() { - var input = LongStream.of(23L, 42L); - - var result = (Stream) CollectionUtils.toStream(input); - - assertThat(result).containsExactly(23L, 42L); - } - - @Test - @SuppressWarnings({ "unchecked", "serial" }) - void toStreamWithCollection() { - var collectionStreamClosed = new AtomicBoolean(false); - Collection input = new ArrayList<>() { - - { - add("foo"); - add("bar"); - } - - @Override - public Stream stream() { - return super.stream().onClose(() -> collectionStreamClosed.set(true)); - } - }; - - try (var stream = (Stream) CollectionUtils.toStream(input)) { - var result = stream.collect(toList()); - assertThat(result).containsExactly("foo", "bar"); - } - - assertThat(collectionStreamClosed.get()).describedAs("collectionStreamClosed").isTrue(); - } - - @Test - @SuppressWarnings("unchecked") - void toStreamWithIterable() { - - Iterable input = () -> List.of("foo", "bar").iterator(); - - var result = (Stream) CollectionUtils.toStream(input); - - assertThat(result).containsExactly("foo", "bar"); - } - - @Test - @SuppressWarnings("unchecked") - void toStreamWithIterator() { - var input = List.of("foo", "bar").iterator(); - - var result = (Stream) CollectionUtils.toStream(input); - - assertThat(result).containsExactly("foo", "bar"); - } - - @Test - @SuppressWarnings("unchecked") - void toStreamWithArray() { - var result = (Stream) CollectionUtils.toStream(new String[] { "foo", "bar" }); - - assertThat(result).containsExactly("foo", "bar"); - } - - @TestFactory - Stream toStreamWithPrimitiveArrays() { - //@formatter:off - return Stream.of( - dynamicTest("boolean[]", - () -> toStreamWithPrimitiveArray(new boolean[] { true, false })), - dynamicTest("byte[]", - () -> toStreamWithPrimitiveArray(new byte[] { 0, Byte.MIN_VALUE, Byte.MAX_VALUE })), - dynamicTest("char[]", - () -> toStreamWithPrimitiveArray(new char[] { 0, Character.MIN_VALUE, Character.MAX_VALUE })), - dynamicTest("double[]", - () -> toStreamWithPrimitiveArray(new double[] { 0, Double.MIN_VALUE, Double.MAX_VALUE })), - dynamicTest("float[]", - () -> toStreamWithPrimitiveArray(new float[] { 0, Float.MIN_VALUE, Float.MAX_VALUE })), - dynamicTest("int[]", - () -> toStreamWithPrimitiveArray(new int[] { 0, Integer.MIN_VALUE, Integer.MAX_VALUE })), - dynamicTest("long[]", - () -> toStreamWithPrimitiveArray(new long[] { 0, Long.MIN_VALUE, Long.MAX_VALUE })), - dynamicTest("short[]", - () -> toStreamWithPrimitiveArray(new short[] { 0, Short.MIN_VALUE, Short.MAX_VALUE })) - ); - //@formatter:on - } - - private void toStreamWithPrimitiveArray(Object primitiveArray) { - assertTrue(primitiveArray.getClass().isArray()); - assertTrue(primitiveArray.getClass().getComponentType().isPrimitive()); - var result = CollectionUtils.toStream(primitiveArray).toArray(); - for (var i = 0; i < result.length; i++) { - assertEquals(Array.get(primitiveArray, i), result[i]); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java deleted file mode 100644 index b3750f52..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; -import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; - -import java.io.IOException; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link ExceptionUtils}. - * - * @since 1.0 - */ -class ExceptionUtilsTests { - - @Test - void throwAsUncheckedExceptionWithNullException() { - assertThrows(PreconditionViolationException.class, () -> throwAsUncheckedException(null)); - } - - @Test - void throwAsUncheckedExceptionWithCheckedException() { - assertThrows(IOException.class, () -> throwAsUncheckedException(new IOException())); - } - - @Test - void throwAsUncheckedExceptionWithUncheckedException() { - assertThrows(RuntimeException.class, () -> throwAsUncheckedException(new NumberFormatException())); - } - - @Test - void readStackTraceForNullThrowable() { - assertThrows(PreconditionViolationException.class, () -> readStackTrace(null)); - } - - @Test - void readStackTraceForLocalJUnitException() { - try { - throw new JUnitException("expected"); - } - catch (JUnitException e) { - // @formatter:off - assertThat(readStackTrace(e)) - .startsWith(JUnitException.class.getName() + ": expected") - .contains("at " + ExceptionUtilsTests.class.getName()); - // @formatter:on - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java deleted file mode 100644 index a543c652..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.function.Predicate.isEqual; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link FunctionUtils}. - * - * @since 1.0 - */ -class FunctionUtilsTests { - - @Test - void whereWithNullFunction() { - var exception = assertThrows(PreconditionViolationException.class, () -> FunctionUtils.where(null, o -> true)); - assertEquals("function must not be null", exception.getMessage()); - } - - @Test - void whereWithNullPredicate() { - var exception = assertThrows(PreconditionViolationException.class, () -> FunctionUtils.where(o -> o, null)); - assertEquals("predicate must not be null", exception.getMessage()); - } - - @Test - void whereWithChecksPredicateAgainstResultOfFunction() { - var combinedPredicate = FunctionUtils.where(String::length, isEqual(3)); - assertFalse(combinedPredicate.test("fo")); - assertTrue(combinedPredicate.test("foo")); - assertFalse(combinedPredicate.test("fooo")); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java deleted file mode 100644 index 7d7d6168..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.6 - */ -class LruCacheTests { - - @Test - void evictsEldestEntryWhenMaxSizeIsReached() { - var cache = new LruCache(1); - - cache.put(0, 0); - cache.put(1, 1); - - assertThat(cache) // - .doesNotContain(entry(0, 0)) // - .hasSize(1); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java deleted file mode 100644 index 5b8a42b5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link ModuleUtils}. - * - * @since 1.1 - */ -class ModuleUtilsTests { - - @Test - void isJavaPlatformModuleSystemAvailable() { - boolean expected; - try { - Class.forName("java.lang.Module"); - expected = true; - } - catch (ClassNotFoundException e) { - expected = false; - } - assertEquals(expected, ModuleUtils.isJavaPlatformModuleSystemAvailable()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java deleted file mode 100644 index 009ba2c5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.util.List; -import java.util.function.Function; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.commons.PreconditionViolationException; -import org.opentest4j.ValueWrapper; - -/** - * Unit tests for {@link PackageUtils}. - * - * @since 1.0 - */ -class PackageUtilsTests { - - @Test - void getAttributeWithNullType() { - var exception = assertThrows(PreconditionViolationException.class, - () -> PackageUtils.getAttribute(null, p -> "any")); - assertEquals("type must not be null", exception.getMessage()); - } - - @Test - void getAttributeWithNullFunction() { - var exception = assertThrows(PreconditionViolationException.class, - () -> PackageUtils.getAttribute(getClass(), (Function) null)); - assertEquals("function must not be null", exception.getMessage()); - } - - @Test - void getAttributeWithFunctionReturningNullIsEmpty() { - assertFalse(PackageUtils.getAttribute(ValueWrapper.class, p -> null).isPresent()); - } - - @Test - void getAttributeFromDefaultPackageMemberIsEmpty() throws Exception { - var classInDefaultPackage = ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get(); - assertFalse(PackageUtils.getAttribute(classInDefaultPackage, Package::getSpecificationTitle).isPresent()); - } - - @TestFactory - List attributesFromValueWrapperClassArePresent() { - return List.of( // - dynamicTest("getName", isPresent(Package::getName)), - dynamicTest("getImplementationTitle", isPresent(Package::getImplementationTitle)), - dynamicTest("getImplementationVendor", isPresent(Package::getImplementationVendor)), - dynamicTest("getImplementationVersion", isPresent(Package::getImplementationVersion)), - dynamicTest("getSpecificationTitle", isPresent(Package::getSpecificationTitle)), - dynamicTest("getSpecificationVendor", isPresent(Package::getSpecificationVendor)), - dynamicTest("getSpecificationVersion", isPresent(Package::getSpecificationVersion)) // - ); - } - - private Executable isPresent(Function function) { - return () -> assertTrue(PackageUtils.getAttribute(ValueWrapper.class, function).isPresent()); - } - - @Test - void getAttributeWithNullTypeAndName() { - var exception = assertThrows(PreconditionViolationException.class, - () -> PackageUtils.getAttribute(null, "foo")); - assertEquals("type must not be null", exception.getMessage()); - } - - @Test - void getAttributeWithNullName() { - var exception = assertThrows(PreconditionViolationException.class, - () -> PackageUtils.getAttribute(getClass(), (String) null)); - assertEquals("name must not be blank", exception.getMessage()); - } - - @Test - void getAttributeWithEmptyName() { - var exception = assertThrows(PreconditionViolationException.class, - () -> PackageUtils.getAttribute(getClass(), "")); - assertEquals("name must not be blank", exception.getMessage()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java deleted file mode 100644 index b525437b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.commons.util.Preconditions.condition; -import static org.junit.platform.commons.util.Preconditions.containsNoNullElements; -import static org.junit.platform.commons.util.Preconditions.notBlank; -import static org.junit.platform.commons.util.Preconditions.notEmpty; -import static org.junit.platform.commons.util.Preconditions.notNull; - -import java.util.Collection; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link Preconditions}. - * - * @since 1.0 - */ -class PreconditionsTests { - - @Test - void notNullPassesForNonNullObject() { - var object = new Object(); - var nonNullObject = notNull(object, "message"); - assertSame(object, nonNullObject); - } - - @Test - void notNullThrowsForNullObject() { - var message = "argument is null"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notNull(null, message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notNullThrowsForNullObjectAndMessageSupplier() { - var message = "argument is null"; - Object object = null; - - var exception = assertThrows(PreconditionViolationException.class, () -> notNull(object, () -> message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notEmptyPassesForNonEmptyArray() { - var array = new String[] { "a", "b", "c" }; - var nonEmptyArray = notEmpty(array, () -> "should not fail"); - assertSame(array, nonEmptyArray); - } - - @Test - void notEmptyPassesForNonEmptyCollection() { - Collection collection = List.of("a", "b", "c"); - var nonEmptyCollection = notEmpty(collection, () -> "should not fail"); - assertSame(collection, nonEmptyCollection); - } - - @Test - void notEmptyPassesForArrayWithNullElements() { - notEmpty(new String[] { null }, "message"); - } - - @Test - void notEmptyPassesForCollectionWithNullElements() { - notEmpty(singletonList(null), "message"); - } - - @Test - void notEmptyThrowsForNullArray() { - var message = "array is empty"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notEmpty((Object[]) null, message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notEmptyThrowsForNullCollection() { - var message = "collection is empty"; - - var exception = assertThrows(PreconditionViolationException.class, - () -> notEmpty((Collection) null, message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notEmptyThrowsForEmptyArray() { - var message = "array is empty"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notEmpty(new Object[0], message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notEmptyThrowsForEmptyCollection() { - var message = "collection is empty"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notEmpty(List.of(), message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void containsNoNullElementsPassesForArrayThatIsNullOrEmpty() { - containsNoNullElements((Object[]) null, "array is null"); - containsNoNullElements((Object[]) null, () -> "array is null"); - - containsNoNullElements(new Object[0], "array is empty"); - containsNoNullElements(new Object[0], () -> "array is empty"); - } - - @Test - void containsNoNullElementsPassesForCollectionThatIsNullOrEmpty() { - containsNoNullElements((List) null, "collection is null"); - containsNoNullElements(List.of(), "collection is empty"); - - containsNoNullElements((List) null, () -> "collection is null"); - containsNoNullElements(List.of(), () -> "collection is empty"); - } - - @Test - void containsNoNullElementsPassesForArrayContainingNonNullElements() { - var input = new String[] { "a", "b", "c" }; - var output = containsNoNullElements(input, "message"); - assertSame(input, output); - } - - @Test - void containsNoNullElementsPassesForCollectionContainingNonNullElements() { - var input = List.of("a", "b", "c"); - var output = containsNoNullElements(input, "message"); - assertSame(input, output); - - output = containsNoNullElements(input, () -> "message"); - assertSame(input, output); - } - - @Test - void containsNoNullElementsThrowsForArrayContainingNullElements() { - var message = "array contains null elements"; - Object[] array = { new Object(), null, new Object() }; - - var exception = assertThrows(PreconditionViolationException.class, - () -> containsNoNullElements(array, message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void containsNoNullElementsThrowsForCollectionContainingNullElements() { - var message = "collection contains null elements"; - - var exception = assertThrows(PreconditionViolationException.class, - () -> containsNoNullElements(singletonList(null), message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notBlankPassesForNonBlankString() { - var string = "abc"; - var nonBlankString = notBlank(string, "message"); - assertSame(string, nonBlankString); - } - - @Test - void notBlankThrowsForNullString() { - var message = "string shouldn't be blank"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(null, message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notBlankThrowsForNullStringWithMessageSupplier() { - var message = "string shouldn't be blank"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(null, () -> message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notBlankThrowsForEmptyString() { - var message = "string shouldn't be blank"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notBlank("", message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notBlankThrowsForEmptyStringWithMessageSupplier() { - var message = "string shouldn't be blank"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notBlank("", () -> message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notBlankThrowsForBlankString() { - var message = "string shouldn't be blank"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(" ", message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void notBlankThrowsForBlankStringWithMessageSupplier() { - var message = "string shouldn't be blank"; - - var exception = assertThrows(PreconditionViolationException.class, () -> notBlank(" ", () -> message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void conditionPassesForTruePredicate() { - condition(true, "error message"); - } - - @Test - void conditionPassesForTruePredicateWithMessageSupplier() { - condition(true, () -> "error message"); - } - - @Test - void conditionThrowsForFalsePredicate() { - var message = "condition does not hold"; - - var exception = assertThrows(PreconditionViolationException.class, () -> condition(false, message)); - - assertEquals(message, exception.getMessage()); - } - - @Test - void conditionThrowsForFalsePredicateWithMessageSupplier() { - var message = "condition does not hold"; - - var exception = assertThrows(PreconditionViolationException.class, () -> condition(false, () -> message)); - - assertEquals(message, exception.getMessage()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java deleted file mode 100644 index d667bce7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ /dev/null @@ -1,1906 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static java.time.Duration.ofMillis; -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.function.Try.success; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; -import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; -import static org.junit.platform.commons.util.ReflectionUtils.readFieldValue; -import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.api.io.TempDir; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.util.ReflectionUtilsTests.ClassWithNestedClasses.Nested1; -import org.junit.platform.commons.util.ReflectionUtilsTests.ClassWithNestedClasses.Nested2; -import org.junit.platform.commons.util.ReflectionUtilsTests.ClassWithNestedClasses.Nested3; -import org.junit.platform.commons.util.ReflectionUtilsTests.Interface45.Nested5; -import org.junit.platform.commons.util.ReflectionUtilsTests.InterfaceWithNestedClass.Nested4; -import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerClass; -import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerClass.RecursiveInnerInnerClass; -import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerSiblingClass; -import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.RecursiveInnerClass; -import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedClass; -import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedSiblingClass; -import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClassImplementingInterface.InnerClassImplementingInterface; - -/** - * Unit tests for {@link ReflectionUtils}. - * - * @since 1.0 - */ -class ReflectionUtilsTests { - - private static final Predicate isFooMethod = method -> method.getName().equals("foo"); - private static final Predicate methodContains1 = method -> method.getName().contains("1"); - private static final Predicate methodContains2 = method -> method.getName().contains("2"); - private static final Predicate methodContains4 = method -> method.getName().contains("4"); - private static final Predicate methodContains5 = method -> method.getName().contains("5"); - - @Test - void isPublic() throws Exception { - assertTrue(ReflectionUtils.isPublic(PublicClass.class)); - assertTrue(ReflectionUtils.isPublic(PublicClass.class.getMethod("publicMethod"))); - - assertFalse(ReflectionUtils.isPublic(PrivateClass.class)); - assertFalse(ReflectionUtils.isPublic(PrivateClass.class.getDeclaredMethod("privateMethod"))); - assertFalse(ReflectionUtils.isPublic(ProtectedClass.class)); - assertFalse(ReflectionUtils.isPublic(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); - assertFalse(ReflectionUtils.isPublic(PackageVisibleClass.class)); - assertFalse(ReflectionUtils.isPublic(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); - } - - @Test - void isPrivate() throws Exception { - assertTrue(ReflectionUtils.isPrivate(PrivateClass.class)); - assertTrue(ReflectionUtils.isPrivate(PrivateClass.class.getDeclaredMethod("privateMethod"))); - - assertFalse(ReflectionUtils.isPrivate(PublicClass.class)); - assertFalse(ReflectionUtils.isPrivate(PublicClass.class.getMethod("publicMethod"))); - assertFalse(ReflectionUtils.isPrivate(ProtectedClass.class)); - assertFalse(ReflectionUtils.isPrivate(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); - assertFalse(ReflectionUtils.isPrivate(PackageVisibleClass.class)); - assertFalse(ReflectionUtils.isPrivate(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); - } - - @Test - void isNotPrivate() throws Exception { - assertTrue(ReflectionUtils.isNotPrivate(PublicClass.class)); - assertTrue(ReflectionUtils.isNotPrivate(PublicClass.class.getDeclaredMethod("publicMethod"))); - assertTrue(ReflectionUtils.isNotPrivate(ProtectedClass.class)); - assertTrue(ReflectionUtils.isNotPrivate(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); - assertTrue(ReflectionUtils.isNotPrivate(PackageVisibleClass.class)); - assertTrue(ReflectionUtils.isNotPrivate(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); - - assertFalse(ReflectionUtils.isNotPrivate(PrivateClass.class.getDeclaredMethod("privateMethod"))); - } - - @Test - void isAbstract() throws Exception { - assertTrue(ReflectionUtils.isAbstract(AbstractClass.class)); - assertTrue(ReflectionUtils.isAbstract(AbstractClass.class.getDeclaredMethod("abstractMethod"))); - - assertFalse(ReflectionUtils.isAbstract(PublicClass.class)); - assertFalse(ReflectionUtils.isAbstract(PublicClass.class.getDeclaredMethod("publicMethod"))); - } - - @Test - void isStatic() throws Exception { - assertTrue(ReflectionUtils.isStatic(StaticClass.class)); - assertTrue(ReflectionUtils.isStatic(StaticClass.class.getDeclaredMethod("staticMethod"))); - - assertFalse(ReflectionUtils.isStatic(PublicClass.class)); - assertFalse(ReflectionUtils.isStatic(PublicClass.class.getDeclaredMethod("publicMethod"))); - } - - @Test - void isNotStatic() throws Exception { - assertTrue(ReflectionUtils.isNotStatic(PublicClass.class)); - assertTrue(ReflectionUtils.isNotStatic(PublicClass.class.getDeclaredMethod("publicMethod"))); - - assertFalse(ReflectionUtils.isNotStatic(StaticClass.class)); - assertFalse(ReflectionUtils.isNotStatic(StaticClass.class.getDeclaredMethod("staticMethod"))); - } - - @Test - void isFinal() throws Exception { - assertTrue(ReflectionUtils.isFinal(FinalClass.class)); - assertTrue(ReflectionUtils.isFinal(FinalClass.class.getDeclaredMethod("finalMethod"))); - - assertFalse(ReflectionUtils.isFinal(PublicClass.class)); - assertFalse(ReflectionUtils.isFinal(PublicClass.class.getDeclaredMethod("publicMethod"))); - } - - @Test - void isNotFinal() throws Exception { - assertTrue(ReflectionUtils.isNotFinal(PublicClass.class)); - assertTrue(ReflectionUtils.isNotFinal(PublicClass.class.getDeclaredMethod("publicMethod"))); - - assertFalse(ReflectionUtils.isNotFinal(FinalClass.class)); - assertFalse(ReflectionUtils.isNotFinal(FinalClass.class.getDeclaredMethod("finalMethod"))); - } - - @Test - void returnsVoid() throws Exception { - Class clazz = ClassWithVoidAndNonVoidMethods.class; - assertTrue(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("voidMethod"))); - - assertFalse(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("methodReturningVoidReference"))); - assertFalse(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("methodReturningObject"))); - assertFalse(ReflectionUtils.returnsVoid(clazz.getDeclaredMethod("methodReturningPrimitive"))); - } - - @Test - void getAllAssignmentCompatibleClassesWithNullClass() { - assertThrows(PreconditionViolationException.class, - () -> ReflectionUtils.getAllAssignmentCompatibleClasses(null)); - } - - @Test - void getAllAssignmentCompatibleClasses() { - var superclasses = ReflectionUtils.getAllAssignmentCompatibleClasses(B.class); - assertThat(superclasses).containsExactly(B.class, InterfaceC.class, InterfaceA.class, InterfaceB.class, A.class, - InterfaceD.class, Object.class); - assertTrue(superclasses.stream().allMatch(clazz -> clazz.isAssignableFrom(B.class))); - } - - @Test - void newInstance() { - // @formatter:off - assertThat(ReflectionUtils.newInstance(C.class, "one", "two")).isNotNull(); - assertThat(ReflectionUtils.newInstance(C.class)).isNotNull(); - - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, "one", null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, null, "two")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, null, null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.newInstance(C.class, ((Object[]) null))); - - var exception = assertThrows(RuntimeException.class, () -> ReflectionUtils.newInstance(Exploder.class)); - assertThat(exception).hasMessage("boom"); - // @formatter:on - } - - @Test - @SuppressWarnings("deprecation") - void readFieldValueOfNonexistentStaticField() { - assertThat(readFieldValue(MyClass.class, "doesNotExist", null)).isNotPresent(); - assertThat(readFieldValue(MySubClass.class, "staticField", null)).isNotPresent(); - } - - @Test - void tryToReadFieldValueOfNonexistentStaticField() { - assertThrows(NoSuchFieldException.class, () -> tryToReadFieldValue(MyClass.class, "doesNotExist", null).get()); - assertThrows(NoSuchFieldException.class, - () -> tryToReadFieldValue(MySubClass.class, "staticField", null).get()); - } - - @Test - @SuppressWarnings("deprecation") - void readFieldValueOfNonexistentInstanceField() { - assertThat(readFieldValue(MyClass.class, "doesNotExist", new MyClass(42))).isNotPresent(); - assertThat(readFieldValue(MyClass.class, "doesNotExist", new MySubClass(42))).isNotPresent(); - } - - @Test - void tryToReadFieldValueOfNonexistentInstanceField() { - assertThrows(NoSuchFieldException.class, - () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MyClass(42)).get()); - assertThrows(NoSuchFieldException.class, - () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MySubClass(42)).get()); - } - - @Test - @SuppressWarnings("deprecation") - void readFieldValueOfExistingStaticField() throws Exception { - assertThat(readFieldValue(MyClass.class, "staticField", null)).contains(42); - - var field = MyClass.class.getDeclaredField("staticField"); - assertThat(readFieldValue(field)).contains(42); - assertThat(readFieldValue(field, null)).contains(42); - } - - @Test - void tryToReadFieldValueOfExistingStaticField() throws Exception { - assertThat(tryToReadFieldValue(MyClass.class, "staticField", null).get()).isEqualTo(42); - - var field = MyClass.class.getDeclaredField("staticField"); - assertThat(tryToReadFieldValue(field).get()).isEqualTo(42); - assertThat(tryToReadFieldValue(field, null).get()).isEqualTo(42); - } - - @Test - @SuppressWarnings("deprecation") - void readFieldValueOfExistingInstanceField() throws Exception { - var instance = new MyClass(42); - assertThat(readFieldValue(MyClass.class, "instanceField", instance)).contains(42); - - var field = MyClass.class.getDeclaredField("instanceField"); - assertThat(readFieldValue(field, instance)).contains(42); - } - - @Test - @SuppressWarnings("deprecation") - void attemptToReadFieldValueOfExistingInstanceFieldAsStaticField() throws Exception { - var field = MyClass.class.getDeclaredField("instanceField"); - Exception exception = assertThrows(PreconditionViolationException.class, () -> readFieldValue(field, null)); - assertThat(exception)// - .hasMessageStartingWith("Cannot read non-static field")// - .hasMessageEndingWith("on a null instance."); - } - - @Test - void tryToReadFieldValueOfExistingInstanceField() throws Exception { - var instance = new MyClass(42); - assertThat(tryToReadFieldValue(MyClass.class, "instanceField", instance).get()).isEqualTo(42); - - var field = MyClass.class.getDeclaredField("instanceField"); - assertThat(tryToReadFieldValue(field, instance).get()).isEqualTo(42); - assertThrows(PreconditionViolationException.class, () -> tryToReadFieldValue(field, null).get()); - } - - @Nested - class IsClassAssignableToClassTests { - - @Test - void isAssignableToForNullSourceType() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> ReflectionUtils.isAssignableTo(null, getClass()))// - .withMessage("source type must not be null"); - } - - @Test - void isAssignableToForPrimitiveSourceType() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> ReflectionUtils.isAssignableTo(int.class, Integer.class))// - .withMessage("source type must not be a primitive type"); - } - - @Test - void isAssignableToForNullTargetType() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> ReflectionUtils.isAssignableTo(getClass(), null))// - .withMessage("target type must not be null"); - } - - @Test - void isAssignableTo() { - // Reference Types - assertTrue(ReflectionUtils.isAssignableTo(String.class, Object.class)); - assertTrue(ReflectionUtils.isAssignableTo(String.class, CharSequence.class)); - assertTrue(ReflectionUtils.isAssignableTo(String.class, String.class)); - assertTrue(ReflectionUtils.isAssignableTo(Integer.class, Number.class)); - assertTrue(ReflectionUtils.isAssignableTo(Integer.class, Integer.class)); - - assertFalse(ReflectionUtils.isAssignableTo(Object.class, String.class)); - assertFalse(ReflectionUtils.isAssignableTo(CharSequence.class, String.class)); - assertFalse(ReflectionUtils.isAssignableTo(Number.class, Integer.class)); - - // Arrays - assertTrue(ReflectionUtils.isAssignableTo(int[].class, int[].class)); - assertTrue(ReflectionUtils.isAssignableTo(double[].class, double[].class)); - assertTrue(ReflectionUtils.isAssignableTo(double[].class, Object.class)); - assertTrue(ReflectionUtils.isAssignableTo(String[].class, Object.class)); - assertTrue(ReflectionUtils.isAssignableTo(String[].class, Object[].class)); - assertTrue(ReflectionUtils.isAssignableTo(String[].class, String[].class)); - - // Wrappers to Primitives - assertTrue(ReflectionUtils.isAssignableTo(Integer.class, int.class)); - assertTrue(ReflectionUtils.isAssignableTo(Boolean.class, boolean.class)); - - // Widening Conversions from Wrappers to Primitives - assertTrue(ReflectionUtils.isAssignableTo(Integer.class, long.class)); - assertTrue(ReflectionUtils.isAssignableTo(Float.class, double.class)); - assertTrue(ReflectionUtils.isAssignableTo(Byte.class, double.class)); - - // Widening Conversions from Wrappers to Wrappers (not supported by Java) - assertFalse(ReflectionUtils.isAssignableTo(Integer.class, Long.class)); - assertFalse(ReflectionUtils.isAssignableTo(Float.class, Double.class)); - assertFalse(ReflectionUtils.isAssignableTo(Byte.class, Double.class)); - - // Narrowing Conversions - assertFalse(ReflectionUtils.isAssignableTo(Integer.class, char.class)); - assertFalse(ReflectionUtils.isAssignableTo(Long.class, byte.class)); - assertFalse(ReflectionUtils.isAssignableTo(Long.class, int.class)); - } - - } - - @Nested - class IsObjectAssignableToClassTests { - - @Test - void isAssignableToForNullClass() { - assertThrows(PreconditionViolationException.class, - () -> ReflectionUtils.isAssignableTo(new Object(), null)); - } - - @Test - void isAssignableTo() { - // Reference Types - assertTrue(ReflectionUtils.isAssignableTo("string", String.class)); - assertTrue(ReflectionUtils.isAssignableTo("string", CharSequence.class)); - assertTrue(ReflectionUtils.isAssignableTo("string", Object.class)); - - assertFalse(ReflectionUtils.isAssignableTo(new Object(), String.class)); - assertFalse(ReflectionUtils.isAssignableTo(Integer.valueOf("1"), StringBuilder.class)); - assertFalse(ReflectionUtils.isAssignableTo(new StringBuilder(), String.class)); - - // Arrays - assertTrue(ReflectionUtils.isAssignableTo(new int[0], int[].class)); - assertTrue(ReflectionUtils.isAssignableTo(new double[0], Object.class)); - assertTrue(ReflectionUtils.isAssignableTo(new String[0], String[].class)); - assertTrue(ReflectionUtils.isAssignableTo(new String[0], Object.class)); - - // Primitive Types - assertTrue(ReflectionUtils.isAssignableTo(1, int.class)); - assertTrue(ReflectionUtils.isAssignableTo(Long.valueOf("1"), long.class)); - assertTrue(ReflectionUtils.isAssignableTo(Boolean.TRUE, boolean.class)); - - // Widening Conversions to Primitives - assertTrue(ReflectionUtils.isAssignableTo(1, long.class)); - assertTrue(ReflectionUtils.isAssignableTo(1f, double.class)); - assertTrue(ReflectionUtils.isAssignableTo((byte) 1, double.class)); - - // Widening Conversions to Wrappers (not supported by Java) - assertFalse(ReflectionUtils.isAssignableTo(1, Long.class)); - assertFalse(ReflectionUtils.isAssignableTo(1f, Double.class)); - assertFalse(ReflectionUtils.isAssignableTo((byte) 1, Double.class)); - - // Narrowing Conversions - assertFalse(ReflectionUtils.isAssignableTo(1, char.class)); - assertFalse(ReflectionUtils.isAssignableTo(1L, byte.class)); - assertFalse(ReflectionUtils.isAssignableTo(1L, int.class)); - } - - @Test - void isAssignableToForNullObject() { - assertTrue(ReflectionUtils.isAssignableTo((Object) null, Object.class)); - assertTrue(ReflectionUtils.isAssignableTo((Object) null, String.class)); - assertTrue(ReflectionUtils.isAssignableTo((Object) null, Long.class)); - assertTrue(ReflectionUtils.isAssignableTo((Object) null, Character[].class)); - } - - @Test - void isAssignableToForNullObjectAndPrimitive() { - assertFalse(ReflectionUtils.isAssignableTo((Object) null, byte.class)); - assertFalse(ReflectionUtils.isAssignableTo((Object) null, int.class)); - assertFalse(ReflectionUtils.isAssignableTo((Object) null, long.class)); - assertFalse(ReflectionUtils.isAssignableTo((Object) null, boolean.class)); - } - - } - - @Test - void wideningConversion() { - // byte - assertTrue(ReflectionUtils.isWideningConversion(byte.class, short.class)); - assertTrue(ReflectionUtils.isWideningConversion(byte.class, int.class)); - assertTrue(ReflectionUtils.isWideningConversion(byte.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(byte.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(byte.class, double.class)); - // Byte - assertTrue(ReflectionUtils.isWideningConversion(Byte.class, short.class)); - assertTrue(ReflectionUtils.isWideningConversion(Byte.class, int.class)); - assertTrue(ReflectionUtils.isWideningConversion(Byte.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(Byte.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(Byte.class, double.class)); - - // short - assertTrue(ReflectionUtils.isWideningConversion(short.class, int.class)); - assertTrue(ReflectionUtils.isWideningConversion(short.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(short.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(short.class, double.class)); - // Short - assertTrue(ReflectionUtils.isWideningConversion(Short.class, int.class)); - assertTrue(ReflectionUtils.isWideningConversion(Short.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(Short.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(Short.class, double.class)); - - // char - assertTrue(ReflectionUtils.isWideningConversion(char.class, int.class)); - assertTrue(ReflectionUtils.isWideningConversion(char.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(char.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(char.class, double.class)); - // Character - assertTrue(ReflectionUtils.isWideningConversion(Character.class, int.class)); - assertTrue(ReflectionUtils.isWideningConversion(Character.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(Character.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(Character.class, double.class)); - - // int - assertTrue(ReflectionUtils.isWideningConversion(int.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(int.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(int.class, double.class)); - // Integer - assertTrue(ReflectionUtils.isWideningConversion(Integer.class, long.class)); - assertTrue(ReflectionUtils.isWideningConversion(Integer.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(Integer.class, double.class)); - - // long - assertTrue(ReflectionUtils.isWideningConversion(long.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(long.class, double.class)); - // Long - assertTrue(ReflectionUtils.isWideningConversion(Long.class, float.class)); - assertTrue(ReflectionUtils.isWideningConversion(Long.class, double.class)); - - // float - assertTrue(ReflectionUtils.isWideningConversion(float.class, double.class)); - // Float - assertTrue(ReflectionUtils.isWideningConversion(Float.class, double.class)); - - // double and Double --> nothing to test - - // Unsupported - assertFalse(ReflectionUtils.isWideningConversion(int.class, byte.class)); // narrowing - assertFalse(ReflectionUtils.isWideningConversion(float.class, int.class)); // narrowing - assertFalse(ReflectionUtils.isWideningConversion(int.class, int.class)); // direct match - assertFalse(ReflectionUtils.isWideningConversion(String.class, int.class)); // neither a primitive nor a wrapper - } - - @Test - void invokeMethodPreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> invokeMethod(null, new Object())); - assertThrows(PreconditionViolationException.class, () -> invokeMethod(Object.class.getMethod("hashCode"), null)); - // @formatter:on - } - - @Test - void invokePublicMethod() throws Exception { - var tracker = new InvocationTracker(); - invokeMethod(InvocationTracker.class.getDeclaredMethod("publicMethod"), tracker); - assertTrue(tracker.publicMethodInvoked); - } - - @Test - void invokePrivateMethod() throws Exception { - var tracker = new InvocationTracker(); - invokeMethod(InvocationTracker.class.getDeclaredMethod("privateMethod"), tracker); - assertTrue(tracker.privateMethodInvoked); - } - - @Test - void invokePublicStaticMethod() throws Exception { - invokeMethod(InvocationTracker.class.getDeclaredMethod("publicStaticMethod"), null); - assertTrue(InvocationTracker.publicStaticMethodInvoked); - } - - @Test - void invokePrivateStaticMethod() throws Exception { - invokeMethod(InvocationTracker.class.getDeclaredMethod("privateStaticMethod"), null); - assertTrue(InvocationTracker.privateStaticMethodInvoked); - } - - @Test - void tryToLoadClassPreconditions() { - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass("")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(" ")); - - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(null, null)); - assertThrows(PreconditionViolationException.class, - () -> ReflectionUtils.tryToLoadClass(getClass().getName(), null)); - } - - @Test - @SuppressWarnings("deprecation") - void loadClassWhenClassNotFoundException() { - assertThat(ReflectionUtils.loadClass("foo.bar.EnigmaClassThatDoesNotExist")).isEmpty(); - } - - @Test - void tryToLoadClassWhenClassNotFoundException() { - assertThrows(ClassNotFoundException.class, - () -> ReflectionUtils.tryToLoadClass("foo.bar.EnigmaClassThatDoesNotExist").get()); - } - - @Test - void tryToLoadClassFailsWithinReasonableTimeForInsanelyLargeAndInvalidMultidimensionalPrimitiveArrayName() { - // Create a class name of the form int[][][]...[][][]X - String className = IntStream.rangeClosed(1, 20_000)// - .mapToObj(i -> "[]")// - .collect(joining("", "int", "X")); - - // The following should ideally fail in less than 50ms. So we just make - // sure it fails in less than 500ms in order to (hopefully) allow the - // test to pass on CI servers with limited resources. - assertTimeoutPreemptively(ofMillis(500), - () -> assertThrows(ClassNotFoundException.class, () -> ReflectionUtils.tryToLoadClass(className).get())); - } - - @Test - @SuppressWarnings("deprecation") - void loadClass() { - var optional = ReflectionUtils.loadClass(Integer.class.getName()); - assertThat(optional).contains(Integer.class); - } - - @Test - void tryToLoadClass() { - assertThat(ReflectionUtils.tryToLoadClass(Integer.class.getName())).isEqualTo(success(Integer.class)); - } - - @Test - void tryToLoadClassTrimsClassName() { - assertThat(ReflectionUtils.tryToLoadClass(" " + Integer.class.getName() + "\t")).isEqualTo( - success(Integer.class)); - } - - @Test - void tryToLoadClassForPrimitive() { - assertThat(ReflectionUtils.tryToLoadClass(int.class.getName())).isEqualTo(success(int.class)); - } - - @Test - void tryToLoadClassForPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[].class.getName())).isEqualTo(success(int[].class)); - } - - @Test - void tryToLoadClassForPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[]")).isEqualTo(success(int[].class)); - } - - @Test - void tryToLoadClassForObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[].class.getName())).isEqualTo(success(String[].class)); - } - - @Test - void tryToLoadClassForObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[]")).isEqualTo(success(String[].class)); - } - - @Test - void tryToLoadClassForTwoDimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][].class.getName())).isEqualTo(success(int[][].class)); - } - - @Test - void tryToLoadClassForTwoDimensionaldimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][]")).isEqualTo(success(int[][].class)); - } - - @Test - void tryToLoadClassForMultidimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][][][][].class.getName())).isEqualTo( - success(int[][][][][].class)); - } - - @Test - void tryToLoadClassForMultidimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][][][][]")).isEqualTo(success(int[][][][][].class)); - } - - @Test - void tryToLoadClassForMultidimensionalObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[][][][][].class.getName())).isEqualTo( - success(String[][][][][].class)); - } - - @Test - void tryToLoadClassForMultidimensionalObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[][][][][]")).isEqualTo( - success(String[][][][][].class)); - } - - @Test - void getFullyQualifiedMethodNamePreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getFullyQualifiedMethodName(null, null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getFullyQualifiedMethodName(null, "testMethod")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getFullyQualifiedMethodName(Object.class, null)); - // @formatter:on - } - - @Test - void getFullyQualifiedMethodNameForMethodWithoutParameters() { - assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "toString"))// - .isEqualTo("java.lang.Object#toString()"); - } - - @Test - void getFullyQualifiedMethodNameForMethodWithNullParameters() { - assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "toString", (Class[]) null))// - .isEqualTo("java.lang.Object#toString()"); - } - - @Test - void getFullyQualifiedMethodNameForMethodWithSingleParameter() { - assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "equals", Object.class))// - .isEqualTo("java.lang.Object#equals(java.lang.Object)"); - } - - @Test - void getFullyQualifiedMethodNameForMethodWithMultipleParameters() { - // @formatter:off - assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "testMethod", int.class, Object.class))// - .isEqualTo("java.lang.Object#testMethod(int, java.lang.Object)"); - // @formatter:on - } - - @Test - void parseFullyQualifiedMethodNamePreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName(null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName(" ")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("java.lang.Object#")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("#equals")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("#")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("java.lang.Object")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("equals")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("()")); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.parseFullyQualifiedMethodName("(int, java.lang.Object)")); - // @formatter:on - } - - @Test - void parseFullyQualifiedMethodNameForMethodWithoutParameters() { - assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method()"))// - .containsExactly("com.example.Test", "method", ""); - } - - @Test - void parseFullyQualifiedMethodNameForMethodWithSingleParameter() { - assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method(java.lang.Object)"))// - .containsExactly("com.example.Test", "method", "java.lang.Object"); - } - - @Test - void parseFullyQualifiedMethodNameForMethodWithMultipleParameters() { - assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method(int, java.lang.Object)"))// - .containsExactly("com.example.Test", "method", "int, java.lang.Object"); - } - - @Test - @SuppressWarnings("deprecation") - void getOutermostInstancePreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(null, null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(null, Object.class)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(new Object(), null)); - // @formatter:on - } - - @Test - @SuppressWarnings("deprecation") - void getOutermostInstance() { - var firstClass = new FirstClass(); - var secondClass = firstClass.new SecondClass(); - var thirdClass = secondClass.new ThirdClass(); - - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.SecondClass.ThirdClass.class))// - .contains(thirdClass); - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.SecondClass.class))// - .contains(secondClass); - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.class)).contains(firstClass); - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, String.class)).isEmpty(); - } - - @Test - void getAllClasspathRootDirectories(@TempDir Path tempDirectory) throws Exception { - var root1 = tempDirectory.resolve("root1").toAbsolutePath(); - var root2 = tempDirectory.resolve("root2").toAbsolutePath(); - var testClassPath = root1 + File.pathSeparator + root2; - - var originalClassPath = System.setProperty("java.class.path", testClassPath); - try { - createDirectories(root1, root2); - - assertThat(ReflectionUtils.getAllClasspathRootDirectories()).containsOnly(root1, root2); - } - finally { - System.setProperty("java.class.path", originalClassPath); - } - } - - @Test - void findNestedClassesPreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.findNestedClasses(null, null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.findNestedClasses(null, clazz -> true)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.findNestedClasses(FirstClass.class, null)); - // @formatter:on - } - - @Test - void findNestedClasses() { - // @formatter:off - assertThat(findNestedClasses(Object.class)).isEmpty(); - - assertThat(findNestedClasses(ClassWithNestedClasses.class)) - .containsOnly(Nested1.class, Nested2.class, Nested3.class); - - assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, clazz -> clazz.getName().contains("1"))) - .containsExactly(Nested1.class); - - assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)) - .containsExactly(Nested3.class); - - assertThat(findNestedClasses(ClassExtendingClassWithNestedClasses.class)) - .containsOnly(Nested1.class, Nested2.class, Nested3.class, Nested4.class, Nested5.class); - - assertThat(findNestedClasses(ClassWithNestedClasses.Nested1.class)).isEmpty(); - // @formatter:on - } - - /** - * @since 1.6 - */ - @Test - void findNestedClassesWithSeeminglyRecursiveHierarchies() { - assertThat(findNestedClasses(AbstractOuterClass.class))// - .containsExactly(AbstractOuterClass.InnerClass.class); - - // OuterClass contains recursive hierarchies, but the non-matching - // predicate should prevent cycle detection. - // See https://github.com/junit-team/junit5/issues/2249 - assertThat(ReflectionUtils.findNestedClasses(OuterClass.class, clazz -> false)).isEmpty(); - // RecursiveInnerInnerClass is part of a recursive hierarchy, but the non-matching - // predicate should prevent cycle detection. - assertThat(ReflectionUtils.findNestedClasses(RecursiveInnerInnerClass.class, clazz -> false)).isEmpty(); - - // Sibling types don't actually result in cycles. - assertThat(findNestedClasses(StaticNestedSiblingClass.class))// - .containsExactly(AbstractOuterClass.InnerClass.class); - assertThat(findNestedClasses(InnerSiblingClass.class))// - .containsExactly(AbstractOuterClass.InnerClass.class); - - // Interfaces with static nested classes - assertThat(findNestedClasses(OuterClassImplementingInterface.class))// - .containsExactly(InnerClassImplementingInterface.class, Nested4.class); - assertThat(findNestedClasses(InnerClassImplementingInterface.class))// - .containsExactly(Nested4.class); - } - - /** - * @since 1.6 - */ - @Test - void findNestedClassesWithRecursiveHierarchies() { - Runnable runnable1 = () -> assertNestedCycle(OuterClass.class, InnerClass.class, OuterClass.class); - Runnable runnable2 = () -> assertNestedCycle(StaticNestedClass.class, InnerClass.class, OuterClass.class); - Runnable runnable3 = () -> assertNestedCycle(RecursiveInnerClass.class, OuterClass.class); - Runnable runnable4 = () -> assertNestedCycle(RecursiveInnerInnerClass.class, OuterClass.class); - Runnable runnable5 = () -> assertNestedCycle(InnerClass.class, RecursiveInnerInnerClass.class, - OuterClass.class); - Stream.of(runnable1, runnable1, runnable1, runnable2, runnable2, runnable2, runnable3, runnable3, runnable3, - runnable4, runnable4, runnable4, runnable5, runnable5, runnable5).parallel().forEach(Runnable::run); - } - - private static List> findNestedClasses(Class clazz) { - return ReflectionUtils.findNestedClasses(clazz, c -> true); - } - - private void assertNestedCycle(Class from, Class to) { - assertNestedCycle(from, from, to); - } - - private void assertNestedCycle(Class start, Class from, Class to) { - assertThatExceptionOfType(JUnitException.class)// - .as("expected cycle from %s to %s", from.getSimpleName(), to.getSimpleName())// - .isThrownBy(() -> findNestedClasses(start))// - .withMessageMatching(String.format("Detected cycle in inner class hierarchy between .+%s and .+%s", - from.getSimpleName(), to.getSimpleName())); - } - - /** - * @since 1.3 - */ - @Test - @TrackLogRecords - void findNestedClassesWithInvalidNestedClassFile(LogRecordListener listener) throws Exception { - var jarUrl = getClass().getResource("/gh-1436-invalid-nested-class-file.jar"); - - try (var classLoader = new URLClassLoader(new URL[] { jarUrl })) { - var fqcn = "tests.NestedInterfaceGroovyTests"; - var classWithInvalidNestedClassFile = classLoader.loadClass(fqcn); - - assertEquals(fqcn, classWithInvalidNestedClassFile.getName()); - var noClassDefFoundError = assertThrows(NoClassDefFoundError.class, - classWithInvalidNestedClassFile::getDeclaredClasses); - assertEquals("tests/NestedInterfaceGroovyTests$NestedInterface$1", noClassDefFoundError.getMessage()); - - assertThat(findNestedClasses(classWithInvalidNestedClassFile)).isEmpty(); - // @formatter:off - var logMessage = listener.stream(ReflectionUtils.class, Level.FINE) - .findFirst() - .map(LogRecord::getMessage) - .orElse("didn't find log record"); - // @formatter:on - assertThat(logMessage).isEqualTo("Failed to retrieve declared classes for " + fqcn); - } - } - - @Test - void getDeclaredConstructorPreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getDeclaredConstructor(null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getDeclaredConstructor(ClassWithTwoConstructors.class)); - // @formatter:on - } - - @Test - void getDeclaredConstructor() { - Constructor constructor = ReflectionUtils.getDeclaredConstructor(getClass()); - assertNotNull(constructor); - assertEquals(getClass(), constructor.getDeclaringClass()); - - constructor = ReflectionUtils.getDeclaredConstructor(ClassWithOneCustomConstructor.class); - assertNotNull(constructor); - assertEquals(ClassWithOneCustomConstructor.class, constructor.getDeclaringClass()); - assertEquals(String.class, constructor.getParameterTypes()[0]); - } - - @Test - void tryToGetMethodPreconditions() { - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(null, null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(String.class, null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(null, "hashCode")); - } - - @Test - void tryToGetMethod() throws Exception { - assertThat(ReflectionUtils.tryToGetMethod(Object.class, "hashCode").get()).isEqualTo( - Object.class.getMethod("hashCode")); - assertThat(ReflectionUtils.tryToGetMethod(String.class, "charAt", int.class).get())// - .isEqualTo(String.class.getMethod("charAt", int.class)); - - assertThat(ReflectionUtils.tryToGetMethod(Path.class, "subpath", int.class, int.class).get())// - .isEqualTo(Path.class.getMethod("subpath", int.class, int.class)); - assertThat(ReflectionUtils.tryToGetMethod(String.class, "chars").get()).isEqualTo( - String.class.getMethod("chars")); - - assertThat(ReflectionUtils.tryToGetMethod(String.class, "noSuchMethod").toOptional()).isEmpty(); - assertThat(ReflectionUtils.tryToGetMethod(Object.class, "clone", int.class).toOptional()).isEmpty(); - } - - @Test - void isMethodPresentPreconditions() { - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isMethodPresent(null, m -> true)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isMethodPresent(getClass(), null)); - } - - @Test - void isMethodPresent() { - Predicate isMethod1 = method -> (method.getName().equals("method1") - && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == String.class); - - assertThat(ReflectionUtils.isMethodPresent(MethodShadowingChild.class, isMethod1)).isTrue(); - - assertThat(ReflectionUtils.isMethodPresent(getClass(), isMethod1)).isFalse(); - } - - @Test - void findMethodByParameterTypesPreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> findMethod(null, null)); - assertThrows(PreconditionViolationException.class, () -> findMethod(null, "method")); - - RuntimeException exception = assertThrows(PreconditionViolationException.class, () -> findMethod(String.class, null)); - assertThat(exception).hasMessage("Method name must not be null or blank"); - - exception = assertThrows(PreconditionViolationException.class, () -> findMethod(String.class, " ")); - assertThat(exception).hasMessage("Method name must not be null or blank"); - - exception = assertThrows(PreconditionViolationException.class, () -> findMethod(Files.class, "copy", (Class[]) null)); - assertThat(exception).hasMessage("Parameter types array must not be null"); - - exception = assertThrows(PreconditionViolationException.class, () -> findMethod(Files.class, "copy", (Class) null)); - assertThat(exception).hasMessage("Individual parameter types must not be null"); - - exception = assertThrows(PreconditionViolationException.class, () -> findMethod(Files.class, "copy", new Class[] { Path.class, null })); - assertThat(exception).hasMessage("Individual parameter types must not be null"); - // @formatter:on - } - - @Test - void findMethodByParameterTypes() throws Exception { - assertThat(findMethod(Object.class, "noSuchMethod")).isEmpty(); - assertThat(findMethod(String.class, "noSuchMethod")).isEmpty(); - - assertThat(findMethod(String.class, "chars")).contains(String.class.getMethod("chars")); - assertThat(findMethod(Files.class, "copy", Path.class, OutputStream.class))// - .contains(Files.class.getMethod("copy", Path.class, OutputStream.class)); - - assertThat(findMethod(MethodShadowingChild.class, "method1", String.class))// - .contains(MethodShadowingChild.class.getMethod("method1", String.class)); - } - - @Test - void findMethodByParameterTypesInGenericInterface() { - Class ifc = InterfaceWithGenericDefaultMethod.class; - var method = findMethod(ifc, "foo", Number.class); - assertThat(method).isNotEmpty(); - assertThat(method.get().getName()).isEqualTo("foo"); - } - - /** - * @see #findMethodByParameterTypesWithOverloadedMethodNextToGenericDefaultMethod() - */ - @Test - void findMethodByParameterTypesInGenericInterfaceViaParameterizedSubclass() { - Class clazz = InterfaceWithGenericDefaultMethodImpl.class; - var method = findMethod(clazz, "foo", Long.class); - assertThat(method).isNotEmpty(); - assertThat(method.get().getName()).isEqualTo("foo"); - - // One might expect or desire that the signature for the generic foo(N) - // default method would be "foo(java.lang.Long)" when looked up via the - // concrete parameterized class, but it apparently is only _visible_ as - // "foo(java.lang.Number)" via reflection. Hence the following assertion - // checks for java.lang.Number instead of java.lang.Long. - assertThat(method.get().getParameterTypes()[0]).isEqualTo(Number.class); - } - - /** - * This test is identical to - * {@link #findMethodByParameterTypesInGenericInterfaceViaParameterizedSubclass()}, - * except that this test attempts to find the overloaded - * {@link InterfaceWithGenericDefaultMethodImpl#foo(Double)} method instead of - * the {@link InterfaceWithGenericDefaultMethod#foo(Number)} default method. - */ - @Test - void findMethodByParameterTypesWithOverloadedMethodNextToGenericDefaultMethod() { - Class clazz = InterfaceWithGenericDefaultMethodImpl.class; - Class parameterType = Double.class; - var method = findMethod(clazz, "foo", parameterType); - assertThat(method).isNotEmpty(); - assertThat(method.get().getName()).isEqualTo("foo"); - assertThat(method.get().getParameterTypes()[0]).isEqualTo(parameterType); - } - - @Test - void findMethodByParameterNamesWithPrimitiveArrayParameter() throws Exception { - assertFindMethodByParameterNames("methodWithPrimitiveArray", int[].class); - } - - @Test - void findMethodByParameterNamesWithTwoDimensionalPrimitiveArrayParameter() throws Exception { - assertFindMethodByParameterNames("methodWithTwoDimensionalPrimitiveArray", int[][].class); - } - - @Test - void findMethodByParameterNamesWithMultidimensionalPrimitiveArrayParameter() throws Exception { - assertFindMethodByParameterNames("methodWithMultidimensionalPrimitiveArray", int[][][][][].class); - } - - @Test - void findMethodByParameterNamesWithObjectArrayParameter() throws Exception { - assertFindMethodByParameterNames("methodWithObjectArray", String[].class); - } - - @Test - void findMethodByParameterNamesWithMultidimensionalObjectArrayParameter() throws Exception { - assertFindMethodByParameterNames("methodWithMultidimensionalObjectArray", Double[][][][][].class); - } - - @Test - void findMethodByParameterNamesWithParameterizedMapParameter() throws Exception { - var methodName = "methodWithParameterizedMap"; - - // standard, supported use case - assertFindMethodByParameterNames(methodName, Map.class); - - // generic type info in parameter list - var method = getClass().getDeclaredMethod(methodName, Map.class); - var genericParameterTypeName = method.getGenericParameterTypes()[0].getTypeName(); - var exception = assertThrows(JUnitException.class, - () -> findMethod(getClass(), methodName, genericParameterTypeName)); - - assertThat(exception).hasMessageStartingWith("Failed to load parameter type [java.util.Map parameterType) - throws NoSuchMethodException { - - var method = getClass().getDeclaredMethod(methodName, parameterType); - var optional = findMethod(getClass(), methodName, parameterType.getName()); - assertThat(optional).contains(method); - } - - @Test - void findMethodsPreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> findMethods(null, null)); - assertThrows(PreconditionViolationException.class, () -> findMethods(null, clazz -> true)); - assertThrows(PreconditionViolationException.class, () -> findMethods(String.class, null)); - - assertThrows(PreconditionViolationException.class, () -> findMethods(null, null, null)); - assertThrows(PreconditionViolationException.class, () -> findMethods(null, clazz -> true, BOTTOM_UP)); - assertThrows(PreconditionViolationException.class, () -> findMethods(String.class, null, BOTTOM_UP)); - assertThrows(PreconditionViolationException.class, () -> findMethods(String.class, clazz -> true, null)); - // @formatter:on - } - - @Test - void findMethodsInInterface() { - assertOneFooMethodIn(InterfaceWithOneDeclaredMethod.class); - assertOneFooMethodIn(InterfaceWithDefaultMethod.class); - assertOneFooMethodIn(InterfaceWithDefaultMethodImpl.class); - assertOneFooMethodIn(InterfaceWithStaticMethod.class); - assertOneFooMethodIn(InterfaceWithStaticMethodImpl.class); - } - - private static void assertOneFooMethodIn(Class clazz) { - assertThat(findMethods(clazz, isFooMethod)).hasSize(1); - } - - /** - * @since 1.9.1 - * @see https://github.com/junit-team/junit5/issues/2993 - */ - @Test - void findMethodsFindsDistinctMethodsDeclaredInMultipleInterfaces() { - Predicate isStringsMethod = method -> method.getName().equals("strings"); - assertThat(findMethods(DoubleInheritedInterfaceMethodTestCase.class, isStringsMethod)).hasSize(1); - } - - @Test - void findMethodsInObject() { - var methods = findMethods(Object.class, method -> true); - assertNotNull(methods); - assertTrue(methods.size() > 10); - } - - @Test - void findMethodsInVoid() { - assertThat(findMethods(void.class, method -> true)).isEmpty(); - assertThat(findMethods(Void.class, method -> true)).isEmpty(); - } - - @Test - void findMethodsInPrimitive() { - assertThat(findMethods(int.class, method -> true)).isEmpty(); - } - - @Test - void findMethodsInArrays() { - assertThat(findMethods(int[].class, method -> true)).isEmpty(); - assertThat(findMethods(Integer[].class, method -> true)).isEmpty(); - } - - @Test - void findMethodsIgnoresSyntheticMethods() { - assertTrue(stream(ClassWithSyntheticMethod.class.getDeclaredMethods()).anyMatch(Method::isSynthetic), - "ClassWithSyntheticMethod must actually contain at least one synthetic method."); - - var methods = findMethods(ClassWithSyntheticMethod.class, method -> true); - assertThat(methods).isEmpty(); - } - - @Test - void findMethodsUsingHierarchyUpMode() throws Exception { - assertThat(findMethods(ChildClass.class, method -> method.getName().contains("method"), BOTTOM_UP))// - .containsExactly(ChildClass.class.getMethod("method4"), ParentClass.class.getMethod("method3"), - GrandparentInterface.class.getMethod("method2"), GrandparentClass.class.getMethod("method1")); - - assertThat(findMethods(ChildClass.class, method -> method.getName().contains("other"), BOTTOM_UP))// - .containsExactly(ChildClass.class.getMethod("otherMethod3"), - ParentClass.class.getMethod("otherMethod2"), GrandparentClass.class.getMethod("otherMethod1")); - - assertThat(findMethods(ChildClass.class, method -> method.getName().equals("method2"), BOTTOM_UP))// - .containsExactly(ParentClass.class.getMethod("method2")); - - assertThat(findMethods(ChildClass.class, method -> method.getName().equals("wrongName"), BOTTOM_UP)).isEmpty(); - - assertThat(findMethods(ParentClass.class, method -> method.getName().contains("method"), BOTTOM_UP))// - .containsExactly(ParentClass.class.getMethod("method3"), - GrandparentInterface.class.getMethod("method2"), GrandparentClass.class.getMethod("method1")); - } - - @Test - void findMethodsUsingHierarchyDownMode() throws Exception { - assertThat(findMethods(ChildClass.class, method -> method.getName().contains("method"), TOP_DOWN))// - .containsExactly(GrandparentClass.class.getMethod("method1"), - GrandparentInterface.class.getMethod("method2"), ParentClass.class.getMethod("method3"), - ChildClass.class.getMethod("method4")); - - assertThat(findMethods(ChildClass.class, method -> method.getName().contains("other"), TOP_DOWN))// - .containsExactly(GrandparentClass.class.getMethod("otherMethod1"), - ParentClass.class.getMethod("otherMethod2"), ChildClass.class.getMethod("otherMethod3")); - - assertThat(findMethods(ChildClass.class, method -> method.getName().equals("method2"), TOP_DOWN))// - .containsExactly(ParentClass.class.getMethod("method2")); - - assertThat(findMethods(ChildClass.class, method -> method.getName().equals("wrongName"), TOP_DOWN)).isEmpty(); - - assertThat(findMethods(ParentClass.class, method -> method.getName().contains("method"), TOP_DOWN))// - .containsExactly(GrandparentClass.class.getMethod("method1"), - GrandparentInterface.class.getMethod("method2"), ParentClass.class.getMethod("method3")); - } - - @Test - void findMethodsWithShadowingUsingHierarchyUpMode() throws Exception { - assertThat(findMethods(MethodShadowingChild.class, methodContains1, BOTTOM_UP))// - .containsExactly(MethodShadowingChild.class.getMethod("method1", String.class)); - - assertThat(findMethods(MethodShadowingChild.class, methodContains2, BOTTOM_UP))// - .containsExactly(MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), - MethodShadowingInterface.class.getMethod("method2", int.class, int.class)); - - assertThat(findMethods(MethodShadowingChild.class, methodContains4, BOTTOM_UP))// - .containsExactly(MethodShadowingChild.class.getMethod("method4", boolean.class)); - - assertThat(findMethods(MethodShadowingChild.class, methodContains5, BOTTOM_UP))// - .containsExactly(MethodShadowingChild.class.getMethod("method5", Long.class), - MethodShadowingParent.class.getMethod("method5", String.class)); - - var methods = findMethods(MethodShadowingChild.class, method -> true, BOTTOM_UP); - assertEquals(6, methods.size()); - assertThat(methods.subList(0, 3)).containsOnly(MethodShadowingChild.class.getMethod("method4", boolean.class), - MethodShadowingChild.class.getMethod("method1", String.class), - MethodShadowingChild.class.getMethod("method5", Long.class)); - assertThat(methods.subList(3, 5)).containsOnly( - MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), - MethodShadowingParent.class.getMethod("method5", String.class)); - assertEquals(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), methods.get(5)); - } - - @Test - void findMethodsWithShadowingUsingHierarchyDownMode() throws Exception { - assertThat(findMethods(MethodShadowingChild.class, methodContains1, TOP_DOWN))// - .containsExactly(MethodShadowingChild.class.getMethod("method1", String.class)); - - assertThat(findMethods(MethodShadowingChild.class, methodContains2, TOP_DOWN))// - .containsExactly(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), - MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class)); - - assertThat(findMethods(MethodShadowingChild.class, methodContains4, TOP_DOWN))// - .containsExactly(MethodShadowingChild.class.getMethod("method4", boolean.class)); - - assertThat(findMethods(MethodShadowingChild.class, methodContains5, TOP_DOWN))// - .containsExactly(MethodShadowingParent.class.getMethod("method5", String.class), - MethodShadowingChild.class.getMethod("method5", Long.class)); - - var methods = findMethods(MethodShadowingChild.class, method -> true, TOP_DOWN); - assertEquals(6, methods.size()); - assertEquals(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), methods.get(0)); - assertThat(methods.subList(1, 3)).containsOnly( - MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), - MethodShadowingParent.class.getMethod("method5", String.class)); - assertThat(methods.subList(3, 6)).containsOnly(MethodShadowingChild.class.getMethod("method4", boolean.class), - MethodShadowingChild.class.getMethod("method1", String.class), - MethodShadowingChild.class.getMethod("method5", Long.class)); - } - - @Test - void findMethodsWithStaticHidingUsingHierarchyUpMode() throws Exception { - Class ifc = StaticMethodHidingInterface.class; - Class parent = StaticMethodHidingParent.class; - Class child = StaticMethodHidingChild.class; - - var ifcMethod2 = ifc.getDeclaredMethod("method2", int.class, int.class); - var childMethod1 = child.getDeclaredMethod("method1", String.class); - var childMethod4 = child.getDeclaredMethod("method4", boolean.class); - var childMethod5 = child.getDeclaredMethod("method5", Long.class); - var parentMethod2 = parent.getDeclaredMethod("method2", int.class, int.class, int.class); - var parentMethod5 = parent.getDeclaredMethod("method5", String.class); - - assertThat(findMethods(child, methodContains1, BOTTOM_UP)).containsExactly(childMethod1); - assertThat(findMethods(child, methodContains2, BOTTOM_UP)).containsExactly(parentMethod2, ifcMethod2); - assertThat(findMethods(child, methodContains4, BOTTOM_UP)).containsExactly(childMethod4); - assertThat(findMethods(child, methodContains5, BOTTOM_UP)).containsExactly(childMethod5, parentMethod5); - - var methods = findMethods(child, method -> true, BOTTOM_UP); - assertEquals(6, methods.size()); - assertThat(methods.subList(0, 3)).containsOnly(childMethod1, childMethod4, childMethod5); - assertThat(methods.subList(3, 5)).containsOnly(parentMethod2, parentMethod5); - assertEquals(ifcMethod2, methods.get(5)); - } - - @Test - void findMethodsWithStaticHidingUsingHierarchyDownMode() throws Exception { - Class ifc = StaticMethodHidingInterface.class; - Class parent = StaticMethodHidingParent.class; - Class child = StaticMethodHidingChild.class; - - var ifcMethod2 = ifc.getDeclaredMethod("method2", int.class, int.class); - var childMethod1 = child.getDeclaredMethod("method1", String.class); - var childMethod4 = child.getDeclaredMethod("method4", boolean.class); - var childMethod5 = child.getDeclaredMethod("method5", Long.class); - var parentMethod2 = parent.getDeclaredMethod("method2", int.class, int.class, int.class); - var parentMethod5 = parent.getDeclaredMethod("method5", String.class); - - assertThat(findMethods(child, methodContains1, TOP_DOWN)).containsExactly(childMethod1); - assertThat(findMethods(child, methodContains2, TOP_DOWN)).containsExactly(ifcMethod2, parentMethod2); - assertThat(findMethods(child, methodContains4, TOP_DOWN)).containsExactly(childMethod4); - assertThat(findMethods(child, methodContains5, TOP_DOWN)).containsExactly(parentMethod5, childMethod5); - - var methods = findMethods(child, method -> true, TOP_DOWN); - assertEquals(6, methods.size()); - assertEquals(ifcMethod2, methods.get(0)); - assertThat(methods.subList(1, 3)).containsOnly(parentMethod2, parentMethod5); - assertThat(methods.subList(3, 6)).containsOnly(childMethod1, childMethod4, childMethod5); - } - - @Test - void findMethodsReturnsAllOverloadedMethodsThatAreNotShadowed() { - Class clazz = InterfaceWithGenericDefaultMethodImpl.class; - - // Search for all foo(*) methods. - var methods = findMethods(clazz, isFooMethod); - - // One might expect or desire that the signature for the generic foo(N) - // default method would be "foo(java.lang.Long)" when looked up via the - // concrete parameterized class, but it apparently is only _visible_ as - // "foo(java.lang.Number)" via reflection. - assertThat(signaturesOf(methods)).containsExactly("foo(java.lang.Number)", "foo(java.lang.Double)"); - } - - @Test - void findMethodsDoesNotReturnOverriddenDefaultMethods() { - Class clazz = InterfaceWithOverriddenGenericDefaultMethodImpl.class; - - // Search for all foo(*) methods. - var methods = findMethods(clazz, isFooMethod); - var signatures = signaturesOf(methods); - - // Although the subsequent assertion covers this case as well, this - // assertion is in place to provide a more informative failure message. - assertThat(signatures).as("overridden default method should not be in results").doesNotContain( - "foo(java.lang.Number)"); - assertThat(signatures).containsExactly("foo(java.lang.Long)", "foo(java.lang.Double)"); - } - - private static List signaturesOf(List methods) { - // @formatter:off - return methods.stream() - .map(m -> String.format("%s(%s)", m.getName(), ClassUtils.nullSafeToString(m.getParameterTypes()))) - .collect(toList()); - // @formatter:on - } - - @Test - void findMethodsIgnoresBridgeMethods() throws Exception { - assertFalse(Modifier.isPublic(PublicChildClass.class.getSuperclass().getModifiers())); - assertTrue(Modifier.isPublic(PublicChildClass.class.getModifiers())); - assertTrue(PublicChildClass.class.getDeclaredMethod("method1").isBridge()); - assertTrue(PublicChildClass.class.getDeclaredMethod("method3").isBridge()); - - var methods = findMethods(PublicChildClass.class, method -> true); - var signatures = signaturesOf(methods); - assertThat(signatures).containsOnly("method1()", "method2()", "method3()", "otherMethod1()", "otherMethod2()"); - assertEquals(0, methods.stream().filter(Method::isBridge).count()); - } - - @Test - void isGeneric() { - for (var method : Generic.class.getMethods()) { - assertTrue(ReflectionUtils.isGeneric(method)); - } - for (var method : PublicClass.class.getMethods()) { - assertFalse(ReflectionUtils.isGeneric(method)); - } - } - - @Test - void readFieldValuesPreconditions() { - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.readFieldValues(null, new Object())); - assertThrows(PreconditionViolationException.class, - () -> ReflectionUtils.readFieldValues(new ArrayList<>(), new Object(), null)); - } - - @Test - void readFieldValuesFromInstance() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, f -> true, TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields()); - - assertThat(values).containsExactly("enigma", 3.14, "text", 2.5, null, 42, "constant", 99); - } - - @Test - void readFieldValuesFromClass() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, ReflectionUtils::isStatic, TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, null); - - assertThat(values).containsExactly(2.5, "constant", 99); - } - - @Test - void readFieldValuesFromInstanceWithTypeFilterForString() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class), TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(String.class)); - - assertThat(values).containsExactly("enigma", "text", null, "constant"); - } - - @Test - void readFieldValuesFromClassWithTypeFilterForString() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class).and(ReflectionUtils::isStatic), - TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, null, isA(String.class)); - - assertThat(values).containsExactly("constant"); - } - - @Test - void readFieldValuesFromInstanceWithTypeFilterForInteger() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(int.class), TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(int.class)); - - assertThat(values).containsExactly(42); - } - - @Test - void readFieldValuesFromClassWithTypeFilterForInteger() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, - isA(Integer.class).and(ReflectionUtils::isStatic), TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, null, isA(Integer.class)); - - assertThat(values).containsExactly(99); - } - - @Test - void readFieldValuesFromInstanceWithTypeFilterForDouble() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(double.class), TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(double.class)); - - assertThat(values).containsExactly(3.14); - } - - @Test - void readFieldValuesFromClassWithTypeFilterForDouble() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(Double.class).and(ReflectionUtils::isStatic), - TOP_DOWN); - - var values = ReflectionUtils.readFieldValues(fields, null, isA(Double.class)); - - assertThat(values).containsExactly(2.5); - } - - private Predicate isA(Class type) { - return f -> f.getType().isAssignableFrom(type); - } - - private static void createDirectories(Path... paths) throws IOException { - for (var path : paths) { - Files.createDirectory(path); - } - } - - // ------------------------------------------------------------------------- - - void methodWithPrimitiveArray(int[] nums) { - } - - void methodWithTwoDimensionalPrimitiveArray(int[][] grid) { - } - - void methodWithMultidimensionalPrimitiveArray(int[][][][][] grid) { - } - - void methodWithObjectArray(String[] info) { - } - - void methodWithTwoDimensionalObjectArray(String[][] info) { - } - - void methodWithMultidimensionalObjectArray(Double[][][][][] data) { - } - - void methodWithParameterizedMap(Map map) { - } - - interface StringsInterface1 { - static Stream strings() { - return Stream.of("abc", "def"); - } - } - - interface StringsInterface2 extends StringsInterface1 { - } - - /** - * Inherits strings() from interfaces StringsInterface1 and StringsInterface2. - */ - static class DoubleInheritedInterfaceMethodTestCase implements StringsInterface1, StringsInterface2 { - } - - interface Generic { - - X foo(); - - Y foo(X x, Y y); - - Z foo(Z[][] zees); - - int foo(T t); - - T foo(int i); - } - - class ClassWithSyntheticMethod { - - // The following lambda expression results in a synthetic method in the - // compiled byte code. - Comparable synthetic = number -> 0; - } - - interface InterfaceWithOneDeclaredMethod { - - void foo(); - } - - interface InterfaceWithDefaultMethod { - - default void foo() { - } - } - - static class InterfaceWithDefaultMethodImpl implements InterfaceWithDefaultMethod { - } - - interface InterfaceWithGenericDefaultMethod { - - default void foo(N number) { - } - } - - static class InterfaceWithGenericDefaultMethodImpl implements InterfaceWithGenericDefaultMethod { - - void foo(Double number) { - } - } - - static class InterfaceWithOverriddenGenericDefaultMethodImpl implements InterfaceWithGenericDefaultMethod { - - @Override - public void foo(Long number) { - } - - void foo(Double number) { - } - } - - interface InterfaceWithStaticMethod { - - static void foo() { - } - } - - static class InterfaceWithStaticMethodImpl implements InterfaceWithStaticMethod { - } - - interface InterfaceA { - } - - interface InterfaceB { - } - - interface InterfaceC extends InterfaceA, InterfaceB { - } - - interface InterfaceD { - } - - static class A implements InterfaceA, InterfaceD { - } - - static class B extends A implements InterfaceC { - } - - static class C { - - C() { - } - - C(String a, String b) { - } - - } - - static class Exploder { - - Exploder() { - throw new RuntimeException("boom"); - } - - } - - static class MyClass { - - static final int staticField = 42; - - final int instanceField; - - MyClass(int value) { - this.instanceField = value; - } - } - - static class MySubClass extends MyClass { - - MySubClass(int value) { - super(value); - } - } - - // Intentionally non-static - public class PublicClass { - - public void publicMethod() { - } - - public void method(String str, Integer num) { - } - - public void method(String[] strings, Integer[] nums) { - } - - public void method(boolean b, char c) { - } - - public void method(char[] characters, int[] nums) { - } - } - - private class PrivateClass { - - @SuppressWarnings("unused") - private void privateMethod() { - } - } - - protected class ProtectedClass { - - @SuppressWarnings("unused") - protected void protectedMethod() { - } - } - - class PackageVisibleClass { - - @SuppressWarnings("unused") - void packageVisibleMethod() { - } - } - - final class FinalClass { - - @SuppressWarnings("unused") - final void finalMethod() { - } - } - - abstract static class AbstractClass { - - abstract void abstractMethod(); - } - - static class StaticClass { - - static void staticMethod() { - } - } - - static class ClassWithVoidAndNonVoidMethods { - - void voidMethod() { - } - - Void methodReturningVoidReference() { - return null; - } - - String methodReturningObject() { - return ""; - } - - int methodReturningPrimitive() { - return 0; - } - - } - - static class InvocationTracker { - - static boolean publicStaticMethodInvoked; - static boolean privateStaticMethodInvoked; - - boolean publicMethodInvoked; - boolean privateMethodInvoked; - - public static void publicStaticMethod() { - publicStaticMethodInvoked = true; - } - - @SuppressWarnings("unused") - private static void privateStaticMethod() { - privateStaticMethodInvoked = true; - } - - public void publicMethod() { - publicMethodInvoked = true; - } - - @SuppressWarnings("unused") - private void privateMethod() { - privateMethodInvoked = true; - } - } - - static class FirstClass { - - class SecondClass { - - class ThirdClass { - } - } - } - - static class ClassWithNestedClasses { - - class Nested1 { - } - - class Nested2 { - } - - static class Nested3 { - } - } - - interface InterfaceWithNestedClass { - - class Nested4 { - } - } - - interface Interface45 extends InterfaceWithNestedClass { - - class Nested5 { - } - } - - static class ClassExtendingClassWithNestedClasses extends ClassWithNestedClasses implements Interface45 { - } - - abstract static class AbstractOuterClass { - - class InnerClass { - } - } - - static class OuterClass extends AbstractOuterClass { - - // sibling of OuterClass due to common super type - static class StaticNestedSiblingClass extends AbstractOuterClass { - } - - // sibling of OuterClass due to common super type - class InnerSiblingClass extends AbstractOuterClass { - } - - static class StaticNestedClass extends OuterClass { - } - - class RecursiveInnerClass extends OuterClass { - } - - class InnerClass { - class RecursiveInnerInnerClass extends OuterClass { - } - } - } - - static class OuterClassImplementingInterface implements InterfaceWithNestedClass { - - class InnerClassImplementingInterface implements InterfaceWithNestedClass { - } - } - - static class GrandparentClass { - - public void method1() { - } - - public void otherMethod1() { - } - } - - interface GrandparentInterface { - - default void method2() { - } - } - - static class ParentClass extends GrandparentClass implements GrandparentInterface { - - public void method3() { - } - - public void otherMethod2() { - } - } - - static class ChildClass extends ParentClass { - - public void method4() { - } - - public void otherMethod3() { - } - } - - interface MethodShadowingInterface { - - default void method1(String string) { - } - - default void method2(int i, int j) { - } - } - - static class MethodShadowingParent implements MethodShadowingInterface { - - @Override - public void method1(String string) { - } - - public void method2(int i, int j, int k) { - } - - public void method4(boolean flag) { - } - - public void method5(String string) { - } - } - - static class MethodShadowingChild extends MethodShadowingParent { - - @Override - public void method1(String string) { - } - - @Override - public void method4(boolean flag) { - } - - public void method5(Long i) { - } - } - - interface StaticMethodHidingInterface { - - static void method1(String string) { - } - - static void method2(int i, int j) { - } - } - - static class StaticMethodHidingParent implements StaticMethodHidingInterface { - - static void method1(String string) { - } - - static void method2(int i, int j, int k) { - } - - static void method4(boolean flag) { - } - - static void method5(String string) { - } - } - - static class StaticMethodHidingChild extends StaticMethodHidingParent { - - static void method1(String string) { - } - - static void method4(boolean flag) { - } - - static void method5(Long i) { - } - } - - // "public" modifier is necessary here, so that the compiler creates a bridge method. - public static class PublicChildClass extends ParentClass { - - @Override - public void otherMethod1() { - } - - @Override - public void otherMethod2() { - } - } - - public static class ClassWithFields { - - public static final String CONST = "constant"; - - public static final Integer CONST_INTEGER = 99; - - public static final Double CONST_DOUBLE = 2.5; - - public final String stringField = "text"; - - @SuppressWarnings("unused") - private final String privateStringField = "enigma"; - - final String nullStringField = null; - - public final int integerField = 42; - - public final double doubleField = 3.14; - - } - - @SuppressWarnings("unused") - private static class ClassWithOneCustomConstructor { - - ClassWithOneCustomConstructor(String str) { - } - } - - @SuppressWarnings("unused") - private static class ClassWithTwoConstructors { - - ClassWithTwoConstructors() { - } - - ClassWithTwoConstructors(String str) { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java deleted file mode 100644 index 8e8bb060..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("TypeParameterExplicitlyExtendsObject") -class ReflectionUtilsWithGenericTypeHierarchiesTests { - @Test - @Disabled("Describes a new case that does not yet yield the expected result.") - void findsMethodsIndependentlyFromOrderOfImplementationsOfInterfaces() { - - class AB implements InterfaceDouble, InterfaceGenericNumber { - } - - class BA implements InterfaceGenericNumber, InterfaceDouble { - } - - var methodAB = findMethod(AB.class, "foo", Double.class).orElseThrow(); - var methodBA = findMethod(BA.class, "foo", Double.class).orElseThrow(); - - assertEquals(methodAB, methodBA); - } - - @Test - void findMoreSpecificMethodFromAbstractImplementationOverDefaultInterfaceMethod() { - class A implements InterfaceGenericNumber { - @Override - public void foo(Long parameter) { - } - } - - var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); - - assertEquals(A.class, foo.getDeclaringClass()); - } - - @Test - @Disabled("Describes a new case that does not yet yield the expected result.") - void findMoreSpecificMethodFromOverriddenImplementationOfGenericInterfaceMethod() { - class A implements InterfaceGenericNumber { - @Override - public void foo(Number parameter) { - } - } - - var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); - - assertEquals(A.class, foo.getDeclaringClass()); - } - - @Test - @Disabled("Describes a new case that does not yet yield the expected result.") - void findMoreSpecificMethodFromImplementationOverDefaultInterfaceMethodAndGenericClassExtension() { - - class AParent { - @SuppressWarnings("unused") - public void foo(Number parameter) { - } - } - - class A extends AParent implements InterfaceGenericNumber { - @Override - public void foo(Number parameter) { - } - } - - var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); - - assertEquals(A.class, foo.getDeclaringClass()); - } - - @Test - @Disabled("Expected behaviour is not clear yet.") - void unclearPrecedenceOfImplementationsInParentClassAndInterfaceDefault() { - - class AParent { - public void foo(@SuppressWarnings("unused") Number parameter) { - } - } - - class A extends AParent implements InterfaceGenericNumber { - } - - var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); - - // ???????? - assertEquals(A.class, foo.getDeclaringClass()); - assertEquals(AParent.class, foo.getDeclaringClass()); - assertEquals(InterfaceGenericNumber.class, foo.getDeclaringClass()); - } - - @Test - @Disabled("Describes cases where current implementation returns unexpected value") - public void findMethodWithMostSpecificParameterTypeInHierarchy() { - // Searched Parameter Type is more specific - assertSpecificFooMethodFound(ClassImplementingInterfaceWithInvertedHierarchy.class, - InterfaceWithGenericNumberParameter.class, Double.class); - assertSpecificFooMethodFound(ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, - ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, Double.class); - assertSpecificFooMethodFound(ClassImplementingGenericAndMoreSpecificInterface.class, - InterfaceWithGenericNumberParameter.class, Double.class); - assertSpecificFooMethodFound(ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface.class, - ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface.class, Double.class); - - // Exact Type Match - assertSpecificFooMethodFound(ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, - ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, Number.class); - } - - private void assertSpecificFooMethodFound(Class classToSearchIn, Class classWithMostSpecificMethod, - Class parameterType) { - var foo = findMethod(classToSearchIn, "foo", parameterType).orElseThrow(); - assertDeclaringClass(foo, classWithMostSpecificMethod); - } - - private void assertDeclaringClass(Method method, Class expectedClass) { - assertEquals(expectedClass, method.getDeclaringClass()); - } - - interface InterfaceDouble { - default void foo(@SuppressWarnings("unused") Double parameter) { - } - } - - interface InterfaceGenericNumber { - default void foo(@SuppressWarnings("unused") T parameter) { - } - } - - public interface InterfaceWithGenericObjectParameter { - default void foo(@SuppressWarnings("unused") T a) { - } - } - - public interface InterfaceWithGenericNumberParameter { - default void foo(@SuppressWarnings("unused") T a) { - } - } - - public interface InterfaceExtendingNumberInterfaceWithGenericObjectMethod - extends InterfaceWithGenericNumberParameter { - default void foo(@SuppressWarnings("unused") T a) { - } - } - - public static class ClassImplementingGenericInterfaceWithMoreSpecificMethod - implements InterfaceWithGenericObjectParameter { - public void foo(@SuppressWarnings("unused") Number a) { - } - } - - public static class ClassImplementingGenericAndMoreSpecificInterface - implements InterfaceWithGenericObjectParameter, InterfaceWithGenericNumberParameter { - } - - public static class ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface - implements InterfaceWithGenericObjectParameter, InterfaceWithGenericNumberParameter { - - @Override - public void foo(@SuppressWarnings("unused") T a) { - } - } - - public static class ClassImplementingInterfaceWithInvertedHierarchy - implements InterfaceExtendingNumberInterfaceWithGenericObjectMethod { - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java deleted file mode 100644 index 4174ab46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link RuntimeUtils}. - * - * @since 1.6 - */ -class RuntimeUtilsTests { - - @Test - void jmxIsAvailableAndInputArgumentsAreReturned() { - var optionalArguments = RuntimeUtils.getInputArguments(); - assertTrue(optionalArguments.isPresent(), "JMX not available or something else happened..."); - var arguments = optionalArguments.get(); - assertNotNull(arguments); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java deleted file mode 100644 index f6989b4e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - -public class SerializationUtils { - - public static Object deserialize(byte[] bytes) throws Exception { - try (var in = new ObjectInputStream(new ByteArrayInputStream(bytes))) { - return in.readObject(); - } - } - - public static byte[] serialize(Object object) throws Exception { - try (var byteArrayOutputStream = new ByteArrayOutputStream(); - var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) { - objectOutputStream.writeObject(object); - objectOutputStream.flush(); - return byteArrayOutputStream.toByteArray(); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java deleted file mode 100644 index d74b1d52..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.StringUtils.containsIsoControlCharacter; -import static org.junit.platform.commons.util.StringUtils.containsWhitespace; -import static org.junit.platform.commons.util.StringUtils.doesNotContainIsoControlCharacter; -import static org.junit.platform.commons.util.StringUtils.doesNotContainWhitespace; -import static org.junit.platform.commons.util.StringUtils.isBlank; -import static org.junit.platform.commons.util.StringUtils.isNotBlank; -import static org.junit.platform.commons.util.StringUtils.nullSafeToString; -import static org.junit.platform.commons.util.StringUtils.replaceIsoControlCharacters; -import static org.junit.platform.commons.util.StringUtils.replaceWhitespaceCharacters; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link StringUtils}. - * - * @since 1.0 - */ -class StringUtilsTests { - - @Test - void blankness() { - // @formatter:off - assertAll("Blankness", - () -> assertTrue(isBlank(null)), - () -> assertTrue(isBlank("")), - () -> assertTrue(isBlank(" \t\n\r")), - () -> assertTrue(isNotBlank(".")) - ); - // @formatter:on - } - - @Test - void whitespace() { - // @formatter:off - assertAll("Whitespace", - () -> shouldContainWhitespace(" "), - () -> shouldContainWhitespace("\u005Ct"), // horizontal tab - () -> shouldContainWhitespace("\t"), - () -> shouldContainWhitespace("\u005Cn"), // line feed - () -> shouldContainWhitespace("\n"), - () -> shouldContainWhitespace("\u005Cf"), // form feed - () -> shouldContainWhitespace("\f"), - () -> shouldContainWhitespace("\u005Cr"), // carriage return - () -> shouldContainWhitespace("\r"), - () -> shouldContainWhitespace("hello world"), - () -> shouldNotContainWhitespace(null), - () -> shouldNotContainWhitespace(""), - () -> shouldNotContainWhitespace("hello-world"), - () -> shouldNotContainWhitespace("0123456789"), - () -> shouldNotContainWhitespace("$-_=+!@.,") - ); - // @formatter:on - } - - @Test - void controlCharacters() { - // @formatter:off - assertAll("ISO Control Characters", - () -> shouldContainIsoControlCharacter("\u005Ct"), // horizontal tab - () -> shouldContainIsoControlCharacter("\t"), - () -> shouldContainIsoControlCharacter("\u005Cn"), // line feed - () -> shouldContainIsoControlCharacter("\n"), - () -> shouldContainIsoControlCharacter("\u005Cf"), // form feed - () -> shouldContainIsoControlCharacter("\f"), - () -> shouldContainIsoControlCharacter("\u005Cr"), // carriage return - () -> shouldContainIsoControlCharacter("\r"), - () -> shouldNotContainIsoControlCharacter(null), - () -> shouldNotContainIsoControlCharacter(""), - () -> shouldNotContainIsoControlCharacter("hello-world"), - () -> shouldNotContainIsoControlCharacter("0123456789"), - () -> shouldNotContainIsoControlCharacter("$-_=+!@.,"), - () -> shouldNotContainIsoControlCharacter(" "), - () -> shouldNotContainIsoControlCharacter("hello world") - ); - // @formatter:on - } - - @Test - void replaceControlCharacters() { - assertNull(replaceIsoControlCharacters(null, "")); - assertEquals("", replaceIsoControlCharacters("", ".")); - assertEquals("", replaceIsoControlCharacters("\t\n\r", "")); - assertEquals("...", replaceIsoControlCharacters("\t\n\r", ".")); - assertEquals("...", replaceIsoControlCharacters("\u005Ct\u005Cn\u005Cr", ".")); - assertEquals("abc", replaceIsoControlCharacters("abc", "?")); - assertEquals("...", replaceIsoControlCharacters("...", "?")); - - assertThrows(PreconditionViolationException.class, () -> replaceIsoControlCharacters("", null)); - } - - @Test - void replaceWhitespaces() { - assertNull(replaceWhitespaceCharacters(null, "")); - assertEquals("", replaceWhitespaceCharacters("", ".")); - assertEquals("", replaceWhitespaceCharacters("\t\n\r", "")); - assertEquals("...", replaceWhitespaceCharacters("\t\n\r", ".")); - assertEquals("...", replaceWhitespaceCharacters("\u005Ct\u005Cn\u005Cr", ".")); - assertEquals("abc", replaceWhitespaceCharacters("abc", "?")); - assertEquals("...", replaceWhitespaceCharacters("...", "?")); - assertEquals(" ", replaceWhitespaceCharacters(" ", " ")); - assertEquals(" ", replaceWhitespaceCharacters("\u000B", " ")); - assertEquals(" ", replaceWhitespaceCharacters("\f", " ")); - - assertThrows(PreconditionViolationException.class, () -> replaceWhitespaceCharacters("", null)); - } - - @Test - void nullSafeToStringChecks() { - assertEquals("null", nullSafeToString(null)); - assertEquals("", nullSafeToString("")); - assertEquals("\t", nullSafeToString("\t")); - assertEquals("foo", nullSafeToString("foo")); - assertEquals("3.14", nullSafeToString(Double.valueOf("3.14"))); - assertEquals("[1, 2, 3]", nullSafeToString(new int[] { 1, 2, 3 })); - assertEquals("[a, b, c]", nullSafeToString(new char[] { 'a', 'b', 'c' })); - assertEquals("[foo, bar]", nullSafeToString(new String[] { "foo", "bar" })); - assertEquals("[34, 42]", nullSafeToString(new Integer[] { 34, 42 })); - assertEquals("[[2, 4], [3, 9]]", nullSafeToString(new Integer[][] { { 2, 4 }, { 3, 9 } })); - } - - @Test - void nullSafeToStringForObjectWhoseToStringImplementationReturnsNull() { - assertEquals("null", nullSafeToString(new ToStringReturnsNull())); - } - - @Test - void nullSafeToStringForObjectWhoseToStringImplementationThrowsAnException() { - assertThat(nullSafeToString(new ToStringThrowsException()))// - .startsWith(ToStringThrowsException.class.getName() + "@"); - } - - private void shouldContainWhitespace(String str) { - assertTrue(containsWhitespace(str), () -> String.format("'%s' should contain whitespace", str)); - assertFalse(doesNotContainWhitespace(str), () -> String.format("'%s' should contain whitespace", str)); - } - - private void shouldNotContainWhitespace(String str) { - assertTrue(doesNotContainWhitespace(str), () -> String.format("'%s' should not contain whitespace", str)); - assertFalse(containsWhitespace(str), () -> String.format("'%s' should not contain whitespace", str)); - } - - private void shouldContainIsoControlCharacter(String str) { - assertTrue(containsIsoControlCharacter(str), - () -> String.format("'%s' should contain ISO control character", str)); - assertFalse(doesNotContainIsoControlCharacter(str), - () -> String.format("'%s' should contain ISO control character", str)); - } - - private void shouldNotContainIsoControlCharacter(String str) { - assertTrue(doesNotContainIsoControlCharacter(str), - () -> String.format("'%s' should not contain ISO control character", str)); - assertFalse(containsIsoControlCharacter(str), - () -> String.format("'%s' should not contain ISO control character", str)); - } - - private static class ToStringReturnsNull { - - @Override - public String toString() { - return null; - } - } - - private static class ToStringThrowsException { - - @Override - public String toString() { - throw new RuntimeException("Boom!"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java deleted file mode 100644 index 9802f344..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link ToStringBuilder}. - * - * @since 1.0 - */ -class ToStringBuilderTests { - - @Test - void withNullObject() { - assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Object) null)); - } - - @Test - void withNullClass() { - assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Class) null)); - } - - @Test - void appendWithIllegalName() { - var builder = new ToStringBuilder(""); - - assertThrows(PreconditionViolationException.class, () -> builder.append(null, "")); - assertThrows(PreconditionViolationException.class, () -> builder.append("", "")); - assertThrows(PreconditionViolationException.class, () -> builder.append(" ", "")); - } - - @Test - void withZeroFields() { - assertEquals("RoleModel []", new ToStringBuilder(new RoleModel()).toString()); - assertEquals("RoleModel []", new ToStringBuilder(RoleModel.class).toString()); - } - - @Test - void withOneField() { - assertEquals("RoleModel [name = 'Dilbert']", - new ToStringBuilder(new RoleModel()).append("name", "Dilbert").toString()); - } - - @Test - void withNullField() { - assertEquals("RoleModel [name = null]", new ToStringBuilder(new RoleModel()).append("name", null).toString()); - } - - @Test - void withTwoFields() { - assertEquals("RoleModel [name = 'Dilbert', age = 42]", - new ToStringBuilder(new RoleModel()).append("name", "Dilbert").append("age", 42).toString()); - } - - @Test - void withIntegerArrayField() { - assertEquals("RoleModel [magic numbers = [1, 42, 99]]", - new ToStringBuilder(new RoleModel()).append("magic numbers", new Integer[] { 1, 42, 99 }).toString()); - } - - @Test - void withIntArrayField() { - assertEquals("RoleModel [magic numbers = [1, 42, 23]]", - new ToStringBuilder(new RoleModel()).append("magic numbers", new int[] { 1, 42, 23 }).toString()); - } - - @Test - void withCharArrayField() { - assertEquals("RoleModel [magic characters = [a, b]]", - new ToStringBuilder(new RoleModel()).append("magic characters", new char[] { 'a', 'b' }).toString()); - } - - @Test - void withPrimitiveBooleanArrayField() { - assertEquals("RoleModel [booleans = [true, false, true]]", - new ToStringBuilder(new RoleModel()).append("booleans", new boolean[] { true, false, true }).toString()); - } - - @Test - void withShortArrayField() { - assertEquals("RoleModel [values = [23, 42]]", - new ToStringBuilder(new RoleModel()).append("values", new short[] { 23, 42 }).toString()); - } - - @Test - void withByteArrayField() { - assertEquals("RoleModel [values = [23, 42]]", - new ToStringBuilder(new RoleModel()).append("values", new byte[] { 23, 42 }).toString()); - } - - @Test - void withPrimitiveLongArrayField() { - assertEquals("RoleModel [values = [23, 42]]", - new ToStringBuilder(new RoleModel()).append("values", new long[] { 23, 42 }).toString()); - } - - @Test - void withPrimitiveFloatArrayField() { - assertEquals("RoleModel [values = [23.45, 17.13]]", - new ToStringBuilder(new RoleModel()).append("values", new float[] { 23.45f, 17.13f }).toString()); - } - - @Test - void withPrimitiveDoubleArrayField() { - assertEquals("RoleModel [values = [23.45, 17.13]]", - new ToStringBuilder(new RoleModel()).append("values", new double[] { 23.45d, 17.13d }).toString()); - } - - @Test - @SuppressWarnings("serial") - void withMapField() { - // @formatter:off - Map map = new LinkedHashMap<>() {{ - put("foo", 42); - put("bar", "enigma"); - }}; - // @formatter:on - assertEquals("RoleModel [mystery map = {foo=42, bar=enigma}]", - new ToStringBuilder(new RoleModel()).append("mystery map", map).toString()); - } - - @Test - void withDemoImplementation() { - var roleModel = new RoleModel("Dilbert", 42); - assertEquals("RoleModel [name = 'Dilbert', age = 42]", roleModel.toString()); - } - - static class RoleModel { - - String name; - int age; - - RoleModel() { - } - - RoleModel(String name, int age) { - this.name = name; - this.age = age; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringBuilder(this) - .append("name", this.name) - .append("age", this.age) - .toString(); - // @formatter:on - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java deleted file mode 100644 index d9d4b2ff..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util.classes; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * @since 5.7 - */ -public class AExecutionConditionClass implements ExecutionCondition { - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return ConditionEvaluationResult.enabled("for Class Name Filter Tests"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java deleted file mode 100644 index 99ad33e0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util.classes; - -import org.junit.platform.launcher.TestExecutionListener; - -/** - * @since 5.7 - */ -public class ATestExecutionListenerClass implements TestExecutionListener { - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java deleted file mode 100644 index bda71e43..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util.classes; - -/** - * @since 5.7 - */ -public class AVanillaEmpty { - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java deleted file mode 100644 index a06b3300..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util.classes; - -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * @since 5.7 - */ -public class BExecutionConditionClass implements ExecutionCondition { - - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return ConditionEvaluationResult.enabled("for Class Name Filter Tests"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java deleted file mode 100644 index f8e9f5f2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util.classes; - -import org.junit.platform.launcher.TestExecutionListener; - -/** - * @since 5.7 - */ -public class BTestExecutionListenerClass implements TestExecutionListener { - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java deleted file mode 100644 index 0ddbb172..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.commons.util.classes; - -/** - * @since 5.7 - */ -public class BVanillaEmpty { - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java deleted file mode 100644 index 1ed5017d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; -import static org.junit.platform.commons.util.ReflectionUtils.getFullyQualifiedMethodName; - -import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DynamicNode; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.console.options.Details; -import org.junit.platform.console.options.Theme; -import org.opentest4j.TestAbortedException; - -/** - * @since 1.0 - */ -class ConsoleDetailsTests { - - @TestFactory - @DisplayName("Basic tests and annotations usage") - List basic() { - return scanContainerClassAndCreateDynamicTests(BasicTestCase.class); - } - - @TestFactory - @DisplayName("Skipped and disabled tests") - List skipped() { - return scanContainerClassAndCreateDynamicTests(SkipTestCase.class); - } - - @TestFactory - @DisplayName("Failed tests") - List failed() { - return scanContainerClassAndCreateDynamicTests(FailTestCase.class); - } - - @TestFactory - @DisplayName("Tests publishing report entries") - List reports() { - return scanContainerClassAndCreateDynamicTests(ReportTestCase.class); - } - - private List scanContainerClassAndCreateDynamicTests(Class containerClass) { - var containerName = containerClass.getSimpleName().replace("TestCase", ""); - // String containerName = containerClass.getSimpleName(); - List nodes = new ArrayList<>(); - Map> map = new EnumMap<>(Details.class); - for (var method : findMethods(containerClass, m -> m.isAnnotationPresent(Test.class))) { - var methodName = method.getName(); - var types = method.getParameterTypes(); - for (var details : Details.values()) { - var tests = map.computeIfAbsent(details, key -> new ArrayList<>()); - for (var theme : Theme.values()) { - var caption = containerName + "-" + methodName + "-" + details + "-" + theme; - String[] args = { // - "--include-engine", "junit-jupiter", // - "--details", details.name(), // - "--details-theme", theme.name(), // - "--disable-ansi-colors", // - "--disable-banner", // - "--include-classname", containerClass.getCanonicalName(), // - "--select-method", getFullyQualifiedMethodName(containerClass, methodName, types) // - }; - var displayName = methodName + "() " + theme.name(); - var dirName = "console/details/" + containerName.toLowerCase(); - var outName = caption + ".out.txt"; - var runner = new Runner(dirName, outName, args); - var source = toUri(dirName, outName).orElse(null); - tests.add(dynamicTest(displayName, source, runner)); - } - } - } - var source = new File("src/test/resources/console/details").toURI(); - map.forEach((details, tests) -> nodes.add(dynamicContainer(details.name(), source, tests.stream()))); - return nodes; - } - - @DisplayName("Basic") - static class BasicTestCase { - - @Test - void empty() { - } - - @Test - @DisplayName(".oO fancy display name Oo.") - void changeDisplayName() { - } - - } - - @DisplayName("Skip") - static class SkipTestCase { - - @Test - @Disabled("single line skip reason") - void skipWithSingleLineReason() { - } - - @Test - @Disabled("multi\nline\nfail\nmessage") - void skipWithMultiLineMessage() { - } - - } - - @DisplayName("Fail") - static class FailTestCase { - - @Test - void failWithSingleLineMessage() { - fail("single line fail message"); - } - - @Test - void failWithMultiLineMessage() { - fail("multi\nline\nfail\nmessage"); - } - - } - - @DisplayName("Report") - static class ReportTestCase { - - @Test - void reportSingleMessage(TestReporter reporter) { - reporter.publishEntry("foo"); - } - - @Test - void reportMultipleMessages(TestReporter reporter) { - reporter.publishEntry("foo"); - reporter.publishEntry("bar"); - } - - @Test - void reportSingleEntryWithSingleMapping(TestReporter reporter) { - reporter.publishEntry("foo", "bar"); - } - - @Test - void reportMultiEntriesWithSingleMapping(TestReporter reporter) { - reporter.publishEntry("foo", "bar"); - reporter.publishEntry("far", "boo"); - } - - @Test - void reportMultiEntriesWithMultiMappings(TestReporter reporter) { - Map values = new LinkedHashMap<>(); - values.put("user name", "dk38"); - values.put("award year", "1974"); - reporter.publishEntry(values); - reporter.publishEntry("single", "mapping"); - Map more = new LinkedHashMap<>(); - more.put("user name", "st77"); - more.put("award year", "1977"); - more.put("last seen", "2001"); - reporter.publishEntry(more); - } - - } - - private static class Runner implements Executable { - - private final String dirName; - private final String outName; - private final String[] args; - - private Runner(String dirName, String outName, String... args) { - this.dirName = dirName; - this.outName = outName; - this.args = args; - } - - @Override - public void execute() throws Throwable { - var wrapper = new ConsoleLauncherWrapper(); - var result = wrapper.execute(Optional.empty(), args); - - var optionalUri = toUri(dirName, outName); - if (optionalUri.isEmpty()) { - if (Boolean.getBoolean("org.junit.platform.console.ConsoleDetailsTests.writeResultOut")) { - // do not use Files.createTempDirectory(prefix) as we want one folder for one container - var temp = Paths.get(System.getProperty("java.io.tmpdir"), dirName.replace('/', '-')); - Files.createDirectories(temp); - var path = Files.writeString(temp.resolve(outName), result.out); - throw new TestAbortedException( - format("resource `%s` not found\nwrote console stdout to: %s/%s", dirName, outName, path)); - } - fail("could not load resource named `" + dirName + "/" + outName + "`"); - } - - var path = Paths.get(optionalUri.get()); - assumeTrue(Files.exists(path), "path does not exist: " + path); - assumeTrue(Files.isReadable(path), "can not read: " + path); - - var expectedLines = Files.readAllLines(path, UTF_8); - var actualLines = List.of(result.out.split("\\R")); - - assertLinesMatch(expectedLines, actualLines); - } - } - - static Optional toUri(String dirName, String outName) { - var resourceName = dirName + "/" + outName; - var url = ConsoleDetailsTests.class.getClassLoader().getResource(resourceName); - if (url == null) { - return Optional.empty(); - } - try { - return Optional.of(url.toURI()); - } - catch (URISyntaxException e) { - return Optional.empty(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java deleted file mode 100644 index 1d37cfc9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherExecutionResultTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.junit.jupiter.api.Test; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.launcher.listeners.TestExecutionSummary; - -/** - * @since 1.3 - */ -class ConsoleLauncherExecutionResultTests { - - private final CommandLineOptions options = new CommandLineOptions(); - private final TestExecutionSummary summary = mock(); - - @Test - void hasStatusCode0ForNoTotalFailures() { - when(summary.getTotalFailureCount()).thenReturn(0L); - - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); - - assertThat(exitCode).isEqualTo(0); - } - - @Test - void hasStatusCode1ForForAnyFailure() { - when(summary.getTotalFailureCount()).thenReturn(1L); - - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); - - assertThat(exitCode).isEqualTo(1); - } - - /** - * @since 1.3 - */ - @Test - void hasStatusCode2ForNoTestsAndHasOptionFailIfNoTestsFound() { - options.setFailIfNoTests(true); - when(summary.getTestsFoundCount()).thenReturn(0L); - - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); - - assertThat(exitCode).isEqualTo(2); - } - - /** - * @since 1.3 - */ - @Test - void hasStatusCode0ForTestsAndHasOptionFailIfNoTestsFound() { - options.setFailIfNoTests(true); - when(summary.getTestsFoundCount()).thenReturn(1L); - when(summary.getTotalFailureCount()).thenReturn(0L); - - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); - - assertThat(exitCode).isEqualTo(0); - } - - /** - * @since 1.3 - */ - @Test - void hasStatusCode0ForNoTestsAndNotFailIfNoTestsFound() { - options.setFailIfNoTests(false); - when(summary.getTestsFoundCount()).thenReturn(0L); - - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(summary, options); - - assertThat(exitCode).isEqualTo(0); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java deleted file mode 100644 index b8ccb37f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.StringUtils.isBlank; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.0 - */ -class ConsoleLauncherIntegrationTests { - - @Test - void executeWithoutArgumentsFailsAndPrintsHelpInformation() { - var result = new ConsoleLauncherWrapper().execute(-1); - assertAll("empty args array results in display of help information and an exception stacktrace", // - () -> assertTrue(result.out.contains("help information")), // - () -> assertTrue(result.err.contains("No arguments were supplied to the ConsoleLauncher")) // - ); - } - - @Test - void executeWithoutExcludeClassnameOptionDoesNotExcludeClassesAndMustIncludeAllClassesMatchingTheStandardClassnamePattern() { - String[] args = { "-e", "junit-jupiter", "-p", "org.junit.platform.console.subpackage" }; - assertEquals(9, new ConsoleLauncherWrapper().execute(args).getTestsFoundCount()); - } - - @Test - void executeWithExcludeClassnameOptionExcludesClasses() { - String[] args = { "-e", "junit-jupiter", "-p", "org.junit.platform.console.subpackage", "--exclude-classname", - "^org\\.junit\\.platform\\.console\\.subpackage\\..*" }; - var result = new ConsoleLauncherWrapper().execute(args); - assertAll("all subpackage test classes are excluded by the class name filter", // - () -> assertArrayEquals(args, result.args), // - () -> assertEquals(0, result.code), // - () -> assertEquals(0, result.getTestsFoundCount()), // - () -> assertTrue(isBlank(result.err)) // - ); - } - - @Test - void executeSelectingModuleNames() { - String[] args1 = { "-e", "junit-jupiter", "-o", "java.base" }; - assertEquals(0, new ConsoleLauncherWrapper().execute(args1).getTestsFoundCount()); - String[] args2 = { "-e", "junit-jupiter", "--select-module", "java.base" }; - assertEquals(0, new ConsoleLauncherWrapper().execute(args2).getTestsFoundCount()); - } - - @Test - void executeScanModules() { - String[] args1 = { "-e", "junit-jupiter", "--scan-modules" }; - assertEquals(0, new ConsoleLauncherWrapper().execute(args1).getTestsFoundCount()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java deleted file mode 100644 index c22e94e1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.console.options.CommandLineOptionsParser; - -/** - * @since 1.0 - */ -class ConsoleLauncherTests { - - private final StringWriter stringWriter = new StringWriter(); - private final PrintWriter printSink = new PrintWriter(stringWriter); - - @Test - void displayHelp() { - var options = new CommandLineOptions(); - options.setDisplayHelp(true); - - var commandLineOptionsParser = mock(CommandLineOptionsParser.class); - when(commandLineOptionsParser.parse(any())).thenReturn(options); - - var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); - var exitCode = consoleLauncher.execute("--help").getExitCode(); - - assertEquals(0, exitCode); - verify(commandLineOptionsParser).parse("--help"); - } - - @Test - void displayBanner() { - var options = new CommandLineOptions(); - options.setBannerDisabled(false); - options.setDisplayHelp(true); - - var commandLineOptionsParser = mock(CommandLineOptionsParser.class); - when(commandLineOptionsParser.parse(any())).thenReturn(options); - - var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); - var exitCode = consoleLauncher.execute("--help").getExitCode(); - - assertEquals(0, exitCode); - assertLinesMatch( - List.of("", "Thanks for using JUnit! Support its development at https://junit.org/sponsoring", ""), - stringWriter.toString().lines().collect(Collectors.toList())); - } - - @Test - void disableBanner() { - var options = new CommandLineOptions(); - options.setBannerDisabled(true); - options.setDisplayHelp(true); - - var commandLineOptionsParser = mock(CommandLineOptionsParser.class); - when(commandLineOptionsParser.parse(any())).thenReturn(options); - - var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); - var exitCode = consoleLauncher.execute("--help", "--disable-banner").getExitCode(); - - assertEquals(0, exitCode); - assertLinesMatch(List.of(), stringWriter.toString().lines().collect(Collectors.toList())); - } - - @Test - void executeWithUnknownCommandLineOption() { - var commandLineOptionsParser = mock(CommandLineOptionsParser.class); - when(commandLineOptionsParser.parse(any())).thenReturn(new CommandLineOptions()); - - var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); - var exitCode = consoleLauncher.execute("--all").getExitCode(); - - assertEquals(-1, exitCode); - verify(commandLineOptionsParser).parse("--all"); - } - - @Test - void executeWithSupportedCommandLineOption() { - var commandLineOptionsParser = mock(CommandLineOptionsParser.class); - when(commandLineOptionsParser.parse(any())).thenReturn(new CommandLineOptions()); - - var consoleLauncher = new ConsoleLauncher(commandLineOptionsParser, printSink, printSink); - var exitCode = consoleLauncher.execute("--scan-classpath").getExitCode(); - - assertEquals(-1, exitCode); - verify(commandLineOptionsParser).parse("--scan-classpath"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java deleted file mode 100644 index 1740d079..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.StringUtils.isBlank; -import static org.junit.platform.commons.util.StringUtils.isNotBlank; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Optional; - -import org.junit.platform.console.options.CommandLineOptionsParser; -import org.junit.platform.console.options.PicocliCommandLineOptionsParser; - -/** - * @since 1.0 - */ -class ConsoleLauncherWrapper { - - private final StringWriter out = new StringWriter(); - private final StringWriter err = new StringWriter(); - private final ConsoleLauncher consoleLauncher; - - ConsoleLauncherWrapper() { - this(new PicocliCommandLineOptionsParser()); - } - - private ConsoleLauncherWrapper(CommandLineOptionsParser parser) { - var outWriter = new PrintWriter(out, false); - var errWriter = new PrintWriter(err, false); - this.consoleLauncher = new ConsoleLauncher(parser, outWriter, errWriter); - - } - - public ConsoleLauncherWrapperResult execute(String... args) { - return execute(0, args); - } - - public ConsoleLauncherWrapperResult execute(int expectedExitCode, String... args) { - return execute(Optional.of(expectedExitCode), args); - } - - public ConsoleLauncherWrapperResult execute(Optional expectedCode, String... args) { - var result = consoleLauncher.execute(args); - var code = result.getExitCode(); - var outText = out.toString(); - var errText = err.toString(); - if (expectedCode.isPresent()) { - int expectedValue = expectedCode.get(); - assertAll("wrapped execution failed:\n" + outText + "\n", // - () -> assertEquals(expectedValue, code, "ConsoleLauncher execute code mismatch!"), // - () -> assertTrue(expectedValue == 0 ? isBlank(errText) : isNotBlank(errText)) // - ); - } - return new ConsoleLauncherWrapperResult(args, outText, errText, result); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java deleted file mode 100644 index bad59d3f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console; - -import java.io.PrintWriter; -import java.util.List; - -import org.junit.platform.launcher.listeners.TestExecutionSummary; - -/** - * @since 1.0 - */ -class ConsoleLauncherWrapperResult implements TestExecutionSummary { - - final String[] args; - final String out; - final String err; - final int code; - private final TestExecutionSummary summary; - - ConsoleLauncherWrapperResult(String[] args, String out, String err, ConsoleLauncherExecutionResult result) { - this.args = args; - this.out = out; - this.err = err; - this.code = result.getExitCode(); - this.summary = result.getTestExecutionSummary().orElse(null); - } - - private void checkTestExecutionSummaryState() { - if (summary == null) { - throw new IllegalStateException("TestExecutionSummary not assigned. Exit code is: " + code); - } - } - - @Override - public long getTimeStarted() { - checkTestExecutionSummaryState(); - return summary.getTimeStarted(); - } - - @Override - public long getTimeFinished() { - checkTestExecutionSummaryState(); - return summary.getTimeFinished(); - } - - @Override - public long getTotalFailureCount() { - checkTestExecutionSummaryState(); - return summary.getTotalFailureCount(); - } - - @Override - public long getContainersFoundCount() { - checkTestExecutionSummaryState(); - return summary.getContainersFoundCount(); - } - - @Override - public long getContainersStartedCount() { - checkTestExecutionSummaryState(); - return summary.getContainersStartedCount(); - } - - @Override - public long getContainersSkippedCount() { - checkTestExecutionSummaryState(); - return summary.getContainersSkippedCount(); - } - - @Override - public long getContainersAbortedCount() { - checkTestExecutionSummaryState(); - return summary.getContainersAbortedCount(); - } - - @Override - public long getContainersSucceededCount() { - checkTestExecutionSummaryState(); - return summary.getContainersSucceededCount(); - } - - @Override - public long getContainersFailedCount() { - checkTestExecutionSummaryState(); - return summary.getContainersFailedCount(); - } - - @Override - public long getTestsFoundCount() { - checkTestExecutionSummaryState(); - return summary.getTestsFoundCount(); - } - - @Override - public long getTestsStartedCount() { - checkTestExecutionSummaryState(); - return summary.getTestsStartedCount(); - } - - @Override - public long getTestsSkippedCount() { - checkTestExecutionSummaryState(); - return summary.getTestsSkippedCount(); - } - - @Override - public long getTestsAbortedCount() { - checkTestExecutionSummaryState(); - return summary.getTestsAbortedCount(); - } - - @Override - public long getTestsSucceededCount() { - checkTestExecutionSummaryState(); - return summary.getTestsSucceededCount(); - } - - @Override - public long getTestsFailedCount() { - checkTestExecutionSummaryState(); - return summary.getTestsFailedCount(); - } - - @Override - public void printTo(PrintWriter writer) { - checkTestExecutionSummaryState(); - summary.printTo(writer); - } - - @Override - public void printFailuresTo(PrintWriter writer) { - checkTestExecutionSummaryState(); - summary.printFailuresTo(writer); - } - - @Override - public void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { - checkTestExecutionSummaryState(); - summary.printFailuresTo(writer, maxStackTraceLines); - } - - @Override - public List getFailures() { - checkTestExecutionSummaryState(); - return summary.getFailures(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java deleted file mode 100644 index 09e807eb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.nio.charset.Charset; - -import org.junit.jupiter.api.Test; - -class ConsoleUtilsTests { - - @Test - void consoleCharsetReportedByConsoleUtilsIsEitherNativeCharsetOrDefaultCharset() { - var console = System.console(); - var expected = console != null ? console.charset() : Charset.defaultCharset(); - assertEquals(expected, ConsoleUtils.charset()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java deleted file mode 100644 index ed793dcf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/PicocliCommandLineOptionsParserTests.java +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; - -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.platform.commons.JUnitException; - -/** - * @since 1.0 - */ -class PicocliCommandLineOptionsParserTests { - - @Test - void parseNoArguments() { - String[] noArguments = {}; - var options = createParser().parse(noArguments); - - // @formatter:off - assertAll( - () -> assertFalse(options.isAnsiColorOutputDisabled()), - () -> assertFalse(options.isDisplayHelp()), - () -> assertEquals(CommandLineOptions.DEFAULT_DETAILS, options.getDetails()), - () -> assertFalse(options.isScanClasspath()), - () -> assertEquals(List.of(STANDARD_INCLUDE_PATTERN), options.getIncludedClassNamePatterns()), - () -> assertEquals(List.of(), options.getExcludedClassNamePatterns()), - () -> assertEquals(List.of(), options.getIncludedPackages()), - () -> assertEquals(List.of(), options.getExcludedPackages()), - () -> assertEquals(List.of(), options.getIncludedTagExpressions()), - () -> assertEquals(List.of(), options.getExcludedTagExpressions()), - () -> assertEquals(List.of(), options.getAdditionalClasspathEntries()), - () -> assertEquals(Optional.empty(), options.getReportsDir()), - () -> assertEquals(List.of(), options.getSelectedUris()), - () -> assertEquals(List.of(), options.getSelectedFiles()), - () -> assertEquals(List.of(), options.getSelectedDirectories()), - () -> assertEquals(List.of(), options.getSelectedModules()), - () -> assertEquals(List.of(), options.getSelectedPackages()), - () -> assertEquals(List.of(), options.getSelectedMethods()), - () -> assertEquals(List.of(), options.getSelectedClasspathEntries()), - () -> assertEquals(Map.of(), options.getConfigurationParameters()) - ); - // @formatter:on - } - - @Test - void parseSwitches() { - // @formatter:off - assertAll( - () -> assertParses("disable ansi", CommandLineOptions::isAnsiColorOutputDisabled, "--disable-ansi-colors"), - () -> assertParses("help", CommandLineOptions::isDisplayHelp, "-h", "--help"), - () -> assertParses("scan class path", CommandLineOptions::isScanClasspath, "--scan-class-path") - ); - // @formatter:on - } - - @ParameterizedTest - @EnumSource - void parseValidDetails(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(Details.VERBOSE, type.parseArgLine("--details verbose").getDetails()), - () -> assertEquals(Details.TREE, type.parseArgLine("--details tree").getDetails()), - () -> assertEquals(Details.FLAT, type.parseArgLine("--details flat").getDetails()), - () -> assertEquals(Details.NONE, type.parseArgLine("--details NONE").getDetails()), - () -> assertEquals(Details.NONE, type.parseArgLine("--details none").getDetails()), - () -> assertEquals(Details.NONE, type.parseArgLine("--details None").getDetails()) - ); - // @formatter:on - } - - @Test - void parseInvalidDetails() { - assertOptionWithMissingRequiredArgumentThrowsException("--details"); - } - - @ParameterizedTest - @EnumSource - void parseValidDetailsTheme(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(Theme.ASCII, type.parseArgLine("--details-theme ascii").getTheme()), - () -> assertEquals(Theme.ASCII, type.parseArgLine("--details-theme ASCII").getTheme()), - () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme unicode").getTheme()), - () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme UNICODE").getTheme()), - () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme uniCode").getTheme()) - ); - // @formatter:on - } - - @Test - void parseInvalidDetailsTheme() { - assertOptionWithMissingRequiredArgumentThrowsException("--details-theme"); - } - - @ParameterizedTest - @EnumSource - void parseValidIncludeClassNamePatterns(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(".*Test"), type.parseArgLine("-n .*Test").getIncludedClassNamePatterns()), - () -> assertEquals(List.of(".*Test", ".*Tests"), type.parseArgLine("--include-classname .*Test --include-classname .*Tests").getIncludedClassNamePatterns()), - () -> assertEquals(List.of(".*Test"), type.parseArgLine("--include-classname=.*Test").getIncludedClassNamePatterns()) - ); - // @formatter:on - } - - @ParameterizedTest - @EnumSource - void parseValidExcludeClassNamePatterns(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(".*Test"), type.parseArgLine("-N .*Test").getExcludedClassNamePatterns()), - () -> assertEquals(List.of(".*Test", ".*Tests"), type.parseArgLine("--exclude-classname .*Test --exclude-classname .*Tests").getExcludedClassNamePatterns()), - () -> assertEquals(List.of(".*Test"), type.parseArgLine("--exclude-classname=.*Test").getExcludedClassNamePatterns()) - ); - // @formatter:on - } - - @Test - void usesDefaultClassNamePatternWithoutExplicitArgument() throws Exception { - assertEquals(List.of(STANDARD_INCLUDE_PATTERN), ArgsType.args.parseArgLine("").getIncludedClassNamePatterns()); - } - - @Test - void parseInvalidIncludeClassNamePatterns() { - assertOptionWithMissingRequiredArgumentThrowsException("-n", "--include-classname"); - } - - @Test - void parseInvalidExcludeClassNamePatterns() { - assertOptionWithMissingRequiredArgumentThrowsException("-N", "--exclude-classname"); - } - - @ParameterizedTest - @EnumSource - void parseValidIncludedPackages(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of("org.junit.included"), - type.parseArgLine("--include-package org.junit.included").getIncludedPackages()), - () -> assertEquals(List.of("org.junit.included"), - type.parseArgLine("--include-package=org.junit.included").getIncludedPackages()), - () -> assertEquals(List.of("org.junit.included1", "org.junit.included2"), - type.parseArgLine("--include-package org.junit.included1 --include-package org.junit.included2").getIncludedPackages()) - ); - // @formatter:on - } - - @ParameterizedTest - @EnumSource - void parseValidExcludedPackages(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of("org.junit.excluded"), - type.parseArgLine("--exclude-package org.junit.excluded").getExcludedPackages()), - () -> assertEquals(List.of("org.junit.excluded"), - type.parseArgLine("--exclude-package=org.junit.excluded").getExcludedPackages()), - () -> assertEquals(List.of("org.junit.excluded1", "org.junit.excluded2"), - type.parseArgLine("--exclude-package org.junit.excluded1 --exclude-package org.junit.excluded2").getExcludedPackages()) - ); - // @formatter:on - } - - @ParameterizedTest - @EnumSource - void parseValidIncludedTags(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of("fast"), type.parseArgLine("-t fast").getIncludedTagExpressions()), - () -> assertEquals(List.of("fast"), type.parseArgLine("--include-tag fast").getIncludedTagExpressions()), - () -> assertEquals(List.of("fast"), type.parseArgLine("--include-tag=fast").getIncludedTagExpressions()), - () -> assertEquals(List.of("fast", "slow"), type.parseArgLine("-t fast -t slow").getIncludedTagExpressions()) - ); - // @formatter:on - } - - @Test - void parseInvalidIncludedTags() { - assertOptionWithMissingRequiredArgumentThrowsException("-t", "--include-tag"); - } - - @ParameterizedTest - @EnumSource - void parseValidExcludedTags(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of("fast"), type.parseArgLine("-T fast").getExcludedTagExpressions()), - () -> assertEquals(List.of("fast"), type.parseArgLine("--exclude-tag fast").getExcludedTagExpressions()), - () -> assertEquals(List.of("fast"), type.parseArgLine("--exclude-tag=fast").getExcludedTagExpressions()), - () -> assertEquals(List.of("fast", "slow"), type.parseArgLine("-T fast -T slow").getExcludedTagExpressions()) - ); - // @formatter:on - } - - @Test - void parseInvalidExcludedTags() { - assertOptionWithMissingRequiredArgumentThrowsException("-T", "--exclude-tag"); - } - - @ParameterizedTest - @EnumSource - void parseValidIncludedEngines(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of("junit-jupiter"), type.parseArgLine("-e junit-jupiter").getIncludedEngines()), - () -> assertEquals(List.of("junit-vintage"), type.parseArgLine("--include-engine junit-vintage").getIncludedEngines()), - () -> assertEquals(List.of(), type.parseArgLine("").getIncludedEngines()) - ); - // @formatter:on - } - - @Test - void parseInvalidIncludedEngines() { - assertOptionWithMissingRequiredArgumentThrowsException("-e", "--include-engine"); - } - - @ParameterizedTest - @EnumSource - void parseValidExcludedEngines(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of("junit-jupiter"), type.parseArgLine("-E junit-jupiter").getExcludedEngines()), - () -> assertEquals(List.of("junit-vintage"), type.parseArgLine("--exclude-engine junit-vintage").getExcludedEngines()), - () -> assertEquals(List.of(), type.parseArgLine("").getExcludedEngines()) - ); - // @formatter:on - } - - @Test - void parseInvalidExcludedEngines() { - assertOptionWithMissingRequiredArgumentThrowsException("-E", "--exclude-engine"); - } - - @ParameterizedTest - @EnumSource - void parseValidAdditionalClasspathEntries(ArgsType type) { - var dir = Paths.get("."); - // @formatter:off - assertAll( - () -> assertEquals(List.of(dir), type.parseArgLine("-cp .").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("--cp .").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("-classpath .").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("-classpath=.").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("--classpath .").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("--classpath=.").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("--class-path .").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("--class-path=.").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir, Path.of("lib/some.jar")), type.parseArgLine("-cp . -cp lib/some.jar").getAdditionalClasspathEntries()), - () -> assertEquals(List.of(dir, Path.of("lib/some.jar")), type.parseArgLine("-cp ." + File.pathSeparator + "lib/some.jar").getAdditionalClasspathEntries()) - ); - // @formatter:on - } - - @Test - @EnabledOnOs(OS.WINDOWS) - void parseValidAndAbsoluteAdditionalClasspathEntries() throws Exception { - ArgsType type = ArgsType.args; - assertEquals(List.of(Path.of("C:\\a.jar")), type.parseArgLine("-cp C:\\a.jar").getAdditionalClasspathEntries()); - assertEquals(List.of(Path.of("C:\\foo.jar"), Path.of("D:\\bar.jar")), - type.parseArgLine("-cp C:\\foo.jar;D:\\bar.jar").getAdditionalClasspathEntries()); - } - - @Test - void parseInvalidAdditionalClasspathEntries() { - assertOptionWithMissingRequiredArgumentThrowsException("-cp", "--classpath", "--class-path"); - } - - @ParameterizedTest - @EnumSource - void parseValidXmlReportsDirs(ArgsType type) { - var dir = Paths.get("build", "test-results"); - // @formatter:off - assertAll( - () -> assertEquals(Optional.of(dir), type.parseArgLine("--reports-dir build/test-results").getReportsDir()), - () -> assertEquals(Optional.of(dir), type.parseArgLine("--reports-dir=build/test-results").getReportsDir()) - ); - // @formatter:on - } - - @Test - void parseInvalidXmlReportsDirs() { - assertOptionWithMissingRequiredArgumentThrowsException("--reports-dir"); - } - - @ParameterizedTest - @EnumSource - void parseValidUriSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-u file:///foo.txt").getSelectedUris()), - () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--u file:///foo.txt").getSelectedUris()), - () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-select-uri file:///foo.txt").getSelectedUris()), - () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-select-uri=file:///foo.txt").getSelectedUris()), - () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri file:///foo.txt").getSelectedUris()), - () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri=file:///foo.txt").getSelectedUris()), - () -> assertEquals(List.of(selectUri("file:///foo.txt"), selectUri("https://example")), type.parseArgLine("-u file:///foo.txt -u https://example").getSelectedUris()) - ); - // @formatter:on - } - - @Test - void parseInvalidUriSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-u", "--select-uri", "-u unknown-scheme:"); - } - - @ParameterizedTest - @EnumSource - void parseValidFileSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-f foo.txt").getSelectedFiles()), - () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--f foo.txt").getSelectedFiles()), - () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-select-file foo.txt").getSelectedFiles()), - () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-select-file=foo.txt").getSelectedFiles()), - () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file foo.txt").getSelectedFiles()), - () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file=foo.txt").getSelectedFiles()), - () -> assertEquals(List.of(selectFile("foo.txt"), selectFile("bar.csv")), type.parseArgLine("-f foo.txt -f bar.csv").getSelectedFiles()) - ); - // @formatter:on - } - - @Test - void parseInvalidFileSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-f", "--select-file"); - } - - @ParameterizedTest - @EnumSource - void parseValidDirectorySelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-d foo/bar").getSelectedDirectories()), - () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--d foo/bar").getSelectedDirectories()), - () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-select-directory foo/bar").getSelectedDirectories()), - () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-select-directory=foo/bar").getSelectedDirectories()), - () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory foo/bar").getSelectedDirectories()), - () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory=foo/bar").getSelectedDirectories()), - () -> assertEquals(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux")), type.parseArgLine("-d foo/bar -d bar/qux").getSelectedDirectories()) - ); - // @formatter:on - } - - @Test - void parseInvalidDirectorySelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-d", "--select-directory"); - } - - @ParameterizedTest - @EnumSource - void parseValidModuleSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-o com.acme.foo").getSelectedModules()), - () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--o com.acme.foo").getSelectedModules()), - () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-select-module com.acme.foo").getSelectedModules()), - () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-select-module=com.acme.foo").getSelectedModules()), - () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module com.acme.foo").getSelectedModules()), - () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module=com.acme.foo").getSelectedModules()), - () -> assertEquals(List.of(selectModule("com.acme.foo"), selectModule("com.example.bar")), type.parseArgLine("-o com.acme.foo -o com.example.bar").getSelectedModules()) - ); - // @formatter:on - } - - @Test - void parseInvalidModuleSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-o", "--select-module"); - } - - @ParameterizedTest - @EnumSource - void parseValidPackageSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-p com.acme.foo").getSelectedPackages()), - () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--p com.acme.foo").getSelectedPackages()), - () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-select-package com.acme.foo").getSelectedPackages()), - () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-select-package=com.acme.foo").getSelectedPackages()), - () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package com.acme.foo").getSelectedPackages()), - () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package=com.acme.foo").getSelectedPackages()), - () -> assertEquals(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar")), type.parseArgLine("-p com.acme.foo -p com.example.bar").getSelectedPackages()) - ); - // @formatter:on - } - - @Test - void parseInvalidPackageSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-p", "--select-package"); - } - - @ParameterizedTest - @EnumSource - void parseValidClassSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-c com.acme.Foo").getSelectedClasses()), - () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--c com.acme.Foo").getSelectedClasses()), - () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-select-class com.acme.Foo").getSelectedClasses()), - () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-select-class=com.acme.Foo").getSelectedClasses()), - () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class com.acme.Foo").getSelectedClasses()), - () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class=com.acme.Foo").getSelectedClasses()), - () -> assertEquals(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar")), type.parseArgLine("-c com.acme.Foo -c com.example.Bar").getSelectedClasses()) - ); - // @formatter:on - } - - @Test - void parseInvalidClassSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-c", "--select-class"); - } - - @ParameterizedTest - @EnumSource - void parseValidMethodSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("-m com.acme.Foo#m()").getSelectedMethods()), - () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--m com.acme.Foo#m()").getSelectedMethods()), - () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("-select-method com.acme.Foo#m()").getSelectedMethods()), - () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("-select-method=com.acme.Foo#m()").getSelectedMethods()), - () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method com.acme.Foo#m()").getSelectedMethods()), - () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method=com.acme.Foo#m()").getSelectedMethods()), - () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)")), - type.parseArgLine("-m com.acme.Foo#m() -m com.example.Bar#method(java.lang.Object)").getSelectedMethods()) - ); - // @formatter:on - } - - @Test - void parseInvalidMethodSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-m", "--select-method"); - } - - @ParameterizedTest - @EnumSource - void parseValidClasspathResourceSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-r /foo.csv").getSelectedClasspathResources()), - () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--r /foo.csv").getSelectedClasspathResources()), - () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-select-resource /foo.csv").getSelectedClasspathResources()), - () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-select-resource=/foo.csv").getSelectedClasspathResources()), - () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource /foo.csv").getSelectedClasspathResources()), - () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource=/foo.csv").getSelectedClasspathResources()), - () -> assertEquals(List.of(selectClasspathResource("/foo.csv"), selectClasspathResource("bar.json")), type.parseArgLine("-r /foo.csv -r bar.json").getSelectedClasspathResources()) - ); - // @formatter:on - } - - @Test - void parseInvalidClasspathResourceSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-r", "--select-resource"); - } - - @ParameterizedTest - @EnumSource - void parseValidIterationSelectors(ArgsType type) { - // @formatter:off - assertAll( - () -> assertEquals(List.of(selectIteration(selectClasspathResource("/foo.csv"), 0)), type.parseArgLine("-i resource:/foo.csv[0]").getSelectedIterations()), - () -> assertEquals(List.of(selectIteration(selectMethod("com.acme.Foo#m()"), 1, 2)), type.parseArgLine("--i method:com.acme.Foo#m()[1..2]").getSelectedIterations()), - () -> assertEquals(List.of(selectIteration(selectClass("com.acme.Foo"), 0, 2)), type.parseArgLine("-select-iteration class:com.acme.Foo[0,2]").getSelectedIterations()), - () -> assertEquals(List.of(selectIteration(selectPackage("com.acme.foo"), 3)), type.parseArgLine("-select-iteration=package:com.acme.foo[3]").getSelectedIterations()), - () -> assertEquals(List.of(selectIteration(selectModule("com.acme.foo"), 0, 1, 2, 4, 5, 6)), type.parseArgLine("--select-iteration module:com.acme.foo[0..2,4..6]").getSelectedIterations()), - () -> assertEquals(List.of(selectIteration(selectDirectory("foo/bar"), 1, 5)), type.parseArgLine("--select-iteration=directory:foo/bar[1,5]").getSelectedIterations()), - () -> assertEquals(List.of(selectIteration(selectFile("foo.txt"), 6), selectIteration(selectUri("file:///foo.txt"), 7)), type.parseArgLine("-i file:foo.txt[6] -i uri:file:///foo.txt[7]").getSelectedIterations()) - ); - // @formatter:on - } - - @Test - void parseInvalidIterationSelectors() { - assertOptionWithMissingRequiredArgumentThrowsException("-i", "--select-iteration"); - } - - @ParameterizedTest - @EnumSource - void parseClasspathScanningEntries(ArgsType type) { - var dir = Paths.get("."); - // @formatter:off - assertAll( - () -> assertTrue(type.parseArgLine("--scan-class-path").isScanClasspath()), - () -> assertEquals(List.of(), type.parseArgLine("--scan-class-path").getSelectedClasspathEntries()), - () -> assertTrue(type.parseArgLine("--scan-classpath").isScanClasspath()), - () -> assertEquals(List.of(), type.parseArgLine("--scan-classpath").getSelectedClasspathEntries()), - () -> assertTrue(type.parseArgLine("--scan-class-path .").isScanClasspath()), - () -> assertEquals(List.of(dir), type.parseArgLine("--scan-class-path .").getSelectedClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("--scan-class-path=.").getSelectedClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("-scan-class-path .").getSelectedClasspathEntries()), - () -> assertEquals(List.of(dir), type.parseArgLine("-scan-class-path=.").getSelectedClasspathEntries()), - () -> assertEquals(List.of(dir, Paths.get("src/test/java")), type.parseArgLine("--scan-class-path . --scan-class-path src/test/java").getSelectedClasspathEntries()), - () -> assertEquals(List.of(dir, Paths.get("src/test/java")), type.parseArgLine("--scan-class-path ." + File.pathSeparator + "src/test/java").getSelectedClasspathEntries()) - ); - // @formatter:on - } - - @ParameterizedTest - @EnumSource - void parseValidConfigurationParameters(ArgsType type) { - // @formatter:off - assertAll( - () -> assertThat(type.parseArgLine("-config foo=bar").getConfigurationParameters()) - .containsOnly(entry("foo", "bar")), - () -> assertThat(type.parseArgLine("-config=foo=bar").getConfigurationParameters()) - .containsOnly(entry("foo", "bar")), - () -> assertThat(type.parseArgLine("--config foo=bar").getConfigurationParameters()) - .containsOnly(entry("foo", "bar")), - () -> assertThat(type.parseArgLine("--config=foo=bar").getConfigurationParameters()) - .containsOnly(entry("foo", "bar")), - () -> assertThat(type.parseArgLine("--config foo=bar --config baz=qux").getConfigurationParameters()) - .containsExactly(entry("foo", "bar"), entry("baz", "qux")) - ); - // @formatter:on - } - - @Test - void parseInvalidConfigurationParameters() { - assertOptionWithMissingRequiredArgumentThrowsException("-config", "--config"); - } - - @ParameterizedTest - @EnumSource - void parseInvalidConfigurationParametersWithDuplicateKey(ArgsType type) { - Exception e = assertThrows(JUnitException.class, () -> type.parseArgLine("--config foo=bar --config foo=baz")); - - assertThat(e.getMessage()).isEqualTo( - "Error parsing command-line arguments: Duplicate key 'foo' for values 'bar' and 'baz'."); - assertThat(e.getCause().getMessage()).isEqualTo("Duplicate key 'foo' for values 'bar' and 'baz'."); - } - - @Test - void printHelpOutputsHelpOption() { - var writer = new StringWriter(); - - createParser().printHelp(writer, true); - - assertThat(writer.toString()).contains("--help"); - } - - @Test - void printHelpPreservesOriginalIOException() { - var writer = new Writer() { - - @SuppressWarnings("NullableProblems") - @Override - public void write(char[] buffer, int off, int len) throws IOException { - throw new IOException("Something went wrong"); - } - - @Override - public void flush() { - } - - @Override - public void close() { - } - }; - - var parser = createParser(); - var exception = assertThrows(RuntimeException.class, () -> parser.printHelp(writer, true)); - - assertThat(exception).hasCauseInstanceOf(IOException.class); - assertThat(exception.getCause()).hasMessage("Something went wrong"); - } - - private void assertOptionWithMissingRequiredArgumentThrowsException(String... options) { - assertAll(Stream.of(options).map( - opt -> () -> assertThrows(JUnitException.class, () -> ArgsType.args.parseArgLine(opt)))); - } - - private void assertParses(String name, Predicate property, String... argLines) { - Stream.of(argLines).forEach(argLine -> { - CommandLineOptions options = null; - try { - options = ArgsType.args.parseArgLine(argLine); - } - catch (IOException e) { - fail(e); - } - assertTrue(property.test(options), () -> name + " should be enabled by: " + argLine); - }); - } - - enum ArgsType { - args { - CommandLineOptions parseArgLine(String argLine) { - return createParser().parse(split(argLine)); - } - }, - atFile { - CommandLineOptions parseArgLine(String argLine) throws IOException { - var atFile = Files.createTempFile("junit-launcher-args", ".txt"); - try { - Files.write(atFile, List.of(split(argLine))); - return createParser().parse("@" + atFile); - } - finally { - Files.deleteIfExists(atFile); - } - } - }; - abstract CommandLineOptions parseArgLine(String argLine) throws IOException; - - private static String[] split(String argLine) { - return "".equals(argLine) ? new String[0] : argLine.split("\\s+"); - } - } - - private static CommandLineOptionsParser createParser() { - return new PicocliCommandLineOptionsParser(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java deleted file mode 100644 index 8c96fed4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.options; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.nio.charset.StandardCharsets; - -import org.junit.jupiter.api.Test; - -class ThemeTests { - - @Test - void givenUtf8ShouldReturnUnicode() { - assertEquals(Theme.UNICODE, Theme.valueOf(StandardCharsets.UTF_8)); - } - - @Test - void givenAnythingElseShouldReturnAscii() { - assertAll("All character sets that are not UTF-8 should return Theme.ASCII", () -> { - assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.ISO_8859_1)); - assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.US_ASCII)); - assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.UTF_16)); - }); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java deleted file mode 100644 index b8181cae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named *Test in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class ContainerForInnerTest { - - @Nested - class NestedTest { - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java deleted file mode 100644 index 410e9d8b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named *Tests in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class ContainerForInnerTests { - - @Nested - class NestedTests { - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java deleted file mode 100644 index 8143890b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named *Test in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class ContainerForStaticNestedTest { - - static class NestedTest { - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java deleted file mode 100644 index c1338725..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named *Tests in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class ContainerForStaticNestedTests { - - static class NestedTests { - - @Test - void test() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java deleted file mode 100644 index 56bccb3e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named *Test in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class SecondTest { - - @Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java deleted file mode 100644 index bcecdc12..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named Test in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class Test { - - @org.junit.jupiter.api.Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java deleted file mode 100644 index d554e0b2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named Test* in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class Test1 { - - @Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java deleted file mode 100644 index 52142a32..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named Tests in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class Tests { - - @Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java deleted file mode 100644 index 9de75239..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.subpackage; - -import org.junit.jupiter.api.Test; - -// Even though our "example test classes" should be named *TestCase -// according to team policy, this class must be named *Tests in order -// for ConsoleLauncherIntegrationTests to pass, but that's not a -// problem since the test method here can never fail. -class ThirdTests { - - @Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java deleted file mode 100644 index 3a0909c3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; - -import java.io.PrintWriter; -import java.io.StringReader; -import java.util.List; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.platform.console.options.Theme; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * Unit tests for {@link ColorPalette}. - * - * @since 1.9 - */ -class ColorPaletteTests { - - @Nested - class LoadFromPropertiesTests { - - @Test - void singleOverride() { - String properties = """ - SUCCESSFUL = 35;1 - """; - ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); - - String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); - - assertEquals("\u001B[35;1mtext\u001B[0m", actual); - } - - @Test - void keysAreCaseInsensitive() { - String properties = """ - suCcESSfuL = 35;1 - """; - ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); - - String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); - - assertEquals("\u001B[35;1mtext\u001B[0m", actual); - } - - @Test - void junkKeysAreIgnored() { - String properties = """ - SUCCESSFUL = 35;1 - JUNK = 1;31;40 - """; - ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); - - String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); - - assertEquals("\u001B[35;1mtext\u001B[0m", actual); - } - - @Test - void multipleOverrides() { - String properties = """ - SUCCESSFUL = 35;1 - FAILED = 33;4 - """; - ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); - - String successful = colorPalette.paint(Style.SUCCESSFUL, "text"); - String failed = colorPalette.paint(Style.FAILED, "text"); - - assertEquals("\u001B[35;1mtext\u001B[0m", successful); - assertEquals("\u001B[33;4mtext\u001B[0m", failed); - } - - @Test - void unspecifiedStylesAreDefault() { - String properties = """ - SUCCESSFUL = 35;1 - """; - ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); - - String actual = colorPalette.paint(Style.FAILED, "text"); - - assertEquals("\u001B[31mtext\u001B[0m", actual); - } - - @Test - void cannotOverrideNone() { - String properties = """ - NONE = 35;1 - """; - StringReader reader = new StringReader(properties); - - assertThrows(IllegalArgumentException.class, () -> new ColorPalette(reader)); - } - } - - /** - * TODO Actually assert something in these "demo" tests and stop printing to SYSOUT. - */ - @Nested - class DemonstratePalettesTests { - - @Test - void verbose_default() { - PrintWriter out = new PrintWriter(System.out); - TestExecutionListener listener = new VerboseTreePrintingListener(out, ColorPalette.DEFAULT, 16, - Theme.ASCII); - - demoTestRun(listener); - - assertDoesNotThrow(out::flush); - } - - @Test - void verbose_single_color() { - PrintWriter out = new PrintWriter(System.out); - TestExecutionListener listener = new VerboseTreePrintingListener(out, ColorPalette.SINGLE_COLOR, 16, - Theme.ASCII); - - demoTestRun(listener); - - assertDoesNotThrow(out::flush); - } - - @Test - void simple_default() { - PrintWriter out = new PrintWriter(System.out); - TestExecutionListener listener = new TreePrintingListener(out, ColorPalette.DEFAULT, Theme.ASCII); - - demoTestRun(listener); - - assertDoesNotThrow(out::flush); - } - - @Test - void simple_single_color() { - PrintWriter out = new PrintWriter(System.out); - TestExecutionListener listener = new TreePrintingListener(out, ColorPalette.SINGLE_COLOR, Theme.ASCII); - - demoTestRun(listener); - - assertDoesNotThrow(out::flush); - } - - @Test - void flat_default() { - PrintWriter out = new PrintWriter(System.out); - TestExecutionListener listener = new FlatPrintingListener(out, ColorPalette.DEFAULT); - - demoTestRun(listener); - - assertDoesNotThrow(out::flush); - } - - @Test - void flat_single_color() { - PrintWriter out = new PrintWriter(System.out); - TestExecutionListener listener = new FlatPrintingListener(out, ColorPalette.SINGLE_COLOR); - - demoTestRun(listener); - - assertDoesNotThrow(out::flush); - } - - private void demoTestRun(TestExecutionListener listener) { - TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "My Test"); - TestPlan testPlan = TestPlan.from(List.of(testDescriptor), mock()); - listener.testPlanExecutionStarted(testPlan); - listener.executionStarted(TestIdentifier.from(testDescriptor)); - listener.executionFinished(TestIdentifier.from(testDescriptor), TestExecutionResult.successful()); - listener.dynamicTestRegistered(TestIdentifier.from(testDescriptor)); - listener.executionStarted(TestIdentifier.from(testDescriptor)); - listener.executionFinished(TestIdentifier.from(testDescriptor), - TestExecutionResult.failed(new Exception())); - listener.executionStarted(TestIdentifier.from(testDescriptor)); - listener.executionFinished(TestIdentifier.from(testDescriptor), - TestExecutionResult.aborted(new Exception())); - listener.reportingEntryPublished(TestIdentifier.from(testDescriptor), ReportEntry.from("Key", "Value")); - listener.executionSkipped(TestIdentifier.from(testDescriptor), "to demonstrate skipping"); - listener.testPlanExecutionFinished(testPlan); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java deleted file mode 100644 index 02c270af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.file.Paths; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.platform.console.ConsoleLauncherExecutionResult; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.console.options.Details; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; - -/** - * @since 1.0 - */ -class ConsoleTestExecutorTests { - - private static final Runnable FAILING_BLOCK = () -> fail("should fail"); - private static final Runnable SUCCEEDING_TEST = () -> { - }; - - private final StringWriter stringWriter = new StringWriter(); - private final CommandLineOptions options = new CommandLineOptions(); - private DemoHierarchicalTestEngine dummyTestEngine = new DemoHierarchicalTestEngine(); - - { - options.setScanClasspath(true); - } - - @Test - void printsSummary() throws Exception { - dummyTestEngine.addTest("succeedingTest", SUCCEEDING_TEST); - dummyTestEngine.addTest("failingTest", FAILING_BLOCK); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter)); - - assertThat(stringWriter.toString()).contains("Test run finished after", "2 tests found", "0 tests skipped", - "2 tests started", "0 tests aborted", "1 tests successful", "1 tests failed"); - } - - @Test - void printsDetailsIfTheyAreNotHidden() throws Exception { - options.setDetails(Details.FLAT); - - dummyTestEngine.addTest("failingTest", FAILING_BLOCK); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter)); - - assertThat(stringWriter.toString()).contains("Test execution started."); - } - - @Test - void printsNoDetailsIfTheyAreHidden() throws Exception { - options.setDetails(Details.NONE); - - dummyTestEngine.addTest("failingTest", FAILING_BLOCK); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter)); - - assertThat(stringWriter.toString()).doesNotContain("Test execution started."); - } - - @Test - void printsFailuresEvenIfDetailsAreHidden() throws Exception { - options.setDetails(Details.NONE); - - dummyTestEngine.addTest("failingTest", FAILING_BLOCK); - dummyTestEngine.addContainer("failingContainer", FAILING_BLOCK); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter)); - - assertThat(stringWriter.toString()).contains("Failures (2)", "failingTest", "failingContainer"); - } - - @Test - void hasStatusCode0ForSucceedingTest() throws Exception { - dummyTestEngine.addTest("succeedingTest", SUCCEEDING_TEST); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); - - assertThat(exitCode).isEqualTo(0); - } - - @Test - void hasStatusCode1ForFailingTest() throws Exception { - dummyTestEngine.addTest("failingTest", FAILING_BLOCK); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); - - assertThat(exitCode).isEqualTo(1); - } - - @Test - void hasStatusCode1ForFailingContainer() throws Exception { - dummyTestEngine.addContainer("failingContainer", FAILING_BLOCK); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); - - assertThat(exitCode).isEqualTo(1); - } - - /** - * @since 1.3 - */ - @Test - void hasStatusCode2ForNoTestsAndHasOptionFailIfNoTestsFound() throws Exception { - options.setFailIfNoTests(true); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); - - assertThat(exitCode).isEqualTo(2); - } - - /** - * @since 1.3 - */ - @Test - void hasStatusCode0ForNoTestsAndNotFailIfNoTestsFound() throws Exception { - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - var exitCode = ConsoleLauncherExecutionResult.computeExitCode(task.execute(dummyWriter()), options); - - assertThat(exitCode).isEqualTo(0); - } - - @Test - void usesCustomClassLoaderIfAdditionalClassPathEntriesArePresent() throws Exception { - options.setAdditionalClasspathEntries(List.of(Paths.get("."))); - - var oldClassLoader = getDefaultClassLoader(); - dummyTestEngine.addTest("failingTest", - () -> assertSame(oldClassLoader, getDefaultClassLoader(), "should fail")); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter)); - - assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); - } - - @Test - void usesSameClassLoaderIfNoAdditionalClassPathEntriesArePresent() throws Exception { - options.setAdditionalClasspathEntries(List.of()); - - var oldClassLoader = getDefaultClassLoader(); - dummyTestEngine.addTest("failingTest", - () -> assertNotSame(oldClassLoader, getDefaultClassLoader(), "should fail")); - - var task = new ConsoleTestExecutor(options, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter)); - - assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); - } - - private PrintWriter dummyWriter() { - return new PrintWriter(new StringWriter()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java deleted file mode 100644 index ef53d1fe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.0 - */ -class CustomContextClassLoaderExecutorTests { - - @Test - void invokeWithoutCustomClassLoaderDoesNotSetClassLoader() throws Exception { - var originalClassLoader = Thread.currentThread().getContextClassLoader(); - var executor = new CustomContextClassLoaderExecutor(Optional.empty()); - - int result = executor.invoke(() -> { - assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); - return 42; - }); - - assertEquals(42, result); - assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); - } - - @Test - void invokeWithCustomClassLoaderSetsCustomAndResetsToOriginal() throws Exception { - var originalClassLoader = Thread.currentThread().getContextClassLoader(); - ClassLoader customClassLoader = URLClassLoader.newInstance(new URL[0]); - var executor = new CustomContextClassLoaderExecutor(Optional.of(customClassLoader)); - - int result = executor.invoke(() -> { - assertSame(customClassLoader, Thread.currentThread().getContextClassLoader()); - return 23; - }); - - assertEquals(23, result); - assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); - } - - @Test - void invokeWithCustomClassLoaderAndEnsureItIsClosedAfterUsage() throws Exception { - var closed = new AtomicBoolean(false); - ClassLoader localClassLoader = new URLClassLoader(new URL[0]) { - @Override - public void close() throws IOException { - closed.set(true); - super.close(); - } - }; - var executor = new CustomContextClassLoaderExecutor(Optional.of(localClassLoader)); - - int result = executor.invoke(() -> 4711); - - assertEquals(4711, result); - assertTrue(closed.get()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java deleted file mode 100644 index 3456eb8f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; - -import java.io.File; -import java.net.URI; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.console.options.CommandLineOptions; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.ClasspathRootSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.IterationSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.PackageNameFilter; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UriSelector; -import org.junit.platform.launcher.LauncherDiscoveryRequest; - -/** - * @since 1.0 - */ -class DiscoveryRequestCreatorTests { - - private final CommandLineOptions options = new CommandLineOptions(); - - @Test - void convertsScanClasspathOptionWithoutExplicitRootDirectories() { - options.setScanClasspath(true); - - var request = convert(); - - var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); - // @formatter:off - assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) - .hasAtLeastOneElementOfType(URI.class) - .doesNotContainNull(); - // @formatter:on - } - - @Test - void convertsScanClasspathOptionWithExplicitRootDirectories() { - options.setScanClasspath(true); - options.setSelectedClasspathEntries(List.of(Paths.get("."), Paths.get(".."))); - - var request = convert(); - - var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); - // @formatter:off - assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) - .containsExactly(new File(".").toURI(), new File("..").toURI()); - // @formatter:on - } - - @Test - void convertsScanClasspathOptionWithAdditionalClasspathEntries() { - options.setScanClasspath(true); - options.setAdditionalClasspathEntries(List.of(Paths.get("."), Paths.get(".."))); - - var request = convert(); - - var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); - // @formatter:off - assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) - .contains(new File(".").toURI(), new File("..").toURI()); - // @formatter:on - } - - @Test - void doesNotSupportScanClasspathAndExplicitSelectors() { - options.setScanClasspath(true); - options.setSelectedClasses(List.of(selectClass("SomeTest"))); - - Throwable cause = assertThrows(PreconditionViolationException.class, this::convert); - - assertThat(cause).hasMessageContaining("not supported"); - } - - @Test - void convertsDefaultIncludeClassNamePatternOption() { - options.setScanClasspath(true); - - var request = convert(); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).hasSize(1); - assertExcludes(filters.get(0), STANDARD_INCLUDE_PATTERN); - } - - @Test - void convertsExplicitIncludeClassNamePatternOption() { - options.setScanClasspath(true); - options.setIncludedClassNamePatterns(List.of("Foo.*Bar", "Bar.*Foo")); - - var request = convert(); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).hasSize(1); - assertIncludes(filters.get(0), "Foo.*Bar"); - assertIncludes(filters.get(0), "Bar.*Foo"); - } - - @Test - void includeSelectedClassesAndMethodsRegardlessOfClassNamePatterns() { - options.setSelectedClasses(List.of(selectClass("SomeTest"))); - options.setSelectedMethods(List.of(selectMethod("com.acme.Foo#m()"))); - options.setSelectedIterations(List.of(selectIteration(selectMethod("com.acme.Bar#n()"), 0))); - options.setIncludedClassNamePatterns(List.of("Foo.*Bar")); - - var request = convert(); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).hasSize(1); - assertIncludes(filters.get(0), "SomeTest"); - assertIncludes(filters.get(0), "com.acme.Foo"); - assertIncludes(filters.get(0), "com.acme.Bar"); - assertIncludes(filters.get(0), "Foo.*Bar"); - } - - @Test - void convertsExcludeClassNamePatternOption() { - options.setScanClasspath(true); - options.setExcludedClassNamePatterns(List.of("Foo.*Bar", "Bar.*Foo")); - - var request = convert(); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).hasSize(2); - assertExcludes(filters.get(1), "Foo.*Bar"); - assertExcludes(filters.get(1), "Bar.*Foo"); - } - - @Test - void convertsPackageOptions() { - options.setScanClasspath(true); - options.setIncludedPackages(List.of("org.junit.included1", "org.junit.included2", "org.junit.included3")); - options.setExcludedPackages(List.of("org.junit.excluded1")); - - var request = convert(); - var packageNameFilters = request.getFiltersByType(PackageNameFilter.class); - - assertThat(packageNameFilters).hasSize(2); - assertIncludes(packageNameFilters.get(0), "org.junit.included1"); - assertIncludes(packageNameFilters.get(0), "org.junit.included2"); - assertIncludes(packageNameFilters.get(0), "org.junit.included3"); - assertExcludes(packageNameFilters.get(1), "org.junit.excluded1"); - } - - @Test - void convertsTagOptions() { - options.setScanClasspath(true); - options.setIncludedTagExpressions(List.of("fast", "medium", "slow")); - options.setExcludedTagExpressions(List.of("slow")); - - var request = convert(); - var postDiscoveryFilters = request.getPostDiscoveryFilters(); - - assertThat(postDiscoveryFilters).hasSize(2); - assertThat(postDiscoveryFilters.get(0).toString()).contains("TagFilter"); - assertThat(postDiscoveryFilters.get(1).toString()).contains("TagFilter"); - } - - @Test - void convertsEngineOptions() { - options.setScanClasspath(true); - options.setIncludedEngines(List.of("engine1", "engine2", "engine3")); - options.setExcludedEngines(List.of("engine2")); - - var request = convert(); - var engineFilters = request.getEngineFilters(); - - assertThat(engineFilters).hasSize(2); - assertThat(engineFilters.get(0).toString()).contains("includes", "[engine1, engine2, engine3]"); - assertThat(engineFilters.get(1).toString()).contains("excludes", "[engine2]"); - } - - @Test - void propagatesUriSelectors() { - options.setSelectedUris(List.of(selectUri("a"), selectUri("b"))); - - var request = convert(); - var uriSelectors = request.getSelectorsByType(UriSelector.class); - - assertThat(uriSelectors).extracting(UriSelector::getUri).containsExactly(URI.create("a"), URI.create("b")); - } - - @Test - void propagatesFileSelectors() { - options.setSelectedFiles(List.of(selectFile("foo.txt"), selectFile("bar.csv"))); - - var request = convert(); - var fileSelectors = request.getSelectorsByType(FileSelector.class); - - assertThat(fileSelectors).extracting(FileSelector::getRawPath).containsExactly("foo.txt", "bar.csv"); - } - - @Test - void propagatesDirectorySelectors() { - options.setSelectedDirectories(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux"))); - - var request = convert(); - var directorySelectors = request.getSelectorsByType(DirectorySelector.class); - - assertThat(directorySelectors).extracting(DirectorySelector::getRawPath).containsExactly("foo/bar", "bar/qux"); - } - - @Test - void propagatesPackageSelectors() { - options.setSelectedPackages(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar"))); - - var request = convert(); - var packageSelectors = request.getSelectorsByType(PackageSelector.class); - - assertThat(packageSelectors).extracting(PackageSelector::getPackageName).containsExactly("com.acme.foo", - "com.example.bar"); - } - - @Test - void propagatesClassSelectors() { - options.setSelectedClasses(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar"))); - - var request = convert(); - var classSelectors = request.getSelectorsByType(ClassSelector.class); - - assertThat(classSelectors).extracting(ClassSelector::getClassName).containsExactly("com.acme.Foo", - "com.example.Bar"); - } - - @Test - void propagatesMethodSelectors() { - options.setSelectedMethods( - List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)"))); - - var request = convert(); - var methodSelectors = request.getSelectorsByType(MethodSelector.class); - - assertThat(methodSelectors).hasSize(2); - assertThat(methodSelectors.get(0).getClassName()).isEqualTo("com.acme.Foo"); - assertThat(methodSelectors.get(0).getMethodName()).isEqualTo("m"); - assertThat(methodSelectors.get(0).getMethodParameterTypes()).isEqualTo(""); - assertThat(methodSelectors.get(1).getClassName()).isEqualTo("com.example.Bar"); - assertThat(methodSelectors.get(1).getMethodName()).isEqualTo("method"); - assertThat(methodSelectors.get(1).getMethodParameterTypes()).isEqualTo("java.lang.Object"); - } - - @Test - void propagatesClasspathResourceSelectors() { - options.setSelectedClasspathResources( - List.of(selectClasspathResource("foo.csv"), selectClasspathResource("com/example/bar.json"))); - - var request = convert(); - var classpathResourceSelectors = request.getSelectorsByType(ClasspathResourceSelector.class); - - assertThat(classpathResourceSelectors).extracting( - ClasspathResourceSelector::getClasspathResourceName).containsExactly("foo.csv", "com/example/bar.json"); - } - - @Test - void propagatesIterationSelectors() { - var methodSelector = selectMethod("com.acme.Foo#m()"); - var classSelector = selectClass("com.example.Bar"); - options.setSelectedIterations(List.of(selectIteration(methodSelector, 1), selectIteration(classSelector, 2))); - - var request = convert(); - var iterationSelectors = request.getSelectorsByType(IterationSelector.class); - - assertThat(iterationSelectors).hasSize(2); - assertThat(iterationSelectors.get(0).getParentSelector()).isEqualTo(methodSelector); - assertThat(iterationSelectors.get(0).getIterationIndices()).containsExactly(1); - assertThat(iterationSelectors.get(1).getParentSelector()).isEqualTo(classSelector); - assertThat(iterationSelectors.get(1).getIterationIndices()).containsExactly(2); - } - - @Test - @SuppressWarnings("deprecation") - void convertsConfigurationParameters() { - options.setScanClasspath(true); - options.setConfigurationParameters(mapOf(entry("foo", "bar"), entry("baz", "true"))); - - var request = convert(); - var configurationParameters = request.getConfigurationParameters(); - - assertThat(configurationParameters.size()).isEqualTo(2); - assertThat(configurationParameters.get("foo")).contains("bar"); - assertThat(configurationParameters.getBoolean("baz")).contains(true); - } - - private LauncherDiscoveryRequest convert() { - var creator = new DiscoveryRequestCreator(); - return creator.toDiscoveryRequest(options); - } - - private void assertIncludes(Filter filter, String included) { - assertThat(filter.apply(included).included()).isTrue(); - } - - private void assertExcludes(Filter filter, String excluded) { - assertThat(filter.apply(excluded).excluded()).isTrue(); - } - - @SafeVarargs - @SuppressWarnings("varargs") - private static Map mapOf(Entry... entries) { - return Stream.of(entries).collect(toMap(Entry::getKey, Entry::getValue)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java deleted file mode 100644 index a79bd43f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.console.tasks.FlatPrintingListener.INDENTATION; -import static org.junit.platform.engine.TestExecutionResult.failed; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.assertj.core.util.Maps; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestIdentifier; - -/** - * @since 1.0 - */ -class FlatPrintingListenerTests { - - private static final String EOL = System.lineSeparator(); - - @Test - void executionSkipped() { - var stringWriter = new StringWriter(); - listener(stringWriter).executionSkipped(newTestIdentifier(), "Test" + EOL + "disabled"); - var lines = lines(stringWriter); - - assertEquals(3, lines.length); - assertAll("lines in the output", // - () -> assertEquals("Skipped: demo-test ([engine:demo-engine])", lines[0]), // - () -> assertEquals(INDENTATION + "=> Reason: Test", lines[1]), // - () -> assertEquals(INDENTATION + "disabled", lines[2])); - } - - @Test - void reportingEntryPublished() { - var stringWriter = new StringWriter(); - listener(stringWriter).reportingEntryPublished(newTestIdentifier(), ReportEntry.from("foo", "bar")); - var lines = lines(stringWriter); - - assertEquals(2, lines.length); - assertAll("lines in the output", // - () -> assertEquals("Reported: demo-test ([engine:demo-engine])", lines[0]), // - () -> assertTrue(lines[1].startsWith(INDENTATION + "=> Reported values: ReportEntry [timestamp =")), // - () -> assertTrue(lines[1].endsWith(", foo = 'bar']"))); - } - - @Test - void executionFinishedWithFailure() { - var stringWriter = new StringWriter(); - listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("Boom!"))); - var lines = lines(stringWriter); - - assertAll("lines in the output", // - () -> assertEquals("Finished: demo-test ([engine:demo-engine])", lines[0]), // - () -> assertEquals(INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", lines[1])); - } - - @Nested - class ColorPaletteTests { - - @Nested - class DefaultColorPaletteTests { - - @Test - void executionSkipped() { - var stringWriter = new StringWriter(); - new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).executionSkipped( - newTestIdentifier(), "Test" + EOL + "disabled"); - var lines = lines(stringWriter); - - assertEquals(3, lines.length); - assertAll("lines in the output", // - () -> assertEquals("\u001B[35mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // - () -> assertEquals("\u001B[35m" + INDENTATION + "=> Reason: Test", lines[1]), // - () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); - } - - @Test - void reportingEntryPublished() { - var stringWriter = new StringWriter(); - new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).reportingEntryPublished( - newTestIdentifier(), ReportEntry.from("foo", "bar")); - var lines = lines(stringWriter); - - assertEquals(2, lines.length); - assertAll("lines in the output", // - () -> assertEquals("\u001B[37mReported: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // - () -> assertTrue(lines[1].startsWith( - "\u001B[37m" + INDENTATION + "=> Reported values: ReportEntry [timestamp =")), // - () -> assertTrue(lines[1].endsWith(", foo = 'bar']\u001B[0m"))); - } - - @Test - void executionFinishedWithFailure() { - var stringWriter = new StringWriter(); - new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).executionFinished( - newTestIdentifier(), failed(new AssertionError("Boom!"))); - var lines = lines(stringWriter); - - assertAll("lines in the output", // - () -> assertEquals("\u001B[31mFinished: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // - () -> assertEquals("\u001B[31m" + INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", - lines[1]), - () -> assertTrue(lines[lines.length - 1].endsWith("\u001B[0m"))); - } - - } - - @Nested - class ColorPaletteOverrideTests { - - @Test - void overridingSkipped() { - var stringWriter = new StringWriter(); - ColorPalette colorPalette = new ColorPalette(Maps.newHashMap(Style.SKIPPED, "36;7")); - new FlatPrintingListener(new PrintWriter(stringWriter), colorPalette).executionSkipped( - newTestIdentifier(), "Test" + EOL + "disabled"); - var lines = lines(stringWriter); - - assertEquals(3, lines.length); - assertAll("lines in the output", // - () -> assertEquals("\u001B[36;7mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // - () -> assertEquals("\u001B[36;7m" + INDENTATION + "=> Reason: Test", lines[1]), // - () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); - } - - @Test - void overridingUnusedStyle() { - var stringWriter = new StringWriter(); - ColorPalette colorPalette = new ColorPalette(Maps.newHashMap(Style.FAILED, "36;7")); - new FlatPrintingListener(new PrintWriter(stringWriter), colorPalette).executionSkipped( - newTestIdentifier(), "Test" + EOL + "disabled"); - var lines = lines(stringWriter); - - assertEquals(3, lines.length); - assertAll("lines in the output", // - () -> assertEquals("\u001B[35mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // - () -> assertEquals("\u001B[35m" + INDENTATION + "=> Reason: Test", lines[1]), // - () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); - } - - } - - } - - private FlatPrintingListener listener(StringWriter stringWriter) { - return new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.NONE); - } - - private static TestIdentifier newTestIdentifier() { - var testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "demo-test"); - return TestIdentifier.from(testDescriptor); - } - - private String[] lines(StringWriter stringWriter) { - return stringWriter.toString().split(EOL); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java deleted file mode 100644 index a1256833..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadPoolExecutor; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.reporting.ReportEntry; - -class TreeNodeTests { - - private static final int NUM_THREADS = 2; - private static final int ITEMS_PER_THREAD = 1000; - - @Test - void caption() { - assertEquals("", TreeNode.createCaption("")); - assertEquals("[@@]", TreeNode.createCaption("[@@]")); - assertEquals("[@ @]", TreeNode.createCaption("[@ @]")); - assertEquals("[@ @]", TreeNode.createCaption("[@\u000B@]")); - assertEquals("[@ @]", TreeNode.createCaption("[@\t@]")); - assertEquals("[@ @]", TreeNode.createCaption("[@\t\n@]")); - assertEquals("[@ @]", TreeNode.createCaption("[@\t\n\r@]")); - assertEquals("[@ @]", TreeNode.createCaption("[@\t\n\r\f@]")); - assertEquals("@".repeat(80) + "...", TreeNode.createCaption("@".repeat(1000) + "!")); - } - - @Test - void childrenCanBeAddedConcurrently() throws Exception { - var treeNode = new TreeNode("root"); - - runConcurrently(() -> { - for (long i = 0; i < ITEMS_PER_THREAD; i++) { - treeNode.addChild(new TreeNode(String.valueOf(i))); - } - }); - - assertThat(treeNode.children.size()).isEqualTo(NUM_THREADS * ITEMS_PER_THREAD); - } - - @Test - void reportEntriesCanBeAddedConcurrently() throws Exception { - var treeNode = new TreeNode("root"); - - runConcurrently(() -> { - for (long i = 0; i < ITEMS_PER_THREAD; i++) { - treeNode.addReportEntry(ReportEntry.from("index", String.valueOf(i))); - } - }); - - assertThat(treeNode.reports.size()).isEqualTo(NUM_THREADS * ITEMS_PER_THREAD); - } - - private void runConcurrently(Runnable action) throws InterruptedException { - ExecutorService executor = new ThreadPoolExecutor(NUM_THREADS, NUM_THREADS, 10, SECONDS, - new ArrayBlockingQueue<>(NUM_THREADS)); - try { - var barrier = new CyclicBarrier(NUM_THREADS); - for (long i = 0; i < NUM_THREADS; i++) { - executor.submit(() -> { - await(barrier); - action.run(); - }); - } - } - finally { - executor.shutdown(); - var terminated = executor.awaitTermination(10, SECONDS); - assertTrue(terminated, "Executor was not terminated"); - } - } - - private void await(CyclicBarrier barrier) { - try { - barrier.await(); - } - catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java deleted file mode 100644 index f7d59827..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.platform.engine.TestExecutionResult.aborted; -import static org.junit.platform.engine.TestExecutionResult.failed; -import static org.junit.platform.engine.TestExecutionResult.successful; - -import java.io.ByteArrayOutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.platform.console.options.Theme; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestIdentifier; - -class TreePrinterTests { - - private final Charset charset = StandardCharsets.UTF_8; - private final ByteArrayOutputStream stream = new ByteArrayOutputStream(1000); - private final PrintWriter out = new PrintWriter(new OutputStreamWriter(stream, charset)); - - private List actual() { - try { - out.flush(); - return List.of(stream.toString(charset.name()).split("\\R")); - } - catch (UnsupportedEncodingException e) { - throw new AssertionError(charset.name() + " is an unsupported encoding?!", e); - } - } - - @Test - void emptyTree() { - new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(new TreeNode("")); - assertIterableEquals(List.of("╷"), actual()); - } - - @Test - void emptyEngines() { - var root = new TreeNode(""); - root.addChild(new TreeNode(identifier("e-0", "engine zero"), "none")); - root.addChild(new TreeNode(identifier("e-1", "engine one")).setResult(successful())); - root.addChild(new TreeNode(identifier("e-2", "engine two")).setResult(failed(null))); - root.addChild(new TreeNode(identifier("e-3", "engine three")).setResult(aborted(null))); - new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); - assertIterableEquals( // - List.of( // - "╷", // - "├─ engine zero ↷ none", // - "├─ engine one ✔", // - "├─ engine two ✘", // - "└─ engine three ■"), // - actual()); - } - - @Test - // https://github.com/junit-team/junit5/issues/786 - void printNodeHandlesNullMessageThrowableGracefully() { - var result = TestExecutionResult.failed(new NullPointerException()); - var node = new TreeNode(identifier("NPE", "test()")).setResult(result); - new TreePrinter(out, Theme.ASCII, ColorPalette.NONE).print(node); - assertLinesMatch(List.of(".", "+-- test() [X] java.lang.NullPointerException"), actual()); - } - - @Test - // https://github.com/junit-team/junit5/issues/1531 - void reportsAreTabbedCorrectly() { - var root = new TreeNode(""); - var e1 = new TreeNode(identifier("e-1", "engine one")).setResult(successful()); - e1.addReportEntry(ReportEntry.from("key", "e-1")); - root.addChild(e1); - - var c1 = new TreeNode(identifier("c-1", "class one")).setResult(successful()); - c1.addReportEntry(ReportEntry.from("key", "c-1")); - e1.addChild(c1); - - var m1 = new TreeNode(identifier("m-1", "method one")).setResult(successful()); - m1.addReportEntry(ReportEntry.from("key", "m-1")); - c1.addChild(m1); - - var m2 = new TreeNode(identifier("m-2", "method two")).setResult(successful()); - m2.addReportEntry(ReportEntry.from("key", "m-2")); - c1.addChild(m2); - - new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); - assertLinesMatch(List.of( // - "╷", // - "└─ engine one ✔", // - " │ ....-..-..T..:...* key = `e-1`", // - " └─ class one ✔", // - " │ ....-..-..T..:...* key = `c-1`", // - " ├─ method one ✔", // - " │ ....-..-..T..:...* key = `m-1`", // - " └─ method two ✔", // - " ....-..-..T..:...* key = `m-2`" // - ), // - actual()); - } - - private static TestIdentifier identifier(String id, String displayName) { - var descriptor = new TestDescriptorStub(UniqueId.forEngine(id), displayName); - return TestIdentifier.from(descriptor); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java deleted file mode 100644 index 2d140433..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.console.tasks; - -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.platform.engine.TestExecutionResult.failed; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.platform.console.options.Theme; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestIdentifier; - -/** - * @since 1.3.2 - */ -class VerboseTreeListenerTests { - - private static final String EOL = System.lineSeparator(); - - @Test - void executionSkipped() { - var stringWriter = new StringWriter(); - listener(stringWriter).executionSkipped(newTestIdentifier(), "Test" + EOL + "disabled"); - var lines = lines(stringWriter); - - assertLinesMatch(List.of( // - "+-- %c ool test", // - " tags: []", // - " uniqueId: [engine:demo-engine]", // - " parent: []", // - " reason: Test", // - " disabled", // - " status: [S] SKIPPED"), List.of(lines)); - } - - @Test - void reportingEntryPublished() { - var stringWriter = new StringWriter(); - listener(stringWriter).reportingEntryPublished(newTestIdentifier(), ReportEntry.from("foo", "bar")); - var lines = lines(stringWriter); - - assertLinesMatch(List.of(" reports: ReportEntry \\[timestamp = .+, foo = 'bar'\\]"), List.of(lines)); - } - - @Test - void executionFinishedWithFailure() { - var stringWriter = new StringWriter(); - listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("Boom!"))); - var lines = lines(stringWriter); - - assertLinesMatch(List.of(" caught: java.lang.AssertionError: Boom!", // - ">> STACKTRACE >>", // - " duration: \\d+ ms", // - " status: [X] FAILED"), List.of(lines)); - } - - @Test - void failureMessageWithFormatSpecifier() { - var stringWriter = new StringWriter(); - listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("%crash"))); - var lines = lines(stringWriter); - - assertLinesMatch(List.of(" caught: java.lang.AssertionError: %crash", // - ">> STACKTRACE >>", // - " duration: \\d+ ms", // - " status: [X] FAILED"), List.of(lines)); - } - - private VerboseTreePrintingListener listener(StringWriter stringWriter) { - return new VerboseTreePrintingListener(new PrintWriter(stringWriter), ColorPalette.NONE, 16, Theme.ASCII); - } - - private static TestIdentifier newTestIdentifier() { - var testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "%c ool test"); - return TestIdentifier.from(testDescriptor); - } - - private String[] lines(StringWriter stringWriter) { - return stringWriter.toString().split(EOL); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java deleted file mode 100644 index 422d86df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.FilterResult.excluded; -import static org.junit.platform.engine.FilterResult.included; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.launcher.FilterStub; - -/** - * Unit tests for {@link Filter#composeFilters}. - * - * {@link Filter#composeFilters} will delegate to {@linkplain CompositeFilter} under the hood. - * - * @since 1.0 - */ -class FilterCompositionTests { - - @Test - void composingNoFiltersCreatesFilterThatIncludesEverything() { - var composedFilter = Filter.composeFilters(); - - assertTrue(composedFilter.apply(String.class).included()); - assertTrue(composedFilter.toPredicate().test(String.class)); - assertTrue(composedFilter.apply(Object.class).included()); - assertTrue(composedFilter.toPredicate().test(Object.class)); - } - - @Test - void composingSingleFilterWillReturnTheOriginalOne() { - Filter singleFilter = ClassNameFilter.includeClassNamePatterns(".*ring.*"); - var composed = Filter.composeFilters(singleFilter); - assertSame(singleFilter, composed); - } - - @Test - void composingMultipleFiltersIsAConjunctionOfFilters() { - Filter firstFilter = ClassNameFilter.includeClassNamePatterns(".*ring.*"); - Filter secondFilter = ClassNameFilter.includeClassNamePatterns(".*Join.*"); - - var composed = Filter.composeFilters(firstFilter, secondFilter); - - assertFalse(composed.apply("java.lang.String").included()); - assertFalse(composed.toPredicate().test("java.lang.String")); - assertTrue(composed.apply("java.util.StringJoiner").included()); - assertTrue(composed.toPredicate().test("java.util.StringJoiner")); - } - - @Test - void aFilterComposedOfMultipleFiltersHasReadableDescription() { - Filter firstFilter = new FilterStub<>(o -> excluded("wrong"), () -> "1st"); - Filter secondFilter = new FilterStub<>(o -> included("right"), () -> "2nd"); - - var composed = Filter.composeFilters(firstFilter, secondFilter); - - assertFalse(composed.apply(String.class).included()); - assertEquals("(1st) and (2nd)", composed.toString()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java deleted file mode 100644 index a98e1ec3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.platform.fakes.TestDescriptorStub; - -/** - * @since 1.0 - */ -class TestDescriptorTests { - - @Test - void isRootWithoutParent() { - TestDescriptor root = new TestDescriptorStub(UniqueId.root("root", "id"), "id"); - - assertTrue(root.isRoot()); - } - - @Test - void isRootWithParent() { - TestDescriptor child = new TestDescriptorStub(UniqueId.root("child", "child"), "child"); - child.setParent(new TestDescriptorStub(UniqueId.root("root", "root"), "root")); - - assertFalse(child.isRoot()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java deleted file mode 100644 index d334b2f9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link TestTag}. - * - * @since 1.0 - */ -class TestTagTests { - - @Test - void validSyntax() { - // @formatter:off - assertAll("Valid Tag Syntax", - () -> yep("fast"), - () -> yep("super_fast"), - () -> yep("unit-test"), - () -> yep("integration.test"), - () -> yep("org.example.CustomTagClass"), - () -> yep(" surrounded-by-whitespace\t\n"), - () -> nope(null), - () -> nope(""), - () -> nope(" "), - () -> nope("\t"), - () -> nope("\f"), - () -> nope("\r"), - () -> nope("\n"), - () -> nope("custom tag"), // internal space - () -> nope(","), // comma - () -> nope("("), // opening parenthesis - () -> nope(")"), // closing parenthesis - () -> nope("&"), // boolean AND - () -> nope("|"), // boolean OR - () -> nope("!") // boolean NOT - ); - // @formatter:on - } - - @Test - void factory() { - assertEquals("foo", TestTag.create("foo").getName()); - assertEquals("foo.tag", TestTag.create("foo.tag").getName()); - assertEquals("foo-tag", TestTag.create("foo-tag").getName()); - assertEquals("foo-tag", TestTag.create(" foo-tag ").getName()); - assertEquals("foo-tag", TestTag.create("\t foo-tag \n").getName()); - } - - @Test - void factoryPreconditions() { - assertSyntaxViolation(null); - assertSyntaxViolation(""); - assertSyntaxViolation(" "); - assertSyntaxViolation("X\tX"); - assertSyntaxViolation("X\nX"); - assertSyntaxViolation("XXX\u005CtXXX"); - } - - @Test - void tagEqualsOtherTagWithSameName() { - assertEquals(TestTag.create("fast"), TestTag.create("fast")); - assertEquals(TestTag.create("fast").hashCode(), TestTag.create("fast").hashCode()); - assertNotEquals(null, TestTag.create("fast")); - assertNotEquals(TestTag.create("fast"), null); - } - - @Test - void toStringPrintsName() { - assertEquals("fast", TestTag.create("fast").toString()); - } - - private static void yep(String tag) { - assertTrue(TestTag.isValid(tag), () -> String.format("'%s' should be a valid tag", tag)); - } - - private static void nope(String tag) { - assertFalse(TestTag.isValid(tag), () -> String.format("'%s' should not be a valid tag", tag)); - } - - private void assertSyntaxViolation(String tag) { - var exception = assertThrows(PreconditionViolationException.class, () -> TestTag.create(tag)); - assertThat(exception).hasMessageStartingWith("Tag name"); - assertThat(exception).hasMessageEndingWith("must be syntactically valid"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java deleted file mode 100644 index 6913a7dc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.engine.UniqueId.Segment; - -/** - * @since 1.0 - */ -class UniqueIdFormatTests { - - @Nested - class Formatting { - - private final UniqueId engineId = UniqueId.root("engine", "junit-jupiter"); - - private final UniqueIdFormat format = UniqueIdFormat.getDefault(); - - @Test - void engineIdOnly() { - assertEquals("[engine:junit-jupiter]", engineId.toString()); - assertEquals(format.format(engineId), engineId.toString()); - } - - @Test - void withTwoSegments() { - var classId = engineId.append("class", "org.junit.MyClass"); - assertEquals("[engine:junit-jupiter]/[class:org.junit.MyClass]", classId.toString()); - assertEquals(format.format(classId), classId.toString()); - } - - @Test - void withManySegments() { - var uniqueId = engineId.append("t1", "v1").append("t2", "v2").append("t3", "v3"); - assertEquals("[engine:junit-jupiter]/[t1:v1]/[t2:v2]/[t3:v3]", uniqueId.toString()); - assertEquals(format.format(uniqueId), uniqueId.toString()); - } - - } - - @Nested - class ParsingWithDefaultFormat implements ParsingTestTrait { - - private final UniqueIdFormat format = UniqueIdFormat.getDefault(); - - @Override - public UniqueIdFormat getFormat() { - return this.format; - } - - @Override - public String getEngineUid() { - return "[engine:junit-jupiter]"; - } - - @Override - public String getMethodUid() { - return "[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"; - } - - } - - @Nested - class ParsingWithCustomFormat implements ParsingTestTrait { - - private final UniqueIdFormat format = new UniqueIdFormat('{', '=', '}', ','); - - @Override - public UniqueIdFormat getFormat() { - return this.format; - } - - @Override - public String getEngineUid() { - return "{engine=junit-jupiter}"; - } - - @Override - public String getMethodUid() { - return "{engine=junit-jupiter},{class=MyClass},{method=myMethod}"; - } - - } - - // ------------------------------------------------------------------------- - - private static void assertSegment(Segment segment, String expectedType, String expectedValue) { - assertEquals(expectedType, segment.getType(), "segment type"); - assertEquals(expectedValue, segment.getValue(), "segment value"); - } - - interface ParsingTestTrait { - - UniqueIdFormat getFormat(); - - String getEngineUid(); - - String getMethodUid(); - - @Test - default void parseMalformedUid() { - Throwable throwable = assertThrows(JUnitException.class, () -> getFormat().parse("malformed UID")); - assertTrue(throwable.getMessage().contains("malformed UID")); - } - - @Test - default void parseEngineUid() { - var parsedId = getFormat().parse(getEngineUid()); - assertSegment(parsedId.getSegments().get(0), "engine", "junit-jupiter"); - assertEquals(getEngineUid(), getFormat().format(parsedId)); - assertEquals(getEngineUid(), parsedId.toString()); - } - - @Test - default void parseMethodUid() { - var parsedId = getFormat().parse(getMethodUid()); - assertSegment(parsedId.getSegments().get(0), "engine", "junit-jupiter"); - assertSegment(parsedId.getSegments().get(1), "class", "MyClass"); - assertSegment(parsedId.getSegments().get(2), "method", "myMethod"); - assertEquals(getMethodUid(), getFormat().format(parsedId)); - assertEquals(getMethodUid(), parsedId.toString()); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java deleted file mode 100644 index 9c87d261..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Optional; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.UniqueId.Segment; - -/** - * Unit tests for {@link UniqueId}. - * - * @since 1.0 - * @see org.junit.jupiter.engine.execution.UniqueIdParsingForArrayParameterIntegrationTests - */ -class UniqueIdTests { - - private static final String ENGINE_ID = "junit-jupiter"; - private static final String SUITE_ENGINE_ID = "junit-platform-suite"; - - @Nested - class Creation { - - @Test - void uniqueIdCanBeCreatedFromEngineId() { - var uniqueId = UniqueId.forEngine(ENGINE_ID); - - assertEquals("[engine:junit-jupiter]", uniqueId.toString()); - assertSegment(uniqueId.getSegments().get(0), "engine", "junit-jupiter"); - } - - @Test - void engineIdCanBeAppended() { - var suiteEngineId = UniqueId.forEngine(SUITE_ENGINE_ID); - var uniqueId = suiteEngineId.appendEngine(ENGINE_ID); - assertSegment(uniqueId.getSegments().get(1), "engine", "junit-jupiter"); - } - - @Test - void retrievingOptionalEngineId() { - var uniqueIdWithEngine = UniqueId.forEngine(ENGINE_ID); - assertThat(uniqueIdWithEngine.getEngineId()).contains("junit-jupiter"); - - var uniqueIdWithoutEngine = UniqueId.root("root", "avalue"); - assertEquals(Optional.empty(), uniqueIdWithoutEngine.getEngineId()); - } - - @Test - void uniqueIdCanBeCreatedFromTypeAndValue() { - var uniqueId = UniqueId.root("aType", "aValue"); - - assertEquals("[aType:aValue]", uniqueId.toString()); - assertSegment(uniqueId.getSegments().get(0), "aType", "aValue"); - } - - @Test - void rootSegmentCanBeRetrieved() { - var uniqueId = UniqueId.root("aType", "aValue"); - - assertThat(uniqueId.getRoot()).contains(new Segment("aType", "aValue")); - } - - @Test - void appendingOneSegment() { - var engineId = UniqueId.root("engine", ENGINE_ID); - var classId = engineId.append("class", "org.junit.MyClass"); - - assertThat(classId.getSegments()).hasSize(2); - assertSegment(classId.getSegments().get(0), "engine", ENGINE_ID); - assertSegment(classId.getSegments().get(1), "class", "org.junit.MyClass"); - } - - @Test - void appendingSegmentLeavesOriginalUnchanged() { - var uniqueId = UniqueId.root("engine", ENGINE_ID); - uniqueId.append("class", "org.junit.MyClass"); - - assertThat(uniqueId.getSegments()).hasSize(1); - assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); - } - - @Test - void appendingSeveralSegments() { - var engineId = UniqueId.root("engine", ENGINE_ID); - var uniqueId = engineId.append("t1", "v1").append("t2", "v2").append("t3", "v3"); - - assertThat(uniqueId.getSegments()).hasSize(4); - assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); - assertSegment(uniqueId.getSegments().get(1), "t1", "v1"); - assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); - assertSegment(uniqueId.getSegments().get(3), "t3", "v3"); - } - - @Test - void appendingSegmentInstance() { - var uniqueId = UniqueId.forEngine(ENGINE_ID).append("t1", "v1"); - - uniqueId = uniqueId.append(new Segment("t2", "v2")); - - assertThat(uniqueId.getSegments()).hasSize(3); - assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); - assertSegment(uniqueId.getSegments().get(1), "t1", "v1"); - assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); - } - - @Test - void appendingNullIsNotAllowed() { - var uniqueId = UniqueId.forEngine(ENGINE_ID); - - assertThrows(PreconditionViolationException.class, () -> uniqueId.append(null)); - assertThrows(PreconditionViolationException.class, () -> uniqueId.append(null, "foo")); - assertThrows(PreconditionViolationException.class, () -> uniqueId.append("foo", null)); - } - - } - - @Nested - class ParsingAndFormatting { - - @Test - void ensureDefaultUniqueIdFormatIsUsedForParsing() { - var uniqueIdString = "[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"; - var parsedDirectly = UniqueId.parse(uniqueIdString); - var parsedViaFormat = UniqueIdFormat.getDefault().parse(uniqueIdString); - assertEquals(parsedViaFormat, parsedDirectly); - } - - @Test - void ensureDefaultUniqueIdFormatIsUsedForFormatting() { - var parsedDirectly = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); - assertEquals("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]", parsedDirectly.toString()); - } - - @Test - void ensureDefaultUniqueIdFormatDecodingEncodesSegmentParts() { - var segment = UniqueId.parse("[%5B+%25+%5D):(%3A+%2B+%2F]").getSegments().get(0); - assertEquals("[ % ])", segment.getType()); - assertEquals("(: + /", segment.getValue()); - } - - @Test - void ensureDefaultUniqueIdFormatCanHandleAllCharacters() { - for (char c = 0; c < Character.MAX_VALUE; c++) { - var value = "foo " + c + " bar"; - var uniqueId = UniqueId.parse(UniqueId.root("type", value).toString()); - var segment = uniqueId.getSegments().get(0); - assertEquals(value, segment.getValue()); - } - } - - @ParameterizedTest - @ValueSource(strings = { "[a:b]", "[a:b]/[a:b]", "[a$b:b()]", "[a:b(%5BI)]", "[%5B%5D:%3A%2F]" }) - void ensureDefaultToStringAndParsingIsIdempotent(String expected) { - assertEquals(expected, UniqueId.parse(expected).toString()); - } - } - - @Nested - class EqualsContract { - - @Test - void sameEnginesAreEqual() { - var id1 = UniqueId.root("engine", "junit-jupiter"); - var id2 = UniqueId.root("engine", "junit-jupiter"); - - assertEquals(id2, id1); - assertEquals(id1, id2); - assertEquals(id1.hashCode(), id2.hashCode()); - } - - @Test - void differentEnginesAreNotEqual() { - var id1 = UniqueId.root("engine", "junit-vintage"); - var id2 = UniqueId.root("engine", "junit-jupiter"); - - assertNotEquals(id2, id1); - assertNotEquals(id1, id2); - } - - @Test - void uniqueIdWithSameSegmentsAreEqual() { - var id1 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); - var id2 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); - - assertEquals(id2, id1); - assertEquals(id1, id2); - assertEquals(id1.hashCode(), id2.hashCode()); - } - - @Test - void differentOrderOfSegmentsAreNotEqual() { - var id1 = UniqueId.root("engine", "junit-jupiter").append("t2", "v2").append("t1", "v1"); - var id2 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); - - assertNotEquals(id2, id1); - assertNotEquals(id1, id2); - } - - @Test - void additionalSegmentMakesItNotEqual() { - var id1 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1"); - var id2 = id1.append("t2", "v2"); - - assertNotEquals(id2, id1); - assertNotEquals(id1, id2); - } - } - - @Nested - class Prefixing { - - @Test - void nullIsNotAPrefix() { - var id = UniqueId.forEngine(ENGINE_ID); - - assertThrows(PreconditionViolationException.class, () -> id.hasPrefix(null)); - } - - @Test - void uniqueIdIsPrefixForItself() { - var id = UniqueId.forEngine(ENGINE_ID).append("t1", "v1").append("t2", "v2"); - - assertTrue(id.hasPrefix(id)); - } - - @Test - void uniqueIdIsPrefixForUniqueIdWithAdditionalSegments() { - var id1 = UniqueId.forEngine(ENGINE_ID); - var id2 = id1.append("t1", "v1"); - var id3 = id2.append("t2", "v2"); - - assertFalse(id1.hasPrefix(id2)); - assertFalse(id1.hasPrefix(id3)); - assertTrue(id2.hasPrefix(id1)); - assertFalse(id2.hasPrefix(id3)); - assertTrue(id3.hasPrefix(id1)); - assertTrue(id3.hasPrefix(id2)); - } - - @Test - void completelyUnrelatedUniqueIdsAreNotPrefixesForEachOther() { - var id1 = UniqueId.forEngine("foo"); - var id2 = UniqueId.forEngine("bar"); - - assertFalse(id1.hasPrefix(id2)); - assertFalse(id2.hasPrefix(id1)); - } - - } - - @Nested - class LastSegment { - - @Test - void returnsLastSegment() { - var uniqueId = UniqueId.forEngine("foo"); - assertSame(uniqueId.getSegments().get(0), uniqueId.getLastSegment()); - - uniqueId = UniqueId.forEngine("foo").append("type", "bar"); - assertSame(uniqueId.getSegments().get(1), uniqueId.getLastSegment()); - } - - @Test - void removesLastSegment() { - var uniqueId = UniqueId.forEngine("foo"); - assertThrows(PreconditionViolationException.class, uniqueId::removeLastSegment); - - var newUniqueId = uniqueId.append("type", "bar").removeLastSegment(); - assertEquals(uniqueId, newUniqueId); - } - - } - - private void assertSegment(Segment segment, String expectedType, String expectedValue) { - assertEquals(expectedType, segment.getType(), "segment type"); - assertEquals(expectedValue, segment.getValue(), "segment value"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java deleted file mode 100644 index 622f0041..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 1.0 - */ -class ClassNameFilterTests { - - @Test - void includeClassNamePatternsChecksPreconditions() { - assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns((String[]) null)) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("patterns array must not be null or empty"); - assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns(new String[0])) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("patterns array must not be null or empty"); - assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns(new String[] { null })) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("patterns array must not contain null elements"); - } - - @Test - void includeClassNamePatternsWithSinglePattern() { - var regex = "^java\\.lang\\..*"; - - var filter = ClassNameFilter.includeClassNamePatterns(regex); - - assertThat(filter).hasToString( - "IncludeClassNameFilter that includes class names that match one of the following regular expressions: '" - + regex + "'"); - - assertTrue(filter.apply("java.lang.String").included()); - assertTrue(filter.toPredicate().test("java.lang.String")); - assertThat(filter.apply("java.lang.String").getReason()).contains( - "Class name [java.lang.String] matches included pattern: '" + regex + "'"); - - assertFalse(filter.apply("java.time.Instant").included()); - assertFalse(filter.toPredicate().test("java.time.Instant")); - assertThat(filter.apply("java.time.Instant").getReason()).contains( - "Class name [java.time.Instant] does not match any included pattern: '" + regex + "'"); - } - - @Test - void includeClassNamePatternsWithMultiplePatterns() { - var firstRegex = "^java\\.lang\\..*"; - var secondRegex = "^java\\.util\\..*"; - - var filter = ClassNameFilter.includeClassNamePatterns(firstRegex, secondRegex); - - assertThat(filter).hasToString( - "IncludeClassNameFilter that includes class names that match one of the following regular expressions: '" - + firstRegex + "' OR '" + secondRegex + "'"); - - assertTrue(filter.apply("java.lang.String").included()); - assertTrue(filter.toPredicate().test("java.lang.String")); - assertThat(filter.apply("java.lang.String").getReason()).contains( - "Class name [java.lang.String] matches included pattern: '" + firstRegex + "'"); - - assertTrue(filter.apply("java.util.Collection").included()); - assertTrue(filter.toPredicate().test("java.util.Collection")); - assertThat(filter.apply("java.util.Collection").getReason()).contains( - "Class name [java.util.Collection] matches included pattern: '" + secondRegex + "'"); - - assertFalse(filter.apply("java.time.Instant").included()); - assertFalse(filter.toPredicate().test("java.time.Instant")); - assertThat(filter.apply("java.time.Instant").getReason()).contains( - "Class name [java.time.Instant] does not match any included pattern: '" + firstRegex + "' OR '" - + secondRegex + "'"); - } - - @Test - void excludeClassNamePatternsChecksPreconditions() { - assertThatThrownBy(() -> ClassNameFilter.excludeClassNamePatterns((String[]) null)) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("patterns array must not be null or empty"); - assertThatThrownBy(() -> ClassNameFilter.excludeClassNamePatterns(new String[0])) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("patterns array must not be null or empty"); - assertThatThrownBy(() -> ClassNameFilter.excludeClassNamePatterns(new String[] { null })) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("patterns array must not contain null elements"); - } - - @Test - void excludeClassNamePatternsWithSinglePattern() { - var regex = "^java\\.lang\\..*"; - - var filter = ClassNameFilter.excludeClassNamePatterns(regex); - - assertThat(filter).hasToString( - "ExcludeClassNameFilter that excludes class names that match one of the following regular expressions: '" - + regex + "'"); - - assertTrue(filter.apply("java.lang.String").excluded()); - assertFalse(filter.toPredicate().test("java.lang.String")); - - assertThat(filter.apply("java.lang.String").getReason()).contains( - "Class name [java.lang.String] matches excluded pattern: '" + regex + "'"); - - assertTrue(filter.apply("java.time.Instant").included()); - assertTrue(filter.toPredicate().test("java.time.Instant")); - assertThat(filter.apply("java.time.Instant").getReason()).contains( - "Class name [java.time.Instant] does not match any excluded pattern: '" + regex + "'"); - } - - @Test - void excludeClassNamePatternsWithMultiplePatterns() { - var firstRegex = "^java\\.lang\\..*"; - var secondRegex = "^java\\.util\\..*"; - - var filter = ClassNameFilter.excludeClassNamePatterns(firstRegex, secondRegex); - - assertThat(filter).hasToString( - "ExcludeClassNameFilter that excludes class names that match one of the following regular expressions: '" - + firstRegex + "' OR '" + secondRegex + "'"); - - assertTrue(filter.apply("java.lang.String").excluded()); - assertFalse(filter.toPredicate().test("java.lang.String")); - assertThat(filter.apply("java.lang.String").getReason()).contains( - "Class name [java.lang.String] matches excluded pattern: '" + firstRegex + "'"); - - assertTrue(filter.apply("java.util.Collection").excluded()); - assertFalse(filter.toPredicate().test("java.util.Collection")); - assertThat(filter.apply("java.util.Collection").getReason()).contains( - "Class name [java.util.Collection] matches excluded pattern: '" + secondRegex + "'"); - - assertFalse(filter.apply("java.time.Instant").excluded()); - assertTrue(filter.toPredicate().test("java.time.Instant")); - assertThat(filter.apply("java.time.Instant").getReason()).contains( - "Class name [java.time.Instant] does not match any excluded pattern: '" + firstRegex + "' OR '" - + secondRegex + "'"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java deleted file mode 100644 index 6da64efd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link ClassSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class ClassSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new ClassSelector("org.example.TestClass"); - var selector2 = new ClassSelector("org.example.TestClass"); - var selector3 = new ClassSelector("org.example.X"); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - - @Test - void preservesOriginalExceptionWhenTryingToLoadClass() { - var selector = new ClassSelector("org.example.TestClass"); - - var e = assertThrows(PreconditionViolationException.class, selector::getJavaClass); - - assertThat(e).hasMessage("Could not load class with name: org.example.TestClass").hasCauseInstanceOf( - ClassNotFoundException.class); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java deleted file mode 100644 index 17559826..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; - -/** - * Unit tests for {@link ClasspathResourceSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class ClasspathResourceSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new ClasspathResourceSelector("/foo/bar.txt", null); - var selector2 = new ClasspathResourceSelector("/foo/bar.txt", null); - var selector3 = new ClasspathResourceSelector("/foo/X.txt", null); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - - @Test - void equalsAndHashCodeWithFilePosition() { - var selector1 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(1)); - var selector2 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(1)); - var selector3 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(2)); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java deleted file mode 100644 index 3f23e8aa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import java.net.URI; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; - -/** - * Unit tests for {@link ClasspathRootSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class ClasspathRootSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() throws Exception { - var selector1 = new ClasspathRootSelector(new URI("file://example/path")); - var selector2 = new ClasspathRootSelector(new URI("file://example/path")); - var selector3 = new ClasspathRootSelector(new URI("file://example/foo")); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java deleted file mode 100644 index 59f6522b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; - -/** - * Unit tests for {@link DirectorySelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class DirectorySelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new DirectorySelector("/example/foo"); - var selector2 = new DirectorySelector("/example/foo"); - var selector3 = new DirectorySelector("/example/bar"); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java deleted file mode 100644 index db65c530..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java +++ /dev/null @@ -1,852 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static java.lang.String.join; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModules; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; - -import java.io.File; -import java.lang.reflect.Method; -import java.net.URI; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; - -/** - * Unit tests for {@link DiscoverySelectors}. - * - * @since 1.0 - */ -class DiscoverySelectorsTests { - - @Test - void selectUriByName() { - assertViolatesPrecondition(() -> selectUri((String) null)); - assertViolatesPrecondition(() -> selectUri(" ")); - assertViolatesPrecondition(() -> selectUri("foo:")); - - var uri = "https://junit.org"; - - var selector = selectUri(uri); - assertEquals(uri, selector.getUri().toString()); - } - - @Test - void selectUriByURI() throws Exception { - assertViolatesPrecondition(() -> selectUri((URI) null)); - assertViolatesPrecondition(() -> selectUri(" ")); - - var uri = new URI("https://junit.org"); - - var selector = selectUri(uri); - assertEquals(uri, selector.getUri()); - } - - @Test - void selectFileByName() { - assertViolatesPrecondition(() -> selectFile((String) null)); - assertViolatesPrecondition(() -> selectFile(" ")); - - var path = "src/test/resources/do_not_delete_me.txt"; - - var selector = selectFile(path); - assertEquals(path, selector.getRawPath()); - assertEquals(new File(path), selector.getFile()); - assertEquals(Paths.get(path), selector.getPath()); - } - - @Test - void selectFileByNameAndPosition() { - var filePosition = FilePosition.from(12, 34); - assertViolatesPrecondition(() -> selectFile((String) null, filePosition)); - assertViolatesPrecondition(() -> selectFile(" ", filePosition)); - - var path = "src/test/resources/do_not_delete_me.txt"; - - var selector = selectFile(path, filePosition); - assertEquals(path, selector.getRawPath()); - assertEquals(new File(path), selector.getFile()); - assertEquals(Paths.get(path), selector.getPath()); - assertEquals(filePosition, selector.getPosition().get()); - } - - @Test - void selectFileByFileReference() throws Exception { - assertViolatesPrecondition(() -> selectFile((File) null)); - assertViolatesPrecondition(() -> selectFile(new File("bogus/nonexistent.txt"))); - - var currentDir = new File(".").getCanonicalFile(); - var relativeDir = new File("..", currentDir.getName()); - var file = new File(relativeDir, "src/test/resources/do_not_delete_me.txt"); - var path = file.getCanonicalFile().getPath(); - - var selector = selectFile(file); - assertEquals(path, selector.getRawPath()); - assertEquals(file.getCanonicalFile(), selector.getFile()); - assertEquals(Paths.get(path), selector.getPath()); - } - - @Test - void selectFileByFileReferenceAndPosition() throws Exception { - var filePosition = FilePosition.from(12, 34); - assertViolatesPrecondition(() -> selectFile((File) null, filePosition)); - assertViolatesPrecondition(() -> selectFile(new File("bogus/nonexistent.txt"), filePosition)); - - var currentDir = new File(".").getCanonicalFile(); - var relativeDir = new File("..", currentDir.getName()); - var file = new File(relativeDir, "src/test/resources/do_not_delete_me.txt"); - var path = file.getCanonicalFile().getPath(); - - var selector = selectFile(file, filePosition); - assertEquals(path, selector.getRawPath()); - assertEquals(file.getCanonicalFile(), selector.getFile()); - assertEquals(Paths.get(path), selector.getPath()); - assertEquals(FilePosition.from(12, 34), selector.getPosition().get()); - } - - @Test - void selectDirectoryByName() { - assertViolatesPrecondition(() -> selectDirectory((String) null)); - assertViolatesPrecondition(() -> selectDirectory(" ")); - - var path = "src/test/resources"; - - var selector = selectDirectory(path); - assertEquals(path, selector.getRawPath()); - assertEquals(new File(path), selector.getDirectory()); - assertEquals(Paths.get(path), selector.getPath()); - } - - @Test - void selectDirectoryByFileReference() throws Exception { - assertViolatesPrecondition(() -> selectDirectory((File) null)); - assertViolatesPrecondition(() -> selectDirectory(new File("bogus/nonexistent"))); - - var currentDir = new File(".").getCanonicalFile(); - var relativeDir = new File("..", currentDir.getName()); - var directory = new File(relativeDir, "src/test/resources"); - var path = directory.getCanonicalFile().getPath(); - - var selector = selectDirectory(directory); - assertEquals(path, selector.getRawPath()); - assertEquals(directory.getCanonicalFile(), selector.getDirectory()); - assertEquals(Paths.get(path), selector.getPath()); - } - - @Test - void selectClasspathResources() { - assertViolatesPrecondition(() -> selectClasspathResource(null)); - assertViolatesPrecondition(() -> selectClasspathResource("")); - assertViolatesPrecondition(() -> selectClasspathResource(" ")); - assertViolatesPrecondition(() -> selectClasspathResource("\t")); - - // with unnecessary "/" prefix - var selector = selectClasspathResource("/foo/bar/spec.xml"); - assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); - - // standard use case - selector = selectClasspathResource("A/B/C/spec.json"); - assertEquals("A/B/C/spec.json", selector.getClasspathResourceName()); - } - - @Test - void selectClasspathResourcesWithFilePosition() { - var filePosition = FilePosition.from(12, 34); - assertViolatesPrecondition(() -> selectClasspathResource(null, filePosition)); - assertViolatesPrecondition(() -> selectClasspathResource("", filePosition)); - assertViolatesPrecondition(() -> selectClasspathResource(" ", filePosition)); - assertViolatesPrecondition(() -> selectClasspathResource("\t", filePosition)); - - // with unnecessary "/" prefix - var selector = selectClasspathResource("/foo/bar/spec.xml", filePosition); - assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); - assertEquals(FilePosition.from(12, 34), selector.getPosition().get()); - - // standard use case - selector = selectClasspathResource("A/B/C/spec.json", filePosition); - assertEquals("A/B/C/spec.json", selector.getClasspathResourceName()); - assertEquals(filePosition, selector.getPosition().get()); - } - - @Test - void selectModuleByName() { - var selector = selectModule("java.base"); - assertEquals("java.base", selector.getModuleName()); - } - - @Test - void selectModuleByNamePreconditions() { - assertViolatesPrecondition(() -> selectModule(null)); - assertViolatesPrecondition(() -> selectModule("")); - assertViolatesPrecondition(() -> selectModule(" ")); - } - - @Test - void selectModulesByNames() { - var selectors = selectModules(Set.of("a", "b")); - var names = selectors.stream().map(ModuleSelector::getModuleName).collect(Collectors.toList()); - assertThat(names).containsExactlyInAnyOrder("b", "a"); - } - - @Test - void selectModulesByNamesPreconditions() { - assertViolatesPrecondition(() -> selectModules(null)); - assertViolatesPrecondition(() -> selectModules(Set.of("a", " "))); - } - - @Test - void selectPackageByName() { - var selector = selectPackage(getClass().getPackage().getName()); - assertEquals(getClass().getPackage().getName(), selector.getPackageName()); - } - - @Test - void selectClassByName() { - var selector = selectClass(getClass().getName()); - assertEquals(getClass(), selector.getJavaClass()); - } - - @Test - void selectMethodByClassNameAndMethodNamePreconditions() { - assertViolatesPrecondition(() -> selectMethod("TestClass", null)); - assertViolatesPrecondition(() -> selectMethod("TestClass", "")); - assertViolatesPrecondition(() -> selectMethod("TestClass", " ")); - assertViolatesPrecondition(() -> selectMethod((String) null, "method")); - assertViolatesPrecondition(() -> selectMethod("", "method")); - assertViolatesPrecondition(() -> selectMethod(" ", "method")); - } - - @Test - void selectMethodByClassNameMethodNameAndMethodParameterTypesPreconditions() { - assertViolatesPrecondition(() -> selectMethod("TestClass", null, "int")); - assertViolatesPrecondition(() -> selectMethod("TestClass", "", "int")); - assertViolatesPrecondition(() -> selectMethod("TestClass", " ", "int")); - assertViolatesPrecondition(() -> selectMethod((String) null, "method", "int")); - assertViolatesPrecondition(() -> selectMethod("", "method", "int")); - assertViolatesPrecondition(() -> selectMethod(" ", "method", "int")); - assertViolatesPrecondition(() -> selectMethod("TestClass", "method", null)); - } - - @Test - void selectMethodByClassAndMethodNamePreconditions() { - assertViolatesPrecondition(() -> selectMethod(getClass(), (String) null)); - assertViolatesPrecondition(() -> selectMethod(getClass(), "")); - assertViolatesPrecondition(() -> selectMethod(getClass(), " ")); - assertViolatesPrecondition(() -> selectMethod((Class) null, "method")); - assertViolatesPrecondition(() -> selectMethod("", "method")); - assertViolatesPrecondition(() -> selectMethod(" ", "method")); - } - - @Test - void selectMethodByClassMethodNameAndMethodParameterTypesPreconditions() { - assertViolatesPrecondition(() -> selectMethod((Class) null, "method", "int")); - assertViolatesPrecondition(() -> selectMethod(getClass(), null, "int")); - assertViolatesPrecondition(() -> selectMethod(getClass(), "", "int")); - assertViolatesPrecondition(() -> selectMethod(getClass(), " ", "int")); - assertViolatesPrecondition(() -> selectMethod(getClass(), "method", null)); - } - - @Test - void selectMethodByClassAndMethodPreconditions() { - var method = getClass().getDeclaredMethods()[0]; - assertViolatesPrecondition(() -> selectMethod(null, method)); - assertViolatesPrecondition(() -> selectMethod(getClass(), (Method) null)); - } - - @ParameterizedTest(name = "FQMN: ''{0}''") - @MethodSource("invalidFullyQualifiedMethodNames") - void selectMethodByFullyQualifiedNamePreconditions(String fqmn, String message) { - Exception exception = assertThrows(PreconditionViolationException.class, () -> selectMethod(fqmn)); - assertThat(exception).hasMessageContaining(message); - } - - static Stream invalidFullyQualifiedMethodNames() { - // @formatter:off - return Stream.of( - arguments(null, "must not be null or blank"), - arguments("", "must not be null or blank"), - arguments(" ", "must not be null or blank"), - arguments("com.example", "not a valid fully qualified method name"), - arguments("com.example.Foo", "not a valid fully qualified method name"), - arguments("method", "not a valid fully qualified method name"), - arguments("#method", "not a valid fully qualified method name"), - arguments("#method()", "not a valid fully qualified method name"), - arguments("#method(int)", "not a valid fully qualified method name"), - arguments("java.lang.String#", "not a valid fully qualified method name") - ); - // @formatter:on - } - - @Test - void selectMethodByFullyQualifiedName() throws Exception { - Class clazz = getClass(); - var method = clazz.getDeclaredMethod("myTest"); - assertSelectMethodByFullyQualifiedName(clazz, method); - } - - @Test - void selectMethodByFullyQualifiedNameForDefaultMethodInInterface() throws Exception { - Class clazz = TestCaseWithDefaultMethod.class; - var method = clazz.getMethod("myTest"); - assertSelectMethodByFullyQualifiedName(clazz, method); - } - - @Test - void selectMethodByFullyQualifiedNameWithPrimitiveParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", int.class); - assertSelectMethodByFullyQualifiedName(getClass(), method, int.class, "int"); - } - - @Test - void selectMethodByFullyQualifiedNameWithPrimitiveParameterUsingSourceCodeSyntax() throws Exception { - var method = getClass().getDeclaredMethod("myTest", int.class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "int", "int"); - } - - @Test - void selectMethodByFullyQualifiedNameWithObjectParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", String.class); - assertSelectMethodByFullyQualifiedName(getClass(), method, String.class, String.class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithObjectParameterUsingSourceCodeSyntax() throws Exception { - var method = getClass().getDeclaredMethod("myTest", String.class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.String", String.class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithPrimitiveArrayParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", int[].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, int[].class, int[].class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithPrimitiveArrayParameterUsingSourceCodeSyntax() throws Exception { - var method = getClass().getDeclaredMethod("myTest", int[].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "int[]", "int[]"); - } - - @Test - void selectMethodByFullyQualifiedNameWithObjectArrayParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", String[].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, String[].class, String[].class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithObjectArrayParameterUsingSourceCodeSyntax() throws Exception { - var method = getClass().getDeclaredMethod("myTest", String[].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.String[]", "java.lang.String[]"); - } - - @Test - void selectMethodByFullyQualifiedNameWithTwoDimensionalPrimitiveArrayParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", int[][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, int[][].class, int[][].class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithTwoDimensionalPrimitiveArrayParameterUsingSourceCodeSyntax() - throws Exception { - var method = getClass().getDeclaredMethod("myTest", int[][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "int[][]", "int[][]"); - } - - @Test - void selectMethodByFullyQualifiedNameWithTwoDimensionalObjectArrayParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", String[][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, String[][].class, String[][].class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithTwoDimensionalObjectArrayParameterUsingSourceCodeSyntax() - throws Exception { - var method = getClass().getDeclaredMethod("myTest", String[][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.String[][]", "java.lang.String[][]"); - } - - @Test - void selectMethodByFullyQualifiedNameWithMultidimensionalPrimitiveArrayParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", int[][][][][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, int[][][][][].class, int[][][][][].class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithMultidimensionalPrimitiveArrayParameterUsingSourceCodeSyntax() - throws Exception { - - var method = getClass().getDeclaredMethod("myTest", int[][][][][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "int[][][][][]", "int[][][][][]"); - } - - @Test - void selectMethodByFullyQualifiedNameWithMultidimensionalObjectArrayParameter() throws Exception { - var method = getClass().getDeclaredMethod("myTest", Double[][][][][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, Double[][][][][].class, - Double[][][][][].class.getName()); - } - - @Test - void selectMethodByFullyQualifiedNameWithMultidimensionalObjectArrayParameterUsingSourceCodeSyntax() - throws Exception { - - var method = getClass().getDeclaredMethod("myTest", Double[][][][][].class); - assertSelectMethodByFullyQualifiedName(getClass(), method, "java.lang.Double[][][][][]", - "java.lang.Double[][][][][]"); - } - - @Test - void selectMethodByFullyQualifiedNameEndingInOpeningParenthesis() { - var className = "org.example.MyClass"; - // The following bizarre method name is not permissible in Java source - // code; however, it's permitted by the JVM -- for example, in Groovy - // or Kotlin source code using back ticks. - var methodName = ")--("; - var fqmn = className + "#" + methodName; - - var selector = selectMethod(fqmn); - assertEquals(className, selector.getClassName()); - assertEquals(methodName, selector.getMethodName()); - assertEquals("", selector.getMethodParameterTypes()); - } - - /** - * Inspired by Spock specifications. - */ - @Test - void selectMethodByFullyQualifiedNameContainingHashtags() { - var className = "org.example.CalculatorSpec"; - var methodName = "#a plus #b equals #c"; - var fqmn = className + "#" + methodName; - - var selector = selectMethod(fqmn); - assertEquals(className, selector.getClassName()); - assertEquals(methodName, selector.getMethodName()); - assertEquals("", selector.getMethodParameterTypes()); - } - - /** - * Inspired by Spock specifications. - */ - @Test - void selectMethodByFullyQualifiedNameContainingHashtagsAndWithParameterList() { - var className = "org.example.CalculatorSpec"; - var methodName = "#a plus #b equals #c"; - var methodParameters = "int, int, int"; - var fqmn = String.format("%s#%s(%s)", className, methodName, methodParameters); - - var selector = selectMethod(fqmn); - assertEquals(className, selector.getClassName()); - assertEquals(methodName, selector.getMethodName()); - assertEquals(methodParameters, selector.getMethodParameterTypes()); - } - - /** - * Inspired by Kotlin tests. - */ - @Test - void selectMethodByFullyQualifiedNameContainingParentheses() { - var className = "org.example.KotlinTestCase"; - var methodName = "🦆 ~|~test with a really, (really) terrible name & that needs to be changed!~|~"; - var fqmn = className + "#" + methodName; - - var selector = selectMethod(fqmn); - - assertEquals(className, selector.getClassName()); - assertEquals(methodName, selector.getMethodName()); - assertEquals("", selector.getMethodParameterTypes()); - } - - /** - * Inspired by Kotlin tests. - */ - @Test - void selectMethodByFullyQualifiedNameEndingWithParentheses() { - var className = "org.example.KotlinTestCase"; - var methodName = "test name ends with parentheses()"; - var fqmn = className + "#" + methodName + "()"; - - var selector = selectMethod(fqmn); - - assertEquals(className, selector.getClassName()); - assertEquals(methodName, selector.getMethodName()); - assertEquals("", selector.getMethodParameterTypes()); - } - - /** - * Inspired by Kotlin tests. - */ - @Test - void selectMethodByFullyQualifiedNameEndingWithParenthesesAndWithParameterList() { - var className = "org.example.KotlinTestCase"; - var methodName = "test name ends with parentheses()"; - var methodParameters = "int, int, int"; - var fqmn = String.format("%s#%s(%s)", className, methodName, methodParameters); - - var selector = selectMethod(fqmn); - - assertEquals(className, selector.getClassName()); - assertEquals(methodName, selector.getMethodName()); - assertEquals(methodParameters, selector.getMethodParameterTypes()); - } - - private void assertSelectMethodByFullyQualifiedName(Class clazz, Method method) { - var selector = selectMethod(fqmn(clazz, method.getName())); - assertEquals(method, selector.getJavaMethod()); - assertEquals(clazz, selector.getJavaClass()); - assertEquals(clazz.getName(), selector.getClassName()); - assertEquals(method.getName(), selector.getMethodName()); - assertEquals("", selector.getMethodParameterTypes()); - } - - private void assertSelectMethodByFullyQualifiedName(Class clazz, Method method, Class parameterType, - String expectedParameterTypes) { - - var selector = selectMethod(fqmn(parameterType)); - assertEquals(method, selector.getJavaMethod()); - assertEquals(clazz, selector.getJavaClass()); - assertEquals(clazz.getName(), selector.getClassName()); - assertEquals(method.getName(), selector.getMethodName()); - assertEquals(expectedParameterTypes, selector.getMethodParameterTypes()); - } - - private void assertSelectMethodByFullyQualifiedName(Class clazz, Method method, String parameterName, - String expectedParameterTypes) { - - var selector = selectMethod(fqmnWithParamNames(parameterName)); - assertEquals(method, selector.getJavaMethod()); - assertEquals(clazz, selector.getJavaClass()); - assertEquals(clazz.getName(), selector.getClassName()); - assertEquals(method.getName(), selector.getMethodName()); - assertEquals(expectedParameterTypes, selector.getMethodParameterTypes()); - } - - @Test - void selectMethodByClassAndMethodName() throws Exception { - var method = getClass().getDeclaredMethod("myTest"); - - var selector = selectMethod(getClass(), "myTest"); - assertEquals(getClass(), selector.getJavaClass()); - assertEquals(getClass().getName(), selector.getClassName()); - assertEquals(method, selector.getJavaMethod()); - assertEquals("myTest", selector.getMethodName()); - assertEquals("", selector.getMethodParameterTypes()); - } - - @Test - void selectMethodByClassAndMethodNameWithParameterTypes() throws Exception { - var method = getClass().getDeclaredMethod("myTest", String.class); - - var selector = selectMethod(getClass(), "myTest", String.class.getName()); - assertEquals(getClass(), selector.getJavaClass()); - assertEquals(getClass().getName(), selector.getClassName()); - assertEquals(method, selector.getJavaMethod()); - assertEquals("myTest", selector.getMethodName()); - assertEquals(String.class.getName(), selector.getMethodParameterTypes()); - } - - @Test - void selectMethodWithParametersByMethodReference() throws Exception { - var method = getClass().getDeclaredMethod("myTest", String.class); - - var selector = selectMethod(getClass(), method); - assertEquals(method, selector.getJavaMethod()); - assertEquals(getClass(), selector.getJavaClass()); - assertEquals(getClass().getName(), selector.getClassName()); - assertEquals("myTest", selector.getMethodName()); - assertEquals(String.class.getName(), selector.getMethodParameterTypes()); - } - - @Test - void selectClassByNameForSpockSpec() { - var className = "org.example.CalculatorSpec"; - var selector = selectClass(className); - assertEquals(className, selector.getClassName()); - } - - @Test - void selectMethodByClassAndNameForSpockSpec() { - var className = "org.example.CalculatorSpec"; - var methodName = "#a plus #b equals #c"; - - var selector = selectMethod(className, methodName); - assertEquals(className, selector.getClassName()); - assertEquals(methodName, selector.getMethodName()); - assertEquals("", selector.getMethodParameterTypes()); - } - - @Test - void selectClasspathRootsWithNonExistingDirectory() { - var selectors = selectClasspathRoots(Set.of(Paths.get("some", "local", "path"))); - - assertThat(selectors).isEmpty(); - } - - @Test - void selectClasspathRootsWithNonExistingJarFile() { - var selectors = selectClasspathRoots(Set.of(Paths.get("some.jar"))); - - assertThat(selectors).isEmpty(); - } - - @Test - void selectClasspathRootsWithExistingDirectory(@TempDir Path tempDir) { - var selectors = selectClasspathRoots(Set.of(tempDir)); - - assertThat(selectors).extracting(ClasspathRootSelector::getClasspathRoot).containsExactly(tempDir.toUri()); - } - - @Test - void selectClasspathRootsWithExistingJarFile() throws Exception { - var jarUri = getClass().getResource("/jartest.jar").toURI(); - var jarFile = Paths.get(jarUri); - - var selectors = selectClasspathRoots(Set.of(jarFile)); - - assertThat(selectors).extracting(ClasspathRootSelector::getClasspathRoot).containsExactly(jarUri); - } - - @Nested - class NestedClassAndMethodSelectors { - - private final String enclosingClassName = getClass().getName() + "$ClassWithNestedInnerClass"; - private final String nestedClassName = getClass().getName() + "$AbstractClassWithNestedInnerClass$NestedClass"; - private final String doubleNestedClassName = nestedClassName + "$DoubleNestedClass"; - private final String methodName = "nestedTest"; - - @Test - void selectNestedClassByClassNames() { - var selector = selectNestedClass(List.of(enclosingClassName), nestedClassName); - - assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); - assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); - - assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); - assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); - } - - @Test - void selectDoubleNestedClassByClassNames() { - var selector = selectNestedClass(List.of(enclosingClassName, nestedClassName), doubleNestedClassName); - - assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class, - AbstractClassWithNestedInnerClass.NestedClass.class); - assertThat(selector.getNestedClass()).isEqualTo( - AbstractClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class); - - assertThat(selector.getEnclosingClassNames()).containsExactly(enclosingClassName, nestedClassName); - assertThat(selector.getNestedClassName()).isEqualTo(doubleNestedClassName); - } - - @Test - void selectNestedClassPreconditions() { - assertViolatesPrecondition(() -> selectNestedClass(null, "ClassName")); - assertViolatesPrecondition(() -> selectNestedClass(List.of(), "ClassName")); - assertViolatesPrecondition(() -> selectNestedClass(List.of("ClassName"), null)); - assertViolatesPrecondition(() -> selectNestedClass(List.of("ClassName"), "")); - assertViolatesPrecondition(() -> selectNestedClass(List.of("ClassName"), " ")); - } - - @Test - void selectNestedMethodByEnclosingClassNamesAndMethodName() throws Exception { - var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName); - - assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); - assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); - assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName)); - - assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); - assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); - assertThat(selector.getMethodName()).isEqualTo(methodName); - } - - @Test - void selectNestedMethodByEnclosingClassesAndMethodName() throws Exception { - var selector = selectNestedMethod(List.of(ClassWithNestedInnerClass.class), - AbstractClassWithNestedInnerClass.NestedClass.class, methodName); - - assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); - assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); - assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName)); - - assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); - assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); - assertThat(selector.getMethodName()).isEqualTo(methodName); - } - - @Test - void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypes() throws Exception { - var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName, - String.class.getName()); - - assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); - assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class); - assertThat(selector.getMethod()).isEqualTo( - selector.getNestedClass().getDeclaredMethod(methodName, String.class)); - - assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); - assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); - assertThat(selector.getMethodName()).isEqualTo(methodName); - } - - @Test - void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Exception { - var doubleNestedMethodName = "doubleNestedTest"; - var selector = selectNestedMethod(List.of(enclosingClassName, nestedClassName), doubleNestedClassName, - doubleNestedMethodName); - - assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class, - AbstractClassWithNestedInnerClass.NestedClass.class); - assertThat(selector.getNestedClass()).isEqualTo( - AbstractClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class); - assertThat(selector.getMethod()).isEqualTo( - selector.getNestedClass().getDeclaredMethod(doubleNestedMethodName)); - - assertThat(selector.getEnclosingClassNames()).containsExactly(enclosingClassName, nestedClassName); - assertThat(selector.getNestedClassName()).isEqualTo(doubleNestedClassName); - assertThat(selector.getMethodName()).isEqualTo(doubleNestedMethodName); - } - - @Test - void selectNestedMethodPreconditions() { - assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName")); - assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName", "int")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName", "int")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName", "int")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName", "int")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null)); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, "int")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ", "int")); - assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", null)); - } - - abstract class AbstractClassWithNestedInnerClass { - - @Nested - class NestedClass { - - @Test - void nestedTest() { - } - - @Test - void nestedTest(String parameter) { - } - - @Nested - class DoubleNestedClass { - - @Test - void doubleNestedTest() { - } - - } - - } - - } - - class ClassWithNestedInnerClass extends AbstractClassWithNestedInnerClass { - } - - class OtherClassWithNestedInnerClass extends AbstractClassWithNestedInnerClass { - } - - } - - // ------------------------------------------------------------------------- - - private void assertViolatesPrecondition(Executable precondition) { - assertThrows(PreconditionViolationException.class, precondition); - } - - private static String fqmn(Class... params) { - return fqmn(DiscoverySelectorsTests.class, "myTest", params); - } - - private static String fqmn(Class clazz, String methodName, Class... params) { - return ReflectionUtils.getFullyQualifiedMethodName(clazz, methodName, params); - } - - private static String fqmnWithParamNames(String... params) { - return String.format("%s#%s(%s)", DiscoverySelectorsTests.class.getName(), "myTest", join(", ", params)); - } - interface TestInterface { - - @Test - default void myTest() { - } - - } - private static class TestCaseWithDefaultMethod implements TestInterface { - - } - - void myTest() { - } - - void myTest(int num) { - } - - void myTest(int[] nums) { - } - - void myTest(int[][] grid) { - } - - void myTest(int[][][][][] grid) { - } - - void myTest(String info) { - } - - void myTest(String[] info) { - } - - void myTest(String[][] info) { - } - - void myTest(Double[][][][][] data) { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java deleted file mode 100644 index bd5afe94..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.AbstractEqualsAndHashCodeTests; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link FilePosition}. - * - * @since 1.7 - */ -@DisplayName("FilePosition unit tests") -class FilePositionTests extends AbstractEqualsAndHashCodeTests { - - @Test - @DisplayName("factory method preconditions") - void preconditions() { - assertThrows(PreconditionViolationException.class, () -> FilePosition.from(-1)); - assertThrows(PreconditionViolationException.class, () -> FilePosition.from(0, -1)); - } - - @Test - @DisplayName("create FilePosition from factory method with line number") - void filePositionFromLine() { - var filePosition = FilePosition.from(42); - - assertThat(filePosition.getLine()).isEqualTo(42); - assertThat(filePosition.getColumn()).isEmpty(); - } - - @Test - @DisplayName("create FilePosition from factory method with line number and column number") - void filePositionFromLineAndColumn() { - var filePosition = FilePosition.from(42, 99); - - assertThat(filePosition.getLine()).isEqualTo(42); - assertThat(filePosition.getColumn()).contains(99); - } - - /** - * @since 1.3 - */ - @ParameterizedTest - @MethodSource - void filePositionFromQuery(String query, int expectedLine, int expectedColumn) { - var optionalFilePosition = FilePosition.fromQuery(query); - - if (optionalFilePosition.isPresent()) { - var filePosition = optionalFilePosition.get(); - - assertThat(filePosition.getLine()).isEqualTo(expectedLine); - assertThat(filePosition.getColumn().orElse(-1)).isEqualTo(expectedColumn); - } - else { - assertEquals(-1, expectedColumn); - assertEquals(-1, expectedLine); - } - } - - @SuppressWarnings("unused") - static Stream filePositionFromQuery() { - return Stream.of( // - arguments(null, -1, -1), // - arguments("?!", -1, -1), // - arguments("line=ZZ", -1, -1), // - arguments("line=42", 42, -1), // - arguments("line=42&column=99", 42, 99), // - arguments("line=42&column=ZZ", 42, -1), // - arguments("line=42&abc=xyz&column=99", 42, 99), // - arguments("1=3&foo=X&line=42&abc=xyz&column=99&enigma=393939", 42, 99), // - // First one wins: - arguments("line=42&line=555", 42, -1), // - arguments("line=42&line=555&column=99&column=555", 42, 99) // - ); - } - - @Test - @DisplayName("equals() and hashCode() with column number cached by Integer.valueOf()") - void equalsAndHashCode() { - var same = FilePosition.from(42, 99); - var sameSame = FilePosition.from(42, 99); - var different = FilePosition.from(1, 2); - - assertEqualsAndHashCode(same, sameSame, different); - } - - @Test - @DisplayName("equals() and hashCode() with column number not cached by Integer.valueOf()") - void equalsAndHashCodeWithColumnNumberNotCachedByJavaLangIntegerDotValueOf() { - var same = FilePosition.from(42, 99999); - var sameSame = FilePosition.from(42, 99999); - var different = FilePosition.from(1, 2); - - assertEqualsAndHashCode(same, sameSame, different); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java deleted file mode 100644 index 2d4b47d1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; - -/** - * Unit tests for {@link FileSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class FileSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new FileSelector("/example/foo.txt", null); - var selector2 = new FileSelector("/example/foo.txt", null); - var selector3 = new FileSelector("/example/bar.txt", null); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - - @Test - void equalsAndHashCodeWithFilePosition() { - var selector1 = new FileSelector("/example/foo.txt", FilePosition.from(1)); - var selector2 = new FileSelector("/example/foo.txt", FilePosition.from(1)); - var selector3 = new FileSelector("/example/bar.txt", FilePosition.from(2)); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java deleted file mode 100644 index 4f6b5412..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link MethodSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class MethodSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new MethodSelector("TestClass", "method", "int, boolean"); - var selector2 = new MethodSelector("TestClass", "method", "int, boolean"); - - assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "method", "int")); - assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "method")); - assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "X", "int, boolean")); - assertEqualsAndHashCode(selector1, selector2, new MethodSelector("TestClass", "X")); - assertEqualsAndHashCode(selector1, selector2, new MethodSelector("X", "method", "int, boolean")); - assertEqualsAndHashCode(selector1, selector2, new MethodSelector("X", "method")); - } - - @Test - void preservesOriginalExceptionWhenTryingToLoadClass() { - var selector = new MethodSelector("TestClass", "method", "int, boolean"); - - var e = assertThrows(PreconditionViolationException.class, selector::getJavaClass); - - assertThat(e).hasMessage("Could not load class with name: TestClass").hasCauseInstanceOf( - ClassNotFoundException.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java deleted file mode 100644 index a637a6c7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; - -/** - * Unit tests for {@link ModuleSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class ModuleSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new ModuleSelector("foo-api"); - var selector2 = new ModuleSelector("foo-api"); - var selector3 = new ModuleSelector("bar-impl"); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java deleted file mode 100644 index c58b24b5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link NestedClassSelector}. - * - * @since 1.6 - * @see DiscoverySelectorsTests - */ -class NestedClassSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), - "org.example.NestedTestClass"); - var selector2 = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), - "org.example.NestedTestClass"); - var selector3 = new NestedClassSelector(List.of("org.example.X"), "org.example.Y"); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - - @Test - void preservesOriginalExceptionWhenTryingToLoadEnclosingClasses() { - var selector = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), - "org.example.NestedTestClass"); - - var exception = assertThrows(PreconditionViolationException.class, selector::getEnclosingClasses); - - assertThat(exception).hasMessage("Could not load class with name: org.example.EnclosingTestClass") // - .hasCauseInstanceOf(ClassNotFoundException.class); - } - - @Test - void preservesOriginalExceptionWhenTryingToLoadNestedClass() { - var selector = new NestedClassSelector(List.of("org.example.EnclosingTestClass"), - "org.example.NestedTestClass"); - - var exception = assertThrows(PreconditionViolationException.class, selector::getNestedClass); - - assertThat(exception).hasMessage("Could not load class with name: org.example.NestedTestClass") // - .hasCauseInstanceOf(ClassNotFoundException.class); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java deleted file mode 100644 index 2c547172..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link NestedMethodSelector}. - * - * @since 1.6 - * @see DiscoverySelectorsTests - */ -class NestedMethodSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", - "int, boolean"); - var selector2 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", - "int, boolean"); - - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("X"), "NestedTestClass", "method", "int, boolean")); - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("X"), "NestedTestClass", "method")); - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int")); - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method")); - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X", "int, boolean")); - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X")); - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("EnclosingClass"), "X", "method", "int, boolean")); - assertEqualsAndHashCode(selector1, selector2, - new NestedMethodSelector(List.of("EnclosingClass"), "X", "method")); - } - - @Test - void preservesOriginalExceptionWhenTryingToLoadEnclosingClass() { - var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); - - var exception = assertThrows(PreconditionViolationException.class, selector::getEnclosingClasses); - - assertThat(exception).hasMessage("Could not load class with name: EnclosingClass") // - .hasCauseInstanceOf(ClassNotFoundException.class); - } - - @Test - void preservesOriginalExceptionWhenTryingToLoadNestedClass() { - var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); - - var exception = assertThrows(PreconditionViolationException.class, selector::getNestedClass); - - assertThat(exception).hasMessage("Could not load class with name: NestedTestClass") // - .hasCauseInstanceOf(ClassNotFoundException.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java deleted file mode 100644 index 68c20f76..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 1.0 - */ -class PackageNameFilterTests { - - @Test - void includePackageChecksPreconditions() { - assertThatThrownBy(() -> PackageNameFilter.includePackageNames((String[]) null)) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("packageNames array must not be null or empty"); - assertThatThrownBy(() -> PackageNameFilter.includePackageNames(new String[0])) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("packageNames array must not be null or empty"); - assertThatThrownBy(() -> PackageNameFilter.includePackageNames(new String[] { null })) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("packageNames array must not contain null elements"); - } - - @Test - void includePackageWithMultiplePackages() { - var includedPackage1 = "java.lang"; - var includedPackage2 = "java.util"; - var filter = PackageNameFilter.includePackageNames(includedPackage1, includedPackage2); - - assertThat(filter).hasToString( - "IncludePackageNameFilter that includes packages whose names are either equal to or start with one of the following: '" - + includedPackage1 + "' OR '" + includedPackage2 + "'"); - - assertTrue(filter.apply("java.lang.String").included()); - assertTrue(filter.toPredicate().test("java.lang.String")); - assertThat(filter.apply("java.lang.String").getReason()).contains( - "Package name [java.lang.String] matches included name: '" + includedPackage1 + "'"); - - assertTrue(filter.apply("java.util.Collection").included()); - assertTrue(filter.toPredicate().test("java.util.Collection")); - assertThat(filter.apply("java.util.Collection").getReason()).contains( - "Package name [java.util.Collection] matches included name: '" + includedPackage2 + "'"); - - assertTrue(filter.apply("java.util.function.Consumer").included()); - assertTrue(filter.toPredicate().test("java.util.function.Consumer")); - assertThat(filter.apply("java.util.function.Consumer").getReason()).contains( - "Package name [java.util.function.Consumer] matches included name: '" + includedPackage2 + "'"); - - assertFalse(filter.apply("java.time.Instant").included()); - assertFalse(filter.toPredicate().test("java.time.Instant")); - assertThat(filter.apply("java.time.Instant").getReason()).contains( - "Package name [java.time.Instant] does not match any included names: '" + includedPackage1 + "' OR '" - + includedPackage2 + "'"); - - assertFalse(filter.apply("java.language.Test").included()); - assertFalse(filter.toPredicate().test("java.language.Test")); - assertThat(filter.apply("java.language.Test").getReason()).contains( - "Package name [java.language.Test] does not match any included names: '" + includedPackage1 + "' OR '" - + includedPackage2 + "'"); - } - - @Test - void excludePackageChecksPreconditions() { - assertThatThrownBy(() -> PackageNameFilter.excludePackageNames((String[]) null)) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("packageNames must not be null or empty"); - assertThatThrownBy(() -> PackageNameFilter.excludePackageNames(new String[0])) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("packageNames must not be null or empty"); - assertThatThrownBy(() -> PackageNameFilter.excludePackageNames(new String[] { null })) // - .isInstanceOf(PreconditionViolationException.class) // - .hasMessage("packageNames must not contain null elements"); - } - - @Test - void excludePackageWithMultiplePackages() { - var excludedPackage1 = "java.lang"; - var excludedPackage2 = "java.util"; - var filter = PackageNameFilter.excludePackageNames(excludedPackage1, excludedPackage2); - - assertThat(filter).hasToString( - "ExcludePackageNameFilter that excludes packages whose names are either equal to or start with one of the following: '" - + excludedPackage1 + "' OR '" + excludedPackage2 + "'"); - - assertTrue(filter.apply("java.lang.String").excluded()); - assertFalse(filter.toPredicate().test("java.lang.String")); - assertThat(filter.apply("java.lang.String").getReason()).contains( - "Package name [java.lang.String] matches excluded name: '" + excludedPackage1 + "'"); - - assertTrue(filter.apply("java.util.Collection").excluded()); - assertFalse(filter.toPredicate().test("java.util.Collection")); - assertThat(filter.apply("java.util.Collection").getReason()).contains( - "Package name [java.util.Collection] matches excluded name: '" + excludedPackage2 + "'"); - - assertTrue(filter.apply("java.util.function.Consumer").excluded()); - assertFalse(filter.toPredicate().test("java.util.function.Consumer")); - assertThat(filter.apply("java.util.function.Consumer").getReason()).contains( - "Package name [java.util.function.Consumer] matches excluded name: '" + excludedPackage2 + "'"); - - assertTrue(filter.apply("java.time.Instant").included()); - assertTrue(filter.toPredicate().test("java.time.Instant")); - assertThat(filter.apply("java.time.Instant").getReason()).contains( - "Package name [java.time.Instant] does not match any excluded names: '" + excludedPackage1 + "' OR '" - + excludedPackage2 + "'"); - - assertTrue(filter.apply("java.language.Test").included()); - assertTrue(filter.toPredicate().test("java.language.Test")); - assertThat(filter.apply("java.language.Test").getReason()).contains( - "Package name [java.language.Test] does not match any excluded names: '" + excludedPackage1 + "' OR '" - + excludedPackage2 + "'"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java deleted file mode 100644 index 3de77a7b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; - -/** - * Unit tests for {@link PackageSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class PackageSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var selector1 = new PackageSelector("org.example.foo"); - var selector2 = new PackageSelector("org.example.foo"); - var selector3 = new PackageSelector("org.example.bar"); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java deleted file mode 100644 index 13084e5f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; -import org.junit.platform.engine.UniqueId; - -/** - * Unit tests for {@link UniqueIdSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class UniqueIdSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() { - var testEngine = UniqueId.forEngine("test-engine"); - var selector1 = new UniqueIdSelector(testEngine.append("test-class", "org.example.TestClass")); - var selector2 = new UniqueIdSelector(testEngine.append("test-class", "org.example.TestClass")); - var selector3 = new UniqueIdSelector(testEngine.append("test-class", "org.example.FooBar")); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java deleted file mode 100644 index 1405c8e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.discovery; - -import java.net.URI; - -import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; - -/** - * Unit tests for {@link UriSelector}. - * - * @since 1.3 - * @see DiscoverySelectorsTests - */ -class UriSelectorTests extends AbstractEqualsAndHashCodeTests { - - @Test - void equalsAndHashCode() throws Exception { - var selector1 = new UriSelector(new URI("https://junit.org")); - var selector2 = new UriSelector(new URI("https://junit.org")); - var selector3 = new UriSelector(new URI("https://example.org")); - - assertEqualsAndHashCode(selector1, selector2, selector3); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java deleted file mode 100644 index 5875c349..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.config; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Optional; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.ConfigurationParameters; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -/** - * Unit tests for {@link PrefixedConfigurationParameters}. - * - * @since 1.3 - */ -@ExtendWith(MockitoExtension.class) -class PrefixedConfigurationParametersTests { - - @Mock - private ConfigurationParameters delegate; - - @Test - void preconditions() { - assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(null, "example.")); - assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(delegate, null)); - assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(delegate, "")); - assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(delegate, " ")); - } - - @Test - void delegatesGetCalls() { - when(delegate.get(any())).thenReturn(Optional.of("result")); - var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); - - assertThat(parameters.get("qux")).contains("result"); - - verify(delegate).get("foo.bar.qux"); - } - - @Test - void delegatesGetBooleanCalls() { - when(delegate.getBoolean(any())).thenReturn(Optional.of(true)); - var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); - - assertThat(parameters.getBoolean("qux")).contains(true); - - verify(delegate).getBoolean("foo.bar.qux"); - } - - @Test - void delegatesGetWithTransformerCalls() { - when(delegate.get(any(), any())).thenReturn(Optional.of("QUX")); - var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); - - Function transformer = String::toUpperCase; - assertThat(parameters.get("qux", transformer)).contains("QUX"); - - verify(delegate).get("foo.bar.qux", transformer); - } - - @Test - @SuppressWarnings("deprecation") - void delegatesSizeCalls() { - when(delegate.size()).thenReturn(42); - var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); - - assertThat(parameters.size()).isEqualTo(42); - - verify(delegate).size(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java deleted file mode 100644 index abbc0f1e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; - -/** - * Unit tests for {@link AbstractTestDescriptor} and {@link EngineDescriptor}. - * - * @since 1.0 - */ -class AbstractTestDescriptorTests { - - private EngineDescriptor engineDescriptor; - - @BeforeEach - void initTree() { - engineDescriptor = new EngineDescriptor(UniqueId.forEngine("testEngine"), "testEngine"); - var group1 = new GroupDescriptor(UniqueId.root("group", "group1")); - engineDescriptor.addChild(group1); - var group2 = new GroupDescriptor(UniqueId.root("group", "group2")); - engineDescriptor.addChild(group2); - var group11 = new GroupDescriptor(UniqueId.root("group", "group1-1")); - group1.addChild(group11); - - group1.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf1-1"))); - group1.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf1-2"))); - - group2.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf2-1"))); - - group11.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf11-1"))); - } - - @Test - void removeRootFromHierarchyFails() { - var e = assertThrows(JUnitException.class, () -> engineDescriptor.removeFromHierarchy()); - assertTrue(e.toString().contains("cannot remove the root of a hierarchy")); - } - - @Test - void removeFromHierarchyClearsParentFromAllChildren() { - var group = engineDescriptor.getChildren().iterator().next(); - assertSame(engineDescriptor, group.getParent().orElseThrow(Error::new)); - assertTrue(group.getChildren().stream().allMatch(d -> d.getParent().orElseThrow(Error::new) == group)); - - var formerChildren = group.getChildren(); - group.removeFromHierarchy(); - - assertFalse(group.getParent().isPresent()); - assertTrue(group.getChildren().isEmpty()); - assertTrue(formerChildren.stream().noneMatch(d -> d.getParent().isPresent())); - } - - @Test - void setParentToOtherInstance() { - TestDescriptor newEngine = new EngineDescriptor(UniqueId.forEngine("newEngine"), "newEngine"); - var group = engineDescriptor.getChildren().iterator().next(); - assertSame(engineDescriptor, group.getParent().orElseThrow(Error::new)); - group.setParent(newEngine); - assertSame(newEngine, group.getParent().orElseThrow(Error::new)); - } - - @Test - void setParentToNull() { - var group = engineDescriptor.getChildren().iterator().next(); - assertTrue(group.getParent().isPresent()); - group.setParent(null); - assertFalse(group.getParent().isPresent()); - } - - @Test - void visitAllNodes() { - List visited = new ArrayList<>(); - engineDescriptor.accept(visited::add); - - assertEquals(8, visited.size()); - } - - @Test - void pruneLeaf() { - TestDescriptor.Visitor visitor = descriptor -> { - if (descriptor.getUniqueId().equals(UniqueId.root("leaf", "leaf1-1"))) - descriptor.removeFromHierarchy(); - }; - engineDescriptor.accept(visitor); - - List visited = new ArrayList<>(); - engineDescriptor.accept(descriptor -> visited.add(descriptor.getUniqueId())); - - assertEquals(7, visited.size()); - assertTrue(visited.contains(UniqueId.root("group", "group1"))); - assertFalse(visited.contains(UniqueId.root("leaf", "leaf1-1"))); - } - - @Test - void pruneGroup() { - final var countVisited = new AtomicInteger(); - TestDescriptor.Visitor visitor = descriptor -> { - if (descriptor.getUniqueId().equals(UniqueId.root("group", "group1"))) - descriptor.removeFromHierarchy(); - countVisited.incrementAndGet(); - }; - engineDescriptor.accept(visitor); - - assertEquals(4, countVisited.get(), "Children of pruned element are not visited"); - - List visited = new ArrayList<>(); - engineDescriptor.accept(descriptor -> visited.add(descriptor.getUniqueId())); - - assertEquals(3, visited.size()); - assertFalse(visited.contains(UniqueId.root("group", "group1"))); - } - -} - -class GroupDescriptor extends AbstractTestDescriptor { - - GroupDescriptor(UniqueId uniqueId) { - super(uniqueId, "group: " + uniqueId); - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - -} - -class LeafDescriptor extends AbstractTestDescriptor { - - LeafDescriptor(UniqueId uniqueId) { - super(uniqueId, "leaf: " + uniqueId); - } - - @Override - public Type getType() { - return Type.TEST; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java deleted file mode 100644 index 50dc4a1a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; -import org.junit.platform.AbstractEqualsAndHashCodeTests; -import org.junit.platform.engine.TestSource; - -/** - * Abstract base class for unit tests involving {@link TestSource TestSources} - * and {@link FilePosition FilePositions}. - * - * @since 1.0 - */ -abstract class AbstractTestSourceTests extends AbstractEqualsAndHashCodeTests { - - abstract Stream createSerializableInstances() throws Exception; - - @TestFactory - Stream assertToString() throws Exception { - return createSerializableInstances() // - .map(instance -> dynamicTest(instance.toString(), () -> assertToString(instance))); - } - - private void assertToString(Object instance) { - assertNotNull(instance); - assertTrue(instance.toString().startsWith(instance.getClass().getSimpleName())); - } - - @TestFactory - Stream assertSerializable() throws Exception { - return createSerializableInstances() // - .map(instance -> dynamicTest(instance.toString(), () -> assertSerializable(instance))); - } - - private void assertSerializable(T instance) { - try { - Class type = instance.getClass(); - var serialized = serialize(instance); - var deserialized = deserialize(serialized); - - assertTrue(type.isAssignableFrom(deserialized.getClass())); - assertEquals(instance, deserialized); - } - catch (Exception e) { - fail("assertSerializable failed: " + instance, e); - } - } - - private byte[] serialize(Object obj) throws Exception { - var b = new ByteArrayOutputStream(); - var o = new ObjectOutputStream(b); - o.writeObject(obj); - return b.toByteArray(); - } - - private Object deserialize(byte[] bytes) throws Exception { - var b = new ByteArrayInputStream(bytes); - var o = new ObjectInputStream(b); - return o.readObject(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java deleted file mode 100644 index 2d5b6ead..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.Serializable; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link ClassSource}. - * - * @since 1.0 - */ -class ClassSourceTests extends AbstractTestSourceTests { - - @Override - Stream createSerializableInstances() { - return Stream.of( // - ClassSource.from("class.source"), // - ClassSource.from("class.and.position", FilePosition.from(1, 2)), // - ClassSource.from(getClass()), // - ClassSource.from(getClass(), FilePosition.from(1, 2)) // - ); - } - - @Test - void preconditions() { - assertThrows(PreconditionViolationException.class, () -> ClassSource.from((String) null)); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from(" ")); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from((String) null, null)); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from(" ", null)); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from((Class) null)); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from((Class) null, null)); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from((URI) null)); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from(new URI("badscheme:/com.foo.Bar"))); - assertThrows(PreconditionViolationException.class, () -> ClassSource.from(new URI("class:?line=1"))); - } - - @Test - void classSourceFromName() { - var testClassName = "com.unknown.mypackage.ClassByName"; - var source = ClassSource.from(testClassName); - - assertThat(source.getClassName()).isEqualTo(testClassName); - assertThat(source.getPosition()).isEmpty(); - - var exception = assertThrows(PreconditionViolationException.class, source::getJavaClass); - assertThat(exception).hasMessage("Could not load class with name: " + testClassName); - } - - @Test - void classSourceFromNameAndFilePosition() { - var testClassName = "com.unknown.mypackage.ClassByName"; - var position = FilePosition.from(42, 23); - var source = ClassSource.from(testClassName, position); - - assertThat(source.getClassName()).isEqualTo(testClassName); - assertThat(source.getPosition()).isNotEmpty(); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void classSourceFromReference() { - var testClass = getClass(); - var source = ClassSource.from(testClass); - - assertThat(source.getJavaClass()).isEqualTo(testClass); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void classSourceFromReferenceAndFilePosition() { - var testClass = getClass(); - var position = FilePosition.from(42, 23); - var source = ClassSource.from(testClass, position); - - assertThat(source.getJavaClass()).isEqualTo(testClass); - assertThat(source.getPosition()).isNotEmpty(); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void classSourceFromUri() throws URISyntaxException { - var source = ClassSource.from(new URI("class:java.lang.Object")); - - assertThat(source.getJavaClass()).isEqualTo(Object.class); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void classSourceFromUriWithLineNumber() throws URISyntaxException { - var position = FilePosition.from(42); - var source = ClassSource.from(new URI("class:java.lang.Object?line=42")); - - assertThat(source.getJavaClass()).isEqualTo(Object.class); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void classSourceFromUriWithLineAndColumnNumbers() throws URISyntaxException { - var position = FilePosition.from(42, 23); - var source = ClassSource.from(new URI("class:java.lang.Object?line=42&foo=bar&column=23")); - - assertThat(source.getJavaClass()).isEqualTo(Object.class); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void classSourceFromUriWithEmptyQuery() throws URISyntaxException { - var source = ClassSource.from(new URI("class:java.lang.Object?")); - - assertThat(source.getJavaClass()).isEqualTo(Object.class); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void classSourceFromUriWithUnsupportedParametersInQuery() throws URISyntaxException { - var source = ClassSource.from(new URI("class:java.lang.Object?foo=42&bar")); - - assertThat(source.getJavaClass()).isEqualTo(Object.class); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void equalsAndHashCodeForClassSourceFromName() { - var name1 = String.class.getName(); - var name2 = Number.class.getName(); - - assertEqualsAndHashCode(ClassSource.from(name1), ClassSource.from(name1), ClassSource.from(name2)); - } - - @Test - void equalsAndHashCodeForClassSourceFromNameAndFilePosition() { - var name1 = String.class.getName(); - var name2 = Number.class.getName(); - var position1 = FilePosition.from(42, 23); - var position2 = FilePosition.from(1, 2); - - assertEqualsAndHashCode(ClassSource.from(name1, position1), ClassSource.from(name1, position1), - ClassSource.from(name2, position1)); - assertEqualsAndHashCode(ClassSource.from(name1, position1), ClassSource.from(name1, position1), - ClassSource.from(name1, position2)); - } - - @Test - void equalsAndHashCodeForClassSourceFromReference() { - var class1 = String.class; - var class2 = Number.class; - - assertEqualsAndHashCode(ClassSource.from(class1), ClassSource.from(class1), ClassSource.from(class2)); - } - - @Test - void equalsAndHashCodeForClassSourceFromReferenceAndFilePosition() { - var class1 = String.class; - var class2 = Number.class; - var position1 = FilePosition.from(42, 23); - var position2 = FilePosition.from(1, 2); - - assertEqualsAndHashCode(ClassSource.from(class1, position1), ClassSource.from(class1, position1), - ClassSource.from(class2, position1)); - assertEqualsAndHashCode(ClassSource.from(class1, position1), ClassSource.from(class1, position1), - ClassSource.from(class1, position2)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java deleted file mode 100644 index cb1f75bd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; - -import java.net.URI; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link ClasspathResourceSource}. - * - * @since 1.0 - */ -class ClasspathResourceSourceTests extends AbstractTestSourceTests { - - private static final String FOO_RESOURCE = "test/foo.xml"; - private static final String BAR_RESOURCE = "/config/bar.json"; - - private static final URI FOO_RESOURCE_URI = URI.create(CLASSPATH_SCHEME + ":/" + FOO_RESOURCE); - - @Override - Stream createSerializableInstances() { - return Stream.of(ClasspathResourceSource.from(FOO_RESOURCE)); - } - - @Test - void preconditions() { - assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from((String) null)); - assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from("")); - assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from(" ")); - - assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from((URI) null)); - assertThrows(PreconditionViolationException.class, - () -> ClasspathResourceSource.from(URI.create("file:/foo.txt"))); - } - - @Test - void resourceWithoutPosition() { - var source = ClasspathResourceSource.from(FOO_RESOURCE); - - assertThat(source).isNotNull(); - assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void resourceWithLeadingSlashWithoutPosition() { - var source = ClasspathResourceSource.from("/" + FOO_RESOURCE); - - assertThat(source).isNotNull(); - assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void resourceWithPosition() { - var position = FilePosition.from(42, 23); - var source = ClasspathResourceSource.from(FOO_RESOURCE, position); - - assertThat(source).isNotNull(); - assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void resourceFromUriWithoutPosition() { - var source = ClasspathResourceSource.from(FOO_RESOURCE_URI); - - assertThat(source).isNotNull(); - assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void resourceFromUriWithLineNumber() { - var position = FilePosition.from(42); - var uri = URI.create(FOO_RESOURCE_URI + "?line=42"); - var source = ClasspathResourceSource.from(uri); - - assertThat(source).isNotNull(); - assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void resourceFromUriWithLineAndColumnNumbers() { - var position = FilePosition.from(42, 23); - var uri = URI.create(FOO_RESOURCE_URI + "?line=42&foo=bar&column=23"); - var source = ClasspathResourceSource.from(uri); - - assertThat(source).isNotNull(); - assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void equalsAndHashCode() { - assertEqualsAndHashCode(ClasspathResourceSource.from(FOO_RESOURCE), ClasspathResourceSource.from(FOO_RESOURCE), - ClasspathResourceSource.from(BAR_RESOURCE)); - - var position = FilePosition.from(42, 23); - assertEqualsAndHashCode(ClasspathResourceSource.from(FOO_RESOURCE, position), - ClasspathResourceSource.from(FOO_RESOURCE, position), ClasspathResourceSource.from(BAR_RESOURCE, position)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java deleted file mode 100644 index 6984fa14..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link CompositeTestSource}. - * - * @since 1.0 - */ -class CompositeTestSourceTests extends AbstractTestSourceTests { - - @Override - Stream createSerializableInstances() { - var fileSource = FileSource.from(new File("sample.instance")); - var classSource = ClassSource.from(getClass()); - var sources = List.of(fileSource, classSource); - return Stream.of(CompositeTestSource.from(sources)); - } - - @Test - void createCompositeTestSourceFromNullList() { - assertThrows(PreconditionViolationException.class, () -> CompositeTestSource.from(null)); - } - - @Test - void createCompositeTestSourceFromEmptyList() { - assertThrows(PreconditionViolationException.class, () -> CompositeTestSource.from(List.of())); - } - - @Test - void createCompositeTestSourceFromClassAndFileSources() { - var fileSource = FileSource.from(new File("example.test")); - var classSource = ClassSource.from(getClass()); - var sources = new ArrayList<>(List.of(fileSource, classSource)); - var compositeTestSource = CompositeTestSource.from(sources); - - assertThat(compositeTestSource.getSources().size()).isEqualTo(2); - assertThat(compositeTestSource.getSources()).contains(fileSource, classSource); - - // Ensure the supplied sources list was defensively copied. - sources.remove(1); - assertThat(compositeTestSource.getSources().size()).isEqualTo(2); - - // Ensure the returned sources list is immutable. - assertThrows(UnsupportedOperationException.class, () -> compositeTestSource.getSources().add(fileSource)); - } - - @Test - void equalsAndHashCode() { - var sources1 = List.of(ClassSource.from(Number.class)); - var sources2 = List.of(ClassSource.from(String.class)); - assertEqualsAndHashCode(CompositeTestSource.from(sources1), CompositeTestSource.from(sources1), - CompositeTestSource.from(sources2)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java deleted file mode 100644 index bb488afb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.net.URI; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DefaultUriSource}. - * - * @since 1.3 - */ -class DefaultUriSourceTests extends AbstractTestSourceTests { - - @Override - Stream createSerializableInstances() { - return Stream.of(new DefaultUriSource(URI.create("sample://instance"))); - } - - @Test - void nullSourceUriYieldsException() { - assertThrows(PreconditionViolationException.class, () -> new DefaultUriSource(null)); - } - - @Test - void getterReturnsSameUriInstanceAsSuppliedToTheConstructor() throws Exception { - var expected = new URI("foo.txt"); - var actual = new DefaultUriSource(expected).getUri(); - assertSame(expected, actual); - } - - @Test - void equalsAndHashCode() throws Exception { - var uri1 = new URI("foo.txt"); - var uri2 = new URI("bar.txt"); - assertEqualsAndHashCode(new DefaultUriSource(uri1), new DefaultUriSource(uri1), new DefaultUriSource(uri2)); - } - - @Test - void testToString() { - var actual = new DefaultUriSource(URI.create("foo.txt")).toString(); - assertEquals("DefaultUriSource [uri = foo.txt]", actual); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java deleted file mode 100644 index 8531561c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static java.util.stream.Collectors.toCollection; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Tag; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; - -/** - * @since 1.0 - */ -public class DemoClassTestDescriptor extends AbstractTestDescriptor { - - private static final Logger logger = LoggerFactory.getLogger(DemoClassTestDescriptor.class); - - private final Class testClass; - - public DemoClassTestDescriptor(UniqueId uniqueId, Class testClass) { - super(uniqueId, Preconditions.notNull(testClass, "Class must not be null").getSimpleName(), - ClassSource.from(testClass)); - this.testClass = testClass; - } - - @Override - public Set getTags() { - // Copied from org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.getTags(AnnotatedElement) - // @formatter:off - return findRepeatableAnnotations(this.testClass, Tag.class).stream() - .map(Tag::value) - .filter(tag -> { - var isValid = TestTag.isValid(tag); - if (!isValid) { - // TODO [#242] Replace logging with precondition check once we have a proper mechanism for - // handling validation exceptions during the TestEngine discovery phase. - // - // As an alternative to a precondition check here, we could catch any - // PreconditionViolationException thrown by TestTag::create. - logger.warn(() -> String.format( - "Configuration error: invalid tag syntax in @Tag(\"%s\") declaration on [%s]. Tag will be ignored.", - tag, this.testClass)); - } - return isValid; - }) - .map(TestTag::create) - .collect(toCollection(LinkedHashSet::new)); - // @formatter:on - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java deleted file mode 100644 index 9b73b713..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static java.util.stream.Collectors.toCollection; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; - -import java.lang.reflect.Method; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.junit.jupiter.api.Tag; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; - -/** - * @since 1.0 - */ -public class DemoMethodTestDescriptor extends AbstractTestDescriptor { - - private static final Logger logger = LoggerFactory.getLogger(DemoMethodTestDescriptor.class); - - private final Class testClass; - private final Method testMethod; - - public DemoMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod) { - super(uniqueId, - String.format("%s(%s)", Preconditions.notNull(testMethod, "Method must not be null").getName(), - ClassUtils.nullSafeToString(Class::getSimpleName, testMethod.getParameterTypes())), - MethodSource.from(testMethod)); - - this.testClass = Preconditions.notNull(testClass, "Class must not be null"); - this.testMethod = testMethod; - } - - @Override - public Set getTags() { - // Copied from org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.getTags(AnnotatedElement) - // @formatter:off - Set methodTags = findRepeatableAnnotations(this.testMethod, Tag.class).stream() - .map(Tag::value) - .filter(tag -> { - var isValid = TestTag.isValid(tag); - if (!isValid) { - // TODO [#242] Replace logging with precondition check once we have a proper mechanism for - // handling validation exceptions during the TestEngine discovery phase. - // - // As an alternative to a precondition check here, we could catch any - // PreconditionViolationException thrown by TestTag::create. - logger.warn(() -> String.format( - "Configuration error: invalid tag syntax in @Tag(\"%s\") declaration on [%s]. Tag will be ignored.", - tag, this.testMethod)); - } - return isValid; - }) - .map(TestTag::create) - .collect(toCollection(LinkedHashSet::new)); - // @formatter:on - - getParent().ifPresent(parentDescriptor -> methodTags.addAll(parentDescriptor.getTags())); - return methodTags; - } - - public final Class getTestClass() { - return this.testClass; - } - - public final Method getTestMethod() { - return this.testMethod; - } - - @Override - public Type getType() { - return Type.TEST; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java deleted file mode 100644 index 94318468..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link FilePosition}. - * - * @since 1.0 - */ -@DisplayName("FilePosition unit tests") -class FilePositionTests extends AbstractTestSourceTests { - - @Override - Stream createSerializableInstances() { - return Stream.of(FilePosition.from(42, 99)); - } - - @Test - @DisplayName("factory method preconditions") - void preconditions() { - assertThrows(PreconditionViolationException.class, () -> FilePosition.from(-1)); - assertThrows(PreconditionViolationException.class, () -> FilePosition.from(0, -1)); - } - - @Test - @DisplayName("create FilePosition from factory method with line number") - void filePositionFromLine() { - var filePosition = FilePosition.from(42); - - assertThat(filePosition.getLine()).isEqualTo(42); - assertThat(filePosition.getColumn()).isEmpty(); - } - - @Test - @DisplayName("create FilePosition from factory method with line number and column number") - void filePositionFromLineAndColumn() { - var filePosition = FilePosition.from(42, 99); - - assertThat(filePosition.getLine()).isEqualTo(42); - assertThat(filePosition.getColumn()).contains(99); - } - - /** - * @since 1.3 - */ - @ParameterizedTest - @MethodSource - void filePositionFromQuery(String query, int expectedLine, int expectedColumn) { - var optionalFilePosition = FilePosition.fromQuery(query); - - if (optionalFilePosition.isPresent()) { - var filePosition = optionalFilePosition.get(); - - assertThat(filePosition.getLine()).isEqualTo(expectedLine); - assertThat(filePosition.getColumn().orElse(-1)).isEqualTo(expectedColumn); - } - else { - assertEquals(-1, expectedColumn); - assertEquals(-1, expectedLine); - } - } - - @SuppressWarnings("unused") - static Stream filePositionFromQuery() { - return Stream.of( // - arguments(null, -1, -1), // - arguments("?!", -1, -1), // - arguments("line=ZZ", -1, -1), // - arguments("line=42", 42, -1), // - arguments("line=42&column=99", 42, 99), // - arguments("line=42&column=ZZ", 42, -1), // - arguments("line=42&abc=xyz&column=99", 42, 99), // - arguments("1=3&foo=X&line=42&abc=xyz&column=99&enigma=393939", 42, 99), // - // First one wins: - arguments("line=42&line=555", 42, -1), // - arguments("line=42&line=555&column=99&column=555", 42, 99) // - ); - } - - @Test - @DisplayName("equals() and hashCode() with column number cached by Integer.valueOf()") - void equalsAndHashCode() { - var same = FilePosition.from(42, 99); - var sameSame = FilePosition.from(42, 99); - var different = FilePosition.from(1, 2); - - assertEqualsAndHashCode(same, sameSame, different); - } - - @Test - @DisplayName("equals() and hashCode() with column number not cached by Integer.valueOf()") - void equalsAndHashCodeWithColumnNumberNotCachedByJavaLangIntegerDotValueOf() { - var same = FilePosition.from(42, 99999); - var sameSame = FilePosition.from(42, 99999); - var different = FilePosition.from(1, 2); - - assertEqualsAndHashCode(same, sameSame, different); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java deleted file mode 100644 index f89d127a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.File; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link FileSource} and {@link DirectorySource}. - * - * @since 1.0 - */ -class FileSystemSourceTests extends AbstractTestSourceTests { - - @Override - Stream createSerializableInstances() { - return Stream.of( // - FileSource.from(new File("file.source")), // - FileSource.from(new File("file.and.position"), FilePosition.from(42, 23))); - } - - @Test - void nullSourceFileOrDirectoryYieldsException() { - assertThrows(PreconditionViolationException.class, () -> FileSource.from(null)); - } - - @Test - void directory() throws Exception { - var canonicalDir = new File(".").getCanonicalFile(); - var relativeDir = new File("..", canonicalDir.getName()); - - var source = DirectorySource.from(relativeDir); - - assertThat(source.getUri()).isEqualTo(canonicalDir.toURI()); - assertThat(source.getFile()).isEqualTo(canonicalDir); - } - - @Test - void fileWithoutPosition() throws Exception { - var canonicalDir = new File(".").getCanonicalFile(); - var relativeDir = new File("..", canonicalDir.getName()); - var relativeFile = new File(relativeDir, "test.txt"); - var canonicalFile = relativeFile.getCanonicalFile(); - - var source = FileSource.from(relativeFile); - - assertThat(source.getUri()).isEqualTo(canonicalFile.toURI()); - assertThat(source.getFile()).isEqualTo(canonicalFile); - assertThat(source.getPosition()).isEmpty(); - } - - @Test - void fileWithPosition() { - var file = new File("test.txt"); - var position = FilePosition.from(42, 23); - var source = FileSource.from(file, position); - - assertThat(source.getUri()).isEqualTo(file.getAbsoluteFile().toURI()); - assertThat(source.getFile()).isEqualTo(file.getAbsoluteFile()); - assertThat(source.getPosition()).hasValue(position); - } - - @Test - void equalsAndHashCodeForFileSource() { - var file1 = new File("foo.txt"); - var file2 = new File("bar.txt"); - assertEqualsAndHashCode(FileSource.from(file1), FileSource.from(file1), FileSource.from(file2)); - - var position = FilePosition.from(42, 23); - assertEqualsAndHashCode(FileSource.from(file1, position), FileSource.from(file1, position), - FileSource.from(file2, position)); - } - - @Test - void equalsAndHashCodeForDirectorySource() { - var dir1 = new File("."); - var dir2 = new File(".."); - assertEqualsAndHashCode(DirectorySource.from(dir1), DirectorySource.from(dir1), DirectorySource.from(dir2)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java deleted file mode 100644 index 44c7f064..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link MethodSource}. - * - * @since 1.0 - */ -class MethodSourceTests extends AbstractTestSourceTests { - - @Override - Stream createSerializableInstances() throws Exception { - return Stream.of( // - MethodSource.from(getMethod("method1")), // - MethodSource.from(getMethod("method2")) // - ); - } - - @Test - void methodSource() throws Exception { - var testMethod = getMethod("method1"); - var source = MethodSource.from(testMethod); - - assertThat(source.getClassName()).isEqualTo(getClass().getName()); - assertThat(source.getMethodName()).isEqualTo(testMethod.getName()); - assertThat(source.getMethodParameterTypes()).isEqualTo(String.class.getName()); - assertThat(source.getJavaClass()).isEqualTo(getClass()); - assertThat(source.getJavaMethod()).isEqualTo(testMethod); - } - - @Test - void equalsAndHashCodeForMethodSource() throws Exception { - var method1 = getMethod("method1"); - var method2 = getMethod("method2"); - assertEqualsAndHashCode(MethodSource.from(method1), MethodSource.from(method1), MethodSource.from(method2)); - } - - @Test - void instantiatingWithNullNamesShouldThrowPreconditionViolationException() { - assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", null)); - assertThrows(PreconditionViolationException.class, () -> MethodSource.from(null, "foo")); - } - - @Test - void instantiatingWithEmptyNamesShouldThrowPreconditionViolationException() { - assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", "")); - assertThrows(PreconditionViolationException.class, () -> MethodSource.from("", "foo")); - } - - @Test - void instantiatingWithBlankNamesShouldThrowPreconditionViolationException() { - assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", " ")); - assertThrows(PreconditionViolationException.class, () -> MethodSource.from(" ", "foo")); - } - - @Test - void instantiationWithNullMethodShouldThrowPreconditionViolationException() { - assertThrows(PreconditionViolationException.class, () -> MethodSource.from(null)); - } - - @Test - void instantiationWithNullClassOrMethodShouldThrowPreconditionViolationException() { - assertThrows(PreconditionViolationException.class, - () -> MethodSource.from(null, String.class.getDeclaredMethod("getBytes"))); - assertThrows(PreconditionViolationException.class, () -> MethodSource.from(String.class, null)); - } - - @Test - void instantiationWithClassAndMethodShouldResultInACorrectObject() throws Exception { - var source = MethodSource.from(String.class, - String.class.getDeclaredMethod("lastIndexOf", String.class, int.class)); - assertEquals(String.class.getName(), source.getClassName()); - assertEquals("lastIndexOf", source.getMethodName()); - assertEquals("java.lang.String, int", source.getMethodParameterTypes()); - } - - @Test - void instantiationWithClassAndMethodAsStringAndParamsAsClassVarargsShouldResultInACorrectObject() { - var source = MethodSource.from(String.class.getName(), "lastIndexOf", String.class, int.class); - assertEquals(String.class.getName(), source.getClassName()); - assertEquals("lastIndexOf", source.getMethodName()); - assertEquals("java.lang.String, int", source.getMethodParameterTypes()); - } - - @Test - void twoEqualMethodsShouldHaveEqualMethodSourceObjects() { - assertEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass1", "testMethod1")); - } - - @Test - void twoUnequalMethodsShouldHaveUnequalMethodSourceObjects() { - assertNotEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass2", "testMethod1")); - } - - @Test - void twoUnequalMethodsInTheSameClassShouldHaveUnequalMethodSourceObjects() { - assertNotEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass1", "testMethod2")); - } - - @Test - void twoEqualMethodSourceObjectsShouldHaveEqualHashCodes() { - assertEquals(MethodSource.from("TestClass1", "testMethod1").hashCode(), - MethodSource.from("TestClass1", "testMethod1").hashCode()); - } - - @Test - void twoEqualMethodsWithEqualParametersShouldHaveEqualMethodSourceObjects() { - assertEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), - MethodSource.from("TestClass1", "testMethod1", "int, String")); - } - - @Test - void twoUnequalMethodsWithEqualParametersShouldHaveUnequalMethodSourceObjects() { - assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), - MethodSource.from("TestClass1", "testMethod2", "int, String")); - } - - @Test - void twoEqualMethodsWithUnequalParametersShouldHaveUnequalMethodSourceObjects() { - assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), - MethodSource.from("TestClass1", "testMethod1", "float, int, String")); - } - - @Test - void twoEqualMethodsWithEqualParametersShouldHaveEqualMethodSourceHashCodes() { - assertEquals(MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode(), - MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode()); - } - - @Test - void twoEqualMethodsWithUnequalParametersShouldHaveUnequalMethodSourceHashCodes() { - assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode(), - MethodSource.from("TestClass1", "testMethod1", "float, int, String").hashCode()); - } - - @Test - void aReflectedMethodsClassNameShouldBeConsistent() throws Exception { - var m = String.class.getDeclaredMethod("valueOf", int.class); - - assertEquals("java.lang.String", MethodSource.from(m).getClassName()); - } - - @Test - void aReflectedMethodsMethodNameShouldBeConsistent() throws Exception { - var m = String.class.getDeclaredMethod("valueOf", int.class); - - assertEquals("valueOf", MethodSource.from(m).getMethodName()); - } - - @Test - void aReflectedMethodsParameterTypesShouldBeConsistent() throws Exception { - var m = String.class.getDeclaredMethod("valueOf", float.class); - - assertEquals("float", MethodSource.from(m).getMethodParameterTypes()); - } - - @Test - void twoEqualReflectedMethodsShouldHaveEqualMethodSourceObjects() throws Exception { - var m1 = String.class.getDeclaredMethod("valueOf", int.class); - var m2 = String.class.getDeclaredMethod("valueOf", int.class); - - assertEquals(MethodSource.from(m1), MethodSource.from(m2)); - } - - @Test - void twoEqualReflectedMethodsShouldHaveEqualMethodSourceHashCodes() throws Exception { - var m1 = String.class.getDeclaredMethod("valueOf", int.class); - var m2 = String.class.getDeclaredMethod("valueOf", int.class); - - assertEquals(MethodSource.from(m1).hashCode(), MethodSource.from(m2).hashCode()); - } - - @Test - void twoUnequalReflectedMethodsShouldNotHaveEqualMethodSourceObjects() throws Exception { - var m1 = String.class.getDeclaredMethod("valueOf", int.class); - var m2 = Byte.class.getDeclaredMethod("byteValue"); - - assertNotEquals(MethodSource.from(m1), MethodSource.from(m2)); - } - - @Test - void twoUnequalReflectedMethodsShouldNotHaveEqualMethodSourceHashCodes() throws Exception { - var m1 = String.class.getDeclaredMethod("valueOf", int.class); - var m2 = Byte.class.getDeclaredMethod("byteValue"); - - assertNotEquals(MethodSource.from(m1).hashCode(), MethodSource.from(m2).hashCode()); - } - - @Test - void getJavaClassFromString() { - var source = MethodSource.from(getClass().getName(), "method1"); - - assertThat(source.getJavaClass()).isEqualTo(getClass()); - } - - @Test - void getJavaClassShouldThrowExceptionIfClassNotFound() { - var source = MethodSource.from(getClass().getName() + "X", "method1"); - - assertThrows(PreconditionViolationException.class, source::getJavaClass); - } - - @Test - void getJavaMethodShouldReturnGivenMethodIfOverloadExists() throws Exception { - var testMethod = getMethod("method3"); - var source = MethodSource.from(testMethod); - - assertThat(source.getJavaMethod()).isEqualTo(testMethod); - } - - @Test - void getJavaMethodFromStringShouldFindVoidMethod() throws Exception { - var testMethod = getClass().getDeclaredMethod("methodVoid"); - var source = MethodSource.from(getClass().getName(), testMethod.getName()); - - assertThat(source.getJavaMethod()).isEqualTo(testMethod); - } - - @Test - void getJavaMethodFromStringShouldFindMethodWithParameter() throws Exception { - var testMethod = getClass().getDeclaredMethod("method3", Integer.TYPE); - var source = MethodSource.from(getClass().getName(), testMethod.getName(), testMethod.getParameterTypes()); - - assertThat(source.getJavaMethod()).isEqualTo(testMethod); - } - - @Test - void getJavaMethodFromStringShouldThrowExceptionIfParameterTypesAreNotSupplied() { - var source = MethodSource.from(getClass().getName(), "method3"); - - assertThrows(PreconditionViolationException.class, source::getJavaMethod); - } - - @Test - void getJavaMethodFromStringShouldThrowExceptionIfParameterTypesDoNotMatch() { - var source = MethodSource.from(getClass().getName(), "method3", Double.TYPE); - - assertThrows(PreconditionViolationException.class, source::getJavaMethod); - } - - @Test - void getJavaMethodFromStringShouldThrowExceptionIfMethodDoesNotExist() { - var source = MethodSource.from(getClass().getName(), "methodX"); - - assertThrows(PreconditionViolationException.class, source::getJavaMethod); - } - - private Method getMethod(String name) throws Exception { - return getClass().getDeclaredMethod(name, String.class); - } - - @SuppressWarnings("unused") - void method1(String text) { - } - - @SuppressWarnings("unused") - void method2(String text) { - } - - @SuppressWarnings("unused") - void method3(String text) { - } - - @SuppressWarnings("unused") - void method3(int number) { - } - - @SuppressWarnings("unused") - void methodVoid() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java deleted file mode 100644 index b7217e6d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.Serializable; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link PackageSource}. - * - * @since 1.0 - */ -class PackageSourceTests extends AbstractTestSourceTests { - - @Override - Stream createSerializableInstances() { - return Stream.of(PackageSource.from("package.source")); - } - - @Test - void packageSourceFromNullPackageName() { - assertThrows(PreconditionViolationException.class, () -> PackageSource.from((String) null)); - } - - @Test - void packageSourceFromEmptyPackageName() { - assertThrows(PreconditionViolationException.class, () -> PackageSource.from(" ")); - } - - @Test - void packageSourceFromNullPackageReference() { - assertThrows(PreconditionViolationException.class, () -> PackageSource.from((Package) null)); - } - - @Test - void packageSourceFromPackageName() { - var testPackage = getClass().getPackage().getName(); - var source = PackageSource.from(testPackage); - - assertThat(source.getPackageName()).isEqualTo(testPackage); - } - - @Test - void packageSourceFromPackageReference() { - var testPackage = getClass().getPackage(); - var source = PackageSource.from(testPackage); - - assertThat(source.getPackageName()).isEqualTo(testPackage.getName()); - } - - @Test - void equalsAndHashCodeForPackageSource() { - var pkg1 = getClass().getPackage(); - var pkg2 = String.class.getPackage(); - assertEqualsAndHashCode(PackageSource.from(pkg1), PackageSource.from(pkg1), PackageSource.from(pkg2)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java deleted file mode 100644 index 8f1ddee3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.locks.Lock; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - -/** - * @since 1.3 - */ -class CompositeLockTests { - - @Test - @SuppressWarnings("resource") - void acquiresAllLocksInOrder() throws Exception { - var lock1 = mock(Lock.class); - var lock2 = mock(Lock.class); - - new CompositeLock(List.of(lock1, lock2)).acquire(); - - var inOrder = inOrder(lock1, lock2); - inOrder.verify(lock1).lockInterruptibly(); - inOrder.verify(lock2).lockInterruptibly(); - } - - @Test - @SuppressWarnings("resource") - void releasesAllLocksInReverseOrder() throws Exception { - var lock1 = mock(Lock.class); - var lock2 = mock(Lock.class); - - new CompositeLock(List.of(lock1, lock2)).acquire().close(); - - var inOrder = inOrder(lock1, lock2); - inOrder.verify(lock2).unlock(); - inOrder.verify(lock1).unlock(); - } - - @Test - @SuppressWarnings("resource") - void releasesLocksInReverseOrderWhenInterruptedDuringAcquire() throws Exception { - var firstTwoLocksWereLocked = new CountDownLatch(2); - var firstLock = mockLock("firstLock", firstTwoLocksWereLocked::countDown); - var secondLock = mockLock("secondLock", firstTwoLocksWereLocked::countDown); - var unavailableLock = mockLock("unavailableLock", new CountDownLatch(1)::await); - - var thread = new Thread(() -> { - try { - new CompositeLock(List.of(firstLock, secondLock, unavailableLock)).acquire(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }); - thread.start(); - firstTwoLocksWereLocked.await(); - thread.interrupt(); - thread.join(); - - var inOrder = inOrder(firstLock, secondLock); - inOrder.verify(secondLock).unlock(); - inOrder.verify(firstLock).unlock(); - verify(unavailableLock, never()).unlock(); - } - - private Lock mockLock(String name, Executable lockAction) throws InterruptedException { - var lock = mock(Lock.class, name); - doAnswer(invocation -> { - lockAction.execute(); - return null; - }).when(lock).lockInterruptibly(); - return lock; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java deleted file mode 100644 index b6cb6422..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Optional; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.engine.ConfigurationParameters; - -/** - * @since 1.3 - */ -class DefaultParallelExecutionConfigurationStrategyTests { - - private ConfigurationParameters configParams = mock(); - - @BeforeEach - void setUp() { - when(configParams.get(any(), any())).thenCallRealMethod(); - } - - @Test - void fixedStrategyCreatesValidConfiguration() { - when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("42")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; - var configuration = strategy.createConfiguration(configParams); - - assertThat(configuration.getParallelism()).isEqualTo(42); - assertThat(configuration.getCorePoolSize()).isEqualTo(42); - assertThat(configuration.getMinimumRunnable()).isEqualTo(42); - assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + 42); - assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); - assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); - } - - @Test - void fixedSaturateStrategyCreatesValidConfiguration() { - when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("42")); - when(configParams.get("fixed.max-pool-size")).thenReturn(Optional.of("42")); - when(configParams.get("fixed.saturate")).thenReturn(Optional.of("false")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; - var configuration = strategy.createConfiguration(configParams); - assertThat(configuration.getParallelism()).isEqualTo(42); - assertThat(configuration.getMaxPoolSize()).isEqualTo(42); - assertThat(configuration.getSaturatePredicate().test(null)).isFalse(); - } - - @Test - void dynamicStrategyCreatesValidConfiguration() { - when(configParams.get("dynamic.factor")).thenReturn(Optional.of("2.0")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; - var configuration = strategy.createConfiguration(configParams); - - var availableProcessors = Runtime.getRuntime().availableProcessors(); - assertThat(configuration.getParallelism()).isEqualTo(availableProcessors * 2); - assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors * 2); - assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2); - assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + (availableProcessors * 2)); - assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); - assertThat(configuration.getSaturatePredicate()).isNull(); - } - - @Test - void customStrategyCreatesValidConfiguration() { - when(configParams.get("custom.class")).thenReturn( - Optional.of(CustomParallelExecutionConfigurationStrategy.class.getName())); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; - var configuration = strategy.createConfiguration(configParams); - - assertThat(configuration.getParallelism()).isEqualTo(1); - assertThat(configuration.getCorePoolSize()).isEqualTo(4); - assertThat(configuration.getMinimumRunnable()).isEqualTo(2); - assertThat(configuration.getMaxPoolSize()).isEqualTo(3); - assertThat(configuration.getKeepAliveSeconds()).isEqualTo(5); - assertThat(configuration.getSaturatePredicate()).isNotNull(); - assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); - } - - @ParameterizedTest - @EnumSource - void createsStrategyFromConfigParam(DefaultParallelExecutionConfigurationStrategy strategy) { - when(configParams.get("strategy")).thenReturn(Optional.of(strategy.name().toLowerCase())); - - assertThat(DefaultParallelExecutionConfigurationStrategy.getStrategy(configParams)).isSameAs(strategy); - } - - @Test - void fixedStrategyThrowsExceptionWhenPropertyIsNotPresent() { - when(configParams.get("fixed.parallelism")).thenReturn(Optional.empty()); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; - assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); - } - - @Test - void fixedStrategyThrowsExceptionWhenPropertyIsNotAnInteger() { - when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("foo")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; - assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); - } - - @Test - void dynamicStrategyUsesDefaultWhenPropertyIsNotPresent() { - when(configParams.get("dynamic.factor")).thenReturn(Optional.empty()); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; - var configuration = strategy.createConfiguration(configParams); - - var availableProcessors = Runtime.getRuntime().availableProcessors(); - assertThat(configuration.getParallelism()).isEqualTo(availableProcessors); - assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors); - assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors); - assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + availableProcessors); - assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); - } - - @Test - void dynamicStrategyThrowsExceptionWhenPropertyIsNotAnInteger() { - when(configParams.get("dynamic.factor")).thenReturn(Optional.of("foo")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; - assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); - } - - @Test - void dynamicStrategyThrowsExceptionWhenFactorIsZero() { - when(configParams.get("dynamic.factor")).thenReturn(Optional.of("0")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; - assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); - } - - @Test - void dynamicStrategyThrowsExceptionWhenFactorIsNegative() { - when(configParams.get("dynamic.factor")).thenReturn(Optional.of("-1")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; - assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); - } - - @Test - void dynamicStrategyUsesAtLeastParallelismOfOneWhenPropertyIsTooSmall() { - when(configParams.get("dynamic.factor")).thenReturn(Optional.of("0.00000000001")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; - var configuration = strategy.createConfiguration(configParams); - - assertThat(configuration.getParallelism()).isEqualTo(1); - assertThat(configuration.getCorePoolSize()).isEqualTo(1); - assertThat(configuration.getMinimumRunnable()).isEqualTo(1); - assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + 1); - assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); - } - - @Test - void customStrategyThrowsExceptionWhenPropertyIsNotPresent() { - when(configParams.get("custom.class")).thenReturn(Optional.empty()); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; - assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); - } - - @Test - void customStrategyThrowsExceptionWhenClassDoesNotExist() { - when(configParams.get("custom.class")).thenReturn(Optional.of("com.acme.ClassDoesNotExist")); - - ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; - assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); - } - - static class CustomParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy { - @Override - public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { - return new DefaultParallelExecutionConfiguration(1, 2, 3, 4, 5, __ -> true); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java deleted file mode 100644 index 868386f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.platform.commons.JUnitException; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class ForkJoinPoolHierarchicalTestExecutorServiceTests { - - @Mock - ParallelExecutionConfiguration configuration; - - @Test - void exceptionsFromInvalidConfigurationAreNotSwallowed() { - when(configuration.getParallelism()).thenReturn(2); - when(configuration.getMaxPoolSize()).thenReturn(1); // invalid, should be > parallelism - when(configuration.getCorePoolSize()).thenReturn(1); - when(configuration.getMinimumRunnable()).thenReturn(1); - when(configuration.getSaturatePredicate()).thenReturn(__ -> true); - when(configuration.getKeepAliveSeconds()).thenReturn(0); - - JUnitException exception = assertThrows(JUnitException.class, - () -> new ForkJoinPoolHierarchicalTestExecutorService(configuration)); - assertThat(exception).hasMessage("Failed to create ForkJoinPool"); - assertThat(exception).rootCause().isInstanceOf(IllegalArgumentException.class); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java deleted file mode 100644 index 80d46102..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java +++ /dev/null @@ -1,753 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.function.ThrowingConsumer; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; -import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; -import org.junit.platform.launcher.core.ConfigurationParametersFactoryForTests; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.stubbing.Answer; -import org.opentest4j.TestAbortedException; - -/** - * Micro-tests that verify behavior of {@link HierarchicalTestExecutor}. - * - * @since 1.0 - */ -@ExtendWith(MockitoExtension.class) -class HierarchicalTestExecutorTests { - - @Spy - MyContainer root = new MyContainer(UniqueId.root("container", "root")); - - @Mock - EngineExecutionListener listener; - - MyEngineExecutionContext rootContext = new MyEngineExecutionContext(); - HierarchicalTestExecutor executor; - - @BeforeEach - void init() { - executor = createExecutor(new SameThreadHierarchicalTestExecutorService()); - } - - private HierarchicalTestExecutor createExecutor( - HierarchicalTestExecutorService executorService) { - var request = new ExecutionRequest(root, listener, null); - return new HierarchicalTestExecutor<>(request, rootContext, executorService, - OpenTest4JAwareThrowableCollector::new); - } - - @Test - void emptyRootDescriptor() throws Exception { - - var inOrder = inOrder(listener, root); - - executor.execute(); - - var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(root).prepare(rootContext); - inOrder.verify(root).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).before(rootContext); - inOrder.verify(root).after(rootContext); - inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); - - assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); - } - - @Test - void rootDescriptorWithOneChildContainer() throws Exception { - - var child = spy(new MyContainer(UniqueId.root("container", "child container"))); - root.addChild(child); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(child).prepare(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(child).before(rootContext); - inOrder.verify(child).after(rootContext); - inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); - } - - @Test - void rootDescriptorWithOneChildLeaf() throws Exception { - - var child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); - root.addChild(child); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(child).prepare(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(child).execute(eq(rootContext), any()); - inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - assertThat(aTestExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); - } - - @Test - void skippingAContainer() throws Exception { - - var child = spy(new MyContainer(UniqueId.root("container", "child container"))); - when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); - root.addChild(child); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - inOrder.verify(listener).executionStarted(root); - inOrder.verify(child).prepare(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(child).cleanUp(rootContext); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - verify(listener, never()).executionStarted(child); - verify(child, never()).execute(any(), any()); - verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); - } - - @Test - void skippingALeaf() throws Exception { - - var child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); - when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); - root.addChild(child); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - inOrder.verify(listener).executionStarted(root); - inOrder.verify(child).prepare(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(child).cleanUp(rootContext); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - verify(listener, never()).executionStarted(child); - verify(child, never()).execute(any(), any()); - verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); - } - - @Test - void exceptionInShouldBeSkipped() throws Exception { - - var child = spy(new MyContainer(UniqueId.root("container", "child container"))); - var anException = new RuntimeException("in skip"); - when(child.shouldBeSkipped(rootContext)).thenThrow(anException); - root.addChild(child); - - var inOrder = inOrder(listener, child); - - executor.execute(); - - var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(child).prepare(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(child).cleanUp(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - verify(child, never()).execute(any(), any()); - - assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anException); - } - - @Test - void exceptionInContainerBeforeAll() throws Exception { - - var child = spy(new MyContainer(UniqueId.root("container", "child container"))); - root.addChild(child); - var anException = new RuntimeException("in test"); - when(root.before(rootContext)).thenThrow(anException); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(root).prepare(rootContext); - inOrder.verify(root).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).before(rootContext); - inOrder.verify(root).after(rootContext); - inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); - - assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(anException); - - verify(child, never()).execute(any(), any()); - } - - @Test - void exceptionInContainerAfterAllAndCleanUp() throws Exception { - - var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); - root.addChild(child); - var afterException = new RuntimeException("in after()"); - doThrow(afterException).when(root).after(rootContext); - var cleanUpException = new RuntimeException("in cleanUp()"); - doThrow(cleanUpException).when(root).cleanUp(rootContext); - - executor.execute(); - - var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - var inOrder = inOrder(listener, root, child); - inOrder.verify(root).prepare(rootContext); - inOrder.verify(root).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).before(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(child).execute(eq(rootContext), any()); - inOrder.verify(listener).executionFinished(eq(child), any(TestExecutionResult.class)); - inOrder.verify(root).after(rootContext); - inOrder.verify(root).cleanUp(rootContext); - inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); - inOrder.verifyNoMoreInteractions(); - - assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(afterException); - assertThat(afterException.getSuppressed()).containsExactly(cleanUpException); - } - - @Test - void exceptionInPrepare() throws Exception { - var prepareException = new RuntimeException("in prepare()"); - doThrow(prepareException).when(root).prepare(rootContext); - - executor.execute(); - - var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - var inOrder = inOrder(listener, root); - inOrder.verify(root).prepare(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); - inOrder.verifyNoMoreInteractions(); - - assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(prepareException); - assertThat(prepareException.getSuppressed()).isEmpty(); - } - - @Test - void exceptionInCleanUp() throws Exception { - var cleanUpException = new RuntimeException("in cleanUp()"); - doThrow(cleanUpException).when(root).cleanUp(rootContext); - - executor.execute(); - - var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - var inOrder = inOrder(listener, root); - inOrder.verify(root).prepare(rootContext); - inOrder.verify(root).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).execute(eq(rootContext), any()); - inOrder.verify(root).after(rootContext); - inOrder.verify(root).cleanUp(rootContext); - inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); - inOrder.verifyNoMoreInteractions(); - - assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(cleanUpException); - assertThat(cleanUpException.getSuppressed()).isEmpty(); - } - - @Test - void exceptionInShouldBeSkippedAndCleanUp() throws Exception { - var shouldBeSkippedException = new RuntimeException("in prepare()"); - doThrow(shouldBeSkippedException).when(root).shouldBeSkipped(rootContext); - var cleanUpException = new RuntimeException("in cleanUp()"); - doThrow(cleanUpException).when(root).cleanUp(rootContext); - - executor.execute(); - - var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - var inOrder = inOrder(listener, root); - inOrder.verify(root).prepare(rootContext); - inOrder.verify(root).shouldBeSkipped(rootContext); - inOrder.verify(root).cleanUp(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); - inOrder.verifyNoMoreInteractions(); - - assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(shouldBeSkippedException); - assertThat(shouldBeSkippedException.getSuppressed()).containsExactly(cleanUpException); - } - - @Test - void exceptionInLeafExecute() throws Exception { - - var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); - var anException = new RuntimeException("in test"); - when(child.execute(eq(rootContext), any())).thenThrow(anException); - root.addChild(child); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).before(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(child).execute(eq(rootContext), any()); - inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); - inOrder.verify(root).after(rootContext); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anException); - } - - @Test - void abortInRootBeforeAll() throws Exception { - - var child = spy(new MyContainer(UniqueId.root("container", "child container"))); - root.addChild(child); - var anAbortedException = new TestAbortedException("in BeforeAll"); - when(root.before(rootContext)).thenThrow(anAbortedException); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(root).prepare(rootContext); - inOrder.verify(root).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).before(rootContext); - inOrder.verify(root).after(rootContext); - inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); - - assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); - assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); - - verify(child, never()).execute(any(), any()); - } - - @Test - void abortInChildContainerBeforeAll() throws Exception { - - var child = spy(new MyContainer(UniqueId.root("container", "child container"))); - root.addChild(child); - var anAbortedException = new TestAbortedException("in BeforeAll"); - when(child.before(rootContext)).thenThrow(anAbortedException); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - - inOrder.verify(root).prepare(rootContext); - inOrder.verify(root).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).before(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(child).before(rootContext); - inOrder.verify(child).after(rootContext); - inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); - inOrder.verify(root).after(rootContext); - - assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); - assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); - - verify(child, never()).execute(any(), any()); - } - - @Test - void abortInLeafExecute() throws Exception { - - var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); - var anAbortedException = new TestAbortedException("in test"); - when(child.execute(eq(rootContext), any())).thenThrow(anAbortedException); - root.addChild(child); - - var inOrder = inOrder(listener, root, child); - - executor.execute(); - - var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(root).before(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(child).execute(eq(rootContext), any()); - inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); - inOrder.verify(root).after(rootContext); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); - assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); - } - - @Test - void executesDynamicTestDescriptors() throws Exception { - - var leafUniqueId = UniqueId.root("leaf", "child leaf"); - var child = spy(new MyLeaf(leafUniqueId)); - var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); - - when(child.execute(any(), any())).thenAnswer(execute(dynamicTestDescriptor)); - root.addChild(child); - - var inOrder = inOrder(listener, root, child, dynamicTestDescriptor); - - executor.execute(); - - var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(listener).executionStarted(root); - inOrder.verify(child).prepare(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(child).execute(eq(rootContext), any()); - inOrder.verify(listener).dynamicTestRegistered(dynamicTestDescriptor); - inOrder.verify(dynamicTestDescriptor).prepare(rootContext); - inOrder.verify(dynamicTestDescriptor).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(dynamicTestDescriptor); - inOrder.verify(dynamicTestDescriptor).execute(eq(rootContext), any()); - inOrder.verify(listener).executionFinished(eq(dynamicTestDescriptor), aTestExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - assertThat(aTestExecutionResult.getAllValues()).extracting(TestExecutionResult::getStatus).containsExactly( - SUCCESSFUL, SUCCESSFUL); - } - - @Test - void executesDynamicTestDescriptorsUsingContainerAndTestType() throws Exception { - - var child = spy(new MyContainerAndTestTestCase(root.getUniqueId().append("c&t", "child"))); - var dynamicContainerAndTest = spy( - new MyContainerAndTestTestCase(child.getUniqueId().append("c&t", "dynamicContainerAndTest"))); - var dynamicLeaf = spy(new MyLeaf(dynamicContainerAndTest.getUniqueId().append("test", "dynamicLeaf"))); - - root.addChild(child); - when(child.execute(any(), any())).thenAnswer(execute(dynamicContainerAndTest)); - when(dynamicContainerAndTest.execute(any(), any())).thenAnswer(execute(dynamicLeaf)); - when(dynamicLeaf.execute(any(), any())).thenAnswer(invocation -> { - throw new AssertionError("test fails"); - }); - - var inOrder = inOrder(listener, root, child, dynamicContainerAndTest, dynamicLeaf); - - executor.execute(); - - var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(listener).executionStarted(root); - - inOrder.verify(child).prepare(rootContext); - inOrder.verify(child).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(child); - inOrder.verify(child).execute(eq(rootContext), any()); - - inOrder.verify(listener).dynamicTestRegistered(dynamicContainerAndTest); - inOrder.verify(dynamicContainerAndTest).prepare(rootContext); - inOrder.verify(dynamicContainerAndTest).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(dynamicContainerAndTest); - inOrder.verify(dynamicContainerAndTest).execute(eq(rootContext), any()); - - inOrder.verify(listener).dynamicTestRegistered(dynamicLeaf); - inOrder.verify(dynamicLeaf).prepare(rootContext); - inOrder.verify(dynamicLeaf).shouldBeSkipped(rootContext); - inOrder.verify(listener).executionStarted(dynamicLeaf); - inOrder.verify(dynamicLeaf).execute(eq(rootContext), any()); - - inOrder.verify(listener).executionFinished(eq(dynamicLeaf), aTestExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(dynamicContainerAndTest), aTestExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); - inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); - - assertThat(aTestExecutionResult.getAllValues()).extracting(TestExecutionResult::getStatus).containsExactly( - FAILED, SUCCESSFUL, SUCCESSFUL); - } - - @Test - void executesDynamicTestDescriptorsWithCustomListener() { - - var leafUniqueId = UniqueId.root("leaf", "child leaf"); - var child = spy(new MyLeaf(leafUniqueId)); - var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); - root.addChild(child); - - var anotherListener = mock(EngineExecutionListener.class); - when(child.execute(any(), any())).thenAnswer( - useDynamicTestExecutor(executor -> executor.execute(dynamicTestDescriptor, anotherListener))); - - executor.execute(); - - var inOrder = inOrder(listener, anotherListener, root, child, dynamicTestDescriptor); - inOrder.verify(anotherListener).dynamicTestRegistered(dynamicTestDescriptor); - inOrder.verify(anotherListener).executionStarted(dynamicTestDescriptor); - inOrder.verify(dynamicTestDescriptor).execute(eq(rootContext), any()); - inOrder.verify(dynamicTestDescriptor).nodeFinished(rootContext, dynamicTestDescriptor, successful()); - inOrder.verify(anotherListener).executionFinished(dynamicTestDescriptor, successful()); - } - - @Test - void canAbortExecutionOfDynamicChild() throws Exception { - - var leafUniqueId = UniqueId.root("leaf", "child leaf"); - var child = spy(new MyLeaf(leafUniqueId)); - var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); - root.addChild(child); - - var startedLatch = new CountDownLatch(1); - var interrupted = new CompletableFuture(); - - when(child.execute(any(), any())).thenAnswer(useDynamicTestExecutor(executor -> { - var future = executor.execute(dynamicTestDescriptor, EngineExecutionListener.NOOP); - startedLatch.await(); - future.cancel(true); - executor.awaitFinished(); - })); - when(dynamicTestDescriptor.execute(any(), any())).thenAnswer(invocation -> { - startedLatch.countDown(); - try { - new CountDownLatch(1).await(); // block until interrupted - interrupted.complete(false); - return null; - } - catch (InterruptedException e) { - interrupted.complete(true); - throw e; - } - }); - - var parameters = ConfigurationParametersFactoryForTests.create(Map.of(// - DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME, "fixed", // - DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, "2")); - - try (var executorService = new ForkJoinPoolHierarchicalTestExecutorService(parameters)) { - createExecutor(executorService).execute().get(); - } - - verify(listener).executionFinished(child, successful()); - assertTrue(interrupted.get(), "dynamic node was interrupted"); - } - - private Answer execute(TestDescriptor dynamicChild) { - return useDynamicTestExecutor(executor -> executor.execute(dynamicChild)); - } - - private Answer useDynamicTestExecutor(ThrowingConsumer action) { - return invocation -> { - DynamicTestExecutor dynamicTestExecutor = invocation.getArgument(1); - action.accept(dynamicTestExecutor); - return invocation.getArgument(0); - }; - } - - /** - * Verifies support for unrecoverable exceptions. - */ - @Test - void outOfMemoryErrorInShouldBeSkipped() throws Exception { - var child = spy(new MyContainer(UniqueId.root("container", "child container"))); - var outOfMemoryError = new OutOfMemoryError("in skip"); - when(child.shouldBeSkipped(rootContext)).thenThrow(outOfMemoryError); - root.addChild(child); - - Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); - assertSame(outOfMemoryError, actualException); - } - - /** - * Verifies support for unrecoverable exceptions. - */ - @Test - void outOfMemoryErrorInLeafExecution() { - var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); - var outOfMemoryError = new OutOfMemoryError("in test"); - when(child.execute(eq(rootContext), any())).thenThrow(outOfMemoryError); - root.addChild(child); - - Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); - assertSame(outOfMemoryError, actualException); - } - - @Test - void exceptionInAfterDoesNotHideEarlierException() throws Exception { - - var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); - Exception exceptionInExecute = new RuntimeException("execute"); - Exception exceptionInAfter = new RuntimeException("after"); - doThrow(exceptionInExecute).when(child).execute(eq(rootContext), any()); - doThrow(exceptionInAfter).when(child).after(eq(rootContext)); - root.addChild(child); - - var inOrder = inOrder(listener, child); - - executor.execute(); - - var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(child).execute(eq(rootContext), any()); - inOrder.verify(child).after(eq(rootContext)); - inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); - - assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(childExecutionResult.getValue().getThrowable().get()).isSameAs( - exceptionInExecute).hasSuppressedException(exceptionInAfter); - } - - @Test - void dynamicTestDescriptorsMustNotDeclareExclusiveResources() { - - var leafUniqueId = UniqueId.root("leaf", "child leaf"); - var child = spy(new MyLeaf(leafUniqueId)); - var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); - when(dynamicTestDescriptor.getExclusiveResources()).thenReturn( - Set.of(new ExclusiveResource("foo", LockMode.READ))); - - when(child.execute(any(), any())).thenAnswer(execute(dynamicTestDescriptor)); - root.addChild(child); - - executor.execute(); - - var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - verify(listener).executionStarted(dynamicTestDescriptor); - verify(listener).executionFinished(eq(dynamicTestDescriptor), aTestExecutionResult.capture()); - - var executionResult = aTestExecutionResult.getValue(); - assertThat(executionResult.getStatus()).isEqualTo(FAILED); - assertThat(executionResult.getThrowable()).isPresent(); - assertThat(executionResult.getThrowable().get()).hasMessageContaining( - "Dynamic test descriptors must not declare exclusive resources"); - } - - @Test - void exceptionInAfterIsReportedInsteadOfEarlierTestAbortedException() throws Exception { - - var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); - Exception exceptionInExecute = new TestAbortedException("execute"); - Exception exceptionInAfter = new RuntimeException("after"); - doThrow(exceptionInExecute).when(child).execute(eq(rootContext), any()); - doThrow(exceptionInAfter).when(child).after(eq(rootContext)); - root.addChild(child); - - var inOrder = inOrder(listener, child); - - executor.execute(); - - var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - inOrder.verify(child).execute(eq(rootContext), any()); - inOrder.verify(child).after(eq(rootContext)); - inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); - - assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); - assertThat(childExecutionResult.getValue().getThrowable().get()).isSameAs( - exceptionInAfter).hasSuppressedException(exceptionInExecute); - } - - // ------------------------------------------------------------------- - - private static class MyEngineExecutionContext implements EngineExecutionContext { - } - - private static class MyContainer extends AbstractTestDescriptor implements Node { - - MyContainer(UniqueId uniqueId) { - super(uniqueId, uniqueId.toString()); - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - } - - private static class MyLeaf extends AbstractTestDescriptor implements Node { - - MyLeaf(UniqueId uniqueId) { - super(uniqueId, uniqueId.toString()); - } - - @Override - public MyEngineExecutionContext execute(MyEngineExecutionContext context, - DynamicTestExecutor dynamicTestExecutor) { - return context; - } - - @Override - public Type getType() { - return Type.TEST; - } - } - - private static class MyContainerAndTestTestCase extends AbstractTestDescriptor - implements Node { - - MyContainerAndTestTestCase(UniqueId uniqueId) { - super(uniqueId, uniqueId.toString()); - } - - @Override - public Type getType() { - return Type.CONTAINER_AND_TEST; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java deleted file mode 100644 index 342978f0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE; - -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; - -/** - * @since 1.3 - */ -class LockManagerTests { - - private LockManager lockManager = new LockManager(); - - @Test - void returnsNopLockWithoutExclusiveResources() { - Collection resources = Set.of(); - - var locks = getLocks(resources, NopLock.class); - - assertThat(locks).isEmpty(); - } - - @Test - void returnsSingleLockForSingleExclusiveResource() { - Collection resources = Set.of(new ExclusiveResource("foo", READ)); - - var locks = getLocks(resources, SingleLock.class); - - assertThat(locks).hasSize(1); - assertThat(locks.get(0)).isInstanceOf(ReadLock.class); - } - - @Test - void returnsCompositeLockForMultipleDifferentExclusiveResources() { - Collection resources = List.of( // - new ExclusiveResource("a", READ), // - new ExclusiveResource("b", READ_WRITE)); - - var locks = getLocks(resources, CompositeLock.class); - - assertThat(locks).hasSize(2); - assertThat(locks.get(0)).isInstanceOf(ReadLock.class); - assertThat(locks.get(1)).isInstanceOf(WriteLock.class); - } - - @Test - void reusesSameLockForExclusiveResourceWithSameKey() { - Collection resources = Set.of(new ExclusiveResource("foo", READ)); - - var locks1 = getLocks(resources, SingleLock.class); - var locks2 = getLocks(resources, SingleLock.class); - - assertThat(locks1).hasSize(1); - assertThat(locks2).hasSize(1); - assertThat(locks1.get(0)).isSameAs(locks2.get(0)); - } - - @Test - void returnsWriteLockForExclusiveResourceWithBothLockModes() { - Collection resources = List.of( // - new ExclusiveResource("bar", READ), // - new ExclusiveResource("foo", READ), // - new ExclusiveResource("foo", READ_WRITE), // - new ExclusiveResource("bar", READ_WRITE)); - - var locks = getLocks(resources, CompositeLock.class); - - assertThat(locks).hasSize(2); - assertThat(locks.get(0)).isInstanceOf(WriteLock.class); - assertThat(locks.get(1)).isInstanceOf(WriteLock.class); - } - - @ParameterizedTest - @EnumSource - void globalLockComesFirst(LockMode globalLockMode) { - Collection resources = List.of( // - new ExclusiveResource("___foo", READ), // - new ExclusiveResource("foo", READ_WRITE), // - new ExclusiveResource(GLOBAL_KEY, globalLockMode), // - new ExclusiveResource("bar", READ_WRITE)); - - var locks = getLocks(resources, CompositeLock.class); - - assertThat(locks).hasSize(4); - assertThat(locks.get(0)).isEqualTo(getSingleLock(GLOBAL_KEY, globalLockMode)); - assertThat(locks.get(1)).isEqualTo(getSingleLock("___foo", READ)); - assertThat(locks.get(2)).isEqualTo(getSingleLock("bar", READ_WRITE)); - assertThat(locks.get(3)).isEqualTo(getSingleLock("foo", READ_WRITE)); - } - - private Lock getSingleLock(String globalResourceLockKey, LockMode read) { - return getLocks(Set.of(new ExclusiveResource(globalResourceLockKey, read)), SingleLock.class).get(0); - } - - private List getLocks(Collection resources, Class type) { - var lock = lockManager.getLockForResources(resources); - assertThat(lock).isInstanceOf(type); - return ResourceLockSupport.getLocks(lock); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java deleted file mode 100644 index ef706b91..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; - -/** - * Integration tests intended to verify that memory leaks do not - * exist with regard to the "context" held by {@link NodeTestTask}. - * - * @since 5.3.1 - * @see GitHub issue #1578 - */ -// Explicitly specifying Lifecycle.PER_METHOD to be certain that the -// test instance state is recreated for every test method executed. -@TestInstance(Lifecycle.PER_METHOD) -class MemoryLeakTests { - - // Allocate 500 MB of memory per test method. - // - // If the test instance is garbage collected, this should not cause any - // problems for the JUnit 5 build; however, if the instances of this test - // class are NOT garbage collected, we should run out of memory pretty - // quickly since the instances of this test class would consume 5GB of - // heap space. - final byte[] state = new byte[524_288_000]; - - @Test - void test01() { - } - - @Test - void test02() { - } - - @Test - void test03() { - } - - @Test - void test04() { - } - - @Test - void test05() { - } - - @Test - void test06() { - } - - @Test - void test07() { - } - - @Test - void test08() { - } - - @Test - void test09() { - } - - @Test - void test10() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java deleted file mode 100644 index e7813801..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE; -import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.function.Function; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceAccessMode; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; - -/** - * @since 1.3 - */ -class NodeTreeWalkerIntegrationTests { - - LockManager lockManager = new LockManager(); - NodeTreeWalker nodeTreeWalker = new NodeTreeWalker(lockManager); - - @Test - void pullUpExclusiveChildResourcesToTestClass() { - var engineDescriptor = discover(TestCaseWithResourceLock.class); - - var advisor = nodeTreeWalker.walk(engineDescriptor); - - var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"), getReadWriteLock("b"))); - assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); - assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); - } - - @Test - void setsForceExecutionModeForChildrenWithWriteLocksOnClass() { - var engineDescriptor = discover(TestCaseWithResourceWriteLockOnClass.class); - - var advisor = nodeTreeWalker.walk(engineDescriptor); - - var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"))); - assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); - assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); - } - - @Test - void doesntSetForceExecutionModeForChildrenWithReadLocksOnClass() { - var engineDescriptor = discover(TestCaseWithResourceReadLockOnClass.class); - - var advisor = nodeTreeWalker.walk(engineDescriptor); - - var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a"))); - assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); - assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); - } - - @Test - void setsForceExecutionModeForChildrenWithReadLocksOnClassAndWriteLockOnTest() { - var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndWriteClockOnTestCase.class); - - var advisor = nodeTreeWalker.walk(engineDescriptor); - - var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"))); - assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); - assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); - } - - @Test - void doesntSetForceExecutionModeForChildrenWithReadLocksOnClassAndReadLockOnTest() { - var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase.class); - - var advisor = nodeTreeWalker.walk(engineDescriptor); - - var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a"), getReadLock("b"))); - assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); - - var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); - assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); - } - - @Test - void leavesResourceLockOnTestMethodWhenClassDoesNotUseResource() { - var engineDescriptor = discover(TestCaseWithoutResourceLock.class); - - var advisor = nodeTreeWalker.walk(engineDescriptor); - - var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ))); - assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); - - assertThat(testClassDescriptor.getChildren()).hasSize(2); - var children = testClassDescriptor.getChildren().iterator(); - var testMethodDescriptor = children.next(); - assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getReadWriteLock("a"))); - assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); - - var nestedTestClassDescriptor = children.next(); - assertThat(advisor.getResourceLock(nestedTestClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getReadWriteLock("b"), getReadWriteLock("c"))); - assertThat(advisor.getForcedExecutionMode(nestedTestClassDescriptor)).isEmpty(); - - var nestedTestMethodDescriptor = getOnlyElement(nestedTestClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(nestedTestMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); - assertThat(advisor.getForcedExecutionMode(nestedTestMethodDescriptor)).contains(SAME_THREAD); - } - - @Test - void coarsensGlobalLockToEngineDescriptorChild() { - var engineDescriptor = discover(TestCaseWithGlobalLockRequiringChild.class); - - var advisor = nodeTreeWalker.walk(engineDescriptor); - - var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ_WRITE))); - assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); - - var nestedTestClassDescriptor = getOnlyElement(testClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(nestedTestClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ))); - assertThat(advisor.getForcedExecutionMode(nestedTestClassDescriptor)).contains(SAME_THREAD); - - var testMethodDescriptor = getOnlyElement(nestedTestClassDescriptor.getChildren()); - assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ_WRITE))); - assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); - } - - private static Function> allLocks() { - return ResourceLockSupport::getLocks; - } - - private Lock getReadWriteLock(String key) { - return getLock(new ExclusiveResource(key, READ_WRITE)); - } - - private Lock getReadLock(String key) { - return getLock(new ExclusiveResource(key, READ)); - } - - private Lock getLock(ExclusiveResource exclusiveResource) { - return getOnlyElement(ResourceLockSupport.getLocks(lockManager.getLockForResource(exclusiveResource))); - } - - private TestDescriptor discover(Class testClass) { - var discoveryRequest = request().selectors(selectClass(testClass)).build(); - return new JupiterTestEngine().discover(discoveryRequest, UniqueId.forEngine("junit-jupiter")); - } - - @ResourceLock("a") - static class TestCaseWithResourceLock { - @Test - @ResourceLock("b") - void test() { - } - } - - static class TestCaseWithoutResourceLock { - @Test - @ResourceLock("a") - void test() { - } - - @Nested - @ResourceLock("c") - class NestedTestCaseWithResourceLock { - @Test - @ResourceLock("b") - void test() { - } - } - } - - static class TestCaseWithGlobalLockRequiringChild { - @Nested - class NestedTestCaseWithResourceLock { - @Test - @ResourceLock(ExclusiveResource.GLOBAL_KEY) - void test() { - } - } - } - - @ResourceLock("a") - static class TestCaseWithResourceWriteLockOnClass { - @Test - void test() { - } - } - - @ResourceLock(value = "a", mode = ResourceAccessMode.READ) - static class TestCaseWithResourceReadLockOnClass { - @Test - void test() { - } - } - - @ResourceLock(value = "a", mode = ResourceAccessMode.READ) - static class TestCaseWithResourceReadLockOnClassAndWriteClockOnTestCase { - @Test - @ResourceLock("a") - void test() { - } - } - - @ResourceLock(value = "a", mode = ResourceAccessMode.READ) - static class TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase { - @Test - @ResourceLock(value = "b", mode = ResourceAccessMode.READ) - void test() { - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java deleted file mode 100644 index ca743337..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java +++ /dev/null @@ -1,845 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.junit.jupiter.engine.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.DEFAULT_PARALLEL_EXECUTION_MODE; -import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME; -import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.EventConditions.type; -import static org.junit.platform.testkit.engine.EventType.REPORTING_ENTRY_PUBLISHED; - -import java.net.URL; -import java.net.URLClassLoader; -import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.MethodOrderer.MethodName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.Isolated; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Event; - -/** - * @since 1.3 - */ -class ParallelExecutionIntegrationTests { - - @Test - void successfulParallelTest(TestReporter reporter) { - var events = executeConcurrently(3, SuccessfulParallelTestCase.class); - - var startedTimestamps = getTimestampsFor(events, event(test(), started())); - var finishedTimestamps = getTimestampsFor(events, event(test(), finishedSuccessfully())); - reporter.publishEntry("startedTimestamps", startedTimestamps.toString()); - reporter.publishEntry("finishedTimestamps", finishedTimestamps.toString()); - - assertThat(startedTimestamps).hasSize(3); - assertThat(finishedTimestamps).hasSize(3); - assertThat(startedTimestamps).allMatch(startTimestamp -> finishedTimestamps.stream().noneMatch( - finishedTimestamp -> finishedTimestamp.isBefore(startTimestamp))); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); - } - - @Test - void failingTestWithoutLock() { - var events = executeConcurrently(3, FailingWithoutLockTestCase.class); - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).hasSize(2); - } - - @Test - void successfulTestWithMethodLock() { - var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); - } - - @Test - void successfulTestWithClassLock() { - var events = executeConcurrently(3, SuccessfulWithClassLockTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); - } - - @Test - void testCaseWithFactory() { - var events = executeConcurrently(3, TestCaseWithTestFactory.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); - } - - @Test - void customContextClassLoader() { - var currentThread = Thread.currentThread(); - var currentLoader = currentThread.getContextClassLoader(); - var smilingLoader = new URLClassLoader("(-:", new URL[0], ClassLoader.getSystemClassLoader()); - currentThread.setContextClassLoader(smilingLoader); - try { - var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); - assertThat(ThreadReporter.getLoaderNames(events)).containsExactly("(-:"); - } - finally { - currentThread.setContextClassLoader(currentLoader); - } - } - - @RepeatedTest(10) - void mixingClassAndMethodLevelLocks() { - var events = executeConcurrently(4, TestCaseWithSortedLocks.class, TestCaseWithUnsortedLocks.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); - assertThat(ThreadReporter.getThreadNames(events).count()).isLessThanOrEqualTo(2); - } - - @RepeatedTest(10) - void locksOnNestedTests() { - var events = executeConcurrently(3, TestCaseWithNestedLocks.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); - } - - @Test - void afterHooksAreCalledAfterConcurrentDynamicTestsAreFinished() { - var events = executeConcurrently(3, ConcurrentDynamicTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(1); - var timestampedEvents = ConcurrentDynamicTestCase.events; - assertThat(timestampedEvents.get("afterEach")).isAfterOrEqualTo(timestampedEvents.get("dynamicTestFinished")); - } - - /** - * @since 1.4 - * @see gh-1688 - */ - @Test - void threadInterruptedByUserCode() { - var events = executeConcurrently(3, InterruptedThreadTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(4); - } - - @Test - void executesTestTemplatesWithResourceLocksInSameThread() { - var events = executeConcurrently(2, ConcurrentTemplateTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(10); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); - } - - @Test - void executesClassesInParallelIfEnabledViaConfigurationParameter() { - ParallelClassesTestCase.GLOBAL_BARRIER.reset(); - - var configParams = Map.of(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "concurrent"); - var results = executeWithFixedParallelism(3, configParams, ParallelClassesTestCaseA.class, - ParallelClassesTestCaseB.class, ParallelClassesTestCaseC.class); - - results.testEvents().assertStatistics(stats -> stats.succeeded(9)); - assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSize(3); - var testClassA = findFirstTestDescriptor(results, container(ParallelClassesTestCaseA.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(1); - var testClassB = findFirstTestDescriptor(results, container(ParallelClassesTestCaseB.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(1); - var testClassC = findFirstTestDescriptor(results, container(ParallelClassesTestCaseC.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(1); - } - - @Test - void executesMethodsInParallelIfEnabledViaConfigurationParameter() { - ParallelMethodsTestCase.barriersPerClass.clear(); - - var configParams = Map.of( // - DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent", // - DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "same_thread"); - var results = executeWithFixedParallelism(3, configParams, ParallelMethodsTestCaseA.class, - ParallelMethodsTestCaseB.class, ParallelMethodsTestCaseC.class); - - results.testEvents().assertStatistics(stats -> stats.succeeded(9)); - assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSizeGreaterThanOrEqualTo(3); - var testClassA = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseA.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(3); - var testClassB = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseB.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(3); - var testClassC = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseC.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(3); - } - - @Test - void canRunTestsIsolatedFromEachOther() { - var events = executeConcurrently(2, IsolatedTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); - } - - @Test - void canRunTestsIsolatedFromEachOtherWithNestedCases() { - var events = executeConcurrently(4, NestedIsolatedTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); - } - - @Test - void canRunTestsIsolatedFromEachOtherAcrossClasses() { - var events = executeConcurrently(4, IndependentClasses.A.class, IndependentClasses.B.class); - - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); - } - - @RepeatedTest(10) - void canRunTestsIsolatedFromEachOtherAcrossClassesWithOtherResourceLocks() { - var events = executeConcurrently(4, IndependentClasses.B.class, IndependentClasses.C.class); - - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); - } - - @Isolated("testing") - static class IsolatedTestCase { - static AtomicInteger sharedResource; - static CountDownLatch countDownLatch; - - @BeforeAll - static void initialize() { - sharedResource = new AtomicInteger(); - countDownLatch = new CountDownLatch(2); - } - - @Test - void a() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void b() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - } - - static class NestedIsolatedTestCase { - static AtomicInteger sharedResource; - static CountDownLatch countDownLatch; - - @BeforeAll - static void initialize() { - sharedResource = new AtomicInteger(); - countDownLatch = new CountDownLatch(6); - } - - @Test - void a() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void b() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - - @Nested - class Inner { - - @Test - void a() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void b() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - - @Nested - @Isolated - class InnerInner { - - @Test - void a() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void b() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - } - } - } - - static class IndependentClasses { - static AtomicInteger sharedResource = new AtomicInteger(); - static CountDownLatch countDownLatch = new CountDownLatch(4); - - static class A { - @Test - void a() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void b() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - } - - @Isolated - static class B { - @Test - void a() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void b() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - } - - @ResourceLock("other") - static class C { - @Test - void a() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void b() throws Exception { - storeAndBlockAndCheck(sharedResource, countDownLatch); - } - } - } - - private List getEventsOfChildren(EngineExecutionResults results, TestDescriptor container) { - return results.testEvents().filter( - event -> event.getTestDescriptor().getParent().orElseThrow().equals(container)).collect(toList()); - } - - private TestDescriptor findFirstTestDescriptor(EngineExecutionResults results, Condition condition) { - return results.allEvents().filter(condition::matches).map(Event::getTestDescriptor).findFirst().orElseThrow(); - } - - private List getTimestampsFor(List events, Condition condition) { - // @formatter:off - return events.stream() - .filter(condition::matches) - .map(Event::getTimestamp) - .collect(toList()); - // @formatter:on - } - - private List executeConcurrently(int parallelism, Class... testClasses) { - return executeWithFixedParallelism(parallelism, Map.of(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent"), - testClasses).allEvents().list(); - } - - private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map configParams, - Class... testClasses) { - // @formatter:off - var discoveryRequest = request() - .selectors(Arrays.stream(testClasses).map(DiscoverySelectors::selectClass).collect(toList())) - .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, String.valueOf(true)) - .configurationParameter(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") - .configurationParameter(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, String.valueOf(parallelism)) - .configurationParameters(configParams) - .build(); - // @formatter:on - return EngineTestKit.execute("junit-jupiter", discoveryRequest); - } - - // ------------------------------------------------------------------------- - - @ExtendWith(ThreadReporter.class) - static class SuccessfulParallelTestCase { - - static AtomicInteger sharedResource; - static CountDownLatch countDownLatch; - - @BeforeAll - static void initialize() { - sharedResource = new AtomicInteger(); - countDownLatch = new CountDownLatch(3); - } - - @Test - void firstTest() throws Exception { - incrementAndBlock(sharedResource, countDownLatch); - } - - @Test - void secondTest() throws Exception { - incrementAndBlock(sharedResource, countDownLatch); - } - - @Test - void thirdTest() throws Exception { - incrementAndBlock(sharedResource, countDownLatch); - } - } - - @ExtendWith(ThreadReporter.class) - static class FailingWithoutLockTestCase { - - static AtomicInteger sharedResource; - static CountDownLatch countDownLatch; - - @BeforeAll - static void initialize() { - sharedResource = new AtomicInteger(); - countDownLatch = new CountDownLatch(3); - } - - @Test - void firstTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void secondTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void thirdTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - } - - @ExtendWith(ThreadReporter.class) - static class SuccessfulWithMethodLockTestCase { - - static AtomicInteger sharedResource; - static CountDownLatch countDownLatch; - - @BeforeAll - static void initialize() { - sharedResource = new AtomicInteger(); - countDownLatch = new CountDownLatch(3); - } - - @Test - @ResourceLock("sharedResource") - void firstTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - @ResourceLock("sharedResource") - void secondTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - @ResourceLock("sharedResource") - void thirdTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - } - - @ExtendWith(ThreadReporter.class) - @ResourceLock("sharedResource") - static class SuccessfulWithClassLockTestCase { - - static AtomicInteger sharedResource; - static CountDownLatch countDownLatch; - - @BeforeAll - static void initialize() { - sharedResource = new AtomicInteger(); - countDownLatch = new CountDownLatch(3); - } - - @Test - void firstTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void secondTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - - @Test - void thirdTest() throws Exception { - incrementBlockAndCheck(sharedResource, countDownLatch); - } - } - - static class TestCaseWithTestFactory { - @TestFactory - @Execution(SAME_THREAD) - Stream testFactory(TestReporter testReporter) { - var sharedResource = new AtomicInteger(0); - var countDownLatch = new CountDownLatch(3); - return IntStream.range(0, 3).mapToObj(i -> dynamicTest("test " + i, () -> { - incrementBlockAndCheck(sharedResource, countDownLatch); - testReporter.publishEntry("thread", Thread.currentThread().getName()); - })); - } - } - - private static final ReentrantLock A = new ReentrantLock(); - private static final ReentrantLock B = new ReentrantLock(); - - @ExtendWith(ThreadReporter.class) - @ResourceLock("A") - static class TestCaseWithSortedLocks { - @ResourceLock("B") - @Test - void firstTest() { - assertTrue(A.tryLock()); - assertTrue(B.tryLock()); - } - - @Execution(CONCURRENT) - @ResourceLock("B") - @Test - void secondTest() { - assertTrue(A.tryLock()); - assertTrue(B.tryLock()); - } - - @ResourceLock("B") - @Test - void thirdTest() { - assertTrue(A.tryLock()); - assertTrue(B.tryLock()); - } - - @AfterEach - void unlock() { - B.unlock(); - A.unlock(); - } - } - - @ExtendWith(ThreadReporter.class) - @ResourceLock("B") - static class TestCaseWithUnsortedLocks { - @ResourceLock("A") - @Test - void firstTest() { - assertTrue(B.tryLock()); - assertTrue(A.tryLock()); - } - - @Execution(CONCURRENT) - @ResourceLock("A") - @Test - void secondTest() { - assertTrue(B.tryLock()); - assertTrue(A.tryLock()); - } - - @ResourceLock("A") - @Test - void thirdTest() { - assertTrue(B.tryLock()); - assertTrue(A.tryLock()); - } - - @AfterEach - void unlock() { - A.unlock(); - B.unlock(); - } - } - - @ExtendWith(ThreadReporter.class) - @ResourceLock("A") - static class TestCaseWithNestedLocks { - - @ResourceLock("B") - @Test - void firstTest() { - assertTrue(A.tryLock()); - assertTrue(B.tryLock()); - } - - @Execution(CONCURRENT) - @ResourceLock("B") - @Test - void secondTest() { - assertTrue(A.tryLock()); - assertTrue(B.tryLock()); - } - - @Test - void thirdTest() { - assertTrue(A.tryLock()); - assertTrue(B.tryLock()); - } - - @AfterEach - void unlock() { - A.unlock(); - B.unlock(); - } - - @Nested - @ResourceLock("B") - class B { - - @ResourceLock("A") - @Test - void firstTest() { - assertTrue(B.tryLock()); - assertTrue(A.tryLock()); - } - - @ResourceLock("A") - @Test - void secondTest() { - assertTrue(B.tryLock()); - assertTrue(A.tryLock()); - } - - @Test - void thirdTest() { - assertTrue(B.tryLock()); - assertTrue(A.tryLock()); - } - } - } - - @Execution(CONCURRENT) - static class ConcurrentDynamicTestCase { - static Map events; - - @BeforeAll - static void beforeAll() { - events = new ConcurrentHashMap<>(); - } - - @AfterEach - void afterEach() { - events.put("afterEach", Instant.now()); - } - - @TestFactory - DynamicTest testFactory() { - return dynamicTest("slow", () -> { - Thread.sleep(100); - events.put("dynamicTestFinished", Instant.now()); - }); - } - } - - @TestMethodOrder(MethodName.class) - static class InterruptedThreadTestCase { - - @Test - void test1() { - Thread.currentThread().interrupt(); - } - - @Test - void test2() throws InterruptedException { - Thread.sleep(10); - } - - @Test - void test3() { - Thread.currentThread().interrupt(); - } - - @Test - void test4() throws InterruptedException { - Thread.sleep(10); - } - - } - - @Execution(CONCURRENT) - @ExtendWith(ThreadReporter.class) - static class ConcurrentTemplateTestCase { - @RepeatedTest(10) - @ResourceLock("a") - void repeatedTest() throws Exception { - Thread.sleep(100); - } - } - - @ExtendWith(ThreadReporter.class) - static abstract class BarrierTestCase { - - @Test - void test1() throws Exception { - getBarrier().await(); - } - - @Test - void test2() throws Exception { - getBarrier().await(); - } - - @Test - void test3() throws Exception { - getBarrier().await(); - } - - abstract CyclicBarrier getBarrier(); - - } - - static class ParallelMethodsTestCase extends BarrierTestCase { - - static final Map, CyclicBarrier> barriersPerClass = new ConcurrentHashMap<>(); - - @Override - CyclicBarrier getBarrier() { - return barriersPerClass.computeIfAbsent(this.getClass(), key -> new CyclicBarrier(3)); - } - } - - static class ParallelClassesTestCase extends BarrierTestCase { - - static final CyclicBarrier GLOBAL_BARRIER = new CyclicBarrier(3); - - @Override - CyclicBarrier getBarrier() { - return GLOBAL_BARRIER; - } - - } - - static class ParallelClassesTestCaseA extends ParallelClassesTestCase { - } - - static class ParallelClassesTestCaseB extends ParallelClassesTestCase { - } - - static class ParallelClassesTestCaseC extends ParallelClassesTestCase { - } - - static class ParallelMethodsTestCaseA extends ParallelMethodsTestCase { - } - - static class ParallelMethodsTestCaseB extends ParallelMethodsTestCase { - } - - static class ParallelMethodsTestCaseC extends ParallelMethodsTestCase { - } - - private static void incrementBlockAndCheck(AtomicInteger sharedResource, CountDownLatch countDownLatch) - throws InterruptedException { - var value = incrementAndBlock(sharedResource, countDownLatch); - assertEquals(value, sharedResource.get()); - } - - private static int incrementAndBlock(AtomicInteger sharedResource, CountDownLatch countDownLatch) - throws InterruptedException { - var value = sharedResource.incrementAndGet(); - countDownLatch.countDown(); - countDownLatch.await(estimateSimulatedTestDurationInMiliseconds(), MILLISECONDS); - return value; - } - - private static void storeAndBlockAndCheck(AtomicInteger sharedResource, CountDownLatch countDownLatch) - throws InterruptedException { - var value = sharedResource.get(); - countDownLatch.countDown(); - countDownLatch.await(estimateSimulatedTestDurationInMiliseconds(), MILLISECONDS); - assertEquals(value, sharedResource.get()); - } - - /** - * To simulate tests running in parallel tests will modify a shared - * resource, simulate work by waiting, then check if the shared resource was - * not modified by any other thread. - * - * Depending on system performance the simulation of work needs to be longer - * on slower systems to ensure tests can run in parallel. - * - * Currently CI is known to be slow. - */ - private static long estimateSimulatedTestDurationInMiliseconds() { - var runningInCi = Boolean.valueOf(System.getenv("CI")); - return runningInCi ? 1000 : 100; - } - - static class ThreadReporter implements AfterTestExecutionCallback { - - private static Stream getLoaderNames(List events) { - return getValues(events, "loader"); - } - - private static Stream getThreadNames(List events) { - return getValues(events, "thread"); - } - - private static Stream getValues(List events, String key) { - // @formatter:off - return events.stream() - .filter(type(REPORTING_ENTRY_PUBLISHED)::matches) - .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) - .map(ReportEntry::getKeyValuePairs) - .filter(keyValuePairs -> keyValuePairs.containsKey(key)) - .map(keyValuePairs -> keyValuePairs.get(key)) - .distinct(); - // @formatter:on - } - - @Override - public void afterTestExecution(ExtensionContext context) { - context.publishReportEntry("thread", Thread.currentThread().getName()); - context.publishReportEntry("loader", Thread.currentThread().getContextClassLoader().getName()); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java deleted file mode 100644 index 59f02ffa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import java.util.List; -import java.util.concurrent.locks.Lock; - -class ResourceLockSupport { - - static List getLocks(ResourceLock resourceLock) { - if (resourceLock instanceof NopLock) { - return List.of(); - } - if (resourceLock instanceof SingleLock) { - return List.of(((SingleLock) resourceLock).getLock()); - } - return ((CompositeLock) resourceLock).getLocks(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java deleted file mode 100644 index 9a17de1a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.MethodOrderer.MethodName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.testkit.engine.EngineTestKit; - -/** - * @since 1.4 - */ -class SameThreadExecutionIntegrationTests { - - /** - * @see gh-1688 - */ - @Test - @TrackLogRecords - void threadInterruptedByUserCode(LogRecordListener listener) { - EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(InterruptedThreadTestCase.class))// - .execute()// - .testEvents()// - .assertStatistics(stats -> stats.succeeded(4)); - - assertThat(firstDebugLogRecord(listener).getMessage()).matches( - "Execution of TestDescriptor with display name .+test1.+ and " - + "unique ID .+ failed to clear the 'interrupted status' flag " - + "for the current thread. JUnit has cleared the flag, but you " - + "may wish to investigate why the flag was not cleared by user code."); - } - - private LogRecord firstDebugLogRecord(LogRecordListener listener) throws AssertionError { - return listener.stream(NodeTestTask.class, Level.FINE).findFirst().orElseThrow( - () -> new AssertionError("Failed to find debug log record")); - } - - // ------------------------------------------------------------------------- - - @TestMethodOrder(MethodName.class) - static class InterruptedThreadTestCase { - - @Test - void test1() { - Thread.currentThread().interrupt(); - } - - @Test - void test2() throws InterruptedException { - Thread.sleep(10); - } - - @Test - void test3() { - Thread.currentThread().interrupt(); - } - - @Test - void test4() throws InterruptedException { - Thread.sleep(10); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java deleted file mode 100644 index 382a82fc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.concurrent.locks.ReentrantLock; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.3 - */ -class SingleLockTests { - - @Test - @SuppressWarnings("resource") - void acquire() throws Exception { - var lock = new ReentrantLock(); - - new SingleLock(lock).acquire(); - - assertTrue(lock.isLocked()); - } - - @Test - @SuppressWarnings("resource") - void release() throws Exception { - var lock = new ReentrantLock(); - - new SingleLock(lock).acquire().close(); - - assertFalse(lock.isLocked()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java deleted file mode 100644 index 2160099a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; - -import java.util.Optional; - -import org.junit.jupiter.api.Test; -import org.opentest4j.TestAbortedException; - -/** - * @since 1.0 - */ -@SuppressWarnings("deprecation") -class SingleTestExecutorTests { - - @Test - void executeSafelySuccessful() { - var result = new SingleTestExecutor().executeSafely(() -> { - }); - - assertEquals(SUCCESSFUL, result.getStatus()); - assertEquals(Optional.empty(), result.getThrowable()); - } - - @Test - void executeSafelyAborted() { - var testAbortedException = new TestAbortedException("assumption violated"); - - var result = new SingleTestExecutor().executeSafely(() -> { - throw testAbortedException; - }); - - assertEquals(ABORTED, result.getStatus()); - assertSame(testAbortedException, result.getThrowable().get()); - } - - @Test - void executeSafelyFailed() { - var assertionError = new AssertionError("assumption violated"); - - var result = new SingleTestExecutor().executeSafely(() -> { - throw assertionError; - }); - - assertEquals(FAILED, result.getStatus()); - assertSame(assertionError, result.getThrowable().get()); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java deleted file mode 100644 index 868e4804..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.hierarchical; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; -import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; -import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; - -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.6 - */ -class ThrowableCollectorTests { - - @Test - void successfulExecution() { - var collector = new ThrowableCollector(x -> true); - collector.execute(() -> { - }); - var result = collector.toTestExecutionResult(); - - assertEquals(SUCCESSFUL, result.getStatus()); - assertEquals(Optional.empty(), result.getThrowable()); - } - - @Test - void abortedExecution() { - var customAbort = new CustomAbort(); - - var collector = new ThrowableCollector(CustomAbort.class::isInstance); - collector.execute(() -> { - throw customAbort; - }); - var result = collector.toTestExecutionResult(); - - assertEquals(ABORTED, result.getStatus()); - assertSame(customAbort, result.getThrowable().get()); - } - - @Test - void failedExecution() { - var assertionError = new AssertionError("assertion violated"); - - var collector = new ThrowableCollector(CustomAbort.class::isInstance); - collector.execute(() -> { - throw assertionError; - }); - var result = collector.toTestExecutionResult(); - - assertEquals(FAILED, result.getStatus()); - assertSame(assertionError, result.getThrowable().get()); - } - - private static class CustomAbort extends Error { - private static final long serialVersionUID = 1L; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java deleted file mode 100644 index 9dfdc786..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.jfr; - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.moditect.jfrunit.ExpectedEvent.event; -import static org.moditect.jfrunit.JfrEventsAssert.assertThat; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly; -import org.moditect.jfrunit.EnableEvent; -import org.moditect.jfrunit.JfrEventTest; -import org.moditect.jfrunit.JfrEvents; - -@JfrEventTest -public class FlightRecordingDiscoveryListenerIntegrationTests { - - public JfrEvents jfrEvents = new JfrEvents(); - - @Test - @EnableEvent("org.junit.*") - void reportsEvents() { - var launcher = LauncherFactoryForTestingPurposesOnly.createLauncher(new JupiterTestEngine()); - var request = request() // - .selectors(selectClass(FlightRecordingDiscoveryListenerIntegrationTests.class)) // - .listeners(new FlightRecordingDiscoveryListener()) // - .build(); - - launcher.discover(request); - jfrEvents.awaitEvents(); - - assertThat(jfrEvents) // - .contains(event("org.junit.LauncherDiscovery") // - // TODO JfrUnit does not yey support checking int values - // .with("selectors", 1) // - // .with("filters", 0) // - ) // - .contains(event("org.junit.EngineDiscovery") // - .with("uniqueId", "[engine:junit-jupiter]")); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java deleted file mode 100644 index d75732ba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.jfr; - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.moditect.jfrunit.ExpectedEvent.event; -import static org.moditect.jfrunit.JfrEventsAssert.assertThat; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestReporter; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly; -import org.moditect.jfrunit.EnableEvent; -import org.moditect.jfrunit.JfrEventTest; -import org.moditect.jfrunit.JfrEvents; - -@JfrEventTest -public class FlightRecordingExecutionListenerIntegrationTests { - - public JfrEvents jfrEvents = new JfrEvents(); - - @Test - @EnableEvent("org.junit.*") - void reportsEvents() { - var launcher = LauncherFactoryForTestingPurposesOnly.createLauncher(new JupiterTestEngine()); - var request = request() // - .selectors(selectClass(TestCase.class)) // - .build(); - - launcher.execute(request, new FlightRecordingExecutionListener()); - jfrEvents.awaitEvents(); - - assertThat(jfrEvents) // - .contains(event("org.junit.TestPlanExecution") // - .with("engineNames", "JUnit Jupiter")) // - .contains(event("org.junit.TestExecution") // - .with("displayName", "JUnit Jupiter") // - .with("type", "CONTAINER")) // - .contains(event("org.junit.TestExecution") // - .with("displayName", FlightRecordingExecutionListenerIntegrationTests.class.getSimpleName() - + "$" + TestCase.class.getSimpleName()) // - .with("type", "CONTAINER")) // - .contains(event("org.junit.TestExecution") // - .with("displayName", "test(TestReporter)") // - .with("type", "TEST") // - .with("result", "SUCCESSFUL")) // - .contains(event("org.junit.ReportEntry") // - .with("key", "message") // - .with("value", "Hello JFR!")) // - .contains(event("org.junit.SkippedTest") // - .with("displayName", "skipped()") // - .with("type", "TEST") // - .with("reason", "for demonstration purposes")); - } - - static class TestCase { - @Test - void test(TestReporter reporter) { - reporter.publishEntry("message", "Hello JFR!"); - } - - @Test - @Disabled("for demonstration purposes") - void skipped() { - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java deleted file mode 100644 index 1ca8e6b4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.FilterResult; - -/** - * @since 1.0 - */ -public class DiscoveryFilterStub extends FilterStub implements DiscoveryFilter { - - public DiscoveryFilterStub(String toString) { - super(toString); - } - - public DiscoveryFilterStub(Function function, Supplier toString) { - super(function, toString); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java deleted file mode 100644 index 04c7eab0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.FilterResult; - -/** - * @since 1.0 - */ -public class FilterStub implements Filter { - - private final Function function; - private final Supplier toString; - - public FilterStub(String toString) { - this(o -> FilterResult.included("always"), () -> toString); - } - - public FilterStub(Function function, Supplier toString) { - this.function = function; - this.toString = toString; - } - - @Override - public FilterResult apply(T object) { - return function.apply(object); - } - - @Override - public String toString() { - return toString.get(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java deleted file mode 100644 index d16a7bbd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestDescriptor; - -/** - * @since 1.0 - */ -public class PostDiscoveryFilterStub extends FilterStub implements PostDiscoveryFilter { - - public PostDiscoveryFilterStub(String toString) { - super(toString); - } - - public PostDiscoveryFilterStub(Function function, Supplier toString) { - super(function, toString); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java deleted file mode 100644 index 14d966ee..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.launcher.TagFilter.excludeTags; -import static org.junit.platform.launcher.TagFilter.includeTags; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.DemoClassTestDescriptor; - -/** - * Unit tests for {@link TagFilter}. - * - *

NOTE: part of the behavior of these tests regarding tags is - * influenced by the implementation of {@link DemoClassTestDescriptor#getTags()} - * rather than any concrete test engine. - * - * @since 1.0 - */ -class TagFilterTests { - - private static final TestDescriptor classWithTag1 = classTestDescriptor("class1", ClassWithTag1.class); - private static final TestDescriptor classWithTag1AndSurroundingWhitespace = classTestDescriptor( - "class1-surrounding-whitespace", ClassWithTag1AndSurroundingWhitespace.class); - private static final TestDescriptor classWithTag2 = classTestDescriptor("class2", ClassWithTag2.class); - private static final TestDescriptor classWithBothTags = classTestDescriptor("class12", ClassWithBothTags.class); - private static final TestDescriptor classWithDifferentTags = classTestDescriptor("classX", - ClassWithDifferentTags.class); - private static final TestDescriptor classWithNoTags = classTestDescriptor("class", ClassWithNoTags.class); - - @Test - void includeTagsWithInvalidSyntax() { - // @formatter:off - assertAll( - () -> assertSyntaxViolationForIncludes(null), - () -> assertSyntaxViolationForIncludes(""), - () -> assertSyntaxViolationForIncludes(" "), - () -> assertSyntaxViolationForIncludes("foo bar") - ); - // @formatter:on - } - - private void assertSyntaxViolationForIncludes(String tag) { - var exception = assertThrows(PreconditionViolationException.class, () -> includeTags(tag)); - assertThat(exception).hasMessageStartingWith("Unable to parse tag expression"); - } - - @Test - void excludeTagsWithInvalidSyntax() { - // @formatter:off - assertAll( - () -> assertSyntaxViolationForExcludes(null), - () -> assertSyntaxViolationForExcludes(""), - () -> assertSyntaxViolationForExcludes(" "), - () -> assertSyntaxViolationForExcludes("foo bar") - ); - // @formatter:on - } - - private void assertSyntaxViolationForExcludes(String tag) { - var exception = assertThrows(PreconditionViolationException.class, () -> excludeTags(tag)); - assertThat(exception).hasMessageStartingWith("Unable to parse tag expression"); - } - - @Test - void includeSingleTag() { - includeSingleTag(includeTags("tag1")); - } - - @Test - void includeSingleTagAndWhitespace() { - includeSingleTag(includeTags("\t \n tag1 ")); - } - - @Test - void includeMultipleTags() { - var filter = includeTags("tag1", " tag2 "); - - assertTrue(filter.apply(classWithBothTags).included()); - assertTrue(filter.apply(classWithTag1).included()); - assertTrue(filter.apply(classWithTag1AndSurroundingWhitespace).included()); - assertTrue(filter.apply(classWithTag2).included()); - - assertTrue(filter.apply(classWithDifferentTags).excluded()); - assertTrue(filter.apply(classWithNoTags).excluded()); - } - - @Test - void excludeSingleTag() { - excludeSingleTag(excludeTags("tag1")); - } - - @Test - void excludeSingleTagAndWhitespace() { - excludeSingleTag(excludeTags("\t \n tag1 ")); - } - - @Test - void excludeMultipleTags() { - var filter = excludeTags("tag1", " tag2 "); - - var exclusionReason = "excluded because tags match tag expression(s): [tag1,tag2]"; - assertExcluded(filter.apply(classWithTag1), exclusionReason); - assertExcluded(filter.apply(classWithTag1AndSurroundingWhitespace), exclusionReason); - assertExcluded(filter.apply(classWithBothTags), exclusionReason); - assertExcluded(filter.apply(classWithTag2), exclusionReason); - - var inclusionReason = "included because tags do not match expression(s): [tag1,tag2]"; - assertIncluded(filter.apply(classWithDifferentTags), inclusionReason); - assertIncluded(filter.apply(classWithNoTags), inclusionReason); - } - - @Test - void rejectSingleUnparsableTagExpressions() { - var brokenTagExpression = "tag & "; - RuntimeException expected = assertThrows(PreconditionViolationException.class, - () -> TagFilter.includeTags(brokenTagExpression)); - assertThat(expected).hasMessageStartingWith("Unable to parse tag expression \"" + brokenTagExpression + "\""); - } - - @Test - void rejectUnparsableTagExpressionFromArray() { - var brokenTagExpression = "tag & "; - RuntimeException expected = assertThrows(PreconditionViolationException.class, - () -> TagFilter.excludeTags(brokenTagExpression, "foo", "bar")); - assertThat(expected).hasMessageStartingWith("Unable to parse tag expression \"" + brokenTagExpression + "\""); - } - - private void includeSingleTag(PostDiscoveryFilter filter) { - var inclusionReason = "included because tags match expression(s): [tag1]"; - assertIncluded(filter.apply(classWithTag1), inclusionReason); - assertIncluded(filter.apply(classWithTag1AndSurroundingWhitespace), inclusionReason); - assertIncluded(filter.apply(classWithBothTags), inclusionReason); - - var exclusionReason = "excluded because tags do not match tag expression(s): [tag1]"; - assertExcluded(filter.apply(classWithTag2), exclusionReason); - assertExcluded(filter.apply(classWithDifferentTags), exclusionReason); - assertExcluded(filter.apply(classWithNoTags), exclusionReason); - } - - private void excludeSingleTag(PostDiscoveryFilter filter) { - var exclusionReason = "excluded because tags match tag expression(s): [tag1]"; - assertExcluded(filter.apply(classWithTag1), exclusionReason); - assertExcluded(filter.apply(classWithTag1AndSurroundingWhitespace), exclusionReason); - assertExcluded(filter.apply(classWithBothTags), exclusionReason); - - var inclusionReason = "included because tags do not match expression(s): [tag1]"; - assertIncluded(filter.apply(classWithTag2), inclusionReason); - assertIncluded(filter.apply(classWithDifferentTags), inclusionReason); - assertIncluded(filter.apply(classWithNoTags), inclusionReason); - } - - private void assertIncluded(FilterResult filterResult, String expectedReason) { - assertTrue(filterResult.included()); - assertThat(filterResult.getReason()).isPresent().contains(expectedReason); - } - - private void assertExcluded(FilterResult filterResult, String expectedReason) { - assertTrue(filterResult.excluded()); - assertThat(filterResult.getReason()).isPresent().contains(expectedReason); - } - - // ------------------------------------------------------------------------- - - @Retention(RetentionPolicy.RUNTIME) - @Tag("tag1") - private @interface Tag1 { - } - - @Retention(RetentionPolicy.RUNTIME) - @Tag("tag2") - private @interface Tag2 { - } - - @Tag1 - private static class ClassWithTag1 { - } - - @Tag(" tag1 \t ") - private static class ClassWithTag1AndSurroundingWhitespace { - } - - @Tag2 - private static class ClassWithTag2 { - } - - @Tag1 - @Tag2 - private static class ClassWithBothTags { - } - - @Tag("foo") - @Tag("bar") - private static class ClassWithDifferentTags { - } - - @Tag(" ") // intentionally "blank" - private static class ClassWithNoTags { - } - - private static TestDescriptor classTestDescriptor(String uniqueId, Class testClass) { - var rootUniqueId = UniqueId.root("class", uniqueId); - return new DemoClassTestDescriptor(rootUniqueId, testClass); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java deleted file mode 100644 index fd9809c1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.TagFilter.includeTags; -import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.doubleTaggedWasExecuted; -import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.tag1WasExecuted; -import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.tag2WasExecuted; -import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.unTaggedWasExecuted; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.testkit.engine.EngineTestKit; - -class TagIntegrationTests { - - @BeforeEach - void init() { - tag1WasExecuted = false; - tag2WasExecuted = false; - unTaggedWasExecuted = false; - doubleTaggedWasExecuted = false; - } - - @Test - void includingWrongTagExecutesNothing() { - executeTaggedTestCase(includeTags("whatever")); - - assertFalse(tag1WasExecuted); - assertFalse(tag2WasExecuted); - assertFalse(doubleTaggedWasExecuted); - assertFalse(unTaggedWasExecuted); - } - - @Test - void includingSuitableTagExecutesTaggedTestOnly() { - executeTaggedTestCase(includeTags("tag1")); - - assertTrue(tag1WasExecuted); - assertFalse(tag2WasExecuted); - assertTrue(doubleTaggedWasExecuted); - assertFalse(unTaggedWasExecuted); - } - - @ParameterizedTest - @ValueSource(strings = { "any()", "!none()" }) - void includingTheAnyKeywordExecutesAllTaggedTests(String tagExpression) { - executeTaggedTestCase(includeTags(tagExpression)); - - assertTrue(tag1WasExecuted); - assertTrue(tag2WasExecuted); - assertTrue(doubleTaggedWasExecuted); - assertFalse(unTaggedWasExecuted); - } - - @ParameterizedTest - @ValueSource(strings = { "none()", "!any()" }) - void includingTheNoneKeywordExecutesAllUntaggedTests(String tagExpression) { - executeTaggedTestCase(includeTags(tagExpression)); - - assertFalse(tag1WasExecuted); - assertFalse(tag2WasExecuted); - assertFalse(doubleTaggedWasExecuted); - assertTrue(unTaggedWasExecuted); - } - - private void executeTaggedTestCase(PostDiscoveryFilter filter) { - EngineTestKit.engine("junit-jupiter") // - .selectors(selectClass(TaggedTestCase.class)) // - .filters(filter) // - .execute(); - } - - static class TaggedTestCase { - - static boolean tag1WasExecuted = false; - static boolean tag2WasExecuted = false; - static boolean unTaggedWasExecuted = false; - static boolean doubleTaggedWasExecuted = false; - - @Test - @Tag("tag1") - void tagged1() { - tag1WasExecuted = true; - } - - @Test - @Tag("tag2") - void tagged2() { - tag2WasExecuted = true; - } - - @Test - @Tag("tag1") - @Tag("tag2") - void doubleTagged() { - doubleTaggedWasExecuted = true; - } - - @Test - void unTagged() { - unTaggedWasExecuted = true; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java deleted file mode 100644 index 8d007f45..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.SerializationUtils.deserialize; -import static org.junit.platform.commons.util.SerializationUtils.serialize; - -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.fakes.TestDescriptorStub; - -/** - * @since 1.0 - */ -class TestIdentifierTests { - - @Test - void inheritsIdAndNamesFromDescriptor() { - TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.root("aType", "uniqueId"), "displayName"); - var testIdentifier = TestIdentifier.from(testDescriptor); - - assertEquals("[aType:uniqueId]", testIdentifier.getUniqueId()); - assertEquals("displayName", testIdentifier.getDisplayName()); - } - - @Test - void inheritsTypeFromDescriptor() { - TestDescriptor descriptor = new TestDescriptorStub(UniqueId.root("aType", "uniqueId"), "displayName"); - var identifier = TestIdentifier.from(descriptor); - assertEquals(TestDescriptor.Type.TEST, identifier.getType()); - assertTrue(identifier.isTest()); - assertFalse(identifier.isContainer()); - - descriptor.addChild(new TestDescriptorStub(UniqueId.root("aChild", "uniqueId"), "displayName")); - identifier = TestIdentifier.from(descriptor); - assertEquals(TestDescriptor.Type.CONTAINER, identifier.getType()); - assertFalse(identifier.isTest()); - assertTrue(identifier.isContainer()); - } - - @Test - void currentVersionCanBeSerializedAndDeserialized() throws Exception { - var originalIdentifier = createOriginalTestIdentifier(); - var deserializedIdentifier = (TestIdentifier) deserialize(serialize(originalIdentifier)); - assertDeepEquals(originalIdentifier, deserializedIdentifier); - } - - @Test - void initialVersionCanBeDeserialized() throws Exception { - try (var inputStream = getClass().getResourceAsStream("/serialized-test-identifier")) { - var bytes = inputStream.readAllBytes(); - var deserializedIdentifier = (TestIdentifier) deserialize(bytes); - assertDeepEquals(createOriginalTestIdentifier(), deserializedIdentifier); - } - } - - private static void assertDeepEquals(TestIdentifier first, TestIdentifier second) { - assertEquals(first, second); - assertEquals(first.getUniqueId(), second.getUniqueId()); - assertEquals(first.getUniqueIdObject(), second.getUniqueIdObject()); - assertEquals(first.getDisplayName(), second.getDisplayName()); - assertEquals(first.getLegacyReportingName(), second.getLegacyReportingName()); - assertEquals(first.getSource(), second.getSource()); - assertEquals(first.getTags(), second.getTags()); - assertEquals(first.getType(), second.getType()); - assertEquals(first.getParentId(), second.getParentId()); - assertEquals(first.getParentIdObject(), second.getParentIdObject()); - } - - private static TestIdentifier createOriginalTestIdentifier() { - var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); - var uniqueId = engineDescriptor.getUniqueId().append("child", "child"); - var testSource = ClassSource.from(TestIdentifierTests.class); - var testDescriptor = new AbstractTestDescriptor(uniqueId, "displayName", testSource) { - - @Override - public Type getType() { - return Type.TEST; - } - - @Override - public String getLegacyReportingName() { - return "reportingName"; - } - - @Override - public Set getTags() { - return Set.of(TestTag.create("aTag")); - } - }; - engineDescriptor.addChild(testDescriptor); - return TestIdentifier.from(testDescriptor); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java deleted file mode 100644 index 489f63ca..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -public class TestLauncherDiscoveryListener implements LauncherDiscoveryListener { - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - return getClass() == obj.getClass(); - } - - @Override - public int hashCode() { - return 1; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java deleted file mode 100644 index 02a39507..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -public class TestLauncherSessionListener implements LauncherSessionListener { - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - return getClass() == obj.getClass(); - } - - @Override - public int hashCode() { - return 1; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java deleted file mode 100644 index 866e085a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; - -class TestPlanTests { - - private final ConfigurationParameters configParams = mock(); - - private EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("foo"), "Foo"); - - @Test - void doesNotContainTestsForEmptyContainers() { - engineDescriptor.addChild( - new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { - @Override - public Type getType() { - return Type.CONTAINER; - } - }); - - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - assertThat(testPlan.containsTests()).as("contains tests").isFalse(); - } - - @Test - void containsTestsForTests() { - engineDescriptor.addChild( - new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { - @Override - public Type getType() { - return Type.TEST; - } - }); - - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - assertThat(testPlan.containsTests()).as("contains tests").isTrue(); - } - - @Test - void containsTestsForContainersThatMayRegisterTests() { - engineDescriptor.addChild( - new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public boolean mayRegisterTests() { - return true; - } - }); - - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - assertThat(testPlan.containsTests()).as("contains tests").isTrue(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java deleted file mode 100644 index 5ba0aada..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher; - -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; - -public class TestPostDiscoveryTagFilter implements PostDiscoveryFilter { - @Override - public FilterResult apply(final TestDescriptor object) { - var include = object.getTags().stream().map(TestTag::getName).anyMatch("test-post-discovery"::equals); - return FilterResult.includedIf(include); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java deleted file mode 100644 index b6ff9bac..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; - -@TrackLogRecords -class CompositeEngineExecutionListenerTests { - - private final List listeners = new ArrayList<>( - List.of(new ThrowingEngineExecutionListener())); - - @Test - void shouldNotThrowExceptionButLogIfDynamicTestRegisteredListenerMethodFails(LogRecordListener logRecordListener) { - var testDescriptor = getSampleMethodTestDescriptor(); - - compositeTestExecutionListener().dynamicTestRegistered(testDescriptor); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, - "dynamicTestRegistered"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionStartedListenerMethodFails(LogRecordListener logRecordListener) { - var testDescriptor = getSampleMethodTestDescriptor(); - - compositeTestExecutionListener().executionStarted(testDescriptor); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, "executionStarted"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionSkippedListenerMethodFails(LogRecordListener logRecordListener) { - var testDescriptor = getSampleMethodTestDescriptor(); - - compositeTestExecutionListener().executionSkipped(testDescriptor, "deliberately skipped container"); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, "executionSkipped"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionFinishedListenerMethodFails(LogRecordListener logRecordListener) { - var testDescriptor = getSampleMethodTestDescriptor(); - - compositeTestExecutionListener().executionFinished(testDescriptor, mock(TestExecutionResult.class)); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, - "executionFinished"); - } - - @Test - void shouldNotThrowExceptionButLogIfReportingEntryPublishedListenerMethodFails( - LogRecordListener logRecordListener) { - var testDescriptor = getSampleMethodTestDescriptor(); - - compositeTestExecutionListener().reportingEntryPublished(testDescriptor, ReportEntry.from("one", "two")); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingEngineExecutionListener.class, - "reportingEntryPublished"); - } - - @Test - void shouldThrowOutOfMemoryExceptionAndStopListenerWithoutLog(LogRecordListener logRecordListener) { - listeners.clear(); - listeners.add(new EngineExecutionListener() { - @Override - public void executionStarted(TestDescriptor testDescriptor) { - throw new OutOfMemoryError(); - } - }); - var testDescriptor = getSampleMethodTestDescriptor(); - assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(testDescriptor)).isInstanceOf( - OutOfMemoryError.class); - - assertNotLogs(logRecordListener); - } - - private EngineExecutionListener compositeTestExecutionListener() { - return new CompositeEngineExecutionListener(listeners); - } - - private LogRecord firstWarnLogRecord(LogRecordListener logRecordListener) throws AssertionError { - return logRecordListener.stream(CompositeEngineExecutionListener.class, Level.WARNING).findFirst().orElseThrow( - () -> new AssertionError("Failed to find error log record")); - } - - private void assertNotLogs(LogRecordListener logRecordListener) throws AssertionError { - assertThat(logRecordListener.stream(CompositeEngineExecutionListener.class, Level.WARNING).count()).isZero(); - } - - private TestDescriptor getSampleMethodTestDescriptor() { - return getDemoMethodTestDescriptor(); - } - - private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListener, Class listenerClass, - String methodName) { - assertThat(firstWarnLogRecord(logRecordListener).getMessage()).startsWith( - "EngineExecutionListener [" + listenerClass.getName() + "] threw exception for method: " + methodName); - } - - private DemoMethodTestDescriptor getDemoMethodTestDescriptor() { - var method = ReflectionUtils.findMethod(this.getClass(), "getDemoMethodTestDescriptor", - new Class[0]).orElseThrow(); - return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), this.getClass(), method); - } - - private static class ThrowingEngineExecutionListener implements EngineExecutionListener { - - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void executionStarted(TestDescriptor testDescriptor) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void executionSkipped(TestDescriptor testDescriptor, String reason) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - throw new RuntimeException("failed to invoke listener"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java deleted file mode 100644 index 18751347..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.core.CompositeTestExecutionListener.EagerTestExecutionListener; - -@TrackLogRecords -class CompositeTestExecutionListenerTests { - - private final List listeners = new ArrayList<>(List.of(new ThrowingTestExecutionListener())); - - @Test - void shouldNotThrowExceptionButLogIfDynamicTestRegisteredListenerMethodFails(LogRecordListener logRecordListener) { - var testIdentifier = getSampleMethodTestIdentifier(); - - compositeTestExecutionListener().dynamicTestRegistered(testIdentifier); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, - "dynamicTestRegistered"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionStartedListenerMethodFails(LogRecordListener logRecordListener) { - var testIdentifier = getSampleMethodTestIdentifier(); - - compositeTestExecutionListener().executionStarted(testIdentifier); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionStarted"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionSkippedListenerMethodFails(LogRecordListener logRecordListener) { - var testIdentifier = getSampleMethodTestIdentifier(); - - compositeTestExecutionListener().executionSkipped(testIdentifier, "deliberately skipped container"); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionSkipped"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionFinishedListenerMethodFails(LogRecordListener logRecordListener) { - var testIdentifier = getSampleMethodTestIdentifier(); - - compositeTestExecutionListener().executionFinished(testIdentifier, mock(TestExecutionResult.class)); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionFinished"); - } - - @Test - void shouldNotThrowExceptionButLogIfReportingEntryPublishedListenerMethodFails( - LogRecordListener logRecordListener) { - var testIdentifier = getSampleMethodTestIdentifier(); - - compositeTestExecutionListener().reportingEntryPublished(testIdentifier, ReportEntry.from("one", "two")); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, - "reportingEntryPublished"); - } - - @Test - void shouldNotThrowExceptionButLogIfTesPlanExecutionStartedListenerMethodFails( - LogRecordListener logRecordListener) { - var testDescriptor = getDemoMethodTestDescriptor(); - - compositeTestExecutionListener().testPlanExecutionStarted(TestPlan.from(Set.of(testDescriptor), mock())); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, - "testPlanExecutionStarted"); - } - - @Test - void shouldNotThrowExceptionButLogIfTesPlanExecutionFinishedListenerMethodFails( - LogRecordListener logRecordListener) { - var testDescriptor = getDemoMethodTestDescriptor(); - - compositeTestExecutionListener().testPlanExecutionFinished(TestPlan.from(Set.of(testDescriptor), mock())); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, - "testPlanExecutionFinished"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionJustStartedEagerTestListenerMethodFails( - LogRecordListener logRecordListener) { - listeners.add(new ThrowingEagerTestExecutionListener()); - - var testIdentifier = getSampleMethodTestIdentifier(); - compositeTestExecutionListener().executionStarted(testIdentifier); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingEagerTestExecutionListener.class, - "executionJustStarted"); - } - - @Test - void shouldNotThrowExceptionButLogIfExecutionJustFinishedEagerTestListenerMethodFails( - LogRecordListener logRecordListener) { - listeners.add(new ThrowingEagerTestExecutionListener()); - - var testIdentifier = getSampleMethodTestIdentifier(); - compositeTestExecutionListener().executionFinished(testIdentifier, mock(TestExecutionResult.class)); - - assertThatTestListenerErrorLogged(logRecordListener, ThrowingEagerTestExecutionListener.class, - "executionJustFinished"); - } - - @Test - void shouldThrowOutOfMemoryExceptionAndStopListenerWithoutLog(LogRecordListener logRecordListener) { - listeners.clear(); - listeners.add(new TestExecutionListener() { - @Override - public void executionStarted(TestIdentifier testIdentifier) { - throw new OutOfMemoryError(); - } - }); - var testIdentifier = getSampleMethodTestIdentifier(); - assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(testIdentifier)).isInstanceOf( - OutOfMemoryError.class); - - assertNotLogs(logRecordListener); - } - - @Test - void shouldThrowOutOfMemoryExceptionAndStopEagerListenerWithoutLog(LogRecordListener logRecordListener) { - listeners.add(new EagerTestExecutionListener() { - @Override - public void executionJustStarted(TestIdentifier testIdentifier) { - throw new OutOfMemoryError(); - } - }); - var testIdentifier = getSampleMethodTestIdentifier(); - assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(testIdentifier)).isInstanceOf( - OutOfMemoryError.class); - - assertNotLogs(logRecordListener); - } - - private TestExecutionListener compositeTestExecutionListener() { - return new CompositeTestExecutionListener(listeners); - } - - private LogRecord firstWarnLogRecord(LogRecordListener logRecordListener) throws AssertionError { - return logRecordListener.stream(CompositeTestExecutionListener.class, Level.WARNING).findFirst().orElseThrow( - () -> new AssertionError("Failed to find error log record")); - } - - private void assertNotLogs(LogRecordListener logRecordListener) throws AssertionError { - assertThat(logRecordListener.stream(CompositeTestExecutionListener.class, Level.WARNING).count()).isZero(); - } - - private TestIdentifier getSampleMethodTestIdentifier() { - var demoMethodTestDescriptor = getDemoMethodTestDescriptor(); - return TestIdentifier.from(demoMethodTestDescriptor); - } - - private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListener, Class listenerClass, - String methodName) { - assertThat(firstWarnLogRecord(logRecordListener).getMessage()).startsWith( - "TestExecutionListener [" + listenerClass.getName() + "] threw exception for method: " + methodName); - } - - private DemoMethodTestDescriptor getDemoMethodTestDescriptor() { - var method = ReflectionUtils.findMethod(this.getClass(), "getDemoMethodTestDescriptor", - new Class[0]).orElseThrow(); - return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), this.getClass(), method); - } - - private static class ThrowingEagerTestExecutionListener extends ThrowingTestExecutionListener - implements EagerTestExecutionListener { - @Override - public void executionJustStarted(TestIdentifier testIdentifier) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - throw new RuntimeException("failed to invoke listener"); - } - } - - private static class ThrowingTestExecutionListener implements TestExecutionListener { - @Override - public void testPlanExecutionStarted(TestPlan testPlan) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void testPlanExecutionFinished(TestPlan testPlan) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void dynamicTestRegistered(TestIdentifier testIdentifier) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - throw new RuntimeException("failed to invoke listener"); - } - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - throw new RuntimeException("failed to invoke listener"); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java deleted file mode 100644 index 85732038..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.logging.Level.WARNING; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.EngineFilter.excludeEngines; -import static org.junit.platform.launcher.EngineFilter.includeEngines; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; -import org.junit.platform.launcher.LauncherDiscoveryRequest; - -/** - * @since 1.0 - */ -class DefaultLauncherEngineFilterTests { - - private static final Runnable noOp = () -> { - }; - - @Test - void launcherWillNotExecuteEnginesIfNotIncludedByAnEngineFilter() { - var firstEngine = new DemoHierarchicalTestEngine("first"); - TestDescriptor test1 = firstEngine.addTest("test1", noOp); - var secondEngine = new DemoHierarchicalTestEngine("second"); - TestDescriptor test2 = secondEngine.addTest("test2", noOp); - - var launcher = createLauncher(firstEngine, secondEngine); - - // @formatter:off - var testPlan = launcher.discover( - request() - .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) - .filters(includeEngines("first")) - .build()); - // @formatter:on - - assertThat(testPlan.getRoots()).hasSize(1); - var rootIdentifier = testPlan.getRoots().iterator().next(); - assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); - assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); - } - - @Test - void launcherWillExecuteAllEnginesExplicitlyIncludedViaSingleEngineFilter() { - var firstEngine = new DemoHierarchicalTestEngine("first"); - TestDescriptor test1 = firstEngine.addTest("test1", noOp); - var secondEngine = new DemoHierarchicalTestEngine("second"); - TestDescriptor test2 = secondEngine.addTest("test2", noOp); - - var launcher = createLauncher(firstEngine, secondEngine); - - // @formatter:off - var testPlan = launcher.discover( - request() - .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) - .filters(includeEngines("first", "second")) - .build()); - // @formatter:on - - assertThat(testPlan.getRoots()).hasSize(2); - } - - @Test - void launcherWillNotExecuteEnginesExplicitlyIncludedViaMultipleCompetingEngineFilters() { - var firstEngine = new DemoHierarchicalTestEngine("first"); - TestDescriptor test1 = firstEngine.addTest("test1", noOp); - var secondEngine = new DemoHierarchicalTestEngine("second"); - TestDescriptor test2 = secondEngine.addTest("test2", noOp); - - var launcher = createLauncher(firstEngine, secondEngine); - - // @formatter:off - var testPlan = launcher.discover( - request() - .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) - .filters(includeEngines("first"), includeEngines("second")) - .build()); - // @formatter:on - - assertThat(testPlan.getRoots()).isEmpty(); - } - - @Test - void launcherWillNotExecuteEnginesExplicitlyExcludedByAnEngineFilter() { - var firstEngine = new DemoHierarchicalTestEngine("first"); - TestDescriptor test1 = firstEngine.addTest("test1", noOp); - var secondEngine = new DemoHierarchicalTestEngine("second"); - TestDescriptor test2 = secondEngine.addTest("test2", noOp); - - var launcher = createLauncher(firstEngine, secondEngine); - - // @formatter:off - var testPlan = launcher.discover( - request() - .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) - .filters(excludeEngines("second")) - .build()); - // @formatter:on - - assertThat(testPlan.getRoots()).hasSize(1); - var rootIdentifier = testPlan.getRoots().iterator().next(); - assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); - assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); - } - - @Test - void launcherWillExecuteEnginesHonoringBothIncludeAndExcludeEngineFilters() { - var firstEngine = new DemoHierarchicalTestEngine("first"); - TestDescriptor test1 = firstEngine.addTest("test1", noOp); - var secondEngine = new DemoHierarchicalTestEngine("second"); - TestDescriptor test2 = secondEngine.addTest("test2", noOp); - var thirdEngine = new DemoHierarchicalTestEngine("third"); - TestDescriptor test3 = thirdEngine.addTest("test3", noOp); - - var launcher = createLauncher(firstEngine, secondEngine, thirdEngine); - - // @formatter:off - var testPlan = launcher.discover( - request() - .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId()), selectUniqueId(test3.getUniqueId())) - .filters(includeEngines("first", "second"), excludeEngines("second")) - .build()); - // @formatter:on - - assertThat(testPlan.getRoots()).hasSize(1); - var rootIdentifier = testPlan.getRoots().iterator().next(); - assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); - assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); - } - - @Test - @TrackLogRecords - void launcherThrowsExceptionWhenNoEngineMatchesIncludeEngineFilter(LogRecordListener log) { - var engine = new DemoHierarchicalTestEngine("first"); - TestDescriptor test1 = engine.addTest("test1", noOp); - LauncherDiscoveryRequest request = request() // - .selectors(selectUniqueId(test1.getUniqueId())) // - .filters(includeEngines("second")) // - .build(); - - var launcher = createLauncher(engine); - var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); - - assertThat(exception.getMessage()) // - .startsWith("No TestEngine ID matched the following include EngineFilters: [second].") // - .contains("Please fix/remove the filter or add the engine.") // - .contains("Registered TestEngines:\n- first (") // - .endsWith("Registered EngineFilters:\n- EngineFilter that includes engines with IDs [second]"); - assertThat(log.stream(WARNING)).isEmpty(); - } - - @Test - @TrackLogRecords - void launcherWillLogWarningWhenAllEnginesWereExcluded(LogRecordListener log) { - var engine = new DemoHierarchicalTestEngine("first"); - TestDescriptor test = engine.addTest("test1", noOp); - - var launcher = createLauncher(engine); - - // @formatter:off - var testPlan = launcher.discover( - request() - .selectors(selectUniqueId(test.getUniqueId())) - .filters(excludeEngines("first")) - .build()); - // @formatter:on - - assertThat(testPlan.getRoots()).isEmpty(); - assertThat(log.stream(WARNING)).hasSize(1); - assertThat(log.stream(WARNING).findAny().orElseThrow().getMessage()) // - .startsWith("All TestEngines were excluded by EngineFilters.") // - .contains("Registered TestEngines:\n- first (") // - .endsWith("Registered EngineFilters:\n- EngineFilter that excludes engines with IDs [first]"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java deleted file mode 100644 index dce04470..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.fakes.TestEngineSpy; -import org.junit.platform.fakes.TestEngineStub; -import org.junit.platform.launcher.EngineDiscoveryResult; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.launcher.PostDiscoveryFilterStub; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.listeners.SummaryGeneratingListener; -import org.mockito.ArgumentCaptor; - -/** - * @since 1.0 - */ -class DefaultLauncherTests { - - private static final String FOO = DefaultLauncherTests.class.getSimpleName() + ".foo"; - private static final String BAR = DefaultLauncherTests.class.getSimpleName() + ".bar"; - - private static final Runnable noOp = () -> { - }; - - @Test - void constructLauncherWithoutAnyEngines() { - Throwable exception = assertThrows(PreconditionViolationException.class, - LauncherFactoryForTestingPurposesOnly::createLauncher); - - assertThat(exception).hasMessageContaining("Cannot create Launcher without at least one TestEngine"); - } - - @Test - void constructLauncherWithMultipleTestEnginesWithDuplicateIds() { - var exception = assertThrows(JUnitException.class, - () -> createLauncher(new DemoHierarchicalTestEngine("dummy id"), - new DemoHierarchicalTestEngine("dummy id"))); - - assertThat(exception).hasMessageContaining("multiple engines with the same ID"); - } - - @Test - void discoverEmptyTestPlanWithEngineWithoutAnyTests() { - var launcher = createLauncher(new DemoHierarchicalTestEngine()); - - var testPlan = launcher.discover(request().build()); - - assertThat(testPlan.getRoots()).hasSize(1); - } - - @Test - void discoverTestPlanForEngineThatReturnsNullForItsRootDescriptor() { - TestEngine engine = new TestEngineStub("some-engine-id") { - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - return null; - } - }; - - var discoveryListener = mock(LauncherDiscoveryListener.class); - var testPlan = createLauncher(engine).discover(request() // - .listeners(discoveryListener) // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .build()); - assertThat(testPlan.getRoots()).hasSize(1); - assertDiscoveryFailed(engine, discoveryListener); - } - - @ParameterizedTest - @ValueSource(classes = { Error.class, RuntimeException.class }) - void discoverErrorTestDescriptorForEngineThatThrowsInDiscoveryPhase(Class throwableClass) { - TestEngine engine = new TestEngineStub("my-engine-id") { - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - try { - var constructor = throwableClass.getDeclaredConstructor(String.class); - throw ExceptionUtils.throwAsUncheckedException(constructor.newInstance("ignored")); - } - catch (Exception ignored) { - return null; - } - } - }; - - var launcher = createLauncher(engine); - var discoveryListener = mock(LauncherDiscoveryListener.class); - var request = request() // - .listeners(discoveryListener) // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .build(); - var testPlan = launcher.discover(request); - - assertThat(testPlan.getRoots()).hasSize(1); - var engineIdentifier = getOnlyElement(testPlan.getRoots()); - assertThat(getOnlyElement(testPlan.getRoots()).getDisplayName()).isEqualTo("my-engine-id"); - verify(discoveryListener).launcherDiscoveryStarted(request); - verify(discoveryListener).launcherDiscoveryFinished(request); - assertDiscoveryFailed(engine, discoveryListener); - - var listener = mock(TestExecutionListener.class); - launcher.execute(testPlan, listener); - - var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - verify(listener).executionStarted(engineIdentifier); - verify(listener).executionFinished(eq(engineIdentifier), testExecutionResult.capture()); - assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); - assertThat(testExecutionResult.getValue().getThrowable().get()) // - .hasMessage("TestEngine with ID 'my-engine-id' failed to discover tests"); - } - - private void assertDiscoveryFailed(TestEngine testEngine, LauncherDiscoveryListener discoveryListener) { - var engineId = testEngine.getId(); - var failureCaptor = ArgumentCaptor.forClass(EngineDiscoveryResult.class); - verify(discoveryListener).engineDiscoveryFinished(eq(UniqueId.forEngine(engineId)), failureCaptor.capture()); - var result = failureCaptor.getValue(); - assertThat(result.getStatus()).isEqualTo(EngineDiscoveryResult.Status.FAILED); - assertThat(result.getThrowable()).isPresent(); - assertThat(result.getThrowable().get()).hasMessage( - "TestEngine with ID '" + engineId + "' failed to discover tests"); - } - - @Test - void reportsEngineExecutionFailuresWithoutPriorEvents() { - var rootCause = new RuntimeException("something went horribly wrong"); - var engine = new TestEngineStub() { - @Override - public void execute(ExecutionRequest request) { - throw rootCause; - } - }; - - var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); - - var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - verify(listener).executionStarted(any()); - verify(listener).executionFinished(any(), testExecutionResult.capture()); - assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); - assertThat(testExecutionResult.getValue().getThrowable().get()) // - .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); - } - - @Test - void reportsEngineExecutionFailuresForSkippedEngine() { - var rootCause = new RuntimeException("something went horribly wrong"); - var engine = new TestEngineStub() { - @Override - public void execute(ExecutionRequest request) { - var engineDescriptor = request.getRootTestDescriptor(); - request.getEngineExecutionListener().executionSkipped(engineDescriptor, "not today"); - throw rootCause; - } - }; - - var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); - - var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - verify(listener).executionStarted(any()); - verify(listener).executionFinished(any(), testExecutionResult.capture()); - assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); - assertThat(testExecutionResult.getValue().getThrowable().get()) // - .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); - } - - @Test - void reportsEngineExecutionFailuresForStartedEngine() { - var rootCause = new RuntimeException("something went horribly wrong"); - var engine = new TestEngineStub() { - @Override - public void execute(ExecutionRequest request) { - var engineDescriptor = request.getRootTestDescriptor(); - request.getEngineExecutionListener().executionStarted(engineDescriptor); - throw rootCause; - } - }; - - var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); - - var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - verify(listener).executionStarted(any()); - verify(listener).executionFinished(any(), testExecutionResult.capture()); - assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); - assertThat(testExecutionResult.getValue().getThrowable().get()) // - .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); - } - - @Test - void reportsEngineExecutionFailuresForSuccessfullyFinishedEngine() { - var rootCause = new RuntimeException("something went horribly wrong"); - var engine = new TestEngineStub() { - @Override - public void execute(ExecutionRequest request) { - var engineDescriptor = request.getRootTestDescriptor(); - request.getEngineExecutionListener().executionStarted(engineDescriptor); - request.getEngineExecutionListener().executionFinished(engineDescriptor, - TestExecutionResult.successful()); - throw rootCause; - } - }; - - var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); - - var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - verify(listener).executionStarted(any()); - verify(listener).executionFinished(any(), testExecutionResult.capture()); - assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); - assertThat(testExecutionResult.getValue().getThrowable().get()) // - .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); - } - - @Test - void reportsEngineExecutionFailuresForFailedFinishedEngine() { - var rootCause = new RuntimeException("something went horribly wrong"); - var originalFailure = new RuntimeException("suppressed"); - var engine = new TestEngineStub() { - @Override - public void execute(ExecutionRequest request) { - var engineDescriptor = request.getRootTestDescriptor(); - var listener = request.getEngineExecutionListener(); - listener.executionStarted(engineDescriptor); - listener.executionFinished(engineDescriptor, TestExecutionResult.failed(originalFailure)); - throw rootCause; - } - }; - - var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); - - var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); - verify(listener).executionStarted(any()); - verify(listener).executionFinished(any(), testExecutionResult.capture()); - assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); - assertThat(testExecutionResult.getValue().getThrowable().get()) // - .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause) // - .hasSuppressedException(originalFailure); - } - - @Test - void reportsSkippedEngines() { - var engine = new TestEngineStub() { - @Override - public void execute(ExecutionRequest request) { - var engineDescriptor = request.getRootTestDescriptor(); - request.getEngineExecutionListener().executionSkipped(engineDescriptor, "not today"); - } - }; - - var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); - - verify(listener).executionSkipped(any(TestIdentifier.class), eq("not today")); - verify(listener, times(0)).executionStarted(any()); - verify(listener, times(0)).executionFinished(any(), any()); - } - - @Test - void reportsFinishedEngines() { - var engine = new TestEngineStub() { - @Override - public void execute(ExecutionRequest request) { - var engineDescriptor = request.getRootTestDescriptor(); - var listener = request.getEngineExecutionListener(); - listener.executionStarted(engineDescriptor); - listener.executionFinished(engineDescriptor, TestExecutionResult.successful()); - } - }; - - var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); - - verify(listener).executionStarted(any()); - verify(listener).executionFinished(any(), eq(TestExecutionResult.successful())); - } - - @Test - void discoverTestPlanForSingleEngine() { - var engine = new DemoHierarchicalTestEngine("myEngine"); - engine.addTest("test1", noOp); - engine.addTest("test2", noOp); - - var launcher = createLauncher(engine); - - var testPlan = launcher.discover(request().selectors(selectPackage("any")).build()); - - assertThat(testPlan.getRoots()).hasSize(1); - var rootIdentifier = testPlan.getRoots().iterator().next(); - assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(2); - assertThat(testPlan.getChildren(UniqueId.parse("[engine:myEngine]"))).hasSize(2); - } - - @Test - void discoverTestPlanForMultipleEngines() { - var firstEngine = new DemoHierarchicalTestEngine("engine1"); - TestDescriptor test1 = firstEngine.addTest("test1", noOp); - var secondEngine = new DemoHierarchicalTestEngine("engine2"); - TestDescriptor test2 = secondEngine.addTest("test2", noOp); - - var launcher = createLauncher(firstEngine, secondEngine); - - var testPlan = launcher.discover( - request().selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())).build()); - - assertThat(testPlan.getRoots()).hasSize(2); - assertThat(testPlan.getChildren(UniqueId.forEngine("engine1"))).hasSize(1); - assertThat(testPlan.getChildren(UniqueId.forEngine("engine2"))).hasSize(1); - } - - @Test - void launcherAppliesPostDiscoveryFilters() { - var engine = new DemoHierarchicalTestEngine("myEngine"); - var test1 = engine.addTest("test1", noOp); - engine.addTest("test2", noOp); - - var launcher = createLauncher(engine); - - PostDiscoveryFilter includeWithUniqueIdContainsTest = new PostDiscoveryFilterStub( - descriptor -> FilterResult.includedIf(descriptor.getUniqueId().toString().contains("test")), - () -> "filter1"); - PostDiscoveryFilter includeWithUniqueIdContains1 = new PostDiscoveryFilterStub( - descriptor -> FilterResult.includedIf(descriptor.getUniqueId().toString().contains("1")), () -> "filter2"); - - var testPlan = launcher.discover( // - request() // - .selectors(selectPackage("any")) // - .filters(includeWithUniqueIdContainsTest, includeWithUniqueIdContains1) // - .build()); - - assertThat(testPlan.getChildren(UniqueId.forEngine("myEngine"))).hasSize(1); - assertThat(testPlan.getTestIdentifier(test1.getUniqueId())).isNotNull(); - } - - @Test - @SuppressWarnings("deprecation") - void withoutConfigurationParameters_LauncherPassesEmptyConfigurationParametersIntoTheExecutionRequest() { - var engine = new TestEngineSpy(); - - var launcher = createLauncher(engine); - launcher.execute(request().build()); - - var configurationParameters = engine.requestForExecution.getConfigurationParameters(); - assertThat(configurationParameters.get("key").isPresent()).isFalse(); - assertThat(configurationParameters.size()).isEqualTo(0); - } - - @Test - @SuppressWarnings("deprecation") - void withConfigurationParameters_LauncherPassesPopulatedConfigurationParametersIntoTheExecutionRequest() { - var engine = new TestEngineSpy(); - - var launcher = createLauncher(engine); - launcher.execute(request().configurationParameter("key", "value").build()); - - var configurationParameters = engine.requestForExecution.getConfigurationParameters(); - assertThat(configurationParameters.size()).isEqualTo(1); - assertThat(configurationParameters.get("key").isPresent()).isTrue(); - assertThat(configurationParameters.get("key").get()).isEqualTo("value"); - } - - @Test - @SuppressWarnings("deprecation") - void withoutConfigurationParameters_LookupFallsBackToSystemProperty() { - System.setProperty(FOO, BAR); - - try { - var engine = new TestEngineSpy(); - - var launcher = createLauncher(engine); - launcher.execute(request().build()); - - var configurationParameters = engine.requestForExecution.getConfigurationParameters(); - assertThat(configurationParameters.size()).isEqualTo(0); - var optionalFoo = configurationParameters.get(FOO); - assertTrue(optionalFoo.isPresent(), "foo should have been picked up via system property"); - assertEquals(BAR, optionalFoo.get(), "foo property"); - } - finally { - System.clearProperty(FOO); - } - } - - @Test - void withAdditionalListener() { - var engine = new TestEngineSpy(); - var listener = new SummaryGeneratingListener(); - - var launcher = createLauncher(engine); - launcher.execute(request().build(), listener); - - assertThat(listener.getSummary()).isNotNull(); - assertThat(listener.getSummary().getContainersFoundCount()).isEqualTo(1); - assertThat(listener.getSummary().getTestsFoundCount()).isEqualTo(1); - } - - @Test - void prunesTestDescriptorsAfterApplyingPostDiscoveryFilters() { - var engine = new TestEngineSpy() { - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - super.discover(discoveryRequest, uniqueId); - var engineDescriptor = new TestDescriptorStub(uniqueId, uniqueId.toString()); - var containerDescriptor = new TestDescriptorStub(uniqueId.append("container", "a"), "container") { - - @Override - public Type getType() { - return Type.CONTAINER; - } - }; - containerDescriptor.addChild( - new TestDescriptorStub(containerDescriptor.getUniqueId().append("test", "b"), "test")); - engineDescriptor.addChild(containerDescriptor); - return engineDescriptor; - } - }; - - var launcher = createLauncher(engine); - var testPlan = launcher.discover(request().filters( - (PostDiscoveryFilter) testDescriptor -> FilterResult.includedIf(testDescriptor.isContainer())).build()); - - assertThat(testPlan.getRoots()).hasSize(1); - var engineIdentifier = getOnlyElement(testPlan.getRoots()); - assertThat(testPlan.getChildren(engineIdentifier)).isEmpty(); - } - - @Test - void reportsDynamicTestDescriptorsCorrectly() { - var engineId = UniqueId.forEngine(TestEngineSpy.ID); - var containerAndTestId = engineId.append("c&t", "c&t"); - var dynamicTestId = containerAndTestId.append("test", "test"); - - var engine = new TestEngineSpy() { - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - super.discover(discoveryRequest, uniqueId); - var engineDescriptor = new TestDescriptorStub(uniqueId, uniqueId.toString()); - engineDescriptor.addChild(new TestDescriptorStub(containerAndTestId, "c&t") { - - @Override - public Type getType() { - return Type.CONTAINER_AND_TEST; - } - }); - return engineDescriptor; - } - - @Override - public void execute(ExecutionRequest request) { - super.execute(request); - var listener = request.getEngineExecutionListener(); - - listener.executionStarted(request.getRootTestDescriptor()); - var containerAndTest = getOnlyElement(request.getRootTestDescriptor().getChildren()); - listener.executionStarted(containerAndTest); - - var dynamicTest = new TestDescriptorStub(dynamicTestId, "test"); - dynamicTest.setParent(containerAndTest); - listener.dynamicTestRegistered(dynamicTest); - listener.executionStarted(dynamicTest); - listener.executionFinished(dynamicTest, successful()); - - listener.executionFinished(containerAndTest, successful()); - listener.executionFinished(request.getRootTestDescriptor(), successful()); - } - }; - - var launcher = createLauncher(engine); - var listener = mock(TestExecutionListener.class); - launcher.execute(request().build(), listener); - - var inOrder = inOrder(listener); - var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); - inOrder.verify(listener).testPlanExecutionStarted(testPlanArgumentCaptor.capture()); - - var testPlan = testPlanArgumentCaptor.getValue(); - var engineTestIdentifier = testPlan.getTestIdentifier(engineId); - var containerAndTestIdentifier = testPlan.getTestIdentifier(containerAndTestId); - var dynamicTestIdentifier = testPlan.getTestIdentifier(dynamicTestId); - assertThat(engineTestIdentifier.getParentIdObject()).isEmpty(); - assertThat(containerAndTestIdentifier.getParentIdObject()).contains(engineId); - assertThat(dynamicTestIdentifier.getParentIdObject()).contains(containerAndTestId); - - inOrder.verify(listener).executionStarted(engineTestIdentifier); - inOrder.verify(listener).executionStarted(containerAndTestIdentifier); - inOrder.verify(listener).dynamicTestRegistered(dynamicTestIdentifier); - inOrder.verify(listener).executionStarted(dynamicTestIdentifier); - inOrder.verify(listener).executionFinished(dynamicTestIdentifier, successful()); - inOrder.verify(listener).executionFinished(containerAndTestIdentifier, successful()); - inOrder.verify(listener).executionFinished(engineTestIdentifier, successful()); - inOrder.verify(listener).testPlanExecutionFinished(same(testPlan)); - } - - @Test - void launcherCanExecuteTestPlanExactlyOnce() { - var engine = mock(TestEngine.class); - when(engine.getId()).thenReturn("some-engine"); - when(engine.discover(any(), any())).thenAnswer(invocation -> { - UniqueId uniqueId = invocation.getArgument(1); - return new EngineDescriptor(uniqueId, uniqueId.toString()); - }); - - var launcher = createLauncher(engine); - var testPlan = launcher.discover(request().build()); - verify(engine, times(1)).discover(any(), any()); - - launcher.execute(testPlan); - verify(engine, times(1)).execute(any()); - - var e = assertThrows(PreconditionViolationException.class, () -> launcher.execute(testPlan)); - assertEquals(e.getMessage(), "TestPlan must only be executed once"); - } - - @Test - @SuppressWarnings("deprecation") - void testPlanThrowsExceptionWhenModified() { - TestEngine engine = new TestEngineSpy(); - var launcher = createLauncher(engine); - var testPlan = launcher.discover(request().build()); - var engineIdentifier = getOnlyElement(testPlan.getRoots()); - var engineUniqueId = engineIdentifier.getUniqueIdObject(); - assertThat(testPlan.getChildren(engineIdentifier)).hasSize(1); - - var addedIdentifier = TestIdentifier.from( - new TestDescriptorStub(engineUniqueId.append("test", "test2"), "test2")); - - var exception = assertThrows(JUnitException.class, () -> testPlan.add(addedIdentifier)); - assertThat(exception).hasMessage("Unsupported attempt to modify the TestPlan was detected. " - + "Please contact your IDE/tool vendor and request a fix or downgrade to JUnit 5.7.x (see https://github.com/junit-team/junit5/issues/1732 for details)."); - assertThat(testPlan.getChildren(engineIdentifier)).hasSize(1).doesNotContain(addedIdentifier); - } - - @Test - @TrackLogRecords - void thirdPartyEngineUsingReservedEngineIdPrefixEmitsWarning(LogRecordListener listener) { - var id = "junit-using-reserved-prefix"; - createLauncher(new TestEngineStub(id)); - assertThat(listener.stream(EngineIdValidator.class, Level.WARNING).map(LogRecord::getMessage)) // - .containsExactly( - "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID: '" - + id + "'"); - } - - @Test - void thirdPartyEngineClaimingToBeJupiterResultsInException() { - assertImposter("junit-jupiter"); - } - - @Test - void thirdPartyEngineClaimingToBeVintageResultsInException() { - assertImposter("junit-vintage"); - } - - private void assertImposter(String id) { - TestEngine impostor = new TestEngineStub(id); - Exception exception = assertThrows(JUnitException.class, () -> createLauncher(impostor)); - assertThat(exception).hasMessage( - "Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.", - impostor.getClass().getName(), id); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java deleted file mode 100644 index e9421a24..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.fakes.TestDescriptorStub; - -/** - * @since 1.3 - */ -class EngineDiscoveryResultValidatorTests { - - private final EngineDiscoveryResultValidator validator = new EngineDiscoveryResultValidator(); - - @Test - void detectCycleWithDoubleRoot() { - var root = new TestDescriptorStub(UniqueId.forEngine("root"), "root"); - assertTrue(validator.isAcyclic(root)); - - root.addChild(root); - assertFalse(validator.isAcyclic(root)); - } - - @Test - void detectCycleWithDoubleGroup() { - var rootId = UniqueId.forEngine("root"); - var root = new TestDescriptorStub(rootId, "root"); - TestDescriptor group1 = new TestDescriptorStub(rootId.append("group", "1"), "1"); - TestDescriptor group2 = new TestDescriptorStub(rootId.append("group", "2"), "2"); - root.addChild(group1); - root.addChild(group2); - assertTrue(validator.isAcyclic(root)); - - group2.addChild(group1); - assertFalse(validator.isAcyclic(root)); - } - - @Test - void detectCycleWithDoubleTest() { - var rootId = UniqueId.forEngine("root"); - var root = new TestDescriptorStub(rootId, "root"); - TestDescriptor group1 = new TestDescriptorStub(rootId.append("group", "1"), "1"); - TestDescriptor group2 = new TestDescriptorStub(rootId.append("group", "2"), "2"); - root.addChild(group1); - root.addChild(group2); - TestDescriptor test1 = new TestDescriptorStub(rootId.append("test", "1"), "1-1"); - TestDescriptor test2 = new TestDescriptorStub(rootId.append("test", "2"), "2-2"); - group1.addChild(test1); - group2.addChild(test2); - assertTrue(validator.isAcyclic(root)); - - group2.addChild(test1); - assertFalse(validator.isAcyclic(root)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java deleted file mode 100644 index c120c5c7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; - -/** - * @since 1.0 - */ -// TODO Test other adapter methods. -class ExecutionListenerAdapterTests { - - @Test - void testReportingEntryPublished() { - var testDescriptor = getSampleMethodTestDescriptor(); - - var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(), testDescriptor), mock()); - var testPlan = InternalTestPlan.from(discoveryResult); - var testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId()); - - //not yet spyable with mockito? -> https://github.com/mockito/mockito/issues/146 - var testExecutionListener = new MockTestExecutionListener(); - var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); - - var entry = ReportEntry.from("one", "two"); - executionListenerAdapter.reportingEntryPublished(testDescriptor, entry); - - assertThat(testExecutionListener.entry).isEqualTo(entry); - assertThat(testExecutionListener.testIdentifier).isEqualTo(testIdentifier); - } - - private TestDescriptor getSampleMethodTestDescriptor() { - var localMethodNamedNothing = ReflectionUtils.findMethod(this.getClass(), "nothing", new Class[0]).get(); - return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), this.getClass(), - localMethodNamedNothing); - } - - //for reflection purposes only - void nothing() { - } - - static class MockTestExecutionListener implements TestExecutionListener { - - public TestIdentifier testIdentifier; - public ReportEntry entry; - - @Override - public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - this.testIdentifier = testIdentifier; - this.entry = entry; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java deleted file mode 100644 index 63182980..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.fakes.TestEngineStub; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.TestExecutionListener; - -/** - * Unit tests for {@link LauncherConfig} and {@link LauncherConfig.Builder}. - * - * @since 1.3 - */ -class LauncherConfigTests { - - @Test - void preconditions() { - assertThrows(PreconditionViolationException.class, - () -> LauncherConfig.builder().addTestEngines((TestEngine[]) null)); - assertThrows(PreconditionViolationException.class, - () -> LauncherConfig.builder().addTestExecutionListeners((TestExecutionListener[]) null)); - - TestEngine engine = new TestEngineStub(); - var listener = new TestExecutionListener() { - }; - assertThrows(PreconditionViolationException.class, - () -> LauncherConfig.builder().addTestEngines(engine, engine, null)); - assertThrows(PreconditionViolationException.class, - () -> LauncherConfig.builder().addTestExecutionListeners(listener, listener, null)); - } - - @Test - void defaultConfig() { - var config = LauncherConfig.DEFAULT; - - assertTrue(config.isTestEngineAutoRegistrationEnabled(), - "Test engine auto-registration should be enabled by default"); - assertTrue(config.isLauncherDiscoveryListenerAutoRegistrationEnabled(), - "Launcher discovery listener auto-registration should be enabled by default"); - assertTrue(config.isTestExecutionListenerAutoRegistrationEnabled(), - "Test execution listener auto-registration should be enabled by default"); - assertTrue(config.isPostDiscoveryFilterAutoRegistrationEnabled(), - "Post-discovery filter auto-registration should be enabled by default"); - - assertThat(config.getAdditionalTestEngines()).isEmpty(); - - assertThat(config.getAdditionalTestExecutionListeners()).isEmpty(); - } - - @Test - void disableTestEngineAutoRegistration() { - var config = LauncherConfig.builder().enableTestEngineAutoRegistration(false).build(); - - assertFalse(config.isTestEngineAutoRegistrationEnabled()); - } - - @Test - void disableLauncherDiscoveryListenerAutoRegistration() { - var config = LauncherConfig.builder().enableLauncherDiscoveryListenerAutoRegistration(false).build(); - - assertFalse(config.isLauncherDiscoveryListenerAutoRegistrationEnabled()); - } - - @Test - void disableTestExecutionListenerAutoRegistration() { - var config = LauncherConfig.builder().enableTestExecutionListenerAutoRegistration(false).build(); - - assertFalse(config.isTestExecutionListenerAutoRegistrationEnabled()); - } - - @Test - void disablePostDiscoveryFilterAutoRegistration() { - var config = LauncherConfig.builder().enablePostDiscoveryFilterAutoRegistration(false).build(); - - assertFalse(config.isPostDiscoveryFilterAutoRegistrationEnabled()); - } - - @Test - void addTestEngines() { - TestEngine first = new TestEngineStub(); - TestEngine second = new TestEngineStub(); - - var config = LauncherConfig.builder().addTestEngines(first, second).build(); - - assertThat(config.getAdditionalTestEngines()).containsOnly(first, second); - } - - @Test - void addLauncherSessionListeners() { - var first = new LauncherSessionListener() { - }; - var second = new LauncherSessionListener() { - }; - - var config = LauncherConfig.builder().addLauncherSessionListeners(first, second).build(); - - assertThat(config.getAdditionalLauncherSessionListeners()).containsOnly(first, second); - } - - @Test - void addLauncherDiscoveryListeners() { - var first = new LauncherDiscoveryListener() { - }; - var second = new LauncherDiscoveryListener() { - }; - - var config = LauncherConfig.builder().addLauncherDiscoveryListeners(first, second).build(); - - assertThat(config.getAdditionalLauncherDiscoveryListeners()).containsOnly(first, second); - } - - @Test - void addTestExecutionListeners() { - var first = new TestExecutionListener() { - }; - var second = new TestExecutionListener() { - }; - - var config = LauncherConfig.builder().addTestExecutionListeners(first, second).build(); - - assertThat(config.getAdditionalTestExecutionListeners()).containsOnly(first, second); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java deleted file mode 100644 index 31154cfa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.launcher.listeners.SummaryGeneratingListener; - -/** - * Unit tests for {@link LauncherConfigurationParameters}. - * - * @since 1.0 - */ -@SuppressWarnings("deprecation") -class LauncherConfigurationParametersTests { - - private static final String CONFIG_FILE_NAME = "test-junit-platform.properties"; - private static final String KEY = LauncherConfigurationParametersTests.class.getName(); - private static final String INHERITED_PARAM = "parent config param"; - private static final String CONFIG_PARAM = "explicit config param"; - private static final String CONFIG_FILE = "from config file"; - private static final String SYSTEM_PROPERTY = "system property"; - - @BeforeEach - @AfterEach - void reset() { - System.clearProperty(KEY); - } - - @Test - void constructorPreconditions() { - assertThrows(PreconditionViolationException.class, () -> fromMap(null)); - assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), null)); - assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), "")); - assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), " ")); - } - - @Test - void getPreconditions() { - ConfigurationParameters configParams = fromMap(Map.of()); - assertThrows(PreconditionViolationException.class, () -> configParams.get(null)); - assertThrows(PreconditionViolationException.class, () -> configParams.get("")); - assertThrows(PreconditionViolationException.class, () -> configParams.get(" ")); - } - - @Test - void noConfigParams() { - ConfigurationParameters configParams = fromMap(Map.of()); - assertThat(configParams.size()).isEqualTo(0); - assertThat(configParams.get(KEY)).isEmpty(); - assertThat(configParams.keySet()).doesNotContain(KEY); - assertThat(configParams.toString()).doesNotContain(KEY); - } - - @Test - void explicitConfigParam() { - ConfigurationParameters configParams = fromMap(Map.of(KEY, CONFIG_PARAM)); - assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(CONFIG_PARAM); - } - - @Test - void systemProperty() { - System.setProperty(KEY, SYSTEM_PROPERTY); - ConfigurationParameters configParams = fromMap(Map.of()); - assertThat(configParams.get(KEY)).contains(SYSTEM_PROPERTY); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).doesNotContain(KEY); - } - - @Test - void configFile() { - ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); - assertThat(configParams.get(KEY)).contains(CONFIG_FILE); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(CONFIG_FILE); - } - - @Test - void inherited() { - ConfigurationParameters configParams = fromMapAndParent( // - Map.of(), // - Map.of(KEY, INHERITED_PARAM)); - assertThat(configParams.get(KEY)).contains(INHERITED_PARAM); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(KEY); - } - - @Test - void explicitConfigParamOverridesSystemProperty() { - System.setProperty(KEY, SYSTEM_PROPERTY); - ConfigurationParameters configParams = fromMap(Map.of(KEY, CONFIG_PARAM)); - assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(CONFIG_PARAM); - } - - @Test - void explicitConfigParamOverridesConfigFile() { - ConfigurationParameters configParams = fromMapAndFile(Map.of(KEY, CONFIG_PARAM), CONFIG_FILE_NAME); - assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(CONFIG_PARAM); - } - - @Test - void explicitConfigParamOverridesInheritedProperty() { - System.setProperty(KEY, SYSTEM_PROPERTY); - ConfigurationParameters configParams = fromMapAndParent( // - Map.of(KEY, CONFIG_PARAM), // - Map.of(KEY, INHERITED_PARAM)); - assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(CONFIG_PARAM); - } - - @Test - void systemPropertyOverridesConfigFile() { - System.setProperty(KEY, SYSTEM_PROPERTY); - ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); - assertThat(configParams.get(KEY)).contains(SYSTEM_PROPERTY); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(CONFIG_FILE); - } - - @Test - void inheritedPropertyOverridesSystemProperty() { - System.setProperty(KEY, SYSTEM_PROPERTY); - ConfigurationParameters configParams = fromMapAndParent(Map.of(), Map.of(KEY, INHERITED_PARAM)); - assertThat(configParams.get(KEY)).contains(INHERITED_PARAM); - assertThat(configParams.keySet()).contains(KEY); - assertThat(configParams.toString()).contains(KEY); - } - - @Test - void getValueInExtensionContext() { - var request = LauncherDiscoveryRequestBuilder.request() // - .configurationParameter("thing", "one else!") // - .selectors(DiscoverySelectors.selectClass(Something.class)).build(); - var summary = new SummaryGeneratingListener(); - LauncherFactory.create().execute(request, summary); - assertEquals(0, summary.getSummary().getTestsFailedCount()); - } - - @Test - void getWithSuccessfulTransformer() { - ConfigurationParameters configParams = fromMap(Map.of(KEY, "42")); - assertThat(configParams.get(KEY, Integer::valueOf)).contains(42); - } - - @Test - void getWithErroneousTransformer() { - ConfigurationParameters configParams = fromMap(Map.of(KEY, "42")); - var exception = assertThrows(JUnitException.class, () -> configParams.get(KEY, input -> { - throw new RuntimeException("foo"); - })); - assertThat(exception).hasMessageContaining( - "Failed to transform configuration parameter with key '" + KEY + "' and initial value '42'"); - } - - @Test - void ignoresSystemPropertyAndConfigFileWhenImplicitLookupsAreDisabled() { - System.setProperty(KEY, SYSTEM_PROPERTY); - ConfigurationParameters configParams = LauncherConfigurationParameters.builder() // - .enableImplicitProviders(false) // - .build(); - assertThat(configParams.get(KEY)).isEmpty(); - } - - private static LauncherConfigurationParameters fromMap(Map map) { - return LauncherConfigurationParameters.builder().explicitParameters(map).build(); - } - - private static LauncherConfigurationParameters fromMapAndFile(Map map, String configFileName) { - return LauncherConfigurationParameters.builder() // - .explicitParameters(map) // - .configFileName(configFileName) // - .build(); - } - - private static LauncherConfigurationParameters fromMapAndParent(Map map, - Map inherited) { - var parameters = LauncherConfigurationParameters.builder() // - .explicitParameters(inherited) // - .build(); - - return LauncherConfigurationParameters.builder() // - .explicitParameters(map) // - .parentConfigurationParameters(parameters) // - .build(); - } - - private static class Mutator implements TestInstancePostProcessor { - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { - var value = context.getConfigurationParameter("thing").orElse("thing"); - Something.class.getField("thing").set(testInstance, value); - } - } - - @ExtendWith(Mutator.class) - static class Something { - - // `public` is needed for simple "Class#getField(String)" to work - public String thing = "body."; - - @Test - void some() { - assertEquals("Someone else!", "Some" + thing); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java deleted file mode 100644 index f75ee101..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.engine.FilterResult.excluded; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.EngineFilter.includeEngines; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.abortOnFailure; -import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.logging; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.DiscoveryFilter; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.fakes.TestEngineStub; -import org.junit.platform.launcher.DiscoveryFilterStub; -import org.junit.platform.launcher.PostDiscoveryFilterStub; - -/** - * @since 1.0 - */ -class LauncherDiscoveryRequestBuilderTests { - - @Nested - class DiscoverySelectionTests { - - @Test - void modulesAreStoredInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .selectors( - selectModule("java.base") - ).build(); - // @formatter:on - - var packageSelectors = discoveryRequest.getSelectorsByType(ModuleSelector.class).stream().map( - ModuleSelector::getModuleName).collect(toList()); - assertThat(packageSelectors).contains("java.base"); - } - - @Test - void packagesAreStoredInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .selectors( - selectPackage("org.junit.platform.engine") - ).build(); - // @formatter:on - - var packageSelectors = discoveryRequest.getSelectorsByType(PackageSelector.class).stream().map( - PackageSelector::getPackageName).collect(toList()); - assertThat(packageSelectors).contains("org.junit.platform.engine"); - } - - @Test - void classesAreStoredInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .selectors( - selectClass(LauncherDiscoveryRequestBuilderTests.class.getName()), - selectClass(SampleTestClass.class) - ) - .build(); - // @formatter:on - - List> classes = discoveryRequest.getSelectorsByType(ClassSelector.class).stream().map( - ClassSelector::getJavaClass).collect(toList()); - assertThat(classes).contains(SampleTestClass.class, LauncherDiscoveryRequestBuilderTests.class); - } - - @Test - void methodsByFullyQualifiedNameAreStoredInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .selectors(selectMethod(fullyQualifiedMethodName())) - .build(); - // @formatter:on - - var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); - assertThat(methodSelectors).hasSize(1); - - var methodSelector = methodSelectors.get(0); - assertThat(methodSelector.getJavaClass()).isEqualTo(LauncherDiscoveryRequestBuilderTests.class); - assertThat(methodSelector.getJavaMethod()).isEqualTo(fullyQualifiedMethod()); - } - - @Test - void methodsByNameAreStoredInDiscoveryRequest() throws Exception { - Class testClass = SampleTestClass.class; - var testMethod = testClass.getDeclaredMethod("test"); - - // @formatter:off - var discoveryRequest = request() - .selectors(selectMethod(SampleTestClass.class.getName(), "test")) - .build(); - // @formatter:on - - var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); - assertThat(methodSelectors).hasSize(1); - - var methodSelector = methodSelectors.get(0); - assertThat(methodSelector.getJavaClass()).isEqualTo(testClass); - assertThat(methodSelector.getJavaMethod()).isEqualTo(testMethod); - } - - @Test - void methodsByClassAreStoredInDiscoveryRequest() throws Exception { - Class testClass = SampleTestClass.class; - var testMethod = testClass.getDeclaredMethod("test"); - - // @formatter:off - var discoveryRequest = (DefaultDiscoveryRequest) request() - .selectors( - selectMethod(testClass, "test") - ).build(); - // @formatter:on - - var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); - assertThat(methodSelectors).hasSize(1); - - var methodSelector = methodSelectors.get(0); - assertThat(methodSelector.getJavaClass()).isEqualTo(testClass); - assertThat(methodSelector.getJavaMethod()).isEqualTo(testMethod); - } - - @Test - void uniqueIdsAreStoredInDiscoveryRequest() { - var id1 = UniqueId.forEngine("engine").append("foo", "id1"); - var id2 = UniqueId.forEngine("engine").append("foo", "id2"); - - // @formatter:off - var discoveryRequest = request() - .selectors( - selectUniqueId(id1), - selectUniqueId(id2) - ).build(); - // @formatter:on - - var uniqueIds = discoveryRequest.getSelectorsByType(UniqueIdSelector.class).stream().map( - UniqueIdSelector::getUniqueId).map(Object::toString).collect(toList()); - - assertThat(uniqueIds).contains(id1.toString(), id2.toString()); - } - } - - @Nested - class DiscoveryFilterTests { - - @Test - void engineFiltersAreStoredInDiscoveryRequest() { - TestEngine engine1 = new TestEngineStub("engine1"); - TestEngine engine2 = new TestEngineStub("engine2"); - TestEngine engine3 = new TestEngineStub("engine3"); - - // @formatter:off - var discoveryRequest = request() - .filters(includeEngines(engine1.getId(), engine2.getId())) - .build(); - // @formatter:on - - var filters = discoveryRequest.getEngineFilters(); - assertThat(filters).hasSize(1); - var engineFilter = filters.get(0); - assertTrue(engineFilter.apply(engine1).included()); - assertTrue(engineFilter.apply(engine2).included()); - assertTrue(engineFilter.apply(engine3).excluded()); - } - - @Test - void discoveryFiltersAreStoredInDiscoveryRequest() { - var filter1 = new DiscoveryFilterStub<>("filter1"); - var filter2 = new DiscoveryFilterStub<>("filter2"); - // @formatter:off - var discoveryRequest = request() - .filters(filter1, filter2) - .build(); - // @formatter:on - - var filters = discoveryRequest.getFiltersByType(DiscoveryFilter.class); - assertThat(filters).containsOnly(filter1, filter2); - } - - @Test - void postDiscoveryFiltersAreStoredInDiscoveryRequest() { - var postFilter1 = new PostDiscoveryFilterStub("postFilter1"); - var postFilter2 = new PostDiscoveryFilterStub("postFilter2"); - // @formatter:off - var discoveryRequest = request() - .filters(postFilter1, postFilter2) - .build(); - // @formatter:on - - var filters = discoveryRequest.getPostDiscoveryFilters(); - assertThat(filters).containsOnly(postFilter1, postFilter2); - } - - @Test - void exceptionForIllegalFilterClass() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> request().filters(o -> excluded("reason"))); - - assertThat(exception).hasMessageStartingWith("Filter"); - assertThat(exception).hasMessageEndingWith( - "must implement EngineFilter, PostDiscoveryFilter, or DiscoveryFilter."); - } - } - - @Nested - class DiscoveryConfigurationParameterTests { - - @Test - void withoutConfigurationParametersSet_NoConfigurationParametersAreStoredInDiscoveryRequest() { - var discoveryRequest = request().build(); - - var configParams = discoveryRequest.getConfigurationParameters(); - assertThat(configParams.get("key")).isNotPresent(); - } - - @Test - void configurationParameterAddedDirectly_isStoredInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .configurationParameter("key", "value") - .build(); - // @formatter:on - - var configParams = discoveryRequest.getConfigurationParameters(); - assertThat(configParams.get("key")).contains("value"); - } - - @Test - void configurationParameterAddedDirectlyTwice_overridesPreviousValueInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .configurationParameter("key", "value") - .configurationParameter("key", "value-new") - .build(); - // @formatter:on - - var configParams = discoveryRequest.getConfigurationParameters(); - assertThat(configParams.get("key")).contains("value-new"); - } - - @Test - void multipleConfigurationParametersAddedDirectly_areStoredInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .configurationParameter("key1", "value1") - .configurationParameter("key2", "value2") - .build(); - // @formatter:on - - var configParams = discoveryRequest.getConfigurationParameters(); - assertThat(configParams.get("key1")).contains("value1"); - assertThat(configParams.get("key2")).contains("value2"); - } - - @Test - void configurationParameterAddedByMap_isStoredInDiscoveryRequest() { - // @formatter:off - var discoveryRequest = request() - .configurationParameters(Map.of("key", "value")) - .build(); - // @formatter:on - - var configParams = discoveryRequest.getConfigurationParameters(); - assertThat(configParams.get("key")).contains("value"); - } - - @Test - void multipleConfigurationParametersAddedByMap_areStoredInDiscoveryRequest() { - Map configurationParams = new HashMap<>(); - configurationParams.put("key1", "value1"); - configurationParams.put("key2", "value2"); - - // @formatter:off - var discoveryRequest = request() - .configurationParameters(configurationParams) - .build(); - // @formatter:on - - var configParams = discoveryRequest.getConfigurationParameters(); - assertThat(configParams.get("key1")).contains("value1"); - assertThat(configParams.get("key2")).contains("value2"); - } - } - - @Nested - class DiscoveryListenerTests { - - @Test - void usesAbortOnFailureByDefault() { - var request = request().build(); - - assertThat(request.getDiscoveryListener()).isEqualTo(abortOnFailure()); - } - - @Test - void onlyAddsAbortOnFailureOnce() { - var request = request() // - .listeners(abortOnFailure()) // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "abortOnFailure") // - .build(); - - assertThat(request.getDiscoveryListener()).isEqualTo(abortOnFailure()); - } - - @Test - void onlyAddsLoggingOnce() { - var request = request() // - .listeners(logging()) // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .build(); - - assertThat(request.getDiscoveryListener()).isEqualTo(logging()); - } - - @Test - void createsCompositeForMultipleListeners() { - var request = request() // - .listeners(logging(), abortOnFailure()) // - .build(); - - assertThat(request.getDiscoveryListener().getClass().getSimpleName()).startsWith("Composite"); - } - - } - - private static class SampleTestClass { - - @Test - void test() { - } - } - - private static String fullyQualifiedMethodName() { - return LauncherDiscoveryRequestBuilderTests.class.getName() + "#" + fullyQualifiedMethod().getName(); - } - - private static Method fullyQualifiedMethod() { - try { - return LauncherDiscoveryRequestBuilderTests.class.getDeclaredMethod("myTest"); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - void myTest() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java deleted file mode 100644 index d33d8895..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import java.net.URL; -import java.net.URLClassLoader; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.launcher.LauncherDiscoveryListener; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.LauncherSessionListener; -import org.junit.platform.launcher.TagFilter; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestLauncherDiscoveryListener; -import org.junit.platform.launcher.TestLauncherSessionListener; -import org.junit.platform.launcher.listeners.AnotherUnusedTestExecutionListener; -import org.junit.platform.launcher.listeners.NoopTestExecutionListener; -import org.junit.platform.launcher.listeners.UnusedTestExecutionListener; - -/** - * @since 1.0 - */ -class LauncherFactoryTests { - - @Test - void preconditions() { - assertThrows(PreconditionViolationException.class, () -> LauncherFactory.create(null)); - } - - @Test - void noopTestExecutionListenerIsLoadedViaServiceApi() { - withTestServices(() -> { - var launcher = (InternalLauncher) LauncherFactory.create(); - var listeners = launcher.getTestExecutionListenerRegistry().getListeners(); - var listener = listeners.stream().filter(NoopTestExecutionListener.class::isInstance).findFirst(); - assertThat(listener).isPresent(); - }); - } - - @Test - void unusedTestExecutionListenerIsNotLoadedViaServiceApi() { - withTestServices(() -> { - var launcher = (InternalLauncher) LauncherFactory.create(); - var listeners = launcher.getTestExecutionListenerRegistry().getListeners(); - - assertThat(listeners).filteredOn(AnotherUnusedTestExecutionListener.class::isInstance).isEmpty(); - assertThat(listeners).filteredOn(UnusedTestExecutionListener.class::isInstance).isEmpty(); - assertThat(listeners).filteredOn(NoopTestExecutionListener.class::isInstance).isNotEmpty(); - }); - } - - @Test - void create() { - var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); - - var testPlan = LauncherFactory.create().discover(discoveryRequest); - var roots = testPlan.getRoots(); - assertThat(roots).hasSize(3); - - // @formatter:off - var ids = roots.stream() - .map(TestIdentifier::getUniqueId) - .collect(toList()); - // @formatter:on - - assertThat(ids).containsOnly("[engine:junit-vintage]", "[engine:junit-jupiter]", - "[engine:junit-platform-suite]"); - } - - @Test - void createWithConfig() { - var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); - - var config = LauncherConfig.builder()// - .enableTestEngineAutoRegistration(false)// - .addTestEngines(new JupiterTestEngine())// - .build(); - - var testPlan = LauncherFactory.create(config).discover(discoveryRequest); - var roots = testPlan.getRoots(); - assertThat(roots).hasSize(1); - - // @formatter:off - var ids = roots.stream() - .map(TestIdentifier::getUniqueId) - .collect(toList()); - // @formatter:on - - assertThat(ids).containsOnly("[engine:junit-jupiter]"); - } - - @Test - void createWithPostDiscoveryFilters() { - var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); - - var config = LauncherConfig.builder()// - .addPostDiscoveryFilters(TagFilter.includeTags("test-post-discovery")).build(); - - var testPlan = LauncherFactory.create(config).discover(discoveryRequest); - final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); - assertThat(vintage).isEmpty(); - - final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); - assertThat(jupiter).hasSize(1); - } - - @Test - void applyPostDiscoveryFiltersViaServiceApi() { - final var current = Thread.currentThread().getContextClassLoader(); - try { - var url = getClass().getClassLoader().getResource("testservices/"); - var classLoader = new URLClassLoader(new URL[] { url }, current); - Thread.currentThread().setContextClassLoader(classLoader); - - var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); - - var config = LauncherConfig.builder()// - .build(); - - var testPlan = LauncherFactory.create(config).discover(discoveryRequest); - final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); - assertThat(vintage).isEmpty(); - - final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); - assertThat(jupiter).hasSize(1); - } - finally { - Thread.currentThread().setContextClassLoader(current); - } - } - - @Test - void notApplyIfDisabledPostDiscoveryFiltersViaServiceApi() { - final var current = Thread.currentThread().getContextClassLoader(); - try { - var url = getClass().getClassLoader().getResource("testservices/"); - var classLoader = new URLClassLoader(new URL[] { url }, current); - Thread.currentThread().setContextClassLoader(classLoader); - - var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); - - var config = LauncherConfig.builder()// - .enablePostDiscoveryFilterAutoRegistration(false).build(); - - var testPlan = LauncherFactory.create(config).discover(discoveryRequest); - final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); - assertThat(vintage).hasSize(1); - - final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); - assertThat(jupiter).hasSize(1); - } - finally { - Thread.currentThread().setContextClassLoader(current); - } - } - - @Test - void doesNotDiscoverLauncherDiscoverRequestListenerViaServiceApiWhenDisabled() { - withTestServices(() -> { - var launcher = (InternalLauncher) LauncherFactory.create( - LauncherConfig.builder().enableLauncherDiscoveryListenerAutoRegistration(false).build()); - var launcherDiscoveryListener = launcher.getLauncherDiscoveryListenerRegistry().getCompositeListener(); - - assertThat(launcherDiscoveryListener).isSameAs(LauncherDiscoveryListener.NOOP); - }); - } - - @Test - void discoversLauncherDiscoverRequestListenerViaServiceApiByDefault() { - withTestServices(() -> { - var launcher = (InternalLauncher) LauncherFactory.create(); - var launcherDiscoveryListener = launcher.getLauncherDiscoveryListenerRegistry().getCompositeListener(); - - assertThat(launcherDiscoveryListener.getClass().getSimpleName()).startsWith("Composite"); - assertThat(launcherDiscoveryListener).extracting("listeners").asList() // - .contains(new TestLauncherDiscoveryListener()); - }); - } - - @Test - void doesNotDiscoverLauncherSessionListenerViaServiceApiWhenDisabled() { - withTestServices(() -> { - var session = (DefaultLauncherSession) LauncherFactory.openSession( - LauncherConfig.builder().enableLauncherSessionListenerAutoRegistration(false).build()); - - assertThat(session.getListener()).isSameAs(LauncherSessionListener.NOOP); - }); - } - - @Test - void discoversLauncherSessionListenerViaServiceApiByDefault() { - withTestServices(() -> { - var session = (DefaultLauncherSession) LauncherFactory.openSession(); - - assertThat(session.getListener()).isEqualTo(new TestLauncherSessionListener()); - }); - } - - private static void withTestServices(Runnable runnable) { - var current = Thread.currentThread().getContextClassLoader(); - try { - var url = LauncherFactoryTests.class.getClassLoader().getResource("testservices/"); - var classLoader = new URLClassLoader(new URL[] { url }, current); - Thread.currentThread().setContextClassLoader(classLoader); - runnable.run(); - } - finally { - Thread.currentThread().setContextClassLoader(current); - } - } - - private LauncherDiscoveryRequest createLauncherDiscoveryRequestForBothStandardEngineExampleClasses() { - // @formatter:off - return request() - .selectors(selectClass(JUnit4Example.class)) - .selectors(selectClass(JUnit5Example.class)) - .build(); - // @formatter:on - } - - public static class JUnit4Example { - - @org.junit.Test - public void testJ4() { - } - - } - - static class JUnit5Example { - - @Tag("test-post-discovery") - @Test - void testJ5() { - } - - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java deleted file mode 100644 index 9cc2063d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncherConfigBuilderWithDisabledServiceLoading; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.fakes.TestEngineStub; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.LauncherSession; -import org.junit.platform.launcher.LauncherSessionListener; -import org.mockito.ArgumentCaptor; - -class LauncherSessionTests { - - LauncherSessionListener firstSessionListener = mock(LauncherSessionListener.class); - LauncherSessionListener secondSessionListener = mock(LauncherSessionListener.class); - LauncherConfig launcherConfig = createLauncherConfigBuilderWithDisabledServiceLoading() // - .addLauncherSessionListeners(firstSessionListener, secondSessionListener) // - .addTestEngines(new TestEngineStub()) // - .build(); - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request().build(); - - @Test - void callsRegisteredListenersWhenLauncherIsUsedDirectly() { - var launcher = LauncherFactory.create(launcherConfig); - - var testPlan = launcher.discover(request); - - var inOrder = inOrder(firstSessionListener, secondSessionListener); - var launcherSession = ArgumentCaptor.forClass(LauncherSession.class); - inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); - inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); - inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); - inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); - - launcher.execute(testPlan); - - inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); - inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); - inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); - inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); - - launcher.execute(request); - - inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); - inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); - inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); - inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); - } - - @Test - void callsRegisteredListenersWhenLauncherIsUsedViaSession() { - @SuppressWarnings("resource") - var session = LauncherFactory.openSession(launcherConfig); - var launcher = session.getLauncher(); - - var inOrder = inOrder(firstSessionListener, secondSessionListener); - inOrder.verify(firstSessionListener).launcherSessionOpened(session); - inOrder.verify(secondSessionListener).launcherSessionOpened(session); - verifyNoMoreInteractions(firstSessionListener, secondSessionListener); - - var testPlan = launcher.discover(request); - - verifyNoMoreInteractions(firstSessionListener, secondSessionListener); - - launcher.execute(testPlan); - - verifyNoMoreInteractions(firstSessionListener, secondSessionListener); - - launcher.execute(request); - - verifyNoMoreInteractions(firstSessionListener, secondSessionListener); - - session.close(); - - inOrder.verify(firstSessionListener).launcherSessionClosed(session); - inOrder.verify(secondSessionListener).launcherSessionClosed(session); - verifyNoMoreInteractions(firstSessionListener, secondSessionListener); - } - - @Test - void closedSessionCannotBeUsed() { - @SuppressWarnings("resource") - var session = LauncherFactory.openSession(launcherConfig); - var launcher = session.getLauncher(); - var testPlan = launcher.discover(request); - - session.close(); - - assertThrows(PreconditionViolationException.class, () -> launcher.discover(request)); - assertThrows(PreconditionViolationException.class, () -> launcher.execute(testPlan)); - assertThrows(PreconditionViolationException.class, () -> launcher.execute(request)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java deleted file mode 100644 index 664076d2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; - -public class ListenerRegistryTests { - - @Test - void registerWithNullArray() { - var registry = ListenerRegistry.create(l -> l.get(0)); - - var exception = assertThrows(PreconditionViolationException.class, () -> registry.addAll((Object[]) null)); - - assertThat(exception).hasMessageContaining("listeners array must not be null or empty"); - } - - @Test - void registerWithEmptyArray() { - var registry = ListenerRegistry.create(l -> l.get(0)); - - var exception = assertThrows(PreconditionViolationException.class, registry::addAll); - - assertThat(exception).hasMessageContaining("listeners array must not be null or empty"); - } - - @Test - void registerWithArrayContainingNullElements() { - var registry = ListenerRegistry.create(l -> l.get(0)); - - var exception = assertThrows(PreconditionViolationException.class, - () -> registry.addAll(new Object[] { null })); - - assertThat(exception).hasMessageContaining("individual listeners must not be null"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java deleted file mode 100644 index cd70637e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; -import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; -import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; -import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; -import org.junit.platform.launcher.LauncherConstants; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.mockito.ArgumentCaptor; - -/** - * @since 1.3 - */ -class StreamInterceptingTestExecutionListenerIntegrationTests { - - @ParameterizedTest(name = "{0}") - @MethodSource("systemStreams") - @ExtendWith(HiddenSystemOutAndErr.class) - void interceptsStream(String configParam, Supplier printStreamSupplier, String reportKey) { - var engine = new DemoHierarchicalTestEngine("engine"); - TestDescriptor test = engine.addTest("test", () -> printStreamSupplier.get().print("4567890")); - var listener = mock(TestExecutionListener.class); - doAnswer(invocation -> { - TestIdentifier testIdentifier = invocation.getArgument(0); - if (testIdentifier.getUniqueIdObject().equals(test.getUniqueId())) { - printStreamSupplier.get().print("123"); - } - return null; - }).when(listener).executionStarted(any()); - - var launcher = createLauncher(engine); - var discoveryRequest = request()// - .selectors(selectUniqueId(test.getUniqueId()))// - .configurationParameter(configParam, String.valueOf(true))// - .configurationParameter(LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME, String.valueOf(5))// - .build(); - launcher.execute(discoveryRequest, listener); - - var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); - var inOrder = inOrder(listener); - inOrder.verify(listener).testPlanExecutionStarted(testPlanArgumentCaptor.capture()); - var testPlan = testPlanArgumentCaptor.getValue(); - var testIdentifier = testPlan.getTestIdentifier(test.getUniqueId()); - - var reportEntryArgumentCaptor = ArgumentCaptor.forClass(ReportEntry.class); - inOrder.verify(listener).reportingEntryPublished(same(testIdentifier), reportEntryArgumentCaptor.capture()); - inOrder.verify(listener).executionFinished(testIdentifier, successful()); - var reportEntry = reportEntryArgumentCaptor.getValue(); - - assertThat(reportEntry.getKeyValuePairs()).containsExactly(entry(reportKey, "12345")); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("systemStreams") - @ExtendWith(HiddenSystemOutAndErr.class) - void doesNotInterceptStreamWhenAlreadyBeingIntercepted(String configParam, - Supplier printStreamSupplier) { - var engine = new DemoHierarchicalTestEngine("engine"); - TestDescriptor test = engine.addTest("test", () -> printStreamSupplier.get().print("1234567890")); - - assertThat(StreamInterceptor.registerStdout(1)).isPresent(); - assertThat(StreamInterceptor.registerStderr(1)).isPresent(); - - var launcher = createLauncher(engine); - var discoveryRequest = request()// - .selectors(selectUniqueId(test.getUniqueId()))// - .configurationParameter(configParam, String.valueOf(true))// - .build(); - var listener = mock(TestExecutionListener.class); - launcher.execute(discoveryRequest, listener); - - verify(listener, never()).reportingEntryPublished(any(), any()); - } - - @SuppressWarnings("unused") // used via @MethodSource("systemStreams") - private static Stream systemStreams() { - return Stream.of(// - streamType(CAPTURE_STDOUT_PROPERTY_NAME, () -> System.out, STDOUT_REPORT_ENTRY_KEY), // - streamType(CAPTURE_STDERR_PROPERTY_NAME, () -> System.err, STDERR_REPORT_ENTRY_KEY)); - } - - private static Arguments streamType(String configParam, Supplier printStreamSupplier, - String reportKey) { - return arguments(configParam, printStreamSupplier, reportKey); - } - - static class HiddenSystemOutAndErr implements BeforeTestExecutionCallback, AfterTestExecutionCallback { - - private static final Namespace NAMESPACE = Namespace.create(HiddenSystemOutAndErr.class); - - @Override - public void beforeTestExecution(ExtensionContext context) { - var store = context.getStore(NAMESPACE); - store.put("out", System.out); - store.put("err", System.err); - System.setOut(new PrintStream(new ByteArrayOutputStream())); - System.setErr(new PrintStream(new ByteArrayOutputStream())); - } - - @Override - public void afterTestExecution(ExtensionContext context) { - var store = context.getStore(NAMESPACE); - System.setOut(store.get("out", PrintStream.class)); - System.setErr(store.get("err", PrintStream.class)); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java deleted file mode 100644 index c3ba0f96..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.core; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertSame; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.stream.IntStream; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.3 - */ -class StreamInterceptorTests { - - private ByteArrayOutputStream originalOut = new ByteArrayOutputStream(); - private PrintStream targetStream = new PrintStream(originalOut); - - @Test - void interceptsWriteOperationsToStreamPerThread() { - var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, - 3).orElseThrow(RuntimeException::new); - // @formatter:off - IntStream.range(0, 1000) - .parallel() - .peek(i -> targetStream.println(i)) - .mapToObj(String::valueOf) - .peek(i -> streamInterceptor.capture()) - .peek(i -> targetStream.println(i)) - .forEach(i -> assertEquals(i, streamInterceptor.consume().trim())); - // @formatter:on - } - - @Test - void unregisterRestoresOriginalStream() { - var originalStream = targetStream; - - var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, - 3).orElseThrow(RuntimeException::new); - assertSame(streamInterceptor, targetStream); - - streamInterceptor.unregister(); - assertSame(originalStream, targetStream); - } - - @Test - void writeForwardsOperationsToOriginalStream() throws IOException { - var originalStream = targetStream; - - StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 2).orElseThrow( - RuntimeException::new); - assertNotSame(originalStream, targetStream); - - targetStream.write('a'); - targetStream.write("b".getBytes()); - targetStream.write("c".getBytes(), 0, 1); - assertEquals("abc", originalOut.toString()); - } - - @Test - void handlesNestedCaptures() { - var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, - 100).orElseThrow(RuntimeException::new); - - String outermost, inner, innermost; - - streamInterceptor.capture(); - streamInterceptor.print("before outermost - "); - { - streamInterceptor.capture(); - streamInterceptor.print("before inner - "); - { - streamInterceptor.capture(); - streamInterceptor.print("innermost"); - innermost = streamInterceptor.consume(); - } - streamInterceptor.print("after inner"); - inner = streamInterceptor.consume(); - } - streamInterceptor.print("after outermost"); - outermost = streamInterceptor.consume(); - - assertAll(// - () -> assertEquals("before outermost - after outermost", outermost), // - () -> assertEquals("before inner - after inner", inner), // - () -> assertEquals("innermost", innermost) // - ); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java deleted file mode 100644 index c3d6cfac..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import org.junit.platform.launcher.TestExecutionListener; - -public class AnotherUnusedTestExecutionListener implements TestExecutionListener { - // empty on purpose -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java deleted file mode 100644 index d471e6e7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import org.junit.platform.launcher.TestExecutionListener; - -/** - * @since 1.0 - */ -public class NoopTestExecutionListener implements TestExecutionListener { - // empty on purpose -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java deleted file mode 100644 index a876c5bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -class OutputDirTests { - - @Test - void getOutputDirUsesCustomOutputDir() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - Path outputDir = OutputDir.create(Optional.of(customDir)).toPath(); - assertThat(Files.isSameFile(Paths.get(customDir), outputDir)).isTrue(); - assertThat(outputDir).exists(); - } - - @Test - void getOutputDirFallsBackToCurrentWorkingDir() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking"; - String expected = cwd; - - assertOutputDirIsDetected(cwd, expected); - } - - @Test - void getOutputDirDetectsMavenPom() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/maven"; - String expected = cwd + "/target"; - - assertOutputDirIsDetected(cwd, expected); - } - - @Test - void getOutputDirDetectsGradleGroovyDefaultBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy"; - String expected = cwd + "/build"; - - assertOutputDirIsDetected(cwd, expected); - } - - @Test - void getOutputDirDetectsGradleGroovyCustomBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy/sub-project"; - String expected = cwd + "/build"; - - assertOutputDirIsDetected(cwd, expected); - } - - @Test - void getOutputDirDetectsGradleKotlinDefaultBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin"; - String expected = cwd + "/build"; - - assertOutputDirIsDetected(cwd, expected); - } - - @Test - void getOutputDirDetectsGradleKotlinCustomBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project"; - String expected = cwd + "/build"; - - assertOutputDirIsDetected(cwd, expected); - } - - private void assertOutputDirIsDetected(String cwd, String expected) throws IOException { - Path outputDir = OutputDir.createSafely(Optional.empty(), () -> Paths.get(cwd)).toPath(); - assertThat(Files.isSameFile(Paths.get(expected), outputDir)).isTrue(); - assertThat(outputDir).exists(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java deleted file mode 100644 index 7c947bf1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; -import static org.mockito.Mockito.mock; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.0 - */ -class SummaryGenerationTests { - - private final SummaryGeneratingListener listener = new SummaryGeneratingListener(); - private final TestPlan testPlan = TestPlan.from(List.of(), mock()); - - @Test - void emptyReport() { - listener.testPlanExecutionStarted(testPlan); - listener.testPlanExecutionFinished(testPlan); - - assertEquals(0, listener.getSummary().getTestsFailedCount()); - - var summaryString = summaryAsString(); - assertAll("summary", // - () -> assertTrue(summaryString.contains("Test run finished after"), "test run"), // - - () -> assertTrue(summaryString.contains("0 containers found"), "containers found"), // - () -> assertTrue(summaryString.contains("0 containers skipped"), "containers skipped"), // - () -> assertTrue(summaryString.contains("0 containers started"), "containers started"), // - () -> assertTrue(summaryString.contains("0 containers aborted"), "containers aborted"), // - () -> assertTrue(summaryString.contains("0 containers successful"), "containers successful"), // - () -> assertTrue(summaryString.contains("0 containers failed"), "containers failed"), // - - () -> assertTrue(summaryString.contains("0 tests found"), "tests found"), // - () -> assertTrue(summaryString.contains("0 tests skipped"), "tests skipped"), // - () -> assertTrue(summaryString.contains("0 tests started"), "tests started"), // - () -> assertTrue(summaryString.contains("0 tests aborted"), "tests aborted"), // - () -> assertTrue(summaryString.contains("0 tests successful"), "tests successful"), // - () -> assertTrue(summaryString.contains("0 tests failed"), "tests failed") // - ); - - assertEquals("", failuresAsString()); - } - - @Test - void reportingCorrectCounts() { - var successfulContainer = createContainerIdentifier("c1"); - var failedContainer = createContainerIdentifier("c2"); - var abortedContainer = createContainerIdentifier("c3"); - var skippedContainer = createContainerIdentifier("c4"); - - var successfulTest = createTestIdentifier("t1"); - var failedTest = createTestIdentifier("t2"); - var abortedTest = createTestIdentifier("t3"); - var skippedTest = createTestIdentifier("t4"); - - listener.testPlanExecutionStarted(testPlan); - - listener.executionSkipped(skippedContainer, "skipped"); - listener.executionSkipped(skippedTest, "skipped"); - - listener.executionStarted(successfulContainer); - listener.executionFinished(successfulContainer, TestExecutionResult.successful()); - - listener.executionStarted(successfulTest); - listener.executionFinished(successfulTest, TestExecutionResult.successful()); - - listener.executionStarted(failedContainer); - listener.executionFinished(failedContainer, TestExecutionResult.failed(new RuntimeException("failed"))); - - listener.executionStarted(failedTest); - listener.executionFinished(failedTest, TestExecutionResult.failed(new RuntimeException("failed"))); - - listener.executionStarted(abortedContainer); - listener.executionFinished(abortedContainer, TestExecutionResult.aborted(new RuntimeException("aborted"))); - - listener.executionStarted(abortedTest); - listener.executionFinished(abortedTest, TestExecutionResult.aborted(new RuntimeException("aborted"))); - - listener.testPlanExecutionFinished(testPlan); - - var summaryString = summaryAsString(); - try { - assertAll("summary", // - () -> assertTrue(summaryString.contains("4 containers found"), "containers found"), // - () -> assertTrue(summaryString.contains("1 containers skipped"), "containers skipped"), // - () -> assertTrue(summaryString.contains("3 containers started"), "containers started"), // - () -> assertTrue(summaryString.contains("1 containers aborted"), "containers aborted"), // - () -> assertTrue(summaryString.contains("1 containers successful"), "containers successful"), // - () -> assertTrue(summaryString.contains("1 containers failed"), "containers failed"), // - - () -> assertTrue(summaryString.contains("4 tests found"), "tests found"), // - () -> assertTrue(summaryString.contains("1 tests skipped"), "tests skipped"), // - () -> assertTrue(summaryString.contains("3 tests started"), "tests started"), // - () -> assertTrue(summaryString.contains("1 tests aborted"), "tests aborted"), // - () -> assertTrue(summaryString.contains("1 tests successful"), "tests successful"), // - () -> assertTrue(summaryString.contains("1 tests failed"), "tests failed") // - ); - } - catch (AssertionError error) { - System.err.println(summaryString); - throw error; - } - } - - @Test - void canGetListOfFailures() { - var failedException = new RuntimeException("Pow!"); - var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "1"), "failingTest") { - - @Override - public Optional getSource() { - return Optional.of(ClassSource.from(Object.class)); - } - }; - var failingTest = TestIdentifier.from(testDescriptor); - listener.testPlanExecutionStarted(testPlan); - listener.executionStarted(failingTest); - listener.executionFinished(failingTest, TestExecutionResult.failed(failedException)); - listener.testPlanExecutionFinished(testPlan); - final var failures = listener.getSummary().getFailures(); - assertThat(failures).hasSize(1); - assertThat(failures.get(0).getException()).isEqualTo(failedException); - assertThat(failures.get(0).getTestIdentifier()).isEqualTo(failingTest); - } - - @Test - void reportingCorrectFailures() { - var iaeCausedBy = new IllegalArgumentException("Illegal Argument Exception"); - var failedException = new RuntimeException("Runtime Exception", iaeCausedBy); - var npeSuppressed = new NullPointerException("Null Pointer Exception"); - failedException.addSuppressed(npeSuppressed); - - var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { - - @Override - public Optional getSource() { - return Optional.of(ClassSource.from(Object.class)); - } - }; - var failed = TestIdentifier.from(testDescriptor); - var aborted = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("root", "3"), "abortedTest")); - - listener.testPlanExecutionStarted(testPlan); - listener.executionStarted(failed); - listener.executionFinished(failed, TestExecutionResult.failed(failedException)); - listener.executionStarted(aborted); - listener.executionFinished(aborted, TestExecutionResult.aborted(new RuntimeException("aborted"))); - listener.testPlanExecutionFinished(testPlan); - - // An aborted test is not a failure - assertEquals(1, listener.getSummary().getTestsFailedCount()); - - var failuresString = failuresAsString(); - assertAll("failures", // - () -> assertTrue(failuresString.contains("Failures (1)"), "test failures"), // - () -> assertTrue(failuresString.contains(Object.class.getName()), "source"), // - () -> assertTrue(failuresString.contains("failingTest"), "display name"), // - () -> assertTrue(failuresString.contains("=> " + failedException), "main exception"), // - () -> assertTrue(failuresString.contains("Caused by: " + iaeCausedBy), "Caused by exception"), // - () -> assertTrue(failuresString.contains("Suppressed: " + npeSuppressed), "Suppressed exception") // - ); - } - - @Test - public void reportingCircularFailure() { - var iaeCausedBy = new IllegalArgumentException("Illegal Argument Exception"); - var failedException = new RuntimeException("Runtime Exception", iaeCausedBy); - var npeSuppressed = new NullPointerException("Null Pointer Exception"); - failedException.addSuppressed(npeSuppressed); - npeSuppressed.addSuppressed(iaeCausedBy); - - var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { - - @Override - public Optional getSource() { - return Optional.of(ClassSource.from(Object.class)); - } - }; - var failed = TestIdentifier.from(testDescriptor); - - listener.testPlanExecutionStarted(testPlan); - listener.executionStarted(failed); - listener.executionFinished(failed, TestExecutionResult.failed(failedException)); - listener.testPlanExecutionFinished(testPlan); - - assertEquals(1, listener.getSummary().getTestsFailedCount()); - - var failuresString = failuresAsString(); - assertAll("failures", // - () -> assertTrue(failuresString.contains("Suppressed: " + npeSuppressed), "Suppressed exception"), // - () -> assertTrue(failuresString.contains("Circular reference: " + iaeCausedBy), "Circular reference"), // - () -> assertFalse(failuresString.contains("Caused by: "), - "'Caused by: ' omitted because of Circular reference") // - ); - } - - @RepeatedTest(10) - void reportingConcurrentlyFinishedTests() throws Exception { - var numThreads = 250; - var testIdentifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { - @Override - public Optional getSource() { - return Optional.of(ClassSource.from(Object.class)); - } - }); - var result = TestExecutionResult.failed(new RuntimeException()); - - listener.testPlanExecutionStarted(testPlan); - executeConcurrently(numThreads, () -> { - listener.executionStarted(testIdentifier); - listener.executionFinished(testIdentifier, result); - }); - listener.testPlanExecutionFinished(testPlan); - - assertThat(listener.getSummary().getFailures()).hasSize(numThreads); - } - - private TestIdentifier createTestIdentifier(String uniqueId) { - var identifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("test", uniqueId), uniqueId)); - testPlan.addInternal(identifier); - return identifier; - } - - private TestIdentifier createContainerIdentifier(String uniqueId) { - var identifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("container", uniqueId), uniqueId) { - - @Override - public Type getType() { - return Type.CONTAINER; - } - }); - testPlan.addInternal(identifier); - return identifier; - } - - private String summaryAsString() { - var summaryWriter = new StringWriter(); - listener.getSummary().printTo(new PrintWriter(summaryWriter)); - return summaryWriter.toString(); - } - - private String failuresAsString() { - var failuresWriter = new StringWriter(); - listener.getSummary().printFailuresTo(new PrintWriter(failuresWriter)); - return failuresWriter.toString(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java deleted file mode 100644 index 86943a9c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.launcher.EngineFilter.includeEngines; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX; -import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.LISTENER_ENABLED_PROPERTY_NAME; -import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME; -import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME; -import static org.junit.platform.testkit.engine.Event.byTestDescriptor; -import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.stream.Stream; - -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.core.LauncherFactory; -import org.junit.platform.testkit.engine.EngineTestKit; -import org.junit.platform.testkit.engine.Event; -import org.opentest4j.AssertionFailedError; -import org.opentest4j.TestAbortedException; - -/** - * Integration tests for the {@link UniqueIdTrackingListener}. - * - * @since 1.8 - */ -class UniqueIdTrackingListenerIntegrationTests { - - private static final String passingTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:passingTest()]"; - private static final String skippedTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:skippedTest()]"; - private static final String abortedTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:abortedTest()]"; - private static final String failingTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:failingTest()]"; - private static final String dynamicTest1 = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[test-factory:dynamicTests()]/[dynamic-test:#1]"; - private static final String dynamicTest2 = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[test-factory:dynamicTests()]/[dynamic-test:#2]"; - private static final String testA = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase2]/[method:testA()]"; - private static final String testB = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase2]/[method:testB()]"; - private static final String testC = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase3]/[method:testC()]"; - private static final String testD = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase3]/[method:testD()]"; - private static final String testE = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase4]/[method:testE()]"; - private static final String testF = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase4]/[method:testF()]"; - - private static final String[] expectedUniqueIds = { passingTest, skippedTest, abortedTest, failingTest, - dynamicTest1, dynamicTest2, testA, testB }; - - private static final String[] expectedConcurrentUniqueIds = { testA, testB, testC, testD, testE, testF }; - - @Test - void confirmExpectedUniqueIdsViaEngineTestKit() { - // @formatter:off - EngineTestKit.engine("junit-jupiter") - .selectors(selectClasses()) - .execute() - .testEvents() - .assertStatistics(stats -> stats.started(7).skipped(1).aborted(1).succeeded(5).failed(1)) - .assertEventsMatchLoosely( - event(test(uniqueId(passingTest)), finishedSuccessfully()), - event(test(uniqueId(abortedTest)), abortedWithReason(instanceOf(TestAbortedException.class))), - event(test(uniqueId(failingTest)), finishedWithFailure(instanceOf(AssertionFailedError.class))), - event(test(uniqueId(dynamicTest1)), finishedSuccessfully()), - event(test(uniqueId(dynamicTest2)), finishedSuccessfully()), - event(test(uniqueId(testA)), finishedSuccessfully()), - event(test(uniqueId(testB)), finishedSuccessfully()) - ); - // @formatter:on - } - - private Condition uniqueId(String uniqueId) { - return new Condition<>( - byTestDescriptor(where(TestDescriptor::getUniqueId, uid -> uid.toString().equals(uniqueId))), - "descriptor with uniqueId '%s'", uniqueId); - } - - @Test - void listenerIsRegisteredButDisabledByDefault() throws Exception { - long numListenersRegistered = ServiceLoader.load(TestExecutionListener.class).stream()// - .filter(provider -> UniqueIdTrackingListener.class.equals(provider.type()))// - .count(); - assertThat(numListenersRegistered).isEqualTo(1); - - String outputDir = "build"; - String prefix = DEFAULT_OUTPUT_FILE_PREFIX; - - deleteFiles(outputDir, prefix); - - try { - List actualUniqueIds = executeTests(Map.of()); - - // Sanity check using the results of our local TestExecutionListener - assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); - - // Check that files were not generated by the UniqueIdTrackingListener - assertThat(findFiles(outputDir, prefix)).isEmpty(); - } - finally { - deleteFiles(outputDir, prefix); - } - } - - @Test - void verifyUniqueIdsAreTrackedWithDefaults() throws Exception { - verifyUniqueIdsAreTracked("build", DEFAULT_OUTPUT_FILE_PREFIX, Map.of()); - } - - @Test - void verifyUniqueIdsAreTrackedWithCustomOutputFile() throws Exception { - String customPrefix = "test_ids"; - verifyUniqueIdsAreTracked("build", customPrefix, Map.of(OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); - } - - @Test - void verifyUniqueIdsAreTrackedWithCustomOutputDir() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - verifyUniqueIdsAreTracked(customDir, DEFAULT_OUTPUT_FILE_PREFIX, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir)); - } - - @Test - void verifyUniqueIdsAreTrackedWithCustomOutputFileAndCustomOutputDir() throws Exception { - String customPrefix = "test_ids"; - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - - verifyUniqueIdsAreTracked(customDir, customPrefix, - Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir, OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); - } - - private void verifyUniqueIdsAreTracked(String outputDir, String prefix, Map configurationParameters) - throws IOException { - - configurationParameters = new HashMap<>(configurationParameters); - configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); - - deleteFiles(outputDir, prefix); - - try { - List actualUniqueIds = executeTests(configurationParameters); - - // Sanity check using the results of our local TestExecutionListener - assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); - - // Check contents of the file (or files) generated by the UniqueIdTrackingListener - assertThat(readAllFiles(outputDir, prefix)).containsExactlyInAnyOrder(expectedUniqueIds); - } - finally { - deleteFiles(outputDir, prefix); - } - } - - @Test - void verifyUniqueIdsAreTrackedWithConcurrentlyExecutingTestPlans() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - String prefix = DEFAULT_OUTPUT_FILE_PREFIX; - - Map configurationParameters = new HashMap<>(); - configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); - configurationParameters.put(OUTPUT_DIR_PROPERTY_NAME, customDir); - - deleteFiles(customDir, prefix); - - try { - Stream.of(TestCase2.class, TestCase3.class, TestCase4.class).parallel()// - .forEach(clazz -> executeTests(configurationParameters, selectClass(clazz))); - - // 3 output files should have been generated. - assertThat(findFiles(customDir, prefix)).hasSize(3); - - // Check contents of the file (or files) generated by the UniqueIdTrackingListener - assertThat(readAllFiles(customDir, prefix)).containsExactlyInAnyOrder(expectedConcurrentUniqueIds); - } - finally { - deleteFiles(customDir, prefix); - } - } - - private static List executeTests(Map configurationParameters) { - return executeTests(configurationParameters, selectClasses()); - } - - private static List executeTests(Map configurationParameters, - ClassSelector... classSelectors) { - List uniqueIds = new ArrayList<>(); - LauncherDiscoveryRequest request = request()// - .selectors(classSelectors)// - .filters(includeEngines("junit-jupiter"))// - .configurationParameters(configurationParameters)// - .build(); - LauncherFactory.create().execute(request, new TestExecutionListener() { - - @Override - public void executionSkipped(TestIdentifier testIdentifier, String reason) { - if (testIdentifier.isTest()) { - uniqueIds.add(testIdentifier.getUniqueId()); - } - } - - @Override - public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { - if (testIdentifier.isTest()) { - uniqueIds.add(testIdentifier.getUniqueId()); - } - } - }); - return uniqueIds; - } - - private static ClassSelector[] selectClasses() { - return new ClassSelector[] { selectClass(TestCase1.class), selectClass(TestCase2.class) }; - } - - private static Stream findFiles(String dir, String prefix) throws IOException { - Path outputDir = Paths.get(dir); - if (!Files.exists(outputDir)) { - return Stream.empty(); - } - return Files.find(outputDir, 1, // - (path, basicFileAttributes) -> (basicFileAttributes.isRegularFile() - && path.getFileName().toString().startsWith(prefix))); - } - - private void deleteFiles(String outputDir, String prefix) throws IOException { - findFiles(outputDir, prefix).forEach(file -> { - try { - Files.deleteIfExists(file); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - }); - } - - private Stream readAllFiles(String outputDir, String prefix) throws IOException { - return findFiles(outputDir, prefix).map(outputFile -> { - try { - return Files.readAllLines(outputFile); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - }).flatMap(List::stream); - } - - // ------------------------------------------------------------------------- - - static class TestCase1 { - - @Test - void passingTest() { - } - - @Test - @Disabled("testing") - void skippedTest() { - } - - @Test - void abortedTest() { - assumeTrue(false); - } - - @Test - void failingTest() { - fail(); - } - - @TestFactory - Stream dynamicTests() { - return Stream.of("cat", "dog").map(text -> dynamicTest(text, () -> assertEquals(3, text.length()))); - } - } - - static class TestCase2 { - - @Test - void testA() { - } - - @Test - void testB() { - } - } - - static class TestCase3 { - - @Test - void testC() { - } - - @Test - void testD() { - } - } - - static class TestCase4 { - - @Test - void testE() { - } - - @Test - void testF() { - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java deleted file mode 100644 index cbf7e6f9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners; - -import org.junit.platform.launcher.TestExecutionListener; - -public class UnusedTestExecutionListener implements TestExecutionListener { - // empty on purpose -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java deleted file mode 100644 index 0ee89659..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; -import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.abortOnFailure; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.JUnitException; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.fakes.TestEngineStub; - -class AbortOnFailureLauncherDiscoveryListenerTests extends AbstractLauncherDiscoveryListenerTests { - - @Test - void abortsDiscoveryOnUnresolvedUniqueIdSelectorWithEnginePrefix() { - var engine = createEngineThatCannotResolveAnything("some-engine"); - var request = request() // - .listeners(abortOnFailure()) // - .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // - .build(); - var launcher = createLauncher(engine); - - var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); - assertThat(exception).hasMessage("TestEngine with ID 'some-engine' failed to discover tests"); - assertThat(exception.getCause()).hasMessage( - "UniqueIdSelector [uniqueId = [engine:some-engine]] could not be resolved"); - } - - @Test - void doesNotAbortDiscoveryOnUnresolvedUniqueIdSelectorWithoutEnginePrefix() { - var engine = createEngineThatCannotResolveAnything("some-engine"); - var request = request() // - .listeners(abortOnFailure()) // - .selectors(selectUniqueId(UniqueId.forEngine("some-other-engine"))) // - .build(); - var launcher = createLauncher(engine); - - assertDoesNotThrow(() -> launcher.discover(request)); - } - - @Test - void abortsDiscoveryOnSelectorResolutionFailure() { - var rootCause = new RuntimeException(); - var engine = createEngineThatFailsToResolveAnything("some-engine", rootCause); - var request = request() // - .listeners(abortOnFailure()) // - .selectors(selectClass(Object.class)) // - .build(); - var launcher = createLauncher(engine); - - var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); - assertThat(exception).hasMessage("TestEngine with ID 'some-engine' failed to discover tests"); - assertThat(exception.getCause()) // - .hasMessageEndingWith("resolution failed") // - .hasCauseReference(rootCause); - } - - @Test - void abortsDiscoveryOnEngineDiscoveryFailure() { - var rootCause = new RuntimeException(); - var engine = new TestEngineStub("some-engine") { - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - throw rootCause; - } - }; - var request = request() // - .listeners(abortOnFailure()) // - .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // - .build(); - var launcher = createLauncher(engine); - - var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); - assertThat(exception) // - .hasMessage("TestEngine with ID 'some-engine' failed to discover tests") // - .hasCauseReference(rootCause); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java deleted file mode 100644 index 1666f66f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.discovery; - -import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.SelectorResolutionResult; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.fakes.TestEngineStub; - -abstract class AbstractLauncherDiscoveryListenerTests { - - protected TestEngineStub createEngineThatCannotResolveAnything(String engineId) { - return new TestEngineStub(engineId) { - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - discoveryRequest.getSelectorsByType(DiscoverySelector.class) // - .forEach(selector -> discoveryRequest.getDiscoveryListener().selectorProcessed(uniqueId, - selector, SelectorResolutionResult.unresolved())); - return new EngineDescriptor(uniqueId, "Some Engine"); - } - }; - } - - protected TestEngineStub createEngineThatFailsToResolveAnything(String engineId, RuntimeException rootCause) { - return new TestEngineStub(engineId) { - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - discoveryRequest.getSelectorsByType(DiscoverySelector.class) // - .forEach(selector -> discoveryRequest.getDiscoveryListener().selectorProcessed(uniqueId, - selector, SelectorResolutionResult.failed(rootCause))); - return new EngineDescriptor(uniqueId, "Some Engine"); - } - }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java deleted file mode 100644 index 4cdecfa2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.listeners.discovery; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; - -import java.util.logging.Level; -import java.util.logging.LogRecord; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.fixtures.TrackLogRecords; -import org.junit.platform.commons.logging.LogRecordListener; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.fakes.TestEngineStub; - -@TrackLogRecords -public class LoggingLauncherDiscoveryListenerTests extends AbstractLauncherDiscoveryListenerTests { - - @Test - void logsWarningOnUnresolvedUniqueIdSelectorWithEnginePrefix(LogRecordListener log) { - var engine = createEngineThatCannotResolveAnything("some-engine"); - var request = request() // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // - .build(); - var launcher = createLauncher(engine); - - launcher.discover(request); - - assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.WARNING)) // - .extracting(LogRecord::getMessage) // - .containsExactly( - "UniqueIdSelector [uniqueId = [engine:some-engine]] could not be resolved by [engine:some-engine]"); - } - - @Test - void logsDebugMessageOnUnresolvedUniqueIdSelectorWithoutEnginePrefix(LogRecordListener log) { - var engine = createEngineThatCannotResolveAnything("some-engine"); - var request = request() // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .selectors(selectUniqueId(UniqueId.forEngine("some-other-engine"))) // - .build(); - var launcher = createLauncher(engine); - - launcher.discover(request); - - assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.FINE)) // - .extracting(LogRecord::getMessage) // - .containsExactly( - "UniqueIdSelector [uniqueId = [engine:some-other-engine]] could not be resolved by [engine:some-engine]"); - } - - @Test - void logsErrorOnSelectorResolutionFailure(LogRecordListener log) { - var rootCause = new RuntimeException(); - var engine = createEngineThatFailsToResolveAnything("some-engine", rootCause); - var request = request() // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .selectors(selectClass(Object.class)) // - .build(); - var launcher = createLauncher(engine); - - launcher.discover(request); - - assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.SEVERE)) // - .extracting(LogRecord::getMessage) // - .containsExactly( - "Resolution of ClassSelector [className = 'java.lang.Object'] by [engine:some-engine] failed"); - } - - @Test - void logsErrorOnEngineDiscoveryFailure(LogRecordListener log) { - var rootCause = new RuntimeException(); - var engine = new TestEngineStub("some-engine") { - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - throw rootCause; - } - }; - var request = request() // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // - .build(); - var launcher = createLauncher(engine); - - launcher.discover(request); - - var logRecord = log.stream(LoggingLauncherDiscoveryListener.class, Level.SEVERE).findFirst().get(); - assertThat(logRecord.getMessage()).isEqualTo("TestEngine with ID 'some-engine' failed to discover tests"); - assertThat(logRecord.getThrown()).isSameAs(rootCause); - } - - @Test - void logsTraceMessageOnStartAndEnd(LogRecordListener log) { - var engine = new TestEngineStub("some-engine"); - var request = request() // - .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // - .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // - .build(); - var launcher = createLauncher(engine); - - launcher.discover(request); - - assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.FINER)) // - .extracting(LogRecord::getMessage) // - .containsExactly( // - "Test discovery started", // - "Engine [engine:some-engine] has started discovering tests", // - "Engine [engine:some-engine] has finished discovering tests", // - "Test discovery finished"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java deleted file mode 100644 index 3691dce2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class ParserErrorTests { - - private final Parser parser = new Parser(); - - @Test - void cantParseExpressionFromNull() { - assertThat(parseErrorFromParsing(null)).contains("empty tag expression"); - } - - @Test - void emptyExpression() { - assertThat(parseErrorFromParsing("")).contains("empty tag expression"); - } - - @Test - void missingClosingParenthesis() { - assertThat(parseErrorFromParsing("(")).contains("missing closing parenthesis for '(' at index <0>"); - assertThat(parseErrorFromParsing("( foo & bar")).contains("missing closing parenthesis for '(' at index <0>"); - } - - @Test - void missingOpeningParenthesis() { - assertThat(parseErrorFromParsing(")")).contains("missing opening parenthesis for ')' at index <0>"); - assertThat(parseErrorFromParsing(" foo | bar)")).contains("missing opening parenthesis for ')' at index <10>"); - } - - @Test - void partialUnaryOperator() { - assertThat(parseErrorFromParsing("!")).contains("missing rhs operand for '!' at index <0>"); - } - - @Test - void partialBinaryOperator() { - assertThat(parseErrorFromParsing("& foo")).contains("missing lhs operand for '&' at index <0>"); - assertThat(parseErrorFromParsing("foo |")).contains("missing rhs operand for '|' at index <4>"); - } - - @ParameterizedTest - @MethodSource("data") - void acceptanceTests(String tagExpression, String parseError) { - assertThat(parseErrorFromParsing(tagExpression)).contains(parseError); - } - - @SuppressWarnings("unused") - private static Stream data() { - // @formatter:off - return Stream.of( - arguments("&", "missing lhs and rhs operand for '&' at index <0>"), - arguments("|", "missing lhs and rhs operand for '|' at index <0>"), - arguments("| |", "missing lhs and rhs operand for '|' at index <0>"), - arguments("!", "missing rhs operand for '!' at index <0>"), - arguments("foo bar", "missing operator between 'foo' at index <2> and 'bar' at index <4>"), - arguments("foo bar |", "missing rhs operand for '|' at index <8>"), - arguments("foo bar | baz", "missing operator between 'foo' at index <2> and '(bar | baz)' at index <4>"), - arguments("foo bar &", "missing rhs operand for '&' at index <8>"), - arguments("foo & (bar !)", "missing rhs operand for '!' at index <11>"), - arguments("( foo & bar ) )", "missing opening parenthesis for ')' at index <14>"), - arguments("( ( foo & bar )", "missing closing parenthesis for '(' at index <0>"), - - arguments("foo & (bar baz) |", "missing operator between 'bar' at index <9> and 'baz' at index <11>"), - - arguments("foo & (bar baz) &", "missing operator between 'bar' at index <9> and 'baz' at index <11>"), - arguments("foo & (bar |baz) &", "missing rhs operand for '&' at index <17>"), - - arguments("foo | (bar baz) &", "missing rhs operand for '&' at index <16>"), - arguments("foo | (bar baz) &quux", "missing operator between 'bar' at index <9> and '(baz & quux)' at index <11>"), - - arguments("foo & |", "missing rhs operand for '&' at index <4>"), - arguments("foo !& bar", "missing rhs operand for '!' at index <4>"), - arguments("foo !| bar", "missing rhs operand for '!' at index <4>") - ); - // @formatter:on - } - - private String parseErrorFromParsing(String tagExpression) { - try { - var parseResult = parser.parse(tagExpression); - parseResult.tagExpressionOrThrow(RuntimeException::new); - return null; - } - catch (RuntimeException ex) { - return ex.getMessage(); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java deleted file mode 100644 index 4b15ba0f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class ParserTests { - - private final Parser parser = new Parser(); - - @Test - void notHasHigherPrecedenceThanAnd() { - assertThat(tagExpressionParsedFrom("! foo & bar")).hasToString("(!foo & bar)"); - } - - @Test - void andHasHigherPrecedenceThanOr() { - assertThat(tagExpressionParsedFrom("foo | bar & baz")).hasToString("(foo | (bar & baz))"); - } - - @Test - void notIsRightAssociative() { - assertThat(tagExpressionParsedFrom("foo &! bar")).hasToString("(foo & !bar)"); - } - - @Test - void andIsLeftAssociative() { - assertThat(tagExpressionParsedFrom("foo & bar & baz")).hasToString("((foo & bar) & baz)"); - } - - @Test - void orIsLeftAssociative() { - assertThat(tagExpressionParsedFrom("foo | bar | baz")).hasToString("((foo | bar) | baz)"); - } - - @ParameterizedTest - @MethodSource("data") - void acceptanceTests(String tagExpression, String expression) { - assertThat(tagExpressionParsedFrom(tagExpression)).hasToString(expression); - } - - @SuppressWarnings("unused") - private static Stream data() { - // @formatter:off - return Stream.of( - arguments("foo", "foo"), - arguments("! foo", "!foo"), - arguments("foo & bar", "(foo & bar)"), - arguments("foo | bar", "(foo | bar)"), - arguments("( ! foo & bar | baz)", "((!foo & bar) | baz)"), - arguments("(foo & bar ) | baz & quux", "((foo & bar) | (baz & quux))"), - arguments("! foo | bar & ! baz | ! quux | quuz & corge", "(((!foo | (bar & !baz)) | !quux) | (quuz & corge))"), - arguments("(foo & bar ) | baz & quux", "((foo & bar) | (baz & quux))"), - arguments("foo | bar & baz|quux", "((foo | (bar & baz)) | quux)"), - arguments("any()", "any()"), - arguments("! none()", "!none()") - ); - // @formatter:on - } - - private TagExpression tagExpressionParsedFrom(String tagExpression) { - return parser.parse(tagExpression).tagExpressionOrThrow( - (error) -> new RuntimeException("[" + tagExpression + "] should be parsable")); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java deleted file mode 100644 index 852ed6af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.engine.TestTag.create; -import static org.junit.platform.launcher.tagexpression.TagExpressions.and; -import static org.junit.platform.launcher.tagexpression.TagExpressions.any; -import static org.junit.platform.launcher.tagexpression.TagExpressions.none; -import static org.junit.platform.launcher.tagexpression.TagExpressions.not; -import static org.junit.platform.launcher.tagexpression.TagExpressions.or; -import static org.junit.platform.launcher.tagexpression.TagExpressions.tag; - -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.TestTag; - -class TagExpressionsTests { - - private static final TagExpression True = tags -> true; - private static final TagExpression False = tags -> false; - - @Test - void tagIsJustATestTag() { - assertThat(tag("foo")).hasToString("foo"); - } - - @Test - void rejectInvalidTestTags() { - RuntimeException expected = assertThrows(PreconditionViolationException.class, - () -> tag("tags with spaces are not allowed")); - assertThat(expected).hasMessageContaining("tags with spaces are not allowed"); - } - - @Test - void tagEvaluation() { - var tagExpression = tag("foo"); - - assertThat(tagExpression.evaluate(Set.of(create("foo")))).isTrue(); - assertThat(tagExpression.evaluate(Set.of(create("not_foo")))).isFalse(); - } - - @Test - void justConcatenateNot() { - assertThat(not(tag("foo"))).hasToString("!foo"); - assertThat(not(and(tag("foo"), tag("bar")))).hasToString("!(foo & bar)"); - assertThat(not(or(tag("foo"), tag("bar")))).hasToString("!(foo | bar)"); - } - - @Test - void notEvaluation() { - assertThat(not(True).evaluate(Set.of())).isFalse(); - assertThat(not(False).evaluate(Set.of())).isTrue(); - } - - @Test - void encloseAndWithParenthesis() { - assertThat(and(tag("foo"), tag("bar"))).hasToString("(foo & bar)"); - } - - @Test - void andEvaluation() { - assertThat(and(True, True).evaluate(Set.of())).isTrue(); - assertThat(and(True, False).evaluate(Set.of())).isFalse(); - assertThat(and(False, onEvaluateThrow()).evaluate(Set.of())).isFalse(); - } - - @Test - void encloseOrWithParenthesis() { - assertThat(or(tag("foo"), tag("bar"))).hasToString("(foo | bar)"); - } - - @Test - void orEvaluation() { - assertThat(or(False, False).evaluate(Set.of())).isFalse(); - assertThat(or(True, onEvaluateThrow()).evaluate(Set.of())).isTrue(); - assertThat(or(False, True).evaluate(Set.of())).isTrue(); - } - - @Test - void anyEvaluation() { - assertThat(any().evaluate(Set.of())).isFalse(); - assertThat(any().evaluate(Set.of(TestTag.create("foo")))).isTrue(); - } - - @Test - void noneEvaluation() { - assertThat(none().evaluate(Set.of())).isTrue(); - assertThat(none().evaluate(Set.of(TestTag.create("foo")))).isFalse(); - } - - private TagExpression onEvaluateThrow() { - return tags -> { - throw new RuntimeException("should not be evaluated"); - }; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java deleted file mode 100644 index 387d2986..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import org.junit.jupiter.api.Test; - -class TokenTests { - - @Test - void startIndexOfTokenString() { - assertThat(new Token(0, "!").trimmedTokenStartIndex()).isEqualTo(0); - assertThat(new Token(0, " !").trimmedTokenStartIndex()).isEqualTo(2); - assertThat(new Token(7, "!").trimmedTokenStartIndex()).isEqualTo(7); - } - - @Test - void endIndexExclusive() { - assertThat(new Token(0, "!").endIndexExclusive()).isEqualTo(1); - assertThat(new Token(0, " !").endIndexExclusive()).isEqualTo(3); - assertThat(new Token(7, "!").endIndexExclusive()).isEqualTo(8); - } - - @Test - void lastCharacterIndex() { - assertThat(new Token(0, "!").lastCharacterIndex()).isEqualTo(0); - assertThat(new Token(0, " !").lastCharacterIndex()).isEqualTo(2); - assertThat(new Token(7, "!").lastCharacterIndex()).isEqualTo(7); - } - - @Test - void concatenateTwoTokens() { - var tokens = new Tokenizer().tokenize(" ! foo"); - var one = tokens.get(0); - var two = tokens.get(1); - var joined = one.concatenate(two); - assertThat(joined.rawString).isEqualTo(" ! foo"); - assertThat(joined.startIndex).isEqualTo(0); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java deleted file mode 100644 index 94aa3044..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.launcher.tagexpression; - -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; - -class TokenizerTests { - - @Test - void nullContainsNoTokens() { - assertThat(tokenStringsExtractedFrom(null)).isEmpty(); - } - - @Test - void removeLeadingAndTrailingSpaces() { - assertThat(tokenStringsExtractedFrom(" tag ")).containsExactly("tag"); - } - - @Test - void notIsAReservedKeyword() { - assertThat(tokenStringsExtractedFrom("! tag")).containsExactly("!", "tag"); - assertThat(tokenStringsExtractedFrom("!tag")).containsExactly("!", "tag"); - } - - @Test - void andIsAReservedKeyword() { - assertThat(tokenStringsExtractedFrom("one & two")).containsExactly("one", "&", "two"); - assertThat(tokenStringsExtractedFrom("one&two")).containsExactly("one", "&", "two"); - } - - @Test - void orIsAReservedKeyword() { - assertThat(tokenStringsExtractedFrom("one | two")).containsExactly("one", "|", "two"); - assertThat(tokenStringsExtractedFrom("one|two")).containsExactly("one", "|", "two"); - } - - @Test - void anyAndNoneAreReservedKeywords() { - assertThat(tokenStringsExtractedFrom("!(any())")).containsExactly("!", "(", "any()", ")"); - assertThat(tokenStringsExtractedFrom("!(none())")).containsExactly("!", "(", "none()", ")"); - } - - @Test - void discoverBrackets() { - assertThat(tokenStringsExtractedFrom("()")).containsExactly("(", ")"); - assertThat(tokenStringsExtractedFrom("(tag)")).containsExactly("(", "tag", ")"); - assertThat(tokenStringsExtractedFrom("( tag )")).containsExactly("(", "tag", ")"); - assertThat(tokenStringsExtractedFrom("( foo &bar)| (baz& qux )")).containsExactly("(", "foo", "&", "bar", ")", - "|", "(", "baz", "&", "qux", ")"); - } - - @Test - void extractRawStringWithSpaceCharactersBeforeTheToken() { - assertThat(rawStringsExtractedFrom("(")).containsExactly("("); - assertThat(rawStringsExtractedFrom(" (")).containsExactly(" ("); - assertThat(rawStringsExtractedFrom(" ( foo ")).containsExactly(" (", " foo"); - assertThat(rawStringsExtractedFrom("(( (( (")).containsExactly("(", "(", " (", "(", " ("); - } - - @Test - void extractStartPositionOfRawString() { - assertThat(startIndicesExtractedFrom("(")).containsExactly(0); - assertThat(startIndicesExtractedFrom(" ( (")).containsExactly(0, 3); - assertThat(startIndicesExtractedFrom("foo &!bar")).containsExactly(0, 3, 5, 6); - } - - private Stream startIndicesExtractedFrom(String expression) { - return tokensExtractedFrom(expression).map(token -> token.startIndex); - } - - private Stream rawStringsExtractedFrom(String expression) { - return tokensExtractedFrom(expression).map(token -> token.rawString); - } - - private List tokenStringsExtractedFrom(String expression) { - return tokensExtractedFrom(expression).map(Token::string).collect(toList()); - } - - private Stream tokensExtractedFrom(String expression) { - return new Tokenizer().tokenize(expression).stream(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java deleted file mode 100644 index 2cf36b30..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestSource; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.0.3 - */ -class LegacyReportingUtilsTests { - - private TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("foo"), "Foo"); - - @Test - void legacyReportingClassNameForTestIdentifierWithoutClassSourceIsParentLegacyReportingName() { - var uniqueId = engineDescriptor.getUniqueId().append("child", "bar"); - var testDescriptor = createTestDescriptor(uniqueId, "Bar", null); - engineDescriptor.addChild(testDescriptor); - - assertThat(getClassName(engineDescriptor.getUniqueId())).isEqualTo(""); - assertThat(getClassName(uniqueId)).isEqualTo("Foo"); - - assertThat(getClassNameFromOldLocation(engineDescriptor.getUniqueId())).isEqualTo(""); - assertThat(getClassNameFromOldLocation(uniqueId)).isEqualTo("Foo"); - } - - @Test - void legacyReportingClassNameForDescendantOfTestIdentifierWithClassSourceIsClassName() { - var classUniqueId = engineDescriptor.getUniqueId().append("class", "class"); - var classDescriptor = createTestDescriptor(classUniqueId, "Class", - ClassSource.from(LegacyReportingUtilsTests.class)); - engineDescriptor.addChild(classDescriptor); - - var subUniqueId = classUniqueId.append("sub", "baz"); - var subDescriptor = createTestDescriptor(subUniqueId, "Baz", null); - classDescriptor.addChild(subDescriptor); - - var subSubUniqueId = subUniqueId.append("subsub", "qux"); - var subSubDescriptor = createTestDescriptor(subSubUniqueId, "Qux", null); - subDescriptor.addChild(subSubDescriptor); - - assertThat(getClassName(engineDescriptor.getUniqueId())).isEqualTo(""); - assertThat(getClassName(classUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); - assertThat(getClassName(subUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); - assertThat(getClassName(subSubUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); - - assertThat(getClassNameFromOldLocation(engineDescriptor.getUniqueId())).isEqualTo(""); - assertThat(getClassNameFromOldLocation(classUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); - assertThat(getClassNameFromOldLocation(subUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); - assertThat(getClassNameFromOldLocation(subSubUniqueId)).isEqualTo(LegacyReportingUtilsTests.class.getName()); - } - - private String getClassName(UniqueId uniqueId) { - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); - return LegacyReportingUtils.getClassName(testPlan, testPlan.getTestIdentifier(uniqueId)); - } - - @SuppressWarnings("deprecation") - private String getClassNameFromOldLocation(UniqueId uniqueId) { - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); - return org.junit.platform.launcher.listeners.LegacyReportingUtils.getClassName(testPlan, - testPlan.getTestIdentifier(uniqueId)); - } - - private TestDescriptor createTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) { - return new AbstractTestDescriptor(uniqueId, displayName, source) { - @Override - public Type getType() { - return Type.CONTAINER_AND_TEST; - } - }; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java deleted file mode 100644 index d46dad4a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; - -/** - * @since 1.0 - */ -final class IncrementingClock extends Clock { - - private final Duration duration; - private final ZoneId zone; - - private int counter; - - IncrementingClock(int start, Duration duration) { - this(start, duration, ZoneId.systemDefault()); - } - - private IncrementingClock(int start, Duration duration, ZoneId zone) { - this.counter = start; - this.duration = duration; - this.zone = zone; - } - - @Override - public Instant instant() { - return Instant.EPOCH.plus(duration.multipliedBy(counter++)); - } - - @Override - public Clock withZone(ZoneId zone) { - return new IncrementingClock(counter, duration, zone); - } - - @Override - public ZoneId getZone() { - return zone; - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java deleted file mode 100644 index 1ae505f8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.joox.JOOX.$; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; -import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; -import static org.mockito.Mockito.mock; - -import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.net.InetAddress; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.Year; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import org.joox.Match; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.engine.support.hierarchical.DemoEngineExecutionContext; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalContainerDescriptor; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestDescriptor; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.opentest4j.AssertionFailedError; - -/** - * Tests for {@link LegacyXmlReportGeneratingListener}. - * - * @since 1.0 - */ -class LegacyXmlReportGeneratingListenerTests { - - @TempDir - Path tempDirectory; - - @Test - void writesFileForSingleSucceedingTest() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("succeedingTest", "display<-->Name 😎", () -> { - }); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("name")).isEqualTo("dummy"); - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); - assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); - assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); - assertThat(testsuite.child("system-out").text()) // - .containsSubsequence("unique-id: [engine:dummy]", "display-name: dummy"); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("display<-->Name 😎"); - assertThat(testcase.attr("classname")).isEqualTo("dummy"); - assertThat(testcase.child("system-out").text()) // - .containsSubsequence("unique-id: [engine:dummy]/[test:succeedingTest]", - "display-name: display<-->Name 😎"); - - assertThat(testsuite.find("skipped")).isEmpty(); - assertThat(testsuite.find("failure")).isEmpty(); - assertThat(testsuite.find("error")).isEmpty(); - } - - @Test - void writesFileForSingleFailingTest() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("failingTest", () -> fail("expected to fail")); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); - assertThat(testsuite.attr("failures", int.class)).isEqualTo(1); - assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("failingTest"); - - var failure = testcase.child("failure"); - assertThat(failure.attr("message")).isEqualTo("expected to fail"); - assertThat(failure.attr("type")).isEqualTo(AssertionFailedError.class.getName()); - assertThat(failure.text()).containsSubsequence("AssertionFailedError: expected to fail", "\tat"); - - assertThat(testsuite.find("skipped")).isEmpty(); - assertThat(testsuite.find("error")).isEmpty(); - } - - @Test - void writesFileForSingleErroneousTest() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("failingTest", () -> { - throw new RuntimeException("error occurred"); - }); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); - assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); - assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("failingTest"); - - var error = testcase.child("error"); - assertThat(error.attr("message")).isEqualTo("error occurred"); - assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); - assertThat(error.text()).containsSubsequence("RuntimeException: error occurred", "\tat"); - - assertThat(testsuite.find("skipped")).isEmpty(); - assertThat(testsuite.find("failure")).isEmpty(); - } - - @Test - void writesFileForSingleSkippedTest() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - var testDescriptor = engine.addTest("skippedTest", () -> fail("never called")); - testDescriptor.markSkipped("should be skipped"); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); - assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); - assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("skippedTest"); - assertThat(testcase.child("skipped").text()).isEqualTo("should be skipped"); - - assertThat(testsuite.find("failure")).isEmpty(); - assertThat(testsuite.find("error")).isEmpty(); - } - - @SuppressWarnings("ConstantConditions") - @Test - void writesFileForSingleAbortedTest() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("abortedTest", () -> assumeFalse(true, "deliberately aborted")); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); - assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); - assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("abortedTest"); - assertThat(testcase.child("skipped").text()) // - .containsSubsequence("TestAbortedException: ", "deliberately aborted", "at "); - - assertThat(testsuite.find("failure")).isEmpty(); - assertThat(testsuite.find("error")).isEmpty(); - } - - @Test - void measuresTimesInSeconds() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("firstTest", () -> { - }); - engine.addTest("secondTest", () -> { - }); - - executeTests(engine, new IncrementingClock(0, Duration.ofMillis(333))); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - // start end - // ----------- ---------- ----------- - // engine 0 (1) 1,665 (6) - // firstTest 333 (2) 666 (3) - // secondTest 999 (4) 1,332 (5) - - assertThat(testsuite.attr("time", double.class)) // - .isEqualTo(1.665); - assertThat(testsuite.children("testcase").matchAttr("name", "firstTest").attr("time", double.class)) // - .isEqualTo(0.333); - assertThat(testsuite.children("testcase").matchAttr("name", "secondTest").attr("time", double.class)) // - .isEqualTo(0.333); - } - - @Test - void testWithImmeasurableTimeIsOutputCorrectly() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("test", () -> { - }); - - executeTests(engine, Clock.fixed(Instant.EPOCH, ZoneId.systemDefault())); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.child("testcase").attr("time")).isEqualTo("0"); - } - - @Test - void writesFileForSkippedContainer() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("test", () -> fail("never called")); - engine.getEngineDescriptor().markSkipped("should be skipped"); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("test"); - assertThat(testcase.child("skipped").text()).isEqualTo("parent was skipped: should be skipped"); - } - - @Test - void writesFileForFailingContainer() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("test", () -> fail("never called")); - engine.getEngineDescriptor().setBeforeAllBehavior(() -> fail("failure before all tests")); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("failures", int.class)).isEqualTo(1); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("test"); - - var failure = testcase.child("failure"); - assertThat(failure.attr("message")).isEqualTo("failure before all tests"); - assertThat(failure.attr("type")).isEqualTo(AssertionFailedError.class.getName()); - assertThat(failure.text()).containsSubsequence("AssertionFailedError: failure before all tests", "\tat"); - } - - @Test - void writesFileForFailingContainerWithoutTest() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addContainer("failingContainer", () -> { - throw new RuntimeException("boom"); - }); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("failingContainer"); - assertThat(testcase.attr("classname")).isEqualTo("dummy"); - - var error = testcase.child("error"); - assertThat(error.attr("message")).isEqualTo("boom"); - assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); - assertThat(error.text()).containsSubsequence("RuntimeException: boom", "\tat"); - } - - @Test - void writesFileForContainerFailingAfterTest() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - - var container = engine.addChild("failingContainer", - uniqueId -> new DemoHierarchicalContainerDescriptor(uniqueId, "failingContainer", null, null) { - @Override - public void after(DemoEngineExecutionContext context) { - throw new RuntimeException("boom"); - } - }, "child"); - container.addChild(new DemoHierarchicalTestDescriptor(container.getUniqueId().append("test", "someTest"), - "someTest", (c, t) -> { - })); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - - assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); - assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); - - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("someTest"); - assertThat(testcase.attr("classname")).isEqualTo("failingContainer"); - - var error = testcase.child("error"); - assertThat(error.attr("message")).isEqualTo("boom"); - assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); - assertThat(error.text()).containsSubsequence("RuntimeException: boom", "\tat"); - } - - @Test - void writesSystemProperties() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("test", () -> { - }); - - executeTests(engine); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - var properties = testsuite.child("properties").children("property"); - assertThat(properties.matchAttr("name", "file\\.separator").attr("value")).isEqualTo(File.separator); - assertThat(properties.matchAttr("name", "path\\.separator").attr("value")).isEqualTo(File.pathSeparator); - } - - @Test - void writesHostNameAndTimestamp() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("test", () -> { - }); - - var now = LocalDateTime.parse("2016-01-28T14:02:59.123"); - var zone = ZoneId.systemDefault(); - - executeTests(engine, Clock.fixed(ZonedDateTime.of(now, zone).toInstant(), zone)); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); - assertThat(testsuite.attr("hostname")).isEqualTo(InetAddress.getLocalHost().getHostName()); - assertThat(testsuite.attr("timestamp")).isEqualTo("2016-01-28T14:02:59"); - } - - @Test - void printsExceptionWhenReportsDirCannotBeCreated() throws Exception { - var reportsDir = tempDirectory.resolve("dummy.txt"); - Files.write(reportsDir, Set.of("content")); - - var out = new StringWriter(); - var listener = new LegacyXmlReportGeneratingListener(reportsDir, new PrintWriter(out)); - - listener.testPlanExecutionStarted(TestPlan.from(Set.of(), mock())); - - assertThat(out.toString()).containsSubsequence("Could not create reports directory", - "FileAlreadyExistsException", "at "); - } - - @Test - void printsExceptionWhenReportCouldNotBeWritten() throws Exception { - var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); - - var xmlFile = tempDirectory.resolve("TEST-engine.xml"); - Files.createDirectories(xmlFile); - - var out = new StringWriter(); - var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); - - listener.testPlanExecutionStarted(TestPlan.from(Set.of(engineDescriptor), mock())); - listener.executionFinished(TestIdentifier.from(engineDescriptor), successful()); - - assertThat(out.toString()).containsSubsequence("Could not write XML report", "Exception", "at "); - } - - @Test - void writesReportEntriesToSystemOutElement() throws Exception { - var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); - var childUniqueId = UniqueId.root("child", "test"); - engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); - - var out = new StringWriter(); - var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); - - listener.testPlanExecutionStarted(testPlan); - var testIdentifier = testPlan.getTestIdentifier(childUniqueId); - listener.executionStarted(testIdentifier); - listener.reportingEntryPublished(testIdentifier, ReportEntry.from("foo", "bar")); - Map map = new LinkedHashMap<>(); - map.put("bar", "baz"); - map.put("qux", "foo"); - listener.reportingEntryPublished(testIdentifier, ReportEntry.from(map)); - listener.executionFinished(testIdentifier, successful()); - listener.executionFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); - - var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-engine.xml")); - - assertThat(String.join("\n", testsuite.child("testcase").children("system-out").texts())) // - .containsSubsequence( // - "Report Entry #1 (timestamp: " + Year.now(), "- foo: bar\n", - "Report Entry #2 (timestamp: " + Year.now(), "- bar: baz\n", "- qux: foo\n"); - } - - private void executeTests(TestEngine engine) { - executeTests(engine, Clock.systemDefaultZone()); - } - - private void executeTests(TestEngine engine, Clock clock) { - var out = new PrintWriter(new StringWriter()); - var reportListener = new LegacyXmlReportGeneratingListener(tempDirectory.toString(), out, clock); - var launcher = createLauncher(engine); - launcher.registerTestExecutionListeners(reportListener); - launcher.execute(request().selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))).build()); - } - - private Match readValidXmlFile(Path xmlFile) throws Exception { - assertTrue(Files.exists(xmlFile), () -> "File does not exist: " + xmlFile); - try (var reader = Files.newBufferedReader(xmlFile)) { - var xml = $(reader); - assertValidAccordingToJenkinsSchema(xml.document()); - return xml; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java deleted file mode 100644 index 22b3d7d0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import static org.junit.jupiter.api.Assertions.fail; - -import javax.xml.XMLConstants; -import javax.xml.transform.dom.DOMSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; - -import org.w3c.dom.Document; -import org.xml.sax.SAXException; - -/** - * @since 1.0 - */ -class XmlReportAssertions { - - static void assertValidAccordingToJenkinsSchema(Document document) throws Exception { - try { - // Schema is thread-safe, Validator is not - var validator = CachedSchema.JENKINS.newValidator(); - validator.validate(new DOMSource(document)); - } - catch (SAXException e) { - fail("Invalid XML document: " + document, e); - } - } - - private enum CachedSchema { - - JENKINS("/jenkins-junit.xsd"); - - private final Schema schema; - - CachedSchema(String resourcePath) { - var schemaFile = LegacyXmlReportGeneratingListener.class.getResource(resourcePath); - var schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - try { - this.schema = schemaFactory.newSchema(schemaFile); - } - catch (SAXException e) { - throw new RuntimeException("Failed to create schema using " + schemaFile, e); - } - } - - Validator newValidator() { - return schema.newValidator(); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java deleted file mode 100644 index fb88bc9f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.engine.TestExecutionResult.failed; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.mockito.Mockito.mock; - -import java.time.Clock; -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.0 - */ -class XmlReportDataTests { - - private final ConfigurationParameters configParams = mock(); - - @Test - void resultsOfTestIdentifierWithoutAnyReportedEventsAreEmpty() { - var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); - var childUniqueId = UniqueId.root("child", "test"); - engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); - - assertThat(results).isEmpty(); - } - - @Test - void resultsOfTestIdentifierWithoutReportedEventsContainsOnlyFailureOfAncestor() { - var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); - var childUniqueId = UniqueId.root("child", "test"); - engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - var failureOfAncestor = failed(new RuntimeException("failed!")); - reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), failureOfAncestor); - - var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); - - assertThat(results).containsExactly(failureOfAncestor); - } - - @Test - void resultsOfTestIdentifierWithoutReportedEventsContainsOnlySuccessOfAncestor() { - var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); - var childUniqueId = UniqueId.root("child", "test"); - engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); - - var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); - - assertThat(results).containsExactly(successful()); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java deleted file mode 100644 index 8487afb1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.legacy.xml; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.joox.JOOX.$; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.TestExecutionResult.failed; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; -import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; -import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; -import static org.mockito.Mockito.mock; - -import java.io.StringReader; -import java.io.StringWriter; -import java.io.Writer; -import java.time.Clock; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -import org.joox.Match; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; - -/** - * @since 1.0 - */ -class XmlReportWriterTests { - - private final ConfigurationParameters configParams = mock(); - - private EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); - - @Test - void writesTestsuiteElementsWithoutTestcaseElementsWithoutAnyTests() throws Exception { - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - - var testsuite = writeXmlReport(testPlan, reportData); - - assertValidAccordingToJenkinsSchema(testsuite.document()); - assertThat(testsuite.document().getDocumentElement().getTagName()).isEqualTo("testsuite"); - assertThat(testsuite.attr("name")).isEqualTo("Engine"); - assertThat(testsuite.attr("tests", int.class)).isEqualTo(0); - assertThat(testsuite.find("testcase")).isEmpty(); - } - - @Test - void writesReportEntry() throws Exception { - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); - engineDescriptor.addChild(testDescriptor); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from("myKey", "myValue")); - reportData.markFinished(testPlan.getTestIdentifier(uniqueId), successful()); - - var testsuite = writeXmlReport(testPlan, reportData); - - assertValidAccordingToJenkinsSchema(testsuite.document()); - assertThat(String.join("\n", testsuite.find("system-out").texts())) // - .containsSubsequence("Report Entry #1 (timestamp: ", "- myKey: myValue"); - } - - @Test - void writesCapturedOutput() throws Exception { - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); - engineDescriptor.addChild(testDescriptor); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - var reportEntry = ReportEntry.from(Map.of( // - STDOUT_REPORT_ENTRY_KEY, "normal output", // - STDERR_REPORT_ENTRY_KEY, "error output", // - "foo", "bar")); - reportData.addReportEntry(TestIdentifier.from(testDescriptor), reportEntry); - reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from(Map.of("baz", "qux"))); - reportData.markFinished(testPlan.getTestIdentifier(uniqueId), successful()); - - var testsuite = writeXmlReport(testPlan, reportData); - - assertValidAccordingToJenkinsSchema(testsuite.document()); - assertThat(testsuite.find("system-out").text(0)) // - .containsSubsequence("unique-id: ", "test:test", "display-name: successfulTest"); - assertThat(testsuite.find("system-out").text(1)) // - .containsSubsequence("Report Entry #1 (timestamp: ", "- foo: bar", "Report Entry #2 (timestamp: ", - "- baz: qux"); - assertThat(testsuite.find("system-out").text(2).trim()) // - .isEqualTo("normal output"); - assertThat(testsuite.find("system-err").text().trim()) // - .isEqualTo("error output"); - } - - @Test - void writesEmptySkippedElementForSkippedTestWithoutReason() throws Exception { - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "skippedTest")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - reportData.markSkipped(testPlan.getTestIdentifier(uniqueId), null); - - var testsuite = writeXmlReport(testPlan, reportData); - - assertValidAccordingToJenkinsSchema(testsuite.document()); - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("skippedTest"); - var skipped = testcase.child("skipped"); - assertThat(skipped.size()).isEqualTo(1); - assertThat(skipped.children()).isEmpty(); - } - - @Test - void writesEmptyErrorElementForFailedTestWithoutCause() throws Exception { - engineDescriptor = new EngineDescriptor(UniqueId.forEngine("myEngineId"), "Fancy Engine") { - @Override - public String getLegacyReportingName() { - return "myEngine"; - } - }; - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "some fancy name") { - @Override - public String getLegacyReportingName() { - return "failedTest"; - } - }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(null)); - - var testsuite = writeXmlReport(testPlan, reportData); - - assertValidAccordingToJenkinsSchema(testsuite.document()); - var testcase = testsuite.child("testcase"); - assertThat(testcase.attr("name")).isEqualTo("failedTest"); - assertThat(testcase.attr("classname")).isEqualTo("myEngine"); - var error = testcase.child("error"); - assertThat(error.size()).isEqualTo(1); - assertThat(error.children()).isEmpty(); - } - - @Test - void omitsMessageAttributeForFailedTestWithThrowableWithoutMessage() throws Exception { - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "failedTest")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(new NullPointerException())); - - var testsuite = writeXmlReport(testPlan, reportData); - - assertValidAccordingToJenkinsSchema(testsuite.document()); - var error = testsuite.find("error"); - assertThat(error.attr("type")).isEqualTo("java.lang.NullPointerException"); - assertThat(error.attr("message")).isNull(); - } - - @Test - void writesValidXmlEvenIfExceptionMessageContainsCData() throws Exception { - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - var assertionError = new AssertionError(""); - reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); - - var testsuite = writeXmlReport(testPlan, reportData); - - assertValidAccordingToJenkinsSchema(testsuite.document()); - assertThat(testsuite.find("failure").attr("message")).isEqualTo(""); - } - - @Test - void escapesInvalidCharactersInSystemPropertiesAndExceptionMessages() throws Exception { - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - var assertionError = new AssertionError("expected: but was: "); - reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); - - System.setProperty("foo.bar", "\1"); - Match testsuite; - try { - testsuite = writeXmlReport(testPlan, reportData); - } - finally { - System.getProperties().remove("foo.bar"); - } - - assertValidAccordingToJenkinsSchema(testsuite.document()); - assertThat(testsuite.find("property").matchAttr("name", "foo\\.bar").attr("value")) // - .isEqualTo(""); - var failure = testsuite.find("failure"); - assertThat(failure.attr("message")) // - .isEqualTo("expected: but was: "); - assertThat(failure.text()) // - .contains("AssertionError: expected: but was: "); - } - - @Test - void doesNotReopenCDataWithinCDataContent() throws Exception { - var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); - engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); - - var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - var assertionError = new AssertionError(""); - reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); - Writer assertingWriter = new StringWriter() { - - @SuppressWarnings("NullableProblems") - @Override - public void write(char[] buffer, int off, int len) { - assertThat(new String(buffer, off, len)).doesNotContain("]]> stringPairs() { - return Stream.of( // - arguments("\0", "�"), // - arguments("\1", ""), // - arguments("\t", "\t"), // - arguments("\r", "\r"), // - arguments("\n", "\n"), // - arguments("\u001f", ""), // - arguments("\u0020", "\u0020"), // - arguments("foo!", "foo!"), // - arguments("\uD801\uDC00", "\uD801\uDC00") // - ); - } - - private Match writeXmlReport(TestPlan testPlan, XmlReportData reportData) throws Exception { - var out = new StringWriter(); - writeXmlReport(testPlan, reportData, out); - return $(new StringReader(out.toString())); - } - - private void writeXmlReport(TestPlan testPlan, XmlReportData reportData, Writer out) throws Exception { - new XmlReportWriter(reportData).writeXmlReport(getOnlyElement(testPlan.getRoots()), out); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java deleted file mode 100644 index 99398dbc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.reporting.open.xml; - -import static java.util.Objects.requireNonNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; -import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME; -import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.OUTPUT_DIR_PROPERTY_NAME; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; -import org.opentest4j.reporting.tooling.validator.DefaultValidator; -import org.opentest4j.reporting.tooling.validator.ValidationResult; -import org.xmlunit.assertj3.XmlAssert; -import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; - -/** - * Tests for {@link OpenTestReportGeneratingListener}. - * - * @since 1.9 - */ -public class OpenTestReportGeneratingListenerTests { - - @TempDir(cleanup = ON_SUCCESS) - Path tempDirectory; - - @Test - void writesValidXmlReport() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("failingTest", "display<-->Name 😎", (context, descriptor) -> { - var listener = context.request.getEngineExecutionListener(); - listener.reportingEntryPublished(descriptor, ReportEntry.from("key", "value")); - fail("failure message"); - }); - - executeTests(engine); - - var xmlFile = findXmlReport(); - assertThat(validate(xmlFile)).isEmpty(); - - var expected = """ - - - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - - - - - [engine:dummy] - dummy - CONTAINER - - - - - [engine:dummy]/[test:failingTest] - display<-->Name 😎 - TEST - - - - - - value - - - - - - - ${xmlunit.matchesRegex(org\\.opentest4j\\.AssertionFailedError: failure message)} - - - - - - - - """; - - XmlAssert.assertThat(xmlFile).and(expected) // - .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // - .ignoreWhitespace() // - .areIdentical(); - } - - private ValidationResult validate(Path xmlFile) throws URISyntaxException { - var catalogUri = requireNonNull(getClass().getResource("catalog.xml")).toURI(); - return new DefaultValidator(catalogUri).validate(xmlFile); - } - - private void executeTests(TestEngine engine) { - var build = request() // - .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // - .configurationParameter(ENABLED_PROPERTY_NAME, String.valueOf(true)) // - .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, tempDirectory.toString()) // - .build(); - createLauncher(engine).execute(build, new OpenTestReportGeneratingListener()); - } - - private Path findXmlReport() throws IOException { - try (var stream = Files.list(tempDirectory)) { - return stream.findAny().orElseThrow(AssertionError::new); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java deleted file mode 100644 index a515e6dd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java +++ /dev/null @@ -1,901 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.runner; - -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; -import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; -import static org.junit.runner.Description.createSuiteDescription; -import static org.junit.runner.Description.createTestDescription; -import static org.junit.runner.manipulation.Filter.matchMethodDescription; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.ExecutionRequest; -import org.junit.platform.engine.Filter; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.PackageNameFilter; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestDescriptor; -import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; -import org.junit.platform.fakes.TestDescriptorStub; -import org.junit.platform.fakes.TestEngineStub; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.suite.api.ExcludeClassNamePatterns; -import org.junit.platform.suite.api.ExcludeEngines; -import org.junit.platform.suite.api.ExcludePackages; -import org.junit.platform.suite.api.ExcludeTags; -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.IncludePackages; -import org.junit.platform.suite.api.IncludeTags; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.SuiteDisplayName; -import org.junit.platform.suite.api.UseTechnicalNames; -import org.junit.runner.Description; -import org.junit.runner.manipulation.NoTestsRemainException; -import org.junit.runner.notification.RunListener; -import org.junit.runner.notification.RunNotifier; -import org.mockito.ArgumentCaptor; - -/** - * Tests for the {@link JUnitPlatform} runner. - * - * @since 1.0 - */ -@Tag("junit4") -@SuppressWarnings("deprecation") -class JUnitPlatformRunnerTests { - - @Nested - class Discovery { - - @Test - void requestsClassSelectorForAnnotatedClassWhenNoAdditionalAnnotationsArePresent() { - - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var selectors = request.getSelectorsByType(ClassSelector.class); - assertThat(selectors).hasSize(1); - var classSelector = getOnlyElement(selectors); - assertEquals(TestCase.class, classSelector.getJavaClass()); - } - - @Test - void requestsClassSelectorsWhenSelectClassesAnnotationIsPresent() { - - @SelectClasses({ Short.class, Byte.class }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var selectors = request.getSelectorsByType(ClassSelector.class); - assertThat(selectors).hasSize(2); - assertEquals(Short.class, selectors.get(0).getJavaClass()); - assertEquals(Byte.class, selectors.get(1).getJavaClass()); - } - - @Test - void updatesIncludeClassNameFilterWhenSelectClassesAnnotationIsPresent() { - - @SelectClasses({ Short.class, Byte.class }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).hasSize(1); - - var filter = filters.get(0); - - // Excluded by default - assertExcludes(filter, "example.MyClass"); - assertExcludes(filter, "example.MyTestClass"); - assertExcludes(filter, "example.Short"); - assertExcludes(filter, "example.Byte"); - - // Included due to ClassNameFilter.STANDARD_INCLUDE_PATTERN - assertIncludes(filter, "TestClass"); - assertIncludes(filter, "example.TestClass"); - assertIncludes(filter, "example.MyTests"); - assertIncludes(filter, "example.MyTest"); - - // Included due to @SelectClasses({ Short.class, Byte.class }) - assertIncludes(filter, Short.class.getName()); - assertIncludes(filter, Byte.class.getName()); - } - - @Test - void requestsPackageSelectorsWhenPackagesAnnotationIsPresent() { - - @SelectPackages({ "foo", "bar" }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var selectors = request.getSelectorsByType(PackageSelector.class); - assertThat(selectors).hasSize(2); - assertEquals("foo", selectors.get(0).getPackageName()); - assertEquals("bar", selectors.get(1).getPackageName()); - } - - @Test - void addsPackageFiltersToRequestWhenIncludePackageAnnotationIsPresent() { - - @IncludePackages({ "includedpackage1", "includedpackage2" }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(PackageNameFilter.class); - assertThat(filters).hasSize(1); - - var filter = filters.get(0); - assertIncludes(filter, "includedpackage1.TestClass"); - assertIncludes(filter, "includedpackage2.TestClass"); - assertExcludes(filter, "excludedpackage1.TestClass"); - } - - @Test - void addsPackageFiltersToRequestWhenExcludePackageAnnotationIsPresent() { - - @ExcludePackages({ "excludedpackage1", "excludedpackage2" }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(PackageNameFilter.class); - assertThat(filters).hasSize(1); - - var filter = filters.get(0); - assertIncludes(filter, "includedpackage1.TestClass"); - assertExcludes(filter, "excludedpackage1.TestClass"); - assertExcludes(filter, "excludedpackage2.TestClass"); - } - - @Test - void addsTagFilterToRequestWhenIncludeTagsAnnotationIsPresent() { - - @IncludeTags({ "foo", "bar" }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getPostDiscoveryFilters(); - assertThat(filters).hasSize(1); - - var filter = filters.get(0); - assertIncludes(filter, testDescriptorWithTags("foo")); - assertIncludes(filter, testDescriptorWithTags("bar")); - assertExcludes(filter, testDescriptorWithTags("baz")); - } - - @Test - void addsTagFilterToRequestWhenExcludeTagsAnnotationIsPresent() { - - @ExcludeTags({ "foo", "bar" }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getPostDiscoveryFilters(); - assertThat(filters).hasSize(1); - - var filter = filters.get(0); - assertExcludes(filter, testDescriptorWithTags("foo")); - assertExcludes(filter, testDescriptorWithTags("bar")); - assertIncludes(filter, testDescriptorWithTags("baz")); - } - - @Test - void includeTagsAcceptsTagExpressions() { - - @IncludeTags("foo & !bar") - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getPostDiscoveryFilters(); - assertThat(filters).hasSize(1); - - var filter = filters.get(0); - assertIncludes(filter, testDescriptorWithTags("foo")); - assertIncludes(filter, testDescriptorWithTags("foo", "any_other_tag")); - assertExcludes(filter, testDescriptorWithTags("foo", "bar")); - assertExcludes(filter, testDescriptorWithTags("bar")); - assertExcludes(filter, testDescriptorWithTags("bar", "any_other_tag")); - } - - @Test - void excludeTagsAcceptsTagExpressions() { - - @ExcludeTags("foo & !bar") - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getPostDiscoveryFilters(); - assertThat(filters).hasSize(1); - - var filter = filters.get(0); - assertExcludes(filter, testDescriptorWithTags("foo")); - assertExcludes(filter, testDescriptorWithTags("foo", "any_other_tag")); - assertIncludes(filter, testDescriptorWithTags("foo", "bar")); - assertIncludes(filter, testDescriptorWithTags("bar")); - assertIncludes(filter, testDescriptorWithTags("bar", "any_other_tag")); - } - - @Test - void addsEngineFiltersToRequestWhenIncludeEnginesOrExcludeEnginesAnnotationsArePresent() { - - @IncludeEngines({ "foo", "bar", "baz" }) - @ExcludeEngines({ "bar", "quux" }) - class TestCase { - } - - TestEngine fooEngine = new TestEngineStub("foo"); - TestEngine barEngine = new TestEngineStub("bar"); - TestEngine bazEngine = new TestEngineStub("baz"); - TestEngine quuxEngine = new TestEngineStub("quux"); - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getEngineFilters(); - assertThat(filters).hasSize(2); - - var includeFilter = filters.get(1); - assertIncludes(includeFilter, fooEngine); - assertIncludes(includeFilter, barEngine); - assertIncludes(includeFilter, bazEngine); - assertExcludes(includeFilter, quuxEngine); - - var excludeFilter = filters.get(0); - assertIncludes(excludeFilter, fooEngine); - assertExcludes(excludeFilter, barEngine); - assertIncludes(excludeFilter, bazEngine); - assertExcludes(excludeFilter, quuxEngine); - } - - @Test - void addsDefaultClassNameFilterToRequestWhenFilterClassNameAnnotationIsNotPresentOnTestSuite() { - - @SelectPackages("foo") - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains(STANDARD_INCLUDE_PATTERN); - } - - @Test - void addsDefaultClassNameFilterToRequestWhenFilterClassNameAnnotationIsNotPresentOnTestClass() { - - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).isEmpty(); - } - - @Test - void addsSingleExplicitClassNameFilterToRequestWhenIncludeClassNamePatternsAnnotationIsPresent() { - - @IncludeClassNamePatterns(".*Foo") - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains(".*Foo"); - } - - @Test - void addsSingleClassNameFilterToRequestWhenExcludeClassNamePatternsAnnotationIsPresent() { - - @ExcludeClassNamePatterns(".*Foo") - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains(".*Foo"); - } - - @Test - void addsMultipleExplicitClassNameFilterToRequestWhenIncludeClassNamePatternsAnnotationIsPresent() { - - @IncludeClassNamePatterns({ ".*Foo", "Bar.*" }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains(".*Foo", "Bar.*"); - } - - @Test - void addsMultipleClassNameFilterToRequestWhenExcludeClassNamePatternsAnnotationIsPresent() { - - @ExcludeClassNamePatterns({ ".*Foo", "Bar.*" }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains(".*Foo", "Bar.*"); - } - - @Test - void usesStandardIncludePatternWhenIncludeClassNamePatternsAnnotationIsPresentWithoutArguments() { - - @IncludeClassNamePatterns - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains(STANDARD_INCLUDE_PATTERN); - } - - @Test - void doesNotAddClassNameFilterWhenIncludeClassNamePatternsAnnotationIsPresentWithEmptyArguments() { - - @IncludeClassNamePatterns({}) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).isEmpty(); - } - - @Test - void doesNotAddClassNameFilterWhenExcludeClassNamePatternsAnnotationIsPresentWithEmptyArguments() { - - @ExcludeClassNamePatterns({}) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(filters).isEmpty(); - } - - @Test - void trimsArgumentsOfIncludeClassNamePatternsAnnotation() { - - @IncludeClassNamePatterns({ " foo", "bar " }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains("'foo'", "'bar'"); - } - - @Test - void trimsArgumentsOfExcludeClassNamePatternsAnnotation() { - - @ExcludeClassNamePatterns({ " foo", "bar " }) - class TestCase { - } - - var request = instantiateRunnerAndCaptureGeneratedRequest(TestCase.class); - - var filters = request.getFiltersByType(ClassNameFilter.class); - assertThat(getOnlyElement(filters).toString()).contains("'foo'", "'bar'"); - } - - @Test - void convertsTestIdentifiersIntoDescriptions() { - - TestDescriptor container1 = new TestDescriptorStub(UniqueId.root("root", "container1"), "container1"); - container1.addChild(new TestDescriptorStub(UniqueId.root("root", "test1"), "test1")); - TestDescriptor container2 = new TestDescriptorStub(UniqueId.root("root", "container2"), "container2"); - container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2a"), "test2a")); - container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2b"), "test2b")); - var testPlan = TestPlan.from(List.of(container1, container2), mock()); - - var launcher = mock(Launcher.class); - when(launcher.discover(any())).thenReturn(testPlan); - - var runner = new JUnitPlatform(TestClass.class, launcher); - - var runnerDescription = runner.getDescription(); - assertEquals(createSuiteDescription(TestClass.class), runnerDescription); - - List containerDescriptions = runnerDescription.getChildren(); - assertThat(containerDescriptions).hasSize(2); - assertEquals(suiteDescription("[root:container1]"), containerDescriptions.get(0)); - assertEquals(suiteDescription("[root:container2]"), containerDescriptions.get(1)); - - List testDescriptions = containerDescriptions.get(0).getChildren(); - assertEquals(testDescription("[root:test1]"), getOnlyElement(testDescriptions)); - - testDescriptions = containerDescriptions.get(1).getChildren(); - assertThat(testDescriptions).hasSize(2); - assertEquals(testDescription("[root:test2a]"), testDescriptions.get(0)); - assertEquals(testDescription("[root:test2b]"), testDescriptions.get(1)); - } - - private static void assertIncludes(Filter filter, T included) { - assertThat(filter.apply(included).included()).isTrue(); - } - - private static void assertExcludes(Filter filter, T excluded) { - assertThat(filter.apply(excluded).excluded()).isTrue(); - } - - } - - @Nested - class Filtering { - - private final ConfigurationParameters configParams = mock(); - - @Test - void appliesFilter() throws Exception { - - TestDescriptor originalParent1 = new TestDescriptorStub(UniqueId.root("root", "parent1"), "parent1"); - originalParent1.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf1"), "leaf1")); - TestDescriptor originalParent2 = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); - originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2a"), "leaf2a")); - originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); - var fullTestPlan = TestPlan.from(List.of(originalParent1, originalParent2), configParams); - - TestDescriptor filteredParent = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); - filteredParent.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); - var filteredTestPlan = TestPlan.from(Set.of(filteredParent), configParams); - - var launcher = mock(Launcher.class); - var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); - when(launcher.discover(captor.capture())).thenReturn(fullTestPlan).thenReturn(filteredTestPlan); - - var runner = new JUnitPlatform(TestClass.class, launcher); - runner.filter(matchMethodDescription(testDescription("[root:leaf2b]"))); - - var lastDiscoveryRequest = captor.getValue(); - var uniqueIdSelectors = lastDiscoveryRequest.getSelectorsByType(UniqueIdSelector.class); - assertEquals("[root:leaf2b]", getOnlyElement(uniqueIdSelectors).getUniqueId().toString()); - - var parentDescription = getOnlyElement(runner.getDescription().getChildren()); - assertEquals(suiteDescription("[root:parent2]"), parentDescription); - - var testDescription = getOnlyElement(parentDescription.getChildren()); - assertEquals(testDescription("[root:leaf2b]"), testDescription); - } - - @Test - void throwsNoTestsRemainExceptionWhenNoTestIdentifierMatchesFilter() { - var testPlan = TestPlan.from(Set.of(new TestDescriptorStub(UniqueId.root("root", "test"), "test")), - configParams); - - var launcher = mock(Launcher.class); - when(launcher.discover(any())).thenReturn(testPlan); - - var runner = new JUnitPlatform(TestClass.class, launcher); - - assertThrows(NoTestsRemainException.class, - () -> runner.filter(matchMethodDescription(suiteDescription("[root:doesNotExist]")))); - } - - } - - @Nested - class Execution { - - @Test - void notifiesRunListenerOfTestExecution() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - engine.addTest("failingTest", () -> fail("expected to fail")); - engine.addTest("succeedingTest", () -> { - }); - engine.addTest("abortedTest", () -> assumeFalse(true)); - engine.addTest("skippedTest", () -> fail("never called")).markSkipped("should be skipped"); - - var runListener = mock(RunListener.class); - - var notifier = new RunNotifier(); - notifier.addListener(runListener); - new JUnitPlatform(TestClass.class, createLauncher(engine)).run(notifier); - - var inOrder = inOrder(runListener); - - inOrder.verify(runListener).testStarted(testDescription("[engine:dummy]/[test:failingTest]")); - inOrder.verify(runListener).testFailure(any()); - inOrder.verify(runListener).testFinished(testDescription("[engine:dummy]/[test:failingTest]")); - - inOrder.verify(runListener).testStarted(testDescription("[engine:dummy]/[test:succeedingTest]")); - inOrder.verify(runListener).testFinished(testDescription("[engine:dummy]/[test:succeedingTest]")); - - inOrder.verify(runListener).testStarted(testDescription("[engine:dummy]/[test:abortedTest]")); - inOrder.verify(runListener).testAssumptionFailure(any()); - inOrder.verify(runListener).testFinished(testDescription("[engine:dummy]/[test:abortedTest]")); - - inOrder.verify(runListener).testIgnored(testDescription("[engine:dummy]/[test:skippedTest]")); - - inOrder.verifyNoMoreInteractions(); - } - - @Test - void supportsDynamicTestRegistration() throws Exception { - var runListener = mock(RunListener.class); - var notifier = new RunNotifier(); - // notifier.addListener(new LoggingRunListener()); - notifier.addListener(runListener); - new JUnitPlatform(TestClass.class, createLauncher(new DynamicTestEngine())).run(notifier); - - var inOrder = inOrder(runListener); - - inOrder.verify(runListener).testStarted(testDescription("[engine:dynamic]/[container:1]/[test:1]")); - inOrder.verify(runListener).testFinished(testDescription("[engine:dynamic]/[container:1]/[test:1]")); - - inOrder.verify(runListener).testStarted(testDescription("[engine:dynamic]/[container:1]/[test:2]")); - inOrder.verify(runListener).testFinished(testDescription("[engine:dynamic]/[container:1]/[test:2]")); - - inOrder.verify(runListener).testStarted(testDescription("[engine:dynamic]/[container:1]/[test:3]")); - inOrder.verify(runListener).testFinished(testDescription("[engine:dynamic]/[container:1]/[test:3]")); - - inOrder.verify(runListener).testStarted( - testDescription("[engine:dynamic]/[container:1]/[test:3]/[test:3a]")); - inOrder.verify(runListener).testFinished( - testDescription("[engine:dynamic]/[container:1]/[test:3]/[test:3a]")); - - inOrder.verifyNoMoreInteractions(); - } - - @Test - void reportsIgnoredEventsForLeavesWhenContainerIsSkipped() throws Exception { - var uniqueEngineId = UniqueId.forEngine("engine"); - TestDescriptor engineDescriptor = new EngineDescriptor(uniqueEngineId, "engine"); - TestDescriptor container = new TestDescriptorStub(UniqueId.root("root", "container"), "container"); - container.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf"), "leaf")); - engineDescriptor.addChild(container); - - var engine = mock(TestEngine.class); - when(engine.getId()).thenReturn("engine"); - when(engine.discover(any(), eq(uniqueEngineId))).thenReturn(engineDescriptor); - doAnswer(invocation -> { - ExecutionRequest request = invocation.getArgument(0); - var listener = request.getEngineExecutionListener(); - listener.executionStarted(engineDescriptor); - listener.executionSkipped(container, "deliberately skipped container"); - listener.executionFinished(engineDescriptor, successful()); - return null; - }).when(engine).execute(any()); - - var runListener = mock(RunListener.class); - - var notifier = new RunNotifier(); - notifier.addListener(runListener); - new JUnitPlatform(TestClass.class, createLauncher(engine)).run(notifier); - - verify(runListener).testIgnored(testDescription("[root:leaf]")); - verifyNoMoreInteractions(runListener); - } - - } - - @Nested - class Descriptions { - - @Test - @DisplayName("Suite with default display name") - void descriptionForTestSuiteWithDefaultDisplayName() { - Class testClass = TestSuiteWithDefaultDisplayName.class; - var platformRunner = new JUnitPlatform(testClass, - createLauncher(new DemoHierarchicalTestEngine("suite names"))); - - assertEquals(testClass.getName(), platformRunner.getDescription().getDisplayName()); - } - - @Test - @DisplayName("Suite with @SuiteDisplayName") - void descriptionForTestSuiteWithCustomDisplayName() { - var platformRunner = new JUnitPlatform(TestSuiteWithCustomDisplayName.class, - createLauncher(new DemoHierarchicalTestEngine("suite names"))); - - assertEquals("Sweeeeeeet Name!", platformRunner.getDescription().getDisplayName()); - } - - @Test - @DisplayName("Suite with @SuiteDisplayName and @UseTechnicalNames") - void descriptionForTestSuiteWithCustomDisplayNameAndTechnicalNames() { - Class testClass = TestSuiteWithCustomDisplayNameAndTechnicalNames.class; - var platformRunner = new JUnitPlatform(testClass, - createLauncher(new DemoHierarchicalTestEngine("suite names"))); - - assertEquals(testClass.getName(), platformRunner.getDescription().getDisplayName()); - } - - @Test - void descriptionForJavaMethodAndClassSources() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - var failingTest = getClass().getDeclaredMethod("failingTest"); - var containerDescriptor = engine.addContainer("uniqueContainerName", "containerDisplayName", - ClassSource.from(getClass())); - containerDescriptor.addChild( - new DemoHierarchicalTestDescriptor(containerDescriptor.getUniqueId().append("test", "failingTest"), - "testDisplayName", MethodSource.from(failingTest), (c, t) -> { - })); - - var platformRunner = new JUnitPlatform(TestClass.class, createLauncher(engine)); - - List children = platformRunner.getDescription().getChildren(); - assertEquals(1, children.size()); - var engineDescription = children.get(0); - assertEquals("dummy", engineDescription.getDisplayName()); - - var containerDescription = getOnlyElement(engineDescription.getChildren()); - var testDescription = getOnlyElement(containerDescription.getChildren()); - - // @formatter:off - assertAll( - () -> assertEquals("dummy", engineDescription.getDisplayName(), "engine display name"), - () -> assertEquals("dummy", engineDescription.getClassName(), "engine class name"), - () -> assertNull(engineDescription.getMethodName(), "engine method name"), - () -> assertEquals("containerDisplayName", containerDescription.getDisplayName(), "container display name"), - () -> assertEquals("containerDisplayName", containerDescription.getClassName(), "container class name"), - () -> assertNull(containerDescription.getMethodName(), "container method name"), - () -> assertEquals("testDisplayName(containerDisplayName)", testDescription.getDisplayName(), "test display name"), - () -> assertEquals("containerDisplayName", testDescription.getClassName(), "test class name"), - () -> assertEquals("testDisplayName", testDescription.getMethodName(), "test method name") - ); - // @formatter:on - } - - @Test - void descriptionForJavaMethodAndClassSourcesUsingTechnicalNames() throws Exception { - var engine = new DemoHierarchicalTestEngine("dummy"); - var failingTest = getClass().getDeclaredMethod("failingTest"); - var containerDescriptor = engine.addContainer("uniqueContainerName", "containerDisplayName", - ClassSource.from(getClass())); - containerDescriptor.addChild( - new DemoHierarchicalTestDescriptor(containerDescriptor.getUniqueId().append("test", "failingTest"), - "testDisplayName", MethodSource.from(failingTest), (c, t) -> { - })); - - var platformRunner = new JUnitPlatform(TestClassWithTechnicalNames.class, createLauncher(engine)); - - List children = platformRunner.getDescription().getChildren(); - assertEquals(1, children.size()); - var engineDescription = children.get(0); - assertEquals("dummy", engineDescription.getDisplayName()); - - var containerDescription = getOnlyElement(engineDescription.getChildren()); - var testDescription = getOnlyElement(containerDescription.getChildren()); - - // @formatter:off - assertAll( - () -> assertEquals("dummy", engineDescription.getDisplayName(), "engine display name"), - () -> assertEquals("dummy", engineDescription.getClassName(), "engine class name"), - () -> assertNull(engineDescription.getMethodName(), "engine method name"), - () -> assertEquals(getClass().getName(), containerDescription.getDisplayName(), "container display name"), - () -> assertEquals(getClass().getName(), containerDescription.getClassName(), "container class name"), - () -> assertNull(containerDescription.getMethodName(), "container method name"), - () -> assertEquals("failingTest(" + getClass().getName() + ")", testDescription.getDisplayName(), "test display name"), - () -> assertEquals(getClass().getName(), testDescription.getClassName(), "test class name"), - () -> assertEquals("failingTest", testDescription.getMethodName(), "test method name") - ); - // @formatter:on - } - - void failingTest() { - // not actually invoked - } - - } - - // ------------------------------------------------------------------------- - - private static Description suiteDescription(String uniqueId) { - return createSuiteDescription(uniqueId, UniqueId.parse(uniqueId)); - } - - private static Description testDescription(String uniqueId) { - return createTestDescription(uniqueId, uniqueId, UniqueId.parse(uniqueId)); - } - - private TestDescriptor testDescriptorWithTags(String... tag) { - var testDescriptor = mock(TestDescriptor.class); - var tags = Arrays.stream(tag).map(TestTag::create).collect(toSet()); - when(testDescriptor.getTags()).thenReturn(tags); - return testDescriptor; - } - - private LauncherDiscoveryRequest instantiateRunnerAndCaptureGeneratedRequest(Class testClass) { - var launcher = mock(Launcher.class); - var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); - when(launcher.discover(captor.capture())).thenReturn(TestPlan.from(Set.of(), mock())); - - new JUnitPlatform(testClass, launcher); - - return captor.getValue(); - } - - private static class TestClass { - } - - @UseTechnicalNames - private static class TestClassWithTechnicalNames { - } - - private static class TestSuiteWithDefaultDisplayName { - } - - @SuiteDisplayName("Sweeeeeeet Name!") - private static class TestSuiteWithCustomDisplayName { - } - - @SuiteDisplayName("Sweeeeeeet Name!") - @UseTechnicalNames - private static class TestSuiteWithCustomDisplayNameAndTechnicalNames { - } - - private static class DynamicTestEngine implements TestEngine { - - @Override - public String getId() { - return "dynamic"; - } - - @Override - public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - return new EngineDescriptor(uniqueId, "Dynamic Engine"); - } - - @Override - public void execute(ExecutionRequest request) { - var engineExecutionListener = request.getEngineExecutionListener(); - var root = request.getRootTestDescriptor(); - - TestDescriptor container = new DemoContainerTestDescriptor(root.getUniqueId().append("container", "1"), - "container #1"); - root.addChild(container); - - engineExecutionListener.dynamicTestRegistered(container); - engineExecutionListener.executionStarted(container); - - var containerUid = container.getUniqueId(); - - TestDescriptor dynamicTest1 = new DemoTestTestDescriptor(containerUid.append("test", "1"), - "dynamic test #1"); - container.addChild(dynamicTest1); - engineExecutionListener.dynamicTestRegistered(dynamicTest1); - engineExecutionListener.executionStarted(dynamicTest1); - engineExecutionListener.executionFinished(dynamicTest1, TestExecutionResult.successful()); - - TestDescriptor dynamicTest2 = new DemoTestTestDescriptor(containerUid.append("test", "2"), - "dynamic test #2"); - container.addChild(dynamicTest2); - engineExecutionListener.dynamicTestRegistered(dynamicTest2); - engineExecutionListener.executionStarted(dynamicTest2); - engineExecutionListener.executionFinished(dynamicTest2, TestExecutionResult.successful()); - - TestDescriptor dynamicTest3 = new DemoContainerAndTestTestDescriptor(containerUid.append("test", "3"), - "dynamic test #3"); - container.addChild(dynamicTest3); - engineExecutionListener.dynamicTestRegistered(dynamicTest3); - engineExecutionListener.executionStarted(dynamicTest3); - engineExecutionListener.executionFinished(dynamicTest3, TestExecutionResult.successful()); - - TestDescriptor dynamicTest3a = new DemoTestTestDescriptor(dynamicTest3.getUniqueId().append("test", "3a"), - "dynamic test #3a"); - dynamicTest3.addChild(dynamicTest3a); - engineExecutionListener.dynamicTestRegistered(dynamicTest3a); - engineExecutionListener.executionStarted(dynamicTest3a); - engineExecutionListener.executionFinished(dynamicTest3a, TestExecutionResult.successful()); - - engineExecutionListener.executionFinished(container, TestExecutionResult.successful()); - } - - } - - private static class DemoContainerTestDescriptor extends AbstractTestDescriptor { - - DemoContainerTestDescriptor(UniqueId uniqueId, String displayName) { - super(uniqueId, displayName); - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - } - - private static class DemoTestTestDescriptor extends AbstractTestDescriptor { - - DemoTestTestDescriptor(UniqueId uniqueId, String displayName) { - super(uniqueId, displayName); - } - - @Override - public Type getType() { - return Type.TEST; - } - } - - private static class DemoContainerAndTestTestDescriptor extends AbstractTestDescriptor { - - DemoContainerAndTestTestDescriptor(UniqueId uniqueId, String displayName) { - super(uniqueId, displayName); - } - - @Override - public Type getType() { - return Type.CONTAINER_AND_TEST; - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java deleted file mode 100644 index 7a985b60..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java +++ /dev/null @@ -1,458 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.commons; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.net.URI; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.JupiterTestEngine; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestTag; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.discovery.ClassNameFilter; -import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.engine.discovery.ClasspathResourceSelector; -import org.junit.platform.engine.discovery.DirectorySelector; -import org.junit.platform.engine.discovery.FilePosition; -import org.junit.platform.engine.discovery.FileSelector; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.engine.discovery.PackageNameFilter; -import org.junit.platform.engine.discovery.PackageSelector; -import org.junit.platform.engine.discovery.UriSelector; -import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; -import org.junit.platform.launcher.EngineFilter; -import org.junit.platform.launcher.LauncherDiscoveryRequest; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.suite.api.ConfigurationParameter; -import org.junit.platform.suite.api.DisableParentConfigurationParameters; -import org.junit.platform.suite.api.ExcludeClassNamePatterns; -import org.junit.platform.suite.api.ExcludeEngines; -import org.junit.platform.suite.api.ExcludePackages; -import org.junit.platform.suite.api.ExcludeTags; -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.IncludeEngines; -import org.junit.platform.suite.api.IncludePackages; -import org.junit.platform.suite.api.IncludeTags; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.SelectClasspathResource; -import org.junit.platform.suite.api.SelectDirectories; -import org.junit.platform.suite.api.SelectFile; -import org.junit.platform.suite.api.SelectModules; -import org.junit.platform.suite.api.SelectPackages; -import org.junit.platform.suite.api.SelectUris; - -class SuiteLauncherDiscoveryRequestBuilderTests { - - SuiteLauncherDiscoveryRequestBuilder builder = SuiteLauncherDiscoveryRequestBuilder.request(); - - @Test - void configurationParameter() { - @ConfigurationParameter(key = "com.example", value = "*") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - ConfigurationParameters configuration = request.getConfigurationParameters(); - Optional parameter = configuration.get("com.example"); - assertEquals(Optional.of("*"), parameter); - } - - @Test - void excludeClassNamePatterns() { - class TestCase { - } - @ExcludeClassNamePatterns("^.*TestCase$") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getFiltersByType(ClassNameFilter.class); - assertTrue(exactlyOne(filters).apply(TestCase.class.getName()).excluded()); - } - - @Test - void excludeEngines() { - @ExcludeEngines("junit-jupiter") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getEngineFilters(); - assertTrue(exactlyOne(filters).apply(new JupiterTestEngine()).excluded()); - } - - @Test - void excludePackages() { - @ExcludePackages("com.example.testcases") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getFiltersByType(PackageNameFilter.class); - assertTrue(exactlyOne(filters).apply("com.example.testcases").excluded()); - } - - @Test - void excludeTags() { - @ExcludeTags("test-tag") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getPostDiscoveryFilters(); - TestDescriptor testDescriptor = new StubAbstractTestDescriptor(); - assertTrue(exactlyOne(filters).apply(testDescriptor).excluded()); - } - - @Test - void includeClassNamePatterns() { - class TestCase { - } - @IncludeClassNamePatterns("^.*TestCase$") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getFiltersByType(ClassNameFilter.class); - assertTrue(exactlyOne(filters).apply(TestCase.class.getName()).included()); - assertTrue(exactlyOne(filters).apply(Suite.class.getName()).excluded()); - } - - @Test - void filtersOnStandardClassNamePatternsWhenIncludeClassNamePatternsIsOmitted() { - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - assertTrue(request.getFiltersByType(ClassNameFilter.class).isEmpty()); - } - - @Test - void filtersOnStandardClassNamePatternsWhenIncludeClassNamePatternsIsOmittedUnlessDisabled() { - class ExampleTest { - } - class Suite { - } - - // @formatter:off - LauncherDiscoveryRequest request = builder - .filterStandardClassNamePatterns(true) - .suite(Suite.class) - .build(); - // @formatter:on - List filters = request.getFiltersByType(ClassNameFilter.class); - assertTrue(exactlyOne(filters).apply(ExampleTest.class.getName()).included()); - assertTrue(exactlyOne(filters).apply(Suite.class.getName()).excluded()); - } - - @Test - void includeEngines() { - @IncludeEngines("junit-jupiter") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getEngineFilters(); - assertTrue(exactlyOne(filters).apply(new JupiterTestEngine()).included()); - } - - @Test - void includePackages() { - @IncludePackages("com.example.testcases") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getFiltersByType(PackageNameFilter.class); - assertTrue(exactlyOne(filters).apply("com.example.testcases").included()); - } - - @Test - void includeTags() { - @IncludeTags("test-tag") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List filters = request.getPostDiscoveryFilters(); - TestDescriptor testDescriptor = new StubAbstractTestDescriptor(); - assertTrue(exactlyOne(filters).apply(testDescriptor).included()); - } - - @Test - void selectClasses() { - class TestCase { - } - @SelectClasses(TestCase.class) - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(ClassSelector.class); - assertFalse(selectors.isEmpty()); - assertEquals(TestCase.class, exactlyOne(selectors).getJavaClass()); - } - - @Test - void selectClasspathResource() { - @SelectClasspathResource("com.example.testcases") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); - assertEquals("com.example.testcases", exactlyOne(selectors).getClasspathResourceName()); - } - - @Test - void selectClasspathResourcePosition() { - @SelectClasspathResource(value = "com.example.testcases", line = 42) - @SelectClasspathResource(value = "com.example.testcases", line = 14, column = 15) - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); - assertEquals(Optional.of(FilePosition.from(42)), selectors.get(0).getPosition()); - assertEquals(Optional.of(FilePosition.from(14, 15)), selectors.get(1).getPosition()); - } - - @Test - void ignoreClasspathResourcePosition() { - @SelectClasspathResource(value = "com.example.testcases", line = -1) - @SelectClasspathResource(value = "com.example.testcases", column = 12) - @SelectClasspathResource(value = "com.example.testcases", line = 42, column = -12) - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); - assertEquals(Optional.empty(), selectors.get(0).getPosition()); - assertEquals(Optional.empty(), selectors.get(1).getPosition()); - assertEquals(Optional.of(FilePosition.from(42)), selectors.get(2).getPosition()); - } - - @Test - void selectDirectories() { - @SelectDirectories("path/to/root") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(DirectorySelector.class); - assertEquals(Paths.get("path/to/root"), exactlyOne(selectors).getPath()); - } - - @Test - void selectDirectoriesFiltersEmptyPaths() { - @SelectDirectories("") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - assertTrue(request.getSelectorsByType(DirectorySelector.class).isEmpty()); - } - - @Test - void selectFile() { - @SelectFile("path/to/root") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(FileSelector.class); - assertEquals(Paths.get("path/to/root"), exactlyOne(selectors).getPath()); - } - - @Test - void selectFilePosition() { - @SelectFile(value = "path/to/root", line = 42) - @SelectFile(value = "path/to/root", line = 14, column = 15) - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(FileSelector.class); - assertEquals(Optional.of(FilePosition.from(42)), selectors.get(0).getPosition()); - assertEquals(Optional.of(FilePosition.from(14, 15)), selectors.get(1).getPosition()); - } - - @Test - void ignoreInvalidFilePosition() { - @SelectFile(value = "path/to/root", line = -1) - @SelectFile(value = "path/to/root", column = 12) - @SelectFile(value = "path/to/root", line = 42, column = -12) - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(FileSelector.class); - assertEquals(Optional.empty(), selectors.get(0).getPosition()); - assertEquals(Optional.empty(), selectors.get(1).getPosition()); - assertEquals(Optional.of(FilePosition.from(42)), selectors.get(2).getPosition()); - } - - @Test - void selectModules() { - @SelectModules("com.example.testcases") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(ModuleSelector.class); - assertEquals("com.example.testcases", exactlyOne(selectors).getModuleName()); - } - - @Test - void selectUris() { - @SelectUris("path/to/root") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(UriSelector.class); - assertEquals(URI.create("path/to/root"), exactlyOne(selectors).getUri()); - } - - @Test - void selectUrisFiltersEmptyUris() { - @SelectUris("") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - assertTrue(request.getSelectorsByType(UriSelector.class).isEmpty()); - } - - @Test - void selectPackages() { - @SelectPackages("com.example.testcases") - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List selectors = request.getSelectorsByType(PackageSelector.class); - assertEquals("com.example.testcases", exactlyOne(selectors).getPackageName()); - } - - @SelectPackages("com.example.testcases") - @Retention(RetentionPolicy.RUNTIME) - @interface Meta { - } - - @Test - void metaAnnotations() { - @Meta - class Suite { - } - - LauncherDiscoveryRequest request = builder.suite(Suite.class).build(); - List pSelectors = request.getSelectorsByType(PackageSelector.class); - assertEquals("com.example.testcases", exactlyOne(pSelectors).getPackageName()); - } - - @Test - void enableParentConfigurationParametersByDefault() { - class Suite { - - } - // @formatter:off - var configuration = new ParentConfigurationParameters("parent", "parent parameters were used"); - var request = builder.suite(Suite.class) - .parentConfigurationParameters(configuration) - .build(); - // @formatter:on - var configurationParameters = request.getConfigurationParameters(); - assertEquals(Optional.of("parent parameters were used"), configurationParameters.get("parent")); - } - - @Test - void disableParentConfigurationParameters() { - @DisableParentConfigurationParameters - class Suite { - - } - // @formatter:off - var configuration = new ParentConfigurationParameters("parent", "parent parameters were used"); - var request = builder.suite(Suite.class) - .parentConfigurationParameters(configuration) - .build(); - // @formatter:on - var configurationParameters = request.getConfigurationParameters(); - assertEquals(Optional.empty(), configurationParameters.get("parent")); - } - - private static T exactlyOne(List list) { - assertEquals(1, list.size()); - return list.get(0); - } - - private static class StubAbstractTestDescriptor extends AbstractTestDescriptor { - public StubAbstractTestDescriptor() { - super(UniqueId.forEngine("test"), "stub"); - } - - @Override - public Type getType() { - return Type.CONTAINER; - } - - @Override - public Set getTags() { - return Collections.singleton(TestTag.create("test-tag")); - } - - } - - private static class ParentConfigurationParameters implements ConfigurationParameters { - private final Map map; - - public ParentConfigurationParameters(String key, String value) { - this.map = Map.of(key, value); - } - - @Override - public Optional get(String key) { - return Optional.ofNullable(map.get(key)); - } - - @Override - public Optional getBoolean(String key) { - return Optional.empty(); - } - - @Override - @SuppressWarnings("deprecation") - public int size() { - return map.size(); - } - - @Override - public Set keySet() { - return null; - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java deleted file mode 100644 index 4c44d1c0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.TagFilter.excludeTags; -import static org.junit.platform.suite.engine.SuiteEngineDescriptor.ENGINE_ID; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.displayName; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; -import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; -import org.junit.platform.engine.FilterResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.launcher.PostDiscoveryFilter; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.DynamicTestsTestCase; -import org.junit.platform.suite.engine.testcases.JUnit4TestsTestCase; -import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; -import org.junit.platform.suite.engine.testcases.TaggedTestTestCase; -import org.junit.platform.suite.engine.testsuites.AbstractSuite; -import org.junit.platform.suite.engine.testsuites.CyclicSuite; -import org.junit.platform.suite.engine.testsuites.DynamicSuite; -import org.junit.platform.suite.engine.testsuites.EmptyCyclicSuite; -import org.junit.platform.suite.engine.testsuites.EmptyDynamicTestSuite; -import org.junit.platform.suite.engine.testsuites.EmptyDynamicTestWithFailIfNoTestFalseSuite; -import org.junit.platform.suite.engine.testsuites.EmptyTestCaseSuite; -import org.junit.platform.suite.engine.testsuites.EmptyTestCaseWithFailIfNoTestFalseSuite; -import org.junit.platform.suite.engine.testsuites.MultiEngineSuite; -import org.junit.platform.suite.engine.testsuites.MultipleSuite; -import org.junit.platform.suite.engine.testsuites.NestedSuite; -import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; -import org.junit.platform.suite.engine.testsuites.SuiteDisplayNameSuite; -import org.junit.platform.suite.engine.testsuites.SuiteSuite; -import org.junit.platform.suite.engine.testsuites.ThreePartCyclicSuite; -import org.junit.platform.testkit.engine.EngineTestKit; - -/** - * @since 1.8 - */ -class SuiteEngineTests { - - @Test - void selectClasses() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(SelectClassesSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void suiteDisplayName() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(SuiteDisplayNameSuite.class)) - .execute() - .allEvents() - .assertThatEvents() - .haveExactly(1, event(container(displayName("Suite Display Name")), finishedSuccessfully())); - // @formatter:on - } - - @Test - void abstractSuiteIsNotExecuted() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(AbstractSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .isEmpty(); - // @formatter:on - } - - @Test - void privateSuiteIsNotExecuted() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(PrivateSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .isEmpty(); - // @formatter:on - } - - @Suite - @SelectClasses(SingleTestTestCase.class) - private static class PrivateSuite { - - } - - @Test - void innerSuiteIsNotExecuted() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(InnerSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .isEmpty(); - // @formatter:on - } - - @SuppressWarnings("InnerClassMayBeStatic") - @Suite - @SelectClasses(SingleTestTestCase.class) - private class InnerSuite { - - } - - @Test - void nestedSuiteIsNotExecuted() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(NestedSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .isEmpty(); - // @formatter:on - } - - @Test - void dynamicSuite() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(DynamicSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(2, event(test(DynamicTestsTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void suiteSuite() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(SuiteSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(1, event(test(SuiteSuite.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void selectClassesByUniqueId() { - // @formatter:off - UniqueId uniqId = UniqueId.forEngine(ENGINE_ID) - .append(SuiteTestDescriptor.SEGMENT_TYPE, SelectClassesSuite.class.getName()); - EngineTestKit.engine(ENGINE_ID) - .selectors(selectUniqueId(uniqId)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void selectMethodInTestPlanByUniqueId() { - // @formatter:off - UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) - .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) - .append("engine", JupiterEngineDescriptor.ENGINE_ID) - .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) - .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); - - EngineTestKit.engine(ENGINE_ID) - .selectors(selectUniqueId(uniqueId)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(1, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void selectSuiteByUniqueId() { - // @formatter:off - UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) - .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()); - - EngineTestKit.engine(ENGINE_ID) - .selectors(selectUniqueId(uniqueId)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) - .haveExactly(2, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void selectMethodAndSuiteInTestPlanByUniqueId() { - // @formatter:off - UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) - .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) - .append("engine", JupiterEngineDescriptor.ENGINE_ID) - .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) - .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); - - EngineTestKit.engine(ENGINE_ID) - .selectors(selectUniqueId(uniqueId)) - .selectors(selectClass(SelectClassesSuite.class)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) - .haveExactly(1, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void selectMethodsInTestPlanByUniqueId() { - // @formatter:off - UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) - .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) - .append("engine", JupiterEngineDescriptor.ENGINE_ID) - .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) - .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); - - UniqueId uniqueId2 = UniqueId.forEngine(ENGINE_ID) - .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) - .append("engine", JupiterEngineDescriptor.ENGINE_ID) - .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) - .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test2()"); - - EngineTestKit.engine(ENGINE_ID) - .selectors(selectUniqueId(uniqueId)) - .selectors(selectUniqueId(uniqueId2)) - .execute() - .testEvents() - .assertThatEvents() - .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) - .haveExactly(2, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void postDiscoveryCanRemoveTestDescriptorsInSuite() { - // @formatter:off - PostDiscoveryFilter postDiscoveryFilter = testDescriptor -> testDescriptor.getSource() - .filter(MethodSource.class::isInstance) - .map(MethodSource.class::cast) - .filter(classSource -> SingleTestTestCase.class.equals(classSource.getJavaClass())) - .map(classSource -> FilterResult.excluded("Was a test in SimpleTest")) - .orElseGet(() -> FilterResult.included("Was not a test in SimpleTest")); - - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(SelectClassesSuite.class)) - .filters(postDiscoveryFilter) - .execute() - .testEvents() - .assertThatEvents() - .isEmpty(); - // @formatter:on - } - - @Test - void emptySuiteFails() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(EmptyTestCaseSuite.class)) - .execute() - .containerEvents() - .assertThatEvents() - .haveExactly(1, event(container(EmptyTestCaseSuite.class), finishedWithFailure(instanceOf(NoTestsDiscoveredException.class)))); - // @formatter:on - } - - @Test - void emptySuitePassesWhenFailIfNoTestIsFalse() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(EmptyTestCaseWithFailIfNoTestFalseSuite.class)) - .execute() - .containerEvents() - .assertThatEvents() - .haveExactly(1, event(engine(), finishedSuccessfully())); - // @formatter:on - } - - @Test - void emptyDynamicSuiteFails() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(EmptyDynamicTestSuite.class)) - .execute() - .containerEvents() - .assertThatEvents() - .haveExactly(1, event(container(EmptyDynamicTestSuite.class), finishedWithFailure(instanceOf(NoTestsDiscoveredException.class)))); - // @formatter:on - } - - @Test - void emptyDynamicSuitePassesWhenFailIfNoTestIsFalse() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(EmptyDynamicTestWithFailIfNoTestFalseSuite.class)) - .execute() - .allEvents() - .assertThatEvents() - .haveAtLeastOne(event(container(EmptyDynamicTestWithFailIfNoTestFalseSuite.class), finishedSuccessfully())); - // @formatter:on - } - - @Test - void pruneAfterPostDiscoveryFilters() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(MultiEngineSuite.class)) - .filters(excludeTags("excluded")) - .execute() - .allEvents() - .assertThatEvents() - .haveExactly(1, event(test(JUnit4TestsTestCase.class.getName()), finishedSuccessfully())) - .doNotHave(test(TaggedTestTestCase.class.getName())) - .doNotHave(container("junit-jupiter")); - // @formatter:on - } - - @Test - void cyclicSuite() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(CyclicSuite.class)) - .execute() - .allEvents() - .assertThatEvents() - .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - - @Test - void emptyCyclicSuite() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(EmptyCyclicSuite.class)) - .execute() - .allEvents() - .assertThatEvents() - .haveExactly(1, event(container(EmptyCyclicSuite.class), finishedWithFailure(message( - "Suite [org.junit.platform.suite.engine.testsuites.EmptyCyclicSuite] did not discover any tests" - )))); - // @formatter:on - } - - @Test - void threePartCyclicSuite() { - // @formatter:off - EngineTestKit.engine(ENGINE_ID) - .selectors(selectClass(ThreePartCyclicSuite.PartA.class)) - .execute() - .allEvents() - .assertThatEvents() - .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); - // @formatter:on - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java deleted file mode 100644 index 627de28d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine; - -import static java.util.Collections.emptySet; -import static java.util.stream.Collectors.toSet; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; -import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; - -/** - * @since 1.8 - */ -class SuiteTestDescriptorTests { - @Suite - static class TestSuite { - - } - UniqueId engineId = UniqueId.forEngine(SuiteEngineDescriptor.ENGINE_ID); - UniqueId suiteId = engineId.append(SuiteTestDescriptor.SEGMENT_TYPE, "test"); - UniqueId jupiterEngineId = suiteId.append("engine", JupiterEngineDescriptor.ENGINE_ID); - UniqueId testClassId = jupiterEngineId.append(ClassTestDescriptor.SEGMENT_TYPE, SingleTestTestCase.class.getName()); - UniqueId methodId = testClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); - - ConfigurationParameters configurationParameters = new EmptyConfigurationParameters(); - SuiteTestDescriptor suite = new SuiteTestDescriptor(suiteId, TestSuite.class, configurationParameters); - - @Test - void suiteIsEmptyBeforeDiscovery() { - suite.addDiscoveryRequestFrom(SelectClassesSuite.class); - assertEquals(emptySet(), suite.getChildren()); - } - - @Test - void suiteDiscoversTestsFromClass() { - suite.addDiscoveryRequestFrom(SelectClassesSuite.class); - suite.discover(); - assertEquals(Set.of(jupiterEngineId, testClassId, methodId), - suite.getDescendants().stream().map(TestDescriptor::getUniqueId).collect(toSet())); - } - - @Test - void suitDiscoversTestsFromUniqueId() { - suite.addDiscoveryRequestFrom(methodId); - suite.discover(); - assertEquals(Set.of(jupiterEngineId, testClassId, methodId), - suite.getDescendants().stream().map(TestDescriptor::getUniqueId).collect(toSet())); - } - - @Test - void discoveryPlanCanNotBeModifiedAfterDiscovery() { - suite.addDiscoveryRequestFrom(SelectClassesSuite.class); - suite.discover(); - assertAll(() -> { - PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> suite.addDiscoveryRequestFrom(SelectClassesSuite.class)); - assertEquals("discovery request can not be modified after discovery", exception.getMessage()); - - }, () -> { - PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> suite.addDiscoveryRequestFrom(methodId)); - assertEquals("discovery request can not be modified after discovery", exception.getMessage()); - }); - } - - @Test - void suiteMayRegisterTests() { - assertTrue(suite.mayRegisterTests()); - } - - private static class EmptyConfigurationParameters implements ConfigurationParameters { - @Override - public Optional get(String key) { - return Optional.empty(); - } - - @Override - public Optional getBoolean(String key) { - return Optional.empty(); - } - - @Override - @SuppressWarnings("deprecation") - public int size() { - return 0; - } - - @Override - public Set keySet() { - return Collections.emptySet(); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java deleted file mode 100644 index 7972eee2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testcases; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; - -/** - * @since 1.8 - */ -public class DynamicTestsTestCase { - - @TestFactory - Stream dynamicTests() { - return Stream.of(// - dynamicTest("Add test", () -> assertEquals(2, Math.addExact(1, 1))), - dynamicTest("Multiply Test", () -> assertEquals(4, Math.multiplyExact(2, 2)))// - ); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java deleted file mode 100644 index be17ec51..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testcases; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; - -/** - * @since 1.9 - */ -public class EmptyDynamicTestsTestCase { - - @TestFactory - Stream dynamicTests() { - return Stream.empty(); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java deleted file mode 100644 index 1251644a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testcases; - -/** - * @since 1.9 - */ -public class EmptyTestTestCase { - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java deleted file mode 100644 index 208d48bb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testcases; - -import org.junit.Test; - -public class JUnit4TestsTestCase { - @Test - public void aTest() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java deleted file mode 100644 index 41ddbbbb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testcases; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.8 - */ -public class MultipleTestsTestCase { - - @Test - void test() { - } - - @Test - void test2() { - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java deleted file mode 100644 index bb6116eb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testcases; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.8 - */ -public class SingleTestTestCase { - - @Test - void test() { - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java deleted file mode 100644 index 0ecc4d9c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testcases; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -public class TaggedTestTestCase { - @Test - @Tag("excluded") - public void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java deleted file mode 100644 index c648e904..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; - -/** - * @since 1.8 - */ -@Suite -@SelectClasses(SingleTestTestCase.class) -public abstract class AbstractSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java deleted file mode 100644 index df2636d5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; - -/** - * @since 1.8 - */ -@Suite -@IncludeClassNamePatterns(".*") -@SelectClasses({ CyclicSuite.class, SingleTestTestCase.class }) -public class CyclicSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java deleted file mode 100644 index 525498e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.DynamicTestsTestCase; - -/** - * @since 1.8 - */ -@Suite -@SelectClasses(DynamicTestsTestCase.class) -public class DynamicSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java deleted file mode 100644 index 38554f36..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; - -/** - * @since 1.9.2 - */ -@Suite -@IncludeClassNamePatterns(".*") -@SelectClasses(EmptyCyclicSuite.class) -public class EmptyCyclicSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java deleted file mode 100644 index b5790485..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.EmptyDynamicTestsTestCase; - -/** - * @since 1.9 - */ -@Suite -@SelectClasses(EmptyDynamicTestsTestCase.class) -public class EmptyDynamicTestSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java deleted file mode 100644 index d1f87c65..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.EmptyDynamicTestsTestCase; - -/** - * @since 1.9 - */ -@Suite(failIfNoTests = false) -@SelectClasses(EmptyDynamicTestsTestCase.class) -public class EmptyDynamicTestWithFailIfNoTestFalseSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java deleted file mode 100644 index 55b72d39..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.EmptyTestTestCase; - -/** - * @since 1.9 - */ -@Suite -@SelectClasses(EmptyTestTestCase.class) -public class EmptyTestCaseSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java deleted file mode 100644 index f1d5bf56..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.EmptyTestTestCase; - -/** - * @since 1.9 - */ -@Suite(failIfNoTests = false) -@SelectClasses(EmptyTestTestCase.class) -public class EmptyTestCaseWithFailIfNoTestFalseSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java deleted file mode 100644 index ac12777b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.JUnit4TestsTestCase; -import org.junit.platform.suite.engine.testcases.TaggedTestTestCase; - -@Suite -@SelectClasses({ JUnit4TestsTestCase.class, TaggedTestTestCase.class }) -public class MultiEngineSuite { - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java deleted file mode 100644 index c05368dd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; - -/** - * @since 1.8 - */ -@Suite -@SelectClasses(MultipleTestsTestCase.class) -public class MultipleSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java deleted file mode 100644 index 7d07a866..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; - -/** - * @since 1.8 - */ -public class NestedSuite { - - @Suite - @SelectClasses(SingleTestTestCase.class) - static class Jupiter { - } - - @Suite - @SelectClasses(MultipleTestsTestCase.class) - static class Tagged { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java deleted file mode 100644 index 1d9df39e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; - -/** - * @since 1.8 - */ -@Suite -@SelectClasses(SingleTestTestCase.class) -public class SelectClassesSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java deleted file mode 100644 index 436e822d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.api.SuiteDisplayName; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; - -/** - * @since 1.8 - */ -@Suite -@SelectClasses(SingleTestTestCase.class) -@SuiteDisplayName("Suite Display Name") -public class SuiteDisplayNameSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java deleted file mode 100644 index 205c71c8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; - -/** - * @since 1.8 - */ -@Suite -@IncludeClassNamePatterns(".*") -@SelectClasses(SelectClassesSuite.class) -public class SuiteSuite { -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java deleted file mode 100644 index febbba58..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.suite.engine.testsuites; - -import org.junit.platform.suite.api.IncludeClassNamePatterns; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; -import org.junit.platform.suite.engine.testcases.SingleTestTestCase; - -/** - * @since 1.9.2 - */ -public class ThreePartCyclicSuite { - - @Suite - @IncludeClassNamePatterns(".*") - @SelectClasses({ PartB.class }) - public static class PartA { - - } - - @Suite - @IncludeClassNamePatterns(".*") - @SelectClasses({ PartC.class }) - public static class PartB { - - } - - @Suite - @IncludeClassNamePatterns(".*") - @SelectClasses({ PartB.class, SingleTestTestCase.class }) - public static class PartC { - - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java deleted file mode 100644 index 61c18d5e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import java.util.Optional; -import java.util.function.UnaryOperator; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.platform.engine.reporting.ReportEntry; - -class EngineTestKitTests { - - private static final String KEY = EngineTestKitTests.class.getName(); - - @BeforeEach - void setSystemProperty() { - System.setProperty(KEY, "from system property"); - } - - @AfterEach - void resetSystemProperty() { - System.clearProperty(KEY); - } - - @Test - void ignoresImplicitConfigurationParametersByDefault() { - var value = executeExampleTestCaseAndCollectValue(builder -> builder); - - assertThat(value).isEmpty(); - } - - @ParameterizedTest - @CsvSource({ "true, from system property", "false," }) - void usesImplicitConfigurationParametersWhenEnabled(boolean enabled, String expectedValue) { - var value = executeExampleTestCaseAndCollectValue( - builder -> builder.enableImplicitConfigurationParameters(enabled)); - - assertThat(value).isEqualTo(Optional.ofNullable(expectedValue)); - } - - private Optional executeExampleTestCaseAndCollectValue(UnaryOperator configuration) { - return configuration.apply(EngineTestKit.engine("junit-jupiter")) // - .selectors(selectClass(ExampleTestCase.class)) // - .execute() // - .allEvents() // - .reportingEntryPublished() // - .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) // - .map(ReportEntry::getKeyValuePairs) // - .map(entries -> entries.get(KEY)) // - .findFirst(); - } - - static class ExampleTestCase { - - @RegisterExtension - BeforeEachCallback callback = context -> context.getConfigurationParameter(KEY) // - .ifPresent(value -> context.publishReportEntry(KEY, value)); - - @Test - void test() { - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java deleted file mode 100644 index aeda1baa..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; -import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; -import static org.junit.platform.testkit.engine.EventConditions.started; - -import java.util.List; - -import org.assertj.core.error.AssertJMultipleFailuresError; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.support.descriptor.EngineDescriptor; -import org.opentest4j.AssertionFailedError; - -class EventsTests { - - TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("e1"), "engine"); - - List list = List.of(Event.executionStarted(engineDescriptor), - Event.executionSkipped(engineDescriptor, "reason1"), Event.executionSkipped(engineDescriptor, "reason2"), - Event.executionFinished(engineDescriptor, TestExecutionResult.successful())); - - Events events = new Events(list, "test"); - - @Test - @DisplayName("assertEventsMatchExactly: all events in order -> match") - void assertEventsMatchExactlyMatchesAllEventsInOrder() { - events.assertEventsMatchExactly( // - event(engine(), started()), // - event(engine(), skippedWithReason("reason1")), // - event(engine(), skippedWithReason("reason2")), // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: all events in order -> match") - void assertEventsMatchLooselyMatchesAllEventsInOrder() { - events.assertEventsMatchLoosely( // - event(engine(), started()), // - event(engine(), skippedWithReason("reason1")), // - event(engine(), skippedWithReason("reason2")), // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: all events in wrong order -> match") - void assertEventsMatchLooselyMatchesAllEventsInWrongOrder() { - events.assertEventsMatchLoosely( // - event(engine(), skippedWithReason("reason2")), // - event(engine(), finishedSuccessfully()), // - event(engine(), skippedWithReason("reason1")), // - event(engine(), started()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: tailing subset -> match") - void assertEventsMatchLooselyMatchesATailingSubset() { - events.assertEventsMatchLoosely( // - event(engine(), skippedWithReason("reason1")), // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: starting subset -> match") - void assertEventsMatchLooselyMatchesAStartingSubset() { - events.assertEventsMatchLoosely( // - event(engine(), started()), // - event(engine(), skippedWithReason("reason1")) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: subset in wrong order -> match") - void assertEventsMatchLooselyMatchesASubsetInWrongOrder() { - events.assertEventsMatchLoosely( // - event(engine(), skippedWithReason("reason1")), // - event(engine(), started()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: only last event -> match") - void assertEventsMatchLooselyMatchesTheLastEventAlone() { - events.assertEventsMatchLoosely( // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: only first event -> match") - void assertEventsMatchLooselyMatchesTheFirstEventAlone() { - events.assertEventsMatchLoosely( // - event(engine(), started()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLoosely: only bad events -> fails") - void assertEventsMatchLooselyWithBadConditionsOnlyFails() { - Executable willFail = () -> events.assertEventsMatchLoosely( // - event(engine(), finishedWithFailure()), // - event(engine(), skippedWithReason("other")) // - ); - - var error = assertThrows(AssertJMultipleFailuresError.class, willFail); - - var failures = error.getFailures(); - assertEquals(2, failures.size()); - assertEquals(AssertionError.class, failures.get(0).getClass()); - assertEquals(AssertionError.class, failures.get(1).getClass()); - } - - @Test - @DisplayName("assertEventsMatchLoosely: one matching and one bad event -> fails") - void assertEventsMatchLooselyWithOneMatchingAndOneBadConditionFailsPartly() { - Executable willFail = () -> events.assertEventsMatchLoosely( // - event(engine(), started()), // - event(engine(), finishedWithFailure()) // - ); - - var error = assertThrows(AssertJMultipleFailuresError.class, willFail); - - var failures = error.getFailures(); - assertEquals(1, failures.size()); - assertEquals(AssertionError.class, failures.get(0).getClass()); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: all events in order -> match") - void assertEventsMatchLooselyInOrderMatchesAllEventsInOrder() { - events.assertEventsMatchLooselyInOrder( // - event(engine(), started()), // - event(engine(), skippedWithReason("reason1")), // - event(engine(), skippedWithReason("reason2")), // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: all events in wrong order -> fail") - void assertEventsMatchLooselyInOrderWithAllEventsInWrongOrderFails() { - Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // - event(engine(), skippedWithReason("reason2")), // - event(engine(), finishedSuccessfully()), // - event(engine(), skippedWithReason("reason1")), // - event(engine(), started()) // - ); - - var error = assertThrows(AssertionFailedError.class, willFail); - assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: tailing subset in order -> match") - void assertEventsMatchLooselyInOrderMatchesATailingSubset() { - events.assertEventsMatchLooselyInOrder( // - event(engine(), skippedWithReason("reason1")), // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: starting subset in order -> match") - void assertEventsMatchLooselyInOrderMatchesAStartingSubset() { - events.assertEventsMatchLooselyInOrder( // - event(engine(), started()), // - event(engine(), skippedWithReason("reason1")) // - ); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: subset in wrong order -> fail") - void assertEventsMatchLooselyInOrderWithASubsetInWrongOrderFails() { - Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // - event(engine(), skippedWithReason("reason1")), // - event(engine(), started()) // - ); - - var error = assertThrows(AssertionFailedError.class, willFail); - assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: last event alone -> match") - void assertEventsMatchLooselyInOrderMatchesTheLastEventAlone() { - events.assertEventsMatchLooselyInOrder( // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: first event alone -> match") - void assertEventsMatchLooselyInOrderMatchesTheFirstEventAlone() { - events.assertEventsMatchLooselyInOrder( // - event(engine(), started()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: bad events only -> fail") - void assertEventsMatchLooselyInOrderWithBadConditionsOnlyFails() { - Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // - event(engine(), finishedWithFailure()), // - event(engine(), skippedWithReason("other")) // - ); - - var error = assertThrows(AssertJMultipleFailuresError.class, willFail); - - var failures = error.getFailures(); - assertEquals(2, failures.size()); - assertEquals(AssertionError.class, failures.get(0).getClass()); - assertEquals(AssertionError.class, failures.get(1).getClass()); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: one matching event and one bad event -> fail") - void assertEventsMatchLooselyInOrderWithOneMatchingAndOneBadConditionFailsPartly() { - Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // - event(engine(), started()), // - event(engine(), finishedWithFailure()) // - ); - - var error = assertThrows(AssertJMultipleFailuresError.class, willFail); - assertEquals(1, error.getFailures().size()); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: first and last event in order -> match") - void assertEventsMatchLooselyInOrderMatchesFirstAndLastEventInOrder() { - events.assertEventsMatchLooselyInOrder( // - event(engine(), started()), // - event(engine(), finishedSuccessfully()) // - ); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: second and last event in bad order -> fail") - void assertEventsMatchLooselyInOrderWithSecondAndLastEventInBadOrderFails() { - Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // - event(engine(), finishedSuccessfully()), // - event(engine(), skippedWithReason("reason1")) // - ); - - var error = assertThrows(AssertionFailedError.class, willFail); - assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); - } - - @Test - @DisplayName("assertEventsMatchLooselyInOrder: too many events -> fail") - void assertEventsMatchLooselyInOrderWithTooManyEventsFails() { - Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // - event(engine(), finishedSuccessfully()), // - event(engine(), finishedSuccessfully()), // - event(engine(), finishedSuccessfully()), // - event(engine(), finishedSuccessfully()), // - event(engine(), finishedSuccessfully()), // - event(engine(), finishedSuccessfully())); - - var error = assertThrows(AssertionError.class, willFail); - assertTrue(error.getMessage().endsWith("to be less than or equal to 4 but was 6")); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java deleted file mode 100644 index ea609b60..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link Executions}. - * - * @since 1.4 - */ -class ExecutionsIntegrationTests { - - @Test - void executionsFromSkippedTestEvents() { - var testEvents = getTestEvents(); - - // We expect 1 for both of the following cases, since an Execution can - // be created for a "skipped event even if "started" and "finished" events - // are filtered out. - assertThat(testEvents.executions().skipped().count()).isEqualTo(1); - assertThat(testEvents.skipped().executions().count()).isEqualTo(1); - } - - @Test - void executionsFromStartedTestEvents() { - var testEvents = getTestEvents(); - - // We expect 3 if the executions are created BEFORE filtering out "finished" events. - assertThat(testEvents.executions().started().count()).isEqualTo(3); - // We expect 0 if the executions are created AFTER filtering out "finished" events. - assertThat(testEvents.started().executions().count()).isEqualTo(0); - } - - @Test - void executionsFromFinishedTestEvents() { - var testEvents = getTestEvents(); - - // We expect 3 if the executions are created BEFORE filtering out "started" events. - assertThat(testEvents.executions().finished().count()).isEqualTo(3); - // We expect 0 if the executions are created AFTER filtering out "started" events. - assertThat(testEvents.finished().executions().count()).isEqualTo(0); - } - - @Test - void executionsFromSucceededTestEvents() { - var testEvents = getTestEvents(); - - // We expect 1 if the executions are created BEFORE filtering out "finished" events. - assertThat(testEvents.executions().succeeded().count()).isEqualTo(1); - // We expect 0 if the executions are created AFTER filtering out "finished" events. - assertThat(testEvents.succeeded().executions().count()).isEqualTo(0); - } - - @Test - void executionsFromAbortedTestEvents() { - var testEvents = getTestEvents(); - - // We expect 1 if the executions are created BEFORE filtering out "started" events. - assertThat(testEvents.executions().aborted().count()).isEqualTo(1); - // We expect 0 if the executions are created AFTER filtering out "started" events. - assertThat(testEvents.aborted().executions().count()).isEqualTo(0); - } - - @Test - void executionsFromFailedTestEvents() { - var testEvents = getTestEvents(); - - // We expect 1 if the executions are created BEFORE filtering out "started" events. - assertThat(testEvents.executions().failed().count()).isEqualTo(1); - // We expect 0 if the executions are created AFTER filtering out "started" events. - assertThat(testEvents.failed().executions().count()).isEqualTo(0); - } - - private Events getTestEvents() { - return EngineTestKit.engine("junit-jupiter")// - .selectors(selectClass(ExampleTestCase.class))// - .execute()// - .testEvents()// - .assertStatistics(stats -> stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); - } - - static class ExampleTestCase { - - @Test - @Disabled - void skippedTest() { - } - - @Test - void succeedingTest() { - } - - @Test - void abortedTest() { - assumeTrue(false); - } - - @Test - void failingTest() { - fail("Boom!"); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java deleted file mode 100644 index c16b2e26..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.testkit.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.testkit.engine.EventConditions.container; -import static org.junit.platform.testkit.engine.EventConditions.displayName; -import static org.junit.platform.testkit.engine.EventConditions.engine; -import static org.junit.platform.testkit.engine.EventConditions.event; -import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; -import static org.junit.platform.testkit.engine.EventConditions.nestedContainer; -import static org.junit.platform.testkit.engine.EventConditions.started; -import static org.junit.platform.testkit.engine.EventConditions.test; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.HashMap; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.testkit.engine.NestedContainerEventConditionTests.ATestCase.BTestCase; -import org.junit.platform.testkit.engine.NestedContainerEventConditionTests.ATestCase.BTestCase.CTestCase; - -/** - * @since 1.6 - */ -class NestedContainerEventConditionTests { - - @Test - void preconditions() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> nestedContainer(null))// - .withMessage("Class must not be null"); - - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> nestedContainer(NestedContainerEventConditionTests.class))// - .withMessage(NestedContainerEventConditionTests.class.getName() + " must be a nested class"); - } - - @Test - void nestedContainerChecksSuppliedClassAndAllEnclosingClasses() { - var uniqueId = UniqueId.root("top-level", getClass().getName())// - .append("nested", ATestCase.class.getSimpleName())// - .append("nested", BTestCase.class.getSimpleName())// - .append("nested", CTestCase.class.getSimpleName()); - var event = createEvent(uniqueId); - - var condition = nestedContainer(HashMap.Entry.class); - assertThat(condition.matches(event)).isFalse(); - assertThat(condition.toString()).contains(// - "is a container", "with uniqueId substring 'Map'", "with uniqueId substring 'Entry'"); - - condition = nestedContainer(ATestCase.BTestCase.CTestCase.class); - assertThat(condition.matches(event)).isTrue(); - assertThat(condition.toString()).contains(// - "is a container", "with uniqueId substring 'NestedContainerEventConditionTests'", - "with uniqueId substring 'ATestCase'", "with uniqueId substring 'BTestCase'", - "with uniqueId substring 'CTestCase'"); - } - - private Event createEvent(UniqueId uniqueId) { - var testDescriptor = mock(TestDescriptor.class); - when(testDescriptor.isContainer()).thenReturn(true); - when(testDescriptor.getUniqueId()).thenReturn(uniqueId); - - var event = mock(Event.class); - when(event.getTestDescriptor()).thenReturn(testDescriptor); - return event; - } - - @Test - void usingNestedContainerCorrectly() { - assertDoesNotThrow(() -> container(ATestCase.class)); - assertDoesNotThrow(() -> nestedContainer(ATestCase.class)); - - assertDoesNotThrow(() -> container(ATestCase.BTestCase.class)); - assertDoesNotThrow(() -> nestedContainer(ATestCase.BTestCase.class)); - - assertDoesNotThrow(() -> container(ATestCase.BTestCase.CTestCase.class)); - assertDoesNotThrow(() -> nestedContainer(ATestCase.BTestCase.CTestCase.class)); - - assertDoesNotThrow(() -> container(NestedContainerEventConditionTests.class)); - } - - @Test - void eventConditionsForMultipleLevelsOfNestedClasses() { - // @formatter:off - EngineTestKit.engine("junit-jupiter") - .selectors(selectClass(ATestCase.class)) - .execute() - .allEvents() - .assertEventsMatchExactly( - event(engine(), started()), - event(container(ATestCase.class), started()), - event(test("test_a"), started()), - event(test("test_a"), finishedSuccessfully()), - event(nestedContainer(ATestCase.BTestCase.class, displayName("Test case B")), started()), - event(test("test_b"), started()), - event(test("test_b"), finishedSuccessfully()), - event(nestedContainer(ATestCase.BTestCase.CTestCase.class), started()), - event(test("test_c"), started()), - event(test("test_c"), finishedSuccessfully()), - event(nestedContainer(ATestCase.BTestCase.CTestCase.class), finishedSuccessfully()), - event(nestedContainer(ATestCase.BTestCase.class, displayName("Test case B")), finishedSuccessfully()), - event(container(ATestCase.class), finishedSuccessfully()), - event(engine(), finishedSuccessfully()) - ); - // @formatter:on - } - - static class ATestCase { - - @Test - void test_a() { - } - - @Nested - @DisplayName("Test case B") - class BTestCase { - @Test - void test_b() { - } - - @Nested - class CTestCase { - @Test - void test_c() { - } - } - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt deleted file mode 100644 index b559d040..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt +++ /dev/null @@ -1,22 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Started: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) -Finished: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) -Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt deleted file mode 100644 index b559d040..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt +++ /dev/null @@ -1,22 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Started: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) -Finished: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) -Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt deleted file mode 100644 index 1a826306..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt +++ /dev/null @@ -1,18 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Basic [OK] - '-- .oO fancy display name Oo. [OK] - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt deleted file mode 100644 index de935d05..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt +++ /dev/null @@ -1,18 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Basic ✔ - └─ .oO fancy display name Oo. ✔ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt deleted file mode 100644 index f401e486..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Basic -| | +-- .oO fancy display name Oo. -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'changeDisplayName', methodParameterTypes = ''] -\| \| \| duration: [\d]+ ms -| | | status: [OK] SUCCESSFUL -\| '-- Basic finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt deleted file mode 100644 index 021c28c3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Basic -│ │ ├─ .oO fancy display name Oo. -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'changeDisplayName', methodParameterTypes = ''] -│ │ │ duration: [\d]+ ms -│ │ │ status: ✔ SUCCESSFUL -│ └─ Basic finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt deleted file mode 100644 index b51dd812..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt +++ /dev/null @@ -1,22 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Started: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) -Finished: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) -Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt deleted file mode 100644 index b51dd812..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt +++ /dev/null @@ -1,22 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Started: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) -Finished: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) -Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt deleted file mode 100644 index 72b9ffb1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt +++ /dev/null @@ -1,18 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Basic [OK] - '-- empty() [OK] - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt deleted file mode 100644 index 3eb81451..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt +++ /dev/null @@ -1,18 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Basic ✔ - └─ empty() ✔ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt deleted file mode 100644 index 7d2c448c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Basic -| | +-- empty() -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'empty', methodParameterTypes = ''] -\| \| \| duration: [\d]+ ms -| | | status: [OK] SUCCESSFUL -\| '-- Basic finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt deleted file mode 100644 index 36a4ec35..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Basic -│ │ ├─ empty() -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'empty', methodParameterTypes = ''] -│ │ │ duration: [\d]+ ms -│ │ │ status: ✔ SUCCESSFUL -│ └─ Basic finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt deleted file mode 100644 index db92d1c7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt +++ /dev/null @@ -1,27 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Started: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) -Finished: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) - => Exception: org.opentest4j.AssertionFailedError: multi - line - fail - message ->> S T A C K T R A C E >> -Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt deleted file mode 100644 index db92d1c7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt +++ /dev/null @@ -1,27 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Started: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) -Finished: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) - => Exception: org.opentest4j.AssertionFailedError: multi - line - fail - message ->> S T A C K T R A C E >> -Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt deleted file mode 100644 index e55bd31b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt +++ /dev/null @@ -1,23 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithMultiLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: multi -line -fail -message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt deleted file mode 100644 index e55bd31b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt +++ /dev/null @@ -1,23 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithMultiLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: multi -line -fail -message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt deleted file mode 100644 index e55bd31b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt +++ /dev/null @@ -1,23 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithMultiLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: multi -line -fail -message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt deleted file mode 100644 index e55bd31b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt +++ /dev/null @@ -1,23 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithMultiLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: multi -line -fail -message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt deleted file mode 100644 index 2342df65..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Fail [OK] - '-- failWithMultiLineMessage() [X] multi - line - fail - message - -Failures (1): - JUnit Jupiter:Fail:failWithMultiLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: multi -line -fail -message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt deleted file mode 100644 index 5e557707..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Fail ✔ - └─ failWithMultiLineMessage() ✘ multi - line - fail - message - -Failures (1): - JUnit Jupiter:Fail:failWithMultiLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: multi -line -fail -message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt deleted file mode 100644 index 62bcc4f6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt +++ /dev/null @@ -1,33 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Fail -| | +-- failWithMultiLineMessage() -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] -| | | caught: org.opentest4j.AssertionFailedError: multi -| | | line -| | | fail -| | | message ->> S T A C K T R A C E >> -\| \| \| duration: [\d]+ ms -| | | status: [X] FAILED -\| '-- Fail finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt deleted file mode 100644 index 5c087c57..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt +++ /dev/null @@ -1,33 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Fail -│ │ ├─ failWithMultiLineMessage() -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] -│ │ │ caught: org.opentest4j.AssertionFailedError: multi -│ │ │ line -│ │ │ fail -│ │ │ message ->> S T A C K T R A C E >> -│ │ │ duration: [\d]+ ms -│ │ │ status: ✘ FAILED -│ └─ Fail finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt deleted file mode 100644 index 04752703..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Started: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) -Finished: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) - => Exception: org.opentest4j.AssertionFailedError: single line fail message ->> S T A C K T R A C E >> -Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt deleted file mode 100644 index 04752703..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Started: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) -Finished: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) - => Exception: org.opentest4j.AssertionFailedError: single line fail message ->> S T A C K T R A C E >> -Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt deleted file mode 100644 index c734ea9f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt +++ /dev/null @@ -1,20 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithSingleLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: single line fail message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt deleted file mode 100644 index c734ea9f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt +++ /dev/null @@ -1,20 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithSingleLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: single line fail message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt deleted file mode 100644 index c734ea9f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt +++ /dev/null @@ -1,20 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithSingleLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: single line fail message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt deleted file mode 100644 index c734ea9f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt +++ /dev/null @@ -1,20 +0,0 @@ - -Failures (1): - JUnit Jupiter:Fail:failWithSingleLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: single line fail message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt deleted file mode 100644 index 256a14db..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Fail [OK] - '-- failWithSingleLineMessage() [X] single line fail message - -Failures (1): - JUnit Jupiter:Fail:failWithSingleLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: single line fail message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt deleted file mode 100644 index d1e99da9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Fail ✔ - └─ failWithSingleLineMessage() ✘ single line fail message - -Failures (1): - JUnit Jupiter:Fail:failWithSingleLineMessage() - MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] - => org.opentest4j.AssertionFailedError: single line fail message ->> STACKTRACE >> - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt deleted file mode 100644 index 6b69d602..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Fail -| | +-- failWithSingleLineMessage() -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] -| | | caught: org.opentest4j.AssertionFailedError: single line fail message ->> S T A C K T R A C E >> -\| \| \| duration: [\d]+ ms -| | | status: [X] FAILED -\| '-- Fail finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt deleted file mode 100644 index 6dc5cca6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Fail -│ │ ├─ failWithSingleLineMessage() -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] -│ │ │ caught: org.opentest4j.AssertionFailedError: single line fail message ->> S T A C K T R A C E >> -│ │ │ duration: [\d]+ ms -│ │ │ status: ✘ FAILED -│ └─ Fail finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 1 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt deleted file mode 100644 index 588f1409..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) -Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] -Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] -Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] -Finished: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt deleted file mode 100644 index 588f1409..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) -Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] -Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] -Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] -Finished: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt deleted file mode 100644 index 177e716f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt +++ /dev/null @@ -1,26 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Report [OK] - '-- reportMultiEntriesWithMultiMappings(TestReporter) [OK] - ....-..-..T..:...* - user name = `dk38` - award year = `1974` - ....-..-..T..:...* single = `mapping` - ....-..-..T..:...* - user name = `st77` - award year = `1977` - last seen = `2001` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt deleted file mode 100644 index ab028e5f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt +++ /dev/null @@ -1,26 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Report ✔ - └─ reportMultiEntriesWithMultiMappings(TestReporter) ✔ - ....-..-..T..:...* - user name = `dk38` - award year = `1974` - ....-..-..T..:...* single = `mapping` - ....-..-..T..:...* - user name = `st77` - award year = `1977` - last seen = `2001` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt deleted file mode 100644 index d67edf08..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt +++ /dev/null @@ -1,31 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Report -| | +-- reportMultiEntriesWithMultiMappings(TestReporter) -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithMultiMappings', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] -\| \| \| duration: [\d]+ ms -| | | status: [OK] SUCCESSFUL -\| '-- Report finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt deleted file mode 100644 index be34c7a2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt +++ /dev/null @@ -1,31 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Report -│ │ ├─ reportMultiEntriesWithMultiMappings(TestReporter) -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithMultiMappings', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] -│ │ │ duration: [\d]+ ms -│ │ │ status: ✔ SUCCESSFUL -│ └─ Report finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt deleted file mode 100644 index 99357843..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt +++ /dev/null @@ -1,26 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] -Finished: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt deleted file mode 100644 index 99357843..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt +++ /dev/null @@ -1,26 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] -Finished: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt deleted file mode 100644 index a7b2f6a4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt +++ /dev/null @@ -1,20 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Report [OK] - '-- reportMultiEntriesWithSingleMapping(TestReporter) [OK] - ....-..-..T..:...* foo = `bar` - ....-..-..T..:...* far = `boo` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt deleted file mode 100644 index 49d82d1c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt +++ /dev/null @@ -1,20 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Report ✔ - └─ reportMultiEntriesWithSingleMapping(TestReporter) ✔ - ....-..-..T..:...* foo = `bar` - ....-..-..T..:...* far = `boo` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt deleted file mode 100644 index 7cb17e6f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Report -| | +-- reportMultiEntriesWithSingleMapping(TestReporter) -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] -\| \| \| duration: [\d]+ ms -| | | status: [OK] SUCCESSFUL -\| '-- Report finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt deleted file mode 100644 index 55dccea9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Report -│ │ ├─ reportMultiEntriesWithSingleMapping(TestReporter) -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] -│ │ │ duration: [\d]+ ms -│ │ │ status: ✔ SUCCESSFUL -│ └─ Report finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt deleted file mode 100644 index 94111e48..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt +++ /dev/null @@ -1,26 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) -Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] -Finished: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt deleted file mode 100644 index 94111e48..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt +++ /dev/null @@ -1,26 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) -Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] -Finished: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt deleted file mode 100644 index 792a952d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt +++ /dev/null @@ -1,20 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Report [OK] - '-- reportMultipleMessages(TestReporter) [OK] - ....-..-..T..:...* value = `foo` - ....-..-..T..:...* value = `bar` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt deleted file mode 100644 index b211a265..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt +++ /dev/null @@ -1,20 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Report ✔ - └─ reportMultipleMessages(TestReporter) ✔ - ....-..-..T..:...* value = `foo` - ....-..-..T..:...* value = `bar` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt deleted file mode 100644 index 38022210..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Report -| | +-- reportMultipleMessages(TestReporter) -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultipleMessages', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] -\| \| \| duration: [\d]+ ms -| | | status: [OK] SUCCESSFUL -\| '-- Report finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt deleted file mode 100644 index a281ac28..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt +++ /dev/null @@ -1,30 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Report -│ │ ├─ reportMultipleMessages(TestReporter) -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultipleMessages', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] -│ │ │ duration: [\d]+ ms -│ │ │ status: ✔ SUCCESSFUL -│ └─ Report finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt deleted file mode 100644 index 58c3d7c7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Reported: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -Finished: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt deleted file mode 100644 index 58c3d7c7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Reported: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -Finished: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt deleted file mode 100644 index f6670d86..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt +++ /dev/null @@ -1,19 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Report [OK] - '-- reportSingleEntryWithSingleMapping(TestReporter) [OK] - ....-..-..T..:...* foo = `bar` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt deleted file mode 100644 index 15319f4d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt +++ /dev/null @@ -1,19 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Report ✔ - └─ reportSingleEntryWithSingleMapping(TestReporter) ✔ - ....-..-..T..:...* foo = `bar` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt deleted file mode 100644 index f5432406..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt +++ /dev/null @@ -1,29 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Report -| | +-- reportSingleEntryWithSingleMapping(TestReporter) -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleEntryWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -\| \| \| duration: [\d]+ ms -| | | status: [OK] SUCCESSFUL -\| '-- Report finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt deleted file mode 100644 index 3a592fab..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt +++ /dev/null @@ -1,29 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Report -│ │ ├─ reportSingleEntryWithSingleMapping(TestReporter) -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleEntryWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] -│ │ │ duration: [\d]+ ms -│ │ │ status: ✔ SUCCESSFUL -│ └─ Report finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt deleted file mode 100644 index a5397dc6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) -Reported: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -Finished: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt deleted file mode 100644 index a5397dc6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt +++ /dev/null @@ -1,24 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Started: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) -Reported: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) - => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -Finished: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) -Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt deleted file mode 100644 index 03825a46..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt deleted file mode 100644 index 06123735..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt +++ /dev/null @@ -1,19 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Report [OK] - '-- reportSingleMessage(TestReporter) [OK] - ....-..-..T..:...* value = `foo` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt deleted file mode 100644 index 485ea73f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt +++ /dev/null @@ -1,19 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Report ✔ - └─ reportSingleMessage(TestReporter) ✔ - ....-..-..T..:...* value = `foo` - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt deleted file mode 100644 index 3cfe0069..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt +++ /dev/null @@ -1,29 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Report -| | +-- reportSingleMessage(TestReporter) -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleMessage', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -\| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -\| \| \| duration: [\d]+ ms -| | | status: [OK] SUCCESSFUL -\| '-- Report finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt deleted file mode 100644 index 0539900d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt +++ /dev/null @@ -1,29 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Report -│ │ ├─ reportSingleMessage(TestReporter) -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleMessage', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] -│ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] -│ │ │ duration: [\d]+ ms -│ │ │ status: ✔ SUCCESSFUL -│ └─ Report finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 0 tests skipped ] -[ 1 tests started ] -[ 0 tests aborted ] -[ 1 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt deleted file mode 100644 index 04e6ca20..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt +++ /dev/null @@ -1,25 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Skipped: skipWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()]) - => Reason: multi - line - fail - message -Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt deleted file mode 100644 index 04e6ca20..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt +++ /dev/null @@ -1,25 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Skipped: skipWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()]) - => Reason: multi - line - fail - message -Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt deleted file mode 100644 index c5e5579e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt deleted file mode 100644 index c5e5579e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt deleted file mode 100644 index ae8bb3e5..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt +++ /dev/null @@ -1,21 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Skip [OK] - '-- skipWithMultiLineMessage() [S] multi - line - fail - message - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt deleted file mode 100644 index d9c1d79c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt +++ /dev/null @@ -1,21 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Skip ✔ - └─ skipWithMultiLineMessage() ↷ multi - line - fail - message - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt deleted file mode 100644 index e63b0b52..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt +++ /dev/null @@ -1,31 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Skip -| | +-- skipWithMultiLineMessage() -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithMultiLineMessage', methodParameterTypes = ''] -| | | reason: multi -| | | line -| | | fail -| | | message -| | | status: [S] SKIPPED -\| '-- Skip finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt deleted file mode 100644 index ebf4caf9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt +++ /dev/null @@ -1,31 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Skip -│ │ ├─ skipWithMultiLineMessage() -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithMultiLineMessage', methodParameterTypes = ''] -│ │ │ reason: multi -│ │ │ line -│ │ │ fail -│ │ │ message -│ │ │ status: ↷ SKIPPED -│ └─ Skip finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt deleted file mode 100644 index d603b695..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt +++ /dev/null @@ -1,22 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Skipped: skipWithSingleLineReason() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()]) - => Reason: single line skip reason -Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt deleted file mode 100644 index d603b695..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt +++ /dev/null @@ -1,22 +0,0 @@ -Test execution started. Number of static tests: 1 -Started: JUnit Jupiter ([engine:junit-jupiter]) -Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Skipped: skipWithSingleLineReason() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()]) - => Reason: single line skip reason -Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) -Finished: JUnit Jupiter ([engine:junit-jupiter]) -Test execution finished. - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt deleted file mode 100644 index c5e5579e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt deleted file mode 100644 index c5e5579e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt deleted file mode 100644 index 7e2b8a40..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt +++ /dev/null @@ -1,18 +0,0 @@ -. -'-- JUnit Jupiter [OK] - '-- Skip [OK] - '-- skipWithSingleLineReason() [S] single line skip reason - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt deleted file mode 100644 index df6349dc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt +++ /dev/null @@ -1,18 +0,0 @@ -╷ -└─ JUnit Jupiter ✔ - └─ Skip ✔ - └─ skipWithSingleLineReason() ↷ single line skip reason - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt deleted file mode 100644 index b24577c6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test plan execution started. Number of static tests: 1 -. -+-- JUnit Jupiter -| +-- Skip -| | +-- skipWithSingleLineReason() -| | | tags: [] -| | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()] -| | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] -| | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithSingleLineReason', methodParameterTypes = ''] -| | | reason: single line skip reason -| | | status: [S] SKIPPED -\| '-- Skip finished after [\d]+ ms\. -'-- JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt deleted file mode 100644 index 01537a1c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt +++ /dev/null @@ -1,28 +0,0 @@ -Test plan execution started. Number of static tests: 1 -╷ -├─ JUnit Jupiter -│ ├─ Skip -│ │ ├─ skipWithSingleLineReason() -│ │ │ tags: [] -│ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()] -│ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] -│ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithSingleLineReason', methodParameterTypes = ''] -│ │ │ reason: single line skip reason -│ │ │ status: ↷ SKIPPED -│ └─ Skip finished after [\d]+ ms\. -└─ JUnit Jupiter finished after [\d]+ ms\. -Test plan execution finished. Number of all tests: 1 - -Test run finished after [\d]+ ms -[ 2 containers found ] -[ 0 containers skipped ] -[ 2 containers started ] -[ 0 containers aborted ] -[ 2 containers successful ] -[ 0 containers failed ] -[ 1 tests found ] -[ 1 tests skipped ] -[ 0 tests started ] -[ 0 tests aborted ] -[ 0 tests successful ] -[ 0 tests failed ] diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt deleted file mode 100644 index 91c5d9e7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/do_not_delete_me.txt +++ /dev/null @@ -1,3 +0,0 @@ -I am used by tests, so... - -Do NOT delete me! \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd deleted file mode 100644 index 9ee9cea4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/jenkins-junit.xsd +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle deleted file mode 100644 index a4185e83..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle deleted file mode 100644 index a4185e83..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts deleted file mode 100644 index a4185e83..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts deleted file mode 100644 index a4185e83..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml deleted file mode 100644 index 1a462ec9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml deleted file mode 100644 index 653e2017..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java deleted file mode 100644 index 0b855e5d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java +++ /dev/null @@ -1,3 +0,0 @@ -package foo.bar; - -public class FooBar {} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java deleted file mode 100644 index d216af08..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java +++ /dev/null @@ -1,3 +0,0 @@ -open module foo.bar { - requires foo; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java deleted file mode 100644 index 7f566baf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/Foo.java +++ /dev/null @@ -1,3 +0,0 @@ -package foo; - -public class Foo {} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java deleted file mode 100644 index 43084978..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/modules-2500/foo/module-info.java +++ /dev/null @@ -1,3 +0,0 @@ -module foo { - exports foo; -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/serialized-test-identifier b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/serialized-test-identifier deleted file mode 100644 index 614b53bb6557e251e93c145414c5f3efa2dd0f53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1079 zcmb7Dv2N5r5S_amkca?DkccRPgcK+u*lj3Kt^kn)5y?V`a~h%`E%u&o!`|B2U5C3S zx`rAlQfT-EdJw-rMFkC?kUA~1YX@7WBM~lad1vO$dv9hxAHxNsVI|cG+0B)(B$Lc` zQ{5+$fPARuLr?iot`ea*b!<9555ZfO zCC0+FqH%78q#bU3V3;8gJqKu=n@Eeyrg}NV0xX(AY!)lz zK&ZDoe#T4>`~Q+ZUmg8kK@it}zRD`&-`3wBef;|6!IV?=ZWhJOJ$mdkLIY86Pw=(e z4(DUY5}%66o@7g%-WNBN7Q^`9K*RmBQO5SVM1%#;_^M~@ZYkMNdA}ZXEy9gXXklq= OujzKw6Ea@)@0~x1PiaX2 diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties deleted file mode 100644 index 1ba78cae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/test-junit-platform.properties +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.launcher.core.LauncherConfigurationParametersTests = from config file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener deleted file mode 100644 index 3c8c2d50..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.launcher.TestLauncherDiscoveryListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener deleted file mode 100644 index a41b4e59..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.launcher.TestLauncherSessionListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter deleted file mode 100644 index c7c86539..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter +++ /dev/null @@ -1 +0,0 @@ -org.junit.platform.launcher.TestPostDiscoveryTagFilter diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener deleted file mode 100644 index 4bb9bbe7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener +++ /dev/null @@ -1,3 +0,0 @@ -org.junit.platform.launcher.listeners.NoopTestExecutionListener -org.junit.platform.launcher.listeners.UnusedTestExecutionListener -org.junit.platform.launcher.listeners.AnotherUnusedTestExecutionListener \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties deleted file mode 100644 index 7b87d3ca..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tests/src/test/resources/testservices/junit-platform.properties +++ /dev/null @@ -1 +0,0 @@ -junit.platform.execution.listeners.deactivate=org.junit.*.launcher.listeners.Unused*,org.junit.*.launcher.listeners.AnotherUnused* \ No newline at end of file diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts deleted file mode 100644 index f594af84..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ /dev/null @@ -1,174 +0,0 @@ -import org.gradle.api.tasks.PathSensitivity.RELATIVE -import org.gradle.jvm.toolchain.internal.NoToolchainAvailableException - -plugins { - `kotlin-library-conventions` - `testing-conventions` -} - -javaLibrary { - mainJavaVersion = JavaVersion.VERSION_11 -} - -spotless { - java { - target(files(project.java.sourceSets.map { it.allJava }), "projects/**/*.java") - } - kotlin { - target("projects/**/*.kt") - } - format("projects") { - target("projects/**/*.gradle.kts", "projects/**/*.md") - trimTrailingWhitespace() - endWithNewline() - } -} - -val thirdPartyJars by configurations.creating -val antJars by configurations.creating -val mavenDistribution by configurations.creating - -dependencies { - implementation(libs.bartholdy) { - because("manage external tool installations") - } - implementation(libs.commons.io) { - because("moving/deleting directory trees") - } - - testImplementation(libs.archunit) { - because("checking the architecture of JUnit 5") - } - testImplementation(libs.apiguardian) { - because("we validate that public classes are annotated") - } - testImplementation(libs.groovy4) { - because("it provides convenience methods to handle process output") - } - testImplementation(libs.bnd) { - because("parsing OSGi metadata") - } - testRuntimeOnly(libs.slf4j.julBinding) { - because("provide appropriate SLF4J binding") - } - testImplementation(libs.ant) { - because("we reference Ant's main class") - } - testImplementation(libs.bundles.xmlunit) - - thirdPartyJars(libs.junit4) - thirdPartyJars(libs.assertj) - thirdPartyJars(libs.apiguardian) - thirdPartyJars(libs.hamcrest) - thirdPartyJars(libs.opentest4j) - - antJars(platform(projects.junitBom)) - antJars(libs.bundles.ant) - antJars(projects.junitPlatformConsoleStandalone) - antJars(projects.junitPlatformLauncher) - antJars(projects.junitPlatformReporting) - - mavenDistribution(libs.maven) { - artifact { - classifier = "bin" - type = "zip" - isTransitive = false - } - } -} - -val unzipMavenDistribution by tasks.registering(Sync::class) { - from(zipTree(mavenDistribution.elements.map { it.single() })) - into(layout.buildDirectory.dir("maven-distribution")) -} - -tasks.test { - // Opt-out via system property: '-Dplatform.tooling.support.tests.enabled=false' - enabled = System.getProperty("platform.tooling.support.tests.enabled")?.toBoolean() ?: true - - // The following if-block is necessary since Gradle will otherwise - // always publish all mavenizedProjects even if this "test" task - // is not executed. - if (enabled) { - - // All maven-aware projects must be installed, i.e. published to the local repository - val mavenizedProjects: List by rootProject - val tempRepoName: String by rootProject - - (mavenizedProjects + projects.junitBom.dependencyProject) - .map { project -> project.tasks.named("publishAllPublicationsTo${tempRepoName.capitalize()}Repository") } - .forEach { dependsOn(it) } - } - - val tempRepoDir: File by rootProject - jvmArgumentProviders += MavenRepo(tempRepoDir) - jvmArgumentProviders += JarPath(thirdPartyJars) - jvmArgumentProviders += JarPath(antJars) - jvmArgumentProviders += MavenDistribution(project, unzipMavenDistribution) - - (options as JUnitPlatformOptions).apply { - includeEngines("archunit") - } - - inputs.apply { - dir("projects").withPathSensitivity(RELATIVE) - file("${rootDir}/gradle.properties") - file("${rootDir}/settings.gradle.kts") - file("${rootDir}/gradlew") - file("${rootDir}/gradlew.bat") - dir("${rootDir}/gradle/wrapper").withPathSensitivity(RELATIVE) - dir("${rootDir}/documentation/src/main").withPathSensitivity(RELATIVE) - dir("${rootDir}/documentation/src/test").withPathSensitivity(RELATIVE) - } - - distribution { - requirements.add("jdk=8") - localOnly { - includeClasses.add("platform.tooling.support.tests.GraalVmStarterTests") // GraalVM is not installed on Test Distribution agents - } - } - jvmArgumentProviders += JavaHomeDir(project, 8) -} - -class MavenRepo(@get:InputDirectory @get:PathSensitive(RELATIVE) val repoDir: File) : CommandLineArgumentProvider { - override fun asArguments() = listOf("-Dmaven.repo=$repoDir") -} - -class JavaHomeDir(project: Project, @Input val version: Int) : CommandLineArgumentProvider { - @Internal - val passToolchain = project.providers.gradleProperty("enableTestDistribution").map(String::toBoolean).orElse(false).map { !it } - - @Internal - val javaLauncher: Property = project.objects.property() - .value(project.provider { - try { - project.javaToolchains.launcherFor { - languageVersion.set(JavaLanguageVersion.of(version)) - }.get() - } catch (e: NoToolchainAvailableException) { - null - } - }) - - override fun asArguments(): List { - if (passToolchain.get()) { - val metadata = javaLauncher.map { it.metadata } - val javaHome = metadata.map { it.installationPath.asFile.absolutePath }.orNull - return javaHome?.let { listOf("-Djava.home.$version=$it") } ?: emptyList() - } - return emptyList() - } -} - -class JarPath(@Classpath val configuration: Configuration, @Input val key: String = configuration.name) : CommandLineArgumentProvider { - override fun asArguments() = listOf("-D${key}=${configuration.asPath}") -} - -class MavenDistribution(project: Project, sourceTask: TaskProvider<*>) : CommandLineArgumentProvider { - @InputDirectory - @PathSensitive(RELATIVE) - val mavenDistribution: DirectoryProperty = project.objects.directoryProperty() - .value(project.layout.dir(sourceTask.map { it.outputs.files.singleFile.listFiles()!!.single() })) - - override fun asArguments() = listOf("-DmavenDistribution=${mavenDistribution.get().asFile.absolutePath}") -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml deleted file mode 100644 index 6c805ceb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/build.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java deleted file mode 100644 index 6feca6bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -public class Calculator { - - public int add(int a, int b) { - return a + b; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java deleted file mode 100644 index 695282ba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -class CalculatorTests { - - @Test - @DisplayName("1 + 1 = 2") - void addsTwoNumbers() { - Calculator calculator = new Calculator(); - assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); - } - - @ParameterizedTest(name = "{0} + {1} = {2}") - @CsvSource({ // - "0, 1, 1", // - "1, 2, 3", // - "49, 51, 100", // - "1, 100, 101" // - }) - void add(int first, int second, int expectedResult) { - Calculator calculator = new Calculator(); - assertEquals(expectedResult, calculator.add(first, second), - () -> first + " + " + second + " should equal " + expectedResult); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts deleted file mode 100644 index edd8bd7b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - java - id("org.graalvm.buildtools.native") -} - -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") - -repositories { - maven { url = uri(file(System.getProperty("maven.repo"))) } - mavenCentral() -} - -dependencies { - testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") - testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") -} - -tasks.test { - useJUnitPlatform() - - val outputDir = reports.junitXml.outputLocation - jvmArgumentProviders += CommandLineArgumentProvider { - listOf( - "-Djunit.platform.reporting.open.xml.enabled=true", - "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" - ) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties deleted file mode 100644 index 83d0ce01..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -org.gradle.java.installations.fromEnv=GRAALVM_HOME diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts deleted file mode 100644 index c928064e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -pluginManagement { - plugins { - id("org.graalvm.buildtools.native") version "0.9.13" - } - repositories { - mavenCentral() - gradlePluginPortal() - } -} - -rootProject.name = "graalvm-starter" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java deleted file mode 100644 index 6feca6bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -public class Calculator { - - public int add(int a, int b) { - return a + b; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java deleted file mode 100644 index 695282ba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -class CalculatorTests { - - @Test - @DisplayName("1 + 1 = 2") - void addsTwoNumbers() { - Calculator calculator = new Calculator(); - assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); - } - - @ParameterizedTest(name = "{0} + {1} = {2}") - @CsvSource({ // - "0, 1, 1", // - "1, 2, 3", // - "49, 51, 100", // - "1, 100, 101" // - }) - void add(int first, int second, int expectedResult) { - Calculator calculator = new Calculator(); - assertEquals(expectedResult, calculator.add(first, second), - () -> first + " + " + second + " should equal " + expectedResult); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts deleted file mode 100644 index c79b5ff7..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts +++ /dev/null @@ -1,33 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - kotlin("jvm") version "1.2.71" -} - -repositories { - maven { url = uri(file(System.getProperty("maven.repo"))) } - mavenCentral() -} - -// grab jupiter version from system environment -val jupiterVersion = System.getenv("JUNIT_JUPITER_VERSION") - -dependencies { - testImplementation(kotlin("stdlib-jdk8")) - testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "1.8" - apiVersion = "1.1" - languageVersion = "1.1" - } -} - -tasks.test { - useJUnitPlatform() - testLogging { - events("passed", "skipped", "failed") - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 5028f28f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew deleted file mode 100644 index 83f2acfd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# 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. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat deleted file mode 100644 index 9618d8d9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradlew.bat +++ /dev/null @@ -1,100 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts deleted file mode 100644 index 7a052453..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "gradle-kotlin-extensions" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt deleted file mode 100644 index 0de75b1f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -package com.example.project - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.fail -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.aggregator.ArgumentsAccessor -import org.junit.jupiter.params.aggregator.get -import org.junit.jupiter.params.provider.CsvSource -import org.opentest4j.AssertionFailedError - -class ExtensionFunctionsTests { - - @Test - fun `assertDoesNotThrow() and assertAll`() { - assertDoesNotThrow { - assertAll(setOf {}) - } - assertDoesNotThrow("message") { - assertAll("header", setOf {}) - } - assertDoesNotThrow({ "message" }) { - assertAll({}) - assertAll("header", {}) - } - } - - @Test - fun `fail() and assertThrows`() { - assertThrows { - fail("message") - } - assertThrows("message") { - fail { "message" } - } - assertThrows({ "message" }) { - fail(IllegalArgumentException()) - } - } - - @ParameterizedTest - @CsvSource("1") - fun accessor(accessor: ArgumentsAccessor) { - val value = accessor.get(0) - assertEquals(1, value) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts deleted file mode 100644 index 022b1d3a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - java -} - -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") - -// emit default file encoding to a file -file("file.encoding.txt").writeText(System.getProperty("file.encoding")) - -// emit more Java runtime information -file("java.runtime.txt").writeText(""" -java.version=${System.getProperty("java.version")} -""") - -// emit versions of JUnit groups -file("junit.versions.txt").writeText(""" -jupiterVersion=$jupiterVersion -vintageVersion=$vintageVersion -platformVersion=$platformVersion -""") - -repositories { - maven { url = uri(file(System.getProperty("maven.repo"))) } - mavenCentral() -} - -dependencies { - testImplementation("org.junit.jupiter:junit-jupiter-api:$jupiterVersion") { - exclude(group = "org.junit.jupiter", module = "junit-jupiter-engine") - } -} - -tasks.test { - useJUnitPlatform() -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts deleted file mode 100644 index c68450bb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "gradle-missing-engine" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java deleted file mode 100644 index 86d922c2..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java +++ /dev/null @@ -1,22 +0,0 @@ - -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -import static org.junit.jupiter.api.Assertions.fail; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class FooTests { - - @Test - void test() { - fail("This test must not be executed!"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts deleted file mode 100644 index dfc4a31a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -plugins { - java -} - -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") - -repositories { - maven { url = uri(file(System.getProperty("maven.repo"))) } - mavenCentral() -} - -dependencies { - testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") - testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") -} - -tasks.test { - useJUnitPlatform() - - testLogging { - events("passed", "skipped", "failed") - } - - reports { - html.isEnabled = true - } - - val outputDir = reports.junitXml.outputLocation - jvmArgumentProviders += CommandLineArgumentProvider { - listOf( - "-Djunit.platform.reporting.open.xml.enabled=true", - "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" - ) - } - - doFirst { - println("Using Java version: ${JavaVersion.current()}") - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts deleted file mode 100644 index 48194085..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "gradle-starter" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java deleted file mode 100644 index 6feca6bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -public class Calculator { - - public int add(int a, int b) { - return a + b; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java deleted file mode 100644 index 695282ba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -class CalculatorTests { - - @Test - @DisplayName("1 + 1 = 2") - void addsTwoNumbers() { - Calculator calculator = new Calculator(); - assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); - } - - @ParameterizedTest(name = "{0} + {1} = {2}") - @CsvSource({ // - "0, 1, 1", // - "1, 2, 3", // - "49, 51, 100", // - "1, 100, 101" // - }) - void add(int first, int second, int expectedResult) { - Calculator calculator = new Calculator(); - assertEquals(expectedResult, calculator.add(first, second), - () -> first + " + " + second + " should equal " + expectedResult); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt deleted file mode 100644 index 0b4810a6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt +++ /dev/null @@ -1,12 +0,0 @@ -org.junit.jupiter.api@${jupiterVersion} jar:file:.+/junit-jupiter-api-\d.+\.jar..module-info\.class -exports org.junit.jupiter.api -exports org.junit.jupiter.api.condition -exports org.junit.jupiter.api.extension -exports org.junit.jupiter.api.function -exports org.junit.jupiter.api.io -exports org.junit.jupiter.api.parallel -requires java.base mandated -requires org.apiguardian.api static transitive -requires org.junit.platform.commons transitive -requires org.opentest4j transitive -qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt deleted file mode 100644 index 5c637457..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt +++ /dev/null @@ -1,11 +0,0 @@ -org.junit.jupiter.engine@${jupiterVersion} jar:file:.+/junit-jupiter-engine-\d.+\.jar..module-info\.class -requires java.base mandated -requires org.apiguardian.api static -requires org.junit.jupiter.api -requires org.junit.platform.commons -requires org.junit.platform.engine -requires org.opentest4j -uses org.junit.jupiter.api.extension.Extension -provides org.junit.platform.engine.TestEngine with org.junit.jupiter.engine.JupiterTestEngine -qualified opens org.junit.jupiter.engine.extension to org.junit.platform.commons -contains org.junit.jupiter.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt deleted file mode 100644 index 74712571..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt +++ /dev/null @@ -1,11 +0,0 @@ -org.junit.jupiter.migrationsupport@${jupiterVersion} jar:file:.+/junit-jupiter-migrationsupport-\d.+\.jar..module-info\.class -exports org.junit.jupiter.migrationsupport -exports org.junit.jupiter.migrationsupport.conditions -exports org.junit.jupiter.migrationsupport.rules -exports org.junit.jupiter.migrationsupport.rules.adapter -exports org.junit.jupiter.migrationsupport.rules.member -requires java.base mandated -requires junit transitive -requires org.apiguardian.api static transitive -requires org.junit.jupiter.api transitive -requires org.junit.platform.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt deleted file mode 100644 index 05e8b8ac..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt +++ /dev/null @@ -1,13 +0,0 @@ -org.junit.jupiter.params@${jupiterVersion} jar:file:.+/junit-jupiter-params-\d.+\.jar..module-info\.class -exports org.junit.jupiter.params -exports org.junit.jupiter.params.aggregator -exports org.junit.jupiter.params.converter -exports org.junit.jupiter.params.provider -exports org.junit.jupiter.params.support -requires java.base mandated -requires org.apiguardian.api static transitive -requires org.junit.jupiter.api transitive -requires org.junit.platform.commons transitive -qualified opens org.junit.jupiter.params to org.junit.platform.commons -qualified opens org.junit.jupiter.params.converter to org.junit.platform.commons -qualified opens org.junit.jupiter.params.provider to org.junit.platform.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt deleted file mode 100644 index 76834239..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt +++ /dev/null @@ -1,5 +0,0 @@ -org.junit.jupiter@${jupiterVersion} jar:file:.+/junit-jupiter-\d.+\.jar..module-info\.class -requires java.base mandated -requires org.junit.jupiter.api transitive -requires org.junit.jupiter.engine transitive -requires org.junit.jupiter.params transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt deleted file mode 100644 index 5c0c9b44..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ /dev/null @@ -1,11 +0,0 @@ -org.junit.platform.commons@${platformVersion} jar:file:.+/junit-platform-commons-\d.+\.jar..module-info\.class -exports org.junit.platform.commons -exports org.junit.platform.commons.annotation -exports org.junit.platform.commons.function -exports org.junit.platform.commons.support -requires java.base mandated -requires java.logging -requires java.management -requires org.apiguardian.api static transitive -qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine -qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.commons org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt deleted file mode 100644 index 4324e45e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt +++ /dev/null @@ -1,13 +0,0 @@ -org.junit.platform.console@${platformVersion} jar:file:.+/junit-platform-console-\d.+\.jar..module-info\.class -requires java.base mandated -requires org.apiguardian.api static -requires org.junit.platform.commons -requires org.junit.platform.engine -requires org.junit.platform.launcher -requires org.junit.platform.reporting -provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider -contains org.junit.platform.console -contains org.junit.platform.console.options -contains org.junit.platform.console.shadow.picocli -contains org.junit.platform.console.tasks -main-class org.junit.platform.console.ConsoleLauncher diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt deleted file mode 100644 index d6f69626..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt +++ /dev/null @@ -1,13 +0,0 @@ -org.junit.platform.engine@${platformVersion} jar:file:.+/junit-platform-engine-\d.+\.jar..module-info\.class -exports org.junit.platform.engine -exports org.junit.platform.engine.discovery -exports org.junit.platform.engine.reporting -exports org.junit.platform.engine.support.config -exports org.junit.platform.engine.support.descriptor -exports org.junit.platform.engine.support.discovery -exports org.junit.platform.engine.support.filter -exports org.junit.platform.engine.support.hierarchical -requires java.base mandated -requires org.apiguardian.api static transitive -requires org.junit.platform.commons transitive -requires org.opentest4j transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt deleted file mode 100644 index 10bec3de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt +++ /dev/null @@ -1,9 +0,0 @@ -org.junit.platform.jfr@${platformVersion} jar:file:.+/junit-platform-jfr-\d.+\.jar..module-info\.class -requires java.base mandated -requires jdk.jfr -requires org.apiguardian.api static -requires org.junit.platform.engine -requires org.junit.platform.launcher -provides org.junit.platform.launcher.LauncherDiscoveryListener with org.junit.platform.jfr.FlightRecordingDiscoveryListener -provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.jfr.FlightRecordingExecutionListener -contains org.junit.platform.jfr diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt deleted file mode 100644 index 1461169b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt +++ /dev/null @@ -1,16 +0,0 @@ -org.junit.platform.launcher@${platformVersion} jar:file:.+/junit-platform-launcher-\d.+\.jar..module-info\.class -exports org.junit.platform.launcher -exports org.junit.platform.launcher.core -exports org.junit.platform.launcher.listeners -exports org.junit.platform.launcher.listeners.discovery -requires java.base mandated -requires java.logging transitive -requires org.apiguardian.api static transitive -requires org.junit.platform.commons transitive -requires org.junit.platform.engine transitive -uses org.junit.platform.engine.TestEngine -uses org.junit.platform.launcher.LauncherDiscoveryListener -uses org.junit.platform.launcher.LauncherSessionListener -uses org.junit.platform.launcher.PostDiscoveryFilter -uses org.junit.platform.launcher.TestExecutionListener -provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.launcher.listeners.UniqueIdTrackingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt deleted file mode 100644 index e9dbd208..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt +++ /dev/null @@ -1,11 +0,0 @@ -org.junit.platform.reporting@${platformVersion} jar:file:.+/junit-platform-reporting-\d.+\.jar..module-info\.class -exports org.junit.platform.reporting.legacy -exports org.junit.platform.reporting.legacy.xml -exports org.junit.platform.reporting.open.xml -requires java.base mandated -requires java.xml -requires org.apiguardian.api static transitive -requires org.junit.platform.commons -requires org.junit.platform.engine transitive -requires org.junit.platform.launcher transitive -provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt deleted file mode 100644 index ee4ae179..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-runner.expected.txt +++ /dev/null @@ -1,8 +0,0 @@ -org.junit.platform.runner@${platformVersion} jar:file:.+/junit-platform-runner-\d.+\.jar..module-info\.class -exports org.junit.platform.runner -requires java.base mandated -requires junit transitive -requires org.apiguardian.api static transitive -requires org.junit.platform.launcher transitive -requires org.junit.platform.suite.api transitive -requires org.junit.platform.suite.commons diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt deleted file mode 100644 index 5e3da71a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt +++ /dev/null @@ -1,5 +0,0 @@ -org.junit.platform.suite.api@${platformVersion} jar:file:.+/junit-platform-suite-api-\d.+\.jar..module-info\.class -exports org.junit.platform.suite.api -requires java.base mandated -requires org.apiguardian.api static transitive -requires org.junit.platform.commons transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt deleted file mode 100644 index 493962b6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt +++ /dev/null @@ -1,8 +0,0 @@ -org.junit.platform.suite.commons@${platformVersion} jar:file:.+/junit-platform-suite-commons-\d.+\.jar..module-info\.class -requires java.base mandated -requires org.apiguardian.api static transitive -requires org.junit.platform.commons -requires org.junit.platform.engine -requires org.junit.platform.launcher transitive -requires org.junit.platform.suite.api -qualified exports org.junit.platform.suite.commons to org.junit.platform.runner org.junit.platform.suite.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt deleted file mode 100644 index 4a569458..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt +++ /dev/null @@ -1,10 +0,0 @@ -org.junit.platform.suite.engine@${platformVersion} jar:file:.+/junit-platform-suite-engine-\d.+\.jar..module-info\.class -requires java.base mandated -requires org.apiguardian.api static -requires org.junit.platform.commons -requires org.junit.platform.engine -requires org.junit.platform.launcher -requires org.junit.platform.suite.api -requires org.junit.platform.suite.commons -provides org.junit.platform.engine.TestEngine with org.junit.platform.suite.engine.SuiteTestEngine -contains org.junit.platform.suite.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt deleted file mode 100644 index d6114f72..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt +++ /dev/null @@ -1,4 +0,0 @@ -org.junit.platform.suite@${platformVersion} jar:file:.+/junit-platform-suite-\d.+\.jar..module-info\.class -requires java.base mandated -requires org.junit.platform.suite.api transitive -requires org.junit.platform.suite.engine transitive diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt deleted file mode 100644 index 2ad3e8d1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt +++ /dev/null @@ -1,10 +0,0 @@ -org.junit.platform.testkit@${platformVersion} jar:file:.+/junit-platform-testkit-\d.+\.jar..module-info\.class -exports org.junit.platform.testkit.engine -requires java.base mandated -requires org.apiguardian.api static transitive -requires org.assertj.core transitive -requires org.junit.platform.commons -requires org.junit.platform.engine transitive -requires org.junit.platform.launcher -requires org.opentest4j transitive -uses org.junit.platform.engine.TestEngine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt deleted file mode 100644 index 854b4981..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt +++ /dev/null @@ -1,7 +0,0 @@ -org.junit.vintage.engine@${vintageVersion} jar:file:.+/junit-vintage-engine-\d.+\.jar..module-info\.class -requires java.base mandated -requires junit -requires org.apiguardian.api static -requires org.junit.platform.engine -provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine -contains org.junit.vintage.engine diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml deleted file mode 100644 index 7123e267..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - 4.0.0 - - platform.tooling.support.tests - java-versions - 1.0-SNAPSHOT - - - UTF-8 - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_PLATFORM_VERSION} - - - - - org.junit.jupiter - junit-jupiter-engine - ${junit.jupiter.version} - test - - - org.junit.platform - junit-platform-commons - ${junit.platform.version} - test - - - - - - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-surefire-plugin - 2.22.2 - - - - - - - - local-temp - file://${maven.repo} - - true - - - true - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java deleted file mode 100644 index 53a15919..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java +++ /dev/null @@ -1,37 +0,0 @@ - -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnJre; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.JRE; -import org.junit.platform.commons.util.ModuleUtils; - -class JUnitPlatformCommonsTests { - - @Test - @EnabledOnJre(JRE.JAVA_8) - void onJava8() { - assertFalse(ModuleUtils.isJavaPlatformModuleSystemAvailable()); - assertFalse(ModuleUtils.getModuleName(Object.class).isPresent()); - } - - @Test - @DisabledOnJre(JRE.JAVA_8) - void onJava9OrHigher() { - assertTrue(ModuleUtils.isJavaPlatformModuleSystemAvailable()); - assertEquals("java.base", ModuleUtils.getModuleName(Object.class).orElseThrow(Error::new)); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml deleted file mode 100644 index 9ec72ae3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/pom.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - 4.0.0 - - com.example - maven-starter - 1.0-SNAPSHOT - - - UTF-8 - 1.8 - ${maven.compiler.source} - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_PLATFORM_VERSION} - - - - - org.junit.jupiter - junit-jupiter - ${junit.jupiter.version} - test - - - org.junit.platform - junit-platform-reporting - ${junit.platform.version} - test - - - - - - - maven-compiler-plugin - 3.8.1 - - - maven-surefire-plugin - 2.22.2 - - - - junit.platform.reporting.open.xml.enabled = true - junit.platform.reporting.output.dir = target/surefire-reports - - - - - - org.codehaus.gmaven - groovy-maven-plugin - 2.1.1 - - - validate - - execute - - - - println("Using Java version: ${java.version}") - - - - - - - - - - - local-temp - file://${maven.repo} - - true - - - true - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java deleted file mode 100644 index 6feca6bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -public class Calculator { - - public int add(int a, int b) { - return a + b; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java deleted file mode 100644 index 695282ba..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -class CalculatorTests { - - @Test - @DisplayName("1 + 1 = 2") - void addsTwoNumbers() { - Calculator calculator = new Calculator(); - assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); - } - - @ParameterizedTest(name = "{0} + {1} = {2}") - @CsvSource({ // - "0, 1, 1", // - "1, 2, 3", // - "49, 51, 100", // - "1, 100, 101" // - }) - void add(int first, int second, int expectedResult) { - Calculator calculator = new Calculator(); - assertEquals(expectedResult, calculator.add(first, second), - () -> first + " + " + second + " should equal " + expectedResult); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml deleted file mode 100644 index 2eda3ba4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - 4.0.0 - - com.example - maven-surefire-compatibility - 1.0-SNAPSHOT - - - UTF-8 - 1.8 - ${maven.compiler.source} - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_PLATFORM_VERSION} - - - - - org.junit.jupiter - junit-jupiter - ${junit.jupiter.version} - test - - - - - - - maven-compiler-plugin - 3.8.1 - - - maven-surefire-plugin - ${surefire.version} - - - - junit.platform.listeners.uid.tracking.enabled = true - - - - - - - - - - local-temp - file://${maven.repo} - - true - - - true - - - - - - - manual-platform-dependency - - - org.junit.platform - junit-platform-launcher - ${junit.platform.version} - test - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java deleted file mode 100644 index a695ded1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.project; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -class DummyTests { - - @Test - void test() { - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml deleted file mode 100644 index 0904feb9..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - 4.0.0 - - platform.tooling.support.tests - multi-release-jar - 1.0-SNAPSHOT - - - UTF-8 - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_VINTAGE_VERSION} - ${env.JUNIT_PLATFORM_VERSION} - - - - - org.junit.jupiter - junit-jupiter-engine - ${junit.jupiter.version} - test - - - org.junit.platform - junit-platform-reporting - ${junit.platform.version} - test - - - - - - - - maven-compiler-plugin - 3.8.1 - - 11 - - - - - - - de.sormuras.junit - junit-platform-maven-plugin - 1.1.5 - true - - JAVA - - true - - - - - - - - - local-temp - file://${maven.repo} - - true - - - true - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java deleted file mode 100644 index 31115397..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package integration; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.platform.launcher.EngineFilter.includeEngines; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; - -import org.junit.jupiter.api.MethodOrderer.Alphanumeric; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.core.LauncherFactory; - -@TestMethodOrder(Alphanumeric.class) -class JupiterIntegrationTests { - - @Test - void packageName() { - assertEquals("integration", getClass().getPackageName()); - } - - @Test - void moduleIsNamed() { - assumeTrue(getClass().getModule().isNamed(), "not running on the module-path"); - assertEquals("integration", getClass().getModule().getName()); - } - - @Test - void resolve() { - var selector = DiscoverySelectors.selectClass(getClass()); - var testPlan = LauncherFactory.create().discover( - request().selectors(selector).filters(includeEngines("junit-jupiter")).build()); - - var engine = testPlan.getRoots().iterator().next(); - - assertEquals(1, testPlan.getChildren(engine).size()); // JupiterIntegrationTests.class - assertEquals(3, testPlan.getChildren(testPlan.getChildren(engine).iterator().next()).size()); // 3 test methods - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java deleted file mode 100644 index 50cecc69..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package integration; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.module.ModuleDescriptor; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ClassFilter; -import org.junit.platform.commons.util.ModuleUtils; - -/** - * Unit tests for {@link ModuleUtils}. - * - * @since 1.1 - */ -class ModuleUtilsTests { - - @Test - void javaPlatformModuleSystemIsAvailable() { - assertTrue(ModuleUtils.isJavaPlatformModuleSystemAvailable()); - } - - @Test - void findAllNonSystemBootModuleNames() { - Set moduleNames = ModuleUtils.findAllNonSystemBootModuleNames(); - - assertTrue(moduleNames.isEmpty()); - } - - @Test - void findAllClassesInModule() { - ClassFilter modular = ClassFilter.of(name -> name.contains("Module"), type -> true); - List> classes = ModuleUtils.findAllClassesInModule("java.base", modular); - assertFalse(classes.isEmpty()); - assertTrue(classes.contains(Module.class)); - assertTrue(classes.contains(ModuleDescriptor.class)); - } - - @Test - void preconditions() { - Class expected = PreconditionViolationException.class; - assertThrows(expected, () -> ModuleUtils.getModuleName(null)); - assertThrows(expected, () -> ModuleUtils.getModuleVersion(null)); - assertThrows(expected, () -> ModuleUtils.findAllClassesInModule(null, null)); - assertThrows(expected, () -> ModuleUtils.findAllClassesInModule("", null)); - assertThrows(expected, () -> ModuleUtils.findAllClassesInModule(" ", null)); - assertThrows(expected, () -> ModuleUtils.findAllClassesInModule("java.base", null)); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt deleted file mode 100644 index 46cdad9e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-err.txt +++ /dev/null @@ -1,18 +0,0 @@ -.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines -.+ Discovered TestEngines: -- junit-jupiter .+ -- junit-vintage .+ -- junit-platform-suite .+ -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded PostDiscoveryFilter instances: .. -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded LauncherDiscoveryListener instances: .. -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded TestExecutionListener instances: .+ -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded LauncherSessionListener instances: .. -.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines -.+ Discovered TestEngines: -- junit-jupiter .+ -- junit-vintage .+ -- junit-platform-suite .+ diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt deleted file mode 100644 index 24338dbc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/expected-out.txt +++ /dev/null @@ -1,19 +0,0 @@ ->> JAVA VERSION + TREE >> - -Failures (2): ->> STACKTRACE >> - -Test run finished after \d+ ms -[ 11 containers found ] -[ 0 containers skipped ] -[ 11 containers started ] -[ 0 containers aborted ] -[ 11 containers successful ] -[ 0 containers failed ] -[ 10 tests found ] -[ 2 tests skipped ] -[ 8 tests started ] -[ 1 tests aborted ] -[ 5 tests successful ] -[ 2 tests failed ] - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties deleted file mode 100644 index f17006de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/logging.properties +++ /dev/null @@ -1,3 +0,0 @@ -handlers=java.util.logging.ConsoleHandler -java.util.logging.ConsoleHandler.level=CONFIG -org.junit.level=CONFIG diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java deleted file mode 100644 index 04505d93..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package standalone; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -class JupiterIntegration { - - @Test - void successful() { - } - - @Test - @Disabled("integration-test-disabled") - void disabled() { - } - - @Test - void abort() { - Assumptions.assumeTrue(false, "integration-test-abort"); - } - - @Test - void fail() { - Assertions.fail("integration-test-fail"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java deleted file mode 100644 index 270b9a16..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package standalone; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class JupiterParamsIntegration { - - @ParameterizedTest(name = "[{index}] argument={0}") - @ValueSource(strings = "test") - void parameterizedTest(String argument) { - assertEquals("test", argument); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java deleted file mode 100644 index a06fc90f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package standalone; - -import org.junit.jupiter.api.Test; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; - -@Suite -@SelectClasses(SuiteIntegration.SingleTestContainer.class) -class SuiteIntegration { - - static class SingleTestContainer { - @Test - void successful() { - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java deleted file mode 100644 index 3fb28863..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package standalone; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.junit.runners.MethodSorters.NAME_ASCENDING; - -import org.junit.FixMethodOrder; -import org.junit.Ignore; -import org.junit.Test; - -@FixMethodOrder(NAME_ASCENDING) -public class VintageIntegration { - - @Test - @Ignore("integr4tion test") - public void ignored() { - fail("this test should be ignored"); - } - - @Test - public void succ3ssful() { - assertEquals(3, 1 + 2); - } - - @Test - public void f4il() { - fail("f4iled"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts deleted file mode 100644 index 4d691459..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/build.gradle.kts +++ /dev/null @@ -1,33 +0,0 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent - -plugins { - java -} - -repositories { - maven { url = uri(file(System.getProperty("maven.repo"))) } - mavenCentral() -} - -dependencies { - val junit4Version = System.getProperty("junit4Version", "4.12") - testImplementation("junit:junit:$junit4Version") - - val vintageVersion = System.getenv("JUNIT_VINTAGE_VERSION") ?: "5.3.2" - testImplementation("org.junit.vintage:junit-vintage-engine:$vintageVersion") { - exclude(group = "junit") - because("we want to override it to test against different versions") - } -} - -tasks.test { - useJUnitPlatform() - - testLogging { - events = setOf(TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.FAILED) - afterSuite(KotlinClosure2({ _, result -> - result.exception?.printStackTrace(System.out) - })) - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml deleted file mode 100644 index 47a0f145..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - com.example - vintage - 1.0-SNAPSHOT - - - UTF-8 - 1.8 - ${maven.compiler.source} - ${env.JUNIT_VINTAGE_VERSION} - - - - - org.junit.vintage - junit-vintage-engine - ${vintageVersion} - test - - - junit - junit - - - - - junit - junit - ${junit4Version} - test - - - - - - - maven-compiler-plugin - 3.8.1 - - - maven-surefire-plugin - 2.22.2 - - - - - - - local-temp - file://${maven.repo} - - true - - - true - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts deleted file mode 100644 index f073959d..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "vintage" diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java deleted file mode 100644 index 186bbf2e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package com.example.vintage; - -import static org.junit.Assert.*; - -import org.junit.Test; - -public class VintageTest { - @Test - public void success() { - // pass - } - - @Test - public void failure() { - fail("expected to fail"); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java deleted file mode 100644 index 517e4e5f..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.jar.JarFile; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * @since 1.3 - */ -public class Helper { - - public static final Duration TOOL_TIMEOUT = Duration.ofMinutes(3); - - private static final Path ROOT = Paths.get(".."); - private static final Path GRADLE_PROPERTIES = ROOT.resolve("gradle.properties"); - private static final Path SETTINGS_GRADLE = ROOT.resolve("settings.gradle.kts"); - - private static final Properties gradleProperties = new Properties(); - - static { - try { - gradleProperties.load(Files.newInputStream(GRADLE_PROPERTIES)); - } - catch (Exception e) { - throw new AssertionError("loading gradle.properties failed", e); - } - } - - public static String version(String module) { - if (module.startsWith("junit-jupiter")) { - return gradleProperties.getProperty("version"); - } - if (module.startsWith("junit-platform")) { - return gradleProperties.getProperty("platformVersion"); - } - if (module.startsWith("junit-vintage")) { - return gradleProperties.getProperty("vintageVersion"); - } - throw new AssertionError("Unknown module: " + module); - } - - static String groupId(String artifactId) { - if (artifactId.startsWith("junit-jupiter")) { - return "org.junit.jupiter"; - } - if (artifactId.startsWith("junit-platform")) { - return "org.junit.platform"; - } - if (artifactId.startsWith("junit-vintage")) { - return "org.junit.vintage"; - } - return "org.junit"; - } - - public static String replaceVersionPlaceholders(String line) { - line = line.replace("${jupiterVersion}", version("junit-jupiter")); - line = line.replace("${vintageVersion}", version("junit-vintage")); - line = line.replace("${platformVersion}", version("junit-platform")); - return line; - } - - public static List loadModuleDirectoryNames() { - var moduleLinePattern = Pattern.compile("include\\(\"(.+)\"\\)"); - try (var stream = Files.lines(SETTINGS_GRADLE) // - .map(moduleLinePattern::matcher) // - .filter(Matcher::matches) // - .map(matcher -> matcher.group(1)) // - .filter(name -> name.startsWith("junit-")) // - .filter(name -> !name.equals("junit-bom")) // - .filter(name -> !name.equals("junit-platform-console-standalone"))) { - return stream.collect(Collectors.toList()); - } - catch (Exception e) { - throw new AssertionError("loading module directory names failed: " + SETTINGS_GRADLE); - } - } - - static JarFile createJarFile(String module) { - var path = MavenRepo.jar(module); - try { - return new JarFile(path.toFile()); - } - catch (IOException e) { - throw new UncheckedIOException("Creating JarFile for '" + path + "' failed.", e); - } - } - - public static List loadJarFiles() { - return loadModuleDirectoryNames().stream().map(Helper::createJarFile).collect(Collectors.toList()); - } - - public static Optional getJavaHome(String version) { - // First, try various system sources... - var sources = Stream.of( // - System.getProperty("java.home." + version), // - System.getProperty("java." + version), // - System.getProperty("jdk.home." + version), // - System.getProperty("jdk." + version), // - System.getenv("JAVA_HOME_" + version), // - System.getenv("JAVA_" + version), // - System.getenv("JDK" + version) // - ); - return sources.filter(Objects::nonNull).findFirst().map(Path::of); - } - - /** Load, here copy, modular jar files to the given target directory. */ - public static void loadAllJUnitModules(Path target) throws Exception { - for (var module : loadModuleDirectoryNames()) { - var jar = MavenRepo.jar(module); - Files.copy(jar, target.resolve(jar.getFileName())); - } - } - - /** Walk directory tree structure. */ - public static List treeWalk(Path root) { - var lines = new ArrayList(); - treeWalk(root, lines::add); - return lines; - } - - /** Walk directory tree structure. */ - public static void treeWalk(Path root, Consumer out) { - try (var stream = Files.walk(root)) { - stream.map(root::relativize) // - .map(path -> path.toString().replace('\\', '/')) // - .sorted().filter(Predicate.not(String::isEmpty)) // - .forEach(out); - } - catch (Exception e) { - throw new Error("Walking tree failed: " + root, e); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java deleted file mode 100644 index 8764aea0..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support; - -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.stream.Stream; - -public class MavenRepo { - - private MavenRepo() { - } - - public static Path dir() { - var candidates = Stream.of(Path.of("../build/repo")); - var candidate = candidates.filter(Files::isDirectory).findFirst().orElse(Path.of("build/repo")); - return Path.of(System.getProperty("maven.repo", candidate.toString())); - } - - public static Path jar(String artifactId) { - return artifact(artifactId, fileName -> fileName.endsWith(".jar") // - && !fileName.endsWith("-sources.jar") // - && !fileName.endsWith("-javadoc.jar")); - } - - public static Path gradleModuleMetadata(String artifactId) { - return artifact(artifactId, fileName -> fileName.endsWith(".module")); - } - - public static Path pom(String artifactId) { - return artifact(artifactId, fileName -> fileName.endsWith(".pom")); - } - - private static Path artifact(String artifactId, Predicate fileNamePredicate) { - var parentDir = dir() // - .resolve(Helper.groupId(artifactId).replace('.', File.separatorChar)) // - .resolve(artifactId) // - .resolve(Helper.version(artifactId)); - try (var files = Files.list(parentDir)) { - return files.filter( - file -> fileNamePredicate.test(file.getFileName().toString())).findFirst().orElseThrow(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java deleted file mode 100644 index 03e2cf13..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support; - -import java.io.FileFilter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import de.sormuras.bartholdy.Configuration; -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.Tool; -import de.sormuras.bartholdy.tool.Maven; - -import org.apache.commons.io.FileUtils; - -/** - * @since 1.3 - */ -public class Request { - - public static final Path PROJECTS = Paths.get("projects"); - private static final Path TOOLS = Paths.get("build", "test-tools"); - public static final Path WORKSPACE = Paths.get("build", "test-workspace"); - - public static Builder builder() { - return new Builder(); - } - - public static Maven maven() { - return new Maven(Path.of(System.getProperty("mavenDistribution"))); - } - - private Tool tool; - private String project; - private String workspace; - private List arguments = new ArrayList<>(); - private Map environment = new HashMap<>(); - private FileFilter copyProjectToWorkspaceFileFilter; - private Duration timeout = Duration.ofMinutes(1); - - public String getProject() { - return project; - } - - public FileFilter getCopyProjectToWorkspaceFileFilter() { - return copyProjectToWorkspaceFileFilter; - } - - public String getWorkspace() { - return workspace; - } - - public Map getEnvironment() { - return environment; - } - - public List getArguments() { - return arguments; - } - - public Duration getTimeout() { - return timeout; - } - - public Result run() { - return run(true); - } - - public Result run(boolean cleanWorkspace) { - try { - // sanity check - if (!Files.isDirectory(PROJECTS)) { - var cwd = Paths.get(".").normalize().toAbsolutePath(); - throw new IllegalStateException("Directory " + PROJECTS + " not found in: " + cwd); - } - - Files.createDirectories(TOOLS); - Files.createDirectories(WORKSPACE); - - var workspace = WORKSPACE.resolve(getWorkspace()); - if (cleanWorkspace) { - FileUtils.deleteQuietly(workspace.toFile()); - var project = PROJECTS.resolve(getProject()); - if (Files.isDirectory(project)) { - var filter = getCopyProjectToWorkspaceFileFilter(); - FileUtils.copyDirectory(project.toFile(), workspace.toFile(), filter); - } - } - - var configuration = Configuration.builder(); - configuration.setArguments(getArguments()); - configuration.setWorkingDirectory(workspace); - configuration.setTimeout(getTimeout()); - configuration.getEnvironment().putAll(getEnvironment()); - - var result = tool.run(configuration.build()); - System.out.println(result.getOutput("out")); - System.err.println(result.getOutput("err")); - return result; - } - catch (Exception e) { - throw new IllegalStateException("run failed", e); - } - } - - public static class Builder { - - private final Request request = new Request(); - - public Request build() { - if (request.project == null) { - throw new IllegalStateException("project must not be null"); - } - if (request.workspace == null) { - request.workspace = request.project; - } - buildEnvironment(request.environment); - request.arguments = List.copyOf(request.arguments); - request.environment = Map.copyOf(request.environment); - return request; - } - - private void buildEnvironment(Map environment) { - environment.computeIfAbsent("JUNIT_JUPITER_VERSION", key -> Helper.version("junit-jupiter")); - environment.computeIfAbsent("JUNIT_VINTAGE_VERSION", key -> Helper.version("junit-vintage")); - environment.computeIfAbsent("JUNIT_PLATFORM_VERSION", key -> Helper.version("junit-platform")); - } - - public Builder setTool(Tool tool) { - request.tool = tool; - return this; - } - - public Builder setJavaHome(Path javaHome) { - return putEnvironment("JAVA_HOME", javaHome.normalize().toAbsolutePath().toString()); - } - - public Builder setProject(String project) { - request.project = project; - return this; - } - - public Builder setProjectToWorkspaceCopyFileFilter(FileFilter copyProjectToWorkspaceFileFilter) { - request.copyProjectToWorkspaceFileFilter = copyProjectToWorkspaceFileFilter; - return this; - } - - public Builder setWorkspace(String workspace) { - request.workspace = workspace; - return this; - } - - public Builder addArguments(Object... arguments) { - Stream.of(arguments).map(Object::toString).forEach(request.arguments::add); - return this; - } - - public Builder putEnvironment(String key, String value) { - request.environment.put(key, value); - return this; - } - - public Builder setTimeout(Duration timeout) { - request.timeout = timeout; - return this; - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java deleted file mode 100644 index 02b256e4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support; - -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.stream.Stream; - -public class ThirdPartyJars { - - private ThirdPartyJars() { - } - - public static void copy(Path targetDir, String group, String artifact) throws Exception { - Path source = Stream.of(System.getProperty("thirdPartyJars").split(File.pathSeparator)) // - .filter(it -> it.replace(File.separator, "/").contains("/" + group + "/" + artifact + "/")) // - .map(Path::of) // - .findFirst() // - .orElseThrow(() -> new AssertionError("Failed to find JAR file for " + group + ":" + artifact)); - Files.copy(source, targetDir.resolve(source.getFileName()), REPLACE_EXISTING); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java deleted file mode 100644 index feb8172b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Tooling support helper. - * - * @since 1.3 - */ - -package platform.tooling.support; diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java deleted file mode 100644 index fad952bf..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -import java.nio.file.Files; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class HelperTests { - - @Test - void loadModuleDirectoryNames() { - assertLinesMatch(List.of( // - "junit-jupiter", // - "junit-jupiter-api", // - "junit-jupiter-engine", // - "junit-jupiter-migrationsupport", // - "junit-jupiter-params", // - "junit-platform-commons", // - "junit-platform-console", // - "junit-platform-engine", // - "junit-platform-jfr", // - "junit-platform-launcher", // - "junit-platform-reporting", // - "junit-platform-runner", // - "junit-platform-suite", // - "junit-platform-suite-api", // - "junit-platform-suite-commons", // - "junit-platform-suite-engine", // - "junit-platform-testkit", // - "junit-vintage-engine"// - ), Helper.loadModuleDirectoryNames()); - } - - @Test - void version() { - assertNotNull(Helper.version("junit-jupiter")); - assertNotNull(Helper.version("junit-vintage")); - assertNotNull(Helper.version("junit-platform")); - } - - @Test - void nonExistingJdkVersionYieldsAnEmptyOptional() { - assertEquals(Optional.empty(), Helper.getJavaHome("does not exist")); - } - - @ParameterizedTest - @ValueSource(ints = 8) - void checkJavaHome(int version) { - var home = Helper.getJavaHome(String.valueOf(version)); - assumeTrue(home.isPresent(), "No 'jdk' element found in Maven toolchain for: " + version); - assertTrue(Files.isDirectory(home.get())); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java deleted file mode 100644 index 4125ae2c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; - -import java.util.List; - -import de.sormuras.bartholdy.tool.Java; - -import org.apache.tools.ant.Main; -import org.junit.jupiter.api.Test; -import platform.tooling.support.Request; - -/** - * @since 1.3 - */ -class AntStarterTests { - - @Test - void ant_starter() { - var request = Request.builder() // - .setTool(new Java()) // - .setProject("ant-starter") // - .addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) // - .addArguments("-verbose") // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err"), "error log isn't empty"); - assertLinesMatch(List.of(">> HEAD >>", // - "test.junit.launcher:", // - ">>>>", // - "\\[junitlauncher\\] Tests run: 5, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // - ">>>>", // - "test.console.launcher:", // - ">>>>", // - " \\[java\\] Test run finished after [\\d]+ ms", // - ">>>>", // - " \\[java\\] \\[ 5 tests successful \\]", // - " \\[java\\] \\[ 0 tests failed \\]", // - ">> TAIL >>"), // - result.getOutputLines("out")); - - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-report"); - verifyContainsExpectedStartedOpenTestReport(testResultsDir); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java deleted file mode 100644 index 2b26d889..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static com.tngtech.archunit.base.DescribedPredicate.describe; -import static com.tngtech.archunit.base.DescribedPredicate.not; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAnyPackage; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; -import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; -import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; -import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; -import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.loadJarFiles; - -import java.util.Set; -import java.util.stream.Collectors; - -import com.tngtech.archunit.core.domain.JavaClasses; -import com.tngtech.archunit.core.importer.Location; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.junit.LocationProvider; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.library.GeneralCodingRules; - -import org.apiguardian.api.API; - -@AnalyzeClasses(locations = ArchUnitTests.AllJars.class) -class ArchUnitTests { - - @ArchTest - private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // - .that(have(modifier(PUBLIC))) // - .and(TOP_LEVEL_CLASSES) // - .and(not(ANONYMOUS_CLASSES)) // - .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // - .and(not(describe("are shadowed", resideInAnyPackage("..shadow..")))) // - .should().beAnnotatedWith(API.class); - - @ArchTest - void allAreIn(JavaClasses classes) { - // about 928 classes found in all jars - assertTrue(classes.size() > 800, "expected more than 800 classes, got: " + classes.size()); - } - - @ArchTest - void freeOfCycles(JavaClasses classes) { - slices().matching("org.junit.(*)..").should().beFreeOfCycles().check(classes); - } - - @ArchTest - void avoidJavaUtilLogging(JavaClasses classes) { - // LoggerFactory.java:80 -> sets field LoggerFactory$DelegatingLogger.julLogger - var subset = classes.that(are(not(name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger")))); - GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(subset); - } - - @ArchTest - void avoidThrowingGenericExceptions(JavaClasses classes) { - // LoggerFactory.java:155 -> new Throwable() - var subset = classes.that(are(not( - name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger").or(nameContaining(".shadow."))))); - GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS.check(subset); - } - - @ArchTest - void avoidAccessingStandardStreams(JavaClasses classes) { - // ConsoleLauncher, StreamInterceptor, Picocli et al... - var subset = classes // - .that(are(not(name("org.junit.platform.console.ConsoleLauncher")))) // - .that(are(not(name("org.junit.platform.launcher.core.StreamInterceptor")))) // - .that(are(not(name("org.junit.platform.runner.JUnitPlatformRunnerListener")))) // - .that(are(not(name("org.junit.platform.testkit.engine.Events")))) // - .that(are(not(name("org.junit.platform.testkit.engine.Executions")))) // - .that(are(not(resideInAPackage("org.junit.platform.console.shadow.picocli")))); - GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(subset); - } - - static class AllJars implements LocationProvider { - - @Override - public Set get(Class testClass) { - return loadJarFiles().stream().map(Location::of).collect(Collectors.toSet()); - } - - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java deleted file mode 100644 index 463dd064..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; - -import java.nio.file.Paths; -import java.time.Duration; - -import de.sormuras.bartholdy.tool.GradleWrapper; - -import org.junit.jupiter.api.Test; - -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.9.1 - */ -class GraalVmStarterTests { - - @Test - void runsTestsInNativeImage() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject("graalvm-starter") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace") // - .setTimeout(Duration.ofMinutes(5)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assumeFalse( - result.getOutputLines("err").stream().anyMatch(line -> line.contains("No compatible toolchains found")), - "Abort test if GraalVM is not installed"); - - assertEquals(0, result.getExitCode()); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); - verifyContainsExpectedStartedOpenTestReport(testResultsDir); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java deleted file mode 100644 index 2b5411be..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; - -import de.sormuras.bartholdy.tool.GradleWrapper; - -import org.junit.jupiter.api.Test; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.3 - */ -class GradleKotlinExtensionsTests { - - @Test - void gradle_wrapper() { - var result = Request.builder() // - .setTool(new GradleWrapper(Request.PROJECTS.resolve("gradle-kotlin-extensions"))) // - .setProject("gradle-kotlin-extensions") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--stacktrace") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build() // - .run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode(), "result=" + result); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java deleted file mode 100644 index c82360af..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; - -import java.nio.file.Paths; -import java.util.List; - -import de.sormuras.bartholdy.Tool; -import de.sormuras.bartholdy.tool.GradleWrapper; - -import org.junit.jupiter.api.Test; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.3 - */ -class GradleMissingEngineTests { - - @Test - void gradle_wrapper() { - test(new GradleWrapper(Paths.get(".."))); - } - - private void test(Tool gradle) { - var project = "gradle-missing-engine"; - var result = Request.builder() // - .setProject(project) // - .setWorkspace(project + "-wrapper") // - .setTool(gradle) // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--debug", "--stacktrace") // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setTimeout(TOOL_TIMEOUT).build() // - .run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(1, result.getExitCode()); - assertLinesMatch(List.of( // - ">> HEAD >>", // - ".+DEBUG.+Cannot create Launcher without at least one TestEngine.+", // - ">> TAIL >>"), // - result.getOutputLines("out")); - assertLinesMatch(List.of( // - ">> HEAD >>", // - ".+ERROR.+FAILURE: Build failed with an exception.", // - ">> TAIL >>"), // - result.getOutputLines("err")); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java deleted file mode 100644 index 008e4bfb..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertLinesMatch; - -import java.nio.file.Files; -import java.util.List; - -import org.junit.jupiter.api.Test; -import platform.tooling.support.MavenRepo; - -/** - * @since 1.6 - */ -class GradleModuleFileTests { - - @Test - void jupiterAggregatorGradleModuleMetadataVariants() throws Exception { - var expected = List.of(">> HEAD >>", // - "{", // - " \"formatVersion\": \"1.1\",", // - " \"component\": {", // - " \"group\": \"org.junit.jupiter\",", // - " \"module\": \"junit-jupiter\",", // - ">> VERSION >>", // - " \"attributes\": {", // - ">> STATUS >>", // - " }", // - " },", // - ">> CREATED_BY >>", // - " \"variants\": [", // - " {", // - " \"name\": \"apiElements\",", // - " \"attributes\": {", // - " \"org.gradle.category\": \"library\",", // - " \"org.gradle.dependency.bundling\": \"external\",", // - " \"org.gradle.jvm.version\": 8,", // - " \"org.gradle.libraryelements\": \"jar\",", // - " \"org.gradle.usage\": \"java-api\"", // - " },", // - " \"dependencies\": [", // - " {", // - " \"group\": \"org.junit\",", // - " \"module\": \"junit-bom\",", // - " \"version\": {", // - ">> VERSION >>", // - " },", // - " \"attributes\": {", // - " \"org.gradle.category\": \"platform\"", // - " },", // - " \"endorseStrictVersions\": true", // - " },", // - " {", // - " \"group\": \"org.junit.jupiter\",", // - " \"module\": \"junit-jupiter-api\",", // - " \"version\": {", // - ">> VERSION >>", // - " }", // - " },", // - " {", // - " \"group\": \"org.junit.jupiter\",", // - " \"module\": \"junit-jupiter-params\",", // - " \"version\": {", // - ">> VERSION >>", // - " }", // - " }", // - " ],", // - " \"files\": [", // - " {", // - ">> JAR_FILE_DETAILS >>", // - " }", // - " ]", // - " },", // - " {", // - " \"name\": \"runtimeElements\",", // - " \"attributes\": {", // - " \"org.gradle.category\": \"library\",", // - " \"org.gradle.dependency.bundling\": \"external\",", // - " \"org.gradle.jvm.version\": 8,", // - " \"org.gradle.libraryelements\": \"jar\",", // - " \"org.gradle.usage\": \"java-runtime\"", // - " },", // - " \"dependencies\": [", // - " {", // - " \"group\": \"org.junit\",", // - " \"module\": \"junit-bom\",", // - " \"version\": {", // - ">> VERSION >>", // - " },", // - " \"attributes\": {", // - " \"org.gradle.category\": \"platform\"", // - " },", // - " \"endorseStrictVersions\": true", // - " },", // - " {", // - " \"group\": \"org.junit.jupiter\",", // - " \"module\": \"junit-jupiter-api\",", // - " \"version\": {", // - ">> VERSION >>", // - " }", // - " },", // - " {", // - " \"group\": \"org.junit.jupiter\",", // - " \"module\": \"junit-jupiter-params\",", // - " \"version\": {", // - ">> VERSION >>", // - " }", // - " },", // - " {", // - " \"group\": \"org.junit.jupiter\",", // - " \"module\": \"junit-jupiter-engine\",", // - " \"version\": {", // - ">> VERSION >>", // - " }", // - " }", // - " ],", // - " \"files\": [", // - " {", // - ">> JAR_FILE_DETAILS >>", // - " }", // - " ]", // - " },", // - " {", // - " \"name\": \"javadocElements\",", // - " \"attributes\": {", // - " \"org.gradle.category\": \"documentation\",", // - " \"org.gradle.dependency.bundling\": \"external\",", // - " \"org.gradle.docstype\": \"javadoc\",", // - " \"org.gradle.usage\": \"java-runtime\"", // - " },", // - " \"files\": [", // - " {", // - ">> JAR_FILE_DETAILS >>", // - " }", // - " ]", // - " },", // - " {", // - " \"name\": \"sourcesElements\",", // - " \"attributes\": {", // - " \"org.gradle.category\": \"documentation\",", // - " \"org.gradle.dependency.bundling\": \"external\",", // - " \"org.gradle.docstype\": \"sources\",", // - " \"org.gradle.usage\": \"java-runtime\"", // - " },", // - " \"files\": [", // - " {", // - ">> JAR_FILE_DETAILS >>", // - " }", // - " ]", // - " }", // - " ]", // - "}"); - - assertLinesMatch(expected, Files.readAllLines(MavenRepo.gradleModuleMetadata("junit-jupiter"))); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java deleted file mode 100644 index 69d884cc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; -import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; - -import java.nio.file.Paths; - -import de.sormuras.bartholdy.tool.GradleWrapper; - -import org.junit.jupiter.api.Test; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.3 - */ -class GradleStarterTests { - - @Test - void gradle_wrapper() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject("gradle-starter") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--stacktrace") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); - - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); - verifyContainsExpectedStartedOpenTestReport(testResultsDir); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java deleted file mode 100644 index ee2d7fae..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.FileInputStream; -import java.nio.file.Files; -import java.util.jar.JarInputStream; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import platform.tooling.support.MavenRepo; - -/** - * @since 1.8 - */ -class JarContainsManifestFirstTests { - - @ParameterizedTest - @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - void manifestFirst(String module) throws Exception { - var modulePath = MavenRepo.jar(module); - - if (Files.notExists(modulePath)) { - fail("No such file: " + modulePath); - } - - // JarInputStream expects the META-INF/MANIFEST.MF to be at the start of the JAR archive - try (final JarInputStream jarInputStream = new JarInputStream(new FileInputStream(modulePath.toFile()))) { - assertNotNull(jarInputStream.getManifest(), "MANIFEST.MF should be available via JarInputStream"); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java deleted file mode 100644 index aeb3f7bd..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.lang.module.ModuleFinder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Collectors; - -import de.sormuras.bartholdy.jdk.Jar; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.3 - */ -class JarDescribeModuleTests { - - @ParameterizedTest - @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - void describeModule(String module) throws Exception { - var modulePath = MavenRepo.jar(module); - var result = Request.builder() // - .setTool(new Jar()) // - .setProject("jar-describe-module") // - .setProjectToWorkspaceCopyFileFilter(file -> file.getName().startsWith(module)) // - .setWorkspace("jar-describe-module/" + module) // - .addArguments("--describe-module", "--file", modulePath) // - .build() // - .run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err"), "error log isn't empty"); - var expected = Paths.get("build", "test-workspace", "jar-describe-module", module, module + ".expected.txt"); - if (Files.notExists(expected)) { - result.getOutputLines("out").forEach(System.err::println); - fail("No such file: " + expected); - } - var expectedLines = Files.lines(expected).map(Helper::replaceVersionPlaceholders).collect(Collectors.toList()); - var origin = Path.of("projects", "jar-describe-module", module + ".expected.txt").toUri(); - assertLinesMatch(expectedLines, result.getOutputLines("out"), () -> String.format("%s\nError", origin)); - } - - @ParameterizedTest - @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - void packageNamesStartWithNameOfTheModule(String module) { - var modulePath = MavenRepo.jar(module); - var moduleDescriptor = ModuleFinder.of(modulePath).findAll().iterator().next().descriptor(); - var moduleName = moduleDescriptor.name(); - for (var packageName : moduleDescriptor.packages()) { - assertTrue(packageName.startsWith(moduleName)); - } - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java deleted file mode 100644 index dce1cfd6..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; - -import java.nio.file.Path; -import java.util.List; - -import de.sormuras.bartholdy.tool.Java; - -import org.junit.jupiter.api.Test; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.4 - */ -class JavaVersionsTests { - - @Test - void java_8() { - var java8Home = Helper.getJavaHome("8"); - assumeTrue(java8Home.isPresent(), "Java 8 installation directory not found!"); - var actualLines = execute("8", java8Home.get()); - - assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); - } - - @Test - void java_default() { - var actualLines = execute("default", new Java().getHome()); - - assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); - } - - List execute(String version, Path javaHome) { - var result = Request.builder() // - .setTool(Request.maven()) // - .setProject("java-versions") // - .setWorkspace("java-versions-" + version) // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("--update-snapshots", "--batch-mode", "verify") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(javaHome) // - .build().run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - return result.getOutputLines("out"); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java deleted file mode 100644 index 4fabbb75..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static aQute.bnd.osgi.Constants.VERSION_ATTRIBUTE; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.jar.Attributes; - -import aQute.bnd.osgi.Domain; -import aQute.bnd.osgi.Jar; -import aQute.bnd.version.MavenVersion; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.osgi.framework.Version; -import org.osgi.framework.VersionRange; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; - -/** - * @since 1.5 - */ -class ManifestTests { - - @ParameterizedTest - @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - void manifestEntriesAdhereToConventions(String module) throws Exception { - var version = Helper.version(module); - var jarFile = MavenRepo.jar(module).toFile(); - try (var jar = new Jar(jarFile)) { - var manifest = jar.getManifest(); - var attributes = manifest.getMainAttributes(); - assertValue(attributes, "Built-By", "JUnit Team"); - assertValue(attributes, "Specification-Title", module); - assertValue(attributes, "Specification-Version", specificationVersion(version)); - assertValue(attributes, "Specification-Vendor", "junit.org"); - assertValue(attributes, "Implementation-Title", module); - assertValue(attributes, "Implementation-Version", version); - assertValue(attributes, "Implementation-Vendor", "junit.org"); - assertValue(attributes, "Automatic-Module-Name", null); - assertValue(attributes, "Bundle-ManifestVersion", "2"); - assertValue(attributes, "Bundle-SymbolicName", module); - assertValue(attributes, "Bundle-Version", - MavenVersion.parseMavenString(version).getOSGiVersion().toString()); - switch (module) { - case "junit-platform-commons" -> assertValue(attributes, "Multi-Release", "true"); - case "junit-platform-console" -> assertValue(attributes, "Main-Class", - "org.junit.platform.console.ConsoleLauncher"); - } - var domain = Domain.domain(manifest); - domain.getExportPackage().forEach((pkg, attrs) -> { - final var stringVersion = attrs.get(VERSION_ATTRIBUTE); - assertNotNull(stringVersion); - assertDoesNotThrow(() -> new Version(stringVersion)); - }); - domain.getImportPackage().forEach((pkg, attrs) -> { - final var stringVersionRange = attrs.get(VERSION_ATTRIBUTE); - if (stringVersionRange == null) { - return; - } - assertDoesNotThrow(() -> new VersionRange(stringVersionRange)); - }); - } - } - - private static String specificationVersion(String version) { - var dash = version.indexOf('-'); - if (dash < 0) { - return version; - } - return version.substring(0, dash); - } - - private static void assertValue(Attributes attributes, String name, String expected) { - var actual = attributes.getValue(name); - assertEquals(expected, actual, - String.format("Manifest attribute %s expected to be %s, but is: %s", name, expected, actual)); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java deleted file mode 100644 index 5b5b6f42..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertLinesMatch; - -import java.nio.file.Files; -import java.util.List; - -import org.junit.jupiter.api.Test; -import platform.tooling.support.MavenRepo; - -/** - * @since 1.4 - */ -class MavenPomFileTests { - - @Test - void jupiterAggregatorPomDependencies() throws Exception { - - var expected = List.of(">> HEAD >>", // - " ", // - " ", // - " ", // - " org.junit", // - " junit-bom", // - ">> VERSION >>", // - " pom", // - " import", // - " ", // - " ", // - " ", // - " ", // - " ", // - " org.junit.jupiter", // - " junit-jupiter-api", // - ">> VERSION >>", // - " compile", // - " ", // - " ", // - " org.junit.jupiter", // - " junit-jupiter-params", // - ">> VERSION >>", // - " compile", // - " ", // - " ", // - " org.junit.jupiter", // - " junit-jupiter-engine", // - ">> VERSION >>", // - " runtime", // - " ", // - " ", // - ">> TAIL >>"); - - assertLinesMatch(expected, Files.readAllLines(MavenRepo.pom("junit-jupiter"))); - } - - @Test - void jupiterAggregatorGradleMetadataMarker() throws Exception { - - var expected = List.of(">> HEAD >>", // - " ", // - ">> TAIL >>"); - - assertLinesMatch(expected, Files.readAllLines(MavenRepo.pom("junit-jupiter"))); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java deleted file mode 100644 index 26a7d371..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; -import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; - -import org.junit.jupiter.api.Test; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.3 - */ -class MavenStarterTests { - - @Test - void verifyMavenStarterProject() { - var request = Request.builder() // - .setTool(Request.maven()) // - .setProject("maven-starter") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("--update-snapshots", "--batch-mode", "verify") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); - assertTrue(result.getOutputLines("out").contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); - - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target/surefire-reports"); - verifyContainsExpectedStartedOpenTestReport(testResultsDir); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java deleted file mode 100644 index f346969b..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; - -import java.io.IOException; -import java.nio.file.Files; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.9.2 - */ -class MavenSurefireCompatibilityTests { - - @ParameterizedTest - @CsvSource(delimiter = '|', nullValues = "", textBlock = """ - 2.22.2 | --activate-profiles=manual-platform-dependency - 3.0.0-M4 | - """) - void testMavenSurefireCompatibilityProject(String surefireVersion, String extraArg) throws IOException { - var extraArgs = extraArg == null ? new Object[0] : new Object[] { extraArg }; - var request = Request.builder() // - .setTool(Request.maven()) // - .setProject("maven-surefire-compatibility") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("-Dsurefire.version=" + surefireVersion) // - .addArguments("--update-snapshots", "--batch-mode", "test") // - .addArguments(extraArgs) // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - - var output = result.getOutputLines("out"); - assertTrue(output.contains("[INFO] BUILD SUCCESS")); - assertTrue(output.contains("[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0")); - - var targetDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target"); - try (var stream = Files.list(targetDir)) { - assertThat(stream.filter(file -> file.getFileName().toString().startsWith("junit-platform-unique-ids"))) // - .hasSize(1); - } - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java deleted file mode 100644 index 3c2fc9fe..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.spi.ToolProvider; - -import org.codehaus.groovy.runtime.ProcessGroovyMethods; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import platform.tooling.support.Helper; -import platform.tooling.support.ThirdPartyJars; - -/** - * @since 1.5 - */ -class ModularUserGuideTests { - - private static final String DOCUMENTATION_MODULE_DESCRIPTOR = """ - open module documentation { - exports example.testkit; // just here to ensure documentation example sources are compiled - - requires org.junit.jupiter.api; - requires org.junit.jupiter.migrationsupport; - requires org.junit.jupiter.params; - - requires org.junit.platform.engine; - requires org.junit.platform.reporting; - requires org.junit.platform.runner; - requires org.junit.platform.testkit; - - // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit - requires net.bytebuddy; - - requires java.desktop; - requires java.logging; - requires java.scripting; - requires jdk.httpserver; - - provides org.junit.platform.launcher.LauncherSessionListener - with example.session.GlobalSetupTeardownListener; - } - """; - - private static List compile(Path temp, Writer out, Writer err) throws Exception { - var documentation = Files.createDirectories(temp.resolve("src/documentation")); - Files.writeString(documentation.resolve("module-info.java"), DOCUMENTATION_MODULE_DESCRIPTOR); - - var args = new ArrayList(); - args.add("-Xlint"); // enable all default warnings - args.add("-proc:none"); // disable annotation processing - args.add("-cp"); - args.add(""); // set empty class path, otherwise system property "java.class.path" is read - - args.add("-d"); - args.add(temp.resolve("destination").toString()); - - var lib = Files.createDirectories(temp.resolve("lib")); - ThirdPartyJars.copy(lib, "junit", "junit"); - ThirdPartyJars.copy(lib, "org.assertj", "assertj-core"); - // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit - ThirdPartyJars.copy(lib, "net.bytebuddy", "byte-buddy"); - ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); - ThirdPartyJars.copy(lib, "org.hamcrest", "hamcrest"); - ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); - Helper.loadAllJUnitModules(lib); - args.add("--module-path"); - args.add(lib.toString()); - - args.add("--patch-module"); - args.add("documentation=" + Path.of("../documentation/src/main/java") + File.pathSeparator - + Path.of("../documentation/src/test/java")); - - args.add("--module-source-path"); - args.add(temp.resolve("src").toString()); - - args.add(documentation.resolve("module-info.java").toString()); - try (var walk = Files.walk(Path.of("../documentation/src/test/java"))) { - walk.map(Path::toString) // - .filter(s -> s.endsWith(".java")) // - // TypeError: systemProperty.get is not a function ?!?! - .filter(s -> !s.endsWith("ConditionalTestExecutionDemo.java")) // - // Don't include command-line tools that "require io.github.classgraph" - .filter(s -> !s.contains("tools")).forEach(args::add); - } - - var javac = ToolProvider.findFirst("javac").orElseThrow(); - var code = javac.run(new PrintWriter(out), new PrintWriter(err), args.toArray(String[]::new)); - - assertEquals(0, code, err.toString()); - assertTrue(out.toString().isBlank(), out.toString()); - return args; - } - - private static void junit(Path temp, Writer out, Writer err) throws Exception { - var command = new ArrayList(); - var projectDir = Path.of("../documentation"); - command.add(Path.of(System.getProperty("java.home"), "bin", "java").toString()); - - command.add("-XX:StartFlightRecording:filename=" + temp.resolve("user-guide.jfr")); - - command.add("--show-version"); - command.add("--show-module-resolution"); - - command.add("--module-path"); - command.add(String.join(File.pathSeparator, // - temp.resolve("destination").toString(), // - temp.resolve("lib").toString() // - )); - - command.add("--add-modules"); - command.add("documentation"); - - // TODO This `patch-module` should work! Why doesn't it? - // command.add("--patch-module"); - // command.add("documentation=../documentation/src/test/resources/"); - Files.copy(projectDir.resolve("src/test/resources/two-column.csv"), - temp.resolve("destination/documentation/two-column.csv")); - - command.add("--module"); - command.add("org.junit.platform.console"); - - command.add("--scan-modules"); - - command.add("--config"); - command.add("enableHttpServer=true"); - - command.add("--fail-if-no-tests"); - command.add("--include-classname"); - command.add(".*Tests"); - command.add("--include-classname"); - command.add(".*Demo"); - command.add("--exclude-tag"); - command.add("exclude"); - - // System.out.println("______________"); - // command.forEach(System.out::println); - - var builder = new ProcessBuilder(command).directory(projectDir.toFile()); - var java = builder.start(); - ProcessGroovyMethods.waitForProcessOutput(java, out, err); - var code = java.exitValue(); - - if (code != 0) { - System.out.println(out); - System.err.println(err); - fail("Unexpected exit code: " + code); - } - } - - @Test - void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp) throws Exception { - var out = new StringWriter(); - var err = new StringWriter(); - - var args = compile(temp, out, err); - // args.forEach(System.out::println); - - assertTrue(err.toString().isBlank(), () -> err + "\n\n" + String.join("\n", args)); - var listing = Helper.treeWalk(temp); - assertLinesMatch(List.of( // - "destination", // - ">> CLASSES >>", // - "lib", // - "lib/apiguardian-api-.+\\.jar", // - "lib/assertj-core-.+\\.jar", // - "lib/byte-buddy-.+", // - "lib/hamcrest-.+\\.jar", // - "lib/junit-.+\\.jar", // - ">> ALL JUNIT 5 JARS >>", // - "lib/opentest4j-.+\\.jar", // - "src", // - "src/documentation", // - "src/documentation/module-info.java" // - ), listing); - // System.out.println("______________"); - // listing.forEach(System.out::println); - - junit(temp, out, err); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java deleted file mode 100644 index 438ca769..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -import de.sormuras.bartholdy.Result; - -import org.junit.jupiter.api.Test; - -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.4 - */ -class MultiReleaseJarTests { - - @Test - void checkDefault() throws Exception { - var variant = "default"; - var expectedLines = List.of( // - ">> BANNER >>", // - ".", // - "'-- JUnit Jupiter [OK]", // - " +-- ModuleUtilsTests [OK]", // - " | +-- javaPlatformModuleSystemIsAvailable() [OK]", // - " | +-- findAllClassesInModule() [OK]", // - " | +-- findAllNonSystemBootModuleNames() [OK]", // - " | '-- preconditions() [OK]", // - " '-- JupiterIntegrationTests [OK]", // - " +-- moduleIsNamed() [A] Assumption failed: not running on the module-path", // - " +-- packageName() [OK]", // - " '-- resolve() [OK]", // - "", // - "Test run finished after \\d+ ms", // - "[ 3 containers found ]", // - "[ 0 containers skipped ]", // - "[ 3 containers started ]", // - "[ 0 containers aborted ]", // - "[ 3 containers successful ]", // - "[ 0 containers failed ]", // - "[ 7 tests found ]", // - "[ 0 tests skipped ]", // - "[ 7 tests started ]", // - "[ 1 tests aborted ]", // - "[ 6 tests successful ]", // - "[ 0 tests failed ]", // - "" // - ); - - var result = mvn(variant); - - result.getOutputLines("out").forEach(System.out::println); - result.getOutputLines("err").forEach(System.err::println); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); - - var workspace = Path.of("build/test-workspace/multi-release-jar", variant); - var actualLines = Files.readAllLines(workspace.resolve("target/junit-platform/console-launcher.out.log")); - assertLinesMatch(expectedLines, actualLines); - } - - private Result mvn(String variant) { - var result = Request.builder() // - .setTool(Request.maven()) // - .setProject("multi-release-jar") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("--update-snapshots", "--show-version", "--errors", "--batch-mode", "--file", variant, - "test") // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - return result; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java deleted file mode 100644 index 1162db67..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import de.sormuras.bartholdy.Configuration; -import de.sormuras.bartholdy.tool.CyclesDetector; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import platform.tooling.support.MavenRepo; - -/** - * @since 1.3 - */ -class PackageCyclesDetectionTests { - - @ParameterizedTest - @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - @Disabled("Need to pass --module-path...") - void moduleDoesNotContainCyclicPackageReferences(String module) { - var jar = MavenRepo.jar(module); - var result = new CyclesDetector(jar, this::ignore).run(Configuration.of()); - assertEquals(0, result.getExitCode(), "result=" + result); - } - - private boolean ignore(String source, String target) { - if (source.equals(target)) { - return true; - } - if (source.startsWith("org.junit.jupiter.params.shadow.com.univocity.parsers.")) { - return true; - } - //noinspection RedundantIfStatement - if (!target.startsWith("org.junit.")) { - return true; - } - return false; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java deleted file mode 100644 index 6bd09543..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; - -import de.sormuras.bartholdy.jdk.Jar; -import de.sormuras.bartholdy.jdk.Javac; -import de.sormuras.bartholdy.tool.Java; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -/** - * @since 1.4 - */ -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class StandaloneTests { - - @Test - void jarFileWithoutCompiledModuleDescriptorClass() throws Exception { - var jar = MavenRepo.jar("junit-platform-console-standalone"); - var name = "module-info.class"; - var found = new ArrayList(); - try (var fileSystem = FileSystems.newFileSystem(jar)) { - for (var rootDirectory : fileSystem.getRootDirectories()) { - try (var stream = Files.walk(rootDirectory)) { - stream.filter(path -> path.getNameCount() > 0) // skip root entry - .filter(path -> path.getFileName().toString().equals(name)).forEach(found::add); - } - } - } - assertTrue(found.isEmpty(), jar + " must not contain any " + name + " files: " + found); - } - - @Test - void listAllObservableEngines() { - var result = Request.builder() // - .setTool(new Java()) // - .setProject("standalone") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("--list-engines").build() // - .run(false); - - assertEquals(0, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); - - var jupiterVersion = Helper.version("junit-jupiter-engine"); - var suiteVersion = Helper.version("junit-platform-suite-engine"); - var vintageVersion = Helper.version("junit-vintage-engine"); - assertLinesMatch(""" - junit-jupiter (org.junit.jupiter:junit-jupiter-engine:%s) - junit-platform-suite (org.junit.platform:junit-platform-suite-engine:%s) - junit-vintage (org.junit.vintage:junit-vintage-engine:%s) - """.formatted(jupiterVersion, suiteVersion, vintageVersion).lines(), // - result.getOutput("out").lines()); - } - - @Test - @Order(1) - void compile() throws Exception { - var workspace = Request.WORKSPACE.resolve("standalone"); - var result = Request.builder() // - .setTool(new Javac()) // - .setProject("standalone") // - .addArguments("-d", workspace.resolve("bin")) // - .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments(workspace.resolve("src/standalone/JupiterIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/JupiterParamsIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/SuiteIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/VintageIntegration.java")).build() // - .run(); - - assertEquals(0, result.getExitCode(), result.getOutput("out") + result.getOutput("err")); - assertTrue(result.getOutput("out").isEmpty()); - assertTrue(result.getOutput("err").isEmpty()); - - // create "tests.jar" that'll be picked-up by "testWithJarredTestClasses()" later - var jarFolder = Files.createDirectories(workspace.resolve("jar")); - var jarResult = Request.builder() // - .setTool(new Jar()) // - .setProject("standalone") // - .addArguments("--create") // - .addArguments("--file", jarFolder.resolve("tests.jar")) // - .addArguments("-C", workspace.resolve("bin"), ".") // - .build().run(false); - assertEquals(0, jarResult.getExitCode(), String.join("\n", jarResult.getOutputLines("out"))); - } - - @Test - @Order(2) - void test() throws IOException { - var result = Request.builder() // - .setTool(new Java()) // - .setProject("standalone") // - .addArguments("--show-version") // - .addArguments("-enableassertions") // - .addArguments("-Djava.util.logging.config.file=logging.properties") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("--scan-class-path") // - .addArguments("--disable-banner") // - .addArguments("--include-classname", "standalone.*") // - .addArguments("--classpath", "bin").build() // - .run(false); - - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); - - var workspace = Request.WORKSPACE.resolve("standalone"); - var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); - assertLinesMatch(expectedOutLines, result.getOutputLines("out"), result.getOutput("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err"), result.getOutput("err")); - - var jupiterVersion = Helper.version("junit-jupiter-engine"); - var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" - + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" - + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); - } - - @Test - @Order(3) - void testOnJava8() throws IOException { - var result = Request.builder() // - .setTool(new Java()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject("standalone") // - .addArguments("--show-version") // - .addArguments("-enableassertions") // - .addArguments("-Djava.util.logging.config.file=logging.properties") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("--scan-class-path") // - .addArguments("--disable-banner") // - .addArguments("--include-classname", "standalone.*") // - .addArguments("--classpath", "bin").build() // - .run(false); - - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); - - var workspace = Request.WORKSPACE.resolve("standalone"); - var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err")); - - var jupiterVersion = Helper.version("junit-jupiter-engine"); - var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" - + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" - + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); - } - - @Test - @Order(3) - // https://github.com/junit-team/junit5/issues/2600 - void testOnJava8SelectPackage() throws IOException { - var result = Request.builder() // - .setTool(new Java()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject("standalone") // - .addArguments("--show-version") // - .addArguments("-enableassertions") // - .addArguments("-Djava.util.logging.config.file=logging.properties") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("--select-package", "standalone") // - .addArguments("--disable-banner") // - .addArguments("--include-classname", "standalone.*") // - .addArguments("--classpath", "bin").build() // - .run(false); - - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); - - var workspace = Request.WORKSPACE.resolve("standalone"); - var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err")); - - var jupiterVersion = Helper.version("junit-jupiter-engine"); - var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" - + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" - + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); - } - - @Test - @Order(5) - @Disabled("https://github.com/junit-team/junit5/issues/1724") - void testWithJarredTestClasses() { - var jar = MavenRepo.jar("junit-platform-console-standalone"); - var path = new ArrayList(); - // path.add("bin"); // "exploded" test classes are found, see also test() above - path.add(Request.WORKSPACE.resolve("standalone/jar/tests.jar").toAbsolutePath().toString()); - path.add(jar.toString()); - var result = Request.builder() // - .setTool(new Java()) // - .setProject("standalone") // - .addArguments("--show-version") // - .addArguments("-enableassertions") // - .addArguments("-Djava.util.logging.config.file=logging.properties") // - .addArguments("--class-path", String.join(File.pathSeparator, path)) // - .addArguments("org.junit.platform.console.ConsoleLauncher") // - .addArguments("--scan-class-path") // - .addArguments("--disable-banner") // - .addArguments("--include-classname", "standalone.*") // - .addArguments("--fail-if-no-tests") // - .build() // - .run(false); - - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java deleted file mode 100644 index 33243c0a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.module.ModuleDescriptor; -import java.lang.module.ModuleFinder; -import java.lang.module.ModuleReference; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.ServiceLoader; -import java.util.Set; -import java.util.spi.ToolProvider; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; -import platform.tooling.support.ThirdPartyJars; - -/** - * @since 1.6 - */ -class ToolProviderTests { - - private static final Path LIB = Request.WORKSPACE.resolve("tool-provider-tests/lib"); - - @BeforeAll - static void prepareLocalLibraryDirectoryWithJUnitPlatformModules() { - try { - var lib = Files.createDirectories(LIB); - try (var directoryStream = Files.newDirectoryStream(lib, "*.jar")) { - for (Path jarFile : directoryStream) { - Files.delete(jarFile); - } - } - for (var module : Helper.loadModuleDirectoryNames()) { - if (module.startsWith("junit-platform")) { - var jar = MavenRepo.jar(module); - Files.copy(jar, lib.resolve(module + ".jar")); - } - } - ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); - ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); - } - catch (Exception e) { - throw new AssertionError("Preparing local library folder failed", e); - } - } - - @Test - void findAndRunJUnitOnTheClassPath() { - try (var loader = new URLClassLoader("junit", urls(LIB), ClassLoader.getPlatformClassLoader())) { - var sl = ServiceLoader.load(ToolProvider.class, loader); - var junit = StreamSupport.stream(sl.spliterator(), false).filter(p -> p.name().equals("junit")).findFirst(); - - assertTrue(junit.isPresent(), "Tool 'junit' not found in: " + LIB); - assertJUnitPrintsHelpMessage(junit.get()); - } - catch (IOException e) { - throw new AssertionError("Closing URLClassLoader failed: " + e, e); - } - } - - @Test - void findAndRunJUnitOnTheModulePath() { - var finder = ModuleFinder.of(LIB); - var modules = finder.findAll().stream() // - .map(ModuleReference::descriptor) // - .map(ModuleDescriptor::toNameAndVersion) // - .sorted() // - .collect(Collectors.toList()); - // modules.forEach(System.out::println); - - var bootLayer = ModuleLayer.boot(); - var configuration = bootLayer.configuration().resolveAndBind(finder, ModuleFinder.of(), Set.of()); - var layer = bootLayer.defineModulesWithOneLoader(configuration, ClassLoader.getPlatformClassLoader()); - - var sl = ServiceLoader.load(layer, ToolProvider.class); - var junit = StreamSupport.stream(sl.spliterator(), false).filter(p -> p.name().equals("junit")).findFirst(); - - assertTrue(junit.isPresent(), "Tool 'junit' not found in modules: " + modules); - assertJUnitPrintsHelpMessage(junit.get()); - } - - private static URL[] urls(Path directory) { - try (var stream = Files.newDirectoryStream(directory, "*.jar")) { - var paths = new ArrayList(); - stream.forEach(path -> paths.add(url(path))); - return paths.toArray(URL[]::new); - } - catch (Exception e) { - throw new AssertionError("Creating URL[] failed: " + e, e); - } - } - - private static URL url(Path path) { - try { - return path.toUri().toURL(); - } - catch (MalformedURLException e) { - throw new AssertionError("Converting path to URL failed: " + e, e); - } - } - - private static void assertJUnitPrintsHelpMessage(ToolProvider junit) { - var out = new StringWriter(); - var err = new StringWriter(); - var code = junit.run(new PrintWriter(out), new PrintWriter(err), "--help"); - assertAll(() -> assertLinesMatch(List.of( // - ">> USAGE >>", // - "Launches the JUnit Platform for test discovery and execution.", // - ">> OPTIONS >>"), // - out.toString().lines().collect(Collectors.toList())), // - () -> assertEquals("", err.toString()), // - () -> assertEquals(0, code, "Expected exit of 0, but got: " + code) // - ); - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java deleted file mode 100644 index 2a1fad0e..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; - -import java.nio.file.Paths; - -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.tool.GradleWrapper; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -class VintageGradleIntegrationTests { - - @Test - void unsupportedVersion() { - var result = run("4.11"); - - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // - .doesNotContain("STARTED") // - .contains("Unsupported version of junit:junit: 4.11"); - } - - @ParameterizedTest(name = "{0}") - @ValueSource(strings = { "4.12", "4.13.2" }) - void supportedVersions(String version) { - var result = run(version); - - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // - .contains("VintageTest > success PASSED") // - .contains("VintageTest > failure FAILED"); - - var testResultsDir = Request.WORKSPACE.resolve("vintage-gradle-" + version).resolve("build/test-results/test"); - assertThat(testResultsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); - } - - private Result run(String version) { - var result = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject("vintage") // - .setWorkspace("vintage-gradle-" + version) // - .addArguments("build", "--no-daemon", "--stacktrace") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("-Djunit4Version=" + version) // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - return result; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java deleted file mode 100644 index 624e239c..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; - -import de.sormuras.bartholdy.Result; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.opentest4j.TestAbortedException; - -import platform.tooling.support.Helper; -import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; - -class VintageMavenIntegrationTests { - - @Test - void unsupportedVersion() { - var result = run("4.11"); - - assertThat(result.getExitCode()).isEqualTo(0); - assertThat(result.getOutput("out")) // - .contains("Tests run: 0, Failures: 0, Errors: 0, Skipped: 0"); - } - - @ParameterizedTest(name = "{0}") - @ValueSource(strings = { "4.12", "4.13.2" }) - void supportedVersions(String version) { - var result = run(version); - - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // - .contains("Running com.example.vintage.VintageTest") // - .contains("VintageTest.failure:") // - .contains("Tests run: 2, Failures: 1, Errors: 0, Skipped: 0"); - - var surefireReportsDir = Request.WORKSPACE.resolve("vintage-maven-" + version).resolve( - "target/surefire-reports"); - assertThat(surefireReportsDir.resolve("com.example.vintage.VintageTest.txt")).isRegularFile(); - assertThat(surefireReportsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); - } - - private Result run(String version) { - var result = Request.builder() // - .setTool(Request.maven()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject("vintage") // - .setWorkspace("vintage-maven-" + version) // - .addArguments("clean", "test", "--update-snapshots", "--batch-mode") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("-Djunit4Version=" + version) // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - return result; - } - -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java deleted file mode 100644 index 312cdaca..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.xmlunit.assertj3.XmlAssert; -import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; - -class XmlAssertions { - - static void verifyContainsExpectedStartedOpenTestReport(Path testResultsDir) { - try (var files = Files.list(testResultsDir)) { - Path xmlFile = files.filter(it -> it.getFileName().toString().startsWith("junit-platform-events-")) // - .findAny() // - .orElseThrow(() -> new AssertionError("Missing open-test-reporting XML file in " + testResultsDir)); - verifyContent(xmlFile); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static void verifyContent(Path xmlFile) { - var expected = """ - - - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - ${xmlunit.ignore} - - - - - [engine:junit-jupiter] - JUnit Jupiter - CONTAINER - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] - com.example.project.CalculatorTests - CONTAINER - - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] - addsTwoNumbers() - TEST - - - - - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] - add(int, int, int) - CONTAINER - - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] - add(int, int, int)[1] - TEST - - - - - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] - add(int, int, int)[2] - TEST - - - - - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] - add(int, int, int)[3] - TEST - - - - - - - - - - - [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] - add(int, int, int)[4] - TEST - - - - - - - - - - - - - - - - - - - """; - - XmlAssert.assertThat(xmlFile).and(expected) // - .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // - .ignoreWhitespace() // - .areIdentical(); - } -} diff --git a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml b/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml deleted file mode 100644 index 8595a6c3..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/platform-tooling-support-tests/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts b/src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts deleted file mode 100644 index 91c857de..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/settings.gradle.kts +++ /dev/null @@ -1,134 +0,0 @@ -import com.gradle.enterprise.gradleplugin.internal.extension.BuildScanExtensionWithHiddenFeatures - -pluginManagement { - repositories { - gradlePluginPortal() - } - plugins { - id("com.gradle.enterprise") version "3.12.1" // keep in sync with buildSrc/build.gradle.kts - id("com.gradle.common-custom-user-data-gradle-plugin") version "1.8.2" - id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" - id("org.ajoberstar.git-publish") version "4.1.1" - kotlin("jvm") version "1.5.31" - // Check if workaround in documentation.gradle.kts can be removed when upgrading - id("org.asciidoctor.jvm.convert") version "3.3.2" - id("org.asciidoctor.jvm.pdf") version "3.3.2" - id("me.champeau.jmh") version "0.6.8" - id("io.spring.nohttp") version "0.0.10" - id("io.github.gradle-nexus.publish-plugin") version "1.1.0" - } -} - -plugins { - id("com.gradle.enterprise") - id("com.gradle.common-custom-user-data-gradle-plugin") - id("org.gradle.toolchains.foojay-resolver-convention") -} - -dependencyResolutionManagement { - repositories { - mavenCentral() - maven(url = "https://oss.sonatype.org/content/repositories/snapshots") { - mavenContent { - snapshotsOnly() - } - } - } -} - -val gradleEnterpriseServer = "https://ge.junit.org" -val isCiServer = System.getenv("CI") != null -val junitBuildCacheUrl: String? by extra -val junitBuildCacheUsername: String? by extra -val junitBuildCachePassword: String? by extra - -gradleEnterprise { - buildScan { - capture.isTaskInputFiles = true - isUploadInBackground = !isCiServer - - publishAlways() - - // Publish to scans.gradle.com when `--scan` is used explicitly - if (!gradle.startParameter.isBuildScan) { - server = gradleEnterpriseServer - this as BuildScanExtensionWithHiddenFeatures - publishIfAuthenticated() - } - - obfuscation { - if (isCiServer) { - username { "github" } - } else { - hostname { null } - ipAddresses { emptyList() } - } - } - - val enableTestDistribution = providers.gradleProperty("enableTestDistribution") - .map(String::toBoolean) - .getOrElse(false) - if (enableTestDistribution) { - tag("test-distribution") - } - } -} - -buildCache { - local { - isEnabled = !isCiServer - } - remote { - url = uri(junitBuildCacheUrl ?: "$gradleEnterpriseServer/cache/") - isPush = isCiServer && !junitBuildCacheUsername.isNullOrEmpty() && !junitBuildCachePassword.isNullOrEmpty() - credentials { - username = junitBuildCacheUsername?.ifEmpty { null } - password = junitBuildCachePassword?.ifEmpty { null } - } - } -} - -val javaVersion = JavaVersion.current() -require(javaVersion == JavaVersion.VERSION_17) { - "The JUnit 5 build must be executed with Java 17. Currently executing with Java ${javaVersion.majorVersion}." -} - -rootProject.name = "junit5" - -include("documentation") -include("junit-jupiter") -include("junit-jupiter-api") -include("junit-jupiter-engine") -include("junit-jupiter-migrationsupport") -include("junit-jupiter-params") -include("junit-platform-commons") -include("junit-platform-console") -include("junit-platform-console-standalone") -include("junit-platform-engine") -include("junit-platform-jfr") -include("junit-platform-launcher") -include("junit-platform-reporting") -include("junit-platform-runner") -include("junit-platform-suite") -include("junit-platform-suite-api") -include("junit-platform-suite-commons") -include("junit-platform-suite-engine") -include("junit-platform-testkit") -include("junit-vintage-engine") -include("platform-tests") -include("platform-tooling-support-tests") -include("junit-bom") - -// check that every subproject has a custom build file -// based on the project name -rootProject.children.forEach { project -> - project.buildFileName = "${project.name}.gradle" - if (!project.buildFile.isFile) { - project.buildFileName = "${project.name}.gradle.kts" - } - require(project.buildFile.isFile) { - "${project.buildFile} must exist" - } -} - -enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh b/src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh deleted file mode 100644 index b0727562..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/checkBuildReproducibility.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -e - -rm -rf checksums* - -export SOURCE_DATE_EPOCH=$(date +%s) - -function calculate_checksums() { - OUTPUT=$1 - - ./gradlew --no-build-cache clean assemble --parallel -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Reproducibility - - find . -name '*.jar' \ - | grep '/build/libs/' \ - | grep --invert-match 'javadoc' \ - | sort \ - | xargs sha256sum > ${OUTPUT} -} - - -calculate_checksums checksums-1.txt -calculate_checksums checksums-2.txt - -diff checksums-1.txt checksums-2.txt diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml deleted file mode 100644 index 447d02df..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleMain.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml deleted file mode 100644 index 6ac9b5d8..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/checkstyleTest.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml b/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml deleted file mode 100644 index 05801fcc..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/checkstyle/suppressions.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml b/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml deleted file mode 100644 index 029e5df1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse-formatter-settings.xml +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder b/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder deleted file mode 100644 index daded8e1..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/eclipse/junit-eclipse.importorder +++ /dev/null @@ -1,7 +0,0 @@ -#Organize Import Order -0=java -1=javax -2=jdk -3=com -4=io -5=org diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh b/src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh deleted file mode 100644 index ecd0482a..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/publishDocumentationSnapshotOnlyIfNecessary.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash - -readonly checksum_directory='documentation/build/checksum' -readonly current="${checksum_directory}/current-checksum.txt" -readonly published="${checksum_directory}/published-checksum.txt" -readonly github_pages_url='https://raw.githubusercontent.com/junit-team/junit5/gh-pages/docs/snapshot/published-checksum.txt' - -# -# always generate current sums -# -echo "Generating checksum file ${current}..." -mkdir --parents "${checksum_directory}" -md5sum documentation/documentation.gradle.kts > "${current}" -md5sum $(find documentation/src -type f) >> "${current}" -# skip module junit-bom because it doesn't contain relevant documentation -md5sum $(find junit-jupiter -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-api -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-migrationsupport -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-params -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-commons -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-console -wholename '**/src/main/*.java') >> "${current}" -# skip module junit-platform-console-standalone because it doesn't contain relevant documentation -md5sum $(find junit-platform-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-jfr -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-launcher -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-reporting -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-runner -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-api -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-commons -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-testkit -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-vintage-engine -wholename '**/src/main/*.java') >> "${current}" -# skip module platform-tests because it doesn't contain relevant documentation -# skip module platform-tooling-support-tests because it doesn't contain relevant documentation -sort --output "${current}" "${current}" -echo -md5sum "${current}" - -# -# compare current with published sums -# -curl --silent --output "${published}" "${github_pages_url}" -md5sum "${published}" -if cmp --silent "${current}" "${published}" ; then - # - # no changes detected: we're done - # - echo - echo "Already published documentation with same source checksum." - echo -else - # - # update checksum file and trigger new documentation build and upload - # - echo - echo "Creating and publishing documentation..." - echo - cp --force "${current}" "${published}" - ./gradlew gitPublishPush -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Documentation -fi diff --git a/src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java b/src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java deleted file mode 100644 index 9bbea1d4..00000000 --- a/src/main/java/com/junit-team-junit5-da216b8/src/spotless/eclipse-public-license-2.0.java +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright 2015-2023 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ diff --git a/src/main/java/com/test.java b/src/main/java/com/test.java deleted file mode 100644 index fa5851ed..00000000 --- a/src/main/java/com/test.java +++ /dev/null @@ -1,7 +0,0 @@ -package main.java.com; -import org.junit.Assert.*; -public class test { - public static void main(String[] args) { - - } -} From 57768504bf222c9332b3c37400b84293e81f8026 Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Fri, 7 Jul 2023 08:28:59 +0700 Subject: [PATCH 17/18] jjh --- .../arraysandstrings/CheckPermutationTest.java | 5 +++-- .../com/ctci/arraysandstrings/IsUniqueTest.java | 4 +++- .../com/ctci/arraysandstrings/OneAwayTest.java | 4 +++- .../PalindromePermutationTest.java | 4 +++- .../ctci/arraysandstrings/RotateMatrixTest.java | 4 ++-- .../arraysandstrings/StringCompressionTest.java | 2 +- .../arraysandstrings/StringRotationTest.java | 2 +- .../com/ctci/arraysandstrings/URLifyTest.java | 2 +- .../ctci/arraysandstrings/ZeroMatrixTest.java | 2 +- .../ctci/linkedlists/DeleteMiddleNodeTest.java | 2 +- .../com/ctci/linkedlists/IntersectionTest.java | 2 +- .../ctci/linkedlists/KthToLastElementTest.java | 2 +- .../com/ctci/linkedlists/LoopDetectionTest.java | 2 +- .../java/com/ctci/linkedlists/NodeTest.java | 2 +- .../com/ctci/linkedlists/PalindromeTest.java | 2 +- .../com/ctci/linkedlists/PartitionTest.java | 4 ++-- .../ctci/linkedlists/RemoveDuplicatesTest.java | 5 +++-- .../java/com/ctci/linkedlists/SumListsTest.java | 14 ++++++++++++++ .../stacksandqueues/QueueViaStacksTest.java | 2 +- .../com/ctci/stacksandqueues/SortStackTest.java | 2 +- .../com/ctci/stacksandqueues/StackMinTest.java | 7 ++++--- .../ctci/stacksandqueues/StackOfPlatesTest.java | 5 ++++- .../com/ctci/treesandgraphs/BuildOrder.java | 5 ++++- .../com/ctci/treesandgraphs/BuildOrderTest.java | 6 ++++-- .../ctci/treesandgraphs/CheckBalancedTest.java | 17 +++++++++++++++++ .../ctci/treesandgraphs/CheckSubtreeTest.java | 17 +++++++++++++++++ .../treesandgraphs/FirstCommonAncestorTest.java | 17 +++++++++++++++++ 27 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/ctci/linkedlists/SumListsTest.java create mode 100644 src/main/java/com/ctci/treesandgraphs/CheckBalancedTest.java create mode 100644 src/main/java/com/ctci/treesandgraphs/CheckSubtreeTest.java create mode 100644 src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorTest.java diff --git a/src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java b/src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java index 20ecaa7c..4a3710ec 100644 --- a/src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java +++ b/src/main/java/com/ctci/arraysandstrings/CheckPermutationTest.java @@ -1,11 +1,12 @@ -package arraysandstrings; +package com.ctci.arraysandstrings; +import com.ctci.arraysandstrings.CheckPermutation; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class CheckPermutationTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(IIRFilter.class); + Result result = JUnitCore.runClasses(CheckPermutation.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java b/src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java index ce619e22..a81d52cd 100644 --- a/src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java +++ b/src/main/java/com/ctci/arraysandstrings/IsUniqueTest.java @@ -1,10 +1,12 @@ +package com.ctci.arraysandstrings; + import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class IsUniqueTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(IIRFilter.class); + Result result = JUnitCore.runClasses(IsUnique.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/arraysandstrings/OneAwayTest.java b/src/main/java/com/ctci/arraysandstrings/OneAwayTest.java index 1b9e535d..0aa4d9c9 100644 --- a/src/main/java/com/ctci/arraysandstrings/OneAwayTest.java +++ b/src/main/java/com/ctci/arraysandstrings/OneAwayTest.java @@ -1,10 +1,12 @@ +package com.ctci.arraysandstrings; + import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class OneAwayTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(IIRFilter.class); + Result result = JUnitCore.runClasses(OneAway.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java b/src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java index eeb36daa..6ade2ec5 100644 --- a/src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java +++ b/src/main/java/com/ctci/arraysandstrings/PalindromePermutationTest.java @@ -1,10 +1,12 @@ +package com.ctci.arraysandstrings; + import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class PalindromePermutationTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(IIRFilter.class); + Result result = JUnitCore.runClasses(PalindromePermutation.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java b/src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java index 9ec23e8b..b922ce8d 100644 --- a/src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java +++ b/src/main/java/com/ctci/arraysandstrings/RotateMatrixTest.java @@ -1,11 +1,11 @@ -package arraysandstrings; +package com.ctci.arraysandstrings; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class RotateMatrixTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(IIRFilter.class); + Result result = JUnitCore.runClasses(RotateMatrix.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java b/src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java index 882fea61..31e48a59 100644 --- a/src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java +++ b/src/main/java/com/ctci/arraysandstrings/StringCompressionTest.java @@ -1,4 +1,4 @@ -package arraysandstrings; +package com.ctci.arraysandstrings; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/arraysandstrings/StringRotationTest.java b/src/main/java/com/ctci/arraysandstrings/StringRotationTest.java index 0c2c3245..7b14fc80 100644 --- a/src/main/java/com/ctci/arraysandstrings/StringRotationTest.java +++ b/src/main/java/com/ctci/arraysandstrings/StringRotationTest.java @@ -1,4 +1,4 @@ -package arraysandstrings; +package com.ctci.arraysandstrings; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/arraysandstrings/URLifyTest.java b/src/main/java/com/ctci/arraysandstrings/URLifyTest.java index 8160df50..dad61347 100644 --- a/src/main/java/com/ctci/arraysandstrings/URLifyTest.java +++ b/src/main/java/com/ctci/arraysandstrings/URLifyTest.java @@ -1,4 +1,4 @@ -package arraysandstrings; +package com.ctci.arraysandstrings; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java b/src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java index 6e8fbfc0..883a477a 100644 --- a/src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java +++ b/src/main/java/com/ctci/arraysandstrings/ZeroMatrixTest.java @@ -1,4 +1,4 @@ -package arraysandstrings; +package com.ctci.arraysandstrings; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java b/src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java index b01b944c..cedcbf44 100644 --- a/src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java +++ b/src/main/java/com/ctci/linkedlists/DeleteMiddleNodeTest.java @@ -1,4 +1,4 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/linkedlists/IntersectionTest.java b/src/main/java/com/ctci/linkedlists/IntersectionTest.java index 3d9485dd..03e0eb9a 100644 --- a/src/main/java/com/ctci/linkedlists/IntersectionTest.java +++ b/src/main/java/com/ctci/linkedlists/IntersectionTest.java @@ -1,4 +1,4 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java b/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java index 44f60480..f51f5610 100644 --- a/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java +++ b/src/main/java/com/ctci/linkedlists/KthToLastElementTest.java @@ -1,4 +1,4 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/linkedlists/LoopDetectionTest.java b/src/main/java/com/ctci/linkedlists/LoopDetectionTest.java index a839ff42..4f0a3798 100644 --- a/src/main/java/com/ctci/linkedlists/LoopDetectionTest.java +++ b/src/main/java/com/ctci/linkedlists/LoopDetectionTest.java @@ -1,4 +1,4 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/linkedlists/NodeTest.java b/src/main/java/com/ctci/linkedlists/NodeTest.java index 4baf7daf..61e61641 100644 --- a/src/main/java/com/ctci/linkedlists/NodeTest.java +++ b/src/main/java/com/ctci/linkedlists/NodeTest.java @@ -1,4 +1,4 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/linkedlists/PalindromeTest.java b/src/main/java/com/ctci/linkedlists/PalindromeTest.java index 0fbd997a..e06e2493 100644 --- a/src/main/java/com/ctci/linkedlists/PalindromeTest.java +++ b/src/main/java/com/ctci/linkedlists/PalindromeTest.java @@ -1,4 +1,4 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/linkedlists/PartitionTest.java b/src/main/java/com/ctci/linkedlists/PartitionTest.java index f643669c..95da9258 100644 --- a/src/main/java/com/ctci/linkedlists/PartitionTest.java +++ b/src/main/java/com/ctci/linkedlists/PartitionTest.java @@ -1,11 +1,11 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class PartitionTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(KthToLastElement.class); + Result result = JUnitCore.runClasses(Partition.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java b/src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java index e48b5ffa..9fa0779f 100644 --- a/src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java +++ b/src/main/java/com/ctci/linkedlists/RemoveDuplicatesTest.java @@ -1,11 +1,12 @@ -package com.ctci.bitmanipulation; +package com.ctci.linkedlists; + import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class RemoveDuplicatesTest { public static void main(String[] args) { //testAssertions(); - Result result = JUnitCore.runClasses(KthToLastElement.class); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } diff --git a/src/main/java/com/ctci/linkedlists/SumListsTest.java b/src/main/java/com/ctci/linkedlists/SumListsTest.java new file mode 100644 index 00000000..9aa86daa --- /dev/null +++ b/src/main/java/com/ctci/linkedlists/SumListsTest.java @@ -0,0 +1,14 @@ +package com.ctci.linkedlists; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +public class SumListsTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java b/src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java index 86e785eb..1c5a253b 100644 --- a/src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java +++ b/src/main/java/com/ctci/stacksandqueues/QueueViaStacksTest.java @@ -1,4 +1,4 @@ -package com.ctci.recursionanddp; +package com.ctci.stacksandqueues; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/stacksandqueues/SortStackTest.java b/src/main/java/com/ctci/stacksandqueues/SortStackTest.java index 69dabcb7..fd4a1b37 100644 --- a/src/main/java/com/ctci/stacksandqueues/SortStackTest.java +++ b/src/main/java/com/ctci/stacksandqueues/SortStackTest.java @@ -1,4 +1,4 @@ -package com.ctci.recursionanddp; +package com.ctci.stacksandqueues; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; diff --git a/src/main/java/com/ctci/stacksandqueues/StackMinTest.java b/src/main/java/com/ctci/stacksandqueues/StackMinTest.java index 398eba31..559a8b81 100644 --- a/src/main/java/com/ctci/stacksandqueues/StackMinTest.java +++ b/src/main/java/com/ctci/stacksandqueues/StackMinTest.java @@ -1,6 +1,7 @@ -package main.java.com.ctci.stacksandqueues; - -import com.ctci.stacksandqueues.StackMin; +package com.ctci.stacksandqueues; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; public class StackMinTest { public static void main(String[] args) { diff --git a/src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java b/src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java index 659bb423..67f6c71c 100644 --- a/src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java +++ b/src/main/java/com/ctci/stacksandqueues/StackOfPlatesTest.java @@ -1,4 +1,7 @@ -package main.java.com.ctci.stacksandqueues; +package com.ctci.stacksandqueues; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; public class StackOfPlatesTest { public static void main(String[] args) { Result result = JUnitCore.runClasses(QueueViaStacks.class); diff --git a/src/main/java/com/ctci/treesandgraphs/BuildOrder.java b/src/main/java/com/ctci/treesandgraphs/BuildOrder.java index f1b4922f..99968092 100644 --- a/src/main/java/com/ctci/treesandgraphs/BuildOrder.java +++ b/src/main/java/com/ctci/treesandgraphs/BuildOrder.java @@ -1,5 +1,7 @@ package com.ctci.treesandgraphs; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -8,7 +10,8 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; -import org.junit.Assert.*; +import static org.junit.Assert.assertNull; + /** * You are given a list of projects and a list of dependencies (which is a list of pairs of projects, where the second * project is dependent on the first project). All of a project's dependencies must be built before the project is. Find diff --git a/src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java b/src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java index 30e8bf46..d31ee0f8 100644 --- a/src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java +++ b/src/main/java/com/ctci/treesandgraphs/BuildOrderTest.java @@ -1,7 +1,9 @@ -package main.java.com.ctci.treesandgraphs; +package com.ctci.treesandgraphs; import com.ctci.treesandgraphs.BuildOrder; - +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; public class BuildOrderTest { public static void main(String[] args) { Result result = JUnitCore.runClasses(BuildOrder.class); diff --git a/src/main/java/com/ctci/treesandgraphs/CheckBalancedTest.java b/src/main/java/com/ctci/treesandgraphs/CheckBalancedTest.java new file mode 100644 index 00000000..221b7a62 --- /dev/null +++ b/src/main/java/com/ctci/treesandgraphs/CheckBalancedTest.java @@ -0,0 +1,17 @@ +package com.ctci.treesandgraphs; + +import com.ctci.linkedlists.RemoveDuplicates; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class CheckBalancedTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/treesandgraphs/CheckSubtreeTest.java b/src/main/java/com/ctci/treesandgraphs/CheckSubtreeTest.java new file mode 100644 index 00000000..035f4014 --- /dev/null +++ b/src/main/java/com/ctci/treesandgraphs/CheckSubtreeTest.java @@ -0,0 +1,17 @@ +package com.ctci.treesandgraphs; + +import com.ctci.linkedlists.RemoveDuplicates; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class CheckSubtreeTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorTest.java b/src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorTest.java new file mode 100644 index 00000000..a9a1818a --- /dev/null +++ b/src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorTest.java @@ -0,0 +1,17 @@ +package com.ctci.treesandgraphs; + +import com.ctci.linkedlists.RemoveDuplicates; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class FirstCommonAncestorTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} From b3e607a1ab67e09ac18240f0daec0d7dc27471df Mon Sep 17 00:00:00 2001 From: bm01wait <121624213+bm01wait@users.noreply.github.com> Date: Fri, 7 Jul 2023 08:31:45 +0700 Subject: [PATCH 18/18] jjh --- ...FirstCommonAncestorWithParentAccessTest.java | 17 +++++++++++++++++ .../ctci/treesandgraphs/ListOfDepthsTest.java | 17 +++++++++++++++++ .../ctci/treesandgraphs/MinimalTreeTest.java | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorWithParentAccessTest.java create mode 100644 src/main/java/com/ctci/treesandgraphs/ListOfDepthsTest.java create mode 100644 src/main/java/com/ctci/treesandgraphs/MinimalTreeTest.java diff --git a/src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorWithParentAccessTest.java b/src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorWithParentAccessTest.java new file mode 100644 index 00000000..4e983b35 --- /dev/null +++ b/src/main/java/com/ctci/treesandgraphs/FirstCommonAncestorWithParentAccessTest.java @@ -0,0 +1,17 @@ +package com.ctci.treesandgraphs; + +import com.ctci.linkedlists.RemoveDuplicates; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class FirstCommonAncestorWithParentAccessTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/treesandgraphs/ListOfDepthsTest.java b/src/main/java/com/ctci/treesandgraphs/ListOfDepthsTest.java new file mode 100644 index 00000000..918922ba --- /dev/null +++ b/src/main/java/com/ctci/treesandgraphs/ListOfDepthsTest.java @@ -0,0 +1,17 @@ +package com.ctci.treesandgraphs; + +import com.ctci.linkedlists.RemoveDuplicates; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class ListOfDepthsTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +} diff --git a/src/main/java/com/ctci/treesandgraphs/MinimalTreeTest.java b/src/main/java/com/ctci/treesandgraphs/MinimalTreeTest.java new file mode 100644 index 00000000..9a30fe02 --- /dev/null +++ b/src/main/java/com/ctci/treesandgraphs/MinimalTreeTest.java @@ -0,0 +1,17 @@ +package com.ctci.treesandgraphs; + +import com.ctci.linkedlists.RemoveDuplicates; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class MinimalTreeTest { + public static void main(String[] args) { + //testAssertions(); + Result result = JUnitCore.runClasses(RemoveDuplicates.class); + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result == "+result.wasSuccessful()); + } +}